RE: [xsl] Tricky inclusion match

Subject: RE: [xsl] Tricky inclusion match
From: "Aron Bock" <aronbock@xxxxxxxxxxx>
Date: Tue, 29 Mar 2005 17:27:18 +0000
Karl, here's an XSLT 1.0 solution:

My basis for this are templates from Sal Mangano's "XSLT Cookbook"; please search this list for other times I've referenced this resource, including mentioning from where to download its code.

My approach is to hold the <colors> element in a variable, and then to compare its children against those of each <picture> element, in an "intersection" operation. If we have 2 or more intersections, print:

Regards,

--A



colors.xsl
======

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset"
extension-element-prefixes="vset">


   <xsl:import href="vset.ops.xsl"/>
   <xsl:output method="text" />


<xsl:template match="/"> <xsl:variable name="colorlist" select="/data/colors"/> <xsl:for-each select="/data/pictures/picture"> <xsl:variable name="matchcount"> <xsl:call-template name="compare"> <xsl:with-param name="node1" select="$colorlist"/> <xsl:with-param name="node2" select="."/> </xsl:call-template> </xsl:variable>

           <xsl:if test="string-length($matchcount) &gt;= 2">
               <xsl:text>picture sample #</xsl:text>
               <xsl:value-of select="@sample"/>
               <xsl:text>&#xa;</xsl:text>
           </xsl:if>
       </xsl:for-each>
   </xsl:template>


<xsl:template name="compare"> <xsl:param name="node1"/> <xsl:param name="node2"/>

       <xsl:call-template name="vset:intersection">
           <xsl:with-param name="nodes1" select="$node1//*"/>
           <xsl:with-param name="nodes2" select="$node2//*"/>
       </xsl:call-template>
   </xsl:template>


<xsl:template match="*" mode="vset:intersection"> <xsl:value-of select="'*'"/> </xsl:template>

<xsl:template match="node( ) | @*" mode="vset:element-equality">
<xsl:param name="other"/>
<xsl:if test="local-name(.) = local-name($other) and ./text() = $other/text()">
<xsl:value-of select="true()"/>
</xsl:if>
</xsl:template>


</xsl:stylesheet>



vset.ops.xsl
======

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">


<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<!-- The default implementation of element equality. Override in the importing
stylesheet as neccessary. -->
<xsl:template match="node( ) | @*" mode="vset:element-equality">
<xsl:param name="other"/>
<xsl:if test=". = $other">
<xsl:value-of select="true( )"/>
</xsl:if>
</xsl:template>



<!-- The default set membership test uses element equality. You will rarely need to
override this in the importing stylesheet. -->
<xsl:template match="node( ) | @*" mode="vset:member-of">
<xsl:param name="elem"/>
<xsl:variable name="member-of">
<xsl:for-each select=".">
<xsl:apply-templates select="." mode="vset:element-equality">
<xsl:with-param name="other" select="$elem"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string($member-of)"/>
</xsl:template>



<!-- Compute the union of two sets using "by value" equality. -->
<xsl:template name="vset:union">
<xsl:param name="nodes1" select="/.." />
<xsl:param name="nodes2" select="/.." />
<!-- for internal use -->
<xsl:param name="nodes" select="$nodes1 | $nodes2" />
<xsl:param name="union" select="/.." />
<xsl:choose>
<xsl:when test="$nodes">
<xsl:variable name="test">
<xsl:apply-templates select="$union" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes[1]" />
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="vset:union">
<xsl:with-param name="nodes" select="$nodes[position( ) &gt; 1]" />
<xsl:with-param name="union" select="$union | $nodes[1][not(string($test))]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$union" mode="vset:union" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>



<!-- Return a copy of union by default. Override in importing stylesheet to recieve
reults as a "callback"-->
<xsl:template match="/ | node( ) | @*" mode="vset:union">
<xsl:copy-of select="."/>
</xsl:template>



<!-- Compute the intersection of two sets using "by value" equality. --> <xsl:template name="vset:intersection"> <xsl:param name="nodes1" select="/.."/> <xsl:param name="nodes2" select="/.."/> <!-- For internal use --> <xsl:param name="intersect" select="/.."/>

<xsl:choose>
<xsl:when test="not($nodes1)">
<xsl:apply-templates select="$intersect" mode="vset:intersection"/>
</xsl:when>
<xsl:when test="not($nodes2)">
<xsl:apply-templates select="$intersect" mode="vset:intersection"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="test1">
<xsl:apply-templates select="$nodes2" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:variable name="test2">
<xsl:apply-templates select="$intersect" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:choose>
<xsl:when test="string($test1) and not(string($test2))">
<xsl:call-template name="vset:intersection">
<xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="intersect" select="$intersect | $nodes1[1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="vset:intersection">
<xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="intersect" select="$intersect"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>



