XML How to select Child Elements using XPath - c#

I've got the following XML, shown in the following image:
But I can't for the life of me, get any code to select the house element between <ArrayOfHouse>.
There will be more than one House element once I've managed to get it to select one, here's my code so far:
// Parse the data as an XML document
XDocument xmlHouseResults = XDocument.Parse(houseSearchResult);
// Select the House elements
XPathNavigator houseNavigator = xmlHouseResults.CreateNavigator();
XPathNodeIterator nodeIter = houseNavigator.Select("/ArrayOfHouse/House");
// Loop through the selected nodes
while (nodeIter.MoveNext())
{
// Show the House id, as taken from the XML document
MessageBox.Show(nodeIter.Current.SelectSingleNode("house_id").ToString());
}
I'm getting the stream of XML, because I have managed to show the data in the MessageBox shown above, but I can't get to the individual houses.

You can select the House nodes like this:
var houses = XDocument.Parse(houseSearchResult).Descendants("House");
foreach(var house in houses)
{
var id = house.Element("house_id");
var location = house.Element("location");
}
Or you can use Select to directly get a strongly typed object:
var houses = XDocument.Parse(houseSearchResult)
.Descendants("House")
.Select(x => new House
{
Id = x.Element("house_id"),
Location = x.Element("location")
});
This assumes that there exists a class House with the properties Id and Location.
Also, please be sure to think about the suggestion by Thomas Levesque to use XML serialization.

With XPath you would need to use an XmlNamespaceManager, however as you have an XDocument you could simply use the LINQ to XML axis methods e.g.
XNamespace df = XmlHouseResults.Root.Name.Namespace;
foreach (XElement house in XmlHouseResults.Descendants("df" + "House"))
{
MessageBox.Show((string)house.Element("df" + "house_id"));
}

Related

Retrieve processing instructions using XDocument

I have an XML document containing processing instructions. I know that, with the XmlDocument class, you can use
var node = xmlDoc.SelectSingleNode("processing-instruction('xml-stylesheet')") as XmlProcessingInstruction;
but I want to use XDocument. How can I do this?
This is how I access an XML file's nodes with the XDocument class.
However, you'll have to be more specific on what you want to do with it.
XDocument doc = XDocument.Load("filepath");
var node = doc.Nodes().OfType<XElement>().SingleOrDefault(n => n.Name == "node name");
var node_value = node.Value;
var node_descendants = node.Descendants();
UPDATE:
As you may have noticed there's no SelectSingleNode in XDocument, in fact, to retrieve the node you want you'll have to fetch it from the corresponding ienumerable collection, or alternatively from the predefined FirstNode, NextNode, PreviousNode, LastNode, but you cannot apply any filters to those. Therefore the only ways to retrieve ProcessingInstruction nodes are
var pI_nodes = doc.Nodes().OfType<XProcessingInstruction>();
And
var pI_nodes = (from node in doc.Nodes()
where node.NodeType == System.Xml.XmlNodeType.ProcessingInstruction
select node);
If you expect to retrieve several ProcessingInstructions and need to filter these as well, the equivalent to the node name would the Target property
var filtered_pIs = pI_nodes_1.Where(pI => pI.Target == "xml-stylesheet");
And as a final reminder the value of the processing instruction is stored in the Data property.
string pI_value = filtered_pIs.First().Data
Here is one way:
var node = xDoc.Root.Nodes().OfType<XProcessingInstruction>().First();

Count ChildElements of the same name, inside an XML Element, with XDocument

I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?
You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();
You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.
How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;
If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();

Linq to XML dynamic XML Decendants

