use LINQ on XmlNodeList - c#

<X version="1.0">
<Y id="abc" abv="a"/>
<Y id="edf" abv="e"/>
</X>
I want to select the node whose id is "abc", and return its abv "a".
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlNodeList list = doc.SelectNodes("X/Y");
var node = list.Cast<XmlNode>().Where(node => node["id"].InnerText == "abc")
.Select(x=>x["abv"].InnerText);
But it does't work, node["id"].InnerText is always "". Can you point out where is a problem?
Thanks a lot

Aside from the fact what your code snippet wouldn't be compiled because of non-unique node variable (first outside of linq query and second in "where" method lambda), you have also missed Attributes in your query.
It should be something like
var node = list.Cast<XmlNode>()
.Where(n => n.Attributes["id"].InnerText == "abc")
.Select(x => x.Attributes["abv"].InnerText);

The InnerText for a node is the text that appears between <node> and </node>. So for, eg <Y attributes /> there is no inner text.
You need to use node => node.Attributes["id"].Value == "abc"

Just cast XmlNodeList to List, like that:
List<XmlNode> list = new List<XmlNode>();
foreach(XmlNode a in xmlNodeList)
{
list.Add(a);
}
list.OrderBy((element) => element.ChildNodes[0].InnerText);

Related

Retrieve nodes from XML belonging to a specific node

I have a XML like this:
<ITEM>
<RACES>
<TAB>
<NUMBER>1</NUMBER>
<A></A>
<B></B>
</TAB>
<TAB>
<NUMBER>2</NUMBER>
<A></A>
<B></B>
</TAB>
</RACES>
</ITEM>
is it possible to retrieve as XmlNodeList all the As and Bs nodes that belong to only TAB with NUMBER 1?
I use the following codes, but it gives me of course 2 nodes. I want only 1 node :
XmlNodeList xnList = xml.SelectNodes("/ITEM/RACES/TAB/A");
You can do xmlDocument.SelectNodes(expression)
where if you need both nodes A & B
expression = #"//TAB[NUMBER=1]/A|//TAB[NUMBER=1]/B"
if you need only A node seperately
expression = #"//TAB[NUMBER=1]/A"
if you need only B node seperately
expression = #"//TAB[NUMBER=1]/B"
Try as below:
XmlNodeList xnList = xml.SelectNodes("/RACES/TAB");
foreach (XmlNode xn in xnList)
{
int num = xn["NUMBER"].InnerText;
if(num==1)
{
Console.WriteLine("Nodes: {0} {1}", xn["A"], xn["B"]);
}
}
I strongly recommend Linq to Xml. You can knock it out in one statement:
var nodes_A_and_B = XDocument.Parse(xml)
.Descendants("TAB")
.Where(t => t.Element("NUMBER").Value == "1")
.Select(t => new
{
A = t.Element("A"),
B = t.Element("B")
});
it will be return what you need
Happy coding
var items = XElement.Parse(xmlelemet)
.Elements("RACES")
.Elements("TAB")
.Where(n => n.Attribute("NUMBER").Value == 1)
.Elements();

Extracting XML values from two nodes

