Xpath-запрос для получения узлов-предков на основе значения элемента - PullRequest
1 голос
/ 14 февраля 2012

Я пытаюсь найти все имена элементов, которые соответствуют двум приведенным ниже правилам.

1. элементы должны иметь <set>erase</set>
2. если два или более элемента имеют иерархию <set>erase</set> (например, <b> и <d> оба имеют <set>erase</set>), то должно быть напечатано только имя родительского узла (т.е. <b> в этом случае).

Таким образом, требуемый результат для ниже xml должен быть:

b, y, p

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a>
    <b>
        <set>erase</set>
        <d>
        <set>erase</set>
        </d>
    </b>

    <c>
        <x>
        </x>
    </c>

    <e>

        <y>
                    <set>erase</set>
            <q>
            </q>
        </y>
        <z>
            <p>
            <set>erase</set>
            </p>
        </z>
    </e>
</a>

Когда я использую query = (//set[contains(.,'erase')])[1]Я получаю только узел b в наборе результатов.Когда я использую query = //set[contains(.,'erase')], я получаю в списке результатов все nodeList b,d,y,p.

Может ли кто-нибудь помочь мне найти запрос, приводящий к nodeList b, y и p.

Вот фрагмент кода Java, который я использовал.

        XPath xpath = factory.newXPath();
    String query = "//set[contains(.,'erase')]";
            XPathExpression expr=null;
    try {
        expr = xpath.compile(query);
    } catch (XPathExpressionException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
        Object result = null;
    try {
        result = expr.evaluate(doc, XPathConstants.NODESET);
    } catch (XPathExpressionException e) {
        e.printStackTrace();
    }
    NodeList nodes = (NodeList) result;


    for (int i = 0; i < nodes.getLength(); i++) {
        String x = "";
        Node n = nodes.item(i).getParentNode();
        x=n.getNodeName();
        while(!n.getNodeName().equalsIgnoreCase(request.getClass().getSimpleName())){
            if ((n = n.getParentNode())!=null){
                x=n.getNodeName()+"."+x;
            }
        }



        System.out.println("Path: "+x);

вывод:

a.b
a.b.d
a.e.y
a.e.z.p

Может кто-нибудь помочь мне разобраться в запросе, который приводит только к a.b , a.e.y and a.e.z.p Дайте мне знать, если вам нужно больше деталей.или любой другой вариант использования.

Ответы [ 4 ]

1 голос
/ 14 февраля 2012

Одно выражение, которое точно выбирает требуемые элементы: :

      //*[set[. = 'erase' and not(node()[2])]
         and
          not(ancestor::*
                 [set
                    [. = 'erase' and not(node()[2])]
                 ]
              )
          ]

Проверка на основе XSLT :

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:template match="/">
         <xsl:for-each select=
         "//*[set[. = 'erase' and not(node()[2])]
             and
              not(ancestor::*
                     [set
                        [. = 'erase' and not(node()[2])]
                     ]
                  )
              ]">

          <xsl:value-of select="name()"/>
          <xsl:text>&#xA;</xsl:text>
        </xsl:for-each>
     </xsl:template>
</xsl:stylesheet>

Это преобразование при применении к предоставленному Шоном Б. Дуркиным XML-документу :

<a>
    <b>
        <set>erase</set>
        <set>
            <a/>erase
        </set>
        <d>
            <set>erase</set>
        </d>
    </b>
    <c>
        <x>         </x>
    </c>
    <e>
        <y>
            <set>erase</set>
            <q>             </q>
        </y>
        <z>
            <p>
                <set>erase</set>
            </p>
        </z>
    </e>
</a>

вычисляет выражение XPath выше и выводит имена выбранных элементов - желаемый, правильный результат получается :

b
y
p

Обратите внимание , что следующие два выражения совершенно неверны:

*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]  

Или:

*[set[text()='erase']][ancestor::*[set[text()!='erase']]] 

Эти два выражения страдают от более чем одной проблемы :

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

  2. set[text()='erase'] выделяет не только элемент вида:

...

<set>erase</set>

но также элементы формы:

<set>
xyz
 <a/>erase</set>   

0,3. Точно так же:

set[text()!='erase']   

выбирает элементы формы:

<set>
xyz
 <a/>erase</set> 
0 голосов
/ 15 февраля 2012

Это моя вторая попытка:

//*[                    set[count(node())=1 and text()='erase'] and
      not( ancestor::*[ set[count(node())=1 and text()='erase']])
   ]

Этот выбор проходит тест, приведенный в моем первом ответе.

0 голосов
/ 14 февраля 2012

Или это небольшое изменение в ответе Гарпо?:

*[set[text()='erase']][ancestor::*[set[text()!='erase']]]

После моего комментария к ответу Новатчева, пожалуйста, рассмотрите полезный контрольный пример:

Это изменение из демонстрационного документа опрашивающего.Я добавил еще один узел.

<?xml version="1.0"?>
<a>
    <b>
        <set>erase</set>
        <set><a/>erase</set>
        <d>
        <set>erase</set>
        </d>
    </b>
    <c>
        <x>
        </x>
    </c>
    <e>
        <y>
        <set>erase</set>
            <q>
            </q>
        </y>
        <z>
            <p>
            <set>erase</set>
            </p>
        </z>
    </e>
</a>

Ответ должен быть

b
y
p
0 голосов
/ 14 февраля 2012

Следующий XPath выбирает нужные вам узлы:

//*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]

Я протестировал его со следующей таблицей стилей

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" encoding="utf-8" indent="yes"/>

    <xsl:template match="@*|text()" />

    <xsl:template match="//*[set[text()='erase']][not(ancestor::*[set[text()='erase']])]">
        <xsl:text>(</xsl:text>
        <xsl:for-each select="self::*|ancestor::*">
            <xsl:value-of select="name()"/>
            <xsl:text>.</xsl:text>
        </xsl:for-each>
        <xsl:text>) </xsl:text>
    </xsl:template>

</xsl:stylesheet>

Он выдал вывод

(a.b.) (a.e.y.) (a.e.z.p.)
...