Using an xsl param as argument to XPath function - xslt

I've been trying to figure out a way to use a param/variable as an argument to a function.
At the very least, I'd like to be able to use basic string parameters as arguments as follows:
<xsl:param name="stringValue" default="'abcdef'"/>
<xsl:value-of select="substring(string($stringValue),1,3)"/>
The above code generates no output.
I feel like I'm missing a simple way of doing this. I'm happy to use exslt or some other extension if an xslt 1.0 processor does not allow this.
Edit:
I am using XSL 1.0 and transforming using Nokogiri, which supports XPATH 1.0 . Here is a more complete snippet of what I am trying to do:
I want to pass column numbers as parameters using nokogiri as follows
document = Nokogiri::XML(File.read('table.xml'))
template = Nokogiri::XSLT(File.read('extractTableData.xsl'))
transformed_document = template.transform(document,
["tableName","'Problems'", #Table Heading
"tablePath","'Table'", #Takes an absolute XPATH String
"nameColumnIndex","2", #column number
"valueColumnIndex","3"]) #column number
File.open('FormattedOutput.xml', 'w').write(transformed_document)
My xsl then wants to access every TD[valueColumnIndex] and and retrieve the first 3 characters at that position, which is why I am using a substring function. So I want to do something like:
<xsl:value-of select="substring(string(TD[$valueColumnIndex]),1,3)"/>
Since I was unable to do that, I tried to extract TD[$valueColumnIndex] to another param valueCode and then do substring(string(valueCode),1,3)
That did not work either (which is to say, no text was output, whereas <xsl:value-of select="$valueCode"/> gave me the expected output).
As a result, i decided to understand how to use parameters better, I would just use a hard coded string, as mentioned in my earlier question.
Things I have tried:
using single quotes around abcdef (and not) while
using string() around the param name (and not)
Based on the comments below, it seems I am handicapped in my ability to understand the error because Nokogiri does not report an error for these situations. I am in the process of installing xsltproc right now and seeing if I receive any errors.
Finally, here is my entire xsl. I use a separate template forLoop because of the valueCode param I am creating. The lines of interest are the last 5 or so. I cannot include the xml as there are data use issues involved.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
exclude-result-prefixes="ext dyn">
<xsl:param name="tableName" />
<xsl:param name="tablePath" />
<xsl:param name= "nameColumnIndex" />
<xsl:param name= "valueColumnIndex"/>
<xsl:template match="/">
<xsl:param name="tableRowPath">
<xsl:value-of select="$tablePath"/><xsl:text>/TR</xsl:text>
</xsl:param>
<!-- Problems -->
<section>
<name>
<xsl:value-of select="$tableName" />
</name>
<!-- <xsl:for-each select="concat($tablePath,'/TR')"> -->
<xsl:for-each select="dyn:evaluate($tableRowPath)">
<!-- Encode record section -->
<xsl:call-template name="forLoop"/>
</xsl:for-each>
</section>
</xsl:template>
<xsl:template name="forLoop">
<xsl:param name="valueCode">
<xsl:value-of select="./TD[number($valueColumnIndex)][text()]"/>
</xsl:param>
<xsl:param name="RandomString" select="'Try123'"/>
<section>
<name>
<xsl:value-of select="./TD[number($nameColumnIndex)]"/>
</name>
<code>
<short>
<xsl:value-of select="substring(string($valueCode),1,3)"/>
</short>
<long>
<xsl:value-of select="$valueCode"/>
</long>
</code>
</section>
</xsl:template>
</xsl:stylesheet>

Use it this way:
<xsl:param name="stringValue" select="'abcdef'"/>
<xsl:value-of select="substring($stringValue,1,3)"/>

Related

XSLT to HTML transformation. generate-id() error in Qt5

