Solved

XSLT keyed lookup not working

Posted on 2013-05-22
16
500 Views
Last Modified: 2013-05-23
I am trying to use the XSLT key function to look up a month name (for example, May) and return the month number (for example, 05), but for some reason this is not working.

My data looks like this:
<Report>
	<Submitted_Date>May 18 2011  1:23PM</Submitted_Date>
</Report>

Open in new window


My XSL looks like this:
<xsl:key name="month" match="lookup:months/month" use="lookup:months/month[@number]"/>

<lookup:months>
	<month number="01">January</month>
	<month number="02">February</month>
	<month number="03">March</month>
	<month number="04">April</month>
	<month number="05">May</month>
	<month number="06">June</month>
	<month number="07">July</month>
	<month number="08">August</month>
	<month number="09">September</month>
	<month number="10">October</month>
	<month number="11">November</month>
	<month number="12">December</month>
</lookup:months>

<xsl:for-each select="$months">
	<xsl:value-of select="key('month', substring-before(/Report/Submitted_Date, ' ') )"/>
</xsl:for-each>

Open in new window

0
Comment
Question by:mariita
  • 8
  • 7
16 Comments
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188059
the key would have to be
<xsl:key name="month" match="lookup:months/month" use="@number"/>

but, this will only work in XSLT2 when you put the lookup in a variable and pass the variable as the third parameter of the key function

with this small a lookup, in XSLT2 you might as well just put it in a variable, say $months
and get       <xsl:value-of select="$months//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>

XSLT1 will not work at all since the index is being searched in the source document, not in the XSLT (this will only work if the lookup:months is part of the source XML instead of the XSLT)

If you transform you $months variable to a node-set (extension function supported in some processors) you could use the $months variable technique in XSLT1 too

In XSLT1 I often use this technique (without the key),
leave the lookup:months as a standalone element at the highest level of th estylesheet (as I assume it is now) and use the document('') function
<xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>
document('') points inside the stylesheet... when not compiled and when the document function is enabled in your processor runtime
0
 
LVL 18

Expert Comment

by:zc2
ID: 39188061
I'd suggest to move the month dictionary to an external file and access to it via the document() function.
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188081
that is another option... but if you have to enable the document('') function anyway... I would prefer having it onboard and use document('')... it saves you the hassle of an extra file

If you want to maintain the information outside the XSLT, you could as well pass it in via a parameter, that is even less hassle than using document() and trying to locate the external file
0
 

Author Comment

by:mariita
ID: 39188197
I'd like to stick with XSLT 1.0 because the stylesheet already has a lot of calculations, which might break if I switch to XSLT 2.0. I don't care if the month dictionary is inside or outside the stylesheet.

I tried putting the month dictionary at the top of the XSLT and then using the code below to get the month number, but it's still not working.
<xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>

The key function was actualy working when I hard-coded "June" as a parameter, but it stopped working when used the substring function to insert the actual month as a parameter. So I assumed that XSLT 1.0 does support keyed lookups.
0
 

Author Comment

by:mariita
ID: 39188244
According to this article, XSLT 1.0 does support keyed lookups:

http://nedbatchelder.com/blog/200506/keyed_lookups_in_xslt_10.html
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188295
Please shoot the guy that wrote that article :-)

He is doing something tricky, he changes the current context to the context of the stylesheet... it is true, you can change the context that is understood by the key...
but I was going to spare you from that knowledge :-) There really is no need to do that, unless showing off

look what he does...

<xsl:variable name='strings' select='document("")//lookup:strings' />

<xsl:template match='/'>
    <xsl:for-each select='$strings'>
        <xsl:value-of select='key("string", "foo")'/>
    </xsl:for-each>
</xsl:template>

As you see (and as I told you), you need the document function pointing inside the stylesheet like this document('') or document("") for that matter
he binds that to the string variable

All that fuss, when you can simply do
<xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>

And no, without switching the context to the document('') context, the lookup will not work

Which XSLT processor are you using?
0
 

Author Comment

by:mariita
ID: 39188400
I'm using the XMLSpy built-in processor, and sometimes the processor that comes bundled with OxygenXML (which I believe is Saxon).

The problem might be that I'm using partly the solution in the above article, and partly the solution that you propose. The two solutions are probably conflicting. This is what is currently in the code:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lookup="http://mydomain.ca/lookup" extension-element-prefixes="lookup">
	<lookup:months>
		<month number="01">January</month>
		<month number="02">February</month>
		<month number="03">March</month>
		<month number="04">April</month>
		<month number="05">May</month>
		<month number="06">June</month>
		<month number="07">July</month>
		<month number="08">August</month>
		<month number="09">September</month>
		<month number="10">October</month>
		<month number="11">November</month>
		<month number="12">December</month>
	</lookup:months>

<xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>

Open in new window

0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188493
OK, in Oxygen you would use Xalan, Saxon 6.5 or Saxon 9
It could be that Saxon9 (XSLT2) in backwardscompatibility mode raises a warning but do what you expect it to do anyway
XML Spy has its own processor, that by default is XSLT2 and that has so many flaws that I don't even try to explain or guess what could possibly go weird with that one

