这是一个通用解决方案,可以正确处理任何具有指定格式的行集——即使每行有不同数量的下划线,并且第一个“名称”在所有行上都不相同:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vLines" select="tokenize(/*, '\r?\n')[.]"/>
<xsl:variable name="vPass1">
<t>
<xsl:apply-templates mode="pass1"/>
</t>
</xsl:variable>
<xsl:template match="/*" mode="pass1">
<xsl:for-each select="$vLines">
<xsl:sequence select="my:makeTree(normalize-space(.))"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1" mode="pass2"/>
</xsl:template>
<xsl:function name="my:makeTree">
<xsl:param name="pLine"/>
<xsl:variable name="vName" select="substring-before($pLine, '_')"/>
<xsl:choose>
<xsl:when test="$vName">
<xsl:element name="{$vName}">
<xsl:sequence select="my:makeTree(substring-after($pLine, '_'))"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name=
"{normalize-space(substring-before($pLine, '='))}">
<xsl:sequence select="substring-after($pLine, '=')"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:function name="my:group">
<xsl:param name="pNodes" as="node()*"/>
<xsl:for-each-group select="$pNodes[self::*]" group-by="name()">
<xsl:element name="{name()}">
<xsl:for-each select="current-group()">
<xsl:sequence select="my:group(node())"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
<xsl:copy-of select="$pNodes[not(self::*)]"/>
</xsl:function>
<xsl:template match="*[not(my:path(.) = preceding::*/my:path(.))]" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="//*[my:path(.) = my:path((current()))]/node()"
mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="pass2"/>
<xsl:template match="/*" mode="pass2" priority="3">
<xsl:apply-templates mode="pass2"/>
</xsl:template>
<xsl:function name="my:path" as="xs:string">
<xsl:param name="pElement" as="element()"/>
<xsl:sequence select=
"string-join($pElement/ancestor-or-self::*/name(.), '/')"/>
</xsl:function>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时(给定的行,包装到顶部元素中以使其成为格式良好的 XML 文档):
<t>
heading1_sub1_element1 = data1
heading1_sub1_element2 = data2
heading1_sub1_element3 = data3
heading1_sub2_element1 = data4
heading1_sub2_element2 = data5
heading1_sub2_element3 = data6
</t>
产生了想要的正确结果:
<heading1>
<sub1>
<element1> data1</element1>
<element2> data2</element2>
<element3> data3</element3>
</sub1>
<sub2>
<element1> data4</element1>
<element2> data5</element2>
<element3> data6</element3>
</sub2>
</heading1>
当对这个更复杂的 XML 文档应用相同的转换时:
<t>
heading1_sub1_element1 = data1
heading1_sub1_element2 = data2
heading1_sub1_element3 = data3
heading1_sub2_element1 = data4
heading1_sub2_element2 = data5
heading1_sub2_element3 = data6
heading2_sub1_sub2_sub3 = data7
heading2_sub1_sub2_sub3_sub4 = data8
heading2_sub1_sub2 = data9
heading2_sub1 = data10
heading2_sub1_sub2_sub3 = data11
</t>
我们再次得到正确的、想要的结果:
<heading1>
<sub1>
<element1> data1</element1>
<element2> data2</element2>
<element3> data3</element3>
</sub1>
<sub2>
<element1> data4</element1>
<element2> data5</element2>
<element3> data6</element3>
</sub2>
</heading1>
<heading2>
<sub1>
<sub2>
<sub3>
data7
<sub4> data8</sub4>
data11
</sub3>
data9
</sub2>
data10
</sub1>
</heading2>
说明:
这是一个两遍处理:
- 在 pass1 中,我们将输入转换为一个临时树(在上面的第一个 XML 文档的情况下),如下所示:
......
<t>
<heading1>
<sub1>
<element1> data1</element1>
</sub1>
</heading1>
<heading1>
<sub1>
<element2> data2</element2>
</sub1>
</heading1>
<heading1>
<sub1>
<element3> data3</element3>
</sub1>
</heading1>
<heading1>
<sub2>
<element1> data4</element1>
</sub2>
</heading1>
<heading1>
<sub2>
<element2> data5</element2>
</sub2>
</heading1>
<heading1>
<sub2>
<element3> data6</element3>
</sub2>
</heading1>
</t>
.2. 在第二遍中,我们执行特定类型的分组,以便产生想要的结果。
注意:在这个解决方案中,我们将输入字符串作为 XML 文档中唯一元素的唯一文本节点子节点来访问。这实际上是不必要的,我这样做只是为了方便。我们可以使用标准 XSLT 2.0 函数从外部文本文件中读取字符串unparsed-text()
。