Calligra/Words/Tutorials/SavingOdf

From KDE Community Wiki
Revision as of 17:58, 6 December 2010 by Cyrille (talk | contribs) (Created page with '==About this document== Third document describes how saving of ODF documents work in KWord. It is highly recommended that you read [[Calligra/Words/Tutorials/LoadingOdf|Loading ...')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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