I am having trouble while load xml file into Observable Collection.
My XML look like
<?xml version="1.0" encoding="utf-8"?>
<Stock>
<Assortment>
<Item>Адельфан - эзидрекс таб №10</Item>
<Quantity>89</Quantity>
<Price>3.0000</Price>
<Summ>267</Summ>
<Price1>3.0000</Price1>
<Summ1>267</Summ1>
<ValidDate>01.01.2031</ValidDate>
<Manufacturer>КРКА</Manufacturer>
</Assortment>
<Assortment>
<Item>Адельфан - эзидрекс таб №10</Item>
<Quantity>8</Quantity>
<Price>3.0000</Price>
<Summ>24</Summ>
<Price1>3.0000</Price1>
<Summ1>24</Summ1>
<ValidDate>01.01.2019</ValidDate>
<Manufacturer>КРКА</Manufacturer>
</Assortment>
</Stock>
And my code which I tried is this.
XDocument xml = XDocument.Load(filename);
foreach (XElement xe in xml.Elements("Assortment"))
{
_dummyCollection1.Add(new DummyClass1()
{
Наименование = xe.Element("Item").Value,
Количество = xe.Element("Quantity").Value,
Цена = xe.Element("Price").Value,
Сумма = xe.Element("Summ").Value,
Цена1 = xe.Element("Price1").Value,
Сумма1 = xe.Element("Summ1").Value,
Срокгодности = xe.Element("ValidDate").Value,
Производитель = xe.Element("Manufacturer").Value
});
}
BuyDataGrid.ItemsSource = _dummyCollection1;
I am getting empty DataGrid. How to pass xml in to Observable Collection?
I found how to do it, in case if someone need;
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes("Assortment");
foreach (XmlNode node in nodes)
{
_dummyCollection1.Add(new DummyClass1()
{
Наименование = node["Item"].InnerText,
Количество = node["Quantity"].InnerText,
Цена = node["Price"].InnerText,
Сумма = node["Summ"].InnerText,
Цена1 = node["Price1"].InnerText,
Сумма1 = node["Summ1"].InnerText,
Срокгодности = node["ValidDate"].InnerText,
Производитель = node["Manufacturer"].InnerText
});
}
BuyDataGrid.ItemsSource = _dummyCollection1;
Your class which you plan to use as an object for the ObservableCollection collection must implement INotifyPropertyChanged
this is my answer with an example how to populate model for dataGrid
example
Related
I am trying to retrieve data from an XML file and return the parsed data in a list. Depending on what I use to access the data (Element or Attributes) I either get null (in case of Element) or something I cannot decipher (in case of Attributes).
XML Looks like this:
<DATA_RESPONSE>
<HEADER>
<MSGID>IS20101P:091317125610:98::34:0</MSGID>
</HEADER>
<DATA>
<ROW ID='IS20101P' PE_NAME='APP-029' PE_ID='4' CODE='4829' DATA='5,1,500,1' />
<ROW ID='IS20101P' PE_NAME='APPS-029' PE_ID='4' CODE='4829' DATA='4,1,500,1' />
...
</DATA>
<SUMMARY>
</SUMMARY>
<ERRORS>
</ERRORS>
</DATA_RESPONSE>
I am using the following to get the data. I read the file and store XML in a string and call a method with this string as argument:
public static Hashtable GetIDSData(string sXMLString)
{
Hashtable result = new Hashtable();
result.Add("Success", false);
result.Add("ErrorMessage", "");
result.Add("ID", "");
result.Add("PE_NAME", "");
result.Add("PE_ID", "");
result.Add("CODE", "");
result.Add("DATA", "");
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID")?.ToString(),
PEName = Address.Attributes("PE_NAME")?.ToString(),
PEID = Address.Attributes("PE_ID")?.ToString(),
Code = Address.Attributes("CODE")?.ToString(),
Data = Address.Attributes("DATA")?.ToString(),
}).ToList();
foreach (var AddressInfo in AddressInfoList)
{
if (string.IsNullOrEmpty(AddressInfo.Code))
{
result["Success"] = false;
result["ErrorMessage"] = "Invalid Code; code is empty.";
}
else
{
result["Success"] = true;
result["ErrorMessage"] = "";
result["ID"] = AddressInfo.ID;
result["PE_NAME"] = AddressInfo.PEName;
result["PE_ID"] = AddressInfo.PEID;
result["CODE"] = AddressInfo.Code;
result["DATA"] = AddressInfo.Data;
}
}
return result;
}
In Linq section, if I use Address.Element("ID").Value, I get null returned.
There is no namespace used in XML.
First off, the GetIDSData() method does not compile as is, because at the line xmlDoc.InnerXml = sXMLString, xmlDoc has not been defined.
I'm assuming you want xmlDoc to be an XmlDocument loaded with the contents of the sXMLString parameter, so I'm changing that line to:
XmlDocument xmlDoc = new XmlDocument {InnerXml = sXMLString};
Also, your root variable is never used, so I removed it for clarity.
Now as for the main part of your question, given your current syntax, you are calling .ToString() on a collection of attributes, which is obviously not what you want. To fix this, when you're iterating the AddressInfoList, You want to fetch the attribute values like:
ID = Address.Attributes("ID")?.Single().Value
or
ID = address.Attribute("ID")?.Value
...rather than Address.Attributes("ID")?.ToString() as you have above.
You are not selecting values of attributes. In your code you are selecting attributes. Not sure what are you trying to achieve, but here is my modified version of your code that loads all elements into DataTable
public static DataTable GetIDSData(string sXMLString)
{
DataTable result = new DataTable();
result.Columns.Add("Success");
result.Columns.Add("ErrorMessage");
result.Columns.Add("ID");
result.Columns.Add("PE_NAME");
result.Columns.Add("PE_ID");
result.Columns.Add("CODE");
result.Columns.Add("DATA");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID").Select(i=>i.Value) ,
PEName = Address.Attributes("PE_NAME").Select(i=>i.Value),
PEID = Address.Attributes("PE_ID").Select(i=>i.Value),
Code = Address.Attributes("CODE").Select(i=>i.Value),
Data = Address.Attributes("DATA").Select(i=>i.Value),
}).ToList();
AddressInfoList.ForEach(e =>
{
e.Code.ToList().ForEach(c =>
{
DataRow row = result.NewRow();
if (!string.IsNullOrEmpty(c))
{
row["Success"] = true;
row["ErrorMessage"] = "";
row["ID"] = e.ID.First();
row["PE_NAME"] = e.PEName.First();
row["PE_ID"] = e.PEID.First();
row["CODE"] = e.Code.First();
row["DATA"] = e.Data.First();
}
else
{
row["Success"] = false;
row["ErrorMessage"] = "Invalid Code; code is empty.";
}
result.Rows.Add(row);
});});
result.Dump();
return result;
}
return result;
}
And this is the result that you will get in your datatable.
ID = Address.Attributes("ID")?.ToString(),
You want to use Attribute(name) (without s) instead:
ID = Address.Attributes("ID")?.Value,
i'm trying to read xml file and update all it's value my xml was
<adf>
<prospect>
<requestdate>2015-10-29 07-38-22</requestdate>
<id sequence="1" source="admin.ss.com">admin.ss.com</id>
<vehicle interest="buy" status="">
<id sequence="1" source=""></id>
<year></year>
<make></make>
<model>camry</model>
<vin></vin>
<stock></stock>
<trim></trim>
</vehicle>
<customer>
<contact primarycontact="1">
<name part="first">Jessica</name>
<name part="last">Sonntag</name>
<email>js#test.com</email>
<phone type="phone" time="day">555-585-5555</phone>
<address>
<street line="1"></street>
<city></city>
<regioncode></regioncode>
<postalcode></postalcode>
<country></country>
</address>
</contact>
<comments>Vehicle Year: 2011 Comments: </comments>
</customer>
<provider>
<name part="full">ST</name>
<service> Engine Marketing</service>
<phone>1-866-572-3952</phone>
</provider>
</prospect>
</adf>
so i select node like below
var items = (from item in xmlDoc.Descendants("requestdate")
select item).ToList();
then i can update only requestdata tag value so do i have to repeat same for all tags or is there any good way to accomplish this.
Regards
There is an easy way to do this. This one is a hidden gem. Most people may not know this. This feature came in VS2013 and it's called "Paste XML as Classes."
Save your xml (Ex: MyXml.XML)
Create a new Console project
Open the Xml in Visual studio
Copy All contents of the xml (Ctl+A, Ctl + C)
Add a new class to your project. You can give any name you like.
Go to Edit>Paste Special>Paste XML as classes.
Add another class to your project. Then add below two methods to that class.
public static string Serialise<T>(T serialisableObject)
{
var doc = new XmlDocument();
using (var stream = new StringWriter())
{
var settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlWriter xmlWriter = XmlWriter.Create(stream, settings);
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(xmlWriter, serialisableObject, ns);
doc.LoadXml(stream.ToString());
}
return doc.InnerXml;
}
public static T Deserialise<T>(string xml)
{
T list;
using (var reader = new StringReader(xml))
{
var serialiser = new XmlSerializer(typeof(T));
list = (T)serialiser.Deserialize(reader);
}
return list;
}
Then in your console applications Main method; add this.
var myObj = new adf();
myObj.prospect = new adfProspect();
myObj.prospect.customer = new adfProspectCustomer(){comments = "dgsrtetetete"};
//populate all fields.....
var xml = MySerializer.Serialise(myObj);
File.WriteAllText(#"C:\myNewXml.xml", xml);
That's it. Same way now you can deserialise an xml object in to your class.
Try the XmlSerializer class: https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer(v=vs.110).aspx If you serialize/deserialize the xml then up dating is trivial.
If you wanted to change every phone number to "0123456789" you could do something like:
var xDoc = XDocument.Load("document.xml");
var results = from phone in xDoc.Descendants("phone") select phone;
foreach (XElement result in results)
{
element.SetValue("0123456789");
}
i have came up with solution with support two extension method i'm iterating all nodes and update.(since my xml is not too big or complicated this one would be a good solution)
with help of these two extension methods
public static void IterateThroughAllNodes(this XmlDocument doc, Action<XmlNode> elementVisitor)
{
if (doc != null && elementVisitor != null)
{
foreach (XmlNode node in doc.ChildNodes)
{
DoIterateNode(node, elementVisitor);
}
}
}
public static void IterateThrough(this XmlNodeList nodes, Action<XmlNode> elementVisitor)
{
if (nodes != null && elementVisitor != null)
{
foreach (XmlNode node in nodes)
{
DoIterateNode(node, elementVisitor);
}
}
}
private static void DoIterateNode(XmlNode node, Action<XmlNode> elementVisitor)
{
elementVisitor(node);
foreach (XmlNode childNode in node.ChildNodes)
{
DoIterateNode(childNode, elementVisitor);
}
}
then i can update my xml nodes as below
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/xmlmail.xml"));
var email = new XmlEmail();
doc.IterateThroughAllNodes(
delegate(XmlNode node)
{
if (node.Name.Equals("requestdate"))
node.InnerText= email.RequestDate.ToLongDateString();
if (node.Name.Equals("vehicle"))
{
XmlNodeList childs = node.ChildNodes;
childs.IterateThrough(delegate(XmlNode vnode)
{
if (vnode.Name.Equals("id"))
vnode.InnerText= email.VehicleId.ToString();
if (vnode.Name.Equals("year"))
vnode.InnerText= email.Year.ToString();
if (vnode.Name.Equals("make"))
vnode.InnerText= email.Make;
if (vnode.Name.Equals("model"))
vnode.InnerText= email.Model;
if (vnode.Name.Equals("vin"))
vnode.InnerText= email.Vin;
if (vnode.Name.Equals("trim"))
vnode.InnerText = email.Trim;
});
}
if (node.Name.Equals("customer"))
{
XmlNodeList childs = node.ChildNodes;
childs.IterateThrough(delegate(XmlNode vnode)
{
if (vnode.Attributes != null && (vnode.Name.Equals("name") && vnode.Attributes["part"].Value.Equals("first")))
vnode.InnerText= email.FirstName;
if (vnode.Attributes != null && (vnode.Name.Equals("name") && vnode.Attributes["part"].Value.Equals("last")))
vnode.InnerText= email.LastName;
if (vnode.Name.Equals("email"))
vnode.InnerText= email.Email;
if (vnode.Name.Equals("phone"))
vnode.InnerText= email.Phone;
if (vnode.Name.Equals("comments"))
vnode.InnerText= email.Comments;
if (vnode.Name.Equals("address"))
{
XmlNodeList addresschilds = vnode.ChildNodes;
addresschilds.IterateThrough(delegate(XmlNode anode)
{
if (anode.Name.Equals("street"))
anode.InnerText= email.Street;
if (anode.Name.Equals("city"))
anode.InnerText= email.City;
if (anode.Name.Equals("phone"))
anode.InnerText= email.Phone;
if (anode.Name.Equals("regioncode"))
anode.InnerText= email.RegionCode;
if (anode.Name.Equals("postalcode"))
anode.InnerText= email.Postalode;
if (anode.Name.Equals("country"))
anode.InnerText= email.Country;
});
}
});
}
});
I have an xml file like this:
<servers>
<general name="1">
<service name="ser1"/>
<service name="ser2"/>
</general>
<general name="2">
<service name="ser1"/>
<service name="ser2"/>
</general>
</servers>
In my winform application, I have a treeview list with checkbox property set to true.What I am trying to achieve is that I am attempting to read this xml file and update both the parent and child node to this tree view.
What I have tried is:
XDocument doc = XDocument.Load(#"D:\\path.xml");
TreeNode node;
var gnrl = from general in doc.Descendants("general")
select new
{
parent = general.Attribute("name").Value,
child = general.Descendants("service")
};
//Loop through results
foreach (var general in gnrl)
{
// Add a root node.
node = dcselectview.Nodes.Add(String.Format(general.parent));
foreach (var ser in general.child)
{
// Add a node as a child of the previously added node.
node = node.Nodes.Add(String.Format(ser.Attribute("name").Value));
}
}
it reads the file and all details are updated but not in a proper way. rather it is shown as below:
Needed:
I want the parent element to be on top and down-right to it,the child elements. If possible, it would be nice if I dont have checkboxes for parent elements.
Any help would be really appreciated..
EDIT:
My code edited. Now I am getting as shown in new picture below:
I want the 2 black lines to be in same line,not as child node of another..
Do you want a hierarchical structure, like this?
If so, I recommend you to look at the Treeview:
http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.checkboxes.aspx
Try this you will get
XDocument doc = XDocument.Load(#"D:\\test.xml");
IEnumerable<XElement> Xele = doc.XPathSelectElements("//general");
foreach (XElement xe in Xele.Descendants())
{
//MessageBox.Show(xe.Attribute("name").Value);
dcselectview.Parent.Text =xe.Parent.Attribute("name").Value; // here parent value ----> name="1" and name="2"
dcselectview.Nodes.Add(xe.Attribute("name").Value); // ser1 ser2 ser1 ser2
}
Try this:
public static class TreeViewExtension
{
public static bool LoadNodesFromXML(this TreeView tv, string xml)
{
try
{
XDocument doc = XDocument.Parse(xml);
TreeNode rootNode = new TreeNode();
rootNode.Text = doc.Root.ToString().Split('>')[0] + ">";
rootNode.LoadTreeNodes(doc.Root.Elements());
tv.Nodes.Add(rootNode);
return true;
}
catch { return false; }
}
public static void LoadTreeNodes(this TreeNode parentNode, IEnumerable<XElement> elements)
{
foreach (var e in elements) {
TreeNode childNode = new TreeNode();
childNode.Text = e.ToString().Split('>')[0] + ">";
parentNode.Nodes.Add(childNode);
childNode.LoadTreeNodes(e.Elements());
}
}
}
//Usage:
var yourInputXMLString = "<servers><general name=\"1\"><service name=\"ser1\"/>" +
"<service name=\"ser2\"/></general><general name=\"2\">" +
"<service name=\"ser1\"/><service name=\"ser2\"/>" +
"</general></servers>";
treeView1.LoadNodesFromXML(yourInputXMLString);
You have to add parent as a node first
public static bool LoadNodesFromXML()
{
XDocument doc = XDocument.Load(#"D:\\path.xml");
var root = doc.Root;
var childenode = dcselectview.Nodes.Add(root.Attribute("Name").Value);
foreach (var xElement in root .Elements())
{
InsertNode(childenode, xElement);
}
}
private void InsertNode(TreeNode parent, XElement element)
{
var childenode = parent.Nodes.Add(element.Attribute("Name").Value);
if(element.Elements().Count() > 0)
foreach (var xElement in element.Elements())
{
InsertNode(childenode, xElement);
}
}
Thanks all for your help::But I have found another solution of my own::
XDocument doc = XDocument.Load(#"path\\test.xml");
// Add nodes to treeView1.
TreeNode pnode;
TreeNode cnode;
var gnrl = from general in doc.Descendants("general")
select new
{
parent = general.Attribute("name").Value,
child = general.Descendants("service")
};
//Loop through results
foreach (var general in gnrl)
{
// Add a root node.
pnode = treeview.Nodes.Add(String.Format(general.parent));
foreach (var ser in general.child)
{
// Add a node as a child of the previously added node.
cnode = pnode.Nodes.Add(String.Format(ser.Attribute("name").Value));
}
}
<?xml version="1.0" encoding="utf-8" ?>
<testcase>
<date>4/12/13</date>
<name>Mrinal</name>
<subject>xmlTest</subject>
</testcase>
I am trying to read the above xml using c#, But i get null exception in the try catch block can any body suggest the required change.
static void Main(string[] args)
{
XmlDocument xd = new XmlDocument();
xd.Load("C:/Users/mkumar/Documents/testcase.xml");
XmlNodeList nodelist = xd.SelectNodes("/testcase"); // get all <testcase> nodes
foreach (XmlNode node in nodelist) // for each <testcase> node
{
CommonLib.TestCase tc = new CommonLib.TestCase();
try
{
tc.name = node.Attributes.GetNamedItem("date").Value;
tc.date = node.Attributes.GetNamedItem("name").Value;
tc.sub = node.Attributes.GetNamedItem("subject").Value;
}
catch (Exception e)
{
MessageBox.Show("Error in reading XML", "xmlError", MessageBoxButtons.OK);
}
........
.....
The testcase element has no attributes. You should be looking to it's child nodes:
tc.name = node.SelectSingleNode("name").InnerText;
tc.date = node.SelectSingleNode("date").InnerText;
tc.sub = node.SelectSingleNode("subject").InnerText;
You might process all nodes like this:
var testCases = nodelist
.Cast<XmlNode>()
.Select(x => new CommonLib.TestCase()
{
name = x.SelectSingleNode("name").InnerText,
date = x.SelectSingleNode("date").InnerText,
sub = x.SelectSingleNode("subject").InnerText
})
.ToList();
You can use LINQ to XML to select all testcase elements from your xml and parse them to TestCase instances:
var xdoc = XDocument.Load("C:/Users/mkumar/Documents/testcase.xml");
var testCases = from tc in xdoc.Descendants("testcase")
select new CommonLib.TestCase {
date = (string)tc.Element("date"),
name = (string)tc.Element("name"),
sub= (string)tc.Element("subject")
};
BTW you have only one testcase element currently, which is root of XML file. So, you can do instead:
var tc = XElement.Load("C:/Users/mkumar/Documents/testcase.xml");
var testCase = new CommonLib.TestCase {
date = (string)tc.Element("date"),
name = (string)tc.Element("name"),
sub= (string)tc.Element("subject")
};
private static void Main(string[] args)
{
XmlDocument xd = new XmlDocument();
xd.Load("C:\\test1.xml");
XmlNodeList nodelist = xd.SelectNodes("/testcase"); // get all <testcase> nodes
foreach (XmlNode node in nodelist) // for each <testcase> node
{
try
{
var name = node.SelectSingleNode("date").InnerText;
var date = node.Attributes.GetNamedItem("name").Value;
var sub = node.Attributes.GetNamedItem("subject").Value;
}
catch (Exception e)
{
MessageBox.Show("Error in reading XML", "xmlError", MessageBoxButtons.OK);
}
}
This will work I have test it #Alex correct answer
You are trying to read attributes whereas date, name and subject are not attributes. They are subnodes.
your code should be like this
XmlDocument xd = new XmlDocument();
xd.Load("test.xml");
XmlNodeList nodelist = xd.SelectNodes("/testcase"); // get all <testcase> nodes
foreach (XmlNode node in nodelist) // for each <testcase> node
{
try
{
string name = node.SelectSingleNode("name").InnerText;
string date = node.SelectSingleNode("date").InnerText;
string sub = node.SelectSingleNode("subject").InnerText;
}
catch (Exception ex)
{
MessageBox.Show("Error in reading XML", "xmlError", MessageBoxButtons.OK);
}
}
Your Xml do not contain Attributes. date, name and subject - it's Child Nodes of the testcase Node.
Try this:
...
tc.name = node["name"].InnerText;
...
or this:
...
tc.name = node.SelectSingleNode("name").InnerText;
...
I am trying to serialize and insert a new created object into a specific child node of an XDocument. I've managed to accomplish this but my code seems smelly.
How can I test against a higher nodes attribute value without chaining through the Parent property like I have done below?
XDocument xdoc = XDocument.Load(path);
var elements = (
from doc in xdoc.Descendants("Unit")
where doc.Parent.Parent.Attribute("name").Value == _UnitTypeName &&
doc.Parent.Parent.Parent.Parent.Attribute("name").Value == _UnitCategoryN
doc.Parent.Parent.Parent.Parent.Parent.Parent.Attribute("name").Value ==
select doc
);
foreach (var element in elements)
{
Unit unit =
new Unit
{
Armour = _Armour,
Attacks = _Attacks,
BallisticSkill = _BallisticSkill,
Composition = _Composition,
DedicatedTransport = _DedicatedTransport,
Initiative = _Initiative,
Leadership = _Leadership,
Options = _Options,
SaveThrow = _SaveThrow,
SpecialRules = _SpecialRules,
Strength = _Strength,
Toughness = _Toughness,
UnitName = _UnitName,
Weapons = _Weapons,
WeaponSkill = _WeaponSkill,
Wounds = _Wounds,
Points = _Points
};
XmlSerializer serializer = new XmlSerializer(typeof(Unit));
using (var stream = new MemoryStream())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(stream, unit, ns);
stream.Position = 0;
//using (XmlReader reader = XmlReader.Create(stream))
//{
// XElement xe = XElement.Load(reader);
XElement xe = XElement.Load(#"C:\Test\tempfile.xml"); // For some reason loading via MemoryStream messes with xml formatting
element.AddBeforeSelf(xe);
//}
}
break;
}
xdoc.Save(path);
This is the structure of the XML Document:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfArmy>
<Army name="Tyranid">
<unit-category>
<UnitCategory name="Troops">
<unit-type>
<UnitType name="Infantry">
<unit>
<Unit points="5" name="Hornmagant" composition="20" weapon-skill="3" ballistic-skill="100" strength="3" toughness="4" wounds="1" initiative="3" attacks="3" leadership="5" saving-throw="6+" armour="Chitin" weapons="Many" special-rules="None" dedicated-transport="No" options="8">
<Amount>0</Amount>
</Unit>
<Unit points="5" name="Termagant" composition="20" weapon-skill="3" ballistic-skill="100" strength="3" toughness="4" wounds="1" initiative="3" attacks="3" leadership="5" saving-throw="6+" armour="Chitin" weapons="Many" special-rules="None" dedicated-transport="No" options="8">
<Amount>0</Amount>
</Unit>
</unit>
</UnitType>
</unit-type>
</UnitCategory>
</unit-category>
</Army>
</ArrayOfArmy>
You can use compound from clauses, which is similar to using nested foreach loops:
var elements = (
from army in xdoc.Descendants("Army")
where army.Attribute("name").Value == _ArmyName
from unitCategory in army.Descendants("UnitCategory")
where unitCategory.Attribute("name").Value == _UnitCategoryName
from unitType in unitCategory.Descendants("UnitType")
where unitType.Attribute("name").Value == _UnitTypeName
from unit in unitType.Descendants("Unit")
select unit
);
Note: the method syntax equivalent is SelectMany().