【问题标题】:XML to Fixed width text file with xsl style sheetXML 到带有 xsl 样式表的固定宽度文本文件
【发布时间】:2013-05-24 20:28:08
【问题描述】:

我需要使用 xsl 样式表将此 xml 格式化为固定宽度的文本文件的帮助。我对 xsl 知之甚少,并且在网上找到的关于如何做到这一点的信息也很少。

基本上我需要这个xml

<?xml version="1.0" encoding="UTF-8"?>
<Report>
   <table1>
      <Detail_Collection>
         <Detail>
            <SSN>*********</SSN>
            <DOB>1980/11/11</DOB>
            <LastName>user</LastName>
            <FirstName>test</FirstName>
            <Date>2013/02/26</Date>
            <Time>14233325</Time>
            <CurrentStreetAddress1>53 MAIN STREET</CurrentStreetAddress1>
            <CurrentCity>san diego</CurrentCity>
            <CurrentState>CA</CurrentState>
      </Detail_Collection>
   </table1>
</Report>

在这种格式下,都在同一行

*********19801111user         test       201302261423332553 MAIN STREET                                    san diego          CA

这些是固定宽度

FR TO
1   9     SSN
10  17    DOB
18  33    LastName
34  46    FirstName
47  54    Date
55  62    Time
63  90    CurrentStreetAddress1 
91  115   CurrentCity
116 131   CurrentStat

非常感谢所有帮助! 提前致谢!

【问题讨论】:

  • 你可以使用像 node-set() 这样的扩展,或者额外的 xml 文档/文件来保存输出的宽度和顺序吗?

标签: xml xslt xslt-1.0 fixed-width


【解决方案1】:

在 XSLT 1.0 中执行此操作的秘诀是认识到您可以将“填充策略”与“子字符串策略”结合起来,以将一段文本填充或剪切到所需的宽度。特别是这种形式的 XSLT 指令:

substring(concat('value to pad or cut', '       '), 1, 5)

...其中concat 用于向字符串添加多个填充字符,substring 用于限制整体宽度,很有帮助。话虽如此,这里有一个 XSLT 1.0 解决方案可以满足您的需求。

请注意,在您的预期输出中,某些字符宽度不符合您的要求;例如,根据要求,&lt;LastName&gt; 的大小应为 16 个字符,而您的输出似乎将其截断为 13。也就是说,我相信下面的解决方案会输出您所期望的。

当这个 XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes" method="text"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Detail">
    <xsl:apply-templates />
    <xsl:text>&#10;</xsl:text>
  </xsl:template>

  <xsl:template match="SSN">
    <xsl:value-of
      select="substring(concat(., '         '), 1, 9)"/>
  </xsl:template>

  <xsl:template match="DOB">
    <xsl:value-of
      select="substring(concat(translate(., '/', ''), '        '), 1, 8)"/>
  </xsl:template>

  <xsl:template match="LastName">
    <xsl:value-of
      select="substring(concat(., '                '), 1, 16)"/>
  </xsl:template>

  <xsl:template match="FirstName">
    <xsl:value-of
      select="substring(concat(., '             '), 1, 13)"/>
  </xsl:template>

  <xsl:template match="Date">
    <xsl:value-of
      select="substring(concat(translate(., '/', ''), '        '), 1, 8)"/>
  </xsl:template>

  <xsl:template match="Time">
    <xsl:value-of
      select="substring(concat(., ' '), 1, 8)"/>
  </xsl:template>

  <xsl:template match="CurrentStreetAddress1">
    <xsl:value-of
      select="substring(concat(., '                            '), 1, 28)"/>
  </xsl:template>

  <xsl:template match="CurrentCity">
    <xsl:value-of
      select="substring(concat(., '                         '), 1, 25)"/>
  </xsl:template>

  <xsl:template match="CurrentStat">
    <xsl:value-of
      select="substring(concat(., '               '), 1, 15)"/>
  </xsl:template>

</xsl:stylesheet>

...针对提供的 XML 运行(添加了 &lt;/Detail&gt; 以使文档格式正确):

<Report>
  <table1>
    <Detail_Collection>
      <Detail>
        <SSN>*********</SSN>
        <DOB>1980/11/11</DOB>
        <LastName>user</LastName>
        <FirstName>test</FirstName>
        <Date>2013/02/26</Date>
        <Time>14233325</Time>
        <CurrentStreetAddress1>53 MAIN STREET</CurrentStreetAddress1>
        <CurrentCity>san diego</CurrentCity>
        <CurrentState>CA</CurrentState>
      </Detail>
    </Detail_Collection>
  </table1>
</Report>

...产生想要的结果:

*********19801111user            test         201302261423332553 MAIN STREET              san diego                CA

【讨论】:

  • 这是完美的,谢谢。当有多个详细信息部分时,每个人一个,有没有办法插入换行符,以便每个详细信息部分都在新行上?再次感谢。
  • @user973671 - 好问题。我刚刚进行了更新,以适应您描述的场景。
【解决方案2】:

要在 XSLT 1.0 中将字符串填充到给定长度,我将使用 concat() 和 substring() 的组合。例如,在 Detail 的模板中,我可能会写类似

