qwebelement.cpp   [plain text]


/*
    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "config.h"
#include "qwebelement.h"

#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSParser.h"
#include "CSSRuleList.h"
#include "CSSRule.h"
#include "CSSStyleRule.h"
#include "CString.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "FrameView.h"
#include "HTMLElement.h"
#include "JSGlobalObject.h"
#include "JSHTMLElement.h"
#include "JSObject.h"
#include "NodeList.h"
#include "PropertyNameArray.h"
#include "ScriptFunctionCall.h"
#include "StaticNodeList.h"
#include "qt_runtime.h"
#include "qwebframe.h"
#include "qwebframe_p.h"
#include "runtime_root.h"
#include <wtf/Vector.h>

using namespace WebCore;

class QWebElementPrivate
{
public:
};

/*!
    \class QWebElement
    \since 4.6
    \brief The QWebElement class provides convenience access to DOM elements in a QWebFrame.
    \preliminary

    QWebElement is the main class to provide easy access to the document model.
    The document model is represented by a tree-like structure of DOM elements.
    The root of the tree is called the document element and can be accessed using QWebFrame::documentElement().

    You can reach specific elements by using the findAll() and findFirst() functions, which
    allow the use of CSS selectors to identify elements.

    \snippet webkitsnippets/webelement/main.cpp FindAll

    The first list contains all span elements in the document. The second list contains
    only the span elements that are children of the paragraph that is classified
    as "intro" paragraph.

    Alternatively you can manually traverse the document using firstChild() and nextSibling():

    \snippet webkitsnippets/webelement/main.cpp Traversing with QWebElement

    The underlying content of QWebElement is explicitly shared. Creating a copy of a QWebElement
    does not create a copy of the content, both instances point to the same underlying element.

    The element's attributes can be read using attribute() and changed using setAttribute().

    The content of the child elements can be converted to plain text using toPlainText() and to
    x(html) using toXml(), and it is possible to replace the content using setPlainText() and setXml().

    Depending on the type of the underlying element there may be extra functionality available, not
    covered through QWebElement's API. For example a HTML form element can be triggered to submit the
    entire form. These list of these functions is available through functions() and they can be called
    directly using callFunction().
*/

/*!
    Constructs a null web element.
*/
QWebElement::QWebElement()
    : d(0)
    , m_element(0)
{
}

/*!
    \internal
*/
QWebElement::QWebElement(WebCore::Element* domElement)
    : d(0)
    , m_element(domElement)
{
    if (m_element)
        m_element->ref();
}

/*!
    \internal
*/
QWebElement::QWebElement(WebCore::Node* node)
    : d(0)
    , m_element(0)
{
    if (node && node->isHTMLElement()) {
        m_element = static_cast<HTMLElement*>(node);
        m_element->ref();
    }
}

/*!
    Constructs a copy of \a other.
*/
QWebElement::QWebElement(const QWebElement &other)
    : d(0)
    , m_element(other.m_element)
{
    if (m_element)
        m_element->ref();
}

/*!
    Assigns \a other to this element and returns a reference to this element.
*/
QWebElement &QWebElement::operator=(const QWebElement &other)
{
    // ### handle "d" assignment
    if (this != &other) {
        Element *otherElement = other.m_element;
        if (otherElement)
            otherElement->ref();
        if (m_element)
            m_element->deref();
        m_element = otherElement;
    }
    return *this;
}

/*!
    Destroys the element. The underlying DOM element is not destroyed.
*/
QWebElement::~QWebElement()
{
    delete d;
    if (m_element)
        m_element->deref();
}

bool QWebElement::operator==(const QWebElement& o) const
{
    return m_element == o.m_element;
}

bool QWebElement::operator!=(const QWebElement& o) const
{
    return m_element != o.m_element;
}

/*!
    Returns true if the element is a null element; false otherwise.
*/
bool QWebElement::isNull() const
{
    return !m_element;
}

/*!
    Returns a new collection of elements that are children of this element
    and that match the given CSS selector \a selectorQuery.

    The query is specified using \l{http://www.w3.org/TR/REC-CSS2/selector.html#q1}{standard CSS2 selectors}.
*/
QList<QWebElement> QWebElement::findAll(const QString &selectorQuery) const
{
    QList<QWebElement> elements;
    if (!m_element)
        return elements;

    ExceptionCode exception = 0; // ###
    RefPtr<NodeList> nodes = m_element->querySelectorAll(selectorQuery, exception);
    if (!nodes)
        return elements;

    for (int i = 0; i < nodes->length(); ++i) {
        WebCore::Node* n = nodes->item(i);
        elements.append(QWebElement(static_cast<Element*>(n)));
    }

    return elements;
}

