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 }