含不规则空格的定长文件XSLT转XML时的子串计算问题
定长空格分隔文件XSLT转换时空格丢失导致子串提取错误
问题场景
我有一个每行固定长度但包含不定数量连续空格的文本文件,示例内容:
01474011240229GASOHOL 000000+000000+0006500064640009987
01474021240216SUPER 001000+001000+0000852011700006022
用XSLT转换为XML时,连续空格被压缩成单个空格,导致按固定位置提取子串的逻辑完全失效——比如提取前5个字符或指定位置的子串,结果都不符合预期。
试过用translate()函数把空格替换成-,但它会把连续空格替换成单个-,还是解决不了问题。当前使用的XSLT代码如下:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:xslextnobjects="urn:xsl-extn-objects"> <xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes" omit-xml-declaration="yes"/> <xsl:include href="shared_csv_to_xml.xsl"/> <xsl:template match="/"> <xsl:variable name="ParsedCsvRow"> <xsl:call-template name="splitNewLine"> <xsl:with-param name="str" select="."/> </xsl:call-template> </xsl:variable> <xsl:call-template name="GenerateTransformedOutput"> <xsl:with-param name="CsvRowData"> <xsl:copy-of select="$ParsedCsvRow"/> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template name="GenerateTransformedOutput"> <xsl:param name="CsvRowData"/> <xsl:element name="pseudoRoot"> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="businessDate"> <xsl:text>0001-01-01T01:01:01.0001-06:00</xsl:text> </xsl:element> </xsl:element> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="businessDate"> <xsl:text>0001-01-01T01:01:01.0001-06:00</xsl:text> </xsl:element> </xsl:element> <xsl:for-each select="msxsl:node-set($CsvRowData)/record"> <xsl:choose> <xsl:when test="position() = 0"/> <xsl:otherwise> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="siteIdentifier"> <xsl:value-of select="xslextnobjects:Substring(./column1, 0, 5)"/> </xsl:element> <xsl:element name="tankIdentifier"> <xsl:value-of select="xslextnobjects:Substring(./column1, 0, 7)"/> </xsl:element> <xsl:element name="businessDate"> <xsl:value-of select="xslextnobjects:ConvertDateTimeOffsetFormat(substring(./column1, 9, 6), 'yyMMdd', 'yyyy-MM-ddTHH:mm:ss.ffff')" /> </xsl:element> <xsl:element name="sales"> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(xslextnobjects:ReplaceText(./column1, ' ', '-'), 38, 6))" /> </xsl:element> <xsl:element name="closeVolume"> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(xslextnobjects:ReplaceText(./column1, ' ', '-'), 44, 5))" /> </xsl:element> <xsl:element name="productIdentifier"> <xsl:value-of select="translate(./column1, ' ', '-')"/> </xsl:element> <xsl:element name="deliveryGrossVolume"> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(xslextnobjects:ReplaceText(./column1, ' ', '-'), 24, 6))" /> </xsl:element> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:for-each> </xsl:element> </xsl:template> </xsl:stylesheet>
问题根源
XSLT处理文本节点时,默认会把连续空白字符(空格、换行、制表符等)合并成单个空格,这是XML的默认行为。处理定长格式的文本必须保留原始空格数量,不能让解析器自动合并。
解决方法
- 保留原始空格:确保输入文本被当作纯文本处理,解析器不合并空白。如果用
splitNewLine模板拆分行,要保证模板不修改文本中的空格数量。 - 逐个替换空格:不要用
translate(),它会把连续空格换成单个占位符。改用递归模板逐个替换每个空格为-,保留原始空格的数量:<xsl:template name="replace-all-spaces"> <xsl:param name="text"/> <xsl:choose> <xsl:when test="contains($text, ' ')"> <xsl:value-of select="substring-before($text, ' ')"/> <xsl:text>-</xsl:text> <xsl:call-template name="replace-all-spaces"> <xsl:with-param name="text" select="substring-after($text, ' ')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> - 直接用原始文本提取子串:只要文本保留了原始空格,
substring()就能按正确位置提取内容,不需要先替换空格。
修改后的完整XSLT示例
把递归替换模板加入样式表,修改相关字段的处理逻辑:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:xslextnobjects="urn:xsl-extn-objects"> <xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes" omit-xml-declaration="yes"/> <xsl:include href="shared_csv_to_xml.xsl"/> <!-- 新增递归替换所有空格为-的模板 --> <xsl:template name="replace-all-spaces"> <xsl:param name="text"/> <xsl:choose> <xsl:when test="contains($text, ' ')"> <xsl:value-of select="substring-before($text, ' ')"/> <xsl:text>-</xsl:text> <xsl:call-template name="replace-all-spaces"> <xsl:with-param name="text" select="substring-after($text, ' ')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="/"> <xsl:variable name="ParsedCsvRow"> <xsl:call-template name="splitNewLine"> <xsl:with-param name="str" select="."/> </xsl:call-template> </xsl:variable> <xsl:call-template name="GenerateTransformedOutput"> <xsl:with-param name="CsvRowData"> <xsl:copy-of select="$ParsedCsvRow"/> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template name="GenerateTransformedOutput"> <xsl:param name="CsvRowData"/> <xsl:element name="pseudoRoot"> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="businessDate"> <xsl:text>0001-01-01T01:01:01.0001-06:00</xsl:text> </xsl:element> </xsl:element> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="businessDate"> <xsl:text>0001-01-01T01:01:01.0001-06:00</xsl:text> </xsl:element> </xsl:element> <xsl:for-each select="msxsl:node-set($CsvRowData)/record"> <xsl:choose> <xsl:when test="position() = 0"/> <xsl:otherwise> <xsl:element name="altFeedIntermediateSchema"> <xsl:element name="siteIdentifier"> <xsl:value-of select="xslextnobjects:Substring(./column1, 0, 5)"/> </xsl:element> <xsl:element name="tankIdentifier"> <xsl:value-of select="xslextnobjects:Substring(./column1, 0, 7)"/> </xsl:element> <xsl:element name="businessDate"> <xsl:value-of select="xslextnobjects:ConvertDateTimeOffsetFormat(substring(./column1, 9, 6), 'yyMMdd', 'yyyy-MM-ddTHH:mm:ss.ffff')" /> </xsl:element> <xsl:element name="sales"> <!-- 直接使用原始文本提取,不替换空格 --> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(./column1, 38, 6))"/> </xsl:element> <xsl:element name="closeVolume"> <!-- 直接使用原始文本提取 --> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(./column1, 44, 5))"/> </xsl:element> <xsl:element name="productIdentifier"> <!-- 使用递归模板替换所有空格为- --> <xsl:call-template name="replace-all-spaces"> <xsl:with-param name="text" select="./column1"/> </xsl:call-template> </xsl:element> <xsl:element name="deliveryGrossVolume"> <!-- 直接使用原始文本提取 --> <xsl:value-of select="xslextnobjects:GetNullableDouble(substring(./column1, 24, 6))"/> </xsl:element> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:for-each> </xsl:element> </xsl:template> </xsl:stylesheet>
额外注意事项
检查shared_csv_to_xml.xsl中的splitNewLine模板,确保它在拆分每行时没有合并空白字符。如果该模板有处理空白的逻辑,需要修改为保留原始空格。
内容的提问来源于stack exchange,提问作者SV1
相关产品推荐
相关产品推荐

