Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QDocument has not expected behavior when creating elements



  • Context

    I was trying to fix QFormBuilder::save(...) by adding putting the result into a buffer first and
    then into a QDocument like this just for the sake of context.

    void FormWriter::save(QIODevice* dev, QWidget* widget)
    {
    	// Create to DOM document for reading the builder XML output.
    	QDomDocument doc("sf::FormWriter");
    	// Scope for buffer to go out of when done.
    	{
    		// Write the UI file to a buffer device.
    		QBuffer buf;
    		// Open the buffer device.
    		buf.open(QIODeviceBase::WriteOnly);
    		// Save the form to the buffer device.
    		QAbstractFormBuilder::save(&buf, widget);
    		// Assign the
    		doc.setContent(buf.data());
    	}
    	// Fix the missing or broken stuff.
    	fixMissingProperties(widget, doc);
    	// Write the fix ui file.
    	dev->write(doc.toByteArray());
    }
    

    Since QFrame properties frameShadow and frameShape are not written to file in QFormBuilder.

    Problem

    I have used an XML DOM from other languages but for some reason the behavior is different then I expected.
    I seems that the return value of QDomNode::setAttributeNode(...) and QDomNode::appendChild(...)
    are not usable for adding elements or attributes.

    Not Working

    QDomElement getDomProperty(QDomDocument& dom, const QString& name, const QString& type, const QString& value)
    {
    	auto prop = dom.createElement("property");
    	prop.setAttributeNode(dom.createAttribute("name")).setValue(name);
    	prop.appendChild(dom.createElement(type)).appendChild(dom.createTextNode(value));
    	return prop;
    };
    

    Does Work

    QDomElement getDomProperty(QDomDocument& dom, const QString& name, const QString& type, const QString& value)
    {
    	auto prop = dom.createElement("property");
    	auto attr = dom.createAttribute("name");
    	attr.setValue(name);
    	prop.setAttributeNode(attr);
    	auto enu = dom.createElement(type);
    	prop.appendChild(enu);
    	enu.appendChild(dom.createTextNode(value));
    	return prop;
    };
    

    Questions

    • Is this normal behavior?
    • If so what is the purpose of the return value?
      (Took me a few hours to find out it wasn't working.)

  • Moderators

    @A-v-O said in QDocument has not expected behavior when creating elements:

    Is this normal behavior?

    Yes, normal and documented.

    If so what is the purpose of the return value?
    (Took me a few hours to find out it wasn't working.)

    See the documentation, it explains everything.

    If the element has another attribute that has the same name as newAttr, this function replaces that attribute and returns it; otherwise the function returns a null attribute.

    QDomElement's methods do not return references to self, so linking them with . won't work.



  • @sierdzio said in QDocument has not expected behavior when creating elements:

    QDomElement's methods do not return references to self, so linking them with . won't work.

    Maybe I missed something in the documentation on QDomNode::insertBefore(...) .
    In my view it exactly does what I expect of it and returns a usable instance to work with.

    I know it does not return it self but it returns a new instance
    of QDomNode referencing the same reference as the passed newChild.

    Before I posted I looked at the source code of QDomNode::insertBefore(...) and the
    code of QDomNodePrivate::insertBefore(...) .

    And I don't see where it says calling appendChild() returns something which is unusable.

    QDomNode QDomNode::appendChild(const QDomNode& newChild)
    {
        if (!impl) {
            qWarning("Calling appendChild() on a null node does nothing.");
            return QDomNode();
        }
        return QDomNode(IMPL->appendChild(newChild.impl));
    }
    

    Code above calls method below.

    QDomNodePrivate* QDomNodePrivate::appendChild(QDomNodePrivate* newChild)
    {
        // No reference manipulation needed. Done in insertAfter.
        return insertAfter(newChild, nullptr);
    }
    

    And this above calls insertAfter(...) .

    QDomNodePrivate* QDomNodePrivate::insertAfter(QDomNodePrivate* newChild, QDomNodePrivate* refChild)
    {
        //
        // Lots of manipulation but no reassignment of the newChild parameter.
        //
        return newChild;
    }
    

    So where does my reasoning go wrong?


  • Moderators

    @A-v-O said in QDocument has not expected behavior when creating elements:

    QDomNode QDomNode::appendChild(const QDomNode& newChild)

    This:

    QDomNode QDomNode::appendChild(const QDomNode& newChild)
    

    Returns a copy of QDomNode. If you want to modify original object (like is usually the case with chained calls) it would have to return a reference QDomNode& QDomNode::appendChild().

    So, looking back at your original code:

    prop.appendChild(dom.createElement(type)).appendChild(dom.createTextNode(value));
    

    You add a new child to prop (which in working code you call enu). Then you add another child to a copy of enu - but it is not added to the original item.



  • @A-v-O said in QDocument has not expected behavior when creating elements:

    prop.setAttributeNode(dom.createAttribute("name")).setValue(name);
    

    And in this case the setAttributeNode() docs show it returns the attribute node it is replacing, or a dummy node, and that's what you setValue() `on.

    Qt's stuff does work and as documented so not unexpected, just not to the chaining expectations you have have from, say, jQuery or whichever XML DOMs.



  • @sierdzio said in QDocument has not expected behavior when creating elements:

    You add a new child to prop (which in working code you call enu). Then you add another child to a copy of enu - but it is not added to the original item.

    If you look too the Qt's source code and header of qdom.h (== link)
    Clearly method QDomNode::insertBefore(...) returns a new QDomNode but referencing the same actual node.

    Look at the source of QDomNode which has only one private member QDomNodePrivate* impl;
    it references the implementation node.

    So QDomNode's only reference to an instance which contains the actual implementation node impl.
    You can have different QDomNode's which reference the same actual node.

    In other words you should not need an original QDomNode.



  • @JonB said in QDocument has not expected behavior when creating elements:

    And in this case the setAttributeNode() docs show it returns the attribute node it is replacing, or a dummy node, and that's what you setValue() `on.
    Qt's stuff does work and as documented so not unexpected, just not to the chaining expectations you have have from, say, jQuery or whichever XML DOMs.

    You are right the QDomElement::setAttributeNode() does indeed not return a copy but a null attribute.

    Manual on QDomNode::insertAfter(...) states:

    Returns a new reference to newChild on success or a null node on failure.
    The DOM specification disallow inserting attribute nodes, but due to historical reasons QDom accept them nevertheless.

    My first interaction was the PHP XML Dom setAttribute(...) where the manual says:

    The new DOMAttr or false if an error occurred.

    Where I'm used to chain functions.

    Looking to the Java implementation, which I never worked with, does the same as Qt's implementation.

    Chaining makes it easy because local variables are superfluous in that case and for me it is more readable.

    Conclusion: My assumption was apparently incorrect.


Log in to reply