How can I change XSLT to using dynamic XPath using C#? - c#

I'm using Asp.net to make a transformation in XML using XSLT by C# code as follows.
protected void Page_Load(object sender, EventArgs e)
{
string strXSLTFile = Server.MapPath("EmployeeXSLT.xslt");
string strXMLFile = Server.MapPath("Employess.xml");
XmlReader reader = XmlReader.Create(strXMLFile);
XslCompiledTransform objXSLTransform = new XslCompiledTransform();
objXSLTransform.Load(strXSLTFile);
StringBuilder htmlOutput = new StringBuilder();
TextWriter htmlWriter = new StringWriter(htmlOutput);
objXSLTransform.Transform(reader, null, htmlWriter);
ltRss.Text = htmlOutput.ToString();
reader.Close();
}
and for full example with (Asp.net, XSLT and XML) follow this link
Doing XSLT Transformation in ASP.Net
Now I need to change the Xpath value of the XSLT file. I conduct a search to find out how to do this. finally, I found tow concepts to affect the XSLT file. However, no examples provided on how to apply these concepts to change XSLT "match" or "for-each select" where I need to add a variable for changeable Xpath.
the first one:
using so-called Dynamic XPath Evaluation
the second one:
using so-called XSLT Parameters
I need to know how can I change the XSLT "match" using C# code to meet specific selection.

You can use an XPathNavigator to compile an xpath expression then go through the matching nodes in your c# code.
// Create and compile the XPathExpression
string _xPathExpression = "/Data/Client[Id = 123]";
XPathExpression exprXPathCompiled
= xmlDocInputNavigator.Compile(_xPathExpression);
// Load the Stylesheet
XsltSettings settings = new XsltSettings();
XslCompiledTransform xsltTemplate = new XslCompiledTransform();
xsltTemplate.Load(_stylesheetFileName, settings, new XmlUrlResolver());
// Create an iterator to loop through the matching nodes
XPathNodeIterator iterator = xmlDocInputNavigator.Select(exprXPathCompiled);
StreamWriter fileOutput = null;
while (iterator.MoveNext())
{
//...
XPathNavigator docs

It would help if you explained the problem you are trying to solve, rather than the method you want to use to solve it.
An XSLT stylesheet is an XML document, so you can always transform it using XSLT (with a so-called "meta-stylesheet").
With XSLT 3.0 (not available from Microsoft, but available to C# users via the Saxon library) you can parameterize a stylesheet using "shadow attributes", for example
<xsl:template _match="{$pattern}"/>
where $pattern is a stylesheet parameter declared as
<xsl:param name="pattern" static="yes"/>
so that a value can be supplied by the calling application.
You can also of course use conventional (run-time) parameters in a match pattern of the form
<xsl:template match="*[#id=$requestedId]"/>
XSLT 1.0 doesn't allow parameter references in match patterns, unfortunately. Some XSLT 1.0 processors don't enforce this restriction but I don't know if that's the case for the Microsoft processor.

Related

why doesn't my treeview pick up my xsl changes?

I am using the treeview control included in asp.net 2.0.
I want to use an xsl file that I created (test3.xslt).
Why isn't the Treeview picking up the changes to the tree made with the xsl (in tw, I think) and displaying them?
tw has the changes made by the xslt transform...
The DataSourceID for my treeview is my xmldatasource (xmldatasource1).
Thanks,
blue
argsList.AddParam("Groups_From_Logged_In_User","",myLocalGroups);
XslCompiledTransform xslTransform = new XslCompiledTransform();
xslTransform.Load("C:\\ANewBeginning3\\test3.xslt");
StringWriter tw = new StringWriter();
using (StreamWriter sw = new StreamWriter("C:\\ANewBeginning3\\output.xml"))
{
xslTransform.Transform(xmldoc.CreateNavigator(), argsList, tw);
XmlDataSource1.Data = tw.ToString();
}
XmlDataSource1.DataBind();
}
I figured it out...
First, xmldatasource blocks any other xml if xmldatasource.DataFile is specified. I was using xmldatasource.data above AND specified the DataFile as well. (see http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.xmldatasource.data.aspx )
Second, do not specify the xslt file in xmldatasource either. I was trying to both transform it (using xslTransform) AND specify the same stylesheet in xmlDatasource.TransFormFile (http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.xmldatasource.transformfile.aspx). Don't do both (unless you know why you're doing it.)

