XSLT shorter version of OR conditional statement - xslt

I was wondering if someone remembers how to write a shorter OR statements in XSLT. I'm sure there was a way but I can't remember.
So instead of
test="$var = 'text1' or $var = 'text2'"
I'd like to use a shorter version like test="$var =['text1','text2']" However, I can't remember or find the right shorthand syntax for such cases.
Would really appreciate if someone could help with that!
Many thanks

With XSLT 2.0 (but not with XSLT 1.0) you can do
<xsl:if test="$var = ('text1','text2')">
Maybe that is the syntax you are looking for.

For string values as you appear to be using you can use a concat trick:-
test="contains('__text1____text2__', concat('__', $var, '__'))"
Not shorter for just two items but given 5 or more it starts to look better.
Having said that you probably can multi-line when using or's so it may be better just to use a series of or's:-
test = "
$var = 'text1'
or $var = 'text2'
or $var = 'text3'
or $var = 'text3'"
More text but clearer solution.

If you find that you do many comparisons against a fixed set of values, you can also do this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cfg="http://tempuri.org/config"
exclude-result-prefixes="cfg"
>
<xsl:output method="text" />
<!-- prepare a fixed list of possible values; note the namespace -->
<config xmlns="http://tempuri.org/config">
<val>text1</val>
<val>text2</val>
<!-- ... -->
</config>
<!-- document('') lets you access the stylesheet itself -->
<xsl:variable name="cfg" select="document('')/*/cfg:config/cfg:val" />
<xsl:template match="/">
<xsl:variable name="var" select="'text2'" />
<!-- check against all possible values in one step -->
<xsl:if test="$cfg[.=$var]">
<xsl:text>Match!</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The above would print
Match!

The [] operator only works on a nodeset. Maybe you're thinking of when you say something like [a|b] to select nodes from your nodeset that have a child element a or a child element b. But for string comparison I don't know of any way other than using "or".

There is no 'contains' function for sequences, but you could use index-of or intersect:
fn:exists(('test1', 'test2') intersect $var))
or
fn:exists(fn:index-of(('test1', 'test2'), $var))
With only two strings, your original solution is shorter though.

Related

How to use xsl:number count as condition in xsl:if test "condition"?

