KHtml

dom_textimpl.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll ([email protected])
5  * (C) 2001-2003 Dirk Mueller ([email protected])
6  * (C) 1999 Antti Koivisto ([email protected])
7  * (C) 2002-2003 Apple Computer, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB. If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #include "dom_textimpl.h"
26 #include "dom2_eventsimpl.h"
27 #include "dom_docimpl.h"
28 #include <dom/dom_exception.h>
29 #include <css/cssstyleselector.h>
30 
31 #include <rendering/render_text.h>
32 #include <rendering/render_flow.h>
33 #include <wtf/PassRefPtr.h>
34 #include <wtf/RefPtr.h>
35 
36 #include "khtml_debug.h"
37 
38 // for SVG
39 #include <rendering/RenderSVGInlineText.h>
40 
41 using namespace DOM;
42 using namespace khtml;
43 
44 static DOMString escapeHTML(const DOMString &in)
45 {
46  return in.implementation()->escapeHTML();
47 }
48 
49 CharacterDataImpl::CharacterDataImpl(DocumentImpl *doc, DOMStringImpl *_text)
50  : NodeImpl(doc)
51 {
52  str = _text ? _text : new DOMStringImpl((QChar *)nullptr, 0);
53  str->ref();
54 }
55 
56 CharacterDataImpl::~CharacterDataImpl()
57 {
58  if (str) {
59  str->deref();
60  }
61 }
62 
63 void CharacterDataImpl::setData(const DOMString &_data, int &exceptioncode)
64 {
65  // NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly
66  if (isReadOnly()) {
67  exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
68  return;
69  }
70 
71  if (str == _data.impl) {
72  return; // ### fire DOMCharacterDataModified if modified?
73  }
74  DOMStringImpl *oldStr = str;
75  str = _data.impl;
76  if (!str) {
77  str = new DOMStringImpl((QChar *)nullptr, 0);
78  }
79  str->ref();
80  if (m_render) {
81  (static_cast<RenderText *>(m_render))->setText(str);
82  }
83  setChanged(true);
84 
85  dispatchModifiedEvent(oldStr);
86  if (oldStr) {
87  oldStr->deref();
88  }
89 }
90 
91 unsigned long CharacterDataImpl::length() const
92 {
93  return str->l;
94 }
95 
96 DOMString CharacterDataImpl::substringData(const unsigned long offset, const unsigned long count, int &exceptioncode)
97 {
98  exceptioncode = 0;
99  if ((long)count < 0) {
100  exceptioncode = DOMException::INDEX_SIZE_ERR;
101  } else {
102  checkCharDataOperation(offset, exceptioncode);
103  }
104  if (exceptioncode) {
105  return DOMString();
106  }
107 
108  return str->substring(offset, count);
109 }
110 
111 void CharacterDataImpl::appendData(const DOMString &arg, int &exceptioncode)
112 {
113  exceptioncode = 0;
114 
115  // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly
116  if (isReadOnly()) {
117  exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
118  return;
119  }
120 
121  DOMStringImpl *oldStr = str;
122  str = str->copy();
123  str->ref();
124  str->append(arg.impl);
125  if (m_render) {
126  (static_cast<RenderText *>(m_render))->setText(str);
127  }
128  setChanged(true);
129 
130  dispatchModifiedEvent(oldStr);
131  oldStr->deref();
132 }
133 
134 void CharacterDataImpl::insertData(const unsigned long offset, const DOMString &arg, int &exceptioncode)
135 {
136  exceptioncode = 0;
137  checkCharDataOperation(offset, exceptioncode);
138  if (exceptioncode) {
139  return;
140  }
141 
142  DOMStringImpl *oldStr = str;
143  str = str->copy();
144  str->ref();
145  str->insert(arg.impl, offset);
146  if (m_render) {
147  (static_cast<RenderText *>(m_render))->setText(str);
148  }
149  setChanged(true);
150 
151  dispatchModifiedEvent(oldStr);
152  oldStr->deref();
153 }
154 
155 void CharacterDataImpl::deleteData(const unsigned long offset, const unsigned long count, int &exceptioncode)
156 {
157  exceptioncode = 0;
158  if ((long)count < 0) {
159  exceptioncode = DOMException::INDEX_SIZE_ERR;
160  } else {
161  checkCharDataOperation(offset, exceptioncode);
162  }
163  if (exceptioncode) {
164  return;
165  }
166 
167  DOMStringImpl *oldStr = str;
168  str = str->copy();
169  str->ref();
170  str->remove(offset, count);
171  if (m_render) {
172  (static_cast<RenderText *>(m_render))->setText(str);
173  }
174  setChanged(true);
175 
176  dispatchModifiedEvent(oldStr);
177  oldStr->deref();
178 }
179 
180 void CharacterDataImpl::replaceData(const unsigned long offset, const unsigned long count, const DOMString &arg, int &exceptioncode)
181 {
182  exceptioncode = 0;
183  if ((long)count < 0) {
184  exceptioncode = DOMException::INDEX_SIZE_ERR;
185  } else {
186  checkCharDataOperation(offset, exceptioncode);
187  }
188  if (exceptioncode) {
189  return;
190  }
191 
192  unsigned long realCount;
193  if (offset + count > str->l) {
194  realCount = str->l - offset;
195  } else {
196  realCount = count;
197  }
198 
199  DOMStringImpl *oldStr = str;
200  str = str->copy();
201  str->ref();
202  str->remove(offset, realCount);
203  str->insert(arg.impl, offset);
204  if (m_render) {
205  (static_cast<RenderText *>(m_render))->setText(str);
206  }
207  setChanged(true);
208 
209  dispatchModifiedEvent(oldStr);
210  oldStr->deref();
211 }
212 
213 DOMString CharacterDataImpl::nodeValue() const
214 {
215  return str;
216 }
217 
218 bool CharacterDataImpl::containsOnlyWhitespace() const
219 {
220  return str->containsOnlyWhitespace();
221 }
222 
223 void CharacterDataImpl::setNodeValue(const DOMString &_nodeValue, int &exceptioncode)
224 {
225  // NO_MODIFICATION_ALLOWED_ERR: taken care of by setData()
226  setData(_nodeValue, exceptioncode);
227 }
228 
229 void CharacterDataImpl::dispatchModifiedEvent(DOMStringImpl *prevValue)
230 {
231  if (parentNode()) {
232  parentNode()->childrenChanged();
233  }
234  if ((str->length() == 0) != (prevValue->length() == 0)) {
235  // changes in emptiness triggers changes in :empty selector
236  if (parentNode() && parentNode()->isElementNode()) {
237  parentNode()->backwardsStructureChanged();
238  }
239  // ### to fully support dynamic changes to :contains selector
240  // backwardsStructureChanged should be called for all changes
241  }
242  if (!document()->hasListenerType(DocumentImpl::DOMCHARACTERDATAMODIFIED_LISTENER)) {
243  return;
244  }
245 
246  DOMStringImpl *newValue = str->copy();
247  newValue->ref();
248  int exceptioncode = 0;
249  MutationEventImpl *const evt = new MutationEventImpl(EventImpl::DOMCHARACTERDATAMODIFIED_EVENT, true, false, nullptr, prevValue, newValue, DOMString(), 0);
250  evt->ref();
251  dispatchEvent(evt, exceptioncode);
252  evt->deref();
253  newValue->deref();
254  dispatchSubtreeModifiedEvent();
255 }
256 
257 void CharacterDataImpl::checkCharDataOperation(const unsigned long offset, int &exceptioncode)
258 {
259  exceptioncode = 0;
260 
261  // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than the number of 16-bit
262  // units in data.
263  if (offset > str->l) {
264  exceptioncode = DOMException::INDEX_SIZE_ERR;
265  return;
266  }
267 
268  // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly
269  if (isReadOnly()) {
270  exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
271  return;
272  }
273 }
274 
275 long CharacterDataImpl::maxOffset() const
276 {
277  RenderText *r = static_cast<RenderText *>(renderer());
278  if (!r || !r->isText()) {
279  return 0;
280  }
281  return (long)length();
282 }
283 
284 long CharacterDataImpl::caretMinOffset() const
285 {
286  RenderText *r = static_cast<RenderText *>(renderer());
287  return r && r->isText() ? r->convertToDOMPosition(r->caretMinOffset()) : 0;
288 }
289 
290 long CharacterDataImpl::caretMaxOffset() const
291 {
292  RenderText *r = static_cast<RenderText *>(renderer());
293  return r && r->isText() ? r->convertToDOMPosition(r->caretMaxOffset()) : (long)length();
294 }
295 
296 unsigned long CharacterDataImpl::caretMaxRenderedOffset() const
297 {
298  RenderText *r = static_cast<RenderText *>(renderer());
299  return r ? r->caretMaxRenderedOffset() : length();
300 }
301 
302 bool CharacterDataImpl::rendererIsNeeded(khtml::RenderStyle *style)
303 {
304  if (!str || !str->l) {
305  return false;
306  }
307  return NodeImpl::rendererIsNeeded(style);
308 }
309 
310 // ---------------------------------------------------------------------------
311 
312 DOMString CommentImpl::nodeName() const
313 {
314  return "#comment";
315 }
316 
317 unsigned short CommentImpl::nodeType() const
318 {
319  return Node::COMMENT_NODE;
320 }
321 
322 WTF::PassRefPtr<NodeImpl> CommentImpl::cloneNode(bool /*deep*/)
323 {
324  return document()->createComment(str);
325 }
326 
327 NodeImpl::Id CommentImpl::id() const
328 {
329  return ID_COMMENT;
330 }
331 
332 // DOM Section 1.1.1
333 bool CommentImpl::childTypeAllowed(unsigned short /*type*/)
334 {
335  return false;
336 }
337 
338 DOMString CommentImpl::toString() const
339 {
340  // FIXME: substitute entity references as needed!
341  return DOMString("<!--") + nodeValue() + DOMString("-->");
342 }
343 
344 // ---------------------------------------------------------------------------
345 
346 TextImpl *TextImpl::splitText(const unsigned long offset, int &exceptioncode)
347 {
348  exceptioncode = 0;
349 
350  // INDEX_SIZE_ERR: Raised if the specified offset is negative or greater than
351  // the number of 16-bit units in data.
352 
353  // ### we explicitly check for a negative long that has been cast to an unsigned long
354  // ... this can happen if JS code passes in -1 - we need to catch this earlier! (in the
355  // kjs bindings)
356  if (offset > str->l || (long)offset < 0) {
357  exceptioncode = DOMException::INDEX_SIZE_ERR;
358  return nullptr;
359  }
360 
361  // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
362  if (isReadOnly()) {
363  exceptioncode = DOMException::NO_MODIFICATION_ALLOWED_ERR;
364  return nullptr;
365  }
366 
367  DOMStringImpl *oldStr = str;
368  TextImpl *newText = createNew(str->substring(offset, str->l - offset));
369  str = str->copy();
370  str->ref();
371  str->remove(offset, str->l - offset);
372 
373  dispatchModifiedEvent(oldStr);
374  oldStr->deref();
375 
376  if (parentNode()) {
377  parentNode()->insertBefore(newText, nextSibling(), exceptioncode);
378  }
379  if (exceptioncode) {
380  return nullptr;
381  }
382 
383  if (m_render) {
384  (static_cast<RenderText *>(m_render))->setText(str);
385  }
386  setChanged(true);
387  return newText;
388 }
389 
390 static const TextImpl *earliestLogicallyAdjacentTextNode(const TextImpl *t)
391 {
392  const NodeImpl *n = t;
393  while ((n = n->previousSibling())) {
394  unsigned short type = n->nodeType();
395  if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) {
396  t = static_cast<const TextImpl *>(n);
397  continue;
398  }
399 
400  // We would need to visit EntityReference child text nodes if they existed
401  assert(type != Node::ENTITY_REFERENCE_NODE || !n->hasChildNodes());
402  break;
403  }
404  return t;
405 }
406 
407 static const TextImpl *latestLogicallyAdjacentTextNode(const TextImpl *t)
408 {
409  const NodeImpl *n = t;
410  while ((n = n->nextSibling())) {
411  unsigned short type = n->nodeType();
412  if (type == Node::TEXT_NODE || type == Node::CDATA_SECTION_NODE) {
413  t = static_cast<const TextImpl *>(n);
414  continue;
415  }
416 
417  // We would need to visit EntityReference child text nodes if they existed
418  assert(type != Node::ENTITY_REFERENCE_NODE || !n->hasChildNodes());
419  break;
420  }
421  return t;
422 }
423 
424 DOMString TextImpl::wholeText() const
425 {
426  const TextImpl *startText = earliestLogicallyAdjacentTextNode(this);
427  const TextImpl *endText = latestLogicallyAdjacentTextNode(this);
428 
429  DOMString result;
430  NodeImpl *onePastEndText = endText->nextSibling();
431  for (const NodeImpl *n = startText; n != onePastEndText; n = n->nextSibling()) {
432  if (!n->isTextNode()) {
433  continue;
434  }
435  const TextImpl *t = static_cast<const TextImpl *>(n);
436  const DOMString &data = t->data();
437  result += data;
438  }
439 
440  return result;
441 }
442 
443 TextImpl *TextImpl::replaceWholeText(const DOMString &newText, int &ec)
444 {
445  Q_UNUSED(ec);
446  // We don't support "read-only" text nodes (no Entity node support)
447  // Thus, we remove all adjacent text nodes, and replace the contents of this one.
448  assert(!isReadOnly());
449  // This method only raises exceptions when dealing with Entity nodes (which we don't support)
450 
451  // Protect startText and endText against mutation event handlers removing the last ref
452  RefPtr<TextImpl> startText = const_cast<TextImpl *>(earliestLogicallyAdjacentTextNode(this));
453  RefPtr<TextImpl> endText = const_cast<TextImpl *>(latestLogicallyAdjacentTextNode(this));
454 
455  RefPtr<TextImpl> protectedThis(this); // Mutation event handlers could cause our last ref to go away
456  NodeImpl *parent = parentNode(); // Protect against mutation handlers moving this node during traversal
457  int ignored = 0;
458  for (RefPtr<NodeImpl> n = startText; n && n != this && n->isTextNode() && n->parentNode() == parent;) {
459  RefPtr<NodeImpl> nodeToRemove(n.release());
460  n = nodeToRemove->nextSibling();
461  parent->removeChild(nodeToRemove.get(), ignored);
462  }
463 
464  if (this != endText) {
465  NodeImpl *onePastEndText = endText->nextSibling();
466  for (RefPtr<NodeImpl> n = nextSibling(); n && n != onePastEndText && n->isTextNode() && n->parentNode() == parent;) {
467  RefPtr<NodeImpl> nodeToRemove(n.release());
468  n = nodeToRemove->nextSibling();
469  parent->removeChild(nodeToRemove.get(), ignored);
470  }
471  }
472 
473  if (newText.isEmpty()) {
474  if (parent && parentNode() == parent) {
475  parent->removeChild(this, ignored);
476  }
477  return nullptr;
478  }
479 
480  setData(newText, ignored);
481  return protectedThis.release().get();
482 }
483 
484 DOMString TextImpl::nodeName() const
485 {
486  return "#text";
487 }
488 
489 unsigned short TextImpl::nodeType() const
490 {
491  return Node::TEXT_NODE;
492 }
493 
494 WTF::PassRefPtr<NodeImpl> TextImpl::cloneNode(bool /*deep*/)
495 {
496  return document()->createTextNode(str);
497 }
498 
499 bool TextImpl::rendererIsNeeded(RenderStyle *style)
500 {
501  if (!CharacterDataImpl::rendererIsNeeded(style)) {
502  return false;
503  }
504  bool onlyWS = containsOnlyWhitespace();
505  if (!onlyWS) {
506  return true;
507  }
508 
509  RenderObject *par = parentNode()->renderer();
510 
511  if (par->isTable() || par->isTableRow() || par->isTableSection()) {
512  return false;
513  }
514 
515  if (style->preserveWS() || style->preserveLF()) {
516  return true;
517  }
518 
519  RenderObject *prev = previousRenderer();
520  if (par->isInlineFlow()) {
521  // <span><div/> <div/></span>
522  if (prev && !prev->isInline()) {
523  return false;
524  }
525  } else {
526  if (par->isRenderBlock() && !par->childrenInline() && (!prev || !prev->isInline())) {
527  return false;
528  }
529 
530  RenderObject *first = par->firstChild();
531  while (first && first->isFloatingOrPositioned()) {
532  first = first->nextSibling();
533  }
534  RenderObject *next = nextRenderer();
535  if (!first || next == first) {
536  // Whitespace at the start of a block just goes away. Don't even
537  // make a render object for this text.
538  return false;
539  }
540  }
541 
542  return true;
543 }
544 
545 RenderObject *TextImpl::createRenderer(RenderArena *arena, RenderStyle * /*style*/)
546 {
547  // for SVG
548  if (parentNode()->isSVGElement()) {
549  return new(arena) WebCore::RenderSVGInlineText(this, str);
550  }
551  return new(arena) RenderText(this, str);
552 }
553 
554 void TextImpl::attach()
555 {
556  createRendererIfNeeded();
557  CharacterDataImpl::attach();
558 }
559 
560 NodeImpl::Id TextImpl::id() const
561 {
562  return ID_TEXT;
563 }
564 
565 void TextImpl::recalcStyle(StyleChange change)
566 {
567 // qDebug("textImpl::recalcStyle");
568  // Create renderer if now needed
569  if (changed() && !m_render) {
570  createRendererIfNeeded();
571  }
572  if (change != NoChange && parentNode()) {
573 // qDebug("DomText::recalcStyle");
574  if (m_render) {
575  m_render->setStyle(parentNode()->renderer()->style());
576  }
577  }
578  if (changed() && m_render && m_render->isText()) {
579  static_cast<RenderText *>(m_render)->setText(str);
580  }
581  setChanged(false);
582 }
583 
584 // DOM Section 1.1.1
585 bool TextImpl::childTypeAllowed(unsigned short /*type*/)
586 {
587  return false;
588 }
589 
590 TextImpl *TextImpl::createNew(DOMStringImpl *_str)
591 {
592  return new TextImpl(docPtr(), _str);
593 }
594 
595 DOMStringImpl *TextImpl::renderString() const
596 {
597  if (renderer()) {
598  return static_cast<RenderText *>(renderer())->string();
599  } else {
600  return string();
601  }
602 }
603 
604 static bool textNeedsEscaping(const NodeImpl *n)
605 {
606  // Exceptions based on "Serializing HTML fragments" section of
607  // HTML 5 specification (with some adaptions to reality)
608  const NodeImpl *p = n->parentNode();
609  if (!p) {
610  return true;
611  }
612  switch (p->id()) {
613  case ID_IFRAME:
614  // follow deviating examples of FF 3.5.6 and Opera 9.6
615  // case ID_NOEMBED:
616  // case ID_NOFRAMES:
617  case ID_NOSCRIPT:
618  case ID_PLAINTEXT:
619  case ID_SCRIPT:
620  case ID_STYLE:
621  case ID_XMP:
622  return false;
623  default:
624  return true;
625  }
626 }
627 
628 DOMString TextImpl::toString() const
629 {
630  return textNeedsEscaping(this) ? escapeHTML(nodeValue()) : nodeValue();
631 }
632 
633 DOMString TextImpl::toString(long long startOffset, long long endOffset) const
634 {
635  DOMString str = nodeValue();
636  if (endOffset >= 0 || startOffset > 0) {
637  str = str.copy(); //we are going to modify this, so make a copy. I hope I'm doing this right.
638  }
639  if (endOffset >= 0) {
640  str.truncate(endOffset);
641  }
642  if (startOffset > 0) { //note the order of these 2 'if' statements so that it works right when n==m_startContainer==m_endContainer
643  str.remove(0, startOffset);
644  }
645  return textNeedsEscaping(this) ? escapeHTML(str) : str;
646 }
647 
648 // ---------------------------------------------------------------------------
649 
650 DOMString CDATASectionImpl::nodeName() const
651 {
652  return "#cdata-section";
653 }
654 
655 unsigned short CDATASectionImpl::nodeType() const
656 {
657  return Node::CDATA_SECTION_NODE;
658 }
659 
660 WTF::PassRefPtr<NodeImpl> CDATASectionImpl::cloneNode(bool /*deep*/)
661 {
662  int ec = 0;
663  return document()->createCDATASection(str, ec);
664 }
665 
666 // DOM Section 1.1.1
667 bool CDATASectionImpl::childTypeAllowed(unsigned short /*type*/)
668 {
669  return false;
670 }
671 
672 TextImpl *CDATASectionImpl::createNew(DOMStringImpl *_str)
673 {
674  return new CDATASectionImpl(docPtr(), _str);
675 }
676 
677 DOMString CDATASectionImpl::toString() const
678 {
679  return DOMString("<![CDATA[") + nodeValue() + DOMString("]]>");
680 }
681 
682 // ---------------------------------------------------------------------------
683 
684 EditingTextImpl::EditingTextImpl(DocumentImpl *impl, const DOMString &text)
685  : TextImpl(impl, text.implementation())
686 {
687 }
688 
689 EditingTextImpl::EditingTextImpl(DocumentImpl *impl)
690  : TextImpl(impl)
691 {
692 }
693 
694 EditingTextImpl::~EditingTextImpl()
695 {
696 }
697 
698 bool EditingTextImpl::rendererIsNeeded(RenderStyle */*style*/)
699 {
700  return true;
701 }
702 
Node insertBefore(const Node &newChild, const Node &refChild)
Inserts the node newChild before the existing child node refChild .
Definition: dom_node.cpp:314
bool dispatchEvent(const Event &evt)
Introduced in DOM Level 2 This method is from the EventTarget interface.
Definition: dom_node.cpp:463
This file is part of the HTML rendering engine for KDE.
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
unsigned long length() const
The number of characters that are available through data and the substringData method below...
Definition: dom_text.cpp:89
Node parentNode() const
The parent of this node.
Definition: dom_node.cpp:250
Type type(const QSqlDatabase &db)
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
DOMString nodeValue() const
The value of this node, depending on its type; see the table above.
Definition: dom_node.cpp:218
DOMString data() const
The character data of the node that implements this interface.
Definition: dom_text.cpp:67
void setData(const DOMString &)
see data NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly.
Definition: dom_text.cpp:75
This library provides a full-featured HTML parser and widget.
Base Class for all rendering tree objects.
DOMStringImpl * implementation() const
Definition: dom_string.h:145
Node nextSibling() const
The node immediately following this node.
Definition: dom_node.cpp:290
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:47:53 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.