I do a XSLT to HTML transformation using the method recommended in Qt doc:
QXmlQuery query(QXmlQuery::XSLT20);
query.setFocus(QUrl("myInput.xml"));
query.setQuery(QUrl("myStylesheet.xsl"));
query.evaluateTo(out);
Inside XSLT I use generate-id() method to generate unique ids for different DIV blocks. It works perfectly in Qt4.8, but not in Qt5.4
¿Anyone knows a reason for that, and how to solve this?
Edit: I get no error. What I get in Qt5 is always the same ID, while in Qt4 I get a different, unique ID each time i call generate-id().
I generate the ID this way:
<xsl:variable name="tc_id" select="generate-id()"/>
And I use it this way:
<xsl:value-of select="$tc_id"/>
This is the cpp code doing the transformation:
// generate output string
QXmlQuery query(QXmlQuery::XSLT20);
QString output;
query.setFocus(QUrl(_final_output_filepath.c_str()));
query.setQuery(xslt_code.c_str());
query.evaluateTo(&output);
Edit 2:
When I use this code...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:xdt="http://www.w3.org/2005/xpath-datatypes">
<xsl:template match="/">
<xsl:for-each select="trial/testsuite">
<xsl:for-each select="testcase">
<xsl:variable name="tc_index" select="position()"/>
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:value-of select="$tc_id"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...I get always the same ID.
<xsl:template match="/">
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/>
Thanks for the snippet. This was indeed why I kept bugging you about giving a reproducible example.
What happens here is that you call the generate-id() function multiple times without changing the context node. The default argument for this function is the context node (here: /, or the root node).
Unless you change the context node, this function is deliberately designed to be stable. That means that, if called repeatedly with the same argument (also meaning: the same default argument, the same context), it must return the same string.
It is also designed such that it always returns a unique string per distinct node. Two nodes are distinct if they have different position in the document (i.e., they are distinct even if they look the same, but appear on multiple places).
Bottom line: you didn't hit a bug in the Qt implementation of XSLT 2.0, but you hit a resolved issue that was a bug and was incidentally used as a feature.
If you require a unique ID in XSLT 2.0, and you are bound to giving the same context, there is probably something else that changes: for instance, you can be in a loop going over a set of numbers or strings. You can use this info to create a unique string.
Another "hack" in XSLT 2.0 is to use a single point in the specification where determinism is not guaranteed: on creation of new nodes:
<xsl:function name="my:gen-id" as="xs:string">
<xsl:sequence select="generate-id(my:gen-id-getnode())" />
</xsl:function>
<xsl:function name="my:gen-id-getnode" as="element()">
<node />
</xsl:function>
This small function touches on some advanced concepts and recently, people discussing in the XSL Working Group, have agreed that optimizing away the creation of the new node is allowed if the node identity is not required. Whether or not a processor correctly detects this is unclear.
In XSLT 3.0 a new property has been introduced on xsl:function: #new-each-time, which informs the processor that the function should be evaluated each time and not get inlined.
Update: tests with Qt 5.5 or 5.4
I have tested a variant of your code with Qt because I couldn't believe that identity (which is a core concept of XSLT) doesn't work with it. So, I created a document with similar-looking nodes of all six types (I ignored namespace nodes, as support for it is optional).
Input test document
<root test="bla">
<?pi do-something ?>
<row></row>
<!-- comment here -->
<row>content</row>
<row>content</row>
<row id="bla" xml:id="bla">content</row>
</root>
XSLT 2.0 code
Code slightly adjusted due to Qt not supporting #separator correctly.
<xsl:stylesheet
xmlns:my="my-functions"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- remove prev. and use this if you think for-each is different -->
<!--xsl:template match="/">
<xsl:for-each select="//node() | //#*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
</xsl:for-each>
</xsl:template-->
<xsl:function name="my:depth" as="xs:string">
<xsl:param name="node" />
<xsl:sequence select="
string(count($node/(/)//node()[$node >> .]) + 1)" />
</xsl:function>
<xsl:function name="my:decorate">
<xsl:param name="node" />
<xsl:sequence select="
($node/self::text(), 'text')[2],
($node/self::element(), concat('Q{}', name($node)))[2],
($node/self::document-node(), 'document')[2],
($node/self::comment(), 'comment')[2],
($node/self::attribute(), concat('#', name($node)))[2],
($node/self::processing-instruction(), 'processing-instruction')[2]
" />
</xsl:function>
</xsl:stylesheet>
Output using Exselt
gen-id(Q{}root[1]) = x1e2
gen-id(#test[2]) = x1e2a0
gen-id(processing-instruction[2]) = x1p3
gen-id(Q{}row[3]) = x1e4
gen-id(comment[4]) = x1c5
gen-id(Q{}row[5]) = x1e6
gen-id(text[6]) = x1t7
gen-id(Q{}row[7]) = x1e8
gen-id(text[8]) = x1t9
gen-id(Q{}row[9]) = x1e10
gen-id(#id[10]) = x1e10a0
gen-id(#xml:id[10]) = x1e10a1
gen-id(text[10]) = x1t11
Output using Qt 5.5 or 5.4
I used the pre-build xmlpatterns.exe and called it as xmlpatterns test.xsl input.xml, but its code uses the same libraries you are using:
gen-id(Q{}root[1]) = T756525610
gen-id(#test[2]) = T756525620
gen-id(text[2]) = T756525630
gen-id(processing-instruction[3]) = T756525640
gen-id(text[4]) = T756525650
gen-id(Q{}row[5]) = T756525660
gen-id(text[6]) = T756525670
gen-id(comment[7]) = T756525680
gen-id(text[8]) = T756525690
gen-id(Q{}row[9]) = T7565256100
gen-id(text[10]) = T7565256110
gen-id(text[11]) = T7565256120
gen-id(Q{}row[12]) = T7565256130
gen-id(text[13]) = T7565256140
gen-id(text[14]) = T7565256150
gen-id(Q{}row[15]) = T7565256160
gen-id(#id[16]) = T7565256170
gen-id(#xml:id[16]) = T7565256180
gen-id(text[16]) = T7565256190
gen-id(text[17]) = T7565256200
As this shows, stripping space does not work with Qt, as it considers them text nodes. But as you can also see, the generate-id function works for each and every node, whether they are processing instructions, text nodes, look the same, are empty elements etc. It didn't matter whether:
Using generate-id() vs generate-id(.)
Putting it in xsl:for-each or normal template processing
Using a variable to store the result prior to using
Hiding generate-id() inside another function
All returned the same, valid result.
UPDATE: Workaround
There's a relative expensive, yet workable workaround that you may be able to use, assuming that the generated ID in itself must be unique per document and node, but is not used in another way then for uniqueness (for instance, if used for cross-references, this will work).
<xsl:variable name="doc" select=".//node()" />
<xsl:function name="my:gen-id" as="xs:integer">
<xsl:param name="elem" as="node()" />
<xsl:sequence select="
for $i in 1 to count($doc)
return if($doc[$i] is $elem then $i else ())" />
</xsl:function>
This obviously has a performance hit, but if your documents are not that big and/or you do not call this function too often, it should be ok. You may consider creating a key if the subset for which you need this is defined.
A call to generate-id() returns a generated id of the context node and of course, if the context does not change, you will always get the same value.
I found out a solution for this problem. It seems to be some differences between Qt4 and Qt5 XSLT transformation engine in Linux.
The following code worked fine in Qt4, but not in Qt5. tc_id has always the same value:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="#result != 'pass'">
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="$tc_id"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="$tc_id"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
And the following code works fine both in Qt4 and Qt5:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="#result != 'pass'">
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="generate-id(.)"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="generate-id(.)"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
It seems there is some problem with declaring the variable.

url query xml and xpath transform

Short question: How to deal with a raw ampersand in xml input file.
ADDED: Im not even selecting the field with the ampersand. The parser complains at the presence of the ampersand within the file.
Long explanation:
im dealing with xml that is generated via a url response.
<NOTE>I%20hope%20this%20won%27t%20require%20a%20signature%3f%20%20
There%20should%20be%20painters%20%26%20stone%20guys%20at%20the
%20house%20on%20Wednesday%2c%20but%20depending%20on%20what%20time%20
it%20is%20delivered%2c%20I%20can%27t%20guarantee%21%20%20
Also%2c%20just%20want%20to%20make%20sure%20the%20billing%20address
%20is%20different%20from%20shipping%20address%3f
</NOTE>
which is url decoded into this:
<NOTE>I hope this won't require a signature?
There should be painters & stone guys at the
house on Wednesday, but depending on what time it is delivered, I can't guarantee!
Also, just want to make sure the billing address is different from shipping address?
</NOTE>
The Problem:
xslproc chokes on that last string because of the '&' in "painters & stone guys"
with the following error:
xmlParseEntityRef: no name
<NOTE>I hope this won't require a signature? There should be painters &
It looks like xsltproc expects a closing </NOTE>
Ive tried all manner of disable-output-escaping="yes" in various locations. xsl:output and xsl:value-of
And also tried xsltproc --decode-uri but cant figure out that one out. No documentation.
Note:
I wonder if its worth keeping the input in urlencoded format. And using a DOCTYPE..such as the following (not sure how to do that). The output is eventually a browser.
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp " ">
<!ENTITY copy "©">
<!ENTITY reg "®">
]>
The XML is malformed if there's an ampersand that's not escaped. If you put the string inside <![CDATA[]]>, then it should work.
<NOTE><![CDATA[I hope this won't require a signature?
There should be painters & stone guys at the
house on Wednesday, but depending on what time it is delivered, I can't guarantee!
Also, just want to make sure the billing address is different from shipping address?]]>
</NOTE>
Or, of course, use & instead of &.
Edit: You can also translate the URL escapes into numeric character references if the XSLT processor supports disable-output-escaping (and xsltproc does):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NOTE">
<xsl:copy>
<xsl:call-template name="decodeURL"/>
</xsl:copy>
</xsl:template>
<xsl:template name="decodeURL">
<xsl:param name="URL" select="string()"/>
<xsl:choose>
<xsl:when test="contains($URL,'%')">
<xsl:variable name="remainingURL" select="substring-after($URL,'%')"/>
<xsl:value-of disable-output-escaping="yes" select="concat(
substring-before($URL,'%'),
'&#x',
substring($remainingURL,1,2),
';')"/>
<xsl:call-template name="decodeURL">
<xsl:with-param name="URL" select="substring($remainingURL,3)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$URL"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Of course you don't have to use this transformation as a preprocessing step, you can re-use decodeURL in a stylesheet that transforms your source XML that includes the URL encoded string to HTML or whatever.

Calling the same xsl:template for different node names of the same complex type

I'm trying to keep my xsl DRY and as a result I wanted to call the same template for 2 sections of an XML document which happen to be the same complex type (ContactDetails and AltContactDetails). Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<RootNode>
<Name>Bob</Name>
<ContactDetails>
<Address>
<Line1>1 High Street</Line1>
<Town>TownName</Town>
<Postcode>AB1 1CD</Postcode>
</Address>
<Email>test#test.com</Email>
</ContactDetails>
<AltContactDetails>
<Address>
<Line1>3 Market Square</Line1>
<Town>TownName</Town>
<Postcode>EF2 2GH</Postcode>
</Address>
<Email>bob#bob.com</Email>
</AltContactDetails>
</RootNode>
I wrote an XSL Stylesheet as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<PersonsName>
<xsl:value-of select="RootNode/Name"/>
</PersonsName>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/ContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'FirstAddress'"/></xsl:with-param>
</xsl:call-template>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/AltContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'SecondAddress'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="ContactDetails">
<xsl:param name="data"></xsl:param>
<xsl:param name="elementName"></xsl:param>
<xsl:element name="{$elementName}">
<FirstLine>
<xsl:value-of select="$data/Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="$data/Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="$data/Address/Postcode"/>
</PostalCode>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When i try to run the style sheet it's complaining to me that I need to:
To use a result tree fragment in a path expression, either use exsl:node-set() or specify version 1.1
I don't want to go to version 1.1.. So does anyone know how to get the exsl:node-set() working for the above example?
Or if someone knows of a better way to apply the same template to 2 different sections then that would also really help me out?
Thanks
Dave
You are rolling this up from the wrong end (the wrong end being nearly always: trying to apply the imperative programming paradigm to XSLT).
This is really easy to do via template matching.
<xsl:template match="RootNode">
<PersonsName>
<xsl:value-of select="Name"/>
</PersonsName>
<xsl:apply-templates select="ContactDetails|AltContactDetails" />
</xsl:template>
<xsl:template match="ContactDetails|AltContactDetails">
<xsl:copy>
<FirstLine>
<xsl:value-of select="Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="Address/Postcode"/>
</PostalCode>
</xsl:copy>
</xsl:template>
Let go of the notion that you have to tell the XSLT processor what to do (through making named templates and calling them, "imperative style").
The XSLT processor chooses what templates to call. Starting at the root (/) it recursively checks for matching templates for every node it visits. It traverses your input XML all on its own - your only job is it to supply it with matching templates for those nodes you want to have processed in a special way.
You can drop in a custom template for those nodes that need special treatment and trust your XSLT processor with calling it once they come up. All you need to make sure in your templates is that traversal goes on by declaring an appropriate <xsl:apply-templates />.
Why not make the template
<xsl:template match="ContactDetails|AltContactDetails">
and do the test to determine the output element name inside the template instead?

XSLT 2.0 External lookup using key() and document()

I'm pulling what's left of my hair out trying to get a simple external lookup working using Saxon 9.1.0.7.
I have a simple source file dummy.xml:
<something>
<monkey>
<genrecode>AAA</genrecode>
</monkey>
<monkey>
<genrecode>BBB</genrecode>
</monkey>
<monkey>
<genrecode>ZZZ</genrecode>
</monkey>
<monkey>
<genrecode>ZER</genrecode>
</monkey>
</something>
Then the lookup file is GenreSet_124.xml:
<GetGenreMappingObjectsResponse>
<tuple>
<old>
<GenreMapping DepartmentCode="AAA"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="10 - NEWS"/>
</old>
</tuple>
<tuple>
<old>
<GenreMapping DepartmentCode="BBB"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="11 - NEWS"/>
</old>
</tuple>
... lots more
</GetGenreMappingObjectsResponse>
What I'm trying to achieve is simply to get hold of the "Genre" value based on the "DepartmentCode" value.
So my XSL looks like:
...
<!-- Set up the genre lookup key -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="lookupDoc" select="document('GenreSet_124.xml')"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc">
<xsl:with-param name="curr-label" select="genrecode"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode)/#Genre"/>
</xsl:template>
...
The issue that I have is that I get nothing back. I currently just get
<?xml version="1.0" encoding="UTF-8"?>
<stuff>
<Genre/>
<Genre/>
<Genre/>
<Genre/>
</stuff>
I have moved all the lookup data to be attributes of GenreMapping, previously as child elements of GenreMapping whenever I entered the template match="GetGenreMappingObjectsResponse" it would just print out all text from every GenreMapping (DepartmentCode, DepartmentName, Genre)!
I can't for the life of me figure out what I am doing wrong. Any helpo/suggestions would be greatly appreciated.
PLease find the current actual XSLT listing:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Define the global parameters -->
<xsl:param name="TransformationID"/>
<xsl:param name="TransformationType"/>
<!-- Specify that XML is the desired output type -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<!-- Set up the genre matching capability -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="documentPath"><xsl:value-of select="concat('GenreSet_',$TransformationID,'.xml')"/></xsl:variable>
<xsl:variable name="lookupDoc" select="document($documentPath)"/>
<!-- Start the first match on the Root level -->
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode, $lookupDoc)/#Genre"/>
</xsl:template>
</xsl:stylesheet>
The TransformationID is alway 124 (so the correct lookup file is opened. The Type is just a name that I am currently not using but intending to.
In XSLT 2.0 there are two ways you can do what you want:
One is the three-parameter version of the key function. The third parameter lets you specify the root node you want the key to work on (by default it's always the root of the main document):
<xsl:value-of select="key('genre-lookup', $curr-genrecode,$lookupDoc)/#Genre"/>
Another way is to use the key function under the $lookupDoc node:
<xsl:value-of select="$lookupDoc/key('genre-lookup', $curr-genrecode)/#Genre"/>
Both of these methods are documented in the XSLT 2.0 specification on keys, and won't work in XSLT 1.0.
For the sake of completeness, you'd have to rewrite this to not use keys if you're restricted to XSLT 1.0.
<xsl:value-of select="$lookupDoc//GenreMapping[#DepartmentCode = $curr-genrecode]/#Genre"/>
Aha! The problem is the select="$lookupDoc" in your apply-templates call is calling a default template rather than the one you expect, so the parameter is getting lost.
Change it to this:
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
That will call your template properly and the key should work.
So the final XSLT sheet should look something like this:
<xsl:variable name="lookupDoc" select="document('XMLFile2.xml')"/>
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup',$curr-genrecode,$lookupDoc)/#Genre"/>
</xsl:template>
OK, so this is a bit mental and I don't claim to understand it but it works (sounds like a career in software).
The issue I was having is that when I call the apply-templates and pass in the external document as a variable it never matched any templates even ones called "Genremapping".
So I used a wildcard to catch it, also when calling the apply-templates I narrowed down the node set to be the child I was interested in. This was pretty bonkers a I could print out the name of the node and see "GenreMapping" yet it never went into any template I had called "GenreMapping" choosing instead to only ever go to "*" template.
What this means is that my new template match gets called for every single GenreMapping node there is so it may be a little inefficient. What I realised then was all I needed to do was print sometihng out if a predicate matched.
So it looks like this now (no key used whatsoever):
...
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<key_value><xsl:value-of select="genrecode"/></key_value>
<xsl:variable name="key_val"><xsl:value-of select="genrecode"/></xsl:variable>
<code>
<xsl:apply-templates select="$lookupDoc/*/*/*/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</code>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="*">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select=".[#DepartmentCode = $curr-genrecode]/#Genre"/>
</xsl:template>
...
All of which output:
Note, the last key_value correctly doesn't have a code entry as there is no match in the lookup document.
<?xml version="1.0" encoding="UTF-8"?>
<stuff xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>BBB</key_value>
<code>11 - NEWS</code>
</Genre>
<Genre>
<key_value>SVVS</key_value>
<code/>
</Genre>
</stuff>
Answer on a postcode. Thanks for the help Welbog.

Producing a new line in XSLT

I want to produce a newline for text output in XSLT. Any ideas?
The following XSL code will produce a newline (line feed) character:
<xsl:text>
</xsl:text>
For a carriage return, use:
<xsl:text>
</xsl:text>
My favoured method for doing this looks something like:
<xsl:stylesheet>
<xsl:output method='text'/>
<xsl:variable name='newline'><xsl:text>
</xsl:text></xsl:variable>
<!-- note that the layout there is deliberate -->
...
</xsl:stylesheet>
Then, whenever you want to output a newline (perhaps in csv) you can output something like the following:
<xsl:value-of select="concat(elem1,elem2,elem3,$newline)" />
I've used this technique when outputting sql from xml input. In fact, I tend to create variables for commas, quotes and newlines.
Include the attribute Method="text" on the xsl:output tag and include newlines in your literal content in the XSL at the appropriate points. If you prefer to keep the source code of your XSL tidy use the entity
where you want a new line.
You can use: <xsl:text>
</xsl:text>
see the example
<xsl:variable name="module-info">
<xsl:value-of select="#name" /> = <xsl:value-of select="#rev" />
<xsl:text>
</xsl:text>
</xsl:variable>
if you write this in file e.g.
<redirect:write file="temp.prop" append="true">
<xsl:value-of select="$module-info" />
</redirect:write>
this variable will produce a new line infile as:
commons-dbcp_commons-dbcp = 1.2.2
junit_junit = 4.4
org.easymock_easymock = 2.4
IMHO no more info than #Florjon gave is needed. Maybe some small details are left to understand why it might not work for us sometimes.
First of all, the &#xa (hex) or &#10 (dec) inside a <xsl:text/> will always work, but you may not see it.
There is no newline in a HTML markup. Using a simple <br/> will do fine. Otherwise you'll see a white space. Viewing the source from the browser will tell you what really happened. However, there are cases you expect this behaviour, especially if the consumer is not directly a browser. For instance, you want to create an HTML page and view its structure formatted nicely with empty lines and idents before serving it to the browser.
Remember where you need to use disable-output-escaping and where you don't. Take the following example where I had to create an xml from another and declare its DTD from a stylesheet.
The first version does escape the characters (default for xsl:text)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:template match="/">
<xsl:text><!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
</xsl:text>
<xsl:copy>
<xsl:apply-templates select="*" mode="copy"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="copy"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
and here is the result:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
<Subscriptions>
<User id="1"/>
</Subscriptions>
Ok, it does what we expect, escaping is done so that the characters we used are displayed properly. The XML part formatting inside the root node is handled by ident="yes". But with a closer look we see that the newline character &#xa was not escaped and translated as is, performing a double linefeed! I don't have an explanation on this, will be good to know. Anyone?
The second version does not escape the characters so they're producing what they're meant for. The change made was:
<xsl:text disable-output-escaping="yes"><!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
</xsl:text>
and here is the result:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
<Subscriptions>
<User id="1"/>
</Subscriptions>
and that will be ok. Both cr and lf are properly rendered.
Don't forget we're talking about nl, not crlf (nl=lf). My first attempt was to use only cr:&#xd and while the output xml was validated by DOM properly.
I was viewing a corrupted xml:
<?xml version="1.0" encoding="utf-8"?>
<Subscriptions>riptions SYSTEM "Subscriptions.dtd">
<User id="1"/>
</Subscriptions>
DOM parser disregarded control characters but the rendered didn't. I spent quite some time bumping my head before I realised how silly I was not seeing this!
For the record, I do use a variable inside the body with both CRLF just to be 100% sure it will work everywhere.
You can try,
<xsl:text>
</xsl:text>
It will work.
I added the DOCTYPE directive you see here:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nl "
">
]>
<xsl:stylesheet xmlns:x="http://www.w3.org/2005/02/query-test-XQTSCatalog"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
This allows me to use &nl; instead of
to produce a newline in the output. Like other solutions, this is typically placed inside a <xsl:text> tag.
I second Nic Gibson's method, this was
always my favorite:
<xsl:variable name='nl'><xsl:text>
</xsl:text></xsl:variable>
However I have been using the Ant task <echoxml> to
create stylesheets and run them against files. The
task will do attribute value templates, e.g. ${DSTAMP} ,
but is also will reformat your xml, so in some
cases, the entity reference is preferable.
<xsl:variable name='nl'><xsl:text>
</xsl:text></xsl:variable>
I have found a difference between literal newlines in <xsl:text> and literal newlines using
.
While literal newlines worked fine in my environment (using both Saxon and the default Java XSLT processor) my code failed when it was executed by another group running in a .NET environment.
Changing to entities (
) got my file generation code running consistently on both Java and .NET.
Also, literal newlines are vulnerable to being reformatted by IDEs and can inadvertently get lost when the file is maintained by someone 'not in the know'.
I've noticed from my experience that producing a new line INSIDE a <xsl:variable> clause doesn't work.
I was trying to do something like:
<xsl:variable name="myVar">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My value: </xsl:text>
<xsl:value-of select="#myValue" />
<xsl:text></xsl:text> <!--NEW LINE-->
<xsl:text>My other value: </xsl:text>
<xsl:value-of select="#myOtherValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<div>
<xsl:value-of select="$myVar"/>
</div>
Anything I tried to put in that "new line" (the empty <xsl:text> node) just didn't work (including most of the simpler suggestions in this page), not to mention the fact that HTML just won't work there, so eventually I had to split it to 2 variables, call them outside the <xsl:variable> scope and put a simple <br/> between them, i.e:
<xsl:variable name="myVar1">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My value: </xsl:text>
<xsl:value-of select="#myValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<xsl:variable name="myVar2">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My other value: </xsl:text>
<xsl:value-of select="#myOtherValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<div>
<xsl:value-of select="$myVar1"/>
<br/>
<xsl:value-of select="$myVar2"/>
</div>
Yeah, I know, it's not the most sophisticated solution but it works, just sharing my frustration experience with XSLs ;)
I couldn't just use the <xsl:text>
</xsl:text> approach because if I format the XML file using XSLT the entity will disappear. So I had to use a slightly more round about approach using variables
<xsl:variable name="nl" select="'
'"/>
<xsl:template match="/">
<xsl:value-of select="$nl" disable-output-escaping="no"/>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:text xml:space="preserve">
</xsl:text>
just add this tag:
<br/>
it works for me ;) .