How to access a specific attribute using LINQ to XML - c#

I wish to access some specific attribute (Tag name) i an XML file, and place them in a list but i cant get i right. What am I doing wrong??
The list should look like this:
Tag_1
Tag_2
Tag_3
Code:
XElement xelement = XElement.Load("C:/...../Desktop/Testxml.xml");
var tagNames = from tag in xelement.Elements("tagGroup")
select tag.Attribute("name").Value;
foreach (XElement xEle in tagNames)
{
//....
}
Here is the XML file:
<configuration>
<logGroup>
<group name="cpm Log 1h 1y Avg" logInterval="* 1 * * * ?" />
<group name="cpm Log 1d 2y Avg" logInterval="* 10 * * * ?" />
</logGroup>
<tagGroup>
<tag name="Tag_1">
<property name="VALUE">
<logGroup name="cpm Log 1h 1y Avg" />
<logGroup name="cpm Log 1d 2y Avg" />
</property>
</tag>
<tag name="Tag_2">
<property name="VALUE">
<logGroup name="cpm Log 1h 1y Avg" />
<logGroup name="cpm Log 1d 2y Avg" />
</property>
</tag>
<tag name="Tag_3">
<property name="VALUE">
<logGroup name="cpm Log 1h 1y Avg" />
<logGroup name="cpm Log 1d 2y Avg" />
</property>
</tag>
</tagGroup>
</configuration>

just change your linq query for:
var tagNames = from tag in xelement.Elements("tagGroup").Elements("tag")
select tag.Attribute("name").Value;
then tagName is an IEnumerable<string> and you can iterating like this:
foreach (var element in tagNames)
{
//element is a string
}

Your code enumerates through the elements called tagGroup, and then attempts to get the attribute in called name. There is no attribute in tagGroup. In fact tagGroup has descendants two levels deep called logGroup. It's logGroup that has the name attribute.
This code will not work:
XElement xelement = XElement.Load("C:/...../Desktop/Testxml.xml");
var tagNames = from tag in xelement.Elements("tagGroup")
select tag.Attribute("name").Value;
What you need is something like
var tagGroups = xelement.Descendants("tag").Select(x => x.Attribute("name")).ToList();
Or if you want to the others:
var tagGroups = xelement.Descendants("logGroup").Select(x => x.Attribute("name")).ToList();
var tagGroups = xelement.Elements("tagGroup").ToList();
var logGroups = tagGroups.SelectMany (g => g.Descendants("logGroup")).ToList();
var logAttributes = tagGroups.SelectMany (g => g.Descendants("logGroup").Select(x => x.Attribute("name"))).ToList();

Something Like :
var tagNames = xe.Element("tagGroup").Elements("tag").Select(a => a.Attribute("name").Value);
foreach (var xEle in tagNames)
{
Console.WriteLine(xEle);
}

Try this...
var tagNames = from tag in xelement.Elements("tagGroup").Elements("tag")
select tag.Attribute("name").Value;
or
var tagNames = xelement.Elements("tagGroup")
.Elements("tag")
.Attribute("name").Value;

Related

Get specific values from XML with ID values

