XElement with LINQ Select and optional elements - c#

I am trying to display some date from some XML I get from an external service. I am using XElement and I try to use LINQ select to get my data.
var xElem = XElement.Load(HttpUtility.UrlPathEncode(url));
var books = (from pubs in xElem.Elements("result")
select new
{
Id = (string)pubs.Element("data").Element("id"),
Title = (string)pubs.Element("data").Element("title"),
Year = (string)pubs.Element("data").Element("year"),
Resources = (string)pubs.Element("data")
.Element("resource")
.Element("url")
.ElementValueNull(),
Authors= pubs.Element("data").Elements("person")
}).ToList();
foreach (var book in books)
{
// Put the string together with string builder....
foreach (var person in book.Authors)
{
//Get the authors
}
}
And of course I have made the class for ElementValueNull.
//This method is to handle if element is missing
public static string ElementValueNull(this XElement element)
{
if (element != null)
return element.Value;
return "";
}
//This method is to handle if attribute is missing
public static string AttributeValueNull(this XElement element, string attributeName)
{
if (element == null)
return "";
else
{
XAttribute attr = element.Attribute(attributeName);
return attr == null ? "" : attr.Value;
}
}
The problem is that the resource tag with it's elements are not always present. And if it isn't there it will skip the whole record. Is there any easy way of making it so that it will just make the Resources have the empty string returned from my class but still add the record still using a LINQ select?
EDIT with XML example:
<?xml version="1.0" encoding="UTF-8"?>
<tester xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://tester.no/xmlSchema/xsd/tester.xsd">
<generert>2014-12-01</generert>
<result>
<data>
<id>297474</id>
<person>
<id>11690</id>
<surname>Medel-Svensson</surname>
<firstname>Ronnie</firstname>
</person>
<title>Title 1</title>
<year>2009</year>
</data>
</result>
<result>
<data>
<id>807059</id>
<person>
<id>11690</id>
<surname>Bronskimlet</surname>
<firstname>Hallstein</firstname>
</person>
<person>
<id>328009</id>
<surname>Kroksleiven</surname>
<firstname>Jostein</firstname>
</person>
<person>
<id>328010</id>
<surname>Gassolini</surname>
<firstname>Ruffino</firstname>
</person>
<person>
<id>327990</id>
<surname>von Schnellfahrer</surname>
<firstname>Heinrich</firstname>
</person>
<title>Title 2</title>
<year>2010</year>
<resource>
<type>
<code>TEXT</code>
</type>
<url>http://www.example.com/</url>
</resource>
</data>
</result>
<result>
<data>
<id>1164653</id>
<person>
<id>11690</id>
<surname>Bergsprekken</surname>
<firstname>Mysil</firstname>
</person>
<title>Title 3</title>
<year>2014</year>
<resource>
<type>
<code>FULLTEKST</code>
</type>
<url>http://www.example.com/</url>
</resource>
</data>
</result>
</tester>

A couple of things:
if you use Element(..), then the result could be null. This may cause null reference exceptions if elements are missing in your path. A more elegant way to handle this would be to use sequences and return an element if present using SingleOrDefault()
Both XElement and XAttribute have a bunch of explicit type conversion operators built in. This means you can cast to string and various other primitives. As string is a reference type, it would return null if the XObject was null. Value types such as int would throw an exception in this case, though int? would not.
With this in mind, something like this should solve your problem. Note as 'data' is common to all, you can put this in the initial selector:
from pubs in xElem.Elements("result").Elements("data")
select new
{
Id = (string)pubs.Element("id"),
Title = (string)pubs.Element("title"),
Year = (string)pubs.Element("year"),
Resources = (string)pubs.Elements("resource")
.Elements("url")
.SingleOrDefault(),
Authors= pubs.Elements("person")
}

Related

How to Mask or Replace the values in XML?

