I have an xml source that approximates a table layout (output from a program) - a is the table holder, b hold the column headings d, c are the rows and e are the row cells:
<?xml version="1.0" encoding="utf-16"?>
<a>
<b>
<d/>
<d/>
<d/>
</b>
<c>
<e/>
<e/>
<e/>
</c>
</a>
</xml>
I have written the following xsl:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/">
<Lessons>
<xsl:for-each select="//c">
<xsl:call-template name="lesson" />
</xsl:for-each>
</Lessons>
</xsl:template>
<xsl:template name="lesson">
<Lesson>
<Teacher>
<xsl:value-of select="count(./preceding-sibling::*)+1" />
<xsl:value-of select="//b[count(./preceding-sibling::*)+1]" />
</Teacher>
</Lesson>
</xsl:template>
</xsl:stylesheet>
The first xsl:value-of of the Teacher element gives me the correct values. I would like the second xsl:value-of to return the value of the heading for that cell i.e. for the first e element that calls the template the first d element is returned and so on. However, it ouputs the value of all of the d elements.
Even if it is not entirely clear what you expect, I will try an answer.
As I understood you like to find the <d> element which has the same position than the <e> element.
This is either possible with getting the current position as a variable.
xsl:variable name ="pos" select="position()" />
<xsl:value-of select="//b/d[position() = $pos]/#id" />
or use count of preceding-sibling
<xsl:value-of select="//b/d[(count(current()/preceding-sibling::*)+1)]/#id" />
Update the part below is changed because of the comment from "George of all trades".
Be aware that the result of position()depends on the calling context.
For example following test xslt (version 1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Lessons>
<!-- Wrong result with position -->
<xsl:for-each select="//c/e">
<xsl:call-template name="lesson" />
</xsl:for-each>
</Lessons>
<Lessons2>
<!-- Wonted result even with position -->
<xsl:for-each select="//c">
<xsl:for-each select="e">
<xsl:call-template name="lesson" />
</xsl:for-each>
</xsl:for-each>
</Lessons2>
</xsl:template>
<xsl:template name="lesson">
<Lesson>
<Teacher>
<xsl:value-of select="#id" />
<xsl:text>,</xsl:text>
<xsl:value-of select="count(./preceding-sibling::*)+1" />
<xsl:text>,</xsl:text>
<xsl:variable name ="pos" select="position()" />
<xsl:value-of select="$pos" />
<xsl:text>,</xsl:text>
<xsl:value-of select="//b/d[position() = $pos]/#id" />
<xsl:text>,</xsl:text>
<xsl:value-of select="//b/d[count(current()/preceding-sibling::*)+1]/#id" />
</Teacher>
</Lesson>
</xsl:template>
</xsl:stylesheet>
With this input:
<?xml version="1.0" encoding="utf-16"?>
<xml>
<a>
<b>
<d id="d1"/>
<d id="d2"/>
<d id="d3"/>
</b>
<c>
<e id="e1"/>
<e id="e2"/>
<e id="e3"/>
</c>
<c>
<e id="e21"/>
<e id="e22"/>
<e id="e23"/>
</c>
<c>
<e id="e21"/>
<e id="e22"/>
<e id="e23"/>
</c>
</a>
</xml>
Generates this output:
<Lessons>
<Lesson>
<Teacher>e1,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e2,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e3,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,4,,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,5,,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,6,,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,7,,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,8,,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,9,,d3</Teacher>
</Lesson>
</Lessons><Lessons2>
<Lesson>
<Teacher>e1,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e2,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e3,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,3,d3,d3</Teacher>
</Lesson>
</Lessons2>
Related
From the XML file :
<store >
<tools>
<tool IDT="T1">
<container>B1</container>
<container>B2</container>
</tool>
<tool IDT="T2">
<container>B1</container>
</tool>
<tool IDT="T3">
<container>B2</container>
</tool>
</tools>
<boxes>
<box IDB="B1" height="10" width="20" length="30" weight="4"/>
<box IDB="B2" height="5" width="40" length="30" weight="2"/>
</boxes>
</store>
I try to display for each box the list of tools that go into each box. For that, I wrote the following XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output
method="html"
encoding="UTF-8"
doctype-public="-//W3C//DTD HTML 4.01//EN"
doctype-system="http://www.w3.org/TR/html4/strict.dtd"
indent="yes" />
<xsl:template match="/">
<html>
<head>
<title>Boxes contents</title>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>Boxes contents</h1>
<ul>
<xsl:apply-templates select="/store/boxes/box" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="box" >
<li><xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:call-template name="findTools" >
<xsl:with-param name="currentBOX" select="#IDB"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="findTools" >
<xsl:param name="currentBOX" />
<xsl:for-each select="/store/tools/tool/container" >
<xsl:if test="container = $currentBOX" >
<br><xsl:value-of select="#IDT"/></br>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When I do it, I never see the tools. In debug under OXYGEN, I see that the IF is never true. I do not understand why? I start in XPath and XSLT, thanks for your help
You already are at a <container> element inside the <xsl:for-each>. There are no children, so selecting another <container> inside the <xsl:if> won't return anything.
You mean to execute your check from the <tool> node.
<xsl:for-each select="/store/tools/tool">
<xsl:if test="container = $currentBOX">
<xsl:value-of select="#IDT"/><br />
</xsl:if>
</xsl:for-each>
which is easier written as
<xsl:for-each select="/store/tools/tool[container = $currentBOX]">
<xsl:value-of select="#IDT"/><br />
</xsl:for-each>
Overall a more straight-forward way to write the two templates would be this:
<xsl:template match="box">
<li>
<xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:apply-templates select="/store/tools/tool[container = current()/#IDB]" />
</xsl:template>
<xsl:template match="tool">
<xsl:value-of select="#IDT"/><br />
</xsl:template>
And alternatively you can use an <xsl:key> to index <tool> elements by their <container> value:
<xsl:key name="kToolByContainer" match="/store/tools/tool" use="container" />
<xsl:template match="box">
<li>
<xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:apply-templates select="key('kToolByContainer', #IDB)" />
</xsl:template>
<xsl:template match="tool">
<xsl:value-of select="#IDT"/><br />
</xsl:template>
I have a XML file which looks roughly like this (actual file is much more complex, everything has been truncated in this example):
<?xml version="1.0" encoding="utf-8"?>
<root>
<element>
<tag1>1</tag1>
<tag2>stuff</tag2>
<type>String</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>2</tag1>
<tag2>stuff</tag2>
<type>String</type>
<type>Date</type>
<type>Float</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>3</tag1>
<tag2>stuff</tag2>
<type>DateTime</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>4</tag1>
<tag2>stuff</tag2>
<type>Float</type>
<type>String</type>
<type>Date</type>
<tag3>stuff</tag3>
</element>
</root>
I process it with the following XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type"><xsl:call-template name="type"/></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="type">
<xsl:variable name="initialType" select="translate(type,' ','')"/>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And I get this resulting file:
<?xml version="1.0" encoding="UTF-8"?>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:float"/>
My problem here is that only the first <type> tag is taken into account. What I would like is to concatenate all the type tag contents into the type tag of the output, preceded by a sign indicating that the tag is an agglomerate if applicable.
However, to avoid creating artificially numerous types, the content of the tags must be alphabetically sorted first. In this example, the <element> number 2 and 4 are both made of only Float, String, and Date, albeit in a different order. They need to have the same type in the output.
The following output would be acceptable:
<?xml version="1.0" encoding="UTF-8"?>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>
I am very new to XLST, and I have not managed to get anywhere close to the desired output so far. The code I have tried is just below, and fails horribly, notably because I failed to understand how to get <xsl:sort> working:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:apply-templates>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:call-template name="type"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="type">
<xsl:choose>
<xsl:when test="following-sibling::type">
<xs:text>union</xs:text>
<xsl:for-each select="following-sibling::type">
<xs:text>translate(type,' ','')</xs:text>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="initialType" select="translate(type,' ','')"/>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Just a few adjustments on your existing code were needed.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:variable name="sorted">
<xsl:for-each select="type">
<xsl:sort select="."/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="$sorted/type"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="type">
<xsl:variable name="initialType" select="translate(., ' ', '')"/>
<xsl:if test="count(preceding-sibling::type) = 0 and count(following-sibling::type) > 0">
<xsl:text>union</xsl:text>
</xsl:if>
<!-- HINT remove if you dont want any seperator -->
<xsl:if test="count(preceding-sibling::type) > 0">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Please verify, for me i get the output (see HINT inline in XSLT):
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
I would do it like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:apply-templates select="type">
<xsl:sort select="."/>
</xsl:apply-templates>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'String']">xs:string</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Date']">xs:date</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'DateTime']">xs:dateTime</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Float']">xs:float</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Integer']">xs:int</xsl:template>
<xsl:template match="type">
<xsl:value-of select="translate(., ' ', '')"/>
</xsl:template>
</xsl:stylesheet>
I have an xml structure like the following :
<doc>
<line void="false">
<lineNumber>1</lineNumber>
<info1>ddddd</info1>
<info2>aaaaa</info2>
</line>
<line void="true">
<lineNumber>2</lineNumber>
<voidLineNumber>1</voidLineNumber>
<voidValue>2.00</voidValue>
</line>
</doc>
I need one single set of data. I would like to select all the lines where void = false as well as the voidLineNumber and voidValue data from the line where void = true and the voidLineNumber = lineNumber from the original line.
Is this possible? Any help would be appreciated. Thanks
As Michael Kay noted, XPath itself can only be used to select nodes, not to transform them. You can do what you want with XSLT:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" />
<xsl:template match="doc">
<xsl:apply-templates select="line" />
</xsl:template>
<xsl:template match="line">
<xsl:variable name="voidvalue"><xsl:value-of select="#void" /></xsl:variable>
<xsl:if test="$voidvalue='false'">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:if test="name(.)='lineNumber'">
<xsl:value-of select="."/>
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This glossary derives the index from the first letter of each entry. I'm trying to work out how to show only the unique values. Have looked into preceding-sibling and position() but cannot seem to find the correct way to. I'm constrained to using XSLT 1.0 and attributes.
glossary.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="glossary.xsl"?>
<include>
<file name="data.xml"/>
</include>
data.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<glossary>
<entry term="cantaloupe" definition="A kind of melon"/>
<entry term="banana" definition="A tropical yellow fruit"/>
<entry term="apple" definition="A red fruit with seeds"/>
<entry term="orange" definition="An orange citrus fruit"/>
<entry term="Cherry" definition="A red fruit that grows in clusters "/>
<entry term="cranberry" definition="A sour berry enjoyed at Thanksgiving"/>
<entry term="avocado" definition="A mellow fruit enjoyed in guacamole"/>
</glossary>
glossary.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Index: how to show unique values? -->
<xsl:for-each select="document('data.xml')/glossary/entry" >
<xsl:sort select="#term" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:variable name="initial" select="substring(#term,1,1)" />
<xsl:value-of select="$initial" /> |
</xsl:for-each>
<!-- Glossary -->
<dl>
<xsl:for-each select="document('data.xml')/glossary/entry" >
<xsl:sort select="#term" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:variable name="initial" select="substring(#term,1,1)" />
<!-- Alphabetical header: how to only the first instance of each letter? -->
<a name="{$initial}"><h1><xsl:value-of select="$initial" /></h1></a>
<dt><xsl:apply-templates select="#term"/></dt>
<dd><xsl:apply-templates select="#definition"/></dd>
</xsl:for-each>
</dl>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Output so far
a | a | b | c | C | c | o |
a
apple
A red fruit with seeds
a
avocado
A mellow fruit enjoyed in guacamole
b
banana
A tropical yellow fruit
c
cantaloupe
A kind of melon
C
Cherry
A red fruit that grows in clusters
c
cranberry
A sour berry enjoyed at Thanksgiving
o
orange
An orange citrus fruit
Desired output
a | b | c | o
a
apple
A red fruit with seeds
avocado
A mellow fruit enjoyed in guacamole
b
banana
A tropical yellow fruit
c
cantaloupe
A kind of melon
Cherry
A red fruit that grows in clusters
cranberry
A sour berry enjoyed at Thanksgiving
o
orange
An orange citrus fruit
This is an example of a grouping problem and in XSLT 1.0, the established way to do grouping is to use Muenchian Grouping. Unfortunately, your scenario requires finding the lower-case of characters on top of that, and that's a bit messy in XSLT 1.0.
Nonetheless, I've produced a solution and it goes as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/#term"
use="translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/#term[generate-id() =
generate-id(key('kEntryInitial',
translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz'))[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="#term" mode="header">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a href="#{$initial}">
<xsl:value-of select="$initial" />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#term" mode="main">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a name="{$initial}">
<h1>
<xsl:value-of select="$initial" />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', $initial)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="#term"/>
</dt>
<dd>
<xsl:apply-templates select="#definition"/>
</dd>
</xsl:template>
<xsl:template name="ToLower">
<xsl:param name="value" />
<xsl:value-of select="translate(substring($value, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:template>
</xsl:stylesheet>
When run on your input XML, this produces the following:
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>a |b |c |o
<dl><a name="a"><h1>a</h1></a><dt>apple</dt>
<dd>A red fruit with seeds</dd>
<dt>avocado</dt>
<dd>A mellow fruit enjoyed in guacamole</dd><a name="b"><h1>b</h1></a><dt>banana</dt>
<dd>A tropical yellow fruit</dd><a name="c"><h1>c</h1></a><dt>cantaloupe</dt>
<dd>A kind of melon</dd>
<dt>Cherry</dt>
<dd>A red fruit that grows in clusters </dd>
<dt>cranberry</dt>
<dd>A sour berry enjoyed at Thanksgiving</dd><a name="o"><h1>o</h1></a><dt>orange</dt>
<dd>An orange citrus fruit</dd>
</dl>
</body>
</html>
One thing I'd suggest considering is using a simple XSLT to "prep" your glossary with initials:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="entry">
<xsl:copy>
<xsl:attribute name="initial">
<xsl:value-of select="translate(substring(#term, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This produces:
<glossary>
<entry initial="c" term="cantaloupe" definition="A kind of melon" />
<entry initial="b" term="banana" definition="A tropical yellow fruit" />
<entry initial="a" term="apple" definition="A red fruit with seeds" />
<entry initial="o" term="orange" definition="An orange citrus fruit" />
<entry initial="c" term="Cherry" definition="A red fruit that grows in clusters " />
<entry initial="c" term="cranberry" definition="A sour berry enjoyed at Thanksgiving" />
<entry initial="a" term="avocado" definition="A mellow fruit enjoyed in guacamole" />
</glossary>
then if you use this prepped version as the glossary, the main XSLT can be rid of all those ugly translate() functions and becomes a lot cleaner:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/#initial" use="."/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data2.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/#initial[generate-id() =
generate-id(key('kEntryInitial', .)[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="#initial" mode="header">
<a href="#{.}">
<xsl:value-of select="." />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#initial" mode="main">
<a name="{.}">
<h1>
<xsl:value-of select="." />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', .)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="#term"/>
</dt>
<dd>
<xsl:apply-templates select="#definition"/>
</dd>
</xsl:template>
</xsl:stylesheet>
Of course, the final output is the same as the first example. If your XSLT processor supports the node-set() function, it's also possible to do both of these processing steps in a single XSLT.
The technique you need is called Muenchian grouping. First define a key that groups entry elements by the downcased first letter of their term
<xsl:key name="entryByInitial" match="entry" use="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
Then you use a trick with generate-id to extract just the first element that matches each key
<xsl:for-each select="document('data.xml')">
<!-- iterate over the "groups" to build the top links -->
<xsl:for-each select="glossary/entry[generate-id() = generate-id(key('entryByInitial', translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))[1])]">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
<xsl:variable name="initial" select="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
<!-- insert a leading | before all but the first link -->
<xsl:if test="position() > 1"> | </xsl:if>
<xsl:value-of select="$initial" />
</xsl:for-each>
<!-- iterate over the groups again -->
<xsl:for-each select="glossary/entry[generate-id() = generate-id(key('entryByInitial', translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))[1])]">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
<xsl:variable name="initial" select="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
<a name="{$initial}"><h1><xsl:value-of select="$initial" /></h1></a>
<dl>
<!-- apply templates for all entries with this key value -->
<xsl:apply-templates select="key('entryByInitial', $initial)">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
</xsl:apply-templates>
</dl>
</xsl:for-each>
</xsl:for-each>
and define a separate template
<xsl:template match="entry">
<dt><xsl:apply-templates select="#term"/></dt>
<dd><xsl:apply-templates select="#definition"/></dd>
</xsl:template>
For debugging purposes it would be handy to output the full path of the context node from within a template, is there unabbreviated xpath or function to report this ?
Example Template:
<xsl:template match="#first">
<tr>
<td>
<xsl:value-of select="??WHAT TO PUT IN HERE??"/>
</td>
</tr>
</xsl:template>
Example (Abridged) input document:
<people>
<person>
<name first="alan">
...
The output from the template would be something like:
people / person / name / #first
Or something similar.
This transformation produces an XPath expression for the wanted node:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="vNode" select=
"/*/*[2]/*/#first"/>
<xsl:apply-templates select="$vNode" mode="path"/>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat('/',name())"/>
<xsl:variable name="vnumPrecSiblings" select=
"count(preceding-sibling::*[name()=name(current())])"/>
<xsl:variable name="vnumFollSiblings" select=
"count(following-sibling::*[name()=name(current())])"/>
<xsl:if test="$vnumPrecSiblings or $vnumFollSiblings">
<xsl:value-of select=
"concat('[', $vnumPrecSiblings +1, ']')"/>
</xsl:if>
</xsl:template>
<xsl:template match="#*" mode="path">
<xsl:apply-templates select="ancestor::*" mode="path"/>
<xsl:value-of select="concat('/#', name())"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<people>
<person>
<name first="betty" last="jones"/>
</person>
<person>
<name first="alan" last="smith"/>
</person>
</people>
the wanted, correct result is produced:
/people/person[2]/name/#first
Here's a stylesheet (of dubious value) that prints the path to every element and attribute in a document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:strip-space elements="*" />
<xsl:template match="*">
<xsl:param name="pathToHere" select="''" />
<xsl:variable name="precSiblings"
select="count(preceding-sibling::*[name()=name(current())])" />
<xsl:variable name="follSiblings"
select="count(following-sibling::*[name()=name(current())])" />
<xsl:variable name="fullPath"
select="concat($pathToHere, '/', name(),
substring(concat('[', $precSiblings + 1, ']'),
1 div ($follSiblings or $precSiblings)))" />
<xsl:value-of select="concat($fullPath, '
')" />
<xsl:apply-templates select="#*|*">
<xsl:with-param name="pathToHere" select="$fullPath" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="pathToHere" select="''" />
<xsl:value-of select="concat($pathToHere, '/#', name(), '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to this input:
<people>
<person>
<name first="betty" last="jones" />
</person>
<person>
<name first="alan" last="smith" />
</person>
<singleElement />
</people>
Produces:
/people
/people/person[1]
/people/person[1]/name
/people/person[1]/name/#first
/people/person[1]/name/#last
/people/person[2]
/people/person[2]/name
/people/person[2]/name/#first
/people/person[2]/name/#last
/people/singleElement