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

含不规则空格的定长文件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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:05:53