<!-- Return a copy of intersection by default. Override in importing stylesheet to
recieve results as a "callback"-->
<xsl:template match="/ | node( ) | @*" mode="vset:intersection">
<xsl:copy-of select="."/>
</xsl:template>



<!-- Compute the differnce between two sets (node1 - nodes2) using "by value" equality. -->
<xsl:template name="vset:difference">
<xsl:param name="nodes1" select="/.."/>
<xsl:param name="nodes2" select="/.."/>
<!-- For internal use -->
<xsl:param name="difference" select="/.."/>


<xsl:choose>
<xsl:when test="not($nodes1)">
<xsl:apply-templates select="$difference" mode="vset:difference"/>
</xsl:when>
<xsl:when test="not($nodes2)">
<xsl:apply-templates select="$nodes1" mode="vset:difference"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="test1">
<xsl:apply-templates select="$nodes2" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:variable name="test2">
<xsl:apply-templates select="$difference" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:choose>
<xsl:when test="string($test1) or string($test2)">
<xsl:call-template name="vset:difference">
<xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="difference" select="$difference"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="vset:difference">
<xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="difference" select="$difference | $nodes1[1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>



<!--
Computes the differnce between two sets (node1 - nodes2) using "by value" equality.
"Short-circuit"-s processing at the first difference-node.
-->
<xsl:template name="vset:difference-short-circuit">
<xsl:param name="nodes1" select="/.."/>
<xsl:param name="nodes2" select="/.."/>
<!-- For internal use -->
<xsl:param name="difference" select="/.."/>


<xsl:choose>
<xsl:when test="not($nodes1)">
<xsl:apply-templates select="$difference" mode="vset:difference"/>
</xsl:when>
<xsl:when test="not($nodes2)">
<xsl:apply-templates select="$nodes1" mode="vset:difference"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="test1">
<xsl:apply-templates select="$nodes2" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:variable name="test2">
<xsl:apply-templates select="$difference" mode="vset:member-of">
<xsl:with-param name="elem" select="$nodes1[1]"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:choose>
<xsl:when test="string($test1) or string($test2)">
<xsl:call-template name="vset:difference-short-circuit">
<xsl:with-param name="nodes1" select="$nodes1[position( ) &gt; 1]"/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="difference" select="$difference"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="vset:difference-short-circuit">
<xsl:with-param name="nodes1" select="/.."/>
<xsl:with-param name="nodes2" select="$nodes2"/>
<xsl:with-param name="difference" select="$difference | $nodes1[1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>



<!-- Return a copy of difference by default. Override in importing stylesheet to
recieve results as a "callback"-->
<xsl:template match="/ | node( ) | @*" mode="vset:difference">
<xsl:copy-of select="."/>
</xsl:template>


</xsl:stylesheet>





From: Karl Stubsjoen <kstubs@xxxxxxxxx>
Reply-To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: [xsl] Tricky inclusion match
Date: Tue, 29 Mar 2005 07:55:43 -0700

Okay,
Lets say you have a set of colors and you have a whole bunch of
pictures and you want to match on all pictures who have one or more of
the given colors but you have to at least match on 2 of them (unique,
so not red and red - so a picture could list red twice but that would
not be a match).  I'm trying to use keys and grouping to solve this
but thinking that I might be making this more difficult.  At any rate,
I'm stuck and would appreciate some help : )

Sample Data (expected results below):

<data>
    <colors>
    	<color>red</color>
    	<color>blue</color>
    	<color>fucia</color>
    	<color>violet</color>
    </colors>
    <pictures>
    	<picture sample="1">
        	<color>black</color>
        	<color>grey</color>
        	<color>white</color>
        </picture>
    	<picture sample="2">
        	<color>red</color>
        	<color>green</color>
        	<color>brown</color>
        	<color>blue</color>
        </picture>
    	<picture sample="3">
        	<color>purple</color>
        	<color>orange</color>
        </picture>
    	<picture sample="4">
        	<color>blue</color>
        	<color>green</color>
        	<color>red</color>
        </picture>
    	<picture sample="5">
        	<color>fucia</color>
        	<color>green</color>
        	<color>violet</color>
        </picture>
    	<picture sample="6">
        	<color>red</color>
        	<color>brown</color>
        	<color>red</color>
        </picture>
    </pictures>
</data>

Expected Results (picture matches based on 2 or more colors used and
listed in colors above)

picture sample #2
picture sample #4
picture sample #5


_________________________________________________________________
Dont just search. Find. Check out the new MSN Search! http://search.msn.click-url.com/go/onm00200636ave/direct/01/


Current Thread