views:

495

answers:

3

Hello,

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!

A: 

Hi,

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.

andyp
Hello,I think XPath is the best solution. Your solution is limited by Menu and MenuItem, I need a solution where I can put any node. Menu and MenuItem was just an example.Regards.
Jooj
I didn't realize that you just gave an example of what you had to parse, sorry. You could extend my solution but depending on the number of nodes you have to support that'd quickly get a lot of work..
andyp
Wow. Great!It works perfect, but when on the top of the xml is something like this, then it dosen't work. What to do?<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/test">
Jooj
This adds a namespace to your xml document which prohibits my previous code from working because the node in the XPath expression is no longer resolveable. I've updated the code to adapt to that change..
andyp
+1  A: 

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;
}
Robert Rossney
Perfect!!! Thank you Robert.
Jooj
Just a little thing, look at my post.
Jooj
A: 

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!

Jooj
You need to use an XmlNameSpaceManager, see here:http://stackoverflow.com/questions/972584/null-returned-when-selecting-a-node-in-xml-documentfrom Roberts solution it will be something like this.. var d = new XmlDocument(); var nameSpace = new XmlNamespaceManager(d.NameTable); nameSpace.AddNamespace("s", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); d.Load(filename); string xpath = searchSubNodes ? "//s:" + elementName : "/*/elementName"; var results = new List<IEnumerable<string>>();
Mark
I have got the error: Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function.
Jooj
Did you see the update to my answer? I added an XmlNamespaceManager to resolve the default namespace you added to the xml. That should resolve the error you have.
andyp
Thanks andyp. It works. One more question. Is it possible to first read the schema string into a variable and then put it to the addNamespace method?So it's not needed to put it every time by my self.
Jooj
This question might answer your last question: http://stackoverflow.com/questions/122463/how-to-retrieve-namespaces-in-xml-files-using-xpath
andyp
To ignore the namespace you could just change the XPath expression to this: //*[local-name() = 'MenuItem'][@Header][@Name]
andyp
You can certainly ignore namespaces by using `local-name()`, but doing that is just a way to introduce namespace-collision bugs into your code.
Robert Rossney