У меня есть плоский файл, который я хотел бы преобразовать в XML с помощью XSLT. Первый символ каждой строки представляет собой блок информации, и я хотел бы сгруппировать все вместе. Строки могут начинаться с нескольких символов. Что я хочу сделать, так это сгруппировать блоки строк, которые находятся между символом 1.

Вот как выглядит входной файл:

0xxxxxxxxxxxxxxxxxxxxxxxxx
1xxxxxxxxxxxxxxxxxxxxxxxxx
2xxxxxxxxxxxxxxxxxxxxxxxxx
3xxxxxxxxxxxxxxxxxxxxxxxxx
5xxxxxxxxxxxxxxxxxxxxxxxxx
8xxxxxxxxxxxxxxxxxxxxxxxxx
1xxxxxxxxxxxxxxxxxxxxxxxxx
2xxxxxxxxxxxxxxxxxxxxxxxxx
5xxxxxxxxxxxxxxxxxxxxxxxxx
8xxxxxxxxxxxxxxxxxxxxxxxxx
1xxxxxxxxxxxxxxxxxxxxxxxxx
8xxxxxxxxxxxxxxxxxxxxxxxxx
9xxxxxxxxxxxxxxxxxxxxxxxxx

x просто представляет данные в строке, о которых я могу позаботиться. Что я хочу сделать, так это продукт:

<Root>
    <Header> // O line
    </Header>
    <Summary id="xxxxx"> // First 1 line
        <data_from_2>
        </data_from_2>
        <data_from_3>
        </data_from_3>
        <data_from_5>
        </data_from_5>
        <data_from_8>
        </data_from_8>
    </Summary>
    <Summary id="xxxxx"> // Second 1 line
        <data_from_2>
        </data_from_2>
        <data_from_3>
        </data_from_3>
        <data_from_5>
        </data_from_5>
        <data_from_8>
        </data_from_8>
    </Summary>
    <Summary id="xxxxx"> // Third 1 line
        <data_from_2>
        </data_from_2>
        <data_from_3>
        </data_from_3>
        <data_from_5>
        </data_from_5>
        <data_from_8>
        </data_from_8>
    </Summary>
    <Footer> // 9 line
    </Footer>
</Root>

Сложность в том, что неизвестно, сколько строк будет под линией 1. Может быть только одна строка для группировки под ней или много других строк.

Вот мой начальный XSLT (в настоящее время он создает плоскую структуру):

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:variable name="newline" select="'&#x0A;'" />
    <xsl:variable name="tab" select="'&#x09;'" />

        <xsl:template match="/">
            <xsl:value-of select="$newline"/>
            <FirstData>
            <xsl:value-of select="$newline"/>

                <xsl:for-each select="tokenize(.,'\r?\n')">
                    <!-- DETERMINE WHAT FIRST CHAR LOOKS LIKE -->
                    <xsl:variable name="lineToken" select="substring(., 1, 1)"/>

                    <!-- HEADER -->
                    <xsl:if test="$lineToken='0'">
                        <xsl:variable name="periodStart" select="substring(., 2, 6)"/>
                        <xsl:value-of select="$tab"/><HEADER><xsl:value-of select="$newline"/>
                            <xsl:value-of select="$tab"/><xsl:value-of select="$tab"/><Period_start_date><xsl:sequence select="$periodStart"/></Period_start_date><xsl:value-of select="$newline"/>
                        <xsl:value-of select="$tab"/></HEADER><xsl:value-of select="$newline"/>
                    </xsl:if>


                    <!-- SUMMARY -->
                    <xsl:if test="$lineToken='1'">
                        <xsl:value-of select="$tab"/><xsl:element name="SUMMARY">
                        <xsl:attribute name="ID"><xsl:value-of select ="substring(., 2, 11)"/></xsl:attribute>
                        <xsl:value-of select="$newline"/>
                            <xsl:variable name="ID" select="substring(., 2, 11)"/>
                            <xsl:variable name="batchDate" select="substring(., 13, 4)"/>
                            <xsl:value-of select="$tab"/><xsl:value-of select="$tab"/><ID><xsl:sequence select="$fdmsMerchantNum"/></FDMS_Merchant_Number><xsl:value-of select="$newline"/>
                            <xsl:value-of select="$tab"/><xsl:value-of select="$tab"/><Batch_Date><xsl:sequence select="$batchDate"/></Batch_Date><xsl:value-of select="$newline"/>
                        <xsl:value-of select="$tab"/></xsl:element><xsl:value-of select="$newline"/>
                    </xsl:if>

                    <!-- Data 2 -->
                    <xsl:if test="$lineToken='2'">
                        <xsl:value-of select="$tab"/><Data_2><xsl:value-of select="$newline"/>
                            <xsl:variable name="Sales" select="substring(., 2, 3)"/>
                            <xsl:value-of select="$tab"/><xsl:value-of select="$tab"/><Sales><xsl:sequence select="$Sales"/></Sales><xsl:value-of select="$newline"/>
                        <xsl:value-of select="$tab"/></Data_2><xsl:value-of select="$newline"/>
                    </xsl:if>
                    <!-- Data 3 -->
                    <xsl:if test="$lineToken='3'">
                        <xsl:value-of select="$tab"/><Data_3><xsl:value-of select="$newline"/>
                            <xsl:variable name="Sales" select="substring(., 2, 3)"/>
                            <xsl:value-of select="$tab"/><xsl:value-of select="$tab"/><Sales><xsl:sequence select="$Sales"/></Sales><xsl:value-of select="$newline"/>
                        <xsl:value-of select="$tab"/></Data_3><xsl:value-of select="$newline"/>
                    </xsl:if>
                            <!-- Data 5 and Data 8 elements are identical -->
                </xsl:for-each>
            </Root>
        </xsl:template>
    </xsl:stylesheet>

