【问题标题】:Using XSLT to convert XML to fixed length text file使用 XSLT 将 XML 转换为固定长度的文本文件
【发布时间】:2016-05-19 13:19:14
【问题描述】:

我有一个 XML,我需要将其转换为固定宽度样式的文本文件,并且必须使用 XSLT 方法来完成。

我是第一次使用 XSLT 用户,我对基础知识有了一定的了解,但是这个源 XML 的某些方面使它变得困难......至少对我来说是这样。

<Prescription>
<Drugs>
    <Drug id="1">
        <DrugName>Red Tablets</DrugName>
    </Drug>
    <Drug id="2">
        <DrugName>Blue Tablets</DrugName>
    </Drug>
</Drugs>
<Patients>
    <Patient id="20">
        <Surname>Doe</Surname>
        <Forenames>John</Forenames>
        <Items>
            <Item>
                <ProductID>1</ProductID>
                <AdminEvent date="2016-05-11" hour="7" qty="1"/>
                <AdminEvent date="2016-05-12" hour="7" qty="1"/>
            </Item>
        </Items>
    </Patient>
    <Patient id="50">
        <Surname>Doe</Surname>
        <Forenames>Jane</Forenames>
        <Items>
            <Item>
                <ProductID>2</ProductID>
                <AdminEvent date="2016-05-11" hour="7" qty="1"/>
                <AdminEvent date="2016-05-12" hour="7" qty="1"/>
            </Item>
        </Items>
    </Patient>
<Patients>

这是一个包含 2 种药物和两个患者的处方。患者 John 早上 7 点服用 Red Tablets 2 天,Jane 服用 Blue Tablets。我需要把它变成一个纯文本文件,比如:

[ForeName]+[Surname]+[DrugName]+[DrugID]+[Hour]+[Qty]+[Date]

所以在这个例子中:

John Doe    Blue Tablets    1   7   1   2016-05-11
John Doe    Blue Tablets    1   7   1   2016-05-12
Jane Doe    Red Tablets     2   7   1   2016-05-11
Jane Doe    Red Tablets     2   7   1   2016-05-12

我对以下概念感到困惑: a) 引用不同的分支(EG 从 Item 元素回溯到 Drug) b) 仅在 Item 部分中获取 ID 的 DrugName

希望这一切都有意义!

【问题讨论】:

  • 那些id 值在整个XML 文档中是唯一的吗?还是仅在 Prescription 元素内(假设单个 XML 文档中可以有多个)?
  • 固定宽度是提前知道的,还是你只是想根据列中最长的单词来对齐?应该将名字/姓氏连接起来还是两个单独的列?

标签: c# .net xml xslt


【解决方案1】:

XSLT 1.0

此转换将动态调整患者和药物名称的字段长度...如果它们是固定的,您可以进一步简化。

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl"
>
  <xsl:output method="text" indent="yes"/>

  <xsl:variable name="name-max-length">
    <xsl:for-each select="//Patient">
      <xsl:sort select="string-length(concat(Forenames,' ',Surname))" data-type="number" />
      <xsl:if test="position() = last()">
        <xsl:value-of select="string-length(concat(Forenames,' ',Surname))" />
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="drug-max-length">
    <xsl:for-each select="//DrugName">
      <xsl:sort select="string-length(.)" data-type="number" />
      <xsl:if test="position() = last()">
        <xsl:value-of select="string-length(.)" />
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/">
    <xsl:apply-templates select="//AdminEvent"/>
  </xsl:template>

  <xsl:template match="AdminEvent">
    <xsl:variable name="name" select="concat(ancestor::Patient/Forenames,' ',ancestor::Patient/Surname)" />

    <xsl:variable name="productId" select="ancestor::Item/ProductID/text()"/>
    <xsl:variable name="drug" select="ancestor::Prescription/Drugs/Drug[@id=$productId]/DrugName"/>

    <xsl:value-of select="substring(concat($name,'                    '),1,($name-max-length + 4))"/>
    <xsl:value-of select="substring(concat($drug,'                    '),1,($drug-max-length + 4))"/>

    <xsl:value-of select="substring(concat($productId,'                    '),1,5)"/>

    <xsl:value-of select="substring(concat(@hour,'                    '),1,5)"/>
    <xsl:value-of select="substring(concat(@qty,'                    '),1,5)"/>
    <xsl:value-of select="substring(concat(@date,'                    '),1,10)"/>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

