/** * 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(m_startContainer->cloneNode(true)); c->deleteData(m_endOffset,static_cast(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(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(m_startContainer->cloneNode(true)); c->deleteData(0,m_startOffset,exceptioncode); leftContents = c; } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) static_cast(m_startContainer)->deleteData( m_startOffset,static_cast(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(m_endContainer->cloneNode(true)); c->deleteData(m_endOffset,static_cast(m_endContainer)->length()-m_endOffset,exceptioncode); rightContents = c; } if (action == EXTRACT_CONTENTS || action == DELETE_CONTENTS) static_cast(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(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(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 otherstuff */ if(n->nodeType() == DOM::Node::ELEMENT_NODE) { int elementId = static_cast(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

etc. Just skip these nodes just to make the output html a bit nicer. text += static_cast(n)->openTagStartToString(true /*safely expand img urls*/); // adds "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 += "(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 how are you? " //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 += "(n)->tagName(); int elementId = static_cast(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 Mr. John How are you?" and we select "John" or even //"John How" and copy. We want to return "John" and "John 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

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. (

) // // 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(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(n)->openTagStartToString(true /*expand img urls*/)+">" +text; // prepends "" } 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(n)->id()) { case ID_TABLE: num_tables++; break; case ID_OL: case ID_UL: in_li=false; break; } text += "(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: "hello" and we select "ll", then we want to go down the tree and // add "" and "" to it, to produce "ll". // // 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(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(n)->openTagStartToString(true /*expand img urls*/)+">" +text; text += "(n)->tagName(); text += ">"; } break; case ID_LI: if(!in_li) break; text = static_cast(n)->openTagStartToString(true /*expand img urls*/)+">" +text; text += "(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(n)->openTagStartToString(true /*expand img urls*/)+">" +text; text += "(n)->tagName(); text += ">"; break; } } } } if(!hasBodyTag) text = DOMString("") + text + ""; else if(!hasHtmlTag) { text = DOMString("\n" "\n" "\n" "\n" "\n") + text + ""; } text = DOMString("\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(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(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(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; }