I am trying to find nodes in an XML document like this:
<?xml version="1.0"?>
<TrainingCenterDatabase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">
<Activities>
<Activity Sport="CyclingTransport">
<Id>2014-07-08T15:28:14Z</Id>
</Activity>
</Activities>
</TrainingCenterDatabase>
I aim to extract the node value 'Id' with code like this:
XDocument doc = XDocument.Load(filePath);
List<string> urlList = doc.Root.Descendants("Id")
.Select(x => (string)x)
.ToList();
Console.WriteLine(urlList.Count);
However the count is 0, where I expect 1.
After some debugging and editing the XML I noticed that if I change the TrainingCenterDatabase node and remove the attributes to this:
<TrainingCenterDatabase>
Then the result is a count of 1 as expected.
So my question is how do I take into account the namespaces so that I can get the value when the TrainingCenterDatabase node has these attributes?
Namespaces in XML can be tricky. I've run into this problem myself a number of times. In all likelihood, the following will fix your problem:
XDocument doc = XDocument.Load(filePath);
List<string> urlList = doc.Root.Descendants(doc.Root.Name.Namespace.GetName("Id"))
.Select(x => (string)x)
.ToList();
Console.WriteLine(urlList.Count);
Basically, this just assumes the underlying element to have the same namespace as your root element. That's true in this case, but of course it doesn't have to be.
The right way, probably, is to do it explicitly. Now, granted, that kind of depends on how you're using this and your datasource, so make the decision for yourself, but that would require doing something more like this:
XDocument doc = XDocument.Load(filePath);
List<string> urlList = doc.Root.Descendants(System.Xml.Linq.XName.Get("Id", "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"))
.Select(x => (string)x)
.ToList();
Console.WriteLine(urlList.Count);
The cause for your problem was that the default behavior for XElement, when not given an explicit namespace, is to assume no namespace. However, the default behavior for the XML spec is to assume the parent's namespace. In your case, those two were different, so it wasn't able to find the descendant.
It Works...
XDocument doc = XDocument.Load(filePath);
XNamespace ns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2";
var root = doc.Descendants(ns + "Id").Select(x => x.Value).ToList();
Console.WriteLine(root.Count);
Related
I'm trying to get a XmlNodeList from an XmlDocument for nodes that have a certain value, with a view to removing those nodes.
XML:
<List xmlns="http://mynamespace.com/v1">
<Category>2144</Category>
<Title>My Object</Title>
<StartPrice>30.00</StartPrice>
<ReservePrice>-999</ReservePrice>
<BuyNowPrice>-999</BuyNowPrice>
</List>
Preferably I don't want to iterate through every node and check its value. I looked at trying to use LINQ from some examples but I just don't understand it enough to even attempt it.
I feel I'm getting close-ish with XPath (https://www.w3schools.com/xml/xpath_syntax.asp) but I'm beginning to think what I want to do isn't supported.
string xml = UtilityClass.SerializeObject<Listing> ( myListing);
XmlDocument xmlDocument = new XmlDocument ();
xmlDocument.LoadXml ( xml );
XmlElement root = xmlDocument.DocumentElement;
XmlNodeList nodes = root.SelectNodes ( "//*['-999']" );
Am open to other suggestions to get the same result, i.e. remove the nodes with -999 from the Xml document.
Thanks in advance
LINQ to XML is preferred API while dealing with XML in .Net Framework since 2007.
Check it out how easy to achieve what you need in one single statement.
LINQ methods are chained one after another and self-explanatory:
Get all descendants of the root node, taking into account a default namespace.
Whatever the names of the elements.
Where element value is -999.
Convert them to a List<>.
Remove those elements from the XML document.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<List xmlns='http://mynamespace.com/v1'>
<Category>2144</Category>
<Title>My Object</Title>
<StartPrice>30.00</StartPrice>
<ReservePrice>-999</ReservePrice>
<BuyNowPrice>-999</BuyNowPrice>
</List>");
XNamespace ns = xdoc.Root.GetDefaultNamespace();
xdoc.Descendants(ns + "List")
.Elements()
.Where(x => x.Value.Equals("-999"))
.ToList()
.ForEach(x => x.Remove());
Console.WriteLine(xdoc);
}
Output
<List xmlns="http://mynamespace.com/v1">
<Category>2144</Category>
<Title>My Object</Title>
<StartPrice>30.00</StartPrice>
</List>
Please read the code bellow. There I am trying to grab all elements under the <GetSellerListResponse> node, then my goal is to grab TotalNumberOfPages value (currently it's 9 as you can see in the XML).
But my problem is I am getting an error:
System.InvalidOperationException: 'Sequence contains no elements'
Error screenshot is attached for better understanding. Can you tell me what's wrong the way I am trying to grab all elements? Also if possible can you tell how I can grab that 9 from TotalNumberOfPage?
Thanks in advance
C#:
var parsedXML = XElement.Parse(xml);
var AllElements = parsedXML.Descendants("GetSellerListResponse")
.Where(x => x.Attribute("xmlns").Value.Equals("urn:ebay:apis:eBLBaseComponents"))
.First();
XML:
<?xml version="1.0" encoding="UTF-8"?>
<GetSellerListResponse xmlns="urn:ebay:apis:eBLBaseComponents">
<Timestamp>2018-06-20T17:26:29.518Z</Timestamp>
<Ack>Success</Ack>
<Version>1059</Version>
<Build>E1059_CORE_APISELLING_18694654_R1</Build>
<PaginationResult>
<TotalNumberOfPages>9</TotalNumberOfPages>
</PaginationResult>
</GetSellerListResponse>
EDIT: your mistake is the usage of XElement: it is searching for matching elements in the children of <GetSellerListResponse>; that's why you are not getting any result. Change XElement.Parse(xml); to XDocument.Parse(xml);, then the following snippets will work.
You could simply check for the local name:
var AllElements = parsedXML.Descendants().First(x => x.Name.LocalName == "GetSellerListResponse");
I would suggest to use XDocument instead of XElement for parsedXML, because you could shorten the above query to var AllElements = parsedXML.Root;
Another thing you could try is prepending the namespace:
XNamespace ns = "urn:ebay:apis:eBLBaseComponents";
var AllElements = parsedXML.Descendants(ns + "GetSellerListResponse").First();
To answer the question "how to get the number of pages":
var pages = AllElements.Element(ns + "PaginationResult").Element(ns + "TotalNumberOfPages").Value;
I would suggest using the XmlDocument class from System.Xml.
Try the code below:
XmlDocument doc = new XmlDocument();
doc.LoadXml("<GetSellerListResponse xmlns=\"urn:ebay:apis:eBLBaseComponents\"><Timestamp>2018-06-20T17: 26:29.518Z</Timestamp><Ack>Success</Ack><Version>1059</Version><Build>E1059_CORE_APISELLING_18694654_R1</Build><PaginationResult><TotalNumberOfPages>9</TotalNumberOfPages></PaginationResult></GetSellerListResponse>");
XmlNodeList nodeList = doc.GetElementsByTagName("TotalNumberOfPages");
In this case, your nodeList will have just the one element for TotalNumberOfPages and you can access the value by checking
nodeList.FirstOrDefault().InnerText
I have an xml whose root has namespaces like
<root version="2.0" xsi:schemaLocation="http://www.sample.org/schemas/2009 http://www.sample1.org/schemas/2009/railML-2.0/railML.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://xyz/elements/1.1/" xmlns="http://www.abcd.org/schemas/2009">
I am able to retrieve the value of xmlns using
var xdoc = XDocument.Load(XmlToParse);
Console.WriteLine(xdoc.Root.Name.NamespaceName);
but how can i get the values of other namespaces in root i.e. value of
xsi
dc
schemaLocation
Before parsing I need to verify these namespaces so i need these values.
how to do it?
can it be done by linq how?
var q = xdoc.Root.Attributes()
.Where(x => x.IsNamespaceDeclaration)
.Select(x => new {Prefixes = x.Name.LocalName, ns = x.Value});
EDIT:
More Ways: Get namespaces from an XML Document with XPathDocument and LINQ to XML
Actullay, I need to get all elements except root node from first xml document and so that I could insert them as child nodes to an element(that has same name as a previous doc's root name) in a new document.
So I have tried various ways to achieve it, one of them is removing the root node of first and then trying to add elements to a new one's as given below:
I have tried the following but could not achieve it.
XDocument testDoc = XDocument.Parse(Mydocument);
testDoc.Descendants().Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove();
var resultDoc = testDoc;
The above code is giving me an empty "{}" result.
my xml document looks something like the below one's:
<rootName xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
And another way is getting all the elements of first document as the following:
var resultDoc = testDoc.Descendants(ns + "rootName").Elements();
the above statement is giving me the list of elements in the "testDoc" which
I need to do something like below, I am clueless:
<AnotherDocument xsi:schemaLocation="" xmlns:xsi="" xmlns="">
<firstNode>
<rootName>
<main>
<child>
</child>
<anotherchild>
</anotherchild>
</main>
</rootName>
</firstNode>
Please let me know how to insert those elements in a new document as above if I am correct else let me know the way to resolve this issue.
Thanks in advance.
You can replace content of rootName element in another document with elements from first document root:
var xDoc = XDocument.Parse(Mydocument);
var anotherXDoc = XDocument.Load("anotherdata.xml");
XNamespace ns = "http://..."; // your xml namespance
var rootName = anotherXDoc.Descendants(ns + "rootName").First();
rootName.ReplaceNodes(xDoc.Root.Elements());
By this page_nodes gets all nodes now you can used all node by for each loop
var page_nodes = from p in xdoc.Descendants.Where(e => e.Name.LocalName == "rootName").Select(m=>m).Single().Remove() select p;
foreach (var page_node in page_nodes)
{
//Do stuff
}
Wouldn't removing a root node, remove all its child nodes as well? The result you are getting is to be expected I think. You should probably get all the children of the root and copy them to your new document.
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();