diff options
Diffstat (limited to 'filters/kword/oowriter/oowriterimport.cpp')
-rw-r--r-- | filters/kword/oowriter/oowriterimport.cpp | 2516 |
1 files changed, 2516 insertions, 0 deletions
diff --git a/filters/kword/oowriter/oowriterimport.cpp b/filters/kword/oowriter/oowriterimport.cpp new file mode 100644 index 000000000..5e54ce06f --- /dev/null +++ b/filters/kword/oowriter/oowriterimport.cpp @@ -0,0 +1,2516 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.com> + Copyright (C) 2003 David Faure <faure@kde.org> + Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <tqcolor.h> +#include <tqfile.h> +#include <tqfont.h> +#include <tqpen.h> +#include <tqregexp.h> +#include <tqimage.h> + +#include "oowriterimport.h" +#include <ooutils.h> + +#include <tdeversion.h> +#include <kdebug.h> +#include <kzip.h> + +#include <KoDocumentInfo.h> +#include <KoDocument.h> + +#include <kgenericfactory.h> +#include <tdemessagebox.h> +#include <KoFilterChain.h> +#include <KoUnit.h> +#include <KoPageLayout.h> +#include <KoPicture.h> +#include "conversion.h" +#include <KoRect.h> +#include <KoDom.h> + +#if ! KDE_IS_VERSION(3,1,90) +# include <kdebugclasses.h> +#endif + +typedef KGenericFactory<OoWriterImport, KoFilter> OoWriterImportFactory; +K_EXPORT_COMPONENT_FACTORY( liboowriterimport, OoWriterImportFactory( "kofficefilters" ) ) + +OoWriterImport::OoWriterImport( KoFilter *, const char *, const TQStringList & ) + : KoFilter(), + m_styleStack( ooNS::style, ooNS::fo ), + m_insideOrderedList( false ), m_nextItemIsListItem( false ), + m_hasTOC( false ), m_hasHeader( false ), m_hasFooter( false ), m_restartNumbering( -1 ), + m_pictureNumber(0), m_zip(NULL) +{ + m_styles.setAutoDelete( true ); + m_masterPages.setAutoDelete( true ); + m_listStyles.setAutoDelete( true ); +} + +OoWriterImport::~OoWriterImport() +{ +} + +KoFilter::ConversionStatus OoWriterImport::convert( TQCString const & from, TQCString const & to ) +{ + kdDebug(30518) << "Entering Oowriter Import filter: " << from << " - " << to << endl; + + if ( ( from != "application/vnd.sun.xml.writer" + && from != "application/vnd.sun.xml.writer.template" + && from != "application/vnd.sun.xml.writer.master" ) + || to != "application/x-kword" ) + { + kdWarning(30518) << "Invalid mimetypes " << from << " " << to << endl; + return KoFilter::NotImplemented; + } + + m_zip=new KZip(m_chain->inputFile()); + + kdDebug(30518) << "Store created" << endl; + + if ( !m_zip->open(IO_ReadOnly) ) + { + kdError(30518) << "Couldn't open the requested file "<< m_chain->inputFile() << endl; + return KoFilter::FileNotFound; + } + + if ( !m_zip->directory() ) + { + kdError(30518) << "Couldn't read ZIP directory of the requested file "<< m_chain->inputFile() << endl; + return KoFilter::FileNotFound; + } + + + KoFilter::ConversionStatus preStatus = openFile(); + + TQImage thumbnail; + if ( preStatus == KoFilter::OK ) + { + // We do not care about the failure + OoUtils::loadThumbnail( thumbnail, m_zip ); + } + + if ( preStatus != KoFilter::OK ) + { + m_zip->close(); + delete m_zip; + return preStatus; + } + + m_currentMasterPage = TQString(); + TQDomDocument mainDocument; + TQDomElement framesetsElem; + prepareDocument( mainDocument, framesetsElem ); + + // Load styles from style.xml + if ( !createStyleMap( m_stylesDoc, mainDocument ) ) + return KoFilter::UserCancelled; + // Also load styles from content.xml + if ( !createStyleMap( m_content, mainDocument ) ) + return KoFilter::UserCancelled; + + // Create main frameset + TQDomElement mainFramesetElement = mainDocument.createElement("FRAMESET"); + mainFramesetElement.setAttribute("frameType",1); + mainFramesetElement.setAttribute("frameInfo",0); + mainFramesetElement.setAttribute("visible",1); + mainFramesetElement.setAttribute("name", i18n( "Main Text Frameset" ) ); + framesetsElem.appendChild(mainFramesetElement); + + createInitialFrame( mainFramesetElement, 29, 798, 42, 566, false, Reconnect ); + createStyles( mainDocument ); + createDocumentContent( mainDocument, mainFramesetElement ); + finishDocumentContent( mainDocument ); + + m_zip->close(); + delete m_zip; // It has to be so late, as pictures might be read. + + KoStoreDevice* out = m_chain->storageFile( "maindoc.xml", KoStore::Write ); + if ( !out ) { + kdError(30518) << "Unable to open output file!" << endl; + return KoFilter::StorageCreationError; + } + else + { + TQCString cstr = mainDocument.toCString(); + kdDebug(30518)<<" maindoc: " << cstr << endl; + // WARNING: we cannot use KoStore::write(const TQByteArray&) because it gives an extra NULL character at the end. + out->writeBlock( cstr, cstr.length() ); + } + + TQDomDocument docinfo; + createDocumentInfo( docinfo ); + + // store document info + out = m_chain->storageFile( "documentinfo.xml", KoStore::Write ); + if( out ) + { + TQCString info = docinfo.toCString(); + kdDebug(30518)<<" info :"<<info<<endl; + // WARNING: we cannot use KoStore::write(const TQByteArray&) because it gives an extra NULL character at the end. + out->writeBlock( info , info.length() ); + } + + // store preview + + if ( ! thumbnail.isNull() ) + { + // ### TODO: thumbnail.setAlphaBuffer( false ); // legacy KOffice previews have no alpha channel + // Legacy KOffice previews are 256x256x8 instead of 128x128x32 + TQImage preview( thumbnail.smoothScale( 256, 256 ).convertDepth(8, Qt::AvoidDither | Qt::DiffuseDither) ); + // Not to be able to generate a preview is not an error + if ( !preview.isNull() ) + { + out = m_chain->storageFile( "preview.png", KoStore::Write ); + if( out ) + { + preview.save( out, "PNG" ); + } + } + } + + kdDebug(30518) << "######################## OoWriterImport::convert done ####################" << endl; + return KoFilter::OK; +} + +void OoWriterImport::createStyles( TQDomDocument& doc ) +{ + TQDomElement stylesElem = doc.createElement( "STYLES" ); + doc.documentElement().appendChild( stylesElem ); + + TQDomNode fixedStyles = KoDom::namedItemNS( m_stylesDoc.documentElement(), ooNS::office, "styles" ); + Q_ASSERT( !fixedStyles.isNull() ); + TQDomElement e; + forEachElement( e, fixedStyles ) + { + if ( !e.hasAttributeNS( ooNS::style, "name" ) ) + continue; + // We only generate paragraph styles for now + if ( e.attributeNS( ooNS::style, "family", TQString() ) != "paragraph" ) + continue; + + // We use the style stack, to flatten out parent styles + // Once KWord supports style inheritance, replace this with a single m_styleStack.push. + // (We still need to use StyleStack, since that's what writeLayout/writeFormat read from) + addStyles( &e ); + + TQDomElement styleElem = doc.createElement("STYLE"); + stylesElem.appendChild( styleElem ); + + TQString styleName = kWordStyleName( e.attributeNS( ooNS::style, "name", TQString() ) ); + TQDomElement element = doc.createElement("NAME"); + element.setAttribute( "value", styleName ); + styleElem.appendChild( element ); + //kdDebug(30518) << k_funcinfo << "generating style " << styleName << endl; + + TQString followingStyle = m_styleStack.attributeNS( ooNS::style, "next-style-name" ); + if ( !followingStyle.isEmpty() ) + { + TQDomElement element = doc.createElement( "FOLLOWING" ); + element.setAttribute( "name", kWordStyleName( followingStyle ) ); + styleElem.appendChild( element ); + } + + // ### In KWord the style says "I'm part of the outline" (TOC) + // ### In OOo the paragraph says that (text:h) + // Hence this hack... + // OASIS solution for this: style:default-outline-level attribute + bool outline = styleName.startsWith( "Heading" ); + if ( outline ) + styleElem.setAttribute( "outline", "true" ); + + writeFormat( doc, styleElem, 1, 0, 0 ); + writeLayout( doc, styleElem ); + + // writeLayout doesn't load the counter. It's modelled differently for parags and for styles. + // ### missing info in the format! (fixed in OASIS) + const int level = styleName.right(1).toInt(); // ## HACK + bool listOK = false; + if ( level > 0 ) { + if ( outline ) + listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level ); + else { + const TQString listStyleName = e.attributeNS( ooNS::style, "list-style-name", TQString() ); + listOK = !listStyleName.isEmpty(); + if ( listOK ) + listOK = pushListLevelStyle( listStyleName, level ); + } + } + if ( listOK ) { + const TQDomElement listStyle = m_listStyleStack.currentListStyle(); + // The tag is either text:list-level-style-number or text:list-level-style-bullet + bool ordered = listStyle.localName() == "list-level-style-number"; + writeCounter( doc, styleElem, outline, level, ordered ); + m_listStyleStack.pop(); + } + + m_styleStack.clear(); + } +} + +void OoWriterImport::parseBodyOrSimilar( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement ) +{ + TQDomElement oldCurrentFrameset = m_currentFrameset; + m_currentFrameset = currentFramesetElement; + Q_ASSERT( !m_currentFrameset.isNull() ); + TQDomElement t; + forEachElement( t, parent ) + { + m_styleStack.save(); + const TQString localName = t.localName(); + const TQString ns = t.namespaceURI(); + const bool isTextNS = ns == ooNS::text; + + TQDomElement e; + if ( isTextNS && localName == "p" ) { // text paragraph + fillStyleStack( t, ooNS::text, "style-name" ); + e = parseParagraph( doc, t ); + } + else if ( isTextNS && localName == "h" ) // heading + { + fillStyleStack( t, ooNS::text, "style-name" ); + int level = t.attributeNS( ooNS::text, "level", TQString() ).toInt(); + bool listOK = false; + // When a heading is inside a list, it seems that the list prevails. + // Example: + // <text:ordered-list text:style-name="Numbering 1"> + // <text:list-item text:start-value="5"> + // <text:h text:style-name="P2" text:level="4">The header</text:h> + // where P2 has list-style-name="something else" + // Result: the numbering of the header follows "Numbering 1". + // So we use the style for the outline level only if we're not inside a list: + if ( !m_nextItemIsListItem ) + listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level ); + m_nextItemIsListItem = true; + if ( t.hasAttributeNS( ooNS::text, "start-value" ) ) + // OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html + m_restartNumbering = t.attributeNS( ooNS::text, "start-value", TQString() ).toInt(); + e = parseParagraph( doc, t ); + if ( listOK ) + m_listStyleStack.pop(); + } + else if ( isTextNS && + ( localName == "unordered-list" || localName == "ordered-list" ) ) + { + parseList( doc, t, currentFramesetElement ); + m_styleStack.restore(); + continue; + } + else if ( isTextNS && localName == "section" ) // Temporary support (###TODO) + { + kdDebug(30518) << "Section found!" << endl; + fillStyleStack( t, ooNS::text, "style-name" ); + parseBodyOrSimilar( doc, t, currentFramesetElement); + } + else if ( localName == "table" && ns == ooNS::table ) + { + kdDebug(30518) << "Table found!" << endl; + parseTable( doc, t, currentFramesetElement ); + } + else if ( localName == "image" && ns == ooNS::draw ) + { + appendPicture( doc, t ); + } + else if ( localName == "text-box" && ns == ooNS::draw ) + { + appendTextBox( doc, t ); + } + else if ( isTextNS && localName == "variable-decls" ) + { + // We don't parse variable-decls since we ignore var types right now + // (and just storing a list of available var names wouldn't be much use) + } + else if ( localName == "table-of-content" && ns == ooNS::text ) + { + appendTOC( doc, t ); + } + // TODO text:sequence-decls + else + { + kdWarning(30518) << "Unsupported body element '" << localName << "'" << endl; + } + + if ( !e.isNull() ) + currentFramesetElement.appendChild( e ); + m_styleStack.restore(); // remove the styles added by the paragraph or list + } + m_currentFrameset = oldCurrentFrameset; // in case of recursive invokations +} + +void OoWriterImport::createDocumentContent( TQDomDocument &doc, TQDomElement& mainFramesetElement ) +{ + TQDomElement content = m_content.documentElement(); + + TQDomElement body ( KoDom::namedItemNS( content, ooNS::office, "body" ) ); + if ( body.isNull() ) + { + kdError(30518) << "No office:body found!" << endl; + return; + } + + parseBodyOrSimilar( doc, body, mainFramesetElement ); +} + +void OoWriterImport::writePageLayout( TQDomDocument& mainDocument, const TQString& masterPageName ) +{ + TQDomElement docElement = mainDocument.documentElement(); + + kdDebug(30518) << "writePageLayout " << masterPageName << endl; + TQDomElement elementPaper = mainDocument.createElement("PAPER"); + KoOrientation orientation; + double width, height; + KoFormat paperFormat; + double marginLeft, marginTop, marginRight, marginBottom; + bool hasEvenOddHeader = false; + bool hasEvenOddFooter = false; + + TQDomElement* masterPage = m_masterPages[ masterPageName ]; + Q_ASSERT( masterPage ); + kdDebug(30518) << "page-master-name: " << masterPage->attributeNS( ooNS::style, "page-master-name", TQString() ) << endl; + TQDomElement *style = masterPage ? m_styles[masterPage->attributeNS( ooNS::style, "page-master-name", TQString() )] : 0; + Q_ASSERT( style ); + if ( style ) + { + TQDomElement properties( KoDom::namedItemNS( *style, ooNS::style, "properties" ) ); + Q_ASSERT( !properties.isNull() ); + orientation = ( (properties.attributeNS( ooNS::style, "print-orientation", TQString()) != "portrait") ? PG_LANDSCAPE : PG_PORTRAIT ); + width = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-width", TQString())); + height = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-height", TQString())); + kdDebug(30518) << "width=" << width << " height=" << height << endl; + // guessFormat takes millimeters + if ( orientation == PG_LANDSCAPE ) + paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(height), POINT_TO_MM(width) ); + else + paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(width), POINT_TO_MM(height) ); + + marginLeft = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-left", TQString())); + marginTop = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-top", TQString())); + marginRight = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-right", TQString())); + marginBottom = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-bottom", TQString())); + + TQDomElement footnoteSep = KoDom::namedItemNS( properties, ooNS::style, "footnote-sep" ); + if ( !footnoteSep.isNull() ) { + // style:width="0.018cm" style:distance-before-sep="0.101cm" + // style:distance-after-sep="0.101cm" style:adjustment="left" + // style:rel-width="25%" style:color="#000000" + TQString width = footnoteSep.attributeNS( ooNS::style, "width", TQString() ); + elementPaper.setAttribute( "slFootNoteWidth", KoUnit::parseValue( width ) ); + TQString pageWidth = footnoteSep.attributeNS( ooNS::style, "rel-width", TQString() ); + if ( pageWidth.endsWith( "%" ) ) { + pageWidth.truncate( pageWidth.length() - 1 ); // remove '%' + elementPaper.setAttribute( "slFootNoteLenth", pageWidth ); + } + elementPaper.setAttribute( "slFootNotePosition", footnoteSep.attributeNS( ooNS::style, "adjustment", TQString() ) ); + // Not in KWord: color, distance before and after separator + // Not in OOo: line type of separator (solid, dot, dash etc.) + } + + + // Header/Footer + TQDomElement headerStyle = KoDom::namedItemNS( *style, ooNS::style, "header-style" ); + TQDomElement footerStyle = KoDom::namedItemNS( *style, ooNS::style, "footer-style" ); + TQDomElement headerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header-left" ); + if ( !headerLeftElem.isNull() ) { + kdDebug(30518) << "Found header-left" << endl; + hasEvenOddHeader = true; + importHeaderFooter( mainDocument, headerLeftElem, hasEvenOddHeader, headerStyle ); + } + TQDomElement headerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header" ); + if ( !headerElem.isNull() ) { + kdDebug(30518) << "Found header" << endl; + importHeaderFooter( mainDocument, headerElem, hasEvenOddHeader, headerStyle ); + } + TQDomElement footerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer-left" ); + if ( !footerLeftElem.isNull() ) { + kdDebug(30518) << "Found footer-left" << endl; + importHeaderFooter( mainDocument, footerLeftElem, hasEvenOddFooter, footerStyle ); + } + TQDomElement footerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer" ); + if ( !footerElem.isNull() ) { + kdDebug(30518) << "Found footer" << endl; + importHeaderFooter( mainDocument, footerElem, hasEvenOddFooter, footerStyle ); + } + } + else + { + // We have no master page! We need defaults. + kdWarning(30518) << "NO MASTER PAGE" << endl; + orientation=PG_PORTRAIT; + paperFormat=PG_DIN_A4; + width=MM_TO_POINT(KoPageFormat::width(paperFormat, orientation)); + height=MM_TO_POINT(KoPageFormat::height(paperFormat, orientation)); + // ### TODO: better defaults for margins? + marginLeft=MM_TO_POINT(10.0); + marginRight=MM_TO_POINT(10.0); + marginTop=MM_TO_POINT(15.0); + marginBottom=MM_TO_POINT(15.0); + } + + elementPaper.setAttribute("orientation", int(orientation) ); + elementPaper.setAttribute("width", width); + elementPaper.setAttribute("height", height); + elementPaper.setAttribute("format", paperFormat); + elementPaper.setAttribute("columns",1); // TODO + elementPaper.setAttribute("columnspacing",2); // TODO + elementPaper.setAttribute("hType", hasEvenOddHeader ? 3 : 0); // ### no support for first-page + elementPaper.setAttribute("fType", hasEvenOddFooter ? 3 : 0); // ### no support for first-page + elementPaper.setAttribute("spHeadBody",9); // where is this in OOo? + elementPaper.setAttribute("spFootBody",9); // ? + elementPaper.setAttribute("zoom",100); + docElement.appendChild(elementPaper); + + // Page margins + TQDomElement element = mainDocument.createElement("PAPERBORDERS"); + element.setAttribute("left", marginLeft); + element.setAttribute("top", marginTop); + element.setAttribute("right", marginRight); + element.setAttribute("bottom", marginBottom); + elementPaper.appendChild(element); +} + +void OoWriterImport::prepareDocument( TQDomDocument& mainDocument, TQDomElement& framesetsElem ) +{ + mainDocument = KoDocument::createDomDocument( "kword", "DOC", "1.2" ); + TQDomElement docElement = mainDocument.documentElement(); + docElement.setAttribute( "editor", "KWord's OOWriter Import Filter" ); + docElement.setAttribute( "mime", "application/x-kword" ); + docElement.setAttribute( "syntaxVersion", "2" ); + + framesetsElem=mainDocument.createElement("FRAMESETS"); + docElement.appendChild(framesetsElem); + + // Now create VARIABLESETTINGS, mostly from meta.xml + TQDomElement varSettings = mainDocument.createElement( "VARIABLESETTINGS" ); + docElement.appendChild( varSettings ); + TQDomNode meta = KoDom::namedItemNS( m_meta, ooNS::office, "document-meta" ); + TQDomNode office = KoDom::namedItemNS( meta, ooNS::office, "meta" ); + if ( !office.isNull() ) { + TQDomElement date = KoDom::namedItemNS( office, ooNS::dc, "date" ); + if ( !date.isNull() && !date.text().isEmpty() ) { + // Both use ISO-8601, no conversion needed. + varSettings.setAttribute( "modificationDate", date.text() ); + } + date = KoDom::namedItemNS( office, ooNS::meta, "creation-date" ); + if ( !date.isNull() && !date.text().isEmpty() ) { + varSettings.setAttribute( "creationDate", date.text() ); + } + date = KoDom::namedItemNS( office, ooNS::meta, "print-date" ); + if ( !date.isNull() && !date.text().isEmpty() ) { + varSettings.setAttribute( "lastPrintingDate", date.text() ); + } + } +} + +// Copied from the msword importer +TQDomElement OoWriterImport::createInitialFrame( TQDomElement& parentFramesetElem, double left, double right, double top, double bottom, bool autoExtend, NewFrameBehavior nfb ) +{ + TQDomElement frameElementOut = parentFramesetElem.ownerDocument().createElement("FRAME"); + frameElementOut.setAttribute( "left", left ); + frameElementOut.setAttribute( "right", right ); + frameElementOut.setAttribute( "top", top ); + frameElementOut.setAttribute( "bottom", bottom ); + frameElementOut.setAttribute( "runaround", 1 ); + // AutoExtendFrame for header/footer/footnote/endnote, AutoCreateNewFrame for body text + frameElementOut.setAttribute( "autoCreateNewFrame", autoExtend ? 0 : 1 ); + frameElementOut.setAttribute( "newFrameBehavior", nfb ); + parentFramesetElem.appendChild( frameElementOut ); + return frameElementOut; +} + +KoFilter::ConversionStatus OoWriterImport::loadAndParse(const TQString& filename, TQDomDocument& doc) +{ + return OoUtils::loadAndParse( filename, doc, m_zip); +} + +KoFilter::ConversionStatus OoWriterImport::openFile() +{ + KoFilter::ConversionStatus status=loadAndParse("content.xml", m_content); + if ( status != KoFilter::OK ) + { + kdError(30518) << "Content.xml could not be parsed correctly! Aborting!" << endl; + return status; + } + + //kdDebug(30518)<<" m_content.toCString() :"<<m_content.toCString()<<endl; + + // We need to keep the TQDomDocument for styles too, unfortunately. + // Otherwise styleElement.parentNode() returns a null node + // (see StyleStack::isUserStyle). Most of styles.xml is in m_styles + // anyway, so this doesn't make a big difference. + // We now also rely on this in createStyles. + //TQDomDocument styles; + + // We do not stop if the following calls fail. + loadAndParse("styles.xml", m_stylesDoc); + loadAndParse("meta.xml", m_meta); + // not used yet: loadAndParse("settings.xml", m_settings); + + emit sigProgress( 10 ); + + return KoFilter::OK; +} + +// Very related to OoImpressImport::createDocumentInfo +void OoWriterImport::createDocumentInfo( TQDomDocument &docinfo ) +{ + docinfo = KoDocument::createDomDocument( "document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1" ); + + OoUtils::createDocumentInfo(m_meta, docinfo); + + //kdDebug(30518)<<" meta-info :"<<m_meta.toCString()<<endl; +} + +// This mainly fills member variables with the styles. +// The 'doc' argument is only for footnotes-configuration. +bool OoWriterImport::createStyleMap( const TQDomDocument & styles, TQDomDocument& doc ) +{ + TQDomElement docElement = styles.documentElement(); + TQDomNode docStyles = KoDom::namedItemNS( docElement, ooNS::office, "document-styles" ); + + if ( docElement.hasAttributeNS( ooNS::office, "version" ) ) + { + bool ok = true; + double d = docElement.attributeNS( ooNS::office, "version", TQString() ).toDouble( &ok ); + + if ( ok ) + { + kdDebug(30518) << "OpenWriter version: " << d << endl; + if ( d > 1.0 ) + { + TQString message( i18n("This document was created with OpenOffice.org version '%1'. This filter was written for version 1.0. Reading this file could cause strange behavior, crashes or incorrect display of the data. Do you want to continue converting the document?") ); + message = message.arg( docElement.attributeNS( ooNS::office, "version", TQString() ) ); + if ( KMessageBox::warningYesNo( 0, message, i18n( "Unsupported document version" ) ) == KMessageBox::No ) + return false; + } + } + } + + TQDomNode fontStyles = KoDom::namedItemNS( docElement, ooNS::office, "font-decls" ); + + if ( !fontStyles.isNull() ) + { + kdDebug(30518) << "Starting reading in font-decl..." << endl; + + insertStyles( fontStyles.toElement(), doc ); + } + else + kdDebug(30518) << "No items found" << endl; + + kdDebug(30518) << "Starting reading in office:automatic-styles" << endl; + + TQDomNode autoStyles = KoDom::namedItemNS( docElement, ooNS::office, "automatic-styles" ); + if ( !autoStyles.isNull() ) + { + insertStyles( autoStyles.toElement(), doc ); + } + else + kdDebug(30518) << "No items found" << endl; + + + kdDebug(30518) << "Reading in master styles" << endl; + + TQDomNode masterStyles = KoDom::namedItemNS( docElement, ooNS::office, "master-styles" ); + + if ( !masterStyles.isNull() ) + { + + TQDomElement master; + forEachElement( master, masterStyles ) + { + if ( master.localName() == "master-page" && master.namespaceURI() == ooNS::style ) + { + TQString name = master.attributeNS( ooNS::style, "name", TQString() ); + kdDebug(30518) << "Master style: '" << name << "' loaded " << endl; + m_masterPages.insert( name, new TQDomElement( master ) ); + } else + kdWarning(30518) << "Unknown tag " << master.tagName() << " in office:master-styles" << endl; + } + } + + + kdDebug(30518) << "Starting reading in office:styles" << endl; + + TQDomNode fixedStyles = KoDom::namedItemNS( docElement, ooNS::office, "styles" ); + + if ( !fixedStyles.isNull() ) + insertStyles( fixedStyles.toElement(), doc ); + + kdDebug(30518) << "Styles read in." << endl; + + return true; +} + +// started as a copy of OoImpressImport::insertStyles +void OoWriterImport::insertStyles( const TQDomElement& styles, TQDomDocument& doc ) +{ + //kdDebug(30518) << "Inserting styles from " << styles.tagName() << endl; + TQDomElement e; + forEachElement( e, styles ) + { + const TQString localName = e.localName(); + const TQString ns = e.namespaceURI(); + + const TQString name = e.attributeNS( ooNS::style, "name", TQString() ); + if ( ns == ooNS::style && ( + localName == "style" + || localName == "page-master" + || localName == "font-decl" ) ) + { + TQDomElement* ep = new TQDomElement( e ); + m_styles.insert( name, ep ); + kdDebug(30518) << "Style: '" << name << "' loaded " << endl; + } else if ( localName == "default-style" && ns == ooNS::style ) { + m_defaultStyle = e; + } else if ( localName == "list-style" && ns == ooNS::text ) { + TQDomElement* ep = new TQDomElement( e ); + m_listStyles.insert( name, ep ); + kdDebug(30518) << "List style: '" << name << "' loaded " << endl; + } else if ( localName == "outline-style" && ns == ooNS::text ) { + m_outlineStyle = e; + } else if ( localName == "footnotes-configuration" && ns == ooNS::text ) { + importFootnotesConfiguration( doc, e, false ); + } else if ( localName == "endnotes-configuration" && ns == ooNS::text ) { + importFootnotesConfiguration( doc, e, true ); + } else if ( localName == "linenumbering-configuration" && ns == ooNS::text ) { + // Not implemented in KWord + } else if ( localName == "number-style" && ns == ooNS::number ) { + // TODO + } else if ( ( localName == "date-style" + || localName == "time-style" ) && ns == ooNS::number ) { + importDateTimeStyle( e ); + } else { + kdWarning(30518) << "Unknown element " << localName << " in styles" << endl; + } + } +} + + +// OO spec 2.5.4. p68. Conversion to TQt format: see qdate.html +// OpenCalcImport::loadFormat has similar code, but slower, intermixed with other stuff, +// lacking long-textual forms. +void OoWriterImport::importDateTimeStyle( const TQDomElement& parent ) +{ + TQString format; + TQDomElement e; + forEachElement( e, parent ) + { + const TQString ns = e.namespaceURI(); + if ( ns != ooNS::number ) + continue; + const TQString localName = e.localName(); + const TQString numberStyle = e.attributeNS( ooNS::number, "style", TQString() ); + const bool shortForm = numberStyle == "short" || numberStyle.isEmpty(); + if ( localName == "day" ) { + format += shortForm ? "d" : "dd"; + } else if ( localName == "day-of-week" ) { + format += shortForm ? "ddd" : "dddd"; + } else if ( localName == "month" ) { + // TODO the spec has a strange mention of number:format-source + if ( e.attributeNS( ooNS::number, "textual", TQString() ) == "true" ) { + format += shortForm ? "MMM" : "MMMM"; + } else { // month number + format += shortForm ? "M" : "MM"; + } + } else if ( localName == "year" ) { + format += shortForm ? "yy" : "yyyy"; + } else if ( localName == "week-of-year" || localName == "quarter") { + // ### not supported in TQt + } else if ( localName == "hours" ) { + format += shortForm ? "h" : "hh"; + } else if ( localName == "minutes" ) { + format += shortForm ? "m" : "mm"; + } else if ( localName == "seconds" ) { + format += shortForm ? "s" : "ss"; + } else if ( localName == "am-pm" ) { + format += "ap"; + } else if ( localName == "text" ) { // litteral + format += e.text(); + } // TODO number:decimal-places + } + +#if 0 + // TQDate doesn't work both ways!!! It can't parse something back from + // a string and a format (e.g. 01/02/03 and dd/MM/yy, it will assume MM/dd/yy). + // So we also need to generate a TDELocale-like format, to parse the value + // Update: we don't need to parse the date back. + + TQString kdeFormat; + TQDomElement e; + forEachElement( e, parent ) + { + const TQString ns = e.namespaceURI(); + if ( ns != ooNS::number ) + continue; + TQString localName = e.tagName(); + const TQString numberStyle = e.attributeNS( ooNS::number, "style", TQString() ); + const bool shortForm = numberStyle == "short" || numberStyle.isEmpty(); + if ( localName == "day" ) { + kdeFormat += shortForm ? "%e" : "%d"; + } else if ( localName == "day-of-week" ) { + kdeFormat += shortForm ? "%a" : "%A"; + } else if ( localName == "month" ) { + // TODO the spec has a strange mention of number:format-source + if ( e.attributeNS( ooNS::number, "textual", TQString() ) == "true" ) { + kdeFormat += shortForm ? "%b" : "%B"; + } else { // month number + kdeFormat += shortForm ? "%n" : "%m"; + } + } else if ( localName == "year" ) { + kdeFormat += shortForm ? "%y" : "%Y"; + } else if ( localName == "week-of-year" || localName == "quarter") { + // ### not supported in TDELocale + } else if ( localName == "hours" ) { + kdeFormat += shortForm ? "%k" : "%H"; // TODO should depend on presence of am/pm + } else if ( localName == "minutes" ) { + kdeFormat += shortForm ? "%M" : "%M"; // TDELocale doesn't have 1-digit minutes + } else if ( localName == "seconds" ) { + kdeFormat += shortForm ? "%S" : "%S"; // TDELocale doesn't have 1-digit seconds + } else if ( localName == "am-pm" ) { + kdeFormat += "%p"; + } else if ( localName == "text" ) { // litteral + kdeFormat += e.text(); + } // TODO number:decimal-places + } +#endif + + TQString styleName = parent.attributeNS( ooNS::style, "name", TQString() ); + kdDebug(30518) << "datetime style: " << styleName << " qt format=" << format << endl; + m_dateTimeFormats.insert( styleName, format ); +} + +void OoWriterImport::fillStyleStack( const TQDomElement& object, const char* nsURI, const TQString& attrName ) +{ + // find all styles associated with an object and push them on the stack + // OoImpressImport has more tests here, but I don't think they're relevant to OoWriterImport + if ( object.hasAttributeNS( nsURI, attrName ) ) { + const TQString styleName = object.attributeNS( nsURI, attrName, TQString() ); + const TQDomElement* style = m_styles[styleName]; + if ( style ) + addStyles( style ); + else + kdWarning(30518) << "fillStyleStack: no style named " << styleName << " found." << endl; + } +} + +void OoWriterImport::addStyles( const TQDomElement* style ) +{ + Q_ASSERT( style ); + if ( !style ) return; + // this recursive function is necessary as parent styles can have parents themselves + if ( style->hasAttributeNS( ooNS::style, "parent-style-name" ) ) { + const TQString parentStyleName = style->attributeNS( ooNS::style, "parent-style-name", TQString() ); + TQDomElement* parentStyle = m_styles[ parentStyleName ]; + if ( parentStyle ) + addStyles( parentStyle ); + else + kdWarning(30518) << "Parent style not found: " << parentStyleName << endl; + } + else if ( !m_defaultStyle.isNull() ) // on top of all, the default style + m_styleStack.push( m_defaultStyle ); + + //kdDebug(30518) << "pushing style " << style->attributeNS( ooNS::style, "name", TQString() ) << endl; + m_styleStack.push( *style ); +} + +void OoWriterImport::applyListStyle( TQDomDocument& doc, TQDomElement& layoutElement, const TQDomElement& paragraph ) +{ + // Spec: see 3.3.5 p137 + if ( m_listStyleStack.hasListStyle() && m_nextItemIsListItem ) { + bool heading = paragraph.localName() == "h"; + m_nextItemIsListItem = false; + int level = heading ? paragraph.attributeNS( ooNS::text, "level", TQString() ).toInt() : m_listStyleStack.level(); + writeCounter( doc, layoutElement, heading, level, m_insideOrderedList ); + } +} + +void OoWriterImport::writeCounter( TQDomDocument& doc, TQDomElement& layoutElement, bool heading, int level, bool ordered ) +{ + const TQDomElement listStyle = m_listStyleStack.currentListStyle(); + //const TQDomElement listStyleProperties = m_listStyleStack.currentListStyleProperties(); + TQDomElement counter = doc.createElement( "COUNTER" ); + counter.setAttribute( "numberingtype", heading ? 1 : 0 ); + counter.setAttribute( "depth", level - 1 ); // "depth" starts at 0 + + //kdDebug(30518) << "Numbered parag. heading=" << heading << " level=" << level + // << " m_restartNumbering=" << m_restartNumbering << endl; + + if ( ordered || heading ) { + counter.setAttribute( "type", Conversion::importCounterType( listStyle.attributeNS( ooNS::style, "num-format", TQString() ) ) ); + counter.setAttribute( "lefttext", listStyle.attributeNS( ooNS::style, "num-prefix", TQString() ) ); + counter.setAttribute( "righttext", listStyle.attributeNS( ooNS::style, "num-suffix", TQString() ) ); + TQString dl = listStyle.attributeNS( ooNS::text, "display-levels", TQString() ); + if ( dl.isEmpty() ) + dl = "1"; + counter.setAttribute( "display-levels", dl ); + if ( m_restartNumbering != -1 ) { + counter.setAttribute( "start", m_restartNumbering ); + counter.setAttribute( "restart", "true" ); + } else { + // useful? + counter.setAttribute( "start", listStyle.attributeNS( ooNS::text, "start-value", TQString() ) ); + } + } + else { // bullets, see 3.3.6 p138 + counter.setAttribute( "type", 6 ); + TQString bulletChar = listStyle.attributeNS( ooNS::text, "bullet-char", TQString() ); + if ( !bulletChar.isEmpty() ) { +#if 0 // doesn't work well. Fonts lack those symbols! + counter.setAttribute( "bullet", bulletChar[0].unicode() ); + kdDebug(30518) << "bullet code " << bulletChar[0].unicode() << endl; + TQString fontName = listStyleProperties.attributeNS( ooNS::style, "font-name", TQString() ); + counter.setAttribute( "bulletfont", fontName ); +#endif + // Reverse engineering, I found those codes: + switch( bulletChar[0].unicode() ) { + case 0x2022: // small disc + counter.setAttribute( "type", 10 ); // a disc bullet + break; + case 0x25CF: // large disc + counter.setAttribute( "type", 10 ); // a disc bullet + break; + case 0xE00C: // losange - TODO in KWord. Not in OASIS either (reserved Unicode area!) + counter.setAttribute( "type", 10 ); // a disc bullet + break; + case 0xE00A: // square. Not in OASIS (reserved Unicode area!) + counter.setAttribute( "type", 9 ); + break; + case 0x2794: // arrow + case 0x27A2: // two-colors right-pointing triangle (TODO) + counter.setAttribute( "bullet", 206 ); // simpler arrow symbol + counter.setAttribute( "bulletfont", "symbol" ); + break; + case 0x2717: // cross + counter.setAttribute( "bullet", 212 ); // simpler cross symbol + counter.setAttribute( "bulletfont", "symbol" ); + break; + case 0x2714: // checkmark + counter.setAttribute( "bullet", 246 ); // hmm that's sqrt + counter.setAttribute( "bulletfont", "symbol" ); + break; + default: + counter.setAttribute( "type", 8 ); // circle + break; + } + } else { // can never happen + counter.setAttribute( "type", 10 ); // a disc bullet + } + } + + layoutElement.appendChild(counter); +} + +static TQDomElement findListLevelStyle( TQDomElement& fullListStyle, int level ) +{ + TQDomElement listLevelItem; + forEachElement( listLevelItem, fullListStyle ) + { + if ( listLevelItem.attributeNS( ooNS::text, "level", TQString() ).toInt() == level ) + return listLevelItem; + } + return TQDomElement(); +} + +bool OoWriterImport::pushListLevelStyle( const TQString& listStyleName, int level ) +{ + TQDomElement* fullListStyle = m_listStyles[listStyleName]; + if ( !fullListStyle ) { + kdWarning(30518) << "List style " << listStyleName << " not found!" << endl; + return false; + } + else + return pushListLevelStyle( listStyleName, *fullListStyle, level ); +} + +bool OoWriterImport::pushListLevelStyle( const TQString& listStyleName, // for debug only + TQDomElement& fullListStyle, int level ) +{ + // Find applicable list-level-style for level + int i = level; + TQDomElement listLevelStyle; + while ( i > 0 && listLevelStyle.isNull() ) { + listLevelStyle = findListLevelStyle( fullListStyle, i ); + --i; + } + if ( listLevelStyle.isNull() ) { + kdWarning(30518) << "List level style for level " << level << " in list style " << listStyleName << " not found!" << endl; + return false; + } + kdDebug(30518) << "Pushing list-level-style from list-style " << listStyleName << " level " << level << endl; + m_listStyleStack.push( listLevelStyle ); + return true; +} + +void OoWriterImport::parseList( TQDomDocument& doc, const TQDomElement& list, TQDomElement& currentFramesetElement ) +{ + //kdDebug(30518) << k_funcinfo << "parseList"<< endl; + + m_insideOrderedList = ( list.localName() == "ordered-list" ); + TQString oldListStyleName = m_currentListStyleName; + if ( list.hasAttributeNS( ooNS::text, "style-name" ) ) + m_currentListStyleName = list.attributeNS( ooNS::text, "style-name", TQString() ); + bool listOK = !m_currentListStyleName.isEmpty(); + const int level = m_listStyleStack.level() + 1; + //kdDebug(30518) << k_funcinfo << " listOK=" << listOK << " level=" << level << endl; + if ( listOK ) + listOK = pushListLevelStyle( m_currentListStyleName, level ); + + // Iterate over list items + TQDomElement listItem; + forEachElement( listItem, list ) + { + // It's either list-header (normal text on top of list) or list-item + m_nextItemIsListItem = ( listItem.localName() != "list-header" ); + m_restartNumbering = -1; + if ( listItem.hasAttributeNS( ooNS::text, "start-value" ) ) + m_restartNumbering = listItem.attributeNS( ooNS::text, "start-value", TQString() ).toInt(); + // ### Oasis: can be p h or list only. + parseBodyOrSimilar( doc, listItem, currentFramesetElement ); + m_restartNumbering = -1; + } + if ( listOK ) + m_listStyleStack.pop(); + m_currentListStyleName = oldListStyleName; +} + +static int numberOfParagraphs( const TQDomElement& frameset ) +{ + const TQDomNodeList children = frameset.childNodes(); + const TQString paragStr = "PARAGRAPH"; + int paragCount = 0; + for ( unsigned int i = 0 ; i < children.length() ; ++i ) { + if ( children.item( i ).toElement().tagName() == paragStr ) + ++paragCount; + } + return paragCount; +} + +void OoWriterImport::parseSpanOrSimilar( TQDomDocument& doc, const TQDomElement& parent, + TQDomElement& outputParagraph, TQDomElement& outputFormats, TQString& paragraphText, uint& pos) +{ + // Parse every child node of the parent + // Can't use forEachElement here since we also care about text nodes + for( TQDomNode node ( parent.firstChild() ); !node.isNull(); node = node.nextSibling() ) + { + TQDomElement ts ( node.toElement() ); + TQString textData; + const TQString localName( ts.localName() ); + const TQString ns = ts.namespaceURI(); + const bool isTextNS = ns == ooNS::text; + TQDomText t ( node.toText() ); + + bool shouldWriteFormat=false; // By default no <FORMAT> element should be written + + // Try to keep the order of the tag names by probability of happening + if ( isTextNS && localName == "span" ) // text:span + { + m_styleStack.save(); + fillStyleStack( ts, ooNS::text, "style-name" ); + parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos); + m_styleStack.restore(); + } + else if ( isTextNS && localName == "s" ) // text:s + { + textData = OoUtils::expandWhitespace(ts); + shouldWriteFormat=true; + } + else if ( isTextNS && localName == "tab-stop" ) // text:tab-stop + { + // KWord currently uses \t. + // Known bug: a line with only \t\t\t\t isn't loaded - XML (TQDom) strips out whitespace. + // One more good reason to switch to <text:tab-stop> instead... + textData = '\t'; + shouldWriteFormat=true; + } + else if ( isTextNS && localName == "line-break" ) + { + textData = '\n'; + shouldWriteFormat=true; + } + else if ( isTextNS && + ( localName == "footnote" || localName == "endnote" ) ) + { + textData = '#'; // anchor placeholder + importFootnote( doc, ts, outputFormats, pos, localName ); + } + else if ( localName == "image" && ns == ooNS::draw ) + { + textData = '#'; // anchor placeholder + TQString frameName = appendPicture(doc, ts); + anchorFrameset( doc, outputFormats, pos, frameName ); + } + else if ( localName == "text-box" && ns == ooNS::draw ) + { + textData = '#'; // anchor placeholder + TQString frameName = appendTextBox(doc, ts); + anchorFrameset( doc, outputFormats, pos, frameName ); + } + else if ( isTextNS && localName == "a" ) + { + m_styleStack.save(); + TQString href( ts.attributeNS( ooNS::xlink, "href", TQString()) ); + if ( href.startsWith("#") ) + { + // We have a reference to a bookmark (### TODO) + // As we do not support it now, treat it as a <text:span> without formatting + parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos); + } + else + { + // The problem is that KWord's hyperlink text is not inside the normal text, but for OOWriter it is nearly a <text:span> + // So we have to fake. + TQDomElement fakeParagraph, fakeFormats; + uint fakePos=0; + TQString text; + parseSpanOrSimilar( doc, ts, fakeParagraph, fakeFormats, text, fakePos); + textData = '#'; // hyperlink placeholder + TQDomElement linkElement (doc.createElement("LINK")); + linkElement.setAttribute("hrefName",ts.attributeNS( ooNS::xlink, "href", TQString())); + linkElement.setAttribute("linkName",text); + appendKWordVariable(doc, outputFormats, ts, pos, "STRING", 9, linkElement); + } + m_styleStack.restore(); + } + else if ( isTextNS && + (localName == "date" // fields + || localName == "print-time" + || localName == "print-date" + || localName == "creation-time" + || localName == "creation-date" + || localName == "modification-time" + || localName == "modification-date" + || localName == "time" + || localName == "page-number" + || localName == "chapter" + || localName == "file-name" + || localName == "author-name" + || localName == "author-initials" + || localName == "subject" + || localName == "title" + || localName == "description" + || localName == "variable-set" + || localName == "page-variable-get" + || localName == "user-defined" + || localName.startsWith( "sender-") + ) ) + // TODO in kword: printed-by, initial-creator + { + textData = "#"; // field placeholder + appendField(doc, outputFormats, ts, pos); + } + else if ( isTextNS && localName == "bookmark" ) + { + // the number of <PARAGRAPH> tags in the frameset element is the parag id + // (-1 for starting at 0, +1 since not written yet) + Q_ASSERT( !m_currentFrameset.isNull() ); + appendBookmark( doc, numberOfParagraphs( m_currentFrameset ), + pos, ts.attributeNS( ooNS::text, "name", TQString() ) ); + } + else if ( isTextNS && localName == "bookmark-start" ) { + m_bookmarkStarts.insert( ts.attributeNS( ooNS::text, "name", TQString() ), + BookmarkStart( m_currentFrameset.attribute( "name" ), + numberOfParagraphs( m_currentFrameset ), + pos ) ); + } + else if ( isTextNS && localName == "bookmark-end" ) { + TQString bkName = ts.attributeNS( ooNS::text, "name", TQString() ); + BookmarkStartsMap::iterator it = m_bookmarkStarts.find( bkName ); + if ( it == m_bookmarkStarts.end() ) { // bookmark end without start. This seems to happen.. + // insert simple bookmark then + appendBookmark( doc, numberOfParagraphs( m_currentFrameset ), + pos, ts.attributeNS( ooNS::text, "name", TQString() ) ); + } else { + if ( (*it).frameSetName != m_currentFrameset.attribute( "name" ) ) { + // Oh tell me this never happens... + kdWarning(30518) << "Cross-frameset bookmark! Not supported." << endl; + } else { + appendBookmark( doc, (*it).paragId, (*it).pos, + numberOfParagraphs( m_currentFrameset ), pos, it.key() ); + } + m_bookmarkStarts.remove( it ); + } + } + else if ( t.isNull() ) // no textnode, we must ignore + { + kdWarning(30518) << "Ignoring tag " << ts.tagName() << endl; + continue; + } + else + { + textData = t.data(); + shouldWriteFormat=true; + } + + paragraphText += textData; + const uint length = textData.length(); + + if (shouldWriteFormat) + { + writeFormat( doc, outputFormats, 1 /* id for normal text */, pos, length ); + } + + pos += length; + } +} + +TQDomElement OoWriterImport::parseParagraph( TQDomDocument& doc, const TQDomElement& paragraph ) +{ + TQDomElement p = doc.createElement( "PARAGRAPH" ); + + TQDomElement formats = doc.createElement( "FORMATS" ); + + TQString paragraphText; + uint pos = 0; + + // parse every child node of the paragraph + parseSpanOrSimilar( doc, paragraph, p, formats, paragraphText, pos); + + TQDomElement text = doc.createElement( "TEXT" ); + text.appendChild( doc.createTextNode( paragraphText ) ); + text.setAttribute( "xml:space", "preserve" ); + p.appendChild( text ); + //kdDebug(30518) << k_funcinfo << "Para text is: " << paragraphText << endl; + + p.appendChild( formats ); + TQDomElement layoutElement = doc.createElement( "LAYOUT" ); + p.appendChild( layoutElement ); + + // Style name + TQString styleName = m_styleStack.userStyleName("paragraph"); + if ( !styleName.isEmpty() ) + { + TQDomElement nameElement = doc.createElement("NAME"); + nameElement.setAttribute( "value", kWordStyleName(styleName) ); + layoutElement.appendChild(nameElement); + } + + writeLayout( doc, layoutElement ); + writeFormat( doc, layoutElement, 1, 0, 0 ); // paragraph format, useful for empty parags + + applyListStyle( doc, layoutElement, paragraph ); + + TQDomElement* paragraphStyle = m_styles[paragraph.attributeNS( ooNS::text, "style-name", TQString() )]; + TQString masterPageName = paragraphStyle ? paragraphStyle->attributeNS( ooNS::style, "master-page-name", TQString() ) : TQString(); + if ( masterPageName.isEmpty() ) + masterPageName = "Standard"; // Seems to be a builtin name for the default layout... + if ( masterPageName != m_currentMasterPage ) + { + // Detected a change in the master page -> this means we have to use a new page layout + // and insert a frame break if not on the first paragraph. + // In KWord we don't support sections so the first paragraph is the one that determines the page layout. + if ( m_currentMasterPage.isEmpty() ) { + m_currentMasterPage = masterPageName; // before writePageLayout to avoid recursion + writePageLayout( doc, masterPageName ); + } + else + { + m_currentMasterPage = masterPageName; + TQDomElement pageBreakElem = layoutElement.namedItem( "PAGEBREAKING" ).toElement(); + if ( !pageBreakElem.isNull() ) { + pageBreakElem = doc.createElement( "PAGEBREAKING" ); + layoutElement.appendChild( pageBreakElem ); + } + pageBreakElem.setAttribute( "hardFrameBreak", "true" ); + // We have no way to store the new page layout, KWord doesn't have sections. + } + } + + return p; +} + +void OoWriterImport::writeFormat( TQDomDocument& doc, TQDomElement& formats, int id, int pos, int length ) +{ + // Prepare a FORMAT element for this run of text + TQDomElement format( doc.createElement( "FORMAT" ) ); + format.setAttribute( "id", id ); + format.setAttribute( "pos", pos ); + format.setAttribute( "len", length ); + + // parse the text-properties + // TODO compare with the paragraph style, and only write out if != from style. + // (This is very important, it's not only an optimization. If no attribute is + // specified in the .kwd file, the style's property will be used, which might + // not always be correct). + // On 2nd thought, this doesn't seem to happen. If a style property needs undoing + // OO always writes it down in the XML, so we write out the property just fine. + // Both apps implement a "write property only if necessary" mechanism, I can't' + // find a case that breaks yet. + + if ( m_styleStack.hasAttributeNS( ooNS::fo, "color" ) ) { // 3.10.3 + TQColor color( m_styleStack.attributeNS( ooNS::fo, "color" ) ); // #rrggbb format + TQDomElement colorElem( doc.createElement( "COLOR" ) ); + colorElem.setAttribute( "red", color.red() ); + colorElem.setAttribute( "blue", color.blue() ); + colorElem.setAttribute( "green", color.green() ); + format.appendChild( colorElem ); + } + if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-family" ) // 3.10.9 + || m_styleStack.hasAttributeNS( ooNS::style, "font-name") ) // 3.10.8 + { + // Hmm, the remove "'" could break it's in the middle of the fontname... + TQString fontName = m_styleStack.attributeNS( ooNS::fo, "font-family" ).remove( "'" ); + if (fontName.isEmpty()) + { + // ##### TODO. This is wrong. style:font-name refers to a font-decl entry. + // We have to look it up there, and retrieve _all_ font attributes from it, not just the name. + fontName = m_styleStack.attributeNS( ooNS::style, "font-name" ).remove( "'" ); + } + // 'Thorndale' is not known outside OpenOffice so we substitute it + // with 'Times New Roman' that looks nearly the same. + if ( fontName == "Thorndale" ) + fontName = "Times New Roman"; + + fontName.remove(TQRegExp("\\sCE$")); // Arial CE -> Arial + + TQDomElement fontElem( doc.createElement( "FONT" ) ); + fontElem.setAttribute( "name", fontName ); + format.appendChild( fontElem ); + } + if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-size" ) ) { // 3.10.14 + double pointSize = m_styleStack.fontSize(); + + TQDomElement fontSize( doc.createElement( "SIZE" ) ); + fontSize.setAttribute( "value", tqRound(pointSize) ); // KWord uses toInt()! + format.appendChild( fontSize ); + } + if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-weight" ) ) { // 3.10.24 + TQDomElement weightElem( doc.createElement( "WEIGHT" ) ); + TQString fontWeight = m_styleStack.attributeNS( ooNS::fo, "font-weight" ); + int boldness = fontWeight.toInt(); + if ( fontWeight == "bold" ) + boldness = 75; + else if ( boldness == 0 ) + boldness = 50; + weightElem.setAttribute( "value", boldness ); + format.appendChild( weightElem ); + } + + if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-style" ) ) // 3.10.19 + if ( m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "italic" || + m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "oblique" ) // no difference in kotext + { + TQDomElement italic = doc.createElement( "ITALIC" ); + italic.setAttribute( "value", 1 ); + format.appendChild( italic ); + } + + bool wordByWord = (m_styleStack.hasAttributeNS( ooNS::fo, "score-spaces")) // 3.10.25 + && (m_styleStack.attributeNS( ooNS::fo, "score-spaces") == "false"); + if( m_styleStack.hasAttributeNS( ooNS::style, "text-crossing-out" )) // 3.10.6 + { + TQString strikeOutType = m_styleStack.attributeNS( ooNS::style, "text-crossing-out" ); + TQDomElement strikeOut = doc.createElement( "STRIKEOUT" ); + if( strikeOutType =="double-line") + { + strikeOut.setAttribute("value", "double"); + strikeOut.setAttribute("styleline","solid"); + } + else if( strikeOutType =="single-line") + { + strikeOut.setAttribute("value", "single"); + strikeOut.setAttribute("styleline","solid"); + } + else if( strikeOutType =="thick-line") + { + strikeOut.setAttribute("value", "single-bold"); + strikeOut.setAttribute("styleline","solid"); + } + if ( wordByWord ) + strikeOut.setAttribute("wordbyword", 1); + // not supported by KWord: "slash" and "X" + // not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot) + format.appendChild( strikeOut ); + } + if( m_styleStack.hasAttributeNS( ooNS::style, "text-position")) // 3.10.7 + { + TQDomElement vertAlign = doc.createElement( "VERTALIGN" ); + TQString text_position = m_styleStack.attributeNS( ooNS::style, "text-position"); + TQString value; + TQString relativetextsize; + OoUtils::importTextPosition( text_position, value, relativetextsize ); + vertAlign.setAttribute( "value", value ); + if ( !relativetextsize.isEmpty() ) + vertAlign.setAttribute( "relativetextsize", relativetextsize ); + format.appendChild( vertAlign ); + } + if ( m_styleStack.hasAttributeNS( ooNS::style, "text-underline" ) ) // 3.10.22 + { + TQString underline; + TQString styleline; + OoUtils::importUnderline( m_styleStack.attributeNS( ooNS::style, "text-underline" ), + underline, styleline ); + TQDomElement underLineElem = doc.createElement( "UNDERLINE" ); + underLineElem.setAttribute( "value", underline ); + underLineElem.setAttribute( "styleline", styleline ); + + TQString underLineColor = m_styleStack.attributeNS( ooNS::style, "text-underline-color" ); // 3.10.23 + if ( !underLineColor.isEmpty() && underLineColor != "font-color" ) + underLineElem.setAttribute("underlinecolor", underLineColor); + if ( wordByWord ) + underLineElem.setAttribute("wordbyword", 1); + format.appendChild( underLineElem ); + } + // Small caps, lowercase, uppercase + if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-variant" ) // 3.10.1 + || m_styleStack.hasAttributeNS( ooNS::fo, "text-transform" ) ) // 3.10.2 + { + TQDomElement fontAttrib( doc.createElement( "FONTATTRIBUTE" ) ); + bool smallCaps = m_styleStack.attributeNS( ooNS::fo, "font-variant" ) == "small-caps"; + if ( smallCaps ) + { + fontAttrib.setAttribute( "value", "smallcaps" ); + } else + { + // Both KWord and OO use "uppercase" and "lowercase". + // TODO in KWord: "capitalize". + fontAttrib.setAttribute( "value", m_styleStack.attributeNS( ooNS::fo, "text-transform" ) ); + } + format.appendChild( fontAttrib ); + } + + if (m_styleStack.hasAttributeNS( ooNS::fo, "language")) // 3.10.17 + { + TQDomElement lang = doc.createElement("LANGUAGE"); + TQString tmp = m_styleStack.attributeNS( ooNS::fo, "language"); + if (tmp=="en") + lang.setAttribute("value", "en_US"); + else + lang.setAttribute("value", tmp); + format.appendChild(lang); + } + + if (m_styleStack.hasAttributeNS( ooNS::style, "text-background-color")) // 3.10.28 + { + TQDomElement bgCol = doc.createElement("TEXTBACKGROUNDCOLOR"); + TQColor tmp = m_styleStack.attributeNS( ooNS::style, "text-background-color"); + if (tmp != "transparent") + { + bgCol.setAttribute("red", tmp.red()); + bgCol.setAttribute("green", tmp.green()); + bgCol.setAttribute("blue", tmp.blue()); + format.appendChild(bgCol); + } + } + + if (m_styleStack.hasAttributeNS( ooNS::fo, "text-shadow")) // 3.10.21 + { + TQDomElement shadow = doc.createElement("SHADOW"); + TQString css = m_styleStack.attributeNS( ooNS::fo, "text-shadow"); + // Workaround for OOo-1.1 bug: they forgot to save the color. + TQStringList tokens = TQStringList::split(' ', css); + if ( !tokens.isEmpty() ) { + TQColor col( tokens.first() ); + if ( !col.isValid() && tokens.count() > 1 ) { + col.setNamedColor( tokens.last() ); + } + if ( !col.isValid() ) // no valid color found at either end -> append gray + css += " gray"; + } + shadow.setAttribute("text-shadow", css); + format.appendChild(shadow); + } + + /* + Missing properties: + style:use-window-font-color, 3.10.4 - this is what KWord uses by default (fg color from the color style) + OO also switches to another color when necessary to avoid dark-on-dark and light-on-light cases. + (that is TODO in KWord) + style:text-outline, 3.10.5 - not implemented in kotext + style:font-family-generic, 3.10.10 - roman, swiss, modern -> map to a font? + style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise + style:font-pitch, 3.10.12 - fixed or variable -> map to a font? + style:font-charset, 3.10.14 - not necessary with TQt + style:font-size-rel, 3.10.15 - TODO in StyleStack::fontSize() + fo:letter-spacing, 3.10.16 - not implemented in kotext + style:text-relief, 3.10.20 - not implemented in kotext + style:letter-kerning, 3.10.20 - not implemented in kotext + style:text-blinking, 3.10.27 - not implemented in kotext IIRC + style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/ + style:text-emphasis, 3.10.31 - not implemented in kotext + style:text-scale, 3.10.33 - not implemented in kotext + style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects) + style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects) + style:punctuation-wrap, 3.10.36 - not implemented in kotext + */ + + if ( format.hasChildNodes() || !length /*hack for styles, they should always have a format*/) + formats.appendChild( format ); +} + +void OoWriterImport::writeLayout( TQDomDocument& doc, TQDomElement& layoutElement ) +{ + Q_ASSERT( layoutElement.ownerDocument() == doc ); + + // Always write out the alignment, it's required + TQDomElement flowElement = doc.createElement("FLOW"); + + /* This was only an intermediate OASIS decision. The final decision is: + * fo:text-align can be "left", "right", "center", "justify", and + * "start" will mean direction-dependent. However if we use this right now, + * OOo won't understand it. So that's for later, we keep our own attribute + * for now, so that export-import works. + */ + if ( m_styleStack.attributeNS( ooNS::style, "text-auto-align" ) == "true" ) + flowElement.setAttribute( "align", "auto" ); + else + { + if ( m_styleStack.hasAttributeNS( ooNS::fo, "text-align" ) ) // 3.11.4 + flowElement.setAttribute( "align", Conversion::importAlignment( m_styleStack.attributeNS( ooNS::fo, "text-align" ) ) ); + else + flowElement.setAttribute( "align", "auto" ); + } + layoutElement.appendChild( flowElement ); + + if ( m_styleStack.hasAttributeNS( ooNS::fo, "writing-mode" ) ) // http://web4.w3.org/TR/xsl/slice7.html#writing-mode + { + // LTR is lr-tb. RTL is rl-tb + TQString writingMode = m_styleStack.attributeNS( ooNS::fo, "writing-mode" ); + flowElement.setAttribute( "dir", writingMode=="rl-tb" || writingMode=="rl" ? "R" : "L" ); + } + + // Indentation (margins) + OoUtils::importIndents( layoutElement, m_styleStack ); + + // Offset before and after paragraph + OoUtils::importTopBottomMargin( layoutElement, m_styleStack ); + + // Line spacing + OoUtils::importLineSpacing( layoutElement, m_styleStack ); + + // Tabulators + OoUtils::importTabulators( layoutElement, m_styleStack ); + + // Borders + OoUtils::importBorders( layoutElement, m_styleStack ); + + // Page breaking. This isn't in OoUtils since it doesn't apply to KPresenter + if( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") || + m_styleStack.hasAttributeNS( ooNS::fo, "break-after") || + m_styleStack.hasAttributeNS( ooNS::style, "break-inside") || + m_styleStack.hasAttributeNS( ooNS::style, "keep-with-next") || + m_styleStack.hasAttributeNS( ooNS::fo, "keep-with-next") ) + { + TQDomElement pageBreak = doc.createElement( "PAGEBREAKING" ); + if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") ) { // 3.11.24 + bool breakBefore = m_styleStack.attributeNS( ooNS::fo, "break-before" ) != "auto"; + // TODO in KWord: implement difference between "column" and "page" + pageBreak.setAttribute("hardFrameBreak", breakBefore ? "true" : "false"); + } + else if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-after") ) { // 3.11.24 + bool breakAfter = m_styleStack.attributeNS( ooNS::fo, "break-after" ) != "auto"; + // TODO in KWord: implement difference between "column" and "page" + pageBreak.setAttribute("hardFrameBreakAfter", breakAfter ? "true" : "false"); + } + + if ( m_styleStack.hasAttributeNS( ooNS::style, "break-inside" ) ) { // 3.11.7 + bool breakInside = m_styleStack.attributeNS( ooNS::style, "break-inside" ) == "true"; + pageBreak.setAttribute("linesTogether", breakInside ? "false" : "true"); // opposite meaning + } + if ( m_styleStack.hasAttributeNS( ooNS::fo, "keep-with-next" ) ) { // 3.11.31 (the doc said style:keep-with-next but DV said it's wrong) + // OASIS spec says it's "auto"/"always", not a boolean. Not sure which one OO uses. + TQString val = m_styleStack.attributeNS( ooNS::fo, "keep-with-next" ); + pageBreak.setAttribute("keepWithNext", ( val == "true" || val == "always" ) ? "true" : "false"); + } + layoutElement.appendChild( pageBreak ); + } + + // TODO in KWord: padding + /* padding works together with the borders. The margins are around a + * paragraph, and the padding is the space between the border and the + * paragraph. In the OOo UI, you can only select padding when you have + * selected a border. + * + * There's some difference in conjunction with other features, in that the + * margin area is outside the paragraph, and the padding area is inside the + * paragraph. So if you set a paragraph background color, the padding area + * will be colored, but the margin area won't. + */ + +/* + Paragraph properties not implemented in KWord: + style:text-align-last + style:justify-single-word + fo:background-color (3.11.25, bg color for a paragraph, unlike style:text-background-color) + style:background-image (3.11.26) + fo:widows + fo:orphans + fo:hyphenate + fo:hyphenation-keep + fo:hyphenation-remain-char-count + fo:hyphenation-push-char-count + fo:hyphenation-ladder-count + style:drop-cap + style:register-true + style:auto-text-indent + "page sequence entry point" + style:background-image + line numbering + punctuation wrap, 3.10.36 + vertical alignment - a bit like offsetfrombaseline (but not for subscript/superscript, in general) + Michael said those are in fact parag properties: + style:text-autospace, 3.10.32 - not implemented in kotext + style:line-break, 3.10.37 - apparently that's for some Asian languages +*/ + +} + +void OoWriterImport::importFrame( TQDomElement& frameElementOut, const TQDomElement& object, bool isText ) +{ + double width = 100; + if ( object.hasAttributeNS( ooNS::svg, "width" ) ) { // fixed width + // TODO handle percentage (of enclosing table/frame/page) + width = KoUnit::parseValue( object.attributeNS( ooNS::svg, "width", TQString() ) ); + } else if ( object.hasAttributeNS( ooNS::fo, "min-width" ) ) { + // min-width is not supported in KWord. Let's use it as a fixed width. + width = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-width", TQString() ) ); + } else { + kdWarning(30518) << "Error in text-box: neither width nor min-width specified!" << endl; + } + double height = 100; + bool hasMinHeight = false; + if ( object.hasAttributeNS( ooNS::svg, "height" ) ) { // fixed height + // TODO handle percentage (of enclosing table/frame/page) + height = KoUnit::parseValue( object.attributeNS( ooNS::svg, "height", TQString() ) ); + } else if ( object.hasAttributeNS( ooNS::fo, "min-height" ) ) { + height = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-height", TQString() ) ); + hasMinHeight = true; + } else { + kdWarning(30518) << "Error in text-box: neither height nor min-height specified!" << endl; + } + + // draw:textarea-vertical-align, draw:textarea-horizontal-align + + // Not supported in KWord: fo:max-height fo:max-width + // Anchor, Shadow (3.11.30), Columns + + // #### horizontal-pos horizontal-rel vertical-pos vertical-rel anchor-type + // All the above changes the placement! + // See 3.8 (p199) for details. + + // TODO draw:auto-grow-height draw:auto-grow-width - hmm? I thought min-height meant auto-grow-height... + + + KoRect frameRect( KoUnit::parseValue( object.attributeNS( ooNS::svg, "x", TQString() ) ), + KoUnit::parseValue( object.attributeNS( ooNS::svg, "y", TQString() ) ), + width, height ); + + frameElementOut.setAttribute("left", frameRect.left() ); + frameElementOut.setAttribute("right", frameRect.right() ); + frameElementOut.setAttribute("top", frameRect.top() ); + frameElementOut.setAttribute("bottom", frameRect.bottom() ); + if ( hasMinHeight ) + frameElementOut.setAttribute("min-height", height ); + frameElementOut.setAttribute( "z-index", object.attributeNS( ooNS::draw, "z-index", TQString() ) ); + TQPair<int, TQString> attribs = Conversion::importWrapping( m_styleStack.attributeNS( ooNS::style, "wrap" ) ); + frameElementOut.setAttribute("runaround", attribs.first ); + if ( !attribs.second.isEmpty() ) + frameElementOut.setAttribute("runaroundSide", attribs.second ); + // ## runaroundGap is a problem. KWord-1.3 had one value, OO has 4 (margins on all sides, see p98). + // Fixed in KWord-post-1.3, it has 4 values now. + + + if ( isText ) { + int overflowBehavior; + if ( m_styleStack.hasAttributeNS( ooNS::style, "overflow-behavior" ) ) { // OASIS extension + overflowBehavior = Conversion::importOverflowBehavior( m_styleStack.attributeNS( ooNS::style, "overflow-behavior" ) ); + } else { + // AutoCreateNewFrame not supported in OO-1.1. The presence of min-height tells if it's an auto-resized frame. + overflowBehavior = hasMinHeight ? 0 /*AutoExtendFrame*/ : 2 /*Ignore, i.e. fixed size*/; + } + // Not implemented in KWord: contour wrapping + frameElementOut.setAttribute("autoCreateNewFrame", overflowBehavior); + } + + // TODO sheetSide (not implemented in KWord, but in its DTD) + + importCommonFrameProperties( frameElementOut ); +} + +void OoWriterImport::importCommonFrameProperties( TQDomElement& frameElementOut ) +{ + // padding. fo:padding for 4 values or padding-left/right/top/bottom (3.11.29 p228) + double paddingLeft = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "left" ) ); + double paddingRight = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "right" ) ); + double paddingTop = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "top" ) ); + double paddingBottom = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "bottom" ) ); + + if ( paddingLeft != 0 ) + frameElementOut.setAttribute( "bleftpt", paddingLeft ); + if ( paddingRight != 0 ) + frameElementOut.setAttribute( "brightpt", paddingRight ); + if ( paddingTop != 0 ) + frameElementOut.setAttribute( "btoppt", paddingTop ); + if ( paddingBottom != 0 ) + frameElementOut.setAttribute( "bbottompt", paddingBottom ); + + // background color (3.11.25) + bool transparent = false; + TQColor bgColor; + if ( m_styleStack.hasAttributeNS( ooNS::fo, "background-color" ) ) { + TQString color = m_styleStack.attributeNS( ooNS::fo, "background-color" ); + if ( color == "transparent" ) + transparent = true; + else + bgColor.setNamedColor( color ); + } + if ( transparent ) + frameElementOut.setAttribute( "bkStyle", 0 ); + else if ( bgColor.isValid() ) { + // OOwriter doesn't support fill patterns (bkStyle). + // But the file support is more generic, and supports: draw:stroke, svg:stroke-color, draw:fill, draw:fill-color + frameElementOut.setAttribute( "bkStyle", 1 ); + frameElementOut.setAttribute( "bkRed", bgColor.red() ); + frameElementOut.setAttribute( "bkBlue", bgColor.blue() ); + frameElementOut.setAttribute( "bkGreen", bgColor.green() ); + } + + + // borders (3.11.27) + // can be none/hidden, solid and double. General form is the XSL/FO "width|style|color" + { + double width; + int style; + TQColor color; + if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "left"), &width, &style, &color)) { + frameElementOut.setAttribute( "lWidth", width ); + if ( color.isValid() ) { // should be always true, but who knows + frameElementOut.setAttribute( "lRed", color.red() ); + frameElementOut.setAttribute( "lBlue", color.blue() ); + frameElementOut.setAttribute( "lGreen", color.green() ); + } + frameElementOut.setAttribute( "lStyle", style ); + } + if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "right"), &width, &style, &color)) { + frameElementOut.setAttribute( "rWidth", width ); + if ( color.isValid() ) { // should be always true, but who knows + frameElementOut.setAttribute( "rRed", color.red() ); + frameElementOut.setAttribute( "rBlue", color.blue() ); + frameElementOut.setAttribute( "rGreen", color.green() ); + } + frameElementOut.setAttribute( "rStyle", style ); + } + if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "top"), &width, &style, &color)) { + frameElementOut.setAttribute( "tWidth", width ); + if ( color.isValid() ) { // should be always true, but who knows + frameElementOut.setAttribute( "tRed", color.red() ); + frameElementOut.setAttribute( "tBlue", color.blue() ); + frameElementOut.setAttribute( "tGreen", color.green() ); + } + frameElementOut.setAttribute( "tStyle", style ); + } + if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "bottom"), &width, &style, &color)) { + frameElementOut.setAttribute( "bWidth", width ); + if ( color.isValid() ) { // should be always true, but who knows + frameElementOut.setAttribute( "bRed", color.red() ); + frameElementOut.setAttribute( "bBlue", color.blue() ); + frameElementOut.setAttribute( "bGreen", color.green() ); + } + frameElementOut.setAttribute( "bStyle", style ); + } + } + // TODO more refined border spec for double borders (3.11.28) +} + +TQString OoWriterImport::appendTextBox(TQDomDocument& doc, const TQDomElement& object) +{ + const TQString frameName ( object.attributeNS( ooNS::draw, "name", TQString()) ); // ### TODO: what if empty, i.e. non-unique + kdDebug(30518) << "appendTextBox " << frameName << endl; + m_styleStack.save(); + fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element + + // Create KWord frameset + TQDomElement framesetElement(doc.createElement("FRAMESET")); + framesetElement.setAttribute("frameType",1); + framesetElement.setAttribute("frameInfo",0); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",frameName); + TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement()); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut(doc.createElement("FRAME")); + framesetElement.appendChild(frameElementOut); + importFrame( frameElementOut, object, true /*text*/ ); + // TODO editable + + // We're done with the graphics style + m_styleStack.restore(); + + // Obey draw:text-style-name + if ( m_styleStack.hasAttributeNS( ooNS::draw, "text-style-name" ) ) + addStyles( m_styles[m_styleStack.attributeNS( ooNS::draw, "text-style-name" )] ); + + // Parse contents + parseBodyOrSimilar( doc, object, framesetElement ); + + return frameName; +} + +// OOo SPEC: 3.6.3 p149 +void OoWriterImport::importFootnote( TQDomDocument& doc, const TQDomElement& object, TQDomElement& formats, uint pos, const TQString& localName ) +{ + const TQString frameName( object.attributeNS( ooNS::text, "id", TQString()) ); + TQDomElement citationElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-citation").latin1() ).toElement(); + + bool endnote = localName == "endnote"; + + TQString label = citationElem.attributeNS( ooNS::text, "label", TQString() ); + bool autoNumbered = label.isEmpty(); + + // The var + TQDomElement footnoteElem = doc.createElement( "FOOTNOTE" ); + if ( autoNumbered ) + footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway + else + footnoteElem.setAttribute( "value", label ); + footnoteElem.setAttribute( "notetype", endnote ? "endnote" : "footnote" ); + footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" ); + footnoteElem.setAttribute( "frameset", frameName ); + + appendKWordVariable( doc, formats, citationElem, pos, "STRI", 11 /*KWord code for footnotes*/, footnoteElem ); + + // The frameset + TQDomElement framesetElement( doc.createElement("FRAMESET") ); + framesetElement.setAttribute( "frameType", 1 /* text */ ); + framesetElement.setAttribute( "frameInfo", 7 /* footnote/endnote */ ); + framesetElement.setAttribute( "name" , frameName ); + TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement()); + framesetsPluralElement.appendChild(framesetElement); + createInitialFrame( framesetElement, 29, 798, 567, 567+41, true, NoFollowup ); + // TODO importCommonFrameProperties ? + + // The text inside the frameset + TQDomElement bodyElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-body").latin1() ).toElement(); + parseBodyOrSimilar( doc, bodyElem, framesetElement ); +} + +TQString OoWriterImport::appendPicture(TQDomDocument& doc, const TQDomElement& object) +{ + const TQString frameName ( object.attributeNS( ooNS::draw, "name", TQString()) ); // ### TODO: what if empty, i.e. non-unique + const TQString href ( object.attributeNS( ooNS::xlink, "href", TQString()) ); + + kdDebug(30518) << "Picture: " << frameName << " " << href << " (in OoWriterImport::appendPicture)" << endl; + + KoPicture picture; + if ( href[0]=='#' ) + { + TQString strExtension; + const int result=href.findRev("."); + if (result>=0) + { + strExtension=href.mid(result+1); // As we are using KoPicture, the extension should be without the dot. + } + TQString filename(href.mid(1)); + KoPictureKey key(filename, TQDateTime::currentDateTime(Qt::UTC)); + picture.setKey(key); + + if (!m_zip) + return frameName; // Should not happen + + const KArchiveEntry* entry = m_zip->directory()->entry( filename ); + if (!entry) + { + kdWarning(30518) << "Picture " << filename << " not found!" << endl; + return frameName; + } + if (entry->isDirectory()) + { + kdWarning(30518) << "Picture " << filename << " is a directory!" << endl; + return frameName; + } + const KZipFileEntry* f = static_cast<const KZipFileEntry *>(entry); + TQIODevice* io=f->device(); + kdDebug(30518) << "Picture " << filename << " has size " << f->size() << endl; + + if (!io) + { + kdWarning(30518) << "No TQIODevice for picture " << frameName << " " << href << endl; + return frameName; + } + if (!picture.load(io,strExtension)) + kdWarning(30518) << "Cannot load picture: " << frameName << " " << href << endl; + } + else + { + KURL url; + url.setPath(href); // ### TODO: is this really right? + picture.setKeyAndDownloadPicture(url, 0); // ### TODO: find a better parent if possible + } + + kdDebug(30518) << "Picture ready! Key: " << picture.getKey().toString() << " Size:" << picture.getOriginalSize() << endl; + + TQString strStoreName; + strStoreName="pictures/picture"; + strStoreName+=TQString::number(++m_pictureNumber); + strStoreName+='.'; + strStoreName+=picture.getExtension(); + + kdDebug(30518) << "Storage name: " << strStoreName << endl; + + KoStoreDevice* out = m_chain->storageFile( strStoreName , KoStore::Write ); + if (out) + { + if (!out->open(IO_WriteOnly)) + { + kdWarning(30518) << "Cannot open for saving picture: " << frameName << " " << href << endl; + return frameName; + } + if (!picture.save(out)) + kdWarning(30518) << "Cannot save picture: " << frameName << " " << href << endl; + out->close(); + } + else + { + kdWarning(30518) << "Cannot store picture: " << frameName << " " << href << endl; + return frameName; + } + + // Now that we have copied the image, we need to make some bookkeeping + + TQDomElement docElement( doc.documentElement() ); + + TQDomElement framesetsPluralElement ( docElement.namedItem("FRAMESETS").toElement() ); + + TQDomElement framesetElement=doc.createElement("FRAMESET"); + framesetElement.setAttribute("frameType",2); + framesetElement.setAttribute("frameInfo",0); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",frameName); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut=doc.createElement("FRAME"); + framesetElement.appendChild(frameElementOut); + + m_styleStack.save(); + fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element + importFrame( frameElementOut, object, false /*not text*/ ); + m_styleStack.restore(); + + TQDomElement element=doc.createElement("PICTURE"); + element.setAttribute("keepAspectRatio","true"); + framesetElement.setAttribute("frameType",2); // Picture + framesetElement.appendChild(element); + + TQDomElement singleKey ( doc.createElement("KEY") ); + picture.getKey().saveAttributes(singleKey); + element.appendChild(singleKey); + + TQDomElement picturesPluralElement ( docElement.namedItem("PICTURES").toElement() ); + if (picturesPluralElement.isNull()) + { + // We do not yet have any <PICTURES> element, so we must create it + picturesPluralElement = doc.createElement("PICTURES"); + docElement.appendChild(picturesPluralElement); + } + + TQDomElement pluralKey ( doc.createElement("KEY") ); + picture.getKey().saveAttributes(pluralKey); + pluralKey.setAttribute("name",strStoreName); + picturesPluralElement.appendChild(pluralKey); + return frameName; +} + +void OoWriterImport::anchorFrameset( TQDomDocument& doc, TQDomElement& formats, uint pos, const TQString& frameSetName ) +{ + TQDomElement formatElementOut=doc.createElement("FORMAT"); + formatElementOut.setAttribute("id",6); // Floating frame + formatElementOut.setAttribute("pos",pos); // Start position + formatElementOut.setAttribute("len",1); // Start position + formats.appendChild(formatElementOut); //Append to <FORMATS> + + TQDomElement anchor=doc.createElement("ANCHOR"); + // No name attribute! + anchor.setAttribute("type","frameset"); + anchor.setAttribute("instance",frameSetName); + formatElementOut.appendChild(anchor); +} + +void OoWriterImport::appendField(TQDomDocument& doc, TQDomElement& outputFormats, TQDomElement& object, uint pos) +// Note: TQDomElement& outputFormats replaces the parameter TQDomElement& e in OoImpressImport::appendField +// (otherwise it should be the same parameters.) +{ + const TQString localName (object.localName()); + //kdDebug(30518) << localName << endl; + int subtype = -1; + + if ( localName.endsWith( "date" ) || localName.endsWith( "time" ) ) + { + TQString dataStyleName = object.attributeNS( ooNS::style, "data-style-name", TQString() ); + TQString dateFormat = "locale"; + DataFormatsMap::const_iterator it = m_dateTimeFormats.find( dataStyleName ); + if ( it != m_dateTimeFormats.end() ) + dateFormat = (*it); + + if ( localName == "date" ) + { + subtype = 1; // current (or fixed) date + // Standard form of the date is in date-value. Example: 2004-01-21T10:57:05 + TQDateTime dt(TQDate::fromString(object.attributeNS( ooNS::text, "date-value", TQString()), Qt::ISODate)); + + bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", TQString())=="true"); + if (!dt.isValid()) + { + dt = TQDateTime::currentDateTime(); // OOo docs say so :) + fixed = false; + } + const TQDate date(dt.date()); + const TQTime time(dt.time()); + if ( fixed ) + subtype = 0; + + TQDomElement dateElement ( doc.createElement("DATE") ); + dateElement.setAttribute("fix", fixed ? 1 : 0); + dateElement.setAttribute("subtype", subtype); + dateElement.setAttribute("day", date.day()); + dateElement.setAttribute("month", date.month()); + dateElement.setAttribute("year", date.year()); + dateElement.setAttribute("hour", time.hour()); + dateElement.setAttribute("minute", time.minute()); + dateElement.setAttribute("second", time.second()); + if (object.hasAttributeNS( ooNS::text, "date-adjust")) + dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", TQString())); + appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement); + } + else if (localName == "time") + { + // Use TQDateTime to work around a possible problem of TQTime::FromString in TQt 3.2.2 + TQDateTime dt(TQDateTime::fromString(object.attributeNS( ooNS::text, "time-value", TQString()), Qt::ISODate)); + + bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", TQString())=="true"); + + if (!dt.isValid()) { + dt = TQDateTime::currentDateTime(); // OOo docs say so :) + fixed = false; + } + + const TQTime time(dt.time()); + TQDomElement timeElement (doc.createElement("TIME") ); + timeElement.setAttribute("fix", fixed ? 1 : 0); + timeElement.setAttribute("hour", time.hour()); + timeElement.setAttribute("minute", time.minute()); + timeElement.setAttribute("second", time.second()); + /*if (object.hasAttributeNS( ooNS::text, "time-adjust")) + timeElem.setAttribute("correct", object.attributeNS( ooNS::text, "time-adjust", TQString()));*/ // ### TODO + appendKWordVariable(doc, outputFormats, object, pos, "TIME" + dateFormat, 2, timeElement); + + } + else if ( localName == "print-time" + || localName == "print-date" + || localName == "creation-time" + || localName == "creation-date" + || localName == "modification-time" + || localName == "modification-date" ) + { + if ( localName.startsWith( "print" ) ) + subtype = 2; + else if ( localName.startsWith( "creation" ) ) + subtype = 3; + else if ( localName.startsWith( "modification" ) ) + subtype = 4; + // We do NOT include the date value here. It will be retrieved from + // meta.xml + TQDomElement dateElement ( doc.createElement("DATE") ); + dateElement.setAttribute("subtype", subtype); + if (object.hasAttributeNS( ooNS::text, "date-adjust")) + dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", TQString())); + appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement); + } + }// end of date/time variables + else if (localName == "page-number") + { + subtype = 0; // VST_PGNUM_CURRENT + + if (object.hasAttributeNS( ooNS::text, "select-page")) + { + const TQString select = object.attributeNS( ooNS::text, "select-page", TQString()); + + if (select == "previous") + subtype = 3; // VST_PGNUM_PREVIOUS + else if (select == "next") + subtype = 4; // VST_PGNUM_NEXT + } + + TQDomElement pgnumElement ( doc.createElement("PGNUM") ); + pgnumElement.setAttribute("subtype", subtype); + pgnumElement.setAttribute("value", object.text()); + appendKWordVariable(doc, outputFormats, object, pos, "NUMBER", 4, pgnumElement); + } + else if (localName == "chapter") + { + const TQString display = object.attributeNS( ooNS::text, "display", TQString() ); + // display can be name, number, number-and-name, plain-number-and-name, plain-number + TQDomElement pgnumElement ( doc.createElement("PGNUM") ); + pgnumElement.setAttribute("subtype", 2); // VST_CURRENT_SECTION + pgnumElement.setAttribute("value", object.text()); + appendKWordVariable(doc, outputFormats, object, pos, "STRING", 4, pgnumElement); + } + else if (localName == "file-name") + { + subtype = 5; + + if (object.hasAttributeNS( ooNS::text, "display")) + { + const TQString display = object.attributeNS( ooNS::text, "display", TQString()); + + if (display == "path") + subtype = 1; // VST_DIRECTORYNAME + else if (display == "name") + subtype = 6; // VST_FILENAMEWITHOUTEXTENSION + else if (display == "name-and-extension") + subtype = 0; // VST_FILENAME + else + subtype = 5; // VST_PATHFILENAME + } + + TQDomElement fieldElement ( doc.createElement("FIELD") ); + fieldElement.setAttribute("subtype", subtype); + fieldElement.setAttribute("value", object.text()); + appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElement); + } + else if (localName == "author-name" + || localName == "author-initials" + || localName == "subject" + || localName == "title" + || localName == "description" + ) + { + subtype = 2; // VST_AUTHORNAME + + if (localName == "author-initials") + subtype = 16; // VST_INITIAL + else if ( localName == "subject" ) // TODO in kword + subtype = 10; // title + else if ( localName == "title" ) + subtype = 10; + else if ( localName == "description" ) + subtype = 11; // Abstract + + TQDomElement authorElem = doc.createElement("FIELD"); + authorElem.setAttribute("subtype", subtype); + authorElem.setAttribute("value", object.text()); + appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, authorElem); + } + else if ( localName.startsWith( "sender-" ) ) + { + int subtype = -1; + const TQCString afterText( localName.latin1() + 5 ); + if ( afterText == "sender-company" ) + subtype = 4; //VST_COMPANYNAME; + else if ( afterText == "sender-firstname" ) + ; // ## This is different from author-name, but the notion of 'sender' is unclear... + else if ( afterText == "sender-lastname" ) + ; // ## This is different from author-name, but the notion of 'sender' is unclear... + else if ( afterText == "sender-initials" ) + ; // ## This is different from author-initials, but the notion of 'sender' is unclear... + else if ( afterText == "sender-street" ) + subtype = 14; // VST_STREET; + else if ( afterText == "sender-country" ) + subtype = 9; // VST_COUNTRY; + else if ( afterText == "sender-postal-code" ) + subtype = 12; //VST_POSTAL_CODE; + else if ( afterText == "sender-city" ) + subtype = 13; // VST_CITY; + else if ( afterText == "sender-title" ) + subtype = 15; // VST_AUTHORTITLE; // Small hack (it's supposed to be about the sender, not about the author) + else if ( afterText == "sender-position" ) + subtype = 15; // VST_AUTHORTITLE; // TODO separate variable + else if ( afterText == "sender-phone-private" ) + subtype = 7; // VST_TELEPHONE; + else if ( afterText == "sender-phone-work" ) + subtype = 7; // VST_TELEPHONE; // ### TODO separate type + else if ( afterText == "sender-fax" ) + subtype = 8; // VST_FAX; + else if ( afterText == "sender-email" ) + subtype = 3; // VST_EMAIL; + if ( subtype != -1 ) + { + TQDomElement fieldElem = doc.createElement("FIELD"); + fieldElem.setAttribute("subtype", subtype); + fieldElem.setAttribute("value", object.text()); + appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElem); + } + } + else if ( localName == "variable-set" + || localName == "user-defined" ) + { + // We treat both the same. For OO the difference is that + // - variable-set is related to variable-decls (defined in <body>); + // its value can change in the middle of the document. + // - user-defined is related to meta::user-defined in meta.xml + TQDomElement customElem = doc.createElement( "CUSTOM" ); + customElem.setAttribute( "name", object.attributeNS( ooNS::text, "name", TQString() ) ); + customElem.setAttribute( "value", object.text() ); + appendKWordVariable(doc, outputFormats, object, pos, "STRING", 6, customElem); + } + else + { + kdWarning(30518) << "Unsupported field " << localName << endl; + } +// TODO localName == "page-variable-get", "initial-creator" and many more +} + +void OoWriterImport::appendKWordVariable(TQDomDocument& doc, TQDomElement& formats, const TQDomElement& object, uint pos, + const TQString& key, int type, TQDomElement& child) +{ + TQDomElement variableElement ( doc.createElement("VARIABLE") ); + + TQDomElement typeElement ( doc.createElement("TYPE") ); + typeElement.setAttribute("key",key); + typeElement.setAttribute("type",type); + typeElement.setAttribute("text",object.text()); + variableElement.appendChild(typeElement); //Append to <VARIABLE> + + variableElement.appendChild(child); //Append to <VARIABLE> + + TQDomElement formatElement ( doc.createElement("FORMAT") ); + formatElement.setAttribute("id",4); // Variable + formatElement.setAttribute("pos",pos); // Start position + formatElement.setAttribute("len",1); + + formatElement.appendChild(variableElement); + + formats.appendChild(formatElement); +} + +void OoWriterImport::parseTable( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement ) +{ + TQString tableName ( parent.attributeNS( ooNS::table, "name", TQString()) ); // TODO: what if empty (non-unique?) + kdDebug(30518) << "Found table " << tableName << endl; + + // In OOWriter a table is never inside a paragraph, in KWord it is always in a paragraph + TQDomElement paragraphElementOut (doc.createElement("PARAGRAPH")); + currentFramesetElement.appendChild(paragraphElementOut); + + TQDomElement textElementOut(doc.createElement("TEXT")); + textElementOut.appendChild(doc.createTextNode("#")); + paragraphElementOut.appendChild(textElementOut); + + TQDomElement formatsPluralElementOut(doc.createElement("FORMATS")); + paragraphElementOut.appendChild(formatsPluralElementOut); + + TQDomElement elementFormat(doc.createElement("FORMAT")); + elementFormat.setAttribute("id",6); + elementFormat.setAttribute("pos",0); + elementFormat.setAttribute("len",1); + formatsPluralElementOut.appendChild(elementFormat); + + // ### FIXME: we have no <LAYOUT> element! + + TQDomElement elementAnchor(doc.createElement("ANCHOR")); + elementAnchor.setAttribute("type","frameset"); + elementAnchor.setAttribute("instance",tableName); + elementFormat.appendChild(elementAnchor); + + + // Left position of the cell/column (similar to RTF's \cellx). The last one defined is the right position of the last cell/column + TQMemArray<double> columnLefts(4); + uint maxColumns=columnLefts.size() - 1; + + uint col=0; + columnLefts[0]=0.0; // Initialize left of first cell + TQDomElement elem; + forEachElement( elem, parent ) + { + if ( elem.localName() == "table-column" && elem.namespaceURI() == ooNS::table ) + { + uint repeat = elem.attributeNS( ooNS::table, "number-columns-repeated", "1").toUInt(); // Default 1 time + if (!repeat) + repeat=1; // At least one column defined! + const TQString styleName ( elem.attributeNS( ooNS::table, "style-name", TQString()) ); + kdDebug(30518) << "Column " << col << " style " << styleName << endl; + const TQDomElement* style=m_styles.find(styleName); + double width=0.0; + if (style) + { + const TQDomElement elemProps( KoDom::namedItemNS( *style, ooNS::style, "properties") ); + if (elemProps.isNull()) + { + kdWarning(30518) << "Could not find table column style properties!" << endl; + } + const TQString strWidth ( elemProps.attributeNS( ooNS::style, "column-width", TQString()) ); + kdDebug(30518) << "- raw style width " << strWidth << endl; + width = KoUnit::parseValue( strWidth ); + } + else + kdWarning(30518) << "Could not find table column style!" << endl; + + if (width < 1.0) // Something is wrong with the width + { + kdWarning(30518) << "Table column width ridiculous, assuming 1 inch!" << endl; + width=72.0; + } + else + kdDebug(30518) << "- style width " << width << endl; + + for (uint j=0; j<repeat; j++) + { + ++col; + if (col>=maxColumns) + { + // We need more columns + maxColumns+=4; + columnLefts.resize(maxColumns+1, TQGArray::SpeedOptim); + } + columnLefts.at(col) = width + columnLefts.at(col-1); + kdDebug(30518) << "Cell column " << col-1 << " left " << columnLefts.at(col-1) << " right " << columnLefts.at(col) << endl; + } + } + } + + uint row=0; + uint column=0; + parseInsideOfTable(doc, parent, currentFramesetElement, tableName, columnLefts, row, column); +} + +void OoWriterImport::parseInsideOfTable( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement, + const TQString& tableName, const TQMemArray<double> & columnLefts, uint& row, uint& column ) +{ + kdDebug(30518) << "parseInsideOfTable: columnLefts.size()=" << columnLefts.size() << endl; + TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement()); + if (framesetsPluralElement.isNull()) + { + kdError(30518) << "Cannot find KWord's <FRAMESETS>! Cannot process table!" << endl; + return; + } + + TQDomElement e; + forEachElement( e, parent ) + { + m_styleStack.save(); + const TQString localName = e.localName(); + const TQString ns = e.namespaceURI(); + if ( ns != ooNS::table ) { + kdWarning(30518) << "Skipping element " << e.tagName() << " (in OoWriterImport::parseInsideOfTable)" << endl; + continue; + } + + if ( localName == "table-cell" ) // OOo SPEC 4.8.1 p267 + { + const TQString frameName(i18n("Frameset name","Table %3, row %1, column %2") + .arg(row).arg(column).arg(tableName)); // The table name could have a % sequence, so use the table name as last! + kdDebug(30518) << "Trying to create " << frameName << endl; + + // We need to create a frameset for the cell + TQDomElement framesetElement(doc.createElement("FRAMESET")); + framesetElement.setAttribute("frameType",1); + framesetElement.setAttribute("frameInfo",0); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",frameName); + framesetElement.setAttribute("row",row); + framesetElement.setAttribute("col",column); + int rowSpan = e.attributeNS( ooNS::table, "number-rows-spanned", TQString() ).toInt(); + framesetElement.setAttribute("rows",rowSpan == 0 ? 1 : rowSpan); + int colSpan = e.attributeNS( ooNS::table, "number-columns-spanned", TQString() ).toInt(); + framesetElement.setAttribute("cols",colSpan == 0 ? 1 : colSpan); + framesetElement.setAttribute("grpMgr",tableName); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut(doc.createElement("FRAME")); + frameElementOut.setAttribute("left",columnLefts.at(column)); + frameElementOut.setAttribute("right",columnLefts.at(column+1)); + frameElementOut.setAttribute("top", 0); + frameElementOut.setAttribute("bottom", 0); + frameElementOut.setAttribute("runaround",1); + frameElementOut.setAttribute("autoCreateNewFrame",0); // Very important for cell growing! + // ### TODO: a few attributes are missing + + m_styleStack.save(); + fillStyleStack( e, ooNS::table, "style-name" ); // get the style for the graphics element + importCommonFrameProperties(frameElementOut); + m_styleStack.restore(); + + framesetElement.appendChild(frameElementOut); + + parseBodyOrSimilar( doc, e, framesetElement ); // We change the frameset! + column++; + } + else if ( localName == "covered-table-cell" ) + { + column++; + } + else if ( localName == "table-row" ) + { + column=0; + parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column); + row++; + } + else if ( localName == "table-header-rows" ) // Provisory (###TODO) + { + parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column); + } + else if ( localName == "table-column" ) + { + // Allready treated in OoWriterImport::parseTable, we do not need to do anything here! + } + // TODO sub-table + else + { + kdWarning(30518) << "Skipping element " << localName << " (in OoWriterImport::parseInsideOfTable)" << endl; + } + + m_styleStack.restore(); + } +} + +void OoWriterImport::appendBookmark( TQDomDocument& doc, int paragId, int pos, const TQString& name ) +{ + appendBookmark( doc, paragId, pos, paragId, pos, name ); +} + +void OoWriterImport::appendBookmark( TQDomDocument& doc, int paragId, int pos, int endParagId, int endPos, const TQString& name ) +{ + Q_ASSERT( !m_currentFrameset.isNull() ); + const TQString frameSetName = m_currentFrameset.attribute( "name" ); + Q_ASSERT( !frameSetName.isEmpty() ); + TQDomElement bookmarks = doc.documentElement().namedItem( "BOOKMARKS" ).toElement(); + if ( bookmarks.isNull() ) { + bookmarks = doc.createElement( "BOOKMARKS" ); + doc.documentElement().appendChild( bookmarks ); + } + TQDomElement bkItem = doc.createElement( "BOOKMARKITEM" ); + bkItem.setAttribute( "name", name ); + bkItem.setAttribute( "frameset", frameSetName ); + bkItem.setAttribute( "startparag", paragId ); + bkItem.setAttribute( "cursorIndexStart", pos ); + bkItem.setAttribute( "endparag", endParagId ); + bkItem.setAttribute( "cursorIndexEnd", endPos ); + bookmarks.appendChild( bkItem ); +} + +// OOo SPEC: 3.6.1 p146 +void OoWriterImport::importFootnotesConfiguration( TQDomDocument& doc, const TQDomElement& elem, bool endnote ) +{ + TQDomElement docElement( doc.documentElement() ); + // can we really be called more than once? + TQString elemName = endnote ? "ENDNOTESETTING" : "FOOTNOTESETTING"; + Q_ASSERT( docElement.namedItem( elemName ).isNull() ); + TQDomElement settings = doc.createElement( elemName ); + docElement.appendChild( settings ); + + // BUG in OO (both 1.0.1 and 1.1). It saves it with an off-by-one (reported to xml@). + // So instead of working around it (which would break with the next version, possibly) + // let's ignore this for now. +#if 0 + if ( elem.hasAttributeNS( ooNS::text, "start-value" ) ) { + int startValue = elem.attributeNS( ooNS::text, "start-value", TQString() ).toInt(); + settings.setAttribute( "start", startValue ); + } +#endif + settings.setAttribute( "type", Conversion::importCounterType( elem.attributeNS( ooNS::style, "num-format", TQString() ) ) ); + settings.setAttribute( "lefttext", elem.attributeNS( ooNS::style, "num-prefix", TQString() ) ); + settings.setAttribute( "righttext", elem.attributeNS( ooNS::style, "num-suffix", TQString() ) ); +} + +void OoWriterImport::appendTOC( TQDomDocument& doc, const TQDomElement& toc ) +{ + // table-of-content OOo SPEC 7.5 p452 + //fillStyleStack( toc, ooNS::text, "style-name" ); that's the section style + + //TQDomElement tocSource = KoDom::namedItemNS( toc, ooNS::text, "table-of-content-source" ); + // TODO parse templates and generate "Contents ..." styles from it + //for ( TQDomNode n(tocSource.firstChild()); !text.isNull(); text = text.nextSibling() ) + //{ + //} + + TQDomElement tocIndexBody = KoDom::namedItemNS( toc, ooNS::text, "index-body" ); + TQDomElement t; + forEachElement( t, tocIndexBody ) + { + m_styleStack.save(); + const TQString localName = t.localName(); + TQDomElement e; + bool isTextNS = e.namespaceURI() == ooNS::text; + if ( isTextNS && localName == "index-title" ) { + parseBodyOrSimilar( doc, t, m_currentFrameset ); // recurse again + } else if ( isTextNS && localName == "p" ) { + fillStyleStack( t, ooNS::text, "style-name" ); + e = parseParagraph( doc, t ); + } + if ( !e.isNull() ) + m_currentFrameset.appendChild( e ); + m_styleStack.restore(); + } + + // KWord has a special attribute to know if a TOC is present + m_hasTOC = true; +} + +// TODO style:num-format, default number format for page styles, +// used for page numbers (2.3.1) + +void OoWriterImport::finishDocumentContent( TQDomDocument& mainDocument ) +{ + TQDomElement attributes = mainDocument.createElement( "ATTRIBUTES" ); + TQDomElement docElement = mainDocument.documentElement(); + docElement.appendChild( attributes ); + attributes.setAttribute( "hasTOC", m_hasTOC ? 1 : 0 ); + attributes.setAttribute( "hasHeader", m_hasHeader ); + attributes.setAttribute( "hasFooter", m_hasFooter ); + // TODO unit?, tabStopValue + // TODO activeFrameset, cursorParagraph, cursorIndex + + // Done at the end: write the type of headers/footers, + // depending on which kind of headers and footers we received. + TQDomElement paperElement = docElement.namedItem("PAPER").toElement(); + Q_ASSERT ( !paperElement.isNull() ); // writePageLayout should have been called! + if ( !paperElement.isNull() ) + { + //kdDebug(30513) << k_funcinfo << "m_headerFooters=" << m_headerFooters << endl; + //paperElement.setAttribute("hType", Conversion::headerMaskToHType( m_headerFooters ) ); + //paperElement.setAttribute("fType", Conversion::headerMaskToFType( m_headerFooters ) ); + } +} + +TQString OoWriterImport::kWordStyleName( const TQString& ooStyleName ) +{ + if ( ooStyleName.startsWith( "Contents " ) ) { + TQString s( ooStyleName ); + return s.replace( 0, 9, TQString("Contents Head ") ); // Awful hack for KWord's broken "update TOC" feature + } else { + return ooStyleName; + } +} + +// OOo SPEC: 2.3.3 p59 +void OoWriterImport::importHeaderFooter( TQDomDocument& doc, const TQDomElement& headerFooter, bool hasEvenOdd, TQDomElement& style ) +{ + const TQString localName = headerFooter.localName(); + TQDomElement framesetElement = doc.createElement("FRAMESET"); + TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement()); + framesetElement.setAttribute( "frameType", 1 /* text */); + framesetElement.setAttribute( "frameInfo", Conversion::headerTypeToFrameInfo( localName, hasEvenOdd ) ); + framesetElement.setAttribute( "name", Conversion::headerTypeToFramesetName( localName, hasEvenOdd ) ); + framesetsPluralElement.appendChild(framesetElement); + + bool isHeader = localName.startsWith( "header" ); + if ( isHeader ) + m_hasHeader = true; + else + m_hasFooter = true; + TQDomElement frameElementOut = createInitialFrame( framesetElement, 29, 798, isHeader?0:567, isHeader?41:567+41, true, Copy ); + if ( !style.isNull() ) + m_styleStack.push( style ); + importCommonFrameProperties( frameElementOut ); + if ( !style.isNull() ) + m_styleStack.pop(); // don't let it be active when parsing the text + + parseBodyOrSimilar( doc, headerFooter, framesetElement ); +} + +#include "oowriterimport.moc" |