diff --git a/words/part/KWOdfWriter.cpp b/words/part/KWOdfWriter.cpp index f28cbfa73b3..c8c30f4d682 100644 --- a/words/part/KWOdfWriter.cpp +++ b/words/part/KWOdfWriter.cpp @@ -1,503 +1,501 @@ /* This file is part of the KDE project * Copyright (C) 2005 David Faure * Copyright (C) 2007-2009 Thomas Zander * Copyright (C) 2007-2008 Sebastian Sauer * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2007-2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KWOdfWriter.h" #include "KWDocument.h" #include "KWPage.h" #include "frames/KWTextFrameSet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const struct { const char * tag; } headerFooterTag[] = { { "style:header" }, { "style:header-left" }, { "style:footer" }, { "style:footer-left" } }; QByteArray KWOdfWriter::serializeHeaderFooter(KoShapeSavingContext &context, KWTextFrameSet *fs) { const char * tag = 0; switch (fs->textFrameSetType()) { case Words::OddPagesHeaderTextFrameSet: tag = headerFooterTag[0].tag; break; case Words::EvenPagesHeaderTextFrameSet: tag = headerFooterTag[1].tag; break; case Words::OddPagesFooterTextFrameSet: tag = headerFooterTag[2].tag; break; case Words::EvenPagesFooterTextFrameSet: tag = headerFooterTag[3].tag; break; default: return QByteArray(); } QByteArray content; QBuffer buffer(&content); buffer.open(QIODevice::WriteOnly); KoXmlWriter writer(&buffer); KoXmlWriter &savedWriter = context.xmlWriter(); KoShapeSavingContext::ShapeSavingOptions options = context.options(); context.setOptions(KoShapeSavingContext::AutoStyleInStyleXml | KoShapeSavingContext::ZIndex); context.setXmlWriter(writer); Q_ASSERT(!fs->shapes().isEmpty()); KoTextShapeData *shapedata = qobject_cast(fs->shapes().first()->userData()); Q_ASSERT(shapedata); writer.startElement(tag); shapedata->saveOdf(context, m_document->documentRdf()); writer.endElement(); context.setOptions(options); context.setXmlWriter(savedWriter); return content; } // rename to save pages ? void KWOdfWriter::saveHeaderFooter(KoShapeSavingContext &context) { //debugWords<< "START saveHeaderFooter ############################################"; // first get all the framesets in a nice quick-to-access data structure // this avoids iterating till we drop QHash > data; foreach (KWFrameSet *fs, m_document->frameSets()) { KWTextFrameSet *tfs = dynamic_cast (fs); if (! tfs) continue; if (! Words::isAutoGenerated(tfs)) continue; if (tfs->textFrameSetType() == Words::MainTextFrameSet) continue; QHash set = data.value(tfs->pageStyle()); set.insert(tfs->textFrameSetType(), tfs); Q_ASSERT(tfs->pageStyle().isValid()); data.insert(tfs->pageStyle(), set); } // save page styles that don't have a header or footer which will be handled later foreach (const KWPageStyle &pageStyle, m_document->pageManager()->pageStyles()) { if (data.contains(pageStyle)) continue; KoGenStyle masterStyle(KoGenStyle::MasterPageStyle); KoGenStyle layoutStyle = pageStyle.saveOdf(); if (!pageStyle.displayName().isEmpty() && pageStyle.displayName() != pageStyle.name()) masterStyle.addProperty("style:display-name", pageStyle.displayName()); if (!pageStyle.nextStyleName().isEmpty()) masterStyle.addProperty("style:next-style-name", pageStyle.nextStyleName()); masterStyle.addProperty("style:page-layout-name", context.mainStyles().insert(layoutStyle, "pm")); QString name = context.mainStyles().insert(masterStyle, pageStyle.name(), KoGenStyles::DontAddNumberToName); m_masterPages.insert(pageStyle, name); } // We need to flush them out ordered as defined in the specs. QList order; order << Words::OddPagesHeaderTextFrameSet << Words::EvenPagesHeaderTextFrameSet << Words::OddPagesFooterTextFrameSet << Words::EvenPagesFooterTextFrameSet; QHash >::ConstIterator it = data.constBegin(); QHash >::ConstIterator end = data.constEnd(); for(; it != end; ++it) { const KWPageStyle &pageStyle = it.key(); const QHash &headersAndFooters = it.value(); KoGenStyle masterStyle(KoGenStyle::MasterPageStyle); //masterStyle.setAutoStyleInStylesDotXml(true); KoGenStyle layoutStyle = pageStyle.saveOdf(); if (!pageStyle.displayName().isEmpty() && pageStyle.displayName() != pageStyle.name()) masterStyle.addProperty("style:display-name", pageStyle.displayName()); if (!pageStyle.nextStyleName().isEmpty()) masterStyle.addProperty("style:next-style-name", pageStyle.nextStyleName()); masterStyle.addProperty("style:page-layout-name", context.mainStyles().insert(layoutStyle, "pm")); int index = 0; foreach (int type, order) { if (! headersAndFooters.contains(type)) continue; KWTextFrameSet *fs = headersAndFooters.value(type); Q_ASSERT(fs); if (fs->shapeCount() == 0) // don't save empty framesets continue; QByteArray content = serializeHeaderFooter(context, fs); if (content.isNull()) continue; masterStyle.addChildElement(QString::number(++index), QString::fromUtf8(content)); } // append the headerfooter-style to the main-style QString name = context.mainStyles().insert(masterStyle, pageStyle.name(), KoGenStyles::DontAddNumberToName); m_masterPages.insert(pageStyle, name); } //foreach (KoGenStyles::NamedStyle s, mainStyles.styles(KoGenStyle::ParagraphAutoStyle)) // mainStyles.markStyleForStylesXml(s.name); //debugWords << "END saveHeaderFooter ############################################"; } KWOdfWriter::KWOdfWriter(KWDocument *document) : QObject(), m_document(document), m_shapeTree(4, 2) { } KWOdfWriter::~KWOdfWriter() { } // 1.6: KWDocument::saveOasisHelper() bool KWOdfWriter::save(KoOdfWriteStore &odfStore, KoEmbeddedDocumentSaver &embeddedSaver) { //debugWords << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; KoStore *store = odfStore.store(); if (! store->open("settings.xml")) { return false; } saveOdfSettings(store); if (!store->close()) return false; KoXmlWriter *manifestWriter = odfStore.manifestWriter(); manifestWriter->addManifestEntry("settings.xml", "text/xml"); KoXmlWriter *contentWriter = odfStore.contentWriter(); if (!contentWriter) return false; QTemporaryFile tmpChangeFile; tmpChangeFile.open(); KoXmlWriter *changeWriter = new KoXmlWriter(&tmpChangeFile, 1); if (!changeWriter) return false; QTemporaryFile tmpTextBodyFile; tmpTextBodyFile.open(); KoXmlWriter *tmpBodyWriter = new KoXmlWriter(&tmpTextBodyFile, 1); if (!tmpBodyWriter) return false; KoGenStyles mainStyles; KoGenChanges changes; KoChangeTracker *changeTracker = m_document->resourceManager()->resource(KoText::ChangeTracker).value(); KoShapeSavingContext context(*tmpBodyWriter, mainStyles, embeddedSaver); context.addOption(KoShapeSavingContext::ZIndex); KoTextSharedSavingData *sharedData = new KoTextSharedSavingData; sharedData->setGenChanges(changes); context.addSharedData(KOTEXT_SHARED_SAVING_ID, sharedData); // Save the named styles if (KoStyleManager *styleManager = m_document->resourceManager()->resource(KoText::StyleManager).value()) { styleManager->saveOdf(context); } // TODO get the pagestyle for the first page and store that as 'style:default-page-layout' // Header and footers save their content into master-styles/master-page, and their // styles into the page-layout automatic-style. saveHeaderFooter(context); KoXmlWriter *bodyWriter = odfStore.bodyWriter(); bodyWriter->startElement("office:body"); bodyWriter->startElement("office:text"); if (m_document->isMasterDocument()) { bodyWriter->addAttribute("text:global", "true"); } // FIXME: text:use-soft-page-breaks calculateZindexOffsets(); KWTextFrameSet *mainTextFrame = 0; foreach (KWFrameSet *fs, m_document->frameSets()) { // For the purpose of saving to ODF we have 3 types of frames. // 1) auto-generated frames. This includes header/footers and the main text FS. // 2) frames that are anchored to text. They have a parent and their parent will save them. // 3) frames that are not anchored but freely positioned somewhere on the page. // in ODF terms those frames are page-anchored. if (fs->shapeCount() == 1) { // may be a frame that is anchored to text, don't save those here. KoShapeAnchor *anchor = fs->shapes().first()->anchor(); if (anchor && anchor->anchorType() != KoShapeAnchor::AnchorPage) continue; } KWTextFrameSet *tfs = dynamic_cast(fs); if (tfs) { if (tfs->textFrameSetType() == Words::MainTextFrameSet) { mainTextFrame = tfs; continue; } else if (Words::isAutoGenerated(tfs)) { continue; } } int counter = 1; QSet uniqueNames; foreach (KoShape *shape, fs->shapes()) { // make sure all shapes have names. if (counter++ == 1) shape->setName(fs->name()); else if (shape->name().isEmpty() || uniqueNames.contains(shape->name())) shape->setName(QString("%1-%2").arg(fs->name(), QString::number(counter))); uniqueNames << shape->name(); } foreach (KoShape *shape, fs->shapes()) { KWPage page = m_document->pageManager()->page(shape); if (m_document->annotationLayoutManager()->isAnnotationShape(shape)) { // Skip to save annotation shapes. continue; } if (shape->minimumHeight() > 1) { shape->setAdditionalAttribute("fo:min-height", QString::number(shape->minimumHeight()) + "pt"); } // shape properties const qreal pagePos = page.offsetInDocument(); - shape->setAdditionalAttribute("text:anchor-type", "page"); shape->setAdditionalAttribute("text:anchor-page-number", QString::number(page.pageNumber())); context.addShapeOffset(shape, QTransform(1, 0, 0 , 1, 0, -pagePos)); - shape->saveOdf(context); + m_document->anchorOfShape(shape)->saveOdf(context); context.removeShapeOffset(shape); shape->removeAdditionalAttribute("fo:min-height"); shape->removeAdditionalAttribute("text:anchor-page-number"); - shape->removeAdditionalAttribute("text:anchor-page-number"); shape->removeAdditionalAttribute("text:anchor-type"); } } if (mainTextFrame) { if (! mainTextFrame->shapes().isEmpty() && mainTextFrame->shapes().first()) { KoTextShapeData *shapeData = qobject_cast(mainTextFrame->shapes().first()->userData()); if (shapeData) { shapeData->saveOdf(context, m_document->documentRdf()); } } } //we save the changes before starting the page sequence element because odf validator insist on having right after the tag mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter); if (!changeTracker || !changeTracker->recordChanges()) { changes.saveOdfChanges(changeWriter, false); } else { changes.saveOdfChanges(changeWriter, true); } delete changeWriter; changeWriter = 0; tmpChangeFile.close(); bodyWriter->addCompleteElement(&tmpChangeFile); // Save user defined variable declarations if (KoVariableManager *variableManager = m_document->inlineTextObjectManager()->variableManager()) { variableManager->saveOdf(bodyWriter); } // Do not write out text:page-sequence, if there is a maintTextFrame // The ODF specification does not allow text:page-sequence in office:text // if there is e.g. text:p or text:h there if (!mainTextFrame) { bodyWriter->startElement("text:page-sequence"); foreach (const KWPage &page, m_document->pageManager()->pages()) { Q_ASSERT(m_masterPages.contains(page.pageStyle())); bodyWriter->startElement("text:page"); bodyWriter->addAttribute("text:master-page-name", m_masterPages.value(page.pageStyle())); bodyWriter->endElement(); // text:page } bodyWriter->endElement(); // text:page-sequence } delete tmpBodyWriter; tmpBodyWriter = 0; tmpTextBodyFile.close(); bodyWriter->addCompleteElement(&tmpTextBodyFile); bodyWriter->endElement(); // office:text bodyWriter->endElement(); // office:body odfStore.closeContentWriter(); // add manifest line for content.xml manifestWriter->addManifestEntry("content.xml", "text/xml"); // update references to xml:id to be to new xml:id // in the external Rdf if (KoDocumentRdfBase *rdf = m_document->documentRdf()) { QMap m = sharedData->getRdfIdMapping(); rdf->updateXmlIdReferences(m); } // save the styles.xml if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) return false; if (!context.saveDataCenter(store, manifestWriter)) { return false; } return true; } bool KWOdfWriter::saveOdfSettings(KoStore *store) { KoStoreDevice settingsDev(store); KoXmlWriter *settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&settingsDev, "office:document-settings"); settingsWriter->startElement("office:settings"); settingsWriter->startElement("config:config-item-set"); settingsWriter->addAttribute("config:name", "view-settings"); m_document->saveUnitOdf(settingsWriter); settingsWriter->endElement(); // config:config-item-set settingsWriter->startElement("config:config-item-set"); settingsWriter->addAttribute("config:name", "ooo:view-settings"); settingsWriter->startElement("config:config-item-map-indexed"); settingsWriter->addAttribute("config:name", "Views"); settingsWriter->startElement("config:config-item-map-entry"); m_document->guidesData().saveOdfSettings(*settingsWriter); m_document->gridData().saveOdfSettings(*settingsWriter); settingsWriter->endElement(); // config:config-item-map-entry settingsWriter->endElement(); // config:config-item-map-indexed settingsWriter->endElement(); // config:config-item-set settingsWriter->startElement("config:config-item-set"); settingsWriter->addAttribute("config:name", "ooo:configuration-settings"); KoTextDocument doc(m_document->mainFrameSet()->document()); settingsWriter->startElement("config:config-item"); settingsWriter->addAttribute("config:name", "TabsRelativeToIndent"); settingsWriter->addAttribute("config:type", "boolean"); settingsWriter->addTextSpan(doc.relativeTabs() ? "true" : "false"); settingsWriter->endElement(); settingsWriter->startElement("config:config-item"); settingsWriter->addAttribute("config:name", "AddParaTableSpacingAtStart"); settingsWriter->addAttribute("config:type", "boolean"); settingsWriter->addTextSpan(doc.paraTableSpacingAtStart() ? "true" : "false"); settingsWriter->endElement(); // OOo requires this config item to display files saved by wors correctly. // If true, then the fo:text-indent attribute will be ignored. settingsWriter->startElement("config:config-item"); settingsWriter->addAttribute("config:name", "IgnoreFirstLineIndentInNumbering"); settingsWriter->addAttribute("config:type", "boolean"); settingsWriter->addTextSpan("false"); settingsWriter->endElement(); settingsWriter->endElement(); // config:config-item-set settingsWriter->endElement(); // office:settings settingsWriter->endElement(); // office:document-settings settingsWriter->endDocument(); delete settingsWriter; return true; } void KWOdfWriter::calculateZindexOffsets() { Q_ASSERT(m_zIndexOffsets.isEmpty()); // call this method only once, please. foreach (KWFrameSet *fs, m_document->frameSets()) { if (Words::isAutoGenerated(fs)) continue; foreach (KoShape *shape, fs->shapes()) { addShapeToTree(shape); } } foreach (const KWPage &page, m_document->pageManager()->pages()) { // TODO handle pageSpread here. int minZIndex = 0; foreach (KoShape *shape, m_shapeTree.intersects(page.rect())) minZIndex = qMin(shape->zIndex(), minZIndex); m_zIndexOffsets.insert(page, -minZIndex); } } void KWOdfWriter::addShapeToTree(KoShape *shape) { if (! dynamic_cast(shape) && ! dynamic_cast(shape)) m_shapeTree.insert(shape->boundingRect(), shape); // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach(KoShape *containerShape, container->shapes()) { addShapeToTree(containerShape); } } }