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