结果

John Doe    Red Tablets     1    7    1    2016-05-11
John Doe    Red Tablets     1    7    1    2016-05-12
Jane Doe    Blue Tablets    2    7    1    2016-05-11
Jane Doe    Blue Tablets    2    7    1    2016-05-12

【讨论】:

    【解决方案2】:

    我对以下概念感到困惑:a) 引用不同的分支 (EG从Item元素回溯到Drug)b)只得到 Item 部分中 ID 的 DrugName

    这很容易解决,方法是使用 key 查找ProductID 引用的药物。例如,下面的样式表:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    
    <xsl:key name="drug" match="Drug" use="@id" />
    
    <xsl:template match="Prescription">
        <xsl:for-each select="Patients/Patient">
            <xsl:variable name="surname" select="Surname" />
            <xsl:variable name="forename" select="Forenames" />
            <xsl:for-each select="Items/Item">
                <xsl:variable name="drugname" select="key('drug', ProductID)/DrugName" />
                <xsl:for-each select="AdminEvent">
                    <xsl:value-of select="$surname" />
                    <xsl:text> | </xsl:text>
                    <xsl:value-of select="$forename" />
                    <xsl:text> | </xsl:text>
                    <xsl:value-of select="$drugname" />
                    <xsl:text> | </xsl:text>
                    <xsl:value-of select="@qty" />
                    <xsl:text> | </xsl:text>
                    <xsl:value-of select="@date" />
                    <xsl:text>&#10;</xsl:text>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
    
    </xsl:stylesheet>
    

    将返回:

    Doe | John | Red Tablets | 1 | 2016-05-11
    Doe | John | Red Tablets | 1 | 2016-05-12
    Doe | Jane | Blue Tablets | 1 | 2016-05-11
    Doe | Jane | Blue Tablets | 1 | 2016-05-12
    

    我建议您单独问一个关于生成固定宽度文件的问题。

    【讨论】:

      【解决方案3】:

      使用 XSLT 2.0,您可以做到相当紧凑

      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
      
          <xsl:output method="text"/>
      
          <xsl:key name="drug-by-id" match="Drugs/Drug" use="@id"/>
      
          <xsl:template match="/">
              <xsl:apply-templates select="//AdminEvent"/>
          </xsl:template>
      
          <xsl:template match="AdminEvent">
              <xsl:value-of
                  select="ancestor::Patient/concat(Forenames, ' ', Surname), key('drug-by-id', ../ProductID)/DrugName, ../ProductID, @qty, @date"/>
              <xsl:text>&#10;</xsl:text>
          </xsl:template>
      
      </xsl:stylesheet>
      

      【讨论】:

        【解决方案4】:

        这是适用于您的示例的 XSLT:

        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        
        <xsl:template match="/">
          <xsl:apply-templates select="//AdminEvent"/>
        </xsl:template>
        
        <xsl:template match="AdminEvent">
          <xsl:value-of select="ancestor::Patient/Forenames"/>
          <xsl:text> </xsl:text>
          <xsl:value-of select="ancestor::Patient/Surname"/>
          <xsl:text> '</xsl:text>
          <xsl:variable name="pid" select="ancestor::Item/ProductID/text()"/>
          <xsl:value-of select="ancestor::Prescription/Drugs/Drug[@id=$pid]/DrugName"/>
          <xsl:text>'
        </xsl:text>
        </xsl:template>
        
        </xsl:stylesheet>
        

        请注意,我会匹配应生成输出行 (AdminSample) 的每个元素,然后根据当前节点收集数据。

        通过ancestor,我从当前的AdminSample节点向上直到找到正确的父节点(Patient),然后再从那里向下,在Drug节点上用括号搜索条件选择更正一个匹配存储在pid 变量中的ProductID。阅读 XPath 轴 here 和 XSLT 变量 here

        现在从AdminEvent 本身添加其他需要的值对您来说应该是微不足道的:-)

        【讨论】:

        • 所有答案都有效,我要感谢大家的帮助。我将其标记为答案,因为它是我决定使用的解决方案。再次感谢大家!
        • @MarkBrad:谢谢,但请注意,虽然它(希望)很容易理解,但在运行时并不是最有效的,所以如果你想处理大文件或大量文件文件,您应该从其他答案中合并 xsl:key 解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多