XML default namespace and XSLT transfornation trouble in C# - c#

In P.S. of this posting you can find a C# code snippet, which uses XSLT transformation + RegEx matching to get currency rate value from an XML document. Please read my question inline quoted by
//+
...
//-
comment lines group.
Thank you.
P.S. Code:
string currencyCode = "RUB";
var xml = new StringReader(
#"<gesmes:Envelope
xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01'
xmlns{{EmptyNameSpace}} ='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'>
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time='2012-06-27'>
<Cube currency='USD' rate='1.2478' />
<Cube currency='RUB' rate='41.1252' />
<Cube currency='ZAR' rate='10.4601' />
</Cube>
</Cube>
</gesmes:Envelope>"
.Replace("{{EmptyNameSpace}}", ":ns1")
//+
// I'd like to get this code working the same way as it does now
// producing
//
// Result: EUR/RUB => 41.1252
//
// output but when the above code line will be commented
// and the below code line uncommented
//-
//.Replace("{{EmptyNameSpace}}", "")
);
var xslt = new XmlTextReader(new StringReader(
#"<xsl:stylesheet version='2.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01'
xmlns:ns1 ='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'
>
<xsl:variable name='x1' select='gesmes:Envelope/Cube/Cube/Cube[#currency=""{0}""]' />
<xsl:template match='gesmes:Envelope'>
[<xsl:apply-templates select='$x1' />]
</xsl:template>
<xsl:template match='Cube'>
<xsl:value-of select='#rate' />
</xsl:template>
</xsl:stylesheet>".Replace("{0}", currencyCode)
));
var xDoc = new XPathDocument(xml);
var xTr = new System.Xml.Xsl.XslCompiledTransform();
xTr.Load(xslt) ;
StringBuilder sb = new StringBuilder();
StringWriter writer = new StringWriter(sb);
xTr.Transform(xDoc, null, writer);
string pattern = #"\[(?'name'[0-9]*\.[0-9]*)\]";
System.Console.WriteLine(
"Result: EUR/{0} => {1}",
currencyCode,
Regex.Match(sb.ToString(), pattern)
.Groups["name"].Value);
// Result: EUR/RUB => 41.1252

As far as I can tell from looking at that code, your problems is handling the default namespace in XPath:
As long as {{EmptyNameSpace}} is :ns1, everything's fine - in your Xml document, the namespaces http://www.gesmes.org/xml/2002-08-01 and http://www.ecb.int/vocabulary/2002-08-01/eurofxref with the prefixes gesmes and ns1 are defined; your Xml document uses identifiers such as <gesmes:Envelope> from gesmes and others, namely <Cube>, that are not associated with any namespace. In the XSLT code, you are using XPath expressions, such as (here shortened) gesmes:Envelope/Cube/Cube/Cube, which is fine, as this expression refers to an <Envelope> element from the namespace declared with the gesmes prefix, as well as to <Cube> elements without a namespace.
Once you set {{EmptyNameSpace}} to an empty string, this changes: Now, http://www.ecb.int/vocabulary/2002-08-01/eurofxref is the default namespace of your document. Hence, the <Cube> elements in your document are considered to belong to that namespace. Your XPath expression, however, still considers the <Cube> elements that appear in the XPath as having no namespace - they do not have any namespace prefix and XPath doesn't know about default namespaces.
As a solution, you should add namespace prefixes to your XPath expression - if the <Cube> elements belong to the http://www.ecb.int/vocabulary/2002-08-01/eurofxref namespace, your XPath should read like this:
gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube
(Of course, the other XPath expressions in your XSLT have to be adapted accordingly.)
Please let me know if you need any further explanations on this - or whether I've gotten on the wrong track with my assumptions.
Update: Here's what the XSLT should look like in order to work if the Xml document has the default namespace:
<xsl:stylesheet version="2.0" xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01' xmlns:ns1='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'>
<xsl:variable name='x1' select='gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube[#currency="RUB"]'/>
<xsl:template match='gesmes:Envelope'>
[<xsl:apply-templates select='$x1'/>]
</xsl:template>
<xsl:template match='ns1:Cube'>
<xsl:value-of select='#rate'/>
</xsl:template>
</xsl:stylesheet>

Related

How to get a particular node from xml to xslt