How do I add an arbitrary namespace to an XslCompiledTransform at run-time?

I'm attempting to perform a very simply transform of Timed Text Markup Language (TTML) documents. Here's a minimalist example of a TTML file:
<?xml version="1.0" encoding="UTF-8"?>
<tt xml:lang="en" xmlns="http://www.w3.org/2006/04/ttaf1"
xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling">
<head>
</head>
<body>
<div xml:lang="en" style="1">
<p begin="00:00:00.20" dur="00:00:02.26">>> One time entry<br/>with a line break.</p>
</div>
</body>
</tt>
Note the document's default namespace. That's key to the problem I'm having.
Here is the transform I'm using. This is the whole thing, it's very simple.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:tt="http://www.w3.org/2006/04/ttaf1">
<xsl:output method="text" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:preserve-space elements="tt:p"/>
<!-- The indentation of the close tag for the following node is crucial to the transformed layout. Don't move it! -->
<xsl:template match="tt:p"><xsl:apply-templates /> 
</xsl:template>
<xsl:template match="tt:p/text()"><xsl:copy /> </xsl:template>
</xsl:stylesheet>
Our dataset has hundreds of documents and they don't all have the same default namespace. However, as you can see from the above XSLT, the transform is simple and based on the core data element <p /> so ultimately the namespace variations are unimportant.
The above XSL currently works with some of the source files since the XSLT file has an explicitly defined namespace prefix (tt). However, this doesn't work when encountering another source file with a different default namespace.
So I need to find a way to provide the XSLT with an arbitrary, unknown namespace value for a known prefix. I have code that can be used to find the source document default namespace:
XPathDocument sourceDoc = new XPathDocument("sourcefile.xml");
XPathNavigator sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string defNS = sourceNS.Single(n => n.Key =="").Value;
This correctly gives me http://www.w3.org/2006/04/ttaf1. However, I can't seem to figure out what to do with this value. I have searched and experimented for many hours trying to somehow provide the XslCompiledTransform instance the variable namespace. It appears that there's nothing within the area of the transform itself that can take it.
I've tried explicitly loading the XSLT file into an XmlDocument in order to manipulate the name table (after removing the explicit xmlns:tt="..." namespace declaration in the above XSLT):
XslCompiledTransform objXsl = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XPathDocument sourceDoc;
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav, xslNav;
XmlNamespaceManager xslNsManager;
sourceDoc = new XPathDocument("sourcefile.xml");
sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
xslDoc.Load("transform.xslt");
xslNsManager = new XmlNamespaceManager(xslDoc.NameTable);
xslNsManager.AddNamespace("tt", sourceNS.Single(n => n.Key =="").Value);
xslNav = xslDoc.CreateNavigator();
objXsl.Load(xslNav);
objXsl.Transform(sourceNav, null, writer);
This runs up until the actual transform, where I get an XslLoadException stating that Prefix 'tt' is not defined.
I'm at a loss at this point. Everything I can find from searching discusses putting the namespace into the XSLT document (which I already have and it works fine for one namespace value). I can't find anything in MSDN documentation or elsewhere that explains how to add the namespace definitions into the transform on the fly.
Anyone have any ideas?
A colleague recommended that I just add/manipulate the variable namespace on the XSL document. My attempt using a name table change was headed in the right direction, but it wasn't working. Here's what I came up with based on his suggestion:
XslCompiledTransform xslXform = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav;
sourceNav = new XPathDocument(sourceFile).CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string ttNamespace = sourceNS.Single(n => n.Key == "").Value;
xslDoc.Load(xslFile);
xslDoc.DocumentElement.SetAttribute("xmlns:tt", ttNamespace);
xslXform.Load(xslDoc.CreateNavigator());
xslXform.Transform(sourceNav, null, writer);
This works but it feels a bit hacky to me. I think the core problem is that I don't understand the relationship between (Xml|XPath)Document types and the name table/namespace manager associated with them.
I try it , this work.
using System;
using System.Xml.Xsl;
class Sample {
static public void Main(){
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("transform.xslt");
xslt.Transform("sourcefile.xml", "result.txt");
}
}
I think Namespace no problem.

