Why isn't local-name() returning anything? - xslt

I'm trying to run the following template:
<xsl:template match="*[starts-with(., 'ATTITUDE_')]/text()">
<xsl:variable name="ElementName" select="local-name()"/>
<xsl:variable name="vVal" select= "$vAttitudes[. = substring-after(current(), '_')]/#val"/>
<xsl:choose>
<xsl:when test="contains($ElementName, 'Refuse')">
<xsl:value-of select="civf:book-capitalise($vAttitudes[#val = $vVal+1])"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="civf:book-capitalise($vAttitudes[#val = $vVal])"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So the premise is, find the name of the element, if it has the text "Refuse" in the name of it then "doTheThing"+1 otherwise just "doTheThing". However this test always fails so +1 never gets called even if the element has "Refuse" in the name. If I just output local-name then I get empty too. Why does local-name() not appear to work here?
I did previously try to start the template with:
<xsl:template match="*[contains(., 'Refuse')]/name()">
But Saxon complained that I was running too many functions in the match sequence.
I apologise in advance for not knowing too much about XSLT.

I believe that local-name() does not work because you are matching text nodes (/text() in the match attribute), and text nodes do not have local names.
I'm not sure what you are trying to do but I don't think you actually want to match /text() but instead the whole element, and obtain its text() afterwards.
Alternatively, you could try using local-name(..) to get the name of parent node but I'm not sure about that.

Related

XSLT REGEX pattern match

Using Saxon 9.7, XSLT 3.0, I'm trying to select square bracketed terms from a string of text and then remove duplicate values of the terms.
So far I have found a template which selects the substrings I want and a function that tokenizes the string and then removes duplicate values.
However, I haven't been able to get the correct regex for the tokenizing of the string.
Here is my XML of the full text
<column>
<columnDerivationPrompt>Option 1: (No visit windowing)</columnDerivationPrompt>
<columnDerivationDescription>Set to collected visit name [EG.VISIT] Set to 'POST-BASELINE MINIMUM' for the new observation generated for derviation type minimum [ADEG.DTYPE] = 'MINIMUM'
Set to 'POST-BASELINE MAXIMUM' for the new observation generated for derviation type maximum [ADEG.DTYPE]= 'MAXIMUM'
</columnDerivationDescription>
<columnDerivationPrompt>Option 2: (User defined visit windows)</columnDerivationPrompt>
<columnDerivationDescription>Set to a re-defined visit range based on user-defined input, using formatting of Analysis Relative Day [ADEG.ADY] range in conjunction with Analysis Window Target [ADEG.AWTARGET] and Analysis Window Diff from Target [ADEG.AWTDIFF] to determine analysis visit.
Set to 'POST-BASELINE MINIMUM' for the new observation generated for derviation type minimum [ADEG.DTYPE] = 'MINIMUM'
Set to 'POST-BASELINE MAXIMUM' for the new observation generated for derviation type maximum [ADEG.DTYPE]= 'MAXIMUM'
</columnDerivationDescription>
</column>
The string of terms taken from the text that I need to remove duplicates from
EG.VISIT ADEG.DTYPE ADEG.DTYPE ADEG.ADY ADEG.AWTARGET ADEG.AWTDIFF ADEG.DTYPE ADEG.DTYPE
What I would like to see
EG.VISIT ADEG.DTYPE ADEG.ADY ADEG.AWTARGET ADEG.AWTDIFF
my XSLT template and function
<xsl:variable name="test">
<xsl:if test="contains($string,'[')">
<xsl:variable name="relevant-part" select="substring-before(substring-after($string,'['),']')"/>
<xsl:variable name="remainder" select="substring-after($string,']')"/>
<xsl:value-of select="$relevant-part"/>
<xsl:if test="contains($remainder,'[')">
<xsl:text disable-output-escaping="yes"> </xsl:text>
</xsl:if>
<xsl:call-template name="find-relevant-text">
<xsl:with-param name="string" select="$remainder"/>
</xsl:call-template>
</xsl:if>
</xsl:variable>
<xsl:value-of select="myfn:sortCSV($test)"/>
</xsl:template>
<xsl:function name="myfn:sortCSV" as="xs:string*">
<xsl:param name="csvString" as="xs:string"/>
<!-- Split up string and remove duplicates -->
<xsl:variable name="values" select="distinct-values(tokenize($csvString,'\W+\.\W+'))" as="xs:string*"/>
<!-- Return all elements, sorted -->
<xsl:for-each select="$values">
<xsl:sort/>
<!-- We don't return empty strings -->
<xsl:sequence select=".[.!='']"/>
</xsl:for-each>
</xsl:function>
\W+\.\W+ is the regex I have been using to identify e.g. EG.VISIT or ADEG.DTYPE. So any pattern including CC.CCCC to CCCC.CCCCCCCC (where C is a char [A-Z]).
The output I am getting is
EG.VISIT ADEG.DTYPE ADEG.DTYPE ADEG.ADY ADEG.AWTARGET ADEG.AWTDIFF ADEG.DTYPE ADEG.DTYPE
So no duplicates have been removed.
QUESTION:
Can anyone see where I am going wrong with my expression or code?
As for your regular expression, note that a \W matches a non-word char and cannot match uppercase (nor lowercase) letters. \w matches a word char.
However, best is to restrict it to [A-Z]+\.[A-Z]+ since you say the items you want to match follow the uppercase+.+uppercase pattern.
See the regex demo
I would use analyze-string, either with XSLT 2.0 the XSLT xsl:anyalyze-string or with XSLT 3.0 the function of the same name, using that approach it is a one-liner:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math fn"
version="3.0">
<xsl:template match="column">
<xsl:value-of select="distinct-values(analyze-string(., '\[([A-Z]+\.[A-Z]+)\]')//fn:match/fn:group[#nr = 1])"/>
</xsl:template>
</xsl:stylesheet>
Output is EG.VISIT ADEG.DTYPE ADEG.ADY ADEG.AWTARGET ADEG.AWTDIFF.
If you want to sort the extracted strings then use <xsl:value-of select="sort(distinct-values(analyze-string(., '\[([A-Z]+\.[A-Z]+)\]')//fn:match/fn:group[#nr = 1]))"/>.