I want to extract the value in the moduleId attibute and the value from the Field node. For example, in this first node I want to extract the 447 in the moduleId and the 124694 from the Field node. I have the XML loaded in an XDocument. The end result will be a Tuple where the first item is the value from the moduleId attribute and the second item is the value from the Field node. Is there a way I can do this using one XLinq statement?
As a bonus...I only want to do it for nodes where the guid = "07a188d3-3f8c-4832-8118-f3353cdd1b73". This part I can probably figure out if someone can tell me how to extract information from two nodes, but a bonus would be to add the WHERE clause in there for me :)
<Records>
<Record moduleId="447">
<Field guid="07a188d3-3f8c-4832-8118-f3353cdd1b73">124694</Field>
</Record>
<Record moduleId="447">
<Field guid="07a188d3-3f8c-4832-8118-f3353cdd1b73">124699</Field>
</Record>
<Records>
I have gotten as far as extracting the Field value using this...
IEnumerable<string> c = from p in sourceDocument.Descendants("Field")
where p.Attribute("guid").Value == "07a188d3-3f8c-4832-8118-f3353cdd1b73"
select p.Value;
But I have no idea how to get information from both the Record node and the Field node.
Give this a try:
var doc = XDocument.Parse(xml);
var r = doc.Descendants("Record")
.Where(n => n.Element("Field").Attribute("guid").Value == "07a188d3-3f8c-4832-8118-f3353cdd1b73")
.Select(n => new { ModuleId = n.Attribute("moduleId").Value, Field = n.Element("Field").Value });
var a = r.ToArray();
Here is a solution that uses LINQ Query Syntax:
XDocument document = XDocument.Parse(xml);
var query =
from el in document.Root.Elements("Record")
where (string)el.Element("Field").Attribute("guid") ==
"07a188d3-3f8c-4832-8118-f3353cdd1b73"
select new
{
ModuleId = (string)el.Attribute("moduleId"),
Field = (string)el.Element("Field")
};
foreach (var item in query)
{
Console.WriteLine
("ModuleId:[{0}]\nField:[{1}]\n--",
item.ModuleId,
item.Field);
}

Find element with specific attribute in xml?

I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)

remove sections of XML document with Linq

How would i using Linq remove all section where their element contains parameter with {} ? In my example i want to remove section with {SecName1}
Source document:
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>Test Layout1</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
<ReceiptLayout>
<Name>{SecName1}</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
</ReceiptLayoutMaintenanceRequest>
Wanted output
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>AAA</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
thanks
This removes any ReceiptLayout node which has a child Name that starts and ends with brackets and produces your desired output:
XDocument doc = XDocument.Load(#"test.xml"); //load xml
var nodesToRemove = doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.ToList();
foreach (var node in nodesToRemove)
node.Remove();
This can be shortened into one Linq statement, personally I prefer to keep Linq query and modification (removal) separate though:
doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.Remove();
var doc = XDocument.Parse(xml);
doc.Descendants()
.Where(n => !n.HasElements && Regex.IsMatch(n.Value, "[{].*?[}]"))
.Select(n=>n.Parent) // because you want to remove the section not the node
.Remove();
xml = doc.ToString();
A variant of BrokenGlass code using the let keyword
var doc = XDocument.Load(#"test.xml");
var list = from p in doc.Descendants("ReceiptLayout")
let q = p.Element("Name")
let r = q != null ? q.Value : string.Empty
where r.StartsWith("{") && r.EndsWith("}")
select p;
list.Remove();
This is "premature optimization" :-) I "cache" the p.Element("Name").Value. Ah... And I check if really there is a Name Element so that everything doesn't crash if there isn't one :-)

Why isn't this XElement query working on my xml

My xml looks like:
<nodes>
<node name="somekey">
<item name="subject">blah</item>
<item name="body">body</item>
</node>
</nodes>
And my code so far is:
XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath(String.Format("~/files/{0}/text.xml", "en")));
if (doc != null)
{
XElement element = doc.Elements().Where(e => e.Elements().Any() && e.Attribute("name").Value == "someKey").First();
}
I am getting an error saying:
Sequence contains no elements
Is my query wrong?
I stepped through the code, and it errors out on the line with XElement..
You want something like this:
var element = doc.Descendants("node").Where(x => x.Attribute("name") != null && x.Attribute("name").Value == "somekey").FirstOrDefault();
Edit: Edited to grab first element from result;
You could also use:
var element = doc.Elements()
.Elements()
.Where(e => (e.Elements().Any()
&& e.Attribute("name").Value == "somekey"))
.First();
Explanation:
The doc.Elements() grabs the root element, which is nodes. Then the .Elements() selects the child elements of that, which is just one, node. The .Where() is then performed on that nodeset, which is what you want. The lambda selects those elements that have child elements, and also have an attribute "name" with value "somekey".
Your original code was not getting the Child-of-Child-elements. Hence the original result set was empty.
You could also do this with .Descendants() but that feels a little sloppy and loose, to me.

Categories

Resources