I'm a bit stuck, currently getting a an error being caught in the exception 'ex' at the start of the second run through of the foreach loop.
Why is this happening and what am I doing wrong?
{"Unable to cast object of type 'System.Xml.Linq.XText' to type 'System.Xml.Linq.XElement'."} System.Exception {System.InvalidCastException}'
My code:
public static void LoadMyThing()
{
string configfp = #"\ConfigFiles\config.xml";
string basefp = #"\ConfigFiles\";
try
{
List<string> xmlNodes = new List<string>();
XDocument Xdoc = XDocument.Load(configfp);
XElement XmlLst = Xdoc.Element("mylistener");
foreach (XElement node in XmlLst.Element("parent").DescendantNodes())
{
thingy = (basefp + node.Value);
Thingylist.Add(thing);
}
}
catch(Exception ex)
{
WriteErrorLog("Thingy Loading error " + ex.ToString());
}
}
The XML it's calling to:
<?xml version="1.0" encoding="utf-8"?>
<mylistener>
<config>
<mailuser>b#c.com</mailuser>
<mailpass>H</mailpass>
<mailip>190</mailip>
<mailport>2</mailport>
<recipient>m</recipient>
</config>
<parent>
<b>\RFC16</b>
<b>\RFC122</b>
<b>\RF1</b>
<b>\RF32</b>
<b>\R33</b>
<b>\RFCIB</b>
</parent>
</mylistener>
Hard to be sure without seeing your XML, but if the parent node has non XElement descendants then I'd expect to see this. For example
<parent>
<some node />
some text
</parent>
You need to know how you want to handle this case, but one way to avoid an exception would be to iterate XNodes rather than XElements
foreach (XNode node in XmlLst.Element("parent").DescendantNodes())
{
if (node is XElement)
{
thingy = (basefp + ((XElement)node).Value);
Thingylist.Add(mibbler);
}
else
{
// do something else
}
}
Related
I'm using XPath to read elements from an XML document. Specifically I want to return the values of any element which is the child of a specified element (here the specified element is <SceneryType> and these elements have single-digit values. So I want to return all of the children of <SceneryType> 1 for example.
Here is the XML:
<MissionObjectives>
<Theme themeName="Gothic">
<SceneryType>
1
<Objective>
Do a river thing.
</Objective>
<Objective>
Get all men to the other side of the river.
</Objective>
</SceneryType>
<SceneryType>
2
<Objective>
Climb some trees!
</Objective>
<Objective>
Shoot the tree!
</Objective>
</SceneryType>
</Theme>
I've tried various ways of getting these child elements, but I can't figure it out. My //objective part of the expression just returns everything from the root it seems, but the iterator isn't running which seems odd, shouldn't it loop through every element if the expression is returning a nodelist of all the elements?
XPathDocument missionDoc = new XPathDocument(objectivesPath + "MissionObjectives" + chosenTheme + ".xml");
XPathNavigator nav = missionDoc.CreateNavigator();
foreach (Scenery scenery in world.currentWorld)
{
int sceneryType = scenery.type;
XPathExpression expr = nav.Compile($"MissionObjectives/Theme/SceneryType[text()='{sceneryType}']//Objective");
XPathNodeIterator iterator = nav.Select(expr);
while (iterator.MoveNext())
{
XPathNavigator nav2 = iterator.Current.Clone();
compatibleObjectivesList.Add(nav2.Value);
}
}
I've tried looking through Stack Overflow for similar questions but I can't seem to find anything which applies to XPath. I can't use LINQ to XML for this. Any idea how I can return all the values of the various 'Objective' nodes?
Cheers for any help!
its much simpler to use the XDocument:
var doc = XDocument.Load(objectivesPath + "MissionObjectives" + chosenTheme + ".xml");
to get all of the first SceneryType child nodes:
var node = doc.XPathSelectElement("//MissionObjectives/Theme/SceneryType[1]");
to get the second objective node:
var node = doc.XPathSelectElement("//MissionObjectives/Theme/SceneryType/Objective[2]");
more xpath samples
For one, your xml data has carriage returns, line feeds, and white spaces in the search element's text node. Keep in mind, that an XML node can be an element, attribute, or text (among other node types). The solution below is a bit on the "long-handed" side and perhaps a little "hacky", but it should work. I wasn't certain if you wanted the child element text data or the entire child element, but I return just the child text node data (without carriage returns and line feeds). Also, while this solution DOES NOT use LINQ to XML in the strictest sense, it does use one LINQ expression.
private List<string> getSceneryTypeObjectiveTextList(string xml, int sceneryTypeId, string xpath = "/MissionObjectives/Theme/SceneryType")
{
List<string> result = null;
XmlDocument doc = null;
XmlNodeList sceneryTypeNodes = null;
try
{
doc = new XmlDocument();
doc.LoadXml(xml);
sceneryTypeNodes = doc.SelectNodes(xpath);
if (sceneryTypeNodes != null)
{
if (sceneryTypeNodes.Count > 0)
{
foreach (XmlNode sceneryTypeNode in sceneryTypeNodes)
{
if (sceneryTypeNode.HasChildNodes)
{
var textNode = from XmlNode n in sceneryTypeNode.ChildNodes
where (n.NodeType == XmlNodeType.Text && n.Value.Replace("\r", "").Replace("\n", "").Replace(" ", "") == sceneryTypeId.ToString())
select n;
if (textNode.Count() > 0)
{
XmlNodeList objectiveNodes = sceneryTypeNode.SelectNodes("Objective");
if (objectiveNodes != null)
{
result = new List<string>(objectiveNodes.Count);
foreach (XmlNode objectiveNode in objectiveNodes)
{
result.Add(objectiveNode.InnerText.Replace("\r", "").Replace("\n", "").Trim());
}
// Could break out of the iteration, here, if we know that SceneryType is always unique (i.e. - no duplicates in Element text node)
}
}
}
}
}
}
}
catch (Exception ex)
{
// Handle error
}
finally
{
}
return result;
}
private sampleCall(string filePath, int sceneryTypeId)
{
List<string> compatibleObjectivesList = null;
try
{
compatibleObjectivesList = getSceneryTypeObjectiveTextList(File.ReadAllText(filePath), sceneryTypeId);
}
catch (Exception ex)
{
// Handle error
}
finally
{
}
}
I found this on internet.
string xml = #"
<food>
<child>
<nested />
</child>
<child>
<other>
</other>
</child>
</food>
";
XmlReader rdr = XmlReader.Create(new System.IO.StringReader(xml));
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
Console.WriteLine(rdr.LocalName);
}
}
The result of the above will be
food
child
nested
child
other
This is working perfect, Just i need to identify which elements contains child elements.
for example, I need this output
startParent_food
startParent_child
nested
endParent_child
startParent_child
other
endParent_child
endParent_food
You can do this with XmlReader, but it won't be particularly easy. You can't know if an element has children without continuing to read further, so you'd have to buffer and track various things (as XmlReader is forward-only). Unless you have a good reason to use such a low level API then I'd strongly suggest you avoid it.
This is fairly trivial with LINQ to XML
private static void Dump(XElement element, int level)
{
var space = new string(' ', level * 4);
if (element.HasElements)
{
Console.WriteLine("{0}startParent_{1}", space, element.Name);
foreach (var child in element.Elements())
{
Dump(child, level + 1);
}
Console.WriteLine("{0}endParent_{1}", space, element.Name);
}
else
{
Console.WriteLine("{0}{1}", space, element.Name);
}
}
If, as you imply in your comment, your actual requirement is to modify some values then you can do this without any need to process the details of the XML structure. For example, to modify the value of your nested element:
var doc = XDocument.Parse(xml);
var target = doc.Descendants("nested").Single();
target.Value = "some text";
var result = doc.ToString();
See this fiddle for a demo of both.
For checking child elements, your code will look something like below:
System.Xml.Linq.XElement _x;
_x = System.Xml.Linq.XElement.Parse(xml);
if (_x.HasElements)
{
// your req element
}
you will have to make it recursive to check all elements.
I am trying to load an xml file into an xmlDocument but receive an error that it cannot cast the xmlelement to a xmldocument why?
XML
<VR>
<SubscriberID>xxxx</SubscriberID>
<EmailAddress>m#gmail.com</EmailAddress>
<FirstName>m</FirstName>
<LastName>x</LastName>
<State>CO</State>
<Country/>
<BirthDate>11/16/3004</BirthDate>
<SendEmail>False</SendEmail>
<Preference Value="true" Key="life"/>
<Preference Value="true" Key="yo"/>
</VR>
C# Test
preferenceHelper target = new preferenceHelper(); // TODO: Initialize to an appropriate value
XmlDocument docIn = new XmlDocument();
docIn.Load(#"C:/INTG/trunk/src/VRI.Integration.Email/Test/xmlIn.xml");
XmlDocument expected = null; // I know this will fail in the test, but it should compile, right?
XmlDocument actual;
actual = target.preferencesXmlDoc(docIn);
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
C# function:
public class preferenceHelper
{
public preferenceHelper() { }
XmlDocument docOut = new XmlDocument();
public XmlDocument preferencesXmlDoc(XmlDocument docIn)
{
foreach (XmlDocument root in docIn.SelectNodes("//VR"))
{
foreach (XmlDocument node in root.SelectNodes("//Preference"))
{
XmlNode Name = docIn.CreateElement("Name");
Name.InnerText = node.InnerText = node.Attributes["Key"].Value;
XmlNode Value = docIn.CreateElement("Value");
Value.InnerText = node.InnerText = node.Attributes["Value"].Value;
docOut.CreateElement("Property").AppendChild(Name).AppendChild(Value);
}
}
return docOut;
}
}
Error
Test method Test.preferenceHelperTest.preferencesXmlDocTest threw exception:
System.InvalidCastException: Unable to cast object of type 'System.Xml.XmlElement' to type 'System.Xml.XmlDocument'.
I will not be adding a namespace to the xmlIn, if this is required - how might I load in my xml File?
Where it fails: actual = target.preferencesXmlDoc(docIn);
Thanks
Your problems are in these statements:
foreach (XmlDocument root in SelectNodes(...))
foreach implicitly casts each value in the sequence to the type you specify. The statement is expanded to:
using(var e = sequence.GetEnumerator())
{
while (e.MoveNext())
{
XmlDocument v = (XmlDocument)e.Current;
// loop body
}
}
The reason this is crashing with an InvalidCastException is that the type of node you're selecting is XmlElement, not XmlDocument. To fix the issue, simply switch the type in your foreach statement to XmlElement.
You can also improve readability by using XPath to reach the Preference elements, replacing both loops with a single:
foreach (XmlElement node in docIn.SelectNodes("/VR/Preference"))
Your outer SelectNodes loop is actually completely redundant because //Preference will get all Preference descendants from the root of the document already, not just from that specific child VR.
The proplem is here:
foreach (XmlDocument root in docIn.SelectNodes("//VR"))
and here:
foreach (XmlDocument node in root.SelectNodes("//Preference"))
XmlNode.SelectNodes() returns an XmlNodeList, which is an IEnumerable of XmlNodes. It will not contain any XmlDocuments.
So do this:
foreach (XmlNode root in docIn.SelectNodes("//VR"))
and this:
foreach (XmlElement node in root.SelectNodes("//Preference"))
XmlDocument.SelectNodes("//VR") returns an XmlNodeList, not an XmlDocument. So at the least you need to change your code to:
public XmlDocument preferencesXmlDoc(XmlDocument docIn)
{
foreach (XmlNode root in docIn.SelectNodes("//VR"))
{
foreach (XmlNode node in root.SelectNodes("//Preference"))
{
A document usually has a header:
<?xml version="1.0"?>
Without the header it is considered an element.
Try adding one.
I have xml
<?xml version="1.0" encoding="UTF-8"?>
<info lang="ru" xmlns:x="http://www.yandex.ru/xscript">
<region id="213" lon="37.617671" lat="55.755768" zoom="10">
<title>Москва</title>
</region>
<traffic lon="37.617671" lat="55.755768" zoom="10"region="213">
<length>489164.0</length>
<level>6</level>
<icon>yellow</icon>
<timestamp>1365162401</timestamp>
<time>15:46</time>
<url>http://maps.yandex.ru/moscow_traffic</url>
<title>Москва</title>
</traffic>
</info>
And I need to get value from "level"
public void GetText(string filename)
{
try
{
XDocument xDocument = LoadPage(filename);
if (xDocument.Root == null) return;
XElement elem = xDocument.Root.Element("info");
if (elem != null)
foreach (var el in elem.Elements("traffic"))
{
Name = el.Element("level").Value;
};
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
This block of code works good with another xml. It can't find "info", and elem=null. What's wrong with this code. Or how can I get this value in other way. Thanks!
This is the problem:
XElement elem = xDocument.Root.Element("info");
In the XML you've given us, xDocument.Root is the info element. Just change that to:
XElement elem = xDocument.Element("info");
and that will check that the root element really is info.
Another alternative would be:
foreach (var el in xDocument.Elements("info")
.Elements("traffic"))
That way you just won't go into the loop body if Elements(info) returns an empty collection.
EDIT: If you need it to work on documents where sometimes info is the root element and sometimes it's not, you might want to use:
foreach (var el in xDocument.Descendants("info")
.Take(1)
.Elements("traffic"))
(It's pretty odd to be in that situation though.)
I have a method that is suppose to edit a xml file:
public void EditItem(Item item, string xml)
{
Data = XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/Items/" + xml + ".xml"));
XElement node = Data.Root.Elements("item").Where(i => (string)i.Element("ID") == item.ID).FirstOrDefault();
node.SetElementValue("ID", item.ID);
node.SetElementValue("Name", item.Name);
node.SetElementValue("Type", item.Type);
node.SetElementValue("Kr", item.Kr);
node.SetElementValue("Euro", item.Euro);
Data.Save(HttpContext.Current.Server.MapPath("~/App_Data/Tables/" + xml + ".xml"));
}
I get this validation error thru a try/catch in my controller: "Object reference not set to an instance of an object." Through some debugging, I found that "node" is null, even though "Data" contains all the right data from the xml, and the model.ID is correct.
the wierd thing is, that I have it working in another repo where the xml isnt dynamic, and the XDocument obj is loaded in the constructor.
Any ideas what causes it? Or maybe some ideas on a workaround.
Update. Xml snippet:
<?xml version="1.0" encoding="utf-8"?>
<catagory id="0">
<module>
<item>
<ID>101</ID>
<Name>ClassicoTable(35x100x100)</Name>
<Type>Model</Type>
<Kr>0</Kr>
<Euro>0</Euro>
<DataType>ClassicoTableA</DataType>
</item>
<item>
<ID>100</ID>
<Name>ClassicoTable(102x100x140)</Name>
<Type>Model</Type>
<Kr>0</Kr>
<Euro>0</Euro>
<DataType>ClassicoTableB</DataType>
</item>
......
</module>
</catagory id="0">
Should this line:
XElement node = Data.Root.Elements("item").Where(i => (string)i.Element("ID") == table.ID).FirstOrDefault();
be this (not sure where table.ID comes from):
XElement node = Data.Root.Elements("item").Where(i => (string)i.Element("ID") == item.ID).FirstOrDefault();
I would also check if node is null:
public void EditItem(Item item, string xml)
{
Data = XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/Items/" + xml + ".xml"));
XElement node = Data.Root.Elements("item").Where(i => (string)i.Element("ID") == item.ID).FirstOrDefault();
if (node != null)
{
node.SetElementValue("ID", item.ID);
node.SetElementValue("Name", item.Name);
node.SetElementValue("Type", item.Type);
node.SetElementValue("Kr", item.Kr);
node.SetElementValue("Euro", item.Euro);
Data.Save(HttpContext.Current.Server.MapPath("~/App_Data/Tables/" + xml + ".xml"));
}
}
Okay found a solution. I started wondering why #Kim wanted a snippet from my xml. So it made me think that Data.Root.Element() maybe wasnt the right way. So I tried with Descendants() instead, and that actually works. Why? I have no clue. Heres why:
In another repo I have the XDocument.load() in the constructor of the repo. I thought that would be good, because then I wouldnt have to repeat the same code in all the CRUD methods. But, as I wanted the xml dynamic, and the constructor doesnt accept parameters, I thought this way (original question) would be fine. Here the code from the "static" repo:
//Constructor
public CubeRepository()
{
allCubes = new List<Cube>();
CubeData = XDocument.Load(HttpContext.Current.Server.MapPath("~/App_Data/Cubes/Cubep10p11.xml"));
var cubes = from cube in CubeData.Descendants("item")
select new Cube(cube.Element("ID").Value,
cube.Element("Name").Value,
cube.Element("Type").Value,
(int)cube.Element("Kr"),
(int)cube.Element("Euro"));
allCubes.AddRange(cubes.ToList<Cube>());
}
And in my edit method of that repo:
public void EditCube(Cube cube)
{
XElement node = CubeData.Root.Elements("item").Where(i => (string)i.Element("ID") == cube.ID).FirstOrDefault();
node.SetElementValue("ID", cube.ID);
node.SetElementValue("Name", cube.Name);
node.SetElementValue("Type", cube.Name);
node.SetElementValue("Kr", cube.Kr);
node.SetElementValue("Euro", cube.Euro);
CubeData.Save(HttpContext.Current.Server.MapPath("~/App_Data/Cubes/Cubep10p11.xml"));
}
And that works like I charm, even though im using CubeData.Root.Elements("item"). Note: Elements insted of Descendants. The structure of the xml files are identical.