/*!
    Returns the first child element that matches the given CSS selector \a selectorQuery.

    This function is equivalent to calling findAll() and taking only the
    first element in the returned collection of elements. However calling
    this function is more efficient.
*/
QWebElement QWebElement::findFirst(const QString &selectorQuery) const
{
    if (!m_element)
        return QWebElement();
    ExceptionCode exception = 0; // ###
    return QWebElement(m_element->querySelector(selectorQuery, exception).get());
}

/*!
    Replaces the existing content of this element with \a text.

    This is equivalent to setting the HTML innerText property.
*/
void QWebElement::setPlainText(const QString &text)
{
    if (!m_element || !m_element->isHTMLElement())
        return;
    ExceptionCode exception = 0;
    static_cast<HTMLElement*>(m_element)->setInnerText(text, exception);
}

/*!
    Returns the text between the start and the end tag of this
    element.

    This is equivalent to reading the HTML innerText property.
*/
QString QWebElement::toPlainText() const
{
    if (!m_element || !m_element->isHTMLElement())
        return QString();
    return static_cast<HTMLElement*>(m_element)->innerText();
}

/*!
    Replaces the contents of this element as well as its own tag with \a markup.
    The string may contain HTML or XML tags, which is parsed and formatted
    before insertion into the document.

    \note This is currently only implemented for (X)HTML elements.
*/
void QWebElement::setOuterXml(const QString &markup)
{
    if (!m_element || !m_element->isHTMLElement())
        return;

    ExceptionCode exception = 0;

    static_cast<HTMLElement*>(m_element)->setOuterHTML(markup, exception);
}

/*!
    Returns this element converted to XML, including the start and the end
    tag of this element and its attributes.

    \note This is currently only implemented for (X)HTML elements.
*/
QString QWebElement::toOuterXml() const
{
    if (!m_element || !m_element->isHTMLElement())
        return QString();

    return static_cast<HTMLElement*>(m_element)->outerHTML();
}

/*!
    Replaces the content of this element with \a markup.
    The string may contain HTML or XML tags, which is parsed and formatted
    before insertion into the document.

    \note This is currently only implemented for (X)HTML elements.
*/
void QWebElement::setInnerXml(const QString &markup)
{
    if (!m_element || !m_element->isHTMLElement())
        return;

    ExceptionCode exception = 0;

    static_cast<HTMLElement*>(m_element)->setInnerHTML(markup, exception);
}

/*!
    Returns the XML between the start and the end tag of this
    element.

    \note This is currently only implemented for (X)HTML elements.
*/
QString QWebElement::toInnerXml() const
{
    if (!m_element || !m_element->isHTMLElement())
        return QString();

    return static_cast<HTMLElement*>(m_element)->innerHTML();
}

/*!
    Adds an attribute called \a name with the value \a value. If an attribute
    with the same name exists, its value is replaced by \a value.
*/
void QWebElement::setAttribute(const QString &name, const QString &value)
{
    if (!m_element)
        return;
    ExceptionCode exception = 0;
    m_element->setAttribute(name, value, exception);
}

/*!
    Adds an attribute called \a name in the namespace described with \a namespaceUri
    with the value \a value. If an attribute with the same name exists, its value is
    replaced by \a value.
*/
void QWebElement::setAttributeNS(const QString &namespaceUri, const QString &name, const QString &value)
{
    if (!m_element)
        return;
    WebCore::ExceptionCode exception = 0;
    m_element->setAttributeNS(namespaceUri, name, value, exception);
}

/*!
    Returns the attributed called \a name. If the attribute does not exist \a defaultValue is
    returned.
*/
QString QWebElement::attribute(const QString &name, const QString &defaultValue) const
{
    if (!m_element)
        return QString();
    if (m_element->hasAttribute(name))
        return m_element->getAttribute(name);
    else
        return defaultValue;
}

/*!
    Returns the attributed called \a name in the namespace described with \a namespaceUri.
    If the attribute does not exist \a defaultValue is returned.
*/
QString QWebElement::attributeNS(const QString &namespaceUri, const QString &name, const QString &defaultValue) const
{
    if (!m_element)
        return QString();
    if (m_element->hasAttributeNS(namespaceUri, name))
        return m_element->getAttributeNS(namespaceUri, name);
    else
        return defaultValue;
}

/*!
    Returns true if this element has an attribute called \a name; otherwise returns false.
*/
bool QWebElement::hasAttribute(const QString &name) const
{
    if (!m_element)
        return false;
    return m_element->hasAttribute(name);
}

/*!
    Returns true if this element has an attribute called \a name in the namespace described
    with \a namespaceUri; otherwise returns false.
*/
bool QWebElement::hasAttributeNS(const QString &namespaceUri, const QString &name) const
{
    if (!m_element)
        return false;
    return m_element->hasAttributeNS(namespaceUri, name);
}

/*!
    Removes the attribute called \a name from this element.
*/
void QWebElement::removeAttribute(const QString &name)
{
    if (!m_element)
        return;
    ExceptionCode exception = 0;
    m_element->removeAttribute(name, exception);
}

