apply templates select substring after - xslt-2.0

I've an XML line like the below.
<title>I. DEFINITION</title>
Here what i'm doing getting the value before '.', this is fine but i want to apply-templates for the content after '.'. i'm unable to know how do i do it. i'm using the below XSLT line.
<xsl:apply-templates select="substring-after(.,'. ')"/>
when i use it, an error is thrown and it is
XSLT 2.0 Debugging Error: Error: file:///C:/Users/u0138039/Desktop/Proview/HK/ArchboldHK2014/XSLT/Chapters.xsl:508: Not a node item - item has type xs:string with value 'DEFINITION' - Details: - XTTE0520: The result of evaluating the 'select' attribute of the <xsl:apply-templates> instruction may only contain nodes
please let me know how i can apply-templates on content after '.'
Thanks.

You can try this template
<xsl:template match="title">
<xsl:copy>
<label><xsl:value-of select="substring-before(., '. ')"/></label>
<caption>
<xsl:variable name="slicetext" select="substring-after(current()/text()[1], '. ')"/>
<xsl:value-of select="$slicetext"/><xsl:apply-templates select="text()[position() > 1]|child::node()[not(self::text())]"/>
</caption>
</xsl:copy>
</xsl:template>

With XSLT 1.0 and 2.0 you can only write and apply-templates for nodes, not for primitive values like strings. I think this changes in XSLT 3.0.
In XSLT 2.0, to process the result of substring-after further, you would need to write a function or a named template taking a string parameter.
If you really want to apply a template you first would need to create a temporary text node with xsl:variable.

Related

XSLT pipeline : Error XPDY0002 - The context item for axis step fn:root(...)/element() is absent

Please, I need some help dealing with saxon api :)
I create a pipeline with 2 XsltTransform of the same xslt and when i run transform i get this error :
2019-01-24 11:32:15,673 [pool-2-thread-1] INFO e.s.e.x.XsltListener - file
2019-01-24 11:32:15,674 [pool-2-thread-1] INFO e.s.e.x.XsltListener - Error
XPDY0002 while evaluating xsl:message content: The context item for axis
step fn:root(...)/element() is absent
here is my xslt :
<xsl:stylesheet exclude-result-prefixes="#all" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="supp" as="xs:string" select="root()/*/name()"/>
<xsl:template match="/">
<xsl:message select="$supp"/>
<file/>
</xsl:template>
the first XsltTransform work fine but It seems that i have no context node during the second XstTransform running.
I use :
transformer1.setSource(source) : source is a SAXSource
transformer1.setDestination(transformr2)
transformr2.setDestination(serialiser)
According to documentation (XsltTransform.setInitialContextNode):
This value is ignored in the case where the XsltTransformer is used as the Destination of another process. In that case the initial context node will always be the document node of the document that is being streamed to this destination.
Thanks for your Help
In general in XSLT 3 you need to distinguish between the initial match selection https://www.w3.org/TR/xslt-30/#dt-initial-match-selection which is used to decide which template to apply first and the global context item https://www.w3.org/TR/xslt-30/#dt-global-context-item that is used to evaluate global parameters and variables. I think you seem to expect that in your second stylesheet the result of your first acts as both but it seems, at least in your setup, Saxon does not assume that but only sets your initial match selection to the result of the first stylesheet. So try moving the <xsl:variable name="supp" as="xs:string" select="root()/*/name()"/> into the template e.g.
<xsl:template match="/">
<xsl:variable name="supp" as="xs:string" select="root()/*/name()"/>
<xsl:message select="$supp"/>
<file/>
</xsl:template>
I am not sure there is another way, at least in the case of chaining two streaming transformations you can't the second stylesheet expect to have access to the whole result tree of the first to be used to evaluate global parameters or variables.

xslt 2.0: read in text files via collection()

