Search code examples
xsltxslt-2.0xslt-1.0xslcompiledtransform

XSLT to transform an xml with repeated sibblings to a flat file


I have an xml like below:

<?xml version="1.0" encoding="utf-8"?>
  <GetSavedReportResponse>
  <ResponseType>Success</ResponseType>
  <FileModifiedDateTime>2012-01-03T17:05:04</FileModifiedDateTime>
  <FileSizeBytes>7816</FileSizeBytes>
  <FileDataFormat>XML</FileDataFormat>
  <FileData>
    <Zthes>
      <term>
        <termId>49555</termId>
        <termUpdate>add</termUpdate>
        <termName>Active Personnel</termName>
        <termVocabulary>People Status Global</termVocabulary>
        <termVocabulary>Global People Status</termVocabulary>
        <termCategory>PDA</termCategory>
        <termCategory>PDI</termCategory>
        <termCategory>GLB</termCategory>
        <relation weight="100">
          <termId>49556</termId>
          <relationType>EQ</relationType>
          <termName>term name</termName>
          <termVocabulary>term vocabulary</termVocabulary>
        </relation>
        <relation weight="100">
          <termId>49557</termId>
          <relationType>BT</relationType>
          <termName>General Active Personnel</termName>
          <termVocabulary>People Status Global Updated</termVocabulary>
        </relation>
      </term>
      <term>
        <termId>49556</termId>
        <termUpdate>add</termUpdate>
        <termName>Leave of Absence Personnel</termName>
        <termVocabulary>People Status Global</termVocabulary>
        <termCategory>GLB</termCategory>
        <termCategory>PDI</termCategory>
        <relation weight="100">
          <relationType>BT</relationType>
          <termId>49554</termId>
          <termName>General Non-Active Personnel</termName>
          <termVocabulary>People Status Global</termVocabulary>
        </relation>
      </term>
    </Zthes>
  </FileData>
</GetSavedReportResponse>

I need to transform it into a flat file. For that, I have writen the following xsl

  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text" />
  <xsl:template match="Zthes">
    <xsl:text>&#10;</xsl:text>
    <xsl:for-each select="term">
      <xsl:text>"</xsl:text>
      <xsl:text>GL</xsl:text>
      <xsl:text>"</xsl:text>
      <xsl:text>;</xsl:text>
      <xsl:text>"</xsl:text>
      <xsl:for-each select="termCategory">
        <xsl:value-of select="." />
      </xsl:for-each>
      <xsl:text>"</xsl:text>
      <xsl:text>;</xsl:text>
      <xsl:text>"</xsl:text>
      <xsl:for-each select="termVocabulary">
        <xsl:value-of select="." />
      </xsl:for-each>
      <xsl:text>"</xsl:text>
      <xsl:text>;</xsl:text>
      <xsl:text>"</xsl:text>
      <xsl:for-each select="relation/termVocabulary">
          <xsl:value-of select="." />
      </xsl:for-each>
      <xsl:text>"</xsl:text>
      <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

so, the output should be
"HDR";"Text";"20120112045620";"F"
"GL";"PDA";"People Status Global";"term vocabulary"
"GL";"PDA";"People Status Global";"People Status Global Updated"
"GL";"PDA";"Global People Status";"term vocabulary"
"GL";"PDA";"Global People Status";"People Status Global Updated"
"GL";"PDI";"People Status Global";"term vocabulary"
"GL";"PDI";"People Status Global";"People Status Global Updated"
"GL";"PDI";"Global People Status";"term vocabulary"
"GL";"PDI";"Global People Status";"People Status Global Updated"
"GL";"GLB";"People Status Global";"term vocabulary"
"GL";"GLB";"People Status Global";"People Status Global Updated"
"GL";"GLB";"Global People Status";"term vocabulary"
"GL";"GLB";"Global People Status";"People Status Global Updated"
"FTR";12

with my xsl I am gettin single row:
"GL";"PDAPDIGLB";"People Status GlobalGlobal People Status";"term vocabularyPeople Status Global Updated"

And a header row :
"HDR";"PIGLSSTD";"20120112045620";"F":
should be appended at the start, along with a footer row
"FTR";

at the bottom.


