Reputation: 99
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 :
The query : is there a way to perform the checks directly in the query itself instead than in the foreach loop? (as additional info not all the elements contain the element with the "INote" attribute)
The foreach loop : the entire loop is repeated more n time so I get the Messagebox from 0 to n multiple times.
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
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
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