How to get XML Attribute and Element with One Linq Query? - c#

I have an XML document that looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<searchlayers>
<searchlayer whereClause="ProjectNumber=a">Herbicide</searchlayer>
<searchlayer whereClause="ProjectNumber=b">Herbicide - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=c">miscellaneous</searchlayer>
<searchlayer whereClause="ProjectNumber=d">miscellaneous - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=e">Regrowth Control</searchlayer>
<searchlayer whereClause="ProjectNumber=f">Regrowth Control - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=g">Tree Removal</searchlayer>
<searchlayer whereClause="ProjectNumber=h">Tree Removal - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=i">Trimming</searchlayer>
<searchlayer whereClause="ProjectNumber=j">Trimming - Point</searchlayer>
</searchlayers>
</configuration>
Is it possible to write one single Linq statement to get each of the element (e.g. Herbicide, miscellaneous, Regrowth Control... etc) with its matching whereClause (e.g. for Herbicide, the where clause would be "ProjectNumber=a")?
I can write two statements separately, one to get the elements, one to get the attributes, but it would be nice to write just one Linq statement that gets both at the same time.
Thanks.

Yes it is possible. But there are many possible data structure can be used to store list of 2 values pair, here is one example using Tuple :
XDocument doc = XDocument.Load("path_to_xml_file.xml");
List<Tuple<string, string>> result =
doc.Root
.Descendants("searchlayer")
.Select(o => Tuple.Create((string) o, (string) o.Attribute("whereClause")))
.ToList();

You can create a set of anonymous objects as follows:
var result = root.Element("searchlayers")
.Elements("searchlayer")
.Select(i =>
new {attribute = i.Attribute("whereClause").Value,
value = i.Value});
This will give a set of records, where the attributes are paired with the element values.
If you want this in query syntax, it looks like this:
var result = from el in root.Elements("searchlayers").Elements("searchlayer")
select new {attribute = el.Attribute("whereClause").Value,
value = el.Value};

You can use this to to select all elements matching Herbicide whose whereClause matches ProjectNumber=a
IEnumerable<XElement> result =
from el in doc.Elements("Herbicide")
where (string)el.Attribute("whereClause") == "ProjectNumber=a"
select el;
Another alternative would be:
var result = doc.Descendants()
.Where(e => e.Attribute("ProjectNumber=a") != null)
.ToList();
Which should provide you every element whose whereClause equals "ProjectNumber=a".

You can use the Attributes property to get the attribute from the XML node, along with the InnerText, like so:
XmlDocument doc = new XmlDocument();
doc.LoadXml(yourxml);
XmlNodeList xlist = doc.GetElementsByTagName("searchlayer");
for(int i=0;i<xlist.Count;i++)
{
Console.WriteLine(xlist[i].InnerText + " " + xlist[i].Attributes["whereClause"].Value);
}
If you must use LINQ, you could use XDocument and then return anonymous class objects consisting of the attribute and text, like so:
XDocument xdoc = XDocument.Load(yourxmlfile);
var result = xdoc.Root.Elements("searchlayers").Elements("searchlayers").Select(x => new {attr = x.Attribute("whereClause").Value, txt = x.Value});
foreach (var r in result)
{
Console.WriteLine(r.attr + " " + r.txt);
}

Related

Concatenating inner text in an XPath array

