Xml Attribute not showing up on ListBox - C# - c#

I'm getting a null reference exception whenever it tries to add the packages titles info and other attributes but the attributes exist and the proper package is selected
Heres the code:
private void categorylist_listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
XmlDocument LoadPackageList = new XmlDocument();
//Removes the text "Select A Category" and refrehes the form
packagelist_listbox.Items.Remove(SelectaCategory_listbox);
if (categorylist_listview.SelectedItem == WWW_listviewitem)
{
LoadPackageList.Load("www.xml");
XmlNodeList WWWPackageList = LoadPackageList.SelectNodes("/Packages/*");
int countthenodes = 0;
foreach (XmlNode WWWPackages in WWWPackageList)
{
//Cycles through all the packages and assings them to a string then adds it to the packagelist
countthenodes++;
PackageTitle[countthenodes] = WWWPackages.Attributes["title"].ToString();
PackageInfo[countthenodes] = WWWPackages.Attributes["info"].ToString();
PackageDownloadUrl[countthenodes] = WWWPackages.Attributes["downloadurl"].ToString();
PackageTags[countthenodes] = WWWPackages.Attributes["tags"].ToString();
packagelist_listbox.Items.Add(PackageTitle[countthenodes]);
}
Refresh(packagelist_listbox);
}
}
It Errors out at PackageTitle[countthenodes] = WWWPackages.Attributes["title"].ToString();
XML File:
<Packages>
<Firefox title="Mozilla Firefox" tags="www firefox web browser mozilla" info="http://google.com" downloadurl="http://firefox.com"></Firefox>
</Packages>
The Variables are declared
public string[] PackageTags;
public string[] PackageTitle;
public string[] PackageInfo;
public string[] PackageDownloadUrl;
At the very beginning of the file

Well, the first problem is that calling ToString() on an XmlAttribute isn't going to do what you want. You should use the Value property. However, I don't believe that's causing a NullReferenceException unless the data isn't quite as you showed it. Here's a short but complete program which works fine:
using System;
using System.Xml;
class Test
{
static void Main()
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XmlNodeList list = doc.SelectNodes("/Packages/*");
foreach (XmlNode node in list)
{
Console.WriteLine(node.Attributes["title"].Value);
}
}
}
That displays "Mozilla Firefox" with the XML you gave us.
Options:
Your real XML actually contains an element without a title attribute
Perhaps PackageTitle is null?
It would help if you could produce a short but complete program demonstrating the problem. Ideally it should avoid using a GUI - I can't see anything here which is likely to be GUI-specific.
If you could tell us more about PackageTitle and how it's being initialized, that would help too. How are you expecting it to just keep expanding for as many elements as you find? Or is it an array which is initialized to a larger size than you ever expect to find elements?

Related

Selecting inner text of XML nodes and adding to list