Substring before throwing error

I've the below XML
<?xml version="1.0" encoding="UTF-8"?>
<body>
<p>Industrial drawing: Any creative composition</p>
<p>Industrial drawing: Any creative<fn>
<fnn>4</fnn>
<fnt>
<p>ftn1"</p>
</fnt>
</fn> composition
</p>
</body>
and the below XSL.
<xsl:template match="p">
<xsl:choose>
<xsl:when test="contains(substring-before(./text(),' '),'Article')">
<xsl:text>sect3</xsl:text>
<xsl:value-of select="./text()"/>
</xsl:when>
<xsl:when test="contains(substring-before(./b/text(),' '),'Section')">
<xsl:text> Sect 2</xsl:text>
<xsl:value-of select="./text()"/>
</xsl:when>
<xsl:when test="contains(substring-before(./b/text(),' '),'Chapter')">
<xsl:text> Sect 1</xsl:text>
<xsl:value-of select="./text()"/>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Here my XSL is working fine for <p>Industrial drawing: Any creative composition</p> but for the below Case
<p>Industrial drawing: Any creative<fn>
<fnn>4</fnn>
<fnt>
<p>ftn1"</p>
</fnt>
</fn> composition
</p>
it is throwing me the below error.
XSLT 2.0 Debugging Error: Error: file:///C:/Users/u0138039/Desktop/Proview/ASAK/DIFC/XSLT/tabel.xslt:38: Wrong occurrence to match required sequence type - Details: - XPTY0004: The supplied sequence ('2' item(s)) has the wrong occurrence to match the sequence type xs:string ('zero or one')
please let me know how can i fix this and grab the text required.
Thanks
The second p element in your example XML has two child text nodes, one containing "Industrial drawing: Any creative" and the other containing a space, "composition", a newline and another six spaces. In XSLT 1.0 it is legal to apply a function that expects a string to an argument that is a set of more than one node, the behaviour is to take the value of the first node and ignore all the others. But in 2.0 it is a type mismatch error to pass two nodes to a function that expected a single value for its parameter.
But in this case I doubt that you really need to use text() at all - if all you care about is seeing whether the string "Article" occurs anywhere within the first word under the p (including when this is nested inside another element) then you can simply use .:
<xsl:when test="contains(substring-before(.,' '),'Article')">
(or better still, use predicates to separate the different conditions into their own templates, with one template matching "Article" paragraphs, another matching "Section" paragraphs, etc.)
The p element in your example has several text nodes, so the expression ./text() creates a sequence. You cannot apply a string function to a sequence; you must convert it to a string first. Instead of:
test="contains(substring-before(./text(),' '),'Article')"
try:
test="contains(substring-before(string-join(text(), ''), ' '), 'Article')"

