source: trunk/src/gui/text/qtextdocumentfragment.cpp@ 1099

Last change on this file since 1099 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 41.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtextdocumentfragment.h"
43#include "qtextdocumentfragment_p.h"
44#include "qtextcursor_p.h"
45#include "qtextlist.h"
46
47#include <qdebug.h>
48#include <qtextcodec.h>
49#include <qbytearray.h>
50#include <qdatastream.h>
51#include <qdatetime.h>
52
53QT_BEGIN_NAMESPACE
54
55QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
56#if defined(Q_CC_DIAB) // compiler bug
57 : formatCollection(*_destination.d->priv->formatCollection()), originalText((const QString)_source.d->priv->buffer())
58#else
59 : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
60#endif
61{
62 src = _source.d->priv;
63 dst = _destination.d->priv;
64 insertPos = _destination.position();
65 this->forceCharFormat = forceCharFormat;
66 primaryCharFormatIndex = convertFormatIndex(fmt);
67 cursor = _source;
68}
69
70int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
71{
72 QTextFormat fmt = oldFormat;
73 if (objectIndexToSet != -1) {
74 fmt.setObjectIndex(objectIndexToSet);
75 } else if (fmt.objectIndex() != -1) {
76 int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
77 if (newObjectIndex == -1) {
78 QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex());
79 Q_ASSERT(objFormat.objectIndex() == -1);
80 newObjectIndex = formatCollection.createObjectIndex(objFormat);
81 objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
82 }
83 fmt.setObjectIndex(newObjectIndex);
84 }
85 int idx = formatCollection.indexForFormat(fmt);
86 Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type());
87 return idx;
88}
89
90int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex)
91{
92 QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos);
93 const QTextFragmentData * const frag = fragIt.value();
94
95 Q_ASSERT(objectIndex == -1
96 || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1));
97
98 int charFormatIndex;
99 if (forceCharFormat)
100 charFormatIndex = primaryCharFormatIndex;
101 else
102 charFormatIndex = convertFormatIndex(frag->format, objectIndex);
103
104 const int inFragmentOffset = qMax(0, pos - fragIt.position());
105 int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos);
106
107 QTextBlock nextBlock = src->blocksFind(pos + 1);
108
109 int blockIdx = -2;
110 if (nextBlock.position() == pos + 1) {
111 blockIdx = convertFormatIndex(nextBlock.blockFormat());
112 } else if (pos == 0 && insertPos == 0) {
113 dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat());
114 dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat());
115 }
116
117 QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy);
118 if (txtToInsert.length() == 1
119 && (txtToInsert.at(0) == QChar::ParagraphSeparator
120 || txtToInsert.at(0) == QTextBeginningOfFrame
121 || txtToInsert.at(0) == QTextEndOfFrame
122 )
123 ) {
124 dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex);
125 ++insertPos;
126 } else {
127 if (nextBlock.textList()) {
128 QTextBlock dstBlock = dst->blocksFind(insertPos);
129 if (!dstBlock.textList()) {
130 // insert a new text block with the block and char format from the
131 // source block to make sure that the following text fragments
132 // end up in a list as they should
133 int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat());
134 int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat());
135 dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex);
136 ++insertPos;
137 }
138 }
139 dst->insert(insertPos, txtToInsert, charFormatIndex);
140 const int userState = nextBlock.userState();
141 if (userState != -1)
142 dst->blocksFind(insertPos).setUserState(userState);
143 insertPos += txtToInsert.length();
144 }
145
146 return charsToCopy;
147}
148
149void QTextCopyHelper::appendFragments(int pos, int endPos)
150{
151 Q_ASSERT(pos < endPos);
152
153 while (pos < endPos)
154 pos += appendFragment(pos, endPos);
155}
156
157void QTextCopyHelper::copy()
158{
159 if (cursor.hasComplexSelection()) {
160 QTextTable *table = cursor.currentTable();
161 int row_start, col_start, num_rows, num_cols;
162 cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
163
164 QTextTableFormat tableFormat = table->format();
165 tableFormat.setColumns(num_cols);
166 tableFormat.clearColumnWidthConstraints();
167 const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat);
168
169 Q_ASSERT(row_start != -1);
170 for (int r = row_start; r < row_start + num_rows; ++r) {
171 for (int c = col_start; c < col_start + num_cols; ++c) {
172 QTextTableCell cell = table->cellAt(r, c);
173 const int rspan = cell.rowSpan();
174 const int cspan = cell.columnSpan();
175 if (rspan != 1) {
176 int cr = cell.row();
177 if (cr != r)
178 continue;
179 }
180 if (cspan != 1) {
181 int cc = cell.column();
182 if (cc != c)
183 continue;
184 }
185
186 // add the QTextBeginningOfFrame
187 QTextCharFormat cellFormat = cell.format();
188 if (r + rspan >= row_start + num_rows) {
189 cellFormat.setTableCellRowSpan(row_start + num_rows - r);
190 }
191 if (c + cspan >= col_start + num_cols) {
192 cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
193 }
194 const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex);
195
196 int blockIdx = -2;
197 const int cellPos = cell.firstPosition();
198 QTextBlock block = src->blocksFind(cellPos);
199 if (block.position() == cellPos) {
200 blockIdx = convertFormatIndex(block.blockFormat());
201 }
202
203 dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex);
204 ++insertPos;
205
206 // nothing to add for empty cells
207 if (cell.lastPosition() > cellPos) {
208 // add the contents
209 appendFragments(cellPos, cell.lastPosition());
210 }
211 }
212 }
213
214 // add end of table
215 int end = table->lastPosition();
216 appendFragment(end, end+1, objectIndex);
217 } else {
218 appendFragments(cursor.selectionStart(), cursor.selectionEnd());
219 }
220}
221
222QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor)
223 : ref(1), doc(new QTextDocument), importedFromPlainText(false)
224{
225 doc->setUndoRedoEnabled(false);
226
227 if (!_cursor.hasSelection())
228 return;
229
230 doc->docHandle()->beginEditBlock();
231 QTextCursor destCursor(doc);
232 QTextCopyHelper(_cursor, destCursor).copy();
233 doc->docHandle()->endEditBlock();
234
235 if (_cursor.d)
236 doc->docHandle()->mergeCachedResources(_cursor.d->priv);
237}
238
239void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const
240{
241 if (_cursor.isNull())
242 return;
243
244 QTextDocumentPrivate *destPieceTable = _cursor.d->priv;
245 destPieceTable->beginEditBlock();
246
247 QTextCursor sourceCursor(doc);
248 sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
249 QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy();
250
251 destPieceTable->endEditBlock();
252}
253
254/*!
255 \class QTextDocumentFragment
256 \reentrant
257
258 \brief The QTextDocumentFragment class represents a piece of formatted text
259 from a QTextDocument.
260
261 \ingroup richtext-processing
262 \ingroup shared
263
264 A QTextDocumentFragment is a fragment of rich text, that can be inserted into
265 a QTextDocument. A document fragment can be created from a
266 QTextDocument, from a QTextCursor's selection, or from another
267 document fragment. Document fragments can also be created by the
268 static functions, fromPlainText() and fromHtml().
269
270 The contents of a document fragment can be obtained as plain text
271 by using the toPlainText() function, or it can be obtained as HTML
272 with toHtml().
273*/
274
275
276/*!
277 Constructs an empty QTextDocumentFragment.
278
279 \sa isEmpty()
280*/
281QTextDocumentFragment::QTextDocumentFragment()
282 : d(0)
283{
284}
285
286/*!
287 Converts the given \a document into a QTextDocumentFragment.
288 Note that the QTextDocumentFragment only stores the document contents, not meta information
289 like the document's title.
290*/
291QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
292 : d(0)
293{
294 if (!document)
295 return;
296
297 QTextCursor cursor(const_cast<QTextDocument *>(document));
298 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
299 d = new QTextDocumentFragmentPrivate(cursor);
300}
301
302/*!
303 Creates a QTextDocumentFragment from the \a{cursor}'s selection.
304 If the cursor doesn't have a selection, the created fragment is empty.
305
306 \sa isEmpty() QTextCursor::selection()
307*/
308QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
309 : d(0)
310{
311 if (!cursor.hasSelection())
312 return;
313
314 d = new QTextDocumentFragmentPrivate(cursor);
315}
316
317/*!
318 \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
319
320 Copy constructor. Creates a copy of the \a other fragment.
321*/
322QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
323 : d(rhs.d)
324{
325 if (d)
326 d->ref.ref();
327}
328
329/*!
330 \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
331
332 Assigns the \a other fragment to this fragment.
333*/
334QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
335{
336 if (rhs.d)
337 rhs.d->ref.ref();
338 if (d && !d->ref.deref())
339 delete d;
340 d = rhs.d;
341 return *this;
342}
343
344/*!
345 Destroys the document fragment.
346*/
347QTextDocumentFragment::~QTextDocumentFragment()
348{
349 if (d && !d->ref.deref())
350 delete d;
351}
352
353/*!
354 Returns true if the fragment is empty; otherwise returns false.
355*/
356bool QTextDocumentFragment::isEmpty() const
357{
358 return !d || !d->doc || d->doc->docHandle()->length() <= 1;
359}
360
361/*!
362 Returns the document fragment's text as plain text (i.e. with no
363 formatting information).
364
365 \sa toHtml()
366*/
367QString QTextDocumentFragment::toPlainText() const
368{
369 if (!d)
370 return QString();
371
372 return d->doc->toPlainText();
373}
374
375// #### Qt 5: merge with other overload
376/*!
377 \overload
378*/
379
380#ifndef QT_NO_TEXTHTMLPARSER
381
382QString QTextDocumentFragment::toHtml() const
383{
384 return toHtml(QByteArray());
385}
386
387/*!
388 \since 4.2
389
390 Returns the contents of the document fragment as HTML,
391 using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
392
393 \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
394*/
395QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
396{
397 if (!d)
398 return QString();
399
400 return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
401}
402
403#endif // QT_NO_TEXTHTMLPARSER
404
405/*!
406 Returns a document fragment that contains the given \a plainText.
407
408 When inserting such a fragment into a QTextDocument the current char format of
409 the QTextCursor used for insertion is used as format for the text.
410*/
411QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
412{
413 QTextDocumentFragment res;
414
415 res.d = new QTextDocumentFragmentPrivate;
416 res.d->importedFromPlainText = true;
417 QTextCursor cursor(res.d->doc);
418 cursor.insertText(plainText);
419 return res;
420}
421
422static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
423{
424 if (style == QTextListFormat::ListDisc)
425 return QTextListFormat::ListCircle;
426 else if (style == QTextListFormat::ListCircle)
427 return QTextListFormat::ListSquare;
428 return style;
429}
430
431#ifndef QT_NO_TEXTHTMLPARSER
432
433QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
434 : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
435{
436 cursor = QTextCursor(doc);
437 wsm = QTextHtmlParserNode::WhiteSpaceNormal;
438
439 QString html = _html;
440 const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
441 if (startFragmentPos != -1) {
442 QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
443
444 // Hack for Qt3
445 const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
446
447 const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
448 if (startFragmentPos < endFragmentPos)
449 html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
450 else
451 html = html.mid(startFragmentPos);
452
453 if (hasQtRichtextMetaTag)
454 html.prepend(qt3RichTextHeader);
455 }
456
457 parse(html, resourceProvider ? resourceProvider : doc);
458// dumpHtml();
459}
460
461void QTextHtmlImporter::import()
462{
463 cursor.beginEditBlock();
464 hasBlock = true;
465 forceBlockMerging = false;
466 compressNextWhitespace = RemoveWhiteSpace;
467 blockTagClosed = false;
468 for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
469 currentNode = &at(currentNodeIdx);
470 wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
471
472 /*
473 * process each node in three stages:
474 * 1) check if the hierarchy changed and we therefore passed the
475 * equivalent of a closing tag -> we may need to finish off
476 * some structures like tables
477 *
478 * 2) check if the current node is a special node like a
479 * <table>, <ul> or <img> tag that requires special processing
480 *
481 * 3) if the node should result in a QTextBlock create one and
482 * finally insert text that may be attached to the node
483 */
484
485 /* emit 'closing' table blocks or adjust current indent level
486 * if we
487 * 1) are beyond the first node
488 * 2) the current node not being a child of the previous node
489 * means there was a tag closing in the input html
490 */
491 if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
492 blockTagClosed = closeTag();
493 // visually collapse subsequent block tags, but if the element after the closed block tag
494 // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
495 // hasBlock to false.
496 if (blockTagClosed
497 && !currentNode->isBlock()
498 && currentNode->id != Html_unknown)
499 {
500 hasBlock = false;
501 } else if (hasBlock) {
502 // when collapsing subsequent block tags we need to clear the block format
503 QTextBlockFormat blockFormat = currentNode->blockFormat;
504 blockFormat.setIndent(indent);
505
506 QTextBlockFormat oldFormat = cursor.blockFormat();
507 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
508 QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
509 if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
510 /* We remove an empty paragrah that requested a page break after.
511 moving that request to the next paragraph means we also need to make
512 that a pagebreak before to keep the same visual appearance.
513 */
514 pageBreak = QTextFormat::PageBreak_AlwaysBefore;
515 blockFormat.setPageBreakPolicy(pageBreak);
516 }
517
518 cursor.setBlockFormat(blockFormat);
519 }
520 }
521
522 if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
523 if (currentNode->id == Html_title)
524 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
525 // ignore explicitly 'invisible' elements
526 continue;
527 }
528
529 if (processSpecialNodes() == ContinueWithNextNode)
530 continue;
531
532 // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
533 if (blockTagClosed
534 && !hasBlock
535 && !currentNode->isBlock()
536 && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
537 && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
538
539 QTextBlockFormat block = currentNode->blockFormat;
540 block.setIndent(indent);
541
542 appendBlock(block, currentNode->charFormat);
543
544 hasBlock = true;
545 }
546
547 if (currentNode->isBlock()) {
548 if (processBlockNode() == ContinueWithNextNode)
549 continue;
550 }
551
552 if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
553 namedAnchors.append(currentNode->charFormat.anchorName());
554 }
555
556 if (appendNodeText())
557 hasBlock = false; // if we actually appended text then we don't
558 // have an empty block anymore
559 }
560
561 cursor.endEditBlock();
562}
563
564bool QTextHtmlImporter::appendNodeText()
565{
566 const int initialCursorPosition = cursor.position();
567 QTextCharFormat format = currentNode->charFormat;
568
569 if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
570 compressNextWhitespace = PreserveWhiteSpace;
571
572 QString text = currentNode->text;
573
574 QString textToInsert;
575 textToInsert.reserve(text.size());
576
577 for (int i = 0; i < text.length(); ++i) {
578 QChar ch = text.at(i);
579
580 if (ch.isSpace()
581 && ch != QChar::Nbsp
582 && ch != QChar::ParagraphSeparator) {
583
584 if (compressNextWhitespace == CollapseWhiteSpace)
585 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
586 else if(compressNextWhitespace == RemoveWhiteSpace)
587 continue;
588
589 if (wsm == QTextHtmlParserNode::WhiteSpacePre
590 || textEditMode
591 ) {
592 if (ch == QLatin1Char('\n')) {
593 if (textEditMode)
594 continue;
595 } else if (ch == QLatin1Char('\r')) {
596 continue;
597 }
598 } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
599 compressNextWhitespace = RemoveWhiteSpace;
600 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
601 ch = QChar::Nbsp;
602 else
603 ch = QLatin1Char(' ');
604 }
605 } else {
606 compressNextWhitespace = PreserveWhiteSpace;
607 }
608
609 if (ch == QLatin1Char('\n')
610 || ch == QChar::ParagraphSeparator) {
611
612 if (!textToInsert.isEmpty()) {
613 cursor.insertText(textToInsert, format);
614 textToInsert.clear();
615 }
616
617 QTextBlockFormat fmt = cursor.blockFormat();
618
619 if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
620 QTextBlockFormat tmp = fmt;
621 tmp.clearProperty(QTextFormat::BlockBottomMargin);
622 cursor.setBlockFormat(tmp);
623 }
624
625 fmt.clearProperty(QTextFormat::BlockTopMargin);
626 appendBlock(fmt, cursor.charFormat());
627 } else {
628 if (!namedAnchors.isEmpty()) {
629 if (!textToInsert.isEmpty()) {
630 cursor.insertText(textToInsert, format);
631 textToInsert.clear();
632 }
633
634 format.setAnchor(true);
635 format.setAnchorNames(namedAnchors);
636 cursor.insertText(ch, format);
637 namedAnchors.clear();
638 format.clearProperty(QTextFormat::IsAnchor);
639 format.clearProperty(QTextFormat::AnchorName);
640 } else {
641 textToInsert += ch;
642 }
643 }
644 }
645
646 if (!textToInsert.isEmpty()) {
647 cursor.insertText(textToInsert, format);
648 }
649
650 return cursor.position() != initialCursorPosition;
651}
652
653QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
654{
655 switch (currentNode->id) {
656 case Html_body:
657 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
658 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
659 fmt.setBackground(currentNode->charFormat.background());
660 doc->rootFrame()->setFrameFormat(fmt);
661 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
662 }
663 compressNextWhitespace = RemoveWhiteSpace;
664 break;
665
666 case Html_ol:
667 case Html_ul: {
668 QTextListFormat::Style style = currentNode->listStyle;
669
670 if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
671 const QTextHtmlParserNode *n = &at(currentNode->parent);
672 while (n) {
673 if (n->id == Html_ul) {
674 style = nextListStyle(currentNode->listStyle);
675 }
676 if (n->parent)
677 n = &at(n->parent);
678 else
679 n = 0;
680 }
681 }
682
683 QTextListFormat listFmt;
684 listFmt.setStyle(style);
685
686 ++indent;
687 if (currentNode->hasCssListIndent)
688 listFmt.setIndent(currentNode->cssListIndent);
689 else
690 listFmt.setIndent(indent);
691
692 List l;
693 l.format = listFmt;
694 l.listNode = currentNodeIdx;
695 lists.append(l);
696 compressNextWhitespace = RemoveWhiteSpace;
697
698 // broken html: <ul>Text here<li>Foo
699 const QString simpl = currentNode->text.simplified();
700 if (simpl.isEmpty() || simpl.at(0).isSpace())
701 return ContinueWithNextNode;
702 break;
703 }
704
705 case Html_table: {
706 Table t = scanTable(currentNodeIdx);
707 tables.append(t);
708 hasBlock = false;
709 compressNextWhitespace = RemoveWhiteSpace;
710 return ContinueWithNextNode;
711 }
712
713 case Html_tr:
714 return ContinueWithNextNode;
715
716 case Html_img: {
717 QTextImageFormat fmt;
718 fmt.setName(currentNode->imageName);
719
720 fmt.merge(currentNode->charFormat);
721
722 if (currentNode->imageWidth != -1)
723 fmt.setWidth(currentNode->imageWidth);
724 if (currentNode->imageHeight != -1)
725 fmt.setHeight(currentNode->imageHeight);
726
727 cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
728
729 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
730 cursor.mergeCharFormat(currentNode->charFormat);
731 cursor.movePosition(QTextCursor::Right);
732 compressNextWhitespace = CollapseWhiteSpace;
733
734 hasBlock = false;
735 return ContinueWithNextNode;
736 }
737
738 case Html_hr: {
739 QTextBlockFormat blockFormat = currentNode->blockFormat;
740 blockFormat.setTopMargin(topMargin(currentNodeIdx));
741 blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
742 blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
743 if (hasBlock && importMode == ImportToDocument)
744 cursor.mergeBlockFormat(blockFormat);
745 else
746 appendBlock(blockFormat);
747 hasBlock = false;
748 compressNextWhitespace = RemoveWhiteSpace;
749 return ContinueWithNextNode;
750 }
751
752 default: break;
753 }
754 return ContinueWithCurrentNode;
755}
756
757// returns true if a block tag was closed
758bool QTextHtmlImporter::closeTag()
759{
760 const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
761 const int endDepth = depth(currentNodeIdx) - 1;
762 int depth = this->depth(currentNodeIdx - 1);
763 bool blockTagClosed = false;
764
765 while (depth > endDepth) {
766 Table *t = 0;
767 if (!tables.isEmpty())
768 t = &tables.last();
769
770 switch (closedNode->id) {
771 case Html_tr:
772 if (t && !t->isTextFrame) {
773 ++t->currentRow;
774
775 // for broken html with rowspans but missing tr tags
776 while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
777 ++t->currentCell;
778 }
779
780 blockTagClosed = true;
781 break;
782
783 case Html_table:
784 if (!t)
785 break;
786 indent = t->lastIndent;
787
788 tables.resize(tables.size() - 1);
789 t = 0;
790
791 if (tables.isEmpty()) {
792 cursor = doc->rootFrame()->lastCursorPosition();
793 } else {
794 t = &tables.last();
795 if (t->isTextFrame)
796 cursor = t->frame->lastCursorPosition();
797 else if (!t->currentCell.atEnd())
798 cursor = t->currentCell.cell().lastCursorPosition();
799 }
800
801 // we don't need an extra block after tables, so we don't
802 // claim to have closed one for the creation of a new one
803 // in import()
804 blockTagClosed = false;
805 compressNextWhitespace = RemoveWhiteSpace;
806 break;
807
808 case Html_th:
809 case Html_td:
810 if (t && !t->isTextFrame)
811 ++t->currentCell;
812 blockTagClosed = true;
813 compressNextWhitespace = RemoveWhiteSpace;
814 break;
815
816 case Html_ol:
817 case Html_ul:
818 if (lists.isEmpty())
819 break;
820 lists.resize(lists.size() - 1);
821 --indent;
822 blockTagClosed = true;
823 break;
824
825 case Html_br:
826 compressNextWhitespace = RemoveWhiteSpace;
827 break;
828
829 case Html_div:
830 if (closedNode->children.isEmpty())
831 break;
832 // fall through
833 default:
834 if (closedNode->isBlock())
835 blockTagClosed = true;
836 break;
837 }
838
839 closedNode = &at(closedNode->parent);
840 --depth;
841 }
842
843 return blockTagClosed;
844}
845
846QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
847{
848 Table table;
849 table.columns = 0;
850
851 QVector<QTextLength> columnWidths;
852
853 int tableHeaderRowCount = 0;
854 QVector<int> rowNodes;
855 rowNodes.reserve(at(tableNodeIdx).children.count());
856 foreach (int row, at(tableNodeIdx).children)
857 switch (at(row).id) {
858 case Html_tr:
859 rowNodes += row;
860 break;
861 case Html_thead:
862 case Html_tbody:
863 case Html_tfoot:
864 foreach (int potentialRow, at(row).children)
865 if (at(potentialRow).id == Html_tr) {
866 rowNodes += potentialRow;
867 if (at(row).id == Html_thead)
868 ++tableHeaderRowCount;
869 }
870 break;
871 default: break;
872 }
873
874 QVector<RowColSpanInfo> rowColSpans;
875 QVector<RowColSpanInfo> rowColSpanForColumn;
876
877 int effectiveRow = 0;
878 foreach (int row, rowNodes) {
879 int colsInRow = 0;
880
881 foreach (int cell, at(row).children)
882 if (at(cell).isTableCell()) {
883 // skip all columns with spans from previous rows
884 while (colsInRow < rowColSpanForColumn.size()) {
885 const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
886
887 if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
888 Q_ASSERT(spanInfo.col == colsInRow);
889 colsInRow += spanInfo.colSpan;
890 } else
891 break;
892 }
893
894 const QTextHtmlParserNode &c = at(cell);
895 const int currentColumn = colsInRow;
896 colsInRow += c.tableCellColSpan;
897
898 RowColSpanInfo spanInfo;
899 spanInfo.row = effectiveRow;
900 spanInfo.col = currentColumn;
901 spanInfo.colSpan = c.tableCellColSpan;
902 spanInfo.rowSpan = c.tableCellRowSpan;
903 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
904 rowColSpans.append(spanInfo);
905
906 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
907 rowColSpanForColumn.resize(columnWidths.size());
908 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
909 if (columnWidths.at(i).type() == QTextLength::VariableLength) {
910 QTextLength w = c.width;
911 if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
912 w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
913 columnWidths[i] = w;
914 }
915 rowColSpanForColumn[i] = spanInfo;
916 }
917 }
918
919 table.columns = qMax(table.columns, colsInRow);
920
921 ++effectiveRow;
922 }
923 table.rows = effectiveRow;
924
925 table.lastIndent = indent;
926 indent = 0;
927
928 if (table.rows == 0 || table.columns == 0)
929 return table;
930
931 QTextFrameFormat fmt;
932 const QTextHtmlParserNode &node = at(tableNodeIdx);
933
934 if (!node.isTextFrame) {
935 QTextTableFormat tableFmt;
936 tableFmt.setCellSpacing(node.tableCellSpacing);
937 tableFmt.setCellPadding(node.tableCellPadding);
938 if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
939 tableFmt.setAlignment(node.blockFormat.alignment());
940 tableFmt.setColumns(table.columns);
941 tableFmt.setColumnWidthConstraints(columnWidths);
942 tableFmt.setHeaderRowCount(tableHeaderRowCount);
943 fmt = tableFmt;
944 }
945
946 fmt.setTopMargin(topMargin(tableNodeIdx));
947 fmt.setBottomMargin(bottomMargin(tableNodeIdx));
948 fmt.setLeftMargin(leftMargin(tableNodeIdx)
949 + table.lastIndent * 40 // ##### not a good emulation
950 );
951 fmt.setRightMargin(rightMargin(tableNodeIdx));
952
953 // compatibility
954 if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
955 && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
956 && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
957 fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
958
959 fmt.setBorderStyle(node.borderStyle);
960 fmt.setBorderBrush(node.borderBrush);
961 fmt.setBorder(node.tableBorder);
962 fmt.setWidth(node.width);
963 fmt.setHeight(node.height);
964 if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
965 fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
966
967 if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
968 fmt.setLayoutDirection(node.blockFormat.layoutDirection());
969 if (node.charFormat.background().style() != Qt::NoBrush)
970 fmt.setBackground(node.charFormat.background());
971 fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
972
973 if (node.isTextFrame) {
974 if (node.isRootFrame) {
975 table.frame = cursor.currentFrame();
976 table.frame->setFrameFormat(fmt);
977 } else
978 table.frame = cursor.insertFrame(fmt);
979
980 table.isTextFrame = true;
981 } else {
982 const int oldPos = cursor.position();
983 QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
984 table.frame = textTable;
985
986 for (int i = 0; i < rowColSpans.count(); ++i) {
987 const RowColSpanInfo &nfo = rowColSpans.at(i);
988 textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
989 }
990
991 table.currentCell = TableCellIterator(textTable);
992 cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
993 }
994 return table;
995}
996
997QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
998{
999 QTextBlockFormat block;
1000 QTextCharFormat charFmt;
1001 bool modifiedBlockFormat = true;
1002 bool modifiedCharFormat = true;
1003
1004 if (currentNode->isTableCell() && !tables.isEmpty()) {
1005 Table &t = tables.last();
1006 if (!t.isTextFrame && !t.currentCell.atEnd()) {
1007 QTextTableCell cell = t.currentCell.cell();
1008 if (cell.isValid()) {
1009 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
1010 if (topPadding(currentNodeIdx) >= 0)
1011 fmt.setTopPadding(topPadding(currentNodeIdx));
1012 if (bottomPadding(currentNodeIdx) >= 0)
1013 fmt.setBottomPadding(bottomPadding(currentNodeIdx));
1014 if (leftPadding(currentNodeIdx) >= 0)
1015 fmt.setLeftPadding(leftPadding(currentNodeIdx));
1016 if (rightPadding(currentNodeIdx) >= 0)
1017 fmt.setRightPadding(rightPadding(currentNodeIdx));
1018 cell.setFormat(fmt);
1019
1020 cursor.setPosition(cell.firstPosition());
1021 }
1022 }
1023 hasBlock = true;
1024 compressNextWhitespace = RemoveWhiteSpace;
1025
1026 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1027 charFmt.setBackground(currentNode->charFormat.background());
1028 cursor.mergeBlockCharFormat(charFmt);
1029 }
1030 }
1031
1032 if (hasBlock) {
1033 block = cursor.blockFormat();
1034 charFmt = cursor.blockCharFormat();
1035 modifiedBlockFormat = false;
1036 modifiedCharFormat = false;
1037 }
1038
1039 // collapse
1040 {
1041 qreal tm = qreal(topMargin(currentNodeIdx));
1042 if (tm > block.topMargin()) {
1043 block.setTopMargin(tm);
1044 modifiedBlockFormat = true;
1045 }
1046 }
1047
1048 int bottomMargin = this->bottomMargin(currentNodeIdx);
1049
1050 // for list items we may want to collapse with the bottom margin of the
1051 // list.
1052 const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1053 if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1054 && parentNode
1055 && (parentNode->isListStart() || parentNode->id == Html_dl)
1056 && (parentNode->children.last() == currentNodeIdx)) {
1057 bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1058 }
1059
1060 if (block.bottomMargin() != bottomMargin) {
1061 block.setBottomMargin(bottomMargin);
1062 modifiedBlockFormat = true;
1063 }
1064
1065 {
1066 const qreal lm = leftMargin(currentNodeIdx);
1067 const qreal rm = rightMargin(currentNodeIdx);
1068
1069 if (block.leftMargin() != lm) {
1070 block.setLeftMargin(lm);
1071 modifiedBlockFormat = true;
1072 }
1073 if (block.rightMargin() != rm) {
1074 block.setRightMargin(rm);
1075 modifiedBlockFormat = true;
1076 }
1077 }
1078
1079 if (currentNode->id != Html_li
1080 && indent != 0
1081 && (lists.isEmpty()
1082 || !hasBlock
1083 || !lists.last().list
1084 || lists.last().list->itemNumber(cursor.block()) == -1
1085 )
1086 ) {
1087 block.setIndent(indent);
1088 modifiedBlockFormat = true;
1089 }
1090
1091 if (currentNode->blockFormat.propertyCount() > 0) {
1092 modifiedBlockFormat = true;
1093 block.merge(currentNode->blockFormat);
1094 }
1095
1096 if (currentNode->charFormat.propertyCount() > 0) {
1097 modifiedCharFormat = true;
1098 charFmt.merge(currentNode->charFormat);
1099 }
1100
1101 // ####################
1102 // block.setFloatPosition(node->cssFloat);
1103
1104 if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1105 block.setNonBreakableLines(true);
1106 modifiedBlockFormat = true;
1107 }
1108
1109 if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1110 block.setBackground(currentNode->charFormat.background());
1111 modifiedBlockFormat = true;
1112 }
1113
1114 if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1115 if (modifiedBlockFormat)
1116 cursor.setBlockFormat(block);
1117 if (modifiedCharFormat)
1118 cursor.setBlockCharFormat(charFmt);
1119 } else {
1120 if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1121 cursor.setBlockFormat(block);
1122 cursor.setBlockCharFormat(charFmt);
1123 } else {
1124 appendBlock(block, charFmt);
1125 }
1126 }
1127
1128 if (currentNode->userState != -1)
1129 cursor.block().setUserState(currentNode->userState);
1130
1131 if (currentNode->id == Html_li && !lists.isEmpty()) {
1132 List &l = lists.last();
1133 if (l.list) {
1134 l.list->add(cursor.block());
1135 } else {
1136 l.list = cursor.createList(l.format);
1137 const qreal listTopMargin = topMargin(l.listNode);
1138 if (listTopMargin > block.topMargin()) {
1139 block.setTopMargin(listTopMargin);
1140 cursor.mergeBlockFormat(block);
1141 }
1142 }
1143 if (hasBlock) {
1144 QTextBlockFormat fmt;
1145 fmt.setIndent(0);
1146 cursor.mergeBlockFormat(fmt);
1147 }
1148 }
1149
1150 forceBlockMerging = false;
1151 if (currentNode->id == Html_body || currentNode->id == Html_html)
1152 forceBlockMerging = true;
1153
1154 if (currentNode->isEmptyParagraph) {
1155 hasBlock = false;
1156 return ContinueWithNextNode;
1157 }
1158
1159 hasBlock = true;
1160 blockTagClosed = false;
1161 return ContinueWithCurrentNode;
1162}
1163
1164void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1165{
1166 if (!namedAnchors.isEmpty()) {
1167 charFmt.setAnchor(true);
1168 charFmt.setAnchorNames(namedAnchors);
1169 namedAnchors.clear();
1170 }
1171
1172 cursor.insertBlock(format, charFmt);
1173
1174 if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1175 compressNextWhitespace = RemoveWhiteSpace;
1176}
1177
1178#endif // QT_NO_TEXTHTMLPARSER
1179
1180/*!
1181 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1182
1183 Returns a QTextDocumentFragment based on the arbitrary piece of
1184 HTML in the given \a text. The formatting is preserved as much as
1185 possible; for example, "<b>bold</b>" will become a document
1186 fragment with the text "bold" with a bold character format.
1187*/
1188
1189#ifndef QT_NO_TEXTHTMLPARSER
1190
1191QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1192{
1193 return fromHtml(html, 0);
1194}
1195
1196/*!
1197 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1198 \since 4.2
1199
1200 Returns a QTextDocumentFragment based on the arbitrary piece of
1201 HTML in the given \a text. The formatting is preserved as much as
1202 possible; for example, "<b>bold</b>" will become a document
1203 fragment with the text "bold" with a bold character format.
1204
1205 If the provided HTML contains references to external resources such as imported style sheets, then
1206 they will be loaded through the \a resourceProvider.
1207*/
1208
1209QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1210{
1211 QTextDocumentFragment res;
1212 res.d = new QTextDocumentFragmentPrivate;
1213
1214 QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1215 importer.import();
1216 return res;
1217}
1218
1219QT_END_NAMESPACE
1220#endif // QT_NO_TEXTHTMLPARSER
Note: See TracBrowser for help on using the repository browser.