Вставить узел XML в определенную позицию существующего документа
у меня есть существующий XML-документ с некоторыми дополнительными узлами, и я хочу вставить новый узел, но в определенной позиции.
документ выглядит примерно так:
<root>
<a>...</a>
...
<r>...</r>
<t>...</t>
...
<z>...</z>
</root>
новый узел (<s>...</s>
) должен быть вставлен между узлом <r>
и <t>
, в результате чего:
<root>
<a>...</a>
...
<r>...</r>
<s>new node</s>
<t>...</t>
...
<z>...</z>
</root>
проблема в том, что существующие узлы являются необязательными. Поэтому я не могу использовать XPath для поиска node <r>
и вставить новый узел после него.
Я бы как избежать "метода грубой силы": Поиск из <r>
до <a>
, чтобы найти узел, который существует.
Я также хочу сохранить порядок, так как XML-документ должен соответствовать XML-схеме.
XSLT, а также обычные библиотеки XML можно использовать, но поскольку я использую только Saxon-B, обработка XSLT с учетом схемы не является опцией.
есть ли у кого-нибудь идея о том, как вставить такой узел?
thx, MyKey_
3 ответов
[заменил мой последний ответ. Теперь я лучше понимаю, что тебе нужно.]
вот решение XSLT 2.0:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:variable name="elements-after" select="t|u|v|w|x|y|z"/>
<xsl:copy>
<xsl:copy-of select="* except $elements-after"/>
<s>new node</s>
<xsl:copy-of select="$elements-after"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
вы должны явно перечислить либо элементы, которые приходят после, либо элементы, которые приходят раньше. (Вам не нужно перечислять оба.) Я бы предпочел выбрать более короткий из двух списков (следовательно, "t" - "z" В приведенном выше примере вместо "a" - "r").
ОПЦИОННОЕ ПОВЫШЕНИЕ:
Это делает работу, но теперь вам нужно ведение списка имен элементов в двух разных местах (в XSLT и в схеме). Если он сильно изменится,они могут разойтись. Если вы добавите новый элемент в схему, но забудете добавить его в XSLT,он не будет скопирован. Если вы беспокоитесь об этом, вы можете реализовать свой собственный вид осознания схемы. Предположим, ваша схема выглядит так:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="a" type="xs:string"/>
<xs:element name="r" type="xs:string"/>
<xs:element name="s" type="xs:string"/>
<xs:element name="t" type="xs:string"/>
<xs:element name="z" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Теперь все, что вам нужно сделать, это изменить определение $ elements-after переменная:
<xsl:variable name="elements-after" as="element()*">
<xsl:variable name="root-decl" select="document('root.xsd')/*/xs:element[@name eq 'root']"/>
<xsl:variable name="child-decls" select="$root-decl/xs:complexType/xs:sequence/xs:element"/>
<xsl:variable name="decls-after" select="$child-decls[preceding-sibling::xs:element[@name eq 's']]"/>
<xsl:sequence select="*[local-name() = $decls-after/@name]"/>
</xsl:variable>
это, очевидно, сложнее, но теперь вам не нужно перечислять какие-либо элементы (кроме "s") в вашем коде. Поведение скрипта будет автоматически обновляться при каждом изменении схемы (в частности, при добавлении новых элементов). Является ли это излишним или нет, зависит от вашего проекта. Я предлагаю его просто в качестве дополнительного дополнения. :-)
вы должны использовать поиск грубой силы, так как у вас нет статического пути для поиска местоположения вставки. Мой подход состоял бы в том, чтобы использовать парсер SAX и прочитать документ. Все узлы копируются в выходные данные без изменений.
вам понадобится флаг sWasWritten
вот почему вы не можете использовать обычный инструмент XSLT; вам нужен тот, где вы можете изменять переменные.
как только я вижу узел > r
(t
, u
, ..., z
) или конечный тег корневого узла, я бы написал s
узел, если sWasWritten
был true
и установить флаг sWasWritten
.
решение XPath:
/root/(.|a|r)[position()=last()]
вы должны явно включить все узлы до одного, который вы хотите, так что вам понадобится другое выражение XPath для каждого узла, который вы хотите вставить после. Например, разместить его сразу после <t>
(если он существует):
/root/(.|a|r|t)[position()=last()]
обратите внимание на частный случай, когда ни один из предыдущих узлов не присутствует: он возвращает <root>
(the "."). Вам нужно будет проверить это и вставить новый узел в качестве первого дочернего элемента root, вместо того, чтобы после него (обычный случай). Это не так уж плохо: вам все равно придется как-то справиться с этим особым случаем. Другим способом обработки этого особого случая является следующий, который возвращает 0 узлов, если нет предыдущих узлов.
/root/(.|a|r|t)[position()=last() and position()!=1]
Challenge: можете ли вы найти лучший способ справиться с этим особым случаем?