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