Merging two XPathDocuments using XmlCompiledTransform - c#

I can't possibly be the first person to do this, it seems like it would be such a common practice to merge two documents using XSLT. However, I can't seem to find a single example on the ol' interweb.
I have two XML documents that are being retrieved as strings of XML from SQL Server. I want to use XslCompiledTransform to merge the two documents. I know that XslCompiledTransform turns off the XSL document() function by default. I have turned that on using XsltSettings when I create my XslCompiledTransform object.
My understanding about how to "add" the second document to the transformation is to use an XsltArgumentList and use the AddParam() method and add an XPathNavigator object:
XsltArgumentList xsltArgs = new XsltArgumentList();
xsltArgs.AddParam(
(string)e.UserState + "s", "http://www.myuri.com/tabledata",
dataXmlDoc.CreateNavigator()
);
However any attempts at accessing the document that is added results in either an error or nothing returned. — C#:
XslCompiledTransform fieldToXhtmlTransform = new XslCompiledTransform(true);
try
{
UriBuilder xsltUri = new UriBuilder(
Request.Url.Scheme, Request.Url.Host,
Request.Url.Port, this.ResolveUrl("Transforms/address1.xslt")
);
XmlSecureResolver resolver = new XmlSecureResolver(
new XmlUrlResolver(), new PermissionSet(PermissionState.Unrestricted)
);
fieldToXhtmlTransform.Load(
xsltUri.ToString(), new XsltSettings(true, false), resolver
);
}
catch
{
//TODO: do something useful here.
}
XPathDocument fieldSchemaXmlDoc = null;
using (MemoryStream fieldMemoryStream = new MemoryStream(
Encoding.UTF8.GetBytes(e.Result.TableMetaDataXml)
))
{
fieldSchemaXmlDoc = new XPathDocument(fieldMemoryStream);
}
XPathDocument dataXmlDoc = null;
using (MemoryStream dataMemoryStream = new MemoryStream(
Encoding.UTF8.GetBytes(e.Result.DataXml)
))
{
dataXmlDoc = new XPathDocument(dataMemoryStream);
}
StringBuilder output = new StringBuilder();
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.OmitXmlDeclaration = true;
writerSettings.Encoding = Encoding.UTF8;
XsltArgumentList xsltArgs = new XsltArgumentList();
xsltArgs.AddParam(
(string)e.UserState + "s", "http://www.myuri.com/tabledata",
dataXmlDoc.CreateNavigator()
);
XmlWriter transformedDataWriter = XmlWriter.Create(output, writerSettings);
fieldToXhtmlTransform.Transform(
fieldSchemaXmlDoc, xsltArgs, transformedDataWriter
);
XSLT - Only accesses the added document, not the document loaded with the transform.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:hlsschema="http://www.myuri.com/tableschema"
xmlns:hlsdata="http://www.myuri.com/tabledata"
exclude-result-prefixes="msxsl hlsschema hlsdata xsl"
>
<xsl:output method="html" indent="yes"/>
<p>
<xsl:template match="hlsdata:Address1s">
<xsl:for-each select="hlsdata:Address1">
<p>
<xsl:value-of select="hlsdata:dr_id"/>
</p>
</xsl:for-each>
</xsl:template>
</p>
</xsl:stylesheet>
XML
<hlsdata:Address1s
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:hlsdata="http://www.myuri.com/tabledata"
>
<hlsdata:Address1>
<hlsdata:dr_id>12345678</hlsdata:dr_id>
</hlsdata:Address1>
</hlsdata:Address1s>
I know I'm missing something obvious, but it is getting beyond frustrating. I know the document gets added as a parameter, but I can't find an example of how to access a document loaded as a parameter.
Any help would be greatly appreciated. Keep in mind that the code above is a work in progress and is between two of hundreds of attempts to make it work so if something looks a bit odd, its probably because its between attempts.