/*!
    Removes the attribute called \a name in the namespace described with \a namespaceUri
    from this element.
*/
void QWebElement::removeAttributeNS(const QString &namespaceUri, const QString &name)
{
    if (!m_element)
        return;
    WebCore::ExceptionCode exception = 0;
    m_element->removeAttributeNS(namespaceUri, name, exception);
}

/*!
    Returns true if the element has any attributes defined; otherwise returns false;
*/
bool QWebElement::hasAttributes() const
{
    if (!m_element)
        return false;
    return m_element->hasAttributes();
}

/*!
    Returns the geometry of this element, relative to its containing frame.
*/
QRect QWebElement::geometry() const
{
    if (!m_element)
        return QRect();
    return m_element->getRect();
}

/*!
    Returns the tag name of this element.
*/
QString QWebElement::tagName() const
{
    if (!m_element)
        return QString();
    return m_element->tagName();
}

/*!
    Returns the namespace prefix of the element or an empty string if the element has no namespace prefix.
*/
QString QWebElement::prefix() const
{
    if (!m_element)
        return QString();
    return m_element->prefix();
}

/*!
    If the element uses namespaces, this function returns the local name of the element;
    otherwise it returns an empty string.
*/
QString QWebElement::localName() const
{
    if (!m_element)
        return QString();
    return m_element->localName();
}

/*!
    Returns the namespace URI of this element or an empty string if the element has no namespace URI.
*/
QString QWebElement::namespaceUri() const
{
    if (!m_element)
        return QString();
    return m_element->namespaceURI();
}

/*!
    Returns the parent element of this element or a null element if this element
    is the root document element.
*/
QWebElement QWebElement::parent() const
{
    if (m_element)
        return QWebElement(m_element->parentElement());
    return QWebElement();
}

/*!
    Returns the first child element of this element.

    \sa lastChild() previousSibling() nextSibling()
*/
QWebElement QWebElement::firstChild() const
{
    if (!m_element)
        return QWebElement();
    for (Node* child = m_element->firstChild(); child; child = child->nextSibling()) {
        if (!child->isElementNode())
            continue;
        Element* e = static_cast<Element*>(child);
        return QWebElement(e);
    }
    return QWebElement();
}

/*!
    Returns the last child element of this element.

    \sa firstChild() previousSibling() nextSibling()
*/
QWebElement QWebElement::lastChild() const
{
    if (!m_element)
        return QWebElement();
    for (Node* child = m_element->lastChild(); child; child = child->previousSibling()) {
        if (!child->isElementNode())
            continue;
        Element* e = static_cast<Element*>(child);
        return QWebElement(e);
    }
    return QWebElement();
}

/*!
    Returns the next sibling element of this element.

    \sa firstChild() previousSibling() lastChild()
*/
QWebElement QWebElement::nextSibling() const
{
    if (!m_element)
        return QWebElement();
    for (Node* sib = m_element->nextSibling(); sib; sib = sib->nextSibling()) {
        if (!sib->isElementNode())
            continue;
        Element* e = static_cast<Element*>(sib);
        return QWebElement(e);
    }
    return QWebElement();
}

/*!
    Returns the previous sibling element of this element.

    \sa firstChild() nextSibling() lastChild()
*/
QWebElement QWebElement::previousSibling() const
{
    if (!m_element)
        return QWebElement();
    for (Node* sib = m_element->previousSibling(); sib; sib = sib->previousSibling()) {
        if (!sib->isElementNode())
            continue;
        Element* e = static_cast<Element*>(sib);
        return QWebElement(e);
    }
    return QWebElement();
}

/*!
    Returns the document this element belongs to.
*/
QWebElement QWebElement::document() const
{
    if (!m_element)
        return QWebElement();
    Document* document = m_element->document();
    if (!document)
        return QWebElement();
    return QWebElement(document->documentElement());
}

/*!
    Returns the web frame this elements is a part of. If the element is
    a null element null is returned.
*/
QWebFrame *QWebElement::webFrame() const
{
    if (!m_element)
        return 0;

    Document* document = m_element->document();
    if (!document)
        return 0;

    Frame* frame = document->frame();
    if (!frame)
        return 0;
    return QWebFramePrivate::kit(frame);
}

static bool setupScriptContext(WebCore::Element* element, JSC::JSValue& thisValue, ScriptState*& state, ScriptController*& scriptController)
{
    if (!element)
        return false;

    Document* document = element->document();
    if (!document)
        return false;

    Frame* frame = document->frame();
    if (!frame)
        return false;

    scriptController = frame->script();
    if (!scriptController)
        return false;

    state = scriptController->globalObject()->globalExec();
    if (!state)
        return false;

    thisValue = toJS(state, element);
    if (!thisValue)
        return false;

    return true;
}


