How properly work with LINQ to XML? - c#

I have generated such xml file
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>212</numRequest>
<IDWork>12</IDWork>
<NumObject>21</NumObject>
<lvlPriority>2</lvlPriority>
<NumIn1Period>21</NumIn1Period>
</Single_Request>
</Requestes>
My aim is to get IDWork,numRequest and etc elements. I tried to get them this way:
foreach (XElement el in doc.Root.Elements())
{
if (el.Name == "Single_Request")
{
string num = el.Elements("numRequest").Value;
// but he says, that he cant do this .Value because it doest exist at all
}
}
How to fix this?

You have this error, because Elements("numRequest") returns collection of elements, not single element. You should use Element("numRequest") instead.
Also I suggest you to use query for getting elements by name instead of enumerating all elements and verifying their names:
var request = doc.Root.Element("Single_Request");
var num = (int)request.Element("numRequest");
Usually you use anonymous types or custom objects to group values parsed from xml:
var query = from r in doc.Root.Elements("Single_Request")
where (int)r.Attribute("num") == 1 // condition
select new {
NumRequest = (int)request.Element("numRequest"),
IdWork = (int)request.Element("IDWork"),
NumObject = (int)request.Element("NumObject")
};
var request = query.SinlgleOrDefault();
// use request.IdWork

Related

Accessing a sub attribute in a XML file using XElement

In C#, I am trying to change an option of a feature in an XML file which presents a Print Ticket and loaded as an XElement by the following code:
XElement ticketRootXElement = null;
using (Stream ticketReadStream = displayedPrintTicket.GetReadStream())
{
ticketRootXElement = XElement.Load(ticketReadStream);
}
The partial XML is something like following:
<?xml version="1.0"?>
<psf:Feature xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" name="psk:PageMediaSize">
<psf:Option name="psk:ISOA4">
<psf:ScoredProperty name="psk:MediaSizeWidth">
<psf:Value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:integer">210000</psf:Value>
</psf:ScoredProperty>
<psf:ScoredProperty name="psk:MediaSizeHeight">
<psf:Value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:integer">297000</psf:Value>
</psf:ScoredProperty>
</psf:Option>
</psf:Feature>
How can I access the "Option" of a specific "Feature" and change it to something like <psf:Option name="psk:ISOA3">?
I tried the following code, but it fails.
foreach (XAttribute xAttr in ticketRootXElement.Descendants(xns_psf + "Feature").Attributes())
{
if (xAttr.Value.Equals("psk:PageMediaSize"))
{
foreach(XAttribute xSubAttr in ticketRootXElement.Element("PageMediaSize").Descendants(xns_psf + "Option").Attributes())
{
if (xAttr.NextAttribute.Name.LocalName.Equals("name"))
{
xAttr.NextAttribute.SetValue("psk:ISO" + cmb_PaperSize.SelectedValue.ToString());
}
}
}
}
You can can modify the option value of your selected feature as as follows:
var featureName = "psk:PageMediaSize";
var newOptionValue = "psk:ISOA3"; // Your modified value here
XNamespace xns_psf = #"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework";
var query = from f in ticketRootXElement.DescendantsAndSelf(xns_psf + "Feature")
where (string)f.Attribute("name") == featureName
select f;
foreach (var f in query)
{
// TODO: handle the situation were a child <psf:Option> element is missing.
f.Element(xns_psf + "Option").SetAttributeValue("name", newOptionValue);
}
Notes:
XElement.Attribute(XName) can be used to look up an attribute by name, and XElement.SetAttributeValue(XName, Object) can be used to set or add an attribute value by name.
Casting an XAttribute to a string returns the value of the attribute, or null if the attribute was missing, and so is convenient to use when filtering by attribute value in a where statement.
If the selected <psf:Feature> element does not have a child <psf:Option> element, the above code will throw an exception. You will need to check your XML schema to determine whether this is possible, and if so, how to handle it.
Demo fiddle here.
Actually I did it using the following code. But "bdc" solution (above answer) sounds much better:
var element = ticketRootXElement.Descendants(xns_psf + "Feature")
.Where(arg => arg.Attribute("name").Value == "psk:PageMediaSize")
.Single();
var subelement = element.Descendants(xns_psf + "Option")
.Single();
subelement.FirstAttribute.SetValue("psk:ISOA3");

Linq to XML Element inside Element

I wrote a method to read XML and write information to an object. The XML contains elements with the information, but some of the information is encapsulated and I can't figure out how to get to the information out of it. The XML contains about 200 "results".
XML structure
<result id="xxxxx">
<name>Name</name>
<age>25</age>
<info>
<x>Some text</x>
<y>More Text</y>
</info>
</result>
Code
XDocument rootDocument = XDocument.Load(file);
var xy = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name")
Age = r.Element("age"),
x = r.Element("info").Element("x"),
y = r.Element("info").Element("y"),
};
foreach (var r in xy)
{
Object o = new Object()
{
Name = r.Name,
Age = r.Age,
x = r.x,
y = r.y
};
}
Error
Object reference not set to an instance of an object.
The Error occurs at the line
x = r.Element("info")...
and the following one.
You could do the following:
var query = from r in rootDocument.Descendants("result")
select new
{
Name = (string)r.Element("name"),
Age = (int?)r.Element("age"),
x = (string)r.Elements("info").Elements("x").SingleOrDefault(),
y = (string)r.Elements("info").Elements("x").SingleOrDefault(),
};
var resultList = query.ToList();
Notes:
Once you have selected an XElement with a primitive value, you can convert the element to a c# primitive such as string or int? by using one of XElement's explicit casting operators, like so:
Name = (string)r.Element("name")
Age = (int?)r.Element("age")
The fact that you are seeing an Object reference not set to an instance of an object exception suggests that an element is unexpectedly missing. That could easily happen if one of the <result> elements were missing an <info> child element. The expression
r.Elements("info").Elements("x")
returns all child elements named <x> of child element(s) named <info>. Then SingleOrDefault() returns the only element of that sequence, or a default value if the sequence is empty. This protects against the situation when an <info> is missing.
Similarly, if the <age> element is missing, trying to cast it to int would throw a null reference exception since int is a value type. Casting to int? instead returns null instead of throwing the exception.
The final ToList() evaluates the query and returns the results in a list.
Sample fiddle.

