Getting XTRE1500 error - xslt-2.0

I'm writing an XSLT and I'm getting the below error.
XSLT 2.0 Debugging Error: Error: file:///C:/Users/u0138039/Desktop/Proview/SG/2016/SICC/XML/XSLT/chapters.xsl:5: Specified URI 'file:///C:/Users/u0138039/Desktop/Proview/SG/2016/SICC/XML/XSLT/chapters.xsl' is already used for writing - Details: - XTRE1500: Cannot write to an external resource and read from the same resource during a single transformation
I recently changed my Machine, in my previoys one, there was no issue, in my current system, I'm getting this error.
The error is thrown at
<xsl:variable name="ThisDocument" select="document('')"/>
In my program I use this as shown below.
<xsl:variable name="d">
<xsl:value-of select="concat('toc-item-',$ThisDocument//ntw:nums[#num=$nu]/#word,'-level')"/>
</xsl:variable>
And the ntws is ass below.
<!-- Namespace ntw-->
<ntw:nums num="1" word="first"/>
<ntw:nums num="2" word="second"/>
<ntw:nums num="3" word="third"/>
<ntw:nums num="4" word="forth"/>
<ntw:nums num="5" word="fifth"/>
<ntw:nums num="6" word="sixth"/>
<ntw:nums num="7" word="seventh"/>
<ntw:nums num="8" word="eighth"/>
<ntw:nums num="9" word="nighth"/>
<ntw:nums num="10" word="tenth"/>
<!-- Namespace ntw ends -->
Here is my full XSLT. http://xsltransform.net/3NJ391b.
I use Altova XML Spy.
please let me know where am I going wrong and how can I fix this.
Thanks,
Rakesh

Not sure why you're getting that error on your new machine, but not your old machine. Could be a difference in how the transformation is being run or maybe a difference in versions of XML Spy. Hard to say without being able to reproduce the issue.
What you can try is to not use document(''). This isn't necessary since you're using XSLT 2.0.
Try moving your ntw:nums elements directly into the ThisDocument variable:
<xsl:variable name="ThisDocument">
<ntw:nums num="1" word="first"/>
<ntw:nums num="2" word="second"/>
<ntw:nums num="3" word="third"/>
<ntw:nums num="4" word="forth"/>
<ntw:nums num="5" word="fifth"/>
<ntw:nums num="6" word="sixth"/>
<ntw:nums num="7" word="seventh"/>
<ntw:nums num="8" word="eighth"/>
<ntw:nums num="9" word="nighth"/>
<ntw:nums num="10" word="tenth"/>
</xsl:variable>
You might want to rename the variable and the reference once you've confirmed it's working. I also think it would be a little more specific to add an as="element()+" and change the usage.
Here's an example of what I would do...
Replacement for ThisDocument variable:
<xsl:variable name="nums" as="element()+">
<ntw:nums num="1" word="first"/>
<ntw:nums num="2" word="second"/>
<ntw:nums num="3" word="third"/>
<ntw:nums num="4" word="forth"/>
<ntw:nums num="5" word="fifth"/>
<ntw:nums num="6" word="sixth"/>
<ntw:nums num="7" word="seventh"/>
<ntw:nums num="8" word="eighth"/>
<ntw:nums num="9" word="nighth"/>
<ntw:nums num="10" word="tenth"/>
</xsl:variable>
Replacement for d variable:
<xsl:variable name="d">
<xsl:value-of select="concat('toc-item-',$nums[#num=$nu]/#word,'-level')"/>
</xsl:variable>

Related

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.

xmlproperty and retrieving property value of xml element if specific attribute is present/set