Что я хочу сделать, так это иметь возможность вложить элементы данных 2 и данных 3 в сводный элемент, но как мне обработать эти строки, а затем запустить новый сводный элемент для следующей встреченной 1 строки?

Прошу прощения за все общие вещи, у меня много данных, с которыми нужно работать, и я пытаюсь упростить вопрос. Если потребуется дополнительная информация, дайте мне знать.

0
RXC 27 Мар 2014 в 22:33

1 ответ

Лучший ответ

Это похоже на задание для for-each-group group-starting-with, но в XSLT 2.0 вы можете использовать это только с последовательностями узлов, а не с последовательностями строк. Поэтому я бы сначала обернул строки, которые вы получаете от tokenize(.,'\r?\n'), в элемент, например.

<xsl:variable name="lines" as="element(line)*">
  <xsl:for-each select="tokenize(.,'\r?\n')">
    <line><xsl:value-of select="."/></line>
  </xsl:for-each>
</xsl:variable>

Тогда я бы использовал

<xsl:for-each-group select="$lines" group-starting-with="line[starts-with(., '1')]">
  <xsl:choose>
    <xsl:when test="not(self::line[starts-with(., '1')])">
      <!-- header -->
      <Header><xsl:value-of select="substring(., 2)"/></Header>
    </xsl:when>
    <xsl:otherwise>
      <Summary id="{substring(., 2)}">
         <!-- now use for-each select="if (position() eq last()) then current-group()[position() gt 1 and position() ne last()] else current-group()[position() gt 1]" or apply-templates to output the lines-->
        <xsl:for-each select="if (position() eq last()) then current-group()[position() gt 1 and position() ne last()] else current-group()[position() gt 1]">
          <xsl:element name="data_from_{substring(., 1, 1)}"><xsl:value-of select="substring(., 2)"/></xsl:element>
         </xsl:for-each>
      </Summary>
      <xsl:if test="position() eq last()">
        <Footer>
          <xsl:value-of select="substring(current-group()[last()], 2)"/>
        </Footer>
      </xsl:if>
   </xsl:otherwise>
 </xsl:choose>
</xsl:for-each-group>

В группу.

Я нашел время написать рабочий образец, XSLT - это

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

<xsl:param name="text-url" select="'test2014032901.txt'"/>

<xsl:output indent="yes"/>

