Reading XML Attributes into a List - c#

I'm new to XML and am having trouble reading my XML response back from a HttpWebResponse.
Here is the response back:
<RESPONSE version="1.2">
<RESULTS>
<AN an_type="C"
an_id="783hdryfdg56a2"
an_num="1"
an_status="100" />
<RESULTS>
</RESPONSE>
I'm looking to extract out the an_id value and save it to a list. Started doing this but seems to get an for xmlnodelist as an int but it thinks nodes["an_id"] is a string
List<int> IDs = new List<int>();
XmlDocument doc = new XmlDocument();
doc.LoadXml(returnValue);
XmlNodeList nodes = doc.SelectNodes("SEARCH_RESULTS/LOAN");
LoanIDs.Add(Convert.ToInt32(nodes["an_id"].InnerText));
Also is their a way to once the an_id's are added to a list. foreach item in the list use it as aparameter for a new xml like this:
<INPUT>
<LOGIN API_ID=""cat"" API_PASSWORD=""dog"" />
<REQUEST>
<AN an_id=""#anID"" />
<AN an_id=""#anID"" />
....foreach one in list it adds a new node with the value
</REQUEST>
</INPUT>

With xml being your string with the XML code:
var ids = XDocument.Parse(xml).
Descendants("AN").
Select(e => (string)e.Attribute("an_id")).
ToList();
However, from your sample it didn't seem that the attribute is always an integer. Are you sure about your conversion? I changed the cast of the attribute to (string) given your latest comment.
When you want to reuse the list, with current being the parent element containing the ones you want to add:
current.Add(ids.Select(i => new XElement("AN", new XAttribute("an_id", i))));

Related

Getting an XElement with a namespace via XPathSelectElements

I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}

How to get child node by number from XML file

Am trying to make sorting of data in my XML by attribute "times" and show it in TreeView
I have XML file like
<main>
<data url="http://www.r.com/" data="13.10.2013 20:16:33" times="6" />
<data url="https://www.google.com/" data="13.10.2013 20:16:14" times="5" />
<data url="http://ya.com/" data="13.10.2013 19:21:15" times="26" />
</main>
what i want is to sort all nodes by attribute "times". If i can get any of node (1, 2 or 3), that i can take attribute and compare it with first ono - so can make some sorting. but i can't get required element.
so the question - how can i get any nodes from XML file, if i know only it's serial number or how can i sort XML file by some attribute?
Found, if i have id - i can use simething like
XmlDocument myXml = new XmlDocument();
myXml.Load(myfile);
myXml.GetElementById(`here put id`).GetAttribute("required attribute")
but i havent any id.
EDIT:
<main>
<data url="http://ya.com/" data="13.10.2013 19:21:15" times="**26**" />
<data url="http://www.r.com/" data="13.10.2013 20:16:33" times="**6**" />
<data url="https://www.google.com/" data="13.10.2013 20:16:14" times="**5**" />
</main>
Am new to Linq->Xml, So I have no idea about efficiency in this but it seems to work.
XDocument xdoc = XDocument.Parse(xml);
var ordered = xdoc.Descendants("data")
.OrderByDescending(x => int.Parse(x.Attribute("times").Value))
.ToArray();
xdoc.Root.RemoveAll();
xdoc.Root.Add(ordered);
Since you want to show data in TreeView,you need to first get all the data from xml and then sort it by times
XDocument doc=XDocument.Load(url);
//extract all data from xml
var data=doc.Descendants("data")
.Select(x=>
new
{
url=x.Attribute("url").Value,
data=x.Attribute("data").Value,
times=int.Parse(x.Attribute("times").Value)
}
);
foreach(var datum in data.OrderByDescending(x=>x.times))
{
datum.url;
datum.data;
datum.times;
}

Inserting and saving xml using Linq to XML

If i have an XML file settings.xml like below
<Root>
<First>
</First>
</Root>
I Load the XML first using XDocument settings = XDocument.Load("settings.xml")
How should I insert a XML node inside the node First and save it using LINQ-to-XML?
First you need to find the First element. Then you can add other elements and attributes to it.
There are more than one way to find an element in the xml: Elements, Descendants, XPathSelectElement, etc.
var firstElement = settings.Descendants("First").Single();
firstElement.Add(new XElement("NewElement"));
settings.Save(fileName);
// or
var newXml = settings.ToString();
Output:
<Root>
<First>
<NewElement />
</First>
</Root>
Or element with attribute:
firstElement.Add(
new XElement("NewElement", new XAttribute("NewAttribute", "TestValue")));
Output:
<Root>
<First>
<NewElement NewAttribute="TestValue" />
</First>
</Root>
[Edit] The answer to the bonus question. What to do if the first element does not exist and I want to create it:
var root = settings.Element("Root");
var firstElement = root.Element("First");
if (firstElement == null)
{
firstElement = new XElement("First");
root.Add(firstElement);
}
firstElement.Add(new XElement("NewElement"));

Append Multiple Similar XML Nodes to XML Document

