Index: /trunk/include/VBox/settings.h
===================================================================
--- /trunk/include/VBox/settings.h	(revision 7314)
+++ /trunk/include/VBox/settings.h	(revision 7315)
@@ -1299,6 +1299,6 @@
 
     /**
-     * The InputResolver class represents an input resolver, the service used to
-     * provide input streams for external entities given an URL and entity ID.
+     * The InputResolver class represents an interface to provide input streams
+     * for external entities given an URL and entity ID.
      */
     class VBOXSETTINGS_CLASS InputResolver
@@ -1322,4 +1322,40 @@
     };
 
+    /**
+     * The AutoConverter class represents an interface to automatically convert
+     * old settings trees to a new version when the tree is read from the
+     * stream.
+     */
+    class VBOXSETTINGS_CLASS AutoConverter
+    {
+    public:
+
+        /**
+         * Returns @true if the given tree needs to be converted using the XSLT
+         * template identified by #templateUri(), or @false if no conversion is
+         * required.
+         *
+         * The implementation normally checks for the "version" value of the
+         * root key to determine if the conversion is necessary. The
+         * implementation must return a string representing the old version
+         * (before conversion) in the @c aOldVersion argument -- this string is
+         * used by XmlTreeBackend::oldVersion() and must be non-NULL to indicate
+         * that the conversion has been performed on the tree. The returned
+         * string must be allocated using RTStrDup or such.
+         *
+         * @param aRoot                 Root settings key.
+         * @param aOldVersionString     Old version string (allocated by
+         *                              RTStrDup or such).
+         */
+        virtual bool needsConversion (const Key &aRoot,
+                                      char *&aOldVersion) const = 0;
+
+        /**
+         * Returns the URI of the XSLT template to perform the conversion.
+         * This template will be applied to the tree if #needsConversion()
+         * returns @c true for this tree.
+         */
+        virtual const char *templateUri() const = 0;
+    };
 
     /**
@@ -1356,45 +1392,43 @@
 
     /**
-     * Sets up automatic settings tree conversion.
-     *
-     * Automatic settings tree conversion is useful for upgrading old settings
-     * files to the new version transparently during execution of the #read()
-     * method.
-     *
-     * Automatic conversion is performed after reading the document from the
-     * stream but before validating it using the given XSLT template if the
-     * version check fails. The version check consists of comparing the given
-     * version string with the value of the given attribute of the root key. The
-     * conversion is performed only when the version strings are not equal.
-     *
-     * Note that in order to make the conversion permanent, the resulting tree
-     * needs to be exlicitly written back to the stream.
-     *
-     * The method will make copies of the supplied strings.
-     *
-     * Specifying a NULL value for all of the arguments will completely disable
-     * automatic conversion. Specifying an invalid root key name or a
-     * non-existent attribute name will disable automatic conversion too. By
-     * default automatic conversion it is disabled.
-     *
-     * Specifying a NULL value only for some of the arguments will throw an
-     * EInvalidArg exception.
-     *
-     * @param aRoot     Name of the root key containing the version attribute.
-     * @param aAttr     Name of the attribute containing the version string.
-     * @param aVersion  Version string to compare with the attribute value.
-     * @param aTemplate URI of the XSLT template that performs conversion.
-     */
-    void setAutoConversion (const char *aRoot, const char *aAttr,
-                            const char *aVersion, const char *aTemplate);
-
-    /**
-     * Disables automatic settings conversion previously enabled by
-     * setAutoConversion(). By default automatic conversion it is disabled.
-     */
-    void resetAutoConversion()
-    {
-        setAutoConversion (NULL, NULL, NULL, NULL);
-    }
+     * Sets a settings tree converter and enables the automatic conversion.
+     *
+     * The Automatic settings tree conversion is useful for upgrading old
+     * settings files to the new version transparently during execution of the
+     * #read() method.
+     *
+     * The automatic conversion takes place after reading the document from the
+     * stream but before validating it. The given converter is asked if the
+     * conversion is necessary using the AutoConverter::needsConversion() call,
+     * and if so, the XSLT template specified by AutoConverter::templateUri() is
+     * applied to the settings tree.
+     *
+     * Note that in order to make the result of the conversion permanent, the
+     * settings tree needs to be exlicitly written back to the stream.
+     *
+     * The given converter object must exist as long as this instance exists or
+     * until a different converter is set using setAutoConverter() or reset
+     * using resetAutoConverter().
+     *
+     * @param aConverter    Settings converter to use.
+     */
+    void setAutoConverter (AutoConverter &aConverter);
+
+    /**
+     * Disables the automatic settings conversion previously enabled by
+     * setAutoConverter(). By default automatic conversion it is disabled.
+     */
+    void resetAutoConverter();
+
+    /**
+     * Returns a non-NULL string if the automatic settings conversion has been
+     * performed during the last successful #read() call. Returns @c NULL if
+     * there was no settings conversion.
+     *
+     * If #read() fails, this method will return the version string set by the
+     * previous successful #read() call or @c NULL if there were no #read()
+     * calls.
+     */
+    const char *oldVersion() const;
 
     void rawRead (Input &aInput, const char *aSchema = NULL, int aFlags = 0);
