I have been searching for the solution to convert XML into CSV, but I cannot find one which matches my case as XML structure is different
XML structure looks like
<VWSRecipeFile>
<EX_Extrusion User="ABC" Version="1.0" Description="" LastChange="41914.7876341204">
<Values>
<C22O01_A_TempFZ1_Set Item="A_TempFZ1_Set" Type="4" Hex="42700000" Value="60"/>
<C13O02_A_TempHZ2_Set Item="A_TempHZ2_Set" Type="4" Hex="43430000" Value="195"/>
<C13O03_A_TempHZ3_Set Item="A_TempHZ3_Set" Type="4" Hex="43430000" Value="195"/>
</Values>
</EX_Extrusion>
</VWSRecipeFile>
Expected CSV Format
A_TempFZ1_Set,A_TempHZ2_Set,A_TempHZ3_Set
60,195,195
i can achieve the new expected csv format, but don't know if it is the best way to do it, any suggestion is appreciated
'
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="/VWSRecipeFile">
<xsl:for-each select="EX_Extrusion/Values/*">
<xsl:value-of select="concat(#Item,',')" />
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:for-each select="EX_Extrusion/Values/*">
<xsl:value-of select="concat(#Value,',')" />
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>'
Thanks
One way you can do this is to use XSLT, the language designed to work with XML. You surely can parse the XML with C# but I like XSLT cause it's cleaner.
You define an external XSLT file, then call it within C# to do the transform.
Edit: added new columns based on new requirements.
File C:\XmlToCSV.xslt (
is the newline character)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="/VWSRecipeFile">
<xsl:variable name="User" select="EX_Extrusion/#User"/>
<xsl:variable name="Version" select="EX_Extrusion/#Version"/>
<xsl:variable name="Description" select="EX_Extrusion/#Description"/>
<xsl:variable name="LastChange" select="EX_Extrusion/#LastChange"/>
<xsl:text>Item,Type,Hex,Value,User,Version,Description,LastChange
</xsl:text>
<xsl:for-each select="EX_Extrusion/Values/*">
<xsl:value-of select="concat(#Item,',',#Type,',',#Hex,',',#Value,',',$User,',',$Version,',',$Description,',',$LastChange,'
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Apply the transform with XslCompiledTransform:
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("C:\\XmlToCSV.xslt");
xslt.Transform("InputFile.xml", "OutputFile.csv");
Adjust it based on your needs.
Basic idea would be to iterate though values nodes and select the attributes you want for each node and keep writing them to a file with comma separator. Simply name the file as .csv. If you want something ready made, check this out.
XSLT is one way to do it. Alternatively you can use, Cinchoo ETL - an open source library available to parse xml, produce CSV the way you want it.
string xml = #"<VWSRecipeFile>
<EX_Extrusion User=""ABC"" Version=""1.0"" Description="""" LastChange=""41914.7876341204"">
<Values>
<C22O01_A_TempFZ1_Set Item=""A_TempFZ1_Set"" Type=""4"" Hex=""42700000"" Value=""60""/>
<C13O02_A_TempHZ2_Set Item=""A_TempHZ2_Set"" Type=""4"" Hex=""43430000"" Value=""195""/>
<C13O03_A_TempHZ3_Set Item=""A_TempHZ3_Set"" Type=""4"" Hex=""43430000"" Value=""196""/>
</Values>
</EX_Extrusion>
</VWSRecipeFile>";
StringBuilder sb = new StringBuilder();
using (var p = ChoXmlReader.LoadText(xml).WithXPath("/Values/*"))
{
using (var w = new ChoCSVWriter(sb)
.WithFirstLineHeader()
)
w.Write(p.ToDictionary(r => r.Item, r => r.Value).ToDynamic());
}
Console.WriteLine(sb.ToString());
Output:
A_TempFZ1_Set,A_TempHZ2_Set,A_TempHZ3_Set
60,195,196
Disclaimer: I'm the author of this library.
Related
I have incoming XML message and each message have different schema. I want to transform that request into another schema using C#.Net as my XSLT processor. Here is simplified scenario of the situation I have.
Incoming request:
<?xml version="1.0" encoding="utf-8"?>
<Request xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<StatusEvent>
<TenderId>TenderId1</TenderId>
<EventCode>TENDER_STARTED</EventCode>
</StatusEvent>
</Request>
Translate to:
<?xml version="1.0" encoding="utf-8"?>
<TransactionStatus xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Event>TRANSACTION_STARTED</Event>
</TransactionStatus>
Incoming request:
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<TenderCreated>
<TenderId>TenderId1</TenderId>
</TenderCreated>
</Response>
Translate to:
<?xml version="1.0" encoding="utf-8"?>
<TransactionStarted xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
</TransactionStarted>
Here is the XSLT I'm currently using to achieve above result,
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.example.com/api"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="text()"/>
<xsl:template match="ns0:StatusEvent[1]">
<TransactionStatus
xmlns="http://www.example1.com/api">
<RequestId>
<xsl:value-of select="//ns0:RequestId"/>
</RequestId>
<TransactionId>
<xsl:value-of select="ns0:TenderId"/>
</TransactionId>
<Event>
<xsl:value-of select="ns0:EventCode"/>
</Event>
</TransactionStatus>
</xsl:template>
<xsl:template match="ns0:TenderCreated[1]">
<TransactionStarted
xmlns="http://www.example1.com/api">
<RequestId>
<xsl:value-of select="//ns0:RequestId"/>
</RequestId>
<TransactionId>
<xsl:value-of select="ns0:TenderId"/>
</TransactionId>
</TransactionStarted>
</xsl:template>
</xsl:stylesheet>
So Here is the two questions I have,
For the current scenario I'm getting correct result but, is there any better way to achieve this?
For some of the incoming request, I want to select template based on external parameter, how do I achieve that?
Update: More clarification on second question,
e.g: In 2nd Incoming request I might have TenderUpdated instead of TenderCreated and for that I want to translate that into either TransactionUpdated or TransactionCanceled depends on external string parameter.
so If incoming request is like,
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns="http://www.example.com/api">
<SourceId>SourceId1</SourceId>
<RequestId>RequestId1</RequestId>
<TenderUpdated>
<TenderId>TenderId1</TenderId>
</TenderUpdated>
</Response>
And parameter passed is Update, translate to
<?xml version="1.0" encoding="utf-8"?>
<TransactionUpdated xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Update/>
</TransactionUpdated>
And if parameter passed is Cancel , translate to
<?xml version="1.0" encoding="utf-8"?>
<TransactionCanceled xmlns="http://www.example1.com/api">
<RequestId>RequestId1</RequestId>
<TransactionId>TenderId1</TransactionId>
<Cancel/>
</TransactionCanceled>
This is simplified scenario, actual message have more xml tag and TransactionUpdated and TransactionCanceled differs much.
If you know all result elements should be in the namespace http://www.example1.com/api then you can put that on the xsl:stylesheet e.g.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.example1.com/api"
xmlns:ns0="http://www.example.com/api"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 xs">
As for the parameter, declare it as
<xsl:param name="transactionName" select="'Updated'"/>
and when you want to create an element using that parameter don't use a literal result element but xsl:element instead:
<xsl:element name="Transaction{$transactionName}">...</xsl:element>
Unfortunately in XSLT 1.0 the use of parameter or variables references inside of patterns is not allowed so to handle the condition you can only write a template matching on the element name and then you need to use xsl:choose/xsl:when to handle the different element names. Here is an example that you can hopefully extend:
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:api="http://www.example.com/api"
xmlns="http://www.example1.com/api"
exclude-result-prefixes="api">
<xsl:param name="transactionName" select="'Update'"/>
<xsl:output indent="yes"/>
<xsl:template match="api:Response">
<xsl:element name="Transaction{$transactionName}">
<xsl:apply-templates select="api:RequestId | api:TenderUpdated/api:TenderId"/>
<xsl:choose>
<xsl:when test="$transactionName = 'Update'">
<Update/>
</xsl:when>
<xsl:when test="$transactionName = 'Cancel'">
<Cancel/>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:template>
<xsl:template match="api:RequestId">
<RequestId>
<xsl:apply-templates/>
</RequestId>
</xsl:template>
<xsl:template match="api:TenderId">
<TransactionId>
<xsl:apply-templates/>
</TransactionId>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/94rmq5R.
If there are lots of differences between the input formats then I might be tempted to handle them by different stylesheets. If that is not possible then it might make sense to branch in the template for the root and use modes on templates to distinguish the handling e.g.
<xsl:template match="api:Response">
<xsl:choose>
<xsl:when test="$transactionName = 'Update'">
<xsl:apply-templates select="." mode="update"/>
</xsl:when>
<xsl:when test="$transactionName = 'Cancel'">
<xsl:apply-templates select="." mode="cancel"/>
</xsl:when>
</xsl:choose>
</xsl:element>
</xsl:template>
<xsl:template match="api:Response" mode="update">
<TransactionUpdate>
<xsl:apply-templates select="api:Foo | api:Bar" mode="update"/>
<Update/>
<TransactionUpdate>
</xsl:template>
<!-- now add templates for the other elements and for other mode(s) here -->
I have an application written in C# that needs to apply a template name to an xml file that is defined in an XSLT.
Example XML:
<Data>
<Person>
<Name>bob</Name>
<Age>43</Age>
</Person>
<Thing>
<Color>Red</Color>
</Thing>
</Data>
Example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="TargetName" />
<xsl:param name="RootPath" />
<xsl:Template Name="AgeGrabber">
<xsl:value-of select="/Person/Age" />
</xsl:Template>
<xsl:Template Name="ColorGrabber">
<xsl:value-of select="/Color" />
</xsl:Template>
</xsl:stylesheet>
Say I wanted to run the template "ColorGrabber" with path "/Data/Thing" and then run another transform with the template "AgeGrabber" with path "/Data". Is this possible? I was thinking I could pass in the path and the template name (hense the 2 params at the top) and then do some type of switch but it looks like xsl:call-template can not take a parameter as the name attribute.
How can I achieve this behaviour?
There are a number of issues with this question:
<xsl:stylesheet version="2.0" ... is specified, however, at present >NET doesn't natively support XSLT 2.0.
Thecode example is not too meaningful, because a single XML document cannot contain both /Person/Age and /Color elements -- a wellformed XML document has only a single top element and it can be either Person or Color, but not both.
In case there was a more meaningful example:
<Person>
<Age>27</Age>
<HairColor>blond</HairColor>
</Person>
one simple solution is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pProperty" select="'Age'"/>
<xsl:template match="/">
<xsl:value-of select="/*/*[name()=$pProperty]"/>
</xsl:template>
</xsl:stylesheet>
and when this transformation is applied on the above XML document, it produces the wanted result:
27
In case the nestedness of the elements of interest can be arbitrary and/or we need to do different processing on the different elements, then an appropriate solution is to use matching templates (not named ones):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pProperty" select="'HairColor'"/>
<xsl:template match="Age">
<xsl:if test="$pProperty = 'Age'">
This person is <xsl:value-of select="."/> old.
</xsl:if>
</xsl:template>
<xsl:template match="HairColor">
<xsl:if test="$pProperty = 'HairColor'">
This person has <xsl:value-of select="."/> hair.
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), again the correct result is produced:
This person has blond hair.
Finally, if you really want to simulate higher-order functions (HOF) in XSLT 1.0 or XSLT 2.0, see this answer: https://stackoverflow.com/a/8363249/36305 , or learn about FXSL.
Much simpler: prepare two apply-templates rules (for Age and Color elements) and conditionally send proper node to transform - //Person/Age or //Thing/Color
You got it backwards. You ought to create templates, matching the nodes you want to use.
<xsl:stylesheet>
<xsl:template match="Person|Thing">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Person">
<xsl:value-of select="Age" />
</xsl:template>
<xsl:template match="Thing">
<xsl:value-of select="Color" />
</xsl:template>
</xsl:stylesheet>
I know I'm missing something simple here, yet I can't figure it out. I have other, more complex, XML and XSLT that are working but for some reason I can't get this specific one going. I believe it's the structure of the XML file that's being generated during serialization.
What I'm looking to do is get the value of an XML element and display it in HTML. I've taken everything else away except the specific areas related to this issue.
In the "html" variable in the code, the value for location is always blank.
XML
<WidgetBuilder>
<DefaultLocation>1234</DefaultLocation>
</WidgetBuilder>
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
LOCATION: '<xsl:value-of select="DefaultLocation" />'
</xsl:template>
</xsl:stylesheet>
Code
string xml = File.ReadAllText(#"..\..\InitXml1.xml");
string xslt = File.ReadAllText(#"..\..\InitXslt1.xslt");
XPathDocument doc = new XPathDocument(new StringReader(xml));
XslCompiledTransform xslTransform = new XslCompiledTransform();
xslTransform.Load(XmlReader.Create(new StringReader(xslt)));
StringWriter sw = new StringWriter();
xslTransform.Transform(doc, null, sw);
string html = sw.ToString();
Console.WriteLine(html);
Your XSL template matches the document root node, not the document element (they're not the same thing). Try:
<xsl:value-of select="WidgetBuilder/DefaultLocation" />
EDIT: Also, since you're using a default namespace, you'll have to make it visible from your stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:dc="schemas.datacontract.org/2004/07/YourFullClassName"
version="1.0" exclude-result-prefixes="msxsl">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
LOCATION: '<xsl:value-of select="dc:WidgetBuilder/dc:DefaultLocation" />'
</xsl:template>
</xsl:stylesheet>
See here for a detailed explanation and other use cases.
i use the following XSLT by using XMLSpy:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-16" indent="yes"/>
<xsl:template match="*">
<xsl:element name="{lower-case(local-name())}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="* | text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{lower-case(local-name())}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
If i try to use it in my source (XslCompiledTransform) i get an exception telling me that the function 'lower-case()' is not part of the XSLT synthax.
So i changed the transformation a little bit:
fn:lower-case
Now my exception is that the script or external object prefixed by 'http://www.w3.org/2005/xpath-functions' can not be found.
Whats the matter here? How can i fix it?
Regards
.NET does not implement XSLT 2.0/XPath 2.0.
In XPath 1.0 one can use the following expression, instead of lower-case():
translate(yourString,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')
Here's my problem.I have 2 xmlfiles with identical structure, with the second xml containing only few node compared to first.
File1
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
File2
<root>
<beta>XX</beta>
<delta>XX</delta>
</root>
This's what the result should look like
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
Basically if the node contents of any node in File1 is blank then it should read the values from File2(if it exists, that is).
I did try my luck with Microsoft XmlDiff API but it didn't work out for me(the patch process didn't apply changes to the source doc). Also I'm a bit worried about the DOM approach that it uses, because of the size of the xml that I'll be dealing with.
Can you please suggest a good way of doing this.
I'm using C# 2
Here is a little bit simpler and more efficient solution that that proposed by Alastair (see my comment to his solution).
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vFile2"
select="document('File2.xml')"/>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text())]">
<xsl:copy>
<xsl:copy-of
select="$vFile2/*/*[name() = name(current())]/text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
produces the wanted result:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
In XSLT you can use the document() function to retrieve nodes from File2 if you encounter an empty node in File1. Something like:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="root/*[.='']">
<xsl:variable name="file2node">
<xsl:copy-of select="document('File2.xml')/root/*[name()=name(current())]"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$file2node != ''">
<xsl:copy-of select="$file2node"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This merge seems very specific.
If that is the case, just write some code to load both xml files and apply the changes as you described.