ValueTree::fromXmlString: Y U NO EXIST?!

This is curiously absent from the framework…

We have ValueTree::toXmlString() which is great for debugging, but it is also great for writing your value tree to disk in the easiest way possible, and also resulting in the most readable version on disk.

File file("mypath");
file.replaceWithText( valueTree.toXmlString() );

Why was ValueTree::fromXmlString() never added?
it’s a huge pain in the a** to load a value tree from disk using the XmlElement approach or the MemoryBlock approach. Not only that, but the output of those approaches is a file on disk that isn’t human readable.

just look at how simple this would be, if ValueTree::fromXmlString() existed:

File file("mypath");
if( file.existsAsFile() )
{
    auto tree = ValueTree::fromXmlString( file.loadFileAsString() );
}

AND the file on disk would be human-readable so we could edit/tweak the settings if we wanted. What’s not to love?!?!

6 Likes

Yeah, good FR, I’m doing some XML modernisation stuff at the moment so will add that, ta!

5 Likes

Use these all the time:

    /**
 Loads a valuetree compatible XML file. Returna ValueTree::invalid if something goes wrong.
 */
inline ValueTree loadValueTreeFromXml(const File & file)
{
    ScopedPointer<XmlElement> xml = XmlDocument(file.loadFileAsString()).getDocumentElement();

    if (!xml)
        return ValueTree{};

    auto tree = ValueTree::fromXml(*xml);
    return tree;
}

inline Result saveValueTreeToXml(const File & file, const ValueTree & tree)
{
    auto string = tree.toXmlString();
    auto result = file.replaceWithText(string);

    if (!result)
        return Result::fail("Save failed to " + file.getFullPathName());

    return Result::ok();
}
5 Likes

this is the bit I kept not quite getting right! thanks for sharing it.

Yeah, I always had to think about it. Stuck those functions in a module i use in every project a few years ago and never thought about it again. But you are right, a function in ValueTree would be neat :wink:

Does anyone happen to know the right way to store booleans such that loading them from XML into a value tree will loading as Booleans and not as strings?

that’s what happens now.

I guess that’s what the operator bool in juce::var is for?

the xml parser calls this for every non memoryBlock in the xml:

var::var (const String& v): type (&VariantType_String::instance) { new (value.stringValue) String (v); }

from here:

void NamedValueSet::setFromXmlAttributes (const XmlElement& xml)
{
    values.clearQuick();

    for (auto* att = xml.attributes.get(); att != nullptr; att = att->nextListItem)
    {
        if (att->name.toString().startsWith ("base64:"))
        {
            MemoryBlock mb;

            if (mb.fromBase64Encoding (att->value))
            {
                values.add ({ att->name.toString().substring (7), var (mb) });
                continue;
            }
        }

        values.add ({ att->name, var (att->value) }); // <- everything is a var::var(String)
    }
}

edit:
solved:

ValueTree t("tree");
t.setProperty("boolVal", false, nullptr);
String xml = t.toXmlString();
ScopedPointer<XmlElement> xmlElem (XmlDocument(xml).getDocumentElement() );

auto tree = ValueTree::fromXml( *xmlElem );
auto treeVal = tree.getProperty("boolVal"); // <- returns a var::VariantType_String
bool treeVal2 = tree.getProperty("boolVal"); // invokes the `var::VariantType_String::toBool()
auto treeVal3 = bool(tree.getProperty("boolVal"); //also invokes var::VariantType_String::toBool()  

got to be explicit when working with booleans, i guess!

Yes, and this brings up a problem I am sometimes facing with ValueTrees. When xml is loaded from a file, the vars inside the ValueTrees all get initialized with strings from the file, regardless of type. The var conversion only takes place when you query the ValueTree property and if queries are made repeatedly without the value changing, this can lead to a lot of conversions between String and the target type. CachedValue can help with the situation.

I also wrote code that iterates through a ValueTree structure, tries to determine each type and sets the property again using the right type, so the vars won’t all be strings internally.

4 Likes

@jules can we get a

template<typename Type>
Type getProperty(const Identifier& id)
{
    return static_cast<Type>(getProperty(id));
} 

That’s not possible with standard C/C++.
A function signature is ambiguous, if name and arguments are identical. The return type doesn’t help for selecting the right overloaded function.

There seems to be a method using constexpr and templates though, maybe @Matthieu_Brucher can tell more, if he is the same on stackoverflow:

EDIT: sorry, I missed that you wanted a templated function… yes, that’s possible of course…