QWebElement sees the light, do I hear a booyakasha!?

One of the main missing parts of QtWebKit so far has been a proper way to inspect and manipulate the document structure. In JavaScript this is provided by the Document Object Model (DOM) bindings -- giving you methods like getElementById(), createElement(), and insertBefore(). These methods were also accessible in Qt 4.4 and 4.5 though QWebFrame::evaluateJavaScript(), but it was hardly a optimal way of working with the document.

The reason a proper API for this was deferred in the earlier versions of QtWebKit was because we wanted to provide an API that was not only powerful, but also easy to use. That left out using QDom, or a similar exhaustive API, as customer feedback has shown that writing code on that level can be both tedious and error prone.

Consider the following snippet:

QDomElement docElem = doc.documentElement();

QDomElement root = docElem.firstChildElement("database");
QDomElement entry = root.firstChildElement("entry");
for (; !entry.isNull(); entry = entry.nextSiblingElement("entry")) {
QDomElement data = entry.firstChildElement("data");
for (; !data.isNull(); data = data.nextSiblingElement("data")) {
QDomAttr attribute = data.attributeNode("format");
if (!attribute.isNull() && attribute.value() == "human") {
cout < < "Data:" << data.text() << endl;
}
}
}

Traversing the DOM like this quickly becomes hairy. The same pattern can be found in JavaScript code: using getElementById(), traversing children, looking for a element of a certain type, etc.

Another typical use case is manipulation, where you would do something like:

QDomElement foo = doc.getElementById("foo");
QDomElement elem = doc.createElement("img");
elem.setAttribute("src", "myimage.png");
foo.parent().insertAfter(elem, foo);

Remember to keep your tongue straight when building that insertAfter() statement!

Now some of you are probably jumping on your chairs now, screaming "this is not how you would do it in JavaScript!". And you're right, the JavaScript world has found a way to shield off the above annoyances: wrapper libraries like jQuery and Prototype.

Taking inspiration from these libraries we did a one-day prototyping/hacking session back in November, and the results were promising. We also asked customers what they really meant when they were asking for a DOM API, and it turned out that the goal was to inspect and manipulate the DOM, but not necessarily though a one-to-one mapping of the API provided by the DOM specification. Last week we managed to reserve some cycles to continue the work, and we're now ready for some feedback.

So how does the new shiny QtWebKit DOM API look? There's two classes: QWebElement and QWebElementSelection.The former wraps a DOM Element, and has methods for manipulation and traversal. The latter is just a list of QWebElements, which can be iterated and extended. The frosting lies in the way elements are selected: using CSS3 selector syntax (similar to jQuery). The implementation of the selectors is already part of WebKit, so we're basically building on a tried and tested code base.

Using the two snippets above as examples, they would become:

QWebElement document = mainFrame.documentElement();
foreach (QWebElement humanData, document.findAll("database entry data[format='human']") {

cout < < "Data:" << humanData.attribute("format") << endl;
}

and

document.findFirst("#foo").insertAfter("<img src='myimage.png'/>");

That's a lot easier on my eyes at least.

The initial implementation of the new API is already landed in trunk, so feel free to try it out. You can find the API documentation here, but please note that this is a work in progress, so the API may change.

Oh, and here's a screenshot of the QtLauncher highlighting all links:

qtwebkit-dom-api.png

Q: Is the DOM API targeted for Qt 4.6?
A: Yes

Happy hacking!


Blog Topics:

Comments