Home > OS >  I am looking for an XSLT usage pattern that will reparent nodes
I am looking for an XSLT usage pattern that will reparent nodes

Time:09-16

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?

  • Related