You are not logged in.

#1 2006-01-11 19:22:04

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

XSLT question

Hey guys,

Started an effort to get to grips with XSLT for a project at work. It's basically about converting xml files in one format to a different xml format. Seemed like a great project to use to learn XSLT. However, it's pretty darn tricky!

Some bits are fine, however, I'm having trouble with the following type of task. Imagine an (artifical) xml file like this:

<exercise>
  <answer>
    <value>choiceA</value>
    <value>choiceC</value>
  <answer>
  <question>
    <prompt>Choose two items from the list</prompt>
    <choice identifier="choiceA>Choice A</choice>
    <choice identifier="choiceB>Choice B</choice>
    <choice identifier="choiceC>Choice C</choice>
  </question>
</exercise>

So, the above is a multiple-choice question. There are two correct answers. It's possible to determine the correct answer by matching the choice identifer with the <values> in the <answer> element.

The task I'm doing at the moment is trying to format the question as an html page. I'm ok with processing the question part, with <xsl:for-each>, and printing them as a list. What I want to be able to do is highlight visually the questions that are correct, perhaps by putting them in bold, or appending a *, or something that differentiates them.

The problem I'm having is that when I'm processing each <choice>, I can grab it's @identifier attribute ok, but I don't know how to see if there is a <value> in the <answers> element that is equal to it.

I'm not at work now so I can't paste you my feable attempt so far. It basically involved embedding another for-each, iterating over the <value> elements, and trying to do a logical test to see if the value in <value> = @identifer. Nothing seemed to work.

Can any one enlighten me?

Cheers

Offline

#2 2006-01-11 20:27:28

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

XPath baby!

something like:

<xsl:if test="count(/exercise/answer/value[text() = @identifier]) > 0">*</xsl:if>

In your 'choice' template would output the '*' only if there was a "/exercise/answer/value" with the same text as the current identifier.  I used to be really good with xslt, but I probably failed this one... at least I can set you on the right track.

Offline

#3 2006-01-11 20:38:58

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

PS <xsl:for-each> is a bit poor to use.  What you should try to do, instead, is craft individual templates for each distinct element.  In this case, something like:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="exercise">
  <div id="exercise">
     <xsl:apply-templates select="question"/>
  </div>
</xsl:template>

<xsl:template match="question">
  <div id="question">
     <span id="prompt"><xsl:value-of select="prompt/text()" /></span>
     <xsl:apply-templates select="choice"/>
  </div>
</xsl:template>

<xsl:template match="choice">
  <input id="question" type="checkbox">
     <xsl:attribute name="name">
        <xsl:value-of select="@identifier" />
     </xsl:attribute>
  </input>
  <xsl:value-of select="text()" />
  <xsl:if test="count(/exercise/answer/value[text() = @identifier]) > 0">
     <span id="correct">CORRECT!!</span>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

NOTE: I do not stand by this being 100% correct, or even vouch for the output.  It was done entirely from memory.

Offline

#4 2006-01-11 20:41:59

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

Thanks for the suggestion Phrak. I shall check that out first thing tomorrow. I must confess that I had written something similar but nothing happened, so I just assumed that I was totally wide of the mark.

I do like XPath a lot, and have been enjoying learning XSLT. However, I'm sort of learning as I go. Skimming through books to see something similar to what I'm trying to achieve, and then adapting (reading as 'messing up') the code to tailor it to my task.

I'll let you know how it goes - I'm sure you're suggestion is correct - it makes sense now that I see it.

Offline

#5 2006-01-12 11:20:01

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

Phrak, your help is greatly appreciated - I now have my desired functionality! Cheers mate. That said, as you expected, I needed to change a couple of things.

1. '> 0' becomes '> 0'
2. In the if, I couldn't get the processor to see the @identifier relative to the <choice> tag.

Therefore, I have something like this:

<xsl:for-each select="simpleChoice">
  <xsl:variable name="label" select="@identifier"/>
  <input type="checkbox"><xsl:value-of select="."/> 
  <xsl:if test="count(/assessmentItem/responseDeclaration/correctResponse/value[text()=$label]) > 0"> *</xsl:if>
  </input><br/>
</xsl:for-each>

As you see, I needed to put the value of the choice identifier in a variable, and use that in the test="".

Also, the reason why I've used a for-each rather than having a template dedicated for the choice element is because there is in fact some extra complexity in the XML structure:

...
  <question maxChoices="2">
  ...

At the moment I have a template for to match 'question' and with that I do a for-each over the choices. There is a xsl:choose for if the maxChoices is > 1, then run the above code (which displays check boxes) otherwise, run a similar block of code that displays radio buttons.

I know this can be refactored to be a bit more streamlined. I hope to experiment more later.

Ta

Offline

#6 2006-01-12 15:28:15

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

Next issue that's bugging me...

The parent node of my xml file starts like this:

<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p0 imsqti_v2p0.xsd"
    identifier="choice" title="Unattended Luggage" adaptive="false" timeDependent="false">
...

Now when I have the above, basically none of my stylesheets work - they never match anything (except '/').

Now if I remove the "
xmlns="http://www.imsglobal.org/xsd/imsqti_v2p0" part, then it does work ok.

So, my question is, wtf is going on?!? I can't find any help in my books. I can't seem to steer Google towards a result either.

For the record, I've tried using the Xalan and MSXML engines to process the XLST and both return nothing useful.

Offline

#7 2006-01-12 16:13:35

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

OK, too much stuff to 'quote':

> instead of >, I forgot about that, heh

as for @identifier, you *are* able to do it that way, but you may need "./@identifier" or "node::@identifier" or something like that.  The variable thing works, though.

