How to reformat XML with related element groups using XSLT

I'm improving some XML that I have inherited using XSLT to clean things up, but I'm struggling with a single section. Which looks like this:

<rules> <if condition="equals" arg1="somevar" arg2="1"/> <elseif condition="equals" arg1="somevar" arg2="2"/> <elseif condition="equals" arg1="somevar" arg2="3"/> <else/> <if condition="equals" arg1="somevar" arg2="4"/> <else/> </rules> 

It's hard to verify with XSD, so I would like to convert it to something like that - ideas?

  <rules> <conditionSet> <if condition="equals" arg1="somevar" arg2="1"/> <elseif condition="equals" arg1="somevar" arg2="2"/> <elseif condition="equals" arg1="somevar" arg2="3"/> <else/> </conditionSet> <conditionSet> <if condition="equals" arg1="somevar" arg2="4"/> <else/> </conditionSet> </rules> 
+4
source share
4 answers

The elseif and else groups by their immediately preceding if element:

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:key name="block" match="elseif|else" use="generate-id(preceding-sibling::if[1])"/> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <xsl:template match="rules"> <xsl:copy> <xsl:apply-templates select="@*| node()[not(self::elseif or self::else)]"/> </xsl:copy> </xsl:template> <xsl:template match="if"> <conditionSet> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> <xsl:apply-templates select="key('block', generate-id())"/> </conditionSet> </xsl:template> </xsl:stylesheet> 

This stylesheet creates the requested result.

Explanation:. xsl:key associates each if element with the following related elements, so that later, when we match the if , we can simply wrap and copy the entire set.

+1
source

This is an interesting XSLT task. But, why are you changing XML again? The input pattern can be easily defined with a regular expression, namely

 (if, elseif*, else)* 

and for this reason it is easy to verify using XSD.

Maybe it's worth changing - an experienced dictionary designer (Lynne A. Price) once told me that any repetition operator in a group is automatically suspected and often means that the group should be replaced by an element. I think she will approve your changes. But to make sense, the rationale for the change should be more ease of processing, rather than simplified verification.

+1
source

Another:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:template match="/rules"> <xsl:copy> <xsl:apply-templates select="if"/> </xsl:copy> </xsl:template> <xsl:template match="if"> <conditionSet> <xsl:copy-of select="."/> <xsl:apply-templates select=" following-sibling::*[not(self::if) and generate-id(preceding-sibling::if[1]) = generate-id(current())] "/> </conditionSet> </xsl:template> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> 
0
source

I. The XSLT 1.0 solution is much simpler and shorter :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:key name="kFollowing" match="elseif|else" use="generate-id(preceding-sibling::if[1])"/> <xsl:template match="/*"> <rules> <xsl:apply-templates select="if"/> </rules> </xsl:template> <xsl:template match="if"> <conditionSet> <xsl:copy-of select=".|key('kFollowing', generate-id())"/> </conditionSet> </xsl:template> </xsl:stylesheet> 

when applied to the provided XML document :

 <rules> <if condition="equals" arg1="somevar" arg2="1"/> <elseif condition="equals" arg1="somevar" arg2="2"/> <elseif condition="equals" arg1="somevar" arg2="3"/> <else/> <if condition="equals" arg1="somevar" arg2="4"/> <else/> </rules> 

required, the correct result is obtained :

 <rules> <conditionSet> <if condition="equals" arg1="somevar" arg2="1"/> <elseif condition="equals" arg1="somevar" arg2="2"/> <elseif condition="equals" arg1="somevar" arg2="3"/> <else/> </conditionSet> <conditionSet> <if condition="equals" arg1="somevar" arg2="4"/> <else/> </conditionSet> </rules> 

II. An even simpler and faster XSLT 2.0 solution :

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/*"> <rules> <xsl:for-each-group select="*" group-starting-with="if"> <conditionSet> <xsl:sequence select="current-group()"/> </conditionSet> </xsl:for-each-group> </rules> </xsl:template> </xsl:stylesheet> 

when this conversion is applied to the same XML document (above), the same correct result is obtained :

 <rules> <conditionSet> <if condition="equals" arg1="somevar" arg2="1"/> <elseif condition="equals" arg1="somevar" arg2="2"/> <elseif condition="equals" arg1="somevar" arg2="3"/> <else/> </conditionSet> <conditionSet> <if condition="equals" arg1="somevar" arg2="4"/> <else/> </conditionSet> </rules> 
0
source

Source: https://habr.com/ru/post/1394054/


All Articles