I am parsing an XML fragment using LINQ to XML and I am finding one of the nodes I am selecting is missing the child nodes I am expecting.
Example XML
<CustomerList>
<Customer>
<LastName>Smith</LastName>
<FirstName>Todd</FirstName>
</Customer>
<Customer>
<LastName>Jones</LastName>
<FirstName>Fred</FirstName>
</Customer>
<Customer>Tom Jones</Customer> <!-- Missing child nodes -->
</CustomerList>
When I try and pull out the LastName and FirstName values I get the error Object reference not set to an instance of an object.
Parsing XML with LINQ
XDocument xml = XDocument.Parse(xmlResponse);
List<CustomerModel> nodeList = xml.Descendants("CustomerList")
.Descendants("Customer")
.Select(x => new CustomerModel
{
LastName = x.Element("LastName").Value,
FirstName = x.Element("FirstName").Value,
}).ToList<CustomerModel>();
In the cases where the <Customer> nodes do not have the <LastName> and <FirstName> nodes how do I skip over them or better yet not even select them in the first place?
What I was suggesting is:
XDocument xml = XDocument.Parse(xmlResponse);
List<CustomerModel> nodeList = xml.Descendants("CustomerList")
.Descendants("Customer")
.Where(x => x.Element("LastName") != null && x.Element("FirstName") != null)
.Select(x => new CustomerModel
{
LastName = x.Element("LastName").Value,
FirstName = x.Element("FirstName").Value,
}).ToList<CustomerModel>();
Related
I have xml structure as below and i try to fetch nodes using linq to xml. I am kind of stuck how to read child's child node also I will have to get all child2 node values as coma separated values. And also have to read any dynamic nodes present under child node.
Here are sample xml's.
File 1:
<parent>
<doc>
<order>testorder</order>
<preorder>yes</preorder>
<child>
<childs1>
<child2>abc</child2>
<child2>efg</child2>
</childs1>
<preview>current</preview>
<provision>enable</provision>
</child>
</doc>
</parent>
File 2 :
<parent>
<doc>
<order>testorder</order>
<preorder>yes</preorder>
<child>
<preview>current</preview>
<provision>enable</provision>
<other>abc</other>
</child>
</doc>
</parent>
My sudo code :
XDocument xdoc = XDocument.Load(file);
var customers =
from cust in xdoc.Descendants("doc")
select new
{
Title = cust.Element("order").Value,
preorder = cust.Element("preorder").Value,
innernode= from inner in cust.Elements("child")
select new {
site = (inner.Element("preview") != null) ? inner.Element("preview").Value : null,
node=(inner.Element("childs1") != null) ? string.Join(",", from seg in inner.Elements("child2") select seg.Value) :null,
Should store if any extra dynamic nodes are there ,
},
};
foreach(var item in customers)
{
// read all nodes
}
Your code to fetch child2 is trying to look in doc's descendants, but you want to look at childs1's descendants. As for dynamic fields you can do it by creating a dictionary to get any elements that don't match the hardcoded ones, as i did below.
var customers =
from cust in xdoc.Descendants("doc")
select new
{
Title = cust.Element("order").Value,
preorder = cust.Element("preorder").Value,
innernode = from inner in cust.Elements("child")
select new
{
site = (inner.Element("preview") != null) ? inner.Element("preview").Value : null,
node = (inner.Element("childs1") != null) ? string.Join(",", from seg in inner.Elements("childs1").Elements("child2") select seg.Value) : null,
dynamicElements = inner.Elements()?.Where(e => e.Name != "preview" && e.Name != "childs1")?.ToDictionary(e => e.Name, e => e.Value)
},
};
I have a xml file from which only specific nodes have to be removed. The node name will be given as input from the user. How to remove the specific nodes which have requested from the user?
<Customers>
<Customer>
<id>michle</id>
<address>newjersy</address>
</Customer>
<Customer>
<id>ann</id>
<address>canada</address>
</Customer>
</Customers>
I have tried
var customer = new XElement("customer",
from o in customers
select
new XElement("id", id),
new XElement("address", address)
);
Customer will contain a new node
<Customer>
<id>ann</id>
<address>canada</address>
</Customer>
doc.Element("customers").Elements(customer).ToList().Remove();
but this is not working. How can I remove the element from the xml?
Tom,
Try this...
private static void RemoveNode(string sID)
{
XDocument doc = XDocument.Load(#"D:\\Projects\\RemoveNode.xml");
var v = from n in doc.Descendants("Customer")
where n.Element("id").Value == sID
select n;
v.Remove();
doc.Save(#"D:\\Projects\\RemoveNode.xml");
}
This removed one node when I called it using
RemoveNode("michle");
Hope this helps.
Your main mistake is that you are creating new nodes that not attached with the source document instead of retrieving existed nodes from it.
You can use article "Removing Elements, Attributes, and Nodes from an XML Tree" on MSDN as a guideline to manipulating XML data.
For example, use XNode.Remove() method to delete one node from the tree or Extensions.Remove<T>(this IEnumerable<T> source) where T : XNode to remove every node in the source collection of nodes:
doc.Descendants("Customer")
.Where(x => x.Element("id").Value == id)
.Remove();
But you also need to save document via Save method after that for commit your changes:
doc.Save();
You can remove this way based on id
xdoc.Descendants("Customer")
.Where(x => (string)x.Element("id") == "michle")
.Remove();
**I have an XML like this-
<?xml version="1.0" encoding="UTF-8"?>
<Tool_Parent>
<tool name="ABCD" id="226">
<category>Centralized</category>
<extension_id>0</extension_id>
<uses_ids>16824943 16824944</uses_ids>
</tool>
<tool name="EFGH" id="228">
<category>Automated</category>
<extension_id>0</extension_id>
<uses_ids>92440 16824</uses_ids>
</tool>
</Tool_Parent>
Based on the id of tool i want to print the uses_ids value,i.e if i search for 228 i should get 92440 16824.
I had tried like-
var toolData = (from toolElement in doc.Descendants("tool")
select new Tool_poco
{
a_Name = tool.Attribute("name").Value,
a_Id = tool.Attribute("id").Value,
e_ExtensionId = tool.Element("extension_id").Value,
e_UsesIds =tool.Element("uses_parm_ids").Value
});
where Tool_poco is a poco class for tool node containing declaration for member variable.
Now I want to get information related to a particular tool id in toolData variable.How to do it?
Note: I have variable like-
searched_Tool_id = Tool_Id_txtBx.Text.ToString();
Please let me know a way through which i can modify my above query for toolData.**
You can modify your query as
Tool_poco toolData = (from el in xelement.Elements("Employee")
where (string)el.Attribute("id") == "226"
select new Tool_poco
{
a_Name = el.Attribute("name").Value,
a_Id = el.Attribute("id").Value,
e_ExtensionId = el.Element("Name").Value,
e_UsesIds = el.Element("uses_ids").Value
}).FirstOrDefault();
You could start by doing something like this once you have an XDocument object loaded and ready:
var xdoc = XDocument.Parse(
#"<?xml version=""1.0"" encoding=""utf-8""?>
<Tool_Parent>
<tool name=""ABCD"" id=""226"">
<category>Centralized</category>
<extension_id>0</extension_id>
<uses_ids>16824943 16824944</uses_ids>
</tool>
<tool name=""EFGH"" id=""228"">
<category>Automated</category>
<extension_id>0</extension_id>
<uses_ids>92440 16824</uses_ids>
</tool>
</Tool_Parent>");
var root = xdoc.Root; // Got to have that root
if (root != null)
{
var id228query = (from toolElement in root.Elements("tool")
where toolElement.HasAttributes
where toolElement.Attribute("id").Value.Equals("228")
let xElement = toolElement.Element("uses_ids")
where xElement != null
select xElement.Value).FirstOrDefault();
Console.WriteLine(id228query);
Console.Read();
}
Output: 92440 16824
**Note: In reference to your example, one possible reason it was not working
for you could be that your xml references an element with name "uses_ids",
however, your query references an element with a similar, but not exact,
spelling with name "uses_parm_ids".**
Here is my code and it keeps throwing exception that System.Xml.XmlException: Element 'Customer' was not found.And XML file pasted at bottom
public static List<Customer> GetCustomers()
{
// create the list
List<Customer> customers = new List<Customer>();
// create the XmlReaderSettings object
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
// create the XmlReader object
XmlReader xmlIn = XmlReader.Create(path, settings);
// read past all nodes to the first Customer node
xmlIn.ReadToDescendant("Customers");
// create one Customer object for each Customer node
do
{
Customer c = new Customer();
xmlIn.ReadStartElement("Customer");
c.FirstName =
xmlIn.ReadElementContentAsString();
c.LastName =
xmlIn.ReadElementContentAsString();
c.Email =
xmlIn.ReadElementContentAsString();
customers.Add(c);
}
while (xmlIn.ReadToNextSibling("Customer"));
// close the XmlReader object
xmlIn.Close();
return customers;
Here is my XML and it clearly contains Element Customer
<?xml version="1.0" encoding="utf-8"?>
<Customers>
<Customer>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Email>jsmith#gmail.com</Email>
</Customer>
<Customer>
<FirstName>Jane</FirstName>
<LastName>Doe</LastName>
<Email>janedoe#yahoo.com</Email>
</Customer>
</Customers>
From the docs for ReadStartElement(string):
Checks that the current content node is an element with the given Name and advances the reader to the next node.
When you've only called ReadToDescendant("Customers") the current node will be Customers, not Customer.
You can fix this either by changing that to ReadToDescendants("Customer") or by adding an extra call like that after the first one.
Do you really need to use XmlReader though? If you could read it using LINQ to XML your code would be much simpler:
return XDocument.Load(path)
.Root
.Elements("Customer")
.Select(x => new Customer {
FirstName = (string) x.Element("FirstName"),
LastName = (string) x.Element("LastName"),
Email = (string) x.Element("Email")
})
.ToList();
If you want to use ReadToDescendent, I think you need to read until the "Customer" node, like so:
// read past all nodes to the first Customer node
xmlIn.ReadToDescendant("Customer");
Following is the example of an XML document.
<People>
<Person>
<Name>ABC </Name>
<SSN>111111</SSN>
<Address>asdfg</Address>
</Person>
</People>
I need to get the tag names but not the values between the tag names. That is, under the person tag, I should grab the Name, SSN, and Address nodes and not the ABC, 111111, and asdfg values.
I need to use LINQ to XML and query it in C#.
How can I do it?
This returns the names as a list of strings:
var doc = XDocument.Parse(#"<People>
<Person>
<Name>ABC </Name>
<SSN>111111</SSN>
<Address>asdfg</Address>
</Person>
</People>"
);
var list = doc.Root.Element("Person").Descendants()
.Select(node => node.Name.LocalName).ToList();
In case you're using an XElement instead of an XDocument, you can remove the .Root from the above code and get the correct results.
Create a class
public class Person
{
public string Name {get; set;}
public int SSN {get; set;}
public string Address {get; set;}
}
And create a new person this way;
List<Person> NewPersons = new List<Person>();
XDocument doc = XDocument.Load(xmlFile);
foreach(XElement xElem in doc.Descendants("Person"))
{
NewPersons.Add(new Person
{
Name = xElem. Element("Name").Value,
SSN = xElem.Element("SSN").Value,
Address = xElem.Element("Address").Value,
});
}
You can get it this way...
string xml = "<People> <Person> <Name>ABC </Name> <SSN>111111</SSN> <Address>asdfg</Address> </Person> <Person> <Name>ABC </Name> <SSN>111111</SSN> <Address>asdfg</Address> </Person> </People>";
XElement xmlData = XElement.Parse(xml);
foreach(XElement xmlPerson in xmlData.Elements("Person"))
{
List<string> TagsForThisPerson = new List<string>();
foreach(XElement xmlTag in xmlPerson.Elements())
{
TagsForThisPerson.Add(xmlTag.Name.ToString());
}
TagsForThisPerson.Dump();
}
Simple LINQ syntax to get the names is listed below. It assumes you have the XML loaded in a XDocument variable named doc.
var nodeNames = from node in doc.Descendants("Person").First().Descendants()
select node.Name.LocalName;
It only looks at the first person. If you have more than one in the XML document, the list of names is probably not what you would want (no reason to repeat all the names of the nodes over and over). This way, you get a list of just the node names for the first person, but it does assume that the first one would have a complete list of names. If they vary, you would need to build a distinct list from all the nodes.