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

Last change on this file since 711 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

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