I have next XML file:
<root>
<location id='IBM'>
<property name='locale' value='en-EN' />
<property name='path' value='c:\program files\IBM' />
<property name='option' value='licence' />
<package kind='offering' name='IBM tools'>
<property name='cic.name' value='IBM Studio'/>
<property name='cic.version' value='13.4'/>
</package>
</location>
<location id='Microsoft'>
<property name='locale' value='en-EN' />
<property name='path' value='c:\program files\MS' />
<property name='option' value='licence' />
<package kind='offering' name='Microsoft'>
<property name='cic.name' value='Windows XP'/>
<property name='cic.version' value='10.3.2'/>
</package>
</location>
</root>
How I can get this values from cic.name and cic.version inside that XML structure:
IBM Studio
13.4
Windows Xp
10.3.2
I have tried this
XElement roots = XElement.Load(#"C:\test.xml");
foreach (var i in roots.Descendants("location"))
{
Console.WriteLine(i.Attribute("id").Value);
}
But I get only:
IBM
Microsoft
Thank you!
Here is the solution
var doc = XDocument.Load(yourXmlFilePath);
var packages = doc.Descendants("package")
.Select(p => new
{
Name = p.Elements("property")
.SingleOrDefault(i => i.Attribute("name")?.Value == "cic.name")
?.Attribute("value")
?.Value,
Version = p.Elements("property")
.SingleOrDefault(i => i.Attribute("name")?.Value == "cic.version")
?.Attribute("value")
?.Value
}).ToList();
var result = names.Select(i => string.Format("{0} {1}", i.Name, i.Version)).ToList();
//result: "IBM Studio 13.4"
// "Windows XP 10.3.2"
Your code only looks for id attributes on location elements.
If you want the cic.name and cic.version values, you'll have to include those in your query instead.
var results =
from package in doc.Descendants("package")
select new
{
Name = (string) package.Elements("property")
.Where(x => (string) x.Attribute("name") == "cic.name")
.Attributes("value")
.Single(),
Version = (string) package.Elements("property")
.Where(x => (string) x.Attribute("name") == "cic.version")
.Attributes("value")
.Single(),
};
See this fiddle for a demo.
Try something like this
foreach (var Locations in roots.Descendants("location"))
{
foreach (var item in Locations.Descendants("package"))
{
Console.WriteLine(item.Attribute("id").Value);
}
}

Split xml into files - method takes less memory

I need to split my XML into files.
This is structure of my sample XML:
<Data Code="L6POS1">
<Lots RowVersion="464775">
<Lot Id="5" Quantity="10068.0000" GUID="AA616D3D-F442-6AEE-0BAB-1D13F6961C2A" />
<Lot Id="99" Quantity="0.0000" GUID="24A9C957-EC98-85D5-8F96-0120F6E8A572" />
<Lot Id="101" Quantity="0.0000" GUID="124D17A2-1568-DB02-4327-4669FE00F741" />
<Lot Id="103" Quantity="0.0000" GUID="DD1730FF-27CF-1269-7AC2-3152CB6FDC46" />
<Lot Id="105" Quantity="0.0000" GUID="1F25378F-30D4-E4E0-9939-1E9E69C806C1" />
<Lot Id="188" Quantity="0.0000" GUID="2E860029-29B3-54C2-B8D1-0C6ABDA42DFF" />
<Lot Id="189" Quantity="0.0000" GUID="D3C58850-BC23-E8DE-A919-09CCB3F8A1D3" />
</Lots>
Expected result: FirstFile:
<Data Code="L6POS1">
<Lots RowVersion="464775">
<Lot Id="5" Quantity="10068.0000" GUID="AA616D3D-F442-6AEE-0BAB-1D13F6961C2A" />
<Lot Id="99" Quantity="0.0000" GUID="24A9C957-EC98-85D5-8F96-0120F6E8A572" />
<Lot Id="101" Quantity="0.0000" GUID="124D17A2-1568-DB02-4327-4669FE00F741" />
<Lot Id="103" Quantity="0.0000" GUID="DD1730FF-27CF-1269-7AC2-3152CB6FDC46" />
</Lots>
</Data>
And SecondFile:
<Data Code="L6POS1">
<Lots RowVersion="464775">
<Lot Id="105" Quantity="0.0000" GUID="1F25378F-30D4-E4E0-9939-1E9E69C806C1" />
<Lot Id="188" Quantity="0.0000" GUID="2E860029-29B3-54C2-B8D1-0C6ABDA42DFF" />
<Lot Id="189" Quantity="0.0000" GUID="D3C58850-BC23-E8DE-A919-09CCB3F8A1D3" />
</Lots>
</Data>
Actually I'm using:
private IEnumerable<XElement> CreateXMLPackagesByType(string syncEntityName, XElement root)
{
var xmlList = new List<XElement>();
IEnumerable<XElement> childNodes = root.Elements();
var childsCount = childNodes.Count();
var skip = 0;
var take = ConfigurationService.MaxImportPackageSize;
var rootAttributes = root.Attributes();
XElement rootWithoutDescendants;
while (skip < childsCount)
{
rootWithoutDescendants = new XElement(root.Name);
rootWithoutDescendants.Add(rootAttributes);
var elems = childNodes.Skip(skip).Take(take);
skip += take;
xmlList.Add(CreatePackage(rootWithoutDescendants, elems));
}
return xmlList;
}
private XElement CreatePackage(XElement type, IEnumerable<XElement> elems)
{
type.Add(elems);
var root = new XElement("Data", type);
root.Add(new XAttribute("Code", ConfigurationService.Code));
return root;
}
Unfortunately, in this way a get OutOfMemoryException with bigger XML files on older hardware. It is better way to split XElement?
Previous comments suggesting to use a SAX parser are correct -- that way you get each event (element, etc) one at a time, and you don't have to keep anything around afterwards.
If you're absolutely certain that your data is as neatly broken into lines as your example, a quick-and-dirty method would be to not even parse, but just read a line at a time. Handle the first two, then break up the rest how you want, then handle the last two. But be really sure (in other words, check) that every <Lot> element takes up exactly one physical line; as you probably already know, there's no reason they have to be that way in XML in general.

insert element after selected node

Am trying to add a new element called entity after the last entity but it keeps adding it inside the selected entity. To understand better this is my xml sample.
<Root>
<Class Name="ECMInstruction" Style="Top">
<Entity Id="1" Name="DocumentInformation" />
<Entity Id="2" Name="CustomerInformation" />
<Property Id="1" Name="DocumentTitle">
</Property>
<Property Id="2" Name="DateCreated">
<Lists>
<ListName>ws_Users</ListName>
<ListName>dfdfdfd</ListName>
</Lists>
</Property>
<Property Id="3" Name="Deadline">
</Property>
</Class>
</Root>
This is how it looks like after is inserted. I've tried using insertAfter but it gives me error.
<Entity Id="1" Name="DocumentInformation">
<Entity Id="2" Name="sds" />
</Entity>
The code:
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("sample.xml");
XmlNodeList cnode = xmldoc.DocumentElement.SelectNodes("//Class[#Name='" + CurrentClass + "']/Entity");
foreach (XmlNode c in cnode)
{
int value = 0;
String entitycount = cnode.Count.ToString();
int.TryParse(entitycount, out value);
value = value + 1;
XmlElement root = xmldoc.CreateElement("Entity");
root.SetAttribute("Id", value.ToString());
root.SetAttribute("Name", EntityNametxt.Text);
c.AppendChild(root);
xmldoc.Save("sample.xml");
}
"I've tried using insertAfter but it gives me error. "
As per documentation, InsertAfter() should be called on parent node of referenced XmlNode (the 2nd argument of the method), otherwise ArgumentException will be thrown :
//instead of this : c.AppendChild(root);
//..you could do as follow :
c.ParentNode.InsertAfter(root, c);

Getting data from xml with specific data name and value key

I want to get data from xml but there are lots of tags, fields and value keys. I couldn't select the value which I want. How can I select the "Error" value from this XML with C#?
<?xml version="1.0" encoding="UTF-8"?>
<Database xmlns="http://www.example.com/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Datas>
<Data name="sMsg" access="private" xsi:type="collection" type="string">
<Value key="Cycle" value="deger1" />
<Value key="Error" value="deger2" />
<Value key="Info" value="deger3" />
<Value key="Jog" />
<Value key="Warning" />
</Data>
<Data name="tTabla" access="private" xsi:type="array" type="tabla" size="1">
<Field name="dddd" xsi:type="array" type="bool" size="1" />
<Field name="ssss" xsi:type="array" type="bool" size="1" />
<Field name="aaaa" xsi:type="array" type="num" size="1" />
<Field name="rrrr" xsi:type="collection" type="num">
<Value key="Actuel" />
<Value key="Expected" />
</Field>
</Data>
</Datas>
</Database>
You can try this way :
var doc = XDocument.Parse(xml);
XNamespace d = doc.Root.GetDefaultNamespace();
var result = (string)
doc.Descendants(d + "Data")
.Elements(d + "Value")
.FirstOrDefault(o => (string) o.Attribute("key") == "Error")
.Attribute("value");
Console.WriteLine(result);
Try this it returns the XElement with Key equal to Error
XDocument m = XDocument.Load(#"Path");
var res = m.Descendants().Where(x => x.Name.LocalName.Equals("Value") && x.Attribute("key") != null && x.Attribute("key").Value.Equals("Error")).FirstOrDefault();
If there are multiple "Error" values for your attributes, you can do:
IEnumerable<XAttribute> answer = xml.Descendants().Attributes().Where(node => node.Value == "Error");
foreach (var xAttribute in answer)
{
Console.WriteLine(xAttribute.Value);
}
If you only want the first or there is only one:
string answer = xml.Descendants().Attributes().FirstOrDefault(node => node.Value == "Error");
Note FirstOrDefault may yield null if it doesn't find any "Error" values inside your xml.
These queries are done using LINQ To XML, i strongly encourage you to read up.

How do I find the maximum value of an attribute value in an XML file?

I have an XML file with the following structure:
<doc>
<rootelement>
<childelement id="0" />
<childelement id="1" />
.
.
</rootelement>
</doc>
I want to find the highest numeric value of the id attribute
The idea I have in mind is something like:
int highest = -1;
foreach(var node in xmldoc.SelectNodes("//doc/rootelement/childelement"))
{
highest = Math.Max(GetID(node), highest);
}
where GetID(XMLNode) would retrieve the value of the attribute of the current node.
Is there a more compact (or more efficient) XPath expression to do that?
You can use Linq to Xml:
var xdoc = XDocument.Load(path_to_xml);
var maxId = xdoc.XPathSelectElements("//doc/rootelement/childelement")
.Max(c => (int)c.Attribute("id"));
Or without XPath:
var maxId = xdoc.Root.Elements("rootelement")
.Elements("childelement")
.Max(c => (int)c.Attribute("id"));
With XmlDocument:
var maxId = doc.SelectNodes("//doc/rootelement/childelement")
.Cast<XmlElement>()
.Max(c => Int32.Parse(c.Attributes["id"].Value));
Use Linq to XML.
string xml =
#"<doc>
<rootelement>
<childelement id='0' />
<childelement id='1' />
</rootelement>
</doc>";
var doc = XDocument.Parse(xml);
int max = doc.Descendants("childelement").Max(e => (int)e.Attribute("id"));

Categories