I have an XSLT key defined. I need to access the key from within a for-each loop, where that loop is processing a node-set that is outside the scope of where the key was defined.
Snippet, where I've marked two lines, one which works and one which does not:
<xsl:value-of select="key('name', 'use')"/> <!-- works -->
<xsl:for-each select="$outOfScopeNodeSet">
<xsl:value-of select="key('name', 'use')"/> <!-- does not work -->
</xsl:for-each>
Is there a way to access the key from within the for-each loop?
XSLT 1.0, msxsl engine.
(I could not think of a reasonable way to provide a full working example for this. I'm also not sure of the correct terminology, such as "scope" - perhaps if I knew the correct terminology I'd be able to find my answer already. If the question is not clear enough please let me know and I'll try to edit it into better shape.)
In XSLT 1.0, keys do not work across documents. It seems that your $outOfScopeNodeSet contains a node-set whose root node is different from the root node of the XML document being processed (probably created by the exsl:node-set() function?) - while the key is supposed to fetch a value from the processed XML document.
To resolve this problem, you need to return the context back to the processed XML document before calling the key() function, for example:
<xsl:variable name="root" select="/" />
<xsl:for-each select="$outOfScopeNodeSet">
<xsl:variable name="use" select="some-value" />
<xsl:for-each select="$root">
<xsl:value-of select="key('name', $use)"/>
</xsl:for-each>
</xsl:for-each>
I have a DITA bookmap where I am storing image paths:
<bookmap>
<bookmeta>
<data id="productLogo">
<image href="images/_notrans/frontcover/productLogo.svg" />
</data>
<data id="productPhoto" >
<image href="images/_notrans/frontcover/productPhoto.jpg" />
</data>
</bookmeta>
</bookmap>
Then I attempt to grab the href values by data[#id]:
<xsl:variable name="productLogo"><xsl:value-of select="//data[#id='productLogo']/image/#href" /></xsl:variable>
<xsl:variable name="productPhoto"><xsl:value-of select="//data[#id='productPhoto']/image/#href" /></xsl:variable>
(These XPath expressions match the href when I test against my bookmap in Oxygen.)
During transformation I output:
<xsl:message>productPhoto: <xsl:value-of select="$productPhoto"/></xsl:message>
The value-of is always empty.
However, everything works as expected if I replace the id attribute with numbers:
<xsl:variable name="productLogo"><xsl:value-of select="//data[1]/image/#href" /></xsl:variable>
<xsl:variable name="productPhoto"><xsl:value-of select="//data[2]/image/#href" /></xsl:variable>
What am I doing wrong that's preventing using #id="whatever"?
The XSLT is not applied directly over the Bookmap contents, it is applied over an XML document which contains the bookmap with all topic references expanded in it and with some preprocessing applied to it.
If you set the "clean.temp" parameter to "no" you will find in the temporary files folder a file called something like "mapName_MERGED.xml", that is the XML document over which the XSLT is applied and as you will see in it, all IDs have been changed to be unique in the context of the entire XML document.
When usually working with data elements you should set the #name attribute to them like:
<data name="productLogo">
and match that name in the XSLT code.
There are examples of using "data" in the DITA 1.2 specs as well:
http://docs.oasis-open.org/dita/v1.2/os/spec/langref/data.html#data
Another option, depending on your needs, is to develop a naming convention for the product photos and use the element to build the URI. As the product logo shouldn't change for a product family, it wouldn't hurt to hard-code that in the XSLT code.
I'm processing an XMI document exported from ArgoUML. It has elements of the form
<UML:DataType href='http://argouml.org/profiles/uml14/default-uml14.xmi#-84-17--56-5-43645a83:11466542d86:-8000:000000000000087C'/>
which points to an item of the form
<UML:DataType xmi.id="-84-17--56-5-43645a83:11466542d86:-8000:000000000000087C"
name="Integer"
isSpecification="false"
isRoot="false"
isLeaf="false"
isAbstract="false"/>
I've already declared xmlns:UML="org.omg.xmi.namespace.UML" at the top of the xslt file. I think I should be using something like :
<xsl:variable name="typeref" select="#href"/>
<xsl:variable name="ns" select='substring-before($typeref, "#")'/>
<xsl:variable name="identifier" select='substring-after($typeref, "#")'/>
<xsl:value-of xmlns:UML="$ns"
select='//UML:DataType[#xmi.id="$identifier"]/#name'/>
to deduce that my UML attributes type is Integer but this gives me
SystemId Unknown; Line #136; Column #94; A location step was expected following the '/' or '//' token.
If I change the xmlns to AAA then I get no error but an empty tag. I'm using Xalan2 on Debian squeeze. What am I missing?
Don't mind me. Just making the classic mistake of conflating namespaces and URIs. What I really needed was <xsl:value-of select='document($ns)//UML:DataType[#xmi.id=$identifier]/#name'/>
I've found a lot of examples that reference Java and C for this, but how do I, or can I, check for the existence of an external file with XSL.
First, I realize that this is only a snippet, but it's part of a huge stylesheet, so I'm hoping it's enough to show my issue.
<!-- Use this template for Received SMSs -->
<xsl:template name="ReceivedSMS">
<!-- Set/Declare "SMSname" variable (local, evaluates per instance) -->
<xsl:variable name="SMSname">
<xsl:value-of select=" following-sibling::Name"/>
</xsl:variable>
<fo:table font-family="Arial Unicode MS" font-size="8pt" text-align="start">
<fo:table-column column-width=".75in"/>
<fo:table-column column-width="6.75in"/>
<fo:table-body>
<fo:table-row>
<!-- Cell contains "speakers" icon -->
<fo:table-cell display-align="after">
<fo:block text-align="start">
<fo:external-graphic src="../images/{$SMSname}.jpg" content-height="0.6in"/>
What I'd like to do, is put in an "if" statement, surronding the {$SMSname}.jpg line. That is:
<fo:block text-align="start">
<xsl:if test="exists( the external file {$SMSname}.jpg)">
<fo:external-graphic src="../images/{$SMSname}.jpg" content-height="0.6in"/>
</xsl:if>
<xsl:if test="not(exists( the external file {$SMSname}.jpg))">
<fo:external-graphic src="../images/unknown.jpg" content-height="0.6in"/>
</xsl:if>
</fo:block>
Because of "grouping", etc., I'm using XSLT 2.0. I hope that this is something that can be done. I hope even more that it's something simple.
As always, thanks in advance for any help.
LO
For anyone else who might stumble upon this old question, by piecing together information from various sources on the internet, I came up with this XSLT2 function that uses a Java extension for checking whether a file exists:
<?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:java="http://www.java.com/"
exclude-result-prefixes="java xs">
<xsl:function name="java:file-exists" xmlns:file="java.io.File" as="xs:boolean">
<xsl:param name="file" as="xs:string"/>
<xsl:param name="base-uri" as="xs:string"/>
<xsl:variable name="absolute-uri" select="resolve-uri($file, $base-uri)" as="xs:anyURI"/>
<xsl:sequence select="file:exists(file:new($absolute-uri))"/>
</xsl:function>
</xsl:stylesheet>
You can then use the function like this:
<xsl:if test="java:file-exists($filename, base-uri())">
<!-- ... -->
</xsl:if>
This works at least with Saxon 9.1.0.5J — haven't tested it with any other XSLT processors.
Note: If the file for whose existence you're checking is an XML file and you're using XSLT2, you can also use the built-in doc-available() function:
<xsl:if test="doc-available('hello_world.xml')">
<!-- ... -->
</xsl:if>
No, this cannot be done using XSLT 2.0/XPath 2.0.
The XSLT 2.0 function unparsed-text-available() is only suitable for locating text files and even if a text file with the specifies URI exists this function may return false(), because it also must read the contents of the file and check that it only contains allowed characters.
From the spec:
"The unparsed-text-available function determines whether a call on the unparsed-text function with identical arguments would return a string.
If the first argument is an empty sequence, the function returns false. If the second argument is an empty sequence, the function behaves as if the second argument were omitted.
In other cases, the function returns true if a call on unparsed-text with the same arguments would succeed, and false if a call on unparsed-text with the same arguments would fail with a non-recoverable dynamic error.
Note:
This requires that the unparsed-text-available function should actually attempt to read the resource identified by the URI, and check that it is correctly encoded and contains no characters that are invalid in XML
"
End of quotation.
In case someone else needs it and is using fop, I would like to share this answer since it worked for me like a charm.
I've found a solution:
<xsl:when test="fs:exists(fs:new('myfile.html'))" xmlns:fs="java.io.File">
<!-- do something here... -->
</xsl:when>
and it works independently of XSLT 1.0 or 2.0
EDIT: Use unparsed-text-available function. It is part of xslt 2.0, but not XQuery or standalone XPath.
I've left my previous answer here so you can follow the trail of uncertainty...
I don't believe there is a way of doing this in XSLT using the standard functions. You can do it using extension functions, as described here, for java.
There is the unparsed-text-available function, but I'm unsure if this is a standard function. There's an example of it's usage at Zvon. The unparsed-text-available is mentioned here as being part of xslt 2.0, and is supported in Saxon.
A proposed File Module EXPath specification would support file-system functions such as this (file:exists() in the spec) as standard XPath extension functions. There isn't yet an XSLT implementation for this, but its worth watching.
Back to pgfearo's notice.
A proposed File Module EXPath specification would support file-system
functions such as this (file:exists() in the spec) as standard XPath
extension functions. There isn't yet an XSLT implementation for this,
but its worth watching.
For those who need to check if an file exists or not.
file:exists($path as xs:string)
works fine now.
If you still wants to do it in XSLT here is the solution, I have done it for myself as explained below.
This will not working with regular java.io.File class in XSLT. So I have used java.nio.file.Files class.
JARS required - servelt.jar
w: is the namespace of the our own Java class where pathFromURI method is defined.
Code:
<xsl:variable name="fileURI" select="u:new($absoluteFilePath)" xmlns:u="java:java.net.URI"/>
<xsl:variable name="filePathFromURI" select="w:pathFromURI($fileURI)"/>
<xsl:variable name="fileNotExist" select="not(files:exists($filePathFromURI, /..)) or files:size($filePathFromURI) = 0"/>
public static java.nio.file.Path pathFromURI(java.net.URI uri) throws Exception {
return java.nio.file.Paths.get(uri);
}
It can't solely be done by standard XSLT, you have to use an extention function or some annoying workaround. There are two methods using extension functions: use of standard java/.NET for custom functions (works with some versions of Saxon, AltovaXML, and others), or use of processor specific extension functions, like saxon:file-last-modified()/saxon:last-modified(). You can find some sample code here, look for intern:file-exists().
If you can't use extension functions, you can either generate an XML file externaly which contains informations about your file system and pass it to your stylesheet, or you can wrap binary images within SVG and than use fn:doc-available().
If you need to check for the existence of an XML file, use an external entity and an inline doctype:
<!DOCTYPE foo [ <!ENTITY bar SYSTEM "baz.xml"> ]>
Then add the entity to the stylesheet:
&bar;
The processor will timeout if the file is not found.
If you know the files exist but want to dynamically load one out of many, use the concat function and a choose/when block:
<xsl:variable name="page_name">
<xsl:choose>
<xsl:when test="$page = 1">
<xsl:value-of select="'page1.xml'"/>
</xsl:when>
<xsl:when test="$page = 2">
<xsl:value-of select="'page2.xml'"/>
</xsl:when>
<!--...-->
<xsl:when test="$page = 9">
<xsl:value-of select="'page9.xml'"/>
</xsl:when>
<xsl:when test="$page = 10">
<xsl:value-of select="'page10.xml'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'page0.xml'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="agree" select="document(concat('include/pages/',$page_name))//processing-instruction()"/>
References
XML External Entity (XXE) Processing
For anyone who needs plain XSLT solution for checking if file exist you can just use
unparsed-text-available(concat(system-property('user.dir'),'/filename.text'))
or wrap it into a function
<xsl:function name="functx:file-exists" xmlns:functx="http://www.functx.com">
<xsl:param name="path"/>
<xsl:value-of select="unparsed-text-available(concat(system-property('user.dir'),'/',$path))"/>
</xsl:function>
using this unparsed-text-available(concat(system-property('user.dir'),'/filename.text'))=false() will mean that file does not exist.
Have fun!
I have an XML document that needs to pass text inside an element with an '&' in it.
This is called from .NET to a Web Service and comes over the wire with the correct encoding &
e.g.
T&O
I then need to use XSLT to create a transform but need to query SQL server through a SP without the encoding on the Ampersand e.g T&O would go to the DB.
(Note this all has to be done through XSLT, I do have the choice to use .NET encoding at this point)
Anyone have any idea how to do this from XSLT?
Note my XSLT knowledge isn’t the best to say the least!
Cheers
<xsl:text disable-output-escaping="yes">&<!--&--></xsl:text>
More info at: http://www.w3schools.com/xsl/el_text.asp
If you have the choice to use .NET you can convert between an HTML-encoded and regular string using (this code requires a reference to System.Web):
string htmlEncodedText = System.Web.HttpUtility.HtmlEncode("T&O");
string text = System.Web.HttpUtility.HtmlDecode(htmlEncodedText);
Update
Since you need to do this in plain XSLT you can use xsl:value-of to decode the HTML encoding:
<xsl:variable name="test">
<xsl:value-of select="'T&O'"/>
</xsl:variable>
The variable string($test) will have the value T&O. You can pass this variable as an argument to your extension function then.
Supposing your XML looks like this:
<root>T&O</root>
you can use this XSLT snippet to get the text out of it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="root"> <!-- Select the root element... -->
<xsl:value-of select="." /> <!-- ...and extract all text from it -->
</xsl:template>
</xsl:stylesheet>
Output (from Saxon 9, that is):
T&O
The point is the <xsl:output/> element. The defauklt would be to output XML, where the ampersand would still be encoded.