Solution

  • You want something like this:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:my="my:my">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <my:defaults>
       <termCat/>
       <termVocab/>
     </my:defaults>
    
     <xsl:variable name="vDefaults" select="document('')/*/my:defaults"/>
    
     <xsl:variable name="vQ">"</xsl:variable>
    
     <xsl:template match="term">
       <xsl:variable name="vTerm" select="."/>
    
       <xsl:variable name="vRow1" select="'&#xA;&quot;GL&quot;;'"/>
    
         <xsl:for-each select=
          "termCategory
          |
           $vDefaults/termCat[not($vTerm/termCategory)]">
           <xsl:variable name="vRow2" select=
               "concat($vRow1, $vQ, ., $vQ, ';')"/>
    
           <xsl:for-each select=
            "$vTerm/termVocabulary
            |
             $vDefaults/termCat[not($vTerm/termVocabulary)]
            ">
             <xsl:variable name="vRow3" select=
               "concat($vRow2, $vQ, ., $vQ, ';')"/>
    
            <xsl:for-each select=
             "$vTerm/relation/termVocabulary
             |
              $vDefaults/termCat[not($vTerm/relation/termVocabulary)]
             ">
            <xsl:value-of select="concat($vRow3, $vQ, ., $vQ, ';')"/>
          </xsl:for-each>
          </xsl:for-each>
         </xsl:for-each>
     </xsl:template>
    
     <xsl:template match="text()"/>
    </xsl:stylesheet>
    

    when this transformation is applied on the provided XML document:

    <GetSavedReportResponse>
        <ResponseType>Success</ResponseType>
        <FileModifiedDateTime>2012-01-03T17:05:04</FileModifiedDateTime>
        <FileSizeBytes>7816</FileSizeBytes>
        <FileDataFormat>XML</FileDataFormat>
        <FileData>
            <Zthes>
                <term>
                    <termId>49555</termId>
                    <termUpdate>add</termUpdate>
                    <termName>Active Personnel</termName>
                    <termVocabulary>People Status Global</termVocabulary>
                    <termVocabulary>Global People Status</termVocabulary>
                    <termCategory>PDA</termCategory>
                    <termCategory>PDI</termCategory>
                    <termCategory>GLB</termCategory>
                    <relation weight="100">
                        <termId>49556</termId>
                        <relationType>EQ</relationType>
                        <termName>term name</termName>
                        <termVocabulary>term vocabulary</termVocabulary>
                    </relation>
                    <relation weight="100">
                        <termId>49557</termId>
                        <relationType>BT</relationType>
                        <termName>General Active Personnel</termName>
                        <termVocabulary>People Status Global Updated</termVocabulary>
                    </relation>
                </term>
                <term>
                    <termId>49556</termId>
                    <termUpdate>add</termUpdate>
                    <termName>Leave of Absence Personnel</termName>
                    <termVocabulary>People Status Global</termVocabulary>
                    <termCategory>GLB</termCategory>
                    <termCategory>PDI</termCategory>
                    <relation weight="100">
                        <relationType>BT</relationType>
                        <termId>49554</termId>
                        <termName>General Non-Active Personnel</termName>
                        <termVocabulary>People Status Global</termVocabulary>
                    </relation>
                </term>
            </Zthes>
        </FileData>
    </GetSavedReportResponse>
    

    the wanted, correct result is produced:

    "GL";"PDA";"People Status Global";"term vocabulary";
    "GL";"PDA";"People Status Global";"People Status Global Updated";
    "GL";"PDA";"Global People Status";"term vocabulary";
    "GL";"PDA";"Global People Status";"People Status Global Updated";
    "GL";"PDI";"People Status Global";"term vocabulary";
    "GL";"PDI";"People Status Global";"People Status Global Updated";
    "GL";"PDI";"Global People Status";"term vocabulary";
    "GL";"PDI";"Global People Status";"People Status Global Updated";
    "GL";"GLB";"People Status Global";"term vocabulary";
    "GL";"GLB";"People Status Global";"People Status Global Updated";
    "GL";"GLB";"Global People Status";"term vocabulary";
    "GL";"GLB";"Global People Status";"People Status Global Updated";
    "GL";"GLB";"People Status Global";"People Status Global";
    "GL";"PDI";"People Status Global";"People Status Global";
    

    Explanation: You want to do output only when a complete line has been formed -- not before that.

    Update: The OP works in an environment, where the document() function is disabled. He also wants a header and a footer.

    In this case a slightly modified transformation can be used (using the exslt:node-set() extension function) now:

    <xsl:stylesheet version="1.0"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
         xmlns:ext="http://exslt.org/common">
         <xsl:output omit-xml-declaration="yes" indent="yes"/>
         <xsl:strip-space elements="*"/>
    
         <xsl:variable name="vrtfDefaults">
           <termCat/>
           <termVocab/>
         </xsl:variable>
    
         <xsl:variable name="vDefaults" select=
          "ext:node-set($vrtfDefaults)"/>
    
         <xsl:variable name="vQ">"</xsl:variable>
    
         <xsl:template match="Zthes">
          <xsl:text>HDR";"PIGLSSTD";"20120112045620";"F":</xsl:text>
    
            <xsl:apply-templates/>
    
          <xsl:text>&#xA;FTR</xsl:text>
         </xsl:template>
    
         <xsl:template match="term">
           <xsl:variable name="vTerm" select="."/>
    
           <xsl:variable name="vRow1" select="'&#xA;&quot;GL&quot;;'"/>
    
             <xsl:for-each select=
              "termCategory
              |
               $vDefaults/termCat[not($vTerm/termCategory)]">
               <xsl:variable name="vRow2" select=
                   "concat($vRow1, $vQ, ., $vQ, ';')"/>
    
               <xsl:for-each select=
                "$vTerm/termVocabulary
                |
                 $vDefaults/termCat[not($vTerm/termVocabulary)]
                ">
                 <xsl:variable name="vRow3" select=
                   "concat($vRow2, $vQ, ., $vQ, ';')"/>
    
                <xsl:for-each select=
                 "$vTerm/relation/termVocabulary
                 |
                  $vDefaults/termCat[not($vTerm/relation/termVocabulary)]
                 ">
                <xsl:value-of select="concat($vRow3, $vQ, ., $vQ, ';')"/>
              </xsl:for-each>
              </xsl:for-each>
             </xsl:for-each>
         </xsl:template>
    
         <xsl:template match="text()"/>
    </xsl:stylesheet>