Как получить xpath для всех листовых элементов из XML? - PullRequest
11 голосов
/ 30 января 2012

Мне интересно, возможно ли создать таблицу стилей XSLT, которая бы извлекала XPATH для всех конечных элементов в данном файле XML. Например. для

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <item1>value1</item1>
    <subitem>
        <item2>value2</item2>
    </subitem>
</root>

Вывод будет

/root/item1
/root/subitem/item2

Ответы [ 4 ]

16 голосов
/ 30 января 2012
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="text" indent="no" />

    <xsl:template match="*[not(*)]">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/', name())"/>

            <xsl:if test="count(preceding-sibling::*[name() = name(current())]) != 0">
                <xsl:value-of select="concat('[', count(preceding-sibling::*[name() = name(current())]) + 1, ']')"/>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="*">
        <xsl:apply-templates select="*"/>
    </xsl:template>

</xsl:stylesheet>

выходы:

/root/item1
/root/subitem/item2
9 голосов
/ 30 января 2012

Это преобразование :

<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:variable name="vApos">'</xsl:variable>

        <xsl:template match="*[@* or not(*)] ">
          <xsl:if test="not(*)">
             <xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
             <xsl:text>&#xA;</xsl:text>
            </xsl:if>
            <xsl:apply-templates select="@*|*"/>
        </xsl:template>

        <xsl:template match="*" mode="path">
            <xsl:value-of select="concat('/',name())"/>
            <xsl:variable name="vnumSiblings" select=
             "count(../*[name()=name(current())])"/>
            <xsl:if test="$vnumSiblings > 1">
                <xsl:value-of select=
                 "concat('[',
                         count(preceding-sibling::*
                                [name()=name(current())]) +1,
                         ']')"/>
            </xsl:if>
        </xsl:template>

        <xsl:template match="@*">
            <xsl:apply-templates select="../ancestor-or-self::*" mode="path"/>
            <xsl:value-of select="concat('[@',name(), '=',$vApos,.,$vApos,']')"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:template>
</xsl:stylesheet>

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

<root>
    <item1>value1</item1>
    <subitem>
        <item2>value2</item2>
    </subitem>
</root>

дает желаемый, правильный результат :

/root/item1
/root/subitem/item2

С этим документом XML :

<root>
    <item1>value1</item1>
    <subitem>
        <item>value2</item>
        <item>value3</item>
    </subitem>
</root>

правильно выдает :

/root/item1
/root/subitem/item[1]
/root/subitem/item[2]

См. Также связанный ответ : https://stackoverflow.com/a/4747858/36305

3 голосов
/ 30 января 2012

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

В любом случае следующий ответ исправляет этот случай (скопировано и изменено из ответа @ Kirill):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output method="text" indent="no" />

   <xsl:template match="*[not(*)]">
      <xsl:for-each select="ancestor-or-self::*">
         <xsl:value-of select="concat('/', name())"/>

         <!-- Suggestions on how to refactor the repetition of long XPath
              expression parts are welcome. -->
         <xsl:if test="count(../*[local-name() = local-name(current())
               and namespace-uri(.) = namespace-uri(current())]) > 1">
            <xsl:value-of select="concat('[', count(
               preceding-sibling::*[local-name() = local-name(current())
               and namespace-uri(.) = namespace-uri(current())]) + 1, ']')"/>
         </xsl:if>
      </xsl:for-each>
      <xsl:text>&#xA;</xsl:text>
      <xsl:apply-templates select="*"/>
   </xsl:template>

   <xsl:template match="*">
      <xsl:apply-templates select="*"/>
   </xsl:template>

</xsl:stylesheet>

Это также решает проблему в других ответах, где элементам, которые являются первыми в ряду родных братьев и сестер, не хватает предиката позиции.

Например, для ввода

<root>
   <item1>value1</item1>
   <subitem>
      <a:item xmlns:a="uri">value2</a:item>
      <b:item xmlns:b="uri">value3</b:item>
   </subitem>
</root>

thisОтвет дает

/root/item1
/root/subitem/a:item[1]
/root/subitem/b:item[2]

, что является правильным.

Однако, как и все выражения XPath, они будут работать, только если среда, в которой они используются, задает правильные привязки для используемых префиксов пространства имен.В теории может быть больше патологических документов, для которых приведенный выше ответ генерирует выражения XPath, которые никогда не могут работать (по крайней мере в XPath 1.0) независимо от привязки префикса.Например, этот ввод:

<root>
   <item1>value1</item1>
   <a:subitem xmlns:a="differentURI">
      <a:item xmlns:a="uri">value2</a:item>
      <b:item xmlns:b="uri">value3</b:item>
   </a:subitem>
</root>

производит вывод

/root/item1
/root/a:subitem/a:item[1]
/root/a:subitem/b:item[2]

Но второе выражение XPath здесь не может работать, так как префикс a относится к двум различным пространствам имен в одном и том же выражении..

2 голосов
/ 30 января 2012

Ну, вы можете найти листовые элементы с //*[not(*)] и, конечно, вы можете for-each ось предка или себя, чтобы вывести путь.Но как только у вас появятся пространства имен, генерация выражений XPath усложняется.

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