[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: [xsl] Comparing direct ancestors


Subject: Re: [xsl] Comparing direct ancestors
From: Spencer Tickner <spencertickner@xxxxxxxxx>
Date: Mon, 28 May 2012 11:27:12 -0700

Hi Ihe,

Thanks for walking though the solution as I'm sure it will help
others. Amazingly my mindset was exactly the same as yours, trying the
XPath remove and replace before tearing some hair out an moving to the
key solution proposed by David.

As with any sample code I have run into a fringe scenario that has got
me thinking of the "context" that the "use" statement,, ummm, uses.
The fringe scenario comes up with nested allowable elements:

<?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 <p>This</p> whatever</t>
		</d>
	</b>
	<change-end/>
	<p>Nothing <change-begin/>to worry<change-end/> about</p>
<!-- Fringe scenario -->
	<b>
		<d>
			<t><change-begin/>This <p>and</p> <t>That</t> and <p>that and </p> a</t>
		</d>
	</b>
	<change-end/>
</root>

In the last case the key puts the <change-end/> in the wrong spot
(attributes added to show context:

Output: <t preceding="0" ancestor-self="1"><change-begin/>This <p
preceding="0" ancestor-self="2">and</p> <t preceding="1"
ancestor-self="2">That</t> and <p preceding="2" ancestor-self="2">that
and <change-end/></p> a</t>

Desired Output: <t preceding="0" ancestor-self="1"><change-begin/>This
<p preceding="0" ancestor-self="2">and</p> <t preceding="1"
ancestor-self="2">That</t> and <p preceding="2" ancestor-self="2">that
and </p> a<change-end/></t>

So I thought this would be easy enough to fix by changing the <key> to
"offset" the element it's applied to by counting up the
preceding-siblings and the ancestor-or-self. If I manually change the
[1] to [4] it works as expected. But I can't figure out why it doesn't
work with the dynamic information:

	<xsl:key name="ce" match="*[not(self::p|self::t)]/change-end"
use="generate-id(preceding::*[self::t|self::p][

		(count(ancestor-or-self::*[self::p|self::t])) +
		(count(preceding-sibling::*[self::p|self::t]))

	])"/>

and have also tried the following in the case that my context for the
xpath is not right:

	<xsl:key name="ce" match="*[not(self::p|self::t)]/change-end"
use="generate-id(preceding::*[self::t|self::p][


		(count(./preceding::*[self::t|self::p]/ancestor-or-self::*[self::p|self::t]
))
+
		(count(./preceding::*[self::t|self::p]/preceding-sibling::*[self::p|self::t
]))




	])"/>

neither work.

