XSLT 2.0 Filter out element where all attributes are blank - xslt

I'm looking to do a simple filter that ignores elements whose attributes are all blank. I can look at each attribute individually:
<xsl:if test="#abc ne '' or #def ne '' ... >
But I think it would be more elegant to just look at all of them at once. This seems close but doesn't work:
<xsl:if test="count(#*[not(matches(., ''))]) ne 0">
Any suggestions on how to do this? Thanks in advance!

I might prefer a readable expression:
every $atr in #* satisfies $atr eq ''
or even:
every $atr in #* satisfies not(string($atr))
If you prefer something shorter, then perhaps:
not(#*!='')
P.S. Consider using a predicate instead of xsl:if.

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>

Access matched pattern within XSL tokenize

Suppose I have the following variable
<xsl:variable name="randomString" select="'COLUMN1 == 400 or COLUMN1 == 5 and COLUMN2 != 3'" />
Is there any convenient way to access the matched pattern within the tokenize() function e.g. using this
<xsl:for-each select="tokenize($randomString, 'and|or')">
<xsl:value-of select="concat('not(', current(), ')')" />
<!-- How do I access the matched pattern? -->
</xsl:for-each>
Or do I have to use a custom template like the one I have found here http://docbook.sourceforge.net/release/xsl/1.77.0/doc/lib/str.tokenize.keep.delimiters.html
No, there is no way to retrieve the matched separator. "The separators themselves are not returned." (http://www.w3.org/TR/xpath-functions/#func-tokenize)
A workaround could be, tokenizing with or as a separator in an outer loop, then tokenizing with and as a separator in an inner loop. Then you would always know which separators you were dealing with, based on where in the loops you are.
Another approach would be to use analyze-string(). See this answer.

XSL - Escaping an apostrophe during xsl:when test

I have the following code which appears to be failing.
<xsl:when test="$trialSiteName = 'Physician&apos;s Office'">
Also, visual studio is complaining saying
"Expected end of expression, found 's"
How am I supposed to escape the character?
XSLT v1.0. Apache XSL-FO processor.
Much more simple -- use:
<xsl:when test="$trialSiteName = "Physician&apos;s Office"">
Declare a variable:
<xsl:variable name="apos" select='"&apos;"'/>
Use the variable like this in the <xsl:when> clause:
<xsl:when test="$trialSiteName = concat('Physician', $apos, 's Office')">
&apos; works for XPath 1.0. If you are using XSLT 2.0 with XPath 2.0 try double apostrophe:
<xsl:when test="$trialSiteName = 'Physician''s Office'">
Look for a full explanation by Dimitre Novatchev in his answer Escape single quote in xslt concat function
in between " you can add what ever special characters you want.
<xsl:when test="$trialSiteName = "Physician's what ever special charactors plainly add Office"">

XSL For-Each exclusion by ID

I've got an XSL issue.
With the following code I exclude Box1
<xsl:for-each select="//box[#id!='box1']">
But I also want to exclude Box7.
Is that possible and how can I do that?
You can AND those predicates together:
<xsl:for-each select="//box[#id!='box1'][#id!='box7']">
Use:
//box[not(#id='box1') and not(#id='box2')]
If you have many ids to exclude, use (in this example I am excluding "box1" - "box4"):
//box[not(contains('|box1|box2|box3|box4|', concat('|', #id, '|'))]

XSLT shorter version of OR conditional statement

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.