blob: b3f3f1c7b8b81ff6df742bdccc212f12b48b65cc [file] [log] [blame]
/*
* 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) && element->itemProp()->length()) {
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)