Getting the value from an XML element via XSLT using value-of - c#

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.

Related

unable to copy node and it's sub-nodes using <xsl:copy-of select="node()"/>

I'm trying to select a node that contains all of the sub-nodes AS IS using a xsl stylesheet. I have a section in my xml doc that contain a dynamic amount of sub-nodes, each with 2 pieces of data, and I just want to grab as they are in put them in my new xml doc (I am transforming other pieces of data in the xml doc).
here is my xml doc i'm trying to transform:
<?xml version="1.0" encoding="utf-8"?>
<Active_Booking_Release_Report>
<Inmate_Data>
<bunch of fields i've already gotten to work />
....
</Inmate_Data>
<Charges_Data>
<charge>
<OffenseStatute>12345</OffenseStatute>
<OffenseOffenseDescription>For Court Only</OffenseOffenseDescription>
</charge>
<charge>
<OffenseStatute>67890</OffenseStatute>
<OffenseOffenseDescription>For Court Only</OffenseOffenseDescription>
</charge>
</Charges_Data>
</Active_Booking_Release_Report>
and my stylesheet
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Match the root node -->
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="/">
<xsl:copy>
<booking>
<inmateData>
<xsl:apply-templates select="*|#*|text()|comment()"/>
</inmateData>
</booking>
</xsl:copy>
</xsl:template>
a whole bunch of statements for <Inmate_Data>
and the Charges section
<xsl:template match="//Active_Booking_Release_Report/Charges_Data">
<xsl:copy-of select="node()"/>
</xsl:template>
</xsl:stylesheet>
and my desired output:
<booking>
<inmateData>
<transformed fields/>
<charge>
<OffenseStatute>12345</OffenseStatute>
<OffenseOffenseDescription>For Court Only</OffenseOffenseDescription>
</charge>
<charge>
<OffenseStatute>67890</OffenseStatute>
<OffenseOffenseDescription>For Court Only</OffenseOffenseDescription>
</charge>
</Inmate Data>
</booking>
I'm loading and transforming the xml in a c# console app, and when I inspect the object containing the transformed data, everything is listed there except for the charges section.
Well, firstly, you've got two template rules that match "/" and only one of them is going to match, and in XSLT 1.0 it's pretty implementation-dependent what happens when two templates match.
Secondly, your desired output isn't well-formed; I guess it was intended as a sketch, but it's not really clear what you wanted.
Thirdly, you haven't shown us the actual output.
Finally, you've left a whole load of your stylesheet code out: "a whole bunch of statements".
I'd suggest you fix the obvious error of the two duplicate template rules, then show us a complete listing of a runnable stylesheet, source document, and desired output, so we can actually see where you've gone wrong.

How to transform XML using XSLT based on external parameter?

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

Convert from XML to CSV

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.

XSLT choose template

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>

XSLT lower-case using .NET

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

Categories

Resources