kevmull
kevmull

Reputation: 103

Grouping with wildcard search in XSLT 1.0

I want to do a sipmle 'Muenchian grouping' in xslt 1.0 but also with an added wildcard search from a different node.

I'm familar with the grouping method and it's awlays worked well but am having trouble with the wildcard issue in that it always shows the group headings that do not have the wildcard search.

Here is my xml:

<objects>
<object>
    <output_title>This is a title</output_title>
   <output_year>2011</output_year>
</object>
<object>
    <output_title>This is my title</output_title>
   <output_year>2012</output_year>
</object>
<object>
    <output_title>This is also my title</output_title>
    <output_year>2012</output_year>
</object>
<object>
    <output_title>This is another my title</output_title>
    <output_year>2012</output_year>
</object>
<object>
    <output_title>This is my title</output_title>
    <output_year>2014</output_year>
</object>
<object>
    <output_title>This is our title</output_title>
    <output_year>2015</output_year>
</object>
</objects>

I want to output the following given a wildcard seach of the string 'my':

<h4>2012</h4>
<ol>
<li>This is my title</li>
<li>This is also my title</li>
<li>This is another my title</li>
</ol>

<h4>2014</h4>
<ol>
<li>This is my title</li>
</ol>

So the headings '2011' and '2015' should not appear (but they do and that is the problem)

Here is my XSLT 1.0:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" version="1.0">
<xsl:param name="search"/>

<!--Key for unique years-->
<xsl:key name="uniqueOutputYearHeading"
    match="contains(translate(objects/object/output_title,$uppercase,$lowercase),translate($search,$uppercase,$lowercase))"
    use="output_year"/>

<xsl:template match="/">

<!--    Generate the unique year headings-->
<xsl:for-each select="//object[generate-id(.)=generate-id(key('uniqueOutputYearHeading',output_year))]">
<xsl:sort select="output_year" data-type="number" order="descending"/>
<h4>
<xsl:value-of select="output_year"/>
</h4>
<ol>
<xsl:for-each select="key('uniqueOutputYearHeading',output_year)">
<xsl:for-each select="output_citation">
<xsl:if test="contains(translate(current(),$uppercase,$lowercase),translate($search,$uppercase,$lowercase))">
<li class="margin-bottom">
<xsl:copy-of select="current()"/>
</li>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</ol>            
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

But I am getting:

<h4>2011</h4>

<h4>2012</h4>
<ol>
<li>This is my title</li>
<li>This is also my title</li>
<li>This is another my title</li>

<h4>2014</h4>
<ol>
<li>This is my title</li>
</ol>

<h4>2015</h4>

I can't seem to get the 'contains' condition to work on the years, only the actual title. I've tried moving and duplicating it before the year heading but it returns nothing. Also I can't include a the search variable in the original key structure which would fix th eproblem (i think).

Upvotes: 0

Views: 327

Answers (2)

Linga Murthy C S
Linga Murthy C S

Reputation: 5432

This would do, I believe:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="search">my</xsl:param>

<xsl:key name="uniqueOutputYearHeading" match="object" use="output_year"/>

<xsl:template match="objects">
    <xsl:copy>
        <xsl:for-each select="object[contains(output_title,$search)][generate-id() = generate-id(key('uniqueOutputYearHeading', output_year)[contains(output_title,$search)][1])]">
            <h4>
                <xsl:value-of select="output_year"/>
            </h4>
            <ol>
                <xsl:for-each select="key('uniqueOutputYearHeading', output_year)[contains(output_title,$search)]">
                    <li>
                        <xsl:value-of select="output_title"/>
                    </li>
                </xsl:for-each>
            </ol>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 116959

You need to do this in two passes. First, find the nodes that meet your search criteria. Then apply Muenchian grouping to the result:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">

<xsl:param name="search" />

<xsl:key name="object-by-year" match="object" use="output_year"/>

<xsl:template match="/objects">
    <!-- first pass -->
    <xsl:variable name="found-objects">
        <xsl:copy-of select="object[contains(., $search)]"/>
    </xsl:variable>
    <xsl:variable name="found-objects-set" select="exsl:node-set($found-objects)" />
    <!-- second (final) pass -->
    <html>
        <!-- switch context to the variable -->
        <xsl:for-each select="$found-objects-set">
            <!-- for each distinct year ... -->
            <xsl:for-each select="object[generate-id(.)=generate-id(key('object-by-year', output_year))]">
                <xsl:sort select="output_year" data-type="number"/>
                <h4>
                    <xsl:value-of select="output_year"/>
                </h4>
                <!-- ... list the found objects in this year -->
                <ol>
                    <xsl:for-each select="key('object-by-year', output_year)">
                        <li><xsl:value-of select="output_title" /></li>
                    </xsl:for-each>
                </ol> 
            </xsl:for-each>
        </xsl:for-each>
    </html>
</xsl:template>

</xsl:stylesheet>

Applied to your input example, with the parameter being the string "my", the result is:

<html>
   <h4>2012</h4>
   <ol>
      <li>This is my title</li>
      <li>This is also my title</li>
      <li>This is another my title</li>
   </ol>
   <h4>2014</h4>
   <ol>
      <li>This is my title</li>
   </ol>
</html>

Note: you cannot use a variable in the match pattern of a key.

Upvotes: 1

Related Questions