C# / XML - Question - c#

I’ve got a problem witch I’ve been trying to solve almost for a week now, but it seems that, unfortunately, I can’t manage it by myself.
Maybe somebody could help me.
I’ve got this type of source XML:
<data>
<para1>24499</para1>
<para2>32080148</para2>
<para4>20e500cc6008d0f8ab1fd108b220ca261f85edd9</para4>
<para6></para6>
<timetype>4</timetype>
<fkcontent>964342</fkcontent>
<season>0</season>
<fmstoken><![CDATA[7bca3c544ad64e526806fb5a6b845148]]></fmstoken>
<fmstoken_user>32010484</fmstoken_user>
<fmstoken_time>1283165972</fmstoken_time>
<fmstoken_renew><![CDATA[http://www.sky.com/logic/fmstoken.php?method=refresh]]></fmstoken_renew>
<adserverXML><![CDATA[http://www.sky.de/dummy.xml]]></adserverXML>
<playlist>
<videoinfo quality="0" name="DSL 1000">
<id>24499</id>
<noad>1</noad>
<productplacement>0</productplacement>
<filename>http://www.sky.com/video/1/V_53511_BB00_E81016_46324_16x9-lq-512x288-vp6-c0_bbb491b3ce64ef667340a21e2bfb3594.f4v</filename>
<title><![CDATA[Who will be the winner?]]></title>
</videoinfo>
<videoinfo quality="1" name="DSL 2000">
<id>24499</id>
<noad>1</noad>
<productplacement>0</productplacement>
<filename>http://www.sky.de/video/1/V_53513_BB00_E81016_46324_16x9-hq-512x288-vp6-c0_fa948bc5429cf28455779666cc59cf5e.f4v</filename>
<title><![CDATA[Who will be the winner?]]></title>
</videoinfo>
</playlist>
</data>
And here are parts of the code that let me get required tag content from xml page above:
private static string getTagContent(string source, string tag)
{
string fullTagBegin = "<" + tag + ">";
string fullTagEnd = "</" + tag + ">";
int indexBegin = source.IndexOf(fullTagBegin) + fullTagBegin.Length;
int indexEnd = source.IndexOf(fullTagEnd);
int indexLength = indexEnd - indexBegin;
if (indexBegin == -1 || indexEnd == -1)
return "UNKNOWN";
return source.Substring(indexBegin, indexLength);
}
public static void Start(String url)
{
try
{
String urlXML = url;
WebClient wClient = new WebClient();
string sourceXML = wClient.DownloadString(urlXML);
sourceXML = sourceXML.Replace("]]>", "");
sourceXML = sourceXML.Replace("<![CDATA[", "");
String para1 = getTagContent(sourceXML, "para1");
String para2 = getTagContent(sourceXML, "para2");
String para4 = getTagContent(sourceXML, "para4");
String timetype = getTagContent(sourceXML, "timetype");
String fkcontent = getTagContent(sourceXML, "fkcontent");
String season = getTagContent(sourceXML, "season");
String fmstoken = getTagContent(sourceXML, "fmstoken");
String fmstoken_user = getTagContent(sourceXML, "fmstoken_user");
String fmstoken_time = getTagContent(sourceXML, "fmstoken_time");
String fmstoken_renew = getTagContent(sourceXML, "fmstoken_renew");
String filename = getTagContent(sourceXML, "filename").Replace("http://", "");
String title = System.Text.RegularExpressions.Regex.Replace(getTagContent(sourceXML, "title"), #"[^a-zA-Z0-9]","_");
The problem is:
everything works fine except the fact, that there are two "filename" and "title" tags in the source xml, but I need to choose only second ones, those that are under this line:
<videoinfo quality="1" name="DSL 2000">,
and somehow skip/ignore first ones, those that are above previous line and right under this line:
<videoinfo quality="0" name="DSL 1000">
I can't figure out how to do that.
(My only guess is that maybe it has something to do with XPathNavigator, but I’m not sure if that’s a right guess, and anyway, I don’t really understand how to use it properly).
Edit: problem solved.
I want to thank everyone who replied for your suggestions.
Really appreciated!

This is really not the right way to go about working with XML in .Net.
You didn't mention which version of .Net you are developing for. Depending on the version look into using XmlDocument, XDocument / LINQ to XML.
MSDN on LINQ to XML
MSDN on XmlDocument

You should really load the XML into XMlDocument object and then edit it.
But if you prefer to use your existing code, this dirty code should do the trick.
int indexBegin = source.IndexOf(fullTagBegin) == source.LastIndexOf(fullTagBegin) ? source.IndexOf(fullTagBegin) + fullTagBegin.Length : source.LastIndexOf(fullTagBegin) + fullTagBegin.Length;
int indexEnd = source.IndexOf(fullTagEnd) == source.LastIndexOf(fullTagEnd) ? source.IndexOf(fullTagEnd) : source.LastIndexOf(fullTagEnd);
This will move the indexes to the last occurrence of whatever tag you're looking for. Just replace your declarations with this ones.
Edit: Additionally, you use this easy few lines to find/manipulate your XML in a much cleaner way.
XmlDocument doc = new XmlDocument();
doc.Load(filename);
// or doc.LoadXML(fullXMLcode);
var elements = doc.GetElementsByTagName("title");
var element = elements.Item(elements.Count - 1); // returns the last element
// element.InnerText gets the value you need. You can use this property to change it, too
Hope this helps.

You need this XPath expression:
/data/playlist/videoinfo[2]/filename | /data/playlist/videoinfo[2]/title
Or
/data/playlist/videoinfo[2]/*[self::filename or self::title]
These expression return a node set with filename and title element in document order.
In C# (I'm not an expert):
XPathDocument doc = new XPathDocument("document.xml");
XPathNodeIterator nodeset = doc.CreateNavigator()
.Select("/data/playlist/videoinfo[2]/*[self::filename or self::title]");
foreach (XPathNavigator node in nodeset)
{
// Your code
}

As many people have already said, XPath and LINQ are both suitable. Here's LINQ to XML sample:
XDocument doc = XDocument.Load("yourXml.xml");
var result =
(from videoInfo in doc.Descendants("videoinfo")
let quality = videoInfo.Attribute("quality")
let name = videoInfo.Attribute("name")
where (quality != null && quality.Value == "1")
&& (name != null && name.Value == "DSL 2000")
select new
{
Title = videoInfo.Element("title"),
FileName = videoInfo.Element("filename")
}
).First();
string title = result.Title.Value;
string fileName = result.FileName.Value;

Related

XDocument Descendants and Element always return null values

Hey all i have looked thoroughly through all the questions containing XDocument and while they are all giving an answer to what I'm looking for (mostly namespaces issues) it seems it just won't work for me.
The problem I'm having is that I'm unable to select any value, be it an attribute or element.
Using this XML
I'm trying to retrieve the speaker's fullname.
public void GetEvent()
{
var xdocument = XDocument.Load(#"Shared\techdays2013.xml");
XNamespace xmlns = "http://www.w3.org/2001/XMLSchema-instance";
var data = from c in xdocument.Descendants(xmlns + "speaker")
select c.Element(xmlns + "fullname").Value;
}
You can omit the namespace declaration in your linq statement.
public void GetEvent()
{
var xdocument = XDocument.Load(#"Shared\techdays2013.xml");
//XNamespace xmlns = "http://www.w3.org/2001/XMLSchema-instance";
var data = from c in xdocument.Descendants("speaker")
select c.Element("fullname").Value;
}
You can omit WebClient because you have direct local access to a file. I'm just showing a way to process your file on my machine.
void Main()
{
string p = #"http://events.feed.comportal.be/agenda.aspx?event=TechDays&year=2013&speakerlist=c%7CExperts";
using (var client = new WebClient())
{
string str = client.DownloadString(p);
var xml = XDocument.Parse(str);
var result = xml.Descendants("speaker")
.Select(speaker => GetNameOrDefault(speaker));
//LinqPad specific call
result.Dump();
}
}
public static string GetNameOrDefault(XElement element)
{
var name = element.Element("fullname");
return name != null ? name.Value : "no name";
}
prints:
Bart De Smet
Daniel Pearson
Scott Schnoll
Ilse Van Criekinge
John Craddock
Corey Hynes
Bryon Surace
Jeff Prosise
1) You have to drop the namespace
2) You'll have to query more precisely. All your <speaker> elements inside <speakers> have a fullname but in the next section I spotted <speaker id="94" />
A simple fix (maybe not the best) :
//untested
var data = from c in xdocument.Root.Descendants("speakers").Descendants("speaker")
select c.Element("fullname").Value;
You may want to specify the path more precise:
xdocument.Element("details").Element("tracks").Element("speakers").

XML Reader threw Object Null exception, but node exists(?!)

I am hoping someone could enlighten me as to why I am getting the annoying - "xml object reference not set to an instance .." error.
The elements (nodes?) I am looking for seem to exist and I have not misspelled it either :[
I might be doing something stupid here, but any help at all would be greatly appreciated.
My Code:
private void button1_Click(object sender, RoutedEventArgs e)
{
XmlDocument reader = new XmlDocument();
reader.Load("Kotaku - powered by FeedBurner.xml");
XmlNodeList titles = reader.GetElementsByTagName("title");
XmlNodeList dates = reader.GetElementsByTagName("pubDate");
XmlNodeList descriptions = reader.GetElementsByTagName("description");
XmlNodeList links = reader.GetElementsByTagName("link");
for (int i = 0; i < titles.Count; i++)
{
textBox1.AppendText(Environment.NewLine + titles[i].InnerText);
textBox1.AppendText(Environment.NewLine + descriptions[i].InnerText); //<<-- Throws Object Ref Null Exception
textBox1.AppendText(Environment.NewLine + links[i].InnerText);
textBox1.AppendText(Environment.NewLine + dates[i].InnerText); //<<-- Throws Object Ref Null Exception
}
}
The XML I am using is a saved XML page from: http://feeds.gawker.com/kotaku/full
The way I am working on it now is as follows: I have saved the page from the above link (which is an XML page) and put it next to my EXE for easier access.
Then I run the code.
The way that you read RSS is wrong. First get all the items and loop though each item and build the text. Better if you can use StringBuilder and finally convert it to string.
Before access properties of an object you can check for null.
XmlDocument RSSXml = new XmlDocument();
RSSXml.Load("Kotaku - powered by FeedBurner.xml");
XmlNodeList RSSNodeList = RSSXml.SelectNodes("rss/channel/item");
StringBuilder sb = new StringBuilder();
foreach (XmlNode RSSNode in RSSNodeList)
{
XmlNode RSSSubNode;
RSSSubNode = RSSNode.SelectSingleNode("title");
string title = RSSSubNode != null ? RSSSubNode.InnerText : "";
RSSSubNode = RSSNode.SelectSingleNode("link");
string link = RSSSubNode != null ? RSSSubNode.InnerText : "";
RSSSubNode = RSSNode.SelectSingleNode("description");
string desc = RSSSubNode != null ? RSSSubNode.InnerText : "";
RSSSubNode = RSSNode.SelectSingleNode("pubDate");
string pubDate = RSSSubNode != null ? RSSSubNode.InnerText : "";
sb.Append("<font face='arial'><p><b><a href='");
sb.Append(link);
sb.Append("'>");
sb.Append(title);
sb.Append("</a></b><br/>");
sb.Append(desc);
sb.Append(pubDate);
sb.Append("</p></font>");
}
textBox1.Text = sb.ToString();
It cannot run in for loop because number of items in titles, descriptions, links and dates was (at the time when I ran it) respectively: 39 38 39 37.
I checked the source of http://feeds.gawker.com/kotaku/full.
What I see - in the HMTL source at least - there is a <title> element in the channel header too. Which not belongs to any item yet. Isn't it possible, that your code count some additional elements like this when you use title.Count as the limit of your for cicle?
If that is the case, it is possible, that you wont have enough elements in the other arrays in your loop. Did you check this too?

c# code for getting xml elements

I have following xml file:
<os:tax>
<os:cat name="abc" id="1">
<os:subcat name="bcd" id="11">
<os:t name="def" id="111">
<os:cut name="hello" id="161" cutURL="/abc/a.html"/>
<os:cut name="hello2" id="162" cutURL="/abc1/a1.html"/>
<os:cut name="hello3" id="163" cutURL="/abc4/a3.html"/>
</os:t>
</os:subcat>
</os:cat>
<os:cat name="def" id="2">
<os:subcat name="bcd" id="22">
<os:t name="def" id="222">
<os:cut name="hello" id="171" cutURL="/abcs/a.html"/>
<os:cut name="hello2" id="172" cutURL="/abcs1/a1.html"/>
<os:cut name="hello3" id="173" cutURL="/abcs4/a3.html"/>
</os:t>
</os:subcat>
</os:cat>
</os:tax>
Its a bigger file with lot of os:cat under it. I need to get string value for:
os:cat -> id , name
os:subcat -> id, name
os: t -> id, name
os: cut -> id, name, cutURL
I have this so far:
XmlNodeList tax = xmlDoc.GetElementsByTagName("os:tax");
foreach (XmlNode node in tax)
{
XmlElement cat = (XmlElement)node;
// than get string values here?
}
Is this correct? Can anyone show me efficient way to do this? Or right way to do this easily?
Here's a sample for LINQ to XML - but I strongly suggest you look for full LINQ to XML tutorials. (And get to grips with the rest of LINQ...)
(EDIT: I hadn't spotted the t part before.)
XDocument doc = XDocument.Load("tax.xml");
XNamespace os = "http://something"; // You haven't included the declaration...
foreach (XElement cat in doc.Descendants(os + "cat"))
{
int catId = (int) cat.Attribute("id");
string catName = (string) cat.Attribute("name");
foreach (XElement subcat in cat.Elements(os + "subcat"))
{
int subId = (int) subcat.Attribute("id");
string subName = (string) subcat.Attribute("name");
foreach (XElement t in subcat.Elements(os + "t"))
{
int tId = (int) t.Attribute("id");
string tName = (string) t.Attribute("name");
foreach (XElement cut in t.Elements(os + "cut"))
{
string cutId = (int) cut.Attribute("id");
string cutName = (string) cut.Attribute("name");
string cutUrl = (string) cut.Attribute("cutURL");
// Use the variables here
}
}
}
}
This assumes there's only one subcat for each cat - I don't know if that's correct.
You may want to express this as a LINQ query instead... it depends on what you need to do.
Here's a LINQ query version - having looked at everything you're using, I think this makes more sense:
XDocument doc = XDocument.Load("tax.xml");
XNamespace os = "http://something"; // You haven't included the declaration...
var query = from cat in doc.Descendants(os + "cat")
from subcat in cat.Elements(os + "subcat")
from t in subcat.Elements(os + "t")
from cut in t.Elements(os + "cut")
select new
{
CatId = (int) cat.Attribute("id"),
CatName = (string) cat.Attribute("name"),
SubCatId = (int) subcat.Attribute("id"),
SubCatName = (string) subcat.Attribute("name"),
TId = (int) t.Attribute("id"),
TName = (string) t.Attribute("name"),
CutId = (int) cut.Attribute("id")
CutName = (string) cut.Attribute("name")
CutUrl = (string) cut.Attribute("cutURL")
};
Note that I've converted all the ID values to int rather than string. You could convert them to strings instead, of course, but if they are all integers, it makes sense to parse them as such.
Jon's suggestion to use LINQ to XML is the way to go, but I've included the old way below. My XPath is a little (very) rusty, so forgive me if there are any mistakes:
var doc = new XmlDocument(); //your document
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
xmlnsManager.AddNamespace("os", "http://bla");
foreach (XmlNode node in doc.SelectNodes("//os:subcat/os:t/os:cut", xmlnsManager))
{
string value = node.Attributes.GetNamedItem("name").Value;
}
See this article if you need more help: http://support.microsoft.com/kb/318545
Consider using XElement along with Lambda expression.
XNamespace osNs = "http://xml.com"; // Like Jon said, you haven't included the namespace url
XElement taxElement = XElement.Load("path/to/your/xml/file");
foreach(var cat in taxElement.Decendents(osNs + "cat"))
{
Console.WriteLine(cat.Attribute("id").Value);
foreach(var subcat in cat.Decendents(osNs + "subcat"))
{
Console.WriteLine(subcat.Attribute("id").Value);
foreach(var t in subcat.Decendents(osNs + "t"))
{
Console.WriteLine(t.Attribute("id").Value);
foreach(var cut in t.Decendents(osNs + "cut"))
{
Console.WriteLine(cut.Attribute("id").Value);
Console.WriteLine(cut.Attribute("name").Value);
Console.WriteLine(cut.Attribute("cutURL").Value);
}
}
}
}
It's just capturing one node at by another. If you want to get all the curURL then you can just write something like this:
foreach(var cut in taxElement.Decendents(osNs + "cut"))
{
Console.WriteLine(cut.Attribute("cutURL"));
}
Even you can use Lambda like if you want something like all os:cut where os:subcat id = 22
taxElement.Decendents("osNs + "subcat").Where(p => p.Attribute("id").Value == "22").Decendents(osNs + "cut");
Please go through some tutorial on LINQ to XML or something on XElement.
Hope this helps!

retrieving xml text value

Would like to ask some advice when working with xml data with C#.
I have a small practice exercise where I am required to retrieve a specific text value at a specific tag.
I have assigned the various names of the element nodes to string values and the the user is required to input a string value to the console and if the name tag is the same as the input then to retrieve the text value positioned at that tag.
This is the C# code I used but I am not sure how to retrieve the text value at the name tag.
int priceSpecific;
string destination;
ArrayList array = new ArrayList();
xRootNode = xdoc.DocumentElement;
string firstValue = xRootNode.FirstChild.FirstChild.Name;
string secondValue = xRootNode.FirstChild.FirstChild.NextSibling.Name;
string thirdValue = xRootNode.FirstChild.FirstChild.NextSibling.NextSibling.Name;
string fourthValue = xRootNode.FirstChild.FirstChild.NextSibling.NextSibling.NextSibling.Name;
array.AddRange(new object[] { firstValue, secondValue, thirdValue, fourthValue});
Console.WriteLine("Please enter your destination, first letter capital");
destination = Console.ReadLine();
The idea is to loop through the arraylist and retrieve the name of the element node that is the same as the string input of the user.
Any advice as to how to retrieve the text value?
Regards
That is some pretty nasty looking code there! I would recommend that you spend a few hours learning about Linq-to-XML. roughly speaking, if you want to find the value of an element with a given name, it can be done as follows:
string elementName = "foo";
XDocument doc = XDocument.Parse("<xml document goes here>");
string matchedValue = doc.Descendants(elementName).Single().Value;
Much simpler!
You can use several approaches, most usable in your scenario seem to be:
XmlDocument + XPath (supported in all .NET versions)
XmlReader (supported in all .NET versions)
XDocument (supported with LINQ since .NET 3.0)
XDocument with LINQ syntax
Choices 3 or 4 are preferred if .NET 3 or above is available and xml document is not too big (document size of several MB is the boundary).
Choice 1 uses XPath, which allows very strong queries into the document structure
1.
XPathDocument document = new XPathDocument(#"myFile.xml");
XPathNavigator navigator = document.CreateNavigator();
string foundElementContent =
navigator.SelectSingleNode("//myElement[position()=1]/text()").ToString();
2.
string elementNameToFind = "myElement";
XmlReader xmlReader = XmlReader.Create(#"myFile.xml");
string foundElementContent = string.Empty;
while (xmlReader.Read())
{
if(xmlReader.NodeType==XmlNodeType.Element &&
xmlReader.Name == elementNameToFind)
{
foundElementContent=xmlReader.ReadInnerXml();
break;
}
}
xmlReader.Close();
3.
string elementNameToFind = "myElement";
XDocument xmlInMemoryDoc = XDocument.Load(#"myFile.xml");
XElement foundElement = xmlInMemoryDoc.Descendants(elementNameToFind).First();
4.
string elementNameToFind = "myElement";
XDocument xmlInMemoryDoc = XDocument.Load(#"myFile.xml");
XElement foundElement =
(
from e in xmlInMemoryDoc.Descendants()
where e.Name == elementNameToFind
select e
).First();

C# Foreach XML node

I'm trying to add all the nodes in an XML file into a listView, and I'm doing something wrong but I can't for the life of me figure it out even after looking at a load of examples. This is the XML snippet:
<queue>
<slots>
<slot>
<status>Downloading</status>
<filename>file1</filename>
<size>1 GB</size>
</slot>
<slot>
<status>Downloading</status>
<filename>file2</filename>
<size>2 GB</size>
</slot>
</slots>
</queue>
And here's the code:
XDocument xDoc = XDocument.Load(xmlFilePath);
List<Download> list = new List<Download>();
foreach (var download in xDoc.Descendants("slots"))
{
string filename = download.Element("filename").Value;
string size = download.Element("size").Value;
string status = download.Element("status").Value;
list.Add(new Download { Filename = filename, Size = size, Status = status });
}
Any help greatly appreciated as always.
EDIT: To clarify, I'm getting a NullReferenceException on
string filename = download.Element("filename").Value;
And i know the listview is missing, I've not done that bit yet :)
var list = (from download in xDoc.Descendats("slot")
select new Download
{
Filename = (string) download.Element("filename"),
Size = (string) download.Element("size"),
Status = (string) download.Element("status")
}).ToList();
This looks nicer, and since you didn't say what exactly is wrong with your code, it's about all I can do.
Update: just tested this, and it fixes your exception.
The XML in your example works fine. The NullReferenceException is happening because the real XML you're using doesn't have a filename element in one of the slots. You can use
string filename = download.Element("filename") == null ?
String.Empty : download.Element("filename").Value;
instead if there is a possible default for the filename. But more likely correctly handling this exception is better.
void LoadSlots()
{
XmlDocument doc = new XmlDocument();
doc.Load(Environment.CurrentDirectory + "\\queue.xml");
XmlNodeList nodes = doc.SelectNodes("//queue/slots/slot");
foreach (XmlNode node in nodes)
{
string filename = node.Attributes["filename"].InnerText;
string size = node.Attributes["size"].InnerText;
string status = node.Attributes["status"].InnerText;
_slots.Add(filename, size, status);
}
}

Categories