Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
|
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
 
Old November 25th, 2009, 12:16 PM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default EQUALITY OF PRECEDING ATTRIBUTE

Hi,

I am using xslt version 1.0 and MSXML

I have created an xslt to display a list of header parameters but my code is not quite doing what I need and I would appreciate some help.

This is the desired output:

<DIV style="MARGIN-TOP: 20px; MARGIN-BOTTOM: 20px" id=dvParams>
<DIV>Study Title: </DIV>
<DIV>reader type selected</DIV>
<DIV>exposure type selected</DIV>
<DIV>Demo: demo selected</DIV>
<DIV>Rx Class: Rx Class Selected</DIV>
<DIV>Cost: </DIV>
<DIV>A-Size: ad page selected</DIV>
<DIV>Tabloid: ad page selected</DIV>
<DIV>Frequency BW: BW Selected, Color: Color Selected </DIV>
<DIV>CPM Emphasis: 50</DIV>
</DIV>

This is what my xslt outputs:

<DIV style="MARGIN-TOP: 20px; MARGIN-BOTTOM: 20px" id=dvParams>
<DIV>Study Title: </DIV>
<DIV>reader type selected</DIV>
<DIV>exposure type selected</DIV>
<DIV>Demo: demo selected</DIV>
<DIV>Rx Class: Rx Class Selected</DIV>
<DIV>Cost: </DIV>
<DIV>A-Size: ad page selected</DIV>
<DIV>Tabloid: ad page selected</DIV>
<DIV>Frequency BW: BW Selected</DIV>
<DIV>Color: Color Selected</DIV>
<DIV>CPM Emphasis: 50</DIV>
</DIV>

I need the Frequency BW: BW Selected and Color: Color Selected to be displayed in the same DIV with a comma separating their values instead of using two DIV tags.

Please note that this applies only when the @col attribute is 2 and the @row value is equal to the preceding @row.

The xml extract below has the @col = 2 and row = 4. Therefore, I need them to be in the same DIV.

<Parameter name="Frequency BW" value="BW Selected" col="2" row="4" />
<Parameter name="Color" value="Color Selected" col="2" row="4" />



This is the xslt I am using:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="//ReportParams">
<xsl:for-each select="Parameter">
<div>

<xsl:choose>
<xsl:when test ="@title = 'true'">
<b><xsl:value-of select="@name"/></b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>

<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>

<xsl:value-of select="@value"/>

</div>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

This is the xml

<ReportParams>
<Parameter name="Study Title" value="" title="true" col="1" row="1" />
<Parameter name="" value="reader type selected" col="1" row="2"/>
<Parameter name="" value="exposure type selected" col="1" row="3"/>
<Parameter name="Demo" value="demo selected" col="1" row="4"/>
<Parameter name="Rx Class" value="Rx Class Selected" col="1" row="5"/>
<!-- for efficiency report -->
<Parameter name="Cost" value="" col="2" title="true" row="1" />
<Parameter name="A-Size" value="ad page selected" col="2" row="2"/>
<Parameter name="Tabloid" value="ad page selected" col="2" row="3" />
<Parameter name="Frequency BW" value="BW Selected" col="2" row="4" />
<Parameter name="Color" value="Color Selected" col="2" row="4" />
<Parameter name="CPM Emphasis" value="50" col="2" row="5" />
</ReportParams>
 
Old November 25th, 2009, 12:40 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

Grouping problems are always much easier with XSLT 2.0.

However, if the number of elements to be grouped is never more than two, this isn't too difficult in 1.0.

I would replace the for-each with an apply-templates, and then have three template rules for parameter elements:

(a) your current logic as the default rule

(b) one for match="parameter[@col=2 and @row=following-sibling::parameter[1]/@row]" which does nothing

(c) one for match="parameter[@col=2 and @row=preceding-sibling::parameter[1]/@row]" which combines the two parameters into one div.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
 
Old November 25th, 2009, 01:30 PM
Friend of Wrox
 
Join Date: Nov 2007
Posts: 1,243
Thanks: 0
Thanked 245 Times in 244 Posts
Default

