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>