0
votes

I've been given the follow XML (produced by someone from an Excel spreadsheet)

<Records>
<record>
    <Domain>Domain 1</Domain>
    <Section>Section A</Section>
    <label>Option X 1</label>
    <Keyword>Unique KW 1</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
<record>
    <Domain>Domain 1</Domain>
    <Section>Section B</Section>
    <label>Option X 2</label>
    <Keyword>Unique KW 2</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
<record>
    <Domain>Domain 2</Domain>
    <Section>Section A</Section>
    <label>Option X 3</label>
    <Keyword>Unique KW 3</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
<record>
    <Domain>Domain 1</Domain>
    <Section>Section A</Section>
    <label>Option X 4</label>
    <Keyword>Unique KW 4</Keyword>
    <Control>Checkbox</Control>
    <Default>FALSE</Default>
</record>
<record>
    <Domain>Domain 1</Domain>
    <Section>Section B</Section>
    <label>Option X 5</label>
    <Keyword>Unique KW 5</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
<record>
    <Domain>Domain 2</Domain>
    <Section>Section B</Section>
    <label>Option X 6</label>
    <Keyword>Unique KW 6</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
<record>
    <Domain>Domain 1</Domain>
    <Section>Section A</Section>
    <label>Option X 7</label>
    <Keyword>Unique KW 7</Keyword>
    <Control>Checkbox</Control>
    <Default>TRUE</Default>
</record>
</Records>

And I've been asked to transform it into the following using XSLT 1.0

<Configuration>
<Domain name="Domain 1">
    <Section name="Section A">
        <Option keyword="Unique KW 1" name="Option X 1">
            <Control type="Checkbox" default="True" />
        </Option>
        <Option keyword="Unique KW 3" name="Option X 3">
            <Control type="Checkbox" default="True" />
        </Option>
        <Option keyword="Unique KW 7" name="Option X 7">
            <Control type="Checkbox" default="True" />
        </Option>
    </Section>
    <Section name="Section B">
        <Option keyword="Unique KW 2" name="Option X 2">
            <Control type="Checkbox" default="True" />
        </Option>
        <Option keyword="Unique KW 5" name="Option X 5">
            <Control type="Checkbox" default="True" />
        </Option>
    </Section>
</Domain>
<Domain name="Domain 2">
    <Section name="Section A">
        <Option keyword="Unique KW 3" name="Option X 3">
            <Control type="Checkbox" default="True" />
        </Option>       
    </Section>
    <Section name="Section B">
        <Option keyword="Unique KW 6" name="Option X 6">
            <Control type="Checkbox" default="True" />
        </Option>       
    </Section>
</Domain>
</Configuration>

I've used XSLT in a limited capacity in the past but I'm struggling getting a single element out (eg Domain and Section), the output I get is one per element in the source where I need 1 per value in the source (if that makes sense). I just seem to get lots of

Domain instead of just two (Domain 1 and Domain 2), same with the Section

Is there any way of doing this with XSLT 1.0 or am I just wasting my time?

(Also, the data I've given is an example, edited to remove sensitive data and size, there are far more Domains and Section etc)

(The little I've got so far)

<xsl:stylesheet version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:ms="urn:schemas-microsoft-com:xslt"
            xmlns:dt="urn:schemas-microsoft-com:datatypes">
<xsl:output media-type="xml" indent="yes" />

<xsl:template match="/Records">
<Configuration>
  <xsl:for-each select="record/Domain">
    <Domain name="{text()}">
    <Section name="{../Section/text()}">
      <Option keyword="{../Keyword/text()}">
        <Control type="{../Control/text()}"/>
      </Option>
    </Section>
    </Domain>
  </xsl:for-each>
</Configuration>
</xsl:template>
</xsl:stylesheet>

EDIT: After looking at Michaels link (thanks) I'm getting closer. The outer group (Domain) is working but the inner group isn't

  <xsl:key name="domain-name" match="record" use="Domain" />
  <xsl:key name="section-name" match="record" use="Section" />

  <xsl:template match="/Records">
    <Configuration>
      <xsl:for-each select="record[count(. | key('domain-name', Domain)[1]) = 1]">
        <xsl:sort select="Domain" />
        <Domain name="{Domain}">
          <xsl:for-each select="record[count(. | key('section-name', Section)[1]) = 1]">
            <xsl:sort select="Section" />
              <Section name="{Section}">
                <Option keyword="{Keyword/text()}">
                  <Control type="{Control/text()}"/>
                </Option>
              </Section>
            </xsl:for-each>
          </Domain>
        </xsl:for-each>
      </Configuration>
    </xsl:template>

Is giving me

  <Configuration>
    <Domain name="Domain 1" />
    <Domain name="Domain 2" />
  </Configuration>
1
I think it will be possible. Please post the XSLT code you have achieved up to now. - Pierre François
There are hundreds of examples of grouping to be found here. Start here: jenitennison.com/xslt/grouping/muenchian.html and come back if you run into any specific problems. - michael.hor257k
Thanks @michael.hor257k the link helped. The specific problem now it the inner grouping (Section). I've added some info to the original post. - Calid Xavier

1 Answers

0
votes

For your inner grouping, you are grouping Section elements within Domain, so both elements need to be within the key

<xsl:key name="section-name" match="record" use="concat(Domain, '|', Section)" />

The inner xsl:for-each then needs to be changed to use they new key format. But also because you are doing xsl:for-each select="group...." which won't work because you are currently positioned on a record and so the inner xsl:for-each is looking for a child element also called record. It should look like this, which only consider record elements for the current Domain value

<xsl:for-each select="key('domain-name', Domain)[count(. | key('section-name', concat(Domain, '|', Section))[1]) = 1]">

Try this XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:key name="domain-name" match="record" use="Domain" />
  <xsl:key name="section-name" match="record" use="concat(Domain, '|', Section)" />

  <xsl:template match="/Records">
    <Configuration>
      <xsl:for-each select="record[count(. | key('domain-name', Domain)[1]) = 1]">
        <xsl:sort select="Domain" />
        <Domain name="{Domain}">
          <xsl:for-each select="key('domain-name', Domain)[count(. | key('section-name', concat(Domain, '|', Section))[1]) = 1]">
            <xsl:sort select="Section" />
              <Section name="{Section}">
                <Option keyword="{Keyword/text()}">
                  <Control type="{Control/text()}"/>
                </Option>
              </Section>
            </xsl:for-each>
          </Domain>
        </xsl:for-each>
      </Configuration>
    </xsl:template>
</xsl:stylesheet>

It looks like you also then need a third level of grouping, on Keyword, but you should be able to work that out now (you will need the key to consider Domain, Section and Keyword)