RE: [xsl] Using the Input Document to Control Generation of Numbers in the Output

Subject: RE: [xsl] Using the Input Document to Control Generation of Numbers in the Output
From: "Kerry, Richard" <richard.kerry@xxxxxxxxxxx>
Date: Thu, 4 Oct 2007 11:35:37 +0100
Well,

Having declared that I'd be using the recursive solution as it was
clearer to me what was going on I've had to change my mind.  And I'll
accept the possibility of O(n^2) performance and worry about that if I
find I've enough input data.

The solutions we've seen here on the list are stand-alone in that they
are assuming just the one type of element 'incoming' in the source
document.  In fact I've got several types of node and they need
different handling.  This seems to me to make the selections for the
recursive solution much more complex if all other nodes are to be passed
unharmed (using copy or copy-of).  I already have some xsl doing other
transformations and I need to add the new index handling for a certain
type of source element.
So with this in mind I've sat down and worked out a solution based on
George's node-set methods that will handle both 'index' and 'size'
atttributes in the input document.
I've left it quite verbose so it's clear to me what's going on.


Regards,
Richard.


My xsl follows.

  <xsl:template match="incoming">
	<xsl:choose>
	  <xsl:when test="@index">
		<!-- Index provided.  Use it. -->
		<outgoing name="{@name}" index="{@index}"/>
 	  </xsl:when>
	  <xsl:otherwise>
		<!-- Else no index provided.  Need to calculate based on
most recent one and any
intervening sizes. -->

		<xsl:choose>
		  <xsl:when test="not(preceding-sibling::*[@index])">
			<!-- There is no preceding node with an index
attribute, so we're indexing from 1.
				 Or whatever our constant base is. -->
			<xsl:variable name="base" select="1" />
			<!-- We're calculating sizes based on this
range. -->
			<outgoing name="{@name}"
index="{count(preceding-sibling::*[not(@size)])
							+ $base
							+
sum(preceding-sibling::*/size)}" />
		  </xsl:when>
		  <xsl:otherwise>
			<!-- There is a preceding node with an index
attribute, so we're indexing from it.-->
			<!-- mypos. Number of nodes preceding current.
-->
			<xsl:variable name="mypos"
select="count(preceding-sibling::*)" />
			<!-- preindex. Set of all nodes before the most
recent one with an 'index' attribute. -->
			<xsl:variable name="preindex"

select="(preceding-sibling::*[@index][1])/preceding-sibling::*" />
			<!-- allpostindex. Set of all nodes after the
most recent one with an 'index'
				 attribute. Note - to the very end of
the input set. -->
			<xsl:variable name="allpostindex"

select="(preceding-sibling::*[@index][1])/following-sibling::*" />
			<!-- postindex. Set of all nodes from the one
after the most recent one with an
					'index' attribute to the current
position. -->
			<xsl:variable name="postindex"
					select="$allpostindex[position()
&lt; ($mypos - count($preindex))]" />
			<!-- To calculate the current index we add :
				 The value of the most recent 'index'
incoming,
				 The number of nodes since the most
recent 'index' incoming with no 'size',
				 The sum of any 'size' attributes since
the most recent 'index' incoming,
				 The 'size', if there is one, of the
most recent 'index' incoming, or
					its size of '1' if not
specified.
			-->
			<outgoing name="{@name}" index="{

preceding-sibling::*[@index][1]/@index
					+ count( $postindex[not(@size)]
)
					+ sum( $postindex[@size]/@size )
					+
(preceding-sibling::*[@index][1]/@size,1)[1]
					}" />
		  </xsl:otherwise>
		</xsl:choose>

	  </xsl:otherwise>
	</xsl:choose>
  </xsl:template>


