diff options
Diffstat (limited to 'khtml/xml')
25 files changed, 15384 insertions, 0 deletions
diff --git a/khtml/xml/Makefile.am b/khtml/xml/Makefile.am new file mode 100644 index 000000000..8f7cd6b7f --- /dev/null +++ b/khtml/xml/Makefile.am @@ -0,0 +1,48 @@ +# This file is part of the KDE libraries +# Copyright (C) 1997 Martin Jones (mjones@kde.org) +# (C) 1997 Torben Weis (weis@kde.org) + +# 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. + +KDE_CXXFLAGS = $(WOVERLOADED_VIRTUAL) + +noinst_LTLIBRARIES = libkhtmlxml.la +libkhtmlxml_la_SOURCES = \ + dom_docimpl.cpp dom_nodeimpl.cpp dom_textimpl.cpp \ + dom_elementimpl.cpp dom_stringimpl.cpp dom2_rangeimpl.cpp \ + dom2_traversalimpl.cpp xml_tokenizer.cpp dom_xmlimpl.cpp \ + dom2_eventsimpl.cpp dom2_viewsimpl.cpp dom_restyler.cpp + +#libkhtmlxml_la_LDFLAGS = -no-undefined +libkhtmlxml_la_METASOURCES = AUTO + +noinst_HEADERS = \ + dom_docimpl.h dom_nodeimpl.h dom_textimpl.h \ + dom_elementimpl.h dom_stringimpl.h dom2_rangeimpl.h \ + dom2_traversalimpl.h xml_tokenizer.h dom_xmlimpl.h \ + dom2_eventsimpl.h dom2_viewsimpl.h dom_restyler.h + +INCLUDES = -I$(top_srcdir)/kimgio -I$(top_srcdir)/kio -I$(top_srcdir)/dcop \ + -I$(top_srcdir)/khtml -I$(top_srcdir) -I$(top_srcdir)/kwallet/client \ + -I$(top_srcdir)/kutils -I$(top_builddir)/kjs $(all_includes) + +SRCDOC_DEST=$(kde_htmldir)/en/kdelibs/khtml + +## generate lib documentation +srcdoc: + $(mkinstalldirs) $(SRCDOC_DEST) + kdoc -H -d $(SRCDOC_DEST) kdecore -lqt + diff --git a/khtml/xml/dom2_eventsimpl.cpp b/khtml/xml/dom2_eventsimpl.cpp new file mode 100644 index 000000000..db8b17b6f --- /dev/null +++ b/khtml/xml/dom2_eventsimpl.cpp @@ -0,0 +1,969 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2001 Peter Kelly (pmk@post.com) + * (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de) + * (C) 2003 Apple Computer, Inc. + * (C) 2006 Maksim Orlovich (maksim@kde.org) + * + * 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 "dom/dom2_views.h" + +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_stringimpl.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "rendering/render_layer.h" +#include "khtmlview.h" + +#include <kdebug.h> + +using namespace DOM; +using namespace khtml; + +EventImpl::EventImpl() +{ + m_type = 0; + m_canBubble = false; + m_cancelable = false; + + m_propagationStopped = false; + m_defaultPrevented = false; + m_id = UNKNOWN_EVENT; + m_currentTarget = 0; + m_eventPhase = 0; + m_target = 0; + m_createTime = QDateTime::currentDateTime(); + m_defaultHandled = false; +} + +EventImpl::EventImpl(EventId _id, bool canBubbleArg, bool cancelableArg) +{ + DOMString t = EventImpl::idToType(_id); + m_type = t.implementation(); + if (m_type) + m_type->ref(); + m_canBubble = canBubbleArg; + m_cancelable = cancelableArg; + + m_propagationStopped = false; + m_defaultPrevented = false; + m_id = _id; + m_currentTarget = 0; + m_eventPhase = 0; + m_target = 0; + m_createTime = QDateTime::currentDateTime(); + m_defaultHandled = false; +} + +EventImpl::~EventImpl() +{ + if (m_type) + m_type->deref(); + if (m_target) + m_target->deref(); +} + +void EventImpl::setTarget(NodeImpl *_target) +{ + if (m_target) + m_target->deref(); + m_target = _target; + if (m_target) + m_target->ref(); +} + +DOMTimeStamp EventImpl::timeStamp() +{ + QDateTime epoch(QDate(1970,1,1),QTime(0,0)); + // ### kjs does not yet support long long (?) so the value wraps around + return epoch.secsTo(m_createTime)*1000+m_createTime.time().msec(); +} + +void EventImpl::initEvent(const DOMString &eventTypeArg, bool canBubbleArg, bool cancelableArg) +{ + // ### ensure this is not called after we have been dispatched (also for subclasses) + + if (m_type) + m_type->deref(); + + m_type = eventTypeArg.implementation(); + if (m_type) + m_type->ref(); + + m_id = typeToId(eventTypeArg); + + m_canBubble = canBubbleArg; + m_cancelable = cancelableArg; +} + +EventImpl::EventId EventImpl::typeToId(DOMString type) +{ + if (type == "DOMFocusIn") + return DOMFOCUSIN_EVENT; + else if (type == "DOMFocusOut") + return DOMFOCUSOUT_EVENT; + else if (type == "DOMActivate") + return DOMACTIVATE_EVENT; + else if (type == "click") + return CLICK_EVENT; + else if (type == "mousedown") + return MOUSEDOWN_EVENT; + else if (type == "mouseup") + return MOUSEUP_EVENT; + else if (type == "mouseover") + return MOUSEOVER_EVENT; + else if (type == "mousemove") + return MOUSEMOVE_EVENT; + else if (type == "mouseout") + return MOUSEOUT_EVENT; + else if (type == "DOMSubtreeModified") + return DOMSUBTREEMODIFIED_EVENT; + else if (type == "DOMNodeInserted") + return DOMNODEINSERTED_EVENT; + else if (type == "DOMNodeRemoved") + return DOMNODEREMOVED_EVENT; + else if (type == "DOMNodeRemovedFromDocument") + return DOMNODEREMOVEDFROMDOCUMENT_EVENT; + else if (type == "DOMNodeInsertedIntoDocument") + return DOMNODEINSERTEDINTODOCUMENT_EVENT; + else if (type == "DOMAttrModified") + return DOMATTRMODIFIED_EVENT; + else if (type == "DOMCharacterDataModified") + return DOMCHARACTERDATAMODIFIED_EVENT; + else if (type == "load") + return LOAD_EVENT; + else if (type == "unload") + return UNLOAD_EVENT; + else if (type == "abort") + return ABORT_EVENT; + else if (type == "error") + return ERROR_EVENT; + else if (type == "select") + return SELECT_EVENT; + else if (type == "change") + return CHANGE_EVENT; + else if (type == "submit") + return SUBMIT_EVENT; + else if (type == "reset") + return RESET_EVENT; + else if (type == "focus") + return FOCUS_EVENT; + else if (type == "blur") + return BLUR_EVENT; + else if (type == "resize") + return RESIZE_EVENT; + else if (type == "scroll") + return SCROLL_EVENT; + else if ( type == "keydown" ) + return KEYDOWN_EVENT; + else if ( type == "keyup" ) + return KEYUP_EVENT; + else if ( type == "textInput" ) + return KEYPRESS_EVENT; + else if ( type == "keypress" ) + return KEYPRESS_EVENT; + else if ( type == "readystatechange" ) + return KHTML_READYSTATECHANGE_EVENT; + else if ( type == "dblclick" ) + return KHTML_ECMA_DBLCLICK_EVENT; + + // ignore: KHTML_CLICK_EVENT + return UNKNOWN_EVENT; +} + +DOMString EventImpl::idToType(EventImpl::EventId id) +{ + switch (id) { + case DOMFOCUSIN_EVENT: + return "DOMFocusIn"; + case DOMFOCUSOUT_EVENT: + return "DOMFocusOut"; + case DOMACTIVATE_EVENT: + return "DOMActivate"; + case CLICK_EVENT: + return "click"; + case MOUSEDOWN_EVENT: + return "mousedown"; + case MOUSEUP_EVENT: + return "mouseup"; + case MOUSEOVER_EVENT: + return "mouseover"; + case MOUSEMOVE_EVENT: + return "mousemove"; + case MOUSEOUT_EVENT: + return "mouseout"; + case DOMSUBTREEMODIFIED_EVENT: + return "DOMSubtreeModified"; + case DOMNODEINSERTED_EVENT: + return "DOMNodeInserted"; + case DOMNODEREMOVED_EVENT: + return "DOMNodeRemoved"; + case DOMNODEREMOVEDFROMDOCUMENT_EVENT: + return "DOMNodeRemovedFromDocument"; + case DOMNODEINSERTEDINTODOCUMENT_EVENT: + return "DOMNodeInsertedIntoDocument"; + case DOMATTRMODIFIED_EVENT: + return "DOMAttrModified"; + case DOMCHARACTERDATAMODIFIED_EVENT: + return "DOMCharacterDataModified"; + case LOAD_EVENT: + return "load"; + case UNLOAD_EVENT: + return "unload"; + case ABORT_EVENT: + return "abort"; + case ERROR_EVENT: + return "error"; + case SELECT_EVENT: + return "select"; + case CHANGE_EVENT: + return "change"; + case SUBMIT_EVENT: + return "submit"; + case RESET_EVENT: + return "reset"; + case FOCUS_EVENT: + return "focus"; + case BLUR_EVENT: + return "blur"; + case RESIZE_EVENT: + return "resize"; + case SCROLL_EVENT: + return "scroll"; + case KEYDOWN_EVENT: + return "keydown"; + case KEYUP_EVENT: + return "keyup"; + case KEYPRESS_EVENT: + return "keypress"; //DOM3 ev. suggests textInput, but it's better for compat this way + + //khtml extensions + case KHTML_ECMA_DBLCLICK_EVENT: + return "dblclick"; + case KHTML_ECMA_CLICK_EVENT: + return "click"; + case KHTML_DRAGDROP_EVENT: + return "khtml_dragdrop"; + case KHTML_MOVE_EVENT: + return "khtml_move"; + case KHTML_READYSTATECHANGE_EVENT: + return "readystatechange"; + + default: + return DOMString(); + break; + } +} + +bool EventImpl::isUIEvent() const +{ + return false; +} + +bool EventImpl::isMouseEvent() const +{ + return false; +} + +bool EventImpl::isMutationEvent() const +{ + return false; +} + +bool EventImpl::isTextInputEvent() const +{ + return false; +} + +bool EventImpl::isKeyboardEvent() const +{ + return false; +} + +// ----------------------------------------------------------------------------- + +UIEventImpl::UIEventImpl(EventId _id, bool canBubbleArg, bool cancelableArg, + AbstractViewImpl *viewArg, long detailArg) + : EventImpl(_id,canBubbleArg,cancelableArg) +{ + m_view = viewArg; + if (m_view) + m_view->ref(); + m_detail = detailArg; +} + +UIEventImpl::~UIEventImpl() +{ + if (m_view) + m_view->deref(); +} + +void UIEventImpl::initUIEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + long detailArg) +{ + EventImpl::initEvent(typeArg,canBubbleArg,cancelableArg); + + if (m_view) + m_view->deref(); + + m_view = viewArg.handle(); + if (m_view) + m_view->ref(); + m_detail = detailArg; +} + +bool UIEventImpl::isUIEvent() const +{ + return true; +} + +// ----------------------------------------------------------------------------- + +MouseEventImpl::MouseEventImpl() +{ + m_screenX = 0; + m_screenY = 0; + m_clientX = 0; + m_clientY = 0; + m_pageX = 0; + m_pageY = 0; + m_ctrlKey = false; + m_altKey = false; + m_shiftKey = false; + m_metaKey = false; + m_button = 0; + m_relatedTarget = 0; + m_qevent = 0; + m_isDoubleClick = false; +} + +MouseEventImpl::MouseEventImpl(EventId _id, + bool canBubbleArg, + bool cancelableArg, + AbstractViewImpl *viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + long pageXArg, + long pageYArg, + bool ctrlKeyArg, + bool altKeyArg, + bool shiftKeyArg, + bool metaKeyArg, + unsigned short buttonArg, + NodeImpl *relatedTargetArg, + QMouseEvent *qe, + bool isDoubleClick) + : UIEventImpl(_id,canBubbleArg,cancelableArg,viewArg,detailArg) +{ + m_screenX = screenXArg; + m_screenY = screenYArg; + m_clientX = clientXArg; + m_clientY = clientYArg; + m_pageX = pageXArg; + m_pageY = pageYArg; + m_ctrlKey = ctrlKeyArg; + m_altKey = altKeyArg; + m_shiftKey = shiftKeyArg; + m_metaKey = metaKeyArg; + m_button = buttonArg; + m_relatedTarget = relatedTargetArg; + if (m_relatedTarget) + m_relatedTarget->ref(); + computeLayerPos(); + m_qevent = qe; + m_isDoubleClick = isDoubleClick; +} + +MouseEventImpl::~MouseEventImpl() +{ + if (m_relatedTarget) + m_relatedTarget->deref(); +} + +void MouseEventImpl::computeLayerPos() +{ + m_layerX = m_pageX; + m_layerY = m_pageY; + + DocumentImpl* doc = view() ? view()->document() : 0; + if (doc) { + khtml::RenderObject::NodeInfo renderInfo(true, false); + doc->renderer()->layer()->nodeAtPoint(renderInfo, m_pageX, m_pageY); + + NodeImpl *node = renderInfo.innerNonSharedNode(); + while (node && !node->renderer()) + node = node->parent(); + + if (node) { + node->renderer()->enclosingLayer()->updateLayerPosition(); + for (RenderLayer* layer = node->renderer()->enclosingLayer(); layer; + layer = layer->parent()) { + m_layerX -= layer->xPos(); + m_layerY -= layer->yPos(); + } + } + } +} + +void MouseEventImpl::initMouseEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + bool ctrlKeyArg, + bool altKeyArg, + bool shiftKeyArg, + bool metaKeyArg, + unsigned short buttonArg, + const Node &relatedTargetArg) +{ + UIEventImpl::initUIEvent(typeArg,canBubbleArg,cancelableArg,viewArg,detailArg); + + if (m_relatedTarget) + m_relatedTarget->deref(); + + m_screenX = screenXArg; + m_screenY = screenYArg; + m_clientX = clientXArg; + m_clientY = clientYArg; + m_pageX = clientXArg; + m_pageY = clientYArg; + KHTMLView* v; + if ( view() && view()->document() && ( v = view()->document()->view() ) ) { + m_pageX += v->contentsX(); + m_pageY += v->contentsY(); + } + m_ctrlKey = ctrlKeyArg; + m_altKey = altKeyArg; + m_shiftKey = shiftKeyArg; + m_metaKey = metaKeyArg; + m_button = buttonArg; + m_relatedTarget = relatedTargetArg.handle(); + if (m_relatedTarget) + m_relatedTarget->ref(); + + + // ### make this on-demand. its soo sloooow + computeLayerPos(); + m_qevent = 0; +} + +bool MouseEventImpl::isMouseEvent() const +{ + return true; +} + +//--------------------------------------------------------------------------------------------- +/* This class is used to do remapping between different encodings reasonably compactly */ + +template<typename L, typename R, typename MemL> +class IDTranslator +{ +public: + struct Info { + MemL l; + R r; + }; + + IDTranslator(const Info* table) { + for (const Info* cursor = table; cursor->l; ++cursor) { + m_lToR.insert(cursor->l, cursor->r); + m_rToL.insert(cursor->r, cursor->l); + } + } + + L toLeft(R r) { + QMapIterator<R,L> i( m_rToL.find(r) ); + if (i != m_rToL.end()) + return *i; + return L(); + } + + R toRight(L l) { + QMapIterator<L,R> i = m_lToR.find(l); + if (i != m_lToR.end()) + return *i; + return R(); + } + +private: + QMap<L, R> m_lToR; + QMap<R, L> m_rToL; +}; + +#define MAKE_TRANSLATOR(name,L,R,MR,table) static IDTranslator<L,R,MR>* s_##name; \ + static IDTranslator<L,R,MR>* name() { if (!s_##name) s_##name = new IDTranslator<L,R,MR>(table); \ + return s_##name; } + +//--------------------------------------------------------------------------------------------- + +/* Mapping between special Qt keycodes and virtual DOM codes */ +IDTranslator<unsigned, unsigned, unsigned>::Info virtKeyToQtKeyTable[] = +{ + {KeyEventBaseImpl::DOM_VK_BACK_SPACE, Qt::Key_Backspace}, + {KeyEventBaseImpl::DOM_VK_ENTER, Qt::Key_Enter}, + {KeyEventBaseImpl::DOM_VK_ENTER, Qt::Key_Return}, + {KeyEventBaseImpl::DOM_VK_NUM_LOCK, Qt::Key_NumLock}, + {KeyEventBaseImpl::DOM_VK_RIGHT_ALT, Qt::Key_Alt}, + {KeyEventBaseImpl::DOM_VK_LEFT_CONTROL, Qt::Key_Control}, + {KeyEventBaseImpl::DOM_VK_LEFT_SHIFT, Qt::Key_Shift}, + {KeyEventBaseImpl::DOM_VK_META, Qt::Key_Meta}, + {KeyEventBaseImpl::DOM_VK_CAPS_LOCK, Qt::Key_CapsLock}, + {KeyEventBaseImpl::DOM_VK_DELETE, Qt::Key_Delete}, + {KeyEventBaseImpl::DOM_VK_END, Qt::Key_End}, + {KeyEventBaseImpl::DOM_VK_ESCAPE, Qt::Key_Escape}, + {KeyEventBaseImpl::DOM_VK_HOME, Qt::Key_Home}, + {KeyEventBaseImpl::DOM_VK_PAUSE, Qt::Key_Pause}, + {KeyEventBaseImpl::DOM_VK_PRINTSCREEN, Qt::Key_Print}, + {KeyEventBaseImpl::DOM_VK_SCROLL_LOCK, Qt::Key_ScrollLock}, + {KeyEventBaseImpl::DOM_VK_LEFT, Qt::Key_Left}, + {KeyEventBaseImpl::DOM_VK_RIGHT, Qt::Key_Right}, + {KeyEventBaseImpl::DOM_VK_UP, Qt::Key_Up}, + {KeyEventBaseImpl::DOM_VK_DOWN, Qt::Key_Down}, + {KeyEventBaseImpl::DOM_VK_PAGE_DOWN, Qt::Key_Next}, + {KeyEventBaseImpl::DOM_VK_PAGE_UP, Qt::Key_Prior}, + {KeyEventBaseImpl::DOM_VK_F1, Qt::Key_F1}, + {KeyEventBaseImpl::DOM_VK_F2, Qt::Key_F2}, + {KeyEventBaseImpl::DOM_VK_F3, Qt::Key_F3}, + {KeyEventBaseImpl::DOM_VK_F4, Qt::Key_F4}, + {KeyEventBaseImpl::DOM_VK_F5, Qt::Key_F5}, + {KeyEventBaseImpl::DOM_VK_F6, Qt::Key_F6}, + {KeyEventBaseImpl::DOM_VK_F7, Qt::Key_F7}, + {KeyEventBaseImpl::DOM_VK_F8, Qt::Key_F8}, + {KeyEventBaseImpl::DOM_VK_F9, Qt::Key_F9}, + {KeyEventBaseImpl::DOM_VK_F10, Qt::Key_F10}, + {KeyEventBaseImpl::DOM_VK_F11, Qt::Key_F11}, + {KeyEventBaseImpl::DOM_VK_F12, Qt::Key_F12}, + {KeyEventBaseImpl::DOM_VK_F13, Qt::Key_F13}, + {KeyEventBaseImpl::DOM_VK_F14, Qt::Key_F14}, + {KeyEventBaseImpl::DOM_VK_F15, Qt::Key_F15}, + {KeyEventBaseImpl::DOM_VK_F16, Qt::Key_F16}, + {KeyEventBaseImpl::DOM_VK_F17, Qt::Key_F17}, + {KeyEventBaseImpl::DOM_VK_F18, Qt::Key_F18}, + {KeyEventBaseImpl::DOM_VK_F19, Qt::Key_F19}, + {KeyEventBaseImpl::DOM_VK_F20, Qt::Key_F20}, + {KeyEventBaseImpl::DOM_VK_F21, Qt::Key_F21}, + {KeyEventBaseImpl::DOM_VK_F22, Qt::Key_F22}, + {KeyEventBaseImpl::DOM_VK_F23, Qt::Key_F23}, + {KeyEventBaseImpl::DOM_VK_F24, Qt::Key_F24}, + {0, 0} +}; + +MAKE_TRANSLATOR(virtKeyToQtKey, unsigned, unsigned, unsigned, virtKeyToQtKeyTable) + +KeyEventBaseImpl::KeyEventBaseImpl(EventId id, bool canBubbleArg, bool cancelableArg, AbstractViewImpl *viewArg, + QKeyEvent *key) : + UIEventImpl(id, canBubbleArg, cancelableArg, viewArg, 0) +{ + m_synthetic = false; + + //Here, we need to map Qt's internal info to browser-style info. + m_keyEvent = new QKeyEvent(key->type(), key->key(), key->ascii(), key->state(), key->text(), key->isAutoRepeat(), key->count() ); + + m_detail = key->count(); + m_keyVal = key->ascii(); + m_virtKeyVal = virtKeyToQtKey()->toLeft(key->key()); + + // m_keyVal should contain the unicode value + // of the pressed key if available. + if (m_virtKeyVal == DOM_VK_UNDEFINED && !key->text().isEmpty()) + m_keyVal = key->text().unicode()[0]; + + // key->state returns enum ButtonState, which is ShiftButton, ControlButton and AltButton or'ed together. + m_modifier = key->state(); +} + +KeyEventBaseImpl::~KeyEventBaseImpl() +{ + delete m_keyEvent; +} + +void KeyEventBaseImpl::initKeyBaseEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + unsigned long keyValArg, + unsigned long virtKeyValArg, + unsigned long modifiersArg) +{ + m_synthetic = true; + delete m_keyEvent; + m_keyEvent = 0; + initUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, 1); + m_virtKeyVal = virtKeyValArg; + m_keyVal = keyValArg; + m_modifier = modifiersArg; +} + +bool KeyEventBaseImpl::checkModifier(unsigned long modifierArg) +{ + return ((m_modifier & modifierArg) == modifierArg); +} + +void KeyEventBaseImpl::initModifier(unsigned long modifierArg, + bool valueArg) +{ + if (valueArg) + m_modifier |= modifierArg; + else + m_modifier &= (modifierArg ^ 0xFFFFFFFF); +} + +void KeyEventBaseImpl::buildQKeyEvent() const +{ + delete m_keyEvent; + + assert(m_synthetic); + //IMPORTANT: we ignore modifers on purpose. + //this is to prevent a website from synthesizing something + //like Ctrl-V or Shift-Insert and stealing contents of the user's clipboard. + unsigned modifiers = 0; + + int key = 0; + int ascii = 0; + QString text; + if (m_virtKeyVal) + key = virtKeyToQtKey()->toRight(m_virtKeyVal); + if (!key) { + ascii = m_keyVal; //###? + key = m_keyVal; + text = QChar(key); + } + + //Neuter F keys as well. + if (key >= Qt::Key_F1 && key <= Qt::Key_F35) + key = Qt::Key_ScrollLock; + + m_keyEvent = new QKeyEvent(id() == KEYUP_EVENT ? QEvent::KeyRelease : QEvent::KeyPress, + key, ascii, modifiers, text); +} + +//------------------------------------------------------------------------------ + + +static const IDTranslator<QCString, unsigned, const char*>::Info keyIdentifiersToVirtKeysTable[] = { + {"Alt", KeyEventBaseImpl::DOM_VK_LEFT_ALT}, + {"Control", KeyEventBaseImpl::DOM_VK_LEFT_CONTROL}, + {"Shift", KeyEventBaseImpl::DOM_VK_LEFT_SHIFT}, + {"Meta", KeyEventBaseImpl::DOM_VK_META}, + {"\0x08", KeyEventBaseImpl::DOM_VK_SPACE}, //1-char virt! + {"CapsLock", KeyEventBaseImpl::DOM_VK_CAPS_LOCK}, + {"\x7F", KeyEventBaseImpl::DOM_VK_DELETE}, //1-char virt! + {"End", KeyEventBaseImpl::DOM_VK_END}, + {"Enter", KeyEventBaseImpl::DOM_VK_ENTER}, + {"\x1b", KeyEventBaseImpl::DOM_VK_ESCAPE}, //1-char virt! + {"Home", KeyEventBaseImpl::DOM_VK_HOME}, + {"NumLock", KeyEventBaseImpl::DOM_VK_NUM_LOCK}, + {"Pause", KeyEventBaseImpl::DOM_VK_PAUSE}, + {"PrintScreen", KeyEventBaseImpl::DOM_VK_PRINTSCREEN}, + {"Scroll", KeyEventBaseImpl::DOM_VK_SCROLL_LOCK}, + {" ", KeyEventBaseImpl::DOM_VK_SPACE}, //1-char virt! + {"\t", KeyEventBaseImpl::DOM_VK_TAB}, //1-char virt! + {"Left", KeyEventBaseImpl::DOM_VK_LEFT}, + {"Left", KeyEventBaseImpl::DOM_VK_LEFT}, + {"Right", KeyEventBaseImpl::DOM_VK_RIGHT}, + {"Up", KeyEventBaseImpl::DOM_VK_UP}, + {"Down", KeyEventBaseImpl::DOM_VK_DOWN}, + {"PageDown", KeyEventBaseImpl::DOM_VK_PAGE_DOWN}, + {"PageUp", KeyEventBaseImpl::DOM_VK_PAGE_UP}, + {"F1", KeyEventBaseImpl::DOM_VK_F1}, + {"F2", KeyEventBaseImpl::DOM_VK_F2}, + {"F3", KeyEventBaseImpl::DOM_VK_F3}, + {"F4", KeyEventBaseImpl::DOM_VK_F4}, + {"F5", KeyEventBaseImpl::DOM_VK_F5}, + {"F6", KeyEventBaseImpl::DOM_VK_F6}, + {"F7", KeyEventBaseImpl::DOM_VK_F7}, + {"F8", KeyEventBaseImpl::DOM_VK_F8}, + {"F9", KeyEventBaseImpl::DOM_VK_F9}, + {"F10", KeyEventBaseImpl::DOM_VK_F10}, + {"F11", KeyEventBaseImpl::DOM_VK_F11}, + {"F12", KeyEventBaseImpl::DOM_VK_F12}, + {"F13", KeyEventBaseImpl::DOM_VK_F13}, + {"F14", KeyEventBaseImpl::DOM_VK_F14}, + {"F15", KeyEventBaseImpl::DOM_VK_F15}, + {"F16", KeyEventBaseImpl::DOM_VK_F16}, + {"F17", KeyEventBaseImpl::DOM_VK_F17}, + {"F18", KeyEventBaseImpl::DOM_VK_F18}, + {"F19", KeyEventBaseImpl::DOM_VK_F19}, + {"F20", KeyEventBaseImpl::DOM_VK_F20}, + {"F21", KeyEventBaseImpl::DOM_VK_F21}, + {"F22", KeyEventBaseImpl::DOM_VK_F22}, + {"F23", KeyEventBaseImpl::DOM_VK_F23}, + {"F24", KeyEventBaseImpl::DOM_VK_F24}, + {0, 0} +}; + +MAKE_TRANSLATOR(keyIdentifiersToVirtKeys, QCString, unsigned, const char*, keyIdentifiersToVirtKeysTable) + +/** These are the modifiers we currently support */ +static const IDTranslator<QCString, unsigned, const char*>::Info keyModifiersToCodeTable[] = { + {"Alt", Qt::AltButton}, + {"Control", Qt::ControlButton}, + {"Shift", Qt::ShiftButton}, + {"Meta", Qt::MetaButton}, + {0, 0} +}; + +MAKE_TRANSLATOR(keyModifiersToCode, QCString, unsigned, const char*, keyModifiersToCodeTable) + +KeyboardEventImpl::KeyboardEventImpl() : m_keyLocation(DOM_KEY_LOCATION_STANDARD) +{} + +DOMString KeyboardEventImpl::keyIdentifier() const +{ + if (unsigned special = virtKeyVal()) + if (const char* id = keyIdentifiersToVirtKeys()->toLeft(special)) + return QString::fromLatin1(id); + + if (unsigned unicode = keyVal()) + return QString(QChar(unicode)); + + return "Unidentified"; +} + +bool KeyboardEventImpl::getModifierState (const DOMString& keyIdentifierArg) const +{ + unsigned mask = keyModifiersToCode()->toRight(keyIdentifierArg.string().latin1()); + return m_modifier & mask; +} + +bool KeyboardEventImpl::isKeyboardEvent() const +{ + return true; +} + +void KeyboardEventImpl::initKeyboardEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + const DOMString &keyIdentifierArg, + unsigned long keyLocationArg, + const DOMString& modifiersList) +{ + unsigned keyVal = 0; + unsigned virtKeyVal = 0; + + m_keyLocation = keyLocationArg; + + //Figure out the code information from the key identifier. + if (keyIdentifierArg.length() == 1) { + //Likely to be normal unicode id, unless it's one of the few + //special values. + unsigned short code = keyIdentifierArg.unicode()[0]; + if (code > 0x20 && code != 0x7F) + keyVal = code; + } + + if (!keyVal) //One of special keys, likely. + virtKeyVal = keyIdentifiersToVirtKeys()->toRight(keyIdentifierArg.string().latin1()); + + //Process modifier list. + QStringList mods = + QStringList::split(' ', + modifiersList.string().stripWhiteSpace().simplifyWhiteSpace()); + + unsigned modifiers = 0; + for (QStringList::Iterator i = mods.begin(); i != mods.end(); ++i) + if (unsigned mask = keyModifiersToCode()->toRight((*i).latin1())) + modifiers |= mask; + + initKeyBaseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, + keyVal, virtKeyVal, modifiers); +} + +KeyboardEventImpl::KeyboardEventImpl(QKeyEvent* key, DOM::AbstractViewImpl* view) : + KeyEventBaseImpl(key->type() == QEvent::KeyRelease ? KEYUP_EVENT : KEYDOWN_EVENT, true, true, view, key) +{ + //Try to put something reasonable in location... + //we don't know direction, so guess left + m_keyLocation = DOM_KEY_LOCATION_STANDARD; + switch (m_virtKeyVal) { + case DOM_VK_LEFT_ALT: + case DOM_VK_LEFT_SHIFT: + case DOM_VK_LEFT_CONTROL: + case DOM_VK_META: + m_keyLocation = DOM_KEY_LOCATION_LEFT; + } +} + +int KeyboardEventImpl::keyCode() const +{ + //Keycode on key events always identifies the -key- and not the input, + //so e.g. 'a' will get 'A' + if (m_virtKeyVal != DOM_VK_UNDEFINED) + return m_virtKeyVal; + else + return QChar((unsigned short)m_keyVal).upper().unicode(); +} + +int KeyboardEventImpl::charCode() const +{ + //IE doesn't support charCode at all, and mozilla returns 0 + //on key events. So return 0 here + return 0; +} + + +// ----------------------------------------------------------------------------- +TextEventImpl::TextEventImpl() +{} + +bool TextEventImpl::isTextInputEvent() const +{ + return true; +} + +TextEventImpl::TextEventImpl(QKeyEvent* key, DOM::AbstractViewImpl* view) : + KeyEventBaseImpl(KEYPRESS_EVENT, true, true, view, key) +{ + m_outputString = key->text(); +} + +void TextEventImpl::initTextEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + const DOMString& text) +{ + m_outputString = text; + + //See whether we can get a key out of this. + unsigned keyCode = 0; + if (text.length() == 1) + keyCode = text.unicode()[0].unicode(); + initKeyBaseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, + keyCode, 0, 0); +} + +int TextEventImpl::keyCode() const +{ + //Mozilla returns 0 here unless this is a non-unicode key. + //IE stuffs everything here, and so we try to match it.. + if (m_keyVal) + return m_keyVal; + return m_virtKeyVal; +} + +int TextEventImpl::charCode() const +{ + //On text events, in Mozilla charCode is 0 for non-unicode keys, + //and the unicode key otherwise... IE doesn't support this. + if (m_virtKeyVal) + return 0; + return m_keyVal; +} + + +// ----------------------------------------------------------------------------- +MutationEventImpl::MutationEventImpl() +{ + m_relatedNode = 0; + m_prevValue = 0; + m_newValue = 0; + m_attrName = 0; + m_attrChange = 0; +} + +MutationEventImpl::MutationEventImpl(EventId _id, + bool canBubbleArg, + bool cancelableArg, + const Node &relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg) + : EventImpl(_id,canBubbleArg,cancelableArg) +{ + m_relatedNode = relatedNodeArg.handle(); + if (m_relatedNode) + m_relatedNode->ref(); + m_prevValue = prevValueArg.implementation(); + if (m_prevValue) + m_prevValue->ref(); + m_newValue = newValueArg.implementation(); + if (m_newValue) + m_newValue->ref(); + m_attrName = attrNameArg.implementation(); + if (m_attrName) + m_attrName->ref(); + m_attrChange = attrChangeArg; +} + +MutationEventImpl::~MutationEventImpl() +{ + if (m_relatedNode) + m_relatedNode->deref(); + if (m_prevValue) + m_prevValue->deref(); + if (m_newValue) + m_newValue->deref(); + if (m_attrName) + m_attrName->deref(); +} + +void MutationEventImpl::initMutationEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node &relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg) +{ + EventImpl::initEvent(typeArg,canBubbleArg,cancelableArg); + + if (m_relatedNode) + m_relatedNode->deref(); + if (m_prevValue) + m_prevValue->deref(); + if (m_newValue) + m_newValue->deref(); + if (m_attrName) + m_attrName->deref(); + + m_relatedNode = relatedNodeArg.handle(); + if (m_relatedNode) + m_relatedNode->ref(); + m_prevValue = prevValueArg.implementation(); + if (m_prevValue) + m_prevValue->ref(); + m_newValue = newValueArg.implementation(); + if (m_newValue) + m_newValue->ref(); + m_attrName = attrNameArg.implementation(); + if (m_newValue) + m_newValue->ref(); + m_attrChange = attrChangeArg; +} + +bool MutationEventImpl::isMutationEvent() const +{ + return true; +} + diff --git a/khtml/xml/dom2_eventsimpl.h b/khtml/xml/dom2_eventsimpl.h new file mode 100644 index 000000000..2f2034088 --- /dev/null +++ b/khtml/xml/dom2_eventsimpl.h @@ -0,0 +1,513 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2001 Peter Kelly (pmk@post.com) + * (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de) + * (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. + * + */ + +#ifndef _DOM_EventsImpl_h_ +#define _DOM_EventsImpl_h_ + +#include "dom/dom2_events.h" +#include "xml/dom2_viewsimpl.h" + +class KHTMLPart; +class QMouseEvent; + +namespace DOM { + +class AbstractViewImpl; +class DOMStringImpl; +class NodeImpl; + +// ### support user-defined events + +class EventImpl : public khtml::Shared<EventImpl> +{ +public: + enum EventId { + UNKNOWN_EVENT = 0, + // UI events + DOMFOCUSIN_EVENT, + DOMFOCUSOUT_EVENT, + DOMACTIVATE_EVENT, + // Mouse events + CLICK_EVENT, + MOUSEDOWN_EVENT, + MOUSEUP_EVENT, + MOUSEOVER_EVENT, + MOUSEMOVE_EVENT, + MOUSEOUT_EVENT, + // Mutation events + DOMSUBTREEMODIFIED_EVENT, + DOMNODEINSERTED_EVENT, + DOMNODEREMOVED_EVENT, + DOMNODEREMOVEDFROMDOCUMENT_EVENT, + DOMNODEINSERTEDINTODOCUMENT_EVENT, + DOMATTRMODIFIED_EVENT, + DOMCHARACTERDATAMODIFIED_EVENT, + // HTML events + LOAD_EVENT, + UNLOAD_EVENT, + ABORT_EVENT, + ERROR_EVENT, + SELECT_EVENT, + CHANGE_EVENT, + SUBMIT_EVENT, + RESET_EVENT, + FOCUS_EVENT, + BLUR_EVENT, + RESIZE_EVENT, + SCROLL_EVENT, + // keyboard events + KEYDOWN_EVENT, + KEYUP_EVENT, + KEYPRESS_EVENT, //Mostly corresponds to DOM3 textInput event. + // khtml events (not part of DOM) + KHTML_ECMA_DBLCLICK_EVENT, // for html ondblclick + KHTML_ECMA_CLICK_EVENT, // for html onclick + KHTML_DRAGDROP_EVENT, + KHTML_MOVE_EVENT, + // XMLHttpRequest events + KHTML_READYSTATECHANGE_EVENT + }; + + EventImpl(); + EventImpl(EventId _id, bool canBubbleArg, bool cancelableArg); + virtual ~EventImpl(); + + EventId id() const { return m_id; } + + DOMString type() const { return m_type; } + NodeImpl *target() const { return m_target; } + void setTarget(NodeImpl *_target); + NodeImpl *currentTarget() const { return m_currentTarget; } + void setCurrentTarget(NodeImpl *_currentTarget) { m_currentTarget = _currentTarget; } + unsigned short eventPhase() const { return m_eventPhase; } + void setEventPhase(unsigned short _eventPhase) { m_eventPhase = _eventPhase; } + bool bubbles() const { return m_canBubble; } + bool cancelable() const { return m_cancelable; } + DOMTimeStamp timeStamp(); + void stopPropagation(bool stop) { m_propagationStopped = stop; } + void preventDefault(bool prevent) { if ( m_cancelable ) m_defaultPrevented = prevent; } + + void initEvent(const DOMString &eventTypeArg, bool canBubbleArg, bool cancelableArg); + + virtual bool isUIEvent() const; + virtual bool isMouseEvent() const; + virtual bool isMutationEvent() const; + virtual bool isTextInputEvent() const; + virtual bool isKeyboardEvent() const; + bool isKeyRelatedEvent() const { return isTextInputEvent() || isKeyboardEvent(); } + + bool propagationStopped() const { return m_propagationStopped; } + bool defaultPrevented() const { return m_defaultPrevented; } + + static EventId typeToId(DOMString type); + static DOMString idToType(EventId id); + + void setDefaultHandled() { m_defaultHandled = true; } + bool defaultHandled() const { return m_defaultHandled; } + + DOMString message() const { return m_message; } + void setMessage(const DOMString &_message) { m_message = _message; } + +protected: + DOMStringImpl *m_type; + bool m_canBubble; + bool m_cancelable; + + bool m_propagationStopped; + bool m_defaultPrevented; + bool m_defaultHandled; + EventId m_id : 6; + unsigned short m_eventPhase : 2; + NodeImpl *m_currentTarget; // ref > 0 maintained externally + NodeImpl *m_target; + QDateTime m_createTime; + DOMString m_message; +}; + + + +class UIEventImpl : public EventImpl +{ +public: + UIEventImpl() : m_view(0), m_detail(0) {} + UIEventImpl(EventId _id, + bool canBubbleArg, + bool cancelableArg, + AbstractViewImpl *viewArg, + long detailArg); + virtual ~UIEventImpl(); + AbstractViewImpl *view() const { return m_view; } + long detail() const { return m_detail; } + void initUIEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + long detailArg); + virtual bool isUIEvent() const; + +protected: + AbstractViewImpl *m_view; + long m_detail; + +}; + +// Introduced in DOM Level 2: - internal +class MouseEventImpl : public UIEventImpl { +public: + MouseEventImpl(); + MouseEventImpl(EventId _id, + bool canBubbleArg, + bool cancelableArg, + AbstractViewImpl *viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + long pageXArg, + long pageYArg, + bool ctrlKeyArg, + bool altKeyArg, + bool shiftKeyArg, + bool metaKeyArg, + unsigned short buttonArg, + NodeImpl *relatedTargetArg, + QMouseEvent *qe = 0, + bool isDoubleClick = false); + virtual ~MouseEventImpl(); + long screenX() const { return m_screenX; } + long screenY() const { return m_screenY; } + long clientX() const { return m_clientX; } + long clientY() const { return m_clientY; } + long layerX() const { return m_layerX; } // non-DOM extension + long layerY() const { return m_layerY; } // non-DOM extension + long pageX() const { return m_pageX; } // non-DOM extension + long pageY() const { return m_pageY; } // non-DOM extension + bool isDoubleClick() const { return m_isDoubleClick; } // non-DOM extension + bool ctrlKey() const { return m_ctrlKey; } + bool shiftKey() const { return m_shiftKey; } + bool altKey() const { return m_altKey; } + bool metaKey() const { return m_metaKey; } + unsigned short button() const { return m_button; } + NodeImpl *relatedTarget() const { return m_relatedTarget; } + + void computeLayerPos(); + + void initMouseEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + long detailArg, + long screenXArg, + long screenYArg, + long clientXArg, + long clientYArg, + bool ctrlKeyArg, + bool altKeyArg, + bool shiftKeyArg, + bool metaKeyArg, + unsigned short buttonArg, + const Node &relatedTargetArg); + virtual bool isMouseEvent() const; + + QMouseEvent *qEvent() const { return m_qevent; } +protected: + long m_screenX; + long m_screenY; + long m_clientX; + long m_clientY; + long m_layerX; + long m_layerY; + long m_pageX; + long m_pageY; + bool m_ctrlKey : 1; + bool m_altKey : 1; + bool m_shiftKey : 1; + bool m_metaKey : 1; + bool m_isDoubleClick : 1; + unsigned short m_button; + NodeImpl *m_relatedTarget; + QMouseEvent *m_qevent; +}; + + +class KeyEventBaseImpl : public UIEventImpl { +public: + // VirtualKeyCode + enum KeyCodes { + DOM_VK_UNDEFINED = 0x0, + DOM_VK_RIGHT_ALT = 0x12, + DOM_VK_LEFT_ALT = 0x12, + DOM_VK_LEFT_CONTROL = 0x11, + DOM_VK_RIGHT_CONTROL = 0x11, + DOM_VK_LEFT_SHIFT = 0x10, + DOM_VK_RIGHT_SHIFT = 0x10, + DOM_VK_META = 0x9D, + DOM_VK_BACK_SPACE = 0x08, + DOM_VK_CAPS_LOCK = 0x14, + DOM_VK_DELETE = 0x7F, + DOM_VK_END = 0x23, + DOM_VK_ENTER = 0x0D, + DOM_VK_ESCAPE = 0x1B, + DOM_VK_HOME = 0x24, + DOM_VK_NUM_LOCK = 0x90, + DOM_VK_PAUSE = 0x13, + DOM_VK_PRINTSCREEN = 0x9A, + DOM_VK_SCROLL_LOCK = 0x91, + DOM_VK_SPACE = 0x20, + DOM_VK_TAB = 0x09, + DOM_VK_LEFT = 0x25, + DOM_VK_RIGHT = 0x27, + DOM_VK_UP = 0x26, + DOM_VK_DOWN = 0x28, + DOM_VK_PAGE_DOWN = 0x22, + DOM_VK_PAGE_UP = 0x21, + DOM_VK_F1 = 0x70, + DOM_VK_F2 = 0x71, + DOM_VK_F3 = 0x72, + DOM_VK_F4 = 0x73, + DOM_VK_F5 = 0x74, + DOM_VK_F6 = 0x75, + DOM_VK_F7 = 0x76, + DOM_VK_F8 = 0x77, + DOM_VK_F9 = 0x78, + DOM_VK_F10 = 0x79, + DOM_VK_F11 = 0x7A, + DOM_VK_F12 = 0x7B, + DOM_VK_F13 = 0xF000, + DOM_VK_F14 = 0xF001, + DOM_VK_F15 = 0xF002, + DOM_VK_F16 = 0xF003, + DOM_VK_F17 = 0xF004, + DOM_VK_F18 = 0xF005, + DOM_VK_F19 = 0xF006, + DOM_VK_F20 = 0xF007, + DOM_VK_F21 = 0xF008, + DOM_VK_F22 = 0xF009, + DOM_VK_F23 = 0xF00A, + DOM_VK_F24 = 0xF00B + }; + + void initKeyBaseEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + unsigned long keyVal, + unsigned long virtKeyVal, + unsigned long modifiers); + + bool ctrlKey() const { return m_modifier & Qt::ControlButton; } + bool shiftKey() const { return m_modifier & Qt::ShiftButton; } + bool altKey() const { return m_modifier & Qt::AltButton; } + bool metaKey() const { return m_modifier & Qt::MetaButton; } + + bool inputGenerated() const { return m_virtKeyVal == 0; } + unsigned long keyVal() const { return m_keyVal; } + unsigned long virtKeyVal() const { return m_virtKeyVal; } + + QKeyEvent *qKeyEvent() const { if (!m_keyEvent) buildQKeyEvent(); return m_keyEvent; } + + //Legacy key stuff... + virtual int keyCode() const = 0; + virtual int charCode() const = 0; + + //### KDE4: remove these 2 + void initModifier(unsigned long modifierArg, bool valueArg); + bool checkModifier(unsigned long modifierArg); + + ~KeyEventBaseImpl(); + + //Returns true if the event was synthesized by client use of DOM + bool isSynthetic() const { return m_synthetic; } +protected: + KeyEventBaseImpl(): m_keyEvent(0), m_keyVal(0), m_virtKeyVal(0), m_modifier(0), m_synthetic(false) + { m_detail = 0; } + + KeyEventBaseImpl(EventId id, + bool canBubbleArg, + bool cancelableArg, + AbstractViewImpl *viewArg, + QKeyEvent *key); + + + mutable QKeyEvent *m_keyEvent; + unsigned long m_keyVal; //Unicode key value + unsigned long m_virtKeyVal; //Virtual key value for keys like arrows, Fn, etc. + + // bitfield containing state of modifiers. not part of the dom. + unsigned long m_modifier; + + bool m_synthetic; + + void buildQKeyEvent() const; //Construct a Qt key event from m_keyVal/m_virtKeyVal +}; + +class TextEventImpl : public KeyEventBaseImpl { +public: + TextEventImpl(); + + TextEventImpl(QKeyEvent* key, DOM::AbstractViewImpl* view); + + void initTextEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + const DOMString& text); + + virtual bool isTextInputEvent() const; + + //Legacy key stuff... + int keyCode() const; + int charCode() const; + + DOMString data() const { return m_outputString; } +private: + DOMString m_outputString; +}; + +class KeyboardEventImpl : public KeyEventBaseImpl { +public: + KeyboardEventImpl(); + KeyboardEventImpl(QKeyEvent* key, DOM::AbstractViewImpl* view); + + virtual bool isKeyboardEvent() const; + + enum KeyLocation { + DOM_KEY_LOCATION_STANDARD = 0x00, + DOM_KEY_LOCATION_LEFT = 0x01, + DOM_KEY_LOCATION_RIGHT = 0x02, + DOM_KEY_LOCATION_NUMPAD = 0x03 + }; + + //Legacy key stuff... + int keyCode() const; + int charCode() const; + + DOMString keyIdentifier() const; + unsigned long keyLocation() const { return m_keyLocation; } + + bool getModifierState(const DOMString& keyIdentifierArg) const; + + void initKeyboardEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + const DOMString &keyIdentifierArg, + unsigned long keyLocationArg, + const DOMString& modifiersList); + + //### KDE4: remove this, it's only for compatibility with + //the old TextEvent wrapper + void initKeyboardEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const AbstractView &viewArg, + unsigned long keyVal, + unsigned long virtKeyVal, + unsigned long modifiers, + unsigned long keyLocationArg) { + initKeyBaseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, + keyVal, virtKeyVal, modifiers); + m_keyLocation = keyLocationArg; + } +private: + unsigned long m_keyLocation; +}; + + + +class MutationEventImpl : public EventImpl { +// ### fire these during parsing (if necessary) +public: + MutationEventImpl(); + MutationEventImpl(EventId _id, + bool canBubbleArg, + bool cancelableArg, + const Node &relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg); + ~MutationEventImpl(); + + Node relatedNode() const { return m_relatedNode; } + DOMString prevValue() const { return m_prevValue; } + DOMString newValue() const { return m_newValue; } + DOMString attrName() const { return m_attrName; } + unsigned short attrChange() const { return m_attrChange; } + void initMutationEvent(const DOMString &typeArg, + bool canBubbleArg, + bool cancelableArg, + const Node &relatedNodeArg, + const DOMString &prevValueArg, + const DOMString &newValueArg, + const DOMString &attrNameArg, + unsigned short attrChangeArg); + virtual bool isMutationEvent() const; +protected: + NodeImpl *m_relatedNode; + DOMStringImpl *m_prevValue; + DOMStringImpl *m_newValue; + DOMStringImpl *m_attrName; + unsigned short m_attrChange; +}; + + +class RegisteredEventListener { +public: + RegisteredEventListener() : id(EventImpl::EventId(0)), useCapture(false), listener(0) {} + + RegisteredEventListener(EventImpl::EventId _id, EventListener *_listener, bool _useCapture) + : id(_id), useCapture(_useCapture), listener(_listener) { if (listener) listener->ref(); } + + ~RegisteredEventListener() { if (listener) listener->deref(); listener = 0; } + + bool operator==(const RegisteredEventListener &other) const + { return id == other.id && listener == other.listener && useCapture == other.useCapture; } + + + EventImpl::EventId id : 6; + bool useCapture; + EventListener *listener; + + RegisteredEventListener( const RegisteredEventListener &other ) : + id(other.id), useCapture(other.useCapture), listener(other.listener) + { if (listener) listener->ref(); } + + RegisteredEventListener & operator=( const RegisteredEventListener &other ) { + id = other.id; + useCapture = other.useCapture; + if (other.listener) + other.listener->ref(); + if (listener) + listener->deref(); + listener = other.listener; + return *this; + } +}; + + + +} //namespace +#endif diff --git a/khtml/xml/dom2_rangeimpl.cpp b/khtml/xml/dom2_rangeimpl.cpp new file mode 100644 index 000000000..b160ce23f --- /dev/null +++ b/khtml/xml/dom2_rangeimpl.cpp @@ -0,0 +1,1640 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2001-2003 Dirk Mueller (mueller@kde.org) + * (C) 2000 Gunnstein Lye (gunnstein@netcom.no) + * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) + * (C) 2001 Peter Kelly (pmk@post.com) + * Copyright (C) 2003 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. + */ + +#include "dom/dom_exception.h" +#include "dom_docimpl.h" +#include "dom2_rangeimpl.h" +#include "dom_textimpl.h" +#include "dom_xmlimpl.h" +#include "html/html_elementimpl.h" +#include "misc/htmltags.h" + +using namespace DOM; + + +RangeImpl::RangeImpl(DocumentImpl *_ownerDocument) +{ + m_ownerDocument = _ownerDocument; + m_ownerDocument->ref(); + m_startContainer = _ownerDocument; + m_startContainer->ref(); + m_endContainer = _ownerDocument; + m_endContainer->ref(); + m_startOffset = 0; + m_endOffset = 0; + m_detached = false; +} + +RangeImpl::RangeImpl(DocumentImpl *_ownerDocument, + NodeImpl *_startContainer, long _startOffset, + NodeImpl *_endContainer, long _endOffset) +{ + m_ownerDocument = _ownerDocument; + m_ownerDocument->ref(); + m_startContainer = _startContainer; + m_startContainer->ref(); + m_startOffset = _startOffset; + m_endContainer = _endContainer; + m_endContainer->ref(); + m_endOffset = _endOffset; + m_detached = false; +} + +RangeImpl::~RangeImpl() +{ + m_ownerDocument->deref(); + int exceptioncode = 0; + if (!m_detached) + detach(exceptioncode); +} + +NodeImpl *RangeImpl::startContainer(int &exceptioncode) const +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return m_startContainer; +} + +long RangeImpl::startOffset(int &exceptioncode) const +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return m_startOffset; +} + +NodeImpl *RangeImpl::endContainer(int &exceptioncode) const +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return m_endContainer; +} + +long RangeImpl::endOffset(int &exceptioncode) const +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return m_endOffset; +} + +NodeImpl *RangeImpl::commonAncestorContainer(int &exceptioncode) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + NodeImpl *com = commonAncestorContainer(m_startContainer,m_endContainer); + if (!com) // should never happen + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return com; +} + +NodeImpl *RangeImpl::commonAncestorContainer(NodeImpl *containerA, NodeImpl *containerB) +{ + NodeImpl *parentStart; + + for (parentStart = containerA; parentStart; parentStart = parentStart->parentNode()) { + NodeImpl *parentEnd = containerB; + while( parentEnd && (parentStart != parentEnd) ) + parentEnd = parentEnd->parentNode(); + + if(parentStart == parentEnd) break; + } + + return parentStart; +} + +bool RangeImpl::collapsed(int &exceptioncode) const +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return (m_startContainer == m_endContainer && m_startOffset == m_endOffset); +} + +void RangeImpl::setStart( NodeImpl *refNode, long offset, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeWOffset( refNode, offset, exceptioncode ); + if (exceptioncode) + return; + + setStartContainer(refNode); + m_startOffset = offset; + + // check if different root container + NodeImpl *endRootContainer = m_endContainer; + while (endRootContainer->parentNode()) + endRootContainer = endRootContainer->parentNode(); + NodeImpl *startRootContainer = m_startContainer; + while (startRootContainer->parentNode()) + startRootContainer = startRootContainer->parentNode(); + if (startRootContainer != endRootContainer) + collapse(true,exceptioncode); + // check if new start after end + else if (compareBoundaryPoints(m_startContainer,m_startOffset,m_endContainer,m_endOffset) > 0) + collapse(true,exceptioncode); +} + +void RangeImpl::setEnd( NodeImpl *refNode, long offset, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeWOffset( refNode, offset, exceptioncode ); + if (exceptioncode) + return; + + setEndContainer(refNode); + m_endOffset = offset; + + // check if different root container + NodeImpl *endRootContainer = m_endContainer; + while (endRootContainer->parentNode()) + endRootContainer = endRootContainer->parentNode(); + NodeImpl *startRootContainer = m_startContainer; + while (startRootContainer->parentNode()) + startRootContainer = startRootContainer->parentNode(); + if (startRootContainer != endRootContainer) + collapse(false,exceptioncode); + // check if new end before start + if (compareBoundaryPoints(m_startContainer,m_startOffset,m_endContainer,m_endOffset) > 0) + collapse(false,exceptioncode); +} + +void RangeImpl::collapse( bool toStart, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if( toStart ) // collapse to start + { + setEndContainer(m_startContainer); + m_endOffset = m_startOffset; + } + else // collapse to end + { + setStartContainer(m_endContainer); + m_startOffset = m_endOffset; + } +} + +short RangeImpl::compareBoundaryPoints( Range::CompareHow how, RangeImpl *sourceRange, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + if (!sourceRange) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; + } + + NodeImpl *thisCont = commonAncestorContainer(exceptioncode); + NodeImpl *sourceCont = sourceRange->commonAncestorContainer(exceptioncode); + if (exceptioncode) + return 0; + + if (thisCont->getDocument() != sourceCont->getDocument()) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return 0; + } + + NodeImpl *thisTop = thisCont; + NodeImpl *sourceTop = sourceCont; + while (thisTop->parentNode()) + thisTop = thisTop->parentNode(); + while (sourceTop->parentNode()) + sourceTop = sourceTop->parentNode(); + if (thisTop != sourceTop) { // in different DocumentFragments + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return 0; + } + + switch(how) + { + case Range::START_TO_START: + return compareBoundaryPoints( m_startContainer, m_startOffset, + sourceRange->startContainer(exceptioncode), sourceRange->startOffset(exceptioncode) ); + break; + case Range::START_TO_END: + return compareBoundaryPoints( m_startContainer, m_startOffset, + sourceRange->endContainer(exceptioncode), sourceRange->endOffset(exceptioncode) ); + break; + case Range::END_TO_END: + return compareBoundaryPoints( m_endContainer, m_endOffset, + sourceRange->endContainer(exceptioncode), sourceRange->endOffset(exceptioncode) ); + break; + case Range::END_TO_START: + return compareBoundaryPoints( m_endContainer, m_endOffset, + sourceRange->startContainer(exceptioncode), sourceRange->startOffset(exceptioncode) ); + break; + default: + exceptioncode = DOMException::SYNTAX_ERR; + return 0; + } +} + +short RangeImpl::compareBoundaryPoints( NodeImpl *containerA, long offsetA, NodeImpl *containerB, long offsetB ) +{ + // see DOM2 traversal & range section 2.5 + + // case 1: both points have the same container + if( containerA == containerB ) + { + if( offsetA == offsetB ) return 0; // A is equal to B + if( offsetA < offsetB ) return -1; // A is before B + else return 1; // A is after B + } + + // case 2: node C (container B or an ancestor) is a child node of A + NodeImpl *c = containerB; + while (c && c->parentNode() != containerA) + c = c->parentNode(); + if (c) { + int offsetC = 0; + NodeImpl* n = containerA->firstChild(); + while (n != c) { + offsetC++; + n = n->nextSibling(); + } + + if( offsetA <= offsetC ) return -1; // A is before B + else return 1; // A is after B + } + + // case 3: node C (container A or an ancestor) is a child node of B + c = containerA; + while (c && c->parentNode() != containerB) + c = c->parentNode(); + if (c) { + int offsetC = 0; + NodeImpl* n = containerB->firstChild(); + while (n != c) { + offsetC++; + n = n->nextSibling(); + } + + if( offsetC < offsetB ) return -1; // A is before B + else return 1; // A is after B + } + + // case 4: containers A & B are siblings, or children of siblings + // ### we need to do a traversal here instead + NodeImpl *cmnRoot = commonAncestorContainer(containerA,containerB); + if (!cmnRoot) return -1; // Whatever... + NodeImpl *childA = containerA; + while (childA->parentNode() != cmnRoot) + childA = childA->parentNode(); + NodeImpl *childB = containerB; + while (childB->parentNode() != cmnRoot) + childB = childB->parentNode(); + + NodeImpl *n = cmnRoot->firstChild(); + int i = 0; + int childAOffset = -1; + int childBOffset = -1; + while (childAOffset < 0 || childBOffset < 0) { + if (n == childA) + childAOffset = i; + if (n == childB) + childBOffset = i; + n = n->nextSibling(); + i++; + } + + if( childAOffset == childBOffset ) return 0; // A is equal to B + if( childAOffset < childBOffset ) return -1; // A is before B + else return 1; // A is after B +} + +bool RangeImpl::boundaryPointsValid( ) +{ + short valid = compareBoundaryPoints( m_startContainer, m_startOffset, + m_endContainer, m_endOffset ); + if( valid == 1 ) return false; + else return true; + +} + +void RangeImpl::deleteContents( int &exceptioncode ) { + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + checkDeleteExtract(exceptioncode); + if (exceptioncode) + return; + + processContents(DELETE_CONTENTS,exceptioncode); +} + +DocumentFragmentImpl *RangeImpl::processContents ( ActionType action, int &exceptioncode ) +{ + // ### when mutation events are implemented, we will have to take into account + // situations where the tree is being transformed while we delete - ugh! + + // ### perhaps disable node deletion notification for this range while we do this? + + if (collapsed(exceptioncode)) + return 0; + if (exceptioncode) + return 0; + + NodeImpl *cmnRoot = commonAncestorContainer(exceptioncode); + if (exceptioncode) + return 0; + + // what is the highest node that partially selects the start of the range? + NodeImpl *partialStart = 0; + if (m_startContainer != cmnRoot) { + partialStart = m_startContainer; + while (partialStart->parentNode() != cmnRoot) + partialStart = partialStart->parentNode(); + } + + // what is the highest node that partially selects the end of the range? + NodeImpl *partialEnd = 0; + if (m_endContainer != cmnRoot) { + partialEnd = m_endContainer; + while (partialEnd->parentNode() != cmnRoot) + partialEnd = partialEnd->parentNode(); + } + + DocumentFragmentImpl *fragment = 0; + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) + fragment = new DocumentFragmentImpl(m_ownerDocument); + + // Simple case: the start and end containers are the same. We just grab + // everything >= start offset and < end offset + if (m_startContainer == m_endContainer) { + if(m_startContainer->nodeType() == Node::TEXT_NODE || + m_startContainer->nodeType() == Node::CDATA_SECTION_NODE || + m_startContainer->nodeType() == Node::COMMENT_NODE) { + + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + CharacterDataImpl *c = static_cast<CharacterDataImpl*>(m_startContainer->cloneNode(true)); + c->deleteData(m_endOffset,static_cast<CharacterDataImpl*>(m_startContainer)->length()-m_endOffset,exceptioncode); + c->deleteData(0,m_startOffset,exceptioncode); + fragment->appendChild(c,exceptioncode); + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast<CharacterDataImpl*>(m_startContainer)->deleteData(m_startOffset,m_endOffset-m_startOffset,exceptioncode); + } + else if (m_startContainer->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) { + // ### operate just on data ? + } + else { + NodeImpl *n = m_startContainer->firstChild(); + unsigned long i; + for(i = 0; i < m_startOffset; i++) // skip until m_startOffset + n = n->nextSibling(); + while (n && i < m_endOffset) { // delete until m_endOffset + NodeImpl *next = n->nextSibling(); + if (action == EXTRACT_CONTENTS) + fragment->appendChild(n,exceptioncode); // will remove n from its parent + else if (action == CLONE_CONTENTS) + fragment->appendChild(n->cloneNode(true),exceptioncode); + else + m_startContainer->removeChild(n,exceptioncode); + n = next; + i++; + } + } + collapse(true,exceptioncode); + return fragment; + } + + // Complex case: Start and end containers are different. + // There are three possiblities here: + // 1. Start container == cmnRoot (End container must be a descendant) + // 2. End container == cmnRoot (Start container must be a descendant) + // 3. Neither is cmnRoot, they are both descendants + // + // In case 3, we grab everything after the start (up until a direct child + // of cmnRoot) into leftContents, and everything before the end (up until + // a direct child of cmnRoot) into rightContents. Then we process all + // cmnRoot children between leftContents and rightContents + // + // In case 1 or 2, we skip either processing of leftContents or rightContents, + // in which case the last lot of nodes either goes from the first or last + // child of cmnRoot. + // + // These are deleted, cloned, or extracted (i.e. both) depending on action. + + NodeImpl *leftContents = 0; + if (m_startContainer != cmnRoot) { + // process the left-hand side of the range, up until the last ancestor of + // m_startContainer before cmnRoot + if(m_startContainer->nodeType() == Node::TEXT_NODE || + m_startContainer->nodeType() == Node::CDATA_SECTION_NODE || + m_startContainer->nodeType() == Node::COMMENT_NODE) { + + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + CharacterDataImpl *c = static_cast<CharacterDataImpl*>(m_startContainer->cloneNode(true)); + c->deleteData(0,m_startOffset,exceptioncode); + leftContents = c; + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast<CharacterDataImpl*>(m_startContainer)->deleteData( + m_startOffset,static_cast<CharacterDataImpl*>(m_startContainer)->length()-m_startOffset,exceptioncode); + } + else if (m_startContainer->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) { + // ### operate just on data ? + // leftContents = ... + } + else { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) + leftContents = m_startContainer->cloneNode(false); + NodeImpl *n = m_startContainer->firstChild(); + unsigned long i; + for(i = 0; i < m_startOffset; i++) // skip until m_startOffset + n = n->nextSibling(); + while (n) { // process until end + NodeImpl *next = n->nextSibling(); + if (action == EXTRACT_CONTENTS) + leftContents->appendChild(n,exceptioncode); // will remove n from m_startContainer + else if (action == CLONE_CONTENTS) + leftContents->appendChild(n->cloneNode(true),exceptioncode); + else + m_startContainer->removeChild(n,exceptioncode); + n = next; + } + } + + NodeImpl *leftParent = m_startContainer->parentNode(); + NodeImpl *n = m_startContainer->nextSibling(); + for (; leftParent != cmnRoot; leftParent = leftParent->parentNode()) { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + NodeImpl *leftContentsParent = leftParent->cloneNode(false); + leftContentsParent->appendChild(leftContents,exceptioncode); + leftContents = leftContentsParent; + } + + NodeImpl *next; + for (; n; n = next ) { + next = n->nextSibling(); + if (action == EXTRACT_CONTENTS) + leftContents->appendChild(n,exceptioncode); // will remove n from leftParent + else if (action == CLONE_CONTENTS) + leftContents->appendChild(n->cloneNode(true),exceptioncode); + else + leftParent->removeChild(n,exceptioncode); + } + n = leftParent->nextSibling(); + } + } + + NodeImpl *rightContents = 0; + if (m_endContainer != cmnRoot) { + // delete the right-hand side of the range, up until the last ancestor of + // m_endContainer before cmnRoot + if(m_endContainer->nodeType() == Node::TEXT_NODE || + m_endContainer->nodeType() == Node::CDATA_SECTION_NODE || + m_endContainer->nodeType() == Node::COMMENT_NODE) { + + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + CharacterDataImpl *c = static_cast<CharacterDataImpl*>(m_endContainer->cloneNode(true)); + c->deleteData(m_endOffset,static_cast<CharacterDataImpl*>(m_endContainer)->length()-m_endOffset,exceptioncode); + rightContents = c; + } + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) + static_cast<CharacterDataImpl*>(m_endContainer)->deleteData(0,m_endOffset,exceptioncode); + } + else if (m_startContainer->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) { + // ### operate just on data ? + // rightContents = ... + } + else { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) + rightContents = m_endContainer->cloneNode(false); + NodeImpl *n = m_endContainer->firstChild(); + unsigned long i; + for(i = 0; i+1 < m_endOffset; i++) // skip to m_endOffset + n = n->nextSibling(); + NodeImpl *prev; + for (; n; n = prev ) { + prev = n->previousSibling(); + if (action == EXTRACT_CONTENTS) + rightContents->insertBefore(n,rightContents->firstChild(),exceptioncode); // will remove n from its parent + else if (action == CLONE_CONTENTS) + rightContents->insertBefore(n->cloneNode(true),rightContents->firstChild(),exceptioncode); + else + m_endContainer->removeChild(n,exceptioncode); + } + } + + NodeImpl *rightParent = m_endContainer->parentNode(); + NodeImpl *n = m_endContainer->previousSibling(); + for (; rightParent != cmnRoot; rightParent = rightParent->parentNode()) { + if (action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) { + NodeImpl *rightContentsParent = rightParent->cloneNode(false); + rightContentsParent->appendChild(rightContents,exceptioncode); + rightContents = rightContentsParent; + } + + NodeImpl *prev; + for (; n; n = prev ) { + prev = n->previousSibling(); + if (action == EXTRACT_CONTENTS) + rightContents->insertBefore(n,rightContents->firstChild(),exceptioncode); // will remove n from its parent + else if (action == CLONE_CONTENTS) + rightContents->insertBefore(n->cloneNode(true),rightContents->firstChild(),exceptioncode); + else + rightParent->removeChild(n,exceptioncode); + + } + n = rightParent->previousSibling(); + } + } + + // delete all children of cmnRoot between the start and end container + + NodeImpl *processStart; // child of cmnRooot + if (m_startContainer == cmnRoot) { + unsigned long i; + processStart = m_startContainer->firstChild(); + for (i = 0; i < m_startOffset; i++) + processStart = processStart->nextSibling(); + } + else { + processStart = m_startContainer; + while (processStart->parentNode() != cmnRoot) + processStart = processStart->parentNode(); + processStart = processStart->nextSibling(); + } + NodeImpl *processEnd; // child of cmnRooot + if (m_endContainer == cmnRoot) { + unsigned long i; + processEnd = m_endContainer->firstChild(); + for (i = 0; i < m_endOffset; i++) + processEnd = processEnd->nextSibling(); + } + else { + processEnd = m_endContainer; + while (processEnd->parentNode() != cmnRoot) + processEnd = processEnd->parentNode(); + } + + // Now add leftContents, stuff in between, and rightContents to the fragment + // (or just delete the stuff in between) + + if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && leftContents) + fragment->appendChild(leftContents,exceptioncode); + + NodeImpl *next; + NodeImpl *n; + if (processStart) { + for (n = processStart; n && n != processEnd; n = next) { + next = n->nextSibling(); + + if (action == EXTRACT_CONTENTS) + fragment->appendChild(n,exceptioncode); // will remove from cmnRoot + else if (action == CLONE_CONTENTS) + fragment->appendChild(n->cloneNode(true),exceptioncode); + else + cmnRoot->removeChild(n,exceptioncode); + } + } + + if ((action == EXTRACT_CONTENTS || action == CLONE_CONTENTS) && rightContents) + fragment->appendChild(rightContents,exceptioncode); + + // collapse to the proper position - see spec section 2.6 + if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) { + if (!partialStart && !partialEnd) + collapse(true,exceptioncode); + else if (partialStart) { + setStartContainer(partialStart->parentNode()); + setEndContainer(partialStart->parentNode()); + m_startOffset = m_endOffset = partialStart->nodeIndex()+1; + } + else if (partialEnd) { + setStartContainer(partialEnd->parentNode()); + setEndContainer(partialEnd->parentNode()); + m_startOffset = m_endOffset = partialEnd->nodeIndex(); + } + } + return fragment; +} + + +DocumentFragmentImpl *RangeImpl::extractContents( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + checkDeleteExtract(exceptioncode); + if (exceptioncode) + return 0; + + return processContents(EXTRACT_CONTENTS,exceptioncode); +} + +DocumentFragmentImpl *RangeImpl::cloneContents( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return processContents(CLONE_CONTENTS,exceptioncode); +} + +void RangeImpl::insertNode( NodeImpl *newNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if an ancestor container of either boundary-point of + // the Range is read-only. + NodeImpl *n = m_startContainer; + while (n && !n->isReadOnly()) + n = n->parentNode(); + if (n) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + n = m_endContainer; + while (n && !n->isReadOnly()) + n = n->parentNode(); + if (n) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + // WRONG_DOCUMENT_ERR: Raised if newParent and the container of the start of the Range were + // not created from the same document. + if (newNode->getDocument() != m_startContainer->getDocument()) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + + // HIERARCHY_REQUEST_ERR: Raised if the container of the start of the Range is of a type that + // does not allow children of the type of newNode or if newNode is an ancestor of the container. + + // an extra one here - if a text node is going to split, it must have a parent to insert into + if (m_startContainer->nodeType() == Node::TEXT_NODE && !m_startContainer->parentNode()) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + + // In the case where the container is a text node, we check against the container's parent, because + // text nodes get split up upon insertion. + NodeImpl *checkAgainst; + if (m_startContainer->nodeType() == Node::TEXT_NODE) + checkAgainst = m_startContainer->parentNode(); + else + checkAgainst = m_startContainer; + + if (newNode->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) { + // check each child node, not the DocumentFragment itself + NodeImpl *c; + for (c = newNode->firstChild(); c; c = c->nextSibling()) { + if (!checkAgainst->childTypeAllowed(c->nodeType())) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + } + else { + if (!checkAgainst->childTypeAllowed(newNode->nodeType())) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + + for (n = m_startContainer; n; n = n->parentNode()) { + if (n == newNode) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + + // INVALID_NODE_TYPE_ERR: Raised if newNode is an Attr, Entity, Notation, or Document node. + if( newNode->nodeType() == Node::ATTRIBUTE_NODE || + newNode->nodeType() == Node::ENTITY_NODE || + newNode->nodeType() == Node::NOTATION_NODE || + newNode->nodeType() == Node::DOCUMENT_NODE) { + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + + if( m_startContainer->nodeType() == Node::TEXT_NODE || + m_startContainer->nodeType() == Node::CDATA_SECTION_NODE ) + { + TextImpl *newText = static_cast<TextImpl*>(m_startContainer)->splitText(m_startOffset,exceptioncode); + if (exceptioncode) + return; + m_startContainer->parentNode()->insertBefore( newNode, newText, exceptioncode ); + } + else { + m_startContainer->insertBefore( newNode, m_startContainer->childNode( m_startOffset ), exceptioncode ); + } +} + +DOMString RangeImpl::toString( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return DOMString(); + } + + DOMString text = ""; + NodeImpl *n = m_startContainer; + + /* This function converts a dom range to the plain text string that the user would see in this + * portion of rendered html. + * + * There are several ways ranges can be used. + * + * The simplest is the start and endContainer is a text node. The start and end offset is the + * number of characters into the text to remove/truncate. + * + * The next case is the start and endContainer is, well, a container, such a P tag or DIV tag. + * In this case the start and end offset is the number of children into the container to start + * from and end at. + * + * The other cases are different arrangements of the first two. + * + * psuedo code: + * + * if start container is not text: + * count through the children to find where we start (m_startOffset children) + * + * loop from the start position: + * if the current node is text, add the text to our variable 'text', truncating/removing if at the end/start. + * + * if the node has children, step to the first child. + * if the node has no children but does have siblings, step to the next sibling + * until we find a sibling, go to next the parent but: + * make sure this sibling isn't past the end of where we are supposed to go. (position > endOffset and the parent is the endContainer) + * + */ + + + if( m_startContainer == m_endContainer && m_startOffset >= m_endOffset) + return text; + + + if(n->firstChild()) { + n = n->firstChild(); + int current_offset = m_startOffset; + while(current_offset-- && n) { + n = n->nextSibling(); + } + } + + while(n) { + if(n->nodeType() == DOM::Node::TEXT_NODE || + n->nodeType() == DOM::Node::CDATA_SECTION_NODE) { + + DOMString str; + str = static_cast<TextImpl *>(n)->string(); + if( n == m_endContainer || n == m_startContainer) + str = str.copy(); //copy if we are going to modify. + + if (n == m_endContainer) + str.truncate(m_endOffset); + if (n == m_startContainer) + str.remove(0,m_startOffset); + text += str; + if (n == m_endContainer) + break; + } + + + NodeImpl *next = n->firstChild(); + if(!next) + next = n->nextSibling(); + + while( !next && n->parentNode() ) { + if (n == m_endContainer) return text; + n = n->parentNode(); + if (n == m_endContainer) return text; + next = n->nextSibling(); + } + + if(n->parentNode() == m_endContainer) { + if(!next) break; + unsigned long current_offset = 0; + NodeImpl *it = n; + while((it = it->previousSibling())) ++current_offset; + if(current_offset >= m_endOffset) { + break; + } + } + + n = next; + } + return text; +} + +DOMString RangeImpl::toHTML( int &exceptioncode ) +{ + bool hasHtmlTag = false; + bool hasBodyTag = false; + //FIXME: What is this section of code below exactly? Do I want it here? + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return DOMString(); + } + DOMString text = ""; + NodeImpl *n = m_startContainer; + int num_tables=0; + bool in_li = false; //whether we have an li in the text, without an ol/ul + int depth_difference = 0; + int lowest_depth_difference = 0; + + if( m_startContainer == m_endContainer && m_startOffset >= m_endOffset) + return text; + + while(n) { + /* First, we could have an tag <tagname key=value>otherstuff</tagname> */ + if(n->nodeType() == DOM::Node::ELEMENT_NODE) { + int elementId = static_cast<ElementImpl *>(n)->id(); + if(elementId == ID_TABLE) num_tables++; + if(elementId == ID_BODY) hasBodyTag = true; + if(elementId == ID_HTML) hasHtmlTag = true; + if(elementId == ID_LI) in_li=true; + if(num_tables==0 && ( elementId == ID_TD || elementId == ID_TR || elementId == ID_TH || elementId == ID_TBODY || elementId == ID_TFOOT || elementId == ID_THEAD)) num_tables++; + if(!( !n->hasChildNodes() && (elementId == ID_H1 || elementId == ID_H2 || elementId == ID_H3 || elementId == ID_H4 || elementId ==ID_H5))) { //Don't add <h1/> etc. Just skip these nodes just to make the output html a bit nicer. + text += static_cast<ElementImpl *>(n)->openTagStartToString(true /*safely expand img urls*/); // adds "<tagname key=value" + if(n->hasChildNodes()) { + depth_difference++; + text += ">"; + } else { + text += "/>"; + } + } + } else + if(n->nodeType() == DOM::Node::TEXT_NODE || + n->nodeType() == DOM::Node::CDATA_SECTION_NODE) { + if(n->nodeType() == DOM::Node::CDATA_SECTION_NODE) text += "<![CDATA[ "; + long long startOffset = (n == m_startContainer)?(long long)m_startOffset:-1; + long long endOffset = (n == m_endContainer)?(long long) m_endOffset:-1; + text += static_cast<TextImpl *>(n)->toString(startOffset, endOffset); //Note this should always work since CDataImpl inherits TextImpl + if(n->nodeType() == DOM::Node::CDATA_SECTION_NODE) text += " ]]>"; + if(n == m_endContainer) { + break; + } + } + if(n->parentNode() == m_endContainer && !n->nextSibling()) { + break; + } + + //if (n == m_endContainer) break; + NodeImpl *next = n->firstChild(); + if(next) { + if(n == m_startContainer) { + //This is the start of our selection, so we have to move to where we have started selecting. + //For example, if 'n' is "hello <img src='hello.png'> how are you? <img src='goodbye.png'>" + //then this has four children. If our selection started on the image, then we need to start from there. + unsigned long current_offset = 0; + while(current_offset < m_startOffset && next) { + next = next->nextSibling(); + ++current_offset; + } + } + } else { + next = n->nextSibling(); + + if(n->parentNode() == m_endContainer) { + unsigned long current_offset = 1; + NodeImpl *it = n; + while((it = it->previousSibling())) ++current_offset; + + if(current_offset >= m_endOffset) { + break; + } + } + } + + while( !next && n->parentNode() ) { + n = n->parentNode(); + if(n->nodeType() == DOM::Node::ELEMENT_NODE) { + text += "</"; + text += static_cast<ElementImpl *>(n)->tagName(); + int elementId = static_cast<ElementImpl *>(n)->id(); + if(elementId == ID_TABLE) num_tables--; + depth_difference--; + if(lowest_depth_difference > depth_difference) lowest_depth_difference=depth_difference; + if(num_tables==0 && ( elementId == ID_TD || elementId == ID_TR || elementId == ID_TH || elementId == ID_TBODY || elementId == ID_TFOOT || elementId == ID_THEAD)) num_tables--; + if(elementId == ID_OL || elementId == ID_UL) in_li=false; + text += ">"; + } + next = n->nextSibling(); + } + n = next; + } + + //We have the html in the selection. But now we need to properly add the opening and closing tags. + //For example say we have: "Hello <b>Mr. John</b> How are you?" and we select "John" or even + //"John</b> How" and copy. We want to return "<b>John</b>" and "<b>John</b> How" respectively + + //To do this, we need to go up the tree from the start, and prepend those tags. + //Imagine our selection was this: + // + // hello</b></p><p>there + // + // The difference in depths between the start and end is -1, and the lowest depth + // difference from the starting point is -2 + // + // So from the start of the selection, we want to go down to the lowest_depth_difference + // and prepend those tags. (<p><b>) + // + // From the end of the selection, we want to also go down to the lowest_depth_difference. + // We know the depth of the end of the selection - i.e. depth_difference. + // + // + n = m_startContainer; + int startdepth = 0; //by definition - we are counting from zero. + while((n = n->parentNode()) && startdepth>lowest_depth_difference) { + if(n->nodeType() == DOM::Node::ELEMENT_NODE) { //This should always be true.. right? + switch (static_cast<ElementImpl *>(n)->id()) { + case ID_TABLE: + num_tables--; + break; + case ID_BODY: + hasBodyTag = true; + break; + case ID_HTML: + hasHtmlTag = true; + break; + case ID_LI: + in_li = true; + break; + } + text = static_cast<ElementImpl *>(n)->openTagStartToString(true /*expand img urls*/)+">" +text; // prepends "<tagname key=value>" + } + startdepth--; + } + n = m_endContainer; + while( depth_difference>lowest_depth_difference && (n = n->parentNode())) { + if(n->nodeType() == DOM::Node::ELEMENT_NODE) { //This should always be true.. right? + switch (static_cast<ElementImpl *>(n)->id()) { + case ID_TABLE: + num_tables++; + break; + case ID_OL: + case ID_UL: + in_li=false; + break; + } + text += "</"; + text += static_cast<ElementImpl *>(n)->tagName(); + text += ">"; + } + depth_difference--; + } + + // Now our text string is the same depth on both sides, with nothing lower (in other words all the + // tags in it match up.) This also means that the end value for n in the first loop is a sibling of the + // end value for n in the second loop. + // + // We now need to go down the tree, and for certain tags, add them in on both ends of the text. + // For example, if have: "<b>hello</b>" and we select "ll", then we want to go down the tree and + // add "<b>" and "</b>" to it, to produce "<b>ll</b>". + // + // I just guessed at which tags you'd want to keep (bold, italic etc) and which you wouldn't (tables etc). + // It's just wild guessing. feel free to change. + // + // Note we can carry on with the value of n + if(n) { + while((n = n->parentNode())) { + if(n->nodeType() == DOM::Node::ELEMENT_NODE) { //This should always be true.. right? + int elementId = static_cast<ElementImpl *>(n)->id(); + switch (elementId) { + case ID_TABLE: + case ID_TD: + case ID_TR: + case ID_TH: + case ID_TBODY: + case ID_TFOOT: + case ID_THEAD: + if(num_tables>0) { + if(elementId == ID_TABLE) num_tables--; + text = static_cast<ElementImpl *>(n)->openTagStartToString(true /*expand img urls*/)+">" +text; + text += "</"; + text += static_cast<ElementImpl *>(n)->tagName(); + text += ">"; + + } + break; + + case ID_LI: + if(!in_li) break; + text = static_cast<ElementImpl *>(n)->openTagStartToString(true /*expand img urls*/)+">" +text; + text += "</"; + text += static_cast<ElementImpl *>(n)->tagName(); + text += ">"; + break; + + case ID_UL: + case ID_OL: + if(!in_li) break; + in_li = false; + case ID_B: + case ID_I: + case ID_U: + case ID_FONT: + case ID_S: + case ID_STRONG: + case ID_STRIKE: + case ID_DEL: + case ID_A: + case ID_H1: + case ID_H2: + case ID_H3: + case ID_H4: + case ID_H5: + //should small, etc be here? so hard to decide. this is such a hack :( + //There's probably tons of others you'd want here. + text = static_cast<ElementImpl *>(n)->openTagStartToString(true /*expand img urls*/)+">" +text; + text += "</"; + text += static_cast<ElementImpl *>(n)->tagName(); + text += ">"; + break; + } + } + } + } + + + if(!hasBodyTag) text = DOMString("<body>") + text + "</body>"; + else if(!hasHtmlTag) { + text = DOMString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" + "<head>\n" + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" + "<meta name=\"Generator\" content=\"KHTML, the KDE Web Page Viewer\" />\n" + "</head>\n") + + text + + "</html>"; + } + text = DOMString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n") + text; + + return text; + +} + +DocumentFragment RangeImpl::createContextualFragment ( const DOMString &html, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return DocumentFragment(); + } + + if (! m_startContainer->isHTMLElement()) { + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + return DocumentFragment(); + } + + HTMLElementImpl *e = static_cast<HTMLElementImpl *>(m_startContainer); + DocumentFragment fragment = e->createContextualFragment(html); + if (fragment.isNull()) { + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + return DocumentFragment(); + } + + return fragment; +} + + +void RangeImpl::detach( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (m_startContainer) + m_startContainer->deref(); + m_startContainer = 0; + if (m_endContainer) + m_endContainer->deref(); + m_endContainer = 0; + m_detached = true; +} + +bool RangeImpl::isDetached() const +{ + return m_detached; +} + +void RangeImpl::checkNodeWOffset( NodeImpl *n, int offset, int &exceptioncode) const +{ + if( offset < 0 ) { + exceptioncode = DOMException::INDEX_SIZE_ERR; + } + + switch (n->nodeType()) { + case Node::ENTITY_NODE: + case Node::NOTATION_NODE: + case Node::DOCUMENT_TYPE_NODE: + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + break; + case Node::TEXT_NODE: + case Node::COMMENT_NODE: + case Node::CDATA_SECTION_NODE: + if ( (unsigned long)offset > static_cast<CharacterDataImpl*>(n)->length() ) + exceptioncode = DOMException::INDEX_SIZE_ERR; + break; + case Node::PROCESSING_INSTRUCTION_NODE: + // ### are we supposed to check with just data or the whole contents? + if ( (unsigned long)offset > static_cast<ProcessingInstructionImpl*>(n)->data().length() ) + exceptioncode = DOMException::INDEX_SIZE_ERR; + break; + default: + if ( (unsigned long)offset > n->childNodeCount() ) + exceptioncode = DOMException::INDEX_SIZE_ERR; + break; + } +} + +void RangeImpl::checkNodeBA( NodeImpl *n, int &exceptioncode ) const +{ + // INVALID_NODE_TYPE_ERR: Raised if the root container of refNode is not an + // Attr, Document or DocumentFragment node or if refNode is a Document, + // DocumentFragment, Attr, Entity, or Notation node. + NodeImpl *root = n; + while (root->parentNode()) + root = root->parentNode(); + if (!(root->nodeType() == Node::ATTRIBUTE_NODE || + root->nodeType() == Node::DOCUMENT_NODE || + root->nodeType() == Node::DOCUMENT_FRAGMENT_NODE)) { + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + + if( n->nodeType() == Node::DOCUMENT_NODE || + n->nodeType() == Node::DOCUMENT_FRAGMENT_NODE || + n->nodeType() == Node::ATTRIBUTE_NODE || + n->nodeType() == Node::ENTITY_NODE || + n->nodeType() == Node::NOTATION_NODE ) + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + +} + +RangeImpl *RangeImpl::cloneRange(int &exceptioncode) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + return new RangeImpl(m_ownerDocument,m_startContainer,m_startOffset,m_endContainer,m_endOffset); +} + +void RangeImpl::setStartAfter( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeBA( refNode, exceptioncode ); + if (exceptioncode) + return; + + setStart( refNode->parentNode(), refNode->nodeIndex()+1, exceptioncode ); +} + +void RangeImpl::setEndBefore( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeBA( refNode, exceptioncode ); + if (exceptioncode) + return; + + setEnd( refNode->parentNode(), refNode->nodeIndex(), exceptioncode ); +} + +void RangeImpl::setEndAfter( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeBA( refNode, exceptioncode ); + if (exceptioncode) + return; + + setEnd( refNode->parentNode(), refNode->nodeIndex()+1, exceptioncode ); + +} + +void RangeImpl::selectNode( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + // INVALID_NODE_TYPE_ERR: Raised if an ancestor of refNode is an Entity, Notation or + // DocumentType node or if refNode is a Document, DocumentFragment, Attr, Entity, or Notation + // node. + NodeImpl *anc; + for (anc = refNode->parentNode(); anc; anc = anc->parentNode()) { + if (anc->nodeType() == Node::ENTITY_NODE || + anc->nodeType() == Node::NOTATION_NODE || + anc->nodeType() == Node::DOCUMENT_TYPE_NODE) { + + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + } + + if (refNode->nodeType() == Node::DOCUMENT_NODE || + refNode->nodeType() == Node::DOCUMENT_FRAGMENT_NODE || + refNode->nodeType() == Node::ATTRIBUTE_NODE || + refNode->nodeType() == Node::ENTITY_NODE || + refNode->nodeType() == Node::NOTATION_NODE) { + + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + + setStartBefore( refNode, exceptioncode ); + if (exceptioncode) + return; + setEndAfter( refNode, exceptioncode ); +} + +void RangeImpl::selectNodeContents( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + // INVALID_NODE_TYPE_ERR: Raised if refNode or an ancestor of refNode is an Entity, Notation + // or DocumentType node. + NodeImpl *n; + for (n = refNode; n; n = n->parentNode()) { + if (n->nodeType() == Node::ENTITY_NODE || + n->nodeType() == Node::NOTATION_NODE || + n->nodeType() == Node::DOCUMENT_TYPE_NODE) { + + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + } + + setStartContainer(refNode); + m_startOffset = 0; + setEndContainer(refNode); + m_endOffset = refNode->childNodeCount(); +} + +void RangeImpl::surroundContents( NodeImpl *newParent, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if( !newParent ) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + // INVALID_NODE_TYPE_ERR: Raised if node is an Attr, Entity, DocumentType, Notation, + // Document, or DocumentFragment node. + if( newParent->nodeType() == Node::ATTRIBUTE_NODE || + newParent->nodeType() == Node::ENTITY_NODE || + newParent->nodeType() == Node::NOTATION_NODE || + newParent->nodeType() == Node::DOCUMENT_TYPE_NODE || + newParent->nodeType() == Node::DOCUMENT_NODE || + newParent->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) { + exceptioncode = RangeException::INVALID_NODE_TYPE_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if an ancestor container of either boundary-point of + // the Range is read-only. + if (readOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + NodeImpl *n = m_startContainer; + while (n && !n->isReadOnly()) + n = n->parentNode(); + if (n) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + n = m_endContainer; + while (n && !n->isReadOnly()) + n = n->parentNode(); + if (n) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + // WRONG_DOCUMENT_ERR: Raised if newParent and the container of the start of the Range were + // not created from the same document. + if (newParent->getDocument() != m_startContainer->getDocument()) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + // HIERARCHY_REQUEST_ERR: Raised if the container of the start of the Range is of a type that + // does not allow children of the type of newParent or if newParent is an ancestor of the container + // or if node would end up with a child node of a type not allowed by the type of node. + if (!m_startContainer->childTypeAllowed(newParent->nodeType())) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + + for (n = m_startContainer; n; n = n->parentNode()) { + if (n == newParent) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + + // ### check if node would end up with a child node of a type not allowed by the type of node + + // BAD_BOUNDARYPOINTS_ERR: Raised if the Range partially selects a non-text node. + if (m_startContainer->nodeType() != Node::TEXT_NODE && + m_startContainer->nodeType() != Node::COMMENT_NODE && + m_startContainer->nodeType() != Node::CDATA_SECTION_NODE && + m_startContainer->nodeType() != Node::PROCESSING_INSTRUCTION_NODE) { + + if (m_startOffset > 0 && m_startOffset < m_startContainer->childNodeCount()) { + exceptioncode = RangeException::BAD_BOUNDARYPOINTS_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + } + + if (m_endContainer->nodeType() != Node::TEXT_NODE && + m_endContainer->nodeType() != Node::COMMENT_NODE && + m_endContainer->nodeType() != Node::CDATA_SECTION_NODE && + m_endContainer->nodeType() != Node::PROCESSING_INSTRUCTION_NODE) { + + if (m_endOffset > 0 && m_endOffset < m_endContainer->childNodeCount()) { + exceptioncode = RangeException::BAD_BOUNDARYPOINTS_ERR + RangeException::_EXCEPTION_OFFSET; + return; + } + } + + while (newParent->firstChild()) { + newParent->removeChild(newParent->firstChild(),exceptioncode); + if (exceptioncode) + return; + } + DocumentFragmentImpl *fragment = extractContents(exceptioncode); + if (exceptioncode) + return; + insertNode( newParent, exceptioncode ); + if (exceptioncode) + return; + newParent->appendChild( fragment, exceptioncode ); + if (exceptioncode) + return; + selectNode( newParent, exceptioncode ); +} + +void RangeImpl::setStartBefore( NodeImpl *refNode, int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return; + } + + if (!refNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + if (refNode->getDocument() != m_ownerDocument) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + checkNodeBA( refNode, exceptioncode ); + if (exceptioncode) + return; + + setStart( refNode->parentNode(), refNode->nodeIndex(), exceptioncode ); +} + +void RangeImpl::setStartContainer(NodeImpl *_startContainer) +{ + if (m_startContainer == _startContainer) + return; + + if (m_startContainer) + m_startContainer->deref(); + m_startContainer = _startContainer; + if (m_startContainer) + m_startContainer->ref(); +} + +void RangeImpl::setEndContainer(NodeImpl *_endContainer) +{ + if (m_endContainer == _endContainer) + return; + + if (m_endContainer) + m_endContainer->deref(); + m_endContainer = _endContainer; + if (m_endContainer) + m_endContainer->ref(); +} + +void RangeImpl::checkDeleteExtract(int &exceptioncode) { + + NodeImpl *start; + if (m_startContainer->nodeType() != Node::TEXT_NODE && + m_startContainer->nodeType() != Node::CDATA_SECTION_NODE && + m_startContainer->nodeType() != Node::COMMENT_NODE && + m_startContainer->nodeType() != Node::PROCESSING_INSTRUCTION_NODE) { + + start = m_startContainer->childNode(m_startOffset); + if (!start) { + if (m_startContainer->lastChild()) + start = m_startContainer->lastChild()->traverseNextNode(); + else + start = m_startContainer->traverseNextNode(); + } + } + else + start = m_startContainer; + + NodeImpl *end; + if (m_endContainer->nodeType() != Node::TEXT_NODE && + m_endContainer->nodeType() != Node::CDATA_SECTION_NODE && + m_endContainer->nodeType() != Node::COMMENT_NODE && + m_endContainer->nodeType() != Node::PROCESSING_INSTRUCTION_NODE) { + + end = m_endContainer->childNode(m_endOffset); + if (!end) { + if (m_endContainer->lastChild()) + end = m_endContainer->lastChild()->traverseNextNode(); + else + end = m_endContainer->traverseNextNode(); + } + } + else + end = m_endContainer; + + NodeImpl *n; + for (n = start; n != end; n = n->traverseNextNode()) { + if (n->isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + if (n->nodeType() == Node::DOCUMENT_TYPE_NODE) { // ### is this for only directly under the DF, or anywhere? + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + + if (containedByReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } +} + + +bool RangeImpl::containedByReadOnly() { + NodeImpl *n; + for (n = m_startContainer; n; n = n->parentNode()) { + if (n->isReadOnly()) + return true; + } + for (n = m_endContainer; n; n = n->parentNode()) { + if (n->isReadOnly()) + return true; + } + return false; +} + + + + + + + + diff --git a/khtml/xml/dom2_rangeimpl.h b/khtml/xml/dom2_rangeimpl.h new file mode 100644 index 000000000..35d532f23 --- /dev/null +++ b/khtml/xml/dom2_rangeimpl.h @@ -0,0 +1,127 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Gunnstein Lye (gunnstein@netcom.no) + * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) + * (C) 2001 Peter Kelly (pmk@post.com) + * + * 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. + * + */ + +#ifndef _DOM2_RangeImpl_h_ +#define _DOM2_RangeImpl_h_ + +#include "dom/dom2_range.h" +#include "misc/shared.h" + +namespace DOM { + +class RangeImpl : public khtml::Shared<RangeImpl> +{ + friend class DocumentImpl; +public: + RangeImpl(DocumentImpl *_ownerDocument); + RangeImpl(DocumentImpl *_ownerDocument, + NodeImpl *_startContainer, long _startOffset, + NodeImpl *_endContainer, long _endOffset); + + ~RangeImpl(); + + NodeImpl *startContainer(int &exceptioncode) const; + long startOffset(int &exceptioncode) const; + NodeImpl *endContainer(int &exceptioncode) const; + long endOffset(int &exceptioncode) const; + bool collapsed(int &exceptioncode) const; + + NodeImpl *commonAncestorContainer(int &exceptioncode); + static NodeImpl *commonAncestorContainer(NodeImpl *containerA, NodeImpl *containerB); + void setStart ( NodeImpl *refNode, long offset, int &exceptioncode ); + void setEnd ( NodeImpl *refNode, long offset, int &exceptioncode ); + void collapse ( bool toStart, int &exceptioncode ); + short compareBoundaryPoints ( Range::CompareHow how, RangeImpl *sourceRange, int &exceptioncode ); + static short compareBoundaryPoints ( NodeImpl *containerA, long offsetA, NodeImpl *containerB, long offsetB ); + bool boundaryPointsValid ( ); + void deleteContents ( int &exceptioncode ); + DocumentFragmentImpl *extractContents ( int &exceptioncode ); + DocumentFragmentImpl *cloneContents ( int &exceptioncode ); + void insertNode( NodeImpl *newNode, int &exceptioncode ); + DOMString toString ( int &exceptioncode ); + /** Converts the selection to HTML. The returned string will have matching + * tags, and all td, tr, etc tags will be inside a table tag. CSS is not + * used at this stage - This needs to be fixed. + * + * This is guaranteed to produce an xml valid snippet, no matter how crappy the input + * html page is. It will have html and body tags. + * + * Any urls in images or links will be expanded to full urls <em>with passwords stripped</em> + * for security reasons. + * + * Note: Originally this function didn't have the exceptioncode argument. I added it + * since all the other functions do. If this is correct, please remove this comment. + * + * @param exceptioncode This will be set if m_detached is true. + * @return A string with html tags for this range. + * + * @since 3.4 + */ + DOMString toHTML ( int &exceptioncode ); + + DocumentFragment createContextualFragment ( const DOMString &html, int &exceptioncode ); + + void detach ( int &exceptioncode ); + bool isDetached() const; + RangeImpl *cloneRange(int &exceptioncode); + + void setStartAfter( NodeImpl *refNode, int &exceptioncode ); + void setEndBefore( NodeImpl *refNode, int &exceptioncode ); + void setEndAfter( NodeImpl *refNode, int &exceptioncode ); + void selectNode( NodeImpl *refNode, int &exceptioncode ); + void selectNodeContents( NodeImpl *refNode, int &exceptioncode ); + void surroundContents( NodeImpl *newParent, int &exceptioncode ); + void setStartBefore( NodeImpl *refNode, int &exceptioncode ); + + enum ActionType { + DELETE_CONTENTS, + EXTRACT_CONTENTS, + CLONE_CONTENTS + }; + DocumentFragmentImpl *processContents ( ActionType action, int &exceptioncode ); + + bool readOnly() { return false; } + +protected: + DocumentImpl *m_ownerDocument; + NodeImpl *m_startContainer; + unsigned long m_startOffset; + NodeImpl *m_endContainer; + unsigned long m_endOffset; + bool m_detached; + +private: + void checkNodeWOffset( NodeImpl *n, int offset, int &exceptioncode) const; + void checkNodeBA( NodeImpl *n, int &exceptioncode ) const; + void setStartContainer(NodeImpl *_startContainer); + void setEndContainer(NodeImpl *_endContainer); + void checkDeleteExtract(int &exceptioncode); + bool containedByReadOnly(); +}; + +} // namespace + +#endif + diff --git a/khtml/xml/dom2_traversalimpl.cpp b/khtml/xml/dom2_traversalimpl.cpp new file mode 100644 index 000000000..2abdadead --- /dev/null +++ b/khtml/xml/dom2_traversalimpl.cpp @@ -0,0 +1,667 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) + * (C) 2001 Peter Kelly (pmk@post.com) + * + * 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 "dom/dom_exception.h" +#include "xml/dom_docimpl.h" + +using namespace DOM; + +NodeIteratorImpl::NodeIteratorImpl(NodeImpl *_root, unsigned long _whatToShow, + NodeFilter _filter, bool _entityReferenceExpansion) +{ + m_root = _root; + m_whatToShow = _whatToShow; + m_filter = _filter; + m_expandEntityReferences = _entityReferenceExpansion; + + m_referenceNode = _root; + m_inFront = false; + + m_doc = m_root->getDocument(); + m_doc->attachNodeIterator(this); + m_doc->ref(); + + m_detached = false; +} + +NodeIteratorImpl::~NodeIteratorImpl() +{ + m_doc->detachNodeIterator(this); + m_doc->deref(); +} + +NodeImpl *NodeIteratorImpl::root() +{ + return m_root; +} + +unsigned long NodeIteratorImpl::whatToShow() +{ + return m_whatToShow; +} + +NodeFilter NodeIteratorImpl::filter() +{ + return m_filter; +} + +bool NodeIteratorImpl::expandEntityReferences() +{ + return m_expandEntityReferences; +} + +NodeImpl *NodeIteratorImpl::nextNode( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + if (!m_referenceNode) { + m_inFront = true; + return 0; + } + + if (!m_inFront) { + m_inFront = true; + if (isAccepted(m_referenceNode) == NodeFilter::FILTER_ACCEPT) + return m_referenceNode; + } + + NodeImpl *_tempCurrent = getNextNode(m_referenceNode); + while( _tempCurrent ) { + m_referenceNode = _tempCurrent; + if(isAccepted(_tempCurrent) == NodeFilter::FILTER_ACCEPT) + return m_referenceNode; + _tempCurrent = getNextNode(_tempCurrent); + } + + return 0; +} + +NodeImpl *NodeIteratorImpl::getNextNode(NodeImpl *n) +{ + /* 1. my first child + * 2. my next sibling + * 3. my parents sibling, or their parents sibling (loop) + * 4. not found + */ + + if( !n ) + return 0; + + if( n->hasChildNodes() ) + return n->firstChild(); + + if( m_root == n) + return 0; + + if( n->nextSibling() ) + return n->nextSibling(); + + NodeImpl *parent = n->parentNode(); + while( parent ) + { + if( m_root == parent ) + return 0; + + n = parent->nextSibling(); + if( n ) + return n; + + parent = parent->parentNode(); + } + return 0; +} + +NodeImpl *NodeIteratorImpl::previousNode( int &exceptioncode ) +{ + if (m_detached) { + exceptioncode = DOMException::INVALID_STATE_ERR; + return 0; + } + + if (!m_referenceNode) { + m_inFront = false; + return 0; + } + + if (m_inFront) { + m_inFront = false; + if (isAccepted(m_referenceNode) == NodeFilter::FILTER_ACCEPT) + return m_referenceNode; + } + + NodeImpl *_tempCurrent = getPreviousNode(m_referenceNode); + while( _tempCurrent ) { + m_referenceNode = _tempCurrent; + if(isAccepted(_tempCurrent) == NodeFilter::FILTER_ACCEPT) + return m_referenceNode; + _tempCurrent = getPreviousNode(_tempCurrent); + } + + return 0; +} + +NodeImpl *NodeIteratorImpl::getPreviousNode(NodeImpl *n) +{ +/* 1. my previous sibling.lastchild + * 2. my previous sibling + * 3. my parent + */ + NodeImpl *_tempCurrent; + + if( !n || m_root == n ) + return 0; + + _tempCurrent = n->previousSibling(); + if( _tempCurrent ) + { + if( _tempCurrent->lastChild() ) + { + while( _tempCurrent->lastChild() ) + _tempCurrent = _tempCurrent->lastChild(); + return _tempCurrent; + } + else + return _tempCurrent; + } + + return n->parentNode(); + +} + +void NodeIteratorImpl::detach(int &/*exceptioncode*/) +{ + m_doc->detachNodeIterator(this); + m_detached = true; +} + + +void NodeIteratorImpl::notifyBeforeNodeRemoval(NodeImpl *removed) +{ + // make sure the deleted node is with the root (but not the root itself) + if (removed == m_root) + return; + + NodeImpl *maybeRoot = removed->parentNode(); + while (maybeRoot && maybeRoot != m_root) + maybeRoot = maybeRoot->parentNode(); + if (!maybeRoot) + return; + + // did I get deleted, or one of my parents? + NodeImpl *_tempDeleted = m_referenceNode; + while( _tempDeleted && _tempDeleted != removed) + _tempDeleted = _tempDeleted->parentNode(); + + if( !_tempDeleted ) // someone that didn't consern me got deleted + return; + + if( !m_inFront) + { + NodeImpl *_next = getNextNode(_tempDeleted); + if( _next ) + m_referenceNode = _next; + else + { + // deleted node was at end of list + m_inFront = true; + m_referenceNode = getPreviousNode(_tempDeleted); + } + } + else { + NodeImpl *_prev = getPreviousNode(_tempDeleted); + if ( _prev ) + m_referenceNode = _prev; + else + { + // deleted node was at start of list + m_inFront = false; + m_referenceNode = getNextNode(_tempDeleted); + } + } + +} + +short NodeIteratorImpl::isAccepted(NodeImpl *n) +{ + // if XML is implemented we have to check expandEntityRerefences in this function + if( ( ( 1 << ( n->nodeType()-1 ) ) & m_whatToShow) != 0 ) + { + if(!m_filter.isNull()) + return m_filter.acceptNode(n); + else + return NodeFilter::FILTER_ACCEPT; + } + return NodeFilter::FILTER_SKIP; +} + +// -------------------------------------------------------------- + + +NodeFilterImpl::NodeFilterImpl() +{ + m_customNodeFilter = 0; +} + +NodeFilterImpl::~NodeFilterImpl() +{ + if (m_customNodeFilter) + m_customNodeFilter->deref(); +} + +short NodeFilterImpl::acceptNode(const Node &n) +{ + if (m_customNodeFilter) + return m_customNodeFilter->acceptNode(n); + else + return NodeFilter::FILTER_ACCEPT; +} + +void NodeFilterImpl::setCustomNodeFilter(CustomNodeFilter *custom) +{ + m_customNodeFilter = custom; + if (m_customNodeFilter) + m_customNodeFilter->ref(); +} + +CustomNodeFilter *NodeFilterImpl::customNodeFilter() +{ + return m_customNodeFilter; +} + +// -------------------------------------------------------------- + +TreeWalkerImpl::TreeWalkerImpl(NodeImpl *n, long _whatToShow, NodeFilterImpl *f, + bool entityReferenceExpansion) +{ + m_currentNode = n; + m_rootNode = n; + m_whatToShow = _whatToShow; + m_filter = f; + if ( m_filter ) + m_filter->ref(); + m_expandEntityReferences = entityReferenceExpansion; + m_doc = m_rootNode->getDocument(); + m_doc->ref(); +} + +TreeWalkerImpl::~TreeWalkerImpl() +{ + m_doc->deref(); + if ( m_filter ) + m_filter->deref(); +} + +NodeImpl *TreeWalkerImpl::getRoot() const +{ + return m_rootNode; +} + +unsigned long TreeWalkerImpl::getWhatToShow() const +{ + return m_whatToShow; +} + +NodeFilterImpl *TreeWalkerImpl::getFilter() const +{ + return m_filter; +} + +bool TreeWalkerImpl::getExpandEntityReferences() const +{ + return m_expandEntityReferences; +} + +NodeImpl *TreeWalkerImpl::getCurrentNode() const +{ + return m_currentNode; +} + +void TreeWalkerImpl::setWhatToShow(long _whatToShow) +{ + // do some testing wether this is an accepted value + m_whatToShow = _whatToShow; +} + +void TreeWalkerImpl::setFilter(NodeFilterImpl *_filter) +{ + m_filter->deref(); + m_filter = _filter; + if ( m_filter ) + m_filter->ref(); +} + +void TreeWalkerImpl::setExpandEntityReferences(bool value) +{ + m_expandEntityReferences = value; +} + +void TreeWalkerImpl::setCurrentNode( NodeImpl *n ) +{ + if ( n ) + { + //m_rootNode = n; + m_currentNode = n; + } +// else +// throw( DOMException::NOT_SUPPORTED_ERR ); +} + +NodeImpl *TreeWalkerImpl::parentNode( ) +{ + NodeImpl *n = getParentNode( m_currentNode ); + if ( n ) + m_currentNode = n; + return n; +} + + +NodeImpl *TreeWalkerImpl::firstChild( ) +{ + NodeImpl *n = getFirstChild( m_currentNode ); + if ( n ) + m_currentNode = n; + return n; +} + + +NodeImpl *TreeWalkerImpl::lastChild( ) +{ + NodeImpl *n = getLastChild(m_currentNode); + if( n ) + m_currentNode = n; + return n; +} + +NodeImpl *TreeWalkerImpl::previousSibling( ) +{ + NodeImpl *n = getPreviousSibling( m_currentNode ); + if( n ) + m_currentNode = n; + return n; +} + +NodeImpl *TreeWalkerImpl::nextSibling( ) +{ + NodeImpl *n = getNextSibling( m_currentNode ); + if( n ) + m_currentNode = n; + return n; +} + +NodeImpl *TreeWalkerImpl::previousNode( ) +{ +/* 1. my previous sibling.lastchild + * 2. my previous sibling + * 3. my parent + */ + + NodeImpl *n = getPreviousSibling( m_currentNode ); + if( !n ) + { + n = getParentNode( m_currentNode ); + if( n ) //parent + { + m_currentNode = n; + return m_currentNode; + } + else // parent failed.. no previous node + return 0; + } + + NodeImpl *child = getLastChild( n ); + if( child ) // previous siblings last child + { + m_currentNode = child; + return m_currentNode; + } + else // previous sibling + { + m_currentNode = n; + return m_currentNode; + } + return 0; // should never get here! +} + +NodeImpl *TreeWalkerImpl::nextNode( ) +{ +/* 1. my first child + * 2. my next sibling + * 3. my parents sibling, or their parents sibling (loop) + * 4. not found + */ + + NodeImpl *n = getFirstChild( m_currentNode ); + if( n ) // my first child + { + m_currentNode = n; + return n; + } + + n = getNextSibling( m_currentNode ); // my next sibling + if( n ) + { + m_currentNode = n; + return m_currentNode; + } + NodeImpl *parent = getParentNode( m_currentNode ); + while( parent ) // parents sibling + { + n = getNextSibling( parent ); + if( n ) + { + m_currentNode = n; + return m_currentNode; + } + else + parent = getParentNode( parent ); + } + return 0; +} + +short TreeWalkerImpl::isAccepted(NodeImpl *n) +{ + // if XML is implemented we have to check expandEntityRerefences in this function + if( ( ( 1 << ( n->nodeType()-1 ) ) & m_whatToShow) != 0 ) + { + if(m_filter) + return m_filter->acceptNode(n); + else + return NodeFilter::FILTER_ACCEPT; + } + return NodeFilter::FILTER_SKIP; +} + +NodeImpl *TreeWalkerImpl::getParentNode(NodeImpl *n) +{ + short _result = NodeFilter::FILTER_ACCEPT; + + if( n == m_rootNode /*|| !n*/ ) + return 0; + + NodeImpl *_tempCurrent = n->parentNode(); + + if( !_tempCurrent ) + return 0; + + _result = isAccepted( _tempCurrent ); + if ( _result == NodeFilter::FILTER_ACCEPT ) + return _tempCurrent; // match found + + return getParentNode( _tempCurrent ); +} + +NodeImpl *TreeWalkerImpl::getFirstChild(NodeImpl *n) +{ + short _result; + + if( !n || !n->firstChild() ) + return 0; + n = n->firstChild(); + + _result = isAccepted( n ); + + switch( _result ) + { + case NodeFilter::FILTER_ACCEPT: + return n; + break; + case NodeFilter::FILTER_SKIP: + if( n->hasChildNodes() ) + return getFirstChild( n ); + else + return getNextSibling( n ); + break; + + case NodeFilter::FILTER_REJECT: + return getNextSibling( n ); + break; + } + return 0; // should never get here! +} + +NodeImpl *TreeWalkerImpl::getLastChild(NodeImpl *n) +{ + short _result; + + if( !n || !n->lastChild() ) + return 0; + n = n->lastChild(); + _result = isAccepted( n ); + + switch( _result ) + { + case NodeFilter::FILTER_ACCEPT: + return n; + break; + + case NodeFilter::FILTER_SKIP: + if( n->hasChildNodes() ) + return getLastChild( n ); + else + return getPreviousSibling( n ); + break; + + case NodeFilter::FILTER_REJECT: + return getPreviousSibling( n ); + break; + } + return 0; +} + +NodeImpl *TreeWalkerImpl::getPreviousSibling(NodeImpl *n) +{ + short _result; + NodeImpl *_tempCurrent; + + if( !n ) + return 0; + //first the cases if we have a previousSibling + _tempCurrent = n->previousSibling(); + if( _tempCurrent ) + { + _result = isAccepted( _tempCurrent ); + switch ( _result ) + { + case NodeFilter::FILTER_ACCEPT: + return _tempCurrent; + break; + + case NodeFilter::FILTER_SKIP: + { + NodeImpl *nskip = getLastChild( _tempCurrent ); + if( nskip ) + return nskip; + return getPreviousSibling( _tempCurrent ); + break; + } + + case NodeFilter::FILTER_REJECT: + return getPreviousSibling( _tempCurrent ); + break; + } + } + // now the case if we don't have previous sibling + else + { + _tempCurrent = n->parentNode(); + if( !_tempCurrent || _tempCurrent == m_rootNode) + return 0; + _result = isAccepted( _tempCurrent ); + if ( _result == NodeFilter::FILTER_SKIP ) + return getPreviousSibling( _tempCurrent ); + + return 0; + + } + return 0; // should never get here! +} + +NodeImpl *TreeWalkerImpl::getNextSibling(NodeImpl *n) +{ + NodeImpl *_tempCurrent = 0; + short _result; + + if( !n ) + return 0; + + _tempCurrent = n->nextSibling(); + if( _tempCurrent ) + { + _result = isAccepted( _tempCurrent ); + switch ( _result ) + { + case NodeFilter::FILTER_ACCEPT: + return _tempCurrent; + break; + + case NodeFilter::FILTER_SKIP: + { + NodeImpl *nskip = getFirstChild( _tempCurrent ); + if( nskip ) + return nskip; + return getNextSibling( _tempCurrent ); + break; + } + + case NodeFilter::FILTER_REJECT: + return getNextSibling( _tempCurrent ); + break; + } + } + else + { + _tempCurrent = n->parentNode(); + if( !_tempCurrent || _tempCurrent == m_rootNode) + return 0; + _result = isAccepted( _tempCurrent ); + if( _result == NodeFilter::FILTER_SKIP ) + return getNextSibling( _tempCurrent ); + + return 0; + } + return 0; +} + diff --git a/khtml/xml/dom2_traversalimpl.h b/khtml/xml/dom2_traversalimpl.h new file mode 100644 index 000000000..3c5ea1dac --- /dev/null +++ b/khtml/xml/dom2_traversalimpl.h @@ -0,0 +1,196 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Frederik Holljen (frederik.holljen@hig.no) + * (C) 2001 Peter Kelly (pmk@post.com) + * + * 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. + * + */ + +#ifndef _DOM2_TraversalImpl_h_ +#define _DOM2_TraversalImpl_h_ + +#include "dom/dom_node.h" +#include "dom/dom_misc.h" +#include "misc/shared.h" +#include "dom/dom2_traversal.h" + +namespace DOM { + +class NodeImpl; +class DocumentImpl; + +class NodeIteratorImpl : public khtml::Shared<NodeIteratorImpl> +{ +public: + NodeIteratorImpl(NodeImpl *_root, unsigned long _whatToShow, NodeFilter _filter, bool _entityReferenceExpansion); + ~NodeIteratorImpl(); + + + NodeImpl *root(); + unsigned long whatToShow(); + NodeFilter filter(); + bool expandEntityReferences(); + + NodeImpl *nextNode(int &exceptioncode); + NodeImpl *previousNode(int &exceptioncode); + void detach(int &exceptioncode); + + + /** + * This function has to be called if you delete a node from the + * document tree and you want the Iterator to react if there + * are any changes concerning it. + */ + void notifyBeforeNodeRemoval(NodeImpl *removed); + + short isAccepted(NodeImpl *n); + NodeImpl *getNextNode(NodeImpl *n); + NodeImpl *getPreviousNode(NodeImpl *n); +protected: + NodeImpl *m_root; + long m_whatToShow; + NodeFilter m_filter; + bool m_expandEntityReferences; + + bool m_inFront; + NodeImpl *m_referenceNode; + bool m_detached; + DocumentImpl *m_doc; +}; + +class NodeFilterImpl : public khtml::Shared<NodeFilterImpl> +{ +public: + NodeFilterImpl(); + ~NodeFilterImpl(); + + short acceptNode(const Node &n); + + void setCustomNodeFilter(CustomNodeFilter *custom); + CustomNodeFilter *customNodeFilter(); +protected: + CustomNodeFilter *m_customNodeFilter; + +}; + +class TreeWalkerImpl : public khtml::Shared<TreeWalkerImpl> +{ +public: + TreeWalkerImpl(); + TreeWalkerImpl(const TreeWalkerImpl &other); + TreeWalkerImpl(NodeImpl *n, NodeFilter f); + TreeWalkerImpl(NodeImpl *n, long _whatToShow, NodeFilterImpl *f, + bool entityReferenceExpansion); + TreeWalkerImpl & operator = (const TreeWalkerImpl &other); + + + ~TreeWalkerImpl(); + + NodeImpl *getRoot() const; + + unsigned long getWhatToShow() const; + + NodeFilterImpl *getFilter() const; + + bool getExpandEntityReferences() const; + + NodeImpl *getCurrentNode() const; + + void setCurrentNode( NodeImpl *_currentNode); + + NodeImpl *parentNode(); + + NodeImpl *firstChild(); + + NodeImpl *lastChild (); + + NodeImpl *previousSibling (); + + NodeImpl *nextSibling(); + + NodeImpl *previousNode(); + + NodeImpl *nextNode(); + + + /** + * Sets which node types are to be presented via the TreeWalker + */ + void setWhatToShow(long _whatToShow); + void setFilter(NodeFilterImpl *_filter); + void setExpandEntityReferences(bool value); + + NodeImpl *getParentNode(NodeImpl *n); + NodeImpl *getFirstChild(NodeImpl *n); + NodeImpl *getLastChild(NodeImpl *n); + NodeImpl *getPreviousSibling(NodeImpl *n); + NodeImpl *getNextSibling(NodeImpl *n); + + short isAccepted(NodeImpl *n); + +protected: + /** + * This attribute determines which node types are presented via + * the TreeWalker. + * + */ + long m_whatToShow; + + /** + * The filter used to screen nodes. + * + */ + NodeFilterImpl *m_filter; + + /** + * The value of this flag determines whether entity reference + * nodes are expanded. To produce a view of the document that has + * entity references expanded and does not expose the entity + * reference node itself, use the whatToShow flags to hide the + * entity reference node and set expandEntityReferences to true + * when creating the iterator. To produce a view of the document + * that has entity reference nodes but no entity expansion, use + * the whatToShow flags to show the entity reference node and set + * expandEntityReferences to true. + * + * This is not implemented (always true) + */ + bool m_expandEntityReferences; + + /** + * The current node. + * + * The value must not be null. Attempting to set it to null will + * raise a NOT_SUPPORTED_ERR exception. When setting a node, the + * whatToShow flags and any Filter associated with the TreeWalker + * are not checked. The currentNode may be set to any Node of any + * type. + * + */ + NodeImpl *m_currentNode; + + NodeImpl *m_rootNode; + DocumentImpl *m_doc; +}; + + +} // namespace + +#endif + diff --git a/khtml/xml/dom2_viewsimpl.cpp b/khtml/xml/dom2_viewsimpl.cpp new file mode 100644 index 000000000..2195a9db1 --- /dev/null +++ b/khtml/xml/dom2_viewsimpl.cpp @@ -0,0 +1,50 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * (C) 2001 Peter Kelly (pmk@post.com) + * + * 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 "dom2_viewsimpl.h" +#include "dom_elementimpl.h" +#include "dom_docimpl.h" +#include "css/css_renderstyledeclarationimpl.h" +#include "css/cssproperties.h" +#include "css/css_stylesheetimpl.h" + +using namespace khtml; +using namespace DOM; + +AbstractViewImpl::AbstractViewImpl(DocumentImpl *_document) +{ + m_document = _document; +} + +AbstractViewImpl::~AbstractViewImpl() +{ +} + +CSSStyleDeclarationImpl *AbstractViewImpl::getComputedStyle(ElementImpl* elt, DOMStringImpl* /*pseudoElt*/) +{ + if (!elt) + return 0; + + CSSStyleDeclarationImpl* style = new RenderStyleDeclarationImpl( elt ); + return style; +} + diff --git a/khtml/xml/dom2_viewsimpl.h b/khtml/xml/dom2_viewsimpl.h new file mode 100644 index 000000000..caff63e11 --- /dev/null +++ b/khtml/xml/dom2_viewsimpl.h @@ -0,0 +1,50 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * (C) 2001 Peter Kelly (pmk@post.com) + * + * 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. + * + */ + +#ifndef _DOM_ViewsImpl_h_ +#define _DOM_ViewsImpl_h_ + +#include "dom/dom_misc.h" +#include "css/css_valueimpl.h" +#include "misc/shared.h" + +namespace DOM { + +class DocumentImpl; +class CSSStyleDeclarationImpl; +class ElementImpl; +class DOMStringImpl; + +// Introduced in DOM Level 2: +class AbstractViewImpl : public khtml::Shared<AbstractViewImpl> +{ +public: + AbstractViewImpl(DocumentImpl *_document); + ~AbstractViewImpl(); + DocumentImpl *document() const { return m_document; } + CSSStyleDeclarationImpl *getComputedStyle(ElementImpl *elt, DOMStringImpl *pseudoElt); +protected: + DocumentImpl *m_document; +}; + +} //namespace +#endif diff --git a/khtml/xml/dom_docimpl.cpp b/khtml/xml/dom_docimpl.cpp new file mode 100644 index 000000000..f6cc0fa64 --- /dev/null +++ b/khtml/xml/dom_docimpl.cpp @@ -0,0 +1,2892 @@ +/** + * 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 Dirk Mueller (mueller@kde.org) + * (C) 2002-2006 Apple Computer, Inc. + * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 "dom/dom_exception.h" + +#include "xml/dom_textimpl.h" +#include "xml/dom_xmlimpl.h" +#include "xml/dom2_rangeimpl.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/xml_tokenizer.h" +#include "html/htmltokenizer.h" +#include "xml/dom_restyler.h" + +#include "css/csshelper.h" +#include "css/cssstyleselector.h" +#include "css/css_stylesheetimpl.h" +#include "misc/htmlhashes.h" +#include "misc/helper.h" +#include "misc/seed.h" +#include "misc/loader.h" +#include "ecma/kjs_proxy.h" +#include "ecma/kjs_binding.h" + +#include <qptrstack.h> +#include <qpaintdevicemetrics.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> + +#include "rendering/counter_tree.h" +#include "rendering/render_canvas.h" +#include "rendering/render_replaced.h" +#include "rendering/render_arena.h" +#include "rendering/render_layer.h" +#include "rendering/render_frames.h" +#include "rendering/render_image.h" + +#include "khtmlview.h" +#include "khtml_part.h" + +#include <kglobalsettings.h> +#include <kstringhandler.h> +#include <krfcdate.h> +#include "khtml_settings.h" +#include "khtmlpart_p.h" + +#include "html/html_baseimpl.h" +#include "html/html_blockimpl.h" +#include "html/html_documentimpl.h" +#include "html/html_formimpl.h" +#include "html/html_headimpl.h" +#include "html/html_imageimpl.h" +#include "html/html_listimpl.h" +#include "html/html_miscimpl.h" +#include "html/html_tableimpl.h" +#include "html/html_objectimpl.h" + +#include <kapplication.h> +#include <kio/job.h> + +#include <stdlib.h> +#include "dom_docimpl.h" + +using namespace DOM; +using namespace khtml; + +// ------------------------------------------------------------------------ + +DOMImplementationImpl *DOMImplementationImpl::m_instance = 0; + +DOMImplementationImpl::DOMImplementationImpl() +{ +} + +DOMImplementationImpl::~DOMImplementationImpl() +{ +} + +bool DOMImplementationImpl::hasFeature ( const DOMString &feature, const DOMString &version ) +{ + // ### update when we (fully) support the relevant features + QString lower = feature.string().lower(); + if ((lower == "html" || lower == "xml") && + (version.isEmpty() || version == "1.0" || version == "2.0" || version == "null")) + return true; + + // ## Do we support Core Level 3 ? + if ((lower == "core" ) && + (version.isEmpty() || version == "2.0" || version == "null")) + return true; + + if ((lower == "events" || lower == "uievents" || + lower == "mouseevents" || lower == "mutationevents" || + lower == "htmlevents" || lower == "textevents" ) && + (version.isEmpty() || version == "2.0" || version == "3.0" || version == "null")) + return true; + return false; +} + +DocumentTypeImpl *DOMImplementationImpl::createDocumentType( const DOMString &qualifiedName, const DOMString &publicId, + const DOMString &systemId, int &exceptioncode ) +{ + // Not mentioned in spec: throw NAMESPACE_ERR if no qualifiedName supplied + if (qualifiedName.isNull()) { + exceptioncode = DOMException::NAMESPACE_ERR; + return 0; + } + + // INVALID_CHARACTER_ERR: Raised if the specified qualified name contains an illegal character. + if (!Element::khtmlValidQualifiedName(qualifiedName)) { + exceptioncode = DOMException::INVALID_CHARACTER_ERR; + return 0; + } + + // NAMESPACE_ERR: Raised if the qualifiedName is malformed. + // Added special case for the empty string, which seems to be a common pre-DOM2 misuse + if (!qualifiedName.isEmpty() && Element::khtmlMalformedQualifiedName(qualifiedName)) { + exceptioncode = DOMException::NAMESPACE_ERR; + return 0; + } + + return new DocumentTypeImpl(this,0,qualifiedName,publicId,systemId); +} + +DOMImplementationImpl* DOMImplementationImpl::getInterface(const DOMString& /*feature*/) const +{ + // ### + return 0; +} + +DocumentImpl *DOMImplementationImpl::createDocument( const DOMString &namespaceURI, const DOMString &qualifiedName, + const DocumentType &doctype, int &exceptioncode ) +{ + exceptioncode = 0; + + if (!checkQualifiedName(qualifiedName, namespaceURI, 0, true/*nameCanBeNull*/, + true /*nameCanBeEmpty, see #61650*/, &exceptioncode) ) + return 0; + + DocumentTypeImpl *dtype = static_cast<DocumentTypeImpl*>(doctype.handle()); + // WRONG_DOCUMENT_ERR: Raised if doctype has already been used with a different document or was + // created from a different implementation. + if (dtype && (dtype->getDocument() || dtype->implementation() != this)) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return 0; + } + + // ### this is completely broken.. without a view it will not work (Dirk) + DocumentImpl *doc = new DocumentImpl(this, 0); + + // now get the interesting parts of the doctype + // ### create new one if not there (currently always there) + if (doc->doctype() && dtype) + doc->doctype()->copyFrom(*dtype); + + // the document must be created empty if all parameters are null + // (or empty for qName/nsURI as a tolerance) - see DOM 3 Core. + if (dtype || !qualifiedName.isEmpty() || !namespaceURI.isEmpty()) { + ElementImpl *element = doc->createElementNS(namespaceURI,qualifiedName); + doc->appendChild(element,exceptioncode); + if (exceptioncode) { + delete element; + delete doc; + return 0; + } + } + return doc; +} + +CSSStyleSheetImpl *DOMImplementationImpl::createCSSStyleSheet(DOMStringImpl* /*title*/, DOMStringImpl *media, + int &/*exceptioncode*/) +{ + // ### TODO : title should be set, and media could have wrong syntax, in which case we should + // generate an exception. + CSSStyleSheetImpl *parent = 0L; + CSSStyleSheetImpl *sheet = new CSSStyleSheetImpl(parent, DOMString()); + sheet->setMedia(new MediaListImpl(sheet, media)); + return sheet; +} + +DocumentImpl *DOMImplementationImpl::createDocument( KHTMLView *v ) +{ + return new DocumentImpl(this, v); +} + +HTMLDocumentImpl *DOMImplementationImpl::createHTMLDocument( KHTMLView *v ) +{ + return new HTMLDocumentImpl(this, v); +} + +DOMImplementationImpl *DOMImplementationImpl::instance() +{ + if (!m_instance) { + m_instance = new DOMImplementationImpl(); + m_instance->ref(); + } + + return m_instance; +} + +// ------------------------------------------------------------------------ + + +ElementMappingCache::ElementMappingCache():m_dict(257) +{ + m_dict.setAutoDelete(true); +} + +void ElementMappingCache::add(const QString& id, ElementImpl* nd) +{ + if (id.isEmpty()) return; + + ItemInfo* info = m_dict.find(id); + if (info) + { + info->ref++; + info->nd = 0; //Now ambigous + } + else + { + ItemInfo* info = new ItemInfo(); + info->ref = 1; + info->nd = nd; + m_dict.insert(id, info); + } +} + +void ElementMappingCache::set(const QString& id, ElementImpl* nd) +{ + if (id.isEmpty()) return; + + ItemInfo* info = m_dict.find(id); + info->nd = nd; +} + +void ElementMappingCache::remove(const QString& id, ElementImpl* nd) +{ + if (id.isEmpty()) return; + + ItemInfo* info = m_dict.find(id); + info->ref--; + if (info->ref == 0) + { + m_dict.take(id); + delete info; + } + else + { + if (info->nd == nd) + info->nd = 0; + } +} + +bool ElementMappingCache::contains(const QString& id) +{ + if (id.isEmpty()) return false; + return m_dict.find(id); +} + +ElementMappingCache::ItemInfo* ElementMappingCache::get(const QString& id) +{ + if (id.isEmpty()) return 0; + return m_dict.find(id); +} + +static KStaticDeleter< QPtrList<DocumentImpl> > s_changedDocumentsDeleter; +QPtrList<DocumentImpl> * DocumentImpl::changedDocuments; + +// KHTMLView might be 0 +DocumentImpl::DocumentImpl(DOMImplementationImpl *_implementation, KHTMLView *v) + : NodeBaseImpl( 0 ), m_domtree_version(0), m_counterDict(257), + m_imageLoadEventTimer(0) +{ + m_document.resetSkippingRef(this); //Make getDocument return us.. + m_selfOnlyRefCount = 0; + + m_paintDeviceMetrics = 0; + m_paintDevice = 0; + m_decoderMibEnum = 0; + m_textColor = Qt::black; + + m_view = v; + m_renderArena.reset(); + + KHTMLFactory::ref(); + + if ( v ) { + m_docLoader = new DocLoader(v->part(), this ); + setPaintDevice( m_view ); + } + else + m_docLoader = new DocLoader( 0, this ); + + visuallyOrdered = false; + m_bParsing = false; + m_docChanged = false; + m_elemSheet = 0; + m_tokenizer = 0; + + // ### this should be created during parsing a <!DOCTYPE> + // not during construction. Not sure who added that and why (Dirk) + m_doctype = new DocumentTypeImpl(_implementation, getDocument(), + DOMString() /* qualifiedName */, + DOMString() /* publicId */, + DOMString() /* systemId */); + m_doctype->ref(); + + m_implementation = _implementation; + m_implementation->ref(); + pMode = Strict; + hMode = XHtml; + m_textColor = "#000000"; + m_attrMap = new IdNameMapping(ATTR_LAST_ATTR+1); + m_elementMap = new IdNameMapping(ID_LAST_TAG+1); + m_namespaceMap = new IdNameMapping(1); + QString xhtml(XHTML_NAMESPACE); + m_namespaceMap->names.insert(emptyNamespace, new DOMStringImpl("")); + m_namespaceMap->names.insert(xhtmlNamespace, new DOMStringImpl(xhtml.unicode(), xhtml.length())); + m_namespaceMap->names[emptyNamespace]->ref(); + m_namespaceMap->names[xhtmlNamespace]->ref(); + m_namespaceMap->count+=2; + m_focusNode = 0; + m_hoverNode = 0; + m_activeNode = 0; + m_defaultView = new AbstractViewImpl(this); + m_defaultView->ref(); + m_listenerTypes = 0; + m_styleSheets = new StyleSheetListImpl; + m_styleSheets->ref(); + m_addedStyleSheets = 0; + m_inDocument = true; + m_styleSelectorDirty = false; + m_styleSelector = 0; + m_counterDict.setAutoDelete(true); + + m_inStyleRecalc = false; + m_pendingStylesheets = 0; + m_ignorePendingStylesheets = false; + m_async = true; + m_hadLoadError = false; + m_docLoading = false; + m_inSyncLoad = false; + m_loadingXMLDoc = 0; + m_cssTarget = 0; + m_dynamicDomRestyler = new khtml::DynamicDomRestyler(); +} + +void DocumentImpl::removedLastRef() +{ + if (m_selfOnlyRefCount) { + /* In this case, the only references to us are from children, + so we have a cycle. We'll try to break it by disconnecting the + children from us; this sucks/is wrong, but it's pretty much + the best we can do without tracing. + + Of course, if dumping the children causes the refcount from them to + drop to 0 we can get killed right here, so better hold + a temporary reference, too + */ + DocPtr<DocumentImpl> guard(this); + + // we must make sure not to be retaining any of our children through + // these extra pointers or we will create a reference cycle + if (m_doctype) { + m_doctype->deref(); + m_doctype = 0; + } + + if (m_cssTarget) { + m_cssTarget->deref(); + m_cssTarget = 0; + } + + if (m_focusNode) { + m_focusNode->deref(); + m_focusNode = 0; + } + + if (m_hoverNode) { + m_hoverNode->deref(); + m_hoverNode = 0; + } + + if (m_activeNode) { + m_activeNode->deref(); + m_activeNode = 0; + } + + removeChildren(); + + delete m_tokenizer; + m_tokenizer = 0; + } else { + delete this; + } +} + +DocumentImpl::~DocumentImpl() +{ + //Important: if you need to remove stuff here, + //you may also have to fix removedLastRef() above - M.O. + assert( !m_render ); + + QIntDictIterator<NodeListImpl::Cache> it(m_nodeListCache); + for (; it.current(); ++it) + it.current()->deref(); + + if (m_loadingXMLDoc) + m_loadingXMLDoc->deref(this); + if (changedDocuments && m_docChanged) + changedDocuments->remove(this); + delete m_tokenizer; + m_document.resetSkippingRef(0); + delete m_styleSelector; + delete m_docLoader; + if (m_elemSheet ) m_elemSheet->deref(); + if (m_doctype) + m_doctype->deref(); + m_implementation->deref(); + delete m_paintDeviceMetrics; + delete m_elementMap; + delete m_attrMap; + delete m_namespaceMap; + delete m_dynamicDomRestyler; + m_defaultView->deref(); + m_styleSheets->deref(); + if (m_addedStyleSheets) + m_addedStyleSheets->deref(); + if (m_cssTarget) + m_cssTarget->deref(); + if (m_focusNode) + m_focusNode->deref(); + if ( m_hoverNode ) + m_hoverNode->deref(); + if (m_activeNode) + m_activeNode->deref(); + + m_renderArena.reset(); + + KHTMLFactory::deref(); +} + + +DocumentTypeImpl *DocumentImpl::doctype() const +{ + return m_doctype; +} + +DOMImplementationImpl *DocumentImpl::implementation() const +{ + return m_implementation; +} + +ElementImpl *DocumentImpl::documentElement() const +{ + NodeImpl *n = firstChild(); + while (n && n->nodeType() != Node::ELEMENT_NODE) + n = n->nextSibling(); + return static_cast<ElementImpl*>(n); +} + +ElementImpl *DocumentImpl::createElement( const DOMString &name, int* pExceptioncode ) +{ + Id id = getId( NodeImpl::ElementId, name.implementation(), + false /* allocate */, false /*HTMLDocumentImpl::createElement looked for HTML elements already*/, + pExceptioncode); + if ( pExceptioncode && *pExceptioncode ) + return 0; + + XMLElementImpl* e = new XMLElementImpl( getDocument(), id ); + e->setHTMLCompat( htmlMode() != XHtml ); // Not a real HTML element, but inside an html-compat doc all tags are uppercase. + return e; +} + +AttrImpl *DocumentImpl::createAttribute( const DOMString &tagName, int* pExceptioncode ) +{ + Id id = getId( NodeImpl::AttributeId, tagName.implementation(), + false /* allocate */, isHTMLDocument(), pExceptioncode); + if ( pExceptioncode && *pExceptioncode ) + return 0; + AttrImpl* attr = new AttrImpl( 0, getDocument(), id, DOMString("").implementation()); + attr->setHTMLCompat( htmlMode() != XHtml ); + return attr; +} + +DocumentFragmentImpl *DocumentImpl::createDocumentFragment( ) +{ + return new DocumentFragmentImpl( docPtr() ); +} + +CommentImpl *DocumentImpl::createComment ( DOMStringImpl* data ) +{ + return new CommentImpl( docPtr(), data ); +} + +CDATASectionImpl *DocumentImpl::createCDATASection ( DOMStringImpl* data ) +{ + return new CDATASectionImpl( docPtr(), data ); +} + +ProcessingInstructionImpl *DocumentImpl::createProcessingInstruction ( const DOMString &target, DOMStringImpl* data ) +{ + return new ProcessingInstructionImpl( docPtr(),target,data); +} + +EntityReferenceImpl *DocumentImpl::createEntityReference ( const DOMString &name ) +{ + return new EntityReferenceImpl(docPtr(), name.implementation()); +} + +NodeImpl *DocumentImpl::importNode(NodeImpl *importedNode, bool deep, int &exceptioncode) +{ + NodeImpl *result = 0; + + // Not mentioned in spec: throw NOT_FOUND_ERR if evt is null + if (!importedNode) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; + } + + if(importedNode->nodeType() == Node::ELEMENT_NODE) + { + // Why not use cloneNode? + ElementImpl *otherElem = static_cast<ElementImpl*>(importedNode); + NamedAttrMapImpl *otherMap = static_cast<ElementImpl *>(importedNode)->attributes(true); + + ElementImpl *tempElementImpl; + if (!importedNode->localName().isNull()) + tempElementImpl = createElementNS(otherElem->namespaceURI(),otherElem->nodeName()); + else + tempElementImpl = createElement(otherElem->nodeName()); + result = tempElementImpl; + + + if(otherMap) { + for(unsigned long i = 0; i < otherMap->length(); i++) + { + AttrImpl *otherAttr = otherMap->attrAt(i)->createAttr(otherElem,otherElem->docPtr()); + + if (!otherAttr->localName().isNull()) { + // attr was created via createElementNS() + tempElementImpl->setAttributeNS(otherAttr->namespaceURI(), + otherAttr->name(), + otherAttr->nodeValue(), + exceptioncode); + } + else { + // attr was created via createElement() + tempElementImpl->setAttribute(otherAttr->id(), + otherAttr->nodeValue(), + otherAttr->name(), + exceptioncode); + } + + if(exceptioncode != 0) + break; // ### properly cleanup here + } + } + } + else if(importedNode->nodeType() == Node::TEXT_NODE) + { + result = createTextNode(static_cast<TextImpl*>(importedNode)->string()); + deep = false; + } + else if(importedNode->nodeType() == Node::CDATA_SECTION_NODE) + { + result = createCDATASection(static_cast<CDATASectionImpl*>(importedNode)->string()); + deep = false; + } + else if(importedNode->nodeType() == Node::ENTITY_REFERENCE_NODE) + result = createEntityReference(importedNode->nodeName()); + else if(importedNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) + { + result = createProcessingInstruction(importedNode->nodeName(), importedNode->nodeValue().implementation()); + deep = false; + } + else if(importedNode->nodeType() == Node::COMMENT_NODE) + { + result = createComment(static_cast<CommentImpl*>(importedNode)->string()); + deep = false; + } + else if (importedNode->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) + result = createDocumentFragment(); + else + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + + //### FIXME: This should handle Attributes, and a few other things + + if(deep && result) + { + for(Node n = importedNode->firstChild(); !n.isNull(); n = n.nextSibling()) + result->appendChild(importNode(n.handle(), true, exceptioncode), exceptioncode); + } + + return result; +} + +ElementImpl *DocumentImpl::createElementNS( const DOMString &_namespaceURI, const DOMString &_qualifiedName, int* pExceptioncode ) +{ + ElementImpl *e = 0; + int colonPos = -2; + // check NAMESPACE_ERR/INVALID_CHARACTER_ERR + if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos, + false/*nameCanBeNull*/, false/*nameCanBeEmpty*/, + pExceptioncode)) + return 0; + DOMString prefix, localName; + splitPrefixLocalName(_qualifiedName.implementation(), prefix, localName, colonPos); + + if ((isHTMLDocument() && _namespaceURI.isNull()) || + (strcasecmp(_namespaceURI, XHTML_NAMESPACE) == 0 && localName == localName.lower())) { + e = createHTMLElement(localName); + if (e) { + int _exceptioncode = 0; + if (!prefix.isNull()) + e->setPrefix(prefix, _exceptioncode); + if ( _exceptioncode ) { + if ( pExceptioncode ) *pExceptioncode = _exceptioncode; + delete e; + return 0; + } + e->setHTMLCompat( _namespaceURI.isNull() && htmlMode() != XHtml ); + } + } + if (!e) { + Id id = getId(NodeImpl::ElementId, _namespaceURI.implementation(), prefix.implementation(), + localName.implementation(), false, false /*HTML already looked up*/); + e = new XMLElementImpl( getDocument(), id, prefix.implementation() ); + } + + return e; +} + +AttrImpl *DocumentImpl::createAttributeNS( const DOMString &_namespaceURI, + const DOMString &_qualifiedName, int* pExceptioncode) +{ + int colonPos = -2; + // check NAMESPACE_ERR/INVALID_CHARACTER_ERR + if (pExceptioncode && !checkQualifiedName(_qualifiedName, _namespaceURI, &colonPos, + false/*nameCanBeNull*/, false/*nameCanBeEmpty*/, + pExceptioncode)) + return 0; + DOMString prefix, localName; + splitPrefixLocalName(_qualifiedName.implementation(), prefix, localName, colonPos); + Id id = getId(NodeImpl::AttributeId, _namespaceURI.implementation(), prefix.implementation(), + localName.implementation(), false, true /*lookupHTML*/); + AttrImpl* attr = new AttrImpl(0, getDocument(), id, DOMString("").implementation(), + prefix.implementation()); + attr->setHTMLCompat( _namespaceURI.isNull() && htmlMode() != XHtml ); + return attr; +} + +ElementImpl *DocumentImpl::getElementById( const DOMString &elementId ) const +{ + QString stringKey = elementId.string(); + + ElementMappingCache::ItemInfo* info = m_getElementByIdCache.get(stringKey); + + if (!info) + return 0; + + //See if cache has an unambiguous answer. + if (info->nd) + return info->nd; + + //Now we actually have to walk. + QPtrStack<NodeImpl> nodeStack; + NodeImpl *current = _first; + + while(1) + { + if(!current) + { + if(nodeStack.isEmpty()) break; + current = nodeStack.pop(); + current = current->nextSibling(); + } + else + { + if(current->isElementNode()) + { + ElementImpl *e = static_cast<ElementImpl *>(current); + if(e->getAttribute(ATTR_ID) == elementId) { + info->nd = e; + return e; + } + } + + NodeImpl *child = current->firstChild(); + if(child) + { + nodeStack.push(current); + current = child; + } + else + { + current = current->nextSibling(); + } + } + } + + assert(0); //If there is no item with such an ID, we should never get here + + //kdDebug() << "WARNING: *DocumentImpl::getElementById not found " << elementId.string() << endl; + + return 0; +} + +void DocumentImpl::setTitle(const DOMString& _title) +{ + if (_title == m_title && !m_title.isNull()) return; + + m_title = _title; + + QString titleStr = m_title.string(); + for (unsigned int i = 0; i < titleStr.length(); ++i) + if (titleStr[i] < ' ') + titleStr[i] = ' '; + titleStr = titleStr.simplifyWhiteSpace(); + titleStr.compose(); + if ( view() && !view()->part()->parentPart() ) { + if (titleStr.isNull() || titleStr.isEmpty()) { + // empty title... set window caption as the URL + KURL url = m_url; + url.setRef(QString::null); + url.setQuery(QString::null); + titleStr = url.prettyURL(); + } + + emit view()->part()->setWindowCaption( KStringHandler::csqueeze( titleStr, 128 ) ); + } +} + +DOMString DocumentImpl::nodeName() const +{ + return "#document"; +} + +unsigned short DocumentImpl::nodeType() const +{ + return Node::DOCUMENT_NODE; +} + +DOMStringImpl* DocumentImpl::textContent() const +{ + return 0; +} + +void DocumentImpl::setTextContent( const DOMString&, int& ) +{} + +ElementImpl *DocumentImpl::createHTMLElement( const DOMString &name ) +{ + uint id = khtml::getTagID( name.string().lower().latin1(), name.string().length() ); +// id = makeId(xhtmlNamespace, id); + + ElementImpl *n = 0; + switch(id) + { + case ID_HTML: + n = new HTMLHtmlElementImpl(docPtr()); + break; + case ID_HEAD: + n = new HTMLHeadElementImpl(docPtr()); + break; + case ID_BODY: + n = new HTMLBodyElementImpl(docPtr()); + break; + +// head elements + case ID_BASE: + n = new HTMLBaseElementImpl(docPtr()); + break; + case ID_LINK: + n = new HTMLLinkElementImpl(docPtr()); + break; + case ID_META: + n = new HTMLMetaElementImpl(docPtr()); + break; + case ID_STYLE: + n = new HTMLStyleElementImpl(docPtr()); + break; + case ID_TITLE: + n = new HTMLTitleElementImpl(docPtr()); + break; + +// frames + case ID_FRAME: + n = new HTMLFrameElementImpl(docPtr()); + break; + case ID_FRAMESET: + n = new HTMLFrameSetElementImpl(docPtr()); + break; + case ID_IFRAME: + n = new HTMLIFrameElementImpl(docPtr()); + break; + +// form elements +// ### FIXME: we need a way to set form dependency after we have made the form elements + case ID_FORM: + n = new HTMLFormElementImpl(docPtr(), false); + break; + case ID_BUTTON: + n = new HTMLButtonElementImpl(docPtr()); + break; + case ID_FIELDSET: + n = new HTMLFieldSetElementImpl(docPtr()); + break; + case ID_INPUT: + n = new HTMLInputElementImpl(docPtr()); + break; + case ID_ISINDEX: + n = new HTMLIsIndexElementImpl(docPtr()); + break; + case ID_LABEL: + n = new HTMLLabelElementImpl(docPtr()); + break; + case ID_LEGEND: + n = new HTMLLegendElementImpl(docPtr()); + break; + case ID_OPTGROUP: + n = new HTMLOptGroupElementImpl(docPtr()); + break; + case ID_OPTION: + n = new HTMLOptionElementImpl(docPtr()); + break; + case ID_SELECT: + n = new HTMLSelectElementImpl(docPtr()); + break; + case ID_TEXTAREA: + n = new HTMLTextAreaElementImpl(docPtr()); + break; + +// lists + case ID_DL: + n = new HTMLDListElementImpl(docPtr()); + break; + case ID_DD: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + case ID_DT: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + case ID_UL: + n = new HTMLUListElementImpl(docPtr()); + break; + case ID_OL: + n = new HTMLOListElementImpl(docPtr()); + break; + case ID_DIR: + n = new HTMLDirectoryElementImpl(docPtr()); + break; + case ID_MENU: + n = new HTMLMenuElementImpl(docPtr()); + break; + case ID_LI: + n = new HTMLLIElementImpl(docPtr()); + break; + +// formatting elements (block) + case ID_DIV: + case ID_P: + n = new HTMLDivElementImpl( docPtr(), id ); + break; + case ID_BLOCKQUOTE: + case ID_H1: + case ID_H2: + case ID_H3: + case ID_H4: + case ID_H5: + case ID_H6: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + case ID_HR: + n = new HTMLHRElementImpl(docPtr()); + break; + case ID_PLAINTEXT: + case ID_XMP: + case ID_PRE: + n = new HTMLPreElementImpl(docPtr(), id); + break; + +// font stuff + case ID_BASEFONT: + n = new HTMLBaseFontElementImpl(docPtr()); + break; + case ID_FONT: + n = new HTMLFontElementImpl(docPtr()); + break; + +// ins/del + case ID_DEL: + case ID_INS: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + +// anchor + case ID_A: + n = new HTMLAnchorElementImpl(docPtr()); + break; + +// images + case ID_IMG: + n = new HTMLImageElementImpl(docPtr()); + break; + case ID_MAP: + n = new HTMLMapElementImpl(docPtr()); + /*n = map;*/ + break; + case ID_AREA: + n = new HTMLAreaElementImpl(docPtr()); + break; + +// objects, applets and scripts + case ID_APPLET: + n = new HTMLAppletElementImpl(docPtr()); + break; + case ID_OBJECT: + n = new HTMLObjectElementImpl(docPtr()); + break; + case ID_EMBED: + n = new HTMLEmbedElementImpl(docPtr()); + break; + case ID_PARAM: + n = new HTMLParamElementImpl(docPtr()); + break; + case ID_SCRIPT: + n = new HTMLScriptElementImpl(docPtr()); + break; + +// tables + case ID_TABLE: + n = new HTMLTableElementImpl(docPtr()); + break; + case ID_CAPTION: + n = new HTMLTableCaptionElementImpl(docPtr()); + break; + case ID_COLGROUP: + case ID_COL: + n = new HTMLTableColElementImpl(docPtr(), id); + break; + case ID_TR: + n = new HTMLTableRowElementImpl(docPtr()); + break; + case ID_TD: + case ID_TH: + n = new HTMLTableCellElementImpl(docPtr(), id); + break; + case ID_THEAD: + case ID_TBODY: + case ID_TFOOT: + n = new HTMLTableSectionElementImpl(docPtr(), id, false); + break; + +// inline elements + case ID_BR: + n = new HTMLBRElementImpl(docPtr()); + break; + case ID_Q: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + +// elements with no special representation in the DOM + +// block: + case ID_ADDRESS: + case ID_CENTER: + n = new HTMLGenericElementImpl(docPtr(), id); + break; +// inline + // %fontstyle + 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: + case ID_BDO: + case ID_NOFRAMES: + n = new HTMLGenericElementImpl(docPtr(), id); + break; + + case ID_MARQUEE: + n = new HTMLMarqueeElementImpl(docPtr()); + break; +// text + case ID_TEXT: + kdDebug( 6020 ) << "Use document->createTextNode()" << endl; + break; + + default: + break; + } + return n; +} + +QString DocumentImpl::nextState() +{ + QString state; + if (!m_state.isEmpty()) + { + state = m_state.first(); + m_state.remove(m_state.begin()); + } + return state; +} + +QStringList DocumentImpl::docState() +{ + QStringList s; + for (QPtrListIterator<NodeImpl> it(m_maintainsState); it.current(); ++it) + s.append(it.current()->state()); + + return s; +} + +bool DocumentImpl::unsubmittedFormChanges() +{ + for (QPtrListIterator<NodeImpl> it(m_maintainsState); it.current(); ++it) + if (it.current()->state().right(1)=="M") + return true; + + return false; +} + +RangeImpl *DocumentImpl::createRange() +{ + return new RangeImpl( docPtr() ); +} + +NodeIteratorImpl *DocumentImpl::createNodeIterator(NodeImpl *root, unsigned long whatToShow, + NodeFilter &filter, bool entityReferenceExpansion, + int &exceptioncode) +{ + if (!root) { + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + return 0; + } + + return new NodeIteratorImpl(root,whatToShow,filter,entityReferenceExpansion); +} + +TreeWalkerImpl *DocumentImpl::createTreeWalker(NodeImpl *root, unsigned long whatToShow, NodeFilterImpl *filter, + bool entityReferenceExpansion, int &exceptioncode) +{ + if (!root) { + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + return 0; + } + + return new TreeWalkerImpl( root, whatToShow, filter, entityReferenceExpansion ); +} + +void DocumentImpl::setDocumentChanged(bool b) +{ + if (!changedDocuments) + changedDocuments = s_changedDocumentsDeleter.setObject( changedDocuments, new QPtrList<DocumentImpl>() ); + + if (b && !m_docChanged) + changedDocuments->append(this); + else if (!b && m_docChanged) + changedDocuments->remove(this); + m_docChanged = b; +} + +void DocumentImpl::recalcStyle( StyleChange change ) +{ +// qDebug("recalcStyle(%p)", this); +// QTime qt; +// qt.start(); + if (m_inStyleRecalc) + return; // Guard against re-entrancy. -dwh + + m_inStyleRecalc = true; + + if( !m_render ) goto bail_out; + + if ( change == Force ) { + RenderStyle* oldStyle = m_render->style(); + if ( oldStyle ) oldStyle->ref(); + RenderStyle* _style = new RenderStyle(); + _style->setDisplay(BLOCK); + _style->setVisuallyOrdered( visuallyOrdered ); + // ### make the font stuff _really_ work!!!! + + khtml::FontDef fontDef; + QFont f = KGlobalSettings::generalFont(); + fontDef.family = f.family(); + fontDef.italic = f.italic(); + fontDef.weight = f.weight(); + if (m_view) { + const KHTMLSettings *settings = m_view->part()->settings(); + QString stdfont = settings->stdFontName(); + if ( !stdfont.isEmpty() ) + fontDef.family = stdfont; + + fontDef.size = m_styleSelector->fontSizes()[3]; + } + + //kdDebug() << "DocumentImpl::attach: setting to charset " << settings->charset() << endl; + _style->setFontDef(fontDef); + _style->htmlFont().update( paintDeviceMetrics() ); + if ( inCompatMode() ) + _style->setHtmlHacks(true); // enable html specific rendering tricks + + StyleChange ch = diff( _style, oldStyle ); + if(m_render && ch != NoChange) + m_render->setStyle(_style); + else + delete _style; + if ( change != Force ) + change = ch; + + if (oldStyle) + oldStyle->deref(); + } + + NodeImpl *n; + for (n = _first; n; n = n->nextSibling()) + if ( change>= Inherit || n->hasChangedChild() || n->changed() ) + n->recalcStyle( change ); + //kdDebug( 6020 ) << "TIME: recalcStyle() dt=" << qt.elapsed() << endl; + + if (changed() && m_view) + m_view->layout(); + +bail_out: + setChanged( false ); + setHasChangedChild( false ); + setDocumentChanged( false ); + + m_inStyleRecalc = false; +} + +void DocumentImpl::updateRendering() +{ + if (!hasChangedChild()) return; + +// QTime time; +// time.start(); +// kdDebug() << "UPDATERENDERING: "<<endl; + + StyleChange change = NoChange; +#if 0 + if ( m_styleSelectorDirty ) { + recalcStyleSelector(); + change = Force; + } +#endif + recalcStyle( change ); + +// kdDebug() << "UPDATERENDERING time used="<<time.elapsed()<<endl; +} + +void DocumentImpl::updateDocumentsRendering() +{ + if (!changedDocuments) + return; + + while ( !changedDocuments->isEmpty() ) { + changedDocuments->first(); + DocumentImpl* it = changedDocuments->take(); + if (it->isDocumentChanged()) + it->updateRendering(); + } +} + +void DocumentImpl::updateLayout() +{ + bool oldIgnore = m_ignorePendingStylesheets; + + if (!haveStylesheetsLoaded()) { + m_ignorePendingStylesheets = true; + updateStyleSelector(); + } + + updateRendering(); + + // Only do a layout if changes have occurred that make it necessary. + if (m_view && renderer() && renderer()->needsLayout()) + m_view->layout(); + + m_ignorePendingStylesheets = oldIgnore; +} + +void DocumentImpl::attach() +{ + assert(!attached()); + + if ( m_view ) + setPaintDevice( m_view ); + + if (!m_renderArena) + m_renderArena.reset(new RenderArena()); + + // Create the rendering tree + assert(!m_styleSelector); + m_styleSelector = new CSSStyleSelector( this, m_usersheet, m_styleSheets, m_url, + !inCompatMode() ); + m_render = new (m_renderArena.get()) RenderCanvas(this, m_view); + m_styleSelector->computeFontSizes(paintDeviceMetrics(), m_view ? m_view->part()->zoomFactor() : 100); + recalcStyle( Force ); + + RenderObject* render = m_render; + m_render = 0; + + NodeBaseImpl::attach(); + m_render = render; +} + +void DocumentImpl::detach() +{ + RenderObject* render = m_render; + + // indicate destruction mode, i.e. attached() but m_render == 0 + m_render = 0; + + delete m_tokenizer; + m_tokenizer = 0; + + // Empty out these lists as a performance optimization + m_imageLoadEventDispatchSoonList.clear(); + m_imageLoadEventDispatchingList.clear(); + NodeBaseImpl::detach(); + + if ( render ) + render->detach(); + + m_view = 0; + + m_renderArena.reset(); +} + +void DocumentImpl::setVisuallyOrdered() +{ + visuallyOrdered = true; + if (m_render) + m_render->style()->setVisuallyOrdered(true); +} + +void DocumentImpl::setSelection(NodeImpl* s, int sp, NodeImpl* e, int ep) +{ + if ( m_render ) + static_cast<RenderCanvas*>(m_render)->setSelection(s->renderer(),sp,e->renderer(),ep); +} + +void DocumentImpl::clearSelection() +{ + if ( m_render ) + static_cast<RenderCanvas*>(m_render)->clearSelection(); +} + +khtml::Tokenizer *DocumentImpl::createTokenizer() +{ + return new khtml::XMLTokenizer(docPtr(),m_view); +} + +void DocumentImpl::setPaintDevice( QPaintDevice *dev ) +{ + if (m_paintDevice != dev) { + m_paintDevice = dev; + delete m_paintDeviceMetrics; + m_paintDeviceMetrics = new QPaintDeviceMetrics( dev ); + } +} + +void DocumentImpl::open( bool clearEventListeners ) +{ + if (parsing()) return; + + if (m_tokenizer) + close(); + + delete m_tokenizer; + m_tokenizer = 0; + + KHTMLView* view = m_view; + bool was_attached = attached(); + if ( was_attached ) + detach(); + + removeChildren(); + delete m_styleSelector; + m_styleSelector = 0; + m_view = view; + if ( was_attached ) + attach(); + + if (clearEventListeners) + m_windowEventListeners.clear(); + + m_tokenizer = createTokenizer(); + m_decoderMibEnum = 0; + connect(m_tokenizer,SIGNAL(finishedParsing()),this,SIGNAL(finishedParsing())); + m_tokenizer->begin(); +} + +HTMLElementImpl* DocumentImpl::body() +{ + NodeImpl *de = documentElement(); + if (!de) + return 0; + + // try to prefer a FRAMESET element over BODY + NodeImpl* body = 0; + for (NodeImpl* i = de->firstChild(); i; i = i->nextSibling()) { + if (i->id() == ID_FRAMESET) + return static_cast<HTMLElementImpl*>(i); + + if (i->id() == ID_BODY) + body = i; + } + return static_cast<HTMLElementImpl *>(body); +} + +void DocumentImpl::close( ) +{ + if (parsing() || !m_tokenizer) return; + + if ( m_render ) + m_render->close(); + + // on an explicit document.close(), the tokenizer might still be waiting on scripts, + // and in that case we don't want to destroy it because that will prevent the + // scripts from getting processed. + if (m_tokenizer && !m_tokenizer->isWaitingForScripts() && !m_tokenizer->isExecutingScript()) { + delete m_tokenizer; + m_tokenizer = 0; + } + + if (m_view) + m_view->part()->checkEmitLoadEvent(); +} + +void DocumentImpl::write( const DOMString &text ) +{ + write(text.string()); +} + +void DocumentImpl::write( const QString &text ) +{ + if (!m_tokenizer) { + open(); + if (m_view) + m_view->part()->resetFromScript(); + m_tokenizer->setAutoClose(); + write(QString::fromLatin1("<html>")); + } + m_tokenizer->write(text, false); +} + +void DocumentImpl::writeln( const DOMString &text ) +{ + write(text); + write(DOMString("\n")); +} + +void DocumentImpl::finishParsing ( ) +{ + if(m_tokenizer) + m_tokenizer->finish(); +} + +void DocumentImpl::setUserStyleSheet( const QString& sheet ) +{ + if ( m_usersheet != sheet ) { + m_usersheet = sheet; + updateStyleSelector(); + } +} + +CSSStyleSheetImpl* DocumentImpl::elementSheet() +{ + if (!m_elemSheet) { + m_elemSheet = new CSSStyleSheetImpl(this, baseURL().url() ); + m_elemSheet->ref(); + } + return m_elemSheet; +} + +void DocumentImpl::determineParseMode( const QString &/*str*/ ) +{ + // For XML documents, use strict parse mode + pMode = Strict; + hMode = XHtml; + kdDebug(6020) << " using strict parseMode" << endl; +} + +NodeImpl *DocumentImpl::nextFocusNode(NodeImpl *fromNode) +{ + unsigned short fromTabIndex; + + if (!fromNode) { + // No starting node supplied; begin with the top of the document + NodeImpl *n; + + int lowestTabIndex = 65535; + for (n = this; n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable()) { + if ((n->tabIndex() > 0) && (n->tabIndex() < lowestTabIndex)) + lowestTabIndex = n->tabIndex(); + } + } + + if (lowestTabIndex == 65535) + lowestTabIndex = 0; + + // Go to the first node in the document that has the desired tab index + for (n = this; n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable() && (n->tabIndex() == lowestTabIndex)) + return n; + } + + return 0; + } + else { + fromTabIndex = fromNode->tabIndex(); + } + + if (fromTabIndex == 0) { + // Just need to find the next selectable node after fromNode (in document order) that doesn't have a tab index + NodeImpl *n = fromNode->traverseNextNode(); + while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) + n = n->traverseNextNode(); + return n; + } + else { + // Find the lowest tab index out of all the nodes except fromNode, that is greater than or equal to fromNode's + // tab index. For nodes with the same tab index as fromNode, we are only interested in those that come after + // fromNode in document order. + // If we don't find a suitable tab index, the next focus node will be one with a tab index of 0. + unsigned short lowestSuitableTabIndex = 65535; + NodeImpl *n; + + bool reachedFromNode = false; + for (n = this; n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable() && + ((reachedFromNode && (n->tabIndex() >= fromTabIndex)) || + (!reachedFromNode && (n->tabIndex() > fromTabIndex))) && + (n->tabIndex() < lowestSuitableTabIndex) && + (n != fromNode)) { + + // We found a selectable node with a tab index at least as high as fromNode's. Keep searching though, + // as there may be another node which has a lower tab index but is still suitable for use. + lowestSuitableTabIndex = n->tabIndex(); + } + + if (n == fromNode) + reachedFromNode = true; + } + + if (lowestSuitableTabIndex == 65535) { + // No next node with a tab index -> just take first node with tab index of 0 + NodeImpl *n = this; + while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) + n = n->traverseNextNode(); + return n; + } + + // Search forwards from fromNode + for (n = fromNode->traverseNextNode(); n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex)) + return n; + } + + // The next node isn't after fromNode, start from the beginning of the document + for (n = this; n != fromNode; n = n->traverseNextNode()) { + if (n->isTabFocusable() && (n->tabIndex() == lowestSuitableTabIndex)) + return n; + } + + assert(false); // should never get here + return 0; + } +} + +NodeImpl *DocumentImpl::previousFocusNode(NodeImpl *fromNode) +{ + NodeImpl *lastNode = this; + while (lastNode->lastChild()) + lastNode = lastNode->lastChild(); + + if (!fromNode) { + // No starting node supplied; begin with the very last node in the document + NodeImpl *n; + + int highestTabIndex = 0; + for (n = lastNode; n != 0; n = n->traversePreviousNode()) { + if (n->isTabFocusable()) { + if (n->tabIndex() == 0) + return n; + else if (n->tabIndex() > highestTabIndex) + highestTabIndex = n->tabIndex(); + } + } + + // No node with a tab index of 0; just go to the last node with the highest tab index + for (n = lastNode; n != 0; n = n->traversePreviousNode()) { + if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex)) + return n; + } + + return 0; + } + else { + unsigned short fromTabIndex = fromNode->tabIndex(); + + if (fromTabIndex == 0) { + // Find the previous selectable node before fromNode (in document order) that doesn't have a tab index + NodeImpl *n = fromNode->traversePreviousNode(); + while (n && !(n->isTabFocusable() && n->tabIndex() == 0)) + n = n->traversePreviousNode(); + if (n) + return n; + + // No previous nodes with a 0 tab index, go to the last node in the document that has the highest tab index + int highestTabIndex = 0; + for (n = this; n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable() && (n->tabIndex() > highestTabIndex)) + highestTabIndex = n->tabIndex(); + } + + if (highestTabIndex == 0) + return 0; + + for (n = lastNode; n != 0; n = n->traversePreviousNode()) { + if (n->isTabFocusable() && (n->tabIndex() == highestTabIndex)) + return n; + } + + assert(false); // should never get here + return 0; + } + else { + // Find the lowest tab index out of all the nodes except fromNode, that is less than or equal to fromNode's + // tab index. For nodes with the same tab index as fromNode, we are only interested in those before + // fromNode. + // If we don't find a suitable tab index, then there will be no previous focus node. + unsigned short highestSuitableTabIndex = 0; + NodeImpl *n; + + bool reachedFromNode = false; + for (n = this; n != 0; n = n->traverseNextNode()) { + if (n->isTabFocusable() && + ((!reachedFromNode && (n->tabIndex() <= fromTabIndex)) || + (reachedFromNode && (n->tabIndex() < fromTabIndex))) && + (n->tabIndex() > highestSuitableTabIndex) && + (n != fromNode)) { + + // We found a selectable node with a tab index no higher than fromNode's. Keep searching though, as + // there may be another node which has a higher tab index but is still suitable for use. + highestSuitableTabIndex = n->tabIndex(); + } + + if (n == fromNode) + reachedFromNode = true; + } + + if (highestSuitableTabIndex == 0) { + // No previous node with a tab index. Since the order specified by HTML is nodes with tab index > 0 + // first, this means that there is no previous node. + return 0; + } + + // Search backwards from fromNode + for (n = fromNode->traversePreviousNode(); n != 0; n = n->traversePreviousNode()) { + if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex)) + return n; + } + // The previous node isn't before fromNode, start from the end of the document + for (n = lastNode; n != fromNode; n = n->traversePreviousNode()) { + if (n->isTabFocusable() && (n->tabIndex() == highestSuitableTabIndex)) + return n; + } + + assert(false); // should never get here + return 0; + } + } +} + +ElementImpl* DocumentImpl::findAccessKeyElement(QChar c) +{ + c = c.upper(); + for( NodeImpl* n = this; + n != NULL; + n = n->traverseNextNode()) { + if( n->isElementNode()) { + ElementImpl* en = static_cast< ElementImpl* >( n ); + DOMString s = en->getAttribute( ATTR_ACCESSKEY ); + if( s.length() == 1 + && s[ 0 ].upper() == c ) + return en; + } + } + return NULL; +} + +int DocumentImpl::nodeAbsIndex(NodeImpl *node) +{ + assert(node->getDocument() == this); + + int absIndex = 0; + for (NodeImpl *n = node; n && n != this; n = n->traversePreviousNode()) + absIndex++; + return absIndex; +} + +NodeImpl *DocumentImpl::nodeWithAbsIndex(int absIndex) +{ + NodeImpl *n = this; + for (int i = 0; n && (i < absIndex); i++) { + n = n->traverseNextNode(); + } + return n; +} + +void DocumentImpl::processHttpEquiv(const DOMString &equiv, const DOMString &content) +{ + assert(!equiv.isNull() && !content.isNull()); + + KHTMLView *v = getDocument()->view(); + + if(strcasecmp(equiv, "refresh") == 0 && v && v->part()->metaRefreshEnabled()) + { + // get delay and url + QString str = content.string().stripWhiteSpace(); + int pos = str.find(QRegExp("[;,]")); + if ( pos == -1 ) + pos = str.find(QRegExp("[ \t]")); + + bool ok = false; + int delay = kMax( 0, content.implementation()->toInt(&ok) ); + if ( !ok && str.length() && str[0] == '.' ) + ok = true; + + if (pos == -1) // There can be no url (David) + { + if(ok) + v->part()->scheduleRedirection(delay, v->part()->url().url() ); + } else { + pos++; + while(pos < (int)str.length() && str[pos].isSpace()) pos++; + str = str.mid(pos); + if(str.find("url", 0, false ) == 0) str = str.mid(3); + str = str.stripWhiteSpace(); + if ( str.length() && str[0] == '=' ) str = str.mid( 1 ).stripWhiteSpace(); + while(str.length() && + (str[str.length()-1] == ';' || str[str.length()-1] == ',')) + str.setLength(str.length()-1); + str = parseURL( DOMString(str) ).string(); + QString newURL = getDocument()->completeURL( str ); + if ( ok ) + v->part()->scheduleRedirection(delay, getDocument()->completeURL( str ), delay < 2 || newURL == URL().url()); + } + } + else if(strcasecmp(equiv, "expires") == 0) + { + bool relative = false; + QString str = content.string().stripWhiteSpace(); + time_t expire_date = KRFCDate::parseDate(str); + if (!expire_date) + { + expire_date = str.toULong(); + relative = true; + } + if (!expire_date) + expire_date = 1; // expire now + if (m_docLoader) + m_docLoader->setExpireDate(expire_date, relative); + } + else if(v && (strcasecmp(equiv, "pragma") == 0 || strcasecmp(equiv, "cache-control") == 0)) + { + QString str = content.string().lower().stripWhiteSpace(); + KURL url = v->part()->url(); + if ((str == "no-cache") && url.protocol().startsWith("http")) + { + KIO::http_update_cache(url, true, 0); + } + } + else if( (strcasecmp(equiv, "set-cookie") == 0)) + { + // ### make setCookie work on XML documents too; e.g. in case of <html:meta .....> + HTMLDocumentImpl *d = static_cast<HTMLDocumentImpl *>(this); + d->setCookie(content); + } + else if (strcasecmp(equiv, "default-style") == 0) { + // HTML 4.0 14.3.2 + // http://www.hixie.ch/tests/evil/css/import/main/preferred.html + m_preferredStylesheetSet = content; + updateStyleSelector(); + } + else if (strcasecmp(equiv, "content-language") == 0) { + m_contentLanguage = content.string(); + } +} + +bool DocumentImpl::prepareMouseEvent( bool readonly, int _x, int _y, MouseEvent *ev ) +{ + if ( m_render ) { + assert(m_render->isCanvas()); + RenderObject::NodeInfo renderInfo(readonly, ev->type == MousePress); + bool isInside = m_render->layer()->nodeAtPoint(renderInfo, _x, _y); + ev->innerNode = renderInfo.innerNode(); + ev->innerNonSharedNode = renderInfo.innerNonSharedNode(); + + if (renderInfo.URLElement()) { + assert(renderInfo.URLElement()->isElementNode()); + //qDebug("urlnode: %s (%d)", getTagName(renderInfo.URLElement()->id()).string().latin1(), renderInfo.URLElement()->id()); + + ElementImpl* e = static_cast<ElementImpl*>(renderInfo.URLElement()); + DOMString href = khtml::parseURL(e->getAttribute(ATTR_HREF)); + DOMString target = e->getAttribute(ATTR_TARGET); + + if (!target.isNull() && !href.isNull()) { + ev->target = target; + ev->url = href; + } + else + ev->url = href; + } + + if (!readonly) + updateRendering(); + + return isInside; + } + + + return false; +} + + +// DOM Section 1.1.1 +bool DocumentImpl::childTypeAllowed( unsigned short type ) +{ + switch (type) { + case Node::ATTRIBUTE_NODE: + case Node::CDATA_SECTION_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + case Node::DOCUMENT_NODE: + case Node::ENTITY_NODE: + case Node::ENTITY_REFERENCE_NODE: + case Node::NOTATION_NODE: + case Node::TEXT_NODE: +// case Node::XPATH_NAMESPACE_NODE: + return false; + case Node::COMMENT_NODE: + case Node::PROCESSING_INSTRUCTION_NODE: + return true; + case Node::DOCUMENT_TYPE_NODE: + case Node::ELEMENT_NODE: + // Documents may contain no more than one of each of these. + // (One Element and one DocumentType.) + for (NodeImpl* c = firstChild(); c; c = c->nextSibling()) + if (c->nodeType() == type) + return false; + return true; + } + return false; +} + +NodeImpl *DocumentImpl::cloneNode ( bool /*deep*/ ) +{ + // Spec says cloning Document nodes is "implementation dependent" + // so we do not support it... + return 0; +} + + +typedef const char* (*NameLookupFunction)(unsigned short id); +typedef int (*IdLookupFunction)(const char *tagStr, int len); + +NodeImpl::Id DocumentImpl::getId( NodeImpl::IdType _type, DOMStringImpl* _nsURI, DOMStringImpl *_prefix, + DOMStringImpl *_name, bool readonly, bool /*lookupHTML*/, int *pExceptioncode) +{ + /*kdDebug() << "DocumentImpl::getId( type: " << _type << ", uri: " << DOMString(_nsURI).string() + << ", prefix: " << DOMString(_prefix).string() << ", name: " << DOMString(_name).string() + << ", readonly: " << readonly + << ", lookupHTML: " << lookupHTML + << ", exceptions: " << (pExceptioncode ? "yes" : "no") + << " )" << endl;*/ + + if(!_name) return 0; + IdNameMapping *map; + IdLookupFunction lookup; + + switch (_type) { + case NodeImpl::ElementId: + map = m_elementMap; + lookup = getTagID; + break; + case NodeImpl::AttributeId: + map = m_attrMap; + lookup = getAttrID; + break; + case NodeImpl::NamespaceId: + if( strcasecmp(_name, XHTML_NAMESPACE) == 0) + return xhtmlNamespace; + if( _name->l == 0) + return emptyNamespace; + // defaultNamespace handled by "if (!_name) return 0" + map = m_namespaceMap; + lookup = 0; + break; + default: + return 0; + } + // Names and attributes with "" + if (_name->l == 0) return 0; + + NodeImpl::Id id, nsid = 0; + QConstString n(_name->s, _name->l); + bool cs = true; // case sensitive + if (_type != NodeImpl::NamespaceId) { + if (_nsURI) + nsid = getId( NodeImpl::NamespaceId, 0, 0, _nsURI, false, false, 0 ) << 16; + + // Each document maintains a mapping of tag name -> id for every tag name encountered + // in the document. + cs = (htmlMode() == XHtml) || (_nsURI && _type != NodeImpl::AttributeId); + + // First see if it's a HTML element name + // xhtml is lower case - case sensitive, easy to implement + if ( cs && (id = lookup(n.string().ascii(), _name->l)) ) { + map->addAlias(_prefix, _name, cs, id); + return nsid + id; + } + // compatibility: upper case - case insensitive + if ( !cs && (id = lookup(n.string().lower().ascii(), _name->l )) ) { + map->addAlias(_prefix, _name, cs, id); + return nsid + id; + } + } + + // Look in the names array for the name + // compatibility mode has to lookup upper case + QString name = cs ? n.string() : n.string().upper(); + + if (!_nsURI) { + id = (NodeImpl::Id)(long) map->ids.find( name ); + if (!id && _type != NodeImpl::NamespaceId) { + id = (NodeImpl::Id)(long) map->ids.find( "aliases: " + name ); + } + } else { + id = (NodeImpl::Id)(long) map->ids.find( name ); + if (!readonly && id && _prefix && _prefix->l) { + // we were called in registration mode... check if the alias exists + QConstString px( _prefix->s, _prefix->l ); + QString qn("aliases: " + (cs ? px.string() : px.string().upper()) + ":" + name); + if (!map->ids.find( qn )) { + map->ids.insert( qn, (void*)id ); + } + } + } + + if (id) return nsid + id; + + // unknown + if (readonly) return 0; + + if ( pExceptioncode && _type != NodeImpl::NamespaceId && !Element::khtmlValidQualifiedName(_name)) { + *pExceptioncode = DOMException::INVALID_CHARACTER_ERR; + return 0; + } + + // Name not found, so let's add it + NodeImpl::Id cid = map->count++ + map->idStart; + map->names.insert( cid, _name ); + _name->ref(); + + map->ids.insert( name, (void*)cid ); + + // and register an alias if needed for DOM1 methods compatibility + map->addAlias(_prefix, _name, cs, cid); + + return nsid + cid; + } + +NodeImpl::Id DocumentImpl::getId( NodeImpl::IdType _type, DOMStringImpl *_nodeName, bool readonly, bool lookupHTML, int *pExceptioncode) +{ + return getId(_type, 0, 0, _nodeName, readonly, lookupHTML, pExceptioncode); +} + +DOMString DocumentImpl::getName( NodeImpl::IdType _type, NodeImpl::Id _id ) const +{ + IdNameMapping *map; + NameLookupFunction lookup; + bool hasNS = (namespacePart(_id) != defaultNamespace); + switch (_type) { + case NodeImpl::ElementId: + map = m_elementMap; + lookup = getTagName; + break; + case NodeImpl::AttributeId: + map = m_attrMap; + lookup = getAttrName; + break; + case NodeImpl::NamespaceId: + if( _id == xhtmlNamespace ) + return XHTML_NAMESPACE; + else + if( _id == emptyNamespace ) + return DOMString(""); + else + if ( _id == defaultNamespace ) + return DOMString(); + map = m_namespaceMap; + lookup = 0; + break; + default: + return DOMString();; + } + _id = localNamePart(_id) ; + if (_id >= map->idStart) { + return map->names[_id]; + } + else if (lookup) { + // ### put them in a cache + if (hasNS) + return DOMString(lookup(_id)).lower(); + else + return lookup(_id); + } else + return DOMString(); +} + +// This method is called whenever a top-level stylesheet has finished loading. +void DocumentImpl::styleSheetLoaded() +{ + // Make sure we knew this sheet was pending, and that our count isn't out of sync. + assert(m_pendingStylesheets > 0); + + m_pendingStylesheets--; + updateStyleSelector(); +} + +DOMString DocumentImpl::selectedStylesheetSet() const +{ + if (!view()) return DOMString(); + + return view()->part()->d->m_sheetUsed; +} + +void DocumentImpl::setSelectedStylesheetSet(const DOMString& s) +{ + // this code is evil + if (view() && view()->part()->d->m_sheetUsed != s.string()) { + view()->part()->d->m_sheetUsed = s.string(); + updateStyleSelector(); + } +} + +void DocumentImpl::addStyleSheet(StyleSheetImpl *sheet, int *exceptioncode) +{ + int excode = 0; + + if (!m_addedStyleSheets) { + m_addedStyleSheets = new StyleSheetListImpl; + m_addedStyleSheets->ref(); + } + + m_addedStyleSheets->add(sheet); + if (sheet->isCSSStyleSheet()) updateStyleSelector(); + + if (exceptioncode) *exceptioncode = excode; +} + +void DocumentImpl::removeStyleSheet(StyleSheetImpl *sheet, int *exceptioncode) +{ + int excode = 0; + bool removed = false; + bool is_css = sheet->isCSSStyleSheet(); + + if (m_addedStyleSheets) { + bool in_main_list = !sheet->hasOneRef(); + removed = m_addedStyleSheets->styleSheets.removeRef(sheet); + sheet->deref(); + + if (m_addedStyleSheets->styleSheets.count() == 0) { + bool reset = m_addedStyleSheets->hasOneRef(); + m_addedStyleSheets->deref(); + if (reset) m_addedStyleSheets = 0; + } + + // remove from main list, too + if (in_main_list) m_styleSheets->remove(sheet); + } + + if (removed) { + if (is_css) updateStyleSelector(); + } else + excode = DOMException::NOT_FOUND_ERR; + + if (exceptioncode) *exceptioncode = excode; +} + +void DocumentImpl::updateStyleSelector(bool shallow) +{ +// kdDebug() << "PENDING " << m_pendingStylesheets << endl; + + // Don't bother updating, since we haven't loaded all our style info yet. + if (m_pendingStylesheets > 0) + return; + + if (shallow) + rebuildStyleSelector(); + else + recalcStyleSelector(); + recalcStyle(Force); +#if 0 + + m_styleSelectorDirty = true; +#endif + if ( renderer() ) + renderer()->setNeedsLayoutAndMinMaxRecalc(); +} + +void DocumentImpl::recalcStyleSelector() +{ + if ( !m_render || !attached() ) return; + + assert(m_pendingStylesheets==0); + + QPtrList<StyleSheetImpl> oldStyleSheets = m_styleSheets->styleSheets; + m_styleSheets->styleSheets.clear(); + QString sheetUsed = view() ? view()->part()->d->m_sheetUsed.replace("&&", "&") : QString(); + bool autoselect = sheetUsed.isEmpty(); + if (autoselect && !m_preferredStylesheetSet.isEmpty()) + sheetUsed = m_preferredStylesheetSet.string(); + NodeImpl *n; + for (int i=0 ; i<2 ; i++) { + m_availableSheets.clear(); + m_availableSheets << i18n("Basic Page Style"); + bool canResetSheet = false; + + for (n = this; n; n = n->traverseNextNode()) { + StyleSheetImpl *sheet = 0; + + if (n->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) + { + // Processing instruction (XML documents only) + ProcessingInstructionImpl* pi = static_cast<ProcessingInstructionImpl*>(n); + sheet = pi->sheet(); + if (!sheet && !pi->localHref().isEmpty()) + { + // Processing instruction with reference to an element in this document - e.g. + // <?xml-stylesheet href="#mystyle">, with the element + // <foo id="mystyle">heading { color: red; }</foo> at some location in + // the document + ElementImpl* elem = getElementById(pi->localHref()); + if (elem) { + DOMString sheetText(""); + NodeImpl *c; + for (c = elem->firstChild(); c; c = c->nextSibling()) { + if (c->nodeType() == Node::TEXT_NODE || c->nodeType() == Node::CDATA_SECTION_NODE) + sheetText += c->nodeValue(); + } + + CSSStyleSheetImpl *cssSheet = new CSSStyleSheetImpl(this); + cssSheet->parseString(sheetText); + pi->setStyleSheet(cssSheet); + sheet = cssSheet; + } + } + + } + else if (n->isHTMLElement() && ( n->id() == ID_LINK || n->id() == ID_STYLE) ) { + QString title; + if ( n->id() == ID_LINK ) { + HTMLLinkElementImpl* l = static_cast<HTMLLinkElementImpl*>(n); + if (l->isCSSStyleSheet()) { + sheet = l->sheet(); + + if (sheet || l->isLoading() || l->isAlternate() ) + title = l->getAttribute(ATTR_TITLE).string(); + + if ((autoselect || title != sheetUsed) && l->isDisabled()) { + sheet = 0; + } else if (!title.isEmpty() && !l->isAlternate() && sheetUsed.isEmpty()) { + sheetUsed = title; + l->setDisabled(false); + } + } + } + else { + // <STYLE> element + HTMLStyleElementImpl* s = static_cast<HTMLStyleElementImpl*>(n); + if (!s->isLoading()) { + sheet = s->sheet(); + if (sheet) title = s->getAttribute(ATTR_TITLE).string(); + } + if (!title.isEmpty() && sheetUsed.isEmpty()) + sheetUsed = title; + } + + if ( !title.isEmpty() ) { + if ( title != sheetUsed ) + sheet = 0; // don't use it + + title = title.replace('&', "&&"); + + if ( !m_availableSheets.contains( title ) ) + m_availableSheets.append( title ); + } + } + else if (n->isHTMLElement() && n->id() == ID_BODY) { + // <BODY> element (doesn't contain styles as such but vlink="..." and friends + // are treated as style declarations) + sheet = static_cast<HTMLBodyElementImpl*>(n)->sheet(); + } + + if (sheet) { + sheet->ref(); + m_styleSheets->styleSheets.append(sheet); + } + + // For HTML documents, stylesheets are not allowed within/after the <BODY> tag. So we + // can stop searching here. + if (isHTMLDocument() && n->id() == ID_BODY) { + canResetSheet = !canResetSheet; + break; + } + } + + // we're done if we don't select an alternative sheet + // or we found the sheet we selected + if (sheetUsed.isEmpty() || + (!canResetSheet && tokenizer()) || + m_availableSheets.contains(sheetUsed)) { + break; + } + + // the alternative sheet we used doesn't exist anymore + // so try from scratch again + if (view()) + view()->part()->d->m_sheetUsed = QString::null; + if (!m_preferredStylesheetSet.isEmpty() && !(sheetUsed == m_preferredStylesheetSet)) + sheetUsed = m_preferredStylesheetSet.string(); + else + sheetUsed = QString::null; + autoselect = true; + } + + // Include programmatically added style sheets + if (m_addedStyleSheets) { + QPtrListIterator<StyleSheetImpl> it = m_addedStyleSheets->styleSheets; + for (; *it; ++it) { + if ((*it)->isCSSStyleSheet() && !(*it)->disabled()) + m_styleSheets->add(*it); + } + } + + // De-reference all the stylesheets in the old list + QPtrListIterator<StyleSheetImpl> it(oldStyleSheets); + for (; it.current(); ++it) + it.current()->deref(); + + rebuildStyleSelector(); +} + +void DocumentImpl::rebuildStyleSelector() +{ + // Create a new style selector + delete m_styleSelector; + QString usersheet = m_usersheet; + if ( m_view && m_view->mediaType() == "print" ) + usersheet += m_printSheet; + m_styleSelector = new CSSStyleSelector( this, usersheet, m_styleSheets, m_url, + !inCompatMode() ); + + m_styleSelectorDirty = false; +} + +void DocumentImpl::setHoverNode(NodeImpl *newHoverNode) +{ + NodeImpl* oldHoverNode = m_hoverNode; + if (newHoverNode ) newHoverNode->ref(); + m_hoverNode = newHoverNode; + if ( oldHoverNode ) oldHoverNode->deref(); +} + +void DocumentImpl::setActiveNode(NodeImpl* newActiveNode) +{ + NodeImpl* oldActiveNode = m_activeNode; + if (newActiveNode ) newActiveNode->ref(); + m_activeNode = newActiveNode; + if ( oldActiveNode ) oldActiveNode->deref(); +} + +void DocumentImpl::setFocusNode(NodeImpl *newFocusNode) +{ + // don't process focus changes while detaching + if( !m_render ) return; + + // We do want to blur if a widget is being detached, + // but we don't want to emit events since that + // triggers updateLayout() and may recurse detach() + bool widgetDetach = m_focusNode && m_focusNode != this && + m_focusNode->renderer() && !m_focusNode->renderer()->parent(); + + // Make sure newFocusNode is actually in this document + if (newFocusNode && (newFocusNode->getDocument() != this)) + return; + + if (m_focusNode != newFocusNode) { + NodeImpl *oldFocusNode = m_focusNode; + // Set focus on the new node + m_focusNode = newFocusNode; + // Remove focus from the existing focus node (if any) + if (oldFocusNode) { + if (oldFocusNode->active()) + oldFocusNode->setActive(false); + + oldFocusNode->setFocus(false); + + if (!widgetDetach) { + oldFocusNode->dispatchHTMLEvent(EventImpl::BLUR_EVENT,false,false); + oldFocusNode->dispatchUIEvent(EventImpl::DOMFOCUSOUT_EVENT); + } + if ((oldFocusNode == this) && oldFocusNode->hasOneRef()) { + oldFocusNode->deref(); // deletes this + return; + } + else { + oldFocusNode->deref(); + } + } + + if (m_focusNode) { + m_focusNode->ref(); + m_focusNode->dispatchHTMLEvent(EventImpl::FOCUS_EVENT,false,false); + if (m_focusNode != newFocusNode) return; + m_focusNode->dispatchUIEvent(EventImpl::DOMFOCUSIN_EVENT); + if (m_focusNode != newFocusNode) return; + m_focusNode->setFocus(); + if (m_focusNode != newFocusNode) return; + + // eww, I suck. set the qt focus correctly + // ### find a better place in the code for this + if (view()) { + if (!m_focusNode->renderer() || !m_focusNode->renderer()->isWidget()) + view()->setFocus(); + else if (static_cast<RenderWidget*>(m_focusNode->renderer())->widget()) + { + if (view()->isVisible()) + static_cast<RenderWidget*>(m_focusNode->renderer())->widget()->setFocus(); + } + } + } else { + //We're blurring. Better clear the Qt focus/give it to the view... + if (view()) + view()->setFocus(); + } + + if (!widgetDetach) + updateRendering(); + } +} + +void DocumentImpl::setCSSTarget(NodeImpl* n) +{ + if (n == m_cssTarget) + return; + + if (m_cssTarget) { + m_cssTarget->setChanged(); + m_cssTarget->deref(); + } + m_cssTarget = n; + if (n) { + n->setChanged(); + n->ref(); + } +} + +void DocumentImpl::attachNodeIterator(NodeIteratorImpl *ni) +{ + m_nodeIterators.append(ni); +} + +void DocumentImpl::detachNodeIterator(NodeIteratorImpl *ni) +{ + m_nodeIterators.remove(ni); +} + +void DocumentImpl::notifyBeforeNodeRemoval(NodeImpl *n) +{ + QPtrListIterator<NodeIteratorImpl> it(m_nodeIterators); + for (; it.current(); ++it) + it.current()->notifyBeforeNodeRemoval(n); +} + +bool DocumentImpl::isURLAllowed(const QString& url) const +{ + KHTMLPart *thisPart = part(); + + KURL newURL(completeURL(url)); + newURL.setRef(QString::null); + + if (KHTMLFactory::defaultHTMLSettings()->isAdFiltered( newURL.url() )) + return false; + + // Prohibit non-file URLs if we are asked to. + if (!thisPart || thisPart->onlyLocalReferences() && newURL.protocol() != "file" && newURL.protocol() != "data") + return false; + + // do we allow this suburl ? + if ( !kapp || (newURL.protocol() != "javascript" && !kapp->authorizeURLAction("redirect", thisPart->url(), newURL)) ) + return false; + + // We allow one level of self-reference because some sites depend on that. + // But we don't allow more than one. + bool foundSelfReference = false; + for (KHTMLPart *part = thisPart; part; part = part->parentPart()) { + KURL partURL = part->url(); + partURL.setRef(QString::null); + if (partURL == newURL) { + if (foundSelfReference) + return false; + foundSelfReference = true; + } + } + + return true; +} + +void DocumentImpl::setDesignMode(bool b) +{ + if (part()) + part()->setEditable(b); +} + +bool DocumentImpl::designMode() const +{ + return part() ? part()->isEditable() : false; +} + +EventImpl *DocumentImpl::createEvent(const DOMString &eventType, int &exceptioncode) +{ + if (eventType == "UIEvents" || eventType == "UIEvent") + return new UIEventImpl(); + else if (eventType == "MouseEvents" || eventType == "MouseEvent") + return new MouseEventImpl(); + else if (eventType == "TextEvent") + return new TextEventImpl(); + else if (eventType == "KeyboardEvent") + return new KeyboardEventImpl(); + else if (eventType == "MutationEvents" || eventType == "MutationEvent") + return new MutationEventImpl(); + else if (eventType == "HTMLEvents" || eventType == "Events" || + eventType == "HTMLEvent" || eventType == "Event") + return new EventImpl(); + else { + exceptioncode = DOMException::NOT_SUPPORTED_ERR; + return 0; + } +} + +CSSStyleDeclarationImpl *DocumentImpl::getOverrideStyle(ElementImpl* /*elt*/, DOMStringImpl* /*pseudoElt*/) +{ + return 0; // ### +} + +void DocumentImpl::abort() +{ + if (m_inSyncLoad) { + m_inSyncLoad = false; + kapp->exit_loop(); + } + + if (m_loadingXMLDoc) + m_loadingXMLDoc->deref(this); + m_loadingXMLDoc = 0; +} + +void DocumentImpl::load(const DOMString &uri) +{ + if (m_inSyncLoad) { + m_inSyncLoad = false; + kapp->exit_loop(); + } + + m_hadLoadError = false; + if (m_loadingXMLDoc) + m_loadingXMLDoc->deref(this); + + // Use the document loader to retrieve the XML file. We use CachedCSSStyleSheet because + // this is an easy way to retrieve an arbitrary text file... it is not specific to + // stylesheets. + + // ### Note: By loading the XML document this way we do not get the proper decoding + // of the data retrieved from the server based on the character set, as happens with + // HTML files. Need to look into a way of using the decoder in CachedCSSStyleSheet. + m_docLoading = true; + m_loadingXMLDoc = m_docLoader->requestStyleSheet(uri.string(),QString(),"text/xml"); + + if (!m_loadingXMLDoc) { + m_docLoading = false; + return; + } + + m_loadingXMLDoc->ref(this); + + if (!m_async && m_docLoading) { + m_inSyncLoad = true; + kapp->enter_loop(); + } +} + +void DocumentImpl::loadXML(const DOMString &source) +{ + open(false); + write(source); + finishParsing(); + close(); + dispatchHTMLEvent(EventImpl::LOAD_EVENT,false,false); +} + +void DocumentImpl::setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheet, const DOM::DOMString &charset) +{ + if (!m_hadLoadError) { + m_url = url.string(); + loadXML(sheet); + } + + m_docLoading = false; + if (m_inSyncLoad) { + m_inSyncLoad = false; + kapp->exit_loop(); + } + + assert(m_loadingXMLDoc != 0); + m_loadingXMLDoc->deref(this); + m_loadingXMLDoc = 0; +} + +void DocumentImpl::error(int err, const QString &text) +{ + m_docLoading = false; + if (m_inSyncLoad) { + m_inSyncLoad = false; + kapp->exit_loop(); + } + + m_hadLoadError = true; + + int exceptioncode = 0; + EventImpl *evt = new EventImpl(EventImpl::ERROR_EVENT,false,false); + if (err != 0) + evt->setMessage(KIO::buildErrorString(err,text)); + else + evt->setMessage(text); + evt->ref(); + dispatchEvent(evt,exceptioncode,true); + evt->deref(); + + assert(m_loadingXMLDoc != 0); + m_loadingXMLDoc->deref(this); + m_loadingXMLDoc = 0; +} + +void DocumentImpl::defaultEventHandler(EventImpl *evt) +{ + // if any html event listeners are registered on the window, then dispatch them here + if (!m_windowEventListeners.listeners || evt->propagationStopped()) + return; + + QValueList<RegisteredEventListener>::iterator it; + + //Grab a copy in case of clear + QValueList<RegisteredEventListener> listeners = *m_windowEventListeners.listeners; + Event ev(evt); + for (it = listeners.begin(); it != listeners.end(); ++it) { + //Check to make sure it didn't get removed. KDE4: use Java-style iterators + if (!m_windowEventListeners.stillContainsListener(*it)) + continue; + + if ((*it).id == evt->id()) { + // currentTarget must be 0 in khtml for kjs_events to set "this" correctly. + // (this is how we identify events dispatched to the window, like window.onmousedown) + // ## currentTarget is unimplemented in IE, and is "window" in Mozilla (how? not a DOM node) + evt->setCurrentTarget(0); + (*it).listener->handleEvent(ev); + } + } +} + +void DocumentImpl::setHTMLWindowEventListener(int id, EventListener *listener) +{ + m_windowEventListeners.setHTMLEventListener(id, listener); +} + +EventListener *DocumentImpl::getHTMLWindowEventListener(int id) +{ + return m_windowEventListeners.getHTMLEventListener(id); +} + +void DocumentImpl::addWindowEventListener(int id, EventListener *listener, const bool useCapture) +{ + m_windowEventListeners.addEventListener(id, listener, useCapture); +} + +void DocumentImpl::removeWindowEventListener(int id, EventListener *listener, bool useCapture) +{ + m_windowEventListeners.removeEventListener(id, listener, useCapture); +} + +bool DocumentImpl::hasWindowEventListener(int id) +{ + return m_windowEventListeners.hasEventListener(id); +} + +EventListener *DocumentImpl::createHTMLEventListener(const QString& code, const QString& name, NodeImpl* node) +{ + return part() ? part()->createHTMLEventListener(code, name, node) : 0; +} + +void DocumentImpl::dispatchImageLoadEventSoon(HTMLImageElementImpl *image) +{ + m_imageLoadEventDispatchSoonList.append(image); + if (!m_imageLoadEventTimer) { + m_imageLoadEventTimer = startTimer(0); + } +} + +void DocumentImpl::removeImage(HTMLImageElementImpl *image) +{ + // Remove instances of this image from both lists. + // Use loops because we allow multiple instances to get into the lists. + while (m_imageLoadEventDispatchSoonList.removeRef(image)) { } + while (m_imageLoadEventDispatchingList.removeRef(image)) { } + if (m_imageLoadEventDispatchSoonList.isEmpty() && m_imageLoadEventTimer) { + killTimer(m_imageLoadEventTimer); + m_imageLoadEventTimer = 0; + } +} + +void DocumentImpl::dispatchImageLoadEventsNow() +{ + // need to avoid re-entering this function; if new dispatches are + // scheduled before the parent finishes processing the list, they + // will set a timer and eventually be processed + if (!m_imageLoadEventDispatchingList.isEmpty()) { + return; + } + + if (m_imageLoadEventTimer) { + killTimer(m_imageLoadEventTimer); + m_imageLoadEventTimer = 0; + } + + m_imageLoadEventDispatchingList = m_imageLoadEventDispatchSoonList; + m_imageLoadEventDispatchSoonList.clear(); + for (QPtrListIterator<HTMLImageElementImpl> it(m_imageLoadEventDispatchingList); it.current(); ) { + HTMLImageElementImpl* image = it.current(); + // Must advance iterator *before* dispatching call. + // Otherwise, it might be advanced automatically if dispatching the call had a side effect + // of destroying the current HTMLImageElementImpl, and then we would advance past the *next* + // item, missing one altogether. + ++it; + image->dispatchLoadEvent(); + } + m_imageLoadEventDispatchingList.clear(); +} + +void DocumentImpl::timerEvent(QTimerEvent *) +{ + dispatchImageLoadEventsNow(); +} + +void DocumentImpl::setDecoderCodec(const QTextCodec *codec) +{ + m_decoderMibEnum = codec->mibEnum(); +} + +ElementImpl *DocumentImpl::ownerElement() const +{ + KHTMLPart *childPart = part(); + if (!childPart) + return 0; + ChildFrame *childFrame = childPart->d->m_frame; + if (!childFrame) + return 0; + RenderPart *renderPart = childFrame->m_frame; + if (!renderPart) + return 0; + return static_cast<ElementImpl *>(renderPart->element()); +} + +DOMString DocumentImpl::domain() const +{ + if ( m_domain.isEmpty() ) // not set yet (we set it on demand to save time and space) + m_domain = URL().host(); // Initially set to the host + return m_domain; +} + +void DocumentImpl::setDomain(const DOMString &newDomain) +{ + if ( m_domain.isEmpty() ) // not set yet (we set it on demand to save time and space) + m_domain = URL().host().lower(); // Initially set to the host + + if ( m_domain.isEmpty() /*&& part() && part()->openedByJS()*/ ) + m_domain = newDomain.lower(); + + // Both NS and IE specify that changing the domain is only allowed when + // the new domain is a suffix of the old domain. + int oldLength = m_domain.length(); + int newLength = newDomain.length(); + if ( newLength < oldLength ) // e.g. newDomain=kde.org (7) and m_domain=www.kde.org (11) + { + DOMString test = m_domain.copy(); + DOMString reference = newDomain.lower(); + if ( test[oldLength - newLength - 1] == '.' ) // Check that it's a subdomain, not e.g. "de.org" + { + test.remove( 0, oldLength - newLength ); // now test is "kde.org" from m_domain + if ( test == reference ) // and we check that it's the same thing as newDomain + m_domain = reference; + } + } +} + +DOMString DocumentImpl::toString() const +{ + DOMString result; + + for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { + result += child->toString(); + } + + return result; +} + + +KHTMLPart* DOM::DocumentImpl::part() const +{ + // ### TODO: make this independent from a KHTMLView one day. + return view() ? view()->part() : 0; +} + +NodeListImpl::Cache* DOM::DocumentImpl::acquireCachedNodeListInfo( + NodeListImpl::CacheFactory* factory, NodeImpl* base, int type) +{ + //### might want to flush the dict when the version number + //changes + NodeListImpl::CacheKey key(base, type); + + //Check to see if we have this sort of item cached. + NodeListImpl::Cache* cached = + (type == NodeListImpl::UNCACHEABLE) ? 0 : m_nodeListCache.find(key.hash()); + + if (cached) { + if (cached->key == key) { + cached->ref(); //Add the nodelist's reference + return cached; + } else { + //Conflict. Drop our reference to the old item. + cached->deref(); + } + } + + //Nothing to reuse, make a new item. + NodeListImpl::Cache* newInfo = factory(); + newInfo->key = key; + newInfo->clear(this); + newInfo->ref(); //Add the nodelist's reference + + if (type != NodeListImpl::UNCACHEABLE) { + newInfo->ref(); //Add the cache's reference + m_nodeListCache.replace(key.hash(), newInfo); + } + + return newInfo; +} + +void DOM::DocumentImpl::releaseCachedNodeListInfo(NodeListImpl::Cache* entry) +{ + entry->deref(); +} + +// ---------------------------------------------------------------------------- + +DocumentFragmentImpl::DocumentFragmentImpl(DocumentImpl *doc) : NodeBaseImpl(doc) +{ +} + +DocumentFragmentImpl::DocumentFragmentImpl(const DocumentFragmentImpl &other) + : NodeBaseImpl(other) +{ +} + +DOMString DocumentFragmentImpl::nodeName() const +{ + return "#document-fragment"; +} + +unsigned short DocumentFragmentImpl::nodeType() const +{ + return Node::DOCUMENT_FRAGMENT_NODE; +} + +// DOM Section 1.1.1 +bool DocumentFragmentImpl::childTypeAllowed( unsigned short type ) +{ + switch (type) { + case Node::ELEMENT_NODE: + case Node::PROCESSING_INSTRUCTION_NODE: + case Node::COMMENT_NODE: + case Node::TEXT_NODE: + case Node::CDATA_SECTION_NODE: + case Node::ENTITY_REFERENCE_NODE: + return true; + break; + default: + return false; + } +} + +DOMString DocumentFragmentImpl::toString() const +{ + DOMString result; + + for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { + result += child->toString(); + } + + return result; +} + +NodeImpl *DocumentFragmentImpl::cloneNode ( bool deep ) +{ + DocumentFragmentImpl *clone = new DocumentFragmentImpl( docPtr() ); + if (deep) + cloneChildNodes(clone); + return clone; +} + + +// ---------------------------------------------------------------------------- + +DocumentTypeImpl::DocumentTypeImpl(DOMImplementationImpl *implementation, DocumentImpl *doc, + const DOMString &qualifiedName, const DOMString &publicId, + const DOMString &systemId) + : NodeImpl(doc), m_implementation(implementation), + m_qualifiedName(qualifiedName), m_publicId(publicId), m_systemId(systemId) +{ + m_implementation->ref(); + + m_entities = 0; + m_notations = 0; + + // if doc is 0, it is not attached to a document and / or + // therefore does not provide entities or notations. (DOM Level 3) +} + +DocumentTypeImpl::~DocumentTypeImpl() +{ + m_implementation->deref(); + if (m_entities) + m_entities->deref(); + if (m_notations) + m_notations->deref(); +} + +void DocumentTypeImpl::copyFrom(const DocumentTypeImpl& other) +{ + m_qualifiedName = other.m_qualifiedName; + m_publicId = other.m_publicId; + m_systemId = other.m_systemId; + m_subset = other.m_subset; +} + +DOMString DocumentTypeImpl::toString() const +{ + DOMString result = "<!DOCTYPE"; + result += m_qualifiedName; + if (!m_publicId.isEmpty()) { + result += " PUBLIC \""; + result += m_publicId; + result += "\" \""; + result += m_systemId; + result += "\""; + } else if (!m_systemId.isEmpty()) { + result += " SYSTEM \""; + result += m_systemId; + result += "\""; + } + + if (!m_subset.isEmpty()) { + result += " ["; + result += m_subset; + result += "]"; + } + + result += ">"; + + return result; +} + +DOMString DocumentTypeImpl::nodeName() const +{ + return name(); +} + +unsigned short DocumentTypeImpl::nodeType() const +{ + return Node::DOCUMENT_TYPE_NODE; +} + +// DOM Section 1.1.1 +bool DocumentTypeImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +NodeImpl *DocumentTypeImpl::cloneNode ( bool /*deep*/ ) +{ + // Spec says cloning Document nodes is "implementation dependent" + // so we do not support it... + return 0; +} + +DOMStringImpl* DocumentTypeImpl::textContent() const +{ + return 0; +} + +void DocumentTypeImpl::setTextContent( const DOMString&, int& ) +{} + +NamedNodeMapImpl * DocumentTypeImpl::entities() const +{ + if ( !m_entities ) { + m_entities = new GenericRONamedNodeMapImpl( docPtr() ); + m_entities->ref(); + } + return m_entities; +} + +NamedNodeMapImpl * DocumentTypeImpl::notations() const +{ + if ( !m_notations ) { + m_notations = new GenericRONamedNodeMapImpl( docPtr() ); + m_notations->ref(); + } + return m_notations; +} + +#include "dom_docimpl.moc" diff --git a/khtml/xml/dom_docimpl.h b/khtml/xml/dom_docimpl.h new file mode 100644 index 000000000..51d762325 --- /dev/null +++ b/khtml/xml/dom_docimpl.h @@ -0,0 +1,763 @@ +/* + * 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 Dirk Mueller (mueller@kde.org) + * (C) 2002-2003 Apple Computer, Inc. + * (C) 2006 Allan Sandfeld Jensen(kde@carewolf.com) + * + * 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. + * + */ + +#ifndef _DOM_DocumentImpl_h_ +#define _DOM_DocumentImpl_h_ + +#include "xml/dom_elementimpl.h" +#include "xml/dom_textimpl.h" +#include "xml/dom2_traversalimpl.h" +#include "misc/shared.h" +#include "misc/loader.h" +#include "misc/seed.h" + +#include <qstringlist.h> +#include <qptrlist.h> +#include <qobject.h> +#include <qintcache.h> +#include <qintdict.h> +#include <qdict.h> +#include <qmap.h> + +#include <kurl.h> + +class QPaintDevice; +class QTextCodec; +class QPaintDeviceMetrics; +class KHTMLView; + +namespace khtml { + class Tokenizer; + class CSSStyleSelector; + class DocLoader; + class CSSStyleSelectorList; + class RenderArena; + class RenderObject; + class CounterNode; + class CachedObject; + class CachedCSSStyleSheet; + class DynamicDomRestyler; +} + +namespace DOM { + + class AbstractViewImpl; + class AttrImpl; + class CDATASectionImpl; + class CSSStyleSheetImpl; + class CommentImpl; + class DocumentFragmentImpl; + class DocumentImpl; + class DocumentType; + class DocumentTypeImpl; + class ElementImpl; + class EntityReferenceImpl; + class EventImpl; + class EventListener; + class GenericRONamedNodeMapImpl; + class HTMLDocumentImpl; + class HTMLElementImpl; + class HTMLImageElementImpl; + class NodeFilter; + class NodeFilterImpl; + class NodeIteratorImpl; + class NodeListImpl; + class ProcessingInstructionImpl; + class RangeImpl; + class RegisteredEventListener; + class StyleSheetImpl; + class StyleSheetListImpl; + class TextImpl; + class TreeWalkerImpl; + +class DOMImplementationImpl : public khtml::Shared<DOMImplementationImpl> +{ +public: + DOMImplementationImpl(); + ~DOMImplementationImpl(); + + // DOM methods & attributes for DOMImplementation + bool hasFeature ( const DOMString &feature, const DOMString &version ); + DocumentTypeImpl *createDocumentType( const DOMString &qualifiedName, const DOMString &publicId, + const DOMString &systemId, int &exceptioncode ); + DocumentImpl *createDocument( const DOMString &namespaceURI, const DOMString &qualifiedName, + const DocumentType &doctype, int &exceptioncode ); + + DOMImplementationImpl* getInterface(const DOMString& feature) const; + + // From the DOMImplementationCSS interface + CSSStyleSheetImpl *createCSSStyleSheet(DOMStringImpl *title, DOMStringImpl *media, int &exceptioncode); + + // From the HTMLDOMImplementation interface + HTMLDocumentImpl* createHTMLDocument( const DOMString& title); + + // Other methods (not part of DOM) + DocumentImpl *createDocument( KHTMLView *v = 0 ); + HTMLDocumentImpl *createHTMLDocument( KHTMLView *v = 0 ); + + // Returns the static instance of this class - only one instance of this class should + // ever be present, and is used as a factory method for creating DocumentImpl objects + static DOMImplementationImpl *instance(); + +protected: + static DOMImplementationImpl *m_instance; +}; + +/** + * @internal A cache of element name (or id) to pointer + * ### KDE4, QHash: better to store values here + */ +class ElementMappingCache +{ +public: + /** + For each name, we hold a reference count, and a + pointer. If the item is in the table, which implies + reference count is > 1, the name is a valid key. + If the pointer is non-null, it points to the appropriate + mapping + */ + struct ItemInfo + { + int ref; + ElementImpl* nd; + }; + + ElementMappingCache(); + + /** + Add a pointer as just one of candidates, not neccesserily the proper one + */ + void add(const QString& id, ElementImpl* nd); + + /** + Set the pointer as the definite mapping; it must have already been added + */ + void set(const QString& id, ElementImpl* nd); + + /** + Remove the item; it must have already been added. + */ + void remove(const QString& id, ElementImpl* nd); + + /** + Returns true if the item exists + */ + bool contains(const QString& id); + + /** + Returns the information for the given ID + */ + ItemInfo* get(const QString& id); +private: + QDict<ItemInfo> m_dict; +}; + + +/** + * @internal + */ +class DocumentImpl : public QObject, private khtml::CachedObjectClient, public NodeBaseImpl +{ + Q_OBJECT +public: + DocumentImpl(DOMImplementationImpl *_implementation, KHTMLView *v); + ~DocumentImpl(); + + // DOM methods & attributes for Document + + DocumentTypeImpl *doctype() const; + + DOMImplementationImpl *implementation() const; + ElementImpl *documentElement() const; + virtual ElementImpl *createElement ( const DOMString &tagName, int* pExceptioncode = 0 ); + virtual AttrImpl *createAttribute( const DOMString &tagName, int* pExceptioncode = 0 ); + DocumentFragmentImpl *createDocumentFragment (); + TextImpl *createTextNode ( DOMStringImpl* data ) { return new TextImpl( docPtr(), data); } + TextImpl *createTextNode ( const QString& data ) + { return createTextNode(new DOMStringImpl(data.unicode(), data.length())); } + CommentImpl *createComment ( DOMStringImpl* data ); + CDATASectionImpl *createCDATASection ( DOMStringImpl* data ); + ProcessingInstructionImpl *createProcessingInstruction ( const DOMString &target, DOMStringImpl* data ); + EntityReferenceImpl *createEntityReference ( const DOMString &name ); + NodeImpl *importNode( NodeImpl *importedNode, bool deep, int &exceptioncode ); + virtual ElementImpl *createElementNS ( const DOMString &_namespaceURI, const DOMString &_qualifiedName, + int* pExceptioncode = 0 ); + virtual AttrImpl *createAttributeNS( const DOMString &_namespaceURI, const DOMString &_qualifiedName, + int* pExceptioncode = 0 ); + ElementImpl *getElementById ( const DOMString &elementId ) const; + + // Actually part of HTMLDocument, but used for giving XML documents a window title as well + DOMString title() const { return m_title; } + void setTitle(const DOMString& _title); + + // DOM methods overridden from parent classes + + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + + virtual DOMStringImpl* textContent() const; + virtual void setTextContent( const DOMString &text, int& exceptioncode ); + + // Other methods (not part of DOM) + virtual bool isDocumentNode() const { return true; } + virtual bool isHTMLDocument() const { return false; } + + virtual ElementImpl *createHTMLElement ( const DOMString &tagName ); + + khtml::CSSStyleSelector *styleSelector() { return m_styleSelector; } + + /** + * Updates the pending sheet count and then calls updateStyleSelector. + */ + void styleSheetLoaded(); + + /** + * This method returns true if all top-level stylesheets have loaded (including + * any \@imports that they may be loading). + */ + bool haveStylesheetsLoaded() { return m_pendingStylesheets <= 0 || m_ignorePendingStylesheets; } + + /** + * Increments the number of pending sheets. The \<link\> elements + * invoke this to add themselves to the loading list. + */ + void addPendingSheet() { m_pendingStylesheets++; } + + /** + * Returns true if the document has pending stylesheets + * loading. + */ + bool hasPendingSheets() const { return m_pendingStylesheets; } + + /** + * Called when one or more stylesheets in the document may have been added, removed or changed. + * + * Creates a new style selector and assign it to this document. This is done by iterating through all nodes in + * document (or those before \<BODY\> in a HTML document), searching for stylesheets. Stylesheets can be contained in + * \<LINK\>, \<STYLE\> or \<BODY\> elements, as well as processing instructions (XML documents only). A list is + * constructed from these which is used to create the a new style selector which collates all of the stylesheets + * found and is used to calculate the derived styles for all rendering objects. + * + * @param shallow If the stylesheet list for the document is unchanged, with only added or removed rules + * in existing sheets, then set this argument to true for efficiency. + */ + void updateStyleSelector(bool shallow=false); + + void recalcStyleSelector(); + void rebuildStyleSelector(); + + QString nextState(); + + // Query all registered elements for their state + QStringList docState(); + bool unsubmittedFormChanges(); + void registerMaintainsState(NodeImpl* e) { m_maintainsState.append(e); } + void deregisterMaintainsState(NodeImpl* e) { m_maintainsState.removeRef(e); } + + // Set the state the document should restore to + void setRestoreState( const QStringList &s) { m_state = s; } + + KHTMLView *view() const { return m_view; } + KHTMLPart* part() const; + + RangeImpl *createRange(); + + NodeIteratorImpl *createNodeIterator(NodeImpl *root, unsigned long whatToShow, + NodeFilter &filter, bool entityReferenceExpansion, int &exceptioncode); + + TreeWalkerImpl *createTreeWalker(NodeImpl *root, unsigned long whatToShow, NodeFilterImpl *filter, + bool entityReferenceExpansion, int &exceptioncode); + + virtual void recalcStyle( StyleChange = NoChange ); + static QPtrList<DocumentImpl> * changedDocuments; + virtual void updateRendering(); + void updateLayout(); + static void updateDocumentsRendering(); + khtml::DocLoader *docLoader() { return m_docLoader; } + + virtual void attach(); + virtual void detach(); + + khtml::RenderArena* renderArena() { return m_renderArena.get(); } + + // to get visually ordered hebrew and arabic pages right + void setVisuallyOrdered(); + // to get URL decoding right + void setDecoderCodec(const QTextCodec *codec); + + void setSelection(NodeImpl* s, int sp, NodeImpl* e, int ep); + void clearSelection(); + + void open ( bool clearEventListeners = true ); + virtual void close ( ); + void write ( const DOMString &text ); + void write ( const QString &text ); + void writeln ( const DOMString &text ); + void finishParsing ( ); + + KURL URL() const { return m_url; } + void setURL(const QString& url) { m_url = url; } + + KURL baseURL() const { return m_baseURL.isEmpty() ? m_url : m_baseURL; } + void setBaseURL(const KURL& baseURL) { m_baseURL = baseURL; } + + QString baseTarget() const { return m_baseTarget; } + void setBaseTarget(const QString& baseTarget) { m_baseTarget = baseTarget; } + + QString completeURL(const QString& url) const { return KURL(baseURL(),url,m_decoderMibEnum).url(); }; + DOMString canonURL(const DOMString& url) const { return url.isEmpty() ? url : completeURL(url.string()); } + + void setUserStyleSheet(const QString& sheet); + QString userStyleSheet() const { return m_usersheet; } + void setPrintStyleSheet(const QString& sheet) { m_printSheet = sheet; } + QString printStyleSheet() const { return m_printSheet; } + + CSSStyleSheetImpl* elementSheet(); + virtual khtml::Tokenizer *createTokenizer(); + khtml::Tokenizer *tokenizer() { return m_tokenizer; } + + QPaintDeviceMetrics *paintDeviceMetrics() { return m_paintDeviceMetrics; } + QPaintDevice *paintDevice() const { return m_paintDevice; } + void setPaintDevice( QPaintDevice *dev ); + + enum HTMLMode { + Html3 = 0, + Html4 = 1, + XHtml = 2 + }; + + enum ParseMode { + Unknown, + Compat, + Transitional, + Strict + }; + virtual void determineParseMode( const QString &str ); + void setParseMode( ParseMode m ) { pMode = m; } + ParseMode parseMode() const { return pMode; } + + bool inCompatMode() const { return pMode == Compat; } + bool inTransitionalMode() const { return pMode == Transitional; } + bool inStrictMode() const { return pMode == Strict; } + + //void setHTMLMode( HTMLMode m ) { hMode = m; } + HTMLMode htmlMode() const { return hMode; } + + void setParsing(bool b) { m_bParsing = b; } + bool parsing() const { return m_bParsing; } + + void setTextColor( QColor color ) { m_textColor = color; } + QColor textColor() const { return m_textColor; } + + void setDesignMode(bool b); + bool designMode() const; + + // internal + bool prepareMouseEvent( bool readonly, int x, int y, MouseEvent *ev ); + + virtual bool childTypeAllowed( unsigned short nodeType ); + virtual NodeImpl *cloneNode ( bool deep ); + + NodeImpl::Id getId( NodeImpl::IdType _type, DOMStringImpl* _nsURI, DOMStringImpl *_localName, + DOMStringImpl *_prefix, bool readonly, bool lookupHTML, int *pExceptioncode = 0); + NodeImpl::Id getId( NodeImpl::IdType _type, DOMStringImpl *_nodeName, bool readonly, bool lookupHTML, + int *pExceptioncode = 0); + DOMString getName( NodeImpl::IdType _type, NodeImpl::Id _id ) const; + + StyleSheetListImpl* styleSheets() { return m_styleSheets; }; + + DOMString preferredStylesheetSet() const { return m_preferredStylesheetSet; } + DOMString selectedStylesheetSet() const; + void setSelectedStylesheetSet(const DOMString&); + void setPreferredStylesheetSet(const DOMString& s) { m_preferredStylesheetSet = s; } + + void addStyleSheet(StyleSheetImpl *, int *exceptioncode = 0); + void removeStyleSheet(StyleSheetImpl *, int *exceptioncode = 0); + + QStringList availableStyleSheets() const { return m_availableSheets; } + + NodeImpl* hoverNode() const { return m_hoverNode; } + void setHoverNode(NodeImpl *newHoverNode); + NodeImpl *focusNode() const { return m_focusNode; } + void setFocusNode(NodeImpl *newFocusNode); + NodeImpl* activeNode() const { return m_activeNode; } + void setActiveNode(NodeImpl *newActiveNode); + + // Updates for :target (CSS3 selector). + void setCSSTarget(NodeImpl* n); + NodeImpl* getCSSTarget() { return m_cssTarget; } + + bool isDocumentChanged() { return m_docChanged; } + virtual void setDocumentChanged(bool = true); + void attachNodeIterator(NodeIteratorImpl *ni); + void detachNodeIterator(NodeIteratorImpl *ni); + void notifyBeforeNodeRemoval(NodeImpl *n); + AbstractViewImpl *defaultView() const { return m_defaultView; } + EventImpl *createEvent(const DOMString &eventType, int &exceptioncode); + + // keep track of what types of event listeners are registered, so we don't + // dispatch events unnecessarily + enum ListenerType { + DOMSUBTREEMODIFIED_LISTENER = 0x01, + DOMNODEINSERTED_LISTENER = 0x02, + DOMNODEREMOVED_LISTENER = 0x04, + DOMNODEREMOVEDFROMDOCUMENT_LISTENER = 0x08, + DOMNODEINSERTEDINTODOCUMENT_LISTENER = 0x10, + DOMATTRMODIFIED_LISTENER = 0x20, + DOMCHARACTERDATAMODIFIED_LISTENER = 0x40 + }; + + bool hasListenerType(ListenerType listenerType) const { return (m_listenerTypes & listenerType); } + void addListenerType(ListenerType listenerType) { m_listenerTypes = m_listenerTypes | listenerType; } + + CSSStyleDeclarationImpl *getOverrideStyle(ElementImpl *elt, DOMStringImpl *pseudoElt); + + bool async() const { return m_async; } + void setAsync(bool b) { m_async = b; } + void abort(); + void load(const DOMString &uri); + void loadXML(const DOMString &source); + // from cachedObjectClient + void setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheet, const DOM::DOMString &charset); + void error(int err, const QString &text); + + typedef QMap<QString, ProcessingInstructionImpl*> LocalStyleRefs; + LocalStyleRefs* localStyleRefs() { return &m_localStyleRefs; } + + virtual void defaultEventHandler(EventImpl *evt); + virtual void setHTMLWindowEventListener(int id, EventListener *listener); + EventListener *getHTMLWindowEventListener(int id); + EventListener *createHTMLEventListener(const QString& code, const QString& name, NodeImpl* node); + + void addWindowEventListener(int id, EventListener *listener, const bool useCapture); + void removeWindowEventListener(int id, EventListener *listener, bool useCapture); + bool hasWindowEventListener(int id); + + EventListener *createHTMLEventListener(QString code); + + /** + * Searches through the document, starting from fromNode, for the next selectable element that comes after fromNode. + * The order followed is as specified in section 17.11.1 of the HTML4 spec, which is elements with tab indexes + * first (from lowest to highest), and then elements without tab indexes (in document order). + * + * @param fromNode The node from which to start searching. The node after this will be focused. May be null. + * + * @return The focus node that comes after fromNode + * + * See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1 + */ + NodeImpl *nextFocusNode(NodeImpl *fromNode); + + /** + * Searches through the document, starting from fromNode, for the previous selectable element (that comes _before_) + * fromNode. The order followed is as specified in section 17.11.1 of the HTML4 spec, which is elements with tab + * indexes first (from lowest to highest), and then elements without tab indexes (in document order). + * + * @param fromNode The node from which to start searching. The node before this will be focused. May be null. + * + * @return The focus node that comes before fromNode + * + * See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1 + */ + NodeImpl *previousFocusNode(NodeImpl *fromNode); + + ElementImpl* findAccessKeyElement(QChar c); + + int nodeAbsIndex(NodeImpl *node); + NodeImpl *nodeWithAbsIndex(int absIndex); + + /** + * Handles a HTTP header equivalent set by a meta tag using <meta http-equiv="..." content="...">. This is called + * when a meta tag is encountered during document parsing, and also when a script dynamically changes or adds a meta + * tag. This enables scripts to use meta tags to perform refreshes and set expiry dates in addition to them being + * specified in a HTML file. + * + * @param equiv The http header name (value of the meta tag's "equiv" attribute) + * @param content The header value (value of the meta tag's "content" attribute) + */ + void processHttpEquiv(const DOMString &equiv, const DOMString &content); + + void dispatchImageLoadEventSoon(HTMLImageElementImpl *); + void dispatchImageLoadEventsNow(); + void removeImage(HTMLImageElementImpl *); + virtual void timerEvent(QTimerEvent *); + + // Returns the owning element in the parent document. + // Returns 0 if this is the top level document. + ElementImpl *ownerElement() const; + + DOMString domain() const; + void setDomain( const DOMString &newDomain ); // not part of the DOM + + bool isURLAllowed(const QString& url) const; + + HTMLElementImpl* body(); + + DOMString toString() const; + + void incDOMTreeVersion() { ++m_domtree_version; } + unsigned int domTreeVersion() const { return m_domtree_version; } + + QDict<khtml::CounterNode>* counters(const khtml::RenderObject* o) { return m_counterDict[(void*)o]; } + void setCounters(const khtml::RenderObject* o, QDict<khtml::CounterNode> *dict) { m_counterDict.insert((void*)o, dict);} + void removeCounters(const khtml::RenderObject* o) { m_counterDict.remove((void*)o); } + + + ElementMappingCache& underDocNamedCache() { + return m_underDocNamedCache; + } + + NodeListImpl::Cache* acquireCachedNodeListInfo(NodeListImpl::CacheFactory* fact, + NodeImpl* base, int type); + void releaseCachedNodeListInfo(NodeListImpl::Cache* cache); + + ElementMappingCache& getElementByIdCache() const { + return m_getElementByIdCache; + } + + QString contentLanguage() const { return m_contentLanguage; } + void setContentLanguage(const QString& cl) { m_contentLanguage = cl; } + + khtml::DynamicDomRestyler& dynamicDomRestyler() { return *m_dynamicDomRestyler; } + const khtml::DynamicDomRestyler& dynamicDomRestyler() const { return *m_dynamicDomRestyler; } + +signals: + void finishedParsing(); + +protected: + khtml::CSSStyleSelector *m_styleSelector; + KHTMLView *m_view; + QStringList m_state; + + khtml::DocLoader *m_docLoader; + khtml::Tokenizer *m_tokenizer; + KURL m_url; + KURL m_baseURL; + QString m_baseTarget; + + DocumentTypeImpl *m_doctype; + DOMImplementationImpl *m_implementation; + + QString m_usersheet; + QString m_printSheet; + QStringList m_availableSheets; + + QString m_contentLanguage; + + // Track the number of currently loading top-level stylesheets. Sheets + // loaded using the @import directive are not included in this count. + // We use this count of pending sheets to detect when we can begin attaching + // elements. + int m_pendingStylesheets; + bool m_ignorePendingStylesheets; + + CSSStyleSheetImpl *m_elemSheet; + + QPaintDevice *m_paintDevice; + QPaintDeviceMetrics *m_paintDeviceMetrics; + ParseMode pMode; + HTMLMode hMode; + + QColor m_textColor; + NodeImpl *m_hoverNode; + NodeImpl *m_focusNode; + NodeImpl *m_activeNode; + NodeImpl *m_cssTarget; + + unsigned int m_domtree_version; + + struct IdNameMapping { + IdNameMapping(unsigned short _start) + : idStart(_start), count(0) {} + ~IdNameMapping() { + QIntDictIterator<DOM::DOMStringImpl> it(names); + for (; it.current() ; ++it) + it.current()->deref(); + } + unsigned short idStart; + unsigned short count; + QIntDict<DOM::DOMStringImpl> names; + QDict<void> ids; + + void expandIfNeeded() { + if (ids.size() <= ids.count() && ids.size() != khtml_MaxSeed) + ids.resize( khtml::nextSeed(ids.count()) ); + if (names.size() <= names.count() && names.size() != khtml_MaxSeed) + names.resize( khtml::nextSeed(names.count()) ); + } + + void addAlias(DOMStringImpl* _prefix, DOMStringImpl* _name, bool cs, NodeImpl::Id id) { + if(_prefix && _prefix->l) { + QConstString n(_name->s, _name->l); + QConstString px( _prefix->s, _prefix->l ); + QString name = cs ? n.string() : n.string().upper(); + QString qn("aliases: " + (cs ? px.string() : px.string().upper()) + ":" + name); + if (!ids.find( qn )) { + ids.insert( qn, (void*)id ); + } + } + expandIfNeeded(); + } + + }; + + IdNameMapping *m_attrMap; + IdNameMapping *m_elementMap; + IdNameMapping *m_namespaceMap; + + QPtrList<NodeIteratorImpl> m_nodeIterators; + AbstractViewImpl *m_defaultView; + + unsigned short m_listenerTypes; + StyleSheetListImpl* m_styleSheets; + StyleSheetListImpl *m_addedStyleSheets; // programmatically added style sheets + LocalStyleRefs m_localStyleRefs; // references to inlined style elements + RegisteredListenerList m_windowEventListeners; + QPtrList<NodeImpl> m_maintainsState; + + // ### evaluate for placement in RenderStyle + QPtrDict<QDict<khtml::CounterNode> > m_counterDict; + + khtml::DynamicDomRestyler *m_dynamicDomRestyler; + + bool visuallyOrdered; + bool m_bParsing; + bool m_docChanged; + bool m_styleSelectorDirty; + bool m_inStyleRecalc; + bool m_async; + bool m_hadLoadError; + bool m_docLoading; + bool m_inSyncLoad; + + DOMString m_title; + DOMString m_preferredStylesheetSet; + khtml::CachedCSSStyleSheet *m_loadingXMLDoc; + + int m_decoderMibEnum; + + //Forms, images, etc., must be quickly accessible via document.name. + ElementMappingCache m_underDocNamedCache; + + //Cache for nodelists and collections. + QIntDict<NodeListImpl::Cache> m_nodeListCache; + + QPtrList<HTMLImageElementImpl> m_imageLoadEventDispatchSoonList; + QPtrList<HTMLImageElementImpl> m_imageLoadEventDispatchingList; + int m_imageLoadEventTimer; + + //Cache for getElementById + mutable ElementMappingCache m_getElementByIdCache; + + khtml::SharedPtr<khtml::RenderArena> m_renderArena; +private: + mutable DOMString m_domain; + int m_selfOnlyRefCount; +public: + // Nodes belonging to this document hold "self-only" references - + // these are enough to keep the document from being destroyed, but + // not enough to keep it from removing its children. This allows a + // node that outlives its document to still have a valid document + // pointer without introducing reference cycles + + void selfOnlyRef() { ++m_selfOnlyRefCount; } + void selfOnlyDeref() { + --m_selfOnlyRefCount; + if (!m_selfOnlyRefCount && !refCount()) + delete this; + } + + // This is called when our last outside reference dies + virtual void removedLastRef(); +}; + +class DocumentFragmentImpl : public NodeBaseImpl +{ +public: + DocumentFragmentImpl(DocumentImpl *doc); + DocumentFragmentImpl(const DocumentFragmentImpl &other); + + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + virtual bool childTypeAllowed( unsigned short type ); + + virtual DOMString toString() const; +}; + + +class DocumentTypeImpl : public NodeImpl +{ +public: + DocumentTypeImpl(DOMImplementationImpl *_implementation, DocumentImpl *doc, + const DOMString &qualifiedName, const DOMString &publicId, + const DOMString &systemId); + ~DocumentTypeImpl(); + + // DOM methods & attributes for DocumentType + NamedNodeMapImpl *entities() const; + NamedNodeMapImpl *notations() const; + + DOMString name() const { return m_qualifiedName; } + DOMString publicId() const { return m_publicId; } + DOMString systemId() const { return m_systemId; } + DOMString internalSubset() const { return m_subset; } + + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual bool childTypeAllowed( unsigned short type ); + virtual NodeImpl *cloneNode ( bool deep ); + + virtual DOMStringImpl* textContent() const; + virtual void setTextContent( const DOMString &text, int& exceptioncode ); + + // Other methods (not part of DOM) + void setName(const DOMString& n) { m_qualifiedName = n; } + void setPublicId(const DOMString& publicId) { m_publicId = publicId; } + void setSystemId(const DOMString& systemId) { m_systemId = systemId; } + DOMImplementationImpl *implementation() const { return m_implementation; } + void copyFrom(const DocumentTypeImpl&); + + virtual DOMString toString() const; + +protected: + DOMImplementationImpl *m_implementation; + mutable NamedNodeMapImpl* m_entities; + mutable NamedNodeMapImpl* m_notations; + + DOMString m_qualifiedName; + DOMString m_publicId; + DOMString m_systemId; + DOMString m_subset; +}; + +} //namespace +#endif diff --git a/khtml/xml/dom_elementimpl.cpp b/khtml/xml/dom_elementimpl.cpp new file mode 100644 index 000000000..5db497e7d --- /dev/null +++ b/khtml/xml/dom_elementimpl.cpp @@ -0,0 +1,1301 @@ +/** + * 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) + * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 EVENT_DEBUG +#include "dom/dom_exception.h" +#include "dom/dom_node.h" +#include "dom/html_image.h" +#include "xml/dom_textimpl.h" +#include "xml/dom_docimpl.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_elementimpl.h" +#include "xml/dom_restyler.h" + +#include "html/dtd.h" +#include "html/htmlparser.h" +#include "html/html_imageimpl.h" + +#include "rendering/render_canvas.h" +#include "misc/htmlhashes.h" +#include "css/css_valueimpl.h" +#include "css/css_stylesheetimpl.h" +#include "css/cssstyleselector.h" +#include "css/cssvalues.h" +#include "css/cssproperties.h" +#include "xml/dom_xmlimpl.h" + +#include <qtextstream.h> +#include <kdebug.h> +#include <stdlib.h> + +// ### support default attributes +// ### dispatch mutation events +// ### check for INVALID_CHARACTER_ERR where appropriate + +using namespace DOM; +using namespace khtml; + +AttrImpl::AttrImpl(ElementImpl* element, DocumentImpl* docPtr, NodeImpl::Id attrId, + DOMStringImpl *value, DOMStringImpl *prefix) + : NodeBaseImpl(docPtr), + m_element(element), + m_attrId(attrId) +{ + m_value = value; + m_value->ref(); + + m_prefix = prefix; + if (m_prefix) + m_prefix->ref(); + m_specified = true; // we don't yet support default attributes +} + +AttrImpl::~AttrImpl() +{ + m_value->deref(); + if (m_prefix) + m_prefix->deref(); +} + +DOMString AttrImpl::nodeName() const +{ + return name(); +} + +unsigned short AttrImpl::nodeType() const +{ + return Node::ATTRIBUTE_NODE; +} + +DOMString AttrImpl::prefix() const +{ + return m_prefix; +} + +void AttrImpl::setPrefix(const DOMString &_prefix, int &exceptioncode ) +{ + checkSetPrefix(_prefix, exceptioncode); + if (exceptioncode) + return; + + if (m_prefix == _prefix.implementation()) + return; + + if (m_prefix) + m_prefix->deref(); + m_prefix = _prefix.implementation(); + if (m_prefix) + m_prefix->ref(); +} + +DOMString AttrImpl::namespaceURI() const +{ + if (m_htmlCompat) + return DOMString(); + return getDocument()->getName(NamespaceId, m_attrId >> 16); +} + +DOMString AttrImpl::localName() const +{ + if (m_htmlCompat) + return DOMString(); + return getDocument()->getName(AttributeId, m_attrId); +} + +DOMString AttrImpl::nodeValue() const +{ + return m_value; +} + +DOMString AttrImpl::name() const +{ + DOMString n = getDocument()->getName(AttributeId, m_attrId); + + // compat mode always return attribute names in lowercase. + // that's not formally in the specification, but common + // practice - a w3c erratum to DOM L2 is pending. + if (m_htmlCompat) + n = n.lower(); + + if (m_prefix && m_prefix->l) + return DOMString(m_prefix) + ":" + n; + + return n; +} + +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; + } + + if (m_value == v.implementation()) + return; + + if (m_element && m_attrId == ATTR_ID) + m_element->updateId(m_value, v.implementation()); + + m_value->deref(); + m_value = v.implementation(); + m_value->ref(); + + if (m_element) { + m_element->parseAttribute(m_attrId,m_value); + m_element->attributeChanged(m_attrId); + } +} + +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*/) +{ + AttrImpl* attr = new AttrImpl(0, docPtr(), m_attrId, m_value, m_prefix); + attr->setHTMLCompat(m_htmlCompat); + return attr; +} + +// 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() ) { + result += "=\""; + + for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { + result += child->toString(); + } + + result += "\""; + } else if ( !nodeValue().isEmpty() ){ + //remove the else once the AttributeImpl changes are merged + result += "=\""; + result += nodeValue(); + result += "\""; + } + + return result; +} + +void AttrImpl::setElement(ElementImpl *element) +{ + m_element = element; +} + +// Strictly speaking, these two methods should not be needed, but +// we can't fully deal with the mess that are DOM attributes right.. +DOMStringImpl* AttrImpl::textContent() const +{ + if (m_value) + return new DOMStringImpl(m_value->s, m_value->l); + else + return 0; +} + +void AttrImpl::setTextContent( const DOMString &text, int& exceptioncode ) +{ + setValue(text, exceptioncode); +} + +// ------------------------------------------------------------------------- + +void AttributeImpl::setValue(DOMStringImpl *value, ElementImpl *element) +{ + assert(value); + if (m_attrId) { + if (m_data.value == value) + return; + + if (element && m_attrId == ATTR_ID) + element->updateId(m_data.value, value); + + m_data.value->deref(); + m_data.value = value; + m_data.value->ref(); + + if (element) { + element->parseAttribute(this); + element->attributeChanged(m_attrId); + } + } + else { + int exceptioncode = 0; + m_data.attr->setValue(value,exceptioncode); + // AttrImpl::setValue() calls parseAttribute() + } +} + +AttrImpl *AttributeImpl::createAttr(ElementImpl *element, DocumentImpl *docPtr) +{ + if (m_attrId) { + AttrImpl *attr = new AttrImpl(element,docPtr,m_attrId,m_data.value); + if (!attr) return 0; + attr->setHTMLCompat( docPtr->htmlMode() != DocumentImpl::XHtml ); + m_data.value->deref(); + m_data.attr = attr; + m_data.attr->ref(); + m_attrId = 0; /* "has implementation" flag */ + } + + return m_data.attr; +} + +void AttributeImpl::free() +{ + if (m_attrId) { + m_data.value->deref(); + } + else { + m_data.attr->setElement(0); + m_data.attr->deref(); + } +} + +// ------------------------------------------------------------------------- + +ElementImpl::ElementImpl(DocumentImpl *doc) + : NodeBaseImpl(doc) +{ + namedAttrMap = 0; + m_styleDecls = 0; + m_prefix = 0; +} + +ElementImpl::~ElementImpl() +{ + if(namedAttrMap) { + namedAttrMap->detachFromElement(); + namedAttrMap->deref(); + } + + if (m_styleDecls) { + m_styleDecls->setNode(0); + m_styleDecls->setParent(0); + m_styleDecls->deref(); + } + + if (m_prefix) + m_prefix->deref(); +} + +unsigned short ElementImpl::nodeType() const +{ + return Node::ELEMENT_NODE; +} + +DOMStringImpl* ElementImpl::getAttributeImpl( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName) const +{ + if (!namedAttrMap) + return 0; + + DOMStringImpl *value = namedAttrMap->getValue(id, nsAware, qName); + if (value) + return value; + + // then search in default attr in case it is not yet set + NamedAttrMapImpl* dm = defaultMap(); + value = dm ? dm->getValue(id, nsAware, qName) : 0; + if (value) + return value; + + return 0; +} + +DOMString ElementImpl::getAttribute( NodeImpl::Id id, bool nsAware, const DOMString& qName) const +{ + return DOMString(getAttributeImpl(id, nsAware, qName.implementation())); +} + +void ElementImpl::setAttribute(NodeImpl::Id id, const DOMString &value, const DOMString& qName, int &exceptioncode) +{ + // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + attributes()->setValue(id, value.implementation(), (qName.isEmpty() ? 0: qName.implementation())); +} + +void ElementImpl::setAttributeNS( const DOMString &namespaceURI, const DOMString &qualifiedName, + const DOMString &value, int &exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + int colonPos; + if (!DOM::checkQualifiedName(qualifiedName, namespaceURI, &colonPos, + false/*nameCanBeNull*/, false/*nameCanBeEmpty*/, + &exceptioncode)) + return; + DOMString prefix, localName; + splitPrefixLocalName(qualifiedName.implementation(), prefix, localName, colonPos); + NodeImpl::Id id = getDocument()->getId(AttributeId, namespaceURI.implementation(), + prefix.implementation(), localName.implementation(), false, true /*lookupHTML*/); + attributes()->setValue(id, value.implementation(), 0, prefix.implementation(), + true /*nsAware*/, !namespaceURI.isNull() /*hasNS*/); +} + +void ElementImpl::setAttribute(NodeImpl::Id id, const DOMString &value) +{ + int exceptioncode = 0; + setAttribute(id,value,DOMString(),exceptioncode); +} + +void ElementImpl::setAttributeMap( NamedAttrMapImpl* list ) +{ + // If setting the whole map changes the id attribute, we need to + // call updateId. + DOMStringImpl *oldId = namedAttrMap ? namedAttrMap->getValue(ATTR_ID) : 0; + DOMStringImpl *newId = list ? list->getValue(ATTR_ID) : 0; + + if (oldId || newId) { + updateId(oldId, newId); + } + + if (namedAttrMap) { + namedAttrMap->detachFromElement(); + namedAttrMap->deref(); + } + + namedAttrMap = list; + + if (namedAttrMap) { + namedAttrMap->ref(); + assert(namedAttrMap->m_element == 0); + namedAttrMap->setElement(this); + unsigned long len = namedAttrMap->length(); + for (unsigned long i = 0; i < len; i++) { + parseAttribute(&namedAttrMap->m_attrs[i]); + attributeChanged(namedAttrMap->m_attrs[i].id()); + } + } +} + +NodeImpl *ElementImpl::cloneNode(bool deep) +{ + ElementImpl *clone; + if ( !localName().isNull() ) + clone = getDocument()->createElementNS( namespaceURI(), nodeName() ); + else + clone = getDocument()->createElement( nodeName() ); + if (!clone) return 0; + finishCloneNode( clone, deep ); + return clone; +} + +void ElementImpl::finishCloneNode( ElementImpl* clone, bool deep ) +{ + // clone attributes + if (namedAttrMap) + clone->attributes()->copyAttributes(namedAttrMap); + + // clone individual style rules + if (m_styleDecls) + *(clone->styleRules()) = *m_styleDecls; + + if (deep) + cloneChildNodes(clone); +} + +DOMString ElementImpl::nodeName() const +{ + return tagName(); +} + +DOMString ElementImpl::namespaceURI() const +{ + if (m_htmlCompat) + return DOMString(); + return getDocument()->getName(NamespaceId, id() >> 16); +} + +DOMString ElementImpl::prefix() const +{ + return m_prefix; +} + +void ElementImpl::setPrefix( const DOMString &_prefix, int &exceptioncode ) +{ + checkSetPrefix(_prefix, exceptioncode); + if (exceptioncode) + return; + + if (m_prefix == _prefix.implementation()) + return; + + if (m_prefix) + m_prefix->deref(); + m_prefix = _prefix.implementation(); + if (m_prefix) + m_prefix->ref(); +} + +void ElementImpl::createAttributeMap() const +{ + namedAttrMap = new NamedAttrMapImpl(const_cast<ElementImpl*>(this)); + namedAttrMap->ref(); +} + +NamedAttrMapImpl* ElementImpl::defaultMap() const +{ + return 0; +} + +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::attach() +{ + assert(!attached()); + assert(!m_render); + assert(parentNode()); + +#if SPEED_DEBUG < 1 + createRendererIfNeeded(); +#endif + + NodeBaseImpl::attach(); +} + +void ElementImpl::close() +{ + NodeImpl::close(); + + // Trigger all the addChild changes as one large dynamic appendChildren change + if (attached()) + backwardsStructureChanged(); +} + +void ElementImpl::detach() +{ + getDocument()->dynamicDomRestyler().resetDependencies(this); + + NodeBaseImpl::detach(); +} + +void ElementImpl::structureChanged() +{ + NodeBaseImpl::structureChanged(); + + if (!getDocument()->renderer()) + return; // the document is about to be destroyed + + getDocument()->dynamicDomRestyler().restyleDepedent(this, StructuralDependency); + // In theory BackwardsStructurualDependencies are indifferent to prepend, + // but it's too rare to optimize. + getDocument()->dynamicDomRestyler().restyleDepedent(this, BackwardsStructuralDependency); +} + +void ElementImpl::backwardsStructureChanged() +{ + NodeBaseImpl::backwardsStructureChanged(); + + if (!getDocument()->renderer()) + return; // the document is about to be destroyed + + // Most selectors are not affected by append. Fire the few that are. + getDocument()->dynamicDomRestyler().restyleDepedent(this, BackwardsStructuralDependency); +} + +void ElementImpl::attributeChanged(NodeImpl::Id id) +{ + if (!getDocument()->renderer()) + return; // the document is about to be destroyed + +#if 0 // one-one dependencies for attributes disabled + getDocument()->dynamicDomRestyler().restyleDepedent(this, AttributeDependency); +#endif + if (getDocument()->dynamicDomRestyler().checkDependency(id, PersonalDependency)) + setChanged(true); + if (getDocument()->dynamicDomRestyler().checkDependency(id, AncestorDependency)) + setChangedAscendentAttribute(true); + if (getDocument()->dynamicDomRestyler().checkDependency(id, PredecessorDependency) && parent()) + // Any element that dependt on a predecessors attribute, also depend structurally on parent + parent()->structureChanged(); +} + +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()->attached() : 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, changed: %d)[%p: %s]", change, debug, changed(), 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(); + return; + } + else if (ch != NoChange) { + if( m_render && newStyle ) { + m_render->setStyle(newStyle); + } + } + newStyle->deref(); + + if ( change != Force) + change = ch; + } + // If a changed attribute has ancestor dependencies, restyle all children + if (changedAscendentAttribute()) { + change = Force; + setChangedAscendentAttribute(false); + } + + NodeImpl *n; + for (n = _first; n; n = n->nextSibling()) { + if ( change >= Inherit || n->isTextNode() || + n->hasChangedChild() || n->changed() ) { + //qDebug(" (%p) calling recalcStyle on child %p/%s, change=%d", this, n, n->isElementNode() ? ((ElementImpl *)n)->tagName().string().latin1() : n->isTextNode() ? "text" : "unknown", change ); + n->recalcStyle( change ); + } + } + + setChanged( false ); + setHasChangedChild( false ); +} + +bool ElementImpl::isFocusable() const +{ + // Only make editable elements selectable if its parent element + // is not editable. FIXME: this is not 100% right as non-editable elements + // within editable elements are focusable too. + return contentEditable() && !(parentNode() && parentNode()->contentEditable()); +} + +// DOM Section 1.1.1 +bool ElementImpl::childAllowed( NodeImpl *newChild ) +{ + if (!childTypeAllowed(newChild->nodeType())) + return false; + + // ### check xml element allowedness according to DTD + + // If either this node or the other node is an XML element node, allow regardless (we don't do DTD checks for XML + // yet) + if (isXMLElementNode() || newChild->isXMLElementNode()) + return true; + else + return checkChild(id(), newChild->id(), !getDocument()->inCompatMode()); +} + +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::scrollIntoView(bool /*alignToTop*/) +{ + // ### + kdWarning() << "non-standard scrollIntoView() not implemented" << endl; +} + +void ElementImpl::createDecl( ) +{ + m_styleDecls = new CSSStyleDeclarationImpl(0); + m_styleDecls->ref(); + m_styleDecls->setParent(getDocument()->elementSheet()); + m_styleDecls->setNode(this); + m_styleDecls->setStrictParsing( !getDocument()->inCompatMode() ); +} + +void ElementImpl::dispatchAttrRemovalEvent(NodeImpl::Id /*id*/, DOMStringImpl * /*value*/) +{ + // ### enable this stuff again + 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(NodeImpl::Id /*id*/, DOMStringImpl * /*value*/) +{ + // ### enable this stuff again + 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); +} + +void ElementImpl::updateId(DOMStringImpl* oldId, DOMStringImpl* newId) +{ + if (!inDocument()) + return; + + if (oldId && oldId->l) + removeId(DOMString(oldId).string()); + + if (newId && newId->l) + addId(DOMString(newId).string()); +} + +void ElementImpl::removeId(const QString& id) +{ + getDocument()->getElementByIdCache().remove(id, this); +} + +void ElementImpl::addId(const QString& id) +{ + getDocument()->getElementByIdCache().add(id, this); +} + +void ElementImpl::insertedIntoDocument() +{ + // need to do superclass processing first so inDocument() is true + // by the time we reach updateId + NodeBaseImpl::insertedIntoDocument(); + + if (hasID()) { + DOMString id = getAttribute(ATTR_ID); + updateId(0, id.implementation()); + } +} + +void ElementImpl::removedFromDocument() +{ + if (hasID()) { + DOMString id = getAttribute(ATTR_ID); + updateId(id.implementation(), 0); + } + + NodeBaseImpl::removedFromDocument(); +} + +DOMString ElementImpl::openTagStartToString(bool expandurls) const +{ + DOMString result = DOMString("<") + tagName(); + + NamedAttrMapImpl *attrMap = attributes(true); + + if (attrMap) { + unsigned long numAttrs = attrMap->length(); + for (unsigned long i = 0; i < numAttrs; i++) { + result += " "; + + AttributeImpl *attribute = attrMap->attrAt(i); + AttrImpl *attr = attribute->attr(); + + if (attr) { + result += attr->toString(); + } else { + result += getDocument()->getName( NodeImpl::AttributeId, attribute->id()); + if (!attribute->value().isNull()) { + result += "=\""; + // FIXME: substitute entities for any instances of " or ' + // Expand out all urls, i.e. the src and href attributes + if(expandurls && ( attribute->id() == ATTR_SRC || attribute->id() == ATTR_HREF)) + if(getDocument()) { + //We need to sanitize the urls - strip out the passwords. + //FIXME: are src= and href= the only places that might have a password and need to be sanitized? + KURL safeURL(getDocument()->completeURL(attribute->value().string())); + safeURL.setPass(QString::null); + result += safeURL.htmlURL(); + } + else { + kdWarning() << "getDocument() returned false"; + result += attribute->value(); + } + else + result += attribute->value(); + result += "\""; + } + } + } + } + + return result; +} +DOMString ElementImpl::selectionToString(NodeImpl *selectionStart, NodeImpl *selectionEnd, int startOffset, int endOffset, bool &found) const +{ + DOMString result = openTagStartToString(); + + if (hasChildNodes()) { + result += ">"; + + for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { + result += child->selectionToString(selectionStart, selectionEnd, startOffset, endOffset, found); // this might set found to true + if(child == selectionEnd) + found = true; + if(found) break; + } + + result += "</"; + result += tagName(); + result += ">"; + } else { + result += " />"; + } + + return result; +} + +DOMString ElementImpl::toString() const +{ + QString result = openTagStartToString().string(); //Accumulate in QString, since DOMString can't append well. + + if (hasChildNodes()) { + result += ">"; + + for (NodeImpl *child = firstChild(); child != NULL; child = child->nextSibling()) { + DOMString kid = child->toString(); + result += QConstString(kid.unicode(), kid.length()).string(); + } + + result += "</"; + result += tagName().string(); + result += ">"; + } else if (result.length() == 1) { + // ensure we dont get results like < /> can happen when serialize document + result = ""; + } else { + result += " />"; + } + + return result; +} + +bool ElementImpl::contentEditable() const { +#if 0 + DOM::CSSPrimitiveValueImpl *val = static_cast<DOM::CSSPrimitiveValueImpl *> + (const_cast<ElementImpl *>(this)->styleRules() + ->getPropertyCSSValue(CSS_PROP__KONQ_USER_INPUT)); +// kdDebug() << "val" << val << endl; + return val ? val->getIdent() == CSS_VAL_ENABLED : false; +#endif + return NodeImpl::contentEditable(); +} + +void ElementImpl::setContentEditable(bool enabled) { + // FIXME: the approach is flawed, better use an enum instead of bool + int value; + if (enabled) + value = CSS_VAL_ENABLED; + else { + // Intelligently use "none" or "disabled", depending on the type of + // element + // FIXME: intelligence not impl'd yet + value = CSS_VAL_NONE; + + // FIXME: reset caret if it is in this node or a child + }/*end if*/ + // FIXME: use addCSSProperty when I get permission to move it here +// kdDebug(6000) << "CSS_PROP__KHTML_USER_INPUT: "<< value << endl; + styleRules()->setProperty(CSS_PROP__KHTML_USER_INPUT, value, false, true); + setChanged(); + +} + +// ------------------------------------------------------------------------- + +XMLElementImpl::XMLElementImpl(DocumentImpl *doc, NodeImpl::Id id) + : ElementImpl(doc) +{ + // Called from createElement(). In this case localName, prefix, and namespaceURI all need to be null. + m_id = id; +} + +XMLElementImpl::XMLElementImpl(DocumentImpl *doc, NodeImpl::Id id, DOMStringImpl *_prefix) + : ElementImpl(doc) +{ + // Called from createElementNS() + m_id = id; + + m_prefix = _prefix; + if (m_prefix) + m_prefix->ref(); +} + +XMLElementImpl::~XMLElementImpl() +{ +} + +DOMString XMLElementImpl::localName() const +{ + if ( m_htmlCompat ) + return DOMString(); // was created with non-namespace-aware createElement() + return getDocument()->getName(ElementId, m_id); +} + +DOMString XMLElementImpl::tagName() const +{ + DOMString tn = getDocument()->getName(ElementId, id()); + if (m_htmlCompat) + tn = tn.upper(); + + if (m_prefix) + return DOMString(m_prefix) + ":" + tn; + + return tn; +} + +NodeImpl *XMLElementImpl::cloneNode ( bool deep ) +{ + XMLElementImpl *clone = new XMLElementImpl(docPtr(), id(), m_prefix); + finishCloneNode( clone, deep ); + return clone; +} + +// ------------------------------------------------------------------------- + +NamedAttrMapImpl::NamedAttrMapImpl(ElementImpl *element) + : m_element(element), + m_attrs(0), + m_attrCount(0) +{ +} + +NamedAttrMapImpl::~NamedAttrMapImpl() +{ + for (unsigned long i = 0; i < m_attrCount; i++) + m_attrs[i].free(); + free(m_attrs); +} + +NodeImpl *NamedAttrMapImpl::getNamedItem ( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName ) const +{ + if (!m_element) + return 0; + unsigned int mask = nsAware ? ~0L : NodeImpl_IdLocalMask; + id = (id & mask); + + for (unsigned long i = 0; i < m_attrCount; i++) { + if ((m_attrs[i].id() & mask) == id) { + // if we are called with a qualified name, filter out NS-aware elements with non-matching name. + if (qName && (namespacePart(m_attrs[i].id()) != defaultNamespace) && + strcasecmp(m_attrs[i].name(), DOMString(qName))) + continue; + return m_attrs[i].createAttr(m_element,m_element->docPtr()); + } + } + + return 0; +} + +Node NamedAttrMapImpl::removeNamedItem ( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName, int &exceptioncode ) +{ + if (!m_element) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return 0; + } + unsigned int mask = nsAware ? ~0L : NodeImpl_IdLocalMask; + id = (id & mask); + + for (unsigned long i = 0; i < m_attrCount; i++) { + if ((m_attrs[i].id() & mask) == id) { + // if we are called with a qualified name, filter out NS-aware elements with non-matching name. + if (qName && (namespacePart(m_attrs[i].id()) != defaultNamespace) && + strcasecmp(m_attrs[i].name(), DOMString(qName))) + continue; + id = m_attrs[i].id(); + if (id == ATTR_ID) + m_element->updateId(m_attrs[i].val(), 0); + Node removed(m_attrs[i].createAttr(m_element,m_element->docPtr())); + m_attrs[i].free(); + memmove(m_attrs+i,m_attrs+i+1,(m_attrCount-i-1)*sizeof(AttributeImpl)); + m_attrCount--; + m_attrs = (AttributeImpl*)realloc(m_attrs,m_attrCount*sizeof(AttributeImpl)); + m_element->parseAttribute(id,0); + m_element->attributeChanged(id); + return removed; + } + } + + // NOT_FOUND_ERR: Raised if there is no node with the specified namespaceURI + // and localName in this map. + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; +} + +Node NamedAttrMapImpl::setNamedItem ( NodeImpl* arg, bool nsAware, DOMStringImpl* qName, int &exceptioncode ) +{ + if (!m_element) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this map is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return 0; + } + + // WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map. + if (arg->getDocument() != m_element->getDocument()) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return 0; + } + + // HIERARCHY_REQUEST_ERR: Raised if an attempt is made to add a node doesn't belong in this NamedNodeMap + if (!arg->isAttributeNode()) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return 0; + } + AttrImpl *attr = static_cast<AttrImpl*>(arg); + + // 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() && attr->ownerElement() != m_element) { + exceptioncode = DOMException::INUSE_ATTRIBUTE_ERR; + return 0; + } + + if (attr->ownerElement() == m_element) { + // Already have this attribute. + // DOMTS core-1 test "hc_elementreplaceattributewithself" says we should return it. + return attr; + } + unsigned int mask = nsAware ? ~0L : NodeImpl_IdLocalMask; + NodeImpl::Id id = (attr->id() & mask); + + for (unsigned long i = 0; i < m_attrCount; i++) { + if ((m_attrs[i].id() & mask) == id) { + // if we are called with a qualified name, filter out NS-aware elements with non-matching name. + if (qName && (namespacePart(m_attrs[i].id()) != defaultNamespace) && + strcasecmp(m_attrs[i].name(), DOMString(qName))) + continue; + // Attribute exists; replace it + if (id == ATTR_ID) + m_element->updateId(m_attrs[i].val(), attr->val()); + + Node replaced = m_attrs[i].createAttr(m_element,m_element->docPtr()); + m_attrs[i].free(); + m_attrs[i].m_attrId = 0; /* "has implementation" flag */ + m_attrs[i].m_data.attr = attr; + m_attrs[i].m_data.attr->ref(); + attr->setElement(m_element); + m_element->parseAttribute(&m_attrs[i]); + m_element->attributeChanged(m_attrs[i].id()); + // ### dispatch mutation events + return replaced; + } + } + + // No existing attribute; add to list + m_attrCount++; + m_attrs = (AttributeImpl*)realloc(m_attrs,m_attrCount*sizeof(AttributeImpl)); + m_attrs[m_attrCount-1].m_attrId = 0; /* "has implementation" flag */ + m_attrs[m_attrCount-1].m_data.attr = attr; + m_attrs[m_attrCount-1].m_data.attr->ref(); + attr->setElement(m_element); + if (id == ATTR_ID) + m_element->updateId(0, attr->val()); + m_element->parseAttribute(&m_attrs[m_attrCount-1]); + m_element->attributeChanged(m_attrs[m_attrCount-1].id()); + // ### dispatch mutation events + + return 0; +} + +NodeImpl *NamedAttrMapImpl::item ( unsigned long index ) const +{ + if (!m_element) + return 0; + + if (index >= m_attrCount) + return 0; + else + return m_attrs[index].createAttr(m_element,m_element->docPtr()); +} + +unsigned long NamedAttrMapImpl::length( ) const +{ + if (!m_element) + return 0; + + return m_attrCount; +} + +NodeImpl::Id NamedAttrMapImpl::idAt(unsigned long index) const +{ + assert(index <= m_attrCount); + return m_attrs[index].id(); +} + +DOMStringImpl *NamedAttrMapImpl::valueAt(unsigned long index) const +{ + assert(index <= m_attrCount); + return m_attrs[index].val(); +} + +DOMStringImpl *NamedAttrMapImpl::getValue(NodeImpl::Id id, bool nsAware, DOMStringImpl* qName) const +{ + unsigned int mask = nsAware ? ~0L : NodeImpl_IdLocalMask; + id = (id & mask); + for (unsigned long i = 0; i < m_attrCount; i++) + if ((m_attrs[i].id() & mask) == id) { + // if we are called with a qualified name, filter out NS-aware elements with non-matching name. + if (qName && (namespacePart(m_attrs[i].id()) != defaultNamespace) && + strcasecmp(m_attrs[i].name(), qName)) + continue; + return m_attrs[i].val(); + } + return 0; +} + +void NamedAttrMapImpl::setValue(NodeImpl::Id id, DOMStringImpl *value, DOMStringImpl* qName, + DOMStringImpl *prefix, bool nsAware, bool hasNS) +{ + assert( !(qName && nsAware) ); + if (!id) return; + // Passing in a null value here causes the attribute to be removed. This is a khtml extension + // (the spec does not specify what to do in this situation). + int exceptioncode = 0; + if (!value) { + removeNamedItem(id, nsAware, qName, exceptioncode); + return; + } + unsigned int mask = nsAware ? ~0L : NodeImpl_IdLocalMask; + NodeImpl::Id mid = (id & mask); + + // Check for an existing attribute. + for (unsigned long i = 0; i < m_attrCount; i++) { + if ((m_attrs[i].id() & mask) == mid) { + // if we are called with a qualified name, filter out NS-aware elements with non-matching name. + if (qName && (namespacePart(m_attrs[i].id()) != defaultNamespace) && + strcasecmp(m_attrs[i].name(), DOMString(qName))) + continue; + if (prefix) + m_attrs[i].attr()->setPrefix(prefix,exceptioncode); + m_attrs[i].setValue(value,m_element); + // ### dispatch mutation events + return; + } + } + + // No existing matching attribute; add a new one + m_attrCount++; + m_attrs = (AttributeImpl*)realloc(m_attrs,m_attrCount*sizeof(AttributeImpl)); + if (!nsAware) { + // Called from setAttribute()... we only have a name + m_attrs[m_attrCount-1].m_attrId = id; + m_attrs[m_attrCount-1].m_data.value = value; + m_attrs[m_attrCount-1].m_data.value->ref(); + } + else { + // Called from setAttributeNS()... need to create a full AttrImpl here + if(!m_element) + return; + m_attrs[m_attrCount-1].m_data.attr = new AttrImpl(m_element,m_element->docPtr(), + id, + value, + prefix); + m_attrs[m_attrCount-1].m_attrId = 0; /* "has implementation" flag */ + m_attrs[m_attrCount-1].m_data.attr->ref(); + m_attrs[m_attrCount-1].m_data.attr->setHTMLCompat( !hasNS && + m_element->getDocument()->htmlMode() != DocumentImpl::XHtml ); + } + if (m_element) { + if (id == ATTR_ID) + m_element->updateId(0, value); + m_element->parseAttribute(&m_attrs[m_attrCount-1]); + m_element->attributeChanged(m_attrs[m_attrCount-1].id()); + } + // ### dispatch mutation events +} + +Attr NamedAttrMapImpl::removeAttr(AttrImpl *attr) +{ + for (unsigned long i = 0; i < m_attrCount; i++) { + if (m_attrs[i].attr() == attr) { + NodeImpl::Id id = m_attrs[i].id(); + if (id == ATTR_ID) + m_element->updateId(attr->val(), 0); + Node removed(m_attrs[i].createAttr(m_element,m_element->docPtr())); + m_attrs[i].free(); + memmove(m_attrs+i,m_attrs+i+1,(m_attrCount-i-1)*sizeof(AttributeImpl)); + m_attrCount--; + m_attrs = (AttributeImpl*)realloc(m_attrs,m_attrCount*sizeof(AttributeImpl)); + m_element->parseAttribute(id,0); + m_element->attributeChanged(id); + // ### dispatch mutation events + return removed; + } + } + + return 0; +} + +NodeImpl::Id NamedAttrMapImpl::mapId(DOMStringImpl* namespaceURI, + DOMStringImpl* localName, bool readonly) +{ + if (!m_element) + return 0; + + return m_element->getDocument()->getId(NodeImpl::AttributeId, namespaceURI, 0, localName, readonly, + true /*lookupHTML*/); +} + +void NamedAttrMapImpl::copyAttributes(NamedAttrMapImpl *other) +{ + assert(m_element); + unsigned long i; + for (i = 0; i < m_attrCount; i++) { + if (m_attrs[i].id() == ATTR_ID) + m_element->updateId(m_attrs[i].val(), 0); + m_attrs[i].free(); + } + m_attrCount = other->m_attrCount; + m_attrs = (AttributeImpl*)realloc(m_attrs,m_attrCount*sizeof(AttributeImpl)); + for (i = 0; i < m_attrCount; i++) { + m_attrs[i].m_attrId = other->m_attrs[i].m_attrId; + if (m_attrs[i].m_attrId) { + m_attrs[i].m_data.value = other->m_attrs[i].m_data.value; + m_attrs[i].m_data.value->ref(); + } + else { + m_attrs[i].m_data.attr = static_cast<AttrImpl*>(other->m_attrs[i].m_data.attr->cloneNode(true)); + m_attrs[i].m_data.attr->ref(); + m_attrs[i].m_data.attr->setElement(m_element); + } + if (m_attrs[i].id() == ATTR_ID) + m_element->updateId(0, m_attrs[i].val()); + m_element->parseAttribute(&m_attrs[i]); + m_element->attributeChanged(m_attrs[i].id()); + } +} + +void NamedAttrMapImpl::setElement(ElementImpl *element) +{ + assert(!m_element); + m_element = element; + + for (unsigned long i = 0; i < m_attrCount; i++) + if (m_attrs[i].attr()) + m_attrs[i].attr()->setElement(element); +} + +void NamedAttrMapImpl::detachFromElement() +{ + // This makes the map invalid; nothing can really be done with it now since it's not + // associated with an element. But we have to keep it around in memory in case there + // are still references to it. + m_element = 0; + for (unsigned long i = 0; i < m_attrCount; i++) + m_attrs[i].free(); + free(m_attrs); + m_attrs = 0; + m_attrCount = 0; +} diff --git a/khtml/xml/dom_elementimpl.h b/khtml/xml/dom_elementimpl.h new file mode 100644 index 000000000..1bc5148d5 --- /dev/null +++ b/khtml/xml/dom_elementimpl.h @@ -0,0 +1,392 @@ +/* + * 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) + * (C) 2003 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. + * + */ +#ifndef _DOM_ELEMENTImpl_h_ +#define _DOM_ELEMENTImpl_h_ + +#include "dom_nodeimpl.h" +#include "dom/dom_exception.h" +#include "dom/dom_element.h" +#include "xml/dom_stringimpl.h" +#include "misc/shared.h" + +namespace khtml { + class CSSStyleSelector; +} + +namespace DOM { + +class ElementImpl; +class DocumentImpl; +class NamedAttrMapImpl; + +// Attr can have Text and EntityReference children +// therefore it has to be a fullblown Node. The plan +// is to dynamically allocate a textchild and store the +// resulting nodevalue in the AttributeImpl upon +// destruction. however, this is not yet implemented. +class AttrImpl : public NodeBaseImpl +{ + friend class ElementImpl; + friend class NamedAttrMapImpl; + +public: + AttrImpl(ElementImpl* element, DocumentImpl* docPtr, NodeImpl::Id attrId, + DOMStringImpl *value, DOMStringImpl *prefix = 0); + ~AttrImpl(); + +private: + AttrImpl(const AttrImpl &other); + AttrImpl &operator = (const AttrImpl &other); +public: + + // DOM methods & attributes for Attr + bool specified() const { return m_specified; } + ElementImpl* ownerElement() const { return m_element; } + void setOwnerElement( ElementImpl* impl ) { m_element = impl; } + DOMString name() const; + + //DOMString value() const; + void setValue( const DOMString &v, int &exceptioncode ); + + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual DOMString prefix() const; + virtual void setPrefix(const DOMString &_prefix, int &exceptioncode ); + virtual DOMString namespaceURI() const; + virtual DOMString localName() const; + + virtual DOMString nodeValue() const; + virtual void setNodeValue( const DOMString &, int &exceptioncode ); + virtual NodeImpl *cloneNode ( bool deep ); + + virtual DOMStringImpl* textContent() const; + virtual void setTextContent( const DOMString &text, int& exceptioncode ); + + + // Other methods (not part of DOM) + virtual bool isAttributeNode() const { return true; } + virtual bool childAllowed( NodeImpl *newChild ); + virtual bool childTypeAllowed( unsigned short type ); + virtual NodeImpl::Id id() const { return m_attrId; } + + virtual DOMString toString() const; + + void setElement(ElementImpl *element); + DOMStringImpl *val() { return m_value; } + +protected: + ElementImpl *m_element; + NodeImpl::Id m_attrId; + DOMStringImpl *m_value; + DOMStringImpl *m_prefix; + DOMStringImpl *m_localName; +}; + +// Mini version of AttrImpl internal to NamedAttrMapImpl. +// Stores either the id and value of an attribute +// (in the case of m_attrId != 0), or a pointer to an AttrImpl (if m_attrId == 0) +// The latter case only happens when the Attr node is requested by some DOM +// code or is an XML attribute. +// In most cases the id and value is all we need to store, which is more +// memory efficient. +struct AttributeImpl +{ + NodeImpl::Id id() const { return m_attrId ? m_attrId : m_data.attr->id(); } + DOMStringImpl *val() const { return m_attrId ? m_data.value : m_data.attr->val(); } + DOMString value() const { return val(); } + AttrImpl *attr() const { return m_attrId ? 0 : m_data.attr; } + DOMString namespaceURI() { return m_attrId ? DOMString() : m_data.attr->namespaceURI(); } + DOMString prefix() { return m_attrId ? DOMString() : m_data.attr->prefix(); } + DOMString localName() { return m_attrId ? DOMString() : m_data.attr->localName(); } + DOMString name() { return m_attrId ? DOMString() : m_data.attr->name(); } + + void setValue(DOMStringImpl *value, ElementImpl *element); + AttrImpl *createAttr(ElementImpl *element, DocumentImpl *docPtr); + void free(); + + NodeImpl::Id m_attrId; + union { + DOMStringImpl *value; + AttrImpl *attr; + } m_data; +}; + +class ElementImpl : public NodeBaseImpl +{ + friend class DocumentImpl; + friend class NamedAttrMapImpl; + friend class AttrImpl; + friend class NodeImpl; + friend class khtml::CSSStyleSelector; +public: + ElementImpl(DocumentImpl *doc); + ~ElementImpl(); + + DOMString getAttribute( NodeImpl::Id id, bool nsAware = 0, const DOMString& qName = DOMString() ) const; + DOMStringImpl* getAttributeImpl( NodeImpl::Id id, bool nsAware = 0, DOMStringImpl* qName = 0 ) const; + void setAttribute( NodeImpl::Id id, const DOMString &value, const DOMString &qName, + int &exceptioncode ); + void setAttributeNS( const DOMString &namespaceURI, const DOMString &qualifiedName, + const DOMString& value, int &exceptioncode ); + virtual DOMString prefix() const; + void setPrefix(const DOMString &_prefix, int &exceptioncode ); + virtual DOMString namespaceURI() const; + + // DOM methods overridden from parent classes + virtual DOMString tagName() const = 0; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode ( bool deep ); + virtual DOMString nodeName() const; + virtual NodeImpl::Id id() const = 0; + virtual bool isElementNode() const { return true; } + virtual void insertedIntoDocument(); + virtual void removedFromDocument(); + + // convenience methods which ignore exceptions + void setAttribute (NodeImpl::Id id, const DOMString &value); + + NamedAttrMapImpl* attributes(bool readonly = false) const + { + if (!readonly && !namedAttrMap) createAttributeMap(); + return namedAttrMap; + } + + //This is always called, whenever an attribute changed + virtual void parseAttribute(AttributeImpl *) {} + void parseAttribute(NodeImpl::Id attrId, DOMStringImpl *value) { + AttributeImpl aimpl; + aimpl.m_attrId = attrId; + aimpl.m_data.value = value; + parseAttribute(&aimpl); + } + + // not part of the DOM + void setAttributeMap ( NamedAttrMapImpl* list ); + + // State of the element. + virtual QString state() { return QString::null; } + + virtual void attach(); + virtual void close(); + virtual void detach(); + virtual void structureChanged(); + virtual void backwardsStructureChanged(); + virtual void attributeChanged(NodeImpl::Id attrId); + + virtual khtml::RenderStyle *styleForRenderer(khtml::RenderObject *parent); + virtual khtml::RenderObject *createRenderer(khtml::RenderArena *, khtml::RenderStyle *); + virtual void recalcStyle( StyleChange = NoChange ); + + virtual void mouseEventHandler( MouseEvent* /*ev*/, bool /*inside*/ ) {} + virtual bool isFocusable() const; + virtual bool childAllowed( NodeImpl *newChild ); + virtual bool childTypeAllowed( unsigned short type ); + + DOM::CSSStyleDeclarationImpl *styleRules() { + if (!m_styleDecls) createDecl(); + return m_styleDecls; + } + + void dispatchAttrRemovalEvent(NodeImpl::Id id, DOMStringImpl *value); + void dispatchAttrAdditionEvent(NodeImpl::Id id, DOMStringImpl *value); + + virtual DOMString toString() const; + virtual DOMString selectionToString(NodeImpl *selectionStart, NodeImpl *selectionEnd, int startOffset, int endOffset, bool &found) const; + + virtual bool contentEditable() const; + void setContentEditable(bool enabled); + + void scrollIntoView(bool alignToTop); + + /** Returns the opening tag and properties. + * Examples: '<b', '<img alt="hello" src="image.png" + * + * For security reasons, passwords are stripped out of all src= and + * href= tags if expandurls is turned on. + * + * @param expandurls If this is set then in the above example, it would give + * src="http://website.com/image.png". Note that the password + * is stripped out of the url. + * + * DOM::RangeImpl uses this which is why it is public. + */ + DOMString openTagStartToString(bool expandurls = false) const; + + void updateId(DOMStringImpl* oldId, DOMStringImpl* newId); + //Called when mapping from id to this node in document should be removed + virtual void removeId(const QString& id); + //Called when mapping from id to this node in document should be added + virtual void addId (const QString& id); + +protected: + void createAttributeMap() const; + void createDecl(); + void finishCloneNode( ElementImpl *clone, bool deep ); + +private: + // map of default attributes. derived element classes are responsible + // for setting this according to the corresponding element description + // in the DTD + virtual NamedAttrMapImpl* defaultMap() const; + +protected: // member variables + mutable NamedAttrMapImpl *namedAttrMap; + + DOM::CSSStyleDeclarationImpl *m_styleDecls; + DOMStringImpl *m_prefix; +}; + + +class XMLElementImpl : public ElementImpl +{ + +public: + XMLElementImpl(DocumentImpl *doc, NodeImpl::Id id); + XMLElementImpl(DocumentImpl *doc, NodeImpl::Id id, DOMStringImpl *_qualifiedName); + ~XMLElementImpl(); + + // DOM methods overridden from parent classes + virtual DOMString tagName() const; + virtual DOMString localName() const; + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + virtual bool isXMLElementNode() const { return true; } + virtual Id id() const { return m_id; } + +protected: + Id m_id; +}; + +// the map of attributes of an element +class NamedAttrMapImpl : public NamedNodeMapImpl +{ + friend class ElementImpl; +public: + NamedAttrMapImpl(ElementImpl *element); + virtual ~NamedAttrMapImpl(); + + // DOM methods & attributes for NamedNodeMap + virtual NodeImpl *getNamedItem ( NodeImpl::Id id, bool nsAware = false, DOMStringImpl* qName = 0 ) const; + virtual Node removeNamedItem ( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName, int &exceptioncode ); + virtual Node setNamedItem ( NodeImpl* arg, bool nsAware, DOMStringImpl* qName, int &exceptioncode ); + + virtual NodeImpl *item ( unsigned long index ) const; + virtual unsigned long length( ) const; + + // Other methods (not part of DOM) + virtual bool isReadOnly() { return false; } + + AttributeImpl *attrAt(unsigned long index) const { return &m_attrs[index]; } + // ### replace idAt and getValueAt with attrAt + NodeImpl::Id idAt(unsigned long index) const; + DOMStringImpl *valueAt(unsigned long index) const; + DOMStringImpl *getValue(NodeImpl::Id id, bool nsAware = false, DOMStringImpl* qName = 0) const; + void setValue(NodeImpl::Id id, DOMStringImpl *value, DOMStringImpl* qName = 0, + DOMStringImpl *prefix = 0, bool nsAware = false, bool hasNS = false); + Attr removeAttr(AttrImpl *attr); + NodeImpl::Id mapId(DOMStringImpl* namespaceURI, DOMStringImpl* localName, bool readonly); + void copyAttributes(NamedAttrMapImpl *other); + void setElement(ElementImpl *element); + void detachFromElement(); + +protected: + ElementImpl *m_element; + AttributeImpl *m_attrs; + unsigned long m_attrCount; +}; + +// ------------ inline DOM helper functions --------------- + +inline bool checkQualifiedName(const DOMString &qualifiedName, const DOMString &namespaceURI, int *colonPos, + bool nameCanBeNull, bool nameCanBeEmpty, int *pExceptioncode) +{ + + // Not mentioned in spec: throw NAMESPACE_ERR if no qualifiedName supplied + if (!nameCanBeNull && qualifiedName.isNull()) { + if (pExceptioncode) + *pExceptioncode = DOMException::NAMESPACE_ERR; + return false; + } + + // INVALID_CHARACTER_ERR: Raised if the specified qualified name contains an illegal character. + if (!qualifiedName.isNull() && !Element::khtmlValidQualifiedName(qualifiedName) + && ( !qualifiedName.isEmpty() || !nameCanBeEmpty ) ) { + if (pExceptioncode) + *pExceptioncode = DOMException::INVALID_CHARACTER_ERR; + return false; + } + + // NAMESPACE_ERR: + // - Raised if the qualifiedName is malformed, + // - if the qualifiedName has a prefix and the namespaceURI is null, or + // - if the qualifiedName is null and the namespaceURI is different from null + // - if the qualifiedName has a prefix that is "xml" and the namespaceURI is different + // from "http://www.w3.org/XML/1998/namespace" [Namespaces]. + int colonpos = -1; + uint i; + DOMStringImpl *qname = qualifiedName.implementation(); + for (i = 0 ; i < qname->l ; i++) { + if ((*qname)[i] == ':') { + colonpos = i; + break; + } + } + + if (!qualifiedName.isNull() && Element::khtmlMalformedQualifiedName(qualifiedName) || + (colonpos >= 0 && namespaceURI.isNull()) || + (qualifiedName.isNull() && !namespaceURI.isNull()) || + (colonpos == 3 && qualifiedName[0] == 'x' && qualifiedName[1] == 'm' && qualifiedName[2] == 'l' && + namespaceURI != "http://www.w3.org/XML/1998/namespace")) { + if (pExceptioncode) + *pExceptioncode = DOMException::NAMESPACE_ERR; + return false; + } + if(colonPos) + *colonPos = colonpos; + return true; +} + +inline void splitPrefixLocalName(DOMStringImpl *qualifiedName, DOMString &prefix, DOMString &localName, int colonPos = -2) +{ + if (colonPos == -2) + for (uint i = 0 ; i < qualifiedName->l ; ++i) + if (qualifiedName->s[i] == ':') { + colonPos = i; + break; + } + if (colonPos >= 0) { + prefix = qualifiedName->copy(); + localName = prefix.split(colonPos+1); + prefix.implementation()->truncate(colonPos); + } else + localName = qualifiedName->copy(); +} + +} //namespace + +#endif diff --git a/khtml/xml/dom_nodeimpl.cpp b/khtml/xml/dom_nodeimpl.cpp new file mode 100644 index 000000000..692ba4394 --- /dev/null +++ b/khtml/xml/dom_nodeimpl.cpp @@ -0,0 +1,2068 @@ +/* + * 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 Dirk Mueller (mueller@kde.org) + * (C) 2003-2006 Apple Computer, Inc. + * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 "dom/dom_exception.h" +#include "misc/htmlattrs.h" +#include "misc/htmltags.h" +#include "xml/dom_elementimpl.h" +#include "xml/dom_textimpl.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_docimpl.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_restyler.h" + +#include <kglobal.h> +#include <kdebug.h> + +#include "rendering/render_text.h" +#include "rendering/render_flow.h" +#include "rendering/render_line.h" + +#include "ecma/kjs_proxy.h" +#include "khtmlview.h" +#include "khtml_part.h" +#include "dom_nodeimpl.h" + +// from khtml_caret_p.h +namespace khtml { +void /*KDE_NO_EXPORT*/ mapDOMPosToRenderPos(DOM::NodeImpl *node, long offset, + khtml::RenderObject *&r, long &r_ofs, bool &outside, bool &outsideEnd); +} + +using namespace DOM; +using namespace khtml; + +NodeImpl::NodeImpl(DocumentImpl *doc) + : m_document(doc), + m_previous(0), + m_next(0), + m_render(0), + m_tabIndex( 0 ), + m_hasId( false ), + m_attached(false), + m_closed(false), + m_changed( false ), + m_hasChangedChild( false ), + m_changedAscendentAttribute( false ), + m_inDocument( false ), + m_hasAnchor( false ), + m_specified( false ), + m_hovered( false ), + m_focused( false ), + m_active( false ), + m_implicit( false ), + m_htmlCompat( false ), + m_hasClassList( false ), + m_hasClass( false ) +{ +} + +NodeImpl::~NodeImpl() +{ + if (m_render) + detach(); + if (m_previous) + m_previous->setNextSibling(0); + if (m_next) + m_next->setPreviousSibling(0); +} + +DOMString NodeImpl::nodeValue() const +{ + return DOMString(); +} + +void NodeImpl::setNodeValue( const DOMString &/*_nodeValue*/, int &/*exceptioncode*/ ) +{ + // by default nodeValue is null, so setting it has no effect + // don't throw NO_MODIFICATION_ALLOWED_ERR from here, DOMTS-Core-Level1's hc_nodevalue03 + // (createEntityReference().setNodeValue())) says it would be wrong. + // This must be done by subclasses instead. +} + +DOMString NodeImpl::nodeName() const +{ + return DOMString(); +} + +unsigned short NodeImpl::nodeType() const +{ + return 0; +} + +NodeListImpl *NodeImpl::childNodes() +{ + return new ChildNodeListImpl(this); +} + +NodeImpl *NodeImpl::firstChild() const +{ + return 0; +} + +NodeImpl *NodeImpl::lastChild() const +{ + return 0; +} + +NodeImpl *NodeImpl::insertBefore( NodeImpl *, NodeImpl *, int &exceptioncode ) +{ + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return 0; +} + +void NodeImpl::replaceChild( NodeImpl *, NodeImpl *, int &exceptioncode ) +{ + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; +} + +void NodeImpl::removeChild( NodeImpl *, int &exceptioncode ) +{ + exceptioncode = DOMException::NOT_FOUND_ERR; +} + +NodeImpl *NodeImpl::appendChild( NodeImpl *, int &exceptioncode ) +{ + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return 0; +} + +bool NodeImpl::hasChildNodes( ) const +{ + return false; +} + +void NodeImpl::normalize () +{ + // ### normalize attributes? (when we store attributes using child nodes) + int exceptioncode = 0; + NodeImpl *child = firstChild(); + + // Recursively go through the subtree beneath us, normalizing all nodes. In the case + // where there are two adjacent text nodes, they are merged together + while (child) { + NodeImpl *nextChild = child->nextSibling(); + + if (nextChild && child->nodeType() == Node::TEXT_NODE && nextChild->nodeType() == Node::TEXT_NODE) { + // Current child and the next one are both text nodes... merge them + TextImpl *currentText = static_cast<TextImpl*>(child); + TextImpl *nextText = static_cast<TextImpl*>(nextChild); + + currentText->appendData(nextText->data(),exceptioncode); + if (exceptioncode) + return; + + removeChild(nextChild,exceptioncode); + if (exceptioncode) + return; + } + else { + child->normalize(); + child = nextChild; + } + } +} + +DOMString NodeImpl::prefix() const +{ + // For nodes other than elements and attributes, the prefix is always null + return DOMString(); +} + +DOMString NodeImpl::namespaceURI() const +{ + return DOMString(); +} + +void NodeImpl::setPrefix(const DOMString &/*_prefix*/, int &exceptioncode ) +{ + // The spec says that for nodes other than elements and attributes, prefix is always null. + // It does not say what to do when the user tries to set the prefix on another type of + // node, however mozilla throws a NAMESPACE_ERR exception + exceptioncode = DOMException::NAMESPACE_ERR; +} + +DOMString NodeImpl::localName() const +{ + return DOMString(); +} + +void NodeImpl::setFirstChild(NodeImpl *) +{ +} + +void NodeImpl::setLastChild(NodeImpl *) +{ +} + +NodeImpl *NodeImpl::addChild(NodeImpl *) +{ + return 0; +} + +void NodeImpl::getCaret(int offset, bool override, int &_x, int &_y, int &width, int &height) +{ + if (m_render) { + RenderObject *r; + long r_ofs; + bool outside, outsideEnd; +#if 0 +kdDebug(6200) << "getCaret: node " << this << " " << nodeName().string() << " offset: " << offset << endl; +#endif + mapDOMPosToRenderPos(this, offset, r, r_ofs, outside, outsideEnd); +#if 0 +kdDebug(6200) << "getCaret: r " << r << " " << (r?r->renderName():QString::null) << " r_ofs: " << r_ofs << " outside " << outside << " outsideEnd " << outsideEnd << endl; +#endif + if (r) { + r->caretPos(r_ofs, override*RenderObject::CFOverride + + outside*RenderObject::CFOutside + + outsideEnd*RenderObject::CFOutsideEnd, _x, _y, width, height); + } else + _x = _y = height = -1, width = 1; + } else _x = _y = height = -1, width = 1; +} + +QRect NodeImpl::getRect() const +{ + int _x, _y; + if(m_render && m_render->absolutePosition(_x, _y)) + return QRect( _x + m_render->inlineXPos(), _y + m_render->inlineYPos(), + m_render->width(), m_render->height() + renderer()->borderTopExtra() + renderer()->borderBottomExtra() ); + + return QRect(); +} + +void NodeImpl::setChanged(bool b) +{ + if (b && !attached()) // changed compared to what? + return; + + m_changed = b; + if ( b ) { + NodeImpl *p = parentNode(); + while ( p ) { + p->setHasChangedChild( true ); + p = p->parentNode(); + } + getDocument()->setDocumentChanged(); + } +} + +bool NodeImpl::isInline() const +{ + if (m_render) return m_render->style()->display() == khtml::INLINE; + return !isElementNode(); +} + + +unsigned long NodeImpl::nodeIndex() const +{ + NodeImpl *_tempNode = previousSibling(); + unsigned long count=0; + for( count=0; _tempNode; count++ ) + _tempNode = _tempNode->previousSibling(); + return count; +} + +void NodeImpl::addEventListener(int id, EventListener *listener, const bool useCapture) +{ + switch (id) { + case EventImpl::DOMSUBTREEMODIFIED_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMSUBTREEMODIFIED_LISTENER); + break; + case EventImpl::DOMNODEINSERTED_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMNODEINSERTED_LISTENER); + break; + case EventImpl::DOMNODEREMOVED_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMNODEREMOVED_LISTENER); + break; + case EventImpl::DOMNODEREMOVEDFROMDOCUMENT_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMNODEREMOVEDFROMDOCUMENT_LISTENER); + break; + case EventImpl::DOMNODEINSERTEDINTODOCUMENT_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMNODEINSERTEDINTODOCUMENT_LISTENER); + break; + case EventImpl::DOMATTRMODIFIED_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMATTRMODIFIED_LISTENER); + break; + case EventImpl::DOMCHARACTERDATAMODIFIED_EVENT: + getDocument()->addListenerType(DocumentImpl::DOMCHARACTERDATAMODIFIED_LISTENER); + break; + default: + break; + } + + m_regdListeners.addEventListener(id, listener, useCapture); +} + +void NodeImpl::removeEventListener(int id, EventListener *listener, bool useCapture) +{ + m_regdListeners.removeEventListener(id, listener, useCapture); +} + +void NodeImpl::setHTMLEventListener(int id, EventListener *listener) +{ + m_regdListeners.setHTMLEventListener(id, listener); +} + +EventListener *NodeImpl::getHTMLEventListener(int id) +{ + return m_regdListeners.getHTMLEventListener(id); +} + +void NodeImpl::dispatchEvent(EventImpl *evt, int &exceptioncode, bool tempEvent) +{ + evt->setTarget(this); + + // Since event handling code could cause this object to be deleted, grab a reference to the view now + KHTMLView *view = getDocument()->view(); + + dispatchGenericEvent( evt, exceptioncode ); + + // If tempEvent is true, this means that the DOM implementation will not be storing a reference to the event, i.e. + // there is no way to retrieve it from javascript if a script does not already have a reference to it in a variable. + // So there is no need for the interpreter to keep the event in its cache + if (tempEvent && view && view->part() && view->part()->jScript()) + view->part()->jScript()->finishedWithEvent(evt); +} + +void NodeImpl::dispatchGenericEvent( EventImpl *evt, int &/*exceptioncode */) +{ + // ### check that type specified + + // work out what nodes to send event to + QPtrList<NodeImpl> nodeChain; + NodeImpl *n; + for (n = this; n; n = n->parentNode()) { + n->ref(); + nodeChain.prepend(n); + } + + // trigger any capturing event handlers on our way down + evt->setEventPhase(Event::CAPTURING_PHASE); + QPtrListIterator<NodeImpl> it(nodeChain); + for (; it.current() && it.current() != this && !evt->propagationStopped(); ++it) { + evt->setCurrentTarget(it.current()); + it.current()->handleLocalEvents(evt,true); + } + + // dispatch to the actual target node + it.toLast(); + NodeImpl* propagationSentinel = 0; + if (!evt->propagationStopped()) { + evt->setEventPhase(Event::AT_TARGET); + evt->setCurrentTarget(it.current()); + it.current()->handleLocalEvents(evt, true); + if (!evt->propagationStopped()) + it.current()->handleLocalEvents(evt,false); + else + propagationSentinel = it.current(); + } + --it; + + if (evt->bubbles()) { + evt->setEventPhase(Event::BUBBLING_PHASE); + for (; it.current() && !evt->propagationStopped(); --it) { + if (evt->propagationStopped()) propagationSentinel = it.current(); + evt->setCurrentTarget(it.current()); + it.current()->handleLocalEvents(evt,false); + } + + // now we call all default event handlers (this is not part of DOM - it is internal to khtml) + evt->setCurrentTarget(0); + evt->setEventPhase(0); // I guess this is correct, the spec does not seem to say + for (it.toLast(); it.current() && it.current() != propagationSentinel && + !evt->defaultPrevented() && !evt->defaultHandled(); --it) + it.current()->defaultEventHandler(evt); + + if (evt->id() == EventImpl::CLICK_EVENT && !evt->defaultPrevented() && + static_cast<MouseEventImpl*>( evt )->button() == 0) // LMB click + dispatchUIEvent(EventImpl::DOMACTIVATE_EVENT, static_cast<UIEventImpl*>(evt)->detail()); + } + + // copy this over into a local variable, as the following deref() calls might cause this to be deleted. + DocumentImpl *doc = m_document.get(); + doc->ref(); + + // deref all nodes in chain + it.toFirst(); + for (; it.current(); ++it) + it.current()->deref(); // this may delete us + + DocumentImpl::updateDocumentsRendering(); + doc->deref(); +} + +bool NodeImpl::dispatchHTMLEvent(int _id, bool canBubbleArg, bool cancelableArg) +{ + int exceptioncode = 0; + EventImpl* const evt = new EventImpl(static_cast<EventImpl::EventId>(_id),canBubbleArg,cancelableArg); + evt->ref(); + dispatchEvent(evt,exceptioncode,true); + bool ret = !evt->defaultPrevented(); + evt->deref(); + return ret; +} + +void NodeImpl::dispatchWindowEvent(int _id, bool canBubbleArg, bool cancelableArg) +{ + int exceptioncode = 0; + EventImpl* const evt = new EventImpl(static_cast<EventImpl::EventId>(_id),canBubbleArg,cancelableArg); + evt->setTarget( 0 ); + evt->ref(); + DocumentImpl *doc = getDocument(); + doc->ref(); + dispatchGenericEvent( evt, exceptioncode ); + if (!evt->defaultPrevented() && doc) + doc->defaultEventHandler(evt); + + if (_id == EventImpl::LOAD_EVENT && !evt->propagationStopped() && doc) { + // For onload events, send them to the enclosing frame only. + // This is a DOM extension and is independent of bubbling/capturing rules of + // the DOM. You send the event only to the enclosing frame. It does not + // bubble through the parent document. + DOM::ElementImpl* elt = doc->ownerElement(); + if (elt && (elt->getDocument()->domain().isNull() || + elt->getDocument()->domain() == doc->domain())) { + // We also do a security check, since we don't want to allow the enclosing + // iframe to see loads of child documents in other domains. + evt->setCurrentTarget(elt); + + // Capturing first. + elt->handleLocalEvents(evt,true); + + // Bubbling second. + if (!evt->propagationStopped()) + elt->handleLocalEvents(evt,false); + } + } + + doc->deref(); + evt->deref(); +} + +void NodeImpl::dispatchMouseEvent(QMouseEvent *_mouse, int overrideId, int overrideDetail) +{ + bool cancelable = true; + int detail = overrideDetail; // defaults to 0 + EventImpl::EventId evtId = EventImpl::UNKNOWN_EVENT; + if (overrideId) { + evtId = static_cast<EventImpl::EventId>(overrideId); + } + else { + switch (_mouse->type()) { + case QEvent::MouseButtonPress: + evtId = EventImpl::MOUSEDOWN_EVENT; + break; + case QEvent::MouseButtonRelease: + evtId = EventImpl::MOUSEUP_EVENT; + break; + case QEvent::MouseButtonDblClick: + evtId = EventImpl::CLICK_EVENT; + detail = 1; // ### support for multiple double clicks + break; + case QEvent::MouseMove: + evtId = EventImpl::MOUSEMOVE_EVENT; + cancelable = false; + break; + default: + break; + } + } + if (evtId == EventImpl::UNKNOWN_EVENT) + return; // shouldn't happen + + + int exceptioncode = 0; + int pageX = _mouse->x(); + int pageY = _mouse->y(); + int clientX = pageX; + int clientY = pageY; + if ( getDocument()->view() ) + getDocument()->view()->viewportToContents( clientX, clientY, pageX, pageY ); + + int screenX = _mouse->globalX(); + int screenY = _mouse->globalY(); + + int button = -1; + switch (_mouse->button()) { + case Qt::LeftButton: + button = 0; + break; + case Qt::MidButton: + button = 1; + break; + case Qt::RightButton: + button = 2; + break; + default: + break; + } + bool ctrlKey = (_mouse->state() & Qt::ControlButton); + bool altKey = (_mouse->state() & Qt::AltButton); + bool shiftKey = (_mouse->state() & Qt::ShiftButton); + bool metaKey = false; // ### qt support? + + EventImpl* const evt = new MouseEventImpl(evtId,true,cancelable,getDocument()->defaultView(), + detail,screenX,screenY,clientX,clientY,pageX,pageY,ctrlKey,altKey,shiftKey,metaKey, + button,0); + evt->ref(); + dispatchEvent(evt,exceptioncode,true); + evt->deref(); +} + +void NodeImpl::dispatchUIEvent(int _id, int detail) +{ + assert (!( (_id != EventImpl::DOMFOCUSIN_EVENT && + _id != EventImpl::DOMFOCUSOUT_EVENT && + _id != EventImpl::DOMACTIVATE_EVENT))); + + bool cancelable = false; + if (_id == EventImpl::DOMACTIVATE_EVENT) + cancelable = true; + + int exceptioncode = 0; + UIEventImpl* const evt = new UIEventImpl(static_cast<EventImpl::EventId>(_id),true, + cancelable,getDocument()->defaultView(),detail); + evt->ref(); + dispatchEvent(evt,exceptioncode,true); + evt->deref(); +} + +void NodeImpl::dispatchSubtreeModifiedEvent() +{ + childrenChanged(); + getDocument()->incDOMTreeVersion(); + if (!getDocument()->hasListenerType(DocumentImpl::DOMSUBTREEMODIFIED_LISTENER)) + return; + int exceptioncode = 0; + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMSUBTREEMODIFIED_EVENT,true, + false,0,DOMString(),DOMString(),DOMString(),0); + evt->ref(); + dispatchEvent(evt,exceptioncode,true); + evt->deref(); +} + +bool NodeImpl::dispatchKeyEvent(QKeyEvent *key, bool keypress) +{ + int exceptioncode = 0; + //kdDebug(6010) << "DOM::NodeImpl: dispatching keyboard event" << endl; + EventImpl* keyEventImpl; + if (keypress) + keyEventImpl = new TextEventImpl(key, getDocument()->defaultView()); + else + keyEventImpl = new KeyboardEventImpl(key, getDocument()->defaultView()); + keyEventImpl->ref(); + dispatchEvent(keyEventImpl,exceptioncode,true); + bool r = keyEventImpl->defaultHandled() || keyEventImpl->defaultPrevented(); + keyEventImpl->deref(); + return r; +} + +void NodeImpl::handleLocalEvents(EventImpl *evt, bool useCapture) +{ + if (!m_regdListeners.listeners) + return; + + Event ev = evt; + // removeEventListener (e.g. called from a JS event listener) might + // invalidate the item after the current iterator (which "it" is pointing to). + // So we make a copy of the list. + QValueList<RegisteredEventListener> listeners = *m_regdListeners.listeners; + QValueList<RegisteredEventListener>::iterator it; + for (it = listeners.begin(); it != listeners.end(); ++it) { + //Check whether this got removed...KDE4: use Java-style iterators + if (!m_regdListeners.stillContainsListener(*it)) + continue; + + RegisteredEventListener& current = (*it); + if (current.id == evt->id() && current.useCapture == useCapture) + current.listener->handleEvent(ev); + + // ECMA legacy hack + if (current.useCapture == useCapture && evt->id() == EventImpl::CLICK_EVENT) { + MouseEventImpl* me = static_cast<MouseEventImpl*>(evt); + if (me->button() == 0) { + // To find whether to call onclick or ondblclick, we can't + // * use me->detail(), it's 2 when clicking twice w/o moving, even very slowly + // * use me->qEvent(), it's not available when using initMouseEvent/dispatchEvent + // So we currently store a bool in MouseEventImpl. If anyone needs to trigger + // dblclicks from the DOM API, we'll need a timer here (well in the doc). + if ( ( !me->isDoubleClick() && current.id == EventImpl::KHTML_ECMA_CLICK_EVENT) || + ( me->isDoubleClick() && current.id == EventImpl::KHTML_ECMA_DBLCLICK_EVENT) ) + current.listener->handleEvent(ev); + } + } + } +} + +void NodeImpl::defaultEventHandler(EventImpl *) +{ +} + +unsigned long NodeImpl::childNodeCount() +{ + return 0; +} + +NodeImpl *NodeImpl::childNode(unsigned long /*index*/) +{ + return 0; +} + +NodeImpl *NodeImpl::traverseNextNode(NodeImpl *stayWithin) const +{ + if (firstChild() || stayWithin == this) + return firstChild(); + else if (nextSibling()) + return nextSibling(); + else { + const NodeImpl *n = this; + while (n && !n->nextSibling() && (!stayWithin || n->parentNode() != stayWithin)) + n = n->parentNode(); + if (n) + return n->nextSibling(); + } + return 0; +} + +NodeImpl *NodeImpl::traversePreviousNode() const +{ + if (previousSibling()) { + NodeImpl *n = previousSibling(); + while (n->lastChild()) + n = n->lastChild(); + return n; + } + else if (parentNode()) { + return parentNode(); + } + else { + return 0; + } +} + +void NodeImpl::checkSetPrefix(const DOMString &_prefix, int &exceptioncode) +{ + // Perform error checking as required by spec for setting Node.prefix. Used by + // ElementImpl::setPrefix() and AttrImpl::setPrefix() + + // INVALID_CHARACTER_ERR: Raised if the specified prefix contains an illegal character. + if (!Element::khtmlValidPrefix(_prefix)) { + exceptioncode = DOMException::INVALID_CHARACTER_ERR; + return; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + // NAMESPACE_ERR: - Raised if the specified prefix is malformed + // - if the namespaceURI of this node is null, + // - if the specified prefix is "xml" and the namespaceURI of this node is different from + // "http://www.w3.org/XML/1998/namespace", + // - if this node is an attribute and the specified prefix is "xmlns" and + // the namespaceURI of this node is different from "http://www.w3.org/2000/xmlns/", + // - or if this node is an attribute and the qualifiedName of this node is "xmlns" [Namespaces]. + if (Element::khtmlMalformedPrefix(_prefix) || (namespacePart(id()) == defaultNamespace && id() > ID_LAST_TAG) || + (_prefix == "xml" && namespaceURI() != "http://www.w3.org/XML/1998/namespace")) { + exceptioncode = DOMException::NAMESPACE_ERR; + return; + } +} + +void NodeImpl::checkAddChild(NodeImpl *newChild, int &exceptioncode) +{ + // Perform error checking as required by spec for adding a new child. Used by + // appendChild(), replaceChild() and insertBefore() + + // Not mentioned in spec: throw NOT_FOUND_ERR if newChild is null + if (!newChild) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + // WRONG_DOCUMENT_ERR: Raised if newChild was created from a different document than the one that + // created this node. + // We assume that if newChild is a DocumentFragment, all children are created from the same document + // as the fragment itself (otherwise they could not have been added as children) + if (newChild->getDocument() != getDocument()) { + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return; + } + + // HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not allow children of the type of the + // newChild node, or if the node to append is one of this node's ancestors. + + // check for ancestor/same node + if (isAncestor(newChild)) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + + // check node allowed + if (newChild->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) { + // newChild is a DocumentFragment... check all its children instead of newChild itself + NodeImpl *child; + for (child = newChild->firstChild(); child; child = child->nextSibling()) { + if (!childTypeAllowed(child->nodeType())) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } + } + else { + // newChild is not a DocumentFragment... check if it's allowed directly + if(!childTypeAllowed(newChild->nodeType())) { + exceptioncode = DOMException::HIERARCHY_REQUEST_ERR; + return; + } + } +} + +bool NodeImpl::isAncestor( NodeImpl *other ) +{ + // Return true if other is the same as this node or an ancestor of it, otherwise false + NodeImpl *n; + for (n = this; n; n = n->parentNode()) { + if (n == other) + return true; + } + return false; +} + +bool NodeImpl::childAllowed( NodeImpl *newChild ) +{ + return childTypeAllowed(newChild->nodeType()); +} + +NodeImpl::StyleChange NodeImpl::diff( khtml::RenderStyle *s1, khtml::RenderStyle *s2 ) +{ + // This method won't work when a style contains noninherited properties with "inherit" value. + StyleChange ch = NoInherit; + + EDisplay display1 = s1 ? s1->display() : NONE; + EDisplay display2 = s2 ? s2->display() : NONE; + EPosition position1 = s1 ? s1->position() : STATIC; + EPosition position2 = s2 ? s2->position() : STATIC; + + if (display1 != display2 || position1 != position2) + ch = Detach; + else if ( !s1 || !s2 ) + ch = Inherit; + else if ( *s1 == *s2 ) + ch = NoChange; + else if (s1->useNormalContent() != s2->useNormalContent()) + ch = Detach; // when we add generated content all children must be detached + else if ( s1->inheritedNotEqual( s2 ) ) + ch = Inherit; + + // Because the first-letter implementation is so f..ked up, the easiest way + // to update first-letter is to remove the entire node and readd it. + if (ch < Detach && pseudoDiff(s1, s2, khtml::RenderStyle::FIRST_LETTER)) + ch = Detach; + // If the other pseudoStyles have changed, we want to return NoInherit + if (ch == NoChange && pseudoDiff(s1, s2, khtml::RenderStyle::BEFORE)) + ch = NoInherit; + if (ch == NoChange && pseudoDiff(s1, s2, khtml::RenderStyle::AFTER)) + ch = NoInherit; + if (ch == NoChange && pseudoDiff(s1, s2, khtml::RenderStyle::MARKER)) + ch = NoInherit; + if (ch == NoChange && pseudoDiff(s1, s2, khtml::RenderStyle::SELECTION)) + ch = NoInherit; + if (ch == NoChange && pseudoDiff(s1, s2, khtml::RenderStyle::FIRST_LINE)) + ch = NoInherit; + + return ch; +} + +bool NodeImpl::pseudoDiff( khtml::RenderStyle *s1, khtml::RenderStyle *s2, unsigned int pid) +{ + khtml::RenderStyle *ps1 = s1 ? s1->getPseudoStyle((khtml::RenderStyle::PseudoId)pid) : 0; + khtml::RenderStyle *ps2 = s2 ? s2->getPseudoStyle((khtml::RenderStyle::PseudoId)pid) : 0; + + if (ps1 == ps2) + return false; + else + if (ps1 && ps2) { + if (*ps1 == *ps2) + return false; + else + return true; + } + else + return true; +} + +void NodeImpl::close() +{ + if (m_render) m_render->close(); + m_closed = true; +} + +void NodeImpl::attach() +{ + assert(!attached()); + assert(!m_render || (m_render->style() && m_render->parent())); + if (m_render) // set states to match node + { + if (closed()) m_render->close(); + if (hovered()) m_render->setMouseInside(); + } + getDocument()->incDOMTreeVersion(); + m_attached = true; +} + +void NodeImpl::detach() +{ +// assert(m_attached); + + if ( m_render ) + m_render->detach(); + + m_render = 0; + getDocument()->incDOMTreeVersion(); + m_attached = false; +} + +bool NodeImpl::maintainsState() +{ + return false; +} + +QString NodeImpl::state() +{ + return QString::null; +} + +void NodeImpl::restoreState(const QString &/*state*/) +{ +} + +void NodeImpl::insertedIntoDocument() +{ + setInDocument(true); +} + +void NodeImpl::removedFromDocument() +{ + setInDocument(false); +} + +void NodeImpl::childrenChanged() +{ + if (parentNode()) + parentNode()->childrenChanged(); +} + +bool NodeImpl::isReadOnly() +{ + // Entity & Entity Reference nodes and their descendants are read-only + NodeImpl *n = this; + while (n) { + if (n->nodeType() == Node::ENTITY_NODE || + n->nodeType() == Node::ENTITY_REFERENCE_NODE) + return true; + n = n->parentNode(); + } + return false; +} + +RenderObject * NodeImpl::previousRenderer() +{ + for (NodeImpl *n = previousSibling(); n; n = n->previousSibling()) { + if (n->renderer()) + return n->renderer(); + } + return 0; +} + +RenderObject * NodeImpl::nextRenderer() +{ + for (NodeImpl *n = nextSibling(); n; n = n->nextSibling()) { + if (n->renderer()) + return n->renderer(); + } + return 0; +} + +void NodeImpl::createRendererIfNeeded() +{ +#ifdef APPLE_CHANGES + if (!getDocument()->shouldCreateRenderers()) + return; +#endif + + assert(!m_render); + + NodeImpl *parent = parentNode(); + assert(parent); + + RenderObject *parentRenderer = parent->renderer(); + if (parentRenderer && parentRenderer->childAllowed()) { + RenderStyle *style = styleForRenderer(parentRenderer); + style->ref(); + if (rendererIsNeeded(style)) { + m_render = createRenderer(getDocument()->renderArena(), style); + m_render->setStyle(style); + parentRenderer->addChild(m_render, nextRenderer()); + } + style->deref(); + } +} + +RenderStyle *NodeImpl::styleForRenderer(RenderObject *parent) +{ + return parent->style(); +} + +bool NodeImpl::rendererIsNeeded(RenderStyle *style) +{ + return (getDocument()->documentElement() == this) || (style->display() != NONE); +} + +RenderObject *NodeImpl::createRenderer(RenderArena* /*arena*/, RenderStyle* /*style*/) +{ + assert(false); + return 0; +} + +bool NodeImpl::contentEditable() const +{ + RenderObject *r = renderer(); + if (!r || !r->style()) return false; + return r->style()->userInput() == UI_ENABLED; +} + +long NodeImpl::minOffset() const +{ + // Arrgh! You'd think *every* offset starts at zero, but loo, + // therefore we need this method + return renderer() ? renderer()->minOffset() : 0; +} + +long NodeImpl::maxOffset() const +{ + return const_cast<NodeImpl *>(this)->childNodeCount(); +// return renderer() ? renderer()->maxOffset() : 1; +} + +DOMStringImpl* NodeImpl::textContent() const +{ + QString out; + for (NodeImpl *child = firstChild(); child != 0; child = child->nextSibling()) { + short type = child->nodeType(); + if (type != Node::COMMENT_NODE && type != Node::PROCESSING_INSTRUCTION_NODE) { + DOMStringImpl* kidText = child->textContent(); + if (kidText) + out += QConstString(kidText->s, kidText->l).string(); + delete kidText; + } + } + return new DOMStringImpl(out.unicode(), out.length()); +} + +//------------------------------------------------------------------------- + +NodeBaseImpl::~NodeBaseImpl() +{ + //kdDebug( 6020 ) << "NodeBaseImpl destructor" << endl; + // we have to tell all children, that the parent has died... + NodeImpl *n; + NodeImpl *next; + + for( n = _first; n != 0; n = next ) { + next = n->nextSibling(); + n->setPreviousSibling(0); + n->setNextSibling(0); + n->setParent(0); + if ( !n->refCount() ) + delete n; + } +} + + +NodeImpl *NodeBaseImpl::firstChild() const +{ + return _first; +} + +NodeImpl *NodeBaseImpl::lastChild() const +{ + return _last; +} + +NodeImpl *NodeBaseImpl::insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ) +{ + exceptioncode = 0; + + // insertBefore(...,null) is equivalent to appendChild() + if(!refChild) + return appendChild(newChild, exceptioncode); + + // Make sure adding the new child is ok + checkAddChild(newChild, exceptioncode); + if (exceptioncode) + return 0; + + // NOT_FOUND_ERR: Raised if refChild is not a child of this node + if (refChild->parentNode() != this) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return 0; + } + + bool isFragment = newChild->nodeType() == Node::DOCUMENT_FRAGMENT_NODE; + + // If newChild is a DocumentFragment with no children.... there's nothing to do. + // Just return the document fragment + if (isFragment && !newChild->firstChild()) + return newChild; + + // Now actually add the child(ren) + NodeImpl *nextChild; + NodeImpl *child = isFragment ? newChild->firstChild() : newChild; + + NodeImpl *prev = refChild->previousSibling(); + if ( prev == newChild || refChild == newChild ) // nothing to do + return newChild; + + while (child) { + nextChild = isFragment ? child->nextSibling() : 0; + + // If child is already present in the tree, first remove it + NodeImpl *newParent = child->parentNode(); + + //...guard it in case we need to move it.. + SharedPtr<NodeImpl> guard(child); + + if(newParent) + newParent->removeChild( child, exceptioncode ); + if ( exceptioncode ) + return 0; + + // Add child in the correct position + if (prev) + prev->setNextSibling(child); + else + _first = child; + refChild->setPreviousSibling(child); + child->setParent(this); + child->setPreviousSibling(prev); + child->setNextSibling(refChild); + + // Add child to the rendering tree + // ### should we detach() it first if it's already attached? + if (attached() && !child->attached()) + child->attach(); + + // Dispatch the mutation events + dispatchChildInsertedEvents(child,exceptioncode); + + prev = child; + child = nextChild; + } + + structureChanged(); + + // ### set style in case it's attached + dispatchSubtreeModifiedEvent(); + return newChild; +} + +void NodeBaseImpl::replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ) +{ + exceptioncode = 0; + + if ( oldChild == newChild ) // nothing to do + return; + + // Make sure adding the new child is ok + checkAddChild(newChild, exceptioncode); + if (exceptioncode) + return; + + // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. + if (!oldChild || oldChild->parentNode() != this) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + bool isFragment = newChild->nodeType() == Node::DOCUMENT_FRAGMENT_NODE; + NodeImpl *nextChild; + NodeImpl *child = isFragment ? newChild->firstChild() : newChild; + + + // Remove the old child + NodeImpl *prev = oldChild->previousSibling(); + NodeImpl *next = oldChild->nextSibling(); + + removeChild(oldChild, exceptioncode); + if (exceptioncode) + return; + + // Add the new child(ren) + while (child) { + nextChild = isFragment ? child->nextSibling() : 0; + + // If child is already present in the tree, first remove it + NodeImpl *newParent = child->parentNode(); + if ( child == next ) + next = child->nextSibling(); + if ( child == prev ) + prev = child->previousSibling(); + //...guard it in case we need to move it.. + SharedPtr<NodeImpl> guard(child); + if(newParent) + newParent->removeChild( child, exceptioncode ); + if (exceptioncode) + return; + + // Add child in the correct position + if (prev) prev->setNextSibling(child); + if (next) next->setPreviousSibling(child); + if(!prev) _first = child; + if(!next) _last = child; + child->setParent(this); + child->setPreviousSibling(prev); + child->setNextSibling(next); + + // Add child to the rendering tree + // ### should we detach() it first if it's already attached? + if (attached() && !child->attached()) + child->attach(); + + // Dispatch the mutation events + dispatchChildInsertedEvents(child,exceptioncode); + + prev = child; + child = nextChild; + } + + structureChanged(); + + // ### set style in case it's attached + dispatchSubtreeModifiedEvent(); + return; +} + +void NodeBaseImpl::removeChild ( NodeImpl *oldChild, int &exceptioncode ) +{ + exceptioncode = 0; + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. + if (!oldChild || oldChild->parentNode() != this) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return; + } + + dispatchChildRemovalEvents(oldChild,exceptioncode); + if (exceptioncode) + return; + + SharedPtr<NodeImpl> memManage(oldChild); //Make sure to free if needed + + // Remove from rendering tree + if (oldChild->attached()) + oldChild->detach(); + + // Remove the child + NodeImpl *prev, *next; + prev = oldChild->previousSibling(); + next = oldChild->nextSibling(); + + if(next) next->setPreviousSibling(prev); + if(prev) prev->setNextSibling(next); + if(_first == oldChild) _first = next; + if(_last == oldChild) _last = prev; + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + structureChanged(); + + // Dispatch post-removal mutation events + dispatchSubtreeModifiedEvent(); + + NodeImpl *p = this; + while (p->parentNode()) + p = p->parentNode(); + if (p->nodeType() == Node::DOCUMENT_NODE) { + for (NodeImpl *c = oldChild; c; c = c->traverseNextNode(oldChild)) + c->removedFromDocument(); + } +} + +void NodeBaseImpl::removeChildren() +{ + bool inDoc = inDocument(); + NodeImpl *n, *next; + for( n = _first, _first = 0; n; n = next ) + { + next = n->nextSibling(); + if (n->attached()) + n->detach(); + n->setPreviousSibling(0); + n->setNextSibling(0); + n->setParent(0); + + if ( inDoc ) + for ( NodeImpl* c = n; c; c = c->traverseNextNode( n ) ) + c->removedFromDocument(); + + if( !n->refCount() ) + delete n; + } + _last = 0; +} + + +NodeImpl *NodeBaseImpl::appendChild ( NodeImpl *newChild, int &exceptioncode ) +{ + exceptioncode = 0; + + // Make sure adding the new child is ok + checkAddChild(newChild, exceptioncode); + if (exceptioncode) + return 0; + + if ( newChild == _last ) // nothing to do + return newChild; + + bool isFragment = newChild->nodeType() == Node::DOCUMENT_FRAGMENT_NODE; + + // If newChild is a DocumentFragment with no children.... there's nothing to do. + // Just return the document fragment + if (isFragment && !newChild->firstChild()) + return newChild; + + // Now actually add the child(ren) + NodeImpl *nextChild; + NodeImpl *child = isFragment ? newChild->firstChild() : newChild; + + while (child) { + nextChild = isFragment ? child->nextSibling() : 0; + + // If child is already present in the tree, first remove it + NodeImpl *oldParent = child->parentNode(); + SharedPtr<NodeImpl> guard(child); //Guard in case we move it + if(oldParent) { + oldParent->removeChild( child, exceptioncode ); + if (exceptioncode) + return 0; + } + + // Append child to the end of the list + child->setParent(this); + + if(_last) + { + child->setPreviousSibling(_last); + _last->setNextSibling(child); + _last = child; + } + else + { + _first = _last = child; + } + + // Add child to the rendering tree + // ### should we detach() it first if it's already attached? + if (attached() && !child->attached()) + child->attach(); + + // Dispatch the mutation events + dispatchChildInsertedEvents(child,exceptioncode); + + child = nextChild; + } + + backwardsStructureChanged(); + + // ### set style in case it's attached + dispatchSubtreeModifiedEvent(); + return newChild; +} + +bool NodeBaseImpl::hasChildNodes ( ) const +{ + return _first != 0; +} + +// not part of the DOM +void NodeBaseImpl::setFirstChild(NodeImpl *child) +{ + _first = child; +} + +void NodeBaseImpl::setLastChild(NodeImpl *child) +{ + _last = child; +} + +// check for same source document: +bool NodeBaseImpl::checkSameDocument( NodeImpl *newChild, int &exceptioncode ) +{ + exceptioncode = 0; + DocumentImpl *ownerDocThis = getDocument(); + DocumentImpl *ownerDocNew = newChild->getDocument(); + if(ownerDocThis != ownerDocNew) { + kdDebug(6010)<< "not same document, newChild = " << newChild << "document = " << getDocument() << endl; + exceptioncode = DOMException::WRONG_DOCUMENT_ERR; + return true; + } + return false; +} + +// check for being child: +bool NodeBaseImpl::checkIsChild( NodeImpl *oldChild, int &exceptioncode ) +{ + if(!oldChild || oldChild->parentNode() != this) { + exceptioncode = DOMException::NOT_FOUND_ERR; + return true; + } + return false; +} + +NodeImpl *NodeBaseImpl::addChild(NodeImpl *newChild) +{ + // do not add applyChanges here! This function is only used during parsing + + // short check for consistency with DTD + if(getDocument()->isHTMLDocument() && !childAllowed(newChild)) + { + //kdDebug( 6020 ) << "AddChild failed! id=" << id() << ", child->id=" << newChild->id() << endl; + return 0; + } + + // just add it... + newChild->setParent(this); + + if(_last) + { + newChild->setPreviousSibling(_last); + _last->setNextSibling(newChild); + _last = newChild; + } + else + { + _first = _last = newChild; + } + + if (inDocument()) + newChild->insertedIntoDocument(); + childrenChanged(); + + if(newChild->nodeType() == Node::ELEMENT_NODE) + return newChild; + return this; +} + +void NodeBaseImpl::attach() +{ + NodeImpl *child = _first; + while(child != 0) + { + child->attach(); + child = child->nextSibling(); + } + NodeImpl::attach(); +} + +void NodeBaseImpl::detach() +{ + NodeImpl *child = _first; + while(child != 0) + { + NodeImpl* prev = child; + child = child->nextSibling(); + prev->detach(); + } + NodeImpl::detach(); +} + +void NodeBaseImpl::cloneChildNodes(NodeImpl *clone) +{ + int exceptioncode = 0; + NodeImpl *n; + for(n = firstChild(); n && !exceptioncode; n = n->nextSibling()) + { + clone->appendChild(n->cloneNode(true),exceptioncode); + } +} + +// I don't like this way of implementing the method, but I didn't find any +// other way. Lars +bool NodeBaseImpl::getUpperLeftCorner(int &xPos, int &yPos) const +{ + if (!m_render) + return false; + RenderObject *o = m_render; + xPos = yPos = 0; + if ( !o->isInline() || o->isReplaced() ) { + o->absolutePosition( xPos, yPos ); + return true; + } + + // find the next text/image child, to get a position + while(o) { + if(o->firstChild()) + o = o->firstChild(); + else if(o->nextSibling()) + o = o->nextSibling(); + else { + RenderObject *next = 0; + while(!next) { + o = o->parent(); + if(!o) return false; + next = o->nextSibling(); + } + o = next; + } + if((o->isText() && !o->isBR()) || o->isReplaced()) { + o->container()->absolutePosition( xPos, yPos ); + if (o->isText()) { + xPos += o->inlineXPos(); + yPos += o->inlineYPos(); + } else { + xPos += o->xPos(); + yPos += o->yPos(); + } + return true; + } + } + return true; +} + +bool NodeBaseImpl::getLowerRightCorner(int &xPos, int &yPos) const +{ + if (!m_render) + return false; + + RenderObject *o = m_render; + xPos = yPos = 0; + if (!o->isInline() || o->isReplaced()) + { + o->absolutePosition( xPos, yPos ); + xPos += o->width(); + yPos += o->height() + o->borderTopExtra() + o->borderBottomExtra(); + return true; + } + // find the last text/image child, to get a position + while(o) { + if(o->lastChild()) + o = o->lastChild(); + else if(o->previousSibling()) + o = o->previousSibling(); + else { + RenderObject *prev = 0; + while(!prev) { + o = o->parent(); + if(!o) return false; + prev = o->previousSibling(); + } + o = prev; + } + if((o->isText() && !o->isBR()) || o->isReplaced()) { + o->container()->absolutePosition(xPos, yPos); + if (o->isText()) { + xPos += o->inlineXPos() + o->width(); + yPos += o->inlineYPos() + o->height(); + } else { + xPos += o->xPos() + o->width(); + yPos += o->yPos() + o->height(); + } + return true; + } + } + return true; +} + +void NodeBaseImpl::setFocus(bool received) +{ + if (m_focused == received) return; + + NodeImpl::setFocus(received); + + // note that we need to recalc the style + setChanged(); // *:focus is a default style, so we just assume personal dependency + if (isElementNode()) { + getDocument()->dynamicDomRestyler().restyleDepedent(static_cast<ElementImpl*>(this), OtherStateDependency); + } +} + +void NodeBaseImpl::setActive(bool down) +{ + if (down == active()) return; + + NodeImpl::setActive(down); + + // note that we need to recalc the style + if (isElementNode()) + getDocument()->dynamicDomRestyler().restyleDepedent(static_cast<ElementImpl*>(this), ActiveDependency); +} + +void NodeBaseImpl::setHovered(bool hover) +{ + if (hover == hovered()) return; + + NodeImpl::setHovered(hover); + + // note that we need to recalc the style + if (isElementNode()) + getDocument()->dynamicDomRestyler().restyleDepedent(static_cast<ElementImpl*>(this), HoverDependency); +} + +unsigned long NodeBaseImpl::childNodeCount() +{ + unsigned long count = 0; + NodeImpl *n; + for (n = firstChild(); n; n = n->nextSibling()) + count++; + return count; +} + +NodeImpl *NodeBaseImpl::childNode(unsigned long index) +{ + unsigned long i; + NodeImpl *n = firstChild(); + for (i = 0; n && i < index; i++) + n = n->nextSibling(); + return n; +} + +void NodeBaseImpl::dispatchChildInsertedEvents( NodeImpl *child, int &exceptioncode ) +{ + if (getDocument()->hasListenerType(DocumentImpl::DOMNODEINSERTED_LISTENER)) { + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMNODEINSERTED_EVENT,true,false,this,DOMString(),DOMString(),DOMString(),0); + evt->ref(); + child->dispatchEvent(evt,exceptioncode,true); + evt->deref(); + if (exceptioncode) + return; + } + + // dispatch the DOMNodeInsertedIntoDocument event to all descendants + bool hasInsertedListeners = getDocument()->hasListenerType(DocumentImpl::DOMNODEINSERTEDINTODOCUMENT_LISTENER); + NodeImpl *p = this; + while (p->parentNode()) + p = p->parentNode(); + if (p->nodeType() == Node::DOCUMENT_NODE) { + for (NodeImpl *c = child; c; c = c->traverseNextNode(child)) { + c->insertedIntoDocument(); + + if (hasInsertedListeners) { + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMNODEINSERTEDINTODOCUMENT_EVENT,false,false,0,DOMString(),DOMString(),DOMString(),0); + evt->ref(); + c->dispatchEvent(evt,exceptioncode,true); + evt->deref(); + if (exceptioncode) + return; + } + } + } +} + +void NodeBaseImpl::dispatchChildRemovalEvents( NodeImpl *child, int &exceptioncode ) +{ + // Dispatch pre-removal mutation events + getDocument()->notifyBeforeNodeRemoval(child); // ### use events instead + if (getDocument()->hasListenerType(DocumentImpl::DOMNODEREMOVED_LISTENER)) { + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMNODEREMOVED_EVENT,true,false,this,DOMString(),DOMString(),DOMString(),0); + evt->ref(); + child->dispatchEvent(evt,exceptioncode,true); + evt->deref(); + if (exceptioncode) + return; + } + + bool hasRemovalListeners = getDocument()->hasListenerType(DocumentImpl::DOMNODEREMOVEDFROMDOCUMENT_LISTENER); + + // dispatch the DOMNodeRemovedFromDocument event to all descendants + NodeImpl *p = this; + while (p->parentNode()) + p = p->parentNode(); + if (p->nodeType() == Node::DOCUMENT_NODE) { + for (NodeImpl *c = child; c; c = c->traverseNextNode(child)) { + if (hasRemovalListeners) { + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMNODEREMOVEDFROMDOCUMENT_EVENT,false,false,0,DOMString(),DOMString(),DOMString(),0); + evt->ref(); + c->dispatchEvent(evt,exceptioncode,true); + evt->deref(); + if (exceptioncode) + return; + } + } + } +} + +void NodeBaseImpl::setTextContent( const DOMString &text, int& exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + removeChildren(); + + if ( !text.isEmpty() && !text.isNull() ) { + TextImpl *t = new TextImpl( docPtr(), text.implementation() ); + appendChild( t, exceptioncode ); + } +} + +// --------------------------------------------------------------------------- +NodeImpl *NodeListImpl::item( unsigned long index ) const +{ + unsigned long requestIndex = index; + + m_cache->updateNodeListInfo(m_refNode->getDocument()); + + NodeImpl* n; + bool usedCache = false; + if (m_cache->current.node) { + //Compute distance from the requested index to the cache node + long cacheDist = QABS(long(index) - long(m_cache->position)); + + if (cacheDist < (long)index) { //Closer to the cached position + usedCache = true; + if (index >= m_cache->position) { //Go ahead + unsigned long relIndex = index - m_cache->position; + n = recursiveItem(m_refNode, m_cache->current.node, relIndex); + } else { //Go backwards + unsigned long relIndex = m_cache->position - index; + n = recursiveItemBack(m_refNode, m_cache->current.node, relIndex); + } + } + } + + if (!usedCache) + n = recursiveItem(m_refNode, m_refNode->firstChild(), index); + + //We always update the cache state, to make starting iteration + //where it was left off easy. + m_cache->current.node = n; + m_cache->position = requestIndex; + return n; +} + +unsigned long NodeListImpl::length() const +{ + m_cache->updateNodeListInfo(m_refNode->getDocument()); + if (!m_cache->hasLength) { + m_cache->length = calcLength( m_refNode ); + m_cache->hasLength = true; + } + return m_cache->length; +} + +unsigned long NodeListImpl::calcLength(NodeImpl *start) const +{ + unsigned long len = 0; + for(NodeImpl *n = start->firstChild(); n != 0; n = n->nextSibling()) { + bool recurse = true; + if (nodeMatches(n, recurse)) + len++; + if (recurse) + len+= NodeListImpl::calcLength(n); + } + + return len; +} + +NodeListImpl::NodeListImpl( NodeImpl *n, int type, CacheFactory* factory ) +{ + m_refNode = n; + m_refNode->ref(); + + m_cache = m_refNode->getDocument()->acquireCachedNodeListInfo( + factory ? factory : Cache::make, + n, type ); +} + +NodeListImpl::~NodeListImpl() +{ + m_refNode->getDocument()->releaseCachedNodeListInfo(m_cache); + m_refNode->deref(); +} + + +/** + Next item in the pre-order walk of tree from node, but not going outside + absStart +*/ +static NodeImpl* helperNext(NodeImpl* node, NodeImpl* absStart) +{ + //Walk up until we wind a sibling to go to. + while (!node->nextSibling() && node != absStart) + node = node->parentNode(); + + if (node != absStart) + return node->nextSibling(); + else + return 0; +} + +NodeImpl *NodeListImpl::recursiveItem ( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const +{ + for(NodeImpl *n = start; n != 0; n = helperNext(n, absStart)) { + bool recurse = true; + if (nodeMatches(n, recurse)) + if (!offset--) + return n; + + NodeImpl *depthSearch = recurse ? recursiveItem(n, n->firstChild(), offset) : 0; + if (depthSearch) + return depthSearch; + } + + return 0; // no matching node in this subtree +} + + +NodeImpl *NodeListImpl::recursiveItemBack ( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const +{ + //### it might be cleaner/faster to split nodeMatches and recursion + //filtering. + bool dummy = true; + NodeImpl* n = start; + + do { + bool recurse = true; + + //Check whether the current node matches. + if (nodeMatches(n, dummy)) + if (!offset--) + return n; + + if (n->previousSibling()) { + //Move to the last node of this whole subtree that we should recurse into + n = n->previousSibling(); + recurse = true; + + while (n->lastChild()) { + (void)nodeMatches(n, recurse); + if (!recurse) + break; //Don't go there + n = n->lastChild(); + } + } else { + //We're done with this whole subtree, so move up + n = n->parentNode(); + } + } + while (n && n != absStart); + + return 0; +} + + +NodeListImpl::Cache::~Cache() +{} + +void NodeListImpl::Cache::clear(DocumentImpl* doc) +{ + hasLength = false; + current.node = 0; + version = doc->domTreeVersion(); +} + +void NodeListImpl::Cache::updateNodeListInfo(DocumentImpl* doc) +{ + //If version doesn't match, clear + if (doc->domTreeVersion() != version) + clear(doc); +} + +ChildNodeListImpl::ChildNodeListImpl( NodeImpl *n ): NodeListImpl(n, CHILD_NODES) +{} + +bool ChildNodeListImpl::nodeMatches( NodeImpl* /*testNode*/, bool& doRecurse ) const +{ + doRecurse = false; + return true; +} + +TagNodeListImpl::TagNodeListImpl( NodeImpl *n, NodeImpl::Id id ) + : NodeListImpl(n, UNCACHEABLE), + m_id(id), + m_namespaceAware(false) +{ + // An id of 0 here means "*" (match all nodes) + m_matchAllNames = (id == 0); + m_matchAllNamespaces = false; +} + +TagNodeListImpl::TagNodeListImpl( NodeImpl *n, const DOMString &namespaceURI, const DOMString &localName ) + : NodeListImpl(n, UNCACHEABLE), + m_id(0), + m_namespaceURI(namespaceURI), + m_localName(localName), + m_namespaceAware(true) +{ + m_matchAllNames = (localName == "*"); + m_matchAllNamespaces = (namespaceURI == "*"); +} + + +bool TagNodeListImpl::nodeMatches( NodeImpl *testNode, bool& /*doRecurse*/ ) const +{ + if ( testNode->nodeType() != Node::ELEMENT_NODE ) return false; + if (m_namespaceAware) + return (m_matchAllNamespaces || testNode->namespaceURI() == m_namespaceURI) && + (m_matchAllNames || testNode->localName() == m_localName); + else { + NodeImpl::Id testId = testNode->id(); + //we have to strip the namespaces if we compare in a namespace unaware fashion + if ( !m_namespaceAware ) testId = localNamePart(testId); + return (m_id == 0 || m_id == testId); + } +} + +NameNodeListImpl::NameNodeListImpl(NodeImpl *n, const DOMString &t ) + : NodeListImpl(n, UNCACHEABLE), + nodeName(t) +{} + +bool NameNodeListImpl::nodeMatches( NodeImpl *testNode, bool& /*doRecurse*/ ) const +{ + if ( testNode->nodeType() != Node::ELEMENT_NODE ) return false; + return static_cast<ElementImpl *>(testNode)->getAttribute(ATTR_NAME) == nodeName; +} + +// --------------------------------------------------------------------------- + +NamedNodeMapImpl::NamedNodeMapImpl() +{ +} + +NamedNodeMapImpl::~NamedNodeMapImpl() +{ +} + +// ---------------------------------------------------------------------------- + +GenericRONamedNodeMapImpl::GenericRONamedNodeMapImpl(DocumentImpl* doc) + : NamedNodeMapImpl() +{ + m_doc = doc; + m_contents = new QPtrList<NodeImpl>; +} + +GenericRONamedNodeMapImpl::~GenericRONamedNodeMapImpl() +{ + while (!m_contents->isEmpty()) + m_contents->take(0)->deref(); + + delete m_contents; +} + +NodeImpl *GenericRONamedNodeMapImpl::getNamedItem ( NodeImpl::Id id, bool /*nsAware*/, DOMStringImpl* /*qName*/ ) const +{ + // ## do we need namespace support in this class? + QPtrListIterator<NodeImpl> it(*m_contents); + for (; it.current(); ++it) + if (it.current()->id() == id) + return it.current(); + return 0; +} + +Node GenericRONamedNodeMapImpl::setNamedItem ( NodeImpl* /*arg*/, bool /*nsAware*/, DOMStringImpl* /*qName*/, int &exceptioncode ) +{ + // can't modify this list through standard DOM functions + // NO_MODIFICATION_ALLOWED_ERR: Raised if this map is readonly + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return 0; +} + +Node GenericRONamedNodeMapImpl::removeNamedItem ( NodeImpl::Id /*id*/, bool /*nsAware*/, DOMStringImpl* /*qName*/, int &exceptioncode ) +{ + // can't modify this list through standard DOM functions + // NO_MODIFICATION_ALLOWED_ERR: Raised if this map is readonly + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return 0; +} + +NodeImpl *GenericRONamedNodeMapImpl::item ( unsigned long index ) const +{ + if (index >= m_contents->count()) + return 0; + + return m_contents->at(index); +} + +unsigned long GenericRONamedNodeMapImpl::length( ) const +{ + return m_contents->count(); +} + +void GenericRONamedNodeMapImpl::addNode(NodeImpl *n) +{ + // The spec says that in the case of duplicates we only keep the first one + if (getNamedItem(n->id(), false, 0)) + return; + + n->ref(); + m_contents->append(n); +} + +NodeImpl::Id GenericRONamedNodeMapImpl::mapId(DOMStringImpl* namespaceURI, + DOMStringImpl* localName, bool readonly) +{ + if (!m_doc) + return 0; + + return m_doc->getId(NodeImpl::ElementId, + namespaceURI, 0, localName, readonly, + false /*don't lookupHTML*/); +} + +// ----------------------------------------------------------------------------- + +void RegisteredListenerList::addEventListener(int id, EventListener *listener, const bool useCapture) +{ + RegisteredEventListener rl(static_cast<EventImpl::EventId>(id),listener,useCapture); + if (!listeners) + listeners = new QValueList<RegisteredEventListener>; + + // if this id/listener/useCapture combination is already registered, do nothing. + // the DOM2 spec says that "duplicate instances are discarded", and this keeps + // the listener order intact. + QValueList<RegisteredEventListener>::iterator it; + for (it = listeners->begin(); it != listeners->end(); ++it) + if (*it == rl) + return; + + listeners->append(rl); +} + +void RegisteredListenerList::removeEventListener(int id, EventListener *listener, bool useCapture) +{ + if (!listeners) // nothing to remove + return; + + RegisteredEventListener rl(static_cast<EventImpl::EventId>(id),listener,useCapture); + + QValueList<RegisteredEventListener>::iterator it; + for (it = listeners->begin(); it != listeners->end(); ++it) + if (*it == rl) { + listeners->remove(it); + return; + } +} + +bool RegisteredListenerList::isHTMLEventListener(EventListener* listener) +{ + return (listener->eventListenerType() == "_khtml_HTMLEventListener"); +} + +void RegisteredListenerList::setHTMLEventListener(int id, EventListener *listener) +{ + if (!listeners) + listeners = new QValueList<RegisteredEventListener>; + + QValueList<RegisteredEventListener>::iterator it; + if (!listener) { + for (it = listeners->begin(); it != listeners->end(); ++it) { + if ((*it).id == id && isHTMLEventListener((*it).listener)) { + listeners->remove(it); + break; + } + } + return; + } + + // if this event already has a registered handler, insert the new one in + // place of the old one, to preserve the order. + RegisteredEventListener rl(static_cast<EventImpl::EventId>(id),listener,false); + + int i; + for (i = 0, it = listeners->begin(); it != listeners->end(); ++it, ++i) + if ((*it).id == id && isHTMLEventListener((*it).listener)) { + listeners->insert(it, rl); + listeners->remove(it); + return; + } + + listeners->append(rl); +} + +EventListener *RegisteredListenerList::getHTMLEventListener(int id) +{ + if (!listeners) + return 0; + + QValueList<RegisteredEventListener>::iterator it; + for (it = listeners->begin(); it != listeners->end(); ++it) + if ((*it).id == id && isHTMLEventListener((*it).listener)) { + return (*it).listener; + } + return 0; +} + +bool RegisteredListenerList::hasEventListener(int id) +{ + if (!listeners) + return false; + + QValueList<RegisteredEventListener>::iterator it; + for (it = listeners->begin(); it != listeners->end(); ++it) + if ((*it).id == id) + return true; + + return false; +} + +void RegisteredListenerList::clear() +{ + delete listeners; + listeners = 0; +} + +bool RegisteredListenerList::stillContainsListener(const RegisteredEventListener& listener) +{ + if (!listeners) + return false; + return listeners->find(listener) != listeners->end(); +} + +RegisteredListenerList::~RegisteredListenerList() { + delete listeners; listeners = 0; +} diff --git a/khtml/xml/dom_nodeimpl.h b/khtml/xml/dom_nodeimpl.h new file mode 100644 index 000000000..1ff150f6a --- /dev/null +++ b/khtml/xml/dom_nodeimpl.h @@ -0,0 +1,736 @@ +/* + * 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 Dirk Mueller (mueller@kde.org) + * (C) 2003 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. + * + */ +#ifndef _DOM_NodeImpl_h_ +#define _DOM_NodeImpl_h_ + +#include "dom/dom_misc.h" +#include "dom/dom_string.h" +#include "dom/dom_node.h" +#include "misc/helper.h" +#include "misc/shared.h" + +// The namespace used for XHTML elements +#define XHTML_NAMESPACE "http://www.w3.org/1999/xhtml" + +class QPainter; +template <class type> class QPtrList; +template <class type> class QValueList; +class KHTMLView; +class QRect; +class QMouseEvent; +class QKeyEvent; +class QTextStream; + +namespace khtml { + class RenderStyle; + class RenderObject; + class RenderArena; +} + +namespace DOM { + +class NodeListImpl; +class NamedNodeMapImpl; +class DocumentImpl; +class CSSStyleDeclarationImpl; +class RegisteredEventListener; +class EventImpl; + +struct RegisteredListenerList { + RegisteredListenerList() : listeners(0) + {} + + ~RegisteredListenerList(); + + void addEventListener(int id, EventListener *listener, const bool useCapture); + void removeEventListener(int id, EventListener *listener, bool useCapture); + + void setHTMLEventListener(int id, EventListener *listener); + EventListener *getHTMLEventListener(int id); + + bool hasEventListener(int id); + void clear(); + + //### KDE4: should disappear + bool stillContainsListener(const RegisteredEventListener& listener); + + QValueList<RegisteredEventListener>* listeners;//The actual listener list - may be 0 +private: + bool isHTMLEventListener(EventListener* listener); +}; + + +// this class implements nodes, which can have a parent but no children: +#define NodeImpl_IdNSMask 0xffff0000 +#define NodeImpl_IdLocalMask 0x0000ffff + +const Q_UINT16 defaultNamespace = 0; +const Q_UINT16 xhtmlNamespace = 1; +const Q_UINT16 emptyNamespace = 2; +const Q_UINT16 anyNamespace = 0xffff; +const Q_UINT16 anyLocalName = 0xffff; + +inline Q_UINT16 localNamePart(Q_UINT32 id) { return id & NodeImpl_IdLocalMask; } +inline Q_UINT16 namespacePart(Q_UINT32 id) { return (((unsigned int)id) & NodeImpl_IdNSMask) >> 16; } +inline Q_UINT32 makeId(Q_UINT16 n, Q_UINT16 l) { return (n << 16) | l; } + +const Q_UINT32 anyQName = makeId(anyNamespace, anyLocalName); + +class NodeImpl : public khtml::TreeShared<NodeImpl> +{ + friend class DocumentImpl; +public: + NodeImpl(DocumentImpl *doc); + virtual ~NodeImpl(); + + // DOM methods & attributes for Node + virtual DOMString nodeName() const; + virtual DOMString nodeValue() const; + virtual void setNodeValue( const DOMString &_nodeValue, int &exceptioncode ); + virtual unsigned short nodeType() const; + NodeImpl *parentNode() const { return m_parent; } + NodeImpl *previousSibling() const { return m_previous; } + NodeImpl *nextSibling() const { return m_next; } + virtual NodeListImpl *childNodes(); + virtual NodeImpl *firstChild() const; + virtual NodeImpl *lastChild() const; + // insertBefore, replaceChild and appendChild also close newChild + // unlike the speed optimized addChild (which is used by the parser) + virtual NodeImpl *insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ); + + /* These two methods may delete the old node, so make sure to reference it if you need it */ + virtual void replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ); + virtual void removeChild ( NodeImpl *oldChild, int &exceptioncode ); + virtual NodeImpl *appendChild ( NodeImpl *newChild, int &exceptioncode ); + virtual bool hasChildNodes ( ) const; + virtual NodeImpl *cloneNode ( bool deep ) = 0; + virtual DOMString localName() const; + virtual DOMString prefix() const; + virtual DOMString namespaceURI() const; + virtual void setPrefix(const DOMString &_prefix, int &exceptioncode ); + void normalize (); + + // Other methods (not part of DOM) + virtual bool isElementNode() const { return false; } + virtual bool isHTMLElement() const { return false; } + virtual bool isAttributeNode() const { return false; } + virtual bool isTextNode() const { return false; } + virtual bool isDocumentNode() const { return false; } + virtual bool isXMLElementNode() const { return false; } + virtual bool isGenericFormElement() const { return false; } + virtual bool containsOnlyWhitespace() const { return false; } + virtual bool contentEditable() const; + + // helper functions not being part of the DOM + // Attention: they assume that the caller did the consistency checking! + void setPreviousSibling(NodeImpl *previous) { m_previous = previous; } + void setNextSibling(NodeImpl *next) { m_next = next; } + + virtual void setFirstChild(NodeImpl *child); + virtual void setLastChild(NodeImpl *child); + + // used by the parser. Doesn't do as many error checkings as + // appendChild(), and returns the node into which will be parsed next. + virtual NodeImpl *addChild(NodeImpl *newChild); + + typedef Q_UINT32 Id; + // id() is used to easily and exactly identify a node. It + // is optimized for quick comparison and low memory consumption. + // its value depends on the owner document of the node and is + // categorized in the following way: + // 1..ID_LAST_TAG: the node inherits HTMLElementImpl and is + // part of the HTML namespace. + // The HTML namespace is either the global + // one (no namespace) or the XHTML namespace + // depending on the owner document's doctype + // ID_LAST_TAG+1..0xffff: non-HTML elements in the global namespace + // others non-HTML elements in a namespace. + // the upper 16 bit identify the namespace + // the lower 16 bit identify the local part of the + // qualified element name. + virtual Id id() const { return 0; } + + // These are the DOM 3 Core answer to innerText/setInnerText, and are used + // quite a bit since Mozilla doesn't do innerText. They do, however, behave slightly + // differently. The default implementation is for ELEMENT_NODE, ATTRIBUTE_NODE, + // ENTITY_NODE, ENTITY_REFERENCE_NODE, DOCUMENT_FRAGMENT_NODE. + virtual DOMStringImpl* textContent() const; + virtual void setTextContent( const DOMString &text, int& exceptioncode ) = 0; + + enum IdType { + AttributeId, + ElementId, + NamespaceId + }; + + enum MouseEventType { + MousePress, + MouseRelease, + MouseClick, + MouseDblClick, + MouseMove + }; + + struct MouseEvent + { + MouseEvent( int _button, MouseEventType _type, + const DOMString &_url = DOMString(), const DOMString& _target = DOMString(), + NodeImpl *_innerNode = 0, NodeImpl *_innerNonSharedNode = 0) + { + button = _button; type = _type; + url = _url; target = _target; + innerNode = _innerNode; + innerNonSharedNode = _innerNonSharedNode; + } + + int button; + MouseEventType type; + DOMString url; // url under mouse or empty + DOMString target; + Node innerNode; + Node innerNonSharedNode; + }; + + // for LINK and STYLE + virtual void sheetLoaded() {} + + bool hasID() const { return m_hasId; } + bool hasClass() const { return m_hasClass; } + bool active() const { return m_active; } + bool focused() const { return m_focused; } + bool hovered() const { return m_hovered; } + bool attached() const { return m_attached; } + bool closed() const { return m_closed; } + bool changed() const { return m_changed; } + bool hasChangedChild() const { return m_hasChangedChild; } + bool hasAnchor() const { return m_hasAnchor; } + bool inDocument() const { return m_inDocument; } + bool implicitNode() const { return m_implicit; } + bool htmlCompat() const { return m_htmlCompat; } + void setHasID(bool b=true) { m_hasId = b; } + void setHasClass(bool b=true) { m_hasClass = b; } + void setHasChangedChild( bool b = true ) { m_hasChangedChild = b; } + void setInDocument(bool b=true) { m_inDocument = b; } + void setHTMLCompat(bool b) { m_htmlCompat = b; } + virtual void setFocus(bool b=true) { m_focused = b; } + virtual void setActive(bool b=true) { m_active = b; } + virtual void setHovered(bool b=true) { m_hovered = b; } + virtual void setChanged(bool b=true); + + // for descending restyle when ID or CLASS changes + bool changedAscendentAttribute() const { return m_changedAscendentAttribute; } + void setChangedAscendentAttribute(bool b) { m_changedAscendentAttribute = b; } + + // for style selection performance: whether the element matches several CSS Classes + bool hasClassList() const { return m_hasClassList; } + void setHasClassList(bool b) { m_hasClassList = b; } + + unsigned short tabIndex() const { return m_tabIndex; } + void setTabIndex(unsigned short _tabIndex) { m_tabIndex = _tabIndex; } + + virtual bool isFocusable() const { return false; } + virtual bool isMouseFocusable() const { return isFocusable(); } + virtual bool isTabFocusable() const { return isFocusable(); } + + virtual bool isInline() const; + + virtual void getCaret(int offset, bool override, int &_x, int &_y, int &width, int &height); + virtual QRect getRect() const; + + enum StyleChange { NoChange, NoInherit, Inherit, Detach, Force }; + virtual void recalcStyle( StyleChange = NoChange ) {} + static StyleChange diff( khtml::RenderStyle *s1, khtml::RenderStyle *s2 ); + static bool pseudoDiff( khtml::RenderStyle *s1, khtml::RenderStyle *s2, unsigned int pid); + + unsigned long nodeIndex() const; + // Returns the document that this node is associated with. This is guaranteed to always be non-null, as opposed to + // DOM's ownerDocument() which is null for Document nodes (and sometimes DocumentType nodes). + DocumentImpl* getDocument() const { return m_document.get(); } + + void addEventListener(int id, EventListener *listener, const bool useCapture); + void removeEventListener(int id, EventListener *listener, bool useCapture); + void setHTMLEventListener(int id, EventListener *listener); + EventListener *getHTMLEventListener(int id); + + void dispatchEvent(EventImpl *evt, int &exceptioncode, bool tempEvent = false); + void dispatchGenericEvent( EventImpl *evt, int &exceptioncode); + // return true if event not prevented + bool dispatchHTMLEvent(int _id, bool canBubbleArg, bool cancelableArg); + void dispatchWindowEvent(int _id, bool canBubbleArg, bool cancelableArg); + void dispatchMouseEvent(QMouseEvent *e, int overrideId = 0, int overrideDetail = 0); + void dispatchUIEvent(int _id, int detail = 0); + void dispatchSubtreeModifiedEvent(); + // return true if defaultPrevented (i.e. event should be swallowed) + // this matches the logic in KHTMLView. + bool dispatchKeyEvent(QKeyEvent *key, bool keypress); + + void handleLocalEvents(EventImpl *evt, bool useCapture); + + /** + * Perform the default action for an event e.g. submitting a form + */ + virtual void defaultEventHandler(EventImpl *evt); + + virtual bool isReadOnly(); + virtual bool childTypeAllowed( unsigned short /*type*/ ) { return false; } + virtual unsigned long childNodeCount(); + virtual NodeImpl *childNode(unsigned long index); + + /** + * Does a pre-order traversal of the tree to find the node next node after this one. This uses the same order that + * the tags appear in the source file. + * + * @param stayWithin If not null, the traversal will stop once the specified node is reached. This can be used to + * restrict traversal to a particular sub-tree. + * + * @return The next node, in document order + * + * see traversePreviousNode() + */ + NodeImpl *traverseNextNode(NodeImpl *stayWithin = 0) const; + + /** + * Does a reverse pre-order traversal to find the node that comes before the current one in document order + * + * see traverseNextNode() + */ + NodeImpl *traversePreviousNode() const; + + DocumentImpl *docPtr() const { return m_document.get(); } + + khtml::RenderObject *renderer() const { return m_render; } + khtml::RenderObject *nextRenderer(); + khtml::RenderObject *previousRenderer(); + void setRenderer(khtml::RenderObject* renderer) { m_render = renderer; } + + void checkSetPrefix(const DOMString &_prefix, int &exceptioncode); + void checkAddChild(NodeImpl *newChild, int &exceptioncode); + bool isAncestor( NodeImpl *other ); + virtual bool childAllowed( NodeImpl *newChild ); + + /** + * Returns the minimum caret offset that is allowed for this node. + * + * This default implementation always returns 0. Textual child nodes + * may return other values. + */ + virtual long minOffset() const; + /** + * Returns the maximum caret offset that is allowed for this node. + * + * This default implementation always returns the node count. + * Textual child nodes return the character count instead. + */ + virtual long maxOffset() const; + + // ----------------------------------------------------------------------------- + // Integration with rendering tree + + /** + * Attaches this node to the rendering tree. This calculates the style to be applied to the node and creates an + * appropriate RenderObject which will be inserted into the tree (except when the style has display: none). This + * makes the node visible in the KHTMLView. + */ + virtual void attach(); + + /** + * Detaches the node from the rendering tree, making it invisible in the rendered view. This method will remove + * the node's rendering object from the rendering tree and delete it. + */ + virtual void detach(); + + /** + * Notifies the node that no more children will be added during parsing. + * After a node has been closed all changes must go through the DOM interface. + */ + virtual void close(); + + virtual void structureChanged() {}; + virtual void backwardsStructureChanged() {}; + + void createRendererIfNeeded(); + virtual khtml::RenderStyle *styleForRenderer(khtml::RenderObject *parent); + virtual bool rendererIsNeeded(khtml::RenderStyle *); + virtual khtml::RenderObject *createRenderer(khtml::RenderArena *, khtml::RenderStyle *); + + // ----------------------------------------------------------------------------- + // Methods for maintaining the state of the element between history navigation + + /** + * Indicates whether or not this type of node maintains its state. If so, the state of the node will be stored when + * the user goes to a different page using the state() method, and restored using the restoreState() method if the + * user returns (e.g. using the back button). This is used to ensure that user-changeable elements such as form + * controls maintain their contents when the user returns to a previous page in the history. + */ + virtual bool maintainsState(); + + /** + * Returns the state of this node represented as a string. This string will be passed to restoreState() if the user + * returns to the page. + * + * @return State information about the node represented as a string + */ + virtual QString state(); + + /** + * Sets the state of the element based on a string previosuly returned by state(). This is used to initialize form + * controls with their old values when the user returns to the page in their history. + * + * @param state A string representation of the node's previously-stored state + */ + virtual void restoreState(const QString &state); + + // ----------------------------------------------------------------------------- + // Notification of document stucture changes + + /** + * Notifies the node that it has been inserted into the document. This is called during document parsing, and also + * when a node is added through the DOM methods insertBefore(), appendChild() or replaceChild(). Note that this only + * happens when the node becomes part of the document tree, i.e. only when the document is actually an ancestor of + * the node. The call happens _after_ the node has been added to the tree. + * + * This is similar to the DOMNodeInsertedIntoDocument DOM event, but does not require the overhead of event + * dispatching. + */ + virtual void insertedIntoDocument(); + + /** + * Notifies the node that it is no longer part of the document tree, i.e. when the document is no longer an ancestor + * node. + * + * This is similar to the DOMNodeRemovedFromDocument DOM event, but does not require the overhead of event + * dispatching, and is called _after_ the node is removed from the tree. + */ + virtual void removedFromDocument(); + + /** + * Notifies the node that its list of children have changed (either by adding or removing child nodes), or a child + * node that is of the type CDATA_SECTION_NODE, TEXT_NODE or COMMENT_NODE has changed its value. + */ + virtual void childrenChanged(); + + virtual DOMString toString() const = 0; + /** + * Sometimes we need to get the string between two points on the DOM graph. Use this function to do this. + * For example, when the user copies some selected text to the clipboard as html. + * @param selectionStart Where to start the selection. If selectionStart != this, it is assumed we are after the start point + * @param selectionEnd Where to end the selection. If selectionEnd != this, it is assumed we are before the end point (unless found is true) + * @param startOffset Number of characters into the text in selectionStart that the start of the selection is. + * @param endOffset Number of characters into the text in selectionEnd that the end of the selection is. + * @param found When this is set to true, don't print anymore but closing tags. + * @return An html formatted string for this node and its children between the selectionStart and selectionEnd. + */ + virtual DOMString selectionToString(NodeImpl * selectionStart, + NodeImpl * selectionEnd, + int startOffset, + int endOffset, + bool &found) const + { Q_UNUSED(selectionStart); + Q_UNUSED(selectionEnd); + Q_UNUSED(startOffset); + Q_UNUSED(endOffset); + Q_UNUSED(found); + return toString(); + } + +private: // members + khtml::DocPtr<DocumentImpl> m_document; + NodeImpl *m_previous; + NodeImpl *m_next; +protected: + khtml::RenderObject *m_render; + RegisteredListenerList m_regdListeners; + + unsigned short m_tabIndex : 15; + bool m_hasTabIndex : 1; + + bool m_hasId : 1; + bool m_attached : 1; + bool m_closed : 1; + bool m_changed : 1; + bool m_hasChangedChild : 1; + bool m_changedAscendentAttribute : 1; + bool m_inDocument : 1; + bool m_hasAnchor : 1; + bool m_specified : 1; // used in AttrImpl. Accessor functions there + + bool m_hovered : 1; + bool m_focused : 1; + bool m_active : 1; + bool m_implicit : 1; // implicitely generated by the parser + bool m_htmlCompat : 1; // true if element was created in HTML compat mode + bool m_hasClassList : 1; + bool m_hasClass : 1; // true if element has a class property, as relevant to CSS +}; + +// this is the full Node Implementation with parents and children. +class NodeBaseImpl : public NodeImpl +{ +public: + NodeBaseImpl(DocumentImpl *doc) + : NodeImpl(doc), _first(0), _last(0) {} + virtual ~NodeBaseImpl(); + + // DOM methods overridden from parent classes + virtual NodeImpl *firstChild() const; + virtual NodeImpl *lastChild() const; + virtual NodeImpl *insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ); + virtual void replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ); + virtual void removeChild ( NodeImpl *oldChild, int &exceptioncode ); + virtual NodeImpl *appendChild ( NodeImpl *newChild, int &exceptioncode ); + virtual bool hasChildNodes ( ) const; + + virtual void setTextContent( const DOMString &text, int& exceptioncode ); + + // Other methods (not part of DOM) + void removeChildren(); + void cloneChildNodes(NodeImpl *clone); + + virtual void setFirstChild(NodeImpl *child); + virtual void setLastChild(NodeImpl *child); + virtual NodeImpl *addChild(NodeImpl *newChild); + virtual void attach(); + virtual void detach(); + + + bool getUpperLeftCorner(int &xPos, int &yPos) const; + bool getLowerRightCorner(int &xPos, int &yPos) const; + + virtual void setFocus(bool=true); + virtual void setActive(bool=true); + virtual void setHovered(bool=true); + virtual unsigned long childNodeCount(); + virtual NodeImpl *childNode(unsigned long index); + +protected: + NodeImpl *_first; + NodeImpl *_last; + + // helper functions for inserting children: + + // ### this should vanish. do it in dom/ ! + // check for same source document: + bool checkSameDocument( NodeImpl *newchild, int &exceptioncode ); + // check for being child: + bool checkIsChild( NodeImpl *oldchild, int &exceptioncode ); + // ### + + // find out if a node is allowed to be our child + void dispatchChildInsertedEvents( NodeImpl *child, int &exceptioncode ); + void dispatchChildRemovalEvents( NodeImpl *child, int &exceptioncode ); +}; + +// -------------------------------------------------------------------------- +class Node; +class NodeImpl; + +class NodeListImpl : public khtml::Shared<NodeListImpl> +{ +public: + //Type of the item stored in the cache. + enum Type { + UNCACHEABLE, //Too complex to be cached like this + CHILD_NODES, + LAST_NODE_LIST = CHILD_NODES + }; + + struct CacheKey + { + NodeImpl* baseNode; + int type; + + CacheKey(): type(UNCACHEABLE) {} + + CacheKey(NodeImpl* _baseNode, int _type): + baseNode(_baseNode), type(_type) + {} + + int hash() const + { + return int(reinterpret_cast<unsigned long>(baseNode) >> 2) ^ + (unsigned(type) << 26); + } + + bool operator==(const CacheKey& other) const + { + return baseNode == other.baseNode && + type == other.type; + } + }; + + struct Cache: public khtml::Shared<Cache> + { + static Cache* make() { return new Cache; } + + CacheKey key;//### We must store this in here due to QCache in Qt3 sucking + + unsigned int version; + union + { + NodeImpl* node; + unsigned int index; + } current; + unsigned int position; + unsigned int length; + bool hasLength; + + void updateNodeListInfo(DocumentImpl* doc); + + virtual void clear(DocumentImpl* doc); + virtual ~Cache(); + }; + + typedef Cache* CacheFactory(); + + NodeListImpl(NodeImpl* node, int type, CacheFactory* factory = 0); + virtual ~NodeListImpl(); + + // DOM methods & attributes for NodeList + virtual unsigned long length() const; + virtual NodeImpl *item ( unsigned long index ) const; + + // Other methods (not part of DOM) + +protected: + virtual unsigned long calcLength(NodeImpl *start) const; + // helper functions for searching all ElementImpls in a tree + + NodeImpl *recursiveItem ( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const; + NodeImpl *recursiveItemBack( NodeImpl* absStart, NodeImpl *start, unsigned long &offset ) const; + + // Override this to determine what nodes to return. Set doRecurse to + // false if the children of this node do not need to be entered. + virtual bool nodeMatches( NodeImpl *testNode, bool& doRecurse ) const = 0; + + NodeImpl* m_refNode; + mutable Cache* m_cache; +}; + +class ChildNodeListImpl : public NodeListImpl +{ +public: + + ChildNodeListImpl( NodeImpl *n); + +protected: + virtual bool nodeMatches( NodeImpl *testNode, bool& doRecurse ) const; +}; + + +/** + * NodeList which lists all Nodes in a document with a given tag name + */ +class TagNodeListImpl : public NodeListImpl +{ +public: + TagNodeListImpl( NodeImpl *n, NodeImpl::Id id ); + TagNodeListImpl( NodeImpl *n, const DOMString &namespaceURI, const DOMString &localName ); + + // Other methods (not part of DOM) + +protected: + virtual bool nodeMatches( NodeImpl *testNode, bool& doRecurse ) const; + NodeImpl::Id m_id; + DOMString m_namespaceURI; + DOMString m_localName; + + bool m_matchAllNames; + bool m_matchAllNamespaces; + bool m_namespaceAware; +}; + + +/** + * NodeList which lists all Nodes in a Element with a given "name=" tag + */ +class NameNodeListImpl : public NodeListImpl +{ +public: + NameNodeListImpl( NodeImpl *doc, const DOMString &t ); + + // Other methods (not part of DOM) + +protected: + virtual bool nodeMatches( NodeImpl *testNode, bool& doRecurse ) const; + + DOMString nodeName; +}; + +// Generic NamedNodeMap interface +// Other classes implement this for more specific situations e.g. attributes +// of an element +class NamedNodeMapImpl : public khtml::Shared<NamedNodeMapImpl> +{ +public: + NamedNodeMapImpl(); + virtual ~NamedNodeMapImpl(); + + // DOM methods & attributes for NamedNodeMap + virtual NodeImpl *getNamedItem ( NodeImpl::Id id, bool nsAware = false, DOMStringImpl* qName = 0 ) const = 0; + virtual Node removeNamedItem ( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName, int &exceptioncode ) = 0; + virtual Node setNamedItem ( NodeImpl* arg, bool nsAware, DOMStringImpl* qName, int &exceptioncode ) = 0; + + virtual NodeImpl *item ( unsigned long index ) const = 0; + virtual unsigned long length( ) const = 0; + + // Other methods (not part of DOM) + virtual NodeImpl::Id mapId(DOMStringImpl* namespaceURI, + DOMStringImpl* localName, bool readonly) = 0; + virtual bool isReadOnly() { return false; } +}; + +// Generic read-only NamedNodeMap implementation +// Used for e.g. entities and notations in DocumentType. +// You can add nodes using addNode +class GenericRONamedNodeMapImpl : public NamedNodeMapImpl +{ +public: + GenericRONamedNodeMapImpl(DocumentImpl* doc); + virtual ~GenericRONamedNodeMapImpl(); + + // DOM methods & attributes for NamedNodeMap + + virtual NodeImpl *getNamedItem ( NodeImpl::Id id, bool nsAware = false, DOMStringImpl* qName = 0 ) const; + virtual Node removeNamedItem ( NodeImpl::Id id, bool nsAware, DOMStringImpl* qName, int &exceptioncode ); + virtual Node setNamedItem ( NodeImpl* arg, bool nsAware, DOMStringImpl* qName, int &exceptioncode ); + + virtual NodeImpl *item ( unsigned long index ) const; + virtual unsigned long length( ) const; + + // Other methods (not part of DOM) + virtual NodeImpl::Id mapId(DOMStringImpl* namespaceURI, + DOMStringImpl* localName, bool readonly); + + virtual bool isReadOnly() { return true; } + + void addNode(NodeImpl *n); + +protected: + DocumentImpl* m_doc; + QPtrList<NodeImpl> *m_contents; +}; + +} //namespace +#endif diff --git a/khtml/xml/dom_restyler.cpp b/khtml/xml/dom_restyler.cpp new file mode 100644 index 000000000..0050c4a03 --- /dev/null +++ b/khtml/xml/dom_restyler.cpp @@ -0,0 +1,122 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 "dom_restyler.h" +#include "dom_elementimpl.h" + +namespace khtml { + +DynamicDomRestyler::DynamicDomRestyler() +{ +} + +void DynamicDomRestyler::addDependency(ElementImpl* subject, ElementImpl* dependency, StructuralDependencyType type) +{ + assert(type < LastStructuralDependency); + + dependency_map[type].append(dependency, subject); + reverse_map.append(subject,dependency); +} + +void DynamicDomRestyler::removeDependency(ElementImpl* subject, ElementImpl* dependency, StructuralDependencyType type) +{ + dependency_map[type].remove(dependency, subject); + // don't remove from reverse_map as there might be other depencies to the same element +} + +void DynamicDomRestyler::removeDependencies(ElementImpl* subject, StructuralDependencyType type) +{ + KMultiMap<ElementImpl>::List* my_dependencies = reverse_map.find(subject); + + if (!my_dependencies) return; + + for (my_dependencies->first(); my_dependencies->current() ; my_dependencies->next()) + { + ElementImpl* e = my_dependencies->current(); + dependency_map[type].remove(e,subject); + } + + // don't remove from reverse_map as there might be other depencies to the same elements +} + +void DynamicDomRestyler::resetDependencies(ElementImpl* subject) +{ + KMultiMap<ElementImpl>::List* my_dependencies = reverse_map.find(subject); + + if (!my_dependencies) return; + + for (my_dependencies->first(); my_dependencies->current() ; my_dependencies->next()) + { + ElementImpl* e = my_dependencies->current(); + for (int type = 0; type < LastStructuralDependency; type++) { + dependency_map[type].remove(e,subject); + } + } + + reverse_map.remove(subject); +} + +void DynamicDomRestyler::restyleDepedent(ElementImpl* dependency, StructuralDependencyType type) +{ + assert(type < LastStructuralDependency); + KMultiMap<ElementImpl>::List* dep = dependency_map[type].find(dependency); + + if (!dep) return; + + // take copy as the restyle will change the list + KMultiMap<ElementImpl>::List dependent(*dep); + + for (dependent.first(); dependent.current() ; dependent.next()) + { +// kdDebug() << "Restyling dependent" << endl; + dependent.current()->setChanged(true); + } +} + +void DynamicDomRestyler::dumpStats() const +{ +/* + kdDebug() << "Keys in structural dependencies: " << dependency_map[StructuralDependency].size() << endl; + kdDebug() << "Keys in attribute dependencies: " << dependency_map[AttributeDependency].size() << endl; + + kdDebug() << "Keys in reverse map: " << reverse_map.size() << endl; + */ +} + +void DynamicDomRestyler::addDependency(uint attrID, AttributeDependencyType type) +{ + assert(type < LastAttributeDependency); + + unsigned int hash = (attrID * 257) % 512; + attribute_map[type][hash] = true; +} + +bool DynamicDomRestyler::checkDependency(uint attrID, AttributeDependencyType type) +{ + assert(type < LastAttributeDependency); + + unsigned int hash = (attrID * 257) % 512; + // ### gives false positives, but that's okay. + return attribute_map[type][hash]; +} + +} // namespace diff --git a/khtml/xml/dom_restyler.h b/khtml/xml/dom_restyler.h new file mode 100644 index 000000000..eb8d795d5 --- /dev/null +++ b/khtml/xml/dom_restyler.h @@ -0,0 +1,102 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ + +#ifndef _DOM_restyler_h_ +#define _DOM_restyler_h_ + +#include "misc/multimap.h" +#include <bitset> + +namespace DOM { + class ElementImpl; +} + +// Restyle dependency tracker for dynamic DOM + +namespace khtml { + +using DOM::ElementImpl; + +// These types are different types of dependencies, and serves to identify which element should be +// restyled when a change of that kind triggers on the element +enum StructuralDependencyType { + // Style relies on the children of the element (unaffected by append & close) + StructuralDependency = 0, + // Style relies on the last children of the element (affected by append & close) + BackwardsStructuralDependency = 1, + // Style relies on the element having hover + HoverDependency = 2, + // Style relies on the element being active + ActiveDependency = 3, + // Style relies on another state of element (focus, disabled, checked, etc.) + // (focus is special cased though since elements always depend on their own focus) + OtherStateDependency = 4, + LastStructuralDependency +}; + +// Attribute dependencies are much coarser than structural, for memory reasons rather than performance +// This tracks global depencies of various kinds. +// The groups are separated into where possible depending elements might be: +enum AttributeDependencyType { + // Style of the changed element depend on this attribute + PersonalDependency = 0, + // Style of the elements children depend on this attribute + AncestorDependency = 1, + // Style of the elements later siblings or their children depend on this attribute + PredecessorDependency = 2, + LastAttributeDependency +}; + + +/** + * @internal + */ +class DynamicDomRestyler { +public: + DynamicDomRestyler(); + + // Structural dependencies are tracked from element to subject + void addDependency(ElementImpl* subject, ElementImpl* dependency, StructuralDependencyType type); + void resetDependencies(ElementImpl* subject); + void removeDependency(ElementImpl* subject, ElementImpl* dependency, StructuralDependencyType type); + void removeDependencies(ElementImpl* subject, StructuralDependencyType type); + void restyleDepedent(ElementImpl* dependency, StructuralDependencyType type); + + // Attribute dependencies are traced on attribute alone + void addDependency(uint attrID, AttributeDependencyType type); + bool checkDependency(uint attrID, AttributeDependencyType type); + + void dumpStats() const; +private: + // Map of dependencies. + KMultiMap<ElementImpl> dependency_map[LastStructuralDependency]; + // Map of reverse dependencies. For fast reset + KMultiMap<ElementImpl> reverse_map; + + // Map of the various attribute dependencies + std::bitset<512> attribute_map[LastAttributeDependency]; +}; + +} + +#endif + diff --git a/khtml/xml/dom_stringimpl.cpp b/khtml/xml/dom_stringimpl.cpp new file mode 100644 index 000000000..cb32f5903 --- /dev/null +++ b/khtml/xml/dom_stringimpl.cpp @@ -0,0 +1,460 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001-2003 Dirk Mueller ( mueller@kde.org ) + * (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. + * + */ + +#include "dom_stringimpl.h" + +#include <kdebug.h> + +#include <string.h> +#include <qstringlist.h> + +using namespace DOM; +using namespace khtml; + + +DOMStringImpl::DOMStringImpl(const char *str) +{ + if(str && *str) + { + l = strlen(str); + s = QT_ALLOC_QCHAR_VEC( l ); + int i = l; + QChar* ptr = s; + while( i-- ) + *ptr++ = *str++; + } + else + { + s = QT_ALLOC_QCHAR_VEC( 1 ); // crash protection + s[0] = 0x0; // == QChar::null; + l = 0; + } +} + +// FIXME: should be a cached flag maybe. +bool DOMStringImpl::containsOnlyWhitespace() const +{ + if (!s) + return true; + + for (uint i = 0; i < l; i++) { + QChar c = s[i]; + if (c.unicode() <= 0x7F) { + if (c.unicode() > ' ') + return false; + } else { + if (c.direction() != QChar::DirWS) + return false; + } + } + return true; +} + + +void DOMStringImpl::append(DOMStringImpl *str) +{ + if(str && str->l != 0) + { + int newlen = l+str->l; + QChar *c = QT_ALLOC_QCHAR_VEC(newlen); + memcpy(c, s, l*sizeof(QChar)); + memcpy(c+l, str->s, str->l*sizeof(QChar)); + if(s) QT_DELETE_QCHAR_VEC(s); + s = c; + l = newlen; + } +} + +void DOMStringImpl::insert(DOMStringImpl *str, unsigned int pos) +{ + if(pos > l) + { + append(str); + return; + } + if(str && str->l != 0) + { + int newlen = l+str->l; + QChar *c = QT_ALLOC_QCHAR_VEC(newlen); + memcpy(c, s, pos*sizeof(QChar)); + memcpy(c+pos, str->s, str->l*sizeof(QChar)); + memcpy(c+pos+str->l, s+pos, (l-pos)*sizeof(QChar)); + if(s) QT_DELETE_QCHAR_VEC(s); + s = c; + l = newlen; + } +} + +void DOMStringImpl::truncate(int len) +{ + if(len > (int)l) return; + + int nl = len < 1 ? 1 : len; + QChar *c = QT_ALLOC_QCHAR_VEC(nl); + memcpy(c, s, nl*sizeof(QChar)); + if(s) QT_DELETE_QCHAR_VEC(s); + s = c; + l = len; +} + +void DOMStringImpl::remove(unsigned int pos, int len) +{ + if(pos >= l ) return; + if(pos+len > l) + len = l - pos; + + uint newLen = l-len; + QChar *c = QT_ALLOC_QCHAR_VEC(newLen); + memcpy(c, s, pos*sizeof(QChar)); + memcpy(c+pos, s+pos+len, (l-len-pos)*sizeof(QChar)); + if(s) QT_DELETE_QCHAR_VEC(s); + s = c; + l = newLen; +} + +DOMStringImpl *DOMStringImpl::split(unsigned int pos) +{ + if( pos >=l ) return new DOMStringImpl(); + + uint newLen = l-pos; + DOMStringImpl *str = new DOMStringImpl(s + pos, newLen); + truncate(pos); + return str; +} + +DOMStringImpl *DOMStringImpl::substring(unsigned int pos, unsigned int len) +{ + if( pos >=l ) return new DOMStringImpl(); + if(pos+len > l) + len = l - pos; + + return new DOMStringImpl(s + pos, len); +} + +// Collapses white-space according to CSS 2.1 rules +DOMStringImpl *DOMStringImpl::collapseWhiteSpace(bool preserveLF, bool preserveWS) +{ + if (preserveLF && preserveWS) return this; + + // Notice we are likely allocating more space than needed (worst case) + QChar *n = QT_ALLOC_QCHAR_VEC(l); + + unsigned int pos = 0; + bool collapsing = false; // collapsing white-space + bool collapsingLF = false; // collapsing around linefeed + bool changedLF = false; + for(unsigned int i=0; i<l; i++) { + QChar ch = s[i]; + + // We act on \r as we would on \n because CSS uses it to indicate new-line + if (ch == '\r') ch = '\n'; + else + // ### The XML parser lets \t through, for now treat them as spaces + if (ch == '\t') ch = ' '; + + if (!preserveLF && ch == '\n') { + // ### Not strictly correct according to CSS3 text-module. + // - In ideographic languages linefeed should be ignored + // - and in Thai and Khmer it should be treated as a zero-width space + ch = ' '; // Treat as space + changedLF = true; + } + + if (collapsing) { + if (ch == ' ') + continue; + if (ch == '\n') { + collapsingLF = true; + continue; + } + + n[pos++] = (collapsingLF) ? '\n' : ' '; + collapsing = false; + collapsingLF = false; + } + else + if (!preserveWS && ch == ' ') { + collapsing = true; + continue; + } + else + if (!preserveWS && ch == '\n') { + collapsing = true; + collapsingLF = true; + continue; + } + + n[pos++] = ch; + } + if (collapsing) + n[pos++] = ((collapsingLF) ? '\n' : ' '); + + if (pos == l && !changedLF) { + QT_DELETE_QCHAR_VEC(n); + return this; + } + else { + DOMStringImpl* out = new DOMStringImpl(); + out->s = n; + out->l = pos; + + return out; + } +} + +static Length parseLength(const QChar *s, unsigned int l) +{ + if (l == 0) { + return Length(1, Relative); + } + + unsigned i = 0; + while (i < l && s[i].isSpace()) + ++i; + if (i < l && (s[i] == '+' || s[i] == '-')) + ++i; + while (i < l && s[i].isDigit()) + ++i; + + bool ok; + int r = QConstString(s, i).string().toInt(&ok); + + /* Skip over any remaining digits, we are not that accurate (5.5% => 5%) */ + while (i < l && (s[i].isDigit() || s[i] == '.')) + ++i; + + /* IE Quirk: Skip any whitespace (20 % => 20%) */ + while (i < l && s[i].isSpace()) + ++i; + + if (ok) { + if (i == l) { + return Length(r, Fixed); + } else { + const QChar* next = s+i; + + if (*next == '%') + return Length(r, Percent); + + if (*next == '*') + return Length(r, Relative); + } + return Length(r, Fixed); + } else { + if (i < l) { + const QChar* next = s+i; + + if (*next == '*') + return Length(1, Relative); + + if (*next == '%') + return Length(1, Relative); + } + } + return Length(0, Relative); +} + +khtml::Length* DOMStringImpl::toCoordsArray(int& len) const +{ + QString str(s, l); + for(unsigned int i=0; i < l; i++) { + QChar cc = s[i]; + if (cc > '9' || (cc < '0' && cc != '-' && cc != '*' && cc != '.')) + str[i] = ' '; + } + str = str.simplifyWhiteSpace(); + + len = str.contains(' ') + 1; + khtml::Length* r = new khtml::Length[len]; + + int i = 0; + int pos = 0; + int pos2; + + while((pos2 = str.find(' ', pos)) != -1) { + r[i++] = parseLength((QChar *) str.unicode()+pos, pos2-pos); + pos = pos2+1; + } + r[i] = parseLength((QChar *) str.unicode()+pos, str.length()-pos); + + return r; +} + +khtml::Length* DOMStringImpl::toLengthArray(int& len) const +{ + QString str(s, l); + str = str.simplifyWhiteSpace(); + + len = str.contains(',') + 1; + + // If we have no commas, we have no array. + if( len == 1 ) + return 0L; + + khtml::Length* r = new khtml::Length[len]; + + int i = 0; + int pos = 0; + int pos2; + + while((pos2 = str.find(',', pos)) != -1) { + r[i++] = parseLength((QChar *) str.unicode()+pos, pos2-pos); + pos = pos2+1; + } + + /* IE Quirk: If the last comma is the last char skip it and reduce len by one */ + if (str.length()-pos > 0) + r[i] = parseLength((QChar *) str.unicode()+pos, str.length()-pos); + else + len--; + + return r; +} + +bool DOMStringImpl::isLower() const +{ + unsigned int i; + for (i = 0; i < l; i++) + if (s[i].lower() != s[i]) + return false; + return true; +} + +DOMStringImpl *DOMStringImpl::lower() const +{ + DOMStringImpl *c = new DOMStringImpl; + if(!l) return c; + + c->s = QT_ALLOC_QCHAR_VEC(l); + c->l = l; + + for (unsigned int i = 0; i < l; i++) + c->s[i] = s[i].lower(); + + return c; +} + +DOMStringImpl *DOMStringImpl::upper() const +{ + DOMStringImpl *c = new DOMStringImpl; + if(!l) return c; + + c->s = QT_ALLOC_QCHAR_VEC(l); + c->l = l; + + for (unsigned int i = 0; i < l; i++) + c->s[i] = s[i].upper(); + + return c; +} + +DOMStringImpl *DOMStringImpl::capitalize(bool noFirstCap) const +{ + bool canCapitalize= !noFirstCap; + DOMStringImpl *c = new DOMStringImpl; + if(!l) return c; + + c->s = QT_ALLOC_QCHAR_VEC(l); + c->l = l; + + for (unsigned int i=0; i<l; i++) + { + if (s[i].isLetterOrNumber() && canCapitalize) + { + c->s[i]=s[i].upper(); + canCapitalize=false; + } + else + { + c->s[i]=s[i]; + if (s[i].isSpace()) + canCapitalize=true; + } + } + + return c; +} + +QString DOMStringImpl::string() const +{ + return QString(s, l); +} + +int DOMStringImpl::toInt(bool* ok) const +{ + // match \s*[+-]?\d* + unsigned i = 0; + while (i < l && s[i].isSpace()) + ++i; + if (i < l && (s[i] == '+' || s[i] == '-')) + ++i; + while (i < l && s[i].isDigit()) + ++i; + + return QConstString(s, i).string().toInt(ok); +} + +static const unsigned short amp[] = {'&', 'a', 'm', 'p', ';'}; +static const unsigned short lt[] = {'&', 'l', 't', ';'}; +static const unsigned short gt[] = {'&', 'g', 't', ';'}; + +DOMStringImpl *DOMStringImpl::escapeHTML() +{ + unsigned outL = 0; + for (unsigned int i = 0; i < l; ++i ) { + if ( s[i] == '&' ) + outL += 5; //& + else if (s[i] == '<' || s[i] == '>') + outL += 4; //>/< + else + ++outL; + } + if (outL == l) + return this; + + + DOMStringImpl* toRet = new DOMStringImpl(); + toRet->s = QT_ALLOC_QCHAR_VEC(outL); + toRet->l = outL; + + unsigned outP = 0; + for (unsigned int i = 0; i < l; ++i ) { + if ( s[i] == '&' ) { + memcpy(&toRet->s[outP], amp, sizeof(amp)); + outP += 5; + } else if (s[i] == '<') { + memcpy(&toRet->s[outP], lt, sizeof(lt)); + outP += 4; + } else if (s[i] == '>') { + memcpy(&toRet->s[outP], gt, sizeof(gt)); + outP += 4; + } else { + toRet->s[outP] = s[i]; + ++outP; + } + } + return toRet; +} + diff --git a/khtml/xml/dom_stringimpl.h b/khtml/xml/dom_stringimpl.h new file mode 100644 index 000000000..b1430bc89 --- /dev/null +++ b/khtml/xml/dom_stringimpl.h @@ -0,0 +1,104 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003 Dirk Mueller (mueller@kde.org) + * + * 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. + * + */ +#ifndef _DOM_DOMStringImpl_h_ +#define _DOM_DOMStringImpl_h_ + +#include <qstring.h> + +#include "dom/dom_misc.h" +#include "misc/khtmllayout.h" +#include "misc/shared.h" + +#define QT_ALLOC_QCHAR_VEC( N ) (QChar*) new char[ sizeof(QChar)*( N ) ] +#define QT_DELETE_QCHAR_VEC( P ) delete[] ((char*)( P )) + +namespace DOM { + +class DOMStringImpl : public khtml::Shared<DOMStringImpl> +{ +private: + DOMStringImpl(const DOMStringImpl&); + DOMStringImpl& operator=(const DOMStringImpl&); +protected: + DOMStringImpl() { s = 0, l = 0; } +public: + DOMStringImpl(const QChar *str, unsigned int len) { + bool havestr = str && len; + s = QT_ALLOC_QCHAR_VEC( havestr ? len : 1 ); + if(str && len) { + memcpy( s, str, len * sizeof(QChar) ); + l = len; + } else { + // crash protection + s[0] = 0x0; + l = 0; + } + } + + explicit DOMStringImpl(const char *str); + explicit DOMStringImpl(const QChar &ch) { + s = QT_ALLOC_QCHAR_VEC( 1 ); + s[0] = ch; + l = 1; + } + ~DOMStringImpl() { + if(s) QT_DELETE_QCHAR_VEC(s); + } + + void append(DOMStringImpl *str); + void insert(DOMStringImpl *str, unsigned int pos); + void truncate(int len); + void remove(unsigned int pos, int len=1); + DOMStringImpl *split(unsigned int pos); + DOMStringImpl *copy() const { + return new DOMStringImpl(s,l); + }; + + + DOMStringImpl *substring(unsigned int pos, unsigned int len); + DOMStringImpl *collapseWhiteSpace(bool preserveLF, bool preserveWS); + + const QChar &operator [] (int pos) { return s[pos]; } + bool containsOnlyWhitespace() const; + + // ignores trailing garbage, unlike QString + int toInt(bool* ok = 0) const; + + khtml::Length* toLengthArray(int& len) const; + khtml::Length* toCoordsArray(int& len) const; + bool isLower() const; + DOMStringImpl *lower() const; + DOMStringImpl *upper() const; + DOMStringImpl *capitalize(bool noFirstCap=false) const; + DOMStringImpl *escapeHTML(); + + QChar *unicode() const { return s; } + uint length() const { return l; } + QString string() const; + + unsigned int l; + QChar *s; +}; + +} +#endif diff --git a/khtml/xml/dom_textimpl.cpp b/khtml/xml/dom_textimpl.cpp new file mode 100644 index 000000000..015135428 --- /dev/null +++ b/khtml/xml/dom_textimpl.cpp @@ -0,0 +1,522 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2001-2003 Dirk Mueller (mueller@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 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. + */ + +#include "dom/dom_exception.h" +#include "css/cssstyleselector.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_textimpl.h" +#include "xml/dom_docimpl.h" + +#include "misc/htmlhashes.h" +#include "rendering/render_text.h" +#include "rendering/render_flow.h" + +#include <kdebug.h> + +using namespace DOM; +using namespace khtml; + +static DOMString escapeHTML( const DOMString& in ) +{ + return in.implementation()->escapeHTML(); +} + +CharacterDataImpl::CharacterDataImpl(DocumentImpl *doc, DOMStringImpl* _text) + : NodeImpl(doc) +{ + str = _text ? _text : new DOMStringImpl(0, 0); + str->ref(); +} + +CharacterDataImpl::~CharacterDataImpl() +{ + if(str) str->deref(); +} + +void CharacterDataImpl::setData( const DOMString &_data, int &exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + if(str == _data.impl) return; // ### fire DOMCharacterDataModified if modified? + DOMStringImpl *oldStr = str; + str = _data.impl; + if(str) str->ref(); + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + + dispatchModifiedEvent(oldStr); + if(oldStr) oldStr->deref(); +} + +unsigned long CharacterDataImpl::length() const +{ + return str->l; +} + +DOMString CharacterDataImpl::substringData( const unsigned long offset, const unsigned long count, int &exceptioncode ) +{ + exceptioncode = 0; + if ((long)count < 0) + exceptioncode = DOMException::INDEX_SIZE_ERR; + else + checkCharDataOperation(offset, exceptioncode); + if (exceptioncode) + return DOMString(); + + return str->substring(offset,count); +} + +void CharacterDataImpl::appendData( const DOMString &arg, int &exceptioncode ) +{ + exceptioncode = 0; + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + DOMStringImpl *oldStr = str; + str = str->copy(); + str->ref(); + str->append(arg.impl); + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + + dispatchModifiedEvent(oldStr); + oldStr->deref(); +} + +void CharacterDataImpl::insertData( const unsigned long offset, const DOMString &arg, int &exceptioncode ) +{ + exceptioncode = 0; + checkCharDataOperation(offset, exceptioncode); + if (exceptioncode) + return; + + DOMStringImpl *oldStr = str; + str = str->copy(); + str->ref(); + str->insert(arg.impl, offset); + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + + dispatchModifiedEvent(oldStr); + oldStr->deref(); +} + +void CharacterDataImpl::deleteData( const unsigned long offset, const unsigned long count, int &exceptioncode ) +{ + exceptioncode = 0; + if ((long)count < 0) + exceptioncode = DOMException::INDEX_SIZE_ERR; + else + checkCharDataOperation(offset, exceptioncode); + if (exceptioncode) + return; + + DOMStringImpl *oldStr = str; + str = str->copy(); + str->ref(); + str->remove(offset,count); + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + + dispatchModifiedEvent(oldStr); + oldStr->deref(); +} + +void CharacterDataImpl::replaceData( const unsigned long offset, const unsigned long count, const DOMString &arg, int &exceptioncode ) +{ + exceptioncode = 0; + if ((long)count < 0) + exceptioncode = DOMException::INDEX_SIZE_ERR; + else + checkCharDataOperation(offset, exceptioncode); + if (exceptioncode) + return; + + unsigned long realCount; + if (offset + count > str->l) + realCount = str->l-offset; + else + realCount = count; + + DOMStringImpl *oldStr = str; + str = str->copy(); + str->ref(); + str->remove(offset,realCount); + str->insert(arg.impl, offset); + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + + dispatchModifiedEvent(oldStr); + oldStr->deref(); +} + +DOMString CharacterDataImpl::nodeValue() const +{ + return str; +} + +bool CharacterDataImpl::containsOnlyWhitespace() const +{ + return str->containsOnlyWhitespace(); +} + +void CharacterDataImpl::setNodeValue( const DOMString &_nodeValue, int &exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: taken care of by setData() + setData(_nodeValue, exceptioncode); +} + +void CharacterDataImpl::dispatchModifiedEvent(DOMStringImpl *prevValue) +{ + if (parentNode()) + parentNode()->childrenChanged(); + if (!getDocument()->hasListenerType(DocumentImpl::DOMCHARACTERDATAMODIFIED_LISTENER)) + return; + + DOMStringImpl *newValue = str->copy(); + newValue->ref(); + int exceptioncode = 0; + MutationEventImpl* const evt = new MutationEventImpl(EventImpl::DOMCHARACTERDATAMODIFIED_EVENT,true,false,0,prevValue,newValue,DOMString(),0); + evt->ref(); + dispatchEvent(evt,exceptioncode); + evt->deref(); + newValue->deref(); + dispatchSubtreeModifiedEvent(); +} + +void CharacterDataImpl::checkCharDataOperation( const unsigned long offset, int &exceptioncode ) +{ + exceptioncode = 0; + + // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than the number of 16-bit + // units in data. + if (offset > str->l) { + exceptioncode = DOMException::INDEX_SIZE_ERR; + return; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } +} + +long CharacterDataImpl::minOffset() const +{ + RenderText *r = static_cast<RenderText *>(renderer()); + if (!r || !r->isText()) return 0; + + // take :first-letter into consideration +#ifdef __GNUC__ +#warning FIXME +#endif +#if 0 + if (r->forcedMinOffset()) { + RenderFlow *firstLetter = static_cast<RenderFlow *>(r->previousSibling()); + if (firstLetter && firstLetter->isFlow() && firstLetter->isFirstLetter()) { + RenderText *letterText = static_cast<RenderText *>(firstLetter->firstChild()); + return letterText->minOffset(); + } + } +#endif + + return r->minOffset(); +} + +long CharacterDataImpl::maxOffset() const +{ + RenderText *r = static_cast<RenderText *>(renderer()); + if (!r || !r->isText()) return (long)length(); + return r->maxOffset(); +} + +DOMStringImpl* CharacterDataImpl::textContent() const +{ + return new DOMStringImpl(str->s, str->l); +} + +void CharacterDataImpl::setTextContent( const DOMString &str, int& exceptioncode ) +{ + setData(str, exceptioncode); +} + +// --------------------------------------------------------------------------- + +DOMString CommentImpl::nodeName() const +{ + return "#comment"; +} + +unsigned short CommentImpl::nodeType() const +{ + return Node::COMMENT_NODE; +} + +NodeImpl *CommentImpl::cloneNode(bool /*deep*/) +{ + return getDocument()->createComment( str ); +} + +NodeImpl::Id CommentImpl::id() const +{ + return ID_COMMENT; +} + +// DOM Section 1.1.1 +bool CommentImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +DOMString CommentImpl::toString() const +{ + // FIXME: substitute entity references as needed! + return DOMString("<!--") + escapeHTML( nodeValue() ) + "-->"; +} + +// --------------------------------------------------------------------------- + +TextImpl *TextImpl::splitText( const unsigned long offset, int &exceptioncode ) +{ + exceptioncode = 0; + + // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than + // the number of 16-bit units in data. + + // ### we explicitly check for a negative long that has been cast to an unsigned long + // ... this can happen if JS code passes in -1 - we need to catch this earlier! (in the + // kjs bindings) + if (offset > str->l || (long)offset < 0) { + exceptioncode = DOMException::INDEX_SIZE_ERR; + return 0; + } + + // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return 0; + } + + DOMStringImpl *oldStr = str; + TextImpl *newText = createNew(str->substring(offset,str->l-offset)); + str = str->copy(); + str->ref(); + str->remove(offset,str->l-offset); + + dispatchModifiedEvent(oldStr); + oldStr->deref(); + + if (parentNode()) + parentNode()->insertBefore(newText,nextSibling(), exceptioncode ); + if ( exceptioncode ) + return 0; + + if (m_render) + (static_cast<RenderText*>(m_render))->setText(str); + setChanged(true); + return newText; +} + +DOMString TextImpl::nodeName() const +{ + return "#text"; +} + +unsigned short TextImpl::nodeType() const +{ + return Node::TEXT_NODE; +} + +NodeImpl *TextImpl::cloneNode(bool /*deep*/) +{ + return getDocument()->createTextNode(str); +} + +bool TextImpl::rendererIsNeeded(RenderStyle *style) +{ + if (!CharacterDataImpl::rendererIsNeeded(style)) { + return false; + } + bool onlyWS = containsOnlyWhitespace(); + if (!onlyWS) { + return true; + } + + RenderObject *par = parentNode()->renderer(); + + if (par->isTable() || par->isTableRow() || par->isTableSection()) { + return false; + } + + if (style->preserveWS() || style->preserveLF()) { + return true; + } + + RenderObject *prev = previousRenderer(); + if (par->isInlineFlow()) { + // <span><div/> <div/></span> + if (prev && !prev->isInline()) { + return false; + } + } else { + if (par->isRenderBlock() && !par->childrenInline() && (!prev || !prev->isInline())) { + return false; + } + + RenderObject *first = par->firstChild(); + while (first && first->isFloatingOrPositioned()) + first = first->nextSibling(); + RenderObject *next = nextRenderer(); + if (!first || next == first) { + // Whitespace at the start of a block just goes away. Don't even + // make a render object for this text. + return false; + } + } + + return true; +} + +RenderObject *TextImpl::createRenderer(RenderArena *arena, RenderStyle *style) +{ + return new (arena) RenderText(this, str); +} + +void TextImpl::attach() +{ + createRendererIfNeeded(); + CharacterDataImpl::attach(); +} + +NodeImpl::Id TextImpl::id() const +{ + return ID_TEXT; +} + +void TextImpl::recalcStyle( StyleChange change ) +{ +// qDebug("textImpl::recalcStyle"); + // Create renderer if now needed + if ( changed() && !m_render) { + createRendererIfNeeded(); + } + if (change != NoChange && parentNode()) { +// qDebug("DomText::recalcStyle"); + if(m_render) + m_render->setStyle(parentNode()->renderer()->style()); + } + if ( changed() && m_render && m_render->isText() ) + static_cast<RenderText*>(m_render)->setText(str); + setChanged( false ); +} + +// DOM Section 1.1.1 +bool TextImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +TextImpl *TextImpl::createNew(DOMStringImpl *_str) +{ + return new TextImpl(docPtr(),_str); +} + +DOMStringImpl* TextImpl::renderString() const +{ + if (renderer()) + return static_cast<RenderText*>(renderer())->string(); + else + return string(); +} + +DOMString TextImpl::toString() const +{ + // FIXME: substitute entity references as needed! + return escapeHTML( nodeValue() ); +} + +DOMString TextImpl::toString(long long startOffset, long long endOffset) const +{ + // FIXME: substitute entity references as needed! + + DOMString str = nodeValue(); + if(endOffset >=0 || startOffset >0) + str = str.copy(); //we are going to modify this, so make a copy. I hope I'm doing this right. + if(endOffset >= 0) + str.truncate(endOffset); + if(startOffset > 0) //note the order of these 2 'if' statements so that it works right when n==m_startContainer==m_endContainer + str.remove(0, startOffset); + return escapeHTML( str ); +} + +// --------------------------------------------------------------------------- + +DOMString CDATASectionImpl::nodeName() const +{ + return "#cdata-section"; +} + +unsigned short CDATASectionImpl::nodeType() const +{ + return Node::CDATA_SECTION_NODE; +} + +NodeImpl *CDATASectionImpl::cloneNode(bool /*deep*/) +{ + return getDocument()->createCDATASection(str); +} + +// DOM Section 1.1.1 +bool CDATASectionImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +TextImpl *CDATASectionImpl::createNew(DOMStringImpl *_str) +{ + return new CDATASectionImpl(docPtr(),_str); +} + +DOMString CDATASectionImpl::toString() const +{ + // FIXME: substitute entity references as needed! + return DOMString("<![CDATA[") + nodeValue() + "]]>"; +} + + + diff --git a/khtml/xml/dom_textimpl.h b/khtml/xml/dom_textimpl.h new file mode 100644 index 000000000..07e4909f6 --- /dev/null +++ b/khtml/xml/dom_textimpl.h @@ -0,0 +1,176 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2001-2003 Dirk Mueller (mueller@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2003 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. + * + */ +#ifndef _DOM_CharacterDataImpl_h_ +#define _DOM_CharacterDataImpl_h_ + +#include "xml/dom_nodeimpl.h" +#include "dom/dom_string.h" + +namespace DOM { + + class DocumentImpl; + class CharacterData; + class Text; + +class CharacterDataImpl : public NodeImpl +{ +public: + CharacterDataImpl(DocumentImpl *doc, DOMStringImpl* _text); + CharacterDataImpl(DocumentImpl *doc) + : NodeImpl(doc), str(0) {} + + virtual ~CharacterDataImpl(); + + // DOM methods & attributes for CharacterData + + virtual void setData( const DOMString &_data, int &exceptioncode ); + virtual unsigned long length ( ) const; + virtual DOMString substringData ( const unsigned long offset, const unsigned long count, int &exceptioncode ); + virtual void appendData ( const DOMString &arg, int &exceptioncode ); + virtual void insertData ( const unsigned long offset, const DOMString &arg, int &exceptioncode ); + virtual void deleteData ( const unsigned long offset, const unsigned long count, int &exceptioncode ); + virtual void replaceData ( const unsigned long offset, const unsigned long count, const DOMString &arg, int &exceptioncode ); + + virtual bool containsOnlyWhitespace() const; + + // DOM methods overridden from parent classes + + virtual DOMString nodeValue() const; + virtual void setNodeValue( const DOMString &_nodeValue, int &exceptioncode ); + + virtual DOMStringImpl* textContent() const; + virtual void setTextContent( const DOMString &text, int& exceptioncode ); + + // Other methods (not part of DOM) + + DOMStringImpl *string() const { return str; } + DOMString data() const { return str; } + + virtual void checkCharDataOperation( const unsigned long offset, int &exceptioncode ); + + virtual long minOffset() const; + virtual long maxOffset() const; + +protected: + // note: since DOMStrings are shared, str should always be copied when making + // a change or returning a string + DOMStringImpl *str; + + void dispatchModifiedEvent(DOMStringImpl *prevValue); +}; + +// ---------------------------------------------------------------------------- + +class CommentImpl : public CharacterDataImpl +{ +public: + CommentImpl(DocumentImpl *doc, DOMStringImpl* _text) + : CharacterDataImpl(doc, _text) {} + CommentImpl(DocumentImpl *doc) + : CharacterDataImpl(doc) {} + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode(bool deep); + + // Other methods (not part of DOM) + + virtual Id id() const; + virtual bool childTypeAllowed( unsigned short type ); + + virtual DOMString toString() const; +}; + +// ---------------------------------------------------------------------------- + +class TextImpl : public CharacterDataImpl +{ +public: + TextImpl(DocumentImpl *impl, DOMStringImpl* _text) + : CharacterDataImpl(impl, _text) {} + TextImpl(DocumentImpl *impl) + : CharacterDataImpl(impl) {} + + // DOM methods & attributes for CharacterData + + TextImpl *splitText ( const unsigned long offset, int &exceptioncode ); + + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode(bool deep); + + // Other methods (not part of DOM) + + virtual bool isTextNode() const { return true; } + virtual Id id() const; + virtual void attach(); + virtual bool rendererIsNeeded(khtml::RenderStyle *); + virtual khtml::RenderObject *createRenderer(khtml::RenderArena *, khtml::RenderStyle *); + virtual void recalcStyle( StyleChange = NoChange ); + virtual bool childTypeAllowed( unsigned short type ); + + DOMStringImpl *renderString() const; + + virtual DOMString toString() const; + /** Return the text for the node, with < replaced with < and so on. + * @param startOffset The number of characters counted from the left, zero indexed, counting "<" as one character, to start from. Use -1 to start from 0. + * @param endOffset The number of characters counted from the left, zero indexed, counting "<" as one character, to end on. Use -1 to end at the end of the string. + * @return An html escaped version of the substring. + */ + DOMString toString(long long startOffset, long long endOffset) const; +protected: + virtual TextImpl *createNew(DOMStringImpl *_str); +}; + +// ---------------------------------------------------------------------------- + +class CDATASectionImpl : public TextImpl +{ +public: + CDATASectionImpl(DocumentImpl *impl, DOMStringImpl* _text) + : TextImpl(impl, _text) {} + CDATASectionImpl(DocumentImpl *impl) + : TextImpl(impl) {} + + // DOM methods overridden from parent classes + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode(bool deep); + + // Other methods (not part of DOM) + + virtual bool childTypeAllowed( unsigned short type ); + + virtual DOMString toString() const; + +protected: + virtual TextImpl *createNew(DOMStringImpl *_str); +}; + + + +} //namespace +#endif diff --git a/khtml/xml/dom_xmlimpl.cpp b/khtml/xml/dom_xmlimpl.cpp new file mode 100644 index 000000000..90ec2b352 --- /dev/null +++ b/khtml/xml/dom_xmlimpl.cpp @@ -0,0 +1,498 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Peter Kelly (pmk@post.com) + * + * 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 "dom/dom_exception.h" + +#include "xml/dom_xmlimpl.h" +#include "xml/dom_docimpl.h" +#include "xml/dom_stringimpl.h" +#include "css/css_stylesheetimpl.h" +#include "misc/loader.h" + +using namespace DOM; + +EntityImpl::EntityImpl(DocumentImpl *doc) : NodeBaseImpl(doc) +{ + m_publicId = 0; + m_systemId = 0; + m_notationName = 0; + m_name = 0; +} + +EntityImpl::EntityImpl(DocumentImpl *doc, DOMString _name) : NodeBaseImpl(doc) +{ + m_publicId = 0; + m_systemId = 0; + m_notationName = 0; + m_name = _name.implementation(); + if (m_name) + m_name->ref(); +} + +EntityImpl::EntityImpl(DocumentImpl *doc, DOMString _publicId, DOMString _systemId, DOMString _notationName) : NodeBaseImpl(doc) +{ + m_publicId = _publicId.implementation(); + if (m_publicId) + m_publicId->ref(); + m_systemId = _systemId.implementation(); + if (m_systemId) + m_systemId->ref(); + m_notationName = _notationName.implementation(); + if (m_notationName) + m_notationName->ref(); + m_name = 0; +} + + +EntityImpl::~EntityImpl() +{ + if (m_publicId) + m_publicId->deref(); + if (m_systemId) + m_systemId->deref(); + if (m_notationName) + m_notationName->deref(); + if (m_name) + m_name->deref(); +} + +DOMString EntityImpl::publicId() const +{ + return m_publicId; +} + +DOMString EntityImpl::systemId() const +{ + return m_systemId; +} + +DOMString EntityImpl::notationName() const +{ + return m_notationName; +} + +DOMString EntityImpl::nodeName() const +{ + return m_name; +} + +unsigned short EntityImpl::nodeType() const +{ + return Node::ENTITY_NODE; +} + +NodeImpl *EntityImpl::cloneNode ( bool /*deep*/) +{ + // Spec says cloning Document nodes is "implementation dependent" + // so we do not support it... + return 0; +} + +// DOM Section 1.1.1 +bool EntityImpl::childTypeAllowed( unsigned short type ) +{ + switch (type) { + case Node::ELEMENT_NODE: + case Node::PROCESSING_INSTRUCTION_NODE: + case Node::COMMENT_NODE: + case Node::TEXT_NODE: + case Node::CDATA_SECTION_NODE: + case Node::ENTITY_REFERENCE_NODE: + return true; + break; + default: + return false; + } +} + +DOMString EntityImpl::toString() const +{ + DOMString result = "<!ENTITY' "; + + if (m_name && m_name->l != 0) { + result += " "; + result += m_name; + } + + if (m_publicId && m_publicId->l != 0) { + result += " PUBLIC \""; + result += m_publicId; + result += "\" \""; + result += m_systemId; + result += "\""; + } else if (m_systemId && m_systemId->l != 0) { + result += " SYSTEM \""; + result += m_systemId; + result += "\""; + } + + if (m_notationName && m_notationName->l != 0) { + result += " NDATA "; + result += m_notationName; + } + + result += ">"; + + return result; +} + +// ------------------------------------------------------------------------- + +EntityReferenceImpl::EntityReferenceImpl(DocumentImpl *doc) : NodeBaseImpl(doc) +{ + m_entityName = 0; +} + +EntityReferenceImpl::EntityReferenceImpl(DocumentImpl *doc, DOMStringImpl *_entityName) : NodeBaseImpl(doc) +{ + m_entityName = _entityName; + if (m_entityName) + m_entityName->ref(); +} + +EntityReferenceImpl::~EntityReferenceImpl() +{ + if (m_entityName) + m_entityName->deref(); +} + +DOMString EntityReferenceImpl::nodeName() const +{ + return m_entityName; +} + +unsigned short EntityReferenceImpl::nodeType() const +{ + return Node::ENTITY_REFERENCE_NODE; +} + +NodeImpl *EntityReferenceImpl::cloneNode ( bool deep ) +{ + EntityReferenceImpl *clone = new EntityReferenceImpl(docPtr(),m_entityName); + // ### make sure children are readonly + // ### since we are a reference, should we clone children anyway (even if not deep?) + if (deep) + cloneChildNodes(clone); + return clone; +} + +// DOM Section 1.1.1 +bool EntityReferenceImpl::childTypeAllowed( unsigned short type ) +{ + switch (type) { + case Node::ELEMENT_NODE: + case Node::PROCESSING_INSTRUCTION_NODE: + case Node::COMMENT_NODE: + case Node::TEXT_NODE: + case Node::CDATA_SECTION_NODE: + case Node::ENTITY_REFERENCE_NODE: + return true; + break; + default: + return false; + } +} + +DOMString EntityReferenceImpl::toString() const +{ + DOMString result = "&"; + result += m_entityName; + result += ";"; + + return result; +} + +// ------------------------------------------------------------------------- + +NotationImpl::NotationImpl(DocumentImpl *doc) : NodeBaseImpl(doc) +{ + m_publicId = 0; + m_systemId = 0; + m_name = 0; +} + +NotationImpl::NotationImpl(DocumentImpl *doc, DOMString _name, DOMString _publicId, DOMString _systemId) : NodeBaseImpl(doc) +{ + m_name = _name.implementation(); + if (m_name) + m_name->ref(); + m_publicId = _publicId.implementation(); + if (m_publicId) + m_publicId->ref(); + m_systemId = _systemId.implementation(); + if (m_systemId) + m_systemId->ref(); +} + +NotationImpl::~NotationImpl() +{ + if (m_name) + m_name->deref(); + if (m_publicId) + m_publicId->deref(); + if (m_systemId) + m_systemId->deref(); +} + +DOMString NotationImpl::publicId() const +{ + return m_publicId; +} + +DOMString NotationImpl::systemId() const +{ + return m_systemId; +} + +DOMString NotationImpl::nodeName() const +{ + return m_name; +} + +unsigned short NotationImpl::nodeType() const +{ + return Node::NOTATION_NODE; +} + +NodeImpl *NotationImpl::cloneNode ( bool /*deep*/) +{ + // Spec says cloning Document nodes is "implementation dependent" + // so we do not support it... + return 0; +} + +// DOM Section 1.1.1 +bool NotationImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +// ------------------------------------------------------------------------- + +// ### need a way of updating these properly whenever child nodes of the processing instruction +// change or are added/removed + +ProcessingInstructionImpl::ProcessingInstructionImpl(DocumentImpl *doc) : NodeBaseImpl(doc) +{ + m_target = 0; + m_data = 0; + m_localHref = 0; + m_sheet = 0; + m_cachedSheet = 0; +} + +ProcessingInstructionImpl::ProcessingInstructionImpl(DocumentImpl *doc, DOMString _target, DOMString _data) : NodeBaseImpl(doc) +{ + m_target = _target.implementation(); + if (m_target) + m_target->ref(); + m_data = _data.implementation(); + if (m_data) + m_data->ref(); + m_sheet = 0; + m_cachedSheet = 0; + m_localHref = 0; +} + +ProcessingInstructionImpl::~ProcessingInstructionImpl() +{ + if (m_target) + m_target->deref(); + if (m_data) + m_data->deref(); + if (m_cachedSheet) + m_cachedSheet->deref(this); + if (m_sheet) + m_sheet->deref(); +} + +DOMString ProcessingInstructionImpl::target() const +{ + return m_target; +} + +void ProcessingInstructionImpl::setData( const DOMString &_data, int &exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. + if (isReadOnly()) { + exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR; + return; + } + + if (m_data) + m_data->deref(); + m_data = _data.implementation(); + if (m_data) + m_data->ref(); +} + +DOMString ProcessingInstructionImpl::nodeName() const +{ + return m_target; +} + +unsigned short ProcessingInstructionImpl::nodeType() const +{ + return Node::PROCESSING_INSTRUCTION_NODE; +} + +DOMString ProcessingInstructionImpl::nodeValue() const +{ + return m_data; +} + +void ProcessingInstructionImpl::setNodeValue( const DOMString &_nodeValue, int &exceptioncode ) +{ + // NO_MODIFICATION_ALLOWED_ERR: taken care of by setData() + setData(_nodeValue, exceptioncode); +} + +NodeImpl *ProcessingInstructionImpl::cloneNode ( bool /*deep*/) +{ + // ### copy m_localHref + return new ProcessingInstructionImpl(docPtr(),m_target,m_data); +} + +DOMString ProcessingInstructionImpl::localHref() const +{ + return m_localHref; +} + +// DOM Section 1.1.1 +bool ProcessingInstructionImpl::childTypeAllowed( unsigned short /*type*/ ) +{ + return false; +} + +void ProcessingInstructionImpl::checkStyleSheet() +{ + if (m_target && DOMString(m_target) == "xml-stylesheet") { + // see http://www.w3.org/TR/xml-stylesheet/ + // ### check that this occurs only in the prolog + // ### support stylesheet included in a fragment of this (or another) document + // ### make sure this gets called when adding from javascript + XMLAttributeReader attrReader(DOMString(m_data).string()); + bool attrsOk; + QXmlAttributes attrs = attrReader.readAttrs(attrsOk); + if (!attrsOk) + return; + if (attrs.value("type") != "text/css" && !attrs.value("type").isEmpty()) + return; + + DOMString href = attrs.value("href"); + + if (href.length()>1) + { + if (href[0]=='#') + { + if (m_localHref) + m_localHref->deref(); + m_localHref=href.implementation()->split(1); + if (m_localHref) + m_localHref->ref(); + } + else + { + // ### some validation on the URL? + // ### FIXME charset + if (m_cachedSheet) m_cachedSheet->deref(this); + m_cachedSheet = getDocument()->docLoader()->requestStyleSheet(getDocument()->completeURL(href.string()), QString::null); + if (m_cachedSheet) { + getDocument()->addPendingSheet(); //before ref, because during the ref it might load! + m_cachedSheet->ref( this ); + } + } + + } + } +} + +StyleSheetImpl *ProcessingInstructionImpl::sheet() const +{ + return m_sheet; +} + +void ProcessingInstructionImpl::setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheet, const DOM::DOMString &charset) +{ + if (m_sheet) + m_sheet->deref(); + m_sheet = new CSSStyleSheetImpl(getDocument(), url); + m_sheet->ref(); + m_sheet->setCharset(charset); + m_sheet->parseString(sheet); + if (m_cachedSheet) + m_cachedSheet->deref(this); + m_cachedSheet = 0; + + getDocument()->styleSheetLoaded(); +} + +void ProcessingInstructionImpl::setStyleSheet(CSSStyleSheetImpl* sheet) +{ + if (m_sheet) + m_sheet->deref(); + m_sheet = sheet; + if (m_sheet) + m_sheet->ref(); +} + +DOMString ProcessingInstructionImpl::toString() const +{ + DOMString result = "<?"; + result += m_target; + result += " "; + result += m_data; + result += ">"; + return result; +} + +// ------------------------------------------------------------------------- + +XMLAttributeReader::XMLAttributeReader(const QString& _attrString) +{ + m_attrString = _attrString; +} + +XMLAttributeReader::~XMLAttributeReader() +{ +} + +QXmlAttributes XMLAttributeReader::readAttrs(bool &ok) +{ + // parse xml file + QXmlInputSource source; + source.setData("<?xml version=\"1.0\"?><attrs "+m_attrString+" />"); + QXmlSimpleReader reader; + reader.setContentHandler( this ); + ok = reader.parse( source ); + return attrs; +} + +bool XMLAttributeReader::startElement(const QString& /*namespaceURI*/, const QString& localName, + const QString& /*qName*/, const QXmlAttributes& atts) +{ + if (localName == "attrs") { + attrs = atts; + return true; + } + else + return false; // we shouldn't have any other elements +} diff --git a/khtml/xml/dom_xmlimpl.h b/khtml/xml/dom_xmlimpl.h new file mode 100644 index 000000000..6fa125af7 --- /dev/null +++ b/khtml/xml/dom_xmlimpl.h @@ -0,0 +1,181 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Peter Kelly (pmk@post.com) + * + * 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. + * + */ + +#ifndef _DOM_XmlImpl_h_ +#define _DOM_XmlImpl_h_ + +#include "xml/dom_nodeimpl.h" +#include "misc/loader_client.h" + +#include <qxml.h> + +namespace khtml { +class CachedCSSStyleSheet; +} + +namespace DOM { + +class DocumentImpl; +class CSSStyleSheetImpl; +class StyleSheetImpl; +class DOMString; + +class EntityImpl : public NodeBaseImpl +{ +public: + EntityImpl(DocumentImpl *doc); + EntityImpl(DocumentImpl *doc, DOMString _name); + EntityImpl(DocumentImpl *doc, DOMString _publicId, DOMString _systemId, DOMString _notationName); + virtual ~EntityImpl(); + + // DOM methods & attributes for Entity + + virtual DOMString publicId() const; + virtual DOMString systemId() const; + virtual DOMString notationName() const; + + // DOM methods overridden from parent classes + + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + + virtual bool childTypeAllowed( unsigned short type ); + + virtual DOMString toString() const; + +protected: + DOMStringImpl *m_publicId; + DOMStringImpl *m_systemId; + DOMStringImpl *m_notationName; + DOMStringImpl *m_name; +}; + + +class EntityReferenceImpl : public NodeBaseImpl +{ +public: + EntityReferenceImpl(DocumentImpl *doc); + EntityReferenceImpl(DocumentImpl *doc, DOMStringImpl *_entityName); + virtual ~EntityReferenceImpl(); + + // DOM methods overridden from parent classes + + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + + virtual bool childTypeAllowed( unsigned short type ); + + virtual DOMString toString() const; +protected: + DOMStringImpl *m_entityName; +}; + +class NotationImpl : public NodeBaseImpl +{ +public: + NotationImpl(DocumentImpl *doc); + NotationImpl(DocumentImpl *doc, DOMString _name, DOMString _publicId, DOMString _systemId); + virtual ~NotationImpl(); + + // DOM methods & attributes for Notation + + virtual DOMString publicId() const; + virtual DOMString systemId() const; + + // DOM methods overridden from parent classes + + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + + virtual bool childTypeAllowed( unsigned short type ); +protected: + DOMStringImpl *m_name; + DOMStringImpl *m_publicId; + DOMStringImpl *m_systemId; +}; + + +class ProcessingInstructionImpl : public NodeBaseImpl, private khtml::CachedObjectClient +{ +public: + ProcessingInstructionImpl(DocumentImpl *doc); + ProcessingInstructionImpl(DocumentImpl *doc, DOMString _target, DOMString _data); + virtual ~ProcessingInstructionImpl(); + + // DOM methods & attributes for Notation + + virtual DOMString target() const; + DOMString data() const { return m_data; } + virtual void setData( const DOMString &_data, int &exceptioncode ); + + // DOM methods overridden from parent classes + + virtual DOMString nodeName() const; + virtual unsigned short nodeType() const; + virtual DOMString nodeValue() const; + virtual void setNodeValue( const DOMString &_nodeValue, int &exceptioncode ); + virtual NodeImpl *cloneNode ( bool deep ); + + // Other methods (not part of DOM) + + virtual DOMString localHref() const; + virtual bool childTypeAllowed( unsigned short type ); + StyleSheetImpl *sheet() const; + void checkStyleSheet(); + virtual void setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheet, const DOM::DOMString &charset); + virtual void setStyleSheet(CSSStyleSheetImpl* sheet); + + virtual DOMString toString() const; + +protected: + DOMStringImpl *m_target; + DOMStringImpl *m_data; + DOMStringImpl *m_localHref; + khtml::CachedCSSStyleSheet *m_cachedSheet; + CSSStyleSheetImpl *m_sheet; +}; + +class XMLAttributeReader : public QXmlDefaultHandler +{ +public: + XMLAttributeReader(const QString& _attrString); + virtual ~XMLAttributeReader(); + QXmlAttributes readAttrs(bool &ok); + bool startElement(const QString& namespaceURI, const QString& localName, const QString& qName, const QXmlAttributes& atts); + +protected: + QXmlAttributes attrs; + QString m_attrString; +}; + +} //namespace + +#endif diff --git a/khtml/xml/xml_tokenizer.cpp b/khtml/xml/xml_tokenizer.cpp new file mode 100644 index 000000000..5489b8c21 --- /dev/null +++ b/khtml/xml/xml_tokenizer.cpp @@ -0,0 +1,609 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Peter Kelly (pmk@post.com) + * Copyright (C) 2003 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. + */ + + +#include "xml_tokenizer.h" +#include "xml/dom_docimpl.h" +#include "xml/dom_textimpl.h" +#include "xml/dom_xmlimpl.h" +#include "html/html_tableimpl.h" +#include "html/html_headimpl.h" +#include "rendering/render_object.h" +#include "misc/htmltags.h" +#include "misc/htmlattrs.h" +#include "misc/loader.h" + +#include "khtmlview.h" +#include "khtml_part.h" +#include <qvariant.h> +#include <kdebug.h> +#include <klocale.h> + +using namespace DOM; +using namespace khtml; + +XMLIncrementalSource::XMLIncrementalSource() + : QXmlInputSource(), m_pos( 0 ), m_unicode( 0 ), + m_finished( false ) +{ +} + +void XMLIncrementalSource::fetchData() +{ + //just a dummy to overwrite default behavior +} + +QChar XMLIncrementalSource::next() +{ + if ( m_finished ) + return QXmlInputSource::EndOfDocument; + else if ( m_data.length() <= m_pos ) + return QXmlInputSource::EndOfData; + else + return m_unicode[m_pos++]; +} + +void XMLIncrementalSource::setData( const QString& str ) +{ + m_data = str; + m_unicode = m_data.unicode(); + m_pos = 0; + if ( !str.isEmpty() ) + m_finished = false; +} +void XMLIncrementalSource::setData( const QByteArray& data ) +{ + setData( fromRawData( data, true ) ); +} + +void XMLIncrementalSource::appendXML( const QString& str ) +{ + m_data += str; + m_unicode = m_data.unicode(); +} + +QString XMLIncrementalSource::data() +{ + return m_data; +} + +void XMLIncrementalSource::setFinished( bool finished ) +{ + m_finished = finished; +} + +XMLHandler::XMLHandler(DocumentImpl *_doc, KHTMLView *_view) + : errorLine(0) +{ + m_doc = _doc; + m_view = _view; + pushNode( _doc ); +} + +XMLHandler::~XMLHandler() +{ +} + +void XMLHandler::pushNode( NodeImpl *node ) +{ + m_nodes.push( node ); +} + +NodeImpl *XMLHandler::popNode() +{ + return m_nodes.pop(); +} + +NodeImpl *XMLHandler::currentNode() const +{ + return m_nodes.current(); +} + +QString XMLHandler::errorProtocol() +{ + return errorProt; +} + + +bool XMLHandler::startDocument() +{ + // at the beginning of parsing: do some initialization + errorProt = ""; + state = StateInit; + + return true; +} + +bool XMLHandler::startPrefixMapping(const QString& prefix, const QString& uri) +{ + namespaceInfo[prefix].push(uri); + return true; +} + +bool XMLHandler::endPrefixMapping(const QString& prefix) +{ + QValueStack<QString>& stack = namespaceInfo[prefix]; + stack.pop(); + if (stack.isEmpty()) + namespaceInfo.remove(prefix); + return true; +} + +void XMLHandler::fixUpNSURI(QString& uri, const QString& qname) +{ + /* QXml does not resolve the namespaces of attributes in the same + tag that preceed the xmlns declaration. This fixes up that case */ + if (uri.isEmpty() && qname.find(':') != -1) { + QXmlNamespaceSupport ns; + QString localName, prefix; + ns.splitName(qname, prefix, localName); + if (namespaceInfo.contains(prefix)) { + uri = namespaceInfo[prefix].top(); + } + } +} + +bool XMLHandler::startElement( const QString& namespaceURI, const QString& /*localName*/, + const QString& qName, const QXmlAttributes& atts ) +{ + if (currentNode()->nodeType() == Node::TEXT_NODE) + exitText(); + + DOMString nsURI; + if (!namespaceURI.isNull()) + nsURI = DOMString(namespaceURI); + else + // No namespace declared, default to the no namespace + nsURI = DOMString(""); + ElementImpl *newElement = m_doc->createElementNS(nsURI,qName); + if (!newElement) + return false; + int i; + for (i = 0; i < atts.length(); i++) { + int exceptioncode = 0; + QString uriString = atts.uri(i); + QString qnString = atts.qName(i); + fixUpNSURI(uriString, qnString); + DOMString uri(uriString); + DOMString qn(qnString); + DOMString val(atts.value(i)); + newElement->setAttributeNS(uri, qn, val, exceptioncode); + if (exceptioncode) // exception setting attributes + return false; + } + + if (newElement->id() == ID_SCRIPT || newElement->id() == makeId(xhtmlNamespace, ID_SCRIPT)) + static_cast<HTMLScriptElementImpl *>(newElement)->setCreatedByParser(true); + + //this is tricky. in general the node doesn't have to attach to the one it's in. as far + //as standards go this is wrong, but there's literally thousands of documents where + //we see <p><ul>...</ul></p>. the following code is there for those cases. + //when we can't attach to the currently holding us node we try to attach to its parent + bool attached = false; + for ( NodeImpl *current = currentNode(); current; current = current->parent() ) { + attached = current->addChild( newElement ); + if ( attached ) + break; + } + if (attached) { + if (m_view && !newElement->attached() && !m_doc->hasPendingSheets()) + newElement->attach(); + pushNode( newElement ); + return true; + } + else { + delete newElement; + return false; + } + + // ### DOM spec states: "if there is no markup inside an element's content, the text is contained in a + // single object implementing the Text interface that is the only child of the element."... do we + // need to ensure that empty elements always have an empty text child? +} + + +bool XMLHandler::endElement( const QString& /*namespaceURI*/, const QString& /*localName*/, const QString& /*qName*/ ) +{ + if (currentNode()->nodeType() == Node::TEXT_NODE) + exitText(); + + NodeImpl *node = popNode(); + if ( node ) { + node->close(); + while ( currentNode() && currentNode()->implicitNode() ) //for the implicit HTMLTableSectionElementImpl + popNode()->close(); + } else + return false; + + return true; +} + + +bool XMLHandler::startCDATA() +{ + if (currentNode()->nodeType() == Node::TEXT_NODE) + exitText(); + + NodeImpl *newNode = m_doc->createCDATASection(new DOMStringImpl("")); + if (currentNode()->addChild(newNode)) { + if (m_view && !newNode->attached() && !m_doc->hasPendingSheets()) + newNode->attach(); + pushNode( newNode ); + return true; + } + else { + delete newNode; + return false; + } + +} + +bool XMLHandler::endCDATA() +{ + popNode(); + Q_ASSERT( currentNode() ); + return currentNode(); +} + +bool XMLHandler::characters( const QString& ch ) +{ + if (currentNode()->nodeType() == Node::TEXT_NODE || + currentNode()->nodeType() == Node::CDATA_SECTION_NODE || + enterText()) { + int exceptioncode = 0; + static_cast<TextImpl*>(currentNode())->appendData(ch,exceptioncode); + if (exceptioncode) + return false; + return true; + } + else { + // Don't worry about white-space violating DTD + if (ch.stripWhiteSpace().isEmpty()) return true; + + return false; + } + +} + +bool XMLHandler::comment(const QString & ch) +{ + if (currentNode()->nodeType() == Node::TEXT_NODE) + exitText(); + // ### handle exceptions + currentNode()->addChild(m_doc->createComment(new DOMStringImpl(ch.unicode(), ch.length()))); + return true; +} + +bool XMLHandler::processingInstruction(const QString &target, const QString &data) +{ + if (currentNode()->nodeType() == Node::TEXT_NODE) + exitText(); + // ### handle exceptions + ProcessingInstructionImpl *pi = + m_doc->createProcessingInstruction(target, new DOMStringImpl(data.unicode(), data.length())); + currentNode()->addChild(pi); + pi->checkStyleSheet(); + return true; +} + + +QString XMLHandler::errorString() +{ + // ### Make better error-messages + return i18n("the document is not in the correct file format"); +} + + +bool XMLHandler::fatalError( const QXmlParseException& exception ) +{ + errorProt += i18n( "fatal parsing error: %1 in line %2, column %3" ) + .arg( exception.message() ) + .arg( exception.lineNumber() ) + .arg( exception.columnNumber() ); + + errorLine = exception.lineNumber(); + errorCol = exception.columnNumber(); + + return false; +} + +bool XMLHandler::enterText() +{ + NodeImpl *newNode = m_doc->createTextNode(""); + if (currentNode()->addChild(newNode)) { + pushNode( newNode ); + return true; + } + else { + delete newNode; + return false; + } +} + +void XMLHandler::exitText() +{ + if ( m_view && !currentNode()->attached() && !m_doc->hasPendingSheets() ) + currentNode()->attach(); + popNode(); +} + +bool XMLHandler::attributeDecl(const QString &/*eName*/, const QString &/*aName*/, const QString &/*type*/, + const QString &/*valueDefault*/, const QString &/*value*/) +{ + // qt's xml parser (as of 2.2.3) does not currently give us values for type, valueDefault and + // value. When it does, we can store these somewhere and have default attributes on elements + return true; +} + +bool XMLHandler::externalEntityDecl(const QString &/*name*/, const QString &/*publicId*/, const QString &/*systemId*/) +{ + // ### insert these too - is there anything special we have to do here? + return true; +} + +bool XMLHandler::internalEntityDecl(const QString &name, const QString &value) +{ + EntityImpl *e = new EntityImpl(m_doc,name); + // ### further parse entities inside the value and add them as separate nodes (or entityreferences)? + e->addChild(m_doc->createTextNode(new DOMStringImpl(value.unicode(), value.length()))); + if (m_doc->doctype()) + static_cast<GenericRONamedNodeMapImpl*>(m_doc->doctype()->entities())->addNode(e); + return true; +} + +bool XMLHandler::notationDecl(const QString &/*name*/, const QString &/*publicId*/, const QString &/*systemId*/) +{ +// ### FIXME +// if (m_doc->document()->doctype()) { +// NotationImpl *n = new NotationImpl(m_doc,name,publicId,systemId); +// static_cast<GenericRONamedNodeMapImpl*>(m_doc->document()->doctype()->notations())->addNode(n); +// } + return true; +} + +bool XMLHandler::unparsedEntityDecl(const QString &/*name*/, const QString &/*publicId*/, + const QString &/*systemId*/, const QString &/*notationName*/) +{ + // ### + return true; +} + + +//------------------------------------------------------------------------------ + +XMLTokenizer::XMLTokenizer(DOM::DocumentImpl *_doc, KHTMLView *_view) + : m_handler(_doc,_view) +{ + m_doc = _doc; + m_view = _view; + m_scriptsIt = 0; + m_cachedScript = 0; + m_noErrors = true; + m_reader.setContentHandler( &m_handler ); + m_reader.setLexicalHandler( &m_handler ); + m_reader.setErrorHandler( &m_handler ); + m_reader.setDeclHandler( &m_handler ); + m_reader.setDTDHandler( &m_handler ); + m_reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); +} + +XMLTokenizer::~XMLTokenizer() +{ + if (m_scriptsIt) + delete m_scriptsIt; + if (m_cachedScript) + m_cachedScript->deref(this); +} + + +void XMLTokenizer::begin() +{ + // parse xml file + m_reader.parse( &m_source, true ); +} + +void XMLTokenizer::write( const TokenizerString &str, bool appendData ) +{ + if ( !m_noErrors && appendData ) + return; + if ( appendData ) { + m_source.appendXML( str.toString() ); + + } else { + m_source.setData( str.toString() ); + } + m_noErrors = m_reader.parseContinue(); +} + +void XMLTokenizer::end() +{ + m_source.setFinished( true ); + //if ( m_noErrors ) + //m_noErrors = m_reader.parseContinue(); + emit finishedParsing(); +} + +void XMLTokenizer::finish() +{ + m_source.setFinished( true ); + if (!m_noErrors) { + // An error occurred during parsing of the code. Display an error page to the user (the DOM + // tree is created manually and includes an excerpt from the code where the error is located) + + // ### for multiple error messages, display the code for each (can this happen?) + + // Clear the document + int exceptioncode = 0; + while (m_doc->hasChildNodes()) + static_cast<NodeImpl*>(m_doc)->removeChild(m_doc->firstChild(),exceptioncode); + + QString line, errorLocPtr; + if ( m_handler.errorLine ) { + QString xmlCode = m_source.data(); + QTextIStream stream(&xmlCode); + for (unsigned long lineno = 0; lineno < m_handler.errorLine-1; lineno++) + stream.readLine(); + line = stream.readLine(); + + for (unsigned long colno = 0; colno < m_handler.errorCol-1; colno++) + errorLocPtr += " "; + errorLocPtr += "^"; + } + + // Create elements for display + DocumentImpl *doc = m_doc; + NodeImpl *html = doc->createElementNS(XHTML_NAMESPACE,"html"); + NodeImpl *body = doc->createElementNS(XHTML_NAMESPACE,"body"); + NodeImpl *h1 = doc->createElementNS(XHTML_NAMESPACE,"h1"); + NodeImpl *headingText = doc->createTextNode(i18n("XML parsing error")); + NodeImpl *errorText = doc->createTextNode(m_handler.errorProtocol()); + NodeImpl *hr = 0; + NodeImpl *pre = 0; + NodeImpl *lineText = 0; + NodeImpl *errorLocText = 0; + if ( !line.isNull() ) { + hr = doc->createElementNS(XHTML_NAMESPACE,"hr"); + pre = doc->createElementNS(XHTML_NAMESPACE,"pre"); + lineText = doc->createTextNode(line+"\n"); + errorLocText = doc->createTextNode(errorLocPtr); + } + + // Construct DOM tree. We ignore exceptions as we assume they will not be thrown here (due to the + // fact we are using a known tag set) + doc->appendChild(html,exceptioncode); + html->appendChild(body,exceptioncode); + if ( body ) + body->appendChild(h1,exceptioncode); + h1->appendChild(headingText,exceptioncode); + body->appendChild(errorText,exceptioncode); + body->appendChild(hr,exceptioncode); + body->appendChild(pre,exceptioncode); + if ( pre ) { + pre->appendChild(lineText,exceptioncode); + pre->appendChild(errorLocText,exceptioncode); + } + + // Close the renderers so that they update their display correctly + // ### this should not be necessary, but requires changes in the rendering code... + h1->close(); + if ( pre ) pre->close(); + body->close(); + + m_doc->recalcStyle( NodeImpl::Inherit ); + m_doc->updateRendering(); + + end(); + } + else { + // Parsing was successful. Now locate all html <script> tags in the document and execute them + // one by one + addScripts(m_doc); + m_scriptsIt = new QPtrListIterator<HTMLScriptElementImpl>(m_scripts); + executeScripts(); + } + +} + +void XMLTokenizer::addScripts(NodeImpl *n) +{ + // Recursively go through the entire document tree, looking for html <script> tags. For each of these + // that is found, add it to the m_scripts list from which they will be executed + + if (n->id() == ID_SCRIPT || n->id() == makeId(xhtmlNamespace, ID_SCRIPT)) { + m_scripts.append(static_cast<HTMLScriptElementImpl*>(n)); + } + + NodeImpl *child; + for (child = n->firstChild(); child; child = child->nextSibling()) + addScripts(child); +} + +void XMLTokenizer::executeScripts() +{ + // Iterate through all of the html <script> tags in the document. For those that have a src attribute, + // start loading the script and return (executeScripts() will be called again once the script is loaded + // and continue where it left off). For scripts that don't have a src attribute, execute the code + // inside the tag + while (m_scriptsIt->current()) { + DOMString scriptSrc = m_scriptsIt->current()->getAttribute(ATTR_SRC); + QString charset = m_scriptsIt->current()->getAttribute(ATTR_CHARSET).string(); + + if (!scriptSrc.isEmpty()) { + // we have a src attribute + m_cachedScript = m_doc->docLoader()->requestScript(scriptSrc, charset); + ++(*m_scriptsIt); + if (m_cachedScript) { + m_cachedScript->ref(this); // will call executeScripts() again if already cached + return; + } + } + else { + // no src attribute - execute from contents of tag + QString scriptCode = ""; + NodeImpl *child; + for (child = m_scriptsIt->current()->firstChild(); child; child = child->nextSibling()) { + if ( ( child->nodeType() == Node::TEXT_NODE || child->nodeType() == Node::CDATA_SECTION_NODE) && + static_cast<TextImpl*>(child)->string() ) + scriptCode += QConstString(static_cast<TextImpl*>(child)->string()->s, + static_cast<TextImpl*>(child)->string()->l).string(); + } + // the script cannot do document.write until we support incremental parsing + // ### handle the case where the script deletes the node or redirects to + // another page, etc. (also in notifyFinished()) + // ### the script may add another script node after this one which should be executed + if (m_view) { + m_view->part()->executeScript(DOM::Node(), scriptCode); + } + ++(*m_scriptsIt); + } + } + + // All scripts have finished executing, so calculate the style for the document and close + // the last element + m_doc->updateStyleSelector(); + + // We are now finished parsing + end(); +} + +void XMLTokenizer::notifyFinished(CachedObject *finishedObj) +{ + // This is called when a script has finished loading that was requested from executeScripts(). We execute + // the script, and then call executeScripts() again to continue iterating through the list of scripts in + // the document + if (finishedObj == m_cachedScript) { + DOMString scriptSource = m_cachedScript->script(); + m_cachedScript->deref(this); + m_cachedScript = 0; + if (m_view) + m_view->part()->executeScript(DOM::Node(), scriptSource.string()); + executeScripts(); + } +} + +bool XMLTokenizer::isWaitingForScripts() const +{ + return m_cachedScript != 0; +} + +#include "xml_tokenizer.moc" + diff --git a/khtml/xml/xml_tokenizer.h b/khtml/xml/xml_tokenizer.h new file mode 100644 index 000000000..4042b905f --- /dev/null +++ b/khtml/xml/xml_tokenizer.h @@ -0,0 +1,198 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Peter Kelly (pmk@post.com) + * + * 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. + * + */ + +#ifndef _XML_Tokenizer_h_ +#define _XML_Tokenizer_h_ + +#include <qxml.h> +#include <qptrlist.h> +#include <qptrstack.h> +#include <qvaluestack.h> +#include <qobject.h> +#include "misc/loader_client.h" +#include "misc/stringit.h" + +class KHTMLView; + +namespace khtml { + class CachedObject; + class CachedScript; +} + +namespace DOM { + class DocumentImpl; + class NodeImpl; + class HTMLScriptElementImpl; + class DocumentImpl; + class HTMLScriptElementImpl; +} + +namespace khtml { + +class XMLHandler : public QXmlDefaultHandler +{ +public: + XMLHandler(DOM::DocumentImpl *_doc, KHTMLView *_view); + virtual ~XMLHandler(); + + // return the error protocol if parsing failed + QString errorProtocol(); + + // overloaded handler functions + bool startDocument(); + bool startElement(const QString& namespaceURI, const QString& localName, const QString& qName, const QXmlAttributes& atts); + bool endElement(const QString& namespaceURI, const QString& localName, const QString& qName); + bool startCDATA(); + bool endCDATA(); + bool characters(const QString& ch); + bool comment(const QString & ch); + bool processingInstruction(const QString &target, const QString &data); + + // namespace handling, to workaround problem in QXML where some attributes + // do not get the namespace resolved properly + bool startPrefixMapping(const QString& prefix, const QString& uri); + bool endPrefixMapping(const QString& prefix); + void fixUpNSURI(QString& uri, const QString& qname); + QMap<QString, QValueStack<QString> > namespaceInfo; + + + // from QXmlDeclHandler + bool attributeDecl(const QString &eName, const QString &aName, const QString &type, const QString &valueDefault, const QString &value); + bool externalEntityDecl(const QString &name, const QString &publicId, const QString &systemId); + bool internalEntityDecl(const QString &name, const QString &value); + + // from QXmlDTDHandler + bool notationDecl(const QString &name, const QString &publicId, const QString &systemId); + bool unparsedEntityDecl(const QString &name, const QString &publicId, const QString &systemId, const QString ¬ationName); + + bool enterText(); + void exitText(); + + QString errorString(); + + bool fatalError( const QXmlParseException& exception ); + + unsigned long errorLine; + unsigned long errorCol; + +private: + void pushNode( DOM::NodeImpl *node ); + DOM::NodeImpl *popNode(); + DOM::NodeImpl *currentNode() const; +private: + QString errorProt; + DOM::DocumentImpl *m_doc; + KHTMLView *m_view; + QPtrStack<DOM::NodeImpl> m_nodes; + DOM::NodeImpl *m_rootNode; + + enum State { + StateInit, + StateDocument, + StateQuote, + StateLine, + StateHeading, + StateP + }; + State state; +}; + +class Tokenizer : public QObject +{ + Q_OBJECT +public: + virtual void begin() = 0; + // script output must be prepended, while new data + // received during executing a script must be appended, hence the + // extra bool to be able to distinguish between both cases. document.write() + // always uses false, while khtmlpart uses true + virtual void write( const TokenizerString &str, bool appendData) = 0; + virtual void end() = 0; + virtual void finish() = 0; + virtual void setOnHold(bool /*_onHold*/) {} + virtual bool isWaitingForScripts() const = 0; + virtual bool isExecutingScript() const = 0; + virtual void abort() {} + virtual void setAutoClose(bool b=true) = 0; + +signals: + void finishedParsing(); + +}; + +class XMLIncrementalSource : public QXmlInputSource +{ +public: + XMLIncrementalSource(); + virtual void fetchData(); + virtual QChar next(); + virtual void setData( const QString& str ); + virtual void setData( const QByteArray& data ); + virtual QString data(); + + void appendXML( const QString& str ); + void setFinished( bool ); + +private: + QString m_data; + uint m_pos; + const QChar *m_unicode; + bool m_finished; +}; + +class XMLTokenizer : public Tokenizer, public khtml::CachedObjectClient +{ +public: + XMLTokenizer(DOM::DocumentImpl *, KHTMLView * = 0); + virtual ~XMLTokenizer(); + virtual void begin(); + virtual void write( const TokenizerString &str, bool ); + virtual void end(); + virtual void finish(); + virtual void setAutoClose(bool b=true) { qWarning("XMLTokenizer::setAutoClose: stub."); (void)b; } + + // from CachedObjectClient + void notifyFinished(khtml::CachedObject *finishedObj); + + virtual bool isWaitingForScripts() const; + virtual bool isExecutingScript() const { return false; } + +protected: + DOM::DocumentImpl *m_doc; + KHTMLView *m_view; + + void executeScripts(); + void addScripts(DOM::NodeImpl *n); + + QPtrList<DOM::HTMLScriptElementImpl> m_scripts; + QPtrListIterator<DOM::HTMLScriptElementImpl> *m_scriptsIt; + khtml::CachedScript *m_cachedScript; + + XMLHandler m_handler; + QXmlSimpleReader m_reader; + XMLIncrementalSource m_source; + bool m_noErrors; +}; + +} // end namespace + +#endif |