如何使用 XSLT 2.0 将 csv 文件转换为结构化 XML 文件?

Posted

技术标签:

【中文标题】如何使用 XSLT 2.0 将 csv 文件转换为结构化 XML 文件?【英文标题】:how to transform a csv file to a structured XML file using XSLT 2.0? 【发布时间】:2020-05-15 18:57:33 【问题描述】:

我想将下面的 CSV 转换为 XML

CSV 输入示例

01,TeacherHeader1
02,StudentHeader1
03,SubjectHeader1
10,Grade1,Score99
10,Grade2,Score99
48,SubjectTrailer1
49,StudentTrailer1
02,StudentHeader2
03,SubjectHeader1
10,Grade1,Score50
10,Grade2,Score50
48,SubjectTrailer1
49,StudentTrailer2
50,TeacherTrailer1

输出应该是

  <FileHeader> 
    <id>01</id>  
    <name>TeacherHeader1</name> 
  </FileHeader>  
  <GroupRecord> 
    <GroupHeader> 
      <id>02</id>  
      <name>StudentHeader1</name> 
    </GroupHeader>  
    <AccountRecord> 
      <AccountHeader> 
        <id>03</id>  
        <name>SubjectHeader1</name> 
      </AccountHeader>  
      <AccountDetails> 
        <Details> 
          <id>10</id>  
          <name>Grade1</name>  
          <value>Score99</value> 
        </Details>  
        <Details> 
          <id>10</id>  
          <name>Grade2</name>  
          <value>Score99</value> 
        </Details> 
      </AccountDetails>  
      <AccountTrailer> 
        <id>48</id>  
        <name>SubjectTrailer1</name> 
      </AccountTrailer> 
    </AccountRecord>  
    <GroupTrailer> 
      <id>49</id>  
      <name>StudentTrailer1</name> 
    </GroupTrailer> 
  </GroupRecord>  
  <GroupRecord> 
    <GroupHeader> 
      <id>02</id>  
      <name>StudentHeader2</name> 
    </GroupHeader>  
    <AccountRecord> 
      <AccountHeader> 
        <id>03</id>  
        <name>SubjectHeader1</name> 
      </AccountHeader>  
      <AccountDetails> 
        <Details> 
          <id>10</id>  
          <name>Grade1</name>  
          <value>Score99</value> 
        </Details>  
        <Details> 
          <id>10</id>  
          <name>Grade2</name>  
          <value>Score99</value> 
        </Details> 
      </AccountDetails>  
      <AccountTrailer> 
        <id>48</id>  
        <name>SubjectTrailer1</name> 
      </AccountTrailer> 
    </AccountRecord>  
    <GroupTrailer> 
      <id>49</id>  
      <name>StudentTrailer2</name> 
    </GroupTrailer> 
  </GroupRecord>  
  <FileTrailer> 
    <id>50</id>  
    <name>TeacherTrailer1</name> 
  </FileTrailer> 

在哪里

01 = FileHeader 
02 = GroupHeader (grouped inside GroupRecord)
03 = AccountHeader (grouped inside AccountRecord)
10 = Details (grouped inside AccountDetails)
48 = AccountTrailer (grouped inside AccountRecord)
49 = GroupTrailer (group inside GroupRecord)
50 = FileTrailer  

我想将上面的 CSV 转换为结构正确的 XML,如上所示。 任何帮助将不胜感激。谢谢。

【问题讨论】:

