Regular expressions, xslt 2, basics - regex

I have went through several examples and tried to modify my search to understand what is what.
Input:
<Description>Ottelu pelattu 22.4.2018. Gagarin Cupin 5. finaaliottelu. Selostus: Antti Mäkinen.</Description>
<Description>Ottelu pelattu 20.4.2018. Gagarin Cupin 1. finaaliottelu. Selostus: Antti Mäkinen.</Description>
<Description>Ottelu pelattu 22.4.2018. Gagarin Cupin 2. puolivälierä. Selostus: Antti Mäkinen.</Description>`
What I want to do is select these to my output:
"Gagarin Cupin 5. finaaliottelu"
"Gagarin Cupin 2. puolivälierä"
Without the dot there in the middle.
I could use substring-before/after, but I understand it could be useful to use regex?
I have made this that fetches what I need: Gagarin Cupin\s\d\W\s\w*[a-zA-ZäöüÄÖÜß]*
But now how can I use this in XSLT? Is it analyze-string() that I should use? or matches() in some way?
XSLT:
<xsl:variable name="episode" select="Description"/>
<xsl:variable name="fetchcup">
<xsl:analyze-string select="$episode" regex="Gagarin Cupin\s\d\W\s\w*[a-zA-ZäöüÄÖÜß]*">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<Cup><xsl:value-of select="$fetchcup"/></Cup>
But honestly, I feel like I am missing some basics of how it works despite looking through tutorial pages and examples. If I get a foot in the door I can apply it further.

Your regular expression works in the context of a single Description element, inside of the xsl:matching-substring if you want to output the matched string you can simply use . for the context item or regex-group(0) (see https://www.w3.org/TR/xslt-30/#func-regex-group). The use of regex-group(1) doesn't make sense in your case as your regular expression does not have any subexpressions.
<xsl:template match="Description">
<cup>
<xsl:analyze-string select="." regex="Gagarin Cupin\s\d\W\s\w*[a-zA-ZäöüÄÖÜß]*">
<xsl:matching-substring>
<xsl:value-of select="."/>
</xsl:matching-substring>
</xsl:analyze-string>
</cup>
</xsl:template>
That template in https://xsltfiddle.liberty-development.net/nc4NzQH outputs
<cup>Gagarin Cupin 5. finaaliottelu</cup>
<cup>Gagarin Cupin 1. finaaliottelu</cup>
<cup>Gagarin Cupin 2. puolivälierä</cup>
for your three Description elements, I hope I understood that correctly as the desired output.

Related

XSLT - curly braces in regex

I have a text node from which I have to extract a particular sub string based on a pattern
Below is the actual string:
//Some_string,"some_string_1":"target_string"},some_other_string//
Following is the regex pattern I am trying to use:
<xsl:analyze-string select="text_node/text()" regex=",("some_string_1":.*?)"}">
<xsl:matching-substring>
<xsl:value-of select="substring-after(regex-group(1),':"')"/>
</xsl:matching-substring>
</xsl:analyze-string>
My extracted sub string should be "target_string"
But I am getting the following error
Fatal error during transformation using //my_path: Closing curly brace in attribute value template ",("some_string_1":.*?)"}" must be doubled;
I tried to use double curly braces also but didn't work
Note - I am using Ant script to run the XSLT with saxon-he-10.1.jar
Thanks in advance!
I tend to put regular expressions into an xs:string typed parameter or variable e.g.
<xsl:param name="pattern" as="xs:string">,("some_string_1":(.*?))"}</xsl:param>
then I can use
<xsl:analyze-string select="." regex="{$pattern}">
<xsl:matching-substring>
<xsl:value-of select="regex-group(2)"/>
</xsl:matching-substring>
</xsl:analyze-string>
where I have a lot less to worry about escaping things in an attribute value template like the regex attribute.
Note, that, if you use XSLT 3, it is safer to have
<xsl:param name="pattern" as="xs:string" expand-text="no">,("some_string_1":(.*?))"}</xsl:param>
to avoid any text value template setting higher up in the tree kicking in.
You need to double the curly bracket because it has special meaning in XSLT and escape it because it has special meaning in regex:
",("some_string_1":.*?)"\}}"

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')"

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)

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

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.