Extracting string parts from data - xslt

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)

Related

Copy text and replace character in XSL

I'm transforming a DITA document to a simplified, formatting-based XML to be used as an import into Adobe InDesign. My transformation is going really well, except for one element which omits the text in the output. The element is codeblock. When I don't have a template specifying it at all, the element and any child elements are passed through to the new XML document, but none of the text is passed through. This element should be passed through with text and child elements like every other element in my document for which a specific template is not defined. There's nothing anywhere else in the XSL stylesheet that specifies codeblock or any of its attributes. I am completely stumped and cannot figure out what's going on here.
It is also worth noting that a number of inline elements (cmdname, parmname, userinput, etc.) are converted to bold on output. The downstream XML is for formatting and does not need to know semantic context.
This is what I'm trying to pass through:
<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that <parmname>child elements</parmname> are passed through.</codeblock>
With no template defined for codeblock, this is what I get as a result:
<codeblock><bold/></codeblock>
The actual result I want is:
<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that <bold>child elements</bold> are passed through.</codeblock>
I need the line feeds replaced with character entities because InDesign sees any new line that does not start with an element as a column break. My goal was to simply replace the line feed character with 
 with the following template:
<xsl:template match="codeblock//text()">
<xsl:analyze-string select="." regex="(
)">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
But what I get is:
<codeblock>
<bold/>
</codeblock>
I was finally able to pass the text through using this template:
<xsl:template match="codeblock//text()">
<xsl:copy/>
</xsl:template>
Success! Incidentally, I have to match at any level under codeblock so it includes the text of the child parmname element too. Since I was able to successfully pass it through with <xsl:copy>, I tried this to pass the text through while replacing the line feed at the same time:
<xsl:template match="codeblock//text()">
<xsl:copy>
<xsl:analyze-string select="." regex="(
)">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
But now it won't replace the new line feed. Instead, I get this (which is what I would expect to get without any template defined):
<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that <bold>child elements</bold> are passed through.</codeblock>
I know this is a long and somewhat convoluted question. I just feel like if I could resolve the issue of why it's not passing the text through in the first place, the rest would be fairly straightforward. And I'm sorry, I can't provide my source XML or XSL as it's under NDA, but if you need more, let me know and I'll try to provide it. (My XSL stylesheets are made up of 12 different files, so there's no way for me to provide all of it, even if genericized.)
Any suggestions for what I might look for in my stylesheet that might explain why the text is coming through or any suggestions for how to force it through as I did with <xsl:copy> while still replacing the line feeds will be greatly appreciated!
Edited to add: It has occurred to me that the reason it's not doing the replacement is that it looks like it's not actually a line feed character. It's more like a new line in the code than a line feed character (or hard return) in the text. I think I might need to normalize the text while inserting the 
 character at the end of each line. Still investigating, but suggestions are welcome!
Edited with update: Thanks to the post How to detect line breaks in XSLT, I have gotten closer, but still not quite where I need to be. With this code, I'm able to detect line feeds in the XML and insert the line break character for InDesign:
<xsl:template match="codeblock//text()">
<xsl:for-each select="tokenize(., '\n?')[.]">
<xsl:sequence select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
However, it also inserts the line feed character at the end of the string, even if it's not the end of the line. For instance, I now get:
<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that 
<bold>child elements
</bold> are passed through.
</codeblock>
I don't want the line feed character in front of the 'bold' start and end tags or the codeblock end tag. I just want it to appear where there's an actual new line. I tried replacing \r but that just ignored the new lines and just put it in front of the tags. Does anyone know of another escape character that would work here?
A very long question - yet it's still not clear what exactly you are asking (and no reproducible example, either).
If - as it seems - you want to replace newline characters with the line separator character in all text nodes under the codeblock element, you should be able to do simply:
<xsl:template match="codeblock//text()">
<xsl:value-of select="translate(., '
', '
')" />
</xsl:template>
If this doesn't work, then either you have an overriding template or the text does not contain newline characters. You can test for the first case by changing the template to say:
<xsl:template match="codeblock//text()">BINGO</xsl:template>
and observe the result to see if all targeted text nodes are changed to "BINGO". To test for the second case, you can analyze the text character-by-character using the string-to-codepoints() function.
Your template is missing xsl:non-matching-substring to process the non-matching sections of the text node.
<xsl:template match="codeblock//text()">
<xsl:analyze-string select="." regex="\n">
<xsl:matching-substring>
<xsl:text>
</xsl:text>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
However, michael.hor257k's answer is more simple, as you don't need xsl:analyze-string to just replace a all substrings.

Regular expressions, xslt 2, basics

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.

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

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.

Prevent whitespaces between two XSLT-elements from being removed by the xslt-processor

my question is a bit different from the other ones..
i got an xsl-code like this:
<xsl:value-of select="..."/> <xsl:value-of select="...">
what i want in my result is:
result_of_select_1 result_of_select_2
what i get is:
result_of_select_1result_of_select_2
how can i prevent this? ( any xsl:output option for example? )
All the other solutions i found were specificly for the same problem but in the XML-Source document and not in the XSLT-document like this one...
btw. a solution like "insert elements instead of the spaces" is not a possible solution for my, because the xslt-code is generated dynamically
thanks in advance
The white space as you have it is insignificant and gets discarded. If that was not the case, every last bit of white space you have in your XSLT code would end up in the result document. You must be explicit about the white space you want in the result.
User either:
<xsl:value-of select="concat(..., ' ', ...)" />
or:
<xsl:value-of select="..." />
<xsl:text> </xsl:text>
<xsl:value-of select="..." />
Use:
<xsl:value-of select="..."/><xsl:text> </xsl:text><xsl:value-of select="...">
EDIT:
Refer to this ASCII table for other symbols