I have some XML that looks like this:
<root>
<instructions type="array">
<item type="string">Cannot do business directly with consumers</item>
<item type="string">Cannot marry a martian</item>
</instructions>
</root>
and an instructions variable that looks like this:
var instructions = myXDocument.XPathSelectElements("/root/instructions").Nodes();
I am attempting to concatenate all of the item strings, thusly:
"Cannot do business directly with consumers, Cannot marry a martian"
and my attempt so far is
instructions.Select(x => x.[[What do I put here?]]).Aggregate((i,j) => i + ", " + j)
but having trouble figuring out how to get the inner text from each node in my lambda expression. x.ToString() yields "<item type="string">Cannot do business directly with consumers</item>"
Using the same approach, you can simply replace Nodes() with Elements(), and then access the Value property of the returned XElements to get the inner texts :
var instructions = myXDocument.XPathSelectElements("/root/instructions").Elements();
var result = instructions.Select(x => x.Value).Aggregate((i,j) => i + ", " + j);
I know this is not the lambda expression which you are looking for but this is way around to get the resultant output.
string result;
XElement root = XElement.Load(SomeXML);
root.Element("root").Elements("instructions").Elements("item").All<XElement>(xe =>
{
result = result + xe.Attribute("type").Value),
return true;
});
Try this
var xml = "<root><instructions type=\"array\"><item type=\"string\">Cannot do business directly with consumers</item><item type=\"string\">Cannot marry a martian</item></instructions></root>";
var document = XDocument.Parse(xml);
var result = string.Join(", ", document.Descendants("instructions").Elements("item").Select(x=>x.Value));
Output:
Cannot do business directly with consumers, Cannot marry a martian

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();

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# Querying an XML Document

