web-dev-qa-db-ja.com

XSLTで現在の要素パスをどのように出力しますか?

XSLTでは、要素を処理するときにXMLドキュメントのどこにいるかを判断する方法はありますか?

例:次のXMLドキュメントフラグメントがあるとします。

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

XSLTで、コンテキストが要素 "Ele111"の場合、XSLTにフルパスを出力させるにはどうすればよいですか? 「/ Doc/Ele1/Ele11/Ele111」を出力したいと思います。

この質問のコンテキスト:非常に大きく、非常に深いドキュメントがあり、徹底的にトラバースしたい(通常は再帰を使用)。特定の属性を持つ要素を見つけた場合は、どこで見つけたかを知りたいです。トラバースしながら現在のパスを実行できると思いますが、XSLT/XPathは知っている必要があると思います。

25
e-holder

これがXPathに組み込まれているとは思わないでください。おそらく、この例に基づいた here のような再帰的なテンプレートが必要です。 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>

2つの子ノードの違いをどのように見分けますか?したがって、たとえば、item-sequence、child 1 およびchild [2]へのインデックスが必要です。

7
brabster

現在受け入れられている回答は、誤ったパスを返します。たとえば、OPサンプルXMLの要素Ele2は、パス/Doc[1]/Ele2[2]を返します。 /Doc[1]/Ele2[1]である必要があります。

正しいパスを返す同様のXSLT1.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.

<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
23
Daniel Haley

ancestorXPath Axes を使用して、すべての親と祖父母を歩くことができます。

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

使用しているXSLTプロセッサはわかりませんが、Saxonの場合は、拡張関数 path() を使用できます。他のプロセッサも同じ機能を持っている場合があります。

3
MartinM

XSLTおよびXmlPrime4(version="3.0"を使用)の--xt30を含むSaxon9.8(すべてのエディション)またはSaxon 9.7、およびAltovaの2017リリース(version="3.0"スタイルシートを使用)でサポートされているXPath3.0以降)組み込みのpath関数があります( https://www.w3.org/TR/xpath-functions-30/#func-pathhttps ://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のサポートが与えられています)。パス式のユーザーがそれを評価するために名前空間バインディングを設定する必要はありません。

1
Martin Honnen