I'm currently working with an XML request, and am trying to create a Reply Document that has multiple child nodes of the same name in the call, so what I'm trying to return is:
<Reply Document>
<ConfirmationItem name = "One">
<ItemDetail />
</ConfirmationItem>
<ConfirmationItem name = "Two">
<ItemDetail />
</ConfirmationItem>
...
<ConfirmationItem name = "Twenty">
<ItemDetail />
</ConfirmationItem>
</Reply Document>
I did a bit of research and found this thread: XmlReader AppendChild is not appending same child value in which the accepted answer was that the OP had to create new Elements to be able to append to the end instead of overwrite the first.
My original code is below, it creates the XmlNode from the incoming Request and appends the result to the XmlDocument itself:
//p_transdoc is the XmlDocument that holds all the items to process.
XmlNodeList nodelst_cnfrm = p_transdoc.SelectNodes("//OrderRequest");
foreach (XmlNode node in nodelst_cnfrm)
{
//this is just an XML Object
XmlNode node_cnfrm_itm = this.CreateElement("ConfirmationItem");
node_cnfrm_itm.Attributes.Append(this.CreateAttribute("name")).InnerText = p_transdoc.Attributes["name"].InnerText;
XmlNode node_itmdtl = this.CreateElement("ItemDetail");
node_cnfrm_itm.AppendChild(node_itmdtl);
//xml_doc is the return XML request
xml_doc.AppendChild(node_cnfrm_itm);
}
So after reading that thread and the answer, I tried to change the code to use a new XmlElement each pass through.
//p_transdoc is the XmlDocument that holds all the items to process.
XmlNodeList nodelst_cnfrm = p_transdoc.SelectNodes("//OrderRequest");
foreach (XmlNode node in nodelst_cnfrm)
{
XmlElement node_cnfrm_itm = new XmlElement();
node_cnfrm_itm = this.CreateElement("ConfirmationItem");
node_cnfrm_itm.Attributes.Append(this.CreateAttribute("name")).InnerText = p_transdoc.Attributes["name"].InnerText;
XmlElement node_itmdtl = new XmlElement();
node_itmdtl = this.CreateElement("ItemDetail");
node_cnfrm_itm.AppendChild(node_itmdtl);
//xml_doc is the return XML request
xml_doc.AppendChild(node_cnfrm_itm);
}
But not only does that not work, it returns a server error. So I've come to you for help. Right now this code is just returning one ConfirmationItem. How would I be able to append the ConfirmationItem to the end of the Document instead of overwrite it, to be able to return as many as were sent in?
(I should point out that this code has been heavily formatted for ease of readability, simplicity, and to reduce clutter. Any typographical errors are purely because of the Asker's internal failure at effective proofreading).
Assuming xml_doc is the xml with the ConfirmationItems you need to create the XmlElements with the new XmlDocument. XmlDocument.CreateElement. Hence I use the Linq extension method OfType<>() here to only return the XmlNode objects of the type XmlElement.
// dummy data
XmlDocument p_transdoc = new XmlDocument();
p_transdoc.LoadXml(#"
<root name='rootAttribute'>
<OrderRequest name='one' />
<OrderRequest name='two' />
<OrderRequest name='three' />
</root>
");
XmlDocument xml_doc = new XmlDocument();
xml_doc.LoadXml("<ReplyDocument />");
foreach (var node in p_transdoc.SelectNodes("//OrderRequest").OfType<XmlElement>())
{
XmlElement node_cnfrm_itm = xml_doc.CreateElement("ConfirmationItem");
node_cnfrm_itm = xml_doc.DocumentElement.AppendChild(node_cnfrm_itm) as XmlElement;
node_cnfrm_itm.SetAttribute("name", node.GetAttribute("name"));
XmlElement node_itmdtl = xml_doc.CreateElement("ItemDetail");
node_itmdtl = node_cnfrm_itm.AppendChild(node_itmdtl) as XmlElement;
}
The method CreateElement returns an XmlElement so you can use the methods SetAttribute and GetAttribute.
The code: p_transdoc.Attributes["name"].InnerText doesn't seem right. If you want to get the attributes for the root element of the document you need to type: p_transdoc.DocumentElement.GetAttribute("name")
IMO this is MUCH easier if you're using Linq to XML.
In Linq to XML this would be similar to (some variables have different names):
// dummy data
var transDoc = XDocument.Parse(#"
<root name='rootAttribute'>
<OrderRequest name='one' />
<OrderRequest name='two' />
<OrderRequest name='three' />
</root>");
var xmlDoc = XDocument.Parse("<ReplyDocument />");
xmlDoc.Root.Add(
transDoc.Root.Elements("OrderRequest").Select(o =>
new XElement("ConfirmationElement",
new XAttribute("name", (string)o.Attribute("name")),
new XElement("ItemDetail"))));
Both examples output:
<ReplyDocument>
<ConfirmationElement name="one">
<ItemDetail />
</ConfirmationElement>
<ConfirmationElement name="two">
<ItemDetail />
</ConfirmationElement>
<ConfirmationElement name="three">
<ItemDetail />
</ConfirmationElement>
</ReplyDocument>

Insert Element within specific tags

I'm trying to insert an element at a specific point within my file, then save that file out. However, I can't seem to get it right. My XML layout is like this:
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<Items />
<Users />
</Settings>
This is my current code:
XDocument xd = XDocument.Load(#"C:\test.xml");
var newPosition = xdoc.Root.Elements("Users");
//I've tried messing around with newPosition methods
XElement newItem = new XElement("User",
new XAttribute("Name", "Test Name"),
new XAttribute("Age", "34"),
);
//how can I insert 'newItem' into the "Users" element tag in the XML file?
xd.Save(new StreamWriter(#"C:\test.xml"));
I'd like to use Linq to XML to insert 'newItem' into the tag. Thanks for any help on this.
Just find the Users element, and append it:
// Note that it's singular - you only want to find one
XElement newPosition = xdoc.Root.Element("User");
XElement newItem = new XElement("User",
new XAttribute("Name", "Test Name"),
new XAttribute("Age", "34"));
// Add the new item to the collection of children
newPosition.Add(newItem);
Your use of var here was leading you astray - because the type of newPosition in your code was really IEnumerable<XElement>... you were finding all the User elements. (Okay, there would only have actually been one element, but that's irrelevant... it was still conceptually a sequence.)

Categories