Looping over distinct values - xslt

Given a variable which returns a list of distinct States using the distinct-values() function, is there a way to tokenize the variable in a for-each loop?
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
The following variable returns AL and NM, but I can't iterate over it using for-each. Is there a way around this?
<xsl:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:for-each select="$FormStates">
XSLT 2.0 ok.

The distinct-values() function returns a sequence of values which you should be able to iterate over. The result is so to speak "tokenized".
fn:distinct-values('AL', 'AL', 'NL') returns the sequence ('AL', 'NL').
If you output the variable with xsl:value-of it will return the string "AL NL" only because the default sequence separator for xsl:value-of is a single space character. This is something you could change with the #separator attribute:
Input
<?xml version="1.0" encoding="UTF-8"?>
<States>
<State>AL</State>
<State>AL</State>
<State>NM</State>
</States>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="FormStates" select="distinct-values(States/State)"/>
<xsl:comment>xsl:value-of</xsl:comment>
<xsl:value-of select="$FormStates" separator=":"/>
<xsl:comment>xsl:for-each</xsl:comment>
<xsl:for-each select="$FormStates">
<xsl:value-of select="."/>
<xsl:text>:</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="UTF-8"?>
<!--xsl:value-of-->
AL:NM
<!--xsl:for-each-->
AL:NM:

Here's an XSLT 1.0 solution that I've used in the past.
<xsl:template match="/">
<ul>
<xsl:for-each select="//State[not(.=preceding::*)]">
<li>
<xsl:value-of select="."/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
Returns:
<ul xmlns="http://www.w3.org/1999/xhtml">
<li>AL</li>
<li>NM</li>
</ul>

In theory it should work; are you sure the XPath given to the distinct-values function is correct? The code you've given requires that the States element is a sibling of the forms element.
You could insert <xsl:value-of select="count($FormStates)"> immediately after the variable declaration to confirm if it is being set properly.

Related

xsl: when two nodes are equal, display child of first node

I'm using XML Editor 19.1, Saxon P.E 9.7.
For each selected div, I'm looking to display a graphic/#url, following each <surface> if surface/#xml:id = div/#facs.
XSL
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
<xsl:variable name="div4tablet" select="#facs"/>
<xsl:choose>
<xsl:when test="translate(.[#n]/$div4tablet, '#', '') = preceding::facsimile/surfaceGrp[#type='tablet']/surface[#n]/#xml:id">
<xsl:value-of select=""/> <!-- DISPLAY graphic/#url that follows facsimile/surfaceGrp/surface -->
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
[....]
</xsl:for-each>
TEI example
<facsimile>
<surfaceGrp n="1" type="tablet">
<surface n="1.1" xml:id="ktu1-2_i_1_to_10_img">
<graphic url="../img/KTU-1-2-1-10-recto.jpg"/>
<zone xml:id=""/>
<zone xml:id=""/>
</surface>
<surface n="1.2" xml:id="ktu1-2_i_10_to_30_img">
<graphic url="../img/KTU-1-2-10-30-recto.jpg"/>
<zone xml:id=""/>
</surface>
[...]
</surfaceGrp>
<surfaceGrp n="2">
[...]
</surfaceGrp>
</facsimile>
<text>
[...]
<div3 type="col">
<div4 n="1.2.1-10" xml:id="ktu1-2_i_1_to_10" facs="#ktu1-2_i_1_to_10_img">
[...]
</div4>
<div4 n="1.2.10-30" xml:id="ktu1-2_i_10_to_30" facs="#ktu1-2_i_10_to_30_img">
[...]
</div4>
</div3>
</text>
I have tried <xsl:value-of select="preceding::facsimile/surfaceGrp[#type='tablet']/surface[#n, #xml:id]/graphic/#url"/>, but it displays all graphic/#url and not only the one that follows fascsimile/surfaceGrp/surface.
So my question: how to display only surface/graphic/#url for each div3[#type='col']/div4[#n]?
In advance, thank you for your kind help.
As you use XSLT 2 or 3 and the elements have the xml:id attribute you do not even need a key but can use the id function:
<xsl:template match="div4">
<div>
<xsl:value-of select="id(substring(#facs, 2))/graphic/#url"/>
</div>
</xsl:template>
I put the use of id into a template matching the div4 element but you can of course use it the same way inside of your for-each selecting those elements.
See a minimal but complete sample at https://xsltfiddle.liberty-development.net/bdxtpR.
you should use xsl:key for this type of problem.
First, we must declare a key for the target node
<xsl:key name="kSurface" match="surface" use="concat('#', #xml:id)"/>
notice the concat function being used here, an # was being added to the xml:id so that the keys would appear as:
#ktu1-2_i_1_to_10_img
#ktu1-2_i_10_to_30_img
now in this loop:
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
we can access the key that matches the #facs attribute by having:
<xsl:value-of select="key('kSurface', #facs)/graphic/#url"/>
The whole stylesheet is below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:key name="kSurface" match="surface" use="concat('#', #xml:id)"/>
<xsl:template match="/">
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
<xsl:value-of select="key('kSurface', #facs)/graphic/#url"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
see it in action here.

