XML structures can be manipulated very easily with LINQ2XML without the need of typed models (classes).
Reading XML file containing many profile
nodes (and i assume your XML file is correct and has one root node), can look like this:
// read existing XML structure
var xml = XDocument.Load("d:\\temp\\xml\\profile.xml");
// take all profile elements
var profiles = xml.Root.Elements("profile").ToList();
foreach (var profile in profiles)
{
Console.WriteLine(profile.Attribute("name").Value);
// find all button elements
var buttons = profile
.Elements()
.Where (e => e.Name.ToString().StartsWith("btn"));
// list elements
foreach (var button in buttons)
{
// tag name
var name = button.Name.ToString();
// attributes
var pressed = button.Attribute("pressed").Value;
var released = button.Attribute("released").Value;
Console.WriteLine(String.Format("{0} - P'{1}' - R'{2}'", name, pressed, released));
}
}
The output is:
FooBar
btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'
XYZ
btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'
Reading a single profile XML structure from a string
and then creating a new one can look like this:
var xmlCode = @"<profile name=""FooBar""><btnA pressed=""actionA"" released=""actionB"" /><btnB pressed=""actionX"" released=""actionY"" /></profile>";
try
{
// read existing XML structure
var xml = XDocument.Parse(xmlCode); // XDocument.Load("c:\\path\\to\\file.xml");
// find all button elements
var buttons = xml.Root
.Elements()
.Where (e => e.Name.ToString().StartsWith("btn"));
// list elements
foreach (var button in buttons)
{
// tag name
var name = button.Name.ToString();
// attributes
var pressed = button.Attribute("pressed").Value;
var released = button.Attribute("released").Value;
Console.WriteLine(String.Format("{0} - P'{1}' - R'{2}'", name, pressed, released));
}
// create xml
// root element
var newXml = new XElement("profile", new XAttribute("name", "FooBaz"),
new XElement("btnA",
new XAttribute("pressed", "actionX"),
new XAttribute("released", "actionXR")),
new XElement("btnB",
new XAttribute("pressed", "actionZ"),
new XAttribute("released", "actionZR")));
Console.WriteLine(newXml.ToString());
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
The output is:
btnA - P'actionA' - R'actionB'
btnB - P'actionX' - R'actionY'
<profile name="FooBaz">
<btnA pressed="actionX" released="actionXR" />
<btnB pressed="actionZ" released="actionZR" />
</profile>
You can use LINQ2XML to read the data and fill a list of objects of your Profile
type like this:
// read existing XML structure
var xml = XDocument.Load("d:\\temp\\xml\\profile.xml");
// take all profile elements
var profiles = xml.Root.Elements("profile").ToList();
var listOfProfiles = new List<Profile>();
foreach (var profile in profiles)
{
var profileObject = new Profile("");
profileObject.Name = profile.Attribute("name").Value;
// find all button elements
var buttons = profile
.Elements()
.Where (e => e.Name.ToString().StartsWith("btn"));
// list elements
foreach (var button in buttons)
{
// attributes
var pressed = button.Attribute("pressed").Value;
var released = button.Attribute("released").Value;
profileObject.BtnA_Pressed = pressed;
}
listOfProfiles.Add(profileObject);
}
You can also use XML serialization - you need to describe your XML structure as a class (typed model) and deserialize (read XML file into your class) resp. serialize (write your XML structure to a file). A generic implementation of the methods for serialization resp. deserialization can look like this:
public void SerializeModel<T>(string fqfn, T entity)
{
var xmls = new XmlSerializer(entity.GetType());
var writer = new StreamWriter(fqfn);
xmls.Serialize(writer, entity);
writer.Close();
}
public T DeserializeModel<T>(string fqfn)
{
var fs = new FileStream(fqfn, FileMode.Open);
var xmls = new XmlSerializer(typeof(T));
var r = (T) xmls.Deserialize(fs);
fs.Close();
return r;
}
The typed model that describes your Profile
class and the lists contained within, looks like this (note the usage of the different XML serialization attributes):
public class Profiles
{
[XmlElement(ElementName="Profile")]
public List<Profile> Profile { get; set; }
}
public class Profile
{
[XmlArray(ElementName="Buttons")]
public List<Button> Buttons { get; set; }
[XmlAttribute]
public String Name;
}
public class Button
{
[XmlAttribute]
public String Pressed { get; set; }
[XmlAttribute]
public String Released;
}
Creation of an XML file:
var profiles = new Profiles();
var profileA = new Profile();
var profileB = new Profile();
var buttonA = new Button();
var buttonB = new Button();
profileA.Buttons = new List<Button>();
profileB.Buttons = new List<Button>();
profiles.Profile = new List<Profile>();
profileA.Name = "Profile A";
profileB.Name = "Profile B";
buttonA.Pressed = "Pressed A";
buttonA.Released = "Release A";
buttonB.Pressed = "Pressed B";
buttonB.Released = "Release B";
profileA.Buttons.Add(buttonA);
profileB.Buttons.Add(buttonB);
profiles.Profile.Add(profileA);
profiles.Profile.Add(profileB);
var xmlFile = "d:\\temp\\xml\\profile_model.xml";
SerializeModel<Profiles>(xmlFile, profiles);
The new XML file looks like this (note, the structure was slightly modified because of the way XML serialization in .NET handles arrays/lists):
<?xml version="1.0" encoding="utf-8"?>
<Profiles xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Profile Name="Profile A">
<Buttons>
<Button Released="Release A" Pressed="Pressed A" />
</Buttons>
</Profile>
<Profile Name="Profile B">
<Buttons>
<Button Released="Release B" Pressed="Pressed B" />
</Buttons>
</Profile>
</Profiles>
The file can be then read like this:
var input = DeserializeModel<Profiles>(xmlFile);
foreach (var profile in input.Profile)
{
var b = profile.Buttons.First();
Console.WriteLine(String.Format("{0} - {1} - {2}", profile.Name, b.Pressed, b.Released));
}
The output is as expected:
Profile A - Pressed A - Release A
Profile B - Pressed B - Release B
Both approaches have advantages and disadvantages.
IMO the answer to your question (changed a bit) Is XML the correct approach for saving structured data to a file?
is - definitely yes! Nowadays XML is one of the standards for representing / manipulating / exchanging structured data and data generally (data kept in a string). As someone already mentioned INI files were not really meant to represent complex nested structures.