 |
| 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
|
|
|
|

September 28th, 2004, 10:03 AM
|
|
Authorized User
|
|
Join Date: Sep 2004
Posts: 36
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Finding the first item
I have a fairly complex document type. Here's the hierarchy of the piece causing trouble:
<Activity>
<ExerciseSet>
<ItemSet>
<AnswerSet>
<AnswerValue>
<AnswerValue>
</AnswerSet>
<Item>
<ItemComponents>
<Question>
<AnswerRef>
</Question>
</ItemComponents>
</Item>
<Item>
<ItemComponents>
<Question>
<AnswerRef>
</Question>
</ItemComponents>
</Item>
</ItemSet>
</ExerciseSet>
</Activity>
In my Question template, I need to figure out if the Question's ancestor Item is the first Item in the ItemSet, and do something only if it's true. I've tried:
<xsl:if test="ancestor::ItemSet[1]/Item[1]">
and
<xsl:if test="ancestor::ItemSet[1]/Item[position()=1]">
and, just as a test,
<xsl:value-of select="ancestor::ItemSet[1]/Item/position()" />
They all evaluate to true (the last one always prints 1.) What do I need to do to find only the first Item in the ItemSet from inside the Question?
Please forgive the newbie question; I'm all for figuring things out myself, but I'm really new at this and learning on the job, and under a tight deadline. Those factors don't really add up to a comprehensive, by-the-book education.
|
|

September 28th, 2004, 10:37 AM
|
|
Authorized User
|
|
Join Date: Jul 2004
Posts: 53
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Quick answer:
<xsl:template match="Question">
<xsl:variable name="firstId" select="generate-id(ancestor::ItemSet/Item[1])" />
<xsl:if test="$firstId=generate-id(ancestor::Item)">
true
</xsl:if>
</xsl:template>
|
|

September 28th, 2004, 11:26 AM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
Guessing at syntax is not something you have time to do when you have a deadline. A better strategy is to get yourself a good book or tutorial and read it.
if the context node is a Question you can do
test="ancestor::Item/preceding-sibling::Item"
which is true only if there is an ancestor Item and it has a preceding sibling Item.
Going up to the ItemSet and then down to an Item, which all your examples do, won't achieve anything, because by the time you get to the ItemSet you don't know which Item you passed to get there.
Michael Kay
http://www.saxonica.com/
|
|

September 28th, 2004, 11:37 AM
|
|
Authorized User
|
|
Join Date: Sep 2004
Posts: 36
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thanks, barcher - that's just what I needed. Michael, I know that you're the guru of all things XSLT, and I obviously don't know a whole lot. I do have several good books (one by Jeni Tennison, and your own Wrox Programmer's Reference), but my whole point is that I don't have time to read the entire books right now.
I'm not sure I understand how your response answers my question, either - I'm not looking for an item with a preceeding Item sibling. Were you trying to indicate that I could surround your entire test with not() and get what I'm looking for? Because I need the *first* Item in the ItemSet, which I was under the impression would not have any preceding-siblings.
|
|

September 28th, 2004, 12:26 PM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
Sorry if I was lecturing a bit, but it's so common to see people thrashing around when they're under time pressure, which is exactly the time you can't afford to.
Yes, my expression was obviously testing whether the ancestor isn't the first, and you can negate the condition if you need to.
Michael Kay
http://www.saxonica.com/
|
|