And with the xsl:choose thing in the loop, you can get rid of even that.  Just a small example:

<xsl:template match="question[choices/text() = 9]">
I HAVE 9!!!
</xsl:template>
<xsl:template match="question[choices/text() != 9]">
I DON'T
</xsl:template>

Now, I don't think you can do a generic match="question" then one with a where clause...

As for the schema thing... ummm no idea.  The only thing I would say to check is to actually see if the xml validates the schema - it may be failing.

Offline

#8 2006-01-12 16:50:21

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

phrakture wrote:

as for @identifier, you *are* able to do it that way, but you may need "./@identifier" or "node::@identifier" or something like that.  The variable thing works, though.

Ahh. Forgive my noob-ness! That's cool. I shall give that a whirl as I felt the variable approach was a hack.

phrakture wrote:

And with the xsl:choose thing in the loop, you can get rid of even that.  Just a small example:

<xsl:template match="question[choices/text() = 9]">
I HAVE 9!!!
</xsl:template>
<xsl:template match="question[choices/text() != 9]">
I DON'T
</xsl:template>

Now, I don't think you can do a generic match="question" then one with a where clause...

Whoa! That's very nice. Talk about more than one way to skin a cat.

phrakture wrote:

As for the schema thing... ummm no idea.  The only thing I would say to check is to actually see if the xml validates the schema - it may be failing.

Ah, that's me being a namespace noob too. I think I understand what the problem is now. There's a confusion between the html tags and the other xml tags that are part of the qti namespace - the problem xmlns  applies that namespace to all tags.

What I need to do is xmlns:qti="http://www.imsglobal.org/xsd/imsqti_v2p0". The input XML needs to be fully qualified with the 'qti' namespace prefix (which it hasn't been so far), etc, etc.

It's a real PITA to be honest. I started learning XSLT because I thought it would be really easy, but it's proving difficult (obviously I know I still need to learn to stop thinking in a procedural fashion), but it seems overly complex for non-trivial files. It may have been better to investigate XOM instead.

Offline

#9 2006-01-12 20:04:41

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

I'm one of those xml/xsl/xslt haters.  I don't like it, it's a waste of processing, and can triple your data size.

That said, it's there.  XML exists, crap is written in it, can't ignore that.

Personally, I think XSLT is ingenious.  I really love the mental shift required to work in it.

Whenever I've done custom stuff, I tried as much as possible to scrub custom namespaces, because it does nothing but complicate things.  Also, I never wrote schemas, because there's no point.  If the XML is coming from another app, you know the format, and if it changes, then you need to change the XSLT along with the schema.  If XML fails schema validation, you kick of an error, much the same as if the XML failed the transformation... no biggie.

Offline

#10 2006-01-23 09:57:24

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

Another question that I wondered if anyone knows a solution to.

Imagine I have an element within an XML file like this:

<description>This <i>needs</i> to keep its <b>formatting</b> intact.</description>

Now, I'm converting to HTML, and I have something like this:

<xsl:template match="description">
  <p><xsl:copy-of select="."/></p>
</xsl:template>

But what I get is:

<p><description>This <i>needs</i> to keep its <b>formatting</b> intact.</description></p>

The <description> tags remain! Now, I guess I understand why this is happening - I'm copying all of that element. However, I just want to copy the contents of it, not that node itself. I'm using the copy-of as it keeps the formatting tags within.

Is there another approach I should be using?

TIA

Offline

#11 2006-01-23 17:40:03

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

<xsl:copy-of select="text()"/>

Try that

Edit scratch that - won't work unless you CDATA the contents (maybe you should if you're embedding non-parsed formatting).

Maybe

<xsl:copy-of select="child::*"/>

?

Offline

#12 2006-01-23 19:51:15

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

Looks interesting. I shall give that a try in a bit. Ta.

Offline

#13 2006-01-24 11:50:40

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

phrakture wrote:

Maybe

<xsl:copy-of select="child::*"/>

?

You da man! smile

Cheers Phrak.

Offline

#14 2006-01-24 16:38:13

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

arooaroo wrote:

You da man! smile

Cheers Phrak.

I might actually recommend placing pre-formatted data like that inside a CDATA section, and just using select="text()" - the CDATA section makes it so the innards are unparsed.  And you don't want crappy HTML tags screwing up your data.

Offline

#15 2006-01-24 16:52:06

arooaroo
Member
From: London, UK
Registered: 2005-01-13
Posts: 1,268
Website

Re: XSLT question

phrakture wrote:

I might actually recommend placing pre-formatted data like that inside a CDATA section, and just using select="text()" - the CDATA section makes it so the innards are unparsed.  And you don't want crappy HTML tags screwing up your data.

Good tip.

Actually, there was a slighlt problem with your tip that I've since noticed. Haven't investigated it closely enough though because I've been busy with other things. But I seem to be losing some whitespace.

Offline

#16 2006-01-24 20:17:03

phrakture
Arch Overlord
From: behind you
Registered: 2003-10-29
Posts: 7,879
Website

Re: XSLT question

arooaroo wrote:
phrakture wrote:

I might actually recommend placing pre-formatted data like that inside a CDATA section, and just using select="text()" - the CDATA section makes it so the innards are unparsed.  And you don't want crappy HTML tags screwing up your data.

Good tip.

Actually, there was a slighlt problem with your tip that I've since noticed. Haven't investigated it closely enough though because I've been busy with other things. But I seem to be losing some whitespace.

Ah, but xml and all that jazz has no notion of whitespace that is not inside a CDATA element.

Offline

Board footer

Powered by FluxBB