如何在Qt的QDomDocument中通过XPath获取QDomNode集合?
Hey there! Let's break down your problem and find a solution that fits your Qt 4.7/4.8 workflow.
Is your requirement achievable?
Absolutely. Even though Qt 4.x doesn’t have native support for directly fetching QDomNode collections via XPath like C#’s XmlDocument.SelectNodes(), we can work around this with custom code using Qt’s existing XML APIs.
Is implementing QAbstractXmlReceiver worth it?
Yes, it’s a solid approach for your use case. The default behavior of QXmlQuery returns string results, but creating a custom receiver lets you capture meaningful identifiers for matching nodes, which you can then map back to your original QDomDocument to get the actual QDomNode objects you need for editing.
Can you associate results back to the original QDomNode?
You can’t get the exact QDomNode directly from QXmlQuery (since it uses a separate XML data model), but you can correlate results by generating unique identifiers for nodes in your XQuery expression, then using those identifiers to find the matching nodes in your QDomDocument.
A Practical Implementation Example
Here’s how to put this together:
First, create a custom QAbstractXmlReceiver that captures node identifiers and maps them back to your QDomDocument:
#include <QAbstractXmlReceiver> #include <QDomDocument> #include <QList> #include <QXmlQuery> class DomNodeReceiver : public QAbstractXmlReceiver { public: DomNodeReceiver(QDomDocument doc, QList<QDomNode>& resultNodes) : m_doc(doc), m_resultNodes(resultNodes) {} void atomizedValue(const QVariant& value) override { // Parse the unique identifier we'll generate in our XQuery const QString identifier = value.toString(); const QStringList parts = identifier.split("|"); if (parts.size() != 2) return; const QString nodeCriteria = parts[0]; const int position = parts[1].toInt(); const QStringList criteriaParts = nodeCriteria.split("@"); if (criteriaParts.size() != 2) return; const QString tagName = criteriaParts[0]; const QStringList attrParts = criteriaParts[1].split("="); if (attrParts.size() != 2) return; const QString attrName = attrParts[0]; const QString attrValue = attrParts[1]; // Find the matching node in the original QDomDocument QDomNodeList nodes = m_doc.elementsByTagName(tagName); int matchCount = 0; for (int i = 0; i < nodes.count(); ++i) { QDomElement elem = nodes.at(i).toElement(); if (elem.hasAttribute(attrName) && elem.attribute(attrName) == attrValue) { if (++matchCount == position) { m_resultNodes.append(elem); break; } } } } // We need to override these empty methods since they're pure virtual void startElement(const QString&, const QXmlNodeModelIndex&) override {} void endElement() override {} void startAttribute(const QString&, const QXmlNodeModelIndex&) override {} void endAttribute() override {} void startNamespaceBinding(const QString&, const QString&) override {} void endNamespaceBinding() override {} void characters(const QString&) override {} void comment(const QString&) override {} void processingInstruction(const QString&, const QString&) override {} void atomicValue(const QVariant&) override {} void endDocument() override {} void startDocument() override {} private: QDomDocument m_doc; QList<QDomNode>& m_resultNodes; };
Then, use this receiver with QXmlQuery to get your QDomNode list:
int main() { QDomDocument doc; // Load your XML into doc (example): QFile xmlFile("your_file.xml"); if (!xmlFile.open(QIODevice::ReadOnly)) { // Handle error return 1; } if (!doc.setContent(&xmlFile)) { xmlFile.close(); // Handle error return 1; } xmlFile.close(); QXmlQuery query; query.bindVariable("doc", doc); // XQuery to generate unique identifiers for nodes with name='target' query.setQuery(R"( declare variable $doc external; for $node in $doc//*[@name='target'] return concat(local-name($node), '@name=', $node/@name, '|', position()) )"); QList<QDomNode> matchedNodes; DomNodeReceiver receiver(doc, matchedNodes); if (!query.evaluateTo(&receiver)) { // Handle query error return 1; } // Now you can modify the matched QDomNodes directly for (QDomNode node : matchedNodes) { QDomElement elem = node.toElement(); // Example: Replace the element's text content elem.setText("Updated content for target node"); } // Save the modified document back to file if needed QFile outputFile("modified_file.xml"); if (outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream stream(&outputFile); doc.save(stream, 4); // 4 spaces for indentation outputFile.close(); } return 0; }
Notes on This Approach
- This method uses unique identifiers (tag name + attribute value + position) to avoid ambiguity when multiple nodes match your XPath.
- If you need to match more complex XPath expressions, you can adjust the XQuery to generate more detailed identifiers (e.g., including parent node paths).
- If you ever upgrade to Qt 5+, you’ll have access to
QDomXPath, which lets you queryQDomNodeListdirectly via XPath without this workaround.
内容的提问来源于stack exchange,提问作者Mike Finch

