I have a lengthy XML like so:
MainXML.xml:
<OpenTag>
<SubTag>Value 1</SubTag>
<SubTag>Value 2</SubTag>
<SubTag>Value 3</SubTag>
<SubTag>Value 4</SubTag>
</OpenTag>
Except that SubTag is a more complex repeated structure with a lot of data. Couldn't I somehow do this instead?:
SubXML1.xml:
<SubTag>Value 1</SubTag>
SubXML2.xml:
<SubTag>Value 2</SubTag>
SubXML3.xml:
<SubTag>Value 3</SubTag>
SubXML4.xml:
<SubTag>Value 4</SubTag>
MainXML.xml:
<OpenTag>
... For Each XML File in the Sub-XML Folder, stick it here.
</OpenTag>
I realize I can do this with basic File and String functions, but wanted to know if there was a native way with XSL/XML to do it.
In case you are OK with LINQ to XML, here is a working code:
public static XDocument AggregateMultipleXmlFilesIntoASingleOne(string parentFolderPath, string fileNamePrefix)
{
return new XDocument(
new XElement("OpenTag",
Directory.GetFiles(parentFolderPath)
.Where(file => Path.GetFileName(file).StartsWith(fileNamePrefix))
.Select(XDocument.Load)
.Select(doc => new XElement(doc.Root))
.ToArray()));
}
Not sure if this is exactly what you are after, but it seems to work OK. It relies on XSLT 2.0, hopefulyl you can use that. You also need to have a folder ready for the SubXML files:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="//SubTag">
<xsl:result-document href="folder/SubXML{position()}.xml" method="xml">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:result-document>
</xsl:for-each>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Related
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.
So I am following a tutorial from here. I have the following code but it simply won't display anything inside the web browser component. The specific instructions I followed are:
"Hi everyone,
I managed to do this using the following strategy:
Download an HTML 1.0 compliant version of defaultss.xsl (I'm using my own)
Create an XMLCompiledTransform object and load the "defaultss.xsl" stylesheet. I actually embedded it is a resource in my assembly and read it from there.
Transform your XML stream using the XMLCompiledTransform into an HTML stream.
Set the WebBrowser.DocumentStream = HTML stream
Remember to "AllowNavigation" otherwise the thing doesn't work.
Hope this helps,
B."
OpenFileDialog OpenCCD = new OpenFileDialog();
OpenFileDialog OpenXSL = new OpenFileDialog();
string xslpath, filepath;
private void button1_Click(object sender, EventArgs e)
{
OpenCCD.ShowDialog();
filepath = OpenCCD.FileName;
}
private void cmd_XSL_Click(object sender, EventArgs e)
{
OpenXSL.ShowDialog();
xslpath = OpenXSL.FileName;
}
private void cmd_View_Click(object sender, EventArgs e)
{
MessageBox.Show("XML Path:" + filepath + "\nXSL Path:" + xslpath);
XslCompiledTransform xsl = new XslCompiledTransform();
xsl.Load(xslpath);
MemoryStream ms = new MemoryStream();
xsl.Transform(filepath, null, ms);
webBrowser1.DocumentStream = ms;
}
The xml is pretty long so here is a piece of it:
<?xml version="1.0" encoding="UTF-8"?>
<CDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:hl7-org:v3 http://xreg2.nist.gov:8080/hitspValidation/schema/cdar2c32/infrastructure/cda/C32_CDA.xsd" xmlns="urn:hl7-org:v3" xmlns:mif="urn:hl7-org:v3/mif">
<realmCode code="US"/>
<typeId root="2.16.840" extension="PO"/>
<templateId root="2.16"/>
<templateId root="2.17"/>
<id root="2.16.84" extension="E5"/>
<code codeSystem="2.16.88" codeSystemName="" code="3" displayName="TITLE"/>
<title>TITLE</title>
<effectiveTime value="2925"/>
<confidentialityCode code="Y" codeSystem=""/>
<recordTarget>
<patientRole>
<id root="2.16.84" extension="215"/>
<addr use="P">
<streetAddressLine>123 GREEN TRAIL RD</streetAddressLine>
<city>BIRMINGHAM</city>
<state>AL</state>
<postalCode>35211</postalCode>
<country>USA</country>
</addr>
<telecom use="H" value="tel:000000000"/>
<patient>
<name>
<given qualifier="L">ADAM</given>
<family qualifier="R">EVERYMAN</family>
</name>
<ethnicGroupCode codeSystem="2.16.84" codeSystemName="Race" displayName="Not Hispanic or Latino"/>
<languageCommunication>
<languageCode code="eng"/>
</languageCommunication>
</patient>
</patientRole>
</recordTarget>
XSL is also long so here is a piece of it:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:n3="http://www.w3.org/1999/xhtml" xmlns:n1="urn:hl7-org:v3" xmlns:n2="urn:hl7-org:v3/meta/voc" xmlns:voc="urn:hl7-org:v3/voc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="html" indent="yes" version="4.01" encoding="ISO-8859-1" doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<xsl:variable name="tableWidth">50%</xsl:variable>
<xsl:variable name="title">
<xsl:choose>
<xsl:when test="/n1:CDocument/n1:title">
<xsl:value-of select="/n1:CDocument/n1:title"/>
</xsl:when>
<xsl:otherwise>CDocument</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="n1:CDocument"/>
</xsl:template>
The solution that I found was to use the following code instead. This code, saves a result.html and renders that inside the web browser control.
XslCompiledTransform myXslTrans = new XslCompiledTransform();
myXslTrans.Load(xslpath);
string directoryPath = Path.GetDirectoryName(filepath);
myXslTrans.Transform(filepath, directoryPath+"result.html");
webBrowser1.Navigate(directoryPath+"result.html");
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 > and <.
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>
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).
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.