<xsl:value-of 
  select="substring(concat(SSN,'          '),1,9)"/>
<xsl:value-of 
  select="substring(concat(DOB,'          '),1,8)"/>
<xsl:value-of 
  select="substring(concat(LastName,'                '),1,16)"/>
...
<xsl:text>&#xA;</xsl:text>

如果您对 XSLT 知之甚少,您还需要学习如何构建样式表:XSLT 通常使用模板匹配来驱动样式表中的控制流,这对于来自命令式编程语言的人来说通常很难掌握他们的头。

如果您知道每个 Detail 元素将在相同的序列中具有相同的子元素(这是 DTD 和模式的优点之一),那么最简单的做法是为可能出现的每种元素类型编写一个模板在输入。以下样式表说明了一些但不是所有元素的模式:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:variable name="blanks10" select="          "/>
  <xsl:variable name="blanks" 
    select="concat($blanks10, $blanks10, $blanks10)"/>

  <!--* For Report, table1, and Detail_collection, we just 
      * recur on the children *-->
  <xsl:template match="Report | table1 | Detail_collection">
    <xsl:apply-templates select="*"/>
  </xsl:template>

  <!--* For Detail, we recur on the children and supply a
      * line-ending newline. *-->
  <xsl:template match="Detail">
    <xsl:apply-templates select="*"/>
    <xsl:text>&#xA;</xsl:text>
  </xsl:template>

  <!--* For SSN, DOB, etc., we pad the value with blanks and
      * truncate at the appropriate length. *-->
  <xsl:template match="SSN">
    <xsl:value-of select="substring(concat(.,$blanks),1,9)"
  </xsl:template>

  <!--* For DOB, we assume input is yyyy/mm/dd and output should
      * be yyyymmdd. *-->
  <xsl:template match="DOB">
    <xsl:value-of 
      select="substring(concat(translate(.,'/',''),$blanks),1,8)"
  </xsl:template>

  <xsl:template match="LastName">
    <xsl:value-of select="substring(concat(.,$blanks),1,16)"
  </xsl:template>     

  <!--* FirstName etc. left as exercise for the reader. *-->

</xsl:stylesheet>

如果 Detail 可以按顺序或总体变化,则可以通过将 Detail 模板中对 xsl:apply-templates 的调用替换为此处第一个代码片段中所示的代码来规范化变化。这种代码风格对一些程序程序员来说也更自然。因此,我建议您在学习 XSLT 时有意识地避免它。如果你想学好 XSLT,与 xsl:apply-templates 成为朋友是值得的。

如果您不关心学习 XSLT,那么我的建议是希望有人通过为您的任务提供完整的解决方案来回答您的问题。

【讨论】:

    【解决方案3】:

    以下是(在我看来)更可靠和可维护的精简版:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
        <xsl:output method="text" indent="no"/>
    
        <xsl:variable name="some_spaces" select="'                                                                  '" />
    
        <xsl:template match="/">
            <xsl:apply-templates select="//Detail_Collection/Detail" />
        </xsl:template>
    
        <xsl:template match="Detail_Collection/Detail">
            <xsl:apply-templates mode="format" select="SSN">
                <xsl:with-param name="width" select="number(9-1)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format_date" select="DOB">
                <xsl:with-param name="width" select="number(17-10)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="LastName">
                <xsl:with-param name="width" select="number(33-18)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="FirstName">
                <xsl:with-param name="width" select="number(46-34)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format_date" select="Date">
                <xsl:with-param name="width" select="number(54-47)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="Time">
                <xsl:with-param name="width" select="number(62-55)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="CurrentStreetAddress1">
                <xsl:with-param name="width" select="number(90-63)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="CurrentCity">
                <xsl:with-param name="width" select="number(115-91)"/>
            </xsl:apply-templates>
            <xsl:apply-templates mode="format" select="CurrentState">
                <xsl:with-param name="width" select="number(131-116)"/>
            </xsl:apply-templates>
            <xsl:text>&#10;</xsl:text>
        </xsl:template>
    
        <xsl:template  match="node()" mode ="format">
            <xsl:param name="width" />
            <xsl:value-of select="substring(concat(text(),$some_spaces ), 1, $width+1)"/>
        </xsl:template>
        <xsl:template  match="node()" mode="format_date">
            <xsl:param name="width" />
            <xsl:value-of select="substring(concat(translate(text(),'/',''),$some_spaces ), 1, $width+1)"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    即使输入中的字段与请求的输出顺序不符,或者输入中缺少字段,它也会创建正确的输出。 它还认为有多个 Detail 条目。

    【讨论】:

    • 嗯,所有的宽度都减一:如果 CurrentStreet 占据列 63 到 90,它的宽度是 28,而不是 90 - 63 (= 27)。对于所有其他人也是如此。
    • @C.M.Sperberg-McQueen:再仔细看看你会发现, $width+1。 ;-) 认为计算机的计算能力比人类好。
    • 啊;我太快了。触摸。
    • 嗨,我非常喜欢您的解决方案,但是如果数据需要以左对齐(文本)或右对齐(数字)表示怎么办
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    相关资源
    最近更新 更多