Good Day,
I am trying to query an XML document and have the following query:
XElement root = XElement.Load(#"..\..\Data.xml");
var entries = root.Descendants()
.Where(x => x.Name.LocalName == "Entry")
.ToList();
Console.WriteLine("There are {0} nodes...", entries.Count());
foreach (XElement v in entries)
{
Console.WriteLine(v.Value);
}
and this code works because it pulls the correct number of Entry nodes. The Entry
nodes look like:
<?xml version="1.0" encoding="UTF-8"?>
<Database xmlns="http://www.someurl.org/schemas">
<InfoFromRecord>
<BaseData>
<Entry>
<Date>2006-03-08</Date>XD
<Time>09:20:00</Time>
<EnteredBy>DNS</EnteredBy>
<TextEntry>Record 1</TextEntry>
</Entry>
<Entry>
<Date>2006-03-08</Date>
<Time>09:33:00</Time>
<EnteredBy>MW</EnteredBy>
<TextEntry>Record 2</TextEntry>
</Entry>
<Entry>
<Date>2006-03-08</Date>
<Time>08:58:00</Time>
<EnteredBy>BH</EnteredBy>
<TextEntry>Record 3</TextEntry>
</Entry>
</BaseData>
</InfoFromRecord>
</Database>
The problem is, I want to extract only the Date and Time, not all four fields.
Let's assume your entire XML file looks like this for a clear example:
<Entries>
<Entry>
<Date>2006-03-08</Date>
<Time>09:33:00</Time>
<EnteredBy>XX</EnteredBy>
<TextEntry>Test Data</TextEntry>
</Entry>
</Entries>
You could then do something like this:
var document = XDocument.Load(#"..\..\Data.xml");
var dateAndTimes =
from d in document.Root.Descendants("Entry")
select new
{
Date = d.Element("Date").Value,
Time = d.Element("Time").Value
};
From there, the dateAndTimes type will select an anonymous type of the Date and Time. You can change the anonymous type to be your own type, or something else.
EDIT: The problem is your xmlns. Change your code like so:
XNamespace namespc = "http://www.someurl.org/schemas";
var document = XDocument.Parse(xml);
var dateAndTimes =
from d in document.Root.Descendants(namespc + "Entry")
select new
{
Date = d.Element(namespc + "Date").Value,
Time = d.Element(namespc + "Time").Value
};
I haven't had a chance to try it but something like the following may give you what you are looking for
var entries = from i in root.Descendants()
where Name=='entry'
let date = i.Element('Date').Value
let time = i.ELement('Time').Value
select new Tuple<string,string>(date,time);
foreach (XElement v in entries)
{
Console.WriteLine(v.Element("Date").Value);
Console.WriteLine(v.Element("Time").Value);
}
Do not forget that Descendants finds children at any level, i.e. children, grand-children, etc where Elements find only direct child. So i guess Elements is the safe option in most of the cases.
EDIT : After seeing the XML
You need to include the Namspace also when getting the data
XNamespace ns = "http://www.someurl.org/schemas";
var entries = elm.Descendants().Where(x => x.Name.LocalName == "Entry").ToList();
foreach (XElement v in entries)
{
Console.WriteLine(v.Element(ns+"Date").Value);
Console.WriteLine(v.Element(ns+"Time").Value);
}
IEnumerable<XElement> de = from el in xdoc.Descendants() select el;
foreach (XElement el in de)
{
if (string.Equals(el.Name.ToString(), "movie",
`StringComparison.InvariantCultureIgnoreCase))`enter code here`
StringComparison.InvariantCultureIgnoreCase))
{
date(el);
}

XML Lambda query in C#

I have an XmlDocument object and xml in the format:
<?xml version="1.0" encoding="utf-8"?>
<S xmlns="http://server.com/DAAPI">
<TIMESTAMP>2010-08-16 17:25:45.633</TIMESTAMP>
<MY_GROUP>
<GROUP>1 </GROUP>
<NAME>Amsterdam</NAME>
....
</MY_GROUP>
<MY_GROUP>
<GROUP>2 </GROUP>
<NAME>Ireland</NAME>
....
</MY_GROUP>
<MY_GROUP>
<GROUP>3 </GROUP>
<NAME>UK</NAME>
....
</MY_GROUP>
Using a Lambda expression (or Linq To XML if it's more appropriate) on the XmlDocument object how can i do the following:
get the text of a specific element, say the text of NAME where GROUP = 1
the value of the first occurance of the element "NAME"
Thanks a lot
Assuming you mean XDocument rather than XmlDcoument:
First question:
XNamespace ns = "http://server.com/DAAPI";
string text = (from my_group in doc.Elements(ns + "MY_GROUP")
where (int) my_group.Element(ns + "GROUP") == 1
select (string) my_group.Element(ns + "NAME")).First();
I didn't really understand the second question... what do yuo mean by "contains an element of that name"? Which name? And if you're checking for NAME being equal to a give name, wouldn't you already know that name? Did you perhaps mean the value of GROUP for a specific name? If so, it's easy:
XNamespace ns = "http://server.com/DAAPI";
int group = (from my_group in doc.Elements(ns + "MY_GROUP")
where (string) my_group.Element(ns + "NAME")
select (int) my_group.Element(ns + "GROUP")).First();
Both of these queries assume that the values do exist, and that each MY_GROUP element has a GROUP and NAME subelement. Please let us know if that's not the case.
I have used Linq to XML.
string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?><S xmlns=\"http://server.com/DAAPI\"><TIMESTAMP>2010-08-16 17:25:45.633</TIMESTAMP><MY_GROUP><GROUP>1 </GROUP><NAME>Amsterdam</NAME>....</MY_GROUP><MY_GROUP><GROUP>2 </GROUP><NAME>Ireland</NAME>....</MY_GROUP><MY_GROUP><GROUP>3 </GROUP><NAME>UK</NAME>....</MY_GROUP></S>";
var doc = XDocument.Parse(input);
XNamespace ns = "http://server.com/DAAPI";
//The first question
var name = (from elem in doc.Root.Elements(ns + "MY_GROUP")
where elem.Element(ns + "GROUP") != null //Checks whether the element actually exists - if you KNOW it does then it can be removed
&& (int)elem.Element(ns + "GROUP") == 1 //This could fail if not an integer - insure it is if nessasary
select (string)elem.Element(ns + "NAME")).SingleOrDefault();
I understood only your first question. Here you are for the first:
var xmlSource = myGroup.Load(#"../../MyGroup.xml");
var q = from c in xmlSource.myGroup
where c.group = 1
select c.name;

Categories