On Sun, May 27, 2012 at 2:23 PM, Ihe Onwuka <ihe.onwuka@xxxxxxxxxxxxxx>
wrote:
> 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>
>> B  B  B  B <change-begin/>
>> B  B  B  B <a>
>> B  B  B  B  B  B  B  B <p>Foo<change-end/></p>
>> B  B  B  B </a>
>> B  B  B  B <change-begin/>
>> B  B  B  B <b>
>> B  B  B  B  B  B  B  B <d>
>> B  B  B  B  B  B  B  B  B  B  B  B <t>Bar</t>
>> B  B  B  B  B  B  B  B </d>
>> B  B  B  B  B  B  B  B <d>
>> B  B  B  B  B  B  B  B  B  B  B  B <t>Foo</t>
>> B  B  B  B  B  B  B  B </d>
>> B  B  B  B </b>
>> B  B  B  B <change-end/>
>> B  B  B  B <p>Nothing <change-begin/>to worry<change-end/> about</p>
>> </root>
>>
>>
>> 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.
>>
>
> OK. Lets start this again. This is long because it's meant to pedagogic.
>
> When this problem initially appeared David Carlisle posted an almost
> instantaneous solution. B I was still half heartedly trying to grok the
> problem when the solution appeared and was completely awestruck at
> David's solution. Actually dumbstruck would be more apt because I
> didn't understand it and I had no idea how he had derived it. I gave
> the code a passing glance and thought to myself - well you're B not in
> his B league, don't even try to figure it out and the OP has a
> solution.
>
> As mentioned before though I wanted to see how far I could get by
> applying Felleisen et al's Design Recipe to the problem.
>
> I'm going to step through it again - this time to conclusion. I stress
> I arrived at my solution independently to his but it will be
> instructive to compare the answer the process yields to David's.
>
> The purpose of the post is pedagogic, so lets slowly and methodically
> step through the process right from the start. If you didn't consider
> yourself capable of solving this problem this is for you.
>
> Step 1 - Let us get a skeleton output where everything that is in it
> is known to be in the right place.
> Simplest way to do that is to remove all the change-begin and
> change-end elements - see the bit commented STEP ONE
>
> <xsl:stylesheet
> B  B xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
> B  B exclude-result-prefixes="">
>
> B  <xsl:template match="@*|node()">
> B  B  B  <xsl:copy>
> B  B  B  B  B <xsl:apply-templates select="@*|node()"/>
> B  B  B  </xsl:copy>
> B  </xsl:template>
>
> B  <!-- STEP ONE Remove all change-begin and change-end elements -->
> B  B <xsl:template match="change-begin|change-end"/>
> </xsl:stylesheet>
>
> resulting output.
>
> <?xml version="1.0" encoding="UTF-8"?>
> <root>
>
> B  B  B  <a>
> B  B  B  B  B  B  B  <p>Foo
> B  B  B </p>
> B  B  B  </a>
>
> B  B  B  <b>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t>Bar</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t>Foo</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  </b>
>
> B  B  B  <p>Nothing to worry about</p>
> </root>
>
> Everything that is there is in the right place but some things that
> were in the right place have been removed.
> Lets put them back. Additional template rule - commented STEP TWO does
that.
>
> <?xml version="1.0" encoding="UTF-8"?>
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
> B  B  B  B  B  B  B  B exclude-result-prefixes="">
>
> B  <xsl:template match="@*|node()">
> B  B  B <xsl:copy>
> B  B  B  B <xsl:apply-templates select="@*|node()"/>
> B  B  B </xsl:copy>
> B  </xsl:template>
>
> B  <!-- STEP ONE Remove all change-begin and change-end elements -->
> B  B <xsl:template match="change-begin|change-end"/>
>
> B  B <!-- STEP TWO Restore all change-begin and change-ends that are
> already in the correct place -->
> B  B <xsl:template
> match="p/change-begin|p/change-end|t/change-begin|t/change-end">
> B  B  B  <xsl:copy-of select="."/>
> B  B </xsl:template>
>
> </xsl:stylesheet>
>
> That gives us this
>
> <?xml version="1.0" encoding="UTF-8"?><root>
>
> B  B  B  <a>
> B  B  B  B  B  B  B  <p>Foo<change-end/></p>
> B  B  B  </a>
>
> B  B  B  <b>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t>Bar</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t>Foo</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  </b>
>
> B  B  B  <p>Nothing <change-begin/>to worry<change-end/> about</p>
> </root>
>
> So the change-begin and the change-end that started off in the right
> place have been put back there.
>
> Right heres where the last attempt went off the rails. We need to pull
> in the stray change-begin's and change-ends into the nearest allowable
> element and the original attempt not worth repeating was to try and
> come up with an XPath expression to do that. It's tough to do in a
> push style, (see the previous post for the attempt and explanation).
>
> Now is the time to step back and remember that all methodical
> approaches have limitations and can only take you so far. Mind you
> they can take you alot farther than you might think and they are often
> abandoned too early. The Design Recipe has it's roots in Jackson
> Structured Programming (very annoying that I can't just use the
> acronym).
>
> We cannot process wholly in push style because as at the time we
> encounter the first change-begin we have not yet read the rest of the
> nodes much less know where it needs to be inserted. The problem we are
> coming up with here looks like what Jackson would have called a
> structure clash.
> Suffice to say that it warrants looking at different techniques the
> language offers because like JSP (oops thats torn it) the basics
> methodology only entails using a very small subset of the language.
> That's not to say we willy nilly pluck any old idea from the language.
> Rather we want to extend our problem solving toolset in a controlled
> way.
>
> Thats when I thought of using xsl:keys. By doing a lookahead (in the
> case of change-behind) and lookbehind in the case of change end, Can
> we associate B the change elements with the allowable elements, worth a
> try isn't it. Sounds easier than trying to figure out the XPath's to
> do the same thing (although YMMV on that one depending on your
> abilities).
>
> Lookahead and Lookbehind seem to be fairly disciplined activities (i.e
> as opposed to goto like hacks). Let me stress that I arrived at the
> idea to use keys independently (and alot slower) than David did. I did
> not look at his code to see what or how had done it. I vaguely
> recollected him using keys in his solution and that merely suggested
> there was mileage in pursuing that angle.
>
> OK step THREE is to define the key and step FOUR is to use it. In case
> we are wrong we will just do the change-begins.
>
> <?xml version="1.0" encoding="UTF-8"?>
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
> B  B  B  B  B  B  B  B exclude-result-prefixes="">
>
> B  B <!-- STEP THREE Define a key to pair up a change to the allowable
> element it should go into -->
> B  B <xsl:key name="changes" match="*[not (self::p or
self::t)]/change-begin"
> B  B  B  B  B  B use="generate-id(following::*[self::p or self::t][1])"/>
>
> B  <xsl:template match="@*|node()">
> B  B  B <xsl:copy>
> B  B  B  B <xsl:apply-templates select="@*|node()"/>
> B  B  B </xsl:copy>
> B  </xsl:template>
>
> B  <!-- STEP ONE Remove all change-begin and change-end elements -->
> B  B <xsl:template match="change-begin|change-end"/>
>
> B  B <!-- STEP TWO Restore all change-begin and change-ends that are
> already in the correct place -->
> B  B <xsl:template
> match="p/change-begin|p/change-end|t/change-begin|t/change-end">
> B  B  B  <xsl:copy-of select="."/>
> B  B </xsl:template>
>
> B  B <!-- STEP FOUR USE the key in STEP THREE to insert the
> change-begin's in the right place -->
> B  B <xsl:template match="p|t">
> B  B  B  B  B  B <xsl:copy>
> B  B  B  B  B  B  B  B  B  B  B  <xsl:copy-of select="key('changes',
generate-id())"/>
> B  B  B  B  B  B  B  B <xsl:apply-templates/>
> B  B  B  B  B  B </xsl:copy>
> B  B </xsl:template>
>
> </xsl:stylesheet>
>
> The key says for every strange change-begin look ahead for the first
> allowable element that should house it. B As to how I came up with that
> xsl:key, well knowledge of keys should be part of B your arsenal as an
> XSLT developer.
>
> Step Four exploits implicit priorities on the template match rule to
> identify allowable elements that could take a change-begin.
>
> Lets run it.
>
> <?xml version="1.0" encoding="UTF-8"?><root>
>
> B  B  B  <a>
> B  B  B  B  B  B  B  <p><change-begin/>Foo<change-end/>
> B  B  B </p>
> B  B  B  </a>
>
> B  B  B  <b>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t><change-begin/>Bar</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  B  B  B  B  <d>
> B  B  B  B  B  B  B  B  B  B  B  <t>Foo</t>
> B  B  B  B  B  B  B  </d>
> B  B  B  </b>
>
> B  B  B  <p>Nothing <change-begin/>to worry<change-end/> about</p>
> </root>
>
> Voila, all the change begin's are in the right place.
>
> Step 5 is to handle the change-end's. There's a symmetry to the
> additional key definition that it entails that often emerges when you
> are on the right track to a solution.
>
>
> <xsl:stylesheet
> B  B xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
> B  B exclude-result-prefixes="">
>
> B  B <!-- STEP THREE Define a key to pair up a change to the allowable
> element it should go into -->
> B  B <xsl:key name="changes" match="*[not (self::p or
> self::t)]/change-begin" use="generate-id(following::*[self::p or
> self::t][1])"/>
>
> B  B <!-- STEP FIVE By symmetry extend the key to pair up change-ends
> to allowable elements -->
> B  B <xsl:key name="changes" match="*[not (self::p or
> self::t)]/change-end" use="generate-id(preceding::*[self::p or
> self::t][1])"/>
>
> <xsl:template match="@*|node()">
> B  B <xsl:copy>
> B  B  B  B <xsl:apply-templates select="@*|node()"/>
> B  B </xsl:copy>
> </xsl:template>
>
> B  <!-- STEP ONE Remove all change-begin and change-end elements -->
> B  B <xsl:template match="change-begin|change-end"/>
>
> B  B <!-- STEP TWO Restore all change-begin and change-ends that are
> already in the correct place -->
> B  B <xsl:template
> match="p/change-begin|p/change-end|t/change-begin|t/change-end">
> B  B  B  <xsl:copy-of select="."/>
> B  B </xsl:template>
>
> B  B <!-- STEP FOUR USE the key in STEP THREE to insert the
> change-begin's in the right place -->
> B  B <xsl:template match="p|t">
> B  B  B  B  B  B <xsl:copy>
> B  B  B  B  B  B  B  B  B  B <xsl:copy-of select="key('changes',
generate-id())"/>
> B  B  B  B  B  B <xsl:apply-templates/>
> B  B  B  B  B  B </xsl:copy>
> B  B </xsl:template>
> </xsl:stylesheet>
>
> Running this B does give the answer that the OP was seeking. I won't
> paste the answer so as to not take up even more space.
>
> Now I have no idea what process David went through but having got the
> answer I thought it would be worth taking a peek at his code.
>
> Again I was awestruck at the similarities in the final solution' given
> there had not been any mental synergy in their construction.
>
> Awestruck but this time, happily, not dumbfounded. :)


Current Thread
Keywords