Подтвердить что ты не робот

Как вы выводите текущий путь элемента в XSLT?

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

Пример: учитывая следующий фрагмент XML Doc...

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

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

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

4b9b3361

Ответ 1

Не думайте, что это встроено в 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>

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

Ответ 2

В принятом в настоящее время ответе будут возвращены неправильные пути. Например, элемент Ele2 в образце OP XML вернет путь /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

Ответ 3

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

<xsl:for-each select="ancestor::*">...

Ответ 4

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

Ответ 5

Так как 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), позволяя использовать пространства имен и получать формат для возвращаемого путь, который не требует, чтобы пользователь выражения пути устанавливал любые привязки пространства имен для его оценки.