// -*- c-basic-offset: 4; -*- /** * 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) 2003 Dirk Mueller (mueller@kde.org) * Copyright (C) 2002 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ // ------------------------------------------------------------------------- //#define DEBUG //#define DEBUG_LAYOUT //#define PAR_DEBUG //#define EVENT_DEBUG //#define UNSUPPORTED_ATTR #include "html/dtd.h" #include "html/html_elementimpl.h" #include "html/html_documentimpl.h" #include "html/htmltokenizer.h" #include "misc/htmlhashes.h" #include "khtmlview.h" #include "khtml_part.h" #include "rendering/render_object.h" #include "rendering/render_replaced.h" #include "css/css_valueimpl.h" #include "css/css_stylesheetimpl.h" #include "css/cssproperties.h" #include "css/cssvalues.h" #include "xml/dom_textimpl.h" #include "xml/dom2_eventsimpl.h" #include #include #include "html_elementimpl.h" using namespace DOM; using namespace khtml; HTMLElementImpl::HTMLElementImpl(DocumentImpl *doc) : ElementImpl( doc ) { m_htmlCompat = doc && doc->htmlMode() != DocumentImpl::XHtml; } HTMLElementImpl::~HTMLElementImpl() { } bool HTMLElementImpl::isInline() const { if (renderer()) return ElementImpl::isInline(); switch(id()) { case ID_A: case ID_FONT: case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: // %phrase case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: // %special case ID_SUB: case ID_SUP: case ID_SPAN: case ID_NOBR: case ID_WBR: return true; default: return ElementImpl::isInline(); } } DOMString HTMLElementImpl::namespaceURI() const { return (!m_htmlCompat) ? DOMString(XHTML_NAMESPACE) : DOMString(); } DOMString HTMLElementImpl::localName() const { // We only have a localName if we were created by createElementNS(), in which // case we are an XHTML element. This also means we have a lowercase name. if (!m_htmlCompat) // XHTML == not HTMLCompat { NodeImpl::Id _id = id(); DOMString tn; if ( _id >= ID_LAST_TAG ) tn = getDocument()->getName(ElementId, _id); else // HTML tag tn = getTagName( _id ); return tn; // lowercase already } // createElement() always returns elements with a null localName. else return DOMString(); } DOMString HTMLElementImpl::tagName() const { DOMString tn; NodeImpl::Id _id = id(); if ( _id >= ID_LAST_TAG ) tn = getDocument()->getName(ElementId, _id); else // HTML tag tn = getTagName( _id ); if ( m_htmlCompat ) tn = tn.upper(); if (m_prefix) return DOMString(m_prefix) + ":" + tn; return tn; } void HTMLElementImpl::parseAttribute(AttributeImpl *attr) { DOMString indexstring; switch( attr->id() ) { case ATTR_ALIGN: if (attr->val()) { if ( strcasecmp(attr->value(), "middle" ) == 0 ) addCSSProperty( CSS_PROP_TEXT_ALIGN, CSS_VAL_CENTER ); else addCSSProperty(CSS_PROP_TEXT_ALIGN, attr->value().lower()); } else removeCSSProperty(CSS_PROP_TEXT_ALIGN); break; // the core attributes... case ATTR_ID: // unique id setHasID(); getDocument()->incDOMTreeVersion(); break; case ATTR_CLASS: if (attr->val()) { DOMString v = attr->value(); const QChar* s = v.unicode(); int l = v.length(); while( l && !s->isSpace() ) l--,s++; setHasClassList(l); setHasClass(true); } else { setHasClassList(false); setHasClass(false); } break; case ATTR_NAME: getDocument()->incDOMTreeVersion(); break; case ATTR_STYLE: if (m_styleDecls) m_styleDecls->removeCSSHints(); else createDecl(); m_styleDecls->setProperty(attr->value()); setChanged(); break; case ATTR_TABINDEX: indexstring=getAttribute(ATTR_TABINDEX); if (indexstring.length()) setTabIndex(indexstring.toInt()); break; // i18n attributes case ATTR_LANG: break; case ATTR_DIR: addCSSProperty(CSS_PROP_DIRECTION, attr->value().lower()); addCSSProperty(CSS_PROP_UNICODE_BIDI, CSS_VAL_EMBED); break; // standard events case ATTR_ONCLICK: setHTMLEventListener(EventImpl::KHTML_ECMA_CLICK_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onclick", this)); break; case ATTR_ONDBLCLICK: setHTMLEventListener(EventImpl::KHTML_ECMA_DBLCLICK_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "ondblclick", this)); break; case ATTR_ONMOUSEDOWN: setHTMLEventListener(EventImpl::MOUSEDOWN_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onmousedown", this)); break; case ATTR_ONMOUSEMOVE: setHTMLEventListener(EventImpl::MOUSEMOVE_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onmousemove", this)); break; case ATTR_ONMOUSEOUT: setHTMLEventListener(EventImpl::MOUSEOUT_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onmouseout", this)); break; case ATTR_ONMOUSEOVER: setHTMLEventListener(EventImpl::MOUSEOVER_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onmouseover", this)); break; case ATTR_ONMOUSEUP: setHTMLEventListener(EventImpl::MOUSEUP_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onmouseup", this)); break; case ATTR_ONKEYDOWN: setHTMLEventListener(EventImpl::KEYDOWN_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onkeydown", this)); break; case ATTR_ONKEYPRESS: setHTMLEventListener(EventImpl::KEYPRESS_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onkeypress", this)); break; case ATTR_ONKEYUP: setHTMLEventListener(EventImpl::KEYUP_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onkeyup", this)); break; case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onfocus", this)); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onblur", this)); break; case ATTR_ONSCROLL: setHTMLEventListener(EventImpl::SCROLL_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), "onscroll", this)); break; // other misc attributes default: #ifdef UNSUPPORTED_ATTR kdDebug(6030) << "UATTR: <" << this->nodeName().string() << "> [" << attr->name().string() << "]=[" << attr->value().string() << "]" << endl; #endif break; } } void HTMLElementImpl::recalcStyle( StyleChange ch ) { ElementImpl::recalcStyle( ch ); if (m_render /*&& changed*/) m_render->updateFromElement(); } void HTMLElementImpl::addCSSProperty(int id, const DOMString &value) { if(!m_styleDecls) createDecl(); m_styleDecls->setProperty(id, value, false, true); setChanged(); } void HTMLElementImpl::addCSSProperty(int id, int value) { if(!m_styleDecls) createDecl(); m_styleDecls->setProperty(id, value, false, true); setChanged(); } void HTMLElementImpl::addCSSLength(int id, const DOMString &value, bool numOnly, bool multiLength) { if(!m_styleDecls) createDecl(); // strip attribute garbage to avoid CSS parsing errors // ### create specialized hook that avoids parsing every // value twice! if ( value.implementation() ) { // match \s*[+-]?\d*(\.\d*)?[%\*]? unsigned i = 0, j = 0; QChar* s = value.implementation()->s; unsigned l = value.implementation()->l; while (i < l && s[i].isSpace()) ++i; if (i < l && (s[i] == '+' || s[i] == '-')) ++i; while (i < l && s[i].isDigit()) ++i,++j; // no digits! if (j == 0) return; int v = kClamp( QConstString(s, i).string().toInt(), -8192, 8191 ) ; const char* suffix = "px"; if (!numOnly || multiLength) { // look if we find a % or * while (i < l) { if (multiLength && s[i] == '*') { suffix = ""; break; } if (s[i] == '%') { suffix = "%"; break; } ++i; } } if (numOnly) suffix = ""; QString ns = QString::number(v) + suffix; m_styleDecls->setLengthProperty( id, DOMString( ns ), false, true, multiLength ); setChanged(); return; } m_styleDecls->setLengthProperty(id, value, false, true, multiLength); setChanged(); } 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 ) ) ); } /* color parsing that tries to match as close as possible IE 6. */ void HTMLElementImpl::addHTMLColor( int id, const DOMString &c ) { if(!m_styleDecls) createDecl(); // this is the only case no color gets applied in IE. if ( !c.length() ) { removeCSSProperty(id); return; } if ( m_styleDecls->setProperty(id, c, false, true) ) 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 ( m_styleDecls->setProperty(id, DOMString(color), false, true) ) return; } } m_styleDecls->setProperty(id, CSS_VAL_BLACK, false, true); } void HTMLElementImpl::removeCSSProperty(int id) { if(!m_styleDecls) return; m_styleDecls->setParent(getDocument()->elementSheet()); m_styleDecls->removeProperty(id, true /*nonCSSHint */); setChanged(); } DOMString HTMLElementImpl::innerHTML() const { QString result; //Use QString to accumulate since DOMString is poor for appends for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { DOMString kid = child->toString(); result += QConstString(kid.unicode(), kid.length()).string(); } return result; } DOMString HTMLElementImpl::innerText() const { QString text = ""; if(!firstChild()) return text; const NodeImpl *n = this; // find the next text/image after the anchor, to get a position while(n) { if(n->firstChild()) n = n->firstChild(); else if(n->nextSibling()) n = n->nextSibling(); else { NodeImpl *next = 0; while(!next) { n = n->parentNode(); if(!n || n == (NodeImpl *)this ) goto end; next = n->nextSibling(); } n = next; } if(n->isTextNode() ) { DOMStringImpl* data = static_cast(n)->string(); text += QConstString(data->s, data->l).string(); } } end: return text; } DocumentFragment HTMLElementImpl::createContextualFragment( const DOMString &html ) { // the following is in accordance with the definition as used by IE if( endTag[id()] == FORBIDDEN ) return DocumentFragment(); // IE disallows innerHTML on inline elements. // I don't see why we should have this restriction, as our // dhtml engine can cope with it. Lars //if ( isInline() ) return false; switch( id() ) { case ID_COL: case ID_COLGROUP: case ID_FRAMESET: case ID_HEAD: case ID_TABLE: case ID_TBODY: case ID_TFOOT: case ID_THEAD: case ID_TITLE: return DocumentFragment(); default: break; } if ( !getDocument()->isHTMLDocument() ) return DocumentFragment(); DocumentFragmentImpl* fragment = new DocumentFragmentImpl( docPtr() ); DocumentFragment f( fragment ); { HTMLTokenizer tok( docPtr(), fragment ); tok.begin(); tok.write( html.string(), true ); tok.end(); } // Exceptions are ignored because none ought to happen here. int ignoredExceptionCode; // we need to pop and elements and remove to // accomadate folks passing complete HTML documents to make the // child of an element. for ( NodeImpl* node = fragment->firstChild(); node; ) { if (node->id() == ID_HTML || node->id() == ID_BODY) { NodeImpl* firstChild = node->firstChild(); NodeImpl* child = firstChild; while ( child ) { NodeImpl *nextChild = child->nextSibling(); fragment->insertBefore(child, node, ignoredExceptionCode); child = nextChild; } if ( !firstChild ) { NodeImpl *nextNode = node->nextSibling(); fragment->removeChild(node, ignoredExceptionCode); node = nextNode; } else { fragment->removeChild(node, ignoredExceptionCode); node = firstChild; } } else if (node->id() == ID_HEAD) { NodeImpl *nextNode = node->nextSibling(); fragment->removeChild(node, ignoredExceptionCode); node = nextNode; } else { node = node->nextSibling(); } } return f; } void HTMLElementImpl::setInnerHTML( const DOMString &html, int &exceptioncode ) { // Works line innerText in Gecko // ### test if needed for ID_SCRIPT as well. if ( id() == ID_STYLE ) { setInnerText(html, exceptioncode); return; } DocumentFragment fragment = createContextualFragment( html ); if ( fragment.isNull() ) { exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; } // Make sure adding the new child is ok, before removing all children (#96187) checkAddChild( fragment.handle(), exceptioncode ); if ( exceptioncode ) return; removeChildren(); appendChild( fragment.handle(), exceptioncode ); } void HTMLElementImpl::setInnerText( const DOMString &text, int& exceptioncode ) { // following the IE specs. if( endTag[id()] == FORBIDDEN ) { exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; } // IE disallows innerHTML on inline elements. I don't see why we should have this restriction, as our // dhtml engine can cope with it. Lars //if ( isInline() ) return false; switch( id() ) { case ID_COL: case ID_COLGROUP: case ID_FRAMESET: case ID_HEAD: case ID_HTML: case ID_TABLE: case ID_TBODY: case ID_TFOOT: case ID_THEAD: case ID_TR: exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; return; default: break; } removeChildren(); TextImpl *t = new TextImpl( docPtr(), text.implementation() ); appendChild( t, exceptioncode ); } void HTMLElementImpl::addHTMLAlignment( DOMString alignment ) { //qDebug("alignment is %s", alignment.string().latin1() ); // vertical alignment with respect to the current baseline of the text // right or left means floating images int propfloat = -1; int propvalign = -1; if ( strcasecmp( alignment, "absmiddle" ) == 0 ) { propvalign = CSS_VAL_MIDDLE; } else if ( strcasecmp( alignment, "absbottom" ) == 0 ) { propvalign = CSS_VAL_BOTTOM; } else if ( strcasecmp( alignment, "left" ) == 0 ) { propfloat = CSS_VAL_LEFT; propvalign = CSS_VAL_TOP; } else if ( strcasecmp( alignment, "right" ) == 0 ) { propfloat = CSS_VAL_RIGHT; propvalign = CSS_VAL_TOP; } else if ( strcasecmp( alignment, "top" ) == 0 ) { propvalign = CSS_VAL_TOP; } else if ( strcasecmp( alignment, "middle" ) == 0 ) { propvalign = CSS_VAL__KHTML_BASELINE_MIDDLE; } else if ( strcasecmp( alignment, "center" ) == 0 ) { propvalign = CSS_VAL_MIDDLE; } else if ( strcasecmp( alignment, "bottom" ) == 0 ) { propvalign = CSS_VAL_BASELINE; } else if ( strcasecmp ( alignment, "texttop") == 0 ) { propvalign = CSS_VAL_TEXT_TOP; } if ( propfloat != -1 ) addCSSProperty( CSS_PROP_FLOAT, propfloat ); if ( propvalign != -1 ) addCSSProperty( CSS_PROP_VERTICAL_ALIGN, propvalign ); } DOMString HTMLElementImpl::toString() const { if (!hasChildNodes()) { DOMString result = openTagStartToString(); result += ">"; if (endTag[id()] == REQUIRED) { result += ""; } return result; } return ElementImpl::toString(); } // ------------------------------------------------------------------------- HTMLGenericElementImpl::HTMLGenericElementImpl(DocumentImpl *doc, ushort i) : HTMLElementImpl(doc) { _id = i; } HTMLGenericElementImpl::~HTMLGenericElementImpl() { }