Cannot find item name C# XML - c#

I'm having a problem with my XML document.
I want my program to find all values of the items in my XML file, but only if the handlingType is of a certain character bunch.
Code (C#) :
string path = "//files//handling.meta";
var doc = XDocument.Load(path);
var items = doc.Descendants("HandlingData").Elements("Item");
var query = from i in items
select new
{
HandlingName = (string)i.Element("handlingName"),
HandlingType = (string)i.Element("HandlingType"),
Mass = (decimal?)i.Element("fMass")
};
foreach (var HandlingType in items)
{
if (HandlingType.ToString() == "HANDLING_TYPE_FLYING")
{
MessageBox.Show(HandlingType.ToString());
}
}
The above code demonstraights a short version of what I want to happen, but fails to find this handlingType (does not show the messageBox)
Here's the XML :
<CHandlingDataMgr>
<HandlingData>
<Item type="CHandlingData">
<handlingName>Plane</handlingName>
<fMass value="380000.000000"/>
<handlingType>HANDLING_TYPE_FLYING</handlingType>
</Item>
<Item type="CHandlingData">
<handlingName>Car1</handlingName>
<fMass value="150000.000000"/>
<handlingType>HANDLING_TYPE_DRIVING</handlingType>
</Item>
</HandlingData>
</CHandlingDataMgr>
I would like the output to show the handlingName if it contains a certain HandlingType
For e.g.
if (handlingType == "HANDLING_TYPE_FLYING")
{
messageBox.Show(this.HandlingName);
}
My problem in short : Program does not find item's handling type, it does find the tag but when asked to display, returns empty/shows as nothing.
Edit: Also in the XML handling_type_flying contains extra elements such as thrust that cannot be found in each item (such as car), I would like the program to also find these elements. (this is a second problem I'm facing, maybe should ask 2nd ques?)

Several things that need fixing.
you are not using your query in your foreach loop. foreach (var item in query)
Your element has an upercase "H" but should be lowercase "handlingType". HandlingType = (string)i.Element("handlingType"),
You are not pulling the Attribute value of your fMass element.Mass = i.Element("fMass").Attribute("value").Value
Once you adjust your Query in your foreach loop you then need to adjust the loop to account for looping over your newly made object.
NOTE that I removed (decimal) from Mass = i.Element("fMass").Attribute("value").Value
here is the code with all the fixes.
class Program
{
static void Main()
{
const string path = "//files//handling.meta";
var doc = XDocument.Load(path);
var items = doc.Descendants("HandlingData").Elements("Item");
var query = from i in items
select new
{
HandlingName = (string)i.Element("handlingName"),
HandlingType = (string)i.Element("handlingType"),
Mass = i.Element("fMass").Attribute("value").Value
};
foreach (var item in query)
{
if (item.HandlingType == "HANDLING_TYPE_FLYING")
{
//Remove messagebox if consoleapp
MessageBox.Show(item.HandlingType);
MessageBox.Show(item.HandlingName);
Console.WriteLine(item.HandlingType);
Console.WriteLine(item.HandlingName);
}
}
}
}
I would recommend looking into serializing your xml to an object.

If you look at http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement(v=vs.110).aspx the ToString() method doesn't return the name of the tag, but the indented XML.
You should instead be using the Value property. Also you should use .equals("...") instead of ==
if (handlingType.Value.equals("HANDLING_TYPE_FLYING")
{
messageBox.Show(this.handlingname);
}

Related

Removing invalid child nodes but keep its contents intact..?

I have some xml files that look like sample file
I want to remove invalid xref nodes from it but keep the contents of those nodes as it is.
The way to know whether a xref node is valid is to check its attribute rid's value exactly matches any of the attributes id of any node present in the entire file, so the output file of the above sample should be something like sample output file
The code I've written thus far is below
XDocument doc=XDocument.Load(#"D:\sample\sample.xml",LoadOptions.None);
var ids = from a in doc.Descendants()
where a.Attribute("id") !=null
select a.Attribute("id").Value;
var xrefs=from x in doc.Descendants("xref")
where x.Attribute("rid")!=null
select x.Attribute("rid").Value;
if (ids.Any() && xrefs.Any())
{
foreach(var xref in xrefs)
{
if (!ids.Contains(xref))
{
string content= File.ReadAllText(#"D:\sample\sample.xml");
string result=Regex.Replace(content,"<xref ref-type=\"[^\"]+\" rid=\""+xref+"\">(.*?)</xref>","$1");
File.WriteAllText(#"D:\sample\sample.xml",result);
}
}
Console.WriteLine("complete");
}
else
{
Console.WriteLine("No value found");
}
Console.ReadLine();
The problem is when the values of xref contain characters like ., *, (etc. which on a regex replace needs to be escaped properly or the replace can mess up the file.
Does anyone have a better solution to the problem?
You don't need regex to do this. Instead use element.ReplaceWith(element.Nodes()) to replace node with its children. Sample code:
XDocument doc = XDocument.Load(#"D:\sample\sample.xml", LoadOptions.None);
// use HashSet, since you only use it for lookups
var ids = new HashSet<string>(from a in doc.Descendants()
where a.Attribute("id") != null
select a.Attribute("id").Value);
// select both element itself (for update), and value of "rid"
var xrefs = from x in doc.Descendants("xref")
where x.Attribute("rid") != null
select new { element = x, rid = x.Attribute("rid").Value };
if (ids.Any()) {
var toUpdate = new List<XElement>();
foreach (var xref in xrefs) {
if (!ids.Contains(xref.rid)) {
toUpdate.Add(xref.element);
}
}
if (toUpdate.Count > 0) {
foreach (var xref in toUpdate) {
// replace with contents
xref.ReplaceWith(xref.Nodes());
}
doc.Save(#"D:\sample\sample.xml");
}
}

Working with XML under C# - Difficulties

it's me again. This time, having some issues with XML. I had everything working in VB.NET (I'll show all the code I used later) but now I'm developing something else for VB application except I'm using C# for it. Part of this involves reading an XML and populating something specific into a ListBox and then being able to click on it and get the attributes for use in other controls (description loads to a text box, etc, you'll see).
I can't seem to figure out XML for the life of me in C# however. In VB, I did it like this:
Dim games() As String = xml...<episode>.Select(Function(n) n.Value).ToArray
AvailableEpisodes.DataSource = games
Where "AvailableEpisodes" is the ListBox I wish to populate. This displayed the "This is a test" term: This is a test
And then this is the SelectedIndexChanged code:
Dim node As XElement = xml...<episode>.First(Function(n) n.Value = AvailableEpisodes.Text)
DescriptionTextBox.Text = node.#Description
AuthorTextBox.Text = node.#Author
generatedDownloadLink = node.#DownloadLink
generatedTechName = node.#TechName
IconImage.ImageLocation = node.#IconLoc
What exactly would be the C# equivalent of this? I already tried copy-pasting (just figured I'd try it) and a couple code converters and none of them seem to work.
The goal of this application will be to be able to double click on the selected index and load these strings into another window (which I can work out by attaching the nodes to a string variable) I just need to get started.
Code I've tried:
using (XmlReader reader = XmlReader.Create(testXml))
{
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.Element) && reader.Name == "episode")
{
listBox1.DataSource = reader.GetAttribute("TechName").ToList();
}
}
}
But that literally just outputs this: http://imgur.com/Naeabf9.png
Any extra information I'll toss in an edit or a reply
Thanks in advanced,
Mike
Its easy: ... corresponds to Descendants. <foo> corresponds to element name. #bar corresponds to attribute name.
var xml = XDocument.Load(path_to_xml);
var node = xml.Descendants("episode")
.First(n => n.Value == AvailableEpisodes.Text);
DescriptionTextBox.Text = (string)node.Attribute("Description");
AuthorTextBox.Text = (string)node.Attribute("Author");
generatedDownloadLink = (string)node.Attribute("DownloadLink");
generatedTechName = (string)node.Attribute("TechName");
IconImage.ImageLocation = (string)node.Attribute("IconLoc");
Note - if there is no matching node, then First will throw exception. Usually you should use FirstOrDefault in such case and then check node for null.
Is "This is a test" supposed to be one single item in the ListBox? If it is, your logic in the C# code is wrong. Indeed, reader.GetAttribute("TechName").ToList() will return an array like this
["T","h","i", "s", " ", "i", "s", " ", "t", "e", "s", "t"]
Therefore, the text gets broken up into many items in the ListBox.
To read all nodes in the xml documents into ListBox, you have to create a temporary list to hold all the results read from the xml, then at the end bind the list to ListBox listBox1.DataSource = results
Why don't you just use XML Serialization?
Assuming your XML looks something like this:
<EpisodeData>
<Episodes>
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
<Episode Description="..." Author="..." DownloadLink="..." ... />
</Episodes>
</EpisodeData>
Create classes in C# that represents the data. Something like this:
[Serializable]
public class EpisodeData
{
[XmlArray("Episodes")]
[XmlArrayItem(ElementName = "Episode")]
List<Episode> Episodes { get; set; }
}
[Serializable]
public class Episode
{
[XmlAttribute]
public string Description { get; set; }
[XmlAttribute]
public string Author { get; set; }
[XmlAttribute]
public string DownloadLink { get; set; }
...
}
Then you can deserialize and use the data like this:
EpisodeData data;
XmlSerializer serializer = new XmlSerializer(typeof(EpisodeData));
using (StreamReader sr = new StreamReader(fileName))
{
data = (EpisodeData)serializer.Deserialize(sr);
}
// Assuming you only want to see the description. If you want something else
// you might want to use a DataGrid to bind to each property in Episode or
// override ToString in Episode.
List<string> descriptions = new List<string>();
foreach (Episode episode in data.Episodes)
{
descriptions.Add(episode.Description);
}
listBox1.DataSource = descriptions;
More info on XML Serialization here:
http://msdn.microsoft.com/en-us/library/58a18dwa(v=vs.110).aspx

How do I merge two XDocuments removing duplicates

I have two XML files (*.resx files) that I am trying to merge in to one removing duplicates, but am unable to do so. I've tried the following without any success:
var resource1 = XDocument.Load("C:\\Resources.resx");
var resource2 = XDocument.Load("C:\\Resources2.resx");
// This results in a file with all the nodes from the second file included inside
// the root element of the first file to form a properly formatted, concatenated file.
resource1.Descendants().FirstOrDefault().Add(resource2.Descendants().FirstOrDefault().Nodes());
var nodeContent = new List<string>();
foreach (XElement node in resource1.Root.Elements())
{
if (nodeContent.Contains(node.ToString()))
resource1.Remove();
else
nodeContent.Add(node.ToString());
}
resource1.Save("C:\\FinalResources.resx");
On the remove statement I get an InvalidOperationException - "The parent is missing.":
Am I doing something wrong?
You need to define an EqualityComparer<XElement> that will enable you to use the standard LINQ operators.
So, as a simple example I created this:
public class ElementComparer : EqualityComparer<XElement>
{
public override int GetHashCode(XElement xe)
{
return xe.Name.GetHashCode() ^ xe.Value.GetHashCode();
}
public override bool Equals(XElement xe1, XElement xe2)
{
var #return = xe1.Name.Equals(xe2.Name);
if (#return)
{
#return = xe1.Value.Equals(xe2.Value);
}
return #return;
}
}
So I can then start with these two XML documents:
<xs>
<x>D</x>
<x>A</x>
<x>B</x>
</xs>
<xs>
<x>E</x>
<x>B</x>
<x>C</x>
</xs>
And do this:
var xml1 = XDocument.Parse(#"<xs><x>D</x><x>A</x><x>B</x></xs>");
var xml2 = XDocument.Parse(#"<xs><x>E</x><x>B</x><x>C</x></xs>");
xml1.Root.Add(
xml2.Root.Elements("x")
.Except(xml1.Root.Elements("x"), new ElementComparer()));
Then xml1 will look like this:
<xs>
<x>D</x>
<x>A</x>
<x>B</x>
<x>E</x>
<x>C</x>
</xs>
Well, the most straight forward way is:
var resource1 = XDocument.Load("C:\\Resources.resx");
var resource2 = XDocument.Load("C:\\Resources2.resx");
foreach (XElement node in resource2.Root.Elements())
{
if (resource1.Root.Contains(node)) continue;
resource1.Add(node);
}
resource1.Save("C:\\FinalResources.resx");
public static class XElementExtensions
{
public static bool Contains(this XElement root, XElement e)
{
//or w/e equality logic you need
return root.Elements().Any(x => x.ToString().Equals(e.ToString()));
}
}
This will only merge first level entries tho. If you need deep merge, then you will have to set up a simple recursion (using the same loop for child elements).
resource1.Remove(); is called twice and what it does is remove the root element. So the second time there is no longer a root element to remove from and thus throwing the exception.

Select specific nodes in XML with LINQ

I'm writing a function that loads and XML document and converts it to a CSV. Since I need only some values from the XML file, the goal i'm trying to achieve is to select only the nodes I'm interested in.
Here's my code:
XDocument csvDocument = XDocument.Load(tempOutput);
StringBuilder csvBuilder = new StringBuilder(1000);
foreach (XElement node in csvDocument.Descendants("Sample"))
{
foreach (XElement innerNode in node.Elements())
{
csvBuilder.AppendFormat("{0},", innerNode.Value);
}
csvBuilder.Remove(csvBuilder.Length -1, 1);
csvBuilder.AppendLine();
}
csvOut = csvBuilder.ToString();
But, in this way I'm selectin ALL the child nodes inside the "Sample" node.
In the XML, "Sample" tree is:
<Sample Type="Object" Class ="Sample">
<ID>1</ID>
<Name>10096</Name>
<Type>2</Type>
<Rep>0</Rep>
<Selected>True</Selected>
<Position>1</Position>
<Pattern>0</Pattern>
</Sample>
Code works flawlessly, but I need only "ID" and "Selected" to be selected and their values written inside the CSV file.
Could anyone point me in the right direction, please?
Thanks.
Learn more about Linq-to-xml here. You're not really taking advantage of the 'linq-edness' of XObjects
var samples = csvDocument.Descendants("Sample")
.Select(el => new {
Id = el.Element("ID").Value,
Selected = el.Elemnt("Selected").Value
});
This creates for you an IEnumerable<T> where 'T' is an anonymous type with the properties Id and Selected.
You can parse (int.Parse or bool.Parse) the Id and Selected values for type safety. But since you are simply writing to a StringBuilder object you may not care ...just an FYI.
The StringBuilder object can then be written as follows:
foreach (var sample in samples) {
csvBuilder.AppendFormat(myFormattedString, sample.Id, sample.Selected);
}
The caveat to this is that your anonymous object and the for-each loop should be within the same scope. But there are ways around that if necessary.
As always, there is more than one way to skin a cat.
Update ...in ref. to comment:
foreach (XElement node in csvDocument.Descendants("Sample"))
{
foreach (XElement innerNode in node.Elements())
{
// this logic assumes different formatting for values
// otherwise, change if statement to || each comparison
if(innerNode.Name == "ID") {
// append/format stringBuilder
continue;
}
if(innerNode.Name == "Selected") {
// append/format stringBuilder
continue;
}
}
csvBuilder.Remove(csvBuilder.Length -1, 1);
csvBuilder.AppendLine();
}

C# LINQ - reading an XML

i need to store all the informationen from the xml in an array. My code doesn't work, because I always get just the first item from the xml.
Does anyone know how to fix this?
XDocument xdoc = XDocument.Load("http://www.thefaxx.de/xml/nano.xml");
var items = from item in xdoc.Descendants("items")
select new
{
Title = item.Element("item").Element("title").Value,
Description = item.Element("item").Element("description").Value
};
foreach (var item in items)
{
listView1.Items.Add(item.Title);
}
How about:
var items = from item in xdoc.Descendants("item")
select new
{
Title = item.Element("title").Value,
// *** NOTE: xml has "desc", not "description"
Description = item.Element("desc").Value
};
It is a little hard to be sure without sample xml - but it looks like you intend to loop over all the <item>...</item> elements - which is what the above does. Your original code loops over the (single?) <items>...</items> element(s), then fetches the first <item>...</item> from within it.
edit after looking at the xml; this would be more efficient:
var items = from item in xdoc.Root.Elements("item")
select new {
Title = item.Element("title").Value,
Description = item.Element("desc").Value
};

Categories

Resources