Access C# methods in xslt - c#

Can I access methods in a dll, already in the GAC, without having to declare them inside a CDATA section within the msxsl:script element?
Here's one example of what i don't want:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude- result-prefixes="xsl in lang user" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" xmlns:lang="http://www.composite.net/ns/localization/1.0" xmlns:f="http://www.composite.net/ns/function/1.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="urn:my-scripts">
<msxsl:script language="C#" implements-prefix="user">
<msxsl:assembly name="System.Web" />
<msxsl:using namespace="System.Web" />
<![CDATA[public string GetDate(string DateFormat){return DateTime.Now.ToString(DateFormat);}]]></msxsl:script>
<xsl:template match="/">
<sometag>
<xsl:value-of select="user:GetDate('dddd, dd MMMM yyyy')" />
</sometag>
</xsl:template>
</xsl:stylesheet>
I dont want to have to put my function inside a CDATA, can't i reference the dll and call my function inside the template tag like in the example above?

It all depends on the XSLT processor you use and its API. Microsoft's XslCompiledTransform allows you to pass in extension objects, see http://msdn.microsoft.com/en-us/library/tf741884.aspx and http://msdn.microsoft.com/en-us/library/system.xml.xsl.xsltargumentlist.addextensionobject.aspx. So you don't have to use the msxsl:script element but you need to define a namespace and make sure you pass in your object as an extension object bound to that namespace.

Related

How can i use csharp code in xslt to transform xml in Online transformer

