add top level tag once while merging xml files in xslt

68 Views Asked by At

I'm trying to merge multiple xml files in specific folder using xslt and saxon-HE 9.9.1 .NET. I need to create a generic merger so that I will not add a static tag inside the template to use it with different nodes' names, I tried to make a loop to add the root or the top level tag once in the beginning but it also close the tag before the xml end but there is an issue with the top level tag Example: XML File1:

<Arr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Src>
    <name>C</name>
    <pr>pr</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
 </Src>
 <Src>
    <name>C</name>
    <pr></pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh2</name>
        <type>xx2</typ>
      </Q>
    </par>
    <st>
      <Src>
        <name>st1</name>
        <pr>prst1</pr>
        <par>
          <Q>
            <isC>false</isC>
            <name>q1</name>
            <type>t1</type>
          </Q>
          <Q>
            <isC>false</isC>
            <name>q2</name>
            <type>t2</type>
          </Q>
        </par>
        <st />
      </Src>
    </st>
  </Src>
 </Arr>

XML File2:

<Arr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Src>
    <name>CFile2</name>
    <pr>C2</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
 </Src>
 </Arr>

expected output:

<Arr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Src>
    <name>C</name>
    <pr>pr</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
 </Src>
 <Src>
    <name>C</name>
    <pr></pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh2</name>
        <type>xx2</typ>
      </Q>
    </par>
    <st>
      <Src>
        <name>st1</name>
        <pr>prst1</pr>
        <par>
          <Q>
            <isC>false</isC>
            <name>q1</name>
            <type>t1</type>
          </Q>
          <Q>
            <isC>false</isC>
            <name>q2</name>
            <type>t2</type>
          </Q>
        </par>
        <st />
      </Src>
    </st>
  </Src>
  <Src>
    <name>CFile2</name>
    <pr>C2</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
  </Src>
 </Arr>

my current template which is produces wrong output because of the top level tag closure

<?xml version="1.0" encoding="windows-1256"?>   
<xsl:stylesheet version="3.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:user="http://tempuri.org/msxsl" >     
 <xsl:output method="xml" indent="yes" encoding="windows-1256" />

<xsl:template name="main">
 
 <xsl:for-each select="collection('.?select=*.xml')/*">
  <xsl:choose>
        <xsl:when test="position() = 1">
          <xsl:copy>
        <xsl:copy-of select="/*/node()"/> 
        </xsl:copy>
   
        </xsl:when>
        <xsl:otherwise>
         <xsl:copy-of select="/*/node()"/> 
        </xsl:otherwise>
      </xsl:choose>
      
</xsl:for-each> 

</xsl:template>

</xsl:stylesheet>

I run from the cmd with the following command:

transform -it:main -xsl:merge_xml.xslt -o:output.xml

my current output which is wrong

<Arr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Src>
    <name>C</name>
    <pr>pr</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
 </Src>
 <Src>
    <name>C</name>
    <pr></pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh2</name>
        <type>xx2</typ>
      </Q>
    </par>
    <st>
      <Src>
        <name>st1</name>
        <pr>prst1</pr>
        <par>
          <Q>
            <isC>false</isC>
            <name>q1</name>
            <type>t1</type>
          </Q>
          <Q>
            <isC>false</isC>
            <name>q2</name>
            <type>t2</type>
          </Q>
        </par>
        <st />
      </Src>
    </st>
  </Src>
</Arr>
  <Src>
    <name>CFile2</name>
    <pr>C2</pr>
    <par>
      <Q>
        <isC>false</isC>
        <name>hrh1</name>
        <type>xx1</typ>
      </Q>
    </par>
    <st />
  </Src>
2

There are 2 best solutions below

3
Martin Honnen On

I would simply use

 <xsl:param name="uris" select="uri-collection('.?select=*.xml')"/>

 <xsl:template name="xsl:initial-template">
    <xsl:copy select="$uris => head() => doc()/*">
      <xsl:copy-of select="($uris ! doc(.))/*/node()"/>
    </xsl:copy>
  </xsl:template>

To be able to switch to streaming if Saxon EE is an option one could try

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all" expand-text="yes">

    <xsl:function name="mf:merge" as="document-node()">
        <xsl:param name="uris"/>
        <xsl:source-document href="{head($uris)}" streamable="yes">
            <xsl:document>
                <xsl:copy select="*">
                    <xsl:copy-of select="node()"/>
                    <xsl:iterate select="tail($uris)">
                        <xsl:source-document href="{.}" streamable="yes">
                            <xsl:copy-of select="*/node()"/>
                        </xsl:source-document>
                    </xsl:iterate>
                </xsl:copy>
            </xsl:document>
        </xsl:source-document>
    </xsl:function>

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

    <xsl:template name="xsl:initial-template">
        <xsl:sequence select="mf:merge(uri-collection('?select=*.xml'))"/>
    </xsl:template>

</xsl:stylesheet>
0
Michael Kay On

Martin's solution will work, but I would do:

  <xsl:template name="xsl:initial-template">
    <xsl:variable name="coll" select="collection('.?select=*.xml')"/>
    <xsl:copy select="$coll[1]/*">
      <xsl:copy-of select="$coll/*/*"/>
    </xsl:copy>
  </xsl:template>