HTMLPropertiesCollection.cpp   [plain text]


/*
 * Copyright (c) 2011 Motorola Mobility, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * Neither the name of Motorola Mobility, Inc. nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#if ENABLE(MICRODATA)

#include "HTMLPropertiesCollection.h"

#include "DOMSettableTokenList.h"
#include "DOMStringList.h"
#include "HTMLElement.h"
#include "HTMLNames.h"
#include "Node.h"
#include "StaticNodeList.h"

namespace WebCore {

using namespace HTMLNames;

PassOwnPtr<HTMLPropertiesCollection> HTMLPropertiesCollection::create(Node* itemNode)
{
    return adoptPtr(new HTMLPropertiesCollection(itemNode));
}

HTMLPropertiesCollection::HTMLPropertiesCollection(Node* itemNode)
    : HTMLCollection(itemNode, ItemProperties)
{
}

HTMLPropertiesCollection::~HTMLPropertiesCollection()
{
}

void HTMLPropertiesCollection::invalidateCacheIfNeeded() const
{
    uint64_t docversion = base()->document()->domTreeVersion();

    if (m_cache.version == docversion)
        return;

    m_cache.clear();
    m_cache.version = docversion;
}

void HTMLPropertiesCollection::updateRefElements() const
{
    if (m_cache.hasItemRefElements)
        return;

    Vector<Element*> itemRefElements;
    HTMLElement* baseElement = toHTMLElement(base());

    if (!baseElement->fastHasAttribute(itemrefAttr)) {
        itemRefElements.append(baseElement);
        m_cache.setItemRefElements(itemRefElements);
        return;
    }

    DOMSettableTokenList* itemRef = baseElement->itemRef();
    RefPtr<DOMSettableTokenList> processedItemRef = DOMSettableTokenList::create();
    Node* rootNode = baseElement->treeScope()->rootNode();

    for (Node* current = rootNode->firstChild(); current; current = current->traverseNextNode(rootNode)) {
        if (!current->isHTMLElement())
            continue;
        HTMLElement* element = toHTMLElement(current);

        if (element == baseElement) {
            itemRefElements.append(element);
            continue;
        }

        const AtomicString& id = element->getIdAttribute();
        if (!processedItemRef->tokens().contains(id) && itemRef->tokens().contains(id)) {
            processedItemRef->setValue(id);
            if (!element->isDescendantOf(baseElement))
                itemRefElements.append(element);
        }
    }

    m_cache.setItemRefElements(itemRefElements);
}

static Node* nextNodeWithProperty(Node* base, Node* node)
{
    // An Microdata item may contain properties which in turn are items themselves. Properties can
    // also themselves be groups of name-value pairs, by putting the itemscope attribute on the element
    // that declares the property. If the property has an itemscope attribute specified then we need
    // to traverse the next sibling.
    return node == base || (node->isHTMLElement() && !toHTMLElement(node)->fastHasAttribute(itemscopeAttr))
            ? node->traverseNextNode(base) : node->traverseNextSibling(base);
}

Element* HTMLPropertiesCollection::itemAfter(Element* base, Element* previous) const
{
    Node* current;
    current = previous ? nextNodeWithProperty(base, previous) : base;

    for (; current; current = nextNodeWithProperty(base, current)) {
        if (!current->isHTMLElement())
            continue;
        HTMLElement* element = toHTMLElement(current);
        if (element->fastHasAttribute(itempropAttr)) {
            return element;
        }
    }

    return 0;
}

unsigned HTMLPropertiesCollection::calcLength() const
{
    unsigned length = 0;
    updateRefElements();

    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
    for (unsigned i = 0; i < itemRefElements.size(); ++i) {
        for (Element* element = itemAfter(itemRefElements[i], 0); element; element = itemAfter(itemRefElements[i], element))
            ++length;
    }

    return length;
}

unsigned HTMLPropertiesCollection::length() const
{
    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
        return 0;

    invalidateCacheIfNeeded();

    if (!m_cache.hasLength)
        m_cache.updateLength(calcLength());

    return m_cache.length;
}

Element* HTMLPropertiesCollection::firstProperty() const
{
    Element* element = 0;
    m_cache.resetPosition();
    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
    for (unsigned i = 0; i < itemRefElements.size(); ++i) {
        element = itemAfter(itemRefElements[i], 0);
        if (element) {
            m_cache.itemRefElementPosition = i;
            break;
        }
    }

    return element;
}

Node* HTMLPropertiesCollection::item(unsigned index) const
{
    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
        return 0;

    invalidateCacheIfNeeded();
    if (m_cache.current && m_cache.position == index)
        return m_cache.current;

    if (m_cache.hasLength && m_cache.length <= index)
        return 0;

    updateRefElements();
    if (!m_cache.current || m_cache.position > index) {
        m_cache.current = firstProperty();
        if (!m_cache.current)
            return 0;
    }

    unsigned currentPosition = m_cache.position;
    Element* element = m_cache.current;
    unsigned itemRefElementPos = m_cache.itemRefElementPosition;
    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();

    bool found = (m_cache.position == index);

    for (unsigned i = itemRefElementPos; i < itemRefElements.size() && !found; ++i) {
        while (currentPosition < index) {
            element = itemAfter(itemRefElements[i], element);
            if (!element)
                break;
            currentPosition++;

            if (currentPosition == index) {
                found = true;
                itemRefElementPos = i;
                break;
            }
        }
    }

    m_cache.updateCurrentItem(element, index, itemRefElementPos);
    return m_cache.current;
}

void HTMLPropertiesCollection::findProperties(Element* base) const
{
    for (Element* element = itemAfter(base, 0); element; element = itemAfter(base, element)) {
        DOMSettableTokenList* itemProperty = element->itemProp();
        for (unsigned i = 0; i < itemProperty->length(); ++i)
            m_cache.updatePropertyCache(element, itemProperty->item(i));
    }
}

void HTMLPropertiesCollection::updateNameCache() const
{
    invalidateCacheIfNeeded();
    if (m_cache.hasNameCache)
        return;

    updateRefElements();

    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
    for (unsigned i = 0; i < itemRefElements.size(); ++i)
        findProperties(itemRefElements[i]);

    m_cache.hasNameCache = true;
}

PassRefPtr<DOMStringList> HTMLPropertiesCollection::names() const
{
    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
        return DOMStringList::create();

    updateNameCache();

    return m_cache.propertyNames;
}

PassRefPtr<NodeList> HTMLPropertiesCollection::namedItem(const String& name) const
{
    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
      return 0;

    Vector<RefPtr<Node> > namedItems;

    updateNameCache();

    Vector<Element*>* propertyResults = m_cache.propertyCache.get(AtomicString(name).impl());
    for (unsigned i = 0; propertyResults && i < propertyResults->size(); ++i)
        namedItems.append(propertyResults->at(i));

    // FIXME: HTML5 specifies that this should return PropertyNodeList.
    return namedItems.isEmpty() ? 0 : StaticNodeList::adopt(namedItems);
}

bool HTMLPropertiesCollection::hasNamedItem(const AtomicString& name) const
{
    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
        return false;

    updateNameCache();

    if (Vector<Element*>* propertyCache = m_cache.propertyCache.get(name.impl())) {
        if (!propertyCache->isEmpty())
            return true;
    }

    return false;
}

} // namespace WebCore

#endif // ENABLE(MICRODATA)