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>