| /** |
| * This file is part of the DOM implementation for KDE. |
| * |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Peter Kelly (pmk@post.com) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2004 Apple Computer, Inc. |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| //#define EVENT_DEBUG |
| #include "dom/dom_exception.h" |
| #include "dom/dom_node.h" |
| #include "xml/dom_textimpl.h" |
| #include "xml/dom_docimpl.h" |
| #include "xml/dom2_eventsimpl.h" |
| #include "xml/dom_elementimpl.h" |
| |
| #include "khtml_part.h" |
| #include "khtmlview.h" |
| |
| #include "html/htmlparser.h" |
| |
| #include "rendering/render_canvas.h" |
| #include "css/css_valueimpl.h" |
| #include "css/cssproperties.h" |
| #include "css/cssvalues.h" |
| #include "css/css_stylesheetimpl.h" |
| #include "css/cssstyleselector.h" |
| #include "xml/dom_xmlimpl.h" |
| |
| #include <qtextstream.h> |
| #include <kdebug.h> |
| |
| using namespace DOM; |
| using namespace khtml; |
| |
| AttributeImpl* AttributeImpl::clone(bool) const |
| { |
| return new AttributeImpl(m_name, m_value); |
| } |
| |
| void AttributeImpl::allocateImpl(ElementImpl* e) { |
| m_impl = new AttrImpl(e, e->docPtr(), this); |
| } |
| |
| AttrImpl::AttrImpl(ElementImpl* element, DocumentPtr* docPtr, AttributeImpl* a) |
| : ContainerNodeImpl(docPtr), |
| m_element(element), |
| m_attribute(a) |
| { |
| assert(!m_attribute->m_impl); |
| m_attribute->m_impl = this; |
| m_attribute->ref(); |
| m_specified = true; |
| } |
| |
| AttrImpl::~AttrImpl() |
| { |
| assert(m_attribute->m_impl == this); |
| m_attribute->m_impl = 0; |
| m_attribute->deref(); |
| } |
| |
| DOMString AttrImpl::nodeName() const |
| { |
| if (m_element && m_element->getDocument()->isHTMLDocument()) |
| return name().upper(); // Have to uppercase attributes when returned in HTML (and not XML). |
| return name(); |
| } |
| |
| unsigned short AttrImpl::nodeType() const |
| { |
| return Node::ATTRIBUTE_NODE; |
| } |
| |
| const AtomicString& AttrImpl::prefix() const |
| { |
| return m_attribute->prefix(); |
| } |
| |
| void AttrImpl::setPrefix(const AtomicString &_prefix, int &exceptioncode ) |
| { |
| checkSetPrefix(_prefix, exceptioncode); |
| if (exceptioncode) |
| return; |
| |
| m_attribute->setPrefix(_prefix); |
| } |
| |
| DOMString AttrImpl::nodeValue() const |
| { |
| return value(); |
| } |
| |
| void AttrImpl::setValue( const DOMString &v, int &exceptioncode ) |
| { |
| exceptioncode = 0; |
| |
| // ### according to the DOM docs, we should create an unparsed Text child |
| // node here |
| // do not interprete entities in the string, its literal! |
| |
| // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly |
| if (isReadOnly()) { |
| exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; |
| return; |
| } |
| |
| // ### what to do on 0 ? |
| if (v.isNull()) { |
| exceptioncode = DOMException::DOMSTRING_SIZE_ERR; |
| return; |
| } |
| |
| m_attribute->setValue(v.implementation()); |
| if (m_element) |
| m_element->attributeChanged(m_attribute); |
| } |
| |
| void AttrImpl::setNodeValue( const DOMString &v, int &exceptioncode ) |
| { |
| exceptioncode = 0; |
| // NO_MODIFICATION_ALLOWED_ERR: taken care of by setValue() |
| setValue(v, exceptioncode); |
| } |
| |
| NodeImpl *AttrImpl::cloneNode ( bool /*deep*/) |
| { |
| return new AttrImpl(0, docPtr(), m_attribute->clone()); |
| } |
| |
| // DOM Section 1.1.1 |
| bool AttrImpl::childAllowed( NodeImpl *newChild ) |
| { |
| if(!newChild) |
| return false; |
| |
| return childTypeAllowed(newChild->nodeType()); |
| } |
| |
| bool AttrImpl::childTypeAllowed( unsigned short type ) |
| { |
| switch (type) { |
| case Node::TEXT_NODE: |
| case Node::ENTITY_REFERENCE_NODE: |
| return true; |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| DOMString AttrImpl::toString() const |
| { |
| DOMString result; |
| |
| result += nodeName(); |
| |
| // FIXME: substitute entities for any instances of " or ' -- |
| // maybe easier to just use text value and ignore existing |
| // entity refs? |
| |
| if (firstChild() != NULL) { |
| result += "=\""; |
| |
| for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { |
| result += child->toString(); |
| } |
| |
| result += "\""; |
| } |
| |
| return result; |
| } |
| |
| DOMString AttrImpl::name() const |
| { |
| return m_attribute->name().toString(); |
| } |
| |
| DOMString AttrImpl::value() const |
| { |
| return m_attribute->value(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| ElementImpl::ElementImpl(const QualifiedName& qName, DocumentPtr *doc) |
| : ContainerNodeImpl(doc), m_tagName(qName) |
| { |
| namedAttrMap = 0; |
| } |
| |
| ElementImpl::~ElementImpl() |
| { |
| if (namedAttrMap) { |
| namedAttrMap->detachFromElement(); |
| namedAttrMap->deref(); |
| } |
| } |
| |
| NodeImpl *ElementImpl::cloneNode(bool deep) |
| { |
| int exceptionCode = 0; |
| ElementImpl *clone = getDocument()->createElementNS(namespaceURI(), nodeName(), exceptionCode); |
| assert(!exceptionCode); |
| |
| // clone attributes |
| if (namedAttrMap) |
| *clone->attributes() = *namedAttrMap; |
| |
| if (deep) |
| cloneChildNodes(clone); |
| |
| return clone; |
| } |
| |
| void ElementImpl::removeAttribute(const QualifiedName& name, int &exceptioncode) |
| { |
| if (namedAttrMap) { |
| namedAttrMap->removeNamedItem(name, exceptioncode); |
| if (exceptioncode == DOMException::NOT_FOUND_ERR) { |
| exceptioncode = 0; |
| } |
| } |
| } |
| |
| void ElementImpl::setAttribute(const QualifiedName& name, const DOMString &value) |
| { |
| int exceptioncode = 0; |
| setAttribute(name, value.implementation(), exceptioncode); |
| } |
| |
| // Virtual function, defined in base class. |
| NamedAttrMapImpl *ElementImpl::attributes() const |
| { |
| return attributes(false); |
| } |
| |
| NamedAttrMapImpl* ElementImpl::attributes(bool readonly) const |
| { |
| updateStyleAttributeIfNeeded(); |
| if (!readonly && !namedAttrMap) |
| createAttributeMap(); |
| return namedAttrMap; |
| } |
| |
| unsigned short ElementImpl::nodeType() const |
| { |
| return Node::ELEMENT_NODE; |
| } |
| |
| const AtomicStringList* ElementImpl::getClassList() const |
| { |
| return 0; |
| } |
| |
| const AtomicString& ElementImpl::getIDAttribute() const |
| { |
| return namedAttrMap ? namedAttrMap->id() : nullAtom; |
| } |
| |
| const AtomicString& ElementImpl::getAttribute(const QualifiedName& name) const |
| { |
| if (name == HTMLAttributes::style()) |
| updateStyleAttributeIfNeeded(); |
| |
| if (namedAttrMap) { |
| AttributeImpl* a = namedAttrMap->getAttributeItem(name); |
| if (a) return a->value(); |
| } |
| return nullAtom; |
| } |
| |
| void ElementImpl::scrollIntoView(bool alignToTop) |
| { |
| KHTMLView *v = getDocument()->view(); |
| QRect bounds = this->getRect(); |
| int x, y, xe, ye; |
| x = bounds.left(); |
| y = bounds.top(); |
| xe = bounds.right(); |
| ye = bounds.bottom(); |
| |
| if (alignToTop) |
| v->setContentsPos(x, y); |
| else |
| v->ensureVisible(x, y, xe-x, ye-y); |
| } |
| |
| const AtomicString& ElementImpl::getAttributeNS(const DOMString &namespaceURI, |
| const DOMString &localName) const |
| { |
| DOMString ln(localName); |
| if (getDocument()->isHTMLDocument()) |
| ln = localName.lower(); |
| QualifiedName name(nullAtom, ln.implementation(), namespaceURI.implementation()); |
| return getAttribute(name); |
| } |
| |
| void ElementImpl::setAttribute(const QualifiedName& name, DOMStringImpl* value, int &exceptioncode ) |
| { |
| if (inDocument()) |
| getDocument()->incDOMTreeVersion(); |
| |
| // allocate attributemap if necessary |
| AttributeImpl* old = attributes(false)->getAttributeItem(name); |
| |
| // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly |
| if (namedAttrMap->isReadOnly()) { |
| exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; |
| return; |
| } |
| |
| if (name == HTMLAttributes::idAttr()) |
| updateId(old ? old->value() : nullAtom, value); |
| |
| if (old && !value) |
| namedAttrMap->removeAttribute(name); |
| else if (!old && value) |
| namedAttrMap->addAttribute(createAttribute(name, value)); |
| else if (old && value) { |
| old->setValue(value); |
| attributeChanged(old); |
| } |
| } |
| |
| AttributeImpl* ElementImpl::createAttribute(const QualifiedName& name, DOMStringImpl* value) |
| { |
| return new AttributeImpl(name, value); |
| } |
| |
| void ElementImpl::setAttributeMap( NamedAttrMapImpl* list ) |
| { |
| if (inDocument()) |
| getDocument()->incDOMTreeVersion(); |
| |
| // If setting the whole map changes the id attribute, we need to |
| // call updateId. |
| |
| AttributeImpl *oldId = namedAttrMap ? namedAttrMap->getAttributeItem(HTMLAttributes::idAttr()) : 0; |
| AttributeImpl *newId = list ? list->getAttributeItem(HTMLAttributes::idAttr()) : 0; |
| |
| if (oldId || newId) { |
| updateId(oldId ? oldId->value() : nullAtom, newId ? newId->value() : nullAtom); |
| } |
| |
| if(namedAttrMap) |
| namedAttrMap->deref(); |
| |
| namedAttrMap = list; |
| |
| if(namedAttrMap) { |
| namedAttrMap->ref(); |
| namedAttrMap->element = this; |
| unsigned int len = namedAttrMap->length(); |
| for(unsigned int i = 0; i < len; i++) |
| attributeChanged(namedAttrMap->attrs[i]); |
| } |
| } |
| |
| bool ElementImpl::hasAttributes() const |
| { |
| updateStyleAttributeIfNeeded(); |
| return namedAttrMap && namedAttrMap->length() > 0; |
| } |
| |
| DOMString ElementImpl::nodeName() const |
| { |
| return m_tagName.toString(); |
| } |
| |
| void ElementImpl::setPrefix(const AtomicString &_prefix, int &exceptioncode) |
| { |
| checkSetPrefix(_prefix, exceptioncode); |
| if (exceptioncode) |
| return; |
| |
| m_tagName.setPrefix(_prefix); |
| } |
| |
| void ElementImpl::createAttributeMap() const |
| { |
| namedAttrMap = new NamedAttrMapImpl(const_cast<ElementImpl*>(this)); |
| namedAttrMap->ref(); |
| } |
| |
| bool ElementImpl::isURLAttribute(AttributeImpl *attr) const |
| { |
| return false; |
| } |
| |
| RenderStyle *ElementImpl::styleForRenderer(RenderObject *parentRenderer) |
| { |
| return getDocument()->styleSelector()->styleForElement(this); |
| } |
| |
| RenderObject *ElementImpl::createRenderer(RenderArena *arena, RenderStyle *style) |
| { |
| if (getDocument()->documentElement() == this && style->display() == NONE) { |
| // Ignore display: none on root elements. Force a display of block in that case. |
| RenderBlock* result = new (arena) RenderBlock(this); |
| if (result) result->setStyle(style); |
| return result; |
| } |
| return RenderObject::createObject(this, style); |
| } |
| |
| |
| void ElementImpl::insertedIntoDocument() |
| { |
| // need to do superclass processing first so inDocument() is true |
| // by the time we reach updateId |
| ContainerNodeImpl::insertedIntoDocument(); |
| |
| if (hasID()) { |
| NamedAttrMapImpl *attrs = attributes(true); |
| if (attrs) { |
| AttributeImpl *idAttr = attrs->getAttributeItem(HTMLAttributes::idAttr()); |
| if (idAttr && !idAttr->isNull()) { |
| updateId(nullAtom, idAttr->value()); |
| } |
| } |
| } |
| } |
| |
| void ElementImpl::removedFromDocument() |
| { |
| if (hasID()) { |
| NamedAttrMapImpl *attrs = attributes(true); |
| if (attrs) { |
| AttributeImpl *idAttr = attrs->getAttributeItem(HTMLAttributes::idAttr()); |
| if (idAttr && !idAttr->isNull()) { |
| updateId(idAttr->value(), nullAtom); |
| } |
| } |
| } |
| |
| ContainerNodeImpl::removedFromDocument(); |
| } |
| |
| void ElementImpl::attach() |
| { |
| #if SPEED_DEBUG < 1 |
| createRendererIfNeeded(); |
| #endif |
| ContainerNodeImpl::attach(); |
| } |
| |
| void ElementImpl::recalcStyle( StyleChange change ) |
| { |
| // ### should go away and be done in renderobject |
| RenderStyle* _style = m_render ? m_render->style() : 0; |
| bool hasParentRenderer = parent() ? parent()->renderer() : false; |
| |
| #if 0 |
| const char* debug; |
| switch(change) { |
| case NoChange: debug = "NoChange"; |
| break; |
| case NoInherit: debug= "NoInherit"; |
| break; |
| case Inherit: debug = "Inherit"; |
| break; |
| case Force: debug = "Force"; |
| break; |
| } |
| qDebug("recalcStyle(%d: %s)[%p: %s]", change, debug, this, tagName().string().latin1()); |
| #endif |
| if ( hasParentRenderer && (change >= Inherit || changed()) ) { |
| RenderStyle *newStyle = getDocument()->styleSelector()->styleForElement(this); |
| newStyle->ref(); |
| StyleChange ch = diff( _style, newStyle ); |
| if (ch == Detach) { |
| if (attached()) detach(); |
| // ### Suboptimal. Style gets calculated again. |
| attach(); |
| // attach recalulates the style for all children. No need to do it twice. |
| setChanged( false ); |
| setHasChangedChild( false ); |
| newStyle->deref(getDocument()->renderArena()); |
| return; |
| } |
| else if (ch != NoChange) { |
| if( m_render && newStyle ) { |
| //qDebug("--> setting style on render element bgcolor=%s", newStyle->backgroundColor().name().latin1()); |
| m_render->setStyle(newStyle); |
| } |
| } |
| else if (changed() && m_render && newStyle && (getDocument()->usesSiblingRules() || getDocument()->usesDescendantRules())) { |
| // Although no change occurred, we use the new style so that the cousin style sharing code won't get |
| // fooled into believing this style is the same. This is only necessary if the document actually uses |
| // sibling/descendant rules, since otherwise it isn't possible for ancestor styles to affect sharing of |
| // descendants. |
| m_render->setStyleInternal(newStyle); |
| } |
| |
| newStyle->deref(getDocument()->renderArena()); |
| |
| if ( change != Force) { |
| if (getDocument()->usesDescendantRules()) |
| change = Force; |
| else |
| change = ch; |
| } |
| } |
| |
| NodeImpl *n; |
| for (n = _first; n; n = n->nextSibling()) { |
| //qDebug(" (%p) calling recalcStyle on child %s/%p, change=%d", this, n, n->isElementNode() ? ((ElementImpl *)n)->tagName().string().latin1() : n->isTextNode() ? "text" : "unknown", change ); |
| if ( change >= Inherit || n->isTextNode() || |
| n->hasChangedChild() || n->changed() ) |
| n->recalcStyle( change ); |
| } |
| |
| setChanged( false ); |
| setHasChangedChild( false ); |
| } |
| |
| bool ElementImpl::childTypeAllowed( unsigned short type ) |
| { |
| switch (type) { |
| case Node::ELEMENT_NODE: |
| case Node::TEXT_NODE: |
| case Node::COMMENT_NODE: |
| case Node::PROCESSING_INSTRUCTION_NODE: |
| case Node::CDATA_SECTION_NODE: |
| case Node::ENTITY_REFERENCE_NODE: |
| return true; |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| void ElementImpl::dispatchAttrRemovalEvent(AttributeImpl *attr) |
| { |
| if (!getDocument()->hasListenerType(DocumentImpl::DOMATTRMODIFIED_LISTENER)) |
| return; |
| //int exceptioncode = 0; |
| // dispatchEvent(new MutationEventImpl(EventImpl::DOMATTRMODIFIED_EVENT,true,false,attr,attr->value(), |
| // attr->value(), getDocument()->attrName(attr->id()),MutationEvent::REMOVAL),exceptioncode); |
| } |
| |
| void ElementImpl::dispatchAttrAdditionEvent(AttributeImpl *attr) |
| { |
| if (!getDocument()->hasListenerType(DocumentImpl::DOMATTRMODIFIED_LISTENER)) |
| return; |
| // int exceptioncode = 0; |
| // dispatchEvent(new MutationEventImpl(EventImpl::DOMATTRMODIFIED_EVENT,true,false,attr,attr->value(), |
| // attr->value(),getDocument()->attrName(attr->id()),MutationEvent::ADDITION),exceptioncode); |
| } |
| |
| DOMString ElementImpl::openTagStartToString() const |
| { |
| DOMString result = DOMString("<") + nodeName(); |
| |
| NamedAttrMapImpl *attrMap = attributes(true); |
| |
| if (attrMap) { |
| unsigned long numAttrs = attrMap->length(); |
| for (unsigned long i = 0; i < numAttrs; i++) { |
| result += " "; |
| |
| AttributeImpl *attribute = attrMap->attributeItem(i); |
| result += attribute->name().toString(); |
| if (!attribute->value().isNull()) { |
| result += "=\""; |
| // FIXME: substitute entities for any instances of " or ' |
| result += attribute->value(); |
| result += "\""; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| DOMString ElementImpl::toString() const |
| { |
| DOMString result = openTagStartToString(); |
| |
| if (hasChildNodes()) { |
| result += ">"; |
| |
| for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { |
| result += child->toString(); |
| } |
| |
| result += "</"; |
| result += nodeName(); |
| result += ">"; |
| } else { |
| result += " />"; |
| } |
| |
| return result; |
| } |
| |
| void ElementImpl::updateId(const AtomicString& oldId, const AtomicString& newId) |
| { |
| if (!inDocument()) |
| return; |
| |
| if (oldId == newId) |
| return; |
| |
| DocumentImpl* doc = getDocument(); |
| if (!oldId.isEmpty()) |
| doc->removeElementById(oldId, this); |
| if (!newId.isEmpty()) |
| doc->addElementById(newId, this); |
| } |
| |
| #ifndef NDEBUG |
| void ElementImpl::dump(QTextStream *stream, QString ind) const |
| { |
| updateStyleAttributeIfNeeded(); |
| if (namedAttrMap) { |
| for (uint i = 0; i < namedAttrMap->length(); i++) { |
| AttributeImpl *attr = namedAttrMap->attributeItem(i); |
| *stream << " " << attr->name().localName().string().ascii() |
| << "=\"" << attr->value().string().ascii() << "\""; |
| } |
| } |
| |
| ContainerNodeImpl::dump(stream,ind); |
| } |
| #endif |
| |
| #ifndef NDEBUG |
| void ElementImpl::formatForDebugger(char *buffer, unsigned length) const |
| { |
| DOMString result; |
| DOMString s; |
| |
| s = nodeName(); |
| if (s.length() > 0) { |
| result += s; |
| } |
| |
| s = getAttribute(HTMLAttributes::idAttr()); |
| if (s.length() > 0) { |
| if (result.length() > 0) |
| result += "; "; |
| result += "id="; |
| result += s; |
| } |
| |
| s = getAttribute(HTMLAttributes::classAttr()); |
| if (s.length() > 0) { |
| if (result.length() > 0) |
| result += "; "; |
| result += "class="; |
| result += s; |
| } |
| |
| strncpy(buffer, result.string().latin1(), length - 1); |
| } |
| #endif |
| |
| SharedPtr<AttrImpl> ElementImpl::setAttributeNode(AttrImpl *attr, int &exception) |
| { |
| return static_pointer_cast<AttrImpl>(attributes(false)->setNamedItem(attr, exception)); |
| } |
| |
| SharedPtr<AttrImpl> ElementImpl::removeAttributeNode(AttrImpl *attr, int &exception) |
| { |
| if (!attr || attr->ownerElement() != this) { |
| exception = DOMException::NOT_FOUND_ERR; |
| return SharedPtr<AttrImpl>(); |
| } |
| if (getDocument() != attr->getDocument()) { |
| exception = DOMException::WRONG_DOCUMENT_ERR; |
| return SharedPtr<AttrImpl>(); |
| } |
| |
| NamedAttrMapImpl *attrs = attributes(true); |
| if (!attrs) |
| return SharedPtr<AttrImpl>(); |
| |
| return static_pointer_cast<AttrImpl>(attrs->removeNamedItem(attr->m_attribute->name(), exception)); |
| } |
| |
| void ElementImpl::setAttributeNS(const DOMString &namespaceURI, const DOMString &qualifiedName, const DOMString &value, int &exception) |
| { |
| DOMString localName = qualifiedName; |
| DOMString prefix; |
| int colonpos; |
| if ((colonpos = qualifiedName.find(':')) >= 0) { |
| prefix = qualifiedName.copy(); |
| localName = qualifiedName.copy(); |
| prefix.truncate(colonpos); |
| localName.remove(0, colonpos+1); |
| } |
| |
| if (!DocumentImpl::isValidName(localName)) { |
| exception = DOMException::INVALID_CHARACTER_ERR; |
| return; |
| } |
| |
| if (getDocument()->isHTMLDocument()) |
| localName = localName.lower(); |
| |
| setAttribute(QualifiedName(prefix.implementation(), localName.implementation(), |
| namespaceURI.implementation()), value.implementation(), exception); |
| } |
| |
| void ElementImpl::removeAttributeNS(const DOMString &namespaceURI, const DOMString &localName, int &exception) |
| { |
| DOMString ln(localName); |
| if (getDocument() && getDocument()->isHTMLDocument()) |
| ln = localName.lower(); |
| removeAttribute(QualifiedName(nullAtom, ln.implementation(), namespaceURI.implementation()), exception); |
| } |
| |
| AttrImpl *ElementImpl::getAttributeNodeNS(const DOMString &namespaceURI, const DOMString &localName) |
| { |
| NamedAttrMapImpl *attrs = attributes(true); |
| if (!attrs) |
| return 0; |
| DOMString ln(localName); |
| if (getDocument() && getDocument()->isHTMLDocument()) |
| ln = localName.lower(); |
| return attrs->getNamedItem(QualifiedName(nullAtom, localName.implementation(), namespaceURI.implementation())); |
| } |
| |
| bool ElementImpl::hasAttributeNS(const DOMString &namespaceURI, const DOMString &localName) const |
| { |
| NamedAttrMapImpl *attrs = attributes(true); |
| if (!attrs) |
| return false; |
| DOMString ln(localName); |
| if (getDocument() && getDocument()->isHTMLDocument()) |
| ln = localName.lower(); |
| return attrs->getAttributeItem(QualifiedName(nullAtom, localName.implementation(), |
| namespaceURI.implementation())); |
| } |
| |
| CSSStyleDeclarationImpl *ElementImpl::style() |
| { |
| return 0; |
| } |
| |
| // ------------------------------------------------------------------------- |
| |
| /* |
| XMLElementImpl::XMLElementImpl(DocumentPtr *doc, DOMStringImpl *_qualifiedName, DOMStringImpl *_namespaceURI) |
| : ElementImpl(doc) |
| { |
| int colonpos = -1; |
| for (uint i = 0; i < _qualifiedName->l; ++i) |
| if (_qualifiedName->s[i] == ':') { |
| colonpos = i; |
| break; |
| } |
| |
| if (colonpos >= 0) { |
| // we have a prefix |
| DOMStringImpl* localName = _qualifiedName->copy(); |
| localName->ref(); |
| localName->remove(0,colonpos+1); |
| m_id = doc->document()->tagId(_namespaceURI, localName, false); |
| localName->deref(); |
| |
| // FIXME: This is a temporary situation for stage one of the QualifiedName patch. This is also a really stupid way to |
| // have to handle the prefix. Way too much copying. We should make sure the XML tokenizer has done the split |
| // already into prefix and localName. |
| DOMStringImpl* prefix = _qualifiedName->copy(); |
| prefix->ref(); |
| prefix->truncate(colonpos); |
| m_tagName = QualifiedName(prefix, nullAtom, nullAtom); |
| prefix->deref(); |
| } |
| else |
| // no prefix |
| m_id = doc->document()->tagId(_namespaceURI, _qualifiedName, false); |
| } |
| |
| */ |
| |
| // ------------------------------------------------------------------------- |
| |
| NamedAttrMapImpl::NamedAttrMapImpl(ElementImpl *e) |
| : element(e) |
| , attrs(0) |
| , len(0) |
| { |
| } |
| |
| NamedAttrMapImpl::~NamedAttrMapImpl() |
| { |
| NamedAttrMapImpl::clearAttributes(); // virtual method, so qualify just to be explicit |
| } |
| |
| bool NamedAttrMapImpl::isMappedAttributeMap() const |
| { |
| return false; |
| } |
| |
| AttrImpl *NamedAttrMapImpl::getNamedItem(const QualifiedName& name) const |
| { |
| AttributeImpl* a = getAttributeItem(name); |
| if (!a) return 0; |
| |
| if (!a->attrImpl()) |
| a->allocateImpl(element); |
| |
| return a->attrImpl(); |
| } |
| |
| SharedPtr<NodeImpl> NamedAttrMapImpl::setNamedItem ( NodeImpl* arg, int &exceptioncode ) |
| { |
| if (!element) { |
| exceptioncode = DOMException::NOT_FOUND_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| // NO_MODIFICATION_ALLOWED_ERR: Raised if this map is readonly. |
| if (isReadOnly()) { |
| exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| // WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map. |
| if (arg->getDocument() != element->getDocument()) { |
| exceptioncode = DOMException::WRONG_DOCUMENT_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| // Not mentioned in spec: throw a HIERARCHY_REQUEST_ERROR if the user passes in a non-attribute node |
| if (!arg->isAttributeNode()) { |
| exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| AttrImpl *attr = static_cast<AttrImpl*>(arg); |
| |
| AttributeImpl* a = attr->attrImpl(); |
| AttributeImpl* old = getAttributeItem(a->name()); |
| if (old == a) |
| return SharedPtr<NodeImpl>(arg); // we know about it already |
| |
| // INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object. |
| // The DOM user must explicitly clone Attr nodes to re-use them in other elements. |
| if (attr->ownerElement()) { |
| exceptioncode = DOMException::INUSE_ATTRIBUTE_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| if (a->name() == HTMLAttributes::idAttr()) |
| element->updateId(old ? old->value() : nullAtom, a->value()); |
| |
| // ### slightly inefficient - resizes attribute array twice. |
| SharedPtr<NodeImpl> r; |
| if (old) { |
| if (!old->attrImpl()) |
| old->allocateImpl(element); |
| r.reset(old->m_impl); |
| removeAttribute(a->name()); |
| } |
| |
| addAttribute(a); |
| return r; |
| } |
| |
| // The DOM2 spec doesn't say that removeAttribute[NS] throws NOT_FOUND_ERR |
| // if the attribute is not found, but at this level we have to throw NOT_FOUND_ERR |
| // because of removeNamedItem, removeNamedItemNS, and removeAttributeNode. |
| SharedPtr<NodeImpl> NamedAttrMapImpl::removeNamedItem(const QualifiedName& name, int &exceptioncode) |
| { |
| // ### should this really be raised when the attribute to remove isn't there at all? |
| // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly |
| if (isReadOnly()) { |
| exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| AttributeImpl* a = getAttributeItem(name); |
| if (!a) { |
| exceptioncode = DOMException::NOT_FOUND_ERR; |
| return SharedPtr<NodeImpl>(); |
| } |
| |
| if (!a->attrImpl()) a->allocateImpl(element); |
| SharedPtr<NodeImpl> r(a->attrImpl()); |
| |
| if (name == HTMLAttributes::idAttr()) |
| element->updateId(a->value(), nullAtom); |
| |
| removeAttribute(name); |
| return r; |
| } |
| |
| AttrImpl *NamedAttrMapImpl::item ( unsigned long index ) const |
| { |
| if (index >= len) |
| return 0; |
| |
| if (!attrs[index]->attrImpl()) |
| attrs[index]->allocateImpl(element); |
| |
| return attrs[index]->attrImpl(); |
| } |
| |
| AttributeImpl* NamedAttrMapImpl::getAttributeItem(const QualifiedName& name) const |
| { |
| for (unsigned long i = 0; i < len; ++i) { |
| if (attrs[i]->name().matches(name)) |
| return attrs[i]; |
| } |
| return 0; |
| } |
| |
| void NamedAttrMapImpl::clearAttributes() |
| { |
| if (attrs) { |
| uint i; |
| for (i = 0; i < len; i++) { |
| if (attrs[i]->m_impl) |
| attrs[i]->m_impl->m_element = 0; |
| attrs[i]->deref(); |
| } |
| main_thread_free(attrs); |
| attrs = 0; |
| } |
| len = 0; |
| } |
| |
| void NamedAttrMapImpl::detachFromElement() |
| { |
| // we allow a NamedAttrMapImpl w/o an element in case someone still has a reference |
| // to if after the element gets deleted - but the map is now invalid |
| element = 0; |
| clearAttributes(); |
| } |
| |
| NamedAttrMapImpl& NamedAttrMapImpl::operator=(const NamedAttrMapImpl& other) |
| { |
| // clone all attributes in the other map, but attach to our element |
| if (!element) return *this; |
| |
| // If assigning the map changes the id attribute, we need to call |
| // updateId. |
| |
| AttributeImpl *oldId = getAttributeItem(HTMLAttributes::idAttr()); |
| AttributeImpl *newId = other.getAttributeItem(HTMLAttributes::idAttr()); |
| |
| if (oldId || newId) { |
| element->updateId(oldId ? oldId->value() : nullAtom, newId ? newId->value() : nullAtom); |
| } |
| |
| clearAttributes(); |
| len = other.len; |
| attrs = static_cast<AttributeImpl **>(main_thread_malloc(len * sizeof(AttributeImpl *))); |
| |
| // first initialize attrs vector, then call attributeChanged on it |
| // this allows attributeChanged to use getAttribute |
| for (uint i = 0; i < len; i++) { |
| attrs[i] = other.attrs[i]->clone(); |
| attrs[i]->ref(); |
| } |
| |
| // FIXME: This is wasteful. The class list could be preserved on a copy, and we |
| // wouldn't have to waste time reparsing the attribute. |
| // The derived class, HTMLNamedAttrMapImpl, which manages a parsed class list for the CLASS attribute, |
| // will update its member variable when parse attribute is called. |
| for(uint i = 0; i < len; i++) |
| element->attributeChanged(attrs[i], true); |
| |
| return *this; |
| } |
| |
| void NamedAttrMapImpl::addAttribute(AttributeImpl *attr) |
| { |
| // Add the attribute to the list |
| AttributeImpl **newAttrs = static_cast<AttributeImpl **>(main_thread_malloc((len + 1) * sizeof(AttributeImpl *))); |
| if (attrs) { |
| for (uint i = 0; i < len; i++) |
| newAttrs[i] = attrs[i]; |
| main_thread_free(attrs); |
| } |
| attrs = newAttrs; |
| attrs[len++] = attr; |
| attr->ref(); |
| |
| AttrImpl * const attrImpl = attr->m_impl; |
| if (attrImpl) |
| attrImpl->m_element = element; |
| |
| // Notify the element that the attribute has been added, and dispatch appropriate mutation events |
| // Note that element may be null here if we are called from insertAttr() during parsing |
| if (element) { |
| element->attributeChanged(attr); |
| element->dispatchAttrAdditionEvent(attr); |
| element->dispatchSubtreeModifiedEvent(false); |
| } |
| } |
| |
| void NamedAttrMapImpl::removeAttribute(const QualifiedName& name) |
| { |
| unsigned long index = len+1; |
| for (unsigned long i = 0; i < len; ++i) |
| if (attrs[i]->name().matches(name)) { |
| index = i; |
| break; |
| } |
| |
| if (index >= len) return; |
| |
| // Remove the attribute from the list |
| AttributeImpl* attr = attrs[index]; |
| if (attrs[index]->m_impl) |
| attrs[index]->m_impl->m_element = 0; |
| if (len == 1) { |
| main_thread_free(attrs); |
| attrs = 0; |
| len = 0; |
| } |
| else { |
| AttributeImpl **newAttrs = static_cast<AttributeImpl **>(main_thread_malloc((len - 1) * sizeof(AttributeImpl *))); |
| uint i; |
| for (i = 0; i < uint(index); i++) |
| newAttrs[i] = attrs[i]; |
| len--; |
| for (; i < len; i++) |
| newAttrs[i] = attrs[i+1]; |
| main_thread_free(attrs); |
| attrs = newAttrs; |
| } |
| |
| // Notify the element that the attribute has been removed |
| // dispatch appropriate mutation events |
| if (element && !attr->m_value.isNull()) { |
| AtomicString value = attr->m_value; |
| attr->m_value = nullAtom; |
| element->attributeChanged(attr); |
| attr->m_value = value; |
| } |
| if (element) { |
| element->dispatchAttrRemovalEvent(attr); |
| element->dispatchSubtreeModifiedEvent(false); |
| } |
| attr->deref(); |
| } |
| |
| // ------------------------------- Styled Element and Mapped Attribute Implementation |
| |
| CSSMappedAttributeDeclarationImpl::~CSSMappedAttributeDeclarationImpl() { |
| if (m_entryType != ePersistent) |
| StyledElementImpl::removeMappedAttributeDecl(m_entryType, m_attrName, m_attrValue); |
| } |
| |
| QPtrDict<QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> > >* StyledElementImpl::m_mappedAttributeDecls = 0; |
| |
| CSSMappedAttributeDeclarationImpl* StyledElementImpl::getMappedAttributeDecl(MappedAttributeEntry entryType, AttributeImpl* attr) |
| { |
| if (!m_mappedAttributeDecls) |
| return 0; |
| |
| QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> >* attrNameDict = m_mappedAttributeDecls->find((void*)entryType); |
| if (attrNameDict) { |
| QPtrDict<CSSMappedAttributeDeclarationImpl>* attrValueDict = |
| attrNameDict->find((void*)attr->name().localName().implementation()); |
| if (attrValueDict) |
| return attrValueDict->find(attr->value().implementation()); |
| } |
| return 0; |
| } |
| |
| void StyledElementImpl::setMappedAttributeDecl(MappedAttributeEntry entryType, AttributeImpl* attr, CSSMappedAttributeDeclarationImpl* decl) |
| { |
| if (!m_mappedAttributeDecls) |
| m_mappedAttributeDecls = new QPtrDict<QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> > >; |
| |
| QPtrDict<CSSMappedAttributeDeclarationImpl>* attrValueDict = 0; |
| QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> >* attrNameDict = m_mappedAttributeDecls->find((void*)entryType); |
| if (!attrNameDict) { |
| attrNameDict = new QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> >; |
| attrNameDict->setAutoDelete(true); |
| m_mappedAttributeDecls->insert((void*)entryType, attrNameDict); |
| } |
| else |
| attrValueDict = attrNameDict->find((void*)attr->name().localName().implementation()); |
| if (!attrValueDict) { |
| attrValueDict = new QPtrDict<CSSMappedAttributeDeclarationImpl>; |
| if (entryType == ePersistent) |
| attrValueDict->setAutoDelete(true); |
| attrNameDict->insert((void*)attr->name().localName().implementation(), attrValueDict); |
| } |
| attrValueDict->replace(attr->value().implementation(), decl); |
| } |
| |
| void StyledElementImpl::removeMappedAttributeDecl(MappedAttributeEntry entryType, |
| const QualifiedName& attrName, const AtomicString& attrValue) |
| { |
| if (!m_mappedAttributeDecls) |
| return; |
| |
| QPtrDict<QPtrDict<CSSMappedAttributeDeclarationImpl> >* attrNameDict = m_mappedAttributeDecls->find((void*)entryType); |
| if (!attrNameDict) |
| return; |
| QPtrDict<CSSMappedAttributeDeclarationImpl>* attrValueDict = attrNameDict->find((void*)attrName.localName().implementation()); |
| if (!attrValueDict) |
| return; |
| attrValueDict->remove(attrValue.implementation()); |
| } |
| |
| void StyledElementImpl::invalidateStyleAttribute() |
| { |
| m_isStyleAttributeValid = false; |
| } |
| |
| void StyledElementImpl::updateStyleAttributeIfNeeded() const |
| { |
| if (!m_isStyleAttributeValid) { |
| m_isStyleAttributeValid = true; |
| m_synchronizingStyleAttribute = true; |
| if (m_inlineStyleDecl) |
| const_cast<StyledElementImpl*>(this)->setAttribute(HTMLAttributes::style(), m_inlineStyleDecl->cssText()); |
| m_synchronizingStyleAttribute = false; |
| } |
| } |
| |
| MappedAttributeImpl::~MappedAttributeImpl() |
| { |
| if (m_styleDecl) |
| m_styleDecl->deref(); |
| } |
| |
| AttributeImpl* MappedAttributeImpl::clone(bool preserveDecl) const |
| { |
| return new MappedAttributeImpl(m_name, m_value, preserveDecl ? m_styleDecl : 0); |
| } |
| |
| NamedMappedAttrMapImpl::NamedMappedAttrMapImpl(ElementImpl *e) |
| :NamedAttrMapImpl(e), m_mappedAttributeCount(0) |
| {} |
| |
| void NamedMappedAttrMapImpl::clearAttributes() |
| { |
| m_classList.clear(); |
| m_mappedAttributeCount = 0; |
| NamedAttrMapImpl::clearAttributes(); |
| } |
| |
| bool NamedMappedAttrMapImpl::isMappedAttributeMap() const |
| { |
| return true; |
| } |
| |
| int NamedMappedAttrMapImpl::declCount() const |
| { |
| int result = 0; |
| for (uint i = 0; i < length(); i++) { |
| MappedAttributeImpl* attr = attributeItem(i); |
| if (attr->decl()) |
| result++; |
| } |
| return result; |
| } |
| |
| bool NamedMappedAttrMapImpl::mapsEquivalent(const NamedMappedAttrMapImpl* otherMap) const |
| { |
| // The # of decls must match. |
| if (declCount() != otherMap->declCount()) |
| return false; |
| |
| // The values for each decl must match. |
| for (uint i = 0; i < length(); i++) { |
| MappedAttributeImpl* attr = attributeItem(i); |
| if (attr->decl()) { |
| AttributeImpl* otherAttr = otherMap->getAttributeItem(attr->name()); |
| if (!otherAttr || (attr->value() != otherAttr->value())) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void NamedMappedAttrMapImpl::parseClassAttribute(const DOMString& classStr) |
| { |
| m_classList.clear(); |
| if (!element->hasClass()) |
| return; |
| |
| DOMString classAttr = element->getDocument()->inCompatMode() ? |
| (classStr.implementation()->isLower() ? classStr : DOMString(classStr.implementation()->lower())) : |
| classStr; |
| |
| if (classAttr.find(' ') == -1 && classAttr.find('\n') == -1) |
| m_classList.setString(AtomicString(classAttr)); |
| else { |
| QString val = classAttr.string(); |
| val.replace('\n', ' '); |
| QStringList list = QStringList::split(' ', val); |
| |
| AtomicStringList* curr = 0; |
| for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { |
| const QString& singleClass = *it; |
| if (!singleClass.isEmpty()) { |
| if (curr) { |
| curr->setNext(new AtomicStringList(AtomicString(singleClass))); |
| curr = curr->next(); |
| } |
| else { |
| m_classList.setString(AtomicString(singleClass)); |
| curr = &m_classList; |
| } |
| } |
| } |
| } |
| } |
| |
| StyledElementImpl::StyledElementImpl(const QualifiedName& name, DocumentPtr *doc) |
| : ElementImpl(name, doc) |
| { |
| m_inlineStyleDecl = 0; |
| m_isStyleAttributeValid = true; |
| m_synchronizingStyleAttribute = false; |
| } |
| |
| StyledElementImpl::~StyledElementImpl() |
| { |
| destroyInlineStyleDecl(); |
| } |
| |
| AttributeImpl* StyledElementImpl::createAttribute(const QualifiedName& name, DOMStringImpl* value) |
| { |
| return new MappedAttributeImpl(name, value); |
| } |
| |
| void StyledElementImpl::createInlineStyleDecl() |
| { |
| m_inlineStyleDecl = new CSSMutableStyleDeclarationImpl; |
| m_inlineStyleDecl->ref(); |
| m_inlineStyleDecl->setParent(getDocument()->elementSheet()); |
| m_inlineStyleDecl->setNode(this); |
| m_inlineStyleDecl->setStrictParsing(!getDocument()->inCompatMode()); |
| } |
| |
| void StyledElementImpl::destroyInlineStyleDecl() |
| { |
| if (m_inlineStyleDecl) { |
| m_inlineStyleDecl->setNode(0); |
| m_inlineStyleDecl->setParent(0); |
| m_inlineStyleDecl->deref(); |
| m_inlineStyleDecl = 0; |
| } |
| } |
| |
| void StyledElementImpl::attributeChanged(AttributeImpl* attr, bool preserveDecls) |
| { |
| MappedAttributeImpl* mappedAttr = static_cast<MappedAttributeImpl*>(attr); |
| if (mappedAttr->decl() && !preserveDecls) { |
| mappedAttr->setDecl(0); |
| setChanged(); |
| if (namedAttrMap) |
| static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->declRemoved(); |
| } |
| |
| bool checkDecl = true; |
| MappedAttributeEntry entry; |
| bool needToParse = mapToEntry(attr->name(), entry); |
| if (preserveDecls) { |
| if (mappedAttr->decl()) { |
| setChanged(); |
| if (namedAttrMap) |
| static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->declAdded(); |
| checkDecl = false; |
| } |
| } |
| else if (!attr->isNull() && entry != eNone) { |
| CSSMappedAttributeDeclarationImpl* decl = getMappedAttributeDecl(entry, attr); |
| if (decl) { |
| mappedAttr->setDecl(decl); |
| setChanged(); |
| if (namedAttrMap) |
| static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->declAdded(); |
| checkDecl = false; |
| } else |
| needToParse = true; |
| } |
| |
| if (needToParse) |
| parseMappedAttribute(mappedAttr); |
| |
| if (checkDecl && mappedAttr->decl()) { |
| // Add the decl to the table in the appropriate spot. |
| setMappedAttributeDecl(entry, attr, mappedAttr->decl()); |
| mappedAttr->decl()->setMappedState(entry, attr->name(), attr->value()); |
| mappedAttr->decl()->setParent(0); |
| mappedAttr->decl()->setNode(0); |
| if (namedAttrMap) |
| static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->declAdded(); |
| } |
| } |
| |
| bool StyledElementImpl::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const |
| { |
| result = eNone; |
| if (attrName == HTMLAttributes::style()) |
| return !m_synchronizingStyleAttribute; |
| return true; |
| } |
| |
| void StyledElementImpl::parseMappedAttribute(MappedAttributeImpl *attr) |
| { |
| if (attr->name() == HTMLAttributes::idAttr()) { |
| // unique id |
| setHasID(!attr->isNull()); |
| if (namedAttrMap) { |
| if (attr->isNull()) |
| namedAttrMap->setID(nullAtom); |
| else if (getDocument() && getDocument()->inCompatMode() && !attr->value().implementation()->isLower()) |
| namedAttrMap->setID(AtomicString(attr->value().domString().lower())); |
| else |
| namedAttrMap->setID(attr->value()); |
| } |
| setChanged(); |
| } else if (attr->name() == HTMLAttributes::classAttr()) { |
| // class |
| setHasClass(!attr->isNull()); |
| if (namedAttrMap) static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->parseClassAttribute(attr->value()); |
| setChanged(); |
| } else if (attr->name() == HTMLAttributes::style()) { |
| setHasStyle(!attr->isNull()); |
| if (attr->isNull()) |
| destroyInlineStyleDecl(); |
| else |
| getInlineStyleDecl()->parseDeclaration(attr->value()); |
| m_isStyleAttributeValid = true; |
| setChanged(); |
| } |
| } |
| |
| void StyledElementImpl::createAttributeMap() const |
| { |
| namedAttrMap = new NamedMappedAttrMapImpl(const_cast<StyledElementImpl*>(this)); |
| namedAttrMap->ref(); |
| } |
| |
| CSSMutableStyleDeclarationImpl* StyledElementImpl::getInlineStyleDecl() |
| { |
| if (!m_inlineStyleDecl) |
| createInlineStyleDecl(); |
| return m_inlineStyleDecl; |
| } |
| |
| CSSStyleDeclarationImpl* StyledElementImpl::style() |
| { |
| return getInlineStyleDecl(); |
| } |
| |
| CSSMutableStyleDeclarationImpl* StyledElementImpl::additionalAttributeStyleDecl() |
| { |
| return 0; |
| } |
| |
| const AtomicStringList* StyledElementImpl::getClassList() const |
| { |
| return namedAttrMap ? static_cast<NamedMappedAttrMapImpl*>(namedAttrMap)->getClassList() : 0; |
| } |
| |
| static inline bool isHexDigit( const QChar &c ) { |
| return ( c >= '0' && c <= '9' ) || |
| ( c >= 'a' && c <= 'f' ) || |
| ( c >= 'A' && c <= 'F' ); |
| } |
| |
| static inline int toHex( const QChar &c ) { |
| return ( (c >= '0' && c <= '9') |
| ? (c.unicode() - '0') |
| : ( ( c >= 'a' && c <= 'f' ) |
| ? (c.unicode() - 'a' + 10) |
| : ( ( c >= 'A' && c <= 'F' ) |
| ? (c.unicode() - 'A' + 10) |
| : -1 ) ) ); |
| } |
| |
| void StyledElementImpl::addCSSProperty(MappedAttributeImpl* attr, int id, const DOMString &value) |
| { |
| if (!attr->decl()) createMappedDecl(attr); |
| attr->decl()->setProperty(id, value, false); |
| } |
| |
| void StyledElementImpl::addCSSProperty(MappedAttributeImpl* attr, int id, int value) |
| { |
| if (!attr->decl()) createMappedDecl(attr); |
| attr->decl()->setProperty(id, value, false); |
| } |
| |
| void StyledElementImpl::addCSSStringProperty(MappedAttributeImpl* attr, int id, const DOMString &value, CSSPrimitiveValue::UnitTypes type) |
| { |
| if (!attr->decl()) createMappedDecl(attr); |
| attr->decl()->setStringProperty(id, value, type, false); |
| } |
| |
| void StyledElementImpl::addCSSImageProperty(MappedAttributeImpl* attr, int id, const DOMString &URL) |
| { |
| if (!attr->decl()) createMappedDecl(attr); |
| attr->decl()->setImageProperty(id, URL, false); |
| } |
| |
| void StyledElementImpl::addCSSLength(MappedAttributeImpl* attr, int id, const DOMString &value) |
| { |
| // FIXME: This function should not spin up the CSS parser, but should instead just figure out the correct |
| // length unit and make the appropriate parsed value. |
| if (!attr->decl()) createMappedDecl(attr); |
| |
| // strip attribute garbage.. |
| DOMStringImpl* v = value.implementation(); |
| if ( v ) { |
| unsigned int l = 0; |
| |
| while ( l < v->l && v->s[l].unicode() <= ' ') l++; |
| |
| for ( ;l < v->l; l++ ) { |
| char cc = v->s[l].latin1(); |
| if ( cc > '9' || ( cc < '0' && cc != '*' && cc != '%' && cc != '.') ) |
| break; |
| } |
| if ( l != v->l ) { |
| attr->decl()->setLengthProperty(id, DOMString( v->s, l ), false); |
| return; |
| } |
| } |
| |
| attr->decl()->setLengthProperty(id, value, false); |
| } |
| |
| /* color parsing that tries to match as close as possible IE 6. */ |
| void StyledElementImpl::addCSSColor(MappedAttributeImpl* attr, int id, const DOMString &c) |
| { |
| // this is the only case no color gets applied in IE. |
| if ( !c.length() ) |
| return; |
| |
| if (!attr->decl()) createMappedDecl(attr); |
| |
| if (attr->decl()->setProperty(id, c, false) ) |
| return; |
| |
| QString color = c.string(); |
| // not something that fits the specs. |
| |
| // we're emulating IEs color parser here. It maps transparent to black, otherwise it tries to build a rgb value |
| // out of everyhting you put in. The algorithm is experimentally determined, but seems to work for all test cases I have. |
| |
| // the length of the color value is rounded up to the next |
| // multiple of 3. each part of the rgb triple then gets one third |
| // of the length. |
| // |
| // Each triplet is parsed byte by byte, mapping |
| // each number to a hex value (0-9a-fA-F to their values |
| // everything else to 0). |
| // |
| // The highest non zero digit in all triplets is remembered, and |
| // used as a normalization point to normalize to values between 0 |
| // and 255. |
| |
| if ( color.lower() != "transparent" ) { |
| if ( color[0] == '#' ) |
| color.remove( 0, 1 ); |
| int basicLength = (color.length() + 2) / 3; |
| if ( basicLength > 1 ) { |
| // IE ignores colors with three digits or less |
| // qDebug("trying to fix up color '%s'. basicLength=%d, length=%d", |
| // color.latin1(), basicLength, color.length() ); |
| int colors[3] = { 0, 0, 0 }; |
| int component = 0; |
| int pos = 0; |
| int maxDigit = basicLength-1; |
| while ( component < 3 ) { |
| // search forward for digits in the string |
| int numDigits = 0; |
| while ( pos < (int)color.length() && numDigits < basicLength ) { |
| int hex = toHex( color[pos] ); |
| colors[component] = (colors[component] << 4); |
| if ( hex > 0 ) { |
| colors[component] += hex; |
| maxDigit = kMin( maxDigit, numDigits ); |
| } |
| numDigits++; |
| pos++; |
| } |
| while ( numDigits++ < basicLength ) |
| colors[component] <<= 4; |
| component++; |
| } |
| maxDigit = basicLength - maxDigit; |
| // qDebug("color is %x %x %x, maxDigit=%d", colors[0], colors[1], colors[2], maxDigit ); |
| |
| // normalize to 00-ff. The highest filled digit counts, minimum is 2 digits |
| maxDigit -= 2; |
| colors[0] >>= 4*maxDigit; |
| colors[1] >>= 4*maxDigit; |
| colors[2] >>= 4*maxDigit; |
| // qDebug("normalized color is %x %x %x", colors[0], colors[1], colors[2] ); |
| // assert( colors[0] < 0x100 && colors[1] < 0x100 && colors[2] < 0x100 ); |
| |
| color.sprintf("#%02x%02x%02x", colors[0], colors[1], colors[2] ); |
| // qDebug( "trying to add fixed color string '%s'", color.latin1() ); |
| if ( attr->decl()->setProperty(id, DOMString(color), false) ) |
| return; |
| } |
| } |
| attr->decl()->setProperty(id, CSS_VAL_BLACK, false); |
| } |
| |
| void StyledElementImpl::createMappedDecl(MappedAttributeImpl* attr) |
| { |
| CSSMappedAttributeDeclarationImpl* decl = new CSSMappedAttributeDeclarationImpl(0); |
| attr->setDecl(decl); |
| decl->setParent(getDocument()->elementSheet()); |
| decl->setNode(this); |
| decl->setStrictParsing(false); // Mapped attributes are just always quirky. |
| } |