My code is working on visual studio but I don't want to depend on visual studio.
When I transform the same code in an online transformer I'm getting this error.
XPST0017 XPath syntax error at char 0 on line 14 near {...:CurrentDateTime('yyyy-MM-d...}:
Cannot find a matching 1-argument function named {urn:my-scripts}CurrentDateTime()
Here is the link to the online transformer with the code
If the site is not working here is the code
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:csharp="urn:my-scripts"
exclude-result-prefixes="msxsl csharp">
<xsl:template match="/Employees">
<xsl:variable name="sDate" select="string(Employee/#date)"/>
<xsl:variable name="stringDate" select="string($sDate)"/>
<p><xsl:value-of select="$stringDate"/></p>
<xsl:value-of select="csharp:CurrentDateTime('yyyy-MM-dd')"/>
</xsl:template>
<msxsl:script language="C#" implements-prefix="csharp">
<msxsl:assembly name="System.Core"/>
<msxsl:assembly name="System.Xml.Linq"/>
<msxsl:assembly name="System.Linq"/>
<msxsl:assembly name="System.Collections"/>
<msxsl:using namespace="System.Linq"/>
<msxsl:using namespace="System.Xml.Linq"/>
<msxsl:using namespace="System.Collections.Generic"/>
<msxsl:using namespace="System.Globalization"/>
<![CDATA[
public string CurrentDateTime(string format)
{
// default format
if (string.IsNullOrEmpty(format)) {
format = "dd-MM-yyyy";
}
return DateTime.Now.ToString(format);
}
]]>
</msxsl:script>
</xsl:stylesheet>
I think the default engine on that site might not support the features you're using. Changing to Xalan 2.7.1 (at the top between Share and Help) got me the below output.
<?xml version="1.0" encoding="UTF-8"?><p>12-Jan-2022</p>
You say:
My code is working on visual studio but I don't want to depend on visual studio.
Your code as written is not dependent on visual studio, but it is dependent on Microsoft XSLT extensions. If you want your code to be portable you are going to have to get rid of these dependencies, in particular the use of msxsl:script.
In XSLT 2.0 you can do what you are trying to achieve using the current-dateTime() and format-dateTime() functions (though the formatting string will be a bit different). If you do this, however, you won't be able to use the Microsoft XSLT processors, which only support XSLT 1.0.

Add Namespaces to Root Element

I'm writing an XSLT transform where I'd like all namespace prefixes to be defined on the root element. By default MS seems to create a new prefix definition on the first element in the XML hierarchy to use that schema; meaning the same schema may be referenced on multiple elements should those elements not be related to a shared ancestor of the same schema.
By coding the root element as such, all works as desired:
<!-- ... -->
<ns0:root xmlns:ns0="http://some/schema" xmlns:ns1 = "http://another/schema">
<!-- rest of XSLT; including calls to other templates -->
</ns0:root>
<!-- ... -->
However I can't find any way to code this using xsl:element; e.g.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://some/schema"
xmlns:ns1 = "http://another/schema"
>
<!-- ... -->
<xsl:element name="ns0:root">
<xsl:attribute name="ns1" namespace="http://www.w3.org/2000/xslns/">http://another/schema</xsl:attribute>
<!-- rest of XSLT; including calls to other templates -->
</xsl:element>
<!-- ... -->
Is it possible to declare namespace prefixes against an xls:element for schemas other than that element itself?
Full Example
XML
<Demo xmlns="http://some/schema">
<a>Hello</a>
<b>World</b>
</Demo>
XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://some/schema"
xmlns:ns1 = "http://another/schema"
exclude-result-prefixes="xsl"
>
<xsl:output method="xml" indent="yes" version="1.0"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="/ns0:Demo/ns0:a">
<xsl:element name="ns1:z">
<xsl:value-of select="./text()" />
</xsl:element>
</xsl:template>
<xsl:template match="/ns0:Demo/ns0:b">
<xsl:element name="ns1:y">
<xsl:value-of select="./text()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<Demo xmlns="http://some/schema">
<ns1:z xmlns:ns1="http://another/schema">Hello</ns1:z>
<ns1:y xmlns:ns1="http://another/schema">World</ns1:y>
</Demo>
Desired Result
<Demo xmlns="http://some/schema" xmlns:ns1="http://another/schema">
<ns1:z>Hello</ns1:z>
<ns1:y>World</ns1:y>
</Demo>
or
<ns0:Demo xmlns:ns0="http://some/schema" xmlns:ns1="http://another/schema">
<ns1:z>Hello</ns1:z>
<ns1:y>World</ns1:y>
</ns0:Demo>
Your minimal example doesn't explain why you need to use xsl:element instead of xsl:copy and/or literal result elements but as XSLT 1.0 has no xsl:namespace instruction (https://www.w3.org/TR/xslt20/#creating-namespace-nodes) your only way is copying the namespace node from the stylesheet root, as in
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://some/schema"
xmlns:ns1 = "http://another/schema"
exclude-result-prefixes="xsl"
>
<xsl:output method="xml" indent="yes" version="1.0"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
<xsl:copy-of select="document('')/*/namespace::*[. = 'http://another/schema']"/>
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="/ns0:Demo/ns0:a">
<xsl:element name="ns1:z">
<xsl:value-of select="./text()" />
</xsl:element>
</xsl:template>
<xsl:template match="/ns0:Demo/ns0:b">
<xsl:element name="ns1:y">
<xsl:value-of select="./text()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
(or any other node having that, such as parameter or variable, but that way you additionally might to convert a result tree fragment to a node set first with exsl:node-set or ms:node-set).
As for why literal result elements and xsl:element give you different results, well, https://www.w3.org/TR/xslt#literal-result-element says:
The created element node will also have a copy of the namespace nodes
that were present on the element node in the stylesheet tree ...
while https://www.w3.org/TR/xslt#section-Creating-Elements-with-xsl:element does not say that.
It is important to understand that although they are represented in XML documents via namespace-declaration attributes, in XPath's and XSLT's data model, the in-scope namespaces for each element are modeled via namespace nodes, not attribute nodes. Moreover, distinct elements do not share namespace nodes; each gets its own set. When using the XML output method, an XSLT processor is responsible for producing namespace declaration attributes that correctly represent the namespace nodes present in the result tree.
That fully explains why Section 7.1.3 of the XSLT 1.0 spec explicitly disallows creating a namespace declaration via an xsl:attribute element:
XSLT processors may make use of the prefix of the QName
specified in the name attribute when selecting the prefix used for
outputting the created attribute as XML; however, they are not
required to do so and, if the prefix is xmlns, they must not do so.
Thus, although it is not an error to do:
<xsl:attribute name="xmlns:xsl" namespace="whatever">http://www.w3.org/1999/XSL/Transform</xsl:attribute>
it will not result in a namespace declaration being output.
(Emphasis added.) If creating a namespace declaration that way were permitted then it would allow for the result document to express namespace nodes that were not actually present in the result tree.
An element in the result tree can obtain a namespace node in any of these ways:
result elements created via xsl:copy or xsl:copy-of receive copies of the original element's namespace nodes.
result elements created via literal result elements in the stylesheet tree get copies of all the namespace nodes of the stylesheet element, whether declared directly on that element or on an ancestor element, with some exceptions.
result elements created via xsl:element stylesheet elements are not explicitly specified to receive any namespace nodes, but in practice, to correctly implement the spec they need to receive a namespace node for the namespace, if any, of the element's name.
Because only elements have namespace nodes, it follows (but is not explicitly specified) that each element must also receive a namespace node for each namespace to which one of its attributes' names belongs, if that namespace differs from that of the element's own name.
a namespace node itself can be copied to the result tree, as demonstrated by the other answer.
There is no reason to expect that an XSLT processor would create additional namespace nodes in the result tree beyond those. In particular, although that might afford the possibility of a simpler XML serialization of the result tree, the tree itself would be strictly more complex.
One way, then, to ensure that the <Demo> element in your result document carries a namespace declaration for a namespace other than that element's own, any obtained by copying the result element from the input tree, or that of an attribute of the element, is to use a literal result element:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://some/schema"
xmlns:ns1 = "http://another/schema">
<xsl:output method="xml" indent="yes" version="1.0"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/ns0:Demo">
<ns0:Demo>
<xsl:apply-templates select="#* | node()" />
<ns0:Demo>
</xsl:template>
<!-- ... -->
</xsl:stylesheet>
On the other hand, if you must create the element via an xsl:element element -- which should only be necessary if its name needs to be computed -- then you'll need to copy a namespace node from the input tree.

msxsl:script for c# in xslt function declared twice error

Hey all I'm trying to write an xslt template that uses a msxsl to create a hyperlink from a web.config appSetting. Every time I try to run the code, it tells me that I have declared the c# method in the script twice. The code is as follows:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:files="urn:my-script" >
<msxsl:script implements-prefix="files" language="CSharp">
<msxsl:assembly name="System.Configuration"/>
<msxsl:using namespace="System.Configuration"/>
<![CDATA[
public string LinkFile()
{
string link = System.Configuration.ConfigurationManager.AppSettings["fileUrl"];
return link;
}
]]>
</msxsl:script>
<xsl:template name="GenerateLinkFile">
<xsl:param name="fileName"/>
<xsl:param name="fileId"/>
<xsl:choose>
<xsl:when test="$fileName = ''">
<xsl:value-of select="$fileName"/>
</xsl:when>
<xsl:otherwise>
<a href="files:LinkFile()">
<xsl:value-of select="$fileName"/>
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The error I'm getting is as follows at runtime when it tries to generate the hyperlink:
System.Xml.Xsl.XslLoadException: Type 'System.Xml.Xsl.CompiledQuery.Script1' already defines a member called 'LinkFile' with the same parameter types.
I ran your XSLT against a sample XML file and it ran well; this led me to believe you are probably calling this XSLT multiple times from other XSLT files.
The best way to handle this is that, if you have a root transform calling other transforms, to include it from there, so it is only referenced once; the aim is to ensure that the function is only included once throughout your transforms, otherwise you will encounter the error you are seeing.
Alternatively, call this transformation independently - a common approach is to apply XSLT's to the source document in turn, performing sets of transformations one at a time.

XSLT: Process an Xml node set in a template while still having access to the document root

I have an xslt stylesheet that needs to call a C# XSLT extension function in order to process a collection of elements. The code looks a little like this:
Xslt:
<xsl:apply-templates mode="MyTemplate" select="myextension:GetSomeCollection(#someattribute)" />
<xsl:template mode="MyTemplate" match="myroot">
<xsl:value-of select="xpath" />...<xsl:value-of select="/xpath" />
</xsl:template>
Extension method:
public XPathNavigator GetSomeCollection(string Attribute)
{
XmlDocument doc = new XmlDocument()
//etc...
return doc.CreateNavigator();
}
The extension method returns a XPathNavigator as thats the only way I can see for an extension method to return any sort of collection.
The problem I'm having is that my template (the one with mode="MyTemplate") needs to be able to access xml nodes in the root / input document to the xslt stylesheet (as well as nodes in the node set returned by the extension method), however the template only seems to have access to the xml fragment returned by GetSomeCollection - xpath expressions starting / just resolve to the start of that fragment.
I can see why this is (the template is processing an xml fragment, however that fragment belongs to a different document), however I can't see how to get around it. There doesn't seem to be any way to get the extension method to produce an xml fragment belonging to the original document.
What can I do?
Use a variable?
<xsl:variable name="root" select="/"/>
<xsl:template mode="MyTemplate" match="myroot">
<xsl:value-of select="$root/..."/>
<xsl:value-of select="xpath" />...<xsl:value-of select="/xpath" />
</xsl:template>
or a parameter:
<xsl:apply-templates mode="MyTemplate"
select="myextension:GetSomeCollection(#someattribute)">
<xsl:with-param name="foo" select="...some local query..."/>
</xsl:apply-templates>
<xsl:template mode="MyTemplate" match="myroot">
<xsl:param name="foo"/>
<xsl:value-of select="$foo/..."/>
<xsl:value-of select="xpath" />...<xsl:value-of select="/xpath" />
</xsl:template>

Working with XML & XSL

FIRST EDIT
I'm fetching the Child 1 tag into a DropDownList in a C# form, Plz suggest the best practise code (C#) for deleting the Parent tag & all it's child tags in an XML file.
Example:
<Parents>
<Parent>
<Child 1>Something</Child 1>
<Child 2>Something</Child 2>
<Child 3>Something</Child 3>
<Child 4>Something</Child 4>
</Parent>
<Parent>
<Child 1>Something 1</Child 1>
<Child 2>Something 1</Child 2>
<Child 3>Something 1</Child 3>
<Child 4>Something 1</Child 4>
</Parent>
</Parents>
--- Previous Question ---
How can I insert the following stylesheet tag into my new xml file which is created using C# code????
<?xml-stylesheet type="text/xsl" href="issuez.xsl"?>
C# code to create the xml file:-
new XDocument(
new XElement("issues",
new XElement("issue",
new XElement("cat", comboBox1.Text),
new XElement("desc", richTextBox1.Text),
new XElement("end", dateTimePicker1.Text),
new XElement("att", textBox2.Text)
)
)
).Save(path);
First, make sure that dates in your XML are represented in the canonical YYYY-MM-DD format, and times as HH:MM:SS, so that XSLT (which, in 1.0, doesn't have date or time datatypes) can compare and sort them.
Second, use Steve Muench's technique for grouping. You generate a key on the items' dates, using xsl:key. The key() function can then be used to find a list of all items on a given date.
Using that key, you can build a list of the distinct dates that appear in the items. This is the Muenchian technique: find each item that's the first item in the list that key() returns for that item's date. This technique guarantees that you're always get one and only one item for each distinct date value. You then sort those items, and use their dates to drive the actual production of your output.
A minimal example:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="dates" match="/data/newsitem" use="#date"/>
<xsl:template match="/">
<output>
<!-- find exactly one newsitem node for each distinct #date in the document -->
<xsl:for-each select="/data/newsitem[generate-id() = generate-id(key('dates', #date)[1])]">
<xsl:sort select="#date" order="descending"/>
<xsl:call-template name="newsitems_for_date">
<xsl:with-param name="date" select="#date"/>
</xsl:call-template>
</xsl:for-each>
</output>
</xsl:template>
<xsl:template name="newsitems_for_date">
<xsl:param name="date"/>
<h1>
<xsl:value-of select="$date"/>
</h1>
<xsl:apply-templates select="/data/newsitem[#date=$date]">
<xsl:sort select="#time" order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="newsitem">
<p>
newsitem for <xsl:value-of select="#date"/>
</p>
</xsl:template>
</xsl:stylesheet>
This is the sort of thing that the back end producing the XML should handle. XSLT isn't the best place for lots of logic. Better to embed all that in the XML after you query for the news items. Just send them to the client in the proper form so they don't have to work so hard.
An XSLT stylesheet allows global parameters that can be set before a transformation is run. So with XSLT 1.0 and .NET's XslCompiledTransform if you need the current date in your stylesheet you can define a global parameter
<xsl:param name="current-date"/>
and set that before running the transformation by creating an XsltArgumentList, setting the parameter to a value and format you want/need and then pass that XsltArgumentList as the second argument to the Transform method. Then in your stylesheet you can compare the date in an XML input element or attribute to the parameter.
As you use .NET a different option is to use XSLT 2.0; Microsoft does not support that but with Saxon 9 there is a third party solution. XSLT/XPath 2.0 have a function named current-date, that way you don't need a parameter.
If you use the XSLT processor from PHP, you can use PHP-functions inside your XSLT script. All you need is call registerPhpFunctions before transformation. The result value in the right order can be used for sorting.

Categories

Resources