I have the following XML file called file.xml:
<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config">
<defaults>
<serializer>pof</serializer>
</defaults>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>broadcast-data|position</cache-name>
<scheme-name>broadcast</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>broadcast-data|position-audit</cache-name>
<scheme-name>broadcast-remote</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>broadcast-data|trade</cache-name>
<scheme-name>broadcast-remote</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
</cache-config>
I'm trying to get the inner text of all the cache names, which exist under each cache-mapping node, and put them all in a list. I have this Model.cs class to do that.
class Model
{
private XmlDocument cacheFile = new XmlDocument();
private List<string> cacheNames = new List<string>();
private int nameCount = 0;
public Model()
{
this.loadNames();
}
public void loadNames()
{
try //exception handling
{
cacheFile.Load("../../resources/file.xml");
}
catch (System.IO.FileNotFoundException)
{
Debug.WriteLine("File not found!");
Environment.Exit(1);
}
catch (System.ArgumentException)
{
Debug.WriteLine("Invalid path!");
Environment.Exit(1);
}
catch (Exception e)
{
Debug.WriteLine("Exception thrown!");
Debug.WriteLine(e);
Environment.Exit(1);
}
//get cache names
XmlNodeList nodes = cacheFile.SelectNodes("/cache-config/caching-scheme-mapping/cache-mapping");
foreach (XmlNode node in nodes)
{
string name = node.FirstChild.InnerText;
cacheNames.Add(name);
nameCount++;
}
}
//accessors
public List<string> getCacheNames()
{
return cacheNames;
}
public int getNameCount()
{
return nameCount;
}
}
However, every time I create a Model object and then check if the List was loaded up, it tells me the list is empty! It appears as though the foreach loop never actually runs, or as if the program can't find the nodes I'm specifying. Please help.
If you use LINQ to XML, this is really quite simple:
XNamespace ns = "http://xmlns.oracle.com/coherence/coherence-cache-config";
var doc = XDocument.Load("../../resources/file.xml");
cacheNames = doc.Descendants(ns + "cache-name").Select(e => e.Value).ToList();
You don't need to keep a separate count of items, you can get this from the list:
cacheNames.Count;
As an aside, idiomatic C# uses pascal casing for methods and properties, so if you stuck to this your methods would start with a capital letter - e.g. GetCacheNames.
This has been puzzling many and has been asked many times here in SO. Your XML has default namespace here :
xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
Descendant elements inherit ancestor default namespace, unless otherwise specified (using explicit namespace prefix or having local default namespace that point to different namespace URI). Using XmlDocument you can use XmlNamespaceManager to register prefix to namespace URI mapping, and use the registered prefix properly in your XPath query, for example :
var nsMgr = new XmlNamespaceManager(new NameTable());
nsMgr.AddNamespace("d", "http://xmlns.oracle.com/coherence/coherence-cache-config");
var xpath = "/d:cache-config/d:caching-scheme-mapping/d:cache-mapping";
XmlNodeList nodes = cacheFile.SelectNodes(xpath, nsMgr);
Anyway, if you have just started this, switching to newer class XDocument would be a better option.

Editing XML Output With LINQ

