xml parsing using linq - c#

I have a combobox and when users select them xml is parsed.
if i choose item1, i need to select this "C:\folder1" as a path.
item 2: "C:\folder2"
item 3: "C:\folder3"
my XML file:
<?xml version="1.0"?>
<main>
<target>C:\folder1</target>
<target>C:\folder2</target>
<target>C:\folder3</target>
</main>
I am using Xdocument to do the parsing:
private void pathselection()
{
XDocument pathdoc = XDocument.Load(#"C:\GUI\path.xml");
var abc = from target in pathdoc.Descendants("target")
...
...
}
now i am stuck over here. should i include if statements inside pathselection()? and how do i parse the path?
EDIT1:
Btw, i would like to store these path as strings
string selectedpath1 = "";

Given that the selectable items in the ComboBox match the items in the XML, you can get the target elements by index:
private void pathselection()
{
XDocument pathdoc = XDocument.Load(#"C:\GUI\path.xml");
var abc = pathdoc.Descendants("target")
.Where((e, i) => i == MyComboBox.SelectedIndex)
.Single();
}
You might want to handle things like when the indexes don't match and you might want to use SingleOrDefault or FirstOrDefault to deal with that.

Related

Linq2XML separate keys into List

I want to read a config.xml and put each item into a combobox so that the XML file is the datasource. This is my code, which only gives me one entry in my combobox. How do I separate the keys? This is my filter:
C#
var xmlDocument = XDocument.Load(configfile);
var anredeItems = from key in xmlDocument.Descendants("Anrede")
select key.Value.Trim();
anredeNrComboBox.DataSource = anredeItems.ToList();
This is the XML:
<?xml version="1.0"?>
<Config>
<Anrede>
<key_1>Herrn</key_1>
<key_2>Frau</key_2>
<key_3>Herrn Dr.</key_3>
<key_4>Frau Dr.</key_4>
<key_5>Herrn Dr. Med.</key_5>
</Anrede>
</Config>
Your Descendants("Anrede") query will get you the element Andrede, and reading the Value property of that will return the concatenation of all descendant text nodes, which is what you are seeing in your combo box.
What you want are each of its child element values:
var items - doc.Descendants("Anrede")
.Elements()
.Select(x => x.Value.Trim())
.ToList();
You can change your code like this:
var xmlDocument = XDocument.Load(configfile);
var anredeItems = xmlDocument.Root.Elements("Anrede").Elements().Select(p => p.Value.Trim());
anredeNrComboBox.DataSource = anredeItems.ToList();

Updating a specific XML node

I am new to XML files and how to manage them. This is for a web app I am writing (aspx).
At the present time I am able to find the first instance of a node and add an item to it with the following code:
xmlClone.Element("PCs").Element("PC").Element("pc_hwStatus").AddAfterSelf(new XElement("user_name", txt_v0_nombre.Text));
What I really want is to add ("user_name", txt_v0_nombre.Text) to a node in particular, not the first one. The content of my XML file is:
<PCs>
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
</PC>
</PCs>
The decision of what node to update is made selecting an item from a dropdown list (the PC name).
With my current code, the new item is always added as last line of node with "pc_
name = esc01". I want to be able to added it to esc02 or esc03 and so on... How can this be accomplished? (Using xdocument)
If I understand you correctly, what you are looking for is the FirstOrDefault extension method. In there specify which node you are wanting, in this case a string from your dropdown box, which can be passed in. So to get the first node:
var pc = xmlClone.Element("PCs").Elements("PC").FirstOrDefault(e => e.Element("pc_name").Value == "esc01");
Now you have this in your XElement:
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
To get any element like that, just replace this clause:
.FirstOrDefault(e => e.Element("pc_name").Value == "esc01");
with this one
.FirstOrDefault(e => e.Element("pc_name").Value == desiredPC);
where desiredPC is the value of the xml node: pc_name.
Now to add your data just call the plain old Add method:
pc.Add(new XElement("user_name", txt_v0_nombre.Text);
That should do the trick for you.
Here's a solution that uses LINQ query syntax with LINQ to XML:
XDocument document = XDocument.Parse(xmlContent);
string pcName = "esc02";
IEnumerable<XElement> query =
from pc in document.Element("PCs").Elements("PC")
where pc.Element("pc_name").Value.Equals(pcName)
select pc;
XElement xe = query.FirstOrDefault();
if (xe != null)
{
xe.Add(new XElement("user_name", "DMS"));
}
I have incorporated your sample data and this query into a demonstration program. Please see below for the output from the demonstration program followed by the program itself.
Expected Output
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
<user_name>DMS</user_name>
</PC>
Demonstration Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace LinqToXmlDemo
{
public class Program
{
public static void Main(string[] args)
{
string xmlContent = GetXml();
XDocument document = XDocument.Parse(xmlContent);
XElement xe = FindPCName(document, "esc02");
if (xe != null)
{
xe.Add(new XElement("user_name", "DMS"));
Console.WriteLine(xe);
}
else
{
Console.WriteLine("Query returned no results.");
}
}
private static XElement FindPCName(XDocument document, String pcName)
{
IEnumerable<XElement> query =
from pc in document.Element("PCs").Elements("PC")
where pc.Element("pc_name").Value.Equals(pcName)
select pc;
return query.FirstOrDefault();
}
private static String GetXml()
{
return
#"<?xml version='1.0' encoding='utf-8'?>
<PCs>
<PC>
<pc_name>esc01</pc_name>
<pc_ip>10.10.10.10</pc_ip>
<pc_hwStatus>Working</pc_hwStatus>
</PC>
<PC>
<pc_name>esc02</pc_name>
<pc_ip>10.10.10.11</pc_ip>
<pc_hwStatus>Under Maintenance</pc_hwStatus>
</PC>
</PCs>";
}
}
}
Method .Element returns the first element with the specified name.
You can get the whole list with method .Elements, and iterate this list to find the one you are looking for.

Returning XML Data from Root on Window 7 Phone

I am having difficulty trying to return values from an XML file. Here is an example of the XML:
<xml>
<item1>Whatever</item1>
<video>
<caption>Video Title</caption>
<width>1280</width>
<height>720</height>
</video>
<element1>Results One</element1>
<element2>Results Two</element2>
</xml>
I am calling the data like this:
XElement xmlData = XElement.Parse(e.Result);
var list = new List<VideoUrl>();
foreach (XElement item in xmlData.Elements("xml"))
{
var element1 = item.Element("element1").Value;
var element2 = item.Element("element2").Value;
list.Add(new VideoUrl
{
etc...
});
and then assigning the data to a list box to return the values. Problem is I am trying to return XML items "element1" and "element2" but nothing is returned when i run the emulator. If I change the code to return Video > Caption it works fine. I feel like its something real simple I am missing. Any ideas or code samples to fix this would be much appreciated. Thanks in advanced.
xmlData is the <xml> element, so xmlData.Elements("xml") will return no values - there are no xml elements directly under xmlData. Given that it's the root, you know there's only one node, so you can just do:
var element1 = (string) xmlData.Element("element1");
var element2 = (string) xmlData.Element("element2");
Note that by casting to string instead of using the Value property, you end up with a null reference if the element doesn't exist, instead of an exception being thrown.

How to get XElement's value and not value of all child-nodes?

Sample xml:
<parent>
<child>test1</child>
<child>test2</child>
</parent>
If I look for parent.Value where parent is XElement, I get "test1test2".
What I am expecting is "". (since there is no text/value for .
What property of XElement should I be looking for?
When looking for text data in the <parent> element you should look for child nodes that have NodeType properties equal to XmlNodeType.Text. These nodes will be of type XText. The following sample illustrates this:
var p = XElement
.Parse("<parent>Hello<child>test1</child>World<child>test2</child>!</parent>");
var textNodes = from c in p.Nodes()
where c.NodeType == XmlNodeType.Text
select (XText)c;
foreach (var t in textNodes)
{
Console.WriteLine(t.Value);
}
Update: if all you want is the first Text node, if any, here's an example using LINQ method calls instead of query comprehension syntax:
var firstTextNode = p.Nodes().OfType<XText>().FirstOrDefault();
if (firstTextNode != null)
{
var textValue = firstTextNode.Value;
...do something interesting with the value
}
Note: using First() or FirstOrDefault() will be more performant than Count() > 0 in this scenario. Count always enumerates the whole collection while FirstOrDefault() will only enumerate until a match is found.
It is amazing that a coder somewhere at Microsoft thought that returning all text values as a concatenated and undelimited string would be useful. Luckily, another MS developer wrote an XElement extension to return what they call the "Shallow Value" of the text node here. For those who get the willies from clicking on links, the function is below...
public static string ShallowValue(this XElement element)
{
return element
.Nodes()
.OfType<XText>()
.Aggregate(new StringBuilder(),
(s, c) => s.Append(c),
s => s.ToString());
}
And you call it like this, because it gives you all the whitespace too (or, come to think of it, you could trim it in the extension, whatever)
// element is a var in your code of type XElement ...
string myTextContent = element.ShallowValue().Trim();
You could concatenate the value of all XText nodes in parent:
XElement parent = XElement.Parse(
#"<parent>Hello<child>test1</child>World<child>test2</child>!</parent>");
string result = string.Concat(
parent.Nodes().OfType<XText>().Select(t => t.Value));
// result == "HelloWorld!"
For comparison:
// parent.Value == "Hellotest1Worldtest2!"
// (parent.HasElements ? "" : parent.Value) == ""
msdn says:
A String that contains all of the text content of this element. If there are multiple text nodes, they will be concatenated.
So the behaviour is to be expected.
You could solve your problem by doing:
string textContent = parent.HasElements ? "" : parent.Value;
// Create the XElement
XElement parent = XElement.Parse(
#"<parent>Hello<child>test1</child>World<child>test2</child>!</parent>");
// Make a copy
XElement temp=new XElement(parent);
// remove all elements but root
temp.RemoveNodes();
// now, do something with temp.value, e.g.
Console.WriteLine(temp.value);

Change Value of the last Attribute in an XML c#

I was working on a bunch of XMLs that all share an attribute that contains the string "name" in them. The following code selects the attribute with string "name" in it and assign a new value to it.
public void updateXmlFile(string strFileName)
{
try
{
//Load the Document
XmlDocument doc = new XmlDocument();
doc.Load(strFileName);
//Set the changed Value
string newValue = GetUniqueKey();
//Select all nodes in the XML then choose from them
//the first node that contain string "name" in it
XmlNodeList list = doc.SelectNodes("//#*");
XmlNode filteredNode = list.Cast<XmlNode>()
.First(item => item.Name.ToLower().Contains("name"));
//Assign the newValue to the value of the node
filteredNode.Value = newValue;
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
Now a new XMLs were added that dosen't have the string "name" in them, so instead of modifying the attribute with string "name" in it I decided to simply modify the last attribute no matter what it was (not the first)
Can anybody tell me how to do that?
EDIT
Here is an example of my XML
<?xml version="1.0" encoding="utf-8"?>
<CO_CallSignLists Version="24" ModDttm="2010-09-13T06:45:38.873" ModUser="EUADEV\SARE100" ModuleOwner="EUADEVS06\SS2008" CreateDttm="2009-11-05T10:19:31.583" CreateUser="EUADEV\A003893">
<CoCallSignLists DataclassId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" EntityId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" MissionID="4CF71AB2-0D92-DE11-B5D1-000C46F3773D" BroadcastType="S" DeputyInSpecialList="1" SunSpots="1537634cb70c6d80">
<CoCallSigns EntityId="DEBF1DDB-3C92-DE11-A280-000C46F377C4" CmdID="C45F3EF1-1292-DE11-B5D1-000C46F3773D" ModuleID="6CB497F3-AD63-43F1-ACAE-2C5C3B1D7F61" ListType="HS" Name="Reda Sabassi" Broadcast="INTO" PhysicalAddress="37" IsGS="1" HCId="0" CommonGeoPos="1" GeoLat="0.0000000" GeoLong="0.0000000">
<CoRadios EntityId="E1BF1DDB-3C92-DE11-A280-000C46F377C4" RadioType="HF" />
</CoCallSigns>
</CoCallSignLists>
</CO_CallSignLists>
#Alex: You notice that the "SunSpots" attribute (last attribute in the first child element) is successfully changed. But now when I wanna load the XML back into the DB it gives me an error
Here is the modified code
public void updateXmlFile(string strFileName)
{
try
{
XDocument doc = XDocument.Load(strFileName);
XAttribute l_attr_1 = (doc.Elements().First().Elements().First().Attributes().Last());
l_attr_1.Value = GetUniqueKey();
Console.WriteLine("Name: {0} Value:{1}", l_attr_1.Name, l_attr_1.Value);
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
I was thinking of making an if statment which checks if the XML has an attribute that contains string "name" in it (since most of my XMLs has an attribute that contains name in them) if it does then change the attribute's value if not look for the last attribute and change it.. not the best solution but just throwing it out there
Then definitely use Linq to XML.
Example:
using System.Xml.Linq;
string xml = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Commands Version=""439"" CreateUser=""Reda"">
<CmCommands DataclassId=""57067ca8-ef96-4d2e-a085-6bd7e8b24126"" OrderName = ""Tea"" Remark=""Black"">
<CmExecutions EntityId=""A9A5B0F2-6AB4-4619-9106-B0F85F86EE01"" Lock=""n"" />
</CmCommands>
</Commands>";
XDocument x = XDocument.Parse(xml);
Debug.Print(x.Elements().First().Elements().First().Attributes().Last().Value);
// Commands ^ CmCommands ^ Remark ^
That is, word for word, the last attribute of the first child of the first element.
You can also query for element/attribute names, like:
Debug.Print(x.Descendants(XName.Get("CmCommands", "")).First().Attribute(XName.Get("Remark", "")).Value);
And of course you can use all of the Linq goodness like Where, Select, Any, All etc.
Note: replace XDocument.Parse with XDocument.Load if appropriate etc.
I've not tested this but you should be able to do all of this in the XPath expression. Something like this:
//#*[contains(node-name(.), 'name')][last()]
This will return only the last attribute with the string name anywhere in its name.
If you only want the last attribute, irrespective of it's name, use this:
//#*[last()]
Look at class XmlAttributeCollection. You can get this collection by reading property Attributes of XmlNode. Just get the last by index.
Instead of .First(), use an extension method like this:
public static T LastOrDefault<T>(this IEnumerable<T> list)
{
T val = null;
foreach(T item in list)
{
val = item;
}
return val;
}

Categories

Resources