static bool setupScriptObject(WebCore::Element* element, ScriptObject& object, ScriptState*& state, ScriptController*& scriptController)
{
    if (!element)
        return false;

    Document* document = element->document();
    if (!document)
        return false;

    Frame* frame = document->frame();
    if (!frame)
        return false;

    scriptController = frame->script();

    state = scriptController->globalObject()->globalExec();

    JSC::JSValue thisValue = toJS(state, element);
    if (!thisValue)
        return false;

    JSC::JSObject* thisObject = thisValue.toObject(state);
    if (!thisObject)
        return false;

    object = ScriptObject(thisObject);
    return true;
}

/*!
    Executes the \a scriptSource with this element as the `this' object.

    \sa callFunction()
*/
QVariant QWebElement::evaluateScript(const QString& scriptSource)
{
    if (scriptSource.isEmpty())
        return QVariant();

    ScriptState* state = 0;
    JSC::JSValue thisValue;
    ScriptController* scriptController = 0;

    if (!setupScriptContext(m_element, thisValue, state, scriptController))
        return QVariant();

    JSC::ScopeChain& scopeChain = state->dynamicGlobalObject()->globalScopeChain();
    JSC::UString script((const UChar*)scriptSource.data(), scriptSource.length());
    JSC::Completion completion = JSC::evaluate(state, scopeChain, JSC::makeSource(script), thisValue);
    if ((completion.complType() != JSC::ReturnValue) && (completion.complType() != JSC::Normal))
        return QVariant();

    JSC::JSValue result = completion.value();
    if (!result)
        return QVariant();

    int distance = 0;
    return JSC::Bindings::convertValueToQVariant(state, result, QMetaType::Void, &distance);
}

/*!
    Calls the function with the given \a name and \a arguments.

    The underlying DOM element that QWebElement wraps may have dedicated functions depending
    on its type. For example a form element can have the "submit" function, that would submit
    the form to the destination specified in the HTML.

    \sa functions()
*/
QVariant QWebElement::callFunction(const QString &name, const QVariantList &arguments)
{
    ScriptState* state = 0;
    ScriptObject thisObject;
    ScriptController* scriptController = 0;

    if (!setupScriptObject(m_element, thisObject, state, scriptController))
        return QVariant();

    ScriptFunctionCall functionCall(state, thisObject, name);

    for (QVariantList::ConstIterator it = arguments.constBegin(), end = arguments.constEnd();
         it != end; ++it)
        functionCall.appendArgument(JSC::Bindings::convertQVariantToValue(state, scriptController->bindingRootObject(), *it));

    bool hadException = false;
    ScriptValue result = functionCall.call(hadException);
    if (hadException)
        return QVariant();

    int distance = 0;
    return JSC::Bindings::convertValueToQVariant(state, result.jsValue(), QMetaType::Void, &distance);
}

/*!
    Returns a list of function names this element supports.

    The function names returned are the same functions that are callable from the DOM
    element's JavaScript binding.

    \sa callFunction()
*/
QStringList QWebElement::functions() const
{
    ScriptState* state = 0;
    ScriptObject thisObject;
    ScriptController* scriptController = 0;

    if (!setupScriptObject(m_element, thisObject, state, scriptController))
        return QStringList();

    JSC::JSObject* object = thisObject.jsObject();
    if (!object)
        return QStringList();

    QStringList names;

    // Enumerate the contents of the object
    JSC::PropertyNameArray properties(state);
    object->getPropertyNames(state, properties);
    for (JSC::PropertyNameArray::const_iterator it = properties.begin();
         it != properties.end(); ++it) {

        JSC::JSValue property = object->get(state, *it);
        if (!property)
            continue;

        JSC::JSObject* function = property.toObject(state);
        if (!function)
            continue;

        JSC::CallData callData;
        JSC::CallType callType = function->getCallData(callData);
        if (callType == JSC::CallTypeNone)
            continue;

        JSC::UString ustring = (*it).ustring();
        names << QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size());
    }

    if (state->hadException())
        state->clearException();

    return names;
}

/*!
    Returns the value of the element's \a name property.

    If no such property exists, the returned variant is invalid.

    The return property has the same value as the corresponding property
    in the element's JavaScript binding with the same name.

    Information about all available properties is provided through scriptProperties().

    \sa setScriptableProperty(), scriptableProperties()
*/
QVariant QWebElement::scriptableProperty(const QString &name) const
{
    ScriptState* state = 0;
    ScriptObject thisObject;
    ScriptController *scriptController = 0;

    if (!setupScriptObject(m_element, thisObject, state, scriptController))
        return QVariant();

    String wcName(name);
    JSC::JSValue property = thisObject.jsObject()->get(state, JSC::Identifier(state, wcName));

    // ###
    if (state->hadException())
        state->clearException();

    int distance = 0;
    return JSC::Bindings::convertValueToQVariant(state, property, QMetaType::Void, &distance);
}