<xsl:template name="main">
  <xsl:variable name="text" select="unparsed-text($text-url)"/>

  <xsl:variable name="lines" as="element(line)*">
    <xsl:for-each select="tokenize($text,'\r?\n')[normalize-space()]">
      <line><xsl:value-of select="."/></line>
    </xsl:for-each>
  </xsl:variable>

  <Root>
    <xsl:for-each-group select="$lines" group-starting-with="line[starts-with(., '1')]">
      <xsl:choose>
        <xsl:when test="not(self::line[starts-with(., '1')])">
          <!-- header -->
          <xsl:variable name="periodStart" select="substring(., 2, 6)"/>
          <Header>
            <Period_start_date>
              <xsl:value-of select="$periodStart"/>
            </Period_start_date>
          </Header>
        </xsl:when>
        <xsl:otherwise>
          <Summary id="{substring(., 2, 11)}">
            <ID><xsl:value-of select="substring(., 2, 11)"/></ID>
            <Batch_Date><xsl:value-of select="substring(., 13, 4)"/></Batch_Date>
            <!-- now use for-each select="if (position() eq last()) then current-group()[position() gt 1 and position() ne last()] else current-group()[position() gt 1]" or apply-templates to output the lines-->
            <xsl:for-each select="if (position() eq last()) then current-group()[position() gt 1 and position() ne last()] else current-group()[position() gt 1]">
              <xsl:element name="data_from_{substring(., 1, 1)}">
                <Sales>
                  <xsl:value-of select="substring(., 2, 3)"/>
                </Sales>
              </xsl:element>
            </xsl:for-each>
          </Summary>
          <xsl:if test="position() eq last()">
            <Footer>
              <xsl:value-of select="substring(current-group()[last()], 2)"/>
            </Footer>
          </xsl:if>
       </xsl:otherwise>
     </xsl:choose>
    </xsl:for-each-group>
  </Root>

</xsl:template>

</xsl:stylesheet>

Имя текстового файла передается в качестве параметра text-url, а таблица стилей должна начинаться с it:main (шаблон с именем main для Saxon)), затем я получаю результат

<Root>
   <Header>
      <Period_start_date>xxxxxx</Period_start_date>
   </Header>
   <Summary id="xxxxxxxxxxx">
      <ID>xxxxxxxxxxx</ID>
      <Batch_Date>xxxx</Batch_Date>
      <data_from_2>
         <Sales>xxx</Sales>
      </data_from_2>
      <data_from_3>
         <Sales>xxx</Sales>
      </data_from_3>
      <data_from_5>
         <Sales>xxx</Sales>
      </data_from_5>
      <data_from_8>
         <Sales>xxx</Sales>
      </data_from_8>
   </Summary>
   <Summary id="xxxxxxxxxxx">
      <ID>xxxxxxxxxxx</ID>
      <Batch_Date>xxxx</Batch_Date>
      <data_from_2>
         <Sales>xxx</Sales>
      </data_from_2>
      <data_from_5>
         <Sales>xxx</Sales>
      </data_from_5>
      <data_from_8>
         <Sales>xxx</Sales>
      </data_from_8>
   </Summary>
   <Summary id="xxxxxxxxxxx">
      <ID>xxxxxxxxxxx</ID>
      <Batch_Date>xxxx</Batch_Date>
      <data_from_8>
         <Sales>xxx</Sales>
      </data_from_8>
   </Summary>
   <Footer>xxxxxxxxxxxxxxxxxxxxxxxxx</Footer>
</Root>
2
Martin Honnen 28 Мар 2014 в 17:39
Я знаю, что написанный вами код не проверен, но я получаю сообщение об ошибке A sequence of more than one item is not allowed as the value of variable $lines (&lt;line/&gt;, &lt;line/&gt;, ...). Я все еще новичок в XSL, поэтому прошу прощения, если не полностью понимаю код.
 – 
RXC
28 Мар 2014 в 16:54
К сожалению, в объявлении типа переменной не было символа *, я это исправил.
 – 
Martin Honnen
28 Мар 2014 в 17:08
@RXC, я отредактировал ответ с полным образцом кода, который вам будет легче адаптировать к вашим потребностям, чем предыдущее предложение.
 – 
Martin Honnen
28 Мар 2014 в 17:40