如何调整XSLT 1.0以保留CSV空令牌并转换为指定XML?
调整XSLT 1.0代码处理含空令牌的CSV生成目标XML
问题背景
你手头有一份包含空令牌的CSV数据,现有的XSLT 1.0代码会跳过这些空值,而且原本是生成层级XML结构,但现在需要改成每个CSV行对应一个<data>节点的格式,每个列要对应表头指定的元素名称。
输入示例
<root> GroupName,GroupValue,SubGroupName,SubGroupValue,ItemName,ItemValue ,A,1,C,1,G 1,,1,C,2,H 1,A,2,D,1,I </root>
现有XSLT的核心问题
原代码的tokenize模板通过<xsl:if test="$token">判断跳过了空令牌,同时输出逻辑是按group层级聚合,完全不符合现在的目标输出结构。
修改后的XSLT代码
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <!-- 拆分CSV为行 --> <xsl:variable name="rows"> <xsl:call-template name="tokenize"> <xsl:with-param name="text" select="root"/> </xsl:call-template> </xsl:variable> <!-- 提取表头并拆分 --> <xsl:variable name="header-row" select="exsl:node-set($rows)/row[1]"/> <xsl:variable name="headers"> <xsl:call-template name="tokenize"> <xsl:with-param name="text" select="$header-row"/> <xsl:with-param name="delimiter" select="','"/> <xsl:with-param name="name" select="'header'"/> </xsl:call-template> </xsl:variable> <!-- 处理数据行,拆分每个行为cell元素 --> <xsl:variable name="data-rows"> <xsl:for-each select="exsl:node-set($rows)/row[position() > 1]"> <row> <xsl:call-template name="tokenize"> <xsl:with-param name="text" select="."/> <xsl:with-param name="delimiter" select="','"/> <xsl:with-param name="name" select="'cell'"/> </xsl:call-template> </row> </xsl:for-each> </xsl:variable> <!-- 生成目标XML结构 --> <Document> <xsl:for-each select="exsl:node-set($data-rows)/row"> <data> <xsl:for-each select="cell"> <xsl:variable name="pos" select="position()"/> <!-- 用表头名称作为元素名,匹配对应位置的cell值 --> <xsl:element name="{exsl:node-set($headers)/header[$pos]}"> <xsl:value-of select="."/> </xsl:element> </xsl:for-each> </data> </xsl:for-each> </Document> </xsl:template> <xsl:template name="tokenize"> <xsl:param name="text"/> <xsl:param name="delimiter" select="' '"/> <xsl:param name="name" select="'row'"/> <xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" /> <!-- 移除空token判断,确保空值也生成对应元素 --> <xsl:element name="{$name}"> <xsl:value-of select="$token"/> </xsl:element> <xsl:if test="contains($text, $delimiter)"> <!-- 递归拆分剩余内容 --> <xsl:call-template name="tokenize"> <xsl:with-param name="text" select="substring-after($text, $delimiter)"/> <xsl:with-param name="delimiter" select="$delimiter"/> <xsl:with-param name="name" select="$name"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>
关键修改说明
- 修复空令牌丢失:删掉了
tokenize模板中判断$token是否为空的<xsl:if test="$token">,这样即使是空字符串也会生成对应的<cell>或<header>元素,完整保留所有空值。 - 重构输出逻辑:放弃了原有的层级聚合逻辑,改成:
- 先提取CSV的表头行,拆分成
<header>元素集合; - 遍历每个数据行,将每个
<cell>和对应位置的表头名称绑定,生成以表头命名的元素; - 每个数据行对应一个
<data>节点,最终统一包裹在<Document>根节点下。
- 先提取CSV的表头行,拆分成
生成的目标输出
<?xml version="1.0" encoding="utf-8"?> <Document> <data> <GroupName></GroupName> <GroupValue>A</GroupValue> <SubGroupName>1</SubGroupName> <SubGroupValue>C</SubGroupValue> <ItemName>1</ItemName> <ItemValue>G</ItemValue> </data> <data> <GroupName>1</GroupName> <GroupValue></GroupValue> <SubGroupName>1</SubGroupName> <SubGroupValue>C</SubGroupValue> <ItemName>2</ItemName> <ItemValue>H</ItemValue> </data> <data> <GroupName>1</GroupName> <GroupValue>A</GroupValue> <SubGroupName>2</SubGroupName> <SubGroupValue>D</SubGroupValue> <ItemName>1</ItemName> <ItemValue>I</ItemValue> </data> </Document>
内容的提问来源于stack exchange,提问作者E. Xue
相关产品推荐
相关产品推荐

