Selecting inner text of XML nodes and adding to list - c#

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.

Related

C# : Deserialize xml with an unknow number of attributes

I would like to deserialize xml files with the following pattern :
I would like to put the data into a class call Parameter. However, as you can see, I don't knwo by advance how many <Device> I get between <Parameters> ... </Parameters> and the deserialization is not possible with such a DeserializeObjectXMLFile method.
public static Parameters DeserializeObjectXMLFile(String fileToRead)
{
try
{
Parameters parameters = null;
XmlSerializer xs = new XmlSerializer(typeof(Parameters));
using (StreamReader sr = new StreamReader(fileToRead))
{
parameters = xs.Deserialize(sr) as Parameters;
}
return parameters;
}
catch (Exception ex)
{
Logger.Error(ex);
throw ex;
}
}
I imagined having a property List<Device> Devices {get; set} in my Parameters class but deserialization won't work and I don't know how I can do it.
Could you help me in finding a solution ?
Best regards.
RĂ©mi
Use the XmlDocument object in the System.Xml namespace to parse the XML instead of serializing it.
Here is an example:
var xDoc = new System.Xml.XmlDocument();
xDoc.Load(pathToXml);
foreach (System.Xml.XmlNode node in xDoc.DocumentElement.ChildNodes)
{
var attrName = node.Attributes[0].Name;
var attrValue = node.Attributes[0].Value;
Device dev = new Device(attrName, attrValue);
deviceList.Add(dev);
}
While parsing you can then create a new instance of Parameters and add it to a List object.

trying to delete xmlns namespaces of nodes

I've got some problems with xml messages and c#.
The problem is a root element with no namespaces and all the namespaces are in the nodes.
I've got a part of the script running to delete the namespaces so I can read all the xml messages that will be sent to the webserver.
The message that gives the problems:
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetOrderResponseRequest xmlns="http://www.edibulb.nl/XML/Order:2">
<Header>
<UserName xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">FBT_000390</UserName>
<Password xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">1FWcgwrx9</Password>
<MessageID xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" schemeDataURI="8719604082016">8719604082016100376</MessageID>
<MessageDateTime xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" format="304">20170523090413+02:00</MessageDateTime>
</Header>
<Body>
<AgentParty>
<PrimaryID xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" schemeID="251" schemeAgencyName="EBC">8719604178115</PrimaryID>
</AgentParty>
<GetOrderResponseDetails>
<MutationDateTime xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" format="304">20170510000000+02:00</MutationDateTime>
<BuyerParty xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">
<PrimaryID xmlns="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:3" schemeID="251" schemeAgencyName="EBC">8719604082016</PrimaryID>
</BuyerParty>
</GetOrderResponseDetails>
</Body>
</GetOrderResponseRequest>
</soap:Body>
</soap:Envelope>
And here is the part of the script to translate on the webservice
the code below works perfectly fine if there are prefixes involved.
but it wont work with the xml defined above.
Here is the class that I call from the webservice.
First I check in the xml string if there are any prefixes.
RemoveNamespace remove = new RemoveNamespace();
public string orderrequest(string xmldoc, string ivbglns, bool success)
{
if (success == true)
{
if (xmldoc.Contains(":UserName"))
{
string xdoc = remove.removeall(xmldoc);
docx = new XmlDocument();
docx.LoadXml(xdoc);
}
else if(xmldoc.Contains("<UserName xmlns"))
{
string xdoc = remove.removexlmns(xmldoc);
docx = new XmlDocument();
docx.LoadXml(xmldoc);
}
// rest of the code for the response
}
}
and below the RemoveNameSpace part:
public string removeall(string xdoc)
{
string docx = RemoveAllNamespaces(xdoc);
return docx;
}
public static string RemoveAllNamespaces(string xmldoc)
{
XElement documentwithoutns = XRemoveAllNamespaces(XElement.Parse(xmldoc));
return documentwithoutns.ToString();
}
private static XElement XRemoveAllNamespaces(XElement Xmldoc)
{
if (!Xmldoc.HasElements)
{
XElement element = new XElement(Xmldoc.Name.LocalName);
element.Value = Xmldoc.Value;
foreach (XAttribute attribute in Xmldoc.Attributes())
element.Add(attribute);
return element;
}
return new XElement(Xmldoc.Name.LocalName, Xmldoc.Elements().Select(el => XRemoveAllNamespaces(el)));
}
public string removexlmns(string xdoc)
{
string pattern = "\\s+xmlns\\s*(:\\w)?\\s*=\\s*\\\"(?<url>[^\\\"]*)\\\"";
MatchCollection matchcol = Regex.Matches(xdoc, pattern);
foreach (Match m in matchcol)
{
xdoc = xdoc.Replace(m.ToString(), "");
}
return xdoc;
}
The error it returns is: The Prefix "cannot be redefined from" to 'urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2' within the same start element tag.
I'm in search for a solution for this. The xml from above is a message thats beyond my control.
with Kind regards
Stephan
I would very strongly suggest you use the namespaces in whatever XML processing you are doing after this. Stop trying to remove them!
If you must remove them, it's worth noting that XElement.Name is mutable. You can remove all the namespace declarations and set all the names to their local names.
var doc = XDocument.Parse(xml);
doc.Descendants()
.Attributes()
.Where(x => x.IsNamespaceDeclaration)
.Remove();
foreach (var element in doc.Descendants())
{
element.Name = element.Name.LocalName;
}
See this fiddle for a demo.

How to edit an Xml file's element values

I have this Xml file, and I want to edit any of the elements like homepage or search_provider.
<?xml version="1.0" encoding="utf-8"?>
<Preferences>
<personal>
<homepage>http://duckduckgo.com</homepage>
<search_provider>DuckDuckGo</search_provider>
<search_provider_url>http://duckduckgo.com/?q=</search_provider_url>
</personal>
</Preferences>
The following is the C# code I'm using to attempt to change the homepage element. Let's say I run saveSetting("homepage", "http://google.com");
public static void saveSetting(String settingName, String newvalue)
{
XmlDocument xml = new XmlDocument();
xml.Load(userSettingsFile);
foreach (XmlElement element in xml.SelectNodes("Preferences"))
{
foreach (XmlElement oldsettingname in element)
{
element.SelectSingleNode(settingName);
XmlNode settingtosave = xml.CreateElement(settingName);
settingtosave.InnerText = newvalue;
element.ReplaceChild(settingtosave, oldsettingname);
xml.Save(userSettingsFile);
}
}
}
Now, while this works to an extent and does change the specified value, it also deletes the entire personal element.
<?xml version="1.0" encoding="utf-8"?>
<Preferences>
<homepage>http://google.com</homepage>
</Preferences>
Hopefully someone can help me out! I've been searching for the last two days for a solution and this is the closest I've come to getting the code to work the way I need it to.
You can just use LINQ to XML like this:
public static void saveSetting(String settingName, String newvalue)
{
var xmlDocument = XDocument.Load("path");
var element = xmlDocument.Descendants(settingName).FirstOrDefault();
if (element != null) element.Value = newvalue;
xmlDocument.Save("path");
}
See this documentation for more details: Modifying Elements, Attributes, and Nodes in an XML Tree
keep your current code and just modify a few lines:
public static void saveSetting(String settingName, String newvalue)
{
XmlDocument xml = new XmlDocument();
xml.Load(userSettingsFile);
foreach (XmlElement element in xml.SelectNodes("Preferences"))
{
if(element.Name.Equals(settingName))
{
element.InnerText = newvalue;
break;
}
}
xml.Save(userSettingsFile);
}

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.

Xml Attribute not showing up on ListBox - 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?

Categories

Resources