/*!
    Sets the value of the element's \a name property to \a value.

    Information about all available properties is provided through scriptProperties().

    Setting the property will affect the corresponding property
    in the element's JavaScript binding with the same name.

    \sa scriptableProperty(), scriptableProperties()
*/
void QWebElement::setScriptableProperty(const QString &name, const QVariant &value)
{
    ScriptState* state = 0;
    ScriptObject thisObject;
    ScriptController* scriptController = 0;

    if (!setupScriptObject(m_element, thisObject, state, scriptController))
        return;

    JSC::JSValue jsValue = JSC::Bindings::convertQVariantToValue(state, scriptController->bindingRootObject(), value);
    if (!jsValue)
        return;

    String wcName(name);
    JSC::PutPropertySlot slot;
    thisObject.jsObject()->put(state, JSC::Identifier(state, wcName), jsValue, slot);
    if (state->hadException())
        state->clearException();
}

/*!
    Returns a list of property names this element supports.

    The function names returned are the same properties that are accessible from the DOM
    element's JavaScript binding.

    \sa setScriptableProperty(), scriptableProperty()
*/
QStringList QWebElement::scriptableProperties() const
{
    if (!m_element)
        return QStringList();

    Document* document = m_element->document();
    if (!document)
        return QStringList();

    Frame* frame = document->frame();
    if (!frame)
        return QStringList();

    ScriptController* script = frame->script();
    JSC::ExecState* exec = script->globalObject()->globalExec();

    JSC::JSValue thisValue = toJS(exec, m_element);
    if (!thisValue)
        return QStringList();

    JSC::JSObject* object = thisValue.toObject(exec);
    if (!object)
        return QStringList();

    QStringList names;

    // Enumerate the contents of the object
    JSC::PropertyNameArray properties(exec);
    object->getPropertyNames(exec, properties);
    for (JSC::PropertyNameArray::const_iterator it = properties.begin();
         it != properties.end(); ++it) {

        JSC::JSValue property = object->get(exec, *it);
        if (!property)
            continue;

        JSC::JSObject* function = property.toObject(exec);
        if (!function)
            continue;

        JSC::CallData callData;
        JSC::CallType callType = function->getCallData(callData);
        if (callType != JSC::CallTypeNone)
            continue;

        JSC::UString ustring = (*it).ustring();
        names << QString::fromUtf16((const ushort*)ustring.rep()->data(),ustring.size());
    }

    if (exec->hadException())
        exec->clearException();

    return names;
}

/*!
    \enum QWebElement::ResolveRule
    \since 4.6

    This enum describes how QWebElement's styleProperty resolves the given
    property name.

    \value IgnoreCascadingStyles Return the property value as it is defined
    in the element, without respecting style inheritance and other CSS rules.
    \value RespectCascadingStyles The property's value is determined using
    the inheritance and importance rules defined in the document's stylesheet.
*/

/*!
    \enum QWebElement::StylePriority
    \since 4.6

    This enum describes the priority newly set CSS properties should have when
    set using QWebElement::setStyleProperty().

    \value NormalStylePriority Define the property without important
    priority even if "!important" is explicitly set in \a value.
    \value DeclaredStylePriority Define the property respecting the
    priority specified in \a value.
    \value ImportantStylePriority Define the property to have
    an important priority, this is equal to appending "!important" to the value.
*/

/*!
    Returns the value of the style named \a name or an empty string if such one
    does not exist.

    If \a rule is IgnoreCascadingStyles, the value defined inside the element
    (inline in CSS terminology) is returned.

    if \a rule is RespectCascadingStyles, the actual style applied to the
    element is returned.

    In CSS, the cascading part has to do with which CSS rule has priority and
    is thus applied. Generally speaking, the last defined rule has priority,
    thus an inline style rule has priority over an embedded block style rule,
    which in return has priority over an external style rule.

    If the !important declaration is set on one of those, the declaration gets
    highest priority, unless other declarations also use the !important
    declaration, in which the last !important declaration takes predecence.
*/
QString QWebElement::styleProperty(const QString &name, ResolveRule rule) const
{
    if (!m_element || !m_element->isStyledElement())
        return QString();

    int propID = cssPropertyID(name);

    if (!propID)
        return QString();

    CSSStyleDeclaration* style = static_cast<StyledElement*>(m_element)->style();

    if (rule == IgnoreCascadingStyles)
        return style->getPropertyValue(propID);

    if (rule == RespectCascadingStyles) {
        if (style->getPropertyPriority(propID))
            return style->getPropertyValue(propID);

        // We are going to resolve the style property by walking through the
        // list of non-inline matched CSS rules for the element, looking for
        // the highest priority definition.

        // Get an array of matched CSS rules for the given element sorted
        // by importance and inheritance order. This include external CSS
        // declarations, as well as embedded and inline style declarations.

        DOMWindow* domWindow = m_element->document()->frame()->domWindow();
        if (RefPtr<CSSRuleList> rules = domWindow->getMatchedCSSRules(m_element, "")) {
            for (int i = rules->length(); i > 0; --i) {
                CSSStyleRule* rule = static_cast<CSSStyleRule*>(rules->item(i - 1));

                if (rule->style()->getPropertyPriority(propID))
                    return rule->style()->getPropertyValue(propID);

                if (style->getPropertyValue(propID).isEmpty())
                    style = rule->style();
            }
        }

        return style->getPropertyValue(propID);
    }

    return QString();
}