使用unparsed-text(-lines) 加上tokenize 您应该能够将文本转换为XML。然后你可以使用普通的xsl:for-each-group。我不确定您到底需要哪种方法,由于它的格式,您想要的输出似乎表明 XML 未显示的 soem 嵌套,您可能想要改进格式并删除 XML 不应该嵌套的任何缩进。 感谢您的回复。我确实在帖子下方添加了一些图例,以指出每个记录标识符的标签是什么(这是 CSV 上每行的前两个字符),并指出应该放置或分组的位置。您可以通过复制 XML 并使用外部编辑器来正确格式化它。 希望有人能尽快帮助我:( CSV有Score99Score50,为什么输出为XML只有Score99 我的错 - 这是复制粘贴时的拼写错误。 【参考方案1】:

正如我在评论中所说,您可以使用 unparsed-texttokenize 处理文本文件以将其转换为 XML(或在 XSLT 3 中使用 unparsed-text-linestokenize 如果可用),然后其余的可以使用嵌套的xsl:for-each-groups 来完成任务,甚至可以在建立规则模式后使用一两个递归函数;以下尝试拼出嵌套的for-each-groups:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    expand-text="yes"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="data" as="xs:string">01,TeacherHeader1
02,StudentHeader1
03,SubjectHeader1
10,Grade1,Score99
10,Grade2,Score99
48,SubjectTrailer1
49,StudentTrailer1
02,StudentHeader2
03,SubjectHeader1
10,Grade1,Score50
10,Grade2,Score50
48,SubjectTrailer1
49,StudentTrailer2
50,TeacherTrailer1</xsl:param>

<xsl:param name="header-ids" as="xs:string*"
  select="'01', '02', '03', '10', '48', '49', '50'"/>

<xsl:param name="header-names" as="xs:string*"
  select="'FileHeader ', 'GroupHeader', 'AccountHeader', 'Details', 'AccountTrailer', 'GroupTrailer', 'FileTrailer'"/>

  <xsl:variable name="lines">
      <xsl:for-each select="tokenize($data, '\r?\n')">
          <line>
              <xsl:variable name="tokens" as="xs:string*" select="tokenize(., ',')"/>
              <id>$tokens[1]</id>
              <name>$tokens[2]</name>
              <xsl:if test="$tokens[3]">
                  <value>$tokens[3]</value>
              </xsl:if>
          </line>
      </xsl:for-each>
  </xsl:variable>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/" name="xsl:initial-template">
      <xsl:for-each-group select="$lines/line" group-starting-with="line[id = '01']">
          <File>
              <xsl:apply-templates select="."/>
              <xsl:for-each-group select="current-group() except ." group-ending-with="line[id = '50']">
                  <xsl:for-each-group select="current-group()[position() lt last()]" group-starting-with="line[id = '02']">
                      <GroupRecord>
                          <xsl:apply-templates select="."/>
                          <xsl:for-each-group select="current-group() except ." group-ending-with="line[id = '49']">
                              <xsl:for-each-group select="current-group()[position() lt last()]" group-starting-with="line[id = '03']">
                                  <AccountRecord>
                                      <xsl:apply-templates select="."/>
                                      <AccountDetails>
                                          <xsl:apply-templates select="(current-group() except .)[id != '48']"/>
                                      </AccountDetails>
                                      <xsl:apply-templates select="current-group()[id = '48']"/>
                                  </AccountRecord>
                              </xsl:for-each-group>
                              <xsl:apply-templates select="current-group()[last()]"/>
                          </xsl:for-each-group>
                      </GroupRecord>
                  </xsl:for-each-group>
                  <xsl:apply-templates select="current-group()[last()]"/>
              </xsl:for-each-group>
          </File>
      </xsl:for-each-group>
  </xsl:template>

  <xsl:template match="line">
      <xsl:element name="$header-names[index-of($header-ids, current()/id)]">
          <xsl:apply-templates/>
      </xsl:element>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/gWEaSv8。为了示例的完整性和紧凑性,示例数据已被内联,但您当然可以使用 &lt;xsl:param name="data" as="xs:string" select="unparsed-text('file.txt')"/&gt; 代替。我还使用了 xsl:mode 声明和 name="xsl:initial-template",这两个 XSLT 3 特性都需要适应 XSLT 2 处理器来拼出身份转换并使用不同的模板名称,例如name="main" 作为代码的入口点。我还在那里使用了像 &lt;id&gt;$tokens[1]&lt;/id&gt; 这样的文本值模板,对于 XSLT 2 处理器,您需要使用例如&lt;id&gt;&lt;xsl:value-of select="$tokens[1]"/&lt;/id&gt;.

【讨论】:

感谢 Martin,我必须删除 XSLT 2 不支持的所有功能 对于 name="xsl:initial-template" 我不必复制它,因为我们有一个标准后端将 CSV 转换为 XML 的解决方案,这是一个基本的解决方案。因此,此 XSL 的实际输入是一个简单的 XML,其中每个元素都包含来自 CSV 的每一行(例如:01,TeacherHeader1 对于 xsl:模式,我还没有想过如何将它转换为 XSLT 2 但是这个解决方案几乎解决了我关于分组技术的问题(带有标题/拖车) 真的很感谢你的努力

以上是关于如何使用 XSLT 2.0 将 csv 文件转换为结构化 XML 文件?的主要内容,如果未能解决你的问题,请参考以下文章

使用 XSLT 将 CSV 文件转换为 XML

如何编写 XSLT 将 XML 转换为 CSV?

使用 Python 或 XSLT 将复杂的 XML 转换为 CSV

使用XSLT将XML转换为csv

使用 XSLT 将 XML 转换为 CSV

XSLT 使用 xslt 2.0 或更高版本将纯文本文件处理为 XML