I think the following should do what you want:
Code:
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:output method="html" indent="yes"/>
  
  <xsl:template match="ReportParams">
    <div style="MARGIN-TOP: 20px; MARGIN-BOTTOM: 20px" id="dvParams">
      <xsl:apply-templates select="Parameter[1]"/>
    </div>
  </xsl:template>
  
  <xsl:template match="Parameter[@col = 2 and @row = preceding-sibling::Parameter[1]/@row]">
    <xsl:text>, </xsl:text>
    <xsl:apply-templates select=". | following-sibling::Parameter[1][@col = 2 and @row = current()/@row]" mode="m1"/>
  </xsl:template>
  
  <xsl:template match="Parameter[not(@col = 2 and @row = preceding-sibling::Parameter[1]/@row)]">
    <div>
      <xsl:apply-templates select="." mode="m1"/>
      <xsl:apply-templates select="following-sibling::Parameter[1][@col = 2 and @row = current()/@row]"/>
    </div>
    <xsl:apply-templates select="following-sibling::Parameter[not(@col = 2 and @row = current()/@row)][1]"/>
  </xsl:template>
  
  <xsl:template match="Parameter" mode="m1">
    <xsl:choose>
      <xsl:when test ="@title = 'true'">
        <b><xsl:value-of select="@name"/></b>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="@name"/>
      </xsl:otherwise>
    </xsl:choose>
    
    <xsl:if test="@name != '' and @value != ''">
      <xsl:text>: </xsl:text>
    </xsl:if>
    
    <xsl:value-of select="@value"/>
  </xsl:template>

</xsl:stylesheet>
__________________
Martin Honnen
Microsoft MVP (XML, Data Platform Development) 2005/04 - 2013/03
My blog
 
Old November 25th, 2009, 01:49 PM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

Hi Michael, Martin,

Thanks a lot for your reply.

I will have a look at that tomorrow since I have to go to a meeting now.

I would like to express my sincere thanks for your help.

Cheers

C
 
Old November 26th, 2009, 05:49 AM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

Hi Michael,

I have changed my xslt to use the templates you suggested but I am doing something wrong. The results are still the same. Could you please shed some light about what I am doing wrong? I have the impression that the template to combine the two parameter into one div is never called.

Thanks

This is the revised xslt:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>

<xsl:template match ="ReportParams">
<xsl:apply-templates select="Parameter[1]"/>
</xsl:template>
<xsl:template match ="Parameter">
<div>
<xsl:choose>
<xsl:when test ="@title = 'true'">
<b>
<xsl:value-of select="@name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>
<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>
</div>
<xsl:apply-templates select="following-sibling::Parameter[1]"/>
</xsl:template>
<xsl:template match="parameter[@col=2 and @row=following-sibling::parameter[1]/@row]">
</xsl:template>

<xsl:template match="parameter[@col=2 and @row=preceding-sibling::parameter[1]/@row]">
<xsl:text>, </xsl:text>
<xsl:choose>
<xsl:when test ="@title = 'true'">
<b>
<xsl:value-of select="@name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>
<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>
<xsl:apply-templates select="following-sibling::Parameter[1]"/>
</xsl:template>

</xsl:stylesheet>
 
Old November 26th, 2009, 06:39 AM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

You're doing what's sometimes called "sibling recursion" where the template rule for each parameter element invokes the template for the next parameter element. That's a good design for this kind of problem. However, when you process the first parameter of a pair, you do this:

Code:
<xsl:template match="parameter[@col=2 and @row=following-sibling::parameter[1]/@row]">
  </xsl:template>
that is to say, you do nothing: which means that the next sibling does not get processed. You need to change this by adding an apply-templates call.

Furthermore, when you process the second parameter of the pair, you only seem to be including its own data. You don't seem to be generating the div element or to collect any data from the first element of the pair.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
 
Old November 26th, 2009, 07:26 AM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

Thanks Michael,

As per your instructions, I was able to get it to work. Thanks a lot

Please could you comment on my concerns below:

1 - Currently, I only need to combine the two parameters into one div when the @col attribute = 2 and the @row attributes match.

example:

<Parameter name="Frequency BW" value="BW Selected" col="2" row="4" />
<Parameter name="Color" value="Color Selected" col="2" row="4" />


However, in future this may change and I may also need to combine the parameters into one div when the @col attribute = 1 and the @row attributes match.

What is the best way to achieve this?

2 - Instead of using

<xsl:value-of select ="@value"/>

I am using this to account for an ampersend problem I have in the @value attribute. Is this a common thing to do? or is there a better way of doing it?

<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>

3 - I am repeating a lot of code in the templates. Is there a way to make it more elegant?


