Jooj
Jooj

Reputation: 707

How to read/write nodes and child nodes from xml file in c# maybe using xpath?

Maybe somebody could help me. I need a two methods.

The first should open a xml file and get all nodes with the given parameter ie.:

XML file (file.xml):

<Menu id="1" Name="myMenu">
  <MenuItem Header="Header 1" Name="header1" />
  <MenuItem Header="Header 2" Name="header2">
    <MenuItem Header="subHeader 2.1" Name="header2_1">
      <MenuItem Header="subsubHeader 2.1.1" Name="header2_1_1" />
    </MenuItem>
  </MenuItem>
  <MenuItem Header="Header 3" Name="header3" />
</Menu>

So, now I need to get the values from the xml with a method like this:

public static List<string, string>ReadXML(string filename, string node, string[] attributes, bool searchSubNodes);

calling method example: ReadXMLValues("file.xml", "MenuItem", new string[] {"Header", "Name"}, true);

and this would return a list of two strings like:

"Header 1", "header1"
"Header 2", "header2"
"subHeader 2.1", "header2_1" <-- this should be in the list only if searchSubNodes is enabled!
"subsubHeader 2.1.1", "header2_1_1" <-- the same for this one!!!
"Header 3", "header3"

THIS was the reading part and now the writting part:

filename is the same like above file.xml. public static void WriteXML(string filename, string node, List attributes);

now lets say the file.xml has empty header attributes like this:

<Menu id="1" Name="myMenu">
  <MenuItem Header="" Name="header1" />
  <MenuItem Header="" Name="header2">

And I need to put values into the headers, the finall result should look like this:

   <Menu id="1" Name="myMenu">
      <MenuItem Header="Header 1" Name="header1" />
      <MenuItem Header="Header 2" Name="header2">

Is something like this possible??? C# gurus and other people who knows how to do this PLEASE PLEASE help me! I don't know how to do it.

Best regards!

Upvotes: 1

Views: 4526

Answers (3)

andyp
andyp

Reputation: 6269

EDIT:

To use XPath to select all nodes that have a given node name AND two given attributes use the following XPath expression (I don't know if there are better ways to do it but this works):

//NodeName[@FirstAttributeName][@SecondAttributeName]

In code you could do it like that (for the MenuItems from your example). I updated the code to make the namespace you put in your document known under the prefix "x". Also the XPath expression has been updated to use that prefix before the node.

XPathDocument doc = new XPathDocument("data2.xml");
XPathNavigator nav = doc.CreateNavigator();
XmlNamespaceManager mgr = new XmlNamespaceManager(nav.NameTable);
mgr.AddNamespace("default", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
XPathNodeIterator iterator = nav.Select("//default:MenuItem[@Header][@Name]", mgr);

while (iterator.MoveNext())
{
    Console.Write(iterator.Current.GetAttribute("Header", ""));
    Console.Write(iterator.Current.GetAttribute("Name", ""));
    Console.Write(Environment.NewLine);
}

=====

Do you have to use XPath? You could just create classes for Menu/MenuItem that map to your XML like this:

public class Menu
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<MenuItem> SubItems { get; set; }
}

public class MenuItem
{
    public string Header { get; set; }
    public string Name { get; set; }
    public List<MenuItem> SubItems { get; set; }
}

And then have an XMLSerializer do the work for you (it can do both, reading and writing). You could afterwards apply filtering to the in-memory lists (e.g. using Linq-To-Objects).

If you want to do it using XPath this Codeproject article might be helpful.

Upvotes: 0

Jooj
Jooj

Reputation: 707

Both solutions works. But when before the searched node is somethnig like this:

<MenuItem x:Class="Test.Win"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/Test" <--this line make problems!
Header="Main" Name="Main">
<Menu id="1" Name="myMenu">
  <MenuItem Header="Header 1" Name="header1" />
  <MenuItem Header="Header 2" Name="header2">
    <MenuItem Header="subHeader 2.1" Name="header2_1">
      <MenuItem Header="subsubHeader 2.1.1" Name="header2_1_1" />
    </MenuItem>
  </MenuItem>
  <MenuItem Header="Header 3" Name="header3" />
</Menu>

</MyNode>

then the solutions doesn't work. How to remove only the schema line or just skip it or somthing like that

Do somebody of you maybe know why and how to solve also this problem?

Regards!

Upvotes: 0

Robert Rossney
Robert Rossney

Reputation: 96830

You can do all of the searching using XPath.

XmlNodeList nodes = doc.SelectNodes("/Menu/MenuItem");

In this case, nodes will contain all of the MenuItem elements that are a child of the top-level Menu element.

XmlNodeList nodes = doc.SelectNodes("/Menu/MenuItem | /Menu/MenuItem/MenuItem");

In this case, nodes will contain all MenuItem elements that are children either of Menu or of a MenuItem that's a child of Menu.

You can also find all MenuItem nodes that have no more than 1 MenuItem ancestor:

XmlNodeList nodes = doc.SelectNodes("//MenuItem[count(ancestor::MenuItem) &lt; 2]");

Or you can just find all MenuItem nodes:

XmlNodeList nodes = doc.SelectNodes("//MenuItem");

However you choose to select them, processing the elements works the same way:

foreach (XmlElement elm in nodes)
{
   myList.Add(new[] { elm.GetAttribute("Header"), elm.GetAttribute("Name") });
}

So this suggests a way that you could encapsulate this as a method with the signature you suggest:

public static List<IEnumerable<string>>ReadXML(
   string filename, 
   string elementName, 
   IEnumerable<string> attributes, 
   bool searchSubNodes)
{
   XmlDocument d = new XmlDocument();
   d.Load(filename);
   string xpath = searchSubNodes ? "//" + elementName : "/*/elementName";
   List<IEnumerable<string>> results = new List<IEnumerable<string>>();
   foreach (XmlElement elm in d.SelectNodes(xpath))
   {
      var values = from name in attributes select elm.GetAttribute(name);
      result.Add(values.ToArray());
   }
   return result;
}

Upvotes: 1

Related Questions