XML node grouping
Im working with PHP5, and I need to convert the XML in the following form:
<list> <item label="(1)">some text</item> <item label="(2)"> <anotherNode>some text</anotherNode <item label="a">some text</item> <item label="b">some text</item> </item> </list> In something like this:
<list> <item label="(1)">some text</item> <item label="(2)"> <anotherNode>some text</anotherNode> <list> <!-- opening new wrapper node--> <item label="a">some text</item> <item label="b">some text</item> </list> <!-- closing new wrapper node--> </item> </list> As you can see above, I need to add a node wrapper to any item nodes that are no longer wrapped in a list of nodes.
What are the possible solutions to convert source xml to target xml?
UPDATED:
Note 1: Any single or group of nodes <item> should be wrapped <list> node, if it is not already completed.
Note 2: The content order must be saved.
Note 3: If there are <item> nodes before and after <anotherNode> . He should convert this:
<list> <item label="(1)">some text</item> <item label="(2)"> <item label="a">some text</item> <item label="b">some text</item> <anotherNode>some text</anotherNode> <item label="c">some text</item> <item label="d">some text</item> </item> </list> in it:
<list> <item label="(1)">some text</item> <item label="(2)"> <list> <!-- opening new wrapper node--> <item label="a">some text</item> <item label="b">some text</item> </list> <!-- closing new wrapper node--> <anotherNode>some text</anotherNode> <list> <!-- opening new wrapper node--> <item label="c">some text</item> <item label="d">some text</item> </list> <!-- closing new wrapper node--> </item> </list> Thanks,
This conversion is :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*" name="identity"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="item/item[1]"> <list> <xsl:apply-templates mode="copy" select=".| following-sibling::item"/> </list> </xsl:template> <xsl:template match="item" mode="copy"> <xsl:call-template name="identity"/> </xsl:template> <xsl:template match="item/item[not(position()=1)]"/> </xsl:stylesheet> when applied to the provided XML document :
<list> <item label="(1)">some text</item> <item label="(2)"> <anotherNode>some text</anotherNode> <item label="a">some text</item> <item label="b">some text</item> </item> </list> creates the desired, correct result :
<list> <item label="(1)">some text</item> <item label="(2)"> <anotherNode>some text</anotherNode> <list> <item label="a">some text</item> <item label="b">some text</item> </list> </item> </list> Please note :
Using and overriding the Identity rule.
Suppression of certain elements.
Processing some elements using a different mode.
Update
OP added additional requirements:
"If there are item elements before and after anothernode , then each such group of item elements must be enclosed in a separate list "
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kfollnonitem" match="item" use="generate-id(preceding-sibling::*[not(self::item)][1])"/> <xsl:key name="kprecnonitem" match="item" use="generate-id(following-sibling::*[not(self::item)][1])"/> <xsl:template match="node()|@*" name="identity"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="*[not(self::list)]/item[1]"> <list> <xsl:apply-templates mode="copy" select="key('kprecnonitem', generate-id(following-sibling::*[not(self::item)][1]) )"/> </list> </xsl:template> <xsl:template match= "*[not(self::list) and item]/*[not(self::item)]"> <xsl:call-template name="identity"/> <list> <xsl:apply-templates mode="copy" select="key('kfollnonitem', generate-id())"/> </list> </xsl:template> <xsl:template match="item" mode="copy"> <xsl:call-template name="identity"/> </xsl:template> <xsl:template match="item/item[not(position()=1)]"/> </xsl:stylesheet> when this conversion is performed against an XML document :
<list> <item label="(1)">some text</item> <item label="(2)"> <item label="a">some text</item> <item label="b">some text</item> <anotherNode>some text</anotherNode> <item label="c">some text</item> <item label="d">some text</item> </item> </list> required, the correct result is obtained :
<list> <item label="(1)">some text</item> <item label="(2)"> <list> <item label="a">some text</item> <item label="b">some text</item> </list> <anotherNode>some text</anotherNode> <list> <item label="c">some text</item> <item label="d">some text</item> </list> </item> </list> This style sheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="@*|node()" name="identity"> <xsl:copy> <xsl:apply-templates select="@*|node()[1]" /> </xsl:copy> <xsl:apply-templates select="following-sibling::node()[1]" /> </xsl:template> <xsl:template match="*[not(self::list)] /item[not(preceding-sibling::*[1][self::item])]"> <list> <xsl:call-template name="identity"/> </list> <xsl:apply-templates select="following-sibling::node() [not(self::item)][1]" /> </xsl:template> <xsl:template match="*[not(self::list)] /item[not(following-sibling::*[1][self::item])]"> <xsl:copy> <xsl:apply-templates select="@*|node()[1]" /> </xsl:copy> </xsl:template> </xsl:stylesheet> Conclusion:
<list> <item label="(1)">some text</item> <item label="(2)"> <anotherNode>some text</anotherNode> <list> <item label="a">some text</item> <item label="b">some text</item> </list> </item> </list> In addition, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:key name="kItemByFirstSibling" match="item[preceding-sibling::*[1][self::item]]" use="generate-id(preceding-sibling::item [not(preceding-sibling::*[1][self::item])][1])"/> <xsl:template match="@*|node()" name="identity"> <xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:template> <xsl:template match="*[not(self::list)]/item"/> <xsl:template match="*[not(self::list)] /item[not(preceding-sibling::*[1][self::item])]" priority="1"> <list> <xsl:for-each select=".|key('kItemByFirstSibling',generate-id())"> <xsl:call-template name="identity"/> </xsl:for-each> </list> </xsl:template> </xsl:stylesheet> Note The first style sheet uses the finest-grained transversal (after the first item it will wrap any node). Repeated identity transformation of the second style sheet.
Change Addressing a new request, with a new input, output of both stylesheets:
<list> <item label="(1)">some text</item> <item label="(2)"> <list> <item label="a">some text</item> <item label="b">some text</item> </list> <anotherNode>some text</anotherNode> <list> <item label="c">some text</item> <item label="d">some text</item> </list> </item> </list> You did not address this in the original question, so this may not be required. But if the input has several sequences of <item> elements that need to be wrapped, which are separated from each other by other sibling elements, for example:
<list> <item label="(1)">some text</item> <item label="(2)"> <item label="a">some text</item> <item label="b">some text</item> <anotherNode>some text</anotherNode> <item label="c">some text</item> <item label="d">some text</item> </item> </list> the previous answers, I think, combine the <item> elements together, changing their order:
<list> <item label="(1)">some text</item> <item label="(2)"> <list> <!-- opening new wrapper node--> <item label="a">some text</item> <item label="b">some text</item> <item label="c">some text</item> <item label="d">some text</item> </list> <!-- closing new wrapper node--> <anotherNode>some text</anotherNode> </item> </list> Do you want it or want to wrap them separately?
<list> <item label="(1)">some text</item> <item label="(2)"> <list> <!-- opening new wrapper node--> <item label="a">some text</item> <item label="b">some text</item> </list> <!-- closing new wrapper node--> <anotherNode>some text</anotherNode> <list> <!-- opening new wrapper node--> <item label="c">some text</item> <item label="d">some text</item> </list> <!-- closing new wrapper node--> </item> </list> If the latter is likely to be the easiest, use the XSLT 2.0 <xsl:for-each-group group-adjacent="name()" /> construct. I don't know if PHP 5 XSLT 2.0 is available, but if you can use such a thing, see this good article .