I have finally understood the problem ...
The previous two solutions that I posted can be proven incorrect, although
it is quite challenging to construct the necessary XML document for this
purpose.
Having a good set of test cases is essential for producing correct
solutions -- in this case the provided XML document allowed multiple
incorrect solutions to produce the wanted result.
Here is an XSLT 2 solution -- a two-pass transformation. In the first pass
all adjacent <step> elements are converted to a single <step> element that
has as children the children of all the adjacent <step> elements.
The second pass is essentially similar to the previously posted code --
recursion processing each child of a <step> -- one at a time.
The line-count is 58 -- not too big for such problem and can probably be
further optimized. No <xsl:iterate> and no <xsl:for-each-group> is used and
this solution can be converted to XSLT 1.0.
<xsl:stylesheet version="2.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAdjStepsChildren" match="step/*"
use="generate-id((../preceding-sibling::*[not(self::step)][1]/following-sibling::*[1]
| ..)[1])"/>
<xsl:variable name="vPass1"><xsl:apply-templates
select="/*"/></xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="$vPass1/*" mode="pass2"/>
</xsl:template>
<xsl:template match="@*|node()" mode="copySingle">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|@*" mode="#default pass2">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]" mode="#current"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="#current"/>
</xsl:template>
<xsl:template match="step"/>
<xsl:template match="step[key('kAdjStepsChildren', generate-id())]">
<step>
<xsl:apply-templates select="key('kAdjStepsChildren', generate-id())"
mode="adjust"/>
</step>
<xsl:apply-templates select="following-sibling::*[1]"/>
</xsl:template>
<xsl:template match="step/*" mode="adjust">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="step" mode="pass2">
<xsl:apply-templates select="*[1] | following-sibling::*[1]"
mode="pass2"/>
</xsl:template>
<xsl:template match="step/*" mode="pass2">
<xsl:param name="pAdjustments" select="0"/>
<xsl:variable name="vNeedsAdjustment" select="self::figure and
(count(preceding-sibling::*) +1 + $pAdjustments) mod 2 = 1"/>
<xsl:if test="$vNeedsAdjustment">
<spacer/>
</xsl:if>
<xsl:apply-templates select="." mode="copySingle"/>
<xsl:apply-templates select="following-sibling::*[1]" mode="pass2">
<xsl:with-param name="pAdjustments" select="$pAdjustments +
(if($vNeedsAdjustment) then 1 else 0 )"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Cheers,
Dimitre
On Sat, Oct 26, 2019 at 6:24 PM Dimitre Novatchev dnovatchev@xxxxxxxxx <
xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx> wrote:
> Actually, we don't need 2-pass processing!
>
> Below is a really short and simple *XSLT 1* transformation -- it is just
> 26 lines -- *40% shorter* than the XSLT 3.0 transformation provided by
> Martin.
>
> Enjoy:
>
> <xsl:stylesheet version="1.0" xmlns:xsl="
> http://www.w3.org/1999/XSL/Transform">
> <xsl:output omit-xml-declaration="yes" indent="yes"/>
> <xsl:strip-space elements="*"/>
>
> <xsl:template match="node()|@*" name="identity">
> <xsl:copy>
> <xsl:apply-templates select="node()|@*"/>
> </xsl:copy>
> </xsl:template>
>
> <xsl:template match="step"><xsl:apply-templates
> select="*[1]"/></xsl:template>
>
> <xsl:template match="step/*">
> <xsl:param name="pAdjustments" select="0"/>
> <xsl:variable name="vNeedsAdjustment" select="self::figure and
> (count(preceding-sibling::*) +1 + $pAdjustments) mod 2 = 1"/>
>
> <xsl:if test="$vNeedsAdjustment">
> <spacer/>
> </xsl:if>
> <xsl:call-template name="identity"/>
>
> <xsl:apply-templates select="following-sibling::*[1]">
> <xsl:with-param name="pAdjustments" select="$pAdjustments +
> $vNeedsAdjustment"/>
> </xsl:apply-templates>
> </xsl:template>
> </xsl:stylesheet>
>
> Cheers,
> Dimitre
>
>
> On Sat, Oct 26, 2019 at 4:22 PM Dimitre Novatchev dnovatchev@xxxxxxxxx <
> xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx> wrote:
>
>> > Now waiting for Dimitre posting a less "fancy"
>> > but equally compact XSLT 1 solution :)
>>
>> The following is a 48-lines XSLT 2.0 solution (2 passes -- equivalent
>> XSLT 1.0 solution is easy to produce but will need the vendor:node-set()
>> extension function).
>>
>> Martin's XSLT 3.0 solution is 43 lines when <xsl:stylesheet> and
>> <xsl:output> are added. So the difference in number of lines is not big --
>> 12% and I believe the XSLT 2 solution below is less complex and more easily
>> understandable.
>>
>> Maybe I am biased, but I personally believe that it is better to use the
>> standard XPath 3 functions fold-left() and fold-right() in the spirit of
>> functional programming. <xsl:iterate> instead helps people avoid thinking
>> using the concepts of functional programming. Also, its definition is so
>> complex that I personally needed more than an hour to find out / construct
>> what cases of its usage are possible and which are mutually exclusive.
>>
>> Here is the transformation:
>>
>> <xsl:stylesheet version="2.0" xmlns:xsl="
>> http://www.w3.org/1999/XSL/Transform">
>> <xsl:output omit-xml-declaration="yes" indent="yes"/>
>> <xsl:strip-space elements="*"/>
>>
>> <xsl:variable name="vPass1Result"><xsl:apply-templates
>> select="/*"/></xsl:variable>
>>
>> <xsl:template match="node()|@*" mode="#default pass2">
>> <xsl:copy>
>> <xsl:apply-templates select="node()|(@* except @stepChild)"
>> mode="#current"/>
>> </xsl:copy>
>> </xsl:template>
>>
>> <xsl:template match="/">
>> <xsl:apply-templates select="$vPass1Result/*" mode="pass2"/>
>> </xsl:template>
>>
>> <xsl:template match="step"><xsl:apply-templates/></xsl:template>
>>
>> <xsl:template match="step/*">
>> <xsl:copy>
>> <xsl:apply-templates select="@*"/>
>> <xsl:attribute name="stepChild">
>> <xsl:number level="any" count="/*/step/*"/>
>> </xsl:attribute>
>> <xsl:apply-templates select="node()"/>
>> </xsl:copy>
>> </xsl:template>
>>
>> <xsl:template match="/*" mode="pass2">
>> <xsl:copy>
>> <xsl:apply-templates select="*[1]" mode="pass2"/>
>> </xsl:copy>
>> </xsl:template>
>>
>> <xsl:template match="/*/*" mode="pass2">
>> <xsl:param name="pAdjustments" select="0"/>
>> <xsl:variable name="vNeedsAdjustment" select="self::figure and
>> (count(preceding-sibling::*[@stepChild]) +1 + $pAdjustments) mod 2 = 1"/>
>>
>> <xsl:if test="$vNeedsAdjustment">
>> <spacer/>
>> </xsl:if>
>> <xsl:next-match/>
>>
>> <xsl:apply-templates select="following-sibling::*[1]" mode="pass2">
>> <xsl:with-param name="pAdjustments" select="$pAdjustments +
>> (if($vNeedsAdjustment) then 1 else 0)"/>
>> </xsl:apply-templates>
>> </xsl:template>
>> </xsl:stylesheet>
>>
>>
>> Cheers,
>> Dimitre
>>
>>
>>
>> On Sat, Oct 26, 2019 at 12:06 PM Martin Honnen martin.honnen@xxxxxx <
>> xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx> wrote:
>>
>>> On 26.10.2019 19:03, Rick Quatro rick@xxxxxxxxxxxxxx wrote:
>>>
>>> > I need to process the <step> child elements so that the <figure>
>>> > elements are always on the "right" (even-numbered position) in the
>>> > output. Immediate children of the <procedure> do not factor into the
>>> > odd/even sequence.
>>> >
>>> > The first child of each group of adjacent <step> elements starts a new
>>> > odd/even series. To ensure that the each <figure> is in an
>>> even-numbered
>>> > position, I want to insert a <spacer> element where it is required.
>>>
>>> > Here is my stylesheet. My basic question is: is there a better or more
>>> > efficient way to do this?
>>>
>>> I think with XSLT 3 it is possible to use xsl:iterate on the child
>>> elements of the adjacent steps found by grouping:
>>>
>>> <xsl:mode on-no-match="shallow-copy"/>
>>>
>>> <xsl:template match="procedure">
>>> <xsl:copy>
>>> <xsl:for-each-group select="*"
>>> group-adjacent="boolean(self::step)">
>>> <xsl:choose>
>>> <xsl:when test="current-grouping-key()">
>>> <xsl:iterate select="current-group()/*">
>>> <xsl:param name="position-in-output"
>>> select="1"/>
>>> <xsl:apply-templates select=".">
>>> <xsl:with-param
>>> name="position-in-output" select="$position-in-output"/>
>>> </xsl:apply-templates>
>>> <xsl:next-iteration>
>>> <xsl:with-param
>>> name="position-in-output"
>>> select="if (self::figure and
>>> $position-in-output mod 2 = 1)
>>> then $position-in-output + 2
>>> else $position-in-output +
>>> 1"/>
>>> </xsl:next-iteration>
>>> </xsl:iterate>
>>> </xsl:when>
>>> <xsl:otherwise>
>>> <xsl:apply-templates select="current-group()"/>
>>> </xsl:otherwise>
>>> </xsl:choose>
>>> </xsl:for-each-group>
>>> </xsl:copy>
>>> </xsl:template>
>>>
>>> <xsl:template match="step/figure">
>>> <xsl:param name="position-in-output"/>
>>> <xsl:if test="$position-in-output mod 2 = 1">
>>> <spacer/>
>>> </xsl:if>
>>> <xsl:next-match/>
>>> </xsl:template>
>>>
>>>
>>>
>>> Now waiting for Dimitre posting a less "fancy" but equally compact XSLT
>>> 1 solution :)
>>>
>>>
>>
>> XSL-List info and archive <http://www.mulberrytech.com/xsl/xsl-list>
> EasyUnsubscribe <http://lists.mulberrytech.com/unsub/xsl-list/782854> (by
> email <>)
|