Calligra/Words/Tutorials/SavingOdf

From KDE Community Wiki

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])