The XML has repetitive node OptionCd
<AdjusterParty AdjusterPartyIdRef="20130000074-001">
<Option>
<OptionCd>SAL</OptionCd>
<OptionValue>N</OptionValue>
</Option>
<Option>
<OptionCd>SUB</OptionCd>
<OptionValue>N</OptionValue>
</Option>
</AdjusterParty>
The xslt is:
<w:p >
<w:r>
<w:t>
<xsl:value-of select ="OptionCd"/>
</w:r>
</w:p>
The requirement is based on the parameter value passed in the function I would either populate the OptionCD SAL or SUB. So how to set the value in OptionCD.
The C# code is
function node(int count)
{
}
is there something like based on the count, I can tell the xslt to fetch that node, say count is
1, then could I let the xslt know it need to print the first OptionCd and so on.
Thanks
Updated the code which I use to read the xml and xslt from a template document and then generates the word document with the values.
string rootPath = #"C:\ExampleWordProcessingML\Docs";
string xmlDataFile = rootPath + #"\Original.xml";
string xsltFile = rootPath + #"\Transactions.xslt";
string templateDocument = rootPath + #"\Transactions.docx";
string outputDocument = rootPath + #"\MyTransactions.docx";
//Create a writer for the output of the Xsl Transformation.
StringWriter stringWriter = new StringWriter();
XmlWriter xmlWriter = XmlWriter.Create(stringWriter);
//Create the Xsl Transformation object.
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xsltFile);
//Transform the xml data into Open XML 2.0 Wordprocessing format.
transform.Transform(xmlDataFile, xmlWriter);
//Create an Xml Document of the new content.
XmlDocument newWordContent = new XmlDocument();
newWordContent.LoadXml(stringWriter.ToString());
//Copy the Word 2007 source document to the output file.
System.IO.File.Copy(templateDocument, outputDocument, true);
//Use the Open XML SDK version 2.0 to open the output
// document in edit mode.
using (WordprocessingDocument output =
WordprocessingDocument.Open(outputDocument, true))
{
//Using the body element within the new content XmlDocument
// create a new Open Xml Body object.
Body updatedBodyContent =
new Body(newWordContent.DocumentElement.InnerXml);
//Replace the existing Document Body with the new content.
output.MainDocumentPart.Document.Body = updatedBodyContent;
//Save the updated output document.
output.MainDocumentPart.Document.Save();
}
You need to define a parameter in your XSLT, inside the xsl:stylesheet element:
<xsl:param name="count" select="1" />
And then you can use something like this in your XSLT:
<xsl:value-of select="/AdjusterParty/Option[$count]/OptionCd"/>
although this might be a bit safer, in case count was passed in as a string value:
<xsl:value-of select="/AdjusterParty/Option[number($count)]/OptionCd"/>
In order to pass the count value into your XSLT, you can do this:
// Create the XsltArgumentList.
XsltArgumentList argList = new XsltArgumentList();
argList.AddParam("count", "", count);
//Transform the xml data into Open XML 2.0 Wordprocessing format.
transform.Transform(xmlDataFile, argList, xmlWriter);
is there something like based on the count, I can tell the xslt to
fetch that node, say count is 1, then could I let the xslt know it
need to print the first OptionCd and so on.
In XSLT, you can use:
<xsl:value-of select="/AdjusterParty/Option[2]/OptionCd"/>
to return the value "SUB" from your XML example. If you pass a parameter named "count" to the stylesheet at runtime, then
<xsl:value-of select="/AdjusterParty/Option[$count]/OptionCd"/>
will select the n-th Option node to get the OptionCd value from (where n = $count parameter).
--
Note: what you posted as XSLT is not.

How to pass document type parameter to xslt using saxon?

