How to Modify XSLT to Reorder XML Elements Based on Specific Criteria?

Questions about XML that are not covered by the other forums should go here.
CarlSimon
Posts: 1
Joined: Fri Jul 12, 2024 9:28 am
Contact:

How to Modify XSLT to Reorder XML Elements Based on Specific Criteria?

Post by CarlSimon »

I'm working on an XML transformation project and need some help with XSLT. I have an XML file with multiple <Student> elements, each containing <HUSID>, <SURNAME>, <NUMHUS>, and <COURSEID> elements. My goal is to reorder the <COURSEID> elements within each <Student> element so that all <COURSEID> values are nested within a single <InstancePeriod> element.

Here's a simplified version of my XML:

Code: Select all

<dataroot>
<Student>
<HUSID>idno</HUSID>
<SURNAME>Second Name</SURNAME>
<NUMHUS>idno2</NUMHUS>
<COURSEID>course1</COURSEID>
</Student>
<Student>
<HUSID>idno</HUSID>
<SURNAME>Surname</SURNAME>
<NUMHUS>idno2</NUMHUS>
<COURSEID>course2</COURSEID>
</Student>
<Student>
<HUSID>idno</HUSID>
<SURNAME>Surname</SURNAME>
<NUMHUS>idno2</NUMHUS>
<COURSEID>course3</COURSEID>
</Student>
</dataroot>
And here's the XSLT I'm currently using:

Code: Select all

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="dataroot">
<dataroot>
<xsl:apply-templates select="Student[not(HUSID = preceding-sibling::Student/HUSID)]" mode="student"/>
</dataroot>
</xsl:template>
<xsl:template match="Student" mode="student">
<xsl:variable name="husid" select="HUSID"/>
<xsl:variable name="surname" select="SURNAME"/>
<Student>
<HUSID><xsl:value-of select="$husid"/></HUSID>
<SURNAME><xsl:value-of select="$surname"/></SURNAME>
<xsl:apply-templates select="/dataroot/Student[HUSID/text()=$husid]" mode="instanceperiod"/>
</Student>
</xsl:template>
<xsl:template match="Student" mode="instanceperiod">
<xsl:variable name="courseid" select="COURSEID"/>
<Instance>
<NUMHUS><xsl:value-of select="NUMHUS"/></NUMHUS>
<InstancePeriod>
<COURSEID><xsl:value-of select="$courseid"/></COURSEID>
</InstancePeriod>
</Instance>
</xsl:template>
</xsl:stylesheet>
The output I'm getting is:

Code: Select all

<dataroot>
<Student>
<HUSID>idno</HUSID>
<SURNAME>Second Name</SURNAME>
<Instance>
<NUMHUS>idno2</NUMHUS>
<InstancePeriod>
<COURSEID>course1</COURSEID>
</InstancePeriod>
</Instance>
<Instance>
<NUMHUS>idno2</NUMHUS>
<InstancePeriod>
<COURSEID>course2</COURSEID>
</InstancePeriod>
</Instance>
<Instance>
<NUMHUS>idno2</NUMHUS>
<InstancePeriod>
<COURSEID>course3</COURSEID>
</InstancePeriod>
</Instance>
</Student>
</dataroot>
But what I want is:

Code: Select all

<dataroot>
<Student>
<HUSID>idno</HUSID>
<SURNAME>Second Name</SURNAME>
<Instance>
<NUMHUS>idno2</NUMHUS>
<InstancePeriod>
<COURSEID>course1</COURSEID>
<COURSEID>course2</COURSEID>
<COURSEID>course3</COURSEID>
</InstancePeriod>
</Instance>
</Student>
</dataroot>
Could anyone help me adjust my XSLT to achieve the desired output? Any tips or suggestions would be greatly appreciated!
tavy
Posts: 390
Joined: Thu Jul 01, 2004 12:29 pm

Re: How to Modify XSLT to Reorder XML Elements Based on Specific Criteria?

Post by tavy »

Hello,

To achieve your desired output, you need to adjust your XSLT to ensure that all `<COURSEID>` elements are nested within a single `<InstancePeriod>` element for each `<Student>`. Here’s how you can modify your XSLT to achieve that:

1. First, collect all `<COURSEID>` elements for each `<Student>` and then wrap them inside a single `<InstancePeriod>` element.
2. Modify the templates to ensure that the correct structure is maintained.

Here's the adjusted XSLT:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
    <xsl:template match="dataroot">
        <dataroot>
            <xsl:apply-templates select="Student[not(HUSID = preceding-sibling::Student/HUSID)]" mode="student"/>
        </dataroot>
    </xsl:template>
    
    <xsl:template match="Student" mode="student">
        <xsl:variable name="husid" select="HUSID"/>
        <xsl:variable name="surname" select="SURNAME"/>
        <Student>
            <HUSID><xsl:value-of select="$husid"/></HUSID>
            <SURNAME><xsl:value-of select="$surname"/></SURNAME>
            <Instance>
                <NUMHUS><xsl:value-of select="NUMHUS"/></NUMHUS>
                <InstancePeriod>
                    <xsl:apply-templates select="/dataroot/Student[HUSID/text()=$husid]" mode="courseid"/>
                </InstancePeriod>
            </Instance>
        </Student>
    </xsl:template>
    
    <xsl:template match="Student" mode="courseid">
        <COURSEID><xsl:value-of select="COURSEID"/></COURSEID>
    </xsl:template>
</xsl:stylesheet>
In this adjusted XSLT:

- The `Student` template in `student` mode collects `<HUSID>` and `<SURNAME>` and constructs the `<Instance>` and `<InstancePeriod>` elements.
- The `Student` template in `courseid` mode only outputs `<COURSEID>` elements, which are then nested within the single `<InstancePeriod>` element created in the previous template.


I recommend trying the "Chat About Code" feature from the "Oxygen AI Positron" plugin. If you pose the same question in the chat, when you are in an XSLT document, you will receive a similar result as the one I posted. Afterwards, you can refine this result.
For more information, visit: https://www.oxygenxml.com/ai_positron/w ... ed_Actions

Best Regards,
Octavian
Octavian Nadolu
<oXygen/> XML Editor
http://www.oxygenxml.com
Post Reply