XSLT version 2.0 transformation problem with C#

Hi I've got several XSLT 2.0 files. I need to transform these with C#..
I use the following code I got from this site: http://www.csharpfriends.com/Articles/getArticle.aspx?articleID=63
public bool Transform(string XMLPath, string XSLPath, string newXMLname){
try{
XPathDocument myXMLPath = new XPathDocument(XMLPath); //load the Xml doc
XslCompiledTransform myXSLTrans = new XslCompiledTransform();
myXSLTrans.Load(XSLPath); //load the Xsl
XmlTextWriter myWriter = new XmlTextWriter(newXMLname, null); //create the output stream
myXSLTrans.Transform(myXMLPath, null, myWriter); //do the actual transform of Xml ---> fout!!??
myWriter.Close() ;
return true;
}catch(Exception e){
return false;
}
}
But it doesn't work.. I think it's because I use XSLT version 2.0.
Is there a code/way to do this? Because there is no way to change my XSLT files to version 1.0...
Thanks in advance!
The two XSLT 2.0 processors that are designed to work in the .NET environment are Saxon.NET and XQSharp.
The XslCompiledTransform and XslTransform processors that come as part of .NET only implement XSLT 1.0.
Natively .Net Framework doesn't support XSLT 2.0. I would suggest to use XSLT 1.0, but if you can't, then use third party component, for example Saxon.

What's the most streamlined way of performing a XSLT transformation in ASP.NET?

In other words, is there a faster, more concise way of writing the following code:
//Create an object for performing XSTL transformations
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(HttpContext.Current.Server.MapPath("/xslt/" + xsltfile.Value), new XsltSettings(true, false), new XmlUrlResolver());
//Create a XmlReader object to read the XML we want to format
//XmlReader needs an input stream (StringReader)
StringReader sr = new StringReader(node.OuterXml);
XmlReader xr = XmlReader.Create(sr);
//Create a StringWriter object to capture the output from the XslCompiledTransform object
StringWriter sw = new StringWriter();
//Perform the transformation
xslt.Transform(xr, null, sw);
//Retrieve the transformed XML from the StringWriter object
string transformedXml = sw.ToString();
UPDATE (thanks for all the answers so far!):
Sorry for my vagueness: by "faster" and more "concise" I mean, am I including any unnecessary steps? Also, I would love a more "readable" solution if someone has one. I use this code in a small part of a web application I'm developing, and I'm about to move it to a large part of the application, so I want to make sure it's as neat as can be before I make the move.
Also, I get the XML from a static class (in a separate data access class library) which communicates with a database. I also manipulate the transformed XML string before shipping it off to a web page. I'm not sure if the input/response streams are still viable in this case.
One more thing: the XML and the XSLT supplied may change (users of the application can make changes to both), so I think I would be forced to compile each time.
Here's code I did for my ASP.NET, which is very similar to yours:
XDocument xDoc = XDocument.Load("output.xml");
XDocument transformedDoc = new XDocument();
using (XmlWriter writer = transformedDoc.CreateWriter())
{
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(XmlReader.Create(new StreamReader("books.xslt")));
transform.Transform(xDoc.CreateReader(), writer);
}
// now just output transformedDoc
If you have a large XSLT you can save the overhead of compiling it at runtime by compiling the XSLT into a .NET assembly when you build your project (e.g. as a post-build step). The compiler to do this is called xsltc.exe and is part of Visual Studio 2008.
In order to load such a pre-compiled XSLT you will need .NET Framework 2.0 SP1 or later installed on your server (the feature was introduced with SP1).
For an example check Anton Lapounov's blog article:
XSLTC — Compile XSLT to .NET Assembly
If pre-compiling the XSLT is not an option you should consider caching the XslCompiledTransform after it is loaded so that you don't have to compile it everytime you want to execute the transform.
Don't have time to do a full example, but some notes:
XML is not the same as System.String. Get it from the class library as XDocument or XmlDocument; finish with it as XDocument or XmlDocument.
You can use the ASP.NET Cache to store the compiled XSL, with a cache dependency on when the .XSLT file changes.
Don't convert the XML to a string then back to XML. Use node.CreateNavigator().ReadSubTree().
Similarly, use XPathNavigator.AppendChild to get an XmlWriter that will write into an XML Document.
Since you mention ASP.NET, the question is whether you can use the response stream directly for your transform output and whether you can use the input stream directly if it is a POST...
I'd rewrite the code like this:
string path = HttpContext.Current.Server.MapPath("/xslt/" + xsltfile.Value);
XmlReader reader = CreateXmlReader(node.OuterXml);
string transformedXml = Transform(path, reader);
private XmlReader CreateXmlReader(string text)
{
StringReader reader = new StringReader(text);
return XmlReader.Create(reader);
}
private string Transform(string xsltPath, XmlReader source)
{
XsltCompiledTransform transformer = new XsltCompiledTransform();
transformer.Load(
xsltPath,
new XsltSettings(true, false),
new XmlUrlResolver());
StringWriter writer = new StringWriter();
transformer.Transform(source, null, writer);
return writer.ToString();
}
The reason why I'd rewrite the code like this is because each block of code now has one and only one purpose. This makes it easier to read and understand. Additionally, the code requires less comments because a lot of information can be inferred from the names of the function and its parameters.

