XslCompiledTransform fails with stack overflow - xslt

I have a large XSLT file with 805 templates that, depending on the system and environment, fails with a stack overflow.
The development environment is Windows 7, InfoPath 2010 and C#. Although .NET 4.0 is installed, this version of InfoPath uses .NET 2.0.
The routine being used is:
private void TransformXML(String inputFileName, String transformFileName, String outputFileName)
{
CorralLog(String.Concat("Transform with ", transformFileName, ": ", inputFileName, " -> ", outputFileName));
using (XmlReader inputFile = XmlReader.Create(inputFileName, null))
{
XslCompiledTransform transform = new XslCompiledTransform(true);
XsltSettings settings = new XsltSettings(true, false);
transform.Load(transformFileName, settings, null);
using(XmlWriter outputFile = XmlWriter.Create(outputFileName))
{
filesToDelete.Add(outputFileName);
transform.Transform(inputFile, outputFile);
}
}
}
I can see some possibilities:
Increase the memory available to the 'transform.Load' command
Start a separate thread with more memory
Start a separate thread and execute the transform with 'msxsl.exe' (which always works)
Split up the XSLT file into smaller pieces and do a transformation multiple times
Does anyone have advice on which option to choose? Or any other suggestions?
Paul
The XLST file in question takes some elements from an XML file, changes the name for some elements, and produces another XML file. There are about 800 elements, each with its own template.
This code shows the three templates at the beginning of the XSLT file and two templates for copying elements: one for copying an element as is, and the other changing the name of the element. All subsequent templates are formatted in one of these two ways.
Is this syntax is causing recursion, or is the number of templates causing the stack overflow. (We have avoided the problem, but perhaps not solved it, by running this transform in a new thread.)
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2011-03-16T10:53:27">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="/SAN/ClientProfiles/ClientProfile">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="/SAN/ClientProfiles/ClientProfile/Name">
<CompanyName>
<xsl:apply-templates/>
</CompanyName>
</xsl:template>

See whether the suggestions in http://blogs.msdn.com/b/xmlteam/archive/2011/09/26/effective-xml-part-5-something-went-really-wrong-outofmemoryexception-and-stackoverflowexception-thrown-when-using-xslcompiledtransform.aspx help avoid the problem.

The most likely cause of a stack overflow is deep recursion in your XSLT code. I would take a look at the relevant templates and see if they can be written some other way, e.g. to use divide-and-conquer recursion rather than head-tail recursion, or perhaps to take advantage of XSLT 2.0 - there are a couple of good XSLT 2.0 processors for .NET.

Related

implementing evaluate(XPath) in msxml

I'm writing an experiment where I take an XML file, that has XPaths embedded in it, and try to process it against another XML file with data in it, where the XPaths refer to elements within some predefined nodeset inside the data....basically binding a view to a list of data.
I've basically got it working, except how to evaluate the XPaths themselves, clearly I can do it in saxon with 3.0 (maybe I should try there first), but it would be initially convenient if this worked in msxml. I've read stuff about "extensions" and embedding javascript...but I can't really see how it would work (it didnt work for me).
any ideas?
(I could make the xslt create an xslt that creates the output, but this is a rough and ready proof of concept, and that might make my head hurt).
I can if necessary create an explicit example, but my actual scenario is quite convoluted.
Within .NET XPathNavigator has an Evaluate method: https://learn.microsoft.com/en-us/dotnet/api/system.xml.xpath.xpathnavigator.evaluate?view=netcore-3.1. You can expose it from an extension object to XslCompiledTransform.
The Mvp.Xml library does this to expose a dyn2:evaluate extension function in the namespace xmlns:dyn2="http://gotdotnet.com/exslt/dynamic" to its wrapper MvpXslTransform class around XslCompiledTransform.
The library is available on NuGet in a .NET standard 2.0 compatible (i.e. both .NET framework and .NET Core compatible) package: https://www.nuget.org/packages/Mvp.Xml.NetStandard.
Simplest code would be (using Mvp.Xml.Common.Xsl;):
var processor = new MvpXslTransform();
processor.SupportedFunctions = Mvp.Xml.Exslt.ExsltFunctionNamespace.GdnDynamic;
processor.Load("XSLTFile1.xslt");
processor.Transform(new XmlInput("XMLFile1.xml"), null, new XmlOutput(Console.Out));
where the stylesheet uses e.g. dyn2:evaluate(., path) with xmlns:dyn2="http://gotdotnet.com/exslt/dynamic" where the path child element of the context node contains an XPath expression.
The source code is at https://github.com/keimpema/Mvp.Xml.NetStandard/blob/master/library/Mvp.Xml/Exslt/GDNDynamic.cs.
In the .NET framework you can also embed .NET code directly in the XSLT and XslCompiledTransform will compile and run it with the proper XsltSettings (new XsltSettings() { EnableScript = true }):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:my-ext="http://example.com/my-ext"
exclude-result-prefixes="msxsl my-ext">
<msxsl:script implements-prefix="my-ext" language="C#">
public object evaluate(XPathNodeIterator context, string expression)
{
if (context.MoveNext()) {
return context.Current.Evaluate(expression);
}
else {
return null;
}
}
</msxsl:script>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xpath-test">
<xsl:copy>
<result>
<xsl:value-of select="my-ext:evaluate(., path)"/>
</result>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I don't know whether Visual Studio allows you to enable that setting for its transformation menu.