I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<TXLife xmlns="http://ACORD.org/Standards/Life/2" Version="2.22.00">
<TXLifeRequest>
<OLifE Version="2.22.0">
<Party id="BEB7-BDDC43FE3F01_10004">
<PartyTypeCode tc="1">PT_PERSON</PartyTypeCode>
<FullName>Gump,Forrest</FullName>
<ResidenceState tc="58">USA_WI</ResidenceState>
<Person id="D7329BB530E8_10304">
<FirstName>Forrest</FirstName>
<LastName>Gump</LastName>
</Person>
</Party>
</OLifE>
</TXLifeRequest>
</TXLife>
I want Replace the FirstName value to No Name.
This is related to Data Masking.
I'm trying to access the node with the given code below, but it is not working.
var testXML = XDocument.Load("C:\\DataMask\\P0500015703691806181259345440127.xml");
var nodePTCode = testXML.Descendants("Party").FirstOrDefault(cd => cd.Element("PartyTypeCode").Value == "PT_PERSON");
The namespace has to be included when addressing the elements.
Also for an XDocument you have to start from its Root property.
XDocument xdoc = XDocument.Load("C:\\DataMask\\P0500015703691806181259345440127.xml");
XNamespace ns = "http://ACORD.org/Standards/Life/2";
XElement firstName = xdoc.Root.Descendants(ns + "FirstName").FirstOrDefault();
if (firstName != null) { firstName.Value = "No Name"; }
The xml will have the update applied:
<TXLife xmlns="http://ACORD.org/Standards/Life/2" Version="2.22.00">
<TXLifeRequest>
<OLifE Version="2.22.0">
<Party id="BEB7-BDDC43FE3F01_10004">
<PartyTypeCode tc="1">PT_PERSON</PartyTypeCode>
<FullName>Gump,Forrest</FullName>
<ResidenceState tc="58">USA_WI</ResidenceState>
<Person id="D7329BB530E8_10304">
<FirstName>No Name</FirstName>
<LastName>Gump</LastName>
</Person>
</Party>
</OLifE>
</TXLifeRequest>
</TXLife>
Edit
If applicable, you'll also have to update the FullName xml element to reflect the change in FirstName.
XElement lastName = xdoc.Root.Descendants(ns + "LastName").FirstOrDefault();
XElement fullName = xdoc.Root.Descendants(ns + "FullName").FirstOrDefault();
fullName.Value = String.Format("{0},{1}", (String)lastName, (String)firstName);

Replacing an xml element value in c#

Here is my xml file data
<Persons>
<Person>
<Name>john</Name>
</Person>
<Employee>
<Detail>
<Firstname>john</FirstName>
</Detail>
</Employee>
<Student>
<FullName>john</FullName>
</Student>
</Persons>
I want to replace "john" to "danny" in all places.
How can I do this in c# ?
One possible way using XDocument :
var doc = XDocument.Load("path_to_xml_file.xml");
//select all leaf elements having value equals "john"
var elementsToUpdate = doc.Descendants()
.Where(o => o.Value == "john" && !o.HasElements);
//update elements value
foreach(XElement element in elementsToUpdate)
{
element.Value = "danny";
}
//save the XML back as file
doc.Save("path_to_xml_file.xml");
Notice that XElement.Value contains all text nodes within the element, concatenated.
The significance of this is, for example, considering your XML as input, not only <Name> has value of "john" but also <Person>. But we only want to update the leaf elements not the ancestors.
*) I assumed you didn't really meant to tag the question by xmldocument so this answer using the newer XML API XDocument, though using XmlDocument is also possible.

Before inserting new element in xml check if exists the value

I have below a xml file with the below format:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Countries>
<country>India</country>
<country>USA</country>
<country>UK</country>
</Countries>
</Root>
string newCountry="UAE"
I want to insert this "UAE" country to the above xml file, before that I want to check whether "UAE" is already exists in the xml. If not exists then only want to insert otherwise no operation. How can I do this?
Like this:
XDocument xml = XDocument.Load("path_to_file");
string newCountry = "UAE";
XElement countries = xml.Descendants("Countries").First();
XElement el = countries.Elements().FirstOrDefault(x => x.Value == newCountry);
if (el == null)
{
el = new XElement("country");
el.Value = newCountry;
countries.Add(el);
}
//Console.WriteLine(countries.ToString());
The easiest way would probably be to read the xml into C# objects, check for the existance of UAE, potentially add it, and write the objects back to XML.

XML node is missing when empty

