Re: [xsl] Tricky inclusion match

Subject: Re: [xsl] Tricky inclusion match
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Thu, 31 Mar 2005 15:49:25 +1000
On Tue, 29 Mar 2005 17:27:18 +0000, Aron Bock <aronbock@xxxxxxxxxxx> wrote:
> 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:

Seems a little bit too long.

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
 
 <xsl:output omit-xml-declaration="yes"/>
 
 <xsl:variable name="vColors" select="/*/colors"/>

  <xsl:template match="/">
    <xsl:for-each select="/*/*/picture">
      <xsl:if test="count($vColors/*[. = current()/color]) >= 2">
        <xsl:value-of select="concat('Picture Id=', @sample, '&#xA;')"/>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

when performed against the originally posted source xml document,
produces the wanted result:

Picture Id=2
Picture Id=4
Picture Id=5


This is really simple. No xxx:node-set() has been used.

Cheers,
Dimitre Novatchev


> 
> 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) >= 2">
>                <xsl:text>picture sample #</xsl:text>
>                <xsl:value-of select="@sample"/>
>                <xsl:text>
> </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(  ) > 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(  ) >
> 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(  ) >
> 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(  ) >
> 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(  ) >
> 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(  ) >
> 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
> >
> 
> _________________________________________________________________
> Don't just search. Find. Check out the new MSN Search!
> http://search.msn.click-url.com/go/onm00200636ave/direct/01/

Current Thread