XSLT Batch Processing - c#

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).

Related

xslt to multiple output

the xsl code is the following
<xsl:template match="/">
<xsl:for-each select="/t:Flow/t:AccountingRecords/t:AccountingRecord">
<xsl:result-document method="xml" href="UBL-invoice.2.1-{t:Reference}-output.xml">
<xsl:apply-templates select="."/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
this runs well from the command line using transform
now I try do use it from a .net app and I get the following error:
$exception {"The system identifier of the principal output file is unknown"} Saxon.Api.DynamicError
If I change my code to
<xsl:result-document method="xml" href="file:///d:/temp/UBL-invoice.2.1-{t:Reference}-output.xml">
<xsl:apply-templates select="."/>
</xsl:result-document>
then I get my files.
My question is: is there a way to work with relative path from an app, or must I add a dir parameter to my xsl ?
My code is quite the one from the samples
Processor proc = new Processor();
var comp = proc.NewXsltCompiler();
Xslt30Transformer exe = comp.Compile(new Uri("file:///" + System.IO.Path.GetFullPath("./Styles/style.xslt"))).Load30();
DocumentBuilder builder = proc.NewDocumentBuilder();
builder.BaseUri = new Uri("file:///" + System.IO.Path.GetFullPath("./ar2.xml"));
XdmNode inp = builder.Build(System.IO.File.OpenRead(System.IO.Path.GetFullPath("./ar2.xml")));
Serializer serializer = proc.NewSerializer();
serializer.SetOutputWriter(Console.Out);
// Transform the source XML and serialize the result document
exe.ApplyTemplates(inp, serializer); // < ==== Exception here
Set the BaseOutputURI property on the Xslt30Transformer object. This will be used as the base URI for resolving the relative URI appearing in xsl:result-document/#href.

Transformation through msxsl namespace

