Search code examples
c++qtxpathqxmlqueryqdomdocument

Search for nodes in a QDomDocument using XPath


I am spoiled (rotten, to be sure) by C# and its XML manipulation classes in the System.Xml namespace. I can load an XML file into an XmlDocument. I can search the whole document for nodes that match an XPath expression, using XmlNode.SelectNodes( "an xpath expression" ). The result is an XmlNodeList that contains XmlNode objects that I can iterate over.

Now I am using C++ Qt (versions 4.7.1 and 4.8, but the particular version may not be important). I can load an XML file into a QDomDocument. But, I am frustrated that I cannot search the document using an XPath expression in a similar way that I did in C#.

I have had limited success using QXmlQuery to find stuff in the XML file. If I write the query in just the right way, I can obtain a QStringList of results, iterate that QStringList, and then store the data somewhere for use later.

But, I still want to be able to obtain a collection of QDomNode objects that are in the document, directly via an XPath expression. One specific use case is to find one element whose "name" attribute has a certain value, and then replace that element with a new element. That is why I want the QDomNode object itself, not just some string-based or other representation of the XML content that QXmlQuery can provide. For the specific use case just mentioned, I am getting by using QDomElement.elementsByTagName() and iterating those elements, but it is not as flexible nor as cool as XPath.

Is it just wishful thinking? Would it be worth the effort to develop some new class that implements the QAbstractXmlReceiver interface? Or, would I just end up with a new collection of data that has no direct relationship to the QDomNode objects in the QDomDocument?


Solution

  • The following is the utility function I use to search for nodes in a QDomDocument using an XPath expression. It uses the QDomNodeModel class suggested by @Alejandro, downloadable from https://adared.ch/qdomnodemodel-qxmlquery. It is based on the usage example from https://www.qtcentre.org/threads/37645-QAbstractXmlNodeModel-implementation-QDomNodeModel-QXmlQuery. Thanks to Stanislaw Adaszewski, who provided both the QDomNodeModel class and the usage example.

    There are a few methods in QDomNodeModel that are commented as not implemented. However, for the simple XML content that I needed to search, QDomNodeModel is sufficient as-is.

    //
    /// @brief Search for nodes in a QDomDocument using an XPath.
    /// @note I cannot return a QDomNodeList, because it has no public methods for adding items to it.
    /// @param[in] doc The document to search.
    /// @param[in] fromNode The node in the document to start searching from.
    ///   e.g., to search the whole document, use <code>doc.documentElement()</code>.
    /// @param[in] xpath The XPath expression.
    /// @return A list of found nodes.
    //
    QList<QDomNode> findNodes( QDomDocument const & doc, QDomNode const & fromNode, QString const & xpath )
    {
      qDebug( "%s", __FUNCTION__ );
      QList<QDomNode> foundNodes;
    
      //------------------------------
      // The name pool that everybody shares.
      QXmlNamePool pool;
    
      //------------------------------
      // The model that wraps the document.
      QDomNodeModel model( pool, doc );
    
      //------------------------------
      // The query.
      // XQuery10 means the default XQuery 1.0 language, as opposed to XSLT20.
      QXmlQuery query( /*QXmlQuery::XQuery10,*/ pool );
    
      // Operate on the given node.
      QXmlNodeModelIndex fromIndex = model.fromDomNode( fromNode );
      query.setFocus( QXmlItem( fromIndex ) );
    
      // The query statement.
      query.setQuery( xpath );
      if ( !query.isValid() )
      {
        qDebug( "Query is not valid" );
        return foundNodes;
      }
    
      //------------------------------
      // The destination for the result of the query.
      QXmlResultItems result;
    
      //------------------------------
      // Evaluate the query.
      query.evaluateTo( &result );
      if ( result.hasError() )
      {
        qDebug( "Query evaluation failed" );
        return foundNodes;
      }
    
      //------------------------------
      // The result of the query.
      qDebug( "Query result:" );
      while ( !result.next().isNull() )
      {
        QXmlNodeModelIndex index = result.current().toNodeModelIndex();
        QDomNode node = model.toDomNode( index );
        qDebug( "  %d %s: %s", node.nodeType(), qPrintable( node.nodeName() ), qPrintable( node.nodeValue() ) );
        foundNodes << node;
      }
    
      return foundNodes;
    }
    

    In my application, I load an XML file, and use the above utility function to search it.

    //------------------------------
    // The path of the XML file.
    QString path = "settings.xml";
    
    //------------------------------
    // Open the file.
    QFile file( path );
    if ( !file.open( QIODevice::ReadOnly ) )
    {
      qDebug( "Failed to open '%s': %s", qPrintable( path ), qPrintable( file.errorString() ) );
      return;
    }
    
    //------------------------------
    // Load the file into a document.
    QDomDocument doc;
    QString error;
    int line;
    int column;
    if ( !doc.setContent( &file, &error, &line, &column ) )
    {
      qDebug( "%s(%d,%d): %s", qPrintable( path ), line, column, qPrintable( error ) );
      return;
    }
    
    //------------------------------
    // The document root element.
    QDomElement rootElem = doc.documentElement();
    
    //------------------------------
    // Search for an element whose name attribute has a certain value.
    QString name = "Alice";
    QString xpath = QString( "setting[@name='%1']" ).arg( name );
    QList<QDomNode> foundNodes = findNodes( doc, rootElem, xpath );
    
    //------------------------------
    // Did I find it?
    if ( foundNodes.size() > 0 )
    {
      QDomElement foundElem = foundNodes.at( 0 ).toElement();
    
      // Do something with that element.      
      ...
    } 
    

    The example XML content to search.

    <?xml version='1.0'?>
    <settings>
      <setting name="Bob">12</setting>
      <setting name="Carol">34</setting>
      <setting name="Ted">56</setting>
      <setting name="Alice">78</setting>
    </settings>