Is there a quick way to format an XmlDocument for display in C#?

I want to output my InnerXml property for display in a web page. I would like to see indentation of the various tags. Is there an easy way to do this?
Here's a little class that I put together some time ago to do exactly this.
It assumes that you're working with the XML in string format.
public static class FormatXML
{
public static string FormatXMLString(string sUnformattedXML)
{
XmlDocument xd = new XmlDocument();
xd.LoadXml(sUnformattedXML);
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XmlTextWriter xtw = null;
try
{
xtw = new XmlTextWriter(sw);
xtw.Formatting = Formatting.Indented;
xd.WriteTo(xtw);
}
finally
{
if(xtw!=null)
xtw.Close();
}
return sb.ToString();
}
}
You should be able to do this with code formatters. You would have to html encode the xml into the page first.
Google has a nice prettifyer that is capable of visualizing XML as well as several programming languages.
Basically, put your XML into a pre tag like this:
<pre class="prettyprint">
<link href="prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="prettify.js"></script>
</pre>
Use the XML Web Server Control to display the content of an xml document on a web page.
EDIT: You should pass the entire XmlDocument to the Document property of the XML Web Server Control to display it. You don't need to use the InnerXml property.
If identation is your only cocern and if you can afford to launch xternall process, you can process xml file with HTML Tidy console tool (~100K).
The code is:
tidy --input-xml y --output-xhtml y --indent "1" $(FilePath)
Then you can display idented string on web page once you get rid of special chars.
It would be also easy to create recursive function that makes such output - simply iterate nodes starting from the root and enter next recursion step for child node, passing identation as a parameter to each new recursion call.
Check out the free Actipro CodeHighlighter for ASP.NET - it can neatly display XML and other formats.
Or are you more interested in actually formatting your XML? Then have a look at the XmlTextWriter - you can specify things like Format (indenting or not) and the indent level, and then write out your XML to e.g. a MemoryStream and read it back from there into a string for display.
Marc
Use an XmlTextWriter with the XmlWriterSettings set up so that indentation is enabled. You can use a StringWriter as "temporary storage" if you want to write the resulting string onto screen.

Categories