将 N 列 XML 展开为树结构

Unflatten N-Columns XML to tree structure

我在创建 XSLT 时遇到问题,它会使该结构变平:

<RS>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>C</C2>
        <C3>D</C3>
        <C4>1</C4>
    </R>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>C</C2>
        <C3>E</C3>
        <C4>2</C4>
    </R>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>F</C2>
        <C3></C3>
        <C4>3</C4>
    </R>
</RS>

或者那个结构:

<RS>
    <R>
        <C0>A->B->C->D</C0>
        <C1>1</C1>
    </R>
    <R>
        <C0>A->B->C->E</C0>
        <C1>2</C1>
    </R>
    <R>
        <C0>A->B->F</C0>
        <C1>3</C1>
    </R>
</RS>

进入这个 XML 嵌套树:

<A>
    <B>
        <C>
            <D>1</D>
            <E>2</E>
        </C>
        <F>3</F>
    </B>
</A>

换句话说:我想将 1-N 列(或者:第一列 '->' 分隔字符串值)作为路径并将其转换为嵌套的 XML 节点,最后一列作为节点值。

我已经挣扎了一个多星期了,开始享受从 XML 到 JSON 的转变。

使用 XSLT 3(略有改动)输入

<RS>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>C</C2>
        <C3>D</C3>
        <C4>1</C4>
    </R>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>C</C2>
        <C3>E</C3>
        <C4>2</C4>
    </R>
    <R>
        <C0>A</C0>
        <C1>B</C1>
        <C2>F</C2>
        <C4>3</C4>
    </R>
</RS>

可以像

那样用递归分组进行转换
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="rows" as="element(R)*"/>
    <xsl:param name="index" as="xs:integer"/>
    <xsl:for-each-group select="$rows" group-by="*[$index]">
      <xsl:try>
        <xsl:element name="{current-grouping-key()}">
          <xsl:sequence select="mf:group(current-group(), $index + 1)"/>
        </xsl:element>
        <xsl:catch>
          <xsl:value-of select="current-grouping-key()"/>
        </xsl:catch>
      </xsl:try>
    </xsl:for-each-group>
  </xsl:function>
  
  <xsl:template match="RS">
    <xsl:sequence select="mf:group(R, 1)"/>
  </xsl:template>

  <xsl:output method="xml" indent="yes"/>

</xsl:stylesheet>

进入

<A>
   <B>
      <C>
         <D>1</D>
         <E>2</E>
      </C>
      <F>3</F>
   </B>
</A>

替代输入结构也可以在 XSLT 3 中使用递归分组进行转换,这次使用映射作为中间数据结构:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="rows" as="map(*)*"/>
    <xsl:param name="index" as="xs:integer"/>
    <xsl:for-each-group select="$rows" group-by="?columns[$index]">
      <xsl:element name="{current-grouping-key()}">
          <xsl:sequence select="if (exists(?columns[$index + 1])) then mf:group(current-group(), $index + 1) else ?value"/>
      </xsl:element>
    </xsl:for-each-group>
  </xsl:function>
  
  <xsl:template match="RS">
    <xsl:sequence select="mf:group(R ! map { 'columns' : tokenize(C0, '->'), 'value' : data(C1) }, 1)"/>
  </xsl:template>

  <xsl:output method="xml" indent="yes"/>

</xsl:stylesheet>

使用地图的算法可以通过使用

适应您的第一个输入结构
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="rows" as="map(*)*"/>
    <xsl:param name="index" as="xs:integer"/>
    <xsl:for-each-group select="$rows" group-by="?columns[$index]">
      <xsl:element name="{current-grouping-key()}">
          <xsl:sequence select="if (exists(?columns[$index + 1])) then mf:group(current-group(), $index + 1) else ?value"/>
      </xsl:element>
    </xsl:for-each-group>
  </xsl:function>
  
  <xsl:template match="RS">
    <xsl:sequence select="mf:group(R ! map { 'columns' : *[. castable as xs:NCName], 'value' : data(*[last()]) }, 1)"/>
  </xsl:template>