xsltfilesplitting

Split XML file by number of records/treshold and copy header - XSLT 1.0


I have the following XML structure

<?xml version="1.0" encoding="UTF-8"?>
<ExportData>
<TransportHeader>
    <Timestamp>2011-01-16 06:00:33</Timestamp>
    <From>
        <Name>DynamicExport</Name>
        <Version>1.</Version>
    </From>
    <MessageId>d7b5c5b69a83</MessageId>
</TransportHeader>
<ExportConfig>
    <DateTimeFormat>yyyy-MM-dd HH:mm:ss</DateTimeFormat>
    <DecimalSymbol>.</DecimalSymbol>
</ExportConfig>
<DataSet> 
    <Tables>
        <Table>
            <RH>...</RH>
            <Rows>  
                <R>Data1</R>
                <R>Data2</R>
                <R>Data3</R>
                <R>Data4</R>
                <R>Data5</R>
            </Rows>
        </Table>
    </Tables>
</DataSet>
</ExportData>

and I need to split the file depending on the amounts of <R>elements. If there are more than 3 <R> elements a second output file needs to be generated. Both files also need the header info.

I came up with this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:redirect="http://xml.apache.org/xalan/redirect"
extension-element-prefixes="redirect"
exclude-result-prefixes="xd"
version="1.0">

<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="text() | @*"/>

<xsl:template match="Rows" name="Rows"> 
    <Rows>    
        <xsl:for-each select="R">
            <xsl:variable name="filename1" select="concat('output1','.xml')"/>
            <xsl:variable name="filename2" select="concat('output2','.xml')"/>
            <xsl:variable name="nodePosition" select="position()" />
            <xsl:if test="$nodePosition &lt; 3">
                <redirect:write select="$filename1">
                    <xsl:copy-of select="." />
                </redirect:write>
            </xsl:if>
            <xsl:if test="$nodePosition = 3 or $nodePosition &gt; 3">
                <redirect:write select="$filename2">
                        <xsl:copy-of select="." />
                </redirect:write> 
            </xsl:if>
        </xsl:for-each>  
    </Rows>        
</xsl:template>
</xsl:stylesheet>

But the two output files that get generated only contain "Data2" and "Data5". Could you help me figuring out why the other 3 data elements are missing? And how can I add the header data?

For the header I came up with this XSLT:

<xsl:template match="//Rows">
    <xsl:apply-templates select="@*|Rows"/>
</xsl:template>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

which works when I apply it to the XML mentioned. But I could not combine the 2 XSLTs - the output just gets messed up.


Solution

  • This transformation shows how to do the splitting. It is left as an exercise to adapt it to your needs:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
     </xsl:template>
    
     <xsl:template match="R[position() mod 3 =1]">
      <RGroup>
       <xsl:copy-of select=
        ".|following-sibling::R[not(position() > 2)]"/>
      </RGroup>
     </xsl:template>
    
     <xsl:template match="R"/>
    </xsl:stylesheet>
    

    When applied on this XML document (a fragment of the originally provided one):

    <Rows>
        <R>Data1</R>
        <R>Data2</R>
        <R>Data3</R>
        <R>Data4</R>
        <R>Data5</R>
    </Rows>
    

    the wanted splitting is produced:

    <Rows>
       <RGroup>
          <R>Data1</R>
          <R>Data2</R>
          <R>Data3</R>
       </RGroup>
       <RGroup>
          <R>Data4</R>
          <R>Data5</R>
       </RGroup>
    </Rows>