Given an XML Document - for instance:
<factory>
<widgets>
<widget>Foo</widget>
<widget>Bar</widget>
<widget>Baz</widget>
<widget>Qux</widget>
</widgets>
</factory>
I wish to build a line-break separated string of widget values - using the above XML, this would be:
Foo
Bar
Baz
Qux
The code I'm using to do this is:
var doc = XDocument.Parse(xml) //where XML is a string containing the above XML
var builder = new StringBuilder();
foreach(var widget in doc.Root.Element("widgets").Elements("widget"))
{
builder.AppendLine(widget.Value);
}
However, the resulting string is FooBarBazQux rather than a newline-separated version. Setting a breakpoint on the AppendLine call reveals that widget.Value is being set to "FooBarBazQux", and the loop runs once rather than the 4 times I'm expecting.
I've tried running the query in LinqPad:
XDocument settings = XDocument.Parse (#"
<factory>
<widgets>
<widget>Foo</widget>
<widget>Bar</widget>
<widget>Baz</widget>
<widget>Qux</widget>
</widgets>
</factory>");
foreach(var x in settings.Root.Elements("widgets").Elements("widget"))
x.Value.Dump("Widget Type");
and the results are correct and as expected.
Can anyone help me in getting a newline separated string of Widget values? I'm at a bit of a loss!
It might be worth noting that this is within a Xamarin.Forms application, using the PCL version of using System.Xml.Linq.
I don't know whether I should add it as an answer so it can be marked as such (or if I will get shouted at because I have already put it in a comment)
builder.ToString()
This was a stupid mistake, caused by "Fast Deployment" being enabled in the Android Xamarin project.
My XML File was being generated by the app if it didn't exist - I assumed that on re-deploying, all app assets were to being removed from the device. It turns out that when Fast Deployment is enabled, the device only updates NEW components of the application - i.e. the XML file wasn't being removed. The problem was caused by the XML file being out of date.
I believe that the XML document being initially incorrect was caused by the potential issue that StuartLC pointed out - I must have at one point called builder.AppendLine(doc.Root.Element("widgets").Value), which returns the concatenation of the value of all child elements of widgets.
Thanks for all the help!
Related
First I load the file in a structure
XElement xTree = XElement.Load(xml_file);
Then I create an enumerable collection of the elements.
IEnumerable<XElement> elements = xTree.Elements();
And iterate elements
foreach (XElement el in elements)
{
}
The problem is - when I fail to parse the element (a user made a typo or inserted a wrong value) - how can I report exact line in the file?
Is there any way to tie an element to its corresponding line in the file?
One way to do it (although not a proper one) –
When you find a wrong value, add an invalid char (e.g. ‘<’) to it.
So instead of: <ExeMode>Bla bla bla</ExeMode>
You’ll have: <ExeMode><Bla bla bla</ExeMode>
Then load the XML again with try / catch (System.Xml.XmlException ex).
This XmlException has LineNumber and LinePosition.
If there is a limited set of acceptable values, I believe XML Schemas have the concept of an enumerated type -- so write a schema for the file and have the parser validate against that. Assuming the parser you're using supports Schemas, which most should by now.
I haven't looked at DTDs in decades, but they may have the same kind of capability.
Otherwise, you would have to consider this semantic checking rather than syntactic checking, and that makes it your application's responsibility. If you are using a SAX parser and interpreting the data as you go, you may be able to get the line number; check your parser's features.
Otherwise the best answer I've found is to report the problem using an xpath to the affected node/token rather than a line number. You may be able to find a canned solution for that, either as a routine to run against a DOM tree or as a state machine you can run alongside your SAX code to track the path as you go.
(All the "maybe"s are because I haven't looked at what's currently available in a very long time, and because I'm trying to give an answer that is valid for all languages and parser implementations. This should still get you pointed in some useful directions.)
I'm working in Xamarin on one android app which is parsing xml from this webiste: http://video.cazin.net/rss.php, and populate listview and in particular I have a problem getting value from this tag:
<media:thumbnail url="http://video.cazin.net/uploads/thumbs/2d07f1e49-1.jpg" width="480" height="360"/>
I created namespace:
xmlNameSpaceManager.AddNamespace("ab", "http://search.yahoo.com/mrss/");
and than tried to get value from url attribute:
XmlNodeList xmlNode = document.SelectNodes("rss/channel/item");
if (xmlNode[i].SelectSingleNode("//ab:thumbnail[#url='http://video.cazin.net/rss.php']", xmlNameSpaceManager) != null)
{
var thumbnail = xmlNode[i].SelectSingleNode("//ab:thumbnail=[#url='http://video.cazin.net/rss.php']", xmlNameSpaceManager);
feedItem.Thumbnail = thumbnail.Value;
}
I also tried something like this:
//ab:thumbnail/#url
but than I got value of just first image. I'm sure the problem is here somewhere because I have the same code parisng images from another xml tag without colon inside and it's working correctly. Does anyone had similar experience and knows what I should put in those braces? Thanks
Your current query is searching for a thumbnail element where the url attribute is equal to http://video.cazin.net/rss.php - there are none that match this.
Your 'I also tried' query of //ab:thumbnail/#url is closer, but the // means that the query will start from the root of the document, so you get the all urls (but you only take the first).
If you want the element that matches taking the current node context into consideration, you need to include the current node context in the query - this is represented by .. So .//ab:thumbnail/#url would find all url attributes in a thumbnail element contained by the current node. You can see the result in this fiddle.
I would strongly suggest you use LINQ to XML instead, however. It's a lot nicer to work with than the old XmlDocument API. For example, you could find all item thumbnail urls using this code:
var doc = XDocument.Load("http://video.cazin.net/rss.php");
XNamespace media = "http://search.yahoo.com/mrss/";
var thumbnailUrls = doc.Descendants("item")
.Descendants(media + "thumbnail")
.Attributes("url");
I need some help with reading an oddly-formatted XML file. Because of the way the nodes and attributes are structured, I keep running into XMLException errors (at least, that's what the output window is telling me; my breakpoints refuse to fire so that I can check it). Anyway, here's the XML. Anyone experienced anything like this before?
<ApplicationMonitoring>
<MonitoredApps>
<Application>
<function1 listenPort="5000"/>
</Application>
<Application>
<function2 listenPort="6000"/>
</Application>
</MonitoredApps>
<MIBs>
<site1 location="test.mib"/>
</MIBs>
<Community value="public"/>
<proxyAgent listenPort="161" timeOut="2"/>
</ApplicationMonitoring>
Cheers
EDIT: Current version of the parsing code (file path shortened - Im not actually using this one):
XmlDocument xml = new XmlDocument();
xml.LoadXml(#"..\..\..\ApplicationMonitoring.xml");
string port = xml.DocumentElement["proxyAgent"].InnerText;
Your problem in loading the XML is that xml.LoadXml expects you to pass the xml document as a string, not a file reference.
Try instead using:
xml.Load(#"..\..\..\ApplicationMonitoring.xml");
Essentially in your original code you are telling it that your xml document is
..\..\..\ApplicationMonitoring.xml
And I'm sure you can now see why there is a parse exception. :) I've tested this with your xml document and the modified load and it works fine (except for the issue that Only Bolivian Here pointed out with the fact that your inner Text is not going to return anything.
For completeness you probably want:
XmlDocument xml = new XmlDocument();
xml.Load(#"..\..\..\ApplicationMonitoring.xml");
string port = xml.DocumentElement["proxyAgent"].Attributes["listenPort"].Value;
//And to get stuff more specifically in the tree something like this
string function1 = xml.SelectSingleNode("//function1").Attributes["listenPort"].Value;
Note the use of the Value property on the attribute and not the ToString method which won't do what you are expecting.
Exactly how you extract the data from the xml is probably dependant on what you are doing with it. For example you may want to get a list of Application nodes to enumerate over with a foreach by doing this xml.SelectNodes("//Application").
If you are having trouble with extdacting stuff though that is probably the scope of a different question since this was just about how to get the XML document loaded.
xml.DocumentElement["proxyAgent"].InnerText;
The proxyAgent element is self closing. InnerText will return the string inside of an XML element, in this case, there is no inner elements.
You need to access an attribute of the element, not the InnerText.
Try this:
string port = xml.GetElementsByTagName("ProxyAgent")[0].Attributes["listenPort"].ToString();
Or use Linq to XML:
http://msdn.microsoft.com/en-us/library/bb387098.aspx
And... your XML is not malformed...
I have an XML file. When I try to load it using .LOAD methods, I get this exception:
System.Xml.XmlException: data at root level invalid at position 1 line 1.
What I have at the beginning of the XML file is this:
<?xml version="1.0" standalone="yes" ?>
I think that string that is used for LoadXml is constructed wrong by either
ignoring BOM and forcing wrong encoding
reading BOM as first character
constructed by hand altogether and first character is not <
Based on last comment I bet that code looks like (or some variation of it) instead of loading XML directly from Stream object (which will handle encoding properly):
// My guess of how wrong code looks like! Not a solution!!!!
StreamReader r = new StreamReader(path, System.Text.Encoding.Unicode);
string xml = r.ReadToEnd();
XmlDocument d = new XmlDocument();
d.LoadXml(xml);
You should review your code that constructs the string you are using in XmlDocument.LoadXml and check if it is indeed valid XML. I'd recommend to create small program that models code that is failing and investigate the behavior.
Position 1 line 1 suggests a problem with the very first char it encounters.
I would suggest firstly confirming that no leading whitespace/other char is in there (sounds silly, but they can creep in easily).
It could also be a char encoding issue, causing that first char to not be read as a '<'.
I bet it's not there. I've found that when I've gotten this error the file or path is missing/incorrect.
Thanks for pouring in your suggestions. The problem was on the build server, the XML file was being pulled from a field called contents in a table called File. I am accessing the XML using the FileID. But the FileID is not the same as FileID on my local database. So, On the build server, I was pulling the XML from a test record which had dummy data. Hence the error. Hope I have made sense. I have fixed the issue by dynamically finding the FileID and querying the contents.
Is it possible to use variables like <%=person.LastName %> in XML string this way?
XElement letters = new XElement("Letters");
XElement xperson = XElement.Parse("<Table><Row><Cell><Text>
<Segment>Dear <%=person.Title%> <%=person.FirstName%> <%=person.LastName%>,
</Segment></Text></Cell></Row>...");
foreach (Person person in persons){
letters.Add(xperson)
}
If it's possible, it would be a lifesaver since I can't use XElement and XAttribute to add the nodes manually. We have multiple templates and they frequently change (edit on the fly).
If this is not doable, can you think of another way so that I can use templates for the XML?
Look like it's possible in VB
http://aspalliance.com/1534_Easy_SQL_to_XML_with_LINQ_and_Visual_Basic_2008.6
This is an exclusive VB.NET feature known as XML Literals. It was added in VB 9.0. C# does not currently support this feature. Although Microsoft has stated its intent to bridge the gap between the languages in the future, it's not clear whether this feature will make it to C# any time soon.
Your example doesn't seem clear to me. You would want to have the foreach loop before parsing the actual XML since the values are bound to the current Person object. For example, here's a VB example of XML literals:
Dim xml = <numbers>
<%= From i In Enumerable.Range(1, 5)
Select <number><%= i %></number>
%>
</numbers>
For Each e In xml.Elements()
Console.WriteLine(e.Value)
Next
The above snippet builds the following XML:
<numbers>
<number>1</number>
<number>2</number>
<number>3</number>
</numbers>
Then it writes 1, 2, 3 to the console.
If you can't modify your C# code to build the XML dynamically then perhaps you could write code that traverses the XML template and searches for predetermined fields in attributes and elements then sets the values as needed. This means you would have to iterate over all the attributes and elements in each node and have some switch statement that checks for the template field name. If you encounter a template field and you are currently iterating attributes you would set it the way attributes are set, whereas if you were iterating elements you would set it the way elements are set. This is likely not the most efficient approach but is one solution.
The simplest solution would be to use VB.NET. You can always develop it as a stand-alone project, add a reference to the dll from the C# project, pass data to the VB.NET class and have it return the final XML.
EDIT: to clarify, using VB.NET doesn't bypass the need to update the template. It allows you to specify the layout easier as an XML literal. So code still needs to be updated in VB once the layout changes. You can't load an XML template from a text file and expect the fields to be bound that way. For a truly dynamic solution that allows you to write the code once and read different templates my first suggestion is more appropriate.