How and where could I output log messages for debug and performance purposes during an XSLT transformation?
I guess the simplest method is using expressions like this:
<xsl:text>message text</xsl:text>
here and there in the code, using xsl:value-of if needed.
But this method prints the messages in a lot of places in the output file (HTML page in my case), that is where it is called, and not always in the same place (like a log file).
Is this the only way or is there a better solution? Thanks!
This is exactly what <xsl:message> is designed for. However, the output location is entirely dependent on the processor. I only have a Mac handy but, sadly, both Firefox and Safari suppress the <xsl:message> output. I expect MSIE will do the same.
Given that, I think your best bet is to use <xsl:comment> to generate your logs. Something like the below should do the trick:
<xsl:template match='my-element'>
<xsl:comment>Entering my-element template</xsl:comment>
<p class='my-element'><xsl:apply-templates/></p>
<xsl:comment>Leaving my-element template</xsl:comment>
</xsl:template>
That would give you something like this in the output:
<!-- Entering my-element template -->
<p class='my-element'>...</p>
<!-- Leaving my-element template -->
Clearly, you can put whatever logging you want into that that output. I would consider creating something like the following and using it to run your logging. This references a global param called 'enable-logging' to determine if logging should occur or not.
<xsl:template name='create-log'>
<xsl:param name='message'/>
<xsl:if test="$enable-logging = 'yes'">
<xsl:comment><xsl:value-of select='$message'/></xsl:comment/>
</xsl:if>
</xsl:template>
Use this in your stylesheet as:
<xsl:template match='my-element'>
<xsl:call-template name='create-log'>
<xsl:with-param name='message'/>Entering my-element template</xsl:with-param>
</xsl:call-template>
<p class='my-element'><xsl:apply-templates/></p>
<xsl:call-template name='create-log'>
<xsl:with-param name='message'/>Leaving my-element template</xsl:with-param>
</xsl:call-template>
</xsl:template>
One benefit of doing it this way is you can change that <xsl:comment> to <xsl:message> when in a more complete environment. It is more verbose but more general.
I would suggest using xsl:message if you are in a development environment such as oXygen or Stylus Studio, and using xsl:comment if you are running in the browser. You shouldn't really be debugging your XSLT code in the browser - the browsers I know about are lousy as XSLT debugging tools.
By looking for a solution to this problem, I ended up implementing a logging mechanism in XSLT in similar fashion to log4j, using mostly xsl:message and pure XSLT 2.x. I took some of the answers in this page as inputs. The library is available here: https://github.com/ukuko/log4xslt
Modifying your XSLT itself for the purposes of logging is inevitably going to impact on performance, you're probably better off using an external tool. There are a few available depending on what you're working with:
Stylus Studio
Visual Studio
Altova XMLSpy
You should be able to use <xsl:message> I think, although where the logging goes is implementation-dependent.
If you're using Xalan you can download the "Some Xalan Extensions" jar (net.adamjenkins.sxe on maven).
It works with slf4j and allows for
<log:debug message="some message ${somexpath}"/>
or
<log:info select="./blahblahblah"/>
etc for each log level.
A simple hack would be to create a variable with xsl:variable and to either just concat() new values to it or to set up a xsl:template which does the same thing. Then you just need to output this variable at the end of the execution and you can explicitly choose where to show the log.
Related
I'm trying to do some operation on dates using XSL. I found a tutorial on IBM developerWorks with this script.
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
version="1.0"
>
<xsl:output method="html"/>
<!-- B -->
<xsl:variable name="now" select="date:date-time()"/>
<xsl:template match="/">
<!-- The rest of the Web site HTML material would go here -->
<xsl:call-template name="date-section"/>
</xsl:template>
<xsl:template name="date-section">
<p>This page was loaded at <xsl:text/>
<!-- C -->
<xsl:value-of select="concat(date:hour-in-day($now), ':',
date:minute-in-hour($now), ':',
date:second-in-minute($now))"/>
<xsl:text> on </xsl:text>
<xsl:value-of select="concat(date:day-in-month($now), ' ',
date:month-name($now), ' ',
date:year($now))"/>
</p>
<p>
<!-- D -->
<xsl:variable name="days-elapsed"
select="concat('-P',date:day-in-month($now),'D')"/>
<xsl:variable name="one-month-hence"
select="date:add($now, 'P1M')"/>
<xsl:variable name="next-month-start"
select="date:add($one-month-hence, $days-elapsed)"/>
<xsl:variable name="seconds"
select="date:seconds(
date:difference($now, $next-month-start)
)"/>
<xsl:text>The next month starts in </xsl:text>
<xsl:value-of select="$seconds div (3600*24)"/>
<xsl:text> days</xsl:text>
</p>
</xsl:template>
I get the following errors:
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.add' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.difference' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.seconds' (must be public).
[ERROR]: Cannot convert data-type 'void' to 'real'.
[FATAL]: Could not compile stylesheet
Any ideas how to fix it. I'm using IntelliJ to run the xsl.
Ultimately I want to to be able to add days to a date. For some reason when I try to use xsl 2 date functions it just tells me they do not exist(And yes I do change the header to version 2 :) ). So I'm trying to make it work with a 3rd party library. The date-time() function from the http://exslt.org/dates-and-times namespace is working but I can't seam to be able to call other functions form that namespace.
the question is a bit old but the answers were, IMHO, not quite correct and my answer might help some other people stumbling upon this thread (as I just did):
The functions used are XSLT 1.0 extension functions, there are different modules (e.g. dates-and-times), each with mandatory and optional functions (and elements). See EXSLT
The issue here is that XALAN does support the module dates-and-times, but not all optional functions.
As suggested, you can surely try to find a "better" XSLT-Processor, but it might require some work to integrate. I personally use on the Linux command line xsltproc, which supports more of the dates-and-times module (try xsltproc --dumpextensions | sort for an overview).
Alternatively, you could try the approach describing how to get current date and time in XSLT 1.0. I haven't tried it myself yet (as said, xsltproc is a good enough workaround), but using a similar approach you should be able to implement what you need without having to touch your tool/java code.
For sake of completeness, my tool with the same problem is Freeplane from freeplane.org, which also uses XALAN (I guess that it's part of standard Java libraries) and fails on similar dates-and-times functions.
Hope this helps,
Eric
I don't know Xalan, so I don't know why it's failing to find these methods.
But if you want to use the XSLT 2.0 date/time functions, you need to invoke an XSLT 2.0 processor, and the obvious one for the Java environment is Saxon (current version is 9.4). Switching processor requires a little bit more than changing the version number: for example, if you are running it from the command line then you will need to use a different command.
This question actually asks something quite different. See the comments to #Tomalak's answer to understand what the OP really wanted. :(
Is there a way to store a variable/param during a for-each loop in a sort of array, and use it in another template, namely <xsl:template match="Foundation.Core.Classifier.feature">.
All the classname values that appear during the for-each should be stored. How would you implement that in XSLT? Here's my current code.
<xsl:for-each select="Foundation.Core.Class">
<xsl:for-each select="Foundation.Core.ModelElement.name">
<xsl:param name="classname">
<xsl:value-of select="Foundation.Core.ModelElement.name"/>
</xsl:param>
</xsl:for-each>
<xsl:apply-templates select="Foundation.Core.Classifier.feature" />
</xsl:for-each>
Here's the template in which the classname parameters should be used.
<xsl:template match="Foundation.Core.Classifier.feature">
<xsl:for-each select="Foundation.Core.Attribute">
<owl:DatatypeProperty rdf:ID="{Foundation.Core.ModelElement.name}">
<rdfs:domain rdf:resource="$classname" />
</owl:DatatypeProperty>
</xsl:for-each>
</xsl:template>
The input file can be found at http://krisvandenbergh.be/uml_pricing.xml
No, it is not possible to store a variable in a for-each loop and use it later.
This is because variables are write-once in XSLT (once set they are immutable) and they are strictly scoped within their parent element. Once processing leaves the for-each loop, the variable is gone.
XSLT does not work as an imperative programming language, but that's what you seem to be trying here. You don't need <xsl:for-each> in 98% of all cases and should not use it because it clogs your view of how XSLT works. To improve your XSLT code, get rid of all <xsl:for-each> loops you have (all of them, I mean it) and use templates instead:
<xsl:template match="Foundation.Core.Class">
<xsl:apply-templates select="
Foundation.Core.Classifier.feature/Foundation.Core.Attribute
" />
</xsl:template>
<xsl:template match="Foundation.Core.Attribute">
<owl:DatatypeProperty rdf:ID="{Foundation.Core.ModelElement.name}">
<rdfs:domain rdf:resource="{
ancestor::Foundation.Core.Class[1]/Foundation.Core.ModelElement.name[1]
}" />
</owl:DatatypeProperty>
</xsl:template>
(I'm not sure if the above is what you actually want, your question is rather ambiguous.)
Note the use of the XPath ancestor axis to refer to an element higher in the hierarchy (you seem to want the <Foundation.Core.ModelElement.name> of the parent class).
PS: Your XML is incredibly bloated and strongly redundant due to structured element names. Structure should come from... well... structure, not from elements like <Foundation.Core.Classifier.feature>. I'm not sure if you can do anything about it, though.
Addition:
To solve your xmi.id / xmi.idref problem, the best way is to use an XSL key:
<!-- this indexes all elements by their #xmi.id attribute -->
<xsl:key name="kElementByIdref" match="*[#xmi.id]" use="#xmi.id" />
<!-- now you can do this -->
<xsl:template match="Foundation.Core.DataType">
<dataTypeName>
<!-- pull out the corresponding element from the key, output its value -->
<xsl:value-of select="key('kElementByIdref', #xmi.idref)" />
</dataTypeName>
</xsl:template>
To better understand how keys work internally, you can read this answer I gave earlier. Don't bother too much with the question, just read the lower part of my answer, I explained keys in terms of JavaScript.
Ok, I now understand why for-each is not always needed. Consider the code below.
<Foundation.Core.DataType xmi.id="UID71848B1D-2741-447E-BD3F-BD606B7FD29E">
<Foundation.Core.ModelElement.name>int</Foundation.Core.ModelElement.name>
</Foundation.Core.DataType>
It has an id UID71848B1D-2741-447E-BD3F-BD606B7FD29E
Way elsewhere I have the following.
<Foundation.Core.StructuralFeature.type>
<Foundation.Core.DataType xmi.idref="UID71848B1D-2741-447E-BD3F-BD606B7FD29E"/>
</Foundation.Core.StructuralFeature.type>
As you can see, both codes have the same ID. Now I want to output "int", everytime this ID appears somewhere in the document. So basically idref="UID71848B1D-2741-447E-BD3F-BD606B7FD29" should be replaced by int, which can be easily derived from the Foundation.Core.ModelElement.name element.
Above is the main reason why I would like to store it in a variable. I don't get it how this can be dealt with using XSLT. If someone could elaborate on this, I hope there exists some kind of pattern to solve such a problem, since I need it quite often. What would be a good approach?
I understand this is maybe a bit off-topic, but I am willing to ask it in this thread anyway since this problem is very close to it.
My problem:
I have a wealth of atom RSS feed files which have many different atom entries in them and a few overlapping entries between files. I need to find and return an entry based on a URL from any one of the RSS feeds.
Technologies:
This code is being run through PHP 5.2.10's XLSTProcessor extension, which uses XSLT 1, has support for EXSLT and ability to run built in PHP functions. Saxan, Xalan or other similar solutions are not too helpful in this particular situation.
The following code is greatly simplified, but represents my situation.
rss-feed-names.xml:
<feeds>
<feed name="travel.xml"/>
<feed name="holidays.xml"/>
...
<feed name="summer.xml"/>
<feed name="sports.xml"/>
</feeds>
stylesheet.xsl
<xsl:stylesheet ...>
...
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<func:result select="$feed-entry"/><!-- this causes errors if more than one result is found -->
</xsl:if>
</xsl:for-each>
</func:function>
</xsl:stylesheet>
...
This code works just fine iff the atom entry that we're looking for appears in ONE of the files we look through. It may appear multiple times within that file, but as soon as it appears in two or more files, the code breaks because func:result was already instantiated and is being over-written, which is a no-no in XSLT.
If there is a way to ACTUALLY exit an EXSLT function or xsl:for-each "loop" (you can assign a return variable for a function, but the function continues; and for-each's are actually not loops, but more similar to function maps), that would be ideal but I have not found a way yet.
I have considered combining all feeds into one variable and removing the for-each loop altogether, but have had problems getting this to work from the beginning.
Any other possible solutions, ideas or pointers are much appreciated! The file relationship here and XML is pretty hard to change, so solutions suggesting such a change are not ideal.
Thanks in advance,
Tristan Eastburn
The general answer (as with Jim's response) is that you shouldn't put <func:result> inside <xsl:for-each>. My more specific solution for this case doesn't require you to use <xsl:for-each> or even <xsl:variable>. You can use just XPath alone:
<func:result select="(document
(document('rss-feed-names.xml')/feeds/#name)
/atom:feed
/atom:entry
[atom:link
[contains(#href, $post-url)]]
)[1]"/>
This works because document() can take a node-set. When it does, it goes and gets the document that's referenced by each node in the argument. Multiple input/multiple output.
That said, judicious use of explaining variables would be good to help readability. Still, you don't need to use <xsl:for-each>.
Since you can't force exit from the loop, you have to build the complete list and then return only the first element:
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:variable name="feedList">
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<xsl:value-of select="$feed-entry"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$feedList[1]"/>
</func:function>
Handling of the empty-list condition is left as an exercise :-)
I have a set of XML files that I am processing with an XSL transform. They have a default namespace, so my XSL transform must contain the declaration:
xpath-default-namespace="urn:CZ-RVV-IS-VaV-XML-NS:data-1.2.2"
The problem is that this value changes from time to time, and my transform suddenly stops working, until I look at an example from the new file, extract this namespace ID and put it in the transform, whereby the transform stops working for old files. Is there a way to pass this as a parameter, or set it somehow at runtime? I have tried the parameter syntaxes that I looked up in various tutorials, but none have worked for this particular use.
I have searched all sorts of forums and found references to namespace-agnostic coding of XSL, but not figured out how to do it. Ian Williams' book "XSLT and Xpath" states that the default namespace must be declared, or you get nothing in the output stream, which is how it has worked for me. But I really don't want to have to change this by hand regularly, I want to give the user something that will work, without needing constant attention from me.
The only 100% reliable way I have invented so far is to use a standard programming language to open both the XML source and XSL transform as text files, extract the URI from the XML source, paste it into the XSL transform, close both files and then, finally run the actual transform. This works, but is incredibly dorky, at least to my taste. How can I better deal with changing default namespaces?
Pete
The value of xpath-default-namespace must be a static URI, so you'll have to pre-process the stylesheet if you want it to vary. One way to do that would be to use XSLT. Apply the following meta-stylesheet to your primary stylesheet each time, and then invoke the pre-processed result instead.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Pass in the new namespace URI as a stylesheet parameter -->
<xsl:param name="new-uri" required="yes"/>
<!-- By default, copy everything as is -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- But update the value of #xpath-default-namespace -->
<xsl:template match="#xpath-default-namespace">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="$new-uri"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is a bit of a strange use case though, because namespaces weren't really designed to be so dynamic. They were designed to qualify names, i.e. make up part of a name. When you look at it that way, dynamic namespaces don't make a lot of sense. Imagine a database whose table and field names arbitrarily changed every once in a while, forcing you to rewrite all your SQL scripts to keep up with the changes. That's what this is akin to.
Have you tried defining a stylesheet parameter <xsl:param name="xpdn"/> and using it in the stylesheet declaration or top level template declaration as in
<xsl:template match="...." xpath-default-namespace="$xpdn">
I can't find anything in the spec that says this won't work (but I'm not in a position to try it just now).
If one is using the document function and opening up files that might not exist e.g.
<xsl:variable name="input" select="document($A)/document/input"/>
what is the graceful way of handling the error? I would like a default value for the variable if the file can't be opened.
There isn't a general way to handle gracefully an error in the document() function.
According to the XSLT 1.0 spec:
" If there is an error retrieving the resource, then the XSLT processor may signal an error; if it does not signal an error, it must recover by returning an empty node-set. "
This means that we are at the mercy of the implementor whether an empty node-set is produced by the function (good, we can test for empty (non-existent) node-set) or "signals an error", which typically may end the transformation.
In case we have checked that a particular implementation of a specific XSLT processor only produces an empty node-set and does not end the transformation, we may decide to test for this condition and "gracefully" recover. However, our application becomes non-portable, it depends on this specific XSLT processor and there is absolutely no guarantee that in the next version this behaviour will not change to the worse one. Risky, isn't it?
Therefore, it is best that whoever launches the transformation (such as from within a C# program), should check for the existence of the file and pass an appropriate parameter to the transformation, reflecting this existence.
What about using doc-available() function available in XPath 2.0 ? :
http://www.w3.org/TR/xpath-functions/#func-doc-available
I believe you can write your <xsl:variable> like so:
<xsl:variable name="input">
<xsl:choose>
<xsl:when test="document($A)/document/testElementCondition = 'foo'">
<xsl:value-of select="document($A)/document/input" />
</xsl:when>
<xsl:otherwise>
<!-- add some default source document and logic that will then direct to an error message. -->
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
It is too bad that you often have to result to hacks to get things done in XSL.
The graceful way would be to check file existence before you feed the parameter to your stylesheet.