File indexing completed on 2025-01-12 13:05:53
0001 /* This file is part of the KDE project 0002 Copyright (C) 2000 David Faure <faure@kde.org> 0003 Copyright (C) 2000 Norbert Andres <nandres@web.de> 0004 Copyright (C) 2005 Laurent Montel <montel@kde.org> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 * Boston, MA 02110-1301, USA. 0020 */ 0021 0022 #include <opencalcexport.h> 0023 0024 #include <float.h> 0025 #include <math.h> 0026 0027 #include <QDateTime> 0028 #include <QDomDocument> 0029 #include <QFile> 0030 #include <QRegExp> 0031 #include <QList> 0032 #include <QByteArray> 0033 0034 #include <kdebug.h> 0035 #include <kcodecs.h> 0036 #include <kpluginfactory.h> 0037 #include <klocale.h> 0038 0039 #include <KoDocumentInfo.h> 0040 #include <KoUnit.h> 0041 #include <KoFilterChain.h> 0042 0043 // Don't show this warning: it occurs because gcc gets confused, but 0044 // it _is_ used. 0045 #ifdef __GNUC__ 0046 #pragma GCC diagnostic ignored "-Wunused-function" 0047 #endif 0048 #include <sheets/part/AboutData.h> // for version 0049 #include <sheets/part/Canvas.h> 0050 #include <sheets/CalculationSettings.h> 0051 #include <sheets/Cell.h> 0052 #include <sheets/part/Doc.h> 0053 #include <sheets/HeaderFooter.h> 0054 #include <sheets/calligra_sheets_limits.h> 0055 #include <sheets/Map.h> 0056 #include <sheets/NamedAreaManager.h> 0057 #include <sheets/PrintSettings.h> 0058 #include <sheets/RowColumnFormat.h> 0059 #include <sheets/RowFormatStorage.h> 0060 #include <sheets/Sheet.h> 0061 #include <sheets/Style.h> 0062 #include <sheets/StyleManager.h> 0063 #include <sheets/Util.h> 0064 #include <sheets/part/View.h> 0065 0066 using namespace Calligra::Sheets; 0067 0068 typedef QList<QString> AreaList; 0069 0070 K_PLUGIN_FACTORY_WITH_JSON(OpenCalcExportFactory, "calligra_filter_sheets2opencalc.json", 0071 registerPlugin<OpenCalcExport>();) 0072 0073 #define STOPEXPORT \ 0074 do \ 0075 { \ 0076 delete store; \ 0077 return false; \ 0078 } while(0) 0079 0080 OpenCalcExport::OpenCalcExport(QObject* parent, const QVariantList &) 0081 : KoFilter(parent), m_locale(0) 0082 { 0083 } 0084 0085 KoFilter::ConversionStatus OpenCalcExport::convert(const QByteArray & from, 0086 const QByteArray & to) 0087 { 0088 /* later... 0089 KSpreadLeader * leader = new KSpreadLeader( m_chain ); 0090 OpenCalcWorker * worker = new OpenCalcWorker(); 0091 leader->setWorker( worker ); 0092 0093 KoFilter::ConversionStatus status = leader->convert(); 0094 0095 delete worker; 0096 delete leader; 0097 0098 return status; 0099 */ 0100 0101 KoDocument * document = m_chain->inputDocument(); 0102 0103 if (!document) 0104 return KoFilter::StupidError; 0105 0106 if (!qobject_cast<const Calligra::Sheets::Doc *>(document)) { 0107 kWarning(30518) << "document isn't a Calligra::Sheets::Doc but a " 0108 << document->metaObject()->className() << endl; 0109 return KoFilter::NotImplemented; 0110 } 0111 0112 if ((to != "application/vnd.sun.xml.calc") || (from != "application/x-kspread")) { 0113 kWarning(30518) << "Invalid mimetypes " << to << " " << from; 0114 return KoFilter::NotImplemented; 0115 } 0116 0117 const Doc * ksdoc = static_cast<const Doc *>(document); 0118 0119 if (ksdoc->mimeType() != "application/x-kspread") { 0120 kWarning(30518) << "Invalid document mimetype " << ksdoc->mimeType(); 0121 return KoFilter::NotImplemented; 0122 } 0123 0124 m_locale = static_cast<Doc*>(document)->map()->calculationSettings()->locale(); 0125 if (!writeFile(ksdoc)) 0126 return KoFilter::CreationError; 0127 0128 emit sigProgress(100); 0129 0130 return KoFilter::OK; 0131 } 0132 0133 bool OpenCalcExport::writeFile(const Doc * ksdoc) 0134 { 0135 KoStore * store = KoStore::createStore(m_chain->outputFile(), KoStore::Write, "", KoStore::Zip); 0136 0137 if (!store) 0138 return false; 0139 0140 uint filesWritten = 0; 0141 0142 if (!exportContent(store, ksdoc)) 0143 STOPEXPORT; 0144 else 0145 filesWritten |= contentXML; 0146 0147 // TODO: pass sheet number and cell number 0148 if (!exportDocInfo(store, ksdoc)) 0149 STOPEXPORT; 0150 else 0151 filesWritten |= metaXML; 0152 0153 if (!exportStyles(store, ksdoc)) 0154 STOPEXPORT; 0155 else 0156 filesWritten |= stylesXML; 0157 0158 if (!exportSettings(store, ksdoc)) 0159 STOPEXPORT; 0160 else 0161 filesWritten |= settingsXML; 0162 0163 if (!writeMetaFile(store, filesWritten)) 0164 STOPEXPORT; 0165 0166 // writes zip file to disc 0167 delete store; 0168 store = 0; 0169 0170 return true; 0171 } 0172 0173 bool OpenCalcExport::exportDocInfo(KoStore * store, const Doc* ksdoc) 0174 { 0175 if (!store->open("meta.xml")) 0176 return false; 0177 0178 KoDocumentInfo * docInfo = ksdoc->documentInfo(); 0179 0180 QDomDocument meta; 0181 meta.appendChild(meta.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); 0182 0183 QDomElement content = meta.createElement("office:document-meta"); 0184 content.setAttribute("xmlns:office", "http://openoffice.org/2000/office"); 0185 content.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 0186 content.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"); 0187 content.setAttribute("xmlns:meta", "http://openoffice.org/2000/meta"); 0188 content.setAttribute("office:version", "1.0"); 0189 0190 QDomNode officeMeta = meta.createElement("office:meta"); 0191 0192 QDomElement data = meta.createElement("meta:generator"); 0193 QString app("KSpread "); 0194 app += Calligra::Sheets::version; 0195 data.appendChild(meta.createTextNode(app)); 0196 officeMeta.appendChild(data); 0197 0198 data = meta.createElement("meta:initial-creator"); 0199 data.appendChild(meta.createTextNode(docInfo->aboutInfo("initial-creator"))); 0200 officeMeta.appendChild(data); 0201 0202 data = meta.createElement("meta:creator"); 0203 data.appendChild(meta.createTextNode(docInfo->authorInfo("creator"))); 0204 officeMeta.appendChild(data); 0205 0206 data = meta.createElement("dc:description"); 0207 data.appendChild(meta.createTextNode(docInfo->aboutInfo("description"))); 0208 officeMeta.appendChild(data); 0209 0210 data = meta.createElement("meta:keywords"); 0211 QDomElement dataItem = meta.createElement("meta:keyword"); 0212 dataItem.appendChild(meta.createTextNode(docInfo->aboutInfo("keyword"))); 0213 data.appendChild(dataItem); 0214 officeMeta.appendChild(data); 0215 0216 data = meta.createElement("dc:title"); 0217 data.appendChild(meta.createTextNode(docInfo->aboutInfo("title"))); 0218 officeMeta.appendChild(data); 0219 0220 data = meta.createElement("dc:subject"); 0221 data.appendChild(meta.createTextNode(docInfo->aboutInfo("subject"))); 0222 officeMeta.appendChild(data); 0223 0224 const QDateTime dt(QDateTime::currentDateTime()); 0225 if (dt.isValid()) { 0226 data = meta.createElement("dc:date"); 0227 data.appendChild(meta.createTextNode(dt.toString(Qt::ISODate))); 0228 officeMeta.appendChild(data); 0229 } 0230 0231 /* TODO: 0232 <meta:creation-date>2003-01-08T23:57:31</meta:creation-date> 0233 <dc:language>en-US</dc:language> 0234 <meta:editing-cycles>2</meta:editing-cycles> 0235 <meta:editing-duration>PT38S</meta:editing-duration> 0236 <meta:user-defined meta:name="Info 3"/> 0237 <meta:user-defined meta:name="Info 4"/> 0238 */ 0239 0240 data = meta.createElement("meta:document-statistic"); 0241 data.setAttribute("meta:table-count", QString::number(ksdoc->map()->count())); 0242 // TODO: data.setAttribute( "meta:cell-count", ); 0243 officeMeta.appendChild(data); 0244 0245 content.appendChild(officeMeta); 0246 meta.appendChild(content); 0247 0248 QByteArray doc(meta.toByteArray()); 0249 kDebug(30518) << "Meta:" << doc; 0250 0251 store->write(doc, doc.length()); 0252 0253 if (!store->close()) 0254 return false; 0255 0256 return true; 0257 } 0258 0259 bool OpenCalcExport::exportSettings(KoStore * store, const Doc * ksdoc) 0260 { 0261 if (!store->open("settings.xml")) 0262 return false; 0263 0264 QDomDocument doc; 0265 doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); 0266 0267 QDomElement settings = doc.createElement("office:document-settings"); 0268 settings.setAttribute("xmlns:office", "http://openoffice.org/2000/office"); 0269 settings.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 0270 settings.setAttribute("xmlns:config", "http://openoffice.org/2001/config"); 0271 settings.setAttribute("office:version", "1.0"); 0272 0273 QDomElement begin = doc.createElement("office:settings"); 0274 0275 QDomElement configItem = doc.createElement("config:config-item-set"); 0276 configItem.setAttribute("config:name", "view-settings"); 0277 0278 QDomElement mapIndexed = doc.createElement("config:config-item-map-indexed"); 0279 mapIndexed.setAttribute("config:name", "Views"); 0280 configItem.appendChild(mapIndexed); 0281 0282 QDomElement mapItem = doc.createElement("config:config-item-map-entry"); 0283 0284 QDomElement attribute = doc.createElement("config:config-item"); 0285 attribute.setAttribute("config:name", "ActiveTable"); 0286 attribute.setAttribute("config:type", "string"); 0287 0288 View *view = const_cast<Doc*>(ksdoc)->documentPart()->views().isEmpty() ? 0 : static_cast<View*>(const_cast<Doc*>(ksdoc)->documentPart()->views().first()); 0289 QString activeTable; 0290 if (view) { // no view if embedded document 0291 Canvas * canvas = view->canvasWidget(); 0292 activeTable = canvas->activeSheet()->sheetName(); 0293 // save current sheet selection before to save marker, otherwise current pos is not saved 0294 view->saveCurrentSheetSelection(); 0295 } 0296 attribute.appendChild(doc.createTextNode(activeTable)); 0297 mapItem.appendChild(attribute); 0298 0299 QDomElement configmaped = doc.createElement("config:config-item-map-named"); 0300 configmaped.setAttribute("config:name", "Tables"); 0301 0302 foreach(Sheet* sheet, ksdoc->map()->sheetList()) { 0303 QPoint marker; 0304 if (view) { 0305 marker = view->markerFromSheet(sheet); 0306 } 0307 QDomElement tmpItemMapNamed = doc.createElement("config:config-item-map-entry"); 0308 tmpItemMapNamed.setAttribute("config:name", sheet->sheetName()); 0309 0310 QDomElement sheetAttribute = doc.createElement("config:config-item"); 0311 sheetAttribute.setAttribute("config:name", "CursorPositionX"); 0312 sheetAttribute.setAttribute("config:type", "int"); 0313 sheetAttribute.appendChild(doc.createTextNode(QString::number(marker.x()))); 0314 tmpItemMapNamed.appendChild(sheetAttribute); 0315 0316 sheetAttribute = doc.createElement("config:config-item"); 0317 sheetAttribute.setAttribute("config:name", "CursorPositionY"); 0318 sheetAttribute.setAttribute("config:type", "int"); 0319 sheetAttribute.appendChild(doc.createTextNode(QString::number(marker.y()))); 0320 tmpItemMapNamed.appendChild(sheetAttribute); 0321 0322 configmaped.appendChild(tmpItemMapNamed); 0323 } 0324 mapItem.appendChild(configmaped); 0325 0326 0327 0328 mapIndexed.appendChild(mapItem); 0329 0330 begin.appendChild(configItem); 0331 0332 settings.appendChild(begin); 0333 0334 doc.appendChild(settings); 0335 0336 QByteArray f(doc.toByteArray()); 0337 kDebug(30518) << "Settings:" << (char const *) f; 0338 0339 store->write(f, f.length()); 0340 0341 if (!store->close()) 0342 return false; 0343 0344 return true; 0345 } 0346 0347 bool OpenCalcExport::exportContent(KoStore * store, const Doc * ksdoc) 0348 { 0349 if (!store->open("content.xml")) 0350 return false; 0351 0352 createDefaultStyles(); 0353 0354 QDomDocument doc; 0355 doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); 0356 0357 QDomElement content = doc.createElement("office:document-content"); 0358 content.setAttribute("xmlns:office", "http://openoffice.org/2000/office"); 0359 content.setAttribute("xmlns:style", "http://openoffice.org/2000/style"); 0360 content.setAttribute("xmlns:text", "http://openoffice.org/2000/text"); 0361 content.setAttribute("xmlns:table", "http://openoffice.org/2000/table"); 0362 content.setAttribute("xmlns:draw", "http://openoffice.org/2000/drawing"); 0363 content.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format"); 0364 content.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 0365 content.setAttribute("xmlns:number", "http://openoffice.org/2000/datastyle"); 0366 content.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg"); 0367 content.setAttribute("xmlns:chart", "http://openoffice.org/2000/chart"); 0368 content.setAttribute("xmlns:dr3d", "http://openoffice.org/2000/dr3d"); 0369 content.setAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML"); 0370 content.setAttribute("xmlns:form", "http://openoffice.org/2000/form"); 0371 content.setAttribute("xmlns:script", "http://openoffice.org/2000/script"); 0372 content.setAttribute("office:class", "spreadsheet"); 0373 content.setAttribute("office:version", "1.0"); 0374 0375 QDomElement data = doc.createElement("office:script"); 0376 content.appendChild(data); 0377 0378 if (!exportBody(doc, content, ksdoc)) 0379 return false; 0380 0381 doc.appendChild(content); 0382 0383 QByteArray f(doc.toByteArray()); 0384 kDebug(30518) << "Content:" << (char const *) f; 0385 0386 store->write(f, f.length()); 0387 0388 if (!store->close()) 0389 return false; 0390 0391 return true; 0392 } 0393 0394 void exportNamedExpr(Doc* kspreadDoc, QDomDocument & doc, QDomElement & parent, 0395 AreaList const & namedAreas) 0396 { 0397 Sheet* sheet = 0; 0398 QRect range; 0399 for (int i = 0; i < namedAreas.count(); ++i) { 0400 QDomElement namedRange = doc.createElement("table:named-range"); 0401 0402 sheet = kspreadDoc->map()->namedAreaManager()->sheet(namedAreas[i]); 0403 if (!sheet) 0404 continue; 0405 range = kspreadDoc->map()->namedAreaManager()->namedArea(namedAreas[i]).firstRange(); 0406 0407 namedRange.setAttribute("table:name", namedAreas[i]); 0408 namedRange.setAttribute("table:base-cell-address", Odf::convertRefToBase(sheet->sheetName(), range)); 0409 namedRange.setAttribute("table:cell-range-address", Odf::convertRefToRange(sheet->sheetName(), range)); 0410 0411 parent.appendChild(namedRange); 0412 } 0413 } 0414 0415 bool OpenCalcExport::exportBody(QDomDocument & doc, QDomElement & content, const Doc * ksdoc) 0416 { 0417 QDomElement fontDecls = doc.createElement("office:font-decls"); 0418 QDomElement autoStyles = doc.createElement("office:automatic-styles"); 0419 QDomElement body = doc.createElement("office:body"); 0420 0421 if (ksdoc->map()->isProtected()) { 0422 body.setAttribute("table:structure-protected", "true"); 0423 0424 QByteArray passwd; 0425 ksdoc->map()->password(passwd); 0426 if (passwd.length() > 0) { 0427 QByteArray str(KCodecs::base64Encode(passwd)); 0428 body.setAttribute("table:protection-key", QString(str.data())); 0429 } 0430 } 0431 0432 foreach(Sheet* sheet, ksdoc->map()->sheetList()) { 0433 SheetStyle ts; 0434 //int maxCols = 1; 0435 //int maxRows = 1; 0436 0437 ts.visible = !sheet->isHidden(); 0438 0439 QDomElement tabElem = doc.createElement("table:table"); 0440 tabElem.setAttribute("table:style-name", m_styles.sheetStyle(ts)); 0441 0442 if (sheet->isProtected()) { 0443 tabElem.setAttribute("table:protected", "true"); 0444 0445 QByteArray passwd; 0446 sheet->password(passwd); 0447 if (passwd.length() > 0) { 0448 QByteArray str(KCodecs::base64Encode(passwd)); 0449 tabElem.setAttribute("table:protection-key", QString(str.data())); 0450 } 0451 } 0452 0453 QString name(sheet->sheetName()); 0454 0455 int n = name.indexOf(' '); 0456 if (n > -1) { 0457 kDebug(30518) << "Sheet name converting:" << name; 0458 name.replace(' ','_'); 0459 kDebug(30518) << "Sheet name converted:" << name; 0460 } 0461 0462 QRect _printRange = sheet->printSettings()->printRegion().lastRange(); 0463 if (_printRange != (QRect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax)))) { 0464 QString range = Odf::convertRangeToRef(name, _printRange); 0465 //kDebug(30518)<<" range :"<<range; 0466 tabElem.setAttribute("table:print-ranges", range); 0467 } 0468 0469 0470 tabElem.setAttribute("table:name", name); 0471 0472 const QRect usedArea = sheet->usedArea(); 0473 0474 exportSheet(doc, tabElem, sheet, usedArea.width(), usedArea.height()); 0475 0476 body.appendChild(tabElem); 0477 } 0478 0479 KoDocument * document = m_chain->inputDocument(); 0480 Doc * kspreadDoc = static_cast<Doc *>(document); 0481 0482 AreaList namedAreas = kspreadDoc->map()->namedAreaManager()->areaNames(); 0483 if (namedAreas.count() > 0) { 0484 QDomElement namedExpr = doc.createElement("table:named-expressions"); 0485 exportNamedExpr(kspreadDoc, doc, namedExpr, namedAreas); 0486 0487 body.appendChild(namedExpr); 0488 } 0489 0490 m_styles.writeStyles(doc, autoStyles); 0491 m_styles.writeFontDecl(doc, fontDecls); 0492 0493 content.appendChild(fontDecls); 0494 content.appendChild(autoStyles); 0495 content.appendChild(body); 0496 0497 return true; 0498 } 0499 0500 void OpenCalcExport::exportSheet(QDomDocument & doc, QDomElement & tabElem, 0501 const Sheet * sheet, int maxCols, int maxRows) 0502 { 0503 kDebug(30518) << "exportSheet:" << sheet->sheetName(); 0504 int i = 1; 0505 0506 while (i <= maxCols) { 0507 const ColumnFormat * column = sheet->columnFormat(i); 0508 ColumnStyle cs; 0509 cs.breakB = ::Style::automatic; 0510 cs.size = POINT_TO_MM(column->width()) / 10; 0511 bool hide = column->isHidden(); 0512 0513 int j = i + 1; 0514 int repeated = 1; 0515 while (j <= maxCols) { 0516 const ColumnFormat *c = sheet->columnFormat(j); 0517 ColumnStyle cs1; 0518 cs1.breakB = ::Style::automatic; 0519 cs1.size = POINT_TO_MM(c->width()) / 10; 0520 0521 if (ColumnStyle::isEqual(&cs, cs1) && (hide == c->isHidden())) 0522 ++repeated; 0523 else 0524 break; 0525 ++j; 0526 } 0527 0528 QDomElement colElem = doc.createElement("table:table-column"); 0529 colElem.setAttribute("table:style-name", m_styles.columnStyle(cs)); 0530 colElem.setAttribute("table:default-cell-style-name", "Default"); //todo fixme create style from cell 0531 if (hide) 0532 colElem.setAttribute("table:visibility", "collapse"); 0533 0534 if (repeated > 1) 0535 colElem.setAttribute("table:number-columns-repeated", QString::number(repeated)); 0536 0537 tabElem.appendChild(colElem); 0538 i += repeated; 0539 } 0540 0541 for (i = 1; i <= maxRows; ++i) { 0542 RowStyle rs; 0543 rs.breakB = ::Style::automatic; 0544 rs.size = POINT_TO_MM(sheet->rowFormats()->rowHeight(i)) / 10; 0545 0546 QDomElement rowElem = doc.createElement("table:table-row"); 0547 rowElem.setAttribute("table:style-name", m_styles.rowStyle(rs)); 0548 if (sheet->rowFormats()->isHidden(i)) 0549 rowElem.setAttribute("table:visibility", "collapse"); 0550 0551 exportCells(doc, rowElem, sheet, i, maxCols); 0552 0553 tabElem.appendChild(rowElem); 0554 } 0555 } 0556 0557 void OpenCalcExport::exportCells(QDomDocument & doc, QDomElement & rowElem, 0558 const Sheet *sheet, int row, int maxCols) 0559 { 0560 int i = 1; 0561 while (i <= maxCols) { 0562 int repeated = 1; 0563 const Cell cell(sheet, i, row); 0564 const Calligra::Sheets::Style style = cell.style(); 0565 QDomElement cellElem; 0566 0567 if (!cell.isPartOfMerged()) 0568 cellElem = doc.createElement("table:table-cell"); 0569 else 0570 cellElem = doc.createElement("table:covered-table-cell"); 0571 0572 QFont font; 0573 Value const value(cell.value()); 0574 font = style.font(); 0575 m_styles.addFont(font); 0576 QString comment = cell.comment(); 0577 0578 CellStyle c; 0579 CellStyle::loadData(c, cell); // TODO: number style 0580 0581 cellElem.setAttribute("table:style-name", m_styles.cellStyle(c)); 0582 0583 // group empty cells with the same style 0584 if (cell.isEmpty() && !comment.isEmpty() && !cell.isPartOfMerged() && !cell.doesMergeCells()) { 0585 int j = i + 1; 0586 while (j <= maxCols) { 0587 const Cell cell1(sheet, j, row); 0588 0589 CellStyle c1; 0590 CellStyle::loadData(c1, cell1); // TODO: number style 0591 0592 if (cell1.isEmpty() && !comment.isEmpty() 0593 && CellStyle::isEqual(&c, c1) && !cell.isPartOfMerged() && !cell.doesMergeCells()) 0594 ++repeated; 0595 else 0596 break; 0597 ++j; 0598 } 0599 if (repeated > 1) 0600 cellElem.setAttribute("table:number-columns-repeated", QString::number(repeated)); 0601 } 0602 0603 if (value.isBoolean()) { 0604 kDebug(30518) << "Type: Boolean"; 0605 cellElem.setAttribute("table:value-type", "boolean"); 0606 cellElem.setAttribute("table:boolean-value", (value.asBoolean() ? "true" : "false")); 0607 } else if (value.isNumber()) { 0608 kDebug(30518) << "Type: Number"; 0609 Format::Type type = style.formatType(); 0610 0611 if (type == Format::Percentage) 0612 cellElem.setAttribute("table:value-type", "percentage"); 0613 else 0614 cellElem.setAttribute("table:value-type", "float"); 0615 0616 cellElem.setAttribute("table:value", QString::number((double)numToDouble(value.asFloat()))); 0617 } else { 0618 kDebug(30518) << "Type:" << value.type(); 0619 } 0620 0621 if (cell.isFormula()) { 0622 kDebug(30518) << "Formula found"; 0623 0624 QString formula(convertFormula(cell.userInput())); 0625 cellElem.setAttribute("table:formula", formula); 0626 } else if (!cell.link().isEmpty()) { 0627 QDomElement link = doc.createElement("text:p"); 0628 QDomElement linkref = doc.createElement("text:a"); 0629 0630 QString tmp = cell.link(); 0631 if (Util::localReferenceAnchor(tmp)) 0632 linkref.setAttribute("xlink:href", ('#' + tmp)); 0633 else 0634 linkref.setAttribute("xlink:href", tmp); 0635 0636 linkref.appendChild(doc.createTextNode(cell.userInput())); 0637 0638 link.appendChild(linkref); 0639 cellElem.appendChild(link); 0640 } else if (!cell.isEmpty()) { 0641 QDomElement textElem = doc.createElement("text:p"); 0642 textElem.appendChild(doc.createTextNode(cell.displayText())); 0643 0644 cellElem.appendChild(textElem); 0645 kDebug(30518) << "Cell StrOut:" << cell.displayText(); 0646 } 0647 0648 if (cell.doesMergeCells()) { 0649 int colSpan = cell.mergedXCells() + 1; 0650 int rowSpan = cell.mergedYCells() + 1; 0651 0652 if (colSpan > 1) 0653 cellElem.setAttribute("table:number-columns-spanned", QString::number(colSpan)); 0654 0655 if (rowSpan > 1) 0656 cellElem.setAttribute("table:number-rows-spanned", QString::number(rowSpan)); 0657 } 0658 0659 if (!comment.isEmpty()) { 0660 QDomElement annotation = doc.createElement("office:annotation"); 0661 QDomElement text = doc.createElement("text:p"); 0662 text.appendChild(doc.createTextNode(comment)); 0663 0664 annotation.appendChild(text); 0665 cellElem.appendChild(annotation); 0666 } 0667 0668 rowElem.appendChild(cellElem); 0669 0670 i += repeated; 0671 } 0672 } 0673 0674 bool OpenCalcExport::exportStyles(KoStore * store, const Doc *ksdoc) 0675 { 0676 if (!store->open("styles.xml")) 0677 return false; 0678 0679 QDomDocument doc; 0680 doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); 0681 0682 QDomElement content = doc.createElement("office:document-styles"); 0683 content.setAttribute("xmlns:office", "http://openoffice.org/2000/office"); 0684 content.setAttribute("xmlns:style", "http://openoffice.org/2000/style"); 0685 content.setAttribute("xmlns:text", "http://openoffice.org/2000/text"); 0686 content.setAttribute("xmlns:table", "http://openoffice.org/2000/table"); 0687 content.setAttribute("xmlns:draw", "http://openoffice.org/2000/drawing"); 0688 content.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format"); 0689 content.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 0690 content.setAttribute("xmlns:number", "http://openoffice.org/2000/datastyle"); 0691 content.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg"); 0692 content.setAttribute("xmlns:chart", "http://openoffice.org/2000/chart"); 0693 content.setAttribute("xmlns:dr3d", "http://openoffice.org/2000/dr3d"); 0694 content.setAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML"); 0695 content.setAttribute("xmlns:form", "http://openoffice.org/2000/form"); 0696 content.setAttribute("xmlns:script", "http://openoffice.org/2000/script"); 0697 content.setAttribute("office:version", "1.0"); 0698 0699 // order important here! 0700 QDomElement officeStyles = doc.createElement("office:styles"); 0701 exportDefaultCellStyle(doc, officeStyles); 0702 0703 QDomElement fontDecls = doc.createElement("office:font-decls"); 0704 m_styles.writeFontDecl(doc, fontDecls); 0705 0706 // TODO: needs in new number/date/time parser... 0707 // exportDefaultNumberStyles( doc, officeStyles ); 0708 0709 QDomElement defaultStyle = doc.createElement("style:style"); 0710 defaultStyle.setAttribute("style:name", "Default"); 0711 defaultStyle.setAttribute("style:family", "table-cell"); 0712 officeStyles.appendChild(defaultStyle); 0713 0714 QDomElement autoStyles = doc.createElement("office:automatic-styles"); 0715 exportPageAutoStyles(doc, autoStyles, ksdoc); 0716 0717 QDomElement masterStyles = doc.createElement("office:master-styles"); 0718 exportMasterStyles(doc, masterStyles, ksdoc); 0719 0720 content.appendChild(fontDecls); 0721 content.appendChild(officeStyles); 0722 content.appendChild(autoStyles); 0723 content.appendChild(masterStyles); 0724 0725 doc.appendChild(content); 0726 0727 QByteArray f(doc.toByteArray()); 0728 kDebug(30518) << "Content:" << (char const *) f; 0729 0730 store->write(f, f.length()); 0731 0732 if (!store->close()) 0733 return false; 0734 0735 return true; 0736 } 0737 0738 void OpenCalcExport::exportDefaultCellStyle(QDomDocument & doc, QDomElement & officeStyles) 0739 { 0740 QDomElement defStyle = doc.createElement("style:default-style"); 0741 defStyle.setAttribute("style:family", "table-cell"); 0742 0743 KoDocument * document = m_chain->inputDocument(); 0744 Doc * ksdoc = static_cast<Doc *>(document); 0745 0746 const KLocale *locale = ksdoc->map()->calculationSettings()->locale(); 0747 QString language; 0748 QString country; 0749 QString charSet; 0750 QString modifier; 0751 0752 QString l(locale->language()); 0753 KLocale::splitLocale(l, language, country, modifier, charSet); 0754 QFont font(ksdoc->map()->styleManager()->defaultStyle()->font()); 0755 m_styles.addFont(font, true); 0756 0757 QDomElement style = doc.createElement("style:properties"); 0758 style.setAttribute("style:font-name", font.family()); 0759 style.setAttribute("fo:font-size", QString("%1pt").arg(font.pointSize())); 0760 style.setAttribute("style:decimal-places", QString::number(locale->decimalPlaces())); 0761 style.setAttribute("fo:language", language); 0762 style.setAttribute("fo:country", country); 0763 style.setAttribute("style:font-name-asian", "HG Mincho Light J"); 0764 style.setAttribute("style:language-asian", "none"); 0765 style.setAttribute("style:country-asian", "none"); 0766 style.setAttribute("style:font-name-complex", "Arial Unicode MS"); 0767 style.setAttribute("style:language-complex", "none"); 0768 style.setAttribute("style:country-complex", "none"); 0769 style.setAttribute("style:tab-stop-distance", "1.25cm"); 0770 0771 defStyle.appendChild(style); 0772 officeStyles.appendChild(defStyle); 0773 } 0774 0775 void OpenCalcExport::createDefaultStyles() 0776 { 0777 // TODO: default number styles, currency styles,... 0778 } 0779 0780 void OpenCalcExport::exportPageAutoStyles(QDomDocument & doc, QDomElement & autoStyles, 0781 const Doc *ksdoc) 0782 { 0783 const Sheet * sheet = ksdoc->map()->sheetList().first(); 0784 0785 float width = 20.999f; 0786 float height = 29.699f; 0787 0788 if (sheet) { 0789 width = sheet->printSettings()->pageLayout().width / 10; 0790 height = sheet->printSettings()->pageLayout().height / 10; 0791 } 0792 0793 QString sWidth = QString("%1cm").arg(width); 0794 QString sHeight = QString("%1cm").arg(height); 0795 0796 QDomElement pageMaster = doc.createElement("style:page-master"); 0797 pageMaster.setAttribute("style:name", "pm1"); 0798 0799 QDomElement properties = doc.createElement("style:properties"); 0800 properties.setAttribute("fo:page-width", sWidth); 0801 properties.setAttribute("fo:page-height", sHeight); 0802 properties.setAttribute("fo:border", "0.002cm solid #000000"); 0803 properties.setAttribute("fo:padding", "0cm"); 0804 properties.setAttribute("fo:background-color", "transparent"); 0805 0806 pageMaster.appendChild(properties); 0807 0808 QDomElement header = doc.createElement("style:header-style"); 0809 properties = doc.createElement("style:properties"); 0810 properties.setAttribute("fo:min-height", "0.75cm"); 0811 properties.setAttribute("fo:margin-left", "0cm"); 0812 properties.setAttribute("fo:margin-right", "0cm"); 0813 properties.setAttribute("fo:margin-bottom", "0.25cm"); 0814 0815 header.appendChild(properties); 0816 0817 QDomElement footer = doc.createElement("style:header-style"); 0818 properties = doc.createElement("style:properties"); 0819 properties.setAttribute("fo:min-height", "0.75cm"); 0820 properties.setAttribute("fo:margin-left", "0cm"); 0821 properties.setAttribute("fo:margin-right", "0cm"); 0822 properties.setAttribute("fo:margin-bottom", "0.25cm"); 0823 0824 footer.appendChild(properties); 0825 0826 pageMaster.appendChild(header); 0827 pageMaster.appendChild(footer); 0828 0829 autoStyles.appendChild(pageMaster); 0830 } 0831 0832 void OpenCalcExport::exportMasterStyles(QDomDocument & doc, QDomElement & masterStyles, 0833 const Doc * ksdoc) 0834 { 0835 QDomElement masterPage = doc.createElement("style:master-page"); 0836 masterPage.setAttribute("style:name", "Default"); 0837 masterPage.setAttribute("style:page-master-name", "pm1"); 0838 0839 const Sheet * sheet = ksdoc->map()->sheetList().first(); 0840 0841 QString headerLeft; 0842 QString headerCenter; 0843 QString headerRight; 0844 QString footerLeft; 0845 QString footerCenter; 0846 QString footerRight; 0847 0848 if (sheet) { 0849 const HeaderFooter *const headerFooter = sheet->headerFooter(); 0850 headerLeft = headerFooter->headLeft(); 0851 headerCenter = headerFooter->headMid(); 0852 headerRight = headerFooter->headRight(); 0853 footerLeft = headerFooter->footLeft(); 0854 footerCenter = headerFooter->footMid(); 0855 footerRight = headerFooter->footRight(); 0856 } 0857 0858 if ((headerLeft.length() > 0) || (headerCenter.length() > 0) 0859 || (headerRight.length() > 0)) { 0860 QDomElement header = doc.createElement("style:header"); 0861 QDomElement left = doc.createElement("style:region-left"); 0862 QDomElement text = doc.createElement("text:p"); 0863 convertPart(headerLeft, doc, text, ksdoc); 0864 left.appendChild(text); 0865 0866 QDomElement center = doc.createElement("style:region-center"); 0867 QDomElement text1 = doc.createElement("text:p"); 0868 convertPart(headerCenter, doc, text1, ksdoc); 0869 center.appendChild(text1); 0870 0871 QDomElement right = doc.createElement("style:region-right"); 0872 QDomElement text2 = doc.createElement("text:p"); 0873 convertPart(headerRight, doc, text2, ksdoc); 0874 right.appendChild(text2); 0875 0876 header.appendChild(left); 0877 header.appendChild(center); 0878 header.appendChild(right); 0879 0880 masterPage.appendChild(header); 0881 } else { 0882 QDomElement header = doc.createElement("style:header"); 0883 QDomElement text = doc.createElement("text:p"); 0884 QDomElement name = doc.createElement("text:sheet-name"); 0885 name.appendChild(doc.createTextNode("???")); 0886 text.appendChild(name); 0887 header.appendChild(text); 0888 0889 masterPage.appendChild(header); 0890 } 0891 0892 if ((footerLeft.length() > 0) || (footerCenter.length() > 0) 0893 || (footerRight.length() > 0)) { 0894 QDomElement footer = doc.createElement("style:footer"); 0895 QDomElement left = doc.createElement("style:region-left"); 0896 QDomElement text = doc.createElement("text:p"); 0897 convertPart(footerLeft, doc, text, ksdoc); 0898 left.appendChild(text); 0899 0900 QDomElement center = doc.createElement("style:region-center"); 0901 QDomElement text1 = doc.createElement("text:p"); 0902 convertPart(footerCenter, doc, text1, ksdoc); 0903 center.appendChild(text1); 0904 0905 QDomElement right = doc.createElement("style:region-right"); 0906 QDomElement text2 = doc.createElement("text:p"); 0907 convertPart(footerRight, doc, text2, ksdoc); 0908 right.appendChild(text2); 0909 0910 footer.appendChild(left); 0911 footer.appendChild(center); 0912 footer.appendChild(right); 0913 0914 masterPage.appendChild(footer); 0915 } else { 0916 QDomElement footer = doc.createElement("style:footer"); 0917 QDomElement text = doc.createElement("text:p"); 0918 text.appendChild(doc.createTextNode(i18n("Page "))); 0919 QDomElement number = doc.createElement("text:page-number"); 0920 number.appendChild(doc.createTextNode("1")); 0921 text.appendChild(number); 0922 footer.appendChild(text); 0923 0924 masterPage.appendChild(footer); 0925 } 0926 0927 masterStyles.appendChild(masterPage); 0928 } 0929 0930 void OpenCalcExport::addText(QString const & text, QDomDocument & doc, 0931 QDomElement & parent) 0932 { 0933 if (text.length() > 0) 0934 parent.appendChild(doc.createTextNode(text)); 0935 } 0936 0937 void OpenCalcExport::convertPart(QString const & part, QDomDocument & doc, 0938 QDomElement & parent, const Doc * ksdoc) 0939 { 0940 QString text; 0941 QString var; 0942 0943 bool inVar = false; 0944 uint i = 0; 0945 uint l = part.length(); 0946 while (i < l) { 0947 if (inVar || part[i] == '<') { 0948 inVar = true; 0949 var += part[i]; 0950 if (part[i] == '>') { 0951 inVar = false; 0952 if (var == "<page>") { 0953 addText(text, doc, parent); 0954 0955 QDomElement page = doc.createElement("text:page-number"); 0956 page.appendChild(doc.createTextNode("1")); 0957 parent.appendChild(page); 0958 } else if (var == "<pages>") { 0959 addText(text, doc, parent); 0960 0961 QDomElement page = doc.createElement("text:page-count"); 0962 page.appendChild(doc.createTextNode("99")); 0963 parent.appendChild(page); 0964 } else if (var == "<date>") { 0965 addText(text, doc, parent); 0966 0967 QDomElement t = doc.createElement("text:date"); 0968 t.setAttribute("text:date-value", "0-00-00"); 0969 // todo: "style:data-style-name", "N2" 0970 t.appendChild(doc.createTextNode(QDate::currentDate().toString())); 0971 parent.appendChild(t); 0972 } else if (var == "<time>") { 0973 addText(text, doc, parent); 0974 0975 QDomElement t = doc.createElement("text:time"); 0976 t.appendChild(doc.createTextNode(QTime::currentTime().toString())); 0977 parent.appendChild(t); 0978 } else if (var == "<file>") { // filepath + name 0979 addText(text, doc, parent); 0980 0981 QDomElement t = doc.createElement("text:file-name"); 0982 t.setAttribute("text:display", "full"); 0983 t.appendChild(doc.createTextNode("???")); 0984 parent.appendChild(t); 0985 } else if (var == "<name>") { // filename 0986 addText(text, doc, parent); 0987 0988 QDomElement t = doc.createElement("text:title"); 0989 t.appendChild(doc.createTextNode("???")); 0990 parent.appendChild(t); 0991 } else if (var == "<author>") { 0992 KoDocumentInfo * docInfo = ksdoc->documentInfo(); 0993 0994 text += docInfo->authorInfo("creator"); 0995 0996 addText(text, doc, parent); 0997 } else if (var == "<email>") { 0998 KoDocumentInfo * docInfo = ksdoc->documentInfo(); 0999 1000 text += docInfo->authorInfo("email"); 1001 1002 addText(text, doc, parent); 1003 } else if (var == "<org>") { 1004 KoDocumentInfo * docInfo = ksdoc->documentInfo(); 1005 1006 text += docInfo->authorInfo("company"); 1007 1008 addText(text, doc, parent); 1009 } else if (var == "<sheet>") { 1010 addText(text, doc, parent); 1011 1012 QDomElement s = doc.createElement("text:sheet-name"); 1013 s.appendChild(doc.createTextNode("???")); 1014 parent.appendChild(s); 1015 } else { 1016 // no known variable: 1017 text += var; 1018 addText(text, doc, parent); 1019 } 1020 1021 text.clear(); 1022 var.clear(); 1023 } 1024 } else { 1025 text += part[i]; 1026 } 1027 ++i; 1028 } 1029 if (!text.isEmpty() || !var.isEmpty()) { 1030 //we don't have var at the end =>store it 1031 addText(text + var, doc, parent); 1032 } 1033 } 1034 1035 QString OpenCalcExport::convertFormula(QString const & formula) const 1036 { 1037 // TODO Stefan: Check if Oasis::encodeFormula could be used instead 1038 QChar decimalSymbol('.'); 1039 if (m_locale) { 1040 const QString decimal(m_locale->decimalSymbol()); 1041 if (!decimal.isEmpty()) { 1042 decimalSymbol = decimal.at(0); 1043 } 1044 } 1045 1046 QString s; 1047 QRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)"); 1048 int n = exp.indexIn(formula, 0); 1049 kDebug(30518) << "Exp:" << formula << ", n:" << n << ", Length:" << formula.length() 1050 << ", Matched length: " << exp.matchedLength() << endl; 1051 1052 bool inQuote1 = false; 1053 bool inQuote2 = false; 1054 int i = 0; 1055 int l = (int) formula.length(); 1056 if (l <= 0) 1057 return formula; 1058 while (i < l) { 1059 if ((n != -1) && (n < i)) { 1060 n = exp.indexIn(formula, i); 1061 kDebug(30518) << "Exp:" << formula.right(l - i) << ", n:" << n; 1062 } 1063 if (formula[i] == '"') { 1064 inQuote1 = !inQuote1; 1065 s += formula[i]; 1066 ++i; 1067 continue; 1068 } 1069 if (formula[i] == '\'') { 1070 // named area 1071 inQuote2 = !inQuote2; 1072 ++i; 1073 continue; 1074 } 1075 if (inQuote1 || inQuote2) { 1076 s += formula[i]; 1077 ++i; 1078 continue; 1079 } 1080 if ((formula[i] == '=') && (formula[i + 1] == '=')) { 1081 s += '='; 1082 ++i; ++i; 1083 continue; 1084 } 1085 if (formula[i] == '!') { 1086 QChar c; 1087 int j = (int) s.length() - 1; 1088 1089 while (j >= 0) { 1090 c = s[j]; 1091 if (c == ' ') 1092 s[j] = '_'; 1093 if (!(c.isLetterOrNumber() || c == ' ' || c == '.' 1094 || c == '_')) { 1095 s.insert(j + 1, '['); 1096 break; 1097 } 1098 --j; 1099 } 1100 s += '.'; 1101 ++i; 1102 continue; 1103 } else if (formula[i] == decimalSymbol) { 1104 s += '.'; // decimal point 1105 ++i; 1106 continue; 1107 } 1108 if (n == i) { 1109 int ml = exp.matchedLength(); 1110 if (ml > -1 && (i + ml) < formula.count() && formula[ i + ml ] == '!') { 1111 kDebug(30518) << "No cell ref but sheet name"; 1112 s += formula[i]; 1113 ++i; 1114 continue; 1115 } 1116 if ((i > 0) && (formula[i - 1] != '!')) 1117 s += "[."; 1118 for (int j = 0; j < ml; ++j) { 1119 s += formula[i]; 1120 ++i; 1121 } 1122 s += ']'; 1123 continue; 1124 } 1125 1126 s += formula[i]; 1127 ++i; 1128 } 1129 1130 return s; 1131 } 1132 1133 bool OpenCalcExport::writeMetaFile(KoStore * store, uint filesWritten) 1134 { 1135 store->enterDirectory("META-INF"); 1136 if (!store->open("manifest.xml")) 1137 return false; 1138 1139 QDomImplementation impl; 1140 QDomDocumentType type(impl.createDocumentType("manifest:manifest", "-//OpenOffice.org//DTD Manifest 1.0//EN", "Manifest.dtd")); 1141 1142 QDomDocument meta(type); 1143 meta.appendChild(meta.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); 1144 1145 QDomElement content = meta.createElement("manifest:manifest"); 1146 content.setAttribute("xmlns:manifest", "http://openoffice.org/2001/manifest"); 1147 1148 QDomElement entry = meta.createElement("manifest:file-entry"); 1149 entry.setAttribute("manifest:media-type", "application/vnd.sun.xml.calc"); 1150 entry.setAttribute("manifest:full-path", "/"); 1151 content.appendChild(entry); 1152 1153 entry = meta.createElement("manifest:file-entry"); 1154 content.appendChild(entry); 1155 1156 if (filesWritten & contentXML) { 1157 entry = meta.createElement("manifest:file-entry"); 1158 entry.setAttribute("manifest:media-type", "text/xml"); 1159 entry.setAttribute("manifest:full-path", "content.xml"); 1160 content.appendChild(entry); 1161 } 1162 1163 if (filesWritten & stylesXML) { 1164 entry = meta.createElement("manifest:file-entry"); 1165 entry.setAttribute("manifest:media-type", "text/xml"); 1166 entry.setAttribute("manifest:full-path", "styles.xml"); 1167 content.appendChild(entry); 1168 } 1169 1170 if (filesWritten & metaXML) { 1171 entry = meta.createElement("manifest:file-entry"); 1172 entry.setAttribute("manifest:media-type", "text/xml"); 1173 entry.setAttribute("manifest:full-path", "meta.xml"); 1174 content.appendChild(entry); 1175 } 1176 1177 if (filesWritten & settingsXML) { 1178 entry = meta.createElement("manifest:file-entry"); 1179 entry.setAttribute("manifest:media-type", "text/xml"); 1180 entry.setAttribute("manifest:full-path", "settings.xml"); 1181 content.appendChild(entry); 1182 } 1183 1184 meta.appendChild(content); 1185 1186 QByteArray doc(meta.toByteArray()); 1187 kDebug(30518) << "Manifest:" << doc; 1188 1189 store->write(doc, doc.length()); 1190 1191 if (!store->close()) 1192 return false; 1193 1194 return true; 1195 } 1196 1197 #include <opencalcexport.moc>