Parsing xml into anonymous type

I am trying to parse the xml below to load the id_name/rel_no pairs into an anonymous type collection. I am having a problem when looping through the collection and when element is missing in one of the elements. Is there a way not to load a particular pair when one of the elements id_name or rel_no is missing?
I get InvalidOperationException (sequence contains no elements) when the loop gets to that particular pair with missing element.
Thanks for any suggestions.
XDocument xdata = XDocument.Parse(data);
var query = from dox in xdata.Descendants("Inc")
select new
{
IDName= dox.Element("id_name").Value,
RelNo= dox.Descendants("rel_no").First().Value
};
XML
<Data>
<Inc>
<id_name>test</id_name>
<Relationships>
<Relationship>
<rel_no>004</rel_no>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test2</id_name>
<Relationships>
<Relationship>
</Relationship>
</Relationships>
</Inc>
<Inc>
<id_name>test3</id_name>
<Relationships>
<Relationship>
<rel_no>006</rel_no>
</Relationship>
</Relationships>
</Inc>
</Data>
Accessing in a loop
foreach (var record in query)
{
}
var xdata = XDocument.Parse(data);
var items = xdata.Descendants("Inc")
.Select(d => new
{
DName = (string)d.Element("id_name"),
RelNo = ((string)d.Descendants("rel_no").FirstOrDefault() ?? "")
})
.ToList();

Get certain xml node and save the value

Considering the following XML:
<Stations>
<Station>
<Code>HT</Code>
<Type>123</Type>
<Names>
<Short>H'bosch</Short>
<Middle>Den Bosch</Middle>
<Long>'s-Hertogenbosch</Long>
</Names>
<Country>NL</Country>
</Station>
</Stations>
There are multiple nodes. I need the value of each node.
I've got the XML from a webpage (http://webservices.ns.nl/ns-api-stations-v2)
Login (--) Pass (--)
Currently i take the XML as a string and parse it to a XDocument.
var xml = XDocument.Parse(xmlString);
foreach (var e in xml.Elements("Long"))
{
var stationName = e.ToString();
}
You can retrieve "Station" nodes using XPath, then get each subsequent child node using more XPath. This example isn't using Linq, which it looks like you possibly are trying to do from your question, but here it is:
XmlDocument xml = new XmlDocument();
xml.Load(xmlStream);
XmlNodeList stations = xml.SelectNodes("//Station");
foreach (XmlNode station in stations)
{
var code = station.SelectSingleNode("Code").InnerXml;
var type = station.SelectSingleNode("Type").InnerXml;
var longName = station.SelectSingleNode("Names/Long").InnerXml;
var blah = "you should get the point by now";
}
NOTE: If your xmlStream variable is a String, rather than a Stream, use xml.LoadXml(xmlStream); for line 2, instead of xml.Load(xmlStream). If this is the case, I would also encourage you to name your variable to be more accurately descriptive of the object you're working with (aka. xmlString).
This will give you all the values of "Long" for every Station element.
var xml = XDocument.Parse(xmlStream);
var longStationNames = xml.Elements("Long").Select(e => e.Value);

Best way to query XDocument with LINQ?

I have an XML document that contains a series of item nodes that look like this:
<data>
<item>
<label>XYZ</label>
<description>lorem ipsum</description>
<parameter type="id">123</parameter>
<parameter type="name">Adam Savage</parameter>
<parameter type="zip">90210</parameter>
</item>
</data>
and I want to LINQ it into an anonymous type like this:
var mydata =
(from root in document.Root.Elements("item")
select new {
label = (string)root.Element("label"),
description = (string)root.Element("description"),
id = ...,
name = ...,
zip = ...
});
What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?
(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:
If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
You may want to pull all the values at once and convert them into a dictionary
If you're using different data types for the contents, you might want to cast the element instead of using .Value
You may want to create a method to return the matching XElement for a given type, instead of having several queries.
Personally I don't think I'd even use a query expression for this. For example:
static XElement FindParameter(XElement element, string type)
{
return element.Elements("parameter")
.SingleOrDefault(p => (string) p.Attribute("type") == type);
}
Then:
var mydata = from item in document.Root.Elements("item")
select new {
Label = (string) item.Element("label"),
Description = (string) item.Element("description"),
Id = (int) FindParameter(item, "id"),
Name = (string) FindParameter(item, "name"),
Zip = (string) FindParameter(item, "zip")
};
I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.
use XPATH - it is very fast ( except xmlreader - but a lot of if's)
using (var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[#type='id']");
foreach (var x in query.Cast<XElement>())
{
Console.WriteLine( x.Value );
}
}

Categories

Resources