For sending atomic data types will use like
transformer.SetParameter(new QName("", "", customXml), new XdmAtomicValue("true"));
how to pass a XML/Node as a param to XSLT from C# ?
Can you please help me
followed your code it's working fine but i am getting only text inside the xml(what i am passing in parameter) but not Nodes
XSLT :
<xsl:param name="look-up" as="document-node()"/>
<xsl:template match="xpp:document">
<w:document xml:space="preserve">
<xsl:value-of select="$look-up"/>
</w:document>
</xsl:template>
XML
<?xml version="1.0" encoding="UTF-8"?>
<document version="1.0" xmlns="http://www.sdl.com/xpp">
//some tags
</document>
passing parameter (xml)
<Job>
</id>
</Job>
I think you should use the Processor object to construct an XdmNode, see the documentation which says:
The Processor provides a method NewDocumentBuilder which, as the name
implies, returns a DocumentBuilder. This may be used to construct a
document (specifically, an XdmNode) from a variety of sources. The
input can come from raw lexical XML by specifying a Stream or a Uri,
or it may come from a DOM document built using the Microsoft XML
parser by specifying an XmlNode, or it may be supplied
programmatically by nominating an XmlReader.
Then you can pass in the XdmNode to the SetParameter method http://saxonica.com/documentation/html/dotnetdoc/Saxon/Api/XsltTransformer.html#SetParameter%28Saxon.Api.QName,Saxon.Api.XdmValue%29 as XdmValue is a base class of XdmNode (XmlValue -> XdmItem -> XdmNode).
Here is an example that works for me with Saxon 9.5 HE on .NET:
Processor proc = new Processor();
DocumentBuilder db = proc.NewDocumentBuilder();
XsltTransformer trans;
using (XmlReader xr = XmlReader.Create("../../XSLTFile1.xslt"))
{
trans = proc.NewXsltCompiler().Compile(xr).Load();
}
XdmNode input, lookup;
using (XmlReader xr = XmlReader.Create("../../XMLFile1.xml"))
{
input = db.Build(xr);
}
using (XmlReader xr = XmlReader.Create("../../XMLFile2.xml"))
{
lookup = db.Build(xr);
}
trans.InitialContextNode = input;
trans.SetParameter(new QName("lookup-doc"), lookup);
using (XmlWriter xw = XmlWriter.Create(Console.Out))
{
trans.Run(new TextWriterDestination(xw));
}
The XSLT is
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:param name="lookup-doc"/>
<xsl:key name="map" match="map" use="key"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:value-of select="key('map', ., $lookup-doc)/value"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the XML documents are
<root>
<item>foo</item>
</root>
and
<root>
<map>
<key>foo</key>
<value>bar</value>
</map>
</root>
the resulting output is
<root>
<item>bar</item>
</root>

How to read in some XML then split out various nodes/elements in .NET?

I have some xml (in a file, but can be a string) which I need to parse, e.g.:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlText);
Given the following XML:
<foo>
<cat>...</cat>
<cat>...</cat>
<dog>...</dog>
<cat>...</cat>
<dog>...</dog>
</foo>
I'm not sure how I can extract all the cat and dog elements and put them into the following output :-
<foo>
<cat>...</cat>
<cat>...</cat>
....
</foo>
and the same with dogs.
What's the trick to extract those nodes and put them into separate XMLDocuments.
Use Linq to XML as it has a much nicer API.
var doc = XElement.Parse(
#"<foo>
<cat>...</cat>
<cat>...</cat>
<dog>...</dog>
<cat>...</cat>
<dog>...</dog>
</foo>");
doc.Descendants("dog").Remove();
doc now contains this:
<foo>
<cat>...</cat>
<cat>...</cat>
<cat>...</cat>
</foo>
Edit:
While Linq to XML itself provides a nice API to work with XML, the power of Linq and its projection capabilities enables you to shape your data as you see fit.
Consider this, for example. Here the descendant elements are grouped by name and projected into a new root element which is then wrapped into a XDocument. Note that this creates an enumerable of XDocument.
var docs=
from d in doc.Descendants()
group d by d.Name into g
select new XDocument(
new XElement("root", g)
);
docs now contains:
<root>
<cat>...</cat>
<cat>...</cat>
<cat>...</cat>
</root>
---
<root>
<dog>...</dog>
<dog>...</dog>
</root>
Oh, by the way. The Descendants method goes through all descendant elements, use Elements if you only want the immediate child elements.
Here are the Linq to XML docs on MSDN
The easiest way will be to use XSLT and apply it on you XMLDocument in such way you won't modify your source and have as much outputs as you need.
The code for applying transform is
XslCompiledTransform xslTransform = new XslCompiledTransform();
StringWriter writer = new StringWriter();
xslTransform.Load("cat.xslt");
xslTransform.Transform(doc.CreateNavigator(),null, writer);
return writer.ToString();
And the simple cat.xslt is
<?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="xml" indent="yes"/>
<xsl:template match="foo">
<xsl:copy>
<xsl:copy-of select = "cat" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Since you are using XmlDocument: Load it twice from the same file and remove the unwanted nodes. Here is a link that shows you how: Removing nodes from an XmlDocument.
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlText);
XmlNode root = doc.DocumentElement;
nodeList = root.SelectNodes("//cat");
foreach (XmlNode node on nodeList)
{
root.RemoveChild(node);
}

Xpath select query with also relative top level node in c#