/*!
    Sets the value of the style named \a name to \a value.

    Setting a value, doesn't necessarily mean that it will become the applied
    value, due to the fact that the style property's value might have been set
    earlier with priority in external or embedded style declarations.

    In order to ensure that the value will be applied, ImportantStylePriority
    should be used as \a priority.

    Following the CSS syntax for property values, this is equal to appending
    "!important" to the value.

    This syntax is supported when using DeclaredStylePriority as \a priority.

    Using NormalStylePriority as \a priority, the property will have normal
    priority, and any "!important" declaration will be ignored. On the other
    hand, using ImportantStylePriority sets the important priority even when
    not explicit passed in \a value.
    By using DeclaredStylePriority as \a priority the property will respect the
    priority specified in \a value.
*/
void QWebElement::setStyleProperty(const QString &name, const QString &value, StylePriority priority)
{
    if (!m_element || !m_element->isStyledElement())
        return;

    int propID = cssPropertyID(name);
    CSSStyleDeclaration* style = static_cast<StyledElement*>(m_element)->style();
    if (!propID || !style)
        return;

    ExceptionCode exception = 0;

    const QRegExp hasImportantTest(QLatin1String("!\\s*important"));
    int index = value.indexOf(hasImportantTest);

    QString newValue = (index != -1) ? value.left(index - 1) : value;

    switch (priority) {
    case NormalStylePriority:
        style->setProperty(name, newValue, "", exception);
        break;
    case DeclaredStylePriority:
        style->setProperty(name, newValue, (index != -1) ? "important" : "", exception);
        break;
    case ImportantStylePriority:
        style->setProperty(name, newValue, "important", exception);
        break;
    default:
        break;
    }
}

/*!
    Returns the computed value for style named \a name or an empty string if the style has no such name.
*/
QString QWebElement::computedStyleProperty(const QString &name) const
{
    if (!m_element || !m_element->isStyledElement())
        return QString();

    int propID = cssPropertyID(name);

    RefPtr<CSSComputedStyleDeclaration> style = computedStyle(m_element);
    if (!propID || !style)
        return QString();

    return style->getPropertyValue(propID);
}

/*!
    Returns the list of classes of this element.
*/
QStringList QWebElement::classes() const
{
    if (!hasAttribute(QLatin1String("class")))
        return QStringList();

    QStringList classes =  attribute(QLatin1String("class")).simplified().split(QLatin1Char(' '), QString::SkipEmptyParts);
#if QT_VERSION >= 0x040500
    classes.removeDuplicates();
#else
    int n = classes.size();
    int j = 0;
    QSet<QString> seen;
    seen.reserve(n);
    for (int i = 0; i < n; ++i) {
        const QString& s = classes.at(i);
        if (seen.contains(s))
            continue;
        seen.insert(s);
        if (j != i)
            classes[j] = s;
        ++j;
    }
    if (n != j)
        classes.erase(classes.begin() + j, classes.end());
#endif
    return classes;
}

/*!
    Returns true if this element has a class called \a name; otherwise returns false.
*/
bool QWebElement::hasClass(const QString &name) const
{
    QStringList list = classes();
    return list.contains(name);
}

/*!
    Adds the specified class \a name to the element.
*/
void QWebElement::addClass(const QString &name)
{
    QStringList list = classes();
    if (!list.contains(name)) {
        list.append(name);
        QString value = list.join(QLatin1String(" "));
        setAttribute(QLatin1String("class"), value);
    }
}

/*!
    Removes the specified class \a name from the element.
*/
void QWebElement::removeClass(const QString &name)
{
    QStringList list = classes();
    if (list.contains(name)) {
        list.removeAll(name);
        QString value = list.join(QLatin1String(" "));
        setAttribute(QLatin1String("class"), value);
    }
}

/*!
    Adds the specified class \a name if it is not present,
    removes it if it is already present.
*/
void QWebElement::toggleClass(const QString &name)
{
    QStringList list = classes();
    if (list.contains(name))
        list.removeAll(name);
    else
        list.append(name);

    QString value = list.join(QLatin1String(" "));
    setAttribute(QLatin1String("class"), value);
}

