kpimtextedit/richtextbuilders
kmarkupdirector.cpp
00001 /* 00002 This file is part of KDE. 00003 00004 Copyright (c) 2008 Stephen Kelly <steveire@gmail.com> 00005 00006 This library is free software; you can redistribute it and/or modify it 00007 under the terms of the GNU Library General Public License as published by 00008 the Free Software Foundation; either version 2 of the License, or (at your 00009 option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, but WITHOUT 00012 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00013 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00014 License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to the 00018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00019 02110-1301, USA. 00020 */ 00021 00022 00023 #include "kmarkupdirector.h" 00024 #include "kmarkupdirector_p.h" 00025 00026 #include <kdebug.h> 00027 00028 #include <QFlags> 00029 #include <QTextDocument> 00030 #include <QTextDocumentFragment> 00031 #include <QString> 00032 #include <QStack> 00033 #include <QTextFrame> 00034 #include <QTextTable> 00035 #include <QTextList> 00036 #include <QTextCursor> 00037 #include <QTextCharFormat> 00038 #include <QMap> 00039 #include <QColor> 00040 #include <QBrush> 00041 00042 #include "kabstractmarkupbuilder.h" 00043 00044 KMarkupDirector::KMarkupDirector(KAbstractMarkupBuilder* builder) : 00045 d(new Private(this)) 00046 { 00047 d->builder = builder; 00048 } 00049 00050 KMarkupDirector::~KMarkupDirector() 00051 { 00052 delete d; 00053 } 00054 00055 void KMarkupDirector::processDocumentContents(QTextFrame::iterator start, QTextFrame::iterator end) 00056 { 00057 for (QTextFrame::iterator it = start; ((!it.atEnd()) && (it != end)); ++it) { 00058 QTextFrame *frame = it.currentFrame(); 00059 if (frame) { 00060 QTextTable *table = dynamic_cast<QTextTable*>(frame); 00061 if (table) { 00062 processTable(table); 00063 } else { 00064 processFrame(frame); 00065 } 00066 } else { 00067 processBlock(it.currentBlock()); 00068 } 00069 } 00070 } 00071 00072 void KMarkupDirector::processFrame(QTextFrame* frame) 00073 { 00074 processDocumentContents(frame->begin(), frame->end()); 00075 } 00076 00077 void KMarkupDirector::processBlock(const QTextBlock &block) 00078 { 00079 if (block.isValid()) { 00080 QTextList *list = block.textList(); 00081 if (list) { 00082 // An entire list is processed when first found. 00083 // Just skip over if not the first item in a list. 00084 if ((list->item(0) == block) && (!block.previous().textList())) { 00085 processList(block); 00086 } 00087 } else { 00088 processBlockContents(block); 00089 } 00090 } 00091 } 00092 00093 void KMarkupDirector::processTable(QTextTable *table) 00094 { 00095 QTextTableFormat format = table->format(); 00096 QVector<QTextLength> colLengths = format.columnWidthConstraints(); 00097 00098 QTextLength tableWidth = format.width(); 00099 QString sWidth; 00100 00101 if (tableWidth.type() == QTextLength::PercentageLength) { 00102 sWidth = "%1%"; 00103 sWidth = sWidth.arg(tableWidth.rawValue()); 00104 } else if (tableWidth.type() == QTextLength::FixedLength) { 00105 sWidth = "%1"; 00106 sWidth = sWidth.arg(tableWidth.rawValue()); 00107 } 00108 00109 d->builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth); 00110 00111 int headerRowCount = format.headerRowCount(); 00112 00113 QList<QTextTableCell> alreadyProcessedCells; 00114 00115 for (int row = 0; row < table->rows(); ++row) { 00116 // Put a thead element around here somewhere? 00117 // if (row < headerRowCount) 00118 // { 00119 // d->builder->beginTableHeader(); 00120 // } 00121 00122 d->builder->beginTableRow(); 00123 00124 // Header attribute should really be on cells, not determined by number of rows. 00125 //http://www.webdesignfromscratch.com/html-tables.cfm 00126 00127 00128 for (int column = 0; column < table->columns(); ++column) { 00129 00130 QTextTableCell tableCell = table->cellAt(row, column); 00131 00132 int columnSpan = tableCell.columnSpan(); 00133 int rowSpan = tableCell.rowSpan(); 00134 if ((rowSpan > 1) || (columnSpan > 1)) { 00135 if (alreadyProcessedCells.contains(tableCell)) { 00136 // Already processed this cell. Move on. 00137 continue; 00138 } else { 00139 alreadyProcessedCells.append(tableCell); 00140 } 00141 } 00142 00143 QTextLength cellWidth = colLengths.at(column); 00144 00145 QString sCellWidth; 00146 00147 if (cellWidth.type() == QTextLength::PercentageLength) { 00148 sCellWidth = "%1%"; 00149 sCellWidth = sCellWidth.arg(cellWidth.rawValue()); 00150 } else if (cellWidth.type() == QTextLength::FixedLength) { 00151 sCellWidth = "%1"; 00152 sCellWidth = sCellWidth.arg(cellWidth.rawValue()); 00153 } 00154 00155 // TODO: Use THEAD instead 00156 if (row < headerRowCount) { 00157 d->builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan); 00158 } else { 00159 d->builder->beginTableCell(sCellWidth, columnSpan, rowSpan); 00160 } 00161 00162 processTableCell(tableCell); 00163 00164 if (row < headerRowCount) { 00165 d->builder->endTableHeaderCell(); 00166 } else { 00167 d->builder->endTableCell(); 00168 } 00169 } 00170 d->builder->endTableRow(); 00171 } 00172 d->builder->endTable(); 00173 } 00174 00175 void KMarkupDirector::processTableCell(const QTextTableCell &cell) 00176 { 00177 processDocumentContents(cell.begin(), cell.end()); 00178 } 00179 00180 void KMarkupDirector::processList(const QTextBlock &ablock) 00181 { 00182 QTextBlock block(ablock); 00183 00184 QTextList *list = block.textList(); 00185 if (!list) { 00186 return; 00187 } 00188 00189 QList<QTextList*> lists; 00190 00191 while (block.isValid() && block.textList()) { 00192 if (list->item(0) == block) { 00193 // Item zero in a list is the first block in the list of blocks that make up a list. 00194 QTextListFormat::Style style = list->format().style(); 00195 d->builder->beginList(style); 00196 00197 lists.append(list); 00198 } 00199 00200 d->builder->beginListItem(); 00201 processBlockContents(block); 00202 d->builder->endListItem(); 00203 00204 block = block.next(); 00205 00206 if (block.isValid()) { 00207 QTextList *newList = block.textList(); 00208 00209 if (!newList) { 00210 while (!lists.isEmpty()) { 00211 lists.removeLast(); 00212 d->builder->endList(); 00213 } 00214 } else if (newList == list) { 00215 //Next block is on the same list; Handled on next iteration. 00216 continue; 00217 } else if (newList != list) { 00218 if (newList->item(0) == block) { 00219 list = newList; 00220 continue; 00221 } else { 00222 while (!lists.isEmpty()) { 00223 if (block.textList() != lists.last()) { 00224 lists.removeLast(); 00225 d->builder->endList(); 00226 } else { 00227 break; 00228 } 00229 } 00230 continue; 00231 } 00232 } 00233 } else { 00234 // Next block is not valid. Maybe at EOF. Close all open lists. 00235 // TODO: Figure out how to handle lists in adjacent table cells. 00236 while (!lists.isEmpty()) { 00237 lists.removeLast(); 00238 d->builder->endList(); 00239 } 00240 } 00241 } 00242 } 00243 00244 void KMarkupDirector::processBlockContents(const QTextBlock &block) 00245 { 00246 QTextBlockFormat blockFormat = block.blockFormat(); 00247 Qt::Alignment blockAlignment = blockFormat.alignment(); 00248 00249 // TODO: decide when to use <h1> etc. 00250 00251 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { 00252 d->builder->insertHorizontalRule(); 00253 return; 00254 } 00255 00256 QTextBlock::iterator it; 00257 it = block.begin(); 00258 00259 // The beginning is the end. This is an empty block. Insert a newline and move on. 00260 // This is what gets generated by a QTextEdit... 00261 if (it.atEnd()) { 00262 // kDebug() << "The beginning is the end"; 00263 d->builder->addNewline(); 00264 return; 00265 } 00266 00267 QTextFragment fragment = it.fragment(); 00268 00269 // .. but if a sequence such as '<br /><br />' is imported into a document with setHtml, Separator_Line 00270 // characters are inserted here within one block. See testNewlines and testNewlinesThroughQTextEdit. 00271 if (fragment.isValid()) { 00272 QTextCharFormat fragmentFormat = fragment.charFormat(); 00273 00274 if (!fragmentFormat.isImageFormat() && fragment.text().at(0).category() == QChar::Separator_Line) { 00275 00276 // Consecutive newlines in a qtextdocument are in a single fragment if inserted with setHtml. 00277 foreach(const QChar &c, fragment.text()) { 00278 // kDebug() << c; 00279 if (c.category() == QChar::Separator_Line) { 00280 d->builder->addNewline(); 00281 } 00282 } 00283 return; 00284 } 00285 } 00286 00287 // Don't have p tags inside li tags. 00288 if (!block.textList()) 00289 { 00290 // Don't instruct builders to use margins. The rich text widget doesn't have an action for them yet, 00291 // So users can't edit them. See bug http://bugs.kde.org/show_bug.cgi?id=160600 00292 d->builder->beginParagraph(blockAlignment //, 00293 // blockFormat.topMargin(), 00294 // blockFormat.bottomMargin(), 00295 // blockFormat.leftMargin(), 00296 // blockFormat.rightMargin() 00297 ); 00298 } 00299 while (!it.atEnd()) { 00300 fragment = it.fragment(); 00301 if (fragment.isValid()) { 00302 QTextCharFormat fragmentFormat = fragment.charFormat(); 00303 00304 if (fragmentFormat.isImageFormat()) { 00305 // TODO: Close any open format elements? 00306 QTextImageFormat imageFormat = fragmentFormat.toImageFormat(); 00307 d->builder->insertImage(imageFormat.name(), imageFormat.width(), imageFormat.height()); 00308 ++it; 00309 continue; 00310 } else { 00311 // The order of closing and opening tags can determine whether generated html is valid or not. 00312 // When processing a document with formatting which appears as '<b><i>Some</i> formatted<b> text', 00313 // the correct generated output will contain '<strong><em>Some</em> formatted<strong> text'. 00314 // However, processing text which appears as '<i><b>Some</b> formatted<i> text' might be incorrectly rendered 00315 // as '<strong><em>Some</strong> formatted</em> text' if tags which start at the same fragment are 00316 // opened out of order. Here, tags are not nested properly, and the html would 00317 // not be valid or render correctly by unforgiving parsers (like QTextEdit). 00318 // One solution is to make the order of opening tags dynamic. In the above case, the em tag would 00319 // be opened before the strong tag '<em><strong>Some</strong> formatted</em> text'. That would 00320 // require knowledge of which tag is going to close first. That might be possible by examining 00321 // the 'next' QTextFragment while processing one. 00322 // 00323 // The other option is to do pessimistic closing of tags. 00324 // In the above case, this means that if a fragment has two or more formats applied (bold and italic here), 00325 // and one of them is closed, then all tags should be closed first. They will of course be reopened 00326 // if necessary while processing the next fragment. 00327 // The above case would be rendered as '<strong><em>Some</em></strong><em> formatted</em> text'. 00328 // 00329 // The first option is taken here, as the redundant opening and closing tags in the second option 00330 // didn't appeal. 00331 // See testDoubleStartDifferentFinish, testDoubleStartDifferentFinishReverseOrder 00332 00333 d->processOpeningElements(it); 00334 00335 // If a sequence such as '<br /><br />' is imported into a document with setHtml, LineSeparator 00336 // characters are inserted. Here I make sure to put them back. 00337 QStringList sl = fragment.text().split(QChar( QChar::LineSeparator ) ); 00338 QStringListIterator i(sl); 00339 bool paraClosed = false; 00340 while (i.hasNext()) 00341 { 00342 d->builder->appendLiteralText(i.next()); 00343 if (i.hasNext()) 00344 { 00345 if (i.peekNext().isEmpty()) 00346 { 00347 if (!paraClosed) 00348 { 00349 d->builder->endParagraph(); 00350 paraClosed = true; 00351 } 00352 d->builder->addNewline(); 00353 } else if (paraClosed) { 00354 d->builder->beginParagraph(blockAlignment); 00355 paraClosed = false; 00356 } 00357 } 00358 } 00359 00360 ++it; 00361 d->processClosingElements(it); 00362 } 00363 } 00364 } 00365 00366 // Don't have p tags inside li tags. 00367 if (!block.textList()) 00368 { 00369 d->builder->endParagraph(); 00370 } 00371 00372 } 00373 00374 void KMarkupDirector::constructContent(QTextDocument* doc) 00375 { 00376 QTextFrame *rootFrame = doc->rootFrame(); 00377 processFrame(rootFrame); 00378 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:17:40 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:17:40 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.