My Xml looks like:
<root>
<foo location="bar"/>
<foo location="in" flag="123"/>
<foo location="pak"/>
<foo location="us" flag="256"/>
<foo location="blah"/>
</root>
For foo xml element flag is optional attribute.
And when I say:
<xmlproperty file="${base.dir}/build/my.xml" keeproot="false"/>
<echo message="foo(location) : ${foo(location)}"/>
prints all locations :
foo(location) : bar,in,pak,us,blah
Is there a way to get locations only if flag is set to some value?
Is there a way to get locations only if flag is set to some value?
Not with xmlproperty, no, as that will always conflate values that have the same tag name. But xmltask can do what you need as it supports the full power of XPath:
<taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask">
<classpath path="xmltask.jar" />
</taskdef>
<xmltask source="${base.dir}/build/my.xml">
<copy path="/root/foo[#flag='123' or #flag='256']/#location"
property="foo.location"
append="true" propertySeparator="," />
</xmltask>
<echo>${foo.location}</echo><!-- prints in,us -->
If you absolutely cannot use third-party tasks then I'd probably approach the problem by using a simple XSLT to extract just the bits of the XML that you do want into another file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="targetFlag" />
<xsl:template name="ident" match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="foo">
<xsl:if test="#flag = $targetFlag">
<xsl:call-template name="ident" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Call this with the xslt task
<xslt in="${base.dir}/build/my.xml" out="filtered.xml" style="extract.xsl">
<param name="targetFlag" expression="123" />
</xslt>
This will create filtered.xml containing just
<root>
<foo location="in" flag="123"/>
</root>
(modulo changes in whitespace) and you can load this using xmlproperty in the normal way.

Unsure whether I need a group or a sort or something else

Hi I am an occasional user of XSLT so am probably missing something obvious, but hopefully someone can point it out!
The original XML has the structure;
<test>
<input>a</input>
<input>b</input>
<input>c</input>
<input>d</input>
<input>e</input>
</test>
The XSL file contains the following processing commands;
<xsl:template name="convertInputToNumeric">
<xsl:param name="inputs" />
<xsl:for-each select="input">
<NumericCode>
<xsl:call-template name="toNumericCode">
<xsl:with-param name="type">Input</xsl:with-param>
<xsl:with-param name="" select="." />
</xsl:call-template>
</NumericCode>
</xsl:for-each>
</xsl:template>
the call template 'toNumericCode' takes the current input and looks up in another xml file a numeric representation for the input eg the input 'a' returns the value '001'
<Conversion type="Input">
<Convert>
<FROM>a</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>b</FROM>
<TO>002</TO>
</Convert>
<Convert>
<FROM>c</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>d</FROM>
<TO>001</TO>
</Convert>
<Convert>
<FROM>e</FROM>
<TO>002</TO>
</Convert>
</Conversion>
so running the XSL I currently get
<test>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
<NumericCode>001</NumericCode>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
</test>
but actually what I want is that I only get the distinct nodes eg
<test>
<NumericCode>001</NumericCode>
<NumericCode>002</NumericCode>
</test>
I don't know how best to do this as I would want to group based on the numeric code value that is returned from the template 'toNumericCode' rather than the initial input value?
You may use distinct-values(). Have look, I have change your shared template with this one:
<xsl:template name="convertInputToNumeric">
<xsl:param name="inputs" />
<xsl:parm name="abc"><xsl:for-each select="input">
<NumericCode>
<xsl:call-template name="toNumericCode">
<xsl:with-param name="type">Input</xsl:with-param>
<xsl:with-param name="" select="." />
</xsl:call-template>
</NumericCode>
</xsl:for-each>
</xsl:parm>
<xsl:for-each select="distinct-values($abc/NumericCode)">
<NumericCode><xsl:value-of select="."/></NumericCode>
</xsl:for-each>
</xsl:template>
output:
<NumericCode>001</NumericCode><NumericCode>002</NumericCode>

Use a dynamic match in XSLT