XSL predicate increase for each iteration of a for-each

Quick Question: Is there a way to increment the predicate of an XPATH, by using a variable, like itereating through an array in C? For example /XPATH/element[i]
I am trying to use an XSL to access data from an XML using XPATHS. The XML is the output of a database where the parent node is the table name and its children are the columns. The XSL must be able to convert the text value of the children into attributes with the column name of the element of the table name.
The problem I am trying to solve is that each table can have multiple rows which is outputted to the XML as sibling nodes with the same names. There could be infinite rows in any table so I am trying to use a for-each with the XPATH of the table name to process each row. This works but when I try to use the document function with the XPATH with a predicate to the first XPATH and then the next XPATH and then the next, I do not know how to do it. I can only access the first XPATH. I want a way to be able to access the next XPATH on each iteration of the for-each. Is there anything which can increment each loop and that the predicate and use to point to the next XPATH?
The XML code below is a sample which I am using for testing, it is called DB.xml:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
Below is the XSL I am using:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="dataset/rtbp">
<xsl:element name="RTBP">
<xsl:attribute name="CFMtype">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmtype" />
</xsl:attribute>
<xsl:attribute name="CFMid">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmid" />
</xsl:attribute>
<xsl:text>
</xsl:text>
<xsl:for-each select="/dataset/FunctionSet">
<xsl:element name="FunctionSet">
<xsl:attribute name="RTBP__CFMid">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/FUNCTIONSET__IDENTIFIER" />
</xsl:attribute>
<xsl:attribute name="RTBP_FunctionSet">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/RTBP__CFMID" />
</xsl:attribute>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The predicates are set to 1 at the moment but I wish it to be a variable which iterates on each loop so the XPATH changes to the next occurence of the table name.
The expected result is below:
<?xml version="1.0"?>
<RTBP CFMtype="dog" CFMid="1">
<FunctionSet RTBP__CFMid="1" RTBP_FunctionSet="1"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="2">
<FunctionSet RTBP__CFMid="2" RTBP_FunctionSet="2"/>
</RTBP>
As you may be able to tell the second table (FunctionSet) is a child of the first (RTBP) hence the for-each inside the for-each. I need a method that will put the first row of the FunctionSet into the First row of the RTBP and likewise for the second rows.
I am new to XML, XSL and Posting questions.
The purpose is to re-create a hierarchical XML from a flat XML
exported from a database using DBunit. The association could be done
by cmfid
You should definitely use a key based on matching the cfmid value - especially if you are expecting a large number of rows. Try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="func" match="FunctionSet" use="RTBP__CFMID" />
<xsl:template match="/">
<root>
<xsl:for-each select="dataset/rtbp">
<RTBP CFMtype="{cfmtype}" CFMid="{cfmid}">
<xsl:for-each select="key('func', cfmid)">
<FunctionSet RTBP__CFMid="{RTBP__CFMID}" RTBP_FunctionSet="{FUNCTIONSET__IDENTIFIER}"/>
</xsl:for-each>
</RTBP>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
When the above is applied to the following test input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>124</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>256</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Canine</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Feline</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>256</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Hound</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
</dataset>
the result is:
<?xml version="1.0" encoding="utf-8"?>
<root>
<RTBP CFMtype="dog" CFMid="124">
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Canine"/>
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Hound"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="256">
<FunctionSet RTBP__CFMid="256" RTBP_FunctionSet="Feline"/>
</RTBP>
</root>
Note that your requested output format needlessly duplicates the cfmid value in both parent and child.
I think you're looking for something like (updated after quetion update) :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="rtbp">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
<xsl:apply-templates
select="//FunctionSet[RTBP__CFMID = current()/cfmid]"
mode="insertFunctionSet"/>
</xsl:copy>
</xsl:template>
<xsl:template match="FunctionSet"/>
<xsl:template match="FunctionSet" mode="insertFunctionSet">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The idea, here, is to handle differently the element FunctionSet in the context of rtbp element.
It should not be part of the output when you recursively loop over the whole tree (that's the goal of the <xsl:template match="FunctionSet"/>).
But it should be handled inside the rtbp element and so we apply the templates on the relevant FunctionSet in a specific mode at this point. That's the goal of the <xsl:template match="FunctionSet" mode="insertFunctionSet">...</xsl:template>
With your input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
The result is:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<rtbp cfmtype="dog" cfmid="1">
<FunctionSet FUNCTIONSET__IDENTIFIER="1" RTBP__CFMID="1"/>
</rtbp>
<rtbp cfmtype="cat" cfmid="2">
<FunctionSet FUNCTIONSET__IDENTIFIER="2" RTBP__CFMID="2"/>
</rtbp>
</dataset>
For anyone who had as little knowledge as me when I posted this question and whom wish to find out the same infomation here is my solution to the question. Short answer to the quick question 'can you increment a variable'. No! But you can set a variable and move the position with the following snippet:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
</xsl:for-each>
This snippet loops through rtbp tables in the source XML and moves the position one position more each loop. This creates an object which you can use inside a XPath to test for a condition of each occurence of the Xpath with the same URI path. Such as:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
<xsl:if test="/dataset/FunctionSet[$i]/cfmid = /dataset/rtbp[$n]/cfmid">
<!--code if condition is true-->
</xsl:for-each>
The [$variable name] is how you direct an XPath to the occurence of the element name. So when i = 1 it looks for the first occurence of the element name in the XPath and then when i = 2 it looks for the second occurence of the element name in the XPath.
The Key function is a good tool to search for a key condition inside a template. However I can only use 1 key function per a template. if you wish to have a multi condition test you have to use a choose when statement with multiple if statements being anded with eeach other. for example:
This is a snippet from my advanced code which has multiple for-each loops inside each other and choose when statements to decide if a XML element is a child of the parent via its Identifiers which are child elements of the parent elements in the example XML in my question.
With the position function and the XPath predicate entry combined with choose when statements with ands you can build up a complex XSL which can re-create a flat XML table list of a database into a hierarchical XML form.
Vincent's Key function answer worked for the small complexity of this question but this answer includes an answer about the XPath predicates so I think it is more relevant of an answer to the question. Please look at Vincent's answer and consider using Key Functions for your solution because it is very useful

Trouble with xsl:for-each

I must be missing some fundamental concept of processing an XML document. Here is my source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Root>
<Element>visitorNameAlt</Element>
<Element>visitorScore</Element>
<Element>visitorTimeouts</Element>
<Element>Blank</Element>
<Element>homeNameAlt</Element>
<Element>homeScore</Element>
<Element>homeTimeouts</Element>
<Element>Blank</Element>
<Element>period</Element>
<Element>optionalText</Element>
<Element>flag</Element>
<Element>Blank</Element>
<Element>scoreLogo</Element>
<Element>sponsorLogo</Element>
</Root>
And my XSL stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/Root">
<xsl:value-of select="position()"/>
<xsl:value-of select="Element"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
All I want is to pluck the "Element" names from the source XML doc with their relative position in front.
My output is just "1" followed by the first element and nothing more.
I am new to XSLT, but have processed other documents successfully with for-each.
Thanks in advance.
Bill
You're looping over Root tags, not Element tags. Try this:
<xsl:template match="/">
<xsl:for-each select="/Root/Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Note that you must change the second value-of select to "." or "text()".
XSLT is not an imperative programming language. The XSLT processor grabs each element in turn and tries to match it to your stylesheet. The idiomatic way to write this is without a for-each:
<xsl:template match="/Root">
<xsl:apply-templates select="Element"/>
</xsl:template>
<xsl:template match="Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:template>
The first template matches the root and tells the processor to apply the stylesheet to all the Element nodes inside the Root. The second template matches those nodes, and outputs the desired information.

XSL associative sorting using a field substring

The transformation I am writing must compose a comma separated string value from a given node set. The resulting string must be sorted according to a random (non-alphabetic) mapping for the first character in the input values.
I came up with this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tmp="http://tempuri.org"
exclude-result-prefixes="tmp"
>
<xsl:output method="xml" indent="yes"/>
<tmp:sorting-criterion>
<code value="A">5</code>
<code value="B">1</code>
<code value="C">3</code>
</tmp:sorting-criterion>
<xsl:template match="/InputValueParentNode">
<xsl:element name="OutputValues">
<xsl:for-each select="InputValue">
<xsl:sort select="document('')/*/tmp:sorting-criterion/code[#value=substring(.,1,1)]" data-type="number"/>
<xsl:value-of select="normalize-space(.)"/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It doesn't work and looks like the XPath document('')/*/tmp:sorting-criterion/code[#value=substring(.,1,1)] does not evaluate as I expect. I've checked to substitute the substring(.,1,1) for a literal and it evaluates to the proper value.
So, am I missing something that makes the sorting XPath expression not to evaluate as I expect or is it simply impossile to do it this way?
If not possible to create a XPath expression that works, is there a work around to achieve my purpose?
Note: I'm constrained to XSLT-1.0
Sample Input:
<?xml version="1.0" encoding="utf-8"?>
<InputValueParentNode>
<InputValue>A input value</InputValue>
<InputValue>B input value</InputValue>
<InputValue>C input value</InputValue>
</InputValueParentNode>
Expected ouput:
<?xml version="1.0" encoding="utf-8"?>
<OutputValues>B input value,C input value,A input value</OutputValues>
Replace the self::node() abbreviation ., with current() function.
A better predicate would be: starts-with(normalize-space(current()),#value)
Besides changing transformation according to Alejandro´s answer, I found it better to use a XSL variable for th mapping data to avoid declaration of a dummy namespace (tmp) as seen in Dimitre´s answer to another related question.
My final implementation:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/InputValueParentNode">
<xsl:variable name="sorting-map">
<i code="A" priority="5"/>
<i code="B" priority="1"/>
<i code="C" priority="3"/>
</xsl:variable>
<xsl:variable name="sorting-criterion" select="document('')//xsl:variable[#name='sorting-map']/*"/>
<xsl:element name="OutputValues">
<xsl:for-each select="InputValue">
<xsl:sort select="$sorting-criterion[#code=substring(normalize-space(current()),1,1)]/#priority" data-type="number"/>
<xsl:value-of select="normalize-space(current())"/>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

How can I select nodes from a tree whose markup is stored in a variable?

Consider the following XSLT script:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:variable name="stringmap">
<map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map>
</xsl:variable>
<xsl:template match="/">
<!-- IMPLEMENT ME -->
</xsl:template>
</xsl:stylesheet>
I'd like this script to print redgreenblue.
Is there any way to treat the XML markup which is stored in the stringmap variable as a document of its own which I can run XPath queries on? I'm basically looking for something like
<xsl:for-each select="document($stringmap)/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
(except that the document() function expects an URI).
Motivation: I have various long <xsl:choose> elements which map a given string to another string. I'd like to replace all those with a single template which takes a 'map' argument (which is a simple XML document). My hope is that I can then replace the <xsl:choose> with a simple statement like <xsl:value-of select="$stringmap/map/entry/value[../key='$givenkey']"/>
I'm using XSLT 1.0 using xsltproc.
You're almost right, using document('') will allow you to process node sets inside the current stylesheet:
<xsl:for-each select="document('')/xsl:stylesheet/xsl:variable[#name='stringmap']/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
It's not necessary to define the map node set as a variable in this case:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:data="some.uri" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<data:map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</data:map>
<xsl:template match="/">
<xsl:for-each select="document('')/xsl:stylesheet/data:map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If you do not use xsl:variable as a wrapper, you must remember that a top level elements must have a non null namespace URI.
In XSLT 2.0 it would've been possible to just iterate over the content in a variable:
<xsl:variable name="map">
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="$map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
A posting by M. David Peterson just taught me how to make this work:
It's not necessary to have an <xsl:variable> for this case. Instead, I can embed the data document directly into the XSL stylesheet (putting it into a namespace for sanity) and then select elements from that. Here's the result:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="uri:map">
<xsl:output method="text" encoding="iso-8859-1"/>
<map:colors>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map:colors>
<xsl:template match="/">
<xsl:for-each select="document('')/*/map:colors/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This generates the expected output redgreenblue.
The trick is to use document('') to get a handle to the XSLT document itself, then * to get into the toplevel xsl:stylesheet element and from there I can access the color map.