【问题标题】:Print the tree of XML using XSL使用 XSL 打印 XML 树
【发布时间】:2013-07-23 15:09:23
【问题描述】:

我有以下 XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/xsl' href='parser.xsl'?>
<NVS>
    <A>
        <F>007</F>
    </A>
    <A>-002</A>
    <B>--003</B>
    <C>
        <D>------005</D>
    </C>
    <E>-006</E>
</NVS>

我想为每个节点打印一棵树,例如:

/NVS/A/
/NVS/A/F/
/NVS/A/
/NVS/B/
/NVS/C/
/NVS/C/D
/NVS/E/

我尝试了一些 XSL,但买不起正确的结果。 最好的 XSL 是:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML//EN" doctype-system="http://www.w3.org/TR/2001/REC-xhtml11-20010531" indent="yes"/>
    <xsl:template match="/*">
        <html>
            <body>
                <xsl:for-each select=".">/<xsl:value-of select="."/>
                    <br/>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

而且我也试过“for-each”,比如:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML//EN" doctype-system="http://www.w3.org/TR/2001/REC-xhtml11-20010531" indent="yes"/>
    <xsl:template match="/*/*">
        <xsl:for-each select=".">/<xsl:value-of select="."/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

但不是更好。而且我只是打印值,而我只想要节点的名称。

有什么想法吗?

【问题讨论】:

    标签: xml xslt tree xmlnode xmlspy


    【解决方案1】:

    一种可能的解决方案是遍历树并随时跟踪适当的输出。例如,当这个 XSLT:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:output omit-xml-declaration="yes" indent="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="/*">
        <xsl:apply-templates mode="children">
          <xsl:with-param name="pName" select="name()"/>
        </xsl:apply-templates>
      </xsl:template>
    
      <xsl:template match="*[*]" mode="children">
        <xsl:param name="pName"/>
        <xsl:variable name="vNewName" select="concat($pName, '/', name())"/>
        <xsl:value-of select="concat('/', $vNewName, '/&#10;')"/>
        <xsl:apply-templates mode="children">
          <xsl:with-param name="pName" select="$vNewName"/>
        </xsl:apply-templates>
      </xsl:template>
    
      <xsl:template match="*[not(*)]" mode="children">
        <xsl:param name="pName"/>
        <xsl:value-of select="concat('/', $pName, '/', name(), '/&#10;')"/>
      </xsl:template>
    
    </xsl:stylesheet>
    

    ...应用于提供的 XML:

    <NVS>
      <A>
        <F>007</F>
      </A>
      <A>-002</A>
      <B>--003</B>
      <C>
        <D>------005</D>
      </C>
      <E>-006</E>
    </NVS>
    

    ...产生了想要的结果:

    /NVS/A/
    /NVS/A/F/
    /NVS/A/
    /NVS/B/
    /NVS/C/
    /NVS/C/D/
    /NVS/E/
    

    说明:

    • 模板#1 匹配根节点并指示处理器将模板应用到其所有子节点,同时单独传递一个$pName 参数,该参数目前包含根节点的名称。注意mode="children"的使用;我使用它是为了将来具有更通用匹配的模板不包含此根节点。
    • 模板#2 匹配所有有子节点(并且使用mode="children")的节点。找到这样的节点后,处理器会输出一行文本,该文本结合了携带的$pName 参数、正斜杠和当前元素的名称。最后,模板通过将模板应用到所有子节点并传递参数来模仿模板 #1 - 但是,这一次,参数包含创建的串联文本直到并包括该节点
    • 模板#3 匹配所有没有子节点的节点(并且再次使用mode="children")。找到这样的节点后,处理器会输出一行文本,其功能与模板 #2 非常相似。

    【讨论】:

    • 感谢您快速而清晰的回答。你的解释对我很有帮助,因为最终它比我想象的要困难一些。这也是我选择第二个更简单的解决方案的原因之一。
    【解决方案2】:

    另一种选择是使用ancestor-or-self 轴返回树。

    XML 输入

    <NVS>
        <A>
            <F>007</F>
        </A>
        <A>-002</A>
        <B>--003</B>
        <C>
            <D>------005</D>
        </C>
        <E>-006</E>
    </NVS>
    

    XSLT 1.0

    <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())"/>
            </xsl:for-each>
            <xsl:text>&#xA;</xsl:text>
            <xsl:apply-templates select="node()"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    输出

    /NVS
    /NVS/A
    /NVS/A/F
    /NVS/A
    /NVS/B
    /NVS/C
    /NVS/C/D
    /NVS/E
    

    当元素在给定级别多次存在时,您还可以通过在谓词中添加位置来轻松修改它以提供精确路径。例如,有两个 A 元素是 /NVS 的子元素。

    XSLT 1.0

    <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())"/>
                <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>
    

    输出(使用与上述相同的输入)

    /NVS
    /NVS/A[1]
    /NVS/A[1]/F
    /NVS/A[2]
    /NVS/B
    /NVS/C
    /NVS/C/D
    /NVS/E
    

    另外,如果你不想要根元素输出的路径,只需添加这个模板:

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

    【讨论】:

    • 属性处理也很简单。如果你想要一个例子,请告诉我。
    • 是的!太棒了,它工作得非常好。这正是我所期望的:简单、轻便、高效。当一个元素被多次使用时,我不需要显示位置,但看看它是如何完成的很有趣。对了,' '需要什么?
    • @OranginaRouge - &amp;#xA; 是换行符。没有它,所有输出都将在一行上。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-27
    相关资源
    最近更新 更多