I have a bunch of text files that I'd like to process witth XSLT 2.0.
Here's how I try to read them in:
<xsl:variable name="input" select="collection(iri-to-uri('file:///.?select=*.txt'))" />
However, when I do this:
<xsl:message>
<xsl:sequence select="count($input)"/>
</xsl:message>
It outputs 0. No files are selected.
If I do it like this:
<xsl:variable name="input" select="collection(iri-to-uri('.?select=*.txt'))" />
I get the error that collection should return a node but is returning an xs:string.
What I would like do to is read each file and then iterate over each file and process the text, like this
<xsl:for-each select="unparsed-text($input, 'UTF-8')">
<!-- tokenizing, etc. -->
How would I do that?
You need the XPath 3.0 uri-collection function supported in version="3.0" stylesheet in Saxon 9.7 (all versions including HE) and 9.6 (commercial versions I think):
<xsl:template match="/" name="main">
<xsl:for-each select="uri-collection('.?select=*.txt')!unparsed-text(.)">
<xsl:message select="'Parsed:' || . || '
'"/>
</xsl:for-each>
</xsl:template>
collection is supposed to return a sequence of nodes while uri-collection can access other resources not parsable as XML.
With Altova XMLSpy respectively RaptorXML and XSLT 3.0 you can also use uri-collection, it seems the way to access all .txt files is a bit different from Saxon and you use uri-collection('*.txt') to access all .txt files in the directory.

xpath expression to select specific xml nodes that are available in a file

I was trying to find the out a way for my strange problem.
How to write an xpath to select specific xml nodes that are available in another text file.
For Instance,
<xsl:for-each select="SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME eq (group name list in a text file as input)]">
For example,
<xsl:for-each select="SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME eq collection('select_nodes.txt')]">
select_nodes.txt contains list of string that can be selected only
For example
ABC
IJK
<SUBSCRIBER>
<MSISDN>123456</MSISDN>
<SUBSCRIBER_PROFILE_LIST>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>12345</PROFILE_MSISDN>
<GROUP_NAME>ABC</GROUP_NAME>
<GROUP_ID>18</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>456778</PROFILE_MSISDN>
<GROUP_NAME>DEF</GROUP_NAME>
<GROUP_ID>100</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>78876</PROFILE_MSISDN>
<GROUP_NAME>IJK</GROUP_NAME>
<GROUP_ID>3</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
</SUBSCRIBER>
XSLT2 has limited functionality for parsing arbitrary text files. I would suggest:
Make the select_nodes.txt an XML file and load it using the doc() function:
<xsl:variable name="group_names" as="xs:string *"
select="doc('select_nodes.xml')/groups/group"/>
with select_nodes.xml looking like this:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group>ABC</group>
<group>IJK</group>
</groups>
Pass the group names as a stylesheet parameter. (How you do this depends on which XSLT engine you're using and whether it's through the command line or an API.) If it's through an API, then you may be able to pass the values in directly as xs:string-typed objects. Otherwise you'll have to parse the parameter:
<xsl:param name="group_names_param"/>
<!-- Assuming the input string is a whitespace-separated list of names -->
<xsl:variable name="group_names" as="xs:string *"
select="tokenize($group_names_param, '\s+')"/>
In either case your for-each expression would then look like this:
<xsl:for-each select="
SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME = $group_names]">
<!-- Do something -->
</xsl:for-each>

xslt how to read the document-node()