Index: /trunk/src/VBox/Main/xml/Settings.cpp
===================================================================
--- /trunk/src/VBox/Main/xml/Settings.cpp	(revision 7314)
+++ /trunk/src/VBox/Main/xml/Settings.cpp	(revision 7315)
@@ -797,5 +797,6 @@
 {
     Data() : ctxt (NULL), doc (NULL)
-           , inputResolver (NULL) {}
+           , inputResolver (NULL)
+           , autoConverter (NULL), oldVersion (NULL) {}
 
     xmlParserCtxtPtr ctxt;
@@ -806,27 +807,8 @@
     InputResolver *inputResolver;
 
+    AutoConverter *autoConverter;
+    char *oldVersion;
+
     std::auto_ptr <stdx::exception_trap_base> trappedErr;
-
-    struct AutoConv
-    {
-        AutoConv() : root (NULL), attr (NULL), version (NULL), xslt (NULL) {}
-        ~AutoConv() { uninit(); }
-
-        void uninit()
-        {
-            RTStrFree (xslt); xslt = NULL;
-            RTStrFree (version); version = NULL;
-            RTStrFree (attr); attr = NULL;
-            RTStrFree (root); root = NULL;
-        }
-
-        bool isNull() { return xslt == NULL; }
-
-        char *root;
-        char *attr;
-        char *version;
-        char *xslt;
-    }
-    autoConv;
 
     /**
@@ -896,20 +878,17 @@
 }
 
-void XmlTreeBackend::setAutoConversion (const char *aRoot, const char *aAttr,
-                                        const char *aVersion, const char *aTemplate)
-{
-    if (aRoot == NULL && aAttr == NULL && aVersion == NULL && aTemplate == NULL)
-    {
-        m->autoConv.uninit();
-        return;
-    }
-
-    if (aRoot == NULL || aAttr == NULL || aVersion == NULL || aTemplate == NULL)
-        throw EInvalidArg (RT_SRC_POS);
-
-    m->autoConv.root = RTStrDup (aRoot);
-    m->autoConv.attr = RTStrDup (aAttr);
-    m->autoConv.version = RTStrDup (aVersion);
-    m->autoConv.xslt = RTStrDup (aTemplate);
+void XmlTreeBackend::setAutoConverter (AutoConverter &aConverter)
+{
+    m->autoConverter = &aConverter;
+}
+
+void XmlTreeBackend::resetAutoConverter()
+{
+    m->autoConverter = NULL;
+}
+
+const char *XmlTreeBackend::oldVersion() const
+{
+    return m->oldVersion;
 }
 
@@ -957,97 +936,96 @@
     }
 
+    char *oldVersion = NULL;
+
     /* perform automatic document transformation if necessary */
-    if (!m->autoConv.isNull())
+    if (m->autoConverter != NULL)
     {
         Key root = Key (new XmlKeyBackend (xmlDocGetRootElement (doc)));
-        if (!strcmp (root.name(), m->autoConv.root))
+        if (m->autoConverter->needsConversion (root, oldVersion))
         {
-            const char *ver = root.stringValue (m->autoConv.attr);
-            if (strcmp (ver, m->autoConv.version))
+            xmlDocPtr xsltDoc = NULL;
+            xsltStylesheetPtr xslt = NULL;
+            xsltTransformContextPtr tranCtxt = NULL;
+            char *errorStr = NULL;
+
+            try
             {
-                /* version mismatch */
-
-                xmlDocPtr xsltDoc = NULL;
-                xsltStylesheetPtr xslt = NULL;
-                xsltTransformContextPtr tranCtxt = NULL;
-                char *errorStr = NULL;
-
-                try
+                /* parse the XSLT template */
                 {
-                    /* parse the XSLT */
-                    {
-                        Input *xsltInput =
-                            m->inputResolver->resolveEntity (m->autoConv.xslt, NULL);
-                        /* NOTE: new InputCtxt instance will be deleted when the
-                         * stream is closed by the libxml2 API */
-                        xsltDoc = xmlCtxtReadIO (m->ctxt,
-                                                 ReadCallback, CloseCallback,
-                                                 new Data::InputCtxt (xsltInput, m->trappedErr),
-                                                 m->autoConv.xslt, NULL,
-                                                 0);
-                        delete xsltInput;
-                    }
-
-                    if (xsltDoc == NULL)
-                    {
-                        /* look if there was a forwared exception from the lower level */
-                        if (m->trappedErr.get() != NULL)
-                            m->trappedErr->rethrow();
-
-                        throw XmlError (xmlCtxtGetLastError (m->ctxt));
-                    }
-
-                    xslt = xsltParseStylesheetDoc (xsltDoc);
-                    if (xslt == NULL)
-                        throw LogicError (RT_SRC_POS);
-
-                    /* setup transformation error reporting */
-                    tranCtxt = xsltNewTransformContext (xslt, xsltDoc);
-                    if (tranCtxt == NULL)
-                        throw LogicError (RT_SRC_POS);
-                    xsltSetTransformErrorFunc (tranCtxt, &errorStr, ValidityErrorCallback);
-
-                    xmlDocPtr newDoc = xsltApplyStylesheetUser (xslt, doc, NULL,
-                                                                NULL, NULL, tranCtxt);
-                    if (newDoc == NULL)
-                        throw LogicError (RT_SRC_POS);
-
-                    if (errorStr != NULL)
-                    {
-                        xmlFreeDoc (newDoc);
-                        throw Error (errorStr);
-                        /* errorStr is freed in catch(...) below */
-                    }
-
-                    /* replace the old document on success */
-                    xmlFreeDoc (doc);
-                    doc = newDoc;
-
+                    Input *xsltInput =
+                        m->inputResolver->resolveEntity
+                            (m->autoConverter->templateUri(), NULL);
+                    /* NOTE: new InputCtxt instance will be deleted when the
+                     * stream is closed by the libxml2 API */
+                    xsltDoc = xmlCtxtReadIO (m->ctxt,
+                                             ReadCallback, CloseCallback,
+                                             new Data::InputCtxt (xsltInput, m->trappedErr),
+                                             m->autoConverter->templateUri(),
+                                             NULL, 0);
+                    delete xsltInput;
+                }
+
+                if (xsltDoc == NULL)
+                {
+                    /* look if there was a forwared exception from the lower level */
+                    if (m->trappedErr.get() != NULL)
+                        m->trappedErr->rethrow();
+
+                    throw XmlError (xmlCtxtGetLastError (m->ctxt));
+                }
+
+                xslt = xsltParseStylesheetDoc (xsltDoc);
+                if (xslt == NULL)
+                    throw LogicError (RT_SRC_POS);
+
+                /* setup transformation error reporting */
+                tranCtxt = xsltNewTransformContext (xslt, xsltDoc);
+                if (tranCtxt == NULL)
+                    throw LogicError (RT_SRC_POS);
+                xsltSetTransformErrorFunc (tranCtxt, &errorStr, ValidityErrorCallback);
+
+                xmlDocPtr newDoc = xsltApplyStylesheetUser (xslt, doc, NULL,
+                                                            NULL, NULL, tranCtxt);
+                if (newDoc == NULL)
+                    throw LogicError (RT_SRC_POS);
+
+                if (errorStr != NULL)
+                {
+                    xmlFreeDoc (newDoc);
+                    throw Error (errorStr);
+                    /* errorStr is freed in catch(...) below */
+                }
+
+                /* replace the old document on success */
+                xmlFreeDoc (doc);
+                doc = newDoc;
+
+                xsltFreeTransformContext (tranCtxt);
+
+                /* NOTE: xsltFreeStylesheet() also fress the document
+                 * passed to xsltParseStylesheetDoc(). */
+                xsltFreeStylesheet (xslt);
+            }
+            catch (...)
+            {
+                /* restore the previous entity resolver */
+                xmlSetExternalEntityLoader (oldEntityLoader);
+                sThat = NULL;
+
+                RTStrFree (errorStr);
+
+                if (tranCtxt != NULL)
                     xsltFreeTransformContext (tranCtxt);
 
-                    /* NOTE: xsltFreeStylesheet() also fress the document
-                     * passed to xsltParseStylesheetDoc(). */
+                /* NOTE: xsltFreeStylesheet() also fress the document
+                 * passed to xsltParseStylesheetDoc(). */
+                if (xslt != NULL)
                     xsltFreeStylesheet (xslt);
-                }
-                catch (...)
-                {
-                    /* restore the previous entity resolver */
-                    xmlSetExternalEntityLoader (oldEntityLoader);
-                    sThat = NULL;
-
-                    RTStrFree (errorStr);
-
-                    if (tranCtxt != NULL)
-                        xsltFreeTransformContext (tranCtxt);
-
-                    /* NOTE: xsltFreeStylesheet() also fress the document
-                     * passed to xsltParseStylesheetDoc(). */
-                    if (xslt != NULL)
-                        xsltFreeStylesheet (xslt);
-                    else if (xsltDoc != NULL)
-                        xmlFreeDoc (xsltDoc);
-
-                    throw;
-                }
+                else if (xsltDoc != NULL)
+                    xmlFreeDoc (xsltDoc);
+
+                RTStrFree (oldVersion);
+
+                throw;
             }
         }
@@ -1130,4 +1108,6 @@
                 xmlSchemaFreeParserCtxt (schemaCtxt);
 
+            RTStrFree (oldVersion);
+
             throw;
         }
@@ -1144,4 +1124,8 @@
     /* assign the root key */
     m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
+
+    /* memorize the old version string also used as a flag that
+     * the conversion has been performed (transfers ownership) */
+    m->oldVersion = oldVersion;
 }
 
@@ -1187,4 +1171,7 @@
 void XmlTreeBackend::reset()
 {
+    RTStrFree (m->oldVersion);
+    m->oldVersion = NULL;
+
     if (m->doc)
     {
