Querying XML file - c#

i should to parse an XML file in my C# application.
This is a sample of my XML file
<?xml version="1.0" encoding="utf-8" ?>
<tokens>
<token id="1" >
<id>1</id>
<nome>"Pippo"</nome>
<numeroSillabe>"2"</numeroSillabe>
<sillabaIniziale>"Pip"</sillabaIniziale>
</token>
<token id="2">
<id>2</id>
<nome>Pluto</nome>
<numeroSillabe>2</numeroSillabe>
<sillabaIniziale>Plu</sillabaIniziale>
</token>
<token id="3">
<id>3</id>
<nome>Palla</nome>
<numeroSillabe>2</numeroSillabe>
<sillabaIniziale>Pa</sillabaIniziale>
</token>
</tokens>
For reading this file i use this code
XmlDocument document = new XMLDocument()
document.Load("Content\\Token.xml");
// arrivo al nodo
//XmlNode node = document.DocumentElement.SelectSingleNode("/tokens/token");
//dichiaro la mappa per i token
Dictionary<string, Token> mappa = new Dictionary<string, Token>();
foreach (XmlNode node in document.DocumentElement.ChildNodes)
{
//creo l'oggetto Token e lo popolo con i risultati del parse
Token token = new Token();
//compilo il campo ID
token.setId((node["id"].InnerText).Replace("\"", ""));
//compilo il nome
token.setNome((node["nome"].InnerText).Replace("\"", ""));
//compilo l numero sillabe
token.setNumeroSillabe((node["numeroSillabe"].InnerText).Replace("\"", ""));
//compilo la sillaba iniziale
token.setSillabaIniziale(node["sillabaIniziale"].InnerText);
//aggiungo all array il token
mappa.Add(token.getId(), token);
}
It works.
Now i would like to search the element from XML document for example SELECT * FROM DOCUMENT_XML WHERE numeroSilabe=2.
How can I perform this task?

If you need to navigate through your XML file, like in your code sample, you could go with something like this:
var mappa = document.SelectNodes("/tokens/token[numeroSillabe=2]") // Filter
.OfType<XmlElement>()
.ToDictionary(
t => t.GetAttribute("id"),
t => new
{
Id = t.GetAttribute("id"),
Nome = t.SelectSingleNode("nome" ).InnerText,
NumeroSillabe = t.SelectSingleNode("numeroSillabe" ).InnerText,
SillabaIniziale = t.SelectSingleNode("sillabaIniziale").InnerText
});

List<Token> tokens = mappa.Select(x=>x.Values.Where(y=>y.getNumeroSillabe()==2)).ToList();
I suppose that your token class has a method getNumeroSillabe() like getId(), which returns the value of NumeroSillabe.
By the way, I would suggest you to use automatic properties instead of defining foreach variable of your class a getter and a setter. Certainly, that would be suitable, if you don't have any logic in this methods instead of setting and getting the value of a variable, without checking anything or doing any calculation etc.
In your case:
// I don't know, which is the access modifier your class and I suppose that is public.
public class Token
{
public string Id { get; set;}
public string Nome { get; set;}
public string NumeroSillabe { get; set;}
public string SillabeIniziale { get; set;}
// here goes the methods of your class, if there are any
}
making this change, the code for the above linq query changes to the following:
List<Token> tokens = mappa.Select(x=>x.Values.Where(y=>y.NumeroSillabe==2)).ToList();
Also your code changes for instance as follows:
token.Nome = node["nome"].InnerText.Replace("\"", "");
The same holds for the other three assignments.
For further reading regrading the auto implemented properties, please look in the following link:
http://msdn.microsoft.com/en-us/library/vstudio/bb384054.aspx

use this:
var mappa = document.SelectNodes("//token[numeroSillabe=2]");

Try below code to get filtered nodes without any loop:
var document = new XmlDocument();
document.Load("Content\\Token.xml");
var filteredNodes = document.SelectNodes("tokens/token/numeroSillabe=2");

Related

How can I use Linq to build a c# object from xml where an element has zero or more elements of the same type?

