XPath последнее вхождение каждого элемента - PullRequest
5 голосов
/ 05 июля 2011

У меня есть XML как

<root>
    <a>One</a>
    <a>Two</a>
    <b>Three</b>
    <c>Four</c>
    <a>Five</a>
    <b>
        <a>Six</a>
    </b>
</root>

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

<c>Four</c>
<a>Five</a>
<b>
    <a>Six</a>
</b>

Любая помощь приветствуется!

Ответы [ 4 ]

6 голосов
/ 06 июля 2011

Как решение XPath 2.0, так и принятый в настоящее время ответ очень неэффективны (O (N ^ 2)).

Это решение имеет сублинейную сложность:

<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:key name="kElemsByName" match="/*/*"
  use="name()"/>

 <xsl:template match="/">
  <xsl:copy-of select=
    "/*/*[generate-id()
         =
          generate-id(key('kElemsByName', name())[last()])
         ]"/>
 </xsl:template>
</xsl:stylesheet>

при применении к предоставленному документу XML :

<root>
    <a>One</a>
    <a>Two</a>
    <b>Three</b>
    <c>Four</c>
    <a>Five</a>
    <b>
        <a>Six</a>
    </b>
</root>

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

<c>Four</c>
<a>Five</a>
<b>
   <a>Six</a>
</b>

Объяснение : Это модифицированный вариант мюнхенской группировки - так, чтобы не первым. но последний узел в каждой группе обрабатывается.

II XPath 2.0 однострочный :

Использование:

/*/*[index-of(/*/*/name(), name())[last()]]

Проверка с использованием XSLT 2.0 в качестве хоста XPath 2.0 :

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

 <xsl:template match="/">
  <xsl:sequence select=
    "/*/*[index-of(/*/*/name(), name())[last()]]"/>
 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к тому же документу XML (предоставленному ранее), получается тот же правильный результат :

<c>Four</c>
<a>Five</a>
<b>
    <a>Six</a>
</b>
4 голосов
/ 05 июля 2011

Если вы можете XPath 2.0, это будет работать

/root//*[not(name() = following-sibling::*/name())]
3 голосов
/ 05 июля 2011

Решение на основе XSLT:

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

    <xsl:template match="root/*">
        <xsl:variable name="n" select="name()"/>
        <xsl:copy-of
            select=".[not(following-sibling::node()[name()=$n])]"/>
    </xsl:template>
</xsl:stylesheet>

Произведенная продукция:

<c>Four</c>
<a>Five</a>
<b>
   <a>Six</a>
</b>

Второе решение (вы можете использовать его как одно выражение XPath):

<xsl:template match="/root">
    <xsl:copy-of select="a[not(./following-sibling::a)]
        | b[not(./following-sibling::b)]
        | c[not(./following-sibling::c)]"/>
</xsl:template>
0 голосов
/ 09 апреля 2014

В настоящее время XSLT 2.0 предлагает методы группировки для решения таких проблем:

<xsl:stylesheet version="2.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="/root">
        <xsl:for-each-group select="*" group-by="name()">
            <!-- <xsl:sort select="index-of(/root/*, current-group()[last()])" order="ascending"/> -->
            <xsl:copy-of select="current-group()[last()]" />
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

даст:

<a>Five</a>
<b>
  <a>Six</a>
</b>
<c>Four</c>

, гдегруппировка выполняется в порядке документа, если на нее явно не влияет <xsl:sort>!

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