[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message] Re: Feedback on grouping solution
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 <>)
|
PURCHASE STYLUS STUDIO ONLINE TODAY!Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced! Download The World's Best XML IDE!Accelerate XML development with our award-winning XML IDE - Download a free trial today! Subscribe in XML format
|