Boolean expressions in XSLT select statements - xslt

I have the following XSLT code that almost does what I want:
<xsl:variable name="scoredItems"
select=
".//item/attributes/scored[#value='true'] |
self::section[attributes/variable_name/#value='SCORE']/item |
.//item//variables//variable_name"/>
I want to change this to a more complicated boolean expression:
<xsl:variable name="scoredItems"
select=
".//item/attributes/scored[#value='true'] or
(self::section[variable_name/#value='SCORE']/item and
(not (.//item/attributes/scored[#value='false']))) or
.//item//variables//variable_name"/>
However, when I run this, I get the following error:
javax.xml.transform.TransformerConfigurationException: Could not compile stylesheet
at org.apache.xalan.xsltc.trax.TransformerFactoryImpl.newTemplates(TransformerFactoryImpl.java:832)
at org.apache.xalan.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:618)
How do I fix this? (Note that I'm using XSLT 1.0.)

In my experience, the default exception thrown by XSLT in Java is not very helpful. You'll need to implement an instance of ErrorListener and use its methods to capture and report the true XSLT problem. You can attach this ErrorListener using the setErrorListener method of your TransformerFactory.

I would greatly discourage anyone to write complicated expressions -- in any language!
This is not an XSLT question at all. It is a general programming question and the answer is:
Never write too complicated expressions because they are challenging to write, read, test, verify, proof, change.
Split a complicated expression onto a number of simpler expressions and assign them to different variables. Then operate on these variables.

Related

Variable for XPath nodes

I'm starting to learn XSLT/XPath, and I copied the following from a study guide, making some modifications:
<xsl:variable name="fname" select="'polist.xml'"/>
<xsl:variable name="thePath" select="'/collection/doc'"/>
...
<xsl:value-of select="count(doc($fname)/collection/doc)"/>
It reports the number of doc elements in the XML file. The doc() function accepts the file name variable 'fname'. But if I try to do the same with the 'thePath' variable in the count() function, using $thePath instead of the "/collection/doc" text, I get an error.
Suggestions on whether/how to use the 'thePath' variable in the count() function? Is it possible? Thanks!
Learning from examples leaves you very exposed to this kind of problem: it's easy to build a completely incorrect mental model of how the examples actually work. That's why I always advise people to start by reading a good book that explains the concepts first.
In your case you've made a common mistake, which is to assume that variables work like macros, that is, that they represent fragments of XPath text that can be substituted into an expression. That's not the case: variables represent values, the result of evaluating an expression, and you can only use a variable in places where a literal value (like a number or string) could appear.
(I suspect it's the use of the $ sign that leads to this false impression. $ is often used to represent variables in macro-like languages, for example shell scripts).
In XPath 1.0 there's no direct way of achieving what you are trying to do. In practice people either use vendor extensions for this, or they construct a pipeline in which phase 1 generates an XSLT stylesheet and phase 2 executes it (that's easier in XSLT than in most other languages, because XSLT is XML and can therefore be easily manipulated using XSLT).
In 3.0 you can evaluate XPath expressions supplied in the form of a string using the xsl:evaluate instruction. But very often, the requirement can be met better using functions. We don't know what the real underlying requirement is here so it's hard to know whether that's true in this case.
An example use of xsl:evaluate in XSLT 3 would be e.g.
<xsl:evaluate xpath="'count(' || $thePath || ')'" context-item="doc($fname)"/>

XSLT template match: recipe for moving disallowed axes to predicate

I understand that the XSLT 1.0 standard disallows most XPath axes in the StepPatern portion of a match expression. (See this question where the recommended alternative was using the desired axis in a Predicate.)
I have a complex XPath expression that returns a node set, node-set-expression. I would like to make a template matching node-set-expression/ following-sibling::*. Is there a general way to rewrite this to use Predicates so that it can be used in the match attribute of a XSLT template element?
And equivalently, is there a general way to translate the following:
node-set-expression/ preceding-sibling::*
node-set-expression/ self-and-following-sibling::* (this is shorthand; I know it's not a valid axis)
If Predicates won't work, are there any other general approaches?
In XSLT 2.0 I tend to handle such cases by preselecting the matching nodes in a global variable:
<xsl:variable name="special-nodes" select="//something/preceding-sibling::*"/>
<xsl:template match="*[. intersect $special-nodes]"/>
In XSLT 3.0 this will simplify further to
<xsl:template match="$special-nodes"/>
An advantage of doing it this way is that searching for the "special nodes" once is likely to be a lot more efficient than testing every node against every such pattern when doing an apply-templates; it also makes the condition clearer, in my view.
The only general solution I know to your question for XSLT 1.0 is to write the pattern as
<xsl:template match="*[count(.|//something/preceding-sibling::*) =
count(//something/preceding-sibling::*)]">
but that really is too horribly inefficient to contemplate.

OR condition not working in xlst if

<xsl:when test="person/id!=127 or 112" >
or condition is not working in the above example. Please help
You've got your syntax wrong, and it should be an and, not or. Try this:
<xsl:when test="person/id!=127 and person/id!=112" >
If you put an or there, your condition is going to be true no matter what the value of ID is, because no number can equal 127 and 112 at the same time.
Try
<xsl:when test="person/id!=127 and person/id!=112">
Use:
not(person/id= 127 or person/id = 112)
Try to always avoid the != XPath operator, due to its unintuitive (and rarely useful) semantics when one or both of its arguments are node-sets 9or sequences in XPath 2.0).
When you have a long list to compare against, this kind of expression may be more convenient and efficient:
not(contains('|101|105|108|112|123|127|', concat('|', person/id, '|'))
I am not an xslt developer and never used it before. However, It appears that test statement is missing. what should xslt parser do with 112? no condition is provided for it.

Xslt: <xsl:value-of select="MyPath/$MyVariable" failed

<xsl:value-of select="$MyVar"/>
works but
<xsl:value-of select="MyDataPfath/$MyVar"/>
do not work.
What is wrong in my code?
From the look of it, what you are trying to achieve is 'dynamic evaluation'. XSLT does not support the dynamic evaluation of XPath by default, so you will need to make use of an extension function.
Depending on your XSLT processor, you might want to look at EXSLT extensions. In particular the dynamic module at http://www.exslt.org/dyn/index.html. This would allow to do something like this
<xsl:value-of select="dyn:evaluate('MyDataPfath/$MyVar')"/>
However, in your case, perhaps the $MyVar contains just a single element name. In which case you could change your command to the following, which would work without any extension functions
<xsl:value-of select="MyDataPfath/*[local-name() = $MyVar]"/>
Your code didn't fail, it did exactly what the specification says it should do. Which was different from what you were hoping/imagining that it might do.
Your hopes/imagination were based on a fundamental misunderstanding of the nature of variables in XPath. XPath variables are not macros. They don't work by textual substitution; they represent values. If the variable $E contains the string "X", then MyPath/$E means the same as MyPath/"X", which is illegal in XPath 1.0, and in XPath 2.0 returns as many instances of the string "X" as there are nodes in MyPath.
You probably intended MyPath/*[name()=$E]
it is not possible to get the value by using syntax 'MyDataPfath/$MyVar' in . it will not recognize the proper path.
suppose $MyVar has value 'Hi'. so it will be represented as 'MyDataPfath/"Hi"', this is not valid path, which you want to retrieve from the XML.
to remove this limitation, You can use name() or local-name() function, that can be used as follows:
or

XSL/XPath Indentation

What conventions (if any) do you use for indenting XSL code?
how do you deal with really long, complicated XPaths
can you plug them into your XML editor of choice?
is there some open source code that does the job well?
For some background, I use nxml-mode in Emacs. For the most part its OK and you can configure the number of spaces that child elements should be indented. Its not very good though when it comes to complicated XPaths. If I have a long XPath in my code, I like to make it's structure as transparent as possible by making it look something like this...
<xsl:for-each select="/some
/very[#test = 'whatever']
/long[#another-test = perhaps
/another
/long
/xpath[#goes='here']]
/xpath"
However, I currently have to do that manually as nxml will just align it all up with the "/some.."
Sometimes a longer xpath can't be avoided, even if you use templates instead of for-eaches (like you should, if you can). This is especially true in XSLT/XPath 2.0:
<xsl:attribute name="tablevel"
select="if (following::*[self::topic | self::part])
then (following::*[self::topic | self::part])[1]/#tablevel
else #tablevel"/>
I tend not to break a "simple" path across lines, but will break the "greater" path at operators or conditionals.
For editing, I use Oxygen (which is cross-platform) and it handles this kind of spacing pretty well. Sometimes it doesn't predict what you want exactly, but it will maintain the space once it's there, even if you re-indent your code.
In my opinion, long xpaths are hard to read and should be avoided. There are 2 ways to do it:
Simplify the source xml.
Split big templates into smaller ones.
Don't use long xpaths. Ditch the for-each and use match templates. Break down the xpath into several templates. It's much easier to read a bunch of trivial match templates than one of these.
I tend to break down the XSL differently if I'm having difficulty reading the xpath statements (which isn't very often, but it happens occasionally)... it's actually rather similar to my methods of breaking up syntax for other languages... So your example in the question might become something more like this:
<xsl:for-each select="/some/very[#test = 'whatever']/long">
<xsl:if test="#another-test = perhaps/another/long/xpath[#goes='here']">
<xsl:for-each select="xpath">
... result xml ....
</xsl:for-each>
</xsl:if>
</xsl:for-each>