0

I have several XML files with a similar structure. Need to join these files into one using XSLT with grouping as shown below.

01.xml (Report number 1, two groups - "Local" and "Web")

<Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>22</A-1>
                 <A-2>33</A-2>
                 <A-3>44</A-3>
              </Data>
            </ReportN1>
         </Group>
         <Group name="Web">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>55</A-1>
                 <A-2>66</A-2>
                 <A-3>77</A-3>
              </Data>
            </ReportN1>
         </Group>
      </Month>
   </Year>
</Reports>

02.xml (Report number 2, two groups - "Local" and "Web"))

<Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN2>
              <Data Kods="10022">
                 <B-1>33</B-1>
                 <B-2>44</B-2>
                 <B-3>55</B-3>
              </Data>
            </ReportN2>
         </Group>
         <Group name="Web">
            <ReportN2>
              <Data Kods="10022">
                 <B-1>66</B-1>
                 <B-2>77</B-2>
                 <B-3>88</B-3>
              </Data>
            </ReportN2>
         </Group>
      </Month>
   </Year>
</Reports>

03.xml (Report number 3, two groups - "Local" and "Web")

<Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN3>
              <Data Kods="10033">
                 <C-1>44</C-1>
                 <C-2>55</C-2>
                 <C-3>66</C-3>
              </Data>
            </ReportN3>
         </Group>
         <Group name="Web">
            <ReportN3>
              <Data Kods="10033">
                 <C-1>77</C-1>
                 <C-2>88</C-2>
                 <C-3>99</C-3>
              </Data>
            </ReportN3>
         </Group>
      </Month>
   </Year>
</Reports>

Expected result with grouping :

<Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>22</A-1>
                 <A-2>33</A-2>
                 <A-3>44</A-3>
              </Data>
            </ReportN1>
            <ReportN2>
              <Data Kods="10022">
                 <B-1>33</B-1>
                 <B-2>44</B-2>
                 <B-3>55</B-3>
              </Data>
            </ReportN2>
            <ReportN3>
              <Data Kods="10033">
                 <C-1>44</C-1>
                 <C-2>55</C-2>
                 <C-3>66</C-3>
              </Data>
            </ReportN3>
         </Group>
         <Group name="Web">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>55</A-1>
                 <A-2>66</A-2>
                 <A-3>77</A-3>
              </Data>
            </ReportN1>
            <ReportN2>
              <Data Kods="10022">
                 <B-1>66</B-1>
                 <B-2>77</B-2>
                 <B-3>88</B-3>
              </Data>
            </ReportN2>
            <ReportN3>
              <Data Kods="10033">
                 <C-1>77</C-1>
                 <C-2>88</C-2>
                 <C-3>99</C-3>
              </Data>
            </ReportN3>
         </Group>
      </Month>
   </Year>
</Reports>
mancubus
  • 1
  • 2

2 Answers2

0

Maybe you can specify what is the exact problem? Is it about reading the documents? Use document() for this.

I would: - store all the descendants of the elements in $group_{name} - group the items inside the variables - create a structure for the new document - use <xsl:copy-of> or <xsl:sequence> to output the values of the variables where they belongs to

For instance,

Store the element ReportN1 in the variable:

<xsl:variable name="xml_01_local-r1" select="document('01.xml')//group[@name='local']/ReportN1"/>

Later output it like this

<Group name="Local">
    <xsl:copy-of select="$xml_01_local-r1"/>
</Group>

There should be certainly more efficient ways to do it, but this one will work.

o-sapov
  • 320
  • 2
  • 13
0

If you know you have exactly three documents you can load them as three separate parameters using the doc function (e.g. <xsl:param name="doc1" select="doc('01.xml')"/>) and then it is simply a question of nested grouping, here is an XSLT 3 (is supported by Saxon 9.8 and later) example as I have made used for one grouping of a composite grouping key:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="doc1"><Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>22</A-1>
                 <A-2>33</A-2>
                 <A-3>44</A-3>
              </Data>
            </ReportN1>
         </Group>
         <Group name="Web">
            <ReportN1>
              <Data Kods="10011">
                 <A-1>55</A-1>
                 <A-2>66</A-2>
                 <A-3>77</A-3>
              </Data>
            </ReportN1>
         </Group>
      </Month>
   </Year>
</Reports>
</xsl:param>

  <xsl:param name="doc2"><Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN2>
              <Data Kods="10022">
                 <B-1>33</B-1>
                 <B-2>44</B-2>
                 <B-3>55</B-3>
              </Data>
            </ReportN2>
         </Group>
         <Group name="Web">
            <ReportN2>
              <Data Kods="10022">
                 <B-1>66</B-1>
                 <B-2>77</B-2>
                 <B-3>88</B-3>
              </Data>
            </ReportN2>
         </Group>
      </Month>
   </Year>
</Reports></xsl:param>

  <xsl:param name="doc3">
<Reports>
   <Year Year="2019">
      <Month mon1="1" mon2="12">
         <Group name="Local">
            <ReportN3>
              <Data Kods="10033">
                 <C-1>44</C-1>
                 <C-2>55</C-2>
                 <C-3>66</C-3>
              </Data>
            </ReportN3>
         </Group>
         <Group name="Web">
            <ReportN3>
              <Data Kods="10033">
                 <C-1>77</C-1>
                 <C-2>88</C-2>
                 <C-3>99</C-3>
              </Data>
            </ReportN3>
         </Group>
      </Month>
   </Year>
</Reports>      
  </xsl:param>


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

  <xsl:template match="/" name="xsl:initial-template">
    <Report>
        <xsl:for-each-group select="$doc1/*/Year, $doc2/*/Year, $doc3/*/Year" group-by="@Year">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:for-each-group select="current-group()/Month" composite="yes" group-by="@mon1, @mon2">
                    <xsl:copy>
                        <xsl:copy-of select="@*"/>
                        <xsl:for-each-group select="current-group()/Group" group-by="@name">
                            <xsl:copy>
                                <xsl:copy-of select="@*, current-group()!*"/>
                            </xsl:copy>
                        </xsl:for-each-group>
                    </xsl:copy>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:for-each-group>
    </Report>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/pPJ8LUU

There I have inlined the sample XML data but of course you can as well load them with the doc function or in general use the collection function with Saxon 9 e.g. <xsl:param name="docs" select="collection('?select=*.xml')"/> to load several documents. You would then replace the <xsl:for-each-group select="$doc1/*/Year, $doc2/*/Year, $doc3/*/Year" group-by="@Year"> with <xsl:for-each-group select="$docs!*!Year" group-by="@Year">.

For XSLT 2 you would use no composite attribute on the second grouping but then group-by="concat(@mon1, '|', @mon2)" and the ! is also not available so if the order matters then use select="for $doc in $docs return $doc/*/Year" instead of select="$docs!*!Year".

I assume the stylesheet is used with a dummy input document or with -it as the command line option for Saxon 9.8 and later, that would be another change you need if you use an older version, I think XSLT 2 doesn't allow <xsl:template match="/" name="xsl:initial-template"> so you would need to use a different name like <xsl:template match="/" name="main"> and start Saxon with -it:main.

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