xslt to multiple output and base output directory - c#

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>
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();
var baseOutUri = new System.Uri(Directory.GetCurrentDirectory());
exe.BaseOutputURI = baseOutUri.AbsoluteUri;
Console.WriteLine(exe.BaseOutputURI);
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
The Console.WriteLine writes:
file:///D:/dev/fromSvn/cclb/bas/bin/debug
But the outputs are generated in:
D:\dev\fromSvn\cclb\bas\bin
If I want to fix this I have to modify my code to:
exe.BaseOutputURI = baseOutUri.AbsoluteUri + "/";
M'I correct or do I miss something ?

This is the way URI resolution works. If the base URI is /a/b/c, and the relative URI is w.xml, then the result of resolving the relative URI against the base URI is /a/b/w.xml. The algorithm for resolving a relative URI is a syntactic operation on two character strings, it never attempts to work out whether the base URI /a/b/c refers to a directory, to an ordinary file, or to nothing in particular.

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);
}

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.

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

while compling junk message was generating by using xslt transform in c#

hi here i am generating hex 01 and hex 03 with xslt and using c# on transformation hex 01 was genarating like a space in text document if i see in hexadecimal format i could see
 some junk is generating before the hex 01, note this issue was not replicating for hex 03 it was working good,exactly etx was generating, how could i solve this issue ? any idea please..
<?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:myScripts="myScripts">
<xsl:output method="text" indent="yes"/>
<msxsl:script implements-prefix="myScripts" language="C#">
public string SOH()
{
return '\u0001'.ToString();
}
</msxsl:script>
<msxsl:script implements-prefix="myScripts" language="C#">
public string ETX()
{
return '\u0003'.ToString();
}
</msxsl:script>
<xsl:template match="/">
<xsl:value-of select="myScripts:SOH()"></xsl:value-of>
<xsl:value-of select="myScripts:ETX()"></xsl:value-of>
</xsl:template>
</xsl:stylesheet>
here is my c# code behind code :
XslCompiledTransform transform = new XslCompiledTransform(true);
transform.Load(strCTD, new XsltSettings() { EnableScript = true }, null); // Loading the given Xslt document
var writerSettigns = transform.OutputSettings.Clone();
writerSettigns.CheckCharacters = false;
string strFileName = strpath + langid + strCSVFILE + strMsgType + strORGMSG + strSeqNum + strNowDate + strHour + strMin + strSec + ".FTS";
try
{
MemoryStream memoryStream = new MemoryStream();
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = new UTF8Encoding(false);
xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document;
xmlWriterSettings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(strFileName, writerSettigns))
{
transform.Transform("sampleCTD.xml", xmlArgsList, writer);
}
string xmlString = Encoding.UTF8.GetString(memoryStream.ToArray());
System.IO.StreamWriter file = new System.IO.StreamWriter(strFileName);
file.WriteLine(xmlString);
file.Close();
}
catch (Exception ex)
{
LogError(1, "Form1", "FileGenerationForCID", ex.Message.ToString(), ex.StackTrace.ToString());
throw ex;
}
i have added some code to to avoid BOM error on starting of the text, even though it was not sorted out kindly please can any one suggest on this to me soon ....
What you're seeing is the UTF-8 BOM, the Byte Order Mark, that allows UTF-8 aware readers to understand the encoding correctly. Perhaps change your output declaration to:
<xsl:output method="text" indent="yes" encoding="unicode"/>
Use an entity instead of outputting the raw character. This takes the form of something like  or .
And on another topic - I don't understand your custom script methods. My first instinct was "Why not just use return "\u0001"; instead of putting a char and converting it to string, but when I think about it, you can jump use &#0001 directly embedded in the XML.
Either use XML entities directly in your XSLT, like this:
<xsl:value-of select=""/>
or, if for some reason you can't embed the entities, make the code snippets simpler, without casting to string;
public string SOH ()
{
return "\u0001";
}

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