-1

I have a tree-structured flat file for which I need to write into a dynamic XSLT. The flat file data keep changing. For example, I have tag A, B, C, D as given below. Then the next file can have E, F, G, H.The tree itself will have 4-6 levels of depth.

I can read the file into the plain list looping each row but unable to create the same tree structure in XSLT. I want to read the same in java object and then create the same the in XSLT.

To read file:

while ((item = in.readLine()) != null) {
lineNo++;
String rowContent = item;
}

Input file:


    Element A
       Element B
          Element C
             Data
          Element D
             Data

Expected Output XSLT:

<xsl:template match="/">
<A>
<B>
<C><xsl:text>data</xsl:text></C>
<D><xsl:text>data</xsl:text></D>
</B>
</A>
</xsl:template>

I'm new to XSLT, any help would be appreciated. Thanks!!!

Syeda Samreen
  • 99
  • 1
  • 13
  • 3
    How do you distinguish levels in that input format, by the amount of indentation? Does the format literally say "Element A"? And I don't understand why you want to convert the non-XML format to XSLT, after all an XSLT program is supposed to parse and process an XML input file. What do you need that XSLT for? – Martin Honnen May 12 '19 at 09:12

1 Answers1

0

Here is an example using XSLT 3 to parse and group and nest your text data into hierarchical XML:

<?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"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="input-string" as="xs:string">    Element A
       Element B
          Element C
             Data
          Element D
             Data</xsl:param>

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

  <xsl:function name="mf:nest" as="node()*">
      <xsl:param name="input" as="map(xs:string, item())*"/>
      <xsl:variable name="min-level" as="xs:integer" select="min($input?level)"/>
      <xsl:for-each-group select="$input" group-starting-with=".[?level = $min-level and ?type = 'element']">
          <xsl:choose>
              <xsl:when test="?type = 'element'">
                  <xsl:element name="{?name}">
                      <xsl:sequence select="mf:nest(tail(current-group()))"/>
                  </xsl:element>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:value-of select="?value"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

  <xsl:template name="xsl:initial-template" match="/">
    <xsl:variable name="lines" as="map(xs:string, item())*"
      select="tokenize($input-string, '\n') ! 
              analyze-string(., '^( *)(((Element) (\w+))|(.+))') ! *:match ! 
              map { 
                'type' : if (.//*:group[@nr = 4] = 'Element') then 'element' else 'data',
                'name' : string(.//*:group[@nr = 5]),
                'level' : string-length(*:group[@nr = 1]),
                'value' : string(.//*:group[@nr = 6]) 
              }"/>
    <xsl:sequence select="mf:nest($lines)"/>
  </xsl:template>

</xsl:stylesheet>

Output is plain XML

<A>
   <B>
      <C>Data</C>
      <D>Data</D>
   </B>
</A>

https://xsltfiddle.liberty-development.net/6r5Gh3y/2

The solution could of course be adapted to output XSLT if that is really the aim by making use of xsl:namespace-alias.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110