Thanks.

XSLT
---------

<xsl:template match ="ReportParams">
<xsl:apply-templates select="Parameter[1]"/>
</xsl:template>

<xsl:template match ="Parameter">
<div>
<xsl:choose>
<xsl:when test ="@title = 'true'">
<b>
<xsl:value-of select="@name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>

<!--<xsl:value-of select ="@value"/>-->

<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>
</div>

<xsl:apply-templates select="following-sibling::Parameter[1]"/>

</xsl:template>

<xsl:template match="Parameter[@col=2 and @row=following-sibling::Parameter[1]/@row]">
<div>
<xsl:choose>
<xsl:when test ="@title = 'true'">
<b>
<xsl:value-of select="@name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>

<!--<xsl:value-of select ="@value"/>-->

<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>

<xsl:apply-templates select="following-sibling::Parameter[1]"/>
</div>
</xsl:template>

<xsl:template match="Parameter[@col=2 and @row=preceding-sibling::Parameter[1]/@row]">
<xsl:text>, </xsl:text>
<xsl:choose>
<xsl:when test ="@title = 'true'">
<b>
<xsl:value-of select="@name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@name != '' and @value != ''">
<xsl:text>: </xsl:text>
</xsl:if>

<!--<xsl:value-of select ="@value"/>-->

<xsl:call-template name="js-escapeAmp">
<xsl:with-param name="string" select="@value"/>
</xsl:call-template>-->

<xsl:apply-templates select="following-sibling::Parameter[1]"/>

</xsl:template>
 
Old November 26th, 2009, 07:43 AM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

I was able to solve concern 1 by using an "OR".

<xsl:templatematch="Parameter[(@col=2 or @col=1) and @row=following-sibling::Parameter[1]/@row]">
 
Old November 26th, 2009, 07:51 AM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

1. I've no idea what your js_escapeAmp does or why you need it. Normally you should be using the HTML output method and that should take care of all escaping that's needed.

2. XSLT 1.0 code is always verbose and there's a limit to how much you can do about it. 2.0 helps a lot (often less than half the number of lines of code). But if you've got common chunks of code you can always put them in a named template.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
 
Old November 26th, 2009, 07:59 AM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

Hi Martin,

I am having a look at your solution and it works fine. It looks much more elegant than the XSLT I have posted in my previous post. There isn't a lot of repetition.

However, I have to confies it is much more difficult to understand

I am debugging it to try an understand the code and would appreciate if you could explain the following bits of code:

1 - <xsl:apply-templates select=". | following-sibling::Parameter[1][@col = 2 and @row = current()/@row]" mode="m1"/>

What does the | do in here?

You select the current node . then use | following-sibling....

Is this an OR????


2 - <xsl:apply-templates select=". | following-sibling::Parameter[1][@col = 2 and @row = current()/@row]" mode="m1"/>

What does the current() function does in here? My debuger keeps throwing an error saying that the current() function is not supported so I cannot see what id does.

3 - I do not unsderstand this template at all. You apply templates 3 times!!

<xsl:template match="Parameter[not(@col = 2 and @row = preceding-sibling::Parameter[1]/@row)]">
<div>
<xsl:apply-templates select="." mode="m1"/>
<xsl:apply-templates select="following-sibling::Parameter[1][@col = 2 and @row = current()/@row]"/>
</div>
<xsl:apply-templates select="following-sibling::Parameter[not(@col = 2 and @row = current()/@row)][1]"/>
</xsl:template>

4 - I have noticed that sometimes you put the [1] index after the expression and sometimes at the beginning. What is the difference?

<xsl:apply-templates select="following-sibling::Parameter[1][@col = 2 and @row = current()/@row]"/>

<xsl:apply-templates select="following-sibling::Parameter[not(@col = 2 and @row = current()/@row)][1]"/>


Cheers

C





Similar Threads
Thread Thread Starter Forum Replies Last Post
Help with Preceding Franky83 XSLT 1 November 12th, 2007 06:20 PM
Testing equality between nodes..... help!! trinkets XSLT 12 February 27th, 2007 12:27 PM
preceding-sibling jonesyp XSLT 2 November 22nd, 2005 12:29 PM
Access to attribute values from class of attribute jacob C# 1 October 28th, 2005 01:11 PM
Name Equality and problems with Java cuskichick Pro JSP 0 June 13th, 2005 12:50 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.