I'm trying to parse an xml file that looks roughly like this:
<?xml version="1.0" encoding="utf-16" ?>
<log>
<request id="1" result="Deny">
<user name="corp\joe"/>
<session number="1"/>
<file name="xyz"/>
<policy type="default" name="Default Rules" result="Deny"/>
<policy type="adgroup" name="Domain Users" result="Allow"/>
</request>
<request id="1" result="Deny">
<user name="corp\joe"/>
<session number="1"/>
<file name="abc"/>
<policy type="default" name="Default Rules" result="Deny"/>
<policy type="device" name="laptop12" result="Deny"/>
</request>
</log>
Note the presence of multiple policy elements per request.
Here's my code so far:
public class Request
{
public int Request_id { get; set; }
public string User_name { get; set; }
public string Session_number { get; set; }
public string File_name { get; set; }
}
void Main()
{
var xml = XDocument.Load(#"c:\temp\test.xml");
var query = from c in xml.Descendants("request")
where (String)c.Attribute("result") == "Deny"
select new Request() {
Request_id = (int) c.Attribute("id"),
User_name = (string) c.Element("user").Attribute("name"),
Session_number = (string) c.Element("session").Attribute("number"),
File_name = (string) c.Element("file").Attribute("name"),
};
foreach (Request r in query.ToList()) {
System.Diagnostics.Debug.WriteLine(r.User_name + "," + r.File_name);
}
}
I'm not sure how to query and capture the 0+ policy elements. Should I:
define a new class Policy {Type, Name, Result}
add a List member to the Request class
somehow populate this list when creating the new Request object as part of the Linq query
It's the somehow bit I'm stuck on. Do I need to add another Linq query within the existing Select, and if so, what would that look like? c.Descendents("policy")...
You're on the very good track! Your points are correct. You need a Policy class and a List<Policy> property in Request class. With that you need a subquery in your existing query to populate them:
var query = from c in xml.Descendants("request")
where (String)c.Attribute("result") == "Deny"
select new Request() {
Request_id = (int) c.Attribute("id"),
User_name = (string) c.Element("user").Attribute("name"),
Session_number = (string) c.Element("session").Attribute("number"),
File_name = (string) c.Element("file").Attribute("name"),
Policies = (from p in c.Elements("policy")
select new Policy() {
Type = (string) p.Attribute("type")
// (...)
}).ToList()
};

Parsing XML using C#, getting attribute value

I have the following (simplified) notepad file, which from what I understand is XML text:
<?xml version="1.0" encoding="utf-8"?>
<appSettings>
<add key="Active_01" value="1">
</add>
</appSettings>
I am trying to parse it using C#.
So far I have the following:
public class RFIDScanner
{
public void GetScannerConfigFile()
{
string File = ConfigurationManager.AppSettings["RFIDScannerConfiguration"];
XmlDocument doc = new XmlDocument();
doc.Load(File);
XmlNode node = doc.DocumentElement.SelectSingleNode("/appSettings");
String nodename = node.Name;
}
}
So far I know this is all correct, as:
nodename = appSettings
Which is as it should be.
My problem is, how do I retrieve the value "1" from the field "Active_01".
I now know that the node "add" is a child of the node "appSettings", and a trying to work out how to get the value stored in it.
Not sure if you wish to parse the '1' from the key Active_01, or get the 1 from the value. In Any case, you can use the following code:
public void GetScannerConfigFile()
{
string File = ConfigurationManager.AppSettings["RFIDScannerConfiguration"];
XmlDocument doc = new XmlDocument();
doc.Load(File);
var yourFile = doc.DocumentElement;
if (yourFile == null) return;
// Here you'll get the attribute key: key = Active_01 - which can be simply parsed for getting only the "1"
string key = yourFile.ChildNodes[0].Attributes["key"].Value;
// Here you'll get the number "1" from the value attribute.
string value = yourFile.ChildNodes[0].Attributes["value"].Value;
}
There are many ways, one is to use the XPath using the function you use above:
var value = doc.DocumentElement.SelectSingleNode("/appSettings/add/#value");
Another option is to use xml serialization:
You'd define classes as follows:
public class add
{
[XmlAttribute]
public string key;
[XmlAttribute]
public int value;
}
public class appSettings
{
public add add;
}
And then deserialize as follows:
var ser = new XmlSerializer(typeof(appSettings));
var xmlReader = new XmlTextReader(new StringReader(s));
appSettings settings = (appSettings) ser.Deserialize(xmlReader);
xmlReader.Close();
You can then get the values from settings
I have used this way in my code to add values to xml
may be it will help
var surveyTypeList = new XElement("SurveyTypes");
foreach (var item in modelData.SurveyTypeList)
{
if (item.IsSelected)
{
var surveyType = new XElement("SurveyType");
var id = new XElement("Id");
id.Add(item.Value);
// **var i= id.Value**
surveyType.Add(id);
surveyTypeList.Add(surveyType);
}
}
you can get value by var i= id.Value in your code;

Reading XML file into memory and searching it

I have a given XML file that I need to process. For the sake of argument, let's say I've already loaded it in as a string.
<?xml version="1.0" encoding="UTF-8" ?>
<GROUP ID="_group_id" ORDERINFO="00000" TITLE="Group 1">
<GROUP ID="_group_id_2" TITLE="Group 2">
<LO ID="_id_code1" LANG="enUS" TYPE="_cust" TITLE="Title 1" />
<LO ID="_id_code2" LANG="enUS" TYPE="_cust" TITLE="Title 2" />
</GROUP>
<GROUP ID="_group_id_3" TITLE="Group 3">
<LO ID="_id_code1" LANG="enUS" TYPE="_cust" TITLE="Title 1" />
<LO ID="_id_code2" LANG="enUS" TYPE="_cust" TITLE="Title 2" />
</GROUP>
</GROUP>
There can be many LOs and many GROUPs in a given XML file. I've been trying various methods with no luck. I need something that will find the matching LO by ID to a given string and then allow me to retrieve the corresponding TYPE and TITLE into strings so that I may use them for processing.
I tried reading the file into an XmlDocument but once loaded I could not figure out how to find the appropriate elements.
Sorry for post prior to edit - some text got cut off
You can use XmlDocument or XDocument to parse the Xml.
Here is an example with XDocument:
Data class:
public class Lo
{
public string Id { get; set; }
public string Lang { get; set; }
public string Type { get; set; }
public string Title { get; set; }
}
Code:
var document = XDocument.Parse(data);
var value = "_id_code1";
IEnumerable<Lo> result =
document.XPathSelectElements(".//LO")
.Where(x => x.Attribute("ID").Value == value)
.Select(x =>
new Lo
{
Id = x.Attribute("ID").Value,
Lang = x.Attribute("LANG").Value,
Type = x.Attribute("TYPE").Value,
Title = x.Attribute("TITLE").Value
});
When loaded into a XmlDocument, you can use XPath to locate notes.
E.g:
XmlNode group = xmlDocument.SelectSingleNode("/GROUP/GROUP[#ID='_group_id_2']");
Or:
XmlNodeList groups = xmlDocument.SelectNodes("/GROUP/GROUP");
foreach(XmlNode group in groups)
{
string id = group.Attributes["ID"].Value;
}
It is very easy. For a more complete walk through you should search the internet.
See the documentation:
Overview of XML in the .NET Framework.
XML Processing Options in the .NET Framework
It's better to cast XAtribute to string, then access its Value property (if some attribute not found, you will get null instead of exception). Also query syntax is more compact here
string id = "_id_code1";
XDocument xdoc = XDocument.Parse(xml);
var query = from lo in xdoc.Descendants("LO")
where (string)lo.Attribute("ID") == id
select new {
Id = (string)lo.Attribute("ID"),
Language = (string)lo.Attribute("LANG"),
Type = (string)lo.Attribute("TYPE"),
Title = (string)lo.Attribute("TITLE")
};
This query will return sequence of anonymous objects with properties Id, Language, Type, Title. You can enumerate them with foreach.
I did small test application for this, I included your xml as the string.
var xmlMessage = #"keep your xml here, I removed due to formatting";
var matchedElements = XDocument.Parse(xmlMessage).Descendants().Where(el => el.Name == "LO" && el.Attribute("ID").Value == "_id_code1");
foreach (var el in matchedElements)
{
Console.WriteLine("ElementName : {0}\nID = {1}\nLANG = {2}\nTYPE = {3}\nTITLE = {4}\n"
, el.Name.LocalName, el.Attribute("ID").Value, el.Attribute("LANG").Value, el.Attribute("TYPE").Value, el.Attribute("TITLE").Value);
}
This would help you to fetch all LO elements having the ID "_id_code" irrespective of the GROUP element.
If you need to consider the group, replace the second line code with this:
var matchedElements = XDocument.Parse(xmlMessage).Descendants().Where(el => el.Parent != null && el.Parent.Attribute("ID").Value == "_group_id_2" && el.Name == "LO" && el.Attribute("ID").Value == "_id_code1");
Here, I'm checking for the "_group_id_2", you can replace with your group id.
The required namespaces:
using System.Linq;
using System.Xml;
using System.Xml.Linq;

How to deserialize only part of a large xml file to c# classes?

I've already read some posts and articles on how to deserialize xml but still haven't figured out the way I should write the code to match my needs, so.. I'm apologizing for another question about deserializing xml ))
I have a large (50 MB) xml file which I need to deserialize. I use xsd.exe to get xsd schema of the document and than autogenerate c# classes file which I put into my project. I want to get some (not all) data from this xml file and put it into my sql database.
Here is the hierarchy of the file (simplified, xsd is very large):
public class yml_catalog
{
public yml_catalogShop[] shop { /*realization*/ }
}
public class yml_catalogShop
{
public yml_catalogShopOffersOffer[][] offers { /*realization*/ }
}
public class yml_catalogShopOffersOffer
{
// here goes all the data (properties) I want to obtain ))
}
And here is my code:
first approach:
yml_catalogShopOffersOffer catalog;
var serializer = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
var reader = new StreamReader(#"C:\div_kid.xml");
catalog = (yml_catalogShopOffersOffer) serializer.Deserialize(reader);//exception occures
reader.Close();
I get InvalidOperationException: There is an error in the XML(3,2) document
second approach:
XmlSerializer ser = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
yml_catalogShopOffersOffer result;
using (XmlReader reader = XmlReader.Create(#"C:\div_kid.xml"))
{
result = (yml_catalogShopOffersOffer)ser.Deserialize(reader); // exception occures
}
InvalidOperationException: There is an error in the XML(0,0) document
third: I tried to deserialize the entire file:
XmlSerializer ser = new XmlSerializer(typeof(yml_catalog)); // exception occures
yml_catalog result;
using (XmlReader reader = XmlReader.Create(#"C:\div_kid.xml"))
{
result = (yml_catalog)ser.Deserialize(reader);
}
And I get the following:
error CS0030: The convertion of type "yml_catalogShopOffersOffer[]" into "yml_catalogShopOffersOffer" is not possible.
error CS0029: The implicit convertion of type "yml_catalogShopOffersOffer" into "yml_catalogShopOffersOffer[]" is not possible.
So, how to fix (or overwrite) the code to not get the exceptions?
edits: Also when I write:
XDocument doc = XDocument.Parse(#"C:\div_kid.xml");
The XmlException occures: unpermitted data on root level, string 1, position 1.
Here is the first string of the xml file:
<?xml version="1.0" encoding="windows-1251"?>
edits 2:
The xml file short example:
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2012-11-01 23:29">
<shop>
<name>OZON.ru</name>
<company>?????? "???????????????? ??????????????"</company>
<url>http://www.ozon.ru/</url>
<currencies>
<currency id="RUR" rate="1" />
</currencies>
<categories>
<category id=""1126233>base category</category>
<category id="1127479" parentId="1126233">bla bla bla</category>
// here goes all the categories
</categories>
<offers>
<offer>
<price></price>
<picture></picture>
</offer>
// other offers
</offers>
</shop>
</yml_catalog>
P.S.
I've already acccepted the answer (it's perfect). But now I need to find "base category" for each Offer using categoryId. The data is hierarchical and the base category is the category that has no "parentId" attribute. So, I wrote a recursive method to find the "base category", but it never finishes. Seems like the algorythm is not very fast))
Here is my code: (in the main() method)
var doc = XDocument.Load(#"C:\div_kid.xml");
var offers = doc.Descendants("shop").Elements("offers").Elements("offer");
foreach (var offer in offers.Take(2))
{
var category = GetCategory(categoryId, doc);
// here goes other code
}
Helper method:
public static string GetCategory(int categoryId, XDocument document)
{
var tempId = categoryId;
var categories = document.Descendants("shop").Elements("categories").Elements("category");
foreach (var category in categories)
{
if (category.Attribute("id").ToString() == categoryId.ToString())
{
if (category.Attributes().Count() == 1)
{
return category.ToString();
}
tempId = Convert.ToInt32(category.Attribute("parentId"));
}
}
return GetCategory(tempId, document);
}
Can I use recursion in such situation? If not, how else can I find the "base category"?
Give LINQ to XML a try. XElement result = XElement.Load(#"C:\div_kid.xml");
Querying in LINQ is brilliant but admittedly a little weird at the start. You select nodes from the Document in a SQL like syntax, or using lambda expressions. Then create anonymous objects (or use existing classes) containing the data you are interested in.
Best is to see it in action.
miscellaneous examples of LINQ to XML
simple sample using xquery and lambdas
sample denoting namespaces
There is tons more on msdn. Search for LINQ to XML.
Based on your sample XML and code, here's a specific example:
var element = XElement.Load(#"C:\div_kid.xml");
var shopsQuery =
from shop in element.Descendants("shop")
select new
{
Name = (string) shop.Descendants("name").FirstOrDefault(),
Company = (string) shop.Descendants("company").FirstOrDefault(),
Categories =
from category in shop.Descendants("category")
select new {
Id = category.Attribute("id").Value,
Parent = category.Attribute("parentId").Value,
Name = category.Value
},
Offers =
from offer in shop.Descendants("offer")
select new {
Price = (string) offer.Descendants("price").FirstOrDefault(),
Picture = (string) offer.Descendants("picture").FirstOrDefault()
}
};
foreach (var shop in shopsQuery){
Console.WriteLine(shop.Name);
Console.WriteLine(shop.Company);
foreach (var category in shop.Categories)
{
Console.WriteLine(category.Name);
Console.WriteLine(category.Id);
}
foreach (var offer in shop.Offers)
{
Console.WriteLine(offer.Price);
Console.WriteLine(offer.Picture);
}
}
As an extra: Here's how to deserialize the tree of categories from the flat category elements.
You need a proper class to house them, for the list of Children must have a type:
class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<Category> Children { get; set; }
public IEnumerable<Category> Descendants {
get
{
return (from child in Children
select child.Descendants).SelectMany(x => x).
Concat(new Category[] { this });
}
}
}
To create a list containing all distinct categories in the document:
var categories = (from category in element.Descendants("category")
orderby int.Parse( category.Attribute("id").Value )
select new Category()
{
Id = int.Parse(category.Attribute("id").Value),
ParentId = category.Attribute("parentId") == null ?
null as int? : int.Parse(category.Attribute("parentId").Value),
Children = new List<Category>()
}).Distinct().ToList();
Then organize them into a tree (Heavily borrowed from flat list to hierarchy):
var lookup = categories.ToLookup(cat => cat.ParentId);
foreach (var category in categories)
{
category.Children = lookup[category.Id].ToList();
}
var rootCategories = lookup[null].ToList();
To find the root which contains theCategory:
var root = (from cat in rootCategories
where cat.Descendants.Contains(theCategory)
select cat).FirstOrDefault();

Select value in Xelement within Xelement?

For a Webradio app in windows Phone, i'm trying to read an XML file with the data, but i'm having a problem with a specific field. The XML File looks like this:
<brands xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<brandgroup>
<brand>
<code>blabla</code>
<name>blabla</name>
<logo>blabla</logo>
<websiteurl>blabla</websiteurl>
<audiostreams>
<audiostream streamurl="www.1.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.2.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.3.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
<brand>
</brand>
</brandgroup>
other 'brandgroups' with other 'brand'
</brand>
With the next Code i'm capable of getting the Name, Code, and Website into and object of Class Station for every brand inside every brandgroup.
XDocument loadedData = XDocument.Load("netten.xml");
var data = from query in loadedData.Descendants("brand")
select new Station
{
Name = (string)query.Element("name"),
Code = (int)query.Element("code"),
Website = (string)query.Element("websiteurl"),
};
However, I can't find a way to get the audiostream. There is the 'audiostreams' element wich has 3 child 'audiostream' elements, where i need the 'streamurl'.
Best would be to store the 3 streamurls so i could change the quality later on. Then i should need to have a field in Class Station:
String[] streamurls = {www.1.mp3, www.2.mp3, www.3.mp3};
and store the 3 streamurls in there to select later on. I've tried some things that are posted here related to XML, Attribute and XElement, but i can't get it to work.
Is there anyone out there that knows a way?
Btw I don't really get how to highlight code and stuff here, I hope it works, otherwse I'm really sorry...
If you really want just the URLs, you can do something like (assuming the is a property of type IEnumerable<string> (or string[]) called StreamUrls on Station):
from brand in loadedData.Descendants("brand")
select new Station
{
Name = (string)brand.Element("name"),
Code = (int)brand.Element("code"),
Website = (string)brand.Element("websiteurl"),
StreamUrls = brand.Element("audiostreams")
.Elements("audiostream")
.Attributes("streamurl")
.Select(a => a.Value)
.ToArray()
}
If you want to get other information from the audiostream elements, declare a class like:
class Stream
{
public string Url { get; set; }
public string Codec { get; set; }
public string Rate { get; set; }
public bool IsDefault { get; set; }
}
And the query would then look like:
from brand in loadedData.Descendants("brand")
select new Station
{
Name = (string)brand.Element("name"),
Code = (int)brand.Element("code"),
Website = (string)brand.Element("websiteurl"),
Streams =
(from stream in brand.Element("audiostreams").Elements("audiostream")
select new Stream
{
Url = (string)stream.Attribute("streamurl"),
Codec = (string)stream.Attribute("codec"),
Rate = (string)stream.Attribute("streamrate"),
IsDefault = (string)stream.Attribute("default") == "true"
}).ToArray()
}
(If Codec and Rate can only have certain values, expressing them as an enum would be better than string.)
Hope it is useful
static void Main(string[] args)
{
XDocument loadedData = XDocument.Load("netten.xml");
var data = from query in loadedData.Descendants("brand")
group query by new { A = query.Element("name"), B = query.Element("code"), C = query.Element("websiteurl"), D = query.Element("audiostreams") } into g
select new
{
Name = g.Key.A + "",
Code = g.Key.B + "",
Website = g.Key.C + "",
AudioStreams = g.Key.D.Elements("audiostream")
.Attributes("streamurl")
.Select(x => x.Value)
.ToArray()
};
foreach (var x in data)
{
Console.WriteLine(x.Name);
Console.WriteLine(x.Code);
Console.WriteLine(x.Website);
foreach (var url in x.AudioStreams)
Console.WriteLine(url);
}
Console.ReadKey();
}
}
The xml file:
<?xml version="1.0" encoding="utf-8" ?>
<brands xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<brandgroup>
<brand>
<code>blabla</code>
<name>blabla</name>
<logo>blabla</logo>
<websiteurl>blabla</websiteurl>
<audiostreams>
<audiostream streamurl="www.1.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.2.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.3.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
</brandgroup>
<brandgroup>
<brand>
<code>blabla2</code>
<name>blabla2</name>
<logo>blabla</logo>
<websiteurl>blabla2</websiteurl>
<audiostreams>
<audiostream streamurl="www.4.mp3" codec="mp3" streamrate="low"/>
<audiostream streamurl="www.5.mp3" codec="mp3" streamrate="med" default="true"/>
<audiostream streamurl="www.6.mp3" codec="mp3" streamrate="high"/>
</audiostreams>
</brand>
</brandgroup>
</brands>

Categories