> -----Original Message-----
> From: Kerry, Richard
> Sent: 02 October 2007 16:34
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: RE: [xsl] Using the Input Document to Control
> Generation of Numbers in the Output
>
>
> Thank you both for your various solutions, and to George for the
> detailed explanation of his non-recursive solutions.
>
> I had wondered about the "(@size, 1)[1]" term, which I'd not
> seen before
> but I think I have now worked out.  If there is no "size"
> attribute, the
> term "@size" returns null and thus does not appear as a first node in
> the node-set, ie what's within the (), and thus [1] returns
> what appears
> to be the second element, the '1'.
>
> Given that I understand the recursive solution better, and I
> can easily
> extend it to do both size and index overrides together I'm going with
> that for the moment.  If I get stack problems I'll need to
> re-implement
> it using the non-recursive method.
>
> My xsl, now doing both overrides, is listed below.
>
> Appreciatively,
> Richard.
>
>
>
> <!-- The incoming size attribute is the size of this element
> so doesn't
> affect its index but affects all following.
> 	 The incoming index attribute affects this outgoing one and all
> following.   -->
>
> <xsl:template match="incoming">
>   <xsl:param name="given-index"/>
>   <!-- Use inputted index here unless overridden. -->
>   <xsl:variable name="this-index" select="(@index, $given-index)[1]"/>
>   <outgoing name="{@name}" index="{$this-index}"/>;
>   <!-- Calculate the new index to pass into the next pass. -->
>   <xsl:variable name="next-index" select="$this-index +
> (@size, 1)[1]"/>
>   <xsl:apply-templates select="following-sibling::incoming[1]">
>     <xsl:with-param name="given-index" select="$next-index"/>
>   </xsl:apply-templates>
> </xsl:template>
>
> <!-- and then fire the process off with -->
>
> <xsl:template match="parent">
>   <xsl:apply-templates select="incoming[1]">
>     <xsl:with-param name="given-index" select="1"/>
>   </xsl:apply-templates>
> </xsl:template>
>
>
>
>
>
>
> -----Original Message-----
> From: George Cristian Bina [mailto:george@xxxxxxxxxxxxx]
> Sent: 02 October 2007 15:42
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: Re: [xsl] Using the Input Document to Control Generation of
> Numbers in the Output
>
> Hi Richard,
>
> Kerry, Richard wrote:
> [...]
> > Since I wrote the above I've received George's solution for
> problem 3,
> > which I don't yet understand.
>
>
>   <one:one xmlns:one="http://www.example.com/one"; index="1"/>
>
> Adds an element in a namespace different than the XSLT
> namespace, just
> to be selected by the variable below.
>
>   <xsl:variable name="one" select="document('')/*/one:one"
> xmlns:one="http://www.example.com/one"/>
>
> The one variable points to the one:one element above, I need
> that below
> to get the default value 1 in case no previous index is found.
>
>   <xsl:template match="incoming">
>
> We are on an incoming element.
>
>    <xsl:choose>
>     <xsl:when test="@index">
>
> If that specifies an index then we just use it.
>
>      <ougoing name="{@name}" index="{@index}"/>
>     </xsl:when>
>     <xsl:otherwise>
>
> otherwise we count
>
>      <outgoing name="{@name}" index="{count(preceding-sibling::*) +
>
> preceding siblings
>
>       (preceding-sibling::*[@index][1]/@index|$one/@index)[1] -
> the index value of the first preceding sibling that has an index
> attribute or, if there is no preceding sibling with an index
> attribute
> we use the index from the one variable, that means the value 1
>
>       count(preceding-sibling::*[@index][1]/preceding-sibling::*)
>
> and we remove the preceding elements after the first element
> that has an
>
> index as we added all the preceding elements in the first count above
>
>       }"/>
>
>
>
>     </xsl:otherwise>
>    </xsl:choose>
>
> For a large number of elements you may end out of stack with
> a recursive
>
> approach unless the processor optimizes that.
>
> In case you are not using XSLT 2.0 then you need to solve
> (@size, 1)[1]
> from Mike's code with something like the one:one approach from my
> example:
>
>   <one:one xmlns:one="http://www.example.com/one";>1</one:one>
>   <xsl:variable name="one" select="document('')/*/one:one"
> xmlns:one="http://www.example.com/one"/>
>
>
>   <xsl:template match="incoming">
>    <xsl:param name="total-so-far"/>
>    <xsl:variable name="new-total" select="$total-so-far + (@size |
> $one)[1]"/>;
>    <outgoing name="{@name}" index="{$new-total}"/>
>    <xsl:apply-templates select="following-sibling::incoming[1]">
>     <xsl:with-param name="total-so-far" select="$new-total"/>
>    </xsl:apply-templates>
>   </xsl:template>
>
> Best Regards,
> George
> ---------------------------------------------------------------------
> George Cristian Bina - http://aboutxml.blogspot.com/
> <oXygen/> XML Editor, Schema Editor and XSLT Editor/Debugger
> http://www.oxygenxml.com

Current Thread