I am pretty skilled at XSLT, but I've not encountered before the need to transform an XML document and implement reparenting. The input XML is in OPML format:
<opml>
<body>
<outline text="root">
<outline text="child 1">
<outline text="some text A">
<outline text="some text B" />
<outline text="some text C" />
...
<outline text="child 2">
<outline text="some text D">
<outline text="some text E" />
<outline text="some text F" />
....
I have another xml doc which is used in the transform, and lists nodes and new parent nodes, and it has entries such as:
<row>
<node>some text A</node>
<newParent>child 2</newParent>
</row>
Has anyone tackled such programmatic reparenting using XSLT?
CodePudding user response:
I am guessing (!!) you mean something like this:
XML
<opml>
<body>
<outline text="root">
<outline text="child 1">
<outline text="some text A">
<outline text="some text B"/>
<outline text="some text C"/>
</outline>
</outline>
<outline text="child 2">
<outline text="some text D">
<outline text="some text E"/>
<outline text="some text F"/>
</outline>
</outline>
</outline>
</body>
</opml>
external.xml
<root>
<row>
<node>some text A</node>
<newParent>child 2</newParent>
</row>
</root>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="reparent" match="row" use="node" />
<xsl:key name="children" match="outline" use="@parent" />
<xsl:template match="/opml">
<xsl:variable name="outlines">
<xsl:for-each select="//outline">
<xsl:variable name="reparent" select="key('reparent', @text, document('path/to/external.xml'))" />
<outline parent="{if ($reparent) then $reparent/newParent else ../@text}" text="{@text}"/>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<opml>
<body>
<xsl:apply-templates select="$outlines/outline[not(string(@parent))]"/>
</body>
</opml>
</xsl:template>
<xsl:template match="outline">
<outline text="{@text}">
<xsl:apply-templates select="key('children', @text)"/>
</outline>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<opml>
<body>
<outline text="root">
<outline text="child 1"/>
<outline text="child 2">
<outline text="some text A">
<outline text="some text B"/>
<outline text="some text C"/>
</outline>
<outline text="some text D">
<outline text="some text E"/>
<outline text="some text F"/>
</outline>
</outline>
</outline>
</body>
</opml>
Note that there is an implied assumption here that the text
of an outline
is unique.
CodePudding user response:
While my first tool of choice is invariably XSLT, I might be inclined to tackle this one by transforming the input
<row>
<node>some text A</node>
<newParent>child 2</newParent>
</row>
into XQuery Update statements:
let $node := //outline[@text='some text A']
let $newParent := //outline[@text='child 2']
return (
insert nodes $node into $newParent,
delete nodes $node
)
and then execute the resulting XQuery Update script.
CodePudding user response:
If the <row>
s are to be executed consecutively, I suggest you use a transformation that processes one row, like
<xsl:param name="node"/>
<xsl:param name="newParent"/>
<xsl:template match="outline">
<xsl:if test="not(@text=$node)">
<xsl:copy>
<xsl:copy-of select="@text"/>
<xsl:apply-templates select="outline"/>
<xsl:if test="self::outline[@text=$newParent]">
<xsl:if test="ancestor-or-self::outline[@text=$node]">
<xsl:message terminate="yes">Forbidden cycle</xsl:message>
</xsl:if>
<xsl:copy-of select="//outline[@text=$node]"/>
</xsl:if>
</xsl:copy>
</xsl:if>
</xsl:template>
and use non-XSLT means to loop over the <row>
list and invoke that transformation once per row.
If they are not to be executed consecutively, can you specify the rules in more detail?