c#Linq To XML Same element names - c#

I have a small problem. I have an XML file where all the level 1 Elements have the same Element name and are distinguishable by a Name attribute. I cannot change the XML file.
Here is an example of the XML file:
<XProperty Name="DeviceInfo" FileVersion="1" xmlns="x-schema:XPropertySchema.xml" xmlns:dt="urn:schemas-microsoft-com:datatypes">
<Value dt:dt="i4">34</Value>
<Reserved>0</Reserved>
<XProperty Name="Manufacturer">
</XProperty>
<XProperty Name="ModelName">
<Value>Advantage N-SL</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name="SerialNumber">
<Value>N40000</Value>
<Reserved>0</Reserved>
</XProperty>
Now, what I need, in c# & Linq to XML, is some code that will set the value of Value sub-node of a specific XProperty node. For example, for the XProperty node with Name 'SerialNumber' set the value of the Value sub-node to 'XYZ'.
Second Example:
The value to change could also be a few descendants down (this is variable)
xml:
<XProperty Name="Versions">
<XProperty Name="Parameters" Persistent="yes">
<ShortDescription>Name</ShortDescription>
<LongDescription>Version object</LongDescription>
<Reserved>0</Reserved>
<XProperty Name="Index" Persistent="yes">
<ShortDescription>VersionIndex</ShortDescription>
<Value dt:dt="i4">1</Value>
<Reserved>0</Reserved>
</XProperty>
</XProperty>
</XProperty>
I need to change the value of Index.
The Class that contains what element to change:
public class XmlChanger
{
public string File { get; set; }
public string Key { get; set; }
public XmlChanger ChildKey { get; set; }
public string ChildKeyString { get; set; }
}
So With the same code I need example 1 to work and also example 2.
In some cases the Value sub-node might not exist, in which case I need to add it.
Any idea what the best way to do this is?
ps. I'm having some problem explaining but I hope someone get's what the meaning of it is.
Thanks in advance!

Use something like this:
var doc = XDocument.Parse(xml);
var element = doc.Root.Elements().
Where(x => x.Name.LocalName == "XProperty" &&
x.Attributes().Any(
y => y.Name == "Name" &&
y.Value == "DeviceInfo")
).SingleOrDefault();
if(element != null)
{
// change it
}
else
{
// add a new one
doc.Root.Add(new XElement(doc.Root.Name.Namespace+ "XProperty",
new XAttribute("Name", "DeviceInfo2")));
}
I used this XML definition:
var xml = #"<xml xmlns=""x-schema:XPropertySchema.xml"" xmlns:dt=""urn:schemas-microsoft-com:datatypes"">
<XProperty Name=""DeviceInfo"" FileVersion=""1"">
<Value dt:dt=""i4"">34</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name=""Manufacturer"">
</XProperty>
<XProperty Name=""ModelName"">
<Value>Advantage N-SL</Value>
<Reserved>0</Reserved>
</XProperty>
<XProperty Name=""SerialNumber"">
<Value>N40000</Value>
<Reserved>0</Reserved>
</XProperty>
</xml>";
I created this code in LINQPad and used a string variable to hold the xml file. In this scenario, I need Parse. If you want to load the XML directly from a file, XDocument.Load is the right way

var docx = XDocument.Load("someUri");
var elements = docx.Elements(XName.Get("XProperty")).Where(x => x.Attributes().Count(a => a.Name == XName.Get("Name") && a.Value == "SerialNumber") > 0);
foreach (var e in elements)
{
if (e.Elements().Count(x => x.Name == XName.Get("Value")) == 1)
{
e.Elements().Single(x => x.Name == XName.Get("Value")).Value = "XYZ";
}
else
{
e.Add(new XElement(XName.Get("Value"), "XYZ"));
}
}
I've not made concessions for the Namespace that your XML fragment contains, so you're going to have to add this to the XName.Get() method.

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 in Compact Framework 3.5

I have a following XML that I need to parse, but I am having some problems. First, the amount of tags that I have inside Class tag is not known, and they not distinct (so I can't specify them by their name).
XML example:
<Class value="1B2">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
<Class value="2C4">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
Now this is my code so far to parse it:
Define xmlReader and XElement
XmlReader xmlReader = XmlReader.Create(modFunctions.InFName);
XElement xElem = new XElement("FirstName");
Then I am making connection to SQL Server CE database and this is my main loop that reads xmlfile:
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element &&
(xmlReader.LocalName == "Class" || xmlReader.LocalName == "FirstName") &&
xmlReader.IsStartElement() == true)
{
// Find Class tag
if (xmlReader.LocalName == "Class")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
// Get 1B2 value
HRName = xElem.FirstAttribute.Value;
// Tried to read each node in xElement to get FirstName values.. this didn't work
for ( (XNode e in (XNode)xElem)
{
string newString = ((string)xElem.Element("FirstName"));
}
}
// Also tried this before, but it is skips it since FirstName tags are inside Class tag.
if (xmlReader.LocalName == "FirstName")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
record.SetValue(0, xElem.Value);
record.SetValue(1, HRName);
rs.Insert(record);
}
}
}
And in my table (to where I am trying to write this) consists of two columns (FirstName and Class)
As mentioned in the comments, Linq to Xml is your best bet: http://msdn.microsoft.com/en-us/library/system.xml.linq(v=vs.90).aspx
Your parse would look something like:
string xml = "<root><Class value='1B2'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class>" +
"<Class value='2C4'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class></root>";
XDocument doc = XDocument.Parse(xml);
foreach(var node in doc.Descendants("Class"))
{
var cls = node.Attribute("value").Value;
string[] firstNames = node.Descendants("FirstName").Select(o => o.Value).ToArray();
}

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;

Parsing xml. return one object not collection

I have a xml file:
<Result>Ok</Result>
<Error></Error>
<Remark></Remark>
<Data>
<Movies>
<Movie ID='2'>
<Name><![CDATA[TestName]]></Name>
<Duration Duration='170'>2h 50min</Duration>
<Properties>
<Property Name='11'><![CDATA[1111110]]></Property>
</Properties>
<Rental from_date='' to_date=''>
<SessionCount></SessionCount>
<PU_NUMBER></PU_NUMBER>
</Rental>
</Movie>
</Movies>
</Data>
</XML>
Code for pasring xml file:
var results = from element in XDocument.Parse(queryResponse).Descendants("Movie")
select new BaseEvent
{
OID = (int)element.Attribute("ID"),
Subject = (string)element.Element("Name"),
Duration = (int)element.Element("Duration").Attribute("Duration")
};
The problem in that Descedants retruns IEumerable<BaseEvent> but I want that will be BaseEvent. How can I do this?
Just use First(), Last(), Single(), FirstOrDefault() etc.
Personally I'd do that initially, rather than doing it all in a query:
var element = XDocument.Parse(queryResponse)
.Descendants("Movie")
.FirstOrDefault();
if (element == null)
{
// Handle the case of no movies
}
else
{
var baseEvent = new BaseEvent
{
OID = (int) element.Attribute("ID"),
Subject = (string) element.Element("Name"),
Duration = (int) element.Element("Duration")
.Attribute("Duration")
};
// Use baseEvent
}
Add a .First() to get the first element:
from element in XDocument.Parse(queryResponse).Descendants("Movie")
select new BaseEvent
{
OID = (int)element.Attribute("ID"),
Subject = (string)element.Element("Name"),
Duration = (int)element.Element("Duration").Attribute("Duration")
}.First();
You can alternatively use FirstOrDefault() (in case there are no such nodes), Last() or Single().

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