Как вы выводите текущий путь элемента в XSLT? - PullRequest
24 голосов
/ 05 июня 2009

В XSLT есть способ определить, где вы находитесь в документе XML при обработке элемента?

Пример: приведен следующий фрагмент документа XML ...

<Doc>
  <Ele1>
    <Ele11>
      <Ele111>
      </Ele111>
    </Ele11>
  </Ele1>
  <Ele2>
  </Ele2>
</Doc>

В XSLT, если моим контекстом является Элемент "Ele111", как я могу получить XSLT для вывода полного пути? Я хотел бы, чтобы это вывело: "/Doc/Ele1/Ele11/Ele111".

Контекст этого вопроса: у меня есть очень большой, очень глубокий документ, который я хочу исчерпывающе просмотреть (обычно используя рекурсию), и если я найду элемент с определенным атрибутом, я хочу знать, где я его нашел. Я полагаю, что я мог бы следовать по моему текущему пути, когда я иду, но я думаю, что XSLT / XPath должны знать.

Ответы [ 5 ]

22 голосов
/ 11 апреля 2012

В настоящее время принятый ответ вернет неверные пути. Например, элемент Ele2 в примере XML OP возвращает путь /Doc[1]/Ele2[2]. Это должно быть /Doc[1]/Ele2[1].

Вот аналогичный шаблон XSLT 1.0, который возвращает правильные пути:

  <xsl:template name="genPath">
    <xsl:param name="prevPath"/>
    <xsl:variable name="currPath" select="concat('/',name(),'[',
      count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
    <xsl:for-each select="parent::*">
      <xsl:call-template name="genPath">
        <xsl:with-param name="prevPath" select="$currPath"/>
      </xsl:call-template>
    </xsl:for-each>
    <xsl:if test="not(parent::*)">
      <xsl:value-of select="$currPath"/>      
    </xsl:if>
  </xsl:template>

Вот пример, который добавляет атрибут path ко всем элементам.

Ввод XML

<Doc>
  <Ele1>
    <Ele11>
      <Ele111>
        <foo/>
        <foo/>
        <bar/>
        <foo/>
        <foo/>
        <bar/>
        <bar/>
      </Ele111>
    </Ele11>
  </Ele1>
  <Ele2/>  
</Doc>

XSLT 1.0

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

  <xsl:template match="text()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*">
    <xsl:copy>
      <xsl:attribute name="path">
        <xsl:call-template name="genPath"/>
      </xsl:attribute>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>    
  </xsl:template>

  <xsl:template name="genPath">
    <xsl:param name="prevPath"/>
    <xsl:variable name="currPath" select="concat('/',name(),'[',
      count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
    <xsl:for-each select="parent::*">
      <xsl:call-template name="genPath">
        <xsl:with-param name="prevPath" select="$currPath"/>
      </xsl:call-template>
    </xsl:for-each>
    <xsl:if test="not(parent::*)">
      <xsl:value-of select="$currPath"/>      
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Вывод XML

<Doc path="/Doc[1]">
   <Ele1 path="/Doc[1]/Ele1[1]">
      <Ele11 path="/Doc[1]/Ele1[1]/Ele11[1]">
         <Ele111 path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]">
            <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[1]"/>
            <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[2]"/>
            <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[1]"/>
            <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[3]"/>
            <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[4]"/>
            <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[2]"/>
            <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[3]"/>
         </Ele111>
      </Ele11>
   </Ele1>
   <Ele2 path="/Doc[1]/Ele2[1]"/>
</Doc>

Вот еще одна версия, которая выводит позиционный предикат только в случае необходимости. Этот пример также отличается тем, что он просто выводит путь вместо добавления атрибута.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="*">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name())"/>
            <!--Predicate is only output when needed.-->
            <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="node()"/>
    </xsl:template>

</xsl:stylesheet>

используя входные данные выше, эта таблица стилей выводит:

/Doc
/Doc/Ele1
/Doc/Ele1/Ele11
/Doc/Ele1/Ele11/Ele111
/Doc/Ele1/Ele11/Ele111/foo[1]
/Doc/Ele1/Ele11/Ele111/foo[2]
/Doc/Ele1/Ele11/Ele111/bar[1]
/Doc/Ele1/Ele11/Ele111/foo[3]
/Doc/Ele1/Ele11/Ele111/foo[4]
/Doc/Ele1/Ele11/Ele111/bar[2]
/Doc/Ele1/Ele11/Ele111/bar[3]
/Doc/Ele2
6 голосов
/ 05 июня 2009

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

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="xs"
      version="2.0">

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

    <xsl:template match="//*">
        <path>
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:call-template name="print-step"/>
        </xsl:for-each>
        </path>
        <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template name="print-step">
        <xsl:text>/</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>[</xsl:text>
        <xsl:value-of select="1+count(preceding-sibling::*)"/>
        <xsl:text>]</xsl:text>
    </xsl:template>

</xsl:stylesheet>

Есть несколько осложнений; рассмотрим это дерево:

<root>
  <child/>
  <child/>
</root>

Как вы определяете разницу между двумя дочерними узлами? Таким образом, вам нужен некоторый индекс в вашей последовательности элементов, например, child 1 и child [2].

3 голосов
/ 11 апреля 2012

Я не уверен, какой процессор XSLT вы используете, но если это Saxon, вы можете использовать функцию расширения path () . Другие процессоры могут иметь такую ​​же функциональность.

3 голосов
/ 05 июня 2009

Вы можете использовать предка Оси XPath для обхода всех родителей, бабушек и дедушек.

<xsl:for-each select="ancestor::*">...
1 голос
/ 29 сентября 2017

Начиная с XPath 3.0, поддерживаемого Saxon 9.8 (все выпуски) или Saxon 9.7 с version="3.0" в XSLT и XmlPrime 4 (с использованием --xt30), а также с выпусками Altova 2017 года (с использованием version="3.0" таблиц стилей) существует встроенная функция path (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path), которая для входа типа

<?xml version="1.0" encoding="UTF-8"?>
<Doc>
    <Ele1>
        <Ele11>
            <Ele111>
                <foo/>
                <foo/>
                <bar/>
                <foo/>
                <foo/>
                <bar/>
                <bar/>
            </Ele111>
        </Ele11>
    </Ele1>
    <Ele2/>  
</Doc>

и таблица стилей вроде

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:value-of select="//*/path()" separator="&#10;"/>
    </xsl:template>

</xsl:stylesheet>

дает вывод

/Q{}Doc[1]
/Q{}Doc[1]/Q{}Ele1[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[2]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[1]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[3]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[4]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[2]
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[3]
/Q{}Doc[1]/Q{}Ele2[1]

Этот вывод не так компактен в случае отсутствия пространств имен, как большинство ручных попыток, но у формата есть преимущество (по крайней мере, учитывая поддержку XPath 3.0 или 3.1), позволяющее использовать пространства имен и получать формат для возвращаемого путь, который не требует от пользователя выражения пути установки каких-либо привязок пространства имен для его оценки.

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