I have an XMLDocument as result of query.
I would like to extract the <Property> value and the appropriate <Notes> for each entry.
<?xml version="1.0"?>
<EADATA version="1.0" exporter="Enterprise Architect">
<Dataset_0>
<Data>
<Row>
<PropertyID>439</PropertyID>
<Object_ID>683</Object_ID>
<Property>tagged value</Property>
<ea_guid>{5BF3E019-277B-45c2-B2DE-1887A90C6944}</ea_guid>
</Row>
<Row>
<PropertyID>444</PropertyID>
<Object_ID>683</Object_ID>
<Property>Another Tagged value</Property>
<Notes>Another tagged value notes.</Notes>
<ea_guid>{42BE8BAA-06B8-4822-B79A-59F653C44453}</ea_guid>
</Row>
</Data>
</Dataset_0>
</EADATA>
However, If the <Notes> is empty there is no <Notes> tag at all.
What XPath should I write in such cases?
Which value do you want if there is no Notes element, a null, an empty string?
I would select the Row elements with SelectNodes and then check whether a Notes child exists and assign null (as done below) or the empty string if not:
foreach (XmlElement row in doc.SelectNodes("//Row"))
{
string prop = row.SelectSingleNode("Property").InnerText;
string notes = row.SelectSingleNode("Notes") != null ? row.SelectSingleNode("Notes").InnerText : null;
}
Try this :
XPathDocument docNav = new XPathDocument(new StringReader(xml));
XPathNavigator navigator = docNav.CreateNavigator();
XPathNodeIterator NodeIter = navigator.Select("/EADATA/Dataset_0/Data/Row");
foreach (XPathNavigator selectedNode in NodeIter)
{
var a= "<root>" + selectedNode.InnerXml + "</root>";
var x= XDocument.Parse(a);
Console.WriteLine (x.Root.Element("Property").Value);
if (x.Root.Element("Notes")!=null)
Console.WriteLine (x.Root.Element("Notes").Value);
}
result :
tagged value
Another Tagged value
Another tagged value notes.

Count people by gender

I need some help with C# and XML.
I'm building a web application and need to present information about number of peope with gender Male in a specific department (How many men in Dep1).
This is my XML file:
<company>
<department>
<departmentname>Dep 1</departmentname>
<people>
<person>
<name>Sean</name>
<date>2013-10-10</date>
<gender>male</gender>
<age>40</age>
</person>
<person>
<name>John</name>
<date>2013-10-18</date>
<gender>male</gender>
<age>45</age>
</person>
<person>
<name>Linda</name>
<date>2013-09-10</date>
<gender>female</gender>
<age>42</age>
</person>
<person>
<name>Bob</name>
<date>2013-10-01</date>
<gender>male</gender>
<age>35</age>
</person>
</people>
</department>
<department>
<departmentname>Dep 2</departmentname>
<people>
<person>
<name>Art</name>
<date>2013-09-10</date>
<gender>male</gender>
<age>38</age>
</person>
<person>
<name>Christina</name>
<date>2013-10-20</date>
<gender>female</gender>
<age>45</age>
</person>
<person>
<name>Marie</name>
<date>2013-09-10</date>
<gender>female</gender>
<age>49</age>
</person>
</people>
</department>
</company>
My code (not finished and not working):
XElement company= XElement.Load(Server.MapPath("myXML.xml"));
XElement department= (from p in company.Elements("department")
where p.Element("departmentname").Value == 'Dep 1' && p.Element
("gender").Value == 'male'
select p).Count;
numberTextBox.Text = department.Element; //???How to write to textbox
var department = (from p in company.Elements("department")
where p.Element("departmentname").Value == "Dep 1" && p.Element
("gender").Value == "male"
select p).Count();
You're not retrieving an XElement, you're retrieving an int (which is what Count returns).
Using var will infer this for you.
And per my comment: kon -> gender.
Let's separate out some of the elements of the search to make it easier to follow:
XElement depName = company.Descendants("departmentname")
.Where(x => x.Value == "Dep 1")
.FirstOrDefault();
XElement[] men = depName.Parent.Descendants("gender")
.Where(x => x.Value == "male")
.Where(x => (int)x.Parent.Element("age") > 40)
.ToArray();
int count = men.Length;
depName.Parent is the department element. From that we get all the gender's with the value male. So the variable men is only the genders. If you want the whole person element, use the men's Parent property to get that. For example:
foreach(XElement man in men)
Console.Writeline(man.Parent.Element("name").Value);
To assign to the textbox:
numberTextBox.Text = count.ToString(); // or men.Length.ToString()
Hope this will help you..
XmlDocument doc = new XmlDocument();
doc.Load(your document);
var items = doc.GetElementsByTagName("gender");
var x=items.Count;
int malecount=doc.SelectNodes("gender[. = \"male\"]").Count;
You could try just XMLDocument and Xpath, just use the following code
var xpath = "/company/department[departmentname='Dep 1']/people/person[/company/department/people/person/gender='male']/name";
var xmldoc = new XmlDocument();
xmldoc.Load("company.xml");
var result = xmldoc.SelectNodes(xpath);
if (result != null) Assert.AreEqual(4,result.Count);
Hope this helps.

Categories

Resources