Re: [xsl] Comparing direct ancestors

Subject: Re: [xsl] Comparing direct ancestors
From: Ihe Onwuka <ihe.onwuka@xxxxxxxxxxxxxx>
Date: Sun, 27 May 2012 14:48:47 +0100
On Thu, May 24, 2012 at 4:40 PM, Spencer Tickner
<spencertickner@xxxxxxxxx> wrote:
> Hi List,
>
> Thanks in advance for any help.
>
> I've been banging my head around this problem for awhile now and could
> use any advice anyone is willing to give. I have the following xml:
>
> <?xml version="1.0"?>
> <root>
>        <change-begin/>
>        <a>
>                <p>Foo<change-end/></p>
>        </a>
>        <change-begin/>
>        <b>
>                <d>
>                        <t>Bar</t>
>                </d>
>                <d>
>                        <t>Foo</t>
>                </d>
>        </b>
>        <change-end/>
>        <p>Nothing <change-begin/>to worry<change-end/> about</p>
> </root>
>
> I need to write an xslt (2.0 is fine) that will "move" the
> <change-begin/> and <change-end/> elements to be children of the
> nearest pre-defined allowable element. So in the example above I have
> <p> and <t> as elements that allow the <change-begin/> and
> <change-end/> elements. The output should look like this:
>
> <?xml version="1.0"?>
> <root>
>        <a>
>                <p><change-begin/>Foo<change-end/></p>
>        </a>
>        <b>
>                <d>
>                        <t><change-begin/>Bar</t>
>                </d>
>                <d>
>                        <t>Foo<change-end/></t>
>                </d>
>        </b>
>        <p>Nothing <change-begin/>to worry<change-end/> about</p>
> </root>
>
> My plan was a multi-pass stylesheet that first took care of the
> <change-begin/> then went on to take care of the <change-ends/>. For
> brevity I've limit this post to trying to deal with the
> <change-begin/>:
>

<snip>

> So far it doesn't work and is fast becoming a bunch of spaghetti
> <choose> statements. If anyone has any ideas I would love to hear
> them.
>

I've been experimenting with this to see how far I can get by applying
Felleisen et al's Design recipe to XSLT.
What follows is deliberately slow and methodical but it is a scalable
approach for dealing with those complex problems. I'm sure somebody
whose  XPath is better than mine can take this all the way.

Look at your input. It has the change-begin and the change-end
elements in the wrong place. However if we took them out we are left
with a skeleton where everything is in the write place.

Let's do that.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0"
                exclude-result-prefixes="">

   <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

    <!-- STEP ONE drop the change-begin and change-end elements copy
everything else -->
    <xsl:template match="change-begin|change-end"/>

</xsl:stylesheet>

That gives us this.

<?xml version="1.0" encoding="UTF-8"?><root>

       <a>
               <p>Foo
      </p>
       </a>

       <b>
               <d>
                       <t>Bar</t>
               </d>
               <d>
                       <t>Foo</t>
               </d>
       </b>

       <p>Nothing to worry about</p>
</root>

OK nothing is in the wrong place but some things that were in the
write place have been removed (see the Nothing to worry about).

STEP 2 - Put back the things that we removed but were in the right place.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0"
                exclude-result-prefixes="">

   <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

<!-- STEP ONE drop the change-begin and change-end elements copy
everything else -->
    <xsl:template match="change-begin|change-end"/>

<!-- STEP TWO put back the change-begin and the change elements that
were wrongfully dropped from step one -->
    <xsl:template
match="p/change-begin|p/change-end|t/change-begin|t/change-end">
       <xsl:copy-of select="."/>
    </xsl:template>

</xsl:stylesheet>

Now we have this.

<?xml version="1.0" encoding="UTF-8"?><root>

       <a>
               <p>Foo<change-end/>
      </p>
       </a>

       <b>
               <d>
                       <t>Bar</t>
               </d>
               <d>
                       <t>Foo</t>
               </d>
       </b>

       <p>Nothing <change-begin/>to worry<change-end/> about</p>
</root>

Let's do the change-begin's first. What shall we add to what we
already have that gets us closer to our goal.

I tried this

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0"
                exclude-result-prefixes="">

   <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

<!-- STEP ONE drop the change-begin and change-end elements copy
everything else -->
    <xsl:template match="change-begin|change-end"/>

<!-- STEP TWO put back the change-begin and the change elements that
were wrongfully dropped from step one -->
    <xsl:template
match="p/change-begin|p/change-end|t/change-begin|t/change-end"
priority="2">
       <xsl:copy-of select="."/>
    </xsl:template>

<!-- STEP 3 Pull in preceding orphan change-begins to the allowable element
-->
<!-- Changed the preceding template rule to priority 2 to overcome
ambiguous rule -->

   <xsl:template match="p[preceding::change-begin] |
t[preceding::change-begin]">
      <xsl:copy>
         <xsl:copy-of select="preceding::change-begin[1]"/>
         <xsl:apply-templates/>
      </xsl:copy>
   </xsl:template>

This gives us

<?xml version="1.0" encoding="UTF-8"?><root>

       <a>
               <p><change-begin/>Foo<change-end/>
      </p>
       </a>

       <b>
               <d>
                       <t><change-begin/>Bar</t>
               </d>
               <d>
                       <t><change-begin/>Foo</t>
               </d>
       </b>

       <p><change-begin/>Nothing <change-begin/>to worry<change-end/>
about</p>
</root>

which is close but not quite right because the same change-begin has
been copied into more than one allowable element
<t><change-begin/>Bar</t> and <t><change-begin/>Foo</t>

Perhaps someone whose XPath is sharper than mine can fix that bit,
what is required is that an allowable element should only consume an
orphan <change-begin> if it's path to it along the preceding axis is
not blocked by another allowable element.

Analagous arguments apply to <change-end> along the following axis.

Note we have stayed within the realm of XSLT 1

</xsl:stylesheet>

Close, but not quite.

Current Thread