Вставить узел 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: можете ли вы найти лучший способ справиться с этим особым случаем?