• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.5 API Reference
  • KDE Home
  • Contact Us
 

kpimtextedit/richtextbuilders

  • kpimtextedit
  • richtextbuilders
kmarkupdirector.cpp
1 /*
2  This file is part of KDE.
3 
4  Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
5 
6  This library is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Library General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or (at your
9  option) any later version.
10 
11  This library is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14  License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301, USA.
20 */
21 
22 
23 #include "kmarkupdirector.h"
24 #include "kmarkupdirector_p.h"
25 
26 #include <kdebug.h>
27 
28 #include <QFlags>
29 #include <QTextDocument>
30 #include <QTextDocumentFragment>
31 #include <QString>
32 #include <QStack>
33 #include <QTextFrame>
34 #include <QTextTable>
35 #include <QTextList>
36 #include <QTextCursor>
37 #include <QTextCharFormat>
38 #include <QMap>
39 #include <QColor>
40 #include <QBrush>
41 
42 #include "kabstractmarkupbuilder.h"
43 
44 KMarkupDirector::KMarkupDirector(KAbstractMarkupBuilder* builder) :
45  d(new Private(this))
46 {
47  d->builder = builder;
48 }
49 
50 KMarkupDirector::~KMarkupDirector()
51 {
52  delete d;
53 }
54 
55 void KMarkupDirector::processDocumentContents(QTextFrame::iterator start, QTextFrame::iterator end)
56 {
57  for (QTextFrame::iterator it = start; ((!it.atEnd()) && (it != end)); ++it) {
58  QTextFrame *frame = it.currentFrame();
59  if (frame) {
60  QTextTable *table = dynamic_cast<QTextTable*>(frame);
61  if (table) {
62  processTable(table);
63  } else {
64  processFrame(frame);
65  }
66  } else {
67  processBlock(it.currentBlock());
68  }
69  }
70 }
71 
72 void KMarkupDirector::processFrame(QTextFrame* frame)
73 {
74  processDocumentContents(frame->begin(), frame->end());
75 }
76 
77 void KMarkupDirector::processBlock(const QTextBlock &block)
78 {
79  if (block.isValid()) {
80  QTextList *list = block.textList();
81  if (list) {
82  // An entire list is processed when first found.
83  // Just skip over if not the first item in a list.
84  if ((list->item(0) == block) && (!block.previous().textList())) {
85  processList(block);
86  }
87  } else {
88  processBlockContents(block);
89  }
90  }
91 }
92 
93 void KMarkupDirector::processTable(QTextTable *table)
94 {
95  QTextTableFormat format = table->format();
96  QVector<QTextLength> colLengths = format.columnWidthConstraints();
97 
98  QTextLength tableWidth = format.width();
99  QString sWidth;
100 
101  if (tableWidth.type() == QTextLength::PercentageLength) {
102  sWidth = "%1%";
103  sWidth = sWidth.arg(tableWidth.rawValue());
104  } else if (tableWidth.type() == QTextLength::FixedLength) {
105  sWidth = "%1";
106  sWidth = sWidth.arg(tableWidth.rawValue());
107  }
108 
109  d->builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth);
110 
111  int headerRowCount = format.headerRowCount();
112 
113  QList<QTextTableCell> alreadyProcessedCells;
114 
115  for (int row = 0; row < table->rows(); ++row) {
116  // Put a thead element around here somewhere?
117  // if (row < headerRowCount)
118  // {
119  // d->builder->beginTableHeader();
120  // }
121 
122  d->builder->beginTableRow();
123 
124  // Header attribute should really be on cells, not determined by number of rows.
125  //http://www.webdesignfromscratch.com/html-tables.cfm
126 
127 
128  for (int column = 0; column < table->columns(); ++column) {
129 
130  QTextTableCell tableCell = table->cellAt(row, column);
131 
132  int columnSpan = tableCell.columnSpan();
133  int rowSpan = tableCell.rowSpan();
134  if ((rowSpan > 1) || (columnSpan > 1)) {
135  if (alreadyProcessedCells.contains(tableCell)) {
136  // Already processed this cell. Move on.
137  continue;
138  } else {
139  alreadyProcessedCells.append(tableCell);
140  }
141  }
142 
143  QTextLength cellWidth = colLengths.at(column);
144 
145  QString sCellWidth;
146 
147  if (cellWidth.type() == QTextLength::PercentageLength) {
148  sCellWidth = "%1%";
149  sCellWidth = sCellWidth.arg(cellWidth.rawValue());
150  } else if (cellWidth.type() == QTextLength::FixedLength) {
151  sCellWidth = "%1";
152  sCellWidth = sCellWidth.arg(cellWidth.rawValue());
153  }
154 
155  // TODO: Use THEAD instead
156  if (row < headerRowCount) {
157  d->builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan);
158  } else {
159  d->builder->beginTableCell(sCellWidth, columnSpan, rowSpan);
160  }
161 
162  processTableCell(tableCell);
163 
164  if (row < headerRowCount) {
165  d->builder->endTableHeaderCell();
166  } else {
167  d->builder->endTableCell();
168  }
169  }
170  d->builder->endTableRow();
171  }
172  d->builder->endTable();
173 }
174 
175 void KMarkupDirector::processTableCell(const QTextTableCell &cell)
176 {
177  processDocumentContents(cell.begin(), cell.end());
178 }
179 
180 void KMarkupDirector::processList(const QTextBlock &ablock)
181 {
182  QTextBlock block(ablock);
183 
184  QTextList *list = block.textList();
185  if (!list) {
186  return;
187  }
188 
189  QList<QTextList*> lists;
190 
191  while (block.isValid() && block.textList()) {
192  if (list->item(0) == block) {
193  // Item zero in a list is the first block in the list of blocks that make up a list.
194  QTextListFormat::Style style = list->format().style();
195  d->builder->beginList(style);
196 
197  lists.append(list);
198  }
199 
200  d->builder->beginListItem();
201  processBlockContents(block);
202  d->builder->endListItem();
203 
204  block = block.next();
205 
206  if (block.isValid()) {
207  QTextList *newList = block.textList();
208 
209  if (!newList) {
210  while (!lists.isEmpty()) {
211  lists.removeLast();
212  d->builder->endList();
213  }
214  } else if (newList == list) {
215  //Next block is on the same list; Handled on next iteration.
216  continue;
217  } else if (newList != list) {
218  if (newList->item(0) == block) {
219  list = newList;
220  continue;
221  } else {
222  while (!lists.isEmpty()) {
223  if (block.textList() != lists.last()) {
224  lists.removeLast();
225  d->builder->endList();
226  } else {
227  break;
228  }
229  }
230  continue;
231  }
232  }
233  } else {
234  // Next block is not valid. Maybe at EOF. Close all open lists.
235  // TODO: Figure out how to handle lists in adjacent table cells.
236  while (!lists.isEmpty()) {
237  lists.removeLast();
238  d->builder->endList();
239  }
240  }
241  }
242 }
243 
244 void KMarkupDirector::processBlockContents(const QTextBlock &block)
245 {
246  QTextBlockFormat blockFormat = block.blockFormat();
247  Qt::Alignment blockAlignment = blockFormat.alignment();
248 
249  // TODO: decide when to use <h1> etc.
250 
251  if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
252  d->builder->insertHorizontalRule();
253  return;
254  }
255 
256  QTextBlock::iterator it;
257  it = block.begin();
258 
259  // The beginning is the end. This is an empty block. Insert a newline and move on.
260  // This is what gets generated by a QTextEdit...
261  if (it.atEnd()) {
262 // kDebug() << "The beginning is the end";
263  d->builder->addNewline();
264  return;
265  }
266 
267  QTextFragment fragment = it.fragment();
268 
269  // .. but if a sequence such as '<br /><br />' is imported into a document with setHtml, Separator_Line
270  // characters are inserted here within one block. See testNewlines and testNewlinesThroughQTextEdit.
271  if (fragment.isValid()) {
272  QTextCharFormat fragmentFormat = fragment.charFormat();
273 
274  if (!fragmentFormat.isImageFormat() && fragment.text().at(0).category() == QChar::Separator_Line) {
275 
276  // Consecutive newlines in a qtextdocument are in a single fragment if inserted with setHtml.
277  foreach(const QChar &c, fragment.text()) {
278 // kDebug() << c;
279  if (c.category() == QChar::Separator_Line) {
280  d->builder->addNewline();
281  }
282  }
283  return;
284  }
285  }
286 
287  // Don't have p tags inside li tags.
288  if (!block.textList())
289  {
290  // Don't instruct builders to use margins. The rich text widget doesn't have an action for them yet,
291  // So users can't edit them. See bug http://bugs.kde.org/show_bug.cgi?id=160600
292  d->builder->beginParagraph(blockAlignment //,
293  // blockFormat.topMargin(),
294  // blockFormat.bottomMargin(),
295  // blockFormat.leftMargin(),
296  // blockFormat.rightMargin()
297  );
298  }
299  while (!it.atEnd()) {
300  fragment = it.fragment();
301  if (fragment.isValid()) {
302  QTextCharFormat fragmentFormat = fragment.charFormat();
303 
304  if (fragmentFormat.isImageFormat()) {
305  // TODO: Close any open format elements?
306  QTextImageFormat imageFormat = fragmentFormat.toImageFormat();
307  d->builder->insertImage(imageFormat.name(), imageFormat.width(), imageFormat.height());
308  ++it;
309  continue;
310  } else {
311  // The order of closing and opening tags can determine whether generated html is valid or not.
312  // When processing a document with formatting which appears as '<b><i>Some</i> formatted<b> text',
313  // the correct generated output will contain '<strong><em>Some</em> formatted<strong> text'.
314  // However, processing text which appears as '<i><b>Some</b> formatted<i> text' might be incorrectly rendered
315  // as '<strong><em>Some</strong> formatted</em> text' if tags which start at the same fragment are
316  // opened out of order. Here, tags are not nested properly, and the html would
317  // not be valid or render correctly by unforgiving parsers (like QTextEdit).
318  // One solution is to make the order of opening tags dynamic. In the above case, the em tag would
319  // be opened before the strong tag '<em><strong>Some</strong> formatted</em> text'. That would
320  // require knowledge of which tag is going to close first. That might be possible by examining
321  // the 'next' QTextFragment while processing one.
322  //
323  // The other option is to do pessimistic closing of tags.
324  // In the above case, this means that if a fragment has two or more formats applied (bold and italic here),
325  // and one of them is closed, then all tags should be closed first. They will of course be reopened
326  // if necessary while processing the next fragment.
327  // The above case would be rendered as '<strong><em>Some</em></strong><em> formatted</em> text'.
328  //
329  // The first option is taken here, as the redundant opening and closing tags in the second option
330  // didn't appeal.
331  // See testDoubleStartDifferentFinish, testDoubleStartDifferentFinishReverseOrder
332 
333  d->processOpeningElements(it);
334 
335  // If a sequence such as '<br /><br />' is imported into a document with setHtml, LineSeparator
336  // characters are inserted. Here I make sure to put them back.
337  QStringList sl = fragment.text().split(QChar( QChar::LineSeparator ) );
338  QStringListIterator i(sl);
339  bool paraClosed = false;
340  while (i.hasNext())
341  {
342  d->builder->appendLiteralText(i.next());
343  if (i.hasNext())
344  {
345  if (i.peekNext().isEmpty())
346  {
347  if (!paraClosed)
348  {
349  d->builder->endParagraph();
350  paraClosed = true;
351  }
352  d->builder->addNewline();
353  } else if (paraClosed) {
354  d->builder->beginParagraph(blockAlignment);
355  paraClosed = false;
356  }
357  }
358  }
359 
360  ++it;
361  d->processClosingElements(it);
362  }
363  }
364  }
365 
366  // Don't have p tags inside li tags.
367  if (!block.textList())
368  {
369  d->builder->endParagraph();
370  }
371 
372 }
373 
374 void KMarkupDirector::constructContent(QTextDocument* doc)
375 {
376  QTextFrame *rootFrame = doc->rootFrame();
377  processFrame(rootFrame);
378 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jan 5 2013 19:44:42 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kpimtextedit/richtextbuilders

Skip menu "kpimtextedit/richtextbuilders"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.9.5 API Reference

Skip menu "kdepimlibs-4.9.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal