File indexing completed on 2024-05-12 16:37:11

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2005 David Faure <faure@kde.org>
0003  * Copyright (C) 2007-2009 Thomas Zander <zander@kde.org>
0004  * Copyright (C) 2007-2008 Sebastian Sauer <mail@dipe.org>
0005  * Copyright (C) 2007-2008 Pierre Ducroquet <pinaraf@gmail.com>
0006  * Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Library General Public
0010  * License as published by the Free Software Foundation; either
0011  * version 2 of the License, or (at your option) any later version.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Library General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Library General Public License
0019  * along with this library; see the file COPYING.LIB.  If not, write to
0020  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0021  * Boston, MA 02110-1301, USA.
0022  */
0023 
0024 #include "KWOdfWriter.h"
0025 #include "KWDocument.h"
0026 #include "KWPage.h"
0027 
0028 #include "frames/KWTextFrameSet.h"
0029 #include <KoXmlWriter.h>
0030 #include <KoOdfWriteStore.h>
0031 #include <KoShapeSavingContext.h>
0032 #include <KoGenStyles.h>
0033 
0034 #include <KoTextDocument.h>
0035 #include <KoTextShapeData.h>
0036 #include <KoStyleManager.h>
0037 #include <KoParagraphStyle.h>
0038 #include <KoGridData.h>
0039 #include <KoGuidesData.h>
0040 #include <KoShapeGroup.h>
0041 #include <KoShapeLayer.h>
0042 #include <KoAnnotationLayoutManager.h>
0043 
0044 #include <KoGenChanges.h>
0045 #include <changetracker/KoChangeTracker.h>
0046 #include <KoTextSharedSavingData.h>
0047 #include <KoInlineTextObjectManager.h>
0048 #include <KoVariableManager.h>
0049 
0050 #include <KoStoreDevice.h>
0051 #include <KoDocumentRdfBase.h>
0052 
0053 #include <QBuffer>
0054 #include <QTextCursor>
0055 #include <WordsDebug.h>
0056 #include <QTemporaryFile>
0057 
0058 static const struct {
0059     const char * tag;
0060 } headerFooterTag[] = {
0061     { "style:header" },
0062     { "style:header-left" },
0063     { "style:footer" },
0064     { "style:footer-left" }
0065 };
0066 
0067 QByteArray KWOdfWriter::serializeHeaderFooter(KoShapeSavingContext &context, KWTextFrameSet *fs)
0068 {
0069     const char * tag = 0;
0070     switch (fs->textFrameSetType()) {
0071     case Words::OddPagesHeaderTextFrameSet:  tag = headerFooterTag[0].tag; break;
0072     case Words::EvenPagesHeaderTextFrameSet: tag = headerFooterTag[1].tag; break;
0073     case Words::OddPagesFooterTextFrameSet:  tag = headerFooterTag[2].tag; break;
0074     case Words::EvenPagesFooterTextFrameSet: tag = headerFooterTag[3].tag; break;
0075     default: return QByteArray();
0076     }
0077 
0078     QByteArray content;
0079     QBuffer buffer(&content);
0080     buffer.open(QIODevice::WriteOnly);
0081     KoXmlWriter writer(&buffer);
0082 
0083     KoXmlWriter &savedWriter = context.xmlWriter();
0084 
0085     KoShapeSavingContext::ShapeSavingOptions options = context.options();
0086     context.setOptions(KoShapeSavingContext::AutoStyleInStyleXml | KoShapeSavingContext::ZIndex);
0087     context.setXmlWriter(writer);
0088 
0089     Q_ASSERT(!fs->shapes().isEmpty());
0090     KoTextShapeData *shapedata = qobject_cast<KoTextShapeData *>(fs->shapes().first()->userData());
0091     Q_ASSERT(shapedata);
0092 
0093     writer.startElement(tag);
0094     shapedata->saveOdf(context, m_document->documentRdf());
0095     writer.endElement();
0096 
0097     context.setOptions(options);
0098     context.setXmlWriter(savedWriter);
0099 
0100     return content;
0101 }
0102 
0103 // rename to save pages ?
0104 void KWOdfWriter::saveHeaderFooter(KoShapeSavingContext &context)
0105 {
0106     //debugWords<< "START saveHeaderFooter ############################################";
0107     // first get all the framesets in a nice quick-to-access data structure
0108     // this avoids iterating till we drop
0109     QHash<KWPageStyle, QHash<int, KWTextFrameSet*> > data;
0110     foreach (KWFrameSet *fs, m_document->frameSets()) {
0111         KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*> (fs);
0112         if (! tfs)
0113             continue;
0114         if (! Words::isAutoGenerated(tfs))
0115             continue;
0116         if (tfs->textFrameSetType() == Words::MainTextFrameSet)
0117             continue;
0118         QHash<int, KWTextFrameSet*> set = data.value(tfs->pageStyle());
0119         set.insert(tfs->textFrameSetType(), tfs);
0120         Q_ASSERT(tfs->pageStyle().isValid());
0121         data.insert(tfs->pageStyle(), set);
0122     }
0123 
0124     // save page styles that don't have a header or footer which will be handled later
0125     foreach (const KWPageStyle &pageStyle, m_document->pageManager()->pageStyles()) {
0126         if (data.contains(pageStyle))
0127             continue;
0128 
0129         KoGenStyle masterStyle(KoGenStyle::MasterPageStyle);
0130         KoGenStyle layoutStyle = pageStyle.saveOdf();
0131         if (!pageStyle.displayName().isEmpty() && pageStyle.displayName() != pageStyle.name())
0132             masterStyle.addProperty("style:display-name", pageStyle.displayName());
0133         if (!pageStyle.nextStyleName().isEmpty())
0134             masterStyle.addProperty("style:next-style-name", pageStyle.nextStyleName());
0135         masterStyle.addProperty("style:page-layout-name", context.mainStyles().insert(layoutStyle, "pm"));
0136         QString name = context.mainStyles().insert(masterStyle, pageStyle.name(), KoGenStyles::DontAddNumberToName);
0137         m_masterPages.insert(pageStyle, name);
0138     }
0139 
0140     // We need to flush them out ordered as defined in the specs.
0141     QList<Words::TextFrameSetType> order;
0142     order << Words::OddPagesHeaderTextFrameSet
0143           << Words::EvenPagesHeaderTextFrameSet
0144           << Words::OddPagesFooterTextFrameSet
0145           << Words::EvenPagesFooterTextFrameSet;
0146 
0147     QHash<KWPageStyle, QHash<int, KWTextFrameSet*> >::ConstIterator it = data.constBegin();
0148     QHash<KWPageStyle, QHash<int, KWTextFrameSet*> >::ConstIterator end = data.constEnd();
0149     for(; it != end; ++it) {
0150         const KWPageStyle &pageStyle = it.key();
0151         const QHash<int, KWTextFrameSet*> &headersAndFooters = it.value();
0152 
0153         KoGenStyle masterStyle(KoGenStyle::MasterPageStyle);
0154         //masterStyle.setAutoStyleInStylesDotXml(true);
0155         KoGenStyle layoutStyle = pageStyle.saveOdf();
0156         if (!pageStyle.displayName().isEmpty() && pageStyle.displayName() != pageStyle.name())
0157             masterStyle.addProperty("style:display-name", pageStyle.displayName());
0158         if (!pageStyle.nextStyleName().isEmpty())
0159             masterStyle.addProperty("style:next-style-name", pageStyle.nextStyleName());
0160         masterStyle.addProperty("style:page-layout-name", context.mainStyles().insert(layoutStyle, "pm"));
0161 
0162         int index = 0;
0163         foreach (int type, order) {
0164             if (! headersAndFooters.contains(type))
0165                 continue;
0166             KWTextFrameSet *fs = headersAndFooters.value(type);
0167             Q_ASSERT(fs);
0168             if (fs->shapeCount() == 0) // don't save empty framesets
0169                 continue;
0170 
0171             QByteArray content = serializeHeaderFooter(context, fs);
0172 
0173             if (content.isNull())
0174                 continue;
0175 
0176             masterStyle.addChildElement(QString::number(++index), QString::fromUtf8(content));
0177         }
0178         // append the headerfooter-style to the main-style
0179         QString name = context.mainStyles().insert(masterStyle, pageStyle.name(), KoGenStyles::DontAddNumberToName);
0180         m_masterPages.insert(pageStyle, name);
0181     }
0182 
0183     //foreach (KoGenStyles::NamedStyle s, mainStyles.styles(KoGenStyle::ParagraphAutoStyle))
0184     //    mainStyles.markStyleForStylesXml(s.name);
0185 
0186     //debugWords << "END saveHeaderFooter ############################################";
0187 }
0188 
0189 KWOdfWriter::KWOdfWriter(KWDocument *document)
0190         : QObject(),
0191         m_document(document),
0192         m_shapeTree(4, 2)
0193 {
0194 }
0195 
0196 KWOdfWriter::~KWOdfWriter()
0197 {
0198 }
0199 
0200 // 1.6: KWDocument::saveOasisHelper()
0201 bool KWOdfWriter::save(KoOdfWriteStore &odfStore, KoEmbeddedDocumentSaver &embeddedSaver)
0202 {
0203     //debugWords << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
0204 
0205     KoStore *store = odfStore.store();
0206 
0207     if (! store->open("settings.xml")) {
0208         return false;
0209     }
0210 
0211     saveOdfSettings(store);
0212 
0213     if (!store->close())
0214         return false;
0215 
0216     KoXmlWriter *manifestWriter = odfStore.manifestWriter();
0217 
0218     manifestWriter->addManifestEntry("settings.xml", "text/xml");
0219 
0220     KoXmlWriter *contentWriter = odfStore.contentWriter();
0221     if (!contentWriter)
0222         return false;
0223 
0224     QTemporaryFile tmpChangeFile;
0225     tmpChangeFile.open();
0226     KoXmlWriter *changeWriter = new KoXmlWriter(&tmpChangeFile, 1);
0227     if (!changeWriter)
0228         return false;
0229 
0230     QTemporaryFile tmpTextBodyFile;
0231     tmpTextBodyFile.open();
0232     KoXmlWriter *tmpBodyWriter = new KoXmlWriter(&tmpTextBodyFile, 1);
0233     if (!tmpBodyWriter)
0234         return false;
0235 
0236     KoGenStyles mainStyles;
0237 
0238     KoGenChanges changes;
0239 
0240     KoChangeTracker *changeTracker = m_document->resourceManager()->resource(KoText::ChangeTracker).value<KoChangeTracker*>();
0241 
0242     KoShapeSavingContext context(*tmpBodyWriter, mainStyles, embeddedSaver);
0243     context.addOption(KoShapeSavingContext::ZIndex);
0244 
0245     KoTextSharedSavingData *sharedData = new KoTextSharedSavingData;
0246     sharedData->setGenChanges(changes);
0247     context.addSharedData(KOTEXT_SHARED_SAVING_ID, sharedData);
0248 
0249     // Save the named styles
0250     if (KoStyleManager *styleManager = m_document->resourceManager()->resource(KoText::StyleManager).value<KoStyleManager*>()) {
0251         styleManager->saveOdf(context);
0252     }
0253 
0254     // TODO get the pagestyle for the first page and store that as 'style:default-page-layout'
0255 
0256     // Header and footers save their content into master-styles/master-page, and their
0257     // styles into the page-layout automatic-style.
0258     saveHeaderFooter(context);
0259 
0260     KoXmlWriter *bodyWriter = odfStore.bodyWriter();
0261     bodyWriter->startElement("office:body");
0262     bodyWriter->startElement("office:text");
0263     if (m_document->isMasterDocument()) {
0264         bodyWriter->addAttribute("text:global", "true");
0265     }
0266     // FIXME: text:use-soft-page-breaks
0267 
0268     calculateZindexOffsets();
0269 
0270     KWTextFrameSet *mainTextFrame = 0;
0271 
0272     foreach (KWFrameSet *fs, m_document->frameSets()) {
0273         // For the purpose of saving to ODF we have 3 types of frames.
0274         //  1) auto-generated frames.  This includes header/footers and the main text FS.
0275         //  2) frames that are anchored to text.  They have a parent and their parent will save them.
0276         //  3) frames that are not anchored but freely positioned somewhere on the page.
0277         //     in ODF terms those frames are page-anchored.
0278 
0279         if (fs->shapeCount() == 1) {
0280             // may be a frame that is anchored to text, don't save those here.
0281             KoShapeAnchor *anchor = fs->shapes().first()->anchor();
0282             if (anchor && anchor->anchorType() != KoShapeAnchor::AnchorPage)
0283                 continue;
0284         }
0285 
0286         KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*>(fs);
0287         if (tfs) {
0288             if (tfs->textFrameSetType() == Words::MainTextFrameSet) {
0289                 mainTextFrame = tfs;
0290                 continue;
0291             }
0292             else if (Words::isAutoGenerated(tfs)) {
0293                 continue;
0294             }
0295         }
0296 
0297         int counter = 1;
0298         QSet<QString> uniqueNames;
0299         foreach (KoShape *shape, fs->shapes()) { // make sure all shapes have names.
0300             if (counter++ == 1)
0301                 shape->setName(fs->name());
0302             else if (shape->name().isEmpty() || uniqueNames.contains(shape->name()))
0303                 shape->setName(QString("%1-%2").arg(fs->name(), QString::number(counter)));
0304             uniqueNames << shape->name();
0305         }
0306         foreach (KoShape *shape, fs->shapes()) {
0307             KWPage page = m_document->pageManager()->page(shape);
0308             if (m_document->annotationLayoutManager()->isAnnotationShape(shape)) {
0309                 // Skip to save annotation shapes.
0310                 continue;
0311             }
0312 
0313             if (shape->minimumHeight() > 1) {
0314                 shape->setAdditionalAttribute("fo:min-height", QString::number(shape->minimumHeight()) + "pt");
0315             }
0316 
0317             // shape properties
0318             const qreal pagePos = page.offsetInDocument();
0319 
0320             shape->setAdditionalAttribute("text:anchor-page-number", QString::number(page.pageNumber()));
0321             context.addShapeOffset(shape, QTransform(1, 0, 0 , 1, 0, -pagePos));
0322             m_document->anchorOfShape(shape)->saveOdf(context);
0323             context.removeShapeOffset(shape);
0324             shape->removeAdditionalAttribute("fo:min-height");
0325             shape->removeAdditionalAttribute("text:anchor-page-number");
0326             shape->removeAdditionalAttribute("text:anchor-type");
0327         }
0328     }
0329 
0330     if (mainTextFrame) {
0331         if (! mainTextFrame->shapes().isEmpty() && mainTextFrame->shapes().first()) {
0332             KoTextShapeData *shapeData = qobject_cast<KoTextShapeData *>(mainTextFrame->shapes().first()->userData());
0333             if (shapeData) {
0334                 shapeData->saveOdf(context, m_document->documentRdf());
0335             }
0336         }
0337     }
0338 
0339     //we save the changes before starting the page sequence element because odf validator insist on having <tracked-changes> right after the <office:text> tag
0340     mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
0341 
0342     if (!changeTracker || !changeTracker->recordChanges()) {
0343         changes.saveOdfChanges(changeWriter, false);
0344     }
0345     else {
0346         changes.saveOdfChanges(changeWriter, true);
0347     }
0348 
0349     delete changeWriter;
0350     changeWriter = 0;
0351 
0352     tmpChangeFile.close();
0353 
0354     bodyWriter->addCompleteElement(&tmpChangeFile);
0355 
0356     // Save user defined variable declarations
0357     if (KoVariableManager *variableManager = m_document->inlineTextObjectManager()->variableManager()) {
0358         variableManager->saveOdf(bodyWriter);
0359     }
0360 
0361     // Do not write out text:page-sequence, if there is a maintTextFrame
0362     // The ODF specification does not allow text:page-sequence in office:text
0363     // if there is e.g. text:p or text:h there
0364     if (!mainTextFrame) {
0365         bodyWriter->startElement("text:page-sequence");
0366         foreach (const KWPage &page, m_document->pageManager()->pages()) {
0367             Q_ASSERT(m_masterPages.contains(page.pageStyle()));
0368             bodyWriter->startElement("text:page");
0369             bodyWriter->addAttribute("text:master-page-name",
0370                     m_masterPages.value(page.pageStyle()));
0371             bodyWriter->endElement(); // text:page
0372         }
0373         bodyWriter->endElement(); // text:page-sequence
0374     }
0375 
0376     delete tmpBodyWriter;
0377     tmpBodyWriter = 0;
0378 
0379     tmpTextBodyFile.close();
0380     bodyWriter->addCompleteElement(&tmpTextBodyFile);
0381 
0382     bodyWriter->endElement(); // office:text
0383     bodyWriter->endElement(); // office:body
0384 
0385     odfStore.closeContentWriter();
0386 
0387     // add manifest line for content.xml
0388     manifestWriter->addManifestEntry("content.xml", "text/xml");
0389 
0390     // update references to xml:id to be to new xml:id
0391     // in the external Rdf
0392     if (KoDocumentRdfBase *rdf = m_document->documentRdf()) {
0393         QMap<QString, QString> m = sharedData->getRdfIdMapping();
0394         rdf->updateXmlIdReferences(m);
0395     }
0396     // save the styles.xml
0397     if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter))
0398         return false;
0399 
0400     if (!context.saveDataCenter(store, manifestWriter)) {
0401         return false;
0402     }
0403 
0404     return true;
0405 }
0406 
0407 bool KWOdfWriter::saveOdfSettings(KoStore *store)
0408 {
0409     KoStoreDevice settingsDev(store);
0410     KoXmlWriter *settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&settingsDev, "office:document-settings");
0411 
0412     settingsWriter->startElement("office:settings");
0413     settingsWriter->startElement("config:config-item-set");
0414     settingsWriter->addAttribute("config:name", "view-settings");
0415 
0416     m_document->saveUnitOdf(settingsWriter);
0417 
0418     settingsWriter->endElement(); // config:config-item-set
0419 
0420     settingsWriter->startElement("config:config-item-set");
0421     settingsWriter->addAttribute("config:name", "ooo:view-settings");
0422     settingsWriter->startElement("config:config-item-map-indexed");
0423     settingsWriter->addAttribute("config:name", "Views");
0424     settingsWriter->startElement("config:config-item-map-entry");
0425 
0426     m_document->guidesData().saveOdfSettings(*settingsWriter);
0427     m_document->gridData().saveOdfSettings(*settingsWriter);
0428 
0429     settingsWriter->endElement(); // config:config-item-map-entry
0430     settingsWriter->endElement(); // config:config-item-map-indexed
0431     settingsWriter->endElement(); // config:config-item-set
0432 
0433     settingsWriter->startElement("config:config-item-set");
0434     settingsWriter->addAttribute("config:name", "ooo:configuration-settings");
0435     KoTextDocument doc(m_document->mainFrameSet()->document());
0436 
0437     settingsWriter->startElement("config:config-item");
0438     settingsWriter->addAttribute("config:name", "TabsRelativeToIndent");
0439     settingsWriter->addAttribute("config:type", "boolean");
0440     settingsWriter->addTextSpan(doc.relativeTabs() ? "true" : "false");
0441     settingsWriter->endElement();
0442 
0443     settingsWriter->startElement("config:config-item");
0444     settingsWriter->addAttribute("config:name", "AddParaTableSpacingAtStart");
0445     settingsWriter->addAttribute("config:type", "boolean");
0446     settingsWriter->addTextSpan(doc.paraTableSpacingAtStart() ? "true" : "false");
0447     settingsWriter->endElement();
0448 
0449     // OOo requires this config item to display files saved by wors correctly.
0450     // If true, then the fo:text-indent attribute will be ignored.
0451     settingsWriter->startElement("config:config-item");
0452     settingsWriter->addAttribute("config:name", "IgnoreFirstLineIndentInNumbering");
0453     settingsWriter->addAttribute("config:type", "boolean");
0454     settingsWriter->addTextSpan("false");
0455     settingsWriter->endElement();
0456 
0457     settingsWriter->endElement(); // config:config-item-set
0458 
0459     settingsWriter->endElement(); // office:settings
0460     settingsWriter->endElement(); // office:document-settings
0461 
0462     settingsWriter->endDocument();
0463 
0464     delete settingsWriter;
0465 
0466     return true;
0467 }
0468 
0469 void KWOdfWriter::calculateZindexOffsets()
0470 {
0471     Q_ASSERT(m_zIndexOffsets.isEmpty()); // call this method only once, please.
0472     foreach (KWFrameSet *fs, m_document->frameSets()) {
0473         if (Words::isAutoGenerated(fs))
0474             continue;
0475         foreach (KoShape *shape, fs->shapes()) {
0476             addShapeToTree(shape);
0477         }
0478     }
0479 
0480     foreach (const KWPage &page, m_document->pageManager()->pages()) {
0481         // TODO handle pageSpread here.
0482         int minZIndex = 0;
0483         foreach (KoShape *shape, m_shapeTree.intersects(page.rect()))
0484             minZIndex = qMin(shape->zIndex(), minZIndex);
0485         m_zIndexOffsets.insert(page, -minZIndex);
0486     }
0487 }
0488 
0489 void KWOdfWriter::addShapeToTree(KoShape *shape)
0490 {
0491     if (! dynamic_cast<KoShapeGroup*>(shape) && ! dynamic_cast<KoShapeLayer*>(shape))
0492         m_shapeTree.insert(shape->boundingRect(), shape);
0493 
0494     // add the children of a KoShapeContainer
0495     KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
0496     if (container) {
0497         foreach(KoShape *containerShape, container->shapes()) {
0498             addShapeToTree(containerShape);
0499         }
0500     }
0501 }