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

Last change on this file since 135 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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