Вставить узел XML в определенную позицию существующего документа - PullRequest
10 голосов
/ 14 мая 2009

У меня есть существующий документ 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 ]

19 голосов
/ 15 мая 2009

[Заменил мой последний ответ. Теперь я лучше понимаю, что вам нужно.]

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

0 голосов
/ 15 мая 2009

Решение 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]

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

0 голосов
/ 14 мая 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...