tags:

views:

72

answers:

3

Sorry about the title — wasn’t sure how to word it.

Basically I have some XML like this:

<countries>
    <country handle="bangladesh"/>
    <country handle="india"/>
    <country handle="pakistan"/>
</countries>

And some XSLT like this (which doesn’t work):

<xsl:template match="/countries">
    <xsl:param name="popular"/>        
    <xsl:apply-templates select="country[count($popular/country[@handle = current()/@handle]) &gt; 0]" />
</xsl:template>    
<xsl:template match="/countries/country">
    …
</xsl:template>

I want to pass in a list of popular destinations like this:

<popular>
    <country handle="india"/>
    <country handle="pakistan"/>
</popular>

…to the /countries template and have it only operate on the ones in the $popular param. At the moment this simply does nothing. Changing the selector to country[true()] operates on them all, so at least I know the basic structure is right.

Any ideas? I think I may be getting confused by what is currently “current()”.

A: 

Here's how you can do with a recursive template. You must pass the popular destinations as a comma separated list (like this: "'india,pakistan,'")

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="/countries">
    <xsl:param name="popular" select="'india,pakistan,'" />
    <xsl:for-each select="country">
        <xsl:call-template name="print-country-from-list">
      <xsl:with-param name="pc" select="."/>
      <xsl:with-param name="listc" select="$popular"/>
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template match="/countries/country">
   ... <xsl:value-of select="@handle"/>
</xsl:template>

<xsl:template name="print-country-from-list">
  <xsl:param name="pc"/>
  <xsl:param name="listc" select="''"/>
  <xsl:variable name="h" select="substring-before($listc, ',')"/>
  <xsl:if test="$h">
    <xsl:choose>
      <xsl:when test="$pc/@handle=$h">
        <xsl:apply-templates select="$pc" />
      </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="print-country-from-list">
        <xsl:with-param name="pc" select="$pc"/>
        <xsl:with-param name="listc" select="substring-after($listc, ',')"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

The output is the 2 countries.

True Soft
+1  A: 

It's a lot simpler than you think:

<xsl:template match="/">
  <popular>
    <xsl:copy-of select="/countries/country[@handle=$popular/country/@handle]"/>
  </popular>
</xsl:template>

Edit

The above was simply showing what was wrong with the OP's original XPath query. Here's a full working example:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:param name="popular"/>

  <xsl:template match="/">
      <xsl:apply-templates select="/countries">
        <xsl:with-param name="popular" select="$popular"/>
      </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="/countries">
    <xsl:param name="popular"/>
    <countries>
      <xsl:apply-templates select="country[@handle=$popular/country/@handle]"/>
    </countries>
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template> 

</xsl:stylesheet>

...and a program to call it:

static void Main(string[] arguments)
{
    XslCompiledTransform xslt = new XslCompiledTransform();
    xslt.Load("xsltfile1.xslt");

    XmlDocument d = new XmlDocument();
    d.LoadXml(@"
<popular>
  <country handle='india'/>
  <country handle='xxx'/>
</popular>");

    XsltArgumentList args = new XsltArgumentList();
    args.AddParam("popular", "", d.DocumentElement);
    xslt.Transform("xmlfile1.xml", args, Console.Out);
    Console.ReadKey();
}
Robert Rossney
Did the OP ever mention he needed a C#-bound solution?
Dimitre Novatchev
The XSLT isn't C#-bound. You can pass parameters into XSLT transforms from any language that has an XSLT processor.
Robert Rossney
Excellent, thank you—I new it would be something simple like this. I’ve been working with XSLT for a while now but for some reason had a complete mental block on this one.Thanks again.
davecardwell
@Robert Rossney: I don't think this is a good answer! Matching root and matching root element next? Also, shadowing parameter in template rule?
Alejandro
+1  A: 

The solution to this problem is simple and straightforward (no need for string encoding or recursion).

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pPopular">
    <country handle="india"/>
    <country handle="pakistan"/>
 </xsl:param>

 <xsl:variable name="vPopular" 
  select="document('')/*/xsl:param[@name='pPopular']"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="country">
  <xsl:if test="@handle = $vPopular/*/@handle">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<countries>
    <country handle="bangladesh"/>
    <country handle="india"/>
    <country handle="pakistan"/>
</countries>

produces the wanted, correct result:

<countries>
    <country handle="india"/>
    <country handle="pakistan"/>
</countries>
Dimitre Novatchev
What's the point of creating $vPopular? Why can't you just use $pPopular? In fact, won't doing it this way make the stylesheet produce the same results irrespective of what value the calling program passes in via the pPopular parameter?
Robert Rossney
@Robert-Rossney: On the contrary. Have you heard of RTF? If you want the parameter's default values to be accessible and useful, you have to take care of converting the RTF to a "normal" tree. Also, this is a good coding practice to be able to test the stylesheet independently of the external caller.
Dimitre Novatchev
When I test this transform in .NET, it ignores whatever value of $pPopular my program passes in and always uses the value present in the XSLT file. The XSLT recommendation does not define the mechanism whereby parameters get passed into XSLT transforms; it looks like the technique you're using assumes that the mechanism is actual modification of the XSLT document tree.
Robert Rossney
@Robert-Rossney: I think this was a bug in .NET 1.1. It must have been fixed in .NET 2.
Dimitre Novatchev
I tested this with .NET 3.5. Also, I don't think .NET's behavior is incorrect. I can find nothing in the XSLT recommendation that specifies that stylesheet parameters should be accessible via the `document` function, or that it's desirable (or even acceptable) for external programs to set parameters by changing the contents of an `xsl:param` element.
Robert Rossney
@Robert-Rossney: Any node of an XSLT stylesheet is accessible using the document() function -- as the stylesheet is an XML file.
Dimitre Novatchev
@Dimitre: I think your answer is best, but you need to invert **xsl:param** and **xsl:variable** elements in order to work with provide parameter as well as with "inline" parameter.
Alejandro
@Alejandro You are absolutely right. I am using this as a convenience -- just to simulate passing the parameters from outside.Nothing prohibits setting and using the parameters within the stylesheet without any external caller.
Dimitre Novatchev