Reputation: 37
Starting point is an XML-list like
<attributes>
<para role="tocmain1"/>
<para role="tocmain1"/>
<other style="fix"/>
<other style="fix"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain3"/>
<para language="de"/>
<para language="de"/>
<para role="tocmain3"/>
</attributes>
I would like to reduce the occurrences of each element + attribute + value instance to just one occurrence.
Like this:
<attributes>
<other style="fix"/>
<para language="de"/>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
</attributes>`
So far I have only succeeded to order the list alphabetically. All my attempts to reduce the list have been in vain so far.
That is what I have right now:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<attributes>
<xsl:for-each select="attributes/node()">
<xsl:sort select="name()" order="ascending"/>
<xsl:sort select="@*" order="ascending"/>
<xsl:choose>
<xsl:when test="name() = name(preceding::*[1]) and self::node()/@* = preceding::*/@*"/>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</attributes>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Views: 1420
Reputation: 37
Okay, based on proposal of Daniel Haley I found an answer which works for my question.
I'm using now two xsl one after the other:
XSL which gives an unordered list without duplicates
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each-group select="*" group-by="@*">
<xsl:sort select="@*"/>
<xsl:apply-templates select="current-group()[1]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XSL which orders the list:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<attributes>
<xsl:for-each select="attributes/node()">
<xsl:sort select="name()" order="ascending"/>
<xsl:sort select="name(@*)" order="ascending"/>
<xsl:sort select="@*" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</attributes>
</xsl:template>
</xsl:stylesheet>
Result as requested:
<attributes>
<other style="fix"/>
<para language="de"/>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
</attributes>
Thank you for helping me find the answer and sorry for misleading example XML! If someone knows how to combine the two steps into one script: you are welcome!
Upvotes: 0
Reputation: 52888
You could also use xsl:for-each-group
and xsl:apply-templates
to the first node in the group. This should make it easy to add any additional transformation that might need to happen in the future.
XML Input (modified to show correct sorting)
<attributes>
<para role="tocmain3"/>
<para role="tocmain2"/>
<para role="tocmain1"/>
<para role="tocmain3"/>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain1"/>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
</attributes>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each-group select="*" group-by="@role">
<xsl:sort select="@role"/>
<xsl:apply-templates select="current-group()[1]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<attributes>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
</attributes>
Upvotes: 2
Reputation: 111726
Your XSLT generates your requested XML modulo some formatting issues that can be resolved with xsl:output
and xsl:strip-space
. Also, you might leverage distinct-values()
to streamline your code:
Your XML input document,
<attributes>
<para role="tocmain1"/>
<para role="tocmain1"/>
<para role="tocmain1"/>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
<para role="tocmain3"/>
<para role="tocmain3"/>
</attributes>
given to this streamlined XSLT,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/attributes">
<attributes>
<xsl:for-each select="distinct-values(para/@role)">
<xsl:sort select="." order="ascending"/>
<para role="{.}"/>
</xsl:for-each>
</attributes>
</xsl:template>
</xsl:stylesheet>
will produce this XML output document,
<?xml version="1.0" encoding="UTF-8"?>
<attributes>
<para role="tocmain1"/>
<para role="tocmain2"/>
<para role="tocmain3"/>
</attributes>
as requested.
Upvotes: 0