/*!
    Appends \a element as the element's last child.

    If \a element is the child of another element, it is re-parented
    to this element. If \a element is a child of this element, then
    its position in the list of children is changed.

    Calling this function on a null element does nothing.

    \sa prependInside(), prependOutside(), appendOutside()
*/
void QWebElement::appendInside(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    ExceptionCode exception = 0;
    m_element->appendChild(element.m_element, exception);
}

/*!
    Appends the result of parsing \a markup as the element's last child.

    Calling this function on a null element does nothing.

    \sa prependInside(), prependOutside(), appendOutside()
*/
void QWebElement::appendInside(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    ExceptionCode exception = 0;
    m_element->appendChild(fragment, exception);
}

/*!
    Prepends \a element as the element's first child.

    If \a element is the child of another element, it is re-parented
    to this element. If \a element is a child of this element, then
    its position in the list of children is changed.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependOutside(), appendOutside()
*/
void QWebElement::prependInside(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    ExceptionCode exception = 0;

    if (m_element->hasChildNodes())
        m_element->insertBefore(element.m_element, m_element->firstChild(), exception);
    else
        m_element->appendChild(element.m_element, exception);
}

/*!
    Prepends the result of parsing \a markup as the element's first child.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependOutside(), appendOutside()
*/
void QWebElement::prependInside(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    ExceptionCode exception = 0;

    if (m_element->hasChildNodes())
        m_element->insertBefore(fragment, m_element->firstChild(), exception);
    else
        m_element->appendChild(fragment, exception);
}


/*!
    Inserts \a element before this element.

    If \a element is the child of another element, it is re-parented
    to the parent of this element.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependInside(), appendOutside()
*/
void QWebElement::prependOutside(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    if (!m_element->parent())
        return;

    ExceptionCode exception = 0;
    m_element->parent()->insertBefore(element.m_element, m_element, exception);
}

/*!
    Inserts the result of parsing \a markup before this element.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependInside(), appendOutside()
*/
void QWebElement::prependOutside(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->parent())
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    ExceptionCode exception = 0;
    m_element->parent()->insertBefore(fragment, m_element, exception);
}

/*!
    Inserts \a element after this element.

    If \a element is the child of another element, it is re-parented
    to the parent of this element.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependInside(), prependOutside()
*/
void QWebElement::appendOutside(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    if (!m_element->parent())
        return;

    ExceptionCode exception = 0;
    if (!m_element->nextSibling())
        m_element->parent()->appendChild(element.m_element, exception);
    else
        m_element->parent()->insertBefore(element.m_element, m_element->nextSibling(), exception);
}

/*!
    Inserts the result of parsing \a markup after this element.

    Calling this function on a null element does nothing.

    \sa appendInside(), prependInside(), prependOutside()
*/
void QWebElement::appendOutside(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->parent())
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    ExceptionCode exception = 0;
    if (!m_element->nextSibling())
        m_element->parent()->appendChild(fragment, exception);
    else
        m_element->parent()->insertBefore(fragment, m_element->nextSibling(), exception);
}

/*!
    Returns a clone of this element.

    The clone may be inserted at any point in the document.

    \sa appendInside(), prependInside(), prependOutside(), appendOutside()
*/
QWebElement QWebElement::clone() const
{
    if (!m_element)
        return QWebElement();

    return QWebElement(m_element->cloneElementWithChildren().get());
}

/*!
    Removes this element from the document and returns a reference
    to this.

    The element is still valid after removal, and can be inserted into
    other parts of the document.

    \sa removeChildren(), removeFromDocument()
*/
QWebElement &QWebElement::takeFromDocument()
{
    if (!m_element)
        return *this;

    ExceptionCode exception = 0;
    m_element->remove(exception);

    return *this;
}

/*!
    Removes this element from the document and makes this
    a null element.

    \sa removeChildren(), takeFromDocument()
*/
void QWebElement::removeFromDocument()
{
    if (!m_element)
        return;

    ExceptionCode exception = 0;
    m_element->remove(exception);
    m_element->deref();
    m_element = 0;
}

/*!
    Removes all children from this element.

    \sa removeFromDocument(), takeFromDocument()
*/
void QWebElement::removeChildren()
{
    if (!m_element)
        return;

    m_element->removeAllChildren();
}

static RefPtr<Node> findInsertionPoint(PassRefPtr<Node> root)
{
    RefPtr<Node> node = root;

    // Go as far down the tree as possible.
    while (node->hasChildNodes() && node->firstChild()->isElementNode())
        node = node->firstChild();

    // TODO: Implement SVG support
    if (node->isHTMLElement()) {
        HTMLElement* element = static_cast<HTMLElement*>(node.get());

        // The insert point could be a non-enclosable tag and it can thus
        // never have children, so go one up. Get the parent element, and not
        // note as a root note will always exist.
        if (element->endTagRequirement() == TagStatusForbidden)
            node = node->parentElement();
    }

    return node;
}