I am using xpath to query my xml file in C#.
Here is my xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
And my C# code is
XPathNavigator nav;
XPathDocument docNav;
XPathNodeIterator NodeIter;
String strExpression;
docNav = new XPathDocument(#"C:\\DCU\\XQUE.xml");
nav = docNav.CreateNavigator();
// This expression uses standard XPath syntax.
strExpression = "/bookstore[./book/price>35.00]";
NodeIter = nav.Select(strExpression);
while (NodeIter.MoveNext())
{
Console.WriteLine("{0}", NodeIter.Current.OuterXml);
}
But I want to get output like this,
<bookstore>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
I think anything missing with my xpath query line, please lead me a way out..
It seems that you're misunderstanding the purpose of XPath selection and what it can do. XPath won't create a whole new XML document for you. The typical choice for taking an XML input and producing a different XML output is to use XSLT. This would do it in your case:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book[price <= 35]" />
</xsl:stylesheet>
If you really want to use just C# to do this, you could always do the following:
XPathNavigator nav;
XPathDocument docNav;
XPathNodeIterator NodeIter;
String strExpression;
docNav = new XPathDocument(#"C:\\DCU\\XQUE.xml");
nav = docNav.CreateNavigator();
// This expression uses standard XPath syntax.
strExpression = "/bookstore/book[price > 35.00]";
NodeIter = nav.Select(strExpression);
Console.WriteLine("<bookstore>");
while (NodeIter.MoveNext())
{
Console.WriteLine("{0}", NodeIter.Current.OuterXml);
}
Console.WriteLine("</bookstore>");
That expression selects <bookstore> elements, so the output will be the whole <bookstore> (with all its child book elements). If you want specific books you need to use a different XPath
strExpression = "/bookstore/book[price>35.00]";
which will print just the <book> elements that match, but without the surrounding <bookstore> tags.

How to query XElement with two namespaces

I'm trying to find the inner text value of an element using LINQ-to-XML (an XElement object). I make my service call and get an XML response back that I've successfully loaded into an XElement object. I want to extract the inner text of one of the elements - however, every time I try to do this, I get a null result.
I feel like I'm missing something super-simple, but I'm fairly new to LINQ-to-XML. Any help is appreciated.
I'm trying to get the inner text value of the StatusInfo/Status element. Here's my XML document that's returned:
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">
<title type="text">My Response</title>
<id>tag:foo.com,2012:/bar/06468dfc-32f7-4650-b765-608f2b852f22</id>
<author>
<name>My Web Services</name>
</author>
<link rel="self" type="application/atom+xml" href="http://myServer/service.svc/myPath" />
<generator uri="http://myServer" version="1">My Web Services</generator>
<entry>
<id>tag:foo.com,2012:/my-web-services</id>
<title type="text" />
<updated>2012-06-27T14:22:42Z</updated>
<category term="tag:foo.com,2008/my/schemas#system" scheme="tag:foo.com,2008/my/schemas#type" />
<content type="application/vnd.my.webservices+xml">
<StatusInfo xmlns="tag:foo.com,2008:/my/data">
<Status>Available</Status> <!-- I want the inner text -->
</StatusInfo>
</content>
</entry>
</feed>
Here's a snippet of code that I'm using to extract the value (which doesn't work):
XElement root = XElement.Load(responseReader);
XNamespace tag = "tag:foo.com,2008:/my/data";
var status = (from s in root.Elements(tag + "Status")
select s).FirstOrDefault();
My status variable is always null. I've tried several variations on this, but to no avail. The part that's confusing me is the namespace -- tag and 2008 are defined. I don't know if I'm handling this correctly or if there's a better way to deal with this.
Also, I don't have control over the XML schema or the structure of the XML. The service I'm using is out of my control.
Thanks for any help!
Try Descendants() instead of Elements():
XElement x = XElement.Load(responseReader);
XNamespace ns = "tag:foo.com,2008:/my/data";
var status = x.Descendants(ns + "Status").FirstOrDefault().Value;
There are 2 Namespaces in the feed:
the Atom namespace
the tag namespace
The outer xml needs to use the Atom namespace, while a portion of the inner xml needs to use the tag namespace. i.e.,
var doc = XDocument.Load(responseReader);
XNamespace nsAtom = "http://www.w3.org/2005/Atom";
XNamespace nsTag = "tag:foo.com,2008:/my/data";
// get all entry nodes / use the atom namespace
var entry = doc.Root.Elements(nsAtom + "entry");
// get all StatusInfo elements / use the atom namespace
var statusInfo = entry.Descendants(nsTag + "StatusInfo");
// get all Status / use the tag namespace
var status = statusInfo.Elements(nsTag + "Status");
// get value of all Status
var values = status.Select(x => x.Value.ToString()).ToList();

Categories

Resources