I have a xml file in which one of the element has the CDATA as the value. I put the CDATA value into a variable which I can see is value type of document-node(1) when i debug my code from oXygen. How do I iterate the document-node()?
copy can give me a new xml file. but what I need is not a new file. I only need to read certain nodes and generate a report based on the values on those nodes. so I directly copy the CDATA to my variable and thought I can manipulate it.
I tried to use substring to read the variable things but failed.
I tried to use document(variable) to open the variable but Oxygen give me the debug-error of FODC0002:I/O error reported by xml parser processing file.
here the file is my variable which looks like a xml file
I did google search for the error but only got bench of non-closed questions like Oxygen throw I/O error when use document().
Would anybody let me know what's going wrong? or give me a better solution?
I also tried parse-xml() but I got the following error from Saxon:
F[Saxon-EE9.5.1.5] the processing instruction target matching "[xX][mM][lL]" is not allowed
F[Saxon-EE9.5.1.5] FODC0006: First argument to parse-xml() is not a well formed and namespace-well-formed XML document.
my code to use parse-xml is as below:
<xsl:template match="data"
<xsl:for-each select="parse-xml(root/outsideData)//nodeLevel1/nodeLevel2">
Could anyone give me a sample about how to use parse-xml()? I did google search but didn't find useful samples.
Thanks very much!
A piece of my data is like the following:
<root>
<outsideData id="123">
<child1 key="124375438"/>
<![CDATA[ <?xml version=1.0 encoding="UTF-8"?><insideData xmlns:xlink="http://www.w3.org/1999/xlink">
<nodeLevel1>
<nodeLevel21>packing</nodeLevel21>
<nodeLevel22 ref="12343-454/560" xlink:href="URN:X-MN:DD%3FM=B888%26SDC=A%26CH=79% .../>
</nodeLevel1>
]]>
</outsideData>
</root>
I want to get the inside CDATA <nodeLevel22> #ref and #xlink which will get DD-FM-B888-26-79
My variables are:
<xsl:for-each select="/root/outsideData">
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<xsl:variable name="Data">
<xsl:value-of
select="normalize-space(substring-after($insideData,'?>'))"
disable-output-escaping="yes"/>
</xsl:variable>
</xsl:foreach>
From the debug I can see that the variable insideData and Data are both value type of document-node(1)
Martin's solution works for me very well :)
But I'm still wondering why the following doesn't work:
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<ref>
<xsl:value-of select="substring-before(substring-after($insideData, '<nodeLevel22 ref'),>/>')"/>
</ref>
Here I got empty <ref/>
If you do <xsl:variable name="varName"><xsl:value-of select="..."/><xsl:variable> then you are creating a temporary document fragment that contains a single text with the string contents of the item(s) selected in the value-of. That does not make sense in most cases, doing <xsl:variable name="varName" select="..."/> is usually sufficient.
As for parsing the contents of the outsideData element with parse-xml, there is indeed not only the escaped XML document inside that element but white space as well, thus if you try to parse the contents as XML you get that error as white space before the XML declaration is not allowed. The whole approach of stuffing the XML into a CDATA section with an element with mixed contents is flawed in my view, if you want to store escaped XML into a CDATA then you should make sure that you use a single element that contains nothing but the CDATA section which then only contains the XML markup with no leading white space.
If you can't change the creation of the input data then you will need to make sure you pass in only that part of the string contents of the element to parse-xml that is a well-formed XML document, so you need some way to strip the white space before the XML declaration doing e.g.
<xsl:for-each select="/root/outsideData">
<xsl:variable name="xml-string" select="replace(., '^\s+', '')"/>
<xsl:variable name="xml-doc" select="parse-xml($xml-string)"/>
<!-- now output data e.g. -->
<xsl:value-of select="$xml-doc//nodeLevel1/nodeLevel22/#ref"/>
...
</xsl:for-each>
Untested but should show the right direction as far as trying to use parse-xml.

XSLT 2.0: filter duplicates

I have this line in a template:
<xsl:for-each select="//img/#src[not(# = preceding::#)]">
to generate a list of filenames of pictures :
<img src="mypic1.jpg"/>
Now I need to filter duplicate pictures from the list of pictures.
<xsl:for-each select="//img[not(node() = preceding::img)]">
does not work because it works on the whole image node.
How can I change the line to work on src attributes?
So the question is about the last part of the line in question.
You could just use distinct-values(//img/#src), e.g.
<xsl:for-each select="distinct-values(//img/#src)">
<li><xsl:value-of select="."/></li>
<xsl:for-each>
Alternatively you can use for-each-group
<xsl:for-each-group select="//img" group-by="#src">
<li><xsl:value-of select="#src"/></li>
<xsl:for-each-group>

Resources