2009-05-14 2 views
9

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

ответ

18

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

Вот 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> 

Теперь все, что вам нужно сделать, это изменить определение $ элементов-после переменной:

<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> 

Это, очевидно, более сложное, но теперь вы дон 't должен перечислять любые элементы (кроме "s") в вашем коде. Поведение скрипта будет автоматически обновляться всякий раз, когда вы меняете схему (в частности, если вам нужно добавлять новые элементы). Является ли это излишним или не зависит от вашего проекта. Я предлагаю его просто как дополнительное дополнение. :-)

+0

Это не работает, когда нет узла 'r' (согласно исходному вопросу: все узлы являются необязательными). Как будет выглядеть шаблон, если вы не можете полагаться на какой-либо узел? –

+0

К сожалению, вы правы. Я неправильно прочитал оригинальный пост. Теперь я полностью заменил ответ. Благодарю. –

+0

Это действительно здорово. Небольшое уточнение: при получении $ elments-after используйте переменную вместо 's', так что вы можете автоматически обрабатывать вставку после любого дочернего элемента . – 13ren

0

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

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

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

+0

SAX обработка будет работать, как вы предлагаете. Но XSLT вполне способен на выполнение этой задачи (см. Мой ответ). –

0

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] 

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

Смежные вопросы