I have an XML output file from a process being run that needs the contents of various fields edited according to a collection of tables in our database. For example, what's included in
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfUserReportPreviewListDto xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<UserReportPreviewListDto>
<ExtensionData />
<Id>previewReportFieldsId</Id>
<Field0>-7</Field0>
<Field1>L</Field1>
<Field2>Lab Work Complete</Field2>
<Field3>False</Field3>
<Field4>LabWorkComplete</Field4>
<Field6>False</Field6>
</UserReportPreviewListDto>
<UserReportPreviewListDto>
<ExtensionData />
<Id>previewReportFieldsId</Id>
<Field0>-6</Field0>
<Field1>S</Field1>
<Field2>Sent to Lab</Field2>
<Field3>False</Field3>
<Field4>SentToLab</Field4>
<Field6>False</Field6>
</UserReportPreviewListDto>
<UserReportPreviewListDto>
<ExtensionData />
<Id>previewReportFieldsId</Id>
<Field0>-5</Field0>
<Field1>V</Field1>
<Field2>Void</Field2>
<Field3>False</Field3>
<Field4>Void</Field4>
<Field6>True</Field6>
<Field7>12/11/2013</Field7>
<Field9>769</Field9>
</UserReportPreviewListDto>
would need Field4 changed from LabWorkComplete (tblEnum.FieldTypeDesc) to 2 (tblEnum.FieldTypeNum).
I'm very new to using LINQ, and am not even completely sure it's the best route for this. I've created a DataSet in the project, with a DataTable populated from the database with what I need to work with. And...that's as far as I've got. Right now I'm using a massive list of tedious If statements to accomplish this, and am thinking this avenue may be more efficient than a collection of statements like this.
var xe = XElement.Load("serializer.xml");
string field4Value = xe.XPathSelectElement(#"/UserReportPreviewListDto/Field4").Value;
if (field4Value == "Incomplete")
{
xe.XPathSelectElement(#"/UserReportPreviewListDto/Field4").Value = "0";
}
else if (field4Value == "SendToLab")
{
xe.XPathSelectElement(#"/UserReportPreviewListDto/Field4").Value = "1";
}
else if (field4Value == "LabWorkComplete")
{
xe.XPathSelectElement(#"/UserReportPreviewListDto/Field4").Value = "2";
}
So that's where I am. If LINQ wouldn't be the best avenue, what would be? If it would be, what would be the best way to do it? Additionally, any particularly helpful resources along these lines that can be recommended would be appreciated; I'd much rather learn code than copy code. I'd hate to have to ask this again next week, after all.
Your XML structure is weird. Field0...Field6 is not common, there are usually meaningful names in there. You can always write a function that encapsulates string to integer string conversion, and just provide an xpath as an argument. Then go higher level, provide xpath + conversion delegate, and from this point it's as easy as one line per property. Here is an implementation example:
using System;
using System.Xml.Linq;
using System.Xml.XPath;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var xe = XElement.Load("serializer.xml");
ConvertValue(xe, #"/UserReportPreviewListDto/Field4", TranslateValueField4);
}
private static void ConvertValue(XElement xe, string xpath, TranslateValue translator)
{
string field4Value = xe.XPathSelectElement(xpath).Value;
xe.XPathSelectElement(xpath).Value = translator(field4Value);
}
private delegate string TranslateValue(string value);
private static string TranslateValueField4(string value)
{
switch (value)
{
case "Incomplete" :
return "0";
case "SendToLab" :
return "1";
case "LabWorkComplete":
return "2";
default:
throw new NotImplementedException(); //or provide handling for unknown values
}
}
}
}
You can also avoid using xpath, and just iterate using foreach:
static void Main(string[] args)
{
var doc = XDocument.Load(#"input.xml");
foreach (var xe in doc.Root.Elements("UserReportPreviewListDto"))
{
ConvertValue(xe, "Field4", TranslateValueField4);
}
//doc.Save(path);
}
private static void ConvertValue(XElement xe, string fieldName, TranslateValue translator)
{
//.Element returns Nothing if element is missing, may want to handle this case
XElement field4 = xe.Element(fieldName);
string field4Converted = TranslateValueField4(field4.Value);
field4.SetValue(field4Converted);
}
I always, always, always prefect to store xml into custom classes and then work with them inside my C# environment. Makes the process of modifying it feel much more natural. Please look at my question here to see the best way to do this. It takes a bit more time, but it makes things SO much easier in the long run. You said you wanted the best route and to learn, right? ;)

changing an element of project file by xml means

some times I will have to load up to 60 winforms or class library projects into a solution.. and change the output path and reference path for each of them..
so I wrote a wpf application for the same
private void Button_Click(object sender, RoutedEventArgs e)
{
var path = txtRootPath.Text;
var projFiles = System.IO.Directory.GetFiles(path, "*.csproj", SearchOption.AllDirectories);
foreach (var item in projFiles)
{
var xDoc = XDocument.Load(item);
var outputNodes = xDoc.Root.Descendants("OutputPath");
foreach (var outoutNode in outputNodes)
{
//this part is never hit..
outoutNode.Value = txtOutputPath.Text;
}
//similarly for referencePath
}
lblResult.Content = string.Format("Files: {0}", projFiles.Count());
}
but outputNodes collection will be empty
could somebody please tell what am I doing wrong here
EDIT:
I figured out that the problem is with xmlns="http://schemas.microsoft.com/developer/msbuild/2003" attribute in Project element..
Solution:
as given in this solution -
Parsing Visual Studio Project File as XML
Linq-to-XML with XDocument namespace issue
You have to use the default namespace when referring to "OutputPath" element.
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
var outputNodes = xDoc.Root.Descendants(ns + "OutputPath");

Deserializing child nodes outside of parent's namespace using XmlSerializer.Deserialize() in C#

I have an application that uses namespaces to help deserialize objects stored in XML. The XML namespace is also the C# namespace where the object resides. For example, given the following XML snip:
<xml-config xmlns:app="MyAppNS">
<app:Person>
<Name>Bill</Name>
<Car>
<Make>Honda</Make>
<Model>Accord</Model>
</Car>
</app:Person>
<app:Person>
<Name>Jane</Name>
<Car>
<Make>VW</Make>
<Model>Jetta</Model>
</Car>
</app:Person>
<app:Car>
<Make>Audi</Make>
<Model>A6</Model>
</app:Car>
</xml-config>
The configuration is really just a random bag of objects. As you can see, there is a mix of Person and Car objects at the top level. I use the namespace to determine the object type at load time for proper deserialization. Here is the code for loading the document:
public static void LoadFile(String file) {
XmlDocument doc = new XmlDocument();
doc.Load(file);
XmlNode root = doc.DocumentElement;
foreach (XmlNode child in root.ChildNodes) {
if (child.NodeType != XmlNodeType.Element) {
continue;
}
Object obj = LoadObject(child);
// TODO handle object here...
}
}
private static Object LoadObject(XmlNode node) {
Object obj = null;
StringBuilder str = new StringBuilder();
if ((node.Prefix != null) && (node.Prefix != "")) {
str.Append(node.NamespaceURI).Append('.');
}
str.Append(node.LocalName);
Assembly assy = Assembly.GetExecutingAssembly();
Type type = assy.GetType(str.ToString());
XmlSerializer serdes = new XmlSerializer(type);
using (XmlNodeReader reader = new XmlNodeReader(node)) {
obj = serdes.Deserialize(reader);
}
return obj;
}
You many see the issue right away, but when this code is used, the resulting Person objects are empty, however there are no errors. The only way to get the deserializer to populate the child elements is to use the same namespace for the child elements:
<app:Person>
<app:Name>Bill</app:Name>
<app:Car>
<app:Make>Honda</app:Make>
<app:Model>Accord</app:Model>
</app:Car>
</app:Person>
The only other option I have tried is to use fully-qualified type names as the element name (no namespaces), however I haven't had any luck with that. Is there a way to cause the deserializer to accept the non-prefixed children of the XML node?
I finally found a method to accomplish this, modifying the answer found here.
I created a custom deserializer that replicates the namespace for the starting node:
public class CustomXmlNodeReader : XmlNodeReader {
private String _namespace;
public CustomXmlNodeReader(XmlNode node) : base(node) {
_namespace = node.NamespaceURI;
}
public override String NamespaceURI {
get { return _namespace; }
}
}
Using this reader, I am able to load stored objects with the following:
using (XmlNodeReader reader = new CustomXmlNodeReader(node)) {
obj = serdes.Deserialize(reader);
}
I know this is a bit bad form for XML (forcing the application of a specific namespace), however it suits my needs quite nicely. Answered here in case it helps anyone else.

XmlSerialization Collection as Array

I'm trying to serialize a custom class that needs to use multiple elements of the same name.
I've tried using xmlarray, but it wraps them in another elements.
I want my xml to look like this.
<root>
<trees>some text</trees>
<trees>some more text</trees>
</root>
My code:
[Serializable(), XmlRoot("root")]
public class test
{
[XmlArray("trees")]
public ArrayList MyProp1 = new ArrayList();
public test()
{
MyProp1.Add("some text");
MyProp1.Add("some more text");
}
}
Try just using [XmlElement("trees")]:
[Serializable(), XmlRoot("root")]
public class test
{
[XmlElement("trees")]
public List<string> MyProp1 = new List<string>();
public test()
{
MyProp1.Add("some text");
MyProp1.Add("some more text");
}
}
Note I changed ArrayList to List<string> to clean up the output; in 1.1, StringCollection would be another option, although that has different case-sensitivity rules.
(edit: obsoleted - my second post ([XmlElement]) is the way to go - I'm leaving this for posterity in using xsd.exe)
xsd.exe is your friend. Copy the xml you want into a file (foo.xml), then use:
xsd foo.xml
xsd foo.xsd /classes
Now read foo.cs; you can use this either directly, or just for inspiration.
(edit: output snipped - not helpful any more)

Categories

Resources