I have to read some tags and attributes from an XML that has a defined structure but since those files can be generated from different sources, they can have different namespaces and prefixes.
This is the first XML sample
<Order xmlns="urn:oasis:names:specification:ubl:schema:xsd:Order-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:CustomizationID>urn:www.cenbii.eu:transaction:biitrns001:ver2.0:extended:urn:www.peppol.eu:bis:peppol3a:ver2.0:extended:urn:www.ubl-italia.org:spec:ordine:ver2.1</cbc:CustomizationID>
<cbc:ID>ORD-001</cbc:ID>
<cbc:IssueDate>2016-10-01</cbc:IssueDate>
<cbc:OrderTypeCode listID="UNCL1001">221</cbc:OrderTypeCode>
<cac:ValidityPeriod>
<cbc:EndDate>2024-10-19</cbc:EndDate>
</cac:ValidityPeriod>
<cac:BuyerCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="IT:IPA">ITAK12MH</cbc:EndpointID>
<cac:PartyIdentification>
<cbc:ID schemeID="IT:VAT">01567570254</cbc:ID>
</cac:PartyIdentification>
<cac:PartyName>
<cbc:Name>A Custom Name</cbc:Name>
</cac:PartyName>
</cac:Party>
</cac:BuyerCustomerParty>
</Order>
This is the second XML sample with different namespaces and prefixes, but same structure (tags, attributes).
<ns10:Order xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:ns2="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:ns3="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:ns4="http://www.w3.org/2000/09/xmldsig#" xmlns:ns5="http://uri.etsi.org/01903/v1.3.2#" xmlns:ns6="urn:oasis:names:specification:ubl:schema:xsd:SignatureBasicComponents-2" xmlns:ns7="urn:oasis:names:specification:ubl:schema:xsd:SignatureAggregateComponents-2" xmlns:ns8="http://uri.etsi.org/01903/v1.4.1#" xmlns:ns9="urn:oasis:names:specification:ubl:schema:xsd:CommonSignatureComponents-2" xmlns:ns10="urn:oasis:names:specification:ubl:schema:xsd:Order-2">
<UBLVersionID>2.1</UBLVersionID>
<CustomizationID>urn:www.cenbii.eu:transaction:biitrns001:ver2.0:extended:urn:www.peppol.eu:bis:peppol3a:ver2.0:extended:urn:www.ubl-italia.org:spec:ordine:ver2.1</CustomizationID>
<ID>ORD-001</ID>
<IssueDate>2016-10-01</IssueDate>
<OrderTypeCode listID="UNCL1001">221</OrderTypeCode>
<ns3:ValidityPeriod>
<EndDate>2024-10-19</EndDate>
</ns3:ValidityPeriod>
<ns3:BuyerCustomerParty>
<ns3:Party>
<EndpointID schemeID="IT:IPA">ITAK12MH</EndpointID>
<ns3:PartyIdentification>
<ID schemeID="IT:VAT">01567570254</ID>
</ns3:PartyIdentification>
<ns3:PartyName>
<Name>A Custom Name</Name>
</ns3:PartyName>
</ns3:Party>
</ns3:BuyerCustomerParty>
</ns10:Order>
Those files must be considered the same and so both valid.
A third example can be a file similar to the second where the namespaces are the same but their prefixes are different. Obviously the important thing is that the prefix used to match the namespace belongs to that particular tag.
I have no way of knowing in advance what will be the prefixes associated with namespaces.
<aaa:Order xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:aaa="urn:oasis:names:specification:ubl:schema:xsd:Order-2" xmlns:bbb="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<UBLVersionID>2.1</UBLVersionID>
<CustomizationID>urn:www.cenbii.eu:transaction:biitrns001:ver2.0:extended:urn:www.peppol.eu:bis:peppol3a:ver2.0:extended:urn:www.ubl-italia.org:spec:ordine:ver2.1</CustomizationID>
<ID>ORD-001</ID>
<IssueDate>2016-10-01</IssueDate>
<OrderTypeCode listID="UNCL1001">221</OrderTypeCode>
<bbb:ValidityPeriod>
<EndDate>2024-10-19</EndDate>
</bbb:ValidityPeriod>
<bbb:BuyerCustomerParty>
<bbb:Party>
<EndpointID schemeID="IT:IPA">ITAK12MH</EndpointID>
<bbb:PartyIdentification>
<ID schemeID="IT:VAT">01567570254</ID>
</bbb:PartyIdentification>
<bbb:PartyName>
<Name>A Custom Name</Name>
</bbb:PartyName>
</bbb:Party>
</bbb:BuyerCustomerParty>
</aaa:Order>
This last file must be considered valid as the others.
As you can see, the association between the tags and their namespaces are always the same. The only things that are changed are the prefixes.
My actual code uses XDocument and XElement classes to read the XML but it can be the way because I need to know the exact prefix for each tag and since they can vary, it works only with the first XML file sample.
XDocument doc;
XmlNamespaceManager manager;
using (XmlReader reader = XmlReader.Create(stream))
{
doc = XDocument.Load(reader);
// Retrieving namespaces of XML file
XPathNavigator navigator = doc.CreateNavigator();
navigator.MoveToFollowing(XPathNodeType.Element);
IDictionary<string, string> namespaces = navigator.GetNamespacesInScope(XmlNamespaceScope.All);
// Add namespaces to an XmlNamespaceManager to read nodes
manager = new XmlNamespaceManager(reader.NameTable);
foreach (KeyValuePair<string, string> ns in namespaces)
{
manager.AddNamespace(ns.Key, ns.Value);
}
}
XElement currentNode;
currentNode = doc.Root.XPathSelectElement("cbc:ID", manager);
if (currentNode != null)
item.DespatchAdviceId = currentNode.Value;
currentNode = doc.Root.XPathSelectElement("cbc:IssueDate", manager);
if (currentNode != null)
{
DateTime dataEmissione;
if (DateTime.TryParseExact(currentNode.Value, validDateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out dataEmissione))
item.OrderIssueDate = dataEmissione;
}
currentNode = doc.Root.XPathSelectElement("cac:BuyerCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID", manager);
if (currentNode != null)
{
item.BuyerPartyId = currentNode.Value;
if (currentNode.Attribute("schemeID") != null)
item.BuyerPartySchemeId = currentNode.Attribute("schemeID").Value;
}
// ... and so on...
How can I read the XMLs without having to specify the namespace prefixes?
Should I use another .NET library or maybe a 3rd party one?
Using LocalName, you can linq it without adding the namespace.
//this is for <cbc:ID>ORD-001</cbc:ID>
var element = doc.Root.Elements().Where(x => x.Name.LocalName == "ID").FirstOrDefault();
If you want to go in the nested elements
var element = doc.Root.Elements().Where(x => x.Name.LocalName == "ValidityPeriod").
Elements().Where(x=> x.Name.LocalName == "EndDate").FirstOrDefault();
I need to know the exact prefix for each tag.
No, you don't. The prefixes are entirely irrelevant to qualified name of an element or attribute. If you want to go the XPath route, then don't read the namespaces and prefixes from the document to create your namespace manager, specify them yourself so you know what they are. Then use those in your query. For example, this will work with any of your XML documents:
var manager = new XmlNamespaceManager(new NameTable());
manager.AddNamespace("cbc",
"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2");
var id = doc.Root.XPathSelectElement("cbc:ID", manager);
What I would encourage, though, is that you ditch XPath. LINQ to XML is so much nicer. And another quick hint, there is an overload of XDocument.Load that accepts a stream. There's no need to create the XmlReader. So:
XNamespace order = "urn:oasis:names:specification:ubl:schema:xsd:Order-2";
XNamespace cbc = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2";
XNamespace cac = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2";
var doc = XDocument.Load(stream);
var id = (string) doc.Elements(order + "Order")
.Elements(cbc + "ID")
.Single();
var issueDate = (DateTime) doc.Elements(order + "Order")
.Elements(cbc + "IssueDate")
.Single();
var buyerPartySchemeId = (string) doc.Descendants(cac + "BuyerCustomerParty")
.Descendants(cbc + "ID")
.Attributes("schemeID")
.Single();
Related
I have the below xml string in one string variable.
string xmlString = "<a:ORegions>
<a:ID>1</a:ID>
<a:regionCode>US</a:regionCode>
</a:ORegions>
<a:ORegions>
<a:ID>2</a:ID>
<a:regionCode>CANADA</a:regionCode>
</a:ORegions>
<a:ORegions>
<a:ID>3</a:ID>
<a:regionCode>ASIA</a:regionCode>
</a:ORegions>
Now i want to access regionCode values, that is US, CANADA, ASIA
How i can do that using c#. I am new to xml parsing.
You can deserialize that string (assuming you fix the various syntax errors) via the System.Xml namespace classes, particularly XmlDocument, such as with its Load method. To access the namespaces (a in a:Oregions and such is a namespace), you'll want an XmlNamespaceManager. You'd then register the namespaces (they must be defined somewhere) with the manager and use that when querying the XmlDocument.
Use LinqToXml
var doc = XDocument.Parse(xmlString);
You can then access elements, values and attributes within:
XNamespace xmlNamespace = "a";
//e.g. Retrieve's a list of regioncodes...
var ids = doc.Elements(xmlNamespace + "ORegions")
.Select(r => r.Element("regionCode").Value);
XmlDocument document = new XmlDocument();
document.Load(filePath);
foreach (XmlNode node in document.GetElementsByTagName("a:regionCode"))
Console.WriteLine(node.InnerText);
I'm trying to write an XML file that will be picked up and parsed by another service. In order for this to happen the XML must be formatted in a very specific way, namely:
<?xml version="1.0"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<Feedback:MfgUnitID></Feedback:MfgUnitID>
<Feedback:MachineId></Feedback:MachineId>
<Feedback:OperationCode></Feedback:OperationCode>
<Feedback:ItemSeqNum></Feedback:ItemSeqNum>
<Feedback:OperDispositionCd></Feedback:OperDispositionCd>
<Feedback:ItemId></Feedback:ItemId>
<Feedback:ParentItemId></Feedback:ParentItemId>
<Feedback:ItemEndSize>1821</Feedback:ItemEndSize>
<Feedback:ItemDispositionCd></Feedback:ItemDispositionCd>
<Feedback:OperStartDate></Feedback:OperStartDate>
<Feedback:OperEndDate></Feedback:OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
with data of course between the innermost elements. Here's the issue though, no matter what I do, I can't get any of the C# classes to keep the semicolons on the innermost nodes. As far as I know these need to stay, so is there a way in C# to force it to format the nodes this way? I've tried all of the create methods that I could find in the XMLDocument class. I can get the outer nodes formatted fine, but the inner ones just keep creating problems.
Edit, sorry here's the code that makes the inner nodes.
private void AppendFile(string filename, string[] headers, Dictionary<string, string> values)
{
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlNode node = doc.GetElementsByTagName(headers[headers.Length - 2]).Item(0);
string[] hPieces = headers[headers.Length - 1].Split(':');
XmlElement appendee = doc.CreateElement(hPieces[0].Trim(), hPieces[1].Trim(), hPieces[0].Trim());
node.AppendChild(appendee);
foreach (KeyValuePair<string, string> pair in values)
{
string[] ePieces = pair.Key.Split(':');
//XmlElement element = doc.CreateElement(ePieces[0].Trim(), string.Empty, ePieces[1].Trim());
//XmlText text = doc.CreateTextNode(pair.Value);
XmlNode innerNode = doc.CreateNode(XmlNodeType.Element, ePieces[1].Trim(), ePieces[0].Trim());
node.InnerText = pair.Value;
// element.AppendChild(text);
appendee.AppendChild(innerNode);
}
doc.Save(filename);
}
The data for the inner nodes comes in as key value pairs in the dictionary. Where the keys contain the intended name.
Edit2: This is what the file output looks like
<?xml version="1.0" encoding="utf-8"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<MfgUnitID></MfgUnitID>
<MachineId></MachineId>
<OperationCode</OperationCode>
<ItemSeqNum></ItemSeqNum>
<OperDispositionCd></OperDispositionCd>
<ItemId></ItemId>
<ParentItemId></ParentItemId>
<ItemEndSize></ItemEndSize>
<ItemDispositionCd></ItemDispositionCd>
<OperStartDate></OperStartDate>
<OperEndDate></OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
You can accompish this easily with XLinq:
using System.Xml.Linq;
XNamespace ns1 = "Feedbacks";
XNamespace ns2 = "Feedback";
var doc = new XElement("Feedbacks",
new XAttribute(XNamespace.Xmlns+"Feedbacks", ns1));
doc.Add(new XElement(ns1 + "Elements",
new XElement(ns2 + "Feedback",
new XAttribute(XNamespace.Xmlns+"Feedback", ns2),
new XElement(ns2 + "Unit"))));
Gives
<Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:Feedback xmlns:Feedback="Feedback">
<Feedback:Unit />
</Feedback:Feedback>
</Feedbacks:Elements>
</Feedbacks>
Although I believe that your own output should be valid XML, relying on the parent namespcae.
Having problems getting NodeList.SelectSingleNode() to work properly.
My XML looks like this:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<inm:Results xmlns:inm="http://www.namespace.com/1.0">
<inm:Recordset setCount="18254">
<inm:Record setEntry="0">
<!-- snip -->
<inm:Image>fileName.jpg</inm:Image>
</inm:Record>
</inm:Recordset>
</inm:Results>
The data is a long series of <inm:Record> entries.
I open the doc and get create a NodeList object based on "inm:Record". This works great.
XmlDocument xdoc = new XmlDocument();
xdoc.Load(openFileDialog1.FileName);
XmlNodeList xRecord = xdoc.GetElementsByTagName("inm:Record");
I start looping through the NodeList using a for loop. Before I process a given entry, I want to check and see if the <inm:Image> is set. I thought it would be super easy just to do
string strImage = xRecord[i].SelectSingleNode("inm:Image").InnerText;
My thinking being, "For the XRecord that I'm on, go find the <inm:Image> value ...But this doesn't work as I get the exception saying that I need a XmlNameSpaceManager. So, I tried to set that up but could never get the syntax right.
Can someone show me how to use the correct XmlNameSpaceManager syntax in this case.
I've worked around the issue for now by looping through all of the childNodes for a given xRecord, and checking the tag once I loop around to it. I would like to check that value first to see if I need to loop over that <inm:Record> entry at all.
No need to loop through all the Record elements, just use XPath to specify the subset that you want:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(openFileDialog1.FileName);
XmlNamespaceManager manager = new XmlNamespaceManager(xdoc.NameTable);
manager.AddNamespace("inm", "http://www.inmagic.com/webpublisher/query");
XmlNodeList nodes = xdoc.SelectNodes("/inm:Results/inm:Recordset/inm:Record[inm:Image != '']", manager);
Using the LINQ to XML libraries, here's an example for retrieving that said node's value:
XDocument doc = XDocument.Load(openFileDialog1.FileName);
List<XElement> docElements = doc.Elements().ToList();
XElement results = docElements.Elements().Where(
ele => ele.Name.LocalName == "Results").First();
XElement firstRecord = results.Elements().Where(
ele => ele.Name.LocalName == "Record").First();
XElement recordImage = firstRecord .Elements().Where(
ele => ele.Name.LocalName == "Image").First();
string imageName = recordImage.Value;
Also, by the way, using Hungarian notation for a type-checked language is overkill. You don't need to prepend string variables with str when it will always be a string.
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
string strImage = xRecord[i].SelectSingleNode("inm:Image",nsMgr).InnerText;
Should do it.
Using this Xml library, you can get all the records that have an Image child element with this:
XElement root = XElement.Load(openFileDialog1.FileName);
XElement[] records = root.XPath("//Record[Image]").ToArray();
If you want to be sure that the Image child contains a value, it can be expressed like this:
XElement[] records = root.XPath("//Record[Image != '']").ToArray();
In my previous question here, I didn’t understand how to solve my problem.
Linq to XML, how to acess an element in C#?
Here is my XML I need to parse:
<root>
<photo>/filesphoto.jpg</photo>
<photo:mtime>12</photo:mtime>
<text>some text</text>
</root>
To access the element I use this code:
var doc = XDocument.Parse(xml.Text);
doc.Descendants("text").FirstOrDefault().Value;
How can I access ?
I have try http://aspnetgotyou.blogspot.com/2010/06/xdocument-or-xelement-with-xmlnamespace.html,
But it is ignored <photo:mtime> and I need to access it.
Please write some code.
Contrary to #BrokenGlass' comments, your XML is not invalid. In fact the technique in the link you provided in your question (for loading namespaces) works fine. Maybe you just didn't change the example for your own needs. Here's a more compact generalization for parsing xml fragments with namespaces into an XElement:
public static XElement parseWithNamespaces(String xml, String[] namespaces) {
XmlNamespaceManager nameSpaceManager = new XmlNamespaceManager(new NameTable());
foreach (String ns in namespaces) { nameSpaceManager.AddNamespace(ns, ns); }
return XElement.Load(new XmlTextReader(xml, XmlNodeType.Element,
new XmlParserContext(null, nameSpaceManager, null, XmlSpace.None)));
}
Using your exact input:
string xml =
#"<root>
<photo>/filesphoto.jpg</photo>
<photo:mtime>12</photo:mtime>
<text>some text</text>
</root>";
XElement x = parseWithNamespaces(xml, new string[] { "photo" });
foreach (XElement e in x.Elements()) {
Console.WriteLine("{0} = {1}", e.Name, e.Value);
}
Console.WriteLine(x.Element("{photo}mtime").Value);
Prints:
photo = /filesphoto.jpg
{photo}mtime = 12
text = some text
12
Try this: (Your xml is changed a little, see )
string xml = "<root><photo>/filesphoto.jpg</photo><photoMtime>12</photoMtime><text>some text</text></root>";
var doc = XDocument.Parse(xml);
string value = doc.Descendants("text").FirstOrDefault().Value;
MessageBox.Show(value);
I have the following Xml in my Resources.xmltest:
<?xml version="1.0" encoding="utf-8" ?>
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://DFISofft.com/SmartPayments/">
<Result>0</Result>
<Message>Pending</Message>
<PNRef>222131</PNRef>
<ExtData>InvNum=123</ExtData>
</Response>
I've tried several ways to get the values, Result,Message,PNRef,ExtData, out of it and I've had no luck. I always get a null value for the NodePath so it never goes into the loop:
var XmlDoc = new XmlDocument();
XmlDoc.LoadXml(Resources.xmltest);
XmlElement NodePath = (XmlElement) XmlDoc.SelectSingleNode("/Response");
while (NodePath != null)
{
foreach (XmlNode Xml_Node in NodePath)
{
Console.WriteLine(Xml_Node.Name + " " + Xml_Node.InnerText);
}
}
I've tried this:
XmlNode node3 = XmlDoc.SelectSingleNode("PNRef");
Console.WriteLine(node3.Value);
Console.WriteLine(XmlDoc.InnerXml);
var tst = XmlDoc.GetElementsByTagName("PNRef");
Console.WriteLine(tst);
And this:
NodePath = (XmlElement) XmlDoc.SelectSingleNode("/Response");
if (NodePath != null)
{
foreach (XmlNode node in NodePath)
{
Console.WriteLine("NodeName: " + Xml_NodeX.Name);
Console.WriteLine("NodeValue: " + node.InnerText);
}
}
Apparently, I'm not getting the xml read/write. I've done it with DataSets but they do all the work for you.
Can someone point me in the right direction? I've been at this for longer than I should have been already.
Thank you!
Your XML has a XML namespace and you're not paying any attention to it:
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://DFISofft.com/SmartPayments/">
******************************************
When selecting from this XML, you need to use that XML namespace!
You're not taking into account the XML namespace (xmlns="http://nts-de-osm1-pxc/webservices/") on the document!
Try this:
var XmlDoc = new XmlDocument();
// setup the XML namespace manager
XmlNamespaceManager mgr = new XmlNamespaceManager(XmlDoc.NameTable);
// add the relevant namespaces to the XML namespace manager
mgr.AddNamespace("ns", "http://DFISofft.com/SmartPayments/");
XmlDoc.LoadXml(Resources.xmltest);
// **USE** the XML anemspace in your XPath !!
XmlElement NodePath = (XmlElement) XmlDoc.SelectSingleNode("/ns:Response");
while (NodePath != null)
{
foreach (XmlNode Xml_Node in NodePath)
{
Console.WriteLine(Xml_Node.Name + " " + Xml_Node.InnerText);
}
}
The problem is your XPath query, which doesn't specify a namespace - despite the fact that your document only has a Response element in the "http://DFISofft.com/SmartPayments/" namespace. Ditto the PNRef elements.
Personally I'd use LINQ to XML if I actually wanted to use namespaces if at all possible. For example:
XNamespace ns = "http://DFISofft.com/SmartPayments/";
XDocument doc = XDocument.Load(filename);
foreach (var element in doc.Descendants(ns + "PNRef"))
{
// Use element here
}
As you can see, LINQ to XML makes it really easy to use namespaces - but of course it requires .NET 3.5 or higher. If I had to use .NET 2.0, I'd either use XmlNamespaceManager or iterate manually and check local names instead of using XPath.
That is because the node isn't called response; you need to take the namespace into account:
var XmlDoc = new XmlDocument();
var nsmgr = new XmlNamespaceManager(XmlDoc.NameTable);
nsmgr.AddNamespace("x", "http://DFISofft.com/SmartPayments/");
XmlDoc.LoadXml(yourXml);
XmlElement NodePath = (XmlElement)XmlDoc.SelectSingleNode("/x:Response", nsmgr);
For future reference...
Bubba Soft
This site has a great free tool for building XPath Expressions (XPath Builder).
It's because you are using the overload of SelectSingleNode that assumes an empty namespace. Since you have a default namespace you will need to use the version that takes an XmlNamespaceManager. See this article for more info.
from the docs:
If the XPath expression does not include a prefix, it is assumed that the namespace URI is the empty namespace. If your XML includes a default namespace, you must still add a prefix and namespace URI to the XmlNamespaceManager; otherwise, you will not get a node selected. For more information, see Select Nodes Using XPath Navigation.