使用 XSLT 展平任何 XML

Flatten any XML using XSLT

我正在尝试使用 XSLT 自动 展平 任何 XML 文件。它可以实现吗?我猜是的,但我找不到办法。

示例输入

<person>
    <name>
        <first>John</first>
        <last>Doe</last>
    </name>
    <data>
        <address>
            <street>Main</street>
            <city>Los Angeles</city>
        </address>
    </data>
</person>

预期输出

<person>
    <name_first>John</name_first>
    <name_last>Doe</name_last>
    <data_address_street>Main</data_address_street>
    <data_address_city>Los Angeles</data_address_city>
</person>

我已经尝试了很多东西,但我得到的更近的是从 中提取的。

<?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:output method="xml" indent="yes"/>

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

    <xsl:template match="/*/*">
        <xsl:for-each select="*">
            <xsl:element name="{concat(name(..),'_',name())}">
                <xsl:apply-templates select="node()"/>
            </xsl:element>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

正如@Michael Kay 评论的那样,一个示例不构成规范。所以我想指出任何注释、处理指令、混合内容以及示例中没有的所有内容都应该被忽略。

你可以用 string-join:

  <xsl:template match="/*">
      <xsl:copy>
          <xsl:apply-templates select="descendant::*[not(*)]"/>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="*">
      <xsl:element name="{string-join(ancestor-or-self::*[position() ne last()]/name(), '_')}">
          <xsl:value-of select="."/>
      </xsl:element>
  </xsl:template>

对于大型文档和 XSLT 3 以及流式传输(例如 Saxon EE),您可以做到

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    
    <xsl:mode streamable="yes"/>
    
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="descendant::text()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="text()">
        <xsl:element name="{string-join(ancestor::*[position() lt last()]/name(), '_')}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>
    
</xsl:stylesheet>