I am using XSLT transformation template which works with MSSQL database in which i have to do some complex computing. I show piece of code below. Important is method GetZaloha. It returns large xml element which I want past it to output xml, but obv. this approach doesn't work. I tried to return value as string through xElem.ToString() but result is represented (ofc.) as string. so "<" and ">" marks are represented as escaped &gt and &lt.
Do somebody have idea how provide some object to transformation which will be represented as xml structured text? Thank you very much.
<msxsl:script implements-prefix="utility" language="C#">
<msxsl:assembly name="System"/>
<msxsl:assembly name="System.Data"/>
<msxsl:using namespace="System"/>
<msxsl:using namespace="System.Xml"/>
<msxsl:assembly name="System.Core" />
<msxsl:assembly name="System.Xml.Linq" />
<msxsl:using namespace="System.Collections.Generic" />
<msxsl:using namespace="System.Linq" />
<msxsl:using namespace="System.Xml.Linq" />
<![CDATA[
public static XElement GetZaloha(string VariableSymbol)
{
XElement xElem = .....
return xElem;
}
]]>
</msxsl:script>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="CisloDokladu">
<xsl:element name="CisloDokladu">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
<xsl:variable name="VariabilniSymbol" select="./VariabilniSymbol"/>
<xsl:element name="OdpoctyZaloh">
<xsl:attribute name="ObjectType">List</xsl:attribute>
<xsl:attribute name="ObjectName">OdpocetZalohy</xsl:attribute>
<xsl:value-of select="utility:GetZaloha($VariabilniSymbol)" />
</xsl:element>
</xsl:template>
Some solution: write output XML with XSLT transform extension object and use XmlWriter method WriteCData:
public class CDataWriter
{
public static XDocument Transform(XDocument doc, string fileXslt)
{
XsltArgumentList xslArg = new XsltArgumentList();
XslCompiledTransform trans = new XslCompiledTransform();
trans.Load(fileXslt);
XDocument xmlOutput = new XDocument();
using (var writer = xmlOutput.CreateWriter())
{
CDataWriter info = new CDataWriter(writer);
xslArg.AddExtensionObject("urn:cdata", info);
trans.Transform(input: doc.CreateReader(), arguments: xslArg, results: writer);
}
return xmlOutput;
}
protected CDataWriter(XmlWriter writer) { this.writer = writer; }
XmlWriter writer;
public string CData(object obj)
{
if (obj != null && obj is System.Xml.XPath.XPathNodeIterator)
{
var iter = obj as System.Xml.XPath.XPathNodeIterator;
if (iter.Count > 0 && iter.MoveNext())
{
var current = iter.Current;
writer.WriteCData(current.OuterXml);
}
}
return string.Empty;
}
}
Sample xslt file:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:trans="urn:cdata">
<xsl:template name="cdata">
<Result>
<xsl:value-of select="trans:CData(*)" />
</Result>
</xsl:template>
</xsl:stylesheet>
You are currently using <xsl:value-of> to include your function result into your XML output. This tag always flattens the XML node tree by (more or less) extracting just the text parts from it. I would suggest that you use
<xsl:copy-of select="utility:GetZaloha($VariabilniSymbol)" />
instead which copies the complete node tree (without alteration) into the XML output.
Five years after, but since I have just figured out the solution I post it anyway.
The xsl engine only accepts a limited set of types. XPathNavigator is one and it allows to return a full xml sub-tree from the script into the document.
Here is some more info about the accepted return types
https://learn.microsoft.com/en-us/dotnet/standard/data/xml/script-blocks-using-msxsl-script
The xsl
<?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-scripts="urn:my-scripts"
exclude-result-prefixes="msxsl my-scripts"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<msxsl:script language="C#" implements-prefix="my-scripts">
<![CDATA[
public static XPathNavigator createXmlNodes(string text)
{
//prepare DOM with doc elent named "root"
XmlDocument doc = new XmlDocument();
doc.LoadXml("<root/>");
var root = doc.DocumentElement;
//Add an element
var elem = doc.CreateElement("token");
elem.AppendChild(doc.CreateTextNode(text));
root.AppendChild(elem);
//Add an element
elem = doc.CreateElement("token2");
elem.AppendChild(doc.CreateTextNode(text));
root.AppendChild(elem);
//returnxpathNavigator, this will include "root"
return root.CreateNavigator();
// A second option using a string instead of a DOM
//var sr = new System.IO.StringReader("<token>" + text + "</token>");
//var xp = new XPathDocument(sr);
//return xp.CreateNavigator();
}
]]>
</msxsl:script>
<!-- Ignore Input just call function using fixed text -->
<xsl:template match="/">
<doc>
<!-- the /* is there to skip the root element -->
<xsl:copy-of select="my-scripts:createXmlNodes('Hello World')/*" />
</doc>
</xsl:template>
</xsl:stylesheet >
Empty dummy input
<?xml version="1.0" encoding="utf-8"?>
<empty/>
result
<?xml version="1.0" encoding="utf-8"?>
<doc>
<token>Hello World</token>
<token2>Hello World</token2>
</doc>

XSLT transformation does not return anything

I am trying to create a small code generator based upon XSLT transformation. I am rather new to XSLT, and it seems that I have made error (not sure where) in my transformations. I have two transformation (main and util), the metadata is pulled from XML file (it stores information about table names, which will be used for class generation- table name = class name; column name = field name ). Here are my transformations:
Main transformation
<?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:dbs="http://kadgen/DatabaseStructure">
<xsl:import href="..\MySolution\UtilTemplates.xslt"/>
<xsl:output method="text" encoding="UTF-8" indent="yes"/>
<xsl:param name="Name"/>
<xsl:param name ="filedName"/>
<xsl:template match="/">
<xsl:apply-templates select=
"//dbs:DataStructure//dbs:Table[#Name='Customer']"
mode="BuildClasses" />
</xsl:template>
<xsl:template match="dbs:Table" mode="BuildClasses">
<xsl:call-template name="Header"/>
Public Class <xsl:value-of select="#Name"/>
{
<xsl:call-template name="ClassConstructors"/>
<xsl:call-template name="ClassLevelDeclarations"/>
<xsl:call-template name="FieldAccessProperties"/>
}
</xsl:template>
<xsl:template name="ClassConstructors">
</xsl:template>
<xsl:template name="ClassLevelDeclarations">
</xsl:template>
<xsl:template name="FieldAccessProperties">
</xsl:template>
</xsl:stylesheet>
Here is the util transformation:
<?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:output method="text"/>
<xsl:template name="Header">
using System;
using System.Collections.Generic;
using System.Xml;
using System.Linq;
using System.Text;
</xsl:template>
</xsl:stylesheet>
Here is part of my XML file:
<?xml version="1.0" encoding="utf-8" ?>
<dbs:MetaDataRoot FreeForm="true" xmlns:dbs="http://kadgen/DatabaseStructure">
<dbs:DataStructures>
<dbs:DataStructure Name="ASPDatabase">
<dbs:Tables>
<dbs:Table Name="Customer" OriginalName="Customer">
<dbs:TableColumns>
<dbs:TableColumn Name="CustomerID" NETType="int" IsPrimaryKey="true" />
<dbs:TableColumn Name="Name" NETType="string" IsPrimaryKey="false"/>
</dbs:TableColumns>
<dbs:TableConstraints>
<dbs:PrimaryKey>
<dbs:PKField Name="CustomerID"/>
</dbs:PrimaryKey>
</dbs:TableConstraints>
</dbs:Table>
</dbs:Tables>
</dbs:DataStructure>
</dbs:DataStructures>
</dbs:MetaDataRoot>
Here is how I start transformation:
XslCompiledTransform myXslTrans = new XslCompiledTransform();
myXslTrans.Load("xslt transformation location");
myXslTrans.Transform("XML source location", "Empty class file location");
After executing above code, only thing I get is empty cs file. It may seem robust but please, go through it and help me with this.
Thanks.
Have you tried debugging your XSLT files with VS XSLT debugger?
Looks like it generates correct output.
This is working for me but I made a couple of small changes and fixed your XML doc.
Here is my test app.
private static void Main(string[] args)
{
var myXslTrans = new XslCompiledTransform();
var doc = new XmlDocument();
doc.LoadXml(GetResourceTextFile("ProjectName.MainTransform.xslt"));
myXslTrans.Load(doc);
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var xsltArgs = new XsltArgumentList();
xsltArgs.AddParam("Name", "", "test name");
xsltArgs.AddParam("filedName", "", "test filed name");
var docXml = new XmlDocument();
docXml.LoadXml(GetResourceTextFile("ProjectName.Test.xml"));
myXslTrans.Transform(docXml, xsltArgs, sw);
var test = sw.ToString();
}
public static string GetResourceTextFile(string filename)
{
string result = string.Empty;
var assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(filename))
{
if (stream != null)
{
using (var sr = new StreamReader(stream))
{
result = sr.ReadToEnd();
}
}
}
return result;
}
The major differences I made were adding XSLT arguments and loading the embedded files into XmlDocuments first. I can't reproduce the blank output so I can't be sure what the root cause of your issue is.

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

Merging two XPathDocuments using XmlCompiledTransform

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.

Categories