I'm parsing a lot of XML files using Linq to XML synatx, everything works when I try to access top level elements
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
model = (string)element.Element("MODEL"),
}).FirstOrDefault()
The problem occurs when I need to access lower level childs of that document I tried:
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
ddName = (string)element.Descendants("DD_NAME").Elements("name").First();
}).FirstOrDefault()
and
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot").Descendants("DD_NAME")
select new
{
ddName = (string)element.Element("name")
}).FirstOrDefault();
Sadly none of that works and i get same error "Sequence contains no elements". And one more thing sometimes the XML document contains those tags and sometimes not is something like this enough for handling this case?
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot").Descendants("DD_NAME")
select new
{
ddName = (string)element.Element("name") ?? "-"
}).FirstOrDefault();
Edit:
I don't think is possible to paste short version of XML that would be simple, so here's full version: http://pastebin.com/uDkP3rnR and for the code example:
XDocument prodcutDocument = XDocument.Load(this.ServerPATHData + file);
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
modelis = (string)element.Element("MODELIS"),
T2918_0 = (string)element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").First()
}).FirstOrDefault();
writeTxt.WriteLine("modelis: " + indexroot.modelis);
writeTxt.WriteLine("T2979_0" + indexroot.T2918_0);
In examining the sample XML that you posted on PasteBin, it appears to me that the elements that you mention appear only once. To access them, you can simply specify a path to each as follows:
XElement indexroot = document.Root.Element("indexroot");
XElement modelis = indexroot.Element("MODELIS");
XElement dd_dgs = indexroot.Element("dd_DARBINIS_GRAFIKAS_SPEC");
XElement voltageuv = dd_dgs.Element("VoltageUV");
string t2979_0 = (string)voltageuv.Element("T2979_0");
string t2861_60 = (string)voltageuv.Element("T2861_60");
string t2757_121 = (string)voltageuv.Element("T2757_121");
(Note that you may need to check for null if there is a chance that any of the elements you are trying to access may not be present. Without doing so, you'll encounter a NullReferenceException.)
Here is a snippet of the XML that you posted to give context to the above code:
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<PDB>
<indexroot>
<ed_BENDRA_MAKS_SUV_GALIA>1.45</ed_BENDRA_MAKS_SUV_GALIA>
<ed_BENDRA_MAKS_SROVE>6.48</ed_BENDRA_MAKS_SROVE>
<TIPAS>1</TIPAS>
<MODELIS>RIS 2500 HW EC 3.0</MODELIS>
<dd_DARBINIS_GRAFIKAS_SPEC>
<VoltageUV>
<T2979_0>229,42</T2979_0>
<T2861_60>227,98</T2861_60>
<T2757_121>228,97</T2757_121>
</VoltageUV>
<CurrentIA>
<T2979_0>2,56</T2979_0>
<T2861_60>2,63</T2861_60>
<T2757_121>2,72</T2757_121>
</CurrentIA>
</dd_DARBINIS_GRAFIKAS_SPEC>
</indexroot>
</PDB>
You can just change:
element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").First()
to this:
element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").FirstOrDefault() ?? "-"

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

c# XML Parsing separating innerxml from innertext

What I am trying to do is create ideally a nested List basically a 2d list, or a 2D array if that is better for this task, that would work as follows ID => 1 Name => Hickory without explicitly selecting the node.
I could use SelectNode (Woods/Wood) and then do something like node["ID"].InnerText but that would require that I know what the nodes name is.
Assume that this would read wood.xml even if there were 36 nodes instead of 7 and that I will never know the name of the nodes. I tried using outerxml/innerxml but that gives me too much information.
XmlDocument doc = new XmlDocument();
doc.Load("wood.xml");
//Here is wood.xml
/*<Woods><Wood><ID>1</ID><Name>Hickory</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood><Wood><ID>2</ID><Name>Soft Maple</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood><Wood><ID>3</ID><Name>Red Oak</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood></Woods>*/
XmlNode root = doc.FirstChild;
//Display the contents of the child nodes.
if (root.HasChildNodes)
{
for (int i=0; i<root.ChildNodes.Count; i++)
{
Console.WriteLine(root.ChildNodes[i].InnerXml);
Console.WriteLine();
}
Console.ReadKey();
}
That would allow me to basically create a wood "buffer" if you will so I can access these values elsewhere.
Sorry if I was unclear I want to essentially make this "abstract" for lack of a better word.
So that if I were someday to change the name of "Weight" to "HowHeavy" or if i were to add an additional element "NumberOfBranches" I would not have to hardcode the structure of the xml file.
Is this what you after ?
class Program
{
static void Main(string[] args)
{
string xml = #"<Woods><Wood><ID>1</ID><Name>Hickory</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood><Wood><ID>2</ID><Name>Soft Maple</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood><Wood><ID>3</ID><Name>Red Oak</Name><Weight>3</Weight><Thickness>4</Thickness><Density>5</Density><Purity>6</Purity><Age>7</Age></Wood></Woods>";
XDocument doc = XDocument.Parse(xml);
//Get your wood nodes and values in a list
List<Tuple<string,string>> list = doc.Descendants().Select(a=> new Tuple<string,string>(a.Name.LocalName,a.Value)).ToList();
// display the list
list.All(a => { Console.WriteLine(string.Format("Node name {0} , Node Value {1}", a.Item1, a.Item2)); return true; });
Console.Read();
}
}
You can use xmlDocument.SelectNodes("//child::node()")

Categories