I have an external document with a list of multiple Xpath like this:
<EncrypRqField>
<EncrypFieldRqXPath01>xpath1</EncrypFieldRqXPath01>
<EncrypFieldRqXPath02>xpath2</EncrypFieldRqXPath02>
</EncrypRqField>
I use this document to obtain the Xpath of the nodes I want to be modified.
The input XML is:
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
</Employees>
I want to obtain something like this:
<Employees>
<Employee>
<id>XXX</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>XXX</age>
<department>xyz</department>
</Employee>
</Employees>
The XXX values are the result of a data encryption, I want to dynamically obtain the Xpath from the document and change the value of its node.
Thanks.
I'm not sure if something like this is possible in XSL 2.0. May be in 3.0 there should be some function evaluate() but I don't know any details.
But I tried some workaround and it seems to be functional. Of course it is not perfect and has many limitations in this form (e.g. you need to specify absolute path, you cannot use more complex XPath like //, [], etc.) so consider it just as an idea. But it could be the way in some easier cases.
It is based on comparing of two string instead of evaluation string as XPath.
Simplified xml with xpaths to encrypt (I ommit the number for simplicity).
<?xml version="1.0" encoding="UTF-8"?>
<EncrypRqField>
<EncrypFieldRqXPath>/Employees/Employee/id</EncrypFieldRqXPath>
<EncrypFieldRqXPath>/Employees/Employee/age</EncrypFieldRqXPath>
</EncrypRqField>
And my transformation
<xsl:template match="element()">
<xsl:variable name="pathToElement">
<xsl:call-template name="getPath">
<xsl:with-param name="element" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$xpaths/EncrypFieldRqXPath[text() = $pathToElement]">
<!-- If exists element with exacty same value as constructed "XPath", ten "encrypt" the content of element -->
<xsl:copy>
<xsl:text>XXX</xsl:text>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- This template will "construct" the XPath for element under investigation. -->
<!-- There might be an easier way (e.g. some build-in function), but it is actually out of my skill. -->
<xsl:template name="getPath">
<xsl:param name="element" />
<xsl:choose>
<xsl:when test="$element/parent::node()">
<xsl:call-template name="getPath">
<xsl:with-param name="element" select="$element/parent::node()" />
</xsl:call-template>
<xsl:text>/</xsl:text>
<xsl:value-of select="$element/name()" />
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Can I apply a character map to a given node?

If I look at the xslt specs it seems a character map applies to the whole document, bit is it also possible to use it on a given node, or within a template ?
Example : I have a node containing look up values, but they might contain characters that don't play well with regular expressions when using it in another template. For now I use a replace functionwhich works well,, but after a few characters that becomes pretty hard to read or maintain. So if I have something like this :
<xsl:variable name="myLookup" select="
replace(
replace(
replace(
replace(
string-join(/*/lookup/*, '|'),
'\[','\\['),
'\]','\\]'),
'\(','\\('),
'\)','\\)')
"/>
is there a way to achieve something like below fictitious example ?
<xsl:character-map name="escapechar">
<xsl:output-character character="[" string="\[" />
<xsl:output-character character="]" string="\]" />
<xsl:output-character character="(" string="\(" />
<xsl:output-character character=")" string="\)" />
</xsl:character-map>
<xsl:variable name="myLookup" select="string-join(/*/lookup/*, '|')" use-character-map="escapechar"/>
I know this is not working at all, it is just to make my request a bit visual.
Any idea ?
I think character maps in XSLT 2.0 are a serialization feature to be applied when a result tree is serialized to a file or stream so I don't see how you could apply one to a certain string or certain node during a transformation.
As for escaping meta characters of regular expression patterns, maybe http://www.xsltfunctions.com/xsl/functx_escape-for-regex.html helps.
Character maps is only a serialization feature, which means that it is only executed when the final output of a transformation is produced. However, you can significantly simplify your current code.
Just use:
replace($pStr, '(\[|\]|\(|\))','\\$1')
Here is a complete example:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:value-of select="my:escape(.)"/>
</xsl:template>
<xsl:function name="my:escape" as="xs:string">
<xsl:param name="pStr" as="xs:string"/>
<xsl:value-of select="replace($pStr, '(\[|\]|\(|\))','\\$1')"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>([a-z]*)</t>
the wanted, correct result is produced:
\(\[a-z\]*\)

Resources