yeah, xml is really a godsend when it comes to data storage, it makes saving and reloading data so simple. I’ll probably get a bit carried away here writing this, and it may be absolutely obvious to you - let me apologise in advance if any of this sounds patronising, i’m a teacher at heart and like to try to make things clear. I just thought it might be useful to explain how I personally go about things.
If my program needs to have any of its data stored in a file, that data is always linked in some way to some type of object. A program will invariably have many object types in, so that means there are usually a bunch of different types of object, each holding data on a different thing.
Thanks to the object oriented nature of C++, and with a bit of programming discipline, you usually have all your data inherently grouped in logical structures due to the design of your object classes. For example, you might have a ‘control’ object, which can hold data on a particular parameter controller (e.g. a Midi CC parameter on your device). That could be a member of an array of these objects, which is in turn a member of a ‘control list’ object, which is in turn a member of a higher level ‘device’ object.
The brilliant thing about this sort of object level structuring is that you can give each class the ability to export and import information about itself really easily. I always give my classes the following two functions:
XmlElement* xml();
void processXml (XmlElement* tag); 
The first one is designed to return all of the object’s data as an XmlElement (a pointer to a dynamically allocated object, because that’s how all the XmlElement operations tend to get done). The second takes a given XmlElement object and extracts all the data from it, filling the object with the new information.
If you work in these functions at every level of your object structure, you can quickly see how such things make life easy. To illustrate, let me go back to the example above; a very basic ‘controller’ object type is shown below, using the xml functions:
class Controller
{
   int number;
   String name;
   // etc..
   ...
   XmlElement* xml ()
   {
      // create a new XmlElement (our 'tag')...
      XmlElement* tag = new XmlElement (T("Controller"));
      // give it our data as attributes...
      tag->setAttribute (T("name"), name);
      tag->setAttribute (T("num"), number);
      // etc...
      return tag;
   }
   void processXml (XmlElement* tag)
   {
      if (tag) // check the tag isn't null
      {
         if (tag->hasTagName (T("Controller")) )
         {
             // get the data from the attributes...
             name = tag->getStringAttribute (T("name"));
             number = tag->getIntAttribute (T("num"));
         }
      }
   }
   ...
};
That shows roughly how to give your class a way of storing and recalling its data to and from an outside Xml source. Now, we’ve programmed this in, every Controller object has this ability, and can return a single tag (containing all of its data) with a single function call. Let us imagine that we have a ControllerList class too, which just holds an array of these…
class ControllerList
{
   OwnedArray <Controller> list;
   String listName;
   // etc..
   ...
   XmlElement* xml ()
   {
      // create a new XmlElement tag to hold our list...
      XmlElement* tag = new XmlElement (T("ControllerList"));
      // set attributes from this object's data...
      tag->setAttribute (T("name"), name);
      // step thru each contained object and add its tag...
      for (int i=0; i<list.size (); i++)
      {
         tag->addChildElement (list[i]->xml());
      }
      // etc...
      return tag;
   }
   void processXml (XmlElement* tag)
   {
      if (tag)
      {
         if (tag->hasTagName (T("ControllerList"))
         {
            listName = tag->getStringAttribute (T("name"));
            // step thru each contained tag and create objects...
            tag = tag->getFirstChildElement ();
            while (tag)
            {
               // create a new object for this tag...
               Controller* control = new Controller;
               // give the object the tag to read itself...
               control->processXml (tag);
               // add the new object to our array!
               list.add (control);
               tag = tag->getNextChildElement ();
            }
         }
      }
   }
   ...
};
Remember that this is already a logically structured class hierarchy; the ControllerList ‘has’ data on a bunch of Controllers, because it makes sense to do so. Thus, in our Xml version of the data, it also makes sense to keep our Controller tags inside a ControllerList tag. Because we know that we’ll have the Controllers inside the list, we can tell the list to call the xml() function we’ve already made in order to get the data- it’s really simple! (although that sentence was a bit convoluted :hihi: )
I won’t show another block of code because it’d be pretty similar to those above, but a higher level ‘device’ class would do the same again; it would generate an XmlElement containing attributes describing the device itself, (e.g. model, manufacturer, inputs, outputs, etc), and it could contain a ControllerList (or several) amongst myriad other things. You can hopefully see that adding ALL the data from the ControllerList is now as simple as simply adding a single child tag element obtained from the ControllerList::xml() function.
Of course, your actual program will likely follow a different design to the very basic example i’ve shown here, but i hope it’s helped to illustrate a simple approach to getting data in and out of your objects. Just be aware of the structure your types will have, and give any classes that hold important data the means to speak via xml.
I spent a large part of the summer writing a .midnam editor (similar to cherryPicker) entirely in Juce. I got the majority of it working, reading and writing the midnam files. If you’ve not heard of them, do a google for midnam or cherry picker; basically it’s the mac’s desired format for holding MIDI device name info. It’s not really specifically geared for sysex stuff, but it might be of interest to you. You could of course expand upon the format to include your own sysex librarian data. In its current form it holds program names, bank names, channel bank groups (called NameSets) control name lists and note name lists. There’s a lot of crap in the format too, and different ways that certain devs have used it. My midnam classes seem okay at reading most of the variants.
I need to tidy them up quite a lot, but if you’re interested in making use of the MidNam format for this, i’ll put my midnam class library up for you to have a look at. It took a lot of learning and effort to make it, and then it turned out to be a complete waste of my time (the project it was for was changed to use a different format!)… but it does mean that i now have a working midnam library reader/writer (in juce, naturally). I also wrote a neat little ‘librarian’ thing which scans the midnam directories, reading the files and automatically building up a little ‘device library’ so you can pick your devices by manufacturer.
There are a great many .midnam files around on the internet, but they are largely unused by PC musicians; this is because the only PC software that supports them is protools! On the mac, they’re used by OSX’s midi device manager (i believe), MOTU DP and of course PT. I was hoping that I could help make it a bit more of a cross platform thing, but got a bit fed up when my work seemed in vain!
Let me know if you’d like to know more.
Crumbs, what a rant that was!