implementing evaluate(XPath) in msxml - xslt

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.

Related

Unknown function saxon:parse-html when compiling stylesheet

I am working on an XSL transformation on Oxygen using the Saxon-EE 10.3 transformer. I want to use the compiled stylesheet (sef.json) later on my website with Saxon-JS 2.
Inside of the XSL transformation I am using the saxon:parse-html function with the saxon namespace declared as following:
<xsl:stylesheet xmlns:prop="http://saxonica.com/ns/html-property"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:style="http://saxonica.com/ns/html-style-property"
xmlns:saxon="http://saxon.sf.net/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ixsl="http://saxonica.com/ns/interactiveXSLT"
xmlns:js="http://saxonica.com/ns/globalJS"
exclude-result-prefixes="xs prop ixsl js style saxon xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xpath-default-namespace="http://www.tei-c.org/ns/1.0"
xmlns="http://www.tei-c.org/ns/1.0">
and the function is called this way:
<xsl:call-template name="nameTemplate">
<xsl:with-param name="html">
<xsl:copy-of select="saxon:parse-html(var)"></xsl:copy-of>
</xsl:with-param>
</xsl:call-template>
I tried to compile the stylesheet through this command:
xslt3 -xsl:test.xsl -export:test.sef.json -t
but I encounter the following error:
Failed to compile stylesheet: Static error in XPath on line 147 in Oxygen/Test.xsl {saxon:parse-html(?Text)}: Unknown function Q{http://saxon.sf.net/}parse-html()
Error Q{http://www.w3.org/2005/xqt-errors}XPST0017 at xpath.xsl#963
Failed to compile stylesheet
Error Q{http://www.w3.org/2005/xqt-errors}XPST0017 at xpath.xsl#963
Static error in XPath on line 147 in Oxygen/Test.xsl {saxon:parse-html(?Text)}: Unknown function Q{http://saxon.sf.net/}parse-html()
The transformation works without problem inside Oxygen though.
You might need to call into JavaScript e.g. set up a script element
<script>
function parseHTML(html) { return new DOMParser().parseFromString(html, 'text/html'); }
</script>
in your HTML document and then inside of XSLT with Saxon JS 2 in the browser you should be able to use e.g.
ixsl:window() => ixsl:get('parseHTML') => ixsl:apply([var])
instead of saxon:parse-html(var), with a namespace declaration of xmlns:ixsl="http://saxonica.com/ns/interactiveXSLT" in your XSLT.
Another way to not require you to set up the script code in addition to the XSLT code is to use ixsl:eval to run the JavaScript directly from XSLT in Saxon-JS 2; I have set up an example at https://martin-honnen.github.io/saxon-js-parse-html-test/html/test-saxon-parse-html2.html which basically uses an implementation
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
xmlns:ixsl="http://saxonica.com/ns/interactiveXSLT"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:function name="saxon:parse-html" as="document-node()" use-when="system-property('xsl:product-name') = 'Saxon-JS'">
<xsl:param name="html" as="xs:string"/>
<xsl:sequence select="ixsl:eval('new DOMParser()') => ixsl:call('parseFromString', [$html, 'text/html'])"/>
</xsl:function>
</xsl:stylesheet>
of the XSLT 3 module https://github.com/martin-honnen/saxon-js-parse-html-test/blob/master/xslt/override-saxon-parse-html2.xsl.
You can xsl:import that in your other XSLT code, as done in https://github.com/martin-honnen/saxon-js-parse-html-test/blob/master/xslt/test-override-saxon-parse.xsl with e.g. <xsl:import href="override-saxon-parse-html2.xsl"/> and call e.g. saxon:parse-html(.).
I managed to compile that code to an SEF file with the settings xslt3 -xsl:test-override-saxon-parse.xsl -nogo -export:test-override-saxon-parse. -sef.json -ns:"##html5" and that way the HTML page https://martin-honnen.github.io/saxon-js-parse-html-test/html/test-saxon-parse-html2.html can simply run that XSLT with
SaxonJS.transform({
stylesheetLocation: '../xslt/test-override-saxon-parse.sef.json',
sourceLocation: '../xml/sample2.xml',
destination: 'appendToBody'
}, 'async')
As an alternative, you could import the pure XSLT 2 HTML parser that David Carlisle has somewhere on GitHub into your XSLT code.

Error "cannot create ActiveX component" when using VBScript extension functions from within XSLT using msxsl:script

I need to call the vbscript externally inside the xsl. I have written a sample xml and xsl when I validate the xml against xsl in have an error of cannot create ActiveX Component.
Next I have transformed the xml against the xsl using Altova but I found XML transformation failed due to following error: Function not in namespaceerror in xpath expression,Function not in namespace.
I have inculded the xml below
XML:
<LOOP_ID>
<ID LINE="1" ID00="ISA" ISA01="00" ID02="" ID03="12" ID04="" ID05="11" ID06="111111" ID07="ZZ" ID08="11111" ID09="121005" ID10="1759" ID11="^" ID12="00501" ID13="005926056" ID14="0" ID15="P" ID16=""/>
</LOOP_ID>
xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://mycompany.com/mynamespace">
<msxsl:script language="VBScript" implements-prefix="user">
Function ValidDateFormat(sDateValue)
Dim Test As DateTime
If DateTime.TryParseExact(datetime, sFormat, New CultureInfo("en-US"), DateTimeStyles.None, Test) = True Then
Return "t"
Else
Return "f"
End If
End Function
Function checkDateLessthanCurrID(sValue)
Dim Test As DateTime
If DateTime.TryParseExact(sDate, "yyMMdd", New CultureInfo("en-US"), DateTimeStyles.None, Test) AndAlso Test < DateTime.Now Then
Return "t"
Else
Return "f"
End If
End Function
Function checkDateLessthanCurr(sValue)
Dim Test As DateTime
If DateTime.TryParseExact(datetime, "MM:dd:yyyy", New CultureInfo("en-US"), DateTimeStyles.None, Test) AndAlso Test < DateTime.Now Then
Return "t"
Else
Return "f"
End If
End Function
</msxsl:script>
<xsl:output method="text" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:apply-templates select="/LOOP_ID"/>
</xsl:template>
<xsl:template match="/LOOP_ID">
<xsl:if test="ID/#ID09 !=''">
<xsl:if test="user:checkDateLessthanCurrISA(string(ID/#ID09))='t'">
</xsl:if>
</xsl:if>
I need to validate the date in the attribute given in xml. But I cannot validate showing error cannot find the namespace.
I have also created a external dll for the project and registered using regasm But I cannot access the dll inside my xsl.
Can any one help me to solve the issue?
Your first error:
cannot create ActiveX Component
means that your code was actually executed, but failed, due to a missing ActiveX reference.
You didn't state it, but most likely you received this by using MSXML, because that is the only XSLT compiler I know of that uses ActiveX. It is also used in Internet Explorer.
Your second error:
Next I have transformed the xml against the xsl using Altova but I found XML transformation failed due to following error: Function not in namespaceerror in xpath expression,Function not in namespace.
is strange. If I run it with Altova (I use the 2013 Community version) using the /xslt commandline switch to turn off XSLT 2.0 backwards compatibility processing, it tries to parse the script block and returns the following:
Script Compile Error(s) (relative to script begin):
Line 3, Character 0: 'datetime' is a type and cannot be used as an expression.
Line 3, Character 0: 'sFormat' is not declared. It may be inaccessible due to its protection level.
Line 3, Character 0: Type 'CultureInfo' is not defined.
Line 3, Character 0: 'DateTimeStyles' is not declared. It may be inaccessible due to its protection level.
Line 12, Character 0: 'sDate' is not declared. It may be inaccessible due to its protection level.
Line 12, Character 0: Type 'CultureInfo' is not defined.
Line 12, Character 0: 'DateTimeStyles' is not declared. It may be inaccessible due to its protection level.
Line 21, Character 0: 'datetime' is a type and cannot be used as an expression.
Line 21, Character 0: Type 'CultureInfo' is not defined.
Line 21, Character 0: 'DateTimeStyles' is not declared. It may be inaccessible due to its protection level.
This suggests that the code is not correct. I think the code uses VBScript and the classes you seem to be using are VB.NET.
When running the code against Microsoft's .NET version of XSLT 1.0, I receive the similar errors as above.
It turns out that Microsoft's script parser does not take your code as VBScript (which is ActiveX), but as a VB.NET script. Which is OK, as your code looks like VB.NET.
However, it is literally full of errors. I am not going to fix every error here, but here's a shortened version of your code that runs correctly on both Altova and Microsoft XSLT versions.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://mycompany.com/mynamespace">
<xsl:strip-space elements="*"/>
<msxsl:script language="VBScript" implements-prefix="user">
Function ValidDateFormat(sDateValue As String) As String
Dim Test As DateTime
Dim sFormat As String = "MM-dd-YY"
If DateTime.TryParseExact(sDateValue, sFormat, New Globalization.CultureInfo("en-US"), Globalization.DateTimeStyles.None, Test) = True Then
Return "true"
Else
Return "false"
End If
End Function
</msxsl:script>
<xsl:output method="text" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="ID[#ID09 !='']">
<xsl:value-of select="user:ValidDateFormat(string(#ID09))"/>
</xsl:template>
</xsl:stylesheet>
The above returns true or false. I suggest you first try the code inside Visual Studio before you try it with XSLT, because inside XSLT it is quite hard to debug.
On mxsxl:script
In my previous post I was incorrect to assume that msxsl:script was not supported by Altova. Martin Honnen corrected me in the comments. It is supported and it looks like it uses the same code provider underneath as Microsoft uses.
Your original error was an ActiveX error. If you want to run your code with an ActiveX XSLT processor, you will need to do a few more things to get it running. First of all your code is not ActiveX VBScript. Second, you will need to make sure the appropriate ActiveX objects your code required can be instantiated (i.e., are on the system path).
In browsers other than Microsoft Internet Explorer and in other processors, msxsl:script is not supported. .NET scripting is not supported in any browser, use ActiveX scripting instead.
On XSLT 2.0
If you can use Altova, you are essentially using XSLT 2.0, which does not require the extensions you wrote: it can natively compare times and dates, and can get the current date and time. XSLT 2.0 processors include, but are not limited to: Altova (native), Saxon (Java and IKVM.NET), Exselt (.NET), XMLPrime (.NET).

XslCompiledTransform fails with stack overflow

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.

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.

Can an XSLT insert the current date?

A program we use in my office exports reports by translating a XML file it exports with an XSLT file into XHTML. I'm rewriting the XSLT to change the formatting and to add more information from the source XML File.
I'd like to include the date the file was created in the final report. But the current date/time is not included in the original XML file, nor do I have any control on how the XML file is created. There doesn't seem to be any date functions building into XSLT that will return the current date.
Does anyone have any idea how I might be able to include the current date during my XSLT transformation?
XSLT 2
Date functions are available natively, such as:
<xsl:value-of select="current-dateTime()"/>
There is also current-date() and current-time().
XSLT 1
Use the EXSLT date and times extension package.
Download the date and times package from GitHub.
Extract date.xsl to the location of your XSL files.
Set the stylesheet header.
Import date.xsl.
For example:
<xsl:stylesheet version="1.0"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="date"
...>
<xsl:import href="date.xsl" />
<xsl:template match="//root">
<xsl:value-of select="date:date-time()"/>
</xsl:template>
</xsl:stylesheet>
Do you have control over running the transformation? If so, you could pass in the current date to the XSL and use $current-date from inside your XSL. Below is how you declare the incoming parameter, but with knowing how you are running the transformation, I can't tell you how to pass in the value.
<xsl:param name="current-date" />
For example, from the bash script, use:
xsltproc --stringparam current-date `date +%Y-%m-%d` -o output.html path-to.xsl path-to.xml
Then, in the xsl you can use:
<xsl:value-of select="$current-date"/>
For MSXML parser, try this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:my="urn:sample" extension-element-prefixes="msxsl">
<msxsl:script language="JScript" implements-prefix="my">
function today()
{
return new Date();
}
</msxsl:script>
<xsl:template match="/">
Today = <xsl:value-of select="my:today()"/>
</xsl:template>
</xsl:stylesheet>
Also read XSLT Stylesheet Scripting using msxsl:script and Extending XSLT with JScript, C#, and Visual Basic .NET
...
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:local="urn:local" extension-element-prefixes="msxsl">
<msxsl:script language="CSharp" implements-prefix="local">
public string dateTimeNow()
{
return DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
}
</msxsl:script>
...
<xsl:value-of select="local:dateTimeNow()"/>
Late answer, but my solution works in Eclipse XSLT. Eclipse uses XSLT 1 at time of this writing. You can install an XSLT 2 engine like Saxon. Or you can use the XSLT 1 solution below to insert current date and time.
<xsl:value-of select="java:util.Date.new()"/>
This will call Java's Data class to output the date. It will not work unless you also put the following "java:" definition in your <xsl:stylesheet> tag.
<xsl:stylesheet [...snip...]
xmlns:java="java"
[...snip...]>
I hope that helps someone. This simple answer was difficult to find for me.
format-date(current-date(), '[M01]/[D01]/[Y0001]') = 09/19/2013
format-time(current-time(), '[H01]:[m01] [z]') = 09:26 GMT+10
format-dateTime(current-dateTime(), '[h1]:[m01] [P] on [MNn] [D].') = 9:26 a.m. on September 19.
reference: Formatting Dates and Times using XSLT 2.0 and XPath