You need to define a parameter in your stylesheet and then use that parameter.
Here is a simple example, the stylesheet looks as follows:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:docs="http://example.com/2010/docs"
exclude-result-prefixes="docs"
>
<xsl:param name="docs:doc1" select="/.."/>
<xsl:template match="/">
<xsl:apply-templates select="$docs:doc1/node()"/>
</xsl:template>
<xsl:template match="root">
<ul>
<xsl:apply-templates/>
</ul>
</xsl:template>
<xsl:template match="foo">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
The C# code looks as follows:
string xml = "<root><foo>1</foo><foo>2</foo></root>";
XPathDocument doc = new XPathDocument(new StringReader(xml));
XslCompiledTransform proc = new XslCompiledTransform();
proc.Load(#"..\..\XSLTFile1.xslt");
XsltArgumentList xsltArgs = new XsltArgumentList();
xsltArgs.AddParam("doc1", "http://example.com/2010/docs", doc.CreateNavigator());
proc.Transform(XmlReader.Create(new StringReader("<dummy/>")), xsltArgs, Console.Out);
This is a console application which for simplicity writes to Console.Out but you can of course use other outputs the Transform method allows.
That example then writes <ul><li>1</li><li>2</li></ul> so the input parameter has been processed.
So that should show you how to pass in a parameter that XslCompiledTransform sees as a node-set you can process with XSLT.
As for writing a stylesheet that merges two documents, please post two input samples and the corresponding result sample you want to create if you have problems writing that XSLT.

Related

How to transform xml string using xslt stylesheet linked by xml-stylesheet href="..." in C# [duplicate]

I'd like to use embedded resources in my XSLT file, but while invoking 'document(...)' C# complains that "Error during loading document ..."
I'd like to use defined resources in XSLT file and get them by this: "document('')//my:resources/"...
How can i do that??
ex xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="xslt-gruper-v1.2.xsl" exclude-result-prefixes="my">
<my:resources>
<one>tryb</one>
</my:resources>
<xsl:variable name="res" select="document('')/*/my:resources/("/>
</xsl:stylesheet>
How can i get access to such structure without exceptions in C#? I'll add that during static transform via ex. Opera everything works fine.
<xsl:variable name="res" select="document('')/*/my:resources/("/>
The value of the select attribute is not a syntactically correct XPath expression. Every compliant XSLT processor must raise an error.
Solution:
Correct the above to:
<xsl:variable name="vRes" select="document('')/*/my:resources"/>
If there is still an exception raised, do read about the XsltSettings class.
Then create an instance of XsltSettings with this constructor, like this:
XsltSettings(true, false)
Do not enable scripting -- keep the second argument of the constructor as false.
Below is a more complete code snippet:
// Create the XsltSettings object with document() enabled and script disabled.
XsltSettings settings = new XsltSettings(true,false);
// Create the XslCompiledTransform object and load the style sheet.
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("sort.xsl", settings, new XmlUrlResolver());
Update: Another possible reason for an error is when the XSLT stylesheet is dynamically created in memory (doesn't come from file). In this case an XSLT processor typically cannot resolve the relative uri in document('').
In this last case the solution is to make the wanted element the content of an xsl:variable and to use the xxx:node-set() extension function to address this element.
After tearing my hair out for an entire day and a half, I finally figured out the solution to an identical issue I was seeing.
My code:
NUnit Test:
[Test]
public void Transform_WhenXslUsesTheDocumentFunction_DoesNotThrow()
{
//Arrange
string testOutputPath = GetTestOutputPath(
nameof(Transform_WhenXslUsesTheDocumentFunction_DoesNotThrow)
);
string inputXsl = TestFilePaths.GetResource("Import\\DocumentImporter.xsl");
XsltSettings xsltSettings = new XsltSettings(true, true);
XmlUrlResolver resolver = new XmlUrlResolver();
XslCompiledTransform xslCompiledTransform = new XslCompiledTransform();
xslCompiledTransform.Load(inputXsl, xsltSettings, resolver);
//Act
TestDelegate testDelegate = () => xslCompiledTransform.Transform(
TestFilePaths.MinimumValidXml
, testOutputPath
);
//Assert
Assert.DoesNotThrow(testDelegate);
}
DocumentImporter.xsl :
<?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"
>
<xsl:variable name="DocumentPath" select="'./Document.xml'"/>
<xsl:variable name="DocumentXML" select="document($DocumentPath)"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:value-of select="$DocumentXML//Message"/>
</xsl:template>
</xsl:stylesheet>
Document.xml :
<?xml version="1.0" encoding="utf-8" ?>
<Message>Document Message</Message>
Eventually I found this:
https://github.com/dotnet/runtime/issues/26969
Informing me that I needed to use a new .Net Core "Feature" which would allow me to use my external xml document, using the snippet:
AppContext.SetSwitch("Switch.System.Xml.AllowDefaultResolver", true);
Final code that works:
[Test]
public void Transform_WhenXslUsesTheDocumentFunction_DoesNotThrow()
{
AppContext.SetSwitch("Switch.System.Xml.AllowDefaultResolver", true);
//Arrange
string testOutputPath = GetTestOutputPath(
nameof(Transform_WhenXslUsesTheDocumentFunction_DoesNotThrow)
);
string inputXsl = TestFilePaths.GetResource("Import\\DocumentImporter.xsl");
XsltSettings xsltSettings = new XsltSettings(true, true);
XmlUrlResolver resolver = new XmlUrlResolver();
XslCompiledTransform xslCompiledTransform = new XslCompiledTransform();
xslCompiledTransform.Load(inputXsl, xsltSettings, resolver);
//Act
TestDelegate testDelegate = () => xslCompiledTransform.Transform(
TestFilePaths.MinimumValidXml
, testOutputPath
);
//Assert
Assert.DoesNotThrow(testDelegate);
}

What happens when I pass an XsltParameter to an XSLT but don't define it in the XSLT?

I'm looking for what happens when you do the following:
Create an XslCompiledTransform
Create an XsltArgumentList
Add a parameter to the list using xslArg.AddParam("name", "", "value")
Load an XSLT that does not define <xsl:param name="name" />
Does this cause an error, or does the .NET XSLT engine handle this gracefully?
This gives no problem at runtime, the provided arguments in the XsltArgumentList are ignored by the Transfrorm method of the XslCompileTransform class. Notice that loading the stylesheet will never be the problem because the parameter binding doesn't need to be provide for that method.
var xct = new XslCompiledTransform();
var xslArg = new XsltArgumentList();
xslArg.AddParam("name", "", "foobaz");
xct.Load(XmlReader.Create(new StringReader(
#"<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">
<xsl:template match=""/"">
result:<xsl:value-of select=""."" />+
</xsl:template></xsl:stylesheet>")));
var ms = new MemoryStream();
xct.Transform(
XmlReader.Create(new StringReader(#"<root>bar</root>")),
xslArg,
ms);
var s = UTF8Encoding.UTF8.GetString(ms.ToArray());
s.Dump();
produced output:
<?xml version="1.0" encoding="utf-8"?>
result:bar+

XSLT Batch Processing

I have the following XML structure:
<School>
<SchoolInfo>
<SchoolName>The Big School</SchoolName>
<Opened>2008</Opened>
<SchoolID>SCH1122</SchoolID>
<Geograpics>
<Location>London</Location>
<PostCode>ZZ11 1ZZ</PostCode>
</Geographics>
</SchoolInfo>
<Pupil>
<Name>Tom</Name>
<LastName>Jones</LastName>
<Class>12B</Class>
<Age>16</Age>
</Pupil>
<Pupil>
<Name>Steve</Name>
<LastName>Jobs</LastName>
<Class>09A</Class>
<Age>17</Age>
</Pupil>
<Pupil>
<Name>Joe</Name>
<LastName>Blogs</LastName>
<Class>13A</Class>
<Age>15</Age>
</Pupil>
</School>
If my XML structure were to contain say.. 400 Pupils and I wanted to process them in batches of 50 and write to seperate PSV's for each 50 pupils, so the first 50, then 50-100, then 100-150 and so on and write each batch to a new file.. Can this be done using XSLT or would it have to be programatic?
I now have the code to process to PSV etc im just being held up on how to process in batch as I quite frankly haven't a clue!
--
PSV: Pipe Seperated Values
SCH1122|London|Tom|12B|16
SCH1122|London|Steve|09A|17
SCH1122|London|Joe|13A|15
The code used to convert the XML is as follows:
private string PerformTransformation(string FilePath)
{
string fullXsltFile;
if (chkDateIncrement.Checked == false)
fullXsltFile = Resources.XSLTTest; // Resources.XSLT;
else
fullXsltFile = Resources.XSLTTest;
XmlDocument xsltTransformDocument = new XmlDocument();
xsltTransformDocument.LoadXml(fullXsltFile);
FileInfo xmlFileInfo = new FileInfo(FilePath);
string outputFile = CreateXmlOutputFileName(xmlFileInfo);
// load the Xslt with any settings
XslCompiledTransform transformation = new XslCompiledTransform();
XsltSettings settings = new XsltSettings(true, false);
settings.EnableScript = true;
transformation.Load(xsltTransformDocument, settings, new XmlUrlResolver());
using (XmlReader reader = XmlReader.Create(FilePath))
{
using (FileStream stream = new FileStream(outputFile, FileMode.Create))
{
transformation.Transform(reader, null, stream);
stream.Close();
}
reader.Close();
}
return outputFile;
}
I am also using the microsofts processer with VS2010 so sadly does not support v2.0, therefore would have to be v1.0 XSLT
Preferably a method of doing this with the standard xslt1.0 build would be best as getting in additional components is not the easiest thing to do.
It isn't possible with pure XSLT 1.0 to produce more than one result document.
To do so, you will need to call an extension function (that you'll have to write) for saving an element in a separate file.
You need to read your MSDN documentation on how to write an extension function.
The transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pBatchLength" select="2"/>
<xsl:variable name="vId" select="/*/SchoolInfo/SchoolID"/>
<xsl:variable name="vLoc" select="/*/SchoolInfo/Geographics/Location"/>
<xsl:template match="/*">
<xsl:apply-templates select="Pupil[position() mod $pBatchLength = 1]"/>
</xsl:template>
<xsl:template match="Pupil">
<xsl:variable name="vrtfBatch">
<batch>
<xsl:apply-templates mode="inbatch" select=
". | following-sibling::Pupil[not(position() > $pBatchLength -1)]"/>
</batch>
</xsl:variable>
<xsl:value-of select=
"my:writeResult($vrtfBatch, ceiling(position() div $pBatchLength))"/>
</xsl:template>
<xsl:template match="Pupil" mode="inbatch">
<xsl:value-of select=
"concat('
', $vId, '|', $vLoc, '|', Name, '|', Class, '|', Age)"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Explanation:
The desired length of a "batch" is specified in the external/global parameter $pBatchLength and its default value (for our small demo-example is defined as 2).
All Pupil elements that start a new batch are processed (in anonymous mode).
If necessary the batch is wrapped in a batch element (if not, this code may be deleted). Then all Pupil element comprising the current batch are processed in "inbatch" mode and the necessary CSV input is generated for each of them.
The iutput is captured in a variable named $vrtfBatch. The extension function (which you have to write) my:writeResult is called with parameters: $vrtfBatch and the sequence number for this batch. The extension function has to create a new file (using the seq. no, for the filename) and write there the contents.
You can do this using eXtensible Stylesheets (xslt).

Generating XSL block in C#

I am trying to generate the following XSL block in my C# application. Can anyone tell me how to to it?
<XSL-Script xmlns:xsl="http://www.w3.org/......">
<xsl:value-of select="$VAR">
</XSL-Script>
I tried to use regular C# XML class, and it removes the xsl: from the tag name, because it thinks xsl: is the namespace. And it also doesn't allow to use "$" in front of VAR for attribute value of "select".
Thanks a lot.
Here is a simple C# program that "generates" a complete XSLT stylesheet and then performs this transformation on a "generated" XML document and outputs the result of the transformation to a file:
using System.IO;
using System.Xml;
using System.Xml.Xsl;
class testTransform
{
static void Main(string[] args)
{
string xslt =
#"<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:variable name='vX' select='1'/>
<xsl:template match='/'>
<xsl:value-of select='$vX'/>
</xsl:template>
</xsl:stylesheet>";
string xml = #"<t/>";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
XmlDocument xslDoc = new XmlDocument();
xslDoc.LoadXml(xslt);
XslCompiledTransform xslTrans = new XslCompiledTransform();
xslTrans.Load(xslDoc);
xslTrans.Transform(xmlDoc, null, new StreamWriter("output.txt"));
}
}
When this application is built and executed it creates a file named "output.txt" and its contents is the expected, correct result from the dynamically generated XSLT transformation:
<?xml version="1.0" encoding="utf-8"?>1

What's the property way to transform with XSL without HTML encoding my final output?

So, I am working with .NET. I have an XSL file, XslTransform object in C# that reads in the XSL file and transforms a piece of XML data (manufactured in-house) into HTML.
I notice that my final output has < and > automatically encoded into < and >. Is there any ways I can prevent that from happening? Sometimes I need to bold or italicize my text but it's been unintentionally sanitized.
Your xsl file should have:
an output of html
omit namespaces for all used in the xslt
i.e.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="xsl msxsl">
<xsl:output method="html" indent="no" omit-xml-declaration="yes"/>
<!-- lots -->
</xsl:stylesheet>
And you should ideally use the overloads that accept either a TextWriter or a Stream (not XmlWriter) - i.e. something like:
StringBuilder sb = new StringBuilder();
using (XmlReader reader = XmlReader.Create(source)
using (TextWriter writer = new StringWriter(sb))
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("Foo.xslt"); // in reality, you'd want to cache this
xslt.Transform(reader, options.XsltOptions, writer);
}
string html = sb.ToString();
In the xslt, if you really want standalone < / > (i.e. you want it to be malformed for some reason), then you need to disable output escaping:
<xsl:text disable-output-escaping="yes">
Your malformed text here
</xsl:text>
However, in general it is correct to escape the characters.
I have used this in the past to transform XMl documents into HTML strings which is what you need.
public static string TransformXMLDocFromFileHTMLString(string orgXML, string transformFilePath)
{
System.Xml.XmlDocument orgDoc = new System.Xml.XmlDocument();
orgDoc.LoadXml(orgXML);
XmlNode transNode = orgDoc.SelectSingleNode("/");
System.Text.StringBuilder sb = new System.Text.StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.ConformanceLevel = ConformanceLevel.Auto;
XmlWriter writer = XmlWriter.Create(sb, settings);
System.Xml.Xsl.XslCompiledTransform trans = new System.Xml.Xsl.XslCompiledTransform();
trans.Load(transformFilePath);
trans.Transform(transNode, writer);
return sb.ToString();
}
(XslTransform is deprecated, according to MSDN. They recommend you switch to XslCompiledTransform.)
Can you post an example of the input/output?

Categories