September 28th, 2004, 05:11 PM
|
|
Authorized User
|
|
Join Date: Sep 2004
Posts: 36
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thanks, Michael. I'm normally the first one to tell people to go read a book; I just don't have time right now to wade through 800 pages trying to find the one function that will do what I need it to do, when I don't know what the function name is.
I do have one more question, if y'all have a minute.
Using the same document structure as above, here's some actual sample XML. As you can probably tell, it's a set of matching questions in a textbook. There's a single set of answers to choose from that apply to all questions within the activity.
<Activity>
<ExerciseSet>
<ItemSet ItemType="Matching">
<AnswerSet>
<AnswerValue id="Coordinator">CoordinatorText</AnswerValue>
<AnswerValue id="Designer">DesignerText</AnswerValue>
<AnswerValue id="Editor">EditorText</AnswerValue>
</AnswerSet>
<Item>
<ItemComponents>
<Question>Lisa<AnswerRef AnswerRef="Coordinator"/></Question>
</ItemComponents>
</Item>
<Item>
<ItemComponents>
<Question>Connie<AnswerRef AnswerRef="Editor"/></Question>
</ItemComponents>
</Item>
<Item>
<ItemComponents>
<Question>Amy<AnswerRef AnswerRef="Designer"/></Question>
</ItemComponents>
</Item>
<Item>
<ItemComponents>
<Question>Ginger<AnswerRef AnswerRef="Coordinator"/></Question>
</ItemComponents>
</Item>
<Item>
<ItemComponents>
<Question>Ruth<AnswerRef AnswerRef="Editor"/></Question>
</ItemComponents>
</Item>
</ItemSet>
</ExerciseSet>
</Activity>
What I need to do is, as I output each question, get the "index" (for lack of a better word, thinking in arrays) of the AnswerRef in the AnswerSet. So, for example, in the Question Lisa, the AnswerRef/@AnswerRef is Coordinator. What I need to do is find the AnswerSet/AnswerValue with an ID of Coordinator, then return the position of that AnswerValue within the AnswerSet - as it's originally entered in the XML, not based on processing position. The reason behind this is that we are outputting to multiple formats, one of which is a user-defined language that feeds into a test generator. All of their tests are based on arrays, and they need to know the array index of the correct answer. So, in the case of Lisa, I need to return the number 1, since the AnswerSet/AnswerValue that corresponds to Lisa's AnswerRef/@AnswerRef is the first one in the list. In the case of Amy, I'd need to return the number 2, because Designer is the second AnswerValue in the AnswerSet.
I can get the actual AnswerValue easily, because the AnswerValue id attribute is of type ID. So I can, inside my AnswerRef template, use <xsl:value-of select="id(@AnswerRef)" /> to print out the word CoordinatorText or DesignerText. Short of adding another attribute that just contains the number (the DTD was created out-of-house and signed off on before I came into this, so no changes are allowed), is there a way to get the actual position of the AnswerValue in relation to the AnswerSet?
|
|

September 29th, 2004, 03:29 AM
|
|
Authorized User
|
|
Join Date: Jul 2004
Posts: 53
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Another quick answer... probably not the most efficient!
You can count the number of preceding-siblings of selected AnswerValue.
<xsl:value-of select="count(id(@AnswerRef)/preceding-sibling::AnswerValue)" />
I used a key to prove this:
<xsl:key name="answer-by-id" match="AnswerValue" use="@id" />
..
..
<xsl:template match="Question">
<xsl:value-of select="count(key('answer-by-id', AnswerRef/@AnswerRef)/preceding-sibling::AnswerValue)" />
</xsl:template>
Bryan
|
|

September 29th, 2004, 10:01 AM
|
|
Authorized User
|
|
Join Date: Sep 2004
Posts: 36
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thanks, Bryan. Maybe I'm putting this in the wrong place or something, but when I run it, Coordinator (which should evaluate to 1) is returning 0, Designer is 1, and Editor is 2. If XSL counting is a base-0 thing consistently, I can just add 1 to the count; I just want to make sure. I'm running the transforms at the command line using MSXSL - could it be the processor? Do some processors count base 0 and others base 1?
|
|

September 29th, 2004, 10:15 AM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
If you count the number of preceding siblings, and there are no preceding siblings, the answer will be zero.
Michael Kay
http://www.saxonica.com/
|
|

September 30th, 2004, 11:17 AM
|
|
Authorized User
|
|
Join Date: Sep 2004
Posts: 36
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
One last question, and then I will shut up. Bryan, your code (<xsl:value-of select="count(id(@AnswerRef)/preceding-sibling::AnswerValue)" />) works brilliantly as long as I keep everything in the question template. But I'm trying to clean things up a bit, and since my question template had a whole bunch of when statements in it, I want to split each of them out into individual named templates that get called by Question.
That part works, but now the id part is broken and I can't figure out how to get it to find the correct info.
Here's what I've done (having removed all but the relevant pieces):
<xsl:template match="ItemComponents/Question">
<xsl:choose>
<xsl:when test="ancestor::ItemSet[1]/@ItemType='Matching'">
<xsl:choose>
<xsl:when test="ancestor::ItemSet[1]/AnswerSet">
<xsl:call-template name="qmm" />
</xsl:when>
<xsl:otherwise>
qm
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
undefined
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="qmm">
<qmm>
<xsl:variable name="firstID" select="generate-id(ancestor::ItemSet/Item[1])" />
<xsl:if test="$firstID=generate-id(ancestor::Item)">
<xsl:apply-templates select="ancestor::ItemSet[1]/AnswerSet" />
</xsl:if>
<xsl:apply-templates select="AnswerRef" />
</xsl:template>
<xsl:template match="ItemSet/Item/ItemComponents/Question/AnswerRef">
<a>
answer ref: <xsl:value-of select="@AnswerRef" />
id/answer ref:<xsl:value-of select="id(@AnswerRef)" />
count: <xsl:value-of select="count(id(@AnswerRef)/preceding-sibling::AnswerValue) + 1" />
</xsl:template>
What am I doing wrong, and how do I get the id function to work from inside a called template?
|
|
 |