powernemo
powernemo

Reputation: 99

C# Linq-XML find match and replace elements

I am writing a quick tool to convert from text to XML.

I've created a Dictionary that contains the note number and the new name.

I would like to search in the XML for the "INote" value (in the example is "118") and find the corresponding key in the Dictionary and replace the "Name" value (in the example is "Sound 119")

The XML structure looks like this

  <list name="Quantize" type="list">
      <item>
         <int name="Grid" value="4"/>
         <int name="Type" value="0"/>
         <float name="Swing" value="0"/>
         <int name="Unquantized" value="0"/>
         <int name="Legato" value="50"/>
      </item>
   </list>
   <list name="Map" type="list">
      <item>
         <int name="INote" value="118"/>
         <int name="ONote" value="118"/>
         <int name="Channel" value="9"/>
         <float name="Length" value="200"/>
         <int name="Mute" value="0"/>
         <int name="DisplayNote" value="118"/>
         <int name="HeadSymbol" value="0"/>
         <int name="Voice" value="0"/>
         <int name="PortIndex" value="0"/>
         <string name="Name" value="Sound 119" wide="true"/>
         <int name="QuantizeIndex" value="0"/>
      </item>
      <item>
      ....
      </item>
   </list>    
  <list name="Order" type="int">
      <item value="0"/>
      <item value="1"/>
      <item value="2"/>
  </list>

And this is my code so far:

   XDocument outFile = XDocument.Load(outputFile);

    var currItem = from item in outFile.Descendants("item")
                 select item;
    foreach (var i in currItem) // this loop is executed many times
    {
        var node = i.Element("int");
        if (node ==null) continue;
        var name = node.Attribute("name").Value;
        var value = i.Element("int").Attribute("value").Value;
        if (name == "INote")
        {
            var str = i.Element("string").Attribute("name").Value;
            if (str == "Name")
            {
                var snd = i.Element("string").Attribute("value").Value;
                MessageBox.Show(string.Format("{0} - {1} - {2} - {3}", name, value,str,snd));
            }
        }
    }

I have 2 problems :

Any help would be appreciated.

ANSWER : Thanks everybody, I think I've fixed the problem after some tests :

        XDocument outFile = XDocument.Load(outputFile);
        var NamesData = outFile.Root.Elements("list")
            .Where(x => x.Attribute("name").Value == "Map")
            .Elements("item")
            .Select(y =>
                new KeyValuePair<XAttribute,XAttribute>
                (
                       y.Elements("int").Where(c => c.Attribute("name").Value == "INote").Select(c => c.Attribute("value")).FirstOrDefault(),
                       y.Elements("string").Where(c => c.Attribute("name").Value == "Name").Select(c => c.Attribute("value")).FirstOrDefault()
                )
                );

I create a KeyValuePair of XAttributes so i can save them back easily :

this is the rest of the method btw (Notedefinition it's just a data container)

        foreach (var data in NamesData)
        {
            NoteDefinition newName;
            if (internalDictionary.TryGetValue(Convert.ToInt32(data.Key.Value), out newName))
            {
                data.Value.SetValue(newName.voiceName);
            }
        }


        outFile.Save(outputFile);

Upvotes: 0

Views: 1980

Answers (2)

Trafz
Trafz

Reputation: 636

To problem #1: Yes. I usually use XPath to select specific elements from XElements. Here's an working example with your code:

XDocument outFile = XDocument.Parse(xDec + XmlDocumentAsString);

foreach (var i in outFile.XPathSelectElements("item/list/item")) // this loop is executed many times
{
    var node = i.Element("int");
    if (node == null) continue;
    var name = node.Attribute("name").Value;
    var value = i.Element("int").Attribute("value").Value;
    if (name == "INote")
    {
        var str = i.Element("string").Attribute("name").Value;
        if (str == "Name")
        {
            var snd = i.Element("string").Attribute("value").Value;
            MessageBox.Show(string.Format("{0} - {1} - {2} - {3}", name, value, str, snd));
        }
    }
}

And here's a quick cheat-sheet I use to refresh the XPath synax: http://www.w3schools.com/xpath/xpath_syntax.asp

And for problem #2: You can use a 'break;' to stop an iteration such as 'foreach'. Here's how:

MessageBox.Show(string.Format("{0} - {1} - {2} - {3}", name, value, str, snd));
break;

Edit: This is a bit more advanced XPath, so I didn't want to add it to the first example. But since you're doing a check if the element has a sub-element named 'int', which in turn has an attribute named 'named' with the value 'INote', and that's the only note you want but you need it's parents values, then you can add this to the XPath and remove the check:

foreach (var i in outFile.XPathSelectElements("item/list/item/int[@name='INote']/.."))
{
    var name = i.Element("int").Attribute("name").Value;
    var value = i.Element("int").Attribute("value").Value;

    var str = i.Element("string").Attribute("name").Value;
    if (str == "Name")
    {
        var snd = i.Element("string").Attribute("value").Value;
        MessageBox.Show(string.Format("{0} - {1} - {2} - {3}", name, value, str, snd));
        break;
    }
}

Notice that I've added the 'break;' after the "MessageBox.Show();" line. The foreach only iterates 1 time for each parent which has that int[@name='INote'] element, but if you still only want 1 message at all, then keep the 'break;'. :)

Edit2: I read the other answer and realized that you might want ALL the data in 1 message. Here's an alternative way to have done that:

var sb = new StringBuilder();
foreach (var i in outFile.XPathSelectElements("item/list/item/int[@name='INote']/.."))
{
    var name = i.Element("int").Attribute("name").Value;
    var value = i.Element("int").Attribute("value").Value;

    var str = i.Element("string").Attribute("name").Value;
    if (str == "Name")
    {
        var snd = i.Element("string").Attribute("value").Value;
        sb.AppendFormat("{0} - {1} - {2} - {3}", name, value, str, snd);
    }
}
MessageBox.Show(sb.ToString().TrimEnd());

Upvotes: 1

thepirat000
thepirat000

Reputation: 13089

You can do it like this:

var qry = from item in outFile.Descendants("item")
            let nodeInt = item.Element("int")
            let nodeStr = item.Element("string")
            where nodeInt != null && nodeStr != null
            let name = nodeInt.Attribute("name").Value
            let value = nodeInt.Attribute("value").Value
            let str = nodeStr.Attribute("name").Value
            let snd = nodeStr.Attribute("value").Value
            where name == "INote" && str == "Name"
            select string.Format("{0} - {1} - {2} - {3}", name, value, str, snd);
MessageBox.Show(string.Join(Environment.NewLine, qry));

Upvotes: 0

Related Questions