calling one xslt file from base xslt - xslt1.0

I went through many other similar questions and answers in stackoverflow. But still not able to resolve the issue. If anyone can help me with it that would be really great.I am using call template to call one small xslt file from my larger xslt
My small xslt(WorkOrders.xsl) is very simple. Below is the part of my xslt:
<xsl:template match="GetWorkOrder">
<tns:Work>
<tns:description>
<xsl:value-of select="//ns0:WORKORDERS_ITEM/ns0:DESCRIPTION"/>
</tns:description>
<tns:workOrderNumber>
<xsl:value-of select="//ns0:WORKORDERS_ITEM/ns0:WORKORDER"/>
</tns:workOrderNumber>
</tns:Work>
</xsl:template>
I want to call WorkOrder.xsl from my base xslt. i am using the import statement
part of my base xslt. (i have got rid of the extra xml definitions to shorten the xslt for this question)
<xsl:stylesheet version="1.0" xmlns:oraxsl="http://www.oracle.com/XSL/Transform/java">
<xsl:import href="../Transformation/WorkOrders.xsl"/>
<oracle-xsl-mapper:schema>
<!--SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY.-->
<oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:source type="XSD">
<oracle-xsl-mapper:schema location="../Schema/Sample.xsd"/>
<oracle-xsl-mapper:rootElement name="OutputParameters"
namespace="****"/>
</oracle-xsl-mapper:source>
</oracle-xsl-mapper:mapSources>
<oracle-xsl-mapper:mapTargets>
<oracle-xsl-mapper:target type="XSD">
<oracle-xsl-mapper:schema location="../Schema/Sample1.xsd"/>
<oracle-xsl-mapper:rootElement name="GetResponse" namespace="*****"/>
</oracle-xsl-mapper:target>
</oracle-xsl-mapper:mapTargets>
<!--GENERATED BY ORACLE XSL MAPPER 12.1.3.0.0(XSLT Build 140529.0700.0211) AT [THU SEP 26 14:08:57 EDT 2019].-->
</oracle-xsl-mapper:schema>
<!--User Editing allowed BELOW this line - DO NOT DELETE THIS LINE-->
<xsl:template match="/">
<tns:GetResponse>
<tns:Response>
<Work>
<xsl:call-template name="GetWorkOrder"></xsl:call-template>
</Work>
</tns:Response>
</tns:GetResponse>
</xsl:template>
</xsl:stylesheet>
I receive an error message saying GetWorkOrder template is been called but not defied. Not sure what i am missing
Your template isn't named GridWalkOrder, you've defined it as matching an element of that name rather than naming the template. Use
<xsl:template name="GetWorkOrder">
instead.

Parameterize path to import a xslt from other xslt

I am trying to parametrize a path to include a xslt file into other, I have been trying with the way described here:
<xsl:param name="basedir" />
<xsl:include href="{$basedir}/team-menu.xsl" />
and call to the xslt doing:
xsltproc --stringparam basedir style example.xslt example.xml
But no way. I am not abel to do it work. It seems like if the xsl:output element was related because the only way I could make it "work" was this:
<xsl:include href="the/path/to/file/team-menu.xsl" />
<xsl:output method="xml" indent="yes"/>
<xsl:param name="basedir" />
I mean... putting the param after the output and the include before. Big problem: I can´t use the variable basedir.
Any way to do this??
Thank you in advance.
In XSLT 3.0 you can do this provided the parameters are declared as static, which means the values have to be supplied at compile time. You also need to prefix the attribute with "_":
<xsl:param name="basedir" static="yes"/>
<xsl:include _href="{$basedir}/team-menu.xsl" />
The reason it doesn't work with ordinary variables should be fairly obvious: variable values aren't known until run-time, and you can't start execution until you have found all the source code making up the stylesheet.
What you can do with earlier releases of XSLT, depending on the processor, is to redirect xs:include/xs:import URIs from the API level, for example (on Java) by using a user-supplied URIResolver.

