Hi i was fiddeling around with xml files and i notised something i have a bit problem of solving.
I have a Xml that starts with a root node and then has another child node that can change name for example:
<root>
<Child1>
</root>
So given that "Child1" can be changed to "Child2" or "Child3" i made this linq be able to extract the name from whatever comes my way.
first:
XElement root = XElement.Parse(xml);
var childType = root.Descendants().First(x => x.Name == "Child1" || x.Name == "Child2"|| x.Name == "Child3").Name;
So when i have my xml without a namespace, as shown above, it workes fine, i manage to extract the name from the node tag.
But when i have a namespace into the root tag it throws an error:
<root xmlns="namespace">
<Child1>
</root>
That xml going through the same linq, throws:
Sequence contains no matching element
Your root element has a namespace defined (xmlns="namespace") thus all child elements are associated with the same namespace. I.e. Child1 element will be in the same namespace, and its name will contain both namespace prefix and local name ("Child1"). So you can either specify full name when searching for Child1 element:
var ns = root.GetDefaultNamespace();
var childType = root.Descendants()
.First(x => x.Name == ns +"Child1" || x.Name == ns + "Child2"|| x.Name == ns + "Child3")
.Name;
Or you can look for x.Name.LocalName (but I don't recommned this approach, though it's unlikely you will have Child1 elements from another namespace).
Note: your Child element does not have closing tag (probably it's a misprint)
Further reading: Xml Namespaces
Related
My goal is to be able to retrieve the value of <Cdtr><Id> from this XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:com.firmaname:response.002">
<DocId>1261076951</DocId>
<CreateDtTm>2016-11-23T14:53:23.938Z</CreateDtTm>
<ResponseCd>OK</ResponseCd>
<Dbtr>
<Id>debtorId</Id>
<Name>debtorName</Name>
</Dbtr>
<Cdtr>
<Id>creditorId</Id>
<Name>creditorName</Name>
</Cdtr>
</Document>
Note that this file 2 <Id> nodes, so I must specify the exact path to the node. The result should be 'creditorId'.
My code is:
XNamespace ns = "urn:com.firmaname:response.002";
var results = requestMessage.Descendants(ns + "Id").First().Value;
But this will return 'debtorId'. I've searched here to find a way to retrieve an exact path, but they all seem to involve using Xpath with XElement. When using XElement I run into trouble with the namespace. All of the suggestions I find are about using XDocument...
I would still use XDocument and select the Cdtr node, then the Id node within it.
One hack you can do with namespaces if you are confident you don't need them is to use <element>.Name.LocalName.
XDocument doc = XDocument.Load("<path to xml");
XNamespace ns = "urn:com.firmaname:response.002";
var creditorid = doc.Descendants().Elements()
.Where(e => e.Name == ns + "Cdtr").Elements()
.First(e => e.Name == ns + "Id");
Update using LocalName
var creditorid = doc.Descendants().Elements()
.Where(e => e.Name.LocalName == "Cdtr").Elements()
.First(e => e.Name.LocalName == "Id");
This works, however you should use the namespace if you can.
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();
Trying to get the value for the "title" node from this XML -> http://feeds.feedburner.com/dotnetshoutout-published
I am using this code:
var d = XDocument.Load("http://feeds.feedburner.com/dotnetshoutout-published");
var node = d.Root.Descendants().Where(x => x.Name == "title").FirstOrDefault();
Always returns null. Making me crazy, any assistance is appreciated.
I guess you have an Xml Namespace on your elements.If so, your element name won't be just title, it will be namespace + title.Instead you should check the LocalName :
var node = d.Root.Descendants().Where(x => x.LocalName == "title").FirstOrDefault();
Or, you can look at the namespace of your elements and create an XNamespace and use it to fetch elements:
XNamespace ns = "yournamespace";
var node = d.Root.Descendants(ns + "title").FirstOrDefault();
You can read the documentation to find more info about how to deal with xml namespaces.
I have an XML document. I want to retrieve a specific descendant node of the root node. The root node does not have a namespace, however, the children nodes do, although they are all the same. What is the best way to retrieve this element as an element?
The namespace of the root node doesn't matter.
You can just write
XNamespace ns = "http://...";
var elem = doc.Element(ns + "TagName");
If you don't know the namespace of the children you could match them by LocalName, which refers to the local (unqualified) part of the name.
string name = "purchase";
var query = xml.Descendants()
.Where(e => e.Name.LocalName == name);
This returns an IEnumerable<XElement>. From there you can loop over it or use SingleOrDefault if you expect only one to exist.
<root xmlns:h="http://www.w3.org/TR/html4/"
xmlns:f="http://www.w3schools.com/furniture">
<h:table>
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
<f:table>
<f:name>African Coffee Table</f:name>
<f:width>80</f:width>
<f:length>120</f:length>
</f:table>
</root>
I am trying to practice LinqToXml but i can't figure out what i wanted.Simply how can i query table elements which has h or f namespace ?
This was what i tried .Also i tried different ones but didn't work.
var query = from item in XDocument.Parse(xml).Elements(ns + "table")
select item;
This won't work because you're missing the root element from your query. This would work:
XNamespace ns = "http://www.w3schools.com/furniture";
var query = XDocument.Parse(xml).Element("root").Elements(ns + "table");
Now if the problem is that you want to find all "table" elements regardless of the namespace, you'd need something like this:
var query = XDocument.Parse(xml)
.Element("root")
.Elements()
.Where(element => element.Name.LocalName == "table");
(EDIT: As noted, you could use XDocument.Root to get to the root element if you want to. The important point is that trying to get to the table element directly from the document node itself won't work.)
Namespace prefixes are not guaranteed to be a particular letter or string. The best approach would be to search by the qualified namespace.
This would get all direct child nodes of XElement xml where the namespace is uri:namespace...
var selectedByNamespace = from element in xml.Elements()
where element.Name.NamespaceName == "uri:namespace"
select element;
Another option would be to select the elements based on the fully qualified name.
var ns = "{uri:namespace}";
var selectedElements = xml.Elements(ns + "table");