как преобразовать CSV-файл в структурированный XML-файл с помощью XSLT 2.0?

Я хотел преобразовать ниже 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, как показано выше. Любая помощь будет принята с благодарностью. Спасибо.


person vii    schedule 30.01.2020    source источник
comment
Используя unparsed-text(-lines) плюс tokenize, вы сможете преобразовать текст в XML. Затем вы можете использовать обычный xsl:for-each-group. Я не уверен, какой именно подход вам нужен, ваш желаемый результат из-за его форматирования, похоже, указывает на вложенность soem, которую XML не показывает, вы можете улучшить форматирование и удалить любые отступы, где XML не должен вкладываться.   -  person Martin Honnen    schedule 30.01.2020
comment
Спасибо за ответ. Я добавил некоторую легенду под своим сообщением, чтобы указать, какие теги предназначены для каждого идентификатора записи (это первые два символа в строке в CSV), а также указать, где он должен быть размещен или сгруппирован. Вы можете правильно отформатировать его, скопировав XML и используя внешний редактор.   -  person vii    schedule 31.01.2020
comment
Надеюсь, кто-нибудь может помочь мне как можно скорее :(   -  person vii    schedule 31.01.2020
comment
В CSV есть Score99 и Score50, почему вывод в виде XML имеет только Score99?   -  person Martin Honnen    schedule 31.01.2020
comment
My Bad - это опечатка из-за копипастинга.   -  person vii    schedule 31.01.2020


Ответы (1)


Как я сказал в комментарии, вы можете обработать текстовый файл с помощью unparsed-text и tokenize, чтобы преобразовать его в XML (или использовать unparsed-text-lines и tokenize в XSLT 3, если они доступны), тогда остальные задачи можно выполнить с вложенными xsl:for-each-group, возможно, даже с одной или двумя рекурсивными функциями после того, как был установлен регулярный шаблон; следующее пытается объяснить вложенные 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. Пример данных был встроен для полноты и компактности примера, но вы, конечно, можете использовать вместо него <xsl:param name="data" as="xs:string" select="unparsed-text('file.txt')"/>. Я также использовал объявление xsl:mode и name="xsl:initial-template", обе функции XSLT 3, которые вам нужно будет адаптировать для процессора XSLT 2, чтобы указать преобразование идентичности и использовать другое имя шаблона, например, например. name="main" в качестве точки входа для кода. Я также использовал шаблон текстовых значений, такой как <id>{$tokens[1]}</id>, для процессора XSLT 2 вам нужно будет использовать, например. <id><xsl:value-of select="$tokens[1]"/</id>.

person Martin Honnen    schedule 31.01.2020
comment
Спасибо, Мартин, мне пришлось удалить все функции, которые не поддерживаются для XSLT 2. Для name=xsl:initial-template мне не нужно копировать это, поскольку у нас есть стандартное внутреннее решение, которое преобразует CSV в XML, что является базовым один. Таким образом, фактический ввод для этого XSL представляет собой простой XML, каждый элемент которого содержит каждую строку из CSV (например: ‹data›‹record›‹field1›01,TeacherHeader1‹/field1›‹/record›‹/data› Для xsl: режим, я еще не думал о том, как преобразовать это для XSLT 2. Но это решение в значительной степени решает мою проблему, связанную с техникой группировки (с заголовком / трейлером). Искренне ценю ваши усилия. - person vii; 03.02.2020