Search

This is a nasty one, for me at least. Is anyone up for helping me figure it out?

Jump in my SO question:

http://stackoverflow.com/questions/7652982/grouping-nodes-based-on-a-rule-set


Symphony-specific note

I have a variable layout situation for some images, and I would like to avoid tying the data structure to the layout by providing multiple upload fields on the same entry.
Each image upload has a multi-select option for left, right, top-left, top-right, bottom-left, etc.

The data source gives these out as siblings, obviously, but I need to be able to group, say, a left image with a right one (or with a top-right and a bottom-right) if that makes sense.

I also know it can be easily achieved via DOM manipulation, but I'm just getting in an XSL trip I'd like to solve. A nice utility could come out of this…

I'm about to answer my own question on SO.

This is the solution I've found to work:

<!-- 
    For each /item:

    - see it it's one of the starting points of a sequence:
        - Not "Standalone"
        - Left, Top-Left

    - if standalone, just return the element
    - if not standlone and not a starting point, skip it, since it means it being added by the previous item.
    - if not standolw and either of the starting points, kick into a recursion loop in a separate template:

    - store the item's current "score" (2 or 1 for single-quadrant images)
    - recur through the following-siblings with a counter for position(), checking if they are in the allowed list, and decreasing the "score" counter.
    - every time a match is found:
        - recreate the "allow" list, minus the current match, and pass the updated list to the next iteration
        - decrease the counter
    - if the iteration completes, reaching zero, return the position() of the last matched item
    - if during the iteration, while the score is still >0, a match is not found, return false(). Our sequence is broken, we have a user error.

    - the calling template (the one matching *every* item) checks whether the returned result is >0 or false()
    - if >0 returns a copy of every node up the number specified by >0
    - if false() print out and error, suggesting possible sequences.
-->

<xsl:variable name="layouts">
    <start handle="left"            score="2">      <!-- The starting score which we'll subtract from on every iteration -->
        <allow handle="right"           value="2"/> <!-- the acceptable position which we'll check against on every iteration -->
        <allow handle="top-right"       value="1"/> <!-- the value for each position which we'll subtract from the <start> score -->
        <allow handle="bottom-right"    value="1"/>
    </start>
    <start handle="top-left"        score="3">
        <allow handle="right"           value="2"/>
        <allow handle="bottom-left"     value="1"/>
        <allow handle="top-right"       value="1"/>
        <allow handle="bottom-right"    value="1"/>
    </start>
    <start handle="full"            score="0"/> <!-- Position which are not acceptable as the start of a sequence are scored 0 -->
    <start handle="right"           score="0"/>
    <start handle="top-right"       score="0"/>
    <start handle="bottom-right"    score="0"/>
    <start handle="bottom-left"     score="0"/>
</xsl:variable>

<!-- Applied to every /item -->
<xsl:template mode="imagewraps" match="item">
    <xsl:param name="i" select="position()"/>
    <xsl:variable name="nodeName"   select="name(.)"/>
    <xsl:variable name="layout"     select="exsl:node-set($layouts)"/>
    <xsl:variable name="position"   select="position/item/@handle"/>
    <xsl:variable name="score"      select="$layout/start[@handle = $position]/@score"/>
    <xsl:variable name="allowList"  select="$layout/start[@handle = $position]"/>

    <!-- This variable will store the final result of the recursion lanunched from within.
         The returned value will be a number, indication the position of the last node that is part of the sequence -->
    <xsl:variable name="sequenceFound">
        <xsl:if test="$score > 0">
            <xsl:apply-templates mode="test" select="parent::node()/*[name() = $nodeName][$i +1]">
                <xsl:with-param name="i" select="$i +1"/>
                <xsl:with-param name="score"        select="$score"/>
                <xsl:with-param name="allowList"    select="$allowList"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:variable>


    <div style="border: 1px solid red">
    <xsl:choose>
        <!-- If the $score is 0 and the position is 'full' just return a copy if the current node -->
        <xsl:when test="$score = 0 and $position = 'full'">
            <xsl:copy-of select="."/>
        </xsl:when>
        <!-- if the $score is greater than 0, return a copy of the current node
             and the siblings the follow, up to the value stored in $sequenceFound -->
        <xsl:when test="$score > 0">
            <xsl:choose>
                <!-- Actually do the above only if $sequenceFound didn't end up being 0
                     (it currently never does, but good to have as an option to handle errors in here) -->
                <xsl:when test="$sequenceFound != 0">
                    <xsl:copy-of select="."/>
                    <xsl:copy-of select="following-sibling::*[$sequenceFound - $i >= position()]"/>
                </xsl:when>
            </xsl:choose>
        </xsl:when>
        <!-- If the first item is wrong, let jsut say it -->
        <xsl:when test="$score = 0 and position() > 1">
            <xsl:message>The first item should either be "full", "left", "top-left".</xsl:message>
        </xsl:when>
    </xsl:choose>
    </div>
</xsl:template>

<xsl:template mode="test" match="*">
    <xsl:param name="i"/>
    <xsl:param name="score"/>
    <xsl:param name="allowList"/>
    <xsl:variable name="this" select="."/>
    <xsl:variable name="nodeName"       select="name()"/>
    <xsl:variable name="position"       select="position/item/@handle"/>
    <xsl:variable name="isInAllowList"  select="count($allowList/allow[@handle = $position]) > 0"/>
    <xsl:variable name="value">
        <xsl:if test="$isInAllowList">
            <xsl:value-of select="$allowList/allow[@handle = $position]/@value"/>
        </xsl:if>
    </xsl:variable>
    <xsl:variable name="allowListMinusMatched">
        <xsl:if test="$isInAllowList">
            <xsl:copy-of select="$allowList/allow[@handle != $position]"/>
        </xsl:if>
    </xsl:variable>

    <xsl:choose>
        <xsl:when test="$isInAllowList">
            <xsl:choose>
                <!-- if we've not ran out of loops, continue -->
                <xsl:when test="($score - $value) > 0">
                    <xsl:apply-templates mode="test" select="parent::node()/*[name() = $nodeName][$i +1]">
                        <xsl:with-param name="i" select="$i +1"/>
                        <xsl:with-param name="allowList" select="$allowListMinusMatched"/>
                        <xsl:with-param name="score" select="$score - $value"/>
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:when test="($score - $value) = 0">
                    <xsl:value-of select="$i"/>
                </xsl:when>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="layout"     select="exsl:node-set($layouts)"/>
            <xsl:variable name="allowed"  select="$layout/start[@handle = $position]"/>
            <xsl:message>Bombing out. Wrong Sequence.</xsl:message>
            <xsl:message>
                Items allowed after "<xsl:value-of select="$allowed/@handle"/>" are:
                <xsl:for-each select="$allowed/allow">
                    <xsl:value-of select="@handle"/>
                    <xsl:if test="count($allowed/allow) > position()">, </xsl:if>
                    <xsl:if test="count($allowed/allow) = position()">.</xsl:if>
                </xsl:for-each>
            </xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Hope someone can take some inspiration out of it.

And I am also immensely grateful to the dude who posted an answer just after I figured it out on my own. Pretty nice trick he used. Be sure to check it out http://stackoverflow.com/questions/7652982/grouping-nodes-based-on-a-rule-set

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details