Вставить узел 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, чтобы найти узел <r> и вставьте новый узел после него.

Я хотел бы избежать "метода грубой силы": поиск из <r> вплоть до <a> найти узел, который существует.

Я также хочу сохранить порядок, так как документ XML должен соответствовать схеме XML.

Можно использовать как XSLT, так и обычные библиотеки XML, но, поскольку я использую только Saxon-B, обработка XSLT с учетом схемы не подходит.

У кого-нибудь есть идеи как вставить такой узел?

спасибо, 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") в вашем коде. Поведение скрипта будет автоматически обновляться всякий раз, когда вы меняете схему (в частности, если вам нужно будет добавить новые элементы). Будет ли это излишним или нет, зависит от вашего проекта. Я предлагаю это просто как дополнительное дополнение.:-)

Решение XPath:

/root/(.|a|r)[position()=last()]

Вы должны явно включить все узлы вплоть до того, который вам нужен, так что вам понадобится другое выражение XPath для каждого узла, который вы хотите вставить после. Например, разместить его сразу после <t> (если он существует):

/root/(.|a|r|t)[position()=last()]

Обратите внимание на особый случай, когда ни один из предыдущих узлов не присутствует: он возвращает <root> ("."). Вам нужно будет проверить это и вставить новый узел как первый дочерний элемент root, а не после него (обычный случай). Это не так уж и плохо: вам все равно придется каким-то образом разобраться с этим особым случаем. Другой способ обработки этого особого случая заключается в следующем, который возвращает 0 узлов, если предшествующих узлов нет.

/root/(.|a|r|t)[position()=last() and position()!=1]

Задача: можете ли вы найти лучший способ справиться с этим особым случаем?

Вы должны использовать поиск методом грубой силы, поскольку у вас нет статического пути для поиска места вставки. Мой подход заключается в том, чтобы использовать SAX-парсер и читать документ. Все узлы копируются на выход без изменений.

Вам понадобится флаг sWasWritten вот почему вы не можете использовать обычный инструмент XSLT; вам нужен один, где вы можете изменить переменные.

Как только я увижу узел> r (t, u,..., z) или конечный тег корневого узла, я бы написал s узел, если sWasWritten было true и установить флаг sWasWritten,

Другие вопросы по тегам