Let me first explain why I am so harsh on that article
When you do a lookup, in my mind you have two options
- small lookup. handle inside stylesheet, don't bother about keys
- large lookup. handle in seperate file outside XSLT and maybe use keys
you would use keys for the performance benefit... there is none on small
specially Saxon is so well optimised that I wonder how big you need the lookup to be to benefit from keys
it is an advantage to handle lookups outside the XSLT for non developers to change lookup values... I do that all the time, so the customer impacts the process without changing the code... but for a simple one language month lookup... don't bother

Please note that in order for this to work
<xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>
you need to save the XSLT first, otherwise Oxygen will use the in memory version and not necessarily point to the right cache

but you could have a context issue there (I did not test)
I think it is safer to first store the date in a variable
<xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>
<xsl:value-of select="document('')//lookup:months/month[@number = $this-month ]"/>

I hope that helps,

cheers

Geert
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188500
one observation...

if you are sticking to XSLT1 for a reason, make sure that you use a XSLT1 processor like xalan or saxon 6.5.5

backward compatibility mode does not always do the same as the old processors
0
 

Author Comment

by:mariita
ID: 39188522
Thanks for the tip. I've actually given up on the keyed lookup for now. I might revisit it if I have to use a longer lookup table. For now, I'm using this as a workaround:

<xsl:variable name="Month_Name">
<xsl:choose>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'January'  ">01</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'February'  ">02</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'March'  ">03</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'April'  ">04</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'May'  ">05</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'June'  ">06</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'July'  ">07</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'August'  ">08</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'September'  ">09</xsl:when>
		<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'October'  ">10</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'November'  ">11</xsl:when>
	<xsl:when test="substring-before(/Report/Submitted_Date, ' ') = 'December'  ">12</xsl:when>
</xsl:choose>
</xsl:variable>

<xsl:value-of select="$Month_Name"/>

Open in new window

0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39188585
if I can comment on that one...

it calculates the substring for every when branch... it will spend some time to get to december
specuially because you have a path in there

please consider
<xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>

and then

<xsl:choose>
      <xsl:when test="$this-month = 'January'  ">01</xsl:when>
....

makes me realize I made an error

<xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>
<xsl:value-of select="document('')//lookup:months/month[@number = $this-month ]"/>

should be

<xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>
<xsl:value-of select="document('')//lookup:months/month[. = $this-month ]/@number"/>

anyhow... even if the series of when clauses don't look so professional...
a lookuptable requires one of the following
- passing a parameter
- having a variable and a node-set() supporting processor
- activating the document function
- having an external document + activating the document function
- using XSLT2

Though the series of when branches cause uglier code... nice code comes at a cost, so don't worry... the series of when branches for this task is just fine
0
 

Author Comment

by:mariita
ID: 39189189
Thanks, I'll give it a shot and let you know if it works.
0
 

Author Comment

by:mariita
ID: 39190331
I didn't realize that putting a path into each When statement would slow things down. Thanks for the tip.

I tried the keyed lookup again, this time with XSLT 2.0, but it's still not working. I'm wondering if there could be a problem with the stylesheet's XSL declaration?

Here is the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lookup="http://mydomain.ca/lookup" extension-element-prefixes="lookup">
    <lookup:months>
        <month number="01">Jan</month>
        <month number="02">Feb</month>
        <month number="03">Mar</month>
        <month number="04">Apr</month>
        <month number="05">May</month>
        <month number="06">Jun</month>
        <month number="07">Jul</month>
        <month number="08">Aug</month>
        <month number="09">Sep</month>
        <month number="10">Oct</month>
        <month number="11">Nov</month>
        <month number="12">Dec</month>
    </lookup:months>
    <xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>
    <xsl:template match="/">
        <xsl:value-of select="document('')//lookup:months/month[@number = substring-before(/Report/Submitted_Date, ' ') ]"/>
    </xsl:template>
</xsl:stylesheet>

Open in new window


Here is the XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="Lookup.xsl"?>
<Report>
    <Submitted_Date>May 18 2011  1:23PM</Submitted_Date>
</Report>

Open in new window

0
 
LVL 60

Accepted Solution

by:
Geert Bormans earned 500 total points
ID: 39190370
That is because you took the wrong version of the lookup

Try this one (tested)

   <xsl:variable name="this-month" select="substring-before(/Report/Submitted_Date, ' ')"/>
    <xsl:template match="/">
        <xsl:value-of select="document('')//lookup:months/month[. = $this-month]/@number"/>
    </xsl:template>
0
 

Author Closing Comment

by:mariita
ID: 39190615
Thanks as always
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 39191208
welcome
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

The Confluence of Individual Knowledge and the Collective Intelligence At this writing (summer 2013) the term API (http://dictionary.reference.com/browse/API?s=t) has made its way into the popular lexicon of the English language.  A few years ago, …
I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now