/*!
    Enclose the contents of this element in \a element as the child
    of the deepest descendant element within the structure of the
    first element provided.

    \sa encloseWith()
*/
void QWebElement::encloseContentsWith(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    RefPtr<Node> insertionPoint = findInsertionPoint(element.m_element);

    if (!insertionPoint)
        return;

    ExceptionCode exception = 0;

    // reparent children
    for (RefPtr<Node> child = m_element->firstChild(); child;) {
        RefPtr<Node> next = child->nextSibling();
        insertionPoint->appendChild(child, exception);
        child = next;
    }

    if (m_element->hasChildNodes())
        m_element->insertBefore(element.m_element, m_element->firstChild(), exception);
    else
        m_element->appendChild(element.m_element, exception);
}

/*!
    Enclose the contents of this element in the result of parsing
    \a markup as the child of the deepest descendant element within
    the structure of the first element provided.

    \sa encloseWith()
*/
void QWebElement::encloseContentsWith(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->parent())
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    if (!fragment || !fragment->firstChild())
        return;

    RefPtr<Node> insertionPoint = findInsertionPoint(fragment->firstChild());

    if (!insertionPoint)
        return;

    ExceptionCode exception = 0;

    // reparent children
    for (RefPtr<Node> child = m_element->firstChild(); child;) {
        RefPtr<Node> next = child->nextSibling();
        insertionPoint->appendChild(child, exception);
        child = next;
    }

    if (m_element->hasChildNodes())
        m_element->insertBefore(fragment, m_element->firstChild(), exception);
    else
        m_element->appendChild(fragment, exception);
}

/*!
    Enclose this element in \a element as the child of the deepest
    descendant element within the structure of the first element
    provided.

    \sa replace()
*/
void QWebElement::encloseWith(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    RefPtr<Node> insertionPoint = findInsertionPoint(element.m_element);

    if (!insertionPoint)
        return;

    // Keep reference to these two nodes before pulling out this element and
    // wrapping it in the fragment. The reason for doing it in this order is
    // that once the fragment has been added to the document it is empty, so
    // we no longer have access to the nodes it contained.
    Node* parentNode = m_element->parent();
    Node* siblingNode = m_element->nextSibling();

    ExceptionCode exception = 0;
    insertionPoint->appendChild(m_element, exception);

    if (!siblingNode)
        parentNode->appendChild(element.m_element, exception);
    else
        parentNode->insertBefore(element.m_element, siblingNode, exception);
}

/*!
    Enclose this element in the result of parsing \a markup,
    as the last child.

    \sa replace()
*/
void QWebElement::encloseWith(const QString &markup)
{
    if (!m_element)
        return;

    if (!m_element->parent())
        return;

    if (!m_element->isHTMLElement())
        return;

    HTMLElement* htmlElement = static_cast<HTMLElement*>(m_element);
    RefPtr<DocumentFragment> fragment = htmlElement->createContextualFragment(markup);

    if (!fragment || !fragment->firstChild())
        return;

    RefPtr<Node> insertionPoint = findInsertionPoint(fragment->firstChild());

    if (!insertionPoint)
        return;

    // Keep reference to these two nodes before pulling out this element and
    // wrapping it in the fragment. The reason for doing it in this order is
    // that once the fragment has been added to the document it is empty, so
    // we no longer have access to the nodes it contained.
    Node* parentNode = m_element->parent();
    Node* siblingNode = m_element->nextSibling();

    ExceptionCode exception = 0;
    insertionPoint->appendChild(m_element, exception);

    if (!siblingNode)
        parentNode->appendChild(fragment, exception);
    else
        parentNode->insertBefore(fragment, siblingNode, exception);
}

/*!
    Replaces this element with \a element.

    It is not possible to replace the <html>, <head>, or <body>
    elements using this method.

    \sa encloseWith()
*/
void QWebElement::replace(const QWebElement &element)
{
    if (!m_element || element.isNull())
        return;

    appendOutside(element);
    takeFromDocument();
}

/*!
    Replaces this element with the result of parsing \a markup.

    It is not possible to replace the <html>, <head>, or <body>
    elements using this method.

    \sa encloseWith()
*/
void QWebElement::replace(const QString &markup)
{
    if (!m_element)
        return;

    appendOutside(markup);
    takeFromDocument();
}

/*!
    \fn inline bool QWebElement::operator==(const QWebElement& o) const;

    Returns true if this element points to the same underlying DOM object than \a o; otherwise returns false.
*/

/*!
    \fn inline bool QWebElement::operator!=(const QWebElement& o) const;

    Returns true if this element points to a different underlying DOM object than \a o; otherwise returns false.
*/