ValueTree::createXml ReferenceCountedObject Assert


#1

Hey Jules, I’m wondering if the assert for object types when creating xml is necessary. I’m wrapping my ValueTrees with model classes and would like to have a reference to the model object so I can make use of the ValueTree listening mechanisms and still be able to access and work with the wrappers. Would a note about it in the documentation suffice? It appears that the object types will return the memory address as a string so perhaps it’s not useful data for translation to xml but otherwise it seems pretty harmless. I suppose the objects could just be skipped? Perhaps I’m abusing the opportunity to link them together but otherwise it seems I’ll have to duplicate all listening functionality into my wrappers as well or have an entirely different design to track changes. If it’s an otherwise bad idea I’d be interested to hear anyone’s thoughts. Maybe I’m missing something.


#2

With an ounce more of looking at it I see the writeToStream also runs into an assert and appears to write nothing when encountering object types. It seems like it would be easy enough to be consistent by not writing objects to whichever format (i.e. just skip them) but if you’re opposed to that then I’ll either patch my copy or come up with a different pattern to track changes, or perhaps get rid of my wrappers entirely. hmm…


#3

Well, I think it’s an important assertion, because if you try to save a tree with objects, it’s best that you should find out pretty quickly that that’s not going to work.

If you do have a use-case where this is actually a sensible thing to do, then you could either do a quick comment-out of the assertion, or make a copy of your tree and strip out the objects before saving it.


#4

Ok thanks man, that’s understandable. Stripping them out might be good enough, but I could see it popping up if I wanted to debug using toXmlString. Anyway not a big deal, I’ll figure something out and go with it.


#5

I’ve had to deal with stuff like this many times, since I often use ‘objects’ for run-time metadata and embedded helper objects (e.g. a ‘name manager’ object for maintaining locally-unique names for children of the node it belongs to).

There are loads of different ways to do it, though I usually make use of a few knocked-together helper functions…

e.g. here’s a relevant selection [disclaimer: yes, I use a nulltree macro. sue me :wink: ]

#define nulltree ValueTree::invalid

namespace ValueTreeTools
{

	void stripObjects (ValueTree& node, UndoManager* undo)
	{
		int n = node.getNumProperties();
		for (int i=0; i<n; i++)
		{
			Identifier propName = node.getPropertyName(i);
			if (node.getProperty(propName).isObject())
			{
				node.removeProperty(propName,undo);
				--n;
				--i;
			}
		}
		n = node.getNumChildren();
		for (int i=0; i<n; i++)
		{
			ValueTree child = node.getChild(i);
			stripObjects (child,undo);
		}
	}

	ValueTree createCopyWithoutObjects (const ValueTree& nodeToCopy)
	{
		ValueTree node = nodeToCopy.createCopy();
		stripObjects(node,nullptr);
		return node;
	}

	String toString (const ValueTree& node, bool oneLine)
	{
		UndoManager undo;
		ValueTree temp(node);
		stripObjects(temp,&undo);
		ScopedPointer<XmlElement> xml (node.createXml());
		undo.undo();

		if (xml != nullptr)
		{
			return xml->createDocument(String::empty,oneLine,false,String::empty);
		}
		return String::empty;
	}

	ValueTree fromString (const String& text)
	{
		XmlDocument doc(text);
		ScopedPointer<XmlElement> xml(doc.getDocumentElement());
		if (xml != nullptr)
		{
			return ValueTree::fromXml(*xml);
		}
		return nulltree;
	}

}

I confess, the ‘toString’ implementation here is a little cheeky, since it uses an UndoManager to strip the objects from the actual data itself, then puts it back. This avoids having to make a total copy if it is unnecessary, but it does mean that listeners might get notifications. I keep meaning to make a less naughty version but I’ve yet to have it present any real problems :wink:


#6

Thanks haydxn. I ended up reimplementing the pertaining ValueTree methods in a utility class. They are only used for my data object ValueTrees which have a single property holding a link to the wrapper. They could be written more generically but the link to the wrapper is just a patch to make one my systems work and I’m working towards not requiring it, so these are just workarounds to support the patch.

XmlElement* NamUtil::createDataObjectXml(const ValueTree & tree) const
{
    Identifier type = tree.getType();
    XmlElement* const xml = new XmlElement (type.toString());

    for (int i = 0; i < tree.getNumProperties(); i++) {
        Identifier propName = tree.getPropertyName(i);
        if (propName != dataObjectTag) {
            var propValue = tree.getProperty(propName);
            xml->setAttribute(propName.toString(), propValue.toString());
        }
    }
    
    for (int i = 0; i < tree.getNumChildren(); ++i) {
        ValueTree child = tree.getChild(i);
        xml->addChildElement (this->createDataObjectXml(child));
    }
    
    return xml;
}

void NamUtil::writeDataObjectToStream (const ValueTree & tree, OutputStream& output) const
{
    Identifier type = tree.getType();
    output.writeString (type.toString());
    
    const int propSize = tree.getNumProperties();
    const int propSizeWithoutDataObject = propSize - 1;
    
    output.writeCompressedInt (propSizeWithoutDataObject);
    
    for (int j = 0; j < propSize; ++j) {
        Identifier propName = tree.getPropertyName(j);
        if (propName != dataObjectTag) {
            var propValue = tree.getProperty(propName);
            output.writeString (propName.toString());
            propValue.writeToStream (output);
        }
    }
    
    output.writeCompressedInt (tree.getNumChildren());
    
    for (int i = 0; i < tree.getNumChildren(); ++i)
        this->writeDataObjectToStream(tree.getChild(i), output);

}


String NamUtil::dataObjectToXmlString(const juce::ValueTree &tree) {
    const ScopedPointer<XmlElement> xml (this->createDataObjectXml(tree));
    return xml != nullptr ? xml->createDocument (String::empty) : String::empty;
}