Value-of select in <a href=> (XSLT)

I try to concstruct link with
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="concat('file:///', substring-before('%RolesPath%', 'roles'),'Flores.chm')"/>
</xsl:attribute>
Help
</xsl:element>
but I get error:
File file:///Flores.chm not found
I'm pretty sure, that variable %RolesPath% works fine. I'm using it in code normally. And if I use in code only
<xsl:value-of select="concat('file:///', substring-before('%RolesPath%', 'roles'),'Flores.chm')"/>
I get
file:///C:\Flores\Flores.chm
which is right path. Where I'm doing mistake please?
edit. %RolesPath% stores path to specify folder of program, which works with this code. In my case %RolesPath% stores "C:\Flores\roles\".
To specify my problem. I need open file(Flores.chm) in root folder of program. Program can be install everywhere in PC and prapably only way, how I can get the path is via %RolesPath%.
What you are passing to substring-before() is just a string ('%RolesPath%'). It appears that you are trying to use a Windows environment variable. This isn't going to work the way you're using it.
I think you have 2 options:
Option 1
Pass the value of the environment variable as an xsl:param when you call the stylesheet. This would work in either XSLT 1.0 or 2.0.
You would need the xsl:param:
<xsl:param name="RolesPath"/>
and this is how you would reference it:
<a href="{concat('file:///', substring-before($RolesPath, 'roles'),'Flores.chm')}"/>
Option 2
Use the environment-variable() function. This would only work with an XSLT 3.0 processor, such as Saxon-PE or EE.
Example:
<a href="{concat('file:///', substring-before(environment-variable('RolesPath'), 'roles'),'Flores.chm')}"/>
Here's another example of environment-variable() to show the function actually working:
XSLT 3.0
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<environment-variable name="TEMP" value="{environment-variable('TEMP')}"/>
</xsl:template>
</xsl:stylesheet>
Output (when applied to any well-formed XML)
<environment-variable name="TEMP" value="C:\Users\dhaley\AppData\Local\Temp"/>
Use this shorter expression:
<a href="file:///{substring-before($RolesPath, 'roles')}Flores.chm"/>
where $RolesPath is passed as an external, global parameter to the transformation.
How exactly to pass an external parameter to the transformation varies from one XSLT processor to another -- read your XSLT processor documentation. Some XSLT processors also allow string-typed parameters to be passed to the transformation from a command-line execution utility.

Xslt transform on special characters

I have an XML document that needs to pass text inside an element with an '&' in it.
This is called from .NET to a Web Service and comes over the wire with the correct encoding &
e.g.
T&O
I then need to use XSLT to create a transform but need to query SQL server through a SP without the encoding on the Ampersand e.g T&O would go to the DB.
(Note this all has to be done through XSLT, I do have the choice to use .NET encoding at this point)
Anyone have any idea how to do this from XSLT?
Note my XSLT knowledge isn’t the best to say the least!
Cheers
<xsl:text disable-output-escaping="yes">&<!--&--></xsl:text>
More info at: http://www.w3schools.com/xsl/el_text.asp
If you have the choice to use .NET you can convert between an HTML-encoded and regular string using (this code requires a reference to System.Web):
string htmlEncodedText = System.Web.HttpUtility.HtmlEncode("T&O");
string text = System.Web.HttpUtility.HtmlDecode(htmlEncodedText);
Update
Since you need to do this in plain XSLT you can use xsl:value-of to decode the HTML encoding:
<xsl:variable name="test">
<xsl:value-of select="'T&O'"/>
</xsl:variable>
The variable string($test) will have the value T&O. You can pass this variable as an argument to your extension function then.
Supposing your XML looks like this:
<root>T&O</root>
you can use this XSLT snippet to get the text out of it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="root"> <!-- Select the root element... -->
<xsl:value-of select="." /> <!-- ...and extract all text from it -->
</xsl:template>
</xsl:stylesheet>
Output (from Saxon 9, that is):
T&O
The point is the <xsl:output/> element. The defauklt would be to output XML, where the ampersand would still be encoded.