You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在XSLT中存储转换后XML并构建分类树结构?

XSLT实现分类树生成:去重与ID/父节点属性处理

问题说明

  1. 关于XSLT中存储转换后XML的问题:在XSLT 1.0中,无法直接将动态生成的XML节点树存储到变量中(XSLT 2.0及以上支持节点序列变量),但可以通过键(Key)、递归模板等方式直接基于原数据完成多阶段处理,无需显式存储中间XML。

  2. 业务需求:从包含层级分类文本的商品XML中,生成无重复的分类树结构,每个分类节点需要分配唯一递增ID,并正确关联父节点ID。

初始XML

<shop>
    <product>
        <name />
        <categorytxt> Garden > Tables > Blue </categorytxt>
    </product>
    <product>
        <name/>
        <categorytxt> Garden > Tables > Green </categorytxt>
    </product>
    <product>
        <name />
        <categorytxt> Home > Tables > Green </categorytxt>
    </product>
    <product>
        <name />
        <categorytxt> Home > Chairs > White </categorytxt>
    </product>
</shop>

期望输出XML

<!-- category treeview structure -->
<categories>
    <category id="1" parent="root">Garden</category>
    <category id="2" parent="1">Tables</category>
    <category id="3" parent="2">Blue</category>
    <category id="4" parent="2">Green</category>

    <category id="5" parent="root">Home</category>
    <category id="6" parent="5">Tables</category>
    <category id="7" parent="6">Green</category>

    <category id="8" parent="5">Chairs</category>
    <category id="9" parent="8">White</category>
</categories>

现有代码(待优化)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="no" method="xml" />

<xsl:template match="/">
    <categories>
        <xsl:for-each select="//categorytxt[not(.=../../preceding-sibling::categorytxt/product)]">
            <xsl:sort select="." />
            <xsl:call-template name="output-tokens">
                <xsl:with-param name="num" select="1" />
                <xsl:with-param name="list" select="concat(. ,' > ')" />
            </xsl:call-template>
        </xsl:for-each>
    </categories>
</xsl:template>

<xsl:template name="output-tokens">
    <xsl:param name="num" />
    <xsl:param name="list" />
    <xsl:variable name="newlist" select="concat(normalize-space($list), ' ')" />
    <xsl:variable name="first" select="substring-before($newlist, '>')" />
    <xsl:variable name="remaining" select="substring-after($newlist, '>')" />

    <xsl:if test="$first">
         <category id="{$num}">
            <xsl:copy-of select="normalize-space($first)" />
         </category>
    </xsl:if>

    <xsl:if test="$remaining">
        <xsl:call-template name="output-tokens">
            <xsl:with-param name="num" select="$num + 1"/>
            <xsl:with-param name="list" select="$remaining" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>
</xsl:stylesheet>

解决方案与完整代码

核心思路:

  • 用<xsl:key>定义分类路径的唯一性,确保每个完整路径(如Garden、Garden > Tables)只生成一次节点
  • 递归处理分类层级,为每个唯一路径分配递增ID,并通过父路径查找对应的父ID
  • 按层级顺序生成节点,保证父节点先于子节点被创建
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes" method="xml"/>
    
    <!-- 定义键:以分类的完整路径为键,确保每个唯一路径只匹配一次 -->
    <xsl:key name="category-path" match="category-path" use="."/>
    
    <!-- 预处理所有分类文本,生成完整路径列表 -->
    <xsl:variable name="all-category-paths">
        <xsl:for-each select="//categorytxt">
            <xsl:call-template name="generate-paths">
                <xsl:with-param name="full-path" select="normalize-space(.)"/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:variable>
    
    <!-- 主模板 -->
    <xsl:template match="/">
        <categories>
            <!-- 提取所有唯一的分类路径,按路径排序 -->
            <xsl:for-each select="msxsl:node-set($all-category-paths)/category-path[generate-id() = generate-id(key('category-path', .)[1])]">
                <xsl:sort select="."/>
                <xsl:call-template name="create-category-node">
                    <xsl:with-param name="path" select="."/>
                </xsl:call-template>
            </xsl:for-each>
        </categories>
    </xsl:template>
    
    <!-- 递归生成完整路径片段(如Garden > Tables会生成Garden、Garden > Tables) -->
    <xsl:template name="generate-paths">
        <xsl:param name="full-path"/>
        <xsl:if test="$full-path">
            <category-path><xsl:value-of select="$full-path"/></category-path>
            <xsl:call-template name="generate-paths">
                <xsl:with-param name="full-path" select="substring-before($full-path, ' > ')"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    
    <!-- 为分类路径生成节点,分配ID和父ID -->
    <xsl:template name="create-category-node">
        <xsl:param name="path"/>
        <xsl:variable name="parent-path" select="substring-before($path, ' > ')"/>
        <xsl:variable name="category-name" select="substring-after($path, ' > ')"/>
        
        <!-- 计算当前分类的ID:统计所有路径长度小于等于当前路径的唯一节点数量 -->
        <xsl:variable name="current-id">
            <xsl:for-each select="msxsl:node-set($all-category-paths)/category-path[generate-id() = generate-id(key('category-path', .)[1])]">
                <xsl:if test="string-length(.) &lt;= string-length($path) and contains(concat(' > ', $path, ' > '), concat(' > ', ., ' > '))">
                    <xsl:value-of select="position()"/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        
        <!-- 生成节点 -->
        <category id="{$current-id}">
            <!-- 设置父节点属性 -->
            <xsl:if test="$parent-path">
                <xsl:attribute name="parent">
                    <xsl:for-each select="msxsl:node-set($all-category-paths)/category-path[generate-id() = generate-id(key('category-path', .)[1])]">
                        <xsl:if test=". = $parent-path">
                            <xsl:value-of select="position()"/>
                        </xsl:if>
                    </xsl:for-each>
                </xsl:attribute>
            </xsl:if>
            <xsl:if test="not($parent-path)">
                <xsl:attribute name="parent">root</xsl:attribute>
            </xsl:if>
            
            <!-- 分类名称:如果是根分类则直接取路径,否则取最后一段 -->
            <xsl:value-of select="if($parent-path) then $category-name else $path"/>
        </category>
    </xsl:template>
</xsl:stylesheet>

关键逻辑说明

  1. 路径预处理:generate-paths模板将每个完整分类路径拆分为所有层级的片段(如Garden > Tables > Blue会生成Garden > Tables > Blue、Garden > Tables、Garden),存储到变量中。
  2. 去重处理:通过<xsl:key>和generate-id()筛选出所有唯一的路径片段,避免重复生成分类节点。
  3. ID与父ID分配:通过统计路径在唯一列表中的位置得到ID;父ID则通过查找父路径在列表中的位置获取,根分类的父节点设为root。

内容的提问来源于stack exchange,提问作者Simon Zeman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 06:45:38