Within an xsl:for-each select loop, I have <xsl:number count="//headline"/> that correctly gives me the node #; Now I want to use that number in an xsl:if test block, but I cannot get the test expression right, msxml4.dll keeps kicking back errors. Am using xsl 1.0 (and stuck with it for now)
So, in <xsl:if test="expression">...output if the expression is true..</xsl:if>
I want the test expression to essentially be like this (so I can do something specific for Node #4, in this example):
<xsl:number count="//headline"/> = 4
This is what I have that does not work:
<xsl:if test="<xsl:number count="//headline"/> = 4">
Thanks in advance for any insights,
George
As #michael.hor257k explains, the general approach is to put the xsl:number call inside an xsl:variable (or in XSLT 2.0, inside an xsl:function). Sometimes though it's more convenient to abandon xsl:number:
<xsl:if test="count(preceding::headline) = 3">...</xsl:if>
If it's a big document then both xsl:number and preceding::headline are potentially expensive when executed inside a loop, and if this is a concern then you should compare them under your chosen XSLT processor: one may be optimized better than the other.
Come to think of it, your use of xsl:number looks odd to me. The count attribute is a pattern, and the pattern //headline matches exactly the same nodes as the pattern headline. As a result I misread your call on xsl:number as counting all the headlines in the document, whereas it actually only counts the preceding-sibling headlines. I wonder if that is what you intended?
If (!) I understand correctly, you want to do something like:
<xsl:variable name="n">
<xsl:number count="headline"/>
</xsl:variable>
<xsl:value-of select="$n"/>
<xsl:if test="$n = 4">
<!-- do something -->
</xsl:if>

How do I remove all excess whitespace from an XML string using ColdFusion?

I receive an XML string from a client in a format like the following...
<root>
<result success="1"/>
<userID>12345</userID>
<classID>56543</classID>
</root>
I need to compress this string down to the following...
<root><result success="1"/><userID>12345</userID><classID>56543</classID></root>
So, all of the whitespace is removed, except inside of the tag (so the space still exists between "result" and "success").
I have used replace statements to remove line breaks, carriage returns, etc, but I can't remove spaces while ignoring the spaces inside tags. Is there a way to use a regular expression or some other method to accomplish this?
The below regx would match the spaces which are not within the tags,
[\s]+(?![^><]*>)
OR
[\s]+(?![^><]*(?:>|<\/))
Just replace the matched spaces with an empty string.
DEMO
Edit Starts Here
From the comments - in the context of ColdFusion it works like this...
strClean = REReplace(strOriginal,"[\s]+(?![^><]*(?:>|<\/))","","All");
How about the simple Regex: >\s+?< and replace with ><. As a bonus over the accepted answer, this will keep the whitespace in leaf/terminal elements.
I didn't see the exact result i wanted using any Regex approach and I have a hunch that treating XML with Regex is not really comme il faut. For XML, I like to stay in the XML realm and you can accomplish what you want using XmlTransform.
Using this XSL
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
you can simply do this:
xmlOut = XmlTransform(xmlIn, stripSpaceXSL);
See Demo: https://trycf.com/gist/d6be6b6b8e04ccea3dbd9ece9c60fa2c/lucee5?theme=monokai
The simplest, working solution I see is to replace all whitespaces near outer side of angle brackets:
>\s+ by >, and
\s+< by <.
In ColdFusion it should be something like:
str = REReplace(str, ">\s+", ">", "All");
str = REReplace(str, "\s+<", "<", "All");

replace string in xslt 2.0 with replace function

I have a string like this
"My string"
Now I want to replace my with best so that the output will be like best string.
I have tried some thing like this
<xsl:value-of select="replace( 'my string',my,best)"/>
but probably its a wrong syntax
I have googled a lot but found nothing..every where the mechanism to do this XSLT 1.0 is explained.Can any one tell me how to do it in XSLT 2.0 ,The easy way compared to 1.0
Given:
<xsl:variable name="s1" select="'My string'"/>
Simply use:
<xsl:value-of select="replace($s1, 'My', 'best')"/>
Note that a regular expression is applied. Meaning:
<xsl:value-of select="replace('test.replace', '.', ':')"/>
Becomes:
::::::::::::
Be sure to escape the characters that have special meaning to the regular expression interpreter:
<xsl:value-of select="replace('test.replace', '\.', '::')"/>
Becomes:
test::replace
First check, if your xslt processor (saxxon) is the latest release. Then you have to set
<xsl:stylesheet version="2.0" in the head of your xslt-stylesheet. That's it.
Your code was fine, besides you forgot the apostrophs:
<xsl:value-of select="replace( 'my string',my,best)"/>
must be
<xsl:value-of select="replace('my string','my','best')"/>

xsl: how to use parameter inside "match"?

My xsl has a parameter
<xsl:param name="halfPath" select="'halfPath'"/>
I want to use it inside match
<xsl:template match="Element[#at1='value1' and not(#at2='{$halfPath}/another/half/of/the/path')]"/>
But this doesn't work. I guess a can not use parameters inside ''. How to fix/workaround that?
The XSLT 1.0 W3C Specification forbids referencing variables/parameters inside a match pattern.:
"It is an error for the value of the
match attribute to contain a
VariableReference"
There is no such limitation in XSLT 2.0, so use XSLT 2.0.
If due to unsurmountable reasons using XSLT2.0 isn't possible, put the complete body of the <xsl:template> instruction inside an <xsl:if> where the test in conjunction with the match pattern is equivalent to the XSLT 2.0 match pattern that contains the variable/parameter reference(s).
In a more complicated case where you have more than one template matching the same kind of node but with different predicates that reference variables/parameters, then a wrapping <xsl:choose> will need to be used instead of a wrapping <xsl:if>.
Well, you could use a conditional instruction inside the template:
<xsl:template match="Element[#at1='value1']">
<xsl:if test="not(#at2=concat($halfPath,'/another/half/of/the/path'))">
.. do something
</xsl:if>
</xsl:template>
You just need to be aware that this template will handle all elements that satisfy the first condition. If you have a different template that handles elements that match the first, but not the second, then use an <xsl:choose>, and put the other template's body in the <xsl:otherwise> block.
Or, XSLT2 can handle it as is if you can switch to an XSLT2 processor.
This topic had the answer to my question, but the proposed solution by Flynn1179 was not quite correct for me (YMMV). So try it the way it is suggested by people more expert than me, but if it doesn't work for you, consider how I solved it. I am using xsltproc that only handles XSL version 1.0.
I needed to match <leadTime hour="0024">, but use a param: <xsl:param name="hour">0024</xsl:param>. I found that:
<xsl:if test="#hour='{$hour}'"> did not work, despite statements here and elsewhere that this is the required syntax for XSL v.1.0.
Instead, the simpler <xsl:if test="#hour=$hour"> did the job.
One other point: it is suggested above by Dimitre that you put template inside if statement. xsltproc complained about this: instead I put the if statement inside the template:
<xsl:template match="leadTime">
<xsl:if test="#hour=$leadhour">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
In XSLT 2.0 you can refer to global variables within a match pattern, but the syntax is simpler than your guess:
<xsl:template match="Element[#at1='value1' and
not(#at2=$halfPath/another/half/of/the/path)]"/>
rather than
<xsl:template match="Element[#at1='value1' and
not(#at2='{$halfPath}/another/half/of/the/path')]"/>
Also, the semantics are not what you appear to be expecting: a variable referenced on the lhs of "/" must contain a node-set, not a fragment of an XPath expression.

Trying to match more than one class in XSLT

I'm very new to XSLT and trying to format some text for pdf's and I need to match and hide a few elements.
I am currently using:
<xsl:template match="*[#outputclass='LC ACaseName']">
to match:
<p outputclass="LC ACaseName">
and it works just fine.
What I now need to do is match 4 or 5 more
<p outputclass="<somestring>">
and apply the same style to them. I could easily just duplicate the above line substituting the different outputclass names each time but this is lazy and I know there must be a correct way of doing this which I should learn.
I hope I have provided enough info here. If I have missed anything please say.
thanks,
Hedley Phillips
You can specify multiple conditions in the predicate:
<xsl:template match="*[#outputclass='test' or #outputclass='blah']">
I couldn't find the duplicate...
In XSLT/XPath 1.0:
<xsl:template match="*[contains(
'|LC ACaseName|other class|',
concat('|',#outputclass,'|')
)
]">
<!-- Content Template -->
<xsl:template>
In XSLT/XPath 2.0:
<xsl:template match="*[#outputclass = ('LC ACaseName','other class')]">
<!-- Content Template -->
<xsl:template>
Note: For XSLT/XPath 1.0 solution you need a separator not being part of any item content.