XSLT, how to use "xsl:value-of" inside "xsl:if"?

I am new to xslt and I am wondering whether it is possible to compare the value of "#userNameKey" and the value of
<xsl:value-of select="./text()"/> in example below?
<xsl:if test="#userNameKey='??????'">
<xsl:attribute name="selected">true</xsl:attribute>
</xsl:if>
<xsl:value-of select="./text()"/>
Basically, I just want to replace the questionmarks with the following fragment: <xsl:value-of select="./text()"/> but there is an issue with the double quotes. Should I use escape characters (if yes, what are they?) or there is a better solution?
If you specifically want to compare against the value of the first text node child of the current element (which is what <xsl:value-of select="./text()"/> gives you), then use
<xsl:if test="#userNameKey=string(text())">
At first sight
<xsl:if test="#userNameKey=text()">
may seem more obvious, but this is subtly different, returning true if the userNameKey matches any one of the text node children of the current element (not necessarily the first one).
But if (as I suspect you really mean) you want to compare the userNameKey against the complete string value of the element even if that consists of more than one text node, then use
<xsl:if test="#userNameKey=.">
Remember that text() is a node set containing all the text node children of the context node, and if you're not sure you need to use it (e.g. when you want to process each separate text node individually) then you probably don't.
You should be able to do just this...
<xsl:if test="#userNameKey=./text()">
<xsl:attribute name="selected">true</xsl:attribute>
</xsl:if>
In fact, the ./ is not needed here, so you can just do this
<xsl:if test="#userNameKey=text()">
<xsl:attribute name="selected">true</xsl:attribute>
</xsl:if>

Extracting string parts from data

Need to extra part of a string. Assume I can access "date" and get on the output
21.01.2013
Now I don't want to have the '.2013'. I tried these lines:
<xsl:value-of select="date"/>
<xsl:variable name="bdate">
<xsl:value-of select="date"/>
</xsl:variable>
<p>Birthday: <xsl:copy-of substring($bdate,1,5) /></p><br/>
The first line only works, with all lines any variations of the last line will always throw an error. But there is a solution for it, I'm sure. Can anyone help for it .. how would last line look like?
<xsl:value-of select="substring($bdate,1,5)" />
Should work (XSLT has to be well formed XML)

xsl strange behaviour using variables

I've stored a file's tree into $onto
<xsl:variable name="onto" select="document('file.xml')"/>
In some places I can use this variable as espected:
<xsl:copy-of select="$onto/rdf:RDF"/>
But I'm having trouble in other places, strange chars are written on output:
<xsl:element name="autor">
<xsl:attribute name="rdf:resource">
<xsl:text>#</xsl:text> <xsl:value-of select="$onto"/>
</xsl:attribute>
</xsl:element>
This is the beginig of the output I've got:
<autor rdf:resource="#
What I'm missing? What's wrong?
If that's to much for an attribute, what can I do?
Thank you
When <xsl:value-of> is applied to a tree fragment, it takes the text content of that tree. In your case, it looks like your XML file doesn't contain any text (other than whitespace) which isn't in an attribute value. I suspect that you mean to select the value of a particular attribute node within the document, e.g.:
<xsl:value-of select="$onto//foo/#bar"/>
(Without knowing the structure of your XML and what you're trying to select, I don't know what the real path would be.)