Calligra/Words/Tutorials/SavingOdf
About this document
Third document describes how saving of ODF documents work in KWord.
It is highly recommended that you read Loading ODF and Saving ODF.
Prologue
Here's a quick recap of what happened after the ODF was loaded/edited.
- We stored all content in a QTextDocument
- The office:styles (aka named styles) are stored as KoCharacterStyle and KoParagraphStyle. These styles are accessible using the KoStyleManager.
- The document styles are converted and applied as appropriate QTextFormat styles in the QTextDocument.
- Automatic styles are loaded as KoCharacter/KoParagraphStyles, applied on the document and then thrown away.
libs/odf
KoGenStyle store properties of a paragraph or character style (i.e) a single <style:style>. KoCharacterStyle and KoParagraphStyle have a saveOdf(KoGenStyle) that populates this structure. KoGenStyle has a enum 'type' that describes what properties it holds (i.e) text-properties or paragraph-properties or list-properties. There are separate enums for automatic and non-automatic for each of them. For example, StyleText, StyleTextAuto. For automatic styles, you also have to specify whether it belongs in styles.xml or content.xml.
KoGenStyles holds all the styles of a complete ODF document. You can give it lots of KoGenStyle and it can help you create the office:automatic-styles, office:list-styles and office:styles.
kword/part
It all starts with the user selecting "save" and the KoDocument::saveOdf is called with the SavingContext. KWDocument reimplements saveOdf().
KoOdfWriteStore { KoOdfStore *store; KoXmlWriter *contentWriter; // to write content.xml KoXmlWriter *bodyWriter; // to write the <body> KoXmlWriter *manifestWriter; // to write the manifest.xml static KoXmlWstoriter* createOasisXmlWriter( QIODevice* dev, const char* rootElementName ); } SavingContext { KoOdfWriteStore &odfStore; KoEmbeddedDocumentSaver &embeddedSaver; // for embedded <object>s } KWDocument::saveOdf(SavingContext context) { KWOdfWriter writer; writer.save(context.odfStore, context.embeddedSaver); }
Every document has many KWFrameSet. TODO: Describe what a KWFrameSet actually is and how frames are inserted/removed.
KWOdfWriter gets the shape (textshape) of the first frame of main frame set (the root) and calls saveOdf() on the user data (KoTextShapeData) of the shape. KoTextShapeData::saveOdf() saves the content of the QTextDocument and to determine the styles that the document uses. KWOdfWriter provides a KoGenStyles to saveOdf that it uses to store all the styles.
In typical KWord style, all necessary parameters are packaged in a Context structure, in this case a KoShapeSavingContext. KoShapeSavingContext contains a KoXmlWriter using which one can write the body, a KoEmbeddedSaver that can save embedded objects and KoGenStyles to save the styles.
More detailed explanation is the pseudo code below:
KoShapeSavingContext { KoXmlWriter *m_xmlWriter; KoGenStyles &m_mainStyles; KoEmbeddedDocumentSaver& m_embeddedSaver; } KWOdfWriter::save(KoOdfStrore odfStore, KoEmbeddedDocumentSaver embeddedSaver) { KoGenStyles mainStyles; save_header_and_footer(embeddedSaver, mainStyles); // master-pages/master-styles KoShapeSavingContext context(odfStore.bodyWriter, mainStyles, embeddedSaver); mainFrameSet = iterate_over_the_KWFrameSet_of_a_document_and_locate_the_main_frame set(); // save context.xml KoTextShapeData *shapeData = mainFrameSet.firstFrame().shape()->userData(); shapeData->saveOdf(context); // saveOdf would store all the automatic-styles that need to be created in context. // save the office:automatic-styles in content.xml mainStyles.saveOdfAutomaticStyles( contentWriter, false ); // add manifest line for content.xml manifestWriter->addManifestEntry( "content.xml", "text/xml" ); // save the office:styles in styles.xml. this also adds the manifest entry for styles.xml, if required if ( !mainStyles.saveOdfStylesDotXml( store, manifestWriter ) ) return false; // TODO: what is this about if ( !context.saveDataCenter( store, manifestWriter ) ) { return false; } }
libs/kotext
KoTextShapeData::saveOdf() is where the actual saving happens. Recall from the Editing ODF tutorial that editing happens straight into the textshape's QTextDocument. Also recall from Loading ODF that information about office:automatic-styles is in the format (QTextFormat) of the document. The only styling information in the document is that the block and char formats have properties KoParagraphStyle::StyleId and KoCharacterStyle::StyleId which are style ids that can be queried using the KoStyleManager using KoStyleManager::paragraphStyle(int) and KoStyleManager::characterStyle(int).
Here's the pseudo code for what it does:
KoTextShapeData::saveOdf(context) { // First: Create the style-name for the fragments for_each(format, document.all_formats) { if (format.type() == CharacterStyle) { // the actual code does the block below for other types too KoCharacterStyle *originalCharStyle = styleManager->characterStyle(format.property(KoCharacterStyle::StyleId)); QString internalName = percentEncoded(originalCharStyle->displayName()); KoCharacterStyle charStyle(format); if (charStyle != originalCharStyle) { // just use the charStyle as is! KoGenStyle genStyle(KoGenStyle::StyleUser, "text"); // it's just a office:style charStyle->saveOdf(genStyle); generatedName = context.mainStyles().lookup(genStyle, internalName); // lookup add the style and returns the name } else { // create an automatic style which is a "diff" of the original style and now KoGenStyle genStyle(KoGenStyle::StyleAuto, "text", internalName /*this is the parent-style*/); // office:automatic-style charStyle->removeDuplicates(originalCharStyle); charStyle.saveOdf(genStyle); generatedName = context.mainStyles().lookup(genStyle, "T"); // lookup adds the style } styleNames[format.id] = generatedName; } } for_each(block, document) { use context.xmlWriter to write text:p with the style-name from styleNames[blockformat.id] for_each(fragment, block) { write fragment as text:span and set style-name attribute from styleNames[charformat.id] } } }
-- Girish Ramakrishnan ([email protected])