filters

oowriterimport.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.com>
00003    Copyright (C) 2003 David Faure <faure@kde.org>
00004    Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  * Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include <qcolor.h>
00023 #include <qfile.h>
00024 #include <qfont.h>
00025 #include <qpen.h>
00026 #include <qregexp.h>
00027 #include <qimage.h>
00028 
00029 #include "oowriterimport.h"
00030 #include <ooutils.h>
00031 
00032 #include <kdeversion.h>
00033 #include <kdebug.h>
00034 #include <kzip.h>
00035 
00036 #include <KoDocumentInfo.h>
00037 #include <KoDocument.h>
00038 
00039 #include <kgenericfactory.h>
00040 #include <kmessagebox.h>
00041 #include <KoFilterChain.h>
00042 #include <KoUnit.h>
00043 #include <KoPageLayout.h>
00044 #include <KoPicture.h>
00045 #include "conversion.h"
00046 #include <KoRect.h>
00047 #include <KoDom.h>
00048 
00049 #if ! KDE_IS_VERSION(3,1,90)
00050 # include <kdebugclasses.h>
00051 #endif
00052 
00053 typedef KGenericFactory<OoWriterImport, KoFilter> OoWriterImportFactory;
00054 K_EXPORT_COMPONENT_FACTORY( liboowriterimport, OoWriterImportFactory(  "kofficefilters" ) )
00055 
00056 OoWriterImport::OoWriterImport( KoFilter *, const char *, const QStringList & )
00057   : KoFilter(),
00058     m_styleStack( ooNS::style, ooNS::fo ),
00059     m_insideOrderedList( false ), m_nextItemIsListItem( false ),
00060     m_hasTOC( false ), m_hasHeader( false ), m_hasFooter( false ), m_restartNumbering( -1 ),
00061     m_pictureNumber(0), m_zip(NULL)
00062 {
00063     m_styles.setAutoDelete( true );
00064     m_masterPages.setAutoDelete( true );
00065     m_listStyles.setAutoDelete( true );
00066 }
00067 
00068 OoWriterImport::~OoWriterImport()
00069 {
00070 }
00071 
00072 KoFilter::ConversionStatus OoWriterImport::convert( QCString const & from, QCString const & to )
00073 {
00074     kdDebug(30518) << "Entering Oowriter Import filter: " << from << " - " << to << endl;
00075 
00076     if ( ( from != "application/vnd.sun.xml.writer"
00077          && from != "application/vnd.sun.xml.writer.template"
00078          && from != "application/vnd.sun.xml.writer.master" )
00079          || to != "application/x-kword" )
00080     {
00081         kdWarning(30518) << "Invalid mimetypes " << from << " " << to << endl;
00082         return KoFilter::NotImplemented;
00083     }
00084 
00085     m_zip=new KZip(m_chain->inputFile());
00086 
00087     kdDebug(30518) << "Store created" << endl;
00088 
00089     if ( !m_zip->open(IO_ReadOnly) )
00090     {
00091         kdError(30518) << "Couldn't open the requested file "<< m_chain->inputFile() << endl;
00092         return KoFilter::FileNotFound;
00093     }
00094 
00095     if ( !m_zip->directory() )
00096     {
00097         kdError(30518) << "Couldn't read ZIP directory of the requested file "<< m_chain->inputFile() << endl;
00098         return KoFilter::FileNotFound;
00099     }
00100 
00101 
00102     KoFilter::ConversionStatus preStatus = openFile();
00103 
00104     QImage thumbnail;
00105     if ( preStatus == KoFilter::OK )
00106     {
00107         // We do not care about the failure
00108         OoUtils::loadThumbnail( thumbnail, m_zip );
00109     }
00110 
00111     if ( preStatus != KoFilter::OK )
00112     {
00113         m_zip->close();
00114         delete m_zip;
00115         return preStatus;
00116     }
00117 
00118     m_currentMasterPage = QString::null;
00119     QDomDocument mainDocument;
00120     QDomElement framesetsElem;
00121     prepareDocument( mainDocument, framesetsElem );
00122 
00123     // Load styles from style.xml
00124     if ( !createStyleMap( m_stylesDoc, mainDocument ) )
00125         return KoFilter::UserCancelled;
00126     // Also load styles from content.xml
00127     if ( !createStyleMap( m_content, mainDocument ) )
00128         return KoFilter::UserCancelled;
00129 
00130     // Create main frameset
00131     QDomElement mainFramesetElement = mainDocument.createElement("FRAMESET");
00132     mainFramesetElement.setAttribute("frameType",1);
00133     mainFramesetElement.setAttribute("frameInfo",0);
00134     mainFramesetElement.setAttribute("visible",1);
00135     mainFramesetElement.setAttribute("name", i18n( "Main Text Frameset" ) );
00136     framesetsElem.appendChild(mainFramesetElement);
00137 
00138     createInitialFrame( mainFramesetElement, 29, 798, 42, 566, false, Reconnect );
00139     createStyles( mainDocument );
00140     createDocumentContent( mainDocument, mainFramesetElement );
00141     finishDocumentContent( mainDocument );
00142 
00143     m_zip->close();
00144     delete m_zip; // It has to be so late, as pictures might be read.
00145 
00146     KoStoreDevice* out = m_chain->storageFile( "maindoc.xml", KoStore::Write );
00147     if ( !out ) {
00148         kdError(30518) << "Unable to open output file!" << endl;
00149         return KoFilter::StorageCreationError;
00150     }
00151     else
00152     {
00153         QCString cstr = mainDocument.toCString();
00154         kdDebug(30518)<<" maindoc: " << cstr << endl;
00155         // WARNING: we cannot use KoStore::write(const QByteArray&) because it gives an extra NULL character at the end.
00156         out->writeBlock( cstr, cstr.length() );
00157     }
00158 
00159     QDomDocument docinfo;
00160     createDocumentInfo( docinfo );
00161 
00162     // store document info
00163     out = m_chain->storageFile( "documentinfo.xml", KoStore::Write );
00164     if( out )
00165     {
00166         QCString info = docinfo.toCString();
00167         kdDebug(30518)<<" info :"<<info<<endl;
00168         // WARNING: we cannot use KoStore::write(const QByteArray&) because it gives an extra NULL character at the end.
00169         out->writeBlock( info , info.length() );
00170     }
00171 
00172     // store preview
00173 
00174     if ( ! thumbnail.isNull() )
00175     {
00176         // ### TODO: thumbnail.setAlphaBuffer( false ); // legacy KOffice previews have no alpha channel
00177         // Legacy KOffice previews are 256x256x8 instead of 128x128x32
00178         QImage preview( thumbnail.smoothScale( 256, 256 ).convertDepth(8, Qt::AvoidDither | Qt::DiffuseDither) );
00179         // Not to be able to generate a preview is not an error
00180         if ( !preview.isNull() )
00181         {
00182             out = m_chain->storageFile( "preview.png", KoStore::Write );
00183             if( out )
00184             {
00185                 preview.save( out, "PNG" );
00186             }
00187         }
00188     }
00189 
00190     kdDebug(30518) << "######################## OoWriterImport::convert done ####################" << endl;
00191     return KoFilter::OK;
00192 }
00193 
00194 void OoWriterImport::createStyles( QDomDocument& doc )
00195 {
00196     QDomElement stylesElem = doc.createElement( "STYLES" );
00197     doc.documentElement().appendChild( stylesElem );
00198 
00199     QDomNode fixedStyles = KoDom::namedItemNS( m_stylesDoc.documentElement(), ooNS::office, "styles" );
00200     Q_ASSERT( !fixedStyles.isNull() );
00201     QDomElement e;
00202     forEachElement( e, fixedStyles )
00203     {
00204         if ( !e.hasAttributeNS( ooNS::style, "name" ) )
00205             continue;
00206         // We only generate paragraph styles for now
00207         if ( e.attributeNS( ooNS::style, "family", QString::null ) != "paragraph" )
00208              continue;
00209 
00210         // We use the style stack, to flatten out parent styles
00211         // Once KWord supports style inheritance, replace this with a single m_styleStack.push.
00212         // (We still need to use StyleStack, since that's what writeLayout/writeFormat read from)
00213         addStyles( &e );
00214 
00215         QDomElement styleElem = doc.createElement("STYLE");
00216         stylesElem.appendChild( styleElem );
00217 
00218         QString styleName = kWordStyleName( e.attributeNS( ooNS::style, "name", QString::null ) );
00219         QDomElement element = doc.createElement("NAME");
00220         element.setAttribute( "value", styleName );
00221         styleElem.appendChild( element );
00222         //kdDebug(30518) << k_funcinfo << "generating style " << styleName << endl;
00223 
00224         QString followingStyle = m_styleStack.attributeNS( ooNS::style, "next-style-name" );
00225         if ( !followingStyle.isEmpty() )
00226         {
00227             QDomElement element = doc.createElement( "FOLLOWING" );
00228             element.setAttribute( "name", kWordStyleName( followingStyle ) );
00229             styleElem.appendChild( element );
00230         }
00231 
00232         // ### In KWord the style says "I'm part of the outline" (TOC)
00233         // ### In OOo the paragraph says that (text:h)
00234         // Hence this hack...
00235         // OASIS solution for this: style:default-outline-level attribute
00236         bool outline = styleName.startsWith( "Heading" );
00237         if ( outline )
00238             styleElem.setAttribute( "outline", "true" );
00239 
00240         writeFormat( doc, styleElem, 1, 0, 0 );
00241         writeLayout( doc, styleElem );
00242 
00243         // writeLayout doesn't load the counter. It's modelled differently for parags and for styles.
00244         // ### missing info in the format! (fixed in OASIS)
00245         const int level = styleName.right(1).toInt(); // ## HACK
00246         bool listOK = false;
00247         if ( level > 0 ) {
00248             if ( outline )
00249                 listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level );
00250             else {
00251                 const QString listStyleName = e.attributeNS( ooNS::style, "list-style-name", QString::null );
00252                 listOK = !listStyleName.isEmpty();
00253                 if ( listOK )
00254                     listOK = pushListLevelStyle( listStyleName, level );
00255             }
00256         }
00257         if ( listOK ) {
00258             const QDomElement listStyle = m_listStyleStack.currentListStyle();
00259             // The tag is either text:list-level-style-number or text:list-level-style-bullet
00260             bool ordered = listStyle.localName() == "list-level-style-number";
00261             writeCounter( doc, styleElem, outline, level, ordered );
00262             m_listStyleStack.pop();
00263         }
00264 
00265         m_styleStack.clear();
00266     }
00267 }
00268 
00269 void OoWriterImport::parseBodyOrSimilar( QDomDocument &doc, const QDomElement& parent, QDomElement& currentFramesetElement )
00270 {
00271     QDomElement oldCurrentFrameset = m_currentFrameset;
00272     m_currentFrameset = currentFramesetElement;
00273     Q_ASSERT( !m_currentFrameset.isNull() );
00274     QDomElement t;
00275     forEachElement( t, parent )
00276     {
00277         m_styleStack.save();
00278         const QString localName = t.localName();
00279         const QString ns = t.namespaceURI();
00280         const bool isTextNS = ns == ooNS::text;
00281 
00282         QDomElement e;
00283         if ( isTextNS && localName == "p" ) {  // text paragraph
00284             fillStyleStack( t, ooNS::text, "style-name" );
00285             e = parseParagraph( doc, t );
00286         }
00287         else if ( isTextNS && localName == "h" ) // heading
00288         {
00289             fillStyleStack( t, ooNS::text, "style-name" );
00290             int level = t.attributeNS( ooNS::text, "level", QString::null ).toInt();
00291             bool listOK = false;
00292             // When a heading is inside a list, it seems that the list prevails.
00293             // Example:
00294             //    <text:ordered-list text:style-name="Numbering 1">
00295             //      <text:list-item text:start-value="5">
00296             //        <text:h text:style-name="P2" text:level="4">The header</text:h>
00297             // where P2 has list-style-name="something else"
00298             // Result: the numbering of the header follows "Numbering 1".
00299             // So we use the style for the outline level only if we're not inside a list:
00300             if ( !m_nextItemIsListItem )
00301                 listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level );
00302             m_nextItemIsListItem = true;
00303             if ( t.hasAttributeNS( ooNS::text, "start-value" ) )
00304                  // OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html
00305                  m_restartNumbering = t.attributeNS( ooNS::text, "start-value", QString::null ).toInt();
00306             e = parseParagraph( doc, t );
00307             if ( listOK )
00308                 m_listStyleStack.pop();
00309         }
00310         else if ( isTextNS &&
00311                   ( localName == "unordered-list" || localName == "ordered-list" ) )
00312         {
00313             parseList( doc, t, currentFramesetElement );
00314             m_styleStack.restore();
00315             continue;
00316         }
00317         else if ( isTextNS && localName == "section" ) // Temporary support (###TODO)
00318         {
00319             kdDebug(30518) << "Section found!" << endl;
00320             fillStyleStack( t, ooNS::text, "style-name" );
00321             parseBodyOrSimilar( doc, t, currentFramesetElement);
00322         }
00323         else if ( localName == "table" && ns == ooNS::table )
00324         {
00325             kdDebug(30518) << "Table found!" << endl;
00326             parseTable( doc, t, currentFramesetElement );
00327         }
00328         else if ( localName == "image" && ns == ooNS::draw )
00329         {
00330             appendPicture( doc, t );
00331         }
00332         else if ( localName == "text-box" && ns == ooNS::draw )
00333         {
00334             appendTextBox( doc, t );
00335         }
00336         else if ( isTextNS && localName == "variable-decls" )
00337         {
00338             // We don't parse variable-decls since we ignore var types right now
00339             // (and just storing a list of available var names wouldn't be much use)
00340         }
00341         else if ( localName == "table-of-content" && ns == ooNS::text )
00342         {
00343             appendTOC( doc, t );
00344         }
00345         // TODO text:sequence-decls
00346         else
00347         {
00348             kdWarning(30518) << "Unsupported body element '" << localName << "'" << endl;
00349         }
00350 
00351         if ( !e.isNull() )
00352             currentFramesetElement.appendChild( e );
00353         m_styleStack.restore(); // remove the styles added by the paragraph or list
00354     }
00355     m_currentFrameset = oldCurrentFrameset; // in case of recursive invokations
00356 }
00357 
00358 void OoWriterImport::createDocumentContent( QDomDocument &doc, QDomElement& mainFramesetElement )
00359 {
00360     QDomElement content = m_content.documentElement();
00361 
00362     QDomElement body ( KoDom::namedItemNS( content, ooNS::office, "body" ) );
00363     if ( body.isNull() )
00364     {
00365         kdError(30518) << "No office:body found!" << endl;
00366         return;
00367     }
00368 
00369     parseBodyOrSimilar( doc, body, mainFramesetElement );
00370 }
00371 
00372 void OoWriterImport::writePageLayout( QDomDocument& mainDocument, const QString& masterPageName )
00373 {
00374     QDomElement docElement = mainDocument.documentElement();
00375 
00376     kdDebug(30518) << "writePageLayout " << masterPageName << endl;
00377     QDomElement elementPaper = mainDocument.createElement("PAPER");
00378     KoOrientation orientation;
00379     double width, height;
00380     KoFormat paperFormat;
00381     double marginLeft, marginTop, marginRight, marginBottom;
00382     bool hasEvenOddHeader = false;
00383     bool hasEvenOddFooter = false;
00384 
00385     QDomElement* masterPage = m_masterPages[ masterPageName ];
00386     Q_ASSERT( masterPage );
00387     kdDebug(30518) << "page-master-name: " << masterPage->attributeNS( ooNS::style, "page-master-name", QString::null ) << endl;
00388     QDomElement *style = masterPage ? m_styles[masterPage->attributeNS( ooNS::style, "page-master-name", QString::null )] : 0;
00389     Q_ASSERT( style );
00390     if ( style )
00391     {
00392         QDomElement properties( KoDom::namedItemNS( *style, ooNS::style, "properties" ) );
00393         Q_ASSERT( !properties.isNull() );
00394         orientation = ( (properties.attributeNS( ooNS::style, "print-orientation", QString::null) != "portrait") ? PG_LANDSCAPE : PG_PORTRAIT );
00395         width = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-width", QString::null));
00396         height = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-height", QString::null));
00397         kdDebug(30518) << "width=" << width << " height=" << height << endl;
00398         // guessFormat takes millimeters
00399         if ( orientation == PG_LANDSCAPE )
00400             paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(height), POINT_TO_MM(width) );
00401         else
00402             paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(width), POINT_TO_MM(height) );
00403 
00404         marginLeft = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-left", QString::null));
00405         marginTop = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-top", QString::null));
00406         marginRight = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-right", QString::null));
00407         marginBottom = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-bottom", QString::null));
00408 
00409         QDomElement footnoteSep = KoDom::namedItemNS( properties, ooNS::style, "footnote-sep" );
00410         if ( !footnoteSep.isNull() ) {
00411             // style:width="0.018cm" style:distance-before-sep="0.101cm"
00412             // style:distance-after-sep="0.101cm" style:adjustment="left"
00413             // style:rel-width="25%" style:color="#000000"
00414             QString width = footnoteSep.attributeNS( ooNS::style, "width", QString::null );
00415             elementPaper.setAttribute( "slFootNoteWidth", KoUnit::parseValue( width ) );
00416             QString pageWidth = footnoteSep.attributeNS( ooNS::style, "rel-width", QString::null );
00417             if ( pageWidth.endsWith( "%" ) ) {
00418                 pageWidth.truncate( pageWidth.length() - 1 ); // remove '%'
00419                 elementPaper.setAttribute( "slFootNoteLenth", pageWidth );
00420             }
00421             elementPaper.setAttribute( "slFootNotePosition", footnoteSep.attributeNS( ooNS::style, "adjustment", QString::null ) );
00422             // Not in KWord: color, distance before and after separator
00423             // Not in OOo: line type of separator (solid, dot, dash etc.)
00424         }
00425 
00426 
00427         // Header/Footer
00428         QDomElement headerStyle = KoDom::namedItemNS( *style, ooNS::style, "header-style" );
00429         QDomElement footerStyle = KoDom::namedItemNS( *style, ooNS::style, "footer-style" );
00430         QDomElement headerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header-left" );
00431         if ( !headerLeftElem.isNull() ) {
00432             kdDebug(30518) << "Found header-left" << endl;
00433             hasEvenOddHeader = true;
00434             importHeaderFooter( mainDocument, headerLeftElem, hasEvenOddHeader, headerStyle );
00435         }
00436         QDomElement headerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header" );
00437         if ( !headerElem.isNull() ) {
00438             kdDebug(30518) << "Found header" << endl;
00439             importHeaderFooter( mainDocument, headerElem, hasEvenOddHeader, headerStyle );
00440         }
00441         QDomElement footerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer-left" );
00442         if ( !footerLeftElem.isNull() ) {
00443             kdDebug(30518) << "Found footer-left" << endl;
00444             importHeaderFooter( mainDocument, footerLeftElem, hasEvenOddFooter, footerStyle );
00445         }
00446         QDomElement footerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer" );
00447         if ( !footerElem.isNull() ) {
00448             kdDebug(30518) << "Found footer" << endl;
00449             importHeaderFooter( mainDocument, footerElem, hasEvenOddFooter, footerStyle );
00450         }
00451     }
00452     else
00453     {
00454         // We have no master page! We need defaults.
00455         kdWarning(30518) << "NO MASTER PAGE" << endl;
00456         orientation=PG_PORTRAIT;
00457         paperFormat=PG_DIN_A4;
00458         width=MM_TO_POINT(KoPageFormat::width(paperFormat, orientation));
00459         height=MM_TO_POINT(KoPageFormat::height(paperFormat, orientation));
00460         // ### TODO: better defaults for margins?
00461         marginLeft=MM_TO_POINT(10.0);
00462         marginRight=MM_TO_POINT(10.0);
00463         marginTop=MM_TO_POINT(15.0);
00464         marginBottom=MM_TO_POINT(15.0);
00465     }
00466 
00467     elementPaper.setAttribute("orientation", int(orientation) );
00468     elementPaper.setAttribute("width", width);
00469     elementPaper.setAttribute("height", height);
00470     elementPaper.setAttribute("format", paperFormat);
00471     elementPaper.setAttribute("columns",1); // TODO
00472     elementPaper.setAttribute("columnspacing",2); // TODO
00473     elementPaper.setAttribute("hType", hasEvenOddHeader ? 3 : 0); // ### no support for first-page
00474     elementPaper.setAttribute("fType", hasEvenOddFooter ? 3 : 0); // ### no support for first-page
00475     elementPaper.setAttribute("spHeadBody",9); // where is this in OOo?
00476     elementPaper.setAttribute("spFootBody",9); // ?
00477     elementPaper.setAttribute("zoom",100);
00478     docElement.appendChild(elementPaper);
00479 
00480     // Page margins
00481     QDomElement element = mainDocument.createElement("PAPERBORDERS");
00482     element.setAttribute("left", marginLeft);
00483     element.setAttribute("top", marginTop);
00484     element.setAttribute("right", marginRight);
00485     element.setAttribute("bottom", marginBottom);
00486     elementPaper.appendChild(element);
00487 }
00488 
00489 void OoWriterImport::prepareDocument( QDomDocument& mainDocument, QDomElement& framesetsElem )
00490 {
00491     mainDocument = KoDocument::createDomDocument( "kword", "DOC", "1.2" );
00492     QDomElement docElement = mainDocument.documentElement();
00493     docElement.setAttribute( "editor", "KWord's OOWriter Import Filter" );
00494     docElement.setAttribute( "mime", "application/x-kword" );
00495     docElement.setAttribute( "syntaxVersion", "2" );
00496 
00497     framesetsElem=mainDocument.createElement("FRAMESETS");
00498     docElement.appendChild(framesetsElem);
00499 
00500     // Now create VARIABLESETTINGS, mostly from meta.xml
00501     QDomElement varSettings = mainDocument.createElement( "VARIABLESETTINGS" );
00502     docElement.appendChild( varSettings );
00503     QDomNode meta   = KoDom::namedItemNS( m_meta, ooNS::office, "document-meta" );
00504     QDomNode office = KoDom::namedItemNS( meta, ooNS::office, "meta" );
00505     if ( !office.isNull() ) {
00506         QDomElement date = KoDom::namedItemNS( office, ooNS::dc, "date" );
00507         if ( !date.isNull() && !date.text().isEmpty() ) {
00508             // Both use ISO-8601, no conversion needed.
00509             varSettings.setAttribute( "modificationDate", date.text() );
00510         }
00511         date = KoDom::namedItemNS( office, ooNS::meta, "creation-date" );
00512         if ( !date.isNull() && !date.text().isEmpty() ) {
00513             varSettings.setAttribute( "creationDate", date.text() );
00514         }
00515         date = KoDom::namedItemNS( office, ooNS::meta, "print-date" );
00516         if ( !date.isNull() && !date.text().isEmpty() ) {
00517             varSettings.setAttribute( "lastPrintingDate", date.text() );
00518         }
00519     }
00520 }
00521 
00522 // Copied from the msword importer
00523 QDomElement OoWriterImport::createInitialFrame( QDomElement& parentFramesetElem, double left, double right, double top, double bottom, bool autoExtend, NewFrameBehavior nfb )
00524 {
00525     QDomElement frameElementOut = parentFramesetElem.ownerDocument().createElement("FRAME");
00526     frameElementOut.setAttribute( "left", left );
00527     frameElementOut.setAttribute( "right", right );
00528     frameElementOut.setAttribute( "top", top );
00529     frameElementOut.setAttribute( "bottom", bottom );
00530     frameElementOut.setAttribute( "runaround", 1 );
00531     // AutoExtendFrame for header/footer/footnote/endnote, AutoCreateNewFrame for body text
00532     frameElementOut.setAttribute( "autoCreateNewFrame", autoExtend ? 0 : 1 );
00533     frameElementOut.setAttribute( "newFrameBehavior", nfb );
00534     parentFramesetElem.appendChild( frameElementOut );
00535     return frameElementOut;
00536 }
00537 
00538 KoFilter::ConversionStatus OoWriterImport::loadAndParse(const QString& filename, QDomDocument& doc)
00539 {
00540     return OoUtils::loadAndParse( filename, doc, m_zip);
00541 }
00542 
00543 KoFilter::ConversionStatus OoWriterImport::openFile()
00544 {
00545     KoFilter::ConversionStatus status=loadAndParse("content.xml", m_content);
00546     if ( status != KoFilter::OK )
00547     {
00548         kdError(30518) << "Content.xml could not be parsed correctly! Aborting!" << endl;
00549         return status;
00550     }
00551 
00552     //kdDebug(30518)<<" m_content.toCString() :"<<m_content.toCString()<<endl;
00553 
00554     // We need to keep the QDomDocument for styles too, unfortunately.
00555     // Otherwise styleElement.parentNode() returns a null node
00556     // (see StyleStack::isUserStyle). Most of styles.xml is in m_styles
00557     // anyway, so this doesn't make a big difference.
00558     // We now also rely on this in createStyles.
00559     //QDomDocument styles;
00560 
00561     // We do not stop if the following calls fail.
00562     loadAndParse("styles.xml", m_stylesDoc);
00563     loadAndParse("meta.xml", m_meta);
00564     // not used yet: loadAndParse("settings.xml", m_settings);
00565 
00566     emit sigProgress( 10 );
00567 
00568     return KoFilter::OK;
00569 }
00570 
00571 // Very related to OoImpressImport::createDocumentInfo
00572 void OoWriterImport::createDocumentInfo( QDomDocument &docinfo )
00573 {
00574     docinfo = KoDocument::createDomDocument( "document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1" );
00575 
00576     OoUtils::createDocumentInfo(m_meta, docinfo);
00577 
00578     //kdDebug(30518)<<" meta-info :"<<m_meta.toCString()<<endl;
00579 }
00580 
00581 // This mainly fills member variables with the styles.
00582 // The 'doc' argument is only for footnotes-configuration.
00583 bool OoWriterImport::createStyleMap( const QDomDocument & styles, QDomDocument& doc )
00584 {
00585   QDomElement docElement  = styles.documentElement();
00586   QDomNode docStyles   = KoDom::namedItemNS( docElement, ooNS::office, "document-styles" );
00587 
00588   if ( docElement.hasAttributeNS( ooNS::office, "version" ) )
00589   {
00590     bool ok = true;
00591     double d = docElement.attributeNS( ooNS::office, "version", QString::null ).toDouble( &ok );
00592 
00593     if ( ok )
00594     {
00595       kdDebug(30518) << "OpenWriter version: " << d << endl;
00596       if ( d > 1.0 )
00597       {
00598         QString 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?") );
00599         message = message.arg( docElement.attributeNS( ooNS::office, "version", QString::null ) );
00600         if ( KMessageBox::warningYesNo( 0, message, i18n( "Unsupported document version" ) ) == KMessageBox::No )
00601           return false;
00602       }
00603     }
00604   }
00605 
00606   QDomNode fontStyles = KoDom::namedItemNS( docElement, ooNS::office, "font-decls" );
00607 
00608   if ( !fontStyles.isNull() )
00609   {
00610     kdDebug(30518) << "Starting reading in font-decl..." << endl;
00611 
00612     insertStyles( fontStyles.toElement(), doc );
00613   }
00614   else
00615     kdDebug(30518) << "No items found" << endl;
00616 
00617   kdDebug(30518) << "Starting reading in office:automatic-styles" << endl;
00618 
00619   QDomNode autoStyles = KoDom::namedItemNS( docElement, ooNS::office, "automatic-styles" );
00620   if ( !autoStyles.isNull() )
00621   {
00622       insertStyles( autoStyles.toElement(), doc );
00623   }
00624   else
00625     kdDebug(30518) << "No items found" << endl;
00626 
00627 
00628   kdDebug(30518) << "Reading in master styles" << endl;
00629 
00630   QDomNode masterStyles = KoDom::namedItemNS( docElement, ooNS::office, "master-styles" );
00631 
00632   if ( !masterStyles.isNull() )
00633   {
00634 
00635       QDomElement master;
00636       forEachElement( master, masterStyles )
00637       {
00638           if ( master.localName() ==  "master-page" && master.namespaceURI() == ooNS::style )
00639           {
00640               QString name = master.attributeNS( ooNS::style, "name", QString::null );
00641               kdDebug(30518) << "Master style: '" << name << "' loaded " << endl;
00642               m_masterPages.insert( name, new QDomElement( master ) );
00643           } else
00644               kdWarning(30518) << "Unknown tag " << master.tagName() << " in office:master-styles" << endl;
00645       }
00646   }
00647 
00648 
00649   kdDebug(30518) << "Starting reading in office:styles" << endl;
00650 
00651   QDomNode fixedStyles = KoDom::namedItemNS( docElement, ooNS::office, "styles" );
00652 
00653   if ( !fixedStyles.isNull() )
00654     insertStyles( fixedStyles.toElement(), doc );
00655 
00656   kdDebug(30518) << "Styles read in." << endl;
00657 
00658   return true;
00659 }
00660 
00661 // started as a copy of OoImpressImport::insertStyles
00662 void OoWriterImport::insertStyles( const QDomElement& styles, QDomDocument& doc )
00663 {
00664     //kdDebug(30518) << "Inserting styles from " << styles.tagName() << endl;
00665     QDomElement e;
00666     forEachElement( e, styles )
00667     {
00668         const QString localName = e.localName();
00669         const QString ns = e.namespaceURI();
00670 
00671         const QString name = e.attributeNS( ooNS::style, "name", QString::null );
00672         if ( ns == ooNS::style && (
00673                 localName == "style"
00674              || localName == "page-master"
00675              || localName == "font-decl" ) )
00676         {
00677             QDomElement* ep = new QDomElement( e );
00678             m_styles.insert( name, ep );
00679             kdDebug(30518) << "Style: '" << name << "' loaded " << endl;
00680         } else if ( localName == "default-style" && ns == ooNS::style ) {
00681             m_defaultStyle = e;
00682         } else if ( localName == "list-style" && ns == ooNS::text ) {
00683             QDomElement* ep = new QDomElement( e );
00684             m_listStyles.insert( name, ep );
00685             kdDebug(30518) << "List style: '" << name << "' loaded " << endl;
00686         } else if ( localName == "outline-style" && ns == ooNS::text ) {
00687             m_outlineStyle = e;
00688         } else if ( localName == "footnotes-configuration" && ns == ooNS::text ) {
00689             importFootnotesConfiguration( doc, e, false );
00690         } else if ( localName == "endnotes-configuration" && ns == ooNS::text ) {
00691             importFootnotesConfiguration( doc, e, true );
00692         } else if ( localName == "linenumbering-configuration" && ns == ooNS::text ) {
00693             // Not implemented in KWord
00694         } else if ( localName == "number-style" && ns == ooNS::number ) {
00695             // TODO
00696         } else if ( ( localName == "date-style"
00697                       || localName == "time-style" ) && ns == ooNS::number ) {
00698             importDateTimeStyle( e );
00699         } else {
00700             kdWarning(30518) << "Unknown element " << localName << " in styles" << endl;
00701         }
00702     }
00703 }
00704 
00705 
00706 // OO spec 2.5.4. p68. Conversion to Qt format: see qdate.html
00707 // OpenCalcImport::loadFormat has similar code, but slower, intermixed with other stuff,
00708 // lacking long-textual forms.
00709 void OoWriterImport::importDateTimeStyle( const QDomElement& parent )
00710 {
00711     QString format;
00712     QDomElement e;
00713     forEachElement( e, parent )
00714     {
00715         const QString ns = e.namespaceURI();
00716         if ( ns != ooNS::number )
00717             continue;
00718         const QString localName = e.localName();
00719         const QString numberStyle = e.attributeNS( ooNS::number, "style", QString::null );
00720         const bool shortForm = numberStyle == "short" || numberStyle.isEmpty();
00721         if ( localName == "day" ) {
00722             format += shortForm ? "d" : "dd";
00723         } else if ( localName == "day-of-week" ) {
00724             format += shortForm ? "ddd" : "dddd";
00725         } else if ( localName == "month" ) {
00726             // TODO the spec has a strange mention of number:format-source
00727             if ( e.attributeNS( ooNS::number, "textual", QString::null ) == "true" ) {
00728                 format += shortForm ? "MMM" : "MMMM";
00729             } else { // month number
00730                 format += shortForm ? "M" : "MM";
00731             }
00732         } else if ( localName == "year" ) {
00733             format += shortForm ? "yy" : "yyyy";
00734         } else if ( localName == "week-of-year" || localName == "quarter") {
00735             // ### not supported in Qt
00736         } else if ( localName == "hours" ) {
00737             format += shortForm ? "h" : "hh";
00738         } else if ( localName == "minutes" ) {
00739             format += shortForm ? "m" : "mm";
00740         } else if ( localName == "seconds" ) {
00741             format += shortForm ? "s" : "ss";
00742         } else if ( localName == "am-pm" ) {
00743             format += "ap";
00744         } else if ( localName == "text" ) { // litteral
00745             format += e.text();
00746         } // TODO number:decimal-places
00747     }
00748 
00749 #if 0
00750     // QDate doesn't work both ways!!! It can't parse something back from
00751     // a string and a format (e.g. 01/02/03 and dd/MM/yy, it will assume MM/dd/yy).
00752     // So we also need to generate a KLocale-like format, to parse the value
00753     // Update: we don't need to parse the date back.
00754 
00755     QString kdeFormat;
00756     QDomElement e;
00757     forEachElement( e, parent )
00758     {
00759         const QString ns = e.namespaceURI();
00760         if ( ns != ooNS::number )
00761             continue;
00762         QString localName = e.tagName();
00763         const QString numberStyle = e.attributeNS( ooNS::number, "style", QString::null );
00764         const bool shortForm = numberStyle == "short" || numberStyle.isEmpty();
00765         if ( localName == "day" ) {
00766             kdeFormat += shortForm ? "%e" : "%d";
00767         } else if ( localName == "day-of-week" ) {
00768             kdeFormat += shortForm ? "%a" : "%A";
00769         } else if ( localName == "month" ) {
00770             // TODO the spec has a strange mention of number:format-source
00771             if ( e.attributeNS( ooNS::number, "textual", QString::null ) == "true" ) {
00772                 kdeFormat += shortForm ? "%b" : "%B";
00773             } else { // month number
00774                 kdeFormat += shortForm ? "%n" : "%m";
00775             }
00776         } else if ( localName == "year" ) {
00777             kdeFormat += shortForm ? "%y" : "%Y";
00778         } else if ( localName == "week-of-year" || localName == "quarter") {
00779             // ### not supported in KLocale
00780         } else if ( localName == "hours" ) {
00781             kdeFormat += shortForm ? "%k" : "%H"; // TODO should depend on presence of am/pm
00782         } else if ( localName == "minutes" ) {
00783             kdeFormat += shortForm ? "%M" : "%M"; // KLocale doesn't have 1-digit minutes
00784         } else if ( localName == "seconds" ) {
00785             kdeFormat += shortForm ? "%S" : "%S"; // KLocale doesn't have 1-digit seconds
00786         } else if ( localName == "am-pm" ) {
00787             kdeFormat += "%p";
00788         } else if ( localName == "text" ) { // litteral
00789             kdeFormat += e.text();
00790         } // TODO number:decimal-places
00791     }
00792 #endif
00793 
00794     QString styleName = parent.attributeNS( ooNS::style, "name", QString::null );
00795     kdDebug(30518) << "datetime style: " << styleName << " qt format=" << format << endl;
00796     m_dateTimeFormats.insert( styleName, format );
00797 }
00798 
00799 void OoWriterImport::fillStyleStack( const QDomElement& object, const char* nsURI, const QString& attrName )
00800 {
00801     // find all styles associated with an object and push them on the stack
00802     // OoImpressImport has more tests here, but I don't think they're relevant to OoWriterImport
00803     if ( object.hasAttributeNS( nsURI, attrName ) ) {
00804         const QString styleName = object.attributeNS( nsURI, attrName, QString::null );
00805         const QDomElement* style = m_styles[styleName];
00806         if ( style )
00807             addStyles( style );
00808         else
00809             kdWarning(30518) << "fillStyleStack: no style named " << styleName << " found." << endl;
00810     }
00811 }
00812 
00813 void OoWriterImport::addStyles( const QDomElement* style )
00814 {
00815     Q_ASSERT( style );
00816     if ( !style ) return;
00817     // this recursive function is necessary as parent styles can have parents themselves
00818     if ( style->hasAttributeNS( ooNS::style, "parent-style-name" ) ) {
00819         const QString parentStyleName = style->attributeNS( ooNS::style, "parent-style-name", QString::null );
00820         QDomElement* parentStyle = m_styles[ parentStyleName ];
00821         if ( parentStyle )
00822             addStyles( parentStyle );
00823         else
00824             kdWarning(30518) << "Parent style not found: " << parentStyleName << endl;
00825     }
00826     else if ( !m_defaultStyle.isNull() ) // on top of all, the default style
00827         m_styleStack.push( m_defaultStyle );
00828 
00829     //kdDebug(30518) << "pushing style " << style->attributeNS( ooNS::style, "name", QString::null ) << endl;
00830     m_styleStack.push( *style );
00831 }
00832 
00833 void OoWriterImport::applyListStyle( QDomDocument& doc, QDomElement& layoutElement, const QDomElement& paragraph )
00834 {
00835     // Spec: see 3.3.5 p137
00836     if ( m_listStyleStack.hasListStyle() && m_nextItemIsListItem ) {
00837         bool heading = paragraph.localName() == "h";
00838         m_nextItemIsListItem = false;
00839         int level = heading ? paragraph.attributeNS( ooNS::text, "level", QString::null ).toInt() : m_listStyleStack.level();
00840         writeCounter( doc, layoutElement, heading, level, m_insideOrderedList );
00841     }
00842 }
00843 
00844 void OoWriterImport::writeCounter( QDomDocument& doc, QDomElement& layoutElement, bool heading, int level, bool ordered )
00845 {
00846     const QDomElement listStyle = m_listStyleStack.currentListStyle();
00847     //const QDomElement listStyleProperties = m_listStyleStack.currentListStyleProperties();
00848     QDomElement counter = doc.createElement( "COUNTER" );
00849     counter.setAttribute( "numberingtype", heading ? 1 : 0 );
00850     counter.setAttribute( "depth", level - 1 ); // "depth" starts at 0
00851 
00852     //kdDebug(30518) << "Numbered parag. heading=" << heading << " level=" << level
00853     //               << " m_restartNumbering=" << m_restartNumbering << endl;
00854 
00855     if ( ordered || heading ) {
00856         counter.setAttribute( "type", Conversion::importCounterType( listStyle.attributeNS( ooNS::style, "num-format", QString::null ) ) );
00857         counter.setAttribute( "lefttext", listStyle.attributeNS( ooNS::style, "num-prefix", QString::null ) );
00858         counter.setAttribute( "righttext", listStyle.attributeNS( ooNS::style, "num-suffix", QString::null ) );
00859         QString dl = listStyle.attributeNS( ooNS::text, "display-levels", QString::null );
00860         if ( dl.isEmpty() )
00861             dl = "1";
00862         counter.setAttribute( "display-levels", dl );
00863         if ( m_restartNumbering != -1 ) {
00864             counter.setAttribute( "start", m_restartNumbering );
00865             counter.setAttribute( "restart", "true" );
00866         } else {
00867             // useful?
00868             counter.setAttribute( "start", listStyle.attributeNS( ooNS::text, "start-value", QString::null ) );
00869         }
00870     }
00871     else { // bullets, see 3.3.6 p138
00872         counter.setAttribute( "type", 6 );
00873         QString bulletChar = listStyle.attributeNS( ooNS::text, "bullet-char", QString::null );
00874         if ( !bulletChar.isEmpty() ) {
00875 #if 0 // doesn't work well. Fonts lack those symbols!
00876             counter.setAttribute( "bullet", bulletChar[0].unicode() );
00877             kdDebug(30518) << "bullet code " << bulletChar[0].unicode() << endl;
00878             QString fontName = listStyleProperties.attributeNS( ooNS::style, "font-name", QString::null );
00879             counter.setAttribute( "bulletfont", fontName );
00880 #endif
00881             // Reverse engineering, I found those codes:
00882             switch( bulletChar[0].unicode() ) {
00883             case 0x2022: // small disc
00884                 counter.setAttribute( "type", 10 ); // a disc bullet
00885                 break;
00886             case 0x25CF: // large disc
00887                 counter.setAttribute( "type", 10 ); // a disc bullet
00888                 break;
00889             case 0xE00C: // losange - TODO in KWord. Not in OASIS either (reserved Unicode area!)
00890                 counter.setAttribute( "type", 10 ); // a disc bullet
00891                 break;
00892             case 0xE00A: // square. Not in OASIS (reserved Unicode area!)
00893                 counter.setAttribute( "type", 9 );
00894                 break;
00895             case 0x2794: // arrow
00896             case 0x27A2: // two-colors right-pointing triangle (TODO)
00897                 counter.setAttribute( "bullet", 206 ); // simpler arrow symbol
00898                 counter.setAttribute( "bulletfont", "symbol" );
00899                 break;
00900             case 0x2717: // cross
00901                 counter.setAttribute( "bullet", 212 ); // simpler cross symbol
00902                 counter.setAttribute( "bulletfont", "symbol" );
00903                 break;
00904             case 0x2714: // checkmark
00905                 counter.setAttribute( "bullet", 246 ); // hmm that's sqrt
00906                 counter.setAttribute( "bulletfont", "symbol" );
00907                 break;
00908             default:
00909                 counter.setAttribute( "type", 8 ); // circle
00910                 break;
00911             }
00912         } else { // can never happen
00913             counter.setAttribute( "type", 10 ); // a disc bullet
00914         }
00915     }
00916 
00917     layoutElement.appendChild(counter);
00918 }
00919 
00920 static QDomElement findListLevelStyle( QDomElement& fullListStyle, int level )
00921 {
00922     QDomElement listLevelItem;
00923     forEachElement( listLevelItem, fullListStyle )
00924     {
00925        if ( listLevelItem.attributeNS( ooNS::text, "level", QString::null ).toInt() == level )
00926            return listLevelItem;
00927     }
00928     return QDomElement();
00929 }
00930 
00931 bool OoWriterImport::pushListLevelStyle( const QString& listStyleName, int level )
00932 {
00933     QDomElement* fullListStyle = m_listStyles[listStyleName];
00934     if ( !fullListStyle ) {
00935         kdWarning(30518) << "List style " << listStyleName << " not found!" << endl;
00936         return false;
00937     }
00938     else
00939         return pushListLevelStyle( listStyleName, *fullListStyle, level );
00940 }
00941 
00942 bool OoWriterImport::pushListLevelStyle( const QString& listStyleName, // for debug only
00943                                          QDomElement& fullListStyle, int level )
00944 {
00945     // Find applicable list-level-style for level
00946     int i = level;
00947     QDomElement listLevelStyle;
00948     while ( i > 0 && listLevelStyle.isNull() ) {
00949         listLevelStyle = findListLevelStyle( fullListStyle, i );
00950         --i;
00951     }
00952     if ( listLevelStyle.isNull() ) {
00953         kdWarning(30518) << "List level style for level " << level << " in list style " << listStyleName << " not found!" << endl;
00954         return false;
00955     }
00956     kdDebug(30518) << "Pushing list-level-style from list-style " << listStyleName << " level " << level << endl;
00957     m_listStyleStack.push( listLevelStyle );
00958     return true;
00959 }
00960 
00961 void OoWriterImport::parseList( QDomDocument& doc, const QDomElement& list, QDomElement& currentFramesetElement )
00962 {
00963     //kdDebug(30518) << k_funcinfo << "parseList"<< endl;
00964 
00965     m_insideOrderedList = ( list.localName() == "ordered-list" );
00966     QString oldListStyleName = m_currentListStyleName;
00967     if ( list.hasAttributeNS( ooNS::text, "style-name" ) )
00968         m_currentListStyleName = list.attributeNS( ooNS::text, "style-name", QString::null );
00969     bool listOK = !m_currentListStyleName.isEmpty();
00970     const int level = m_listStyleStack.level() + 1;
00971     //kdDebug(30518) << k_funcinfo << " listOK=" << listOK << " level=" << level << endl;
00972     if ( listOK )
00973         listOK = pushListLevelStyle( m_currentListStyleName, level );
00974 
00975     // Iterate over list items
00976     QDomElement listItem;
00977     forEachElement( listItem, list )
00978     {
00979         // It's either list-header (normal text on top of list) or list-item
00980         m_nextItemIsListItem = ( listItem.localName() != "list-header" );
00981         m_restartNumbering = -1;
00982         if ( listItem.hasAttributeNS( ooNS::text, "start-value" ) )
00983             m_restartNumbering = listItem.attributeNS( ooNS::text, "start-value", QString::null ).toInt();
00984         // ### Oasis: can be p h or list only.
00985         parseBodyOrSimilar( doc, listItem, currentFramesetElement );
00986         m_restartNumbering = -1;
00987     }
00988     if ( listOK )
00989         m_listStyleStack.pop();
00990     m_currentListStyleName = oldListStyleName;
00991 }
00992 
00993 static int numberOfParagraphs( const QDomElement& frameset )
00994 {
00995     const QDomNodeList children = frameset.childNodes();
00996     const QString paragStr = "PARAGRAPH";
00997     int paragCount = 0;
00998     for ( unsigned int i = 0 ; i < children.length() ; ++i ) {
00999         if ( children.item( i ).toElement().tagName() == paragStr )
01000             ++paragCount;
01001     }
01002     return paragCount;
01003 }
01004 
01005 void OoWriterImport::parseSpanOrSimilar( QDomDocument& doc, const QDomElement& parent,
01006     QDomElement& outputParagraph, QDomElement& outputFormats, QString& paragraphText, uint& pos)
01007 {
01008     // Parse every child node of the parent
01009     // Can't use forEachElement here since we also care about text nodes
01010     for( QDomNode node ( parent.firstChild() ); !node.isNull(); node = node.nextSibling() )
01011     {
01012         QDomElement ts ( node.toElement() );
01013         QString textData;
01014         const QString localName( ts.localName() );
01015         const QString ns = ts.namespaceURI();
01016         const bool isTextNS = ns == ooNS::text;
01017         QDomText t ( node.toText() );
01018 
01019         bool shouldWriteFormat=false; // By default no <FORMAT> element should be written
01020 
01021         // Try to keep the order of the tag names by probability of happening
01022         if ( isTextNS && localName == "span" ) // text:span
01023         {
01024             m_styleStack.save();
01025             fillStyleStack( ts, ooNS::text, "style-name" );
01026             parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos);
01027             m_styleStack.restore();
01028         }
01029         else if ( isTextNS && localName == "s" ) // text:s
01030         {
01031             textData = OoUtils::expandWhitespace(ts);
01032             shouldWriteFormat=true;
01033         }
01034         else if ( isTextNS && localName == "tab-stop" ) // text:tab-stop
01035         {
01036             // KWord currently uses \t.
01037             // Known bug: a line with only \t\t\t\t isn't loaded - XML (QDom) strips out whitespace.
01038             // One more good reason to switch to <text:tab-stop> instead...
01039             textData = '\t';
01040             shouldWriteFormat=true;
01041         }
01042         else if ( isTextNS && localName == "line-break" )
01043         {
01044             textData = '\n';
01045             shouldWriteFormat=true;
01046         }
01047         else if ( isTextNS &&
01048                   ( localName == "footnote" || localName == "endnote" ) )
01049         {
01050             textData = '#'; // anchor placeholder
01051             importFootnote( doc, ts, outputFormats, pos, localName );
01052         }
01053         else if ( localName == "image" && ns == ooNS::draw )
01054         {
01055             textData = '#'; // anchor placeholder
01056             QString frameName = appendPicture(doc, ts);
01057             anchorFrameset( doc, outputFormats, pos, frameName );
01058         }
01059         else if ( localName == "text-box" && ns == ooNS::draw )
01060         {
01061             textData = '#'; // anchor placeholder
01062             QString frameName = appendTextBox(doc, ts);
01063             anchorFrameset( doc, outputFormats, pos, frameName );
01064         }
01065         else if ( isTextNS && localName == "a" )
01066         {
01067             m_styleStack.save();
01068             QString href( ts.attributeNS( ooNS::xlink, "href", QString::null) );
01069             if ( href.startsWith("#") )
01070             {
01071                 // We have a reference to a bookmark (### TODO)
01072                 // As we do not support it now, treat it as a <text:span> without formatting
01073                 parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos);
01074             }
01075             else
01076             {
01077                 // The problem is that KWord's hyperlink text is not inside the normal text, but for OOWriter it is nearly a <text:span>
01078                 // So we have to fake.
01079                 QDomElement fakeParagraph, fakeFormats;
01080                 uint fakePos=0;
01081                 QString text;
01082                 parseSpanOrSimilar( doc, ts, fakeParagraph, fakeFormats, text, fakePos);
01083                 textData = '#'; // hyperlink placeholder
01084                 QDomElement linkElement (doc.createElement("LINK"));
01085                 linkElement.setAttribute("hrefName",ts.attributeNS( ooNS::xlink, "href", QString::null));
01086                 linkElement.setAttribute("linkName",text);
01087                 appendKWordVariable(doc, outputFormats, ts, pos, "STRING", 9, linkElement);
01088             }
01089             m_styleStack.restore();
01090         }
01091         else if ( isTextNS &&
01092                   (localName == "date" // fields
01093                    || localName == "print-time"
01094                    || localName == "print-date"
01095                    || localName == "creation-time"
01096                    || localName == "creation-date"
01097                    || localName == "modification-time"
01098                    || localName == "modification-date"
01099                    || localName == "time"
01100                    || localName == "page-number"
01101                    || localName == "chapter"
01102                    || localName == "file-name"
01103                    || localName == "author-name"
01104                    || localName == "author-initials"
01105                    || localName == "subject"
01106                    || localName == "title"
01107                    || localName == "description"
01108                    || localName == "variable-set"
01109                    || localName == "page-variable-get"
01110                    || localName == "user-defined"
01111                    || localName.startsWith( "sender-")
01112                       ) )
01113             // TODO in kword: printed-by, initial-creator
01114         {
01115             textData = "#";     // field placeholder
01116             appendField(doc, outputFormats, ts, pos);
01117         }
01118         else if ( isTextNS && localName == "bookmark" )
01119         {
01120             // the number of <PARAGRAPH> tags in the frameset element is the parag id
01121             // (-1 for starting at 0, +1 since not written yet)
01122             Q_ASSERT( !m_currentFrameset.isNull() );
01123             appendBookmark( doc, numberOfParagraphs( m_currentFrameset ),
01124                             pos, ts.attributeNS( ooNS::text, "name", QString::null ) );
01125         }
01126         else if ( isTextNS && localName == "bookmark-start" ) {
01127             m_bookmarkStarts.insert( ts.attributeNS( ooNS::text, "name", QString::null ),
01128                                      BookmarkStart( m_currentFrameset.attribute( "name" ),
01129                                                     numberOfParagraphs( m_currentFrameset ),
01130                                                     pos ) );
01131         }
01132         else if ( isTextNS && localName == "bookmark-end" ) {
01133             QString bkName = ts.attributeNS( ooNS::text, "name", QString::null );
01134             BookmarkStartsMap::iterator it = m_bookmarkStarts.find( bkName );
01135             if ( it == m_bookmarkStarts.end() ) { // bookmark end without start. This seems to happen..
01136                 // insert simple bookmark then
01137                 appendBookmark( doc, numberOfParagraphs( m_currentFrameset ),
01138                                 pos, ts.attributeNS( ooNS::text, "name", QString::null ) );
01139             } else {
01140                 if ( (*it).frameSetName != m_currentFrameset.attribute( "name" ) ) {
01141                     // Oh tell me this never happens...
01142                     kdWarning(30518) << "Cross-frameset bookmark! Not supported." << endl;
01143                 } else {
01144                     appendBookmark( doc, (*it).paragId, (*it).pos,
01145                                     numberOfParagraphs( m_currentFrameset ), pos, it.key() );
01146                 }
01147                 m_bookmarkStarts.remove( it );
01148             }
01149         }
01150         else if ( t.isNull() ) // no textnode, we must ignore
01151         {
01152             kdWarning(30518) << "Ignoring tag " << ts.tagName() << endl;
01153             continue;
01154         }
01155         else
01156         {
01157             textData = t.data();
01158             shouldWriteFormat=true;
01159         }
01160 
01161         paragraphText += textData;
01162         const uint length = textData.length();
01163 
01164         if (shouldWriteFormat)
01165         {
01166             writeFormat( doc, outputFormats, 1 /* id for normal text */, pos, length );
01167         }
01168 
01169         pos += length;
01170     }
01171 }
01172 
01173 QDomElement OoWriterImport::parseParagraph( QDomDocument& doc, const QDomElement& paragraph )
01174 {
01175     QDomElement p = doc.createElement( "PARAGRAPH" );
01176 
01177     QDomElement formats = doc.createElement( "FORMATS" );
01178 
01179     QString paragraphText;
01180     uint pos = 0;
01181 
01182     // parse every child node of the paragraph
01183     parseSpanOrSimilar( doc, paragraph, p, formats, paragraphText, pos);
01184 
01185     QDomElement text = doc.createElement( "TEXT" );
01186     text.appendChild( doc.createTextNode( paragraphText ) );
01187     text.setAttribute( "xml:space", "preserve" );
01188     p.appendChild( text );
01189     //kdDebug(30518) << k_funcinfo << "Para text is: " << paragraphText << endl;
01190 
01191     p.appendChild( formats );
01192     QDomElement layoutElement = doc.createElement( "LAYOUT" );
01193     p.appendChild( layoutElement );
01194 
01195     // Style name
01196     QString styleName = m_styleStack.userStyleName("paragraph");
01197     if ( !styleName.isEmpty() )
01198     {
01199         QDomElement nameElement = doc.createElement("NAME");
01200         nameElement.setAttribute( "value", kWordStyleName(styleName) );
01201         layoutElement.appendChild(nameElement);
01202     }
01203 
01204     writeLayout( doc, layoutElement );
01205     writeFormat( doc, layoutElement, 1, 0, 0 ); // paragraph format, useful for empty parags
01206 
01207     applyListStyle( doc, layoutElement, paragraph );
01208 
01209     QDomElement* paragraphStyle = m_styles[paragraph.attributeNS( ooNS::text, "style-name", QString::null )];
01210     QString masterPageName = paragraphStyle ? paragraphStyle->attributeNS( ooNS::style, "master-page-name", QString::null ) : QString::null;
01211     if ( masterPageName.isEmpty() )
01212         masterPageName = "Standard"; // Seems to be a builtin name for the default layout...
01213     if ( masterPageName != m_currentMasterPage )
01214     {
01215         // Detected a change in the master page -> this means we have to use a new page layout
01216         // and insert a frame break if not on the first paragraph.
01217         // In KWord we don't support sections so the first paragraph is the one that determines the page layout.
01218         if ( m_currentMasterPage.isEmpty() ) {
01219             m_currentMasterPage = masterPageName; // before writePageLayout to avoid recursion
01220             writePageLayout( doc, masterPageName );
01221         }
01222         else
01223         {
01224             m_currentMasterPage = masterPageName;
01225             QDomElement pageBreakElem = layoutElement.namedItem( "PAGEBREAKING" ).toElement();
01226             if ( !pageBreakElem.isNull() )  {
01227                 pageBreakElem = doc.createElement( "PAGEBREAKING" );
01228                 layoutElement.appendChild( pageBreakElem );
01229             }
01230             pageBreakElem.setAttribute( "hardFrameBreak", "true" );
01231             // We have no way to store the new page layout, KWord doesn't have sections.
01232         }
01233     }
01234 
01235     return p;
01236 }
01237 
01238 void OoWriterImport::writeFormat( QDomDocument& doc, QDomElement& formats, int id, int pos, int length )
01239 {
01240     // Prepare a FORMAT element for this run of text
01241     QDomElement format( doc.createElement( "FORMAT" ) );
01242     format.setAttribute( "id", id );
01243     format.setAttribute( "pos", pos );
01244     format.setAttribute( "len", length );
01245 
01246     // parse the text-properties
01247     // TODO compare with the paragraph style, and only write out if != from style.
01248     // (This is very important, it's not only an optimization. If no attribute is
01249     // specified in the .kwd file, the style's property will be used, which might
01250     // not always be correct).
01251     // On 2nd thought, this doesn't seem to happen. If a style property needs undoing
01252     // OO always writes it down in the XML, so we write out the property just fine.
01253     // Both apps implement a "write property only if necessary" mechanism, I can't'
01254     // find a case that breaks yet.
01255 
01256     if ( m_styleStack.hasAttributeNS( ooNS::fo, "color" ) ) { // 3.10.3
01257         QColor color( m_styleStack.attributeNS( ooNS::fo, "color" ) ); // #rrggbb format
01258         QDomElement colorElem( doc.createElement( "COLOR" ) );
01259         colorElem.setAttribute( "red", color.red() );
01260         colorElem.setAttribute( "blue", color.blue() );
01261         colorElem.setAttribute( "green", color.green() );
01262         format.appendChild( colorElem );
01263     }
01264     if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-family" )  // 3.10.9
01265          || m_styleStack.hasAttributeNS( ooNS::style, "font-name") ) // 3.10.8
01266     {
01267         // Hmm, the remove "'" could break it's in the middle of the fontname...
01268         QString fontName = m_styleStack.attributeNS( ooNS::fo, "font-family" ).remove( "'" );
01269         if (fontName.isEmpty())
01270         {
01271             // ##### TODO. This is wrong. style:font-name refers to a font-decl entry.
01272             // We have to look it up there, and retrieve _all_ font attributes from it, not just the name.
01273             fontName = m_styleStack.attributeNS( ooNS::style, "font-name" ).remove( "'" );
01274         }
01275         // 'Thorndale' is not known outside OpenOffice so we substitute it
01276         // with 'Times New Roman' that looks nearly the same.
01277         if ( fontName == "Thorndale" )
01278             fontName = "Times New Roman";
01279 
01280         fontName.remove(QRegExp("\\sCE$")); // Arial CE -> Arial
01281 
01282         QDomElement fontElem( doc.createElement( "FONT" ) );
01283         fontElem.setAttribute( "name", fontName );
01284         format.appendChild( fontElem );
01285     }
01286     if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-size" ) ) { // 3.10.14
01287         double pointSize = m_styleStack.fontSize();
01288 
01289         QDomElement fontSize( doc.createElement( "SIZE" ) );
01290         fontSize.setAttribute( "value", qRound(pointSize) ); // KWord uses toInt()!
01291         format.appendChild( fontSize );
01292     }
01293     if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-weight" ) ) { // 3.10.24
01294         QDomElement weightElem( doc.createElement( "WEIGHT" ) );
01295         QString fontWeight = m_styleStack.attributeNS( ooNS::fo, "font-weight" );
01296         int boldness = fontWeight.toInt();
01297         if ( fontWeight == "bold" )
01298             boldness = 75;
01299         else if ( boldness == 0 )
01300             boldness = 50;
01301         weightElem.setAttribute( "value", boldness );
01302         format.appendChild( weightElem );
01303     }
01304 
01305     if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-style" ) ) // 3.10.19
01306         if ( m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "italic" ||
01307              m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "oblique" ) // no difference in kotext
01308         {
01309             QDomElement italic = doc.createElement( "ITALIC" );
01310             italic.setAttribute( "value", 1 );
01311             format.appendChild( italic );
01312         }
01313 
01314     bool wordByWord = (m_styleStack.hasAttributeNS( ooNS::fo, "score-spaces")) // 3.10.25
01315                       && (m_styleStack.attributeNS( ooNS::fo, "score-spaces") == "false");
01316     if( m_styleStack.hasAttributeNS( ooNS::style, "text-crossing-out" )) // 3.10.6
01317     {
01318         QString strikeOutType = m_styleStack.attributeNS( ooNS::style, "text-crossing-out" );
01319         QDomElement strikeOut = doc.createElement( "STRIKEOUT" );
01320         if( strikeOutType =="double-line")
01321         {
01322             strikeOut.setAttribute("value", "double");
01323             strikeOut.setAttribute("styleline","solid");
01324         }
01325         else if( strikeOutType =="single-line")
01326         {
01327             strikeOut.setAttribute("value", "single");
01328             strikeOut.setAttribute("styleline","solid");
01329         }
01330         else if( strikeOutType =="thick-line")
01331         {
01332             strikeOut.setAttribute("value", "single-bold");
01333             strikeOut.setAttribute("styleline","solid");
01334         }
01335         if ( wordByWord )
01336             strikeOut.setAttribute("wordbyword", 1);
01337         // not supported by KWord: "slash" and "X"
01338         // not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot)
01339         format.appendChild( strikeOut );
01340     }
01341     if( m_styleStack.hasAttributeNS( ooNS::style, "text-position")) // 3.10.7
01342     {
01343         QDomElement vertAlign = doc.createElement( "VERTALIGN" );
01344         QString text_position = m_styleStack.attributeNS( ooNS::style, "text-position");
01345         QString value;
01346         QString relativetextsize;
01347         OoUtils::importTextPosition( text_position, value, relativetextsize );
01348         vertAlign.setAttribute( "value", value );
01349         if ( !relativetextsize.isEmpty() )
01350             vertAlign.setAttribute( "relativetextsize", relativetextsize );
01351         format.appendChild( vertAlign );
01352     }
01353     if ( m_styleStack.hasAttributeNS( ooNS::style, "text-underline" ) ) // 3.10.22
01354     {
01355         QString underline;
01356         QString styleline;
01357         OoUtils::importUnderline( m_styleStack.attributeNS( ooNS::style, "text-underline" ),
01358                                   underline, styleline );
01359         QDomElement underLineElem = doc.createElement( "UNDERLINE" );
01360         underLineElem.setAttribute( "value", underline );
01361         underLineElem.setAttribute( "styleline", styleline );
01362 
01363         QString underLineColor = m_styleStack.attributeNS( ooNS::style, "text-underline-color" ); // 3.10.23
01364         if ( !underLineColor.isEmpty() && underLineColor != "font-color" )
01365             underLineElem.setAttribute("underlinecolor", underLineColor);
01366         if ( wordByWord )
01367             underLineElem.setAttribute("wordbyword", 1);
01368         format.appendChild( underLineElem );
01369     }
01370     // Small caps, lowercase, uppercase
01371     if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-variant" ) // 3.10.1
01372          || m_styleStack.hasAttributeNS( ooNS::fo, "text-transform" ) ) // 3.10.2
01373     {
01374         QDomElement fontAttrib( doc.createElement( "FONTATTRIBUTE" ) );
01375         bool smallCaps = m_styleStack.attributeNS( ooNS::fo, "font-variant" ) == "small-caps";
01376         if ( smallCaps )
01377         {
01378             fontAttrib.setAttribute( "value", "smallcaps" );
01379         } else
01380         {
01381             // Both KWord and OO use "uppercase" and "lowercase".
01382             // TODO in KWord: "capitalize".
01383             fontAttrib.setAttribute( "value", m_styleStack.attributeNS( ooNS::fo, "text-transform" ) );
01384         }
01385         format.appendChild( fontAttrib );
01386     }
01387 
01388     if (m_styleStack.hasAttributeNS( ooNS::fo, "language")) // 3.10.17
01389     {
01390         QDomElement lang = doc.createElement("LANGUAGE");
01391         QString tmp = m_styleStack.attributeNS( ooNS::fo, "language");
01392         if (tmp=="en")
01393             lang.setAttribute("value", "en_US");
01394         else
01395             lang.setAttribute("value", tmp);
01396         format.appendChild(lang);
01397     }
01398 
01399     if (m_styleStack.hasAttributeNS( ooNS::style, "text-background-color")) // 3.10.28
01400     {
01401         QDomElement bgCol = doc.createElement("TEXTBACKGROUNDCOLOR");
01402         QColor tmp = m_styleStack.attributeNS( ooNS::style, "text-background-color");
01403         if (tmp != "transparent")
01404         {
01405             bgCol.setAttribute("red", tmp.red());
01406             bgCol.setAttribute("green", tmp.green());
01407             bgCol.setAttribute("blue", tmp.blue());
01408             format.appendChild(bgCol);
01409         }
01410     }
01411 
01412     if (m_styleStack.hasAttributeNS( ooNS::fo, "text-shadow")) // 3.10.21
01413     {
01414         QDomElement shadow = doc.createElement("SHADOW");
01415         QString css = m_styleStack.attributeNS( ooNS::fo, "text-shadow");
01416         // Workaround for OOo-1.1 bug: they forgot to save the color.
01417         QStringList tokens = QStringList::split(' ', css);
01418         if ( !tokens.isEmpty() ) {
01419             QColor col( tokens.first() );
01420             if ( !col.isValid() && tokens.count() > 1 ) {
01421                 col.setNamedColor( tokens.last() );
01422             }
01423             if ( !col.isValid() ) // no valid color found at either end -> append gray
01424                 css += " gray";
01425         }
01426         shadow.setAttribute("text-shadow", css);
01427         format.appendChild(shadow);
01428     }
01429 
01430     /*
01431       Missing properties:
01432       style:use-window-font-color, 3.10.4 - this is what KWord uses by default (fg color from the color style)
01433          OO also switches to another color when necessary to avoid dark-on-dark and light-on-light cases.
01434          (that is TODO in KWord)
01435       style:text-outline, 3.10.5 - not implemented in kotext
01436       style:font-family-generic, 3.10.10 - roman, swiss, modern -> map to a font?
01437       style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise
01438       style:font-pitch, 3.10.12 - fixed or variable -> map to a font?
01439       style:font-charset, 3.10.14 - not necessary with Qt
01440       style:font-size-rel, 3.10.15 - TODO in StyleStack::fontSize()
01441       fo:letter-spacing, 3.10.16 - not implemented in kotext
01442       style:text-relief, 3.10.20 - not implemented in kotext
01443       style:letter-kerning, 3.10.20 - not implemented in kotext
01444       style:text-blinking, 3.10.27 - not implemented in kotext IIRC
01445       style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/
01446       style:text-emphasis, 3.10.31 - not implemented in kotext
01447       style:text-scale, 3.10.33 - not implemented in kotext
01448       style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects)
01449       style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects)
01450       style:punctuation-wrap, 3.10.36 - not implemented in kotext
01451     */
01452 
01453     if ( format.hasChildNodes() || !length /*hack for styles, they should always have a format*/)
01454         formats.appendChild( format );
01455 }
01456 
01457 void OoWriterImport::writeLayout( QDomDocument& doc, QDomElement& layoutElement )
01458 {
01459     Q_ASSERT( layoutElement.ownerDocument() == doc );
01460 
01461     // Always write out the alignment, it's required
01462     QDomElement flowElement = doc.createElement("FLOW");
01463 
01464     /* This was only an intermediate OASIS decision. The final decision is:
01465      *  fo:text-align can be "left", "right", "center", "justify", and
01466      *  "start" will mean direction-dependent. However if we use this right now,
01467      *  OOo won't understand it. So that's for later, we keep our own attribute
01468      *  for now, so that export-import works.
01469      */
01470     if ( m_styleStack.attributeNS( ooNS::style, "text-auto-align" ) == "true" )
01471         flowElement.setAttribute( "align", "auto" );
01472     else
01473     {
01474         if ( m_styleStack.hasAttributeNS( ooNS::fo, "text-align" ) ) // 3.11.4
01475             flowElement.setAttribute( "align", Conversion::importAlignment( m_styleStack.attributeNS( ooNS::fo, "text-align" ) ) );
01476         else
01477             flowElement.setAttribute( "align", "auto" );
01478     }
01479     layoutElement.appendChild( flowElement );
01480 
01481     if ( m_styleStack.hasAttributeNS( ooNS::fo, "writing-mode" ) ) // http://web4.w3.org/TR/xsl/slice7.html#writing-mode
01482     {
01483         // LTR is lr-tb. RTL is rl-tb
01484         QString writingMode = m_styleStack.attributeNS( ooNS::fo, "writing-mode" );
01485         flowElement.setAttribute( "dir", writingMode=="rl-tb" || writingMode=="rl" ? "R" : "L" );
01486     }
01487 
01488     // Indentation (margins)
01489     OoUtils::importIndents( layoutElement, m_styleStack );
01490 
01491     // Offset before and after paragraph
01492     OoUtils::importTopBottomMargin( layoutElement, m_styleStack );
01493 
01494     // Line spacing
01495     OoUtils::importLineSpacing( layoutElement, m_styleStack );
01496 
01497     // Tabulators
01498     OoUtils::importTabulators( layoutElement, m_styleStack );
01499 
01500     // Borders
01501     OoUtils::importBorders( layoutElement, m_styleStack );
01502 
01503     // Page breaking. This isn't in OoUtils since it doesn't apply to KPresenter
01504     if( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") ||
01505         m_styleStack.hasAttributeNS( ooNS::fo, "break-after") ||
01506         m_styleStack.hasAttributeNS( ooNS::style, "break-inside") ||
01507         m_styleStack.hasAttributeNS( ooNS::style, "keep-with-next") ||
01508         m_styleStack.hasAttributeNS( ooNS::fo, "keep-with-next") )
01509     {
01510         QDomElement pageBreak = doc.createElement( "PAGEBREAKING" );
01511         if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") ) { // 3.11.24
01512             bool breakBefore = m_styleStack.attributeNS( ooNS::fo, "break-before" ) != "auto";
01513             // TODO in KWord: implement difference between "column" and "page"
01514             pageBreak.setAttribute("hardFrameBreak", breakBefore ? "true" : "false");
01515         }
01516         else if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-after") ) { // 3.11.24
01517             bool breakAfter = m_styleStack.attributeNS( ooNS::fo, "break-after" ) != "auto";
01518             // TODO in KWord: implement difference between "column" and "page"
01519             pageBreak.setAttribute("hardFrameBreakAfter", breakAfter ? "true" : "false");
01520         }
01521 
01522         if ( m_styleStack.hasAttributeNS( ooNS::style, "break-inside" ) ) { // 3.11.7
01523             bool breakInside = m_styleStack.attributeNS( ooNS::style, "break-inside" ) == "true";
01524             pageBreak.setAttribute("linesTogether", breakInside ? "false" : "true"); // opposite meaning
01525         }
01526         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)
01527             // OASIS spec says it's "auto"/"always", not a boolean. Not sure which one OO uses.
01528             QString val = m_styleStack.attributeNS( ooNS::fo, "keep-with-next" );
01529             pageBreak.setAttribute("keepWithNext", ( val == "true" || val == "always" ) ? "true" : "false");
01530         }
01531         layoutElement.appendChild( pageBreak );
01532     }
01533 
01534     // TODO in KWord: padding
01535     /* padding works together with the borders. The margins are around a
01536      * paragraph, and the padding is the space between the border and the
01537      * paragraph. In the OOo UI, you can only select padding when you have
01538      * selected a border.
01539      *
01540      * There's some difference in conjunction with other features, in that the
01541      * margin area is outside the paragraph, and the padding area is inside the
01542      * paragraph. So if you set a paragraph background color, the padding area
01543      * will be colored, but the margin area won't.
01544      */
01545 
01546 /*
01547   Paragraph properties not implemented in KWord:
01548     style:text-align-last
01549     style:justify-single-word
01550     fo:background-color (3.11.25, bg color for a paragraph, unlike style:text-background-color)
01551     style:background-image (3.11.26)
01552     fo:widows
01553     fo:orphans
01554     fo:hyphenate
01555     fo:hyphenation-keep
01556     fo:hyphenation-remain-char-count
01557     fo:hyphenation-push-char-count
01558     fo:hyphenation-ladder-count
01559     style:drop-cap
01560     style:register-true
01561     style:auto-text-indent
01562     "page sequence entry point"
01563     style:background-image
01564     line numbering
01565     punctuation wrap, 3.10.36
01566     vertical alignment - a bit like offsetfrombaseline (but not for subscript/superscript, in general)
01567   Michael said those are in fact parag properties:
01568     style:text-autospace, 3.10.32 - not implemented in kotext
01569     style:line-break, 3.10.37 - apparently that's for some Asian languages
01570 */
01571 
01572 }
01573 
01574 void OoWriterImport::importFrame( QDomElement& frameElementOut, const QDomElement& object, bool isText )
01575 {
01576     double width = 100;
01577     if ( object.hasAttributeNS( ooNS::svg, "width" ) ) { // fixed width
01578         // TODO handle percentage (of enclosing table/frame/page)
01579         width = KoUnit::parseValue( object.attributeNS( ooNS::svg, "width", QString::null ) );
01580     } else if ( object.hasAttributeNS( ooNS::fo, "min-width" ) ) {
01581         // min-width is not supported in KWord. Let's use it as a fixed width.
01582         width = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-width", QString::null ) );
01583     } else {
01584         kdWarning(30518) << "Error in text-box: neither width nor min-width specified!" << endl;
01585     }
01586     double height = 100;
01587     bool hasMinHeight = false;
01588     if ( object.hasAttributeNS( ooNS::svg, "height" ) ) { // fixed height
01589         // TODO handle percentage (of enclosing table/frame/page)
01590         height = KoUnit::parseValue( object.attributeNS( ooNS::svg, "height", QString::null ) );
01591     } else if ( object.hasAttributeNS( ooNS::fo, "min-height" ) ) {
01592         height = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-height", QString::null ) );
01593         hasMinHeight = true;
01594     } else {
01595         kdWarning(30518) << "Error in text-box: neither height nor min-height specified!" << endl;
01596     }
01597 
01598     // draw:textarea-vertical-align, draw:textarea-horizontal-align
01599 
01600     // Not supported in KWord: fo:max-height  fo:max-width
01601     //                         Anchor, Shadow (3.11.30), Columns
01602 
01603     //  #### horizontal-pos horizontal-rel vertical-pos vertical-rel anchor-type
01604     //  All the above changes the placement!
01605     //  See 3.8 (p199) for details.
01606 
01607     // TODO draw:auto-grow-height  draw:auto-grow-width - hmm? I thought min-height meant auto-grow-height...
01608 
01609 
01610     KoRect frameRect( KoUnit::parseValue( object.attributeNS( ooNS::svg, "x", QString::null ) ),
01611                       KoUnit::parseValue( object.attributeNS( ooNS::svg, "y", QString::null ) ),
01612                       width, height );
01613 
01614     frameElementOut.setAttribute("left", frameRect.left() );
01615     frameElementOut.setAttribute("right", frameRect.right() );
01616     frameElementOut.setAttribute("top", frameRect.top() );
01617     frameElementOut.setAttribute("bottom", frameRect.bottom() );
01618     if ( hasMinHeight )
01619         frameElementOut.setAttribute("min-height", height );
01620     frameElementOut.setAttribute( "z-index", object.attributeNS( ooNS::draw, "z-index", QString::null ) );
01621     QPair<int, QString> attribs = Conversion::importWrapping( m_styleStack.attributeNS( ooNS::style, "wrap" ) );
01622     frameElementOut.setAttribute("runaround", attribs.first );
01623     if ( !attribs.second.isEmpty() )
01624         frameElementOut.setAttribute("runaroundSide", attribs.second );
01625     // ## runaroundGap is a problem. KWord-1.3 had one value, OO has 4 (margins on all sides, see p98).
01626     // Fixed in KWord-post-1.3, it has 4 values now.
01627 
01628 
01629     if ( isText ) {
01630         int overflowBehavior;
01631         if ( m_styleStack.hasAttributeNS( ooNS::style, "overflow-behavior" ) ) { // OASIS extension
01632             overflowBehavior = Conversion::importOverflowBehavior( m_styleStack.attributeNS( ooNS::style, "overflow-behavior" ) );
01633         } else {
01634             // AutoCreateNewFrame not supported in OO-1.1. The presence of min-height tells if it's an auto-resized frame.
01635             overflowBehavior = hasMinHeight ? 0 /*AutoExtendFrame*/ : 2 /*Ignore, i.e. fixed size*/;
01636         }
01637         // Not implemented in KWord: contour wrapping
01638         frameElementOut.setAttribute("autoCreateNewFrame", overflowBehavior);
01639     }
01640 
01641     // TODO sheetSide (not implemented in KWord, but in its DTD)
01642 
01643     importCommonFrameProperties( frameElementOut );
01644 }
01645 
01646 void OoWriterImport::importCommonFrameProperties( QDomElement& frameElementOut )
01647 {
01648     // padding. fo:padding for 4 values or padding-left/right/top/bottom (3.11.29 p228)
01649     double paddingLeft = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "left" ) );
01650     double paddingRight = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "right" ) );
01651     double paddingTop = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "top" ) );
01652     double paddingBottom = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "bottom" ) );
01653 
01654     if ( paddingLeft != 0 )
01655         frameElementOut.setAttribute( "bleftpt", paddingLeft );
01656     if ( paddingRight != 0 )
01657         frameElementOut.setAttribute( "brightpt", paddingRight );
01658     if ( paddingTop != 0 )
01659         frameElementOut.setAttribute( "btoppt", paddingTop );
01660     if ( paddingBottom != 0 )
01661         frameElementOut.setAttribute( "bbottompt", paddingBottom );
01662 
01663     // background color (3.11.25)
01664     bool transparent = false;
01665     QColor bgColor;
01666     if ( m_styleStack.hasAttributeNS( ooNS::fo, "background-color" ) ) {
01667         QString color = m_styleStack.attributeNS( ooNS::fo, "background-color" );
01668         if ( color == "transparent" )
01669             transparent = true;
01670         else
01671             bgColor.setNamedColor( color );
01672     }
01673     if ( transparent )
01674         frameElementOut.setAttribute( "bkStyle", 0 );
01675     else if ( bgColor.isValid() ) {
01676         // OOwriter doesn't support fill patterns (bkStyle).
01677         // But the file support is more generic, and supports: draw:stroke, svg:stroke-color, draw:fill, draw:fill-color
01678         frameElementOut.setAttribute( "bkStyle", 1 );
01679         frameElementOut.setAttribute( "bkRed", bgColor.red() );
01680         frameElementOut.setAttribute( "bkBlue", bgColor.blue() );
01681         frameElementOut.setAttribute( "bkGreen", bgColor.green() );
01682     }
01683 
01684 
01685     // borders (3.11.27)
01686     // can be none/hidden, solid and double. General form is the XSL/FO "width|style|color"
01687     {
01688         double width;
01689         int style;
01690         QColor color;
01691         if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "left"), &width, &style, &color)) {
01692             frameElementOut.setAttribute( "lWidth", width );
01693             if ( color.isValid() ) { // should be always true, but who knows
01694                 frameElementOut.setAttribute( "lRed", color.red() );
01695                 frameElementOut.setAttribute( "lBlue", color.blue() );
01696                 frameElementOut.setAttribute( "lGreen", color.green() );
01697             }
01698             frameElementOut.setAttribute( "lStyle", style );
01699         }
01700         if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "right"), &width, &style, &color)) {
01701             frameElementOut.setAttribute( "rWidth", width );
01702             if ( color.isValid() ) { // should be always true, but who knows
01703                 frameElementOut.setAttribute( "rRed", color.red() );
01704                 frameElementOut.setAttribute( "rBlue", color.blue() );
01705                 frameElementOut.setAttribute( "rGreen", color.green() );
01706             }
01707             frameElementOut.setAttribute( "rStyle", style );
01708         }
01709         if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "top"), &width, &style, &color)) {
01710             frameElementOut.setAttribute( "tWidth", width );
01711             if ( color.isValid() ) { // should be always true, but who knows
01712                 frameElementOut.setAttribute( "tRed", color.red() );
01713                 frameElementOut.setAttribute( "tBlue", color.blue() );
01714                 frameElementOut.setAttribute( "tGreen", color.green() );
01715             }
01716             frameElementOut.setAttribute( "tStyle", style );
01717         }
01718         if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "bottom"), &width, &style, &color)) {
01719             frameElementOut.setAttribute( "bWidth", width );
01720             if ( color.isValid() ) { // should be always true, but who knows
01721                 frameElementOut.setAttribute( "bRed", color.red() );
01722                 frameElementOut.setAttribute( "bBlue", color.blue() );
01723                 frameElementOut.setAttribute( "bGreen", color.green() );
01724             }
01725             frameElementOut.setAttribute( "bStyle", style );
01726         }
01727     }
01728     // TODO more refined border spec for double borders (3.11.28)
01729 }
01730 
01731 QString OoWriterImport::appendTextBox(QDomDocument& doc, const QDomElement& object)
01732 {
01733     const QString frameName ( object.attributeNS( ooNS::draw, "name", QString::null) ); // ### TODO: what if empty, i.e. non-unique
01734     kdDebug(30518) << "appendTextBox " << frameName << endl;
01735     m_styleStack.save();
01736     fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element
01737 
01738     // Create KWord frameset
01739     QDomElement framesetElement(doc.createElement("FRAMESET"));
01740     framesetElement.setAttribute("frameType",1);
01741     framesetElement.setAttribute("frameInfo",0);
01742     framesetElement.setAttribute("visible",1);
01743     framesetElement.setAttribute("name",frameName);
01744     QDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
01745     framesetsPluralElement.appendChild(framesetElement);
01746 
01747     QDomElement frameElementOut(doc.createElement("FRAME"));
01748     framesetElement.appendChild(frameElementOut);
01749     importFrame( frameElementOut, object, true /*text*/ );
01750     // TODO editable
01751 
01752     // We're done with the graphics style
01753     m_styleStack.restore();
01754 
01755     // Obey draw:text-style-name
01756     if ( m_styleStack.hasAttributeNS( ooNS::draw, "text-style-name" ) )
01757         addStyles( m_styles[m_styleStack.attributeNS( ooNS::draw, "text-style-name" )] );
01758 
01759     // Parse contents
01760     parseBodyOrSimilar( doc, object, framesetElement );
01761 
01762     return frameName;
01763 }
01764 
01765 // OOo SPEC: 3.6.3 p149
01766 void OoWriterImport::importFootnote( QDomDocument& doc, const QDomElement& object, QDomElement& formats, uint pos, const QString& localName )
01767 {
01768     const QString frameName( object.attributeNS( ooNS::text, "id", QString::null) );
01769     QDomElement citationElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-citation").latin1() ).toElement();
01770 
01771     bool endnote = localName == "endnote";
01772 
01773     QString label = citationElem.attributeNS( ooNS::text, "label", QString::null );
01774     bool autoNumbered = label.isEmpty();
01775 
01776     // The var
01777     QDomElement footnoteElem = doc.createElement( "FOOTNOTE" );
01778     if ( autoNumbered )
01779         footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
01780     else
01781         footnoteElem.setAttribute( "value", label );
01782     footnoteElem.setAttribute( "notetype", endnote ? "endnote" : "footnote" );
01783     footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
01784     footnoteElem.setAttribute( "frameset", frameName );
01785 
01786     appendKWordVariable( doc, formats, citationElem, pos, "STRI", 11 /*KWord code for footnotes*/, footnoteElem );
01787 
01788     // The frameset
01789     QDomElement framesetElement( doc.createElement("FRAMESET") );
01790     framesetElement.setAttribute( "frameType", 1 /* text */ );
01791     framesetElement.setAttribute( "frameInfo", 7 /* footnote/endnote */ );
01792     framesetElement.setAttribute( "name" , frameName );
01793     QDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
01794     framesetsPluralElement.appendChild(framesetElement);
01795     createInitialFrame( framesetElement, 29, 798, 567, 567+41, true, NoFollowup );
01796     // TODO importCommonFrameProperties ?
01797 
01798     // The text inside the frameset
01799     QDomElement bodyElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-body").latin1() ).toElement();
01800     parseBodyOrSimilar( doc, bodyElem, framesetElement );
01801 }
01802 
01803 QString OoWriterImport::appendPicture(QDomDocument& doc, const QDomElement& object)
01804 {
01805     const QString frameName ( object.attributeNS( ooNS::draw, "name", QString::null) ); // ### TODO: what if empty, i.e. non-unique
01806     const QString href ( object.attributeNS( ooNS::xlink, "href", QString::null) );
01807 
01808     kdDebug(30518) << "Picture: " << frameName << " " << href << " (in OoWriterImport::appendPicture)" << endl;
01809 
01810     KoPicture picture;
01811     if ( href[0]=='#' )
01812     {
01813         QString strExtension;
01814         const int result=href.findRev(".");
01815         if (result>=0)
01816         {
01817             strExtension=href.mid(result+1); // As we are using KoPicture, the extension should be without the dot.
01818         }
01819         QString filename(href.mid(1));
01820         KoPictureKey key(filename, QDateTime::currentDateTime(Qt::UTC));
01821         picture.setKey(key);
01822 
01823         if (!m_zip)
01824             return frameName; // Should not happen
01825 
01826         const KArchiveEntry* entry = m_zip->directory()->entry( filename );
01827         if (!entry)
01828         {
01829             kdWarning(30518) << "Picture " << filename << " not found!" << endl;
01830             return frameName;
01831         }
01832         if (entry->isDirectory())
01833         {
01834             kdWarning(30518) << "Picture " << filename << " is a directory!" << endl;
01835             return frameName;
01836         }
01837         const KZipFileEntry* f = static_cast<const KZipFileEntry *>(entry);
01838         QIODevice* io=f->device();
01839         kdDebug(30518) << "Picture " << filename << " has size " << f->size() << endl;
01840 
01841         if (!io)
01842         {
01843             kdWarning(30518) << "No QIODevice for picture  " << frameName << " " << href << endl;
01844             return frameName;
01845         }
01846         if (!picture.load(io,strExtension))
01847             kdWarning(30518) << "Cannot load picture: " << frameName << " " << href << endl;
01848     }
01849     else
01850     {
01851         KURL url;
01852         url.setPath(href); // ### TODO: is this really right?
01853         picture.setKeyAndDownloadPicture(url, 0); // ### TODO: find a better parent if possible
01854     }
01855 
01856     kdDebug(30518) << "Picture ready! Key: " << picture.getKey().toString() << " Size:" << picture.getOriginalSize() << endl;
01857 
01858     QString strStoreName;
01859     strStoreName="pictures/picture";
01860     strStoreName+=QString::number(++m_pictureNumber);
01861     strStoreName+='.';
01862     strStoreName+=picture.getExtension();
01863 
01864     kdDebug(30518) << "Storage name: " << strStoreName << endl;
01865 
01866     KoStoreDevice* out = m_chain->storageFile( strStoreName , KoStore::Write );
01867     if (out)
01868     {
01869         if (!out->open(IO_WriteOnly))
01870         {
01871             kdWarning(30518) << "Cannot open for saving picture: " << frameName << " " << href << endl;
01872             return frameName;
01873         }
01874         if (!picture.save(out))
01875         kdWarning(30518) << "Cannot save picture: " << frameName << " " << href << endl;
01876         out->close();
01877     }
01878     else
01879     {
01880          kdWarning(30518) << "Cannot store picture: " << frameName << " " << href << endl;
01881          return frameName;
01882     }
01883 
01884     // Now that we have copied the image, we need to make some bookkeeping
01885 
01886     QDomElement docElement( doc.documentElement() );
01887 
01888     QDomElement framesetsPluralElement ( docElement.namedItem("FRAMESETS").toElement() );
01889 
01890     QDomElement framesetElement=doc.createElement("FRAMESET");
01891     framesetElement.setAttribute("frameType",2);
01892     framesetElement.setAttribute("frameInfo",0);
01893     framesetElement.setAttribute("visible",1);
01894     framesetElement.setAttribute("name",frameName);
01895     framesetsPluralElement.appendChild(framesetElement);
01896 
01897     QDomElement frameElementOut=doc.createElement("FRAME");
01898     framesetElement.appendChild(frameElementOut);
01899 
01900     m_styleStack.save();
01901     fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element
01902     importFrame( frameElementOut, object, false /*not text*/ );
01903     m_styleStack.restore();
01904 
01905     QDomElement element=doc.createElement("PICTURE");
01906     element.setAttribute("keepAspectRatio","true");
01907     framesetElement.setAttribute("frameType",2); // Picture
01908     framesetElement.appendChild(element);
01909 
01910     QDomElement singleKey ( doc.createElement("KEY") );
01911     picture.getKey().saveAttributes(singleKey);
01912     element.appendChild(singleKey);
01913 
01914     QDomElement picturesPluralElement ( docElement.namedItem("PICTURES").toElement() );
01915     if (picturesPluralElement.isNull())
01916     {
01917         // We do not yet have any <PICTURES> element, so we must create it
01918         picturesPluralElement = doc.createElement("PICTURES");
01919         docElement.appendChild(picturesPluralElement);
01920     }
01921 
01922     QDomElement pluralKey ( doc.createElement("KEY") );
01923     picture.getKey().saveAttributes(pluralKey);
01924     pluralKey.setAttribute("name",strStoreName);
01925     picturesPluralElement.appendChild(pluralKey);
01926     return frameName;
01927 }
01928 
01929 void OoWriterImport::anchorFrameset( QDomDocument& doc, QDomElement& formats, uint pos, const QString& frameSetName )
01930 {
01931     QDomElement formatElementOut=doc.createElement("FORMAT");
01932     formatElementOut.setAttribute("id",6); // Floating frame
01933     formatElementOut.setAttribute("pos",pos); // Start position
01934     formatElementOut.setAttribute("len",1); // Start position
01935     formats.appendChild(formatElementOut); //Append to <FORMATS>
01936 
01937     QDomElement anchor=doc.createElement("ANCHOR");
01938     // No name attribute!
01939     anchor.setAttribute("type","frameset");
01940     anchor.setAttribute("instance",frameSetName);
01941     formatElementOut.appendChild(anchor);
01942 }
01943 
01944 void OoWriterImport::appendField(QDomDocument& doc, QDomElement& outputFormats, QDomElement& object, uint pos)
01945 // Note: QDomElement& outputFormats replaces the parameter QDomElement& e in OoImpressImport::appendField
01946 //  (otherwise it should be the same parameters.)
01947 {
01948     const QString localName (object.localName());
01949     //kdDebug(30518) << localName << endl;
01950     int subtype = -1;
01951 
01952     if ( localName.endsWith( "date" ) || localName.endsWith( "time" ) )
01953     {
01954         QString dataStyleName = object.attributeNS( ooNS::style, "data-style-name", QString::null );
01955         QString dateFormat = "locale";
01956         DataFormatsMap::const_iterator it = m_dateTimeFormats.find( dataStyleName );
01957         if ( it != m_dateTimeFormats.end() )
01958             dateFormat = (*it);
01959 
01960         if ( localName == "date" )
01961         {
01962             subtype = 1; // current (or fixed) date
01963             // Standard form of the date is in date-value. Example: 2004-01-21T10:57:05
01964             QDateTime dt(QDate::fromString(object.attributeNS( ooNS::text, "date-value", QString::null), Qt::ISODate));
01965 
01966             bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", QString::null)=="true");
01967             if (!dt.isValid())
01968             {
01969                 dt = QDateTime::currentDateTime(); // OOo docs say so :)
01970                 fixed = false;
01971             }
01972             const QDate date(dt.date());
01973             const QTime time(dt.time());
01974             if ( fixed )
01975                 subtype = 0;
01976 
01977             QDomElement dateElement ( doc.createElement("DATE") );
01978             dateElement.setAttribute("fix", fixed ? 1 : 0);
01979             dateElement.setAttribute("subtype", subtype);
01980             dateElement.setAttribute("day", date.day());
01981             dateElement.setAttribute("month", date.month());
01982             dateElement.setAttribute("year", date.year());
01983             dateElement.setAttribute("hour", time.hour());
01984             dateElement.setAttribute("minute", time.minute());
01985             dateElement.setAttribute("second", time.second());
01986             if (object.hasAttributeNS( ooNS::text, "date-adjust"))
01987                 dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", QString::null));
01988             appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement);
01989         }
01990         else if (localName == "time")
01991         {
01992             // Use QDateTime to work around a possible problem of QTime::FromString in Qt 3.2.2
01993             QDateTime dt(QDateTime::fromString(object.attributeNS( ooNS::text, "time-value", QString::null), Qt::ISODate));
01994 
01995             bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", QString::null)=="true");
01996 
01997             if (!dt.isValid()) {
01998                 dt = QDateTime::currentDateTime(); // OOo docs say so :)
01999                 fixed = false;
02000             }
02001 
02002             const QTime time(dt.time());
02003             QDomElement timeElement (doc.createElement("TIME") );
02004             timeElement.setAttribute("fix", fixed ? 1 : 0);
02005             timeElement.setAttribute("hour", time.hour());
02006             timeElement.setAttribute("minute", time.minute());
02007             timeElement.setAttribute("second", time.second());
02008             /*if (object.hasAttributeNS( ooNS::text, "time-adjust"))
02009               timeElem.setAttribute("correct", object.attributeNS( ooNS::text, "time-adjust", QString::null));*/ // ### TODO
02010             appendKWordVariable(doc, outputFormats, object, pos, "TIME" + dateFormat, 2, timeElement);
02011 
02012         }
02013         else if ( localName == "print-time"
02014                   || localName == "print-date"
02015                   || localName == "creation-time"
02016                   || localName == "creation-date"
02017                   || localName == "modification-time"
02018                   || localName == "modification-date" )
02019         {
02020             if ( localName.startsWith( "print" ) )
02021                 subtype = 2;
02022             else if ( localName.startsWith( "creation" ) )
02023                 subtype = 3;
02024             else if ( localName.startsWith( "modification" ) )
02025                 subtype = 4;
02026             // We do NOT include the date value here. It will be retrieved from
02027             // meta.xml
02028             QDomElement dateElement ( doc.createElement("DATE") );
02029             dateElement.setAttribute("subtype", subtype);
02030             if (object.hasAttributeNS( ooNS::text, "date-adjust"))
02031                 dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", QString::null));
02032             appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement);
02033         }
02034     }// end of date/time variables
02035     else if (localName == "page-number")
02036     {
02037         subtype = 0;        // VST_PGNUM_CURRENT
02038 
02039         if (object.hasAttributeNS( ooNS::text, "select-page"))
02040         {
02041             const QString select = object.attributeNS( ooNS::text, "select-page", QString::null);
02042 
02043             if (select == "previous")
02044                 subtype = 3;    // VST_PGNUM_PREVIOUS
02045             else if (select == "next")
02046                 subtype = 4;    // VST_PGNUM_NEXT
02047         }
02048 
02049         QDomElement pgnumElement ( doc.createElement("PGNUM") );
02050         pgnumElement.setAttribute("subtype", subtype);
02051         pgnumElement.setAttribute("value", object.text());
02052         appendKWordVariable(doc, outputFormats, object, pos, "NUMBER", 4, pgnumElement);
02053     }
02054     else if (localName == "chapter")
02055     {
02056         const QString display = object.attributeNS( ooNS::text, "display", QString::null );
02057         // display can be name, number, number-and-name, plain-number-and-name, plain-number
02058         QDomElement pgnumElement ( doc.createElement("PGNUM") );
02059         pgnumElement.setAttribute("subtype", 2); // VST_CURRENT_SECTION
02060         pgnumElement.setAttribute("value", object.text());
02061         appendKWordVariable(doc, outputFormats, object, pos, "STRING", 4, pgnumElement);
02062     }
02063     else if (localName == "file-name")
02064     {
02065         subtype = 5;
02066 
02067         if (object.hasAttributeNS( ooNS::text, "display"))
02068         {
02069             const QString display = object.attributeNS( ooNS::text, "display", QString::null);
02070 
02071             if (display == "path")
02072                 subtype = 1;    // VST_DIRECTORYNAME
02073             else if (display == "name")
02074                 subtype = 6;    // VST_FILENAMEWITHOUTEXTENSION
02075             else if (display == "name-and-extension")
02076                 subtype = 0;    // VST_FILENAME
02077             else
02078                 subtype = 5;    // VST_PATHFILENAME
02079         }
02080 
02081         QDomElement fieldElement ( doc.createElement("FIELD") );
02082         fieldElement.setAttribute("subtype", subtype);
02083         fieldElement.setAttribute("value", object.text());
02084         appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElement);
02085     }
02086     else if (localName == "author-name"
02087              || localName == "author-initials"
02088              || localName == "subject"
02089              || localName == "title"
02090              || localName == "description"
02091         )
02092     {
02093         subtype = 2;        // VST_AUTHORNAME
02094 
02095         if (localName == "author-initials")
02096             subtype = 16;       // VST_INITIAL
02097         else if ( localName == "subject" ) // TODO in kword
02098             subtype = 10; // title
02099         else if ( localName == "title" )
02100             subtype = 10;
02101         else if ( localName == "description" )
02102             subtype = 11; // Abstract
02103 
02104         QDomElement authorElem = doc.createElement("FIELD");
02105         authorElem.setAttribute("subtype", subtype);
02106         authorElem.setAttribute("value", object.text());
02107         appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, authorElem);
02108     }
02109     else if ( localName.startsWith( "sender-" ) )
02110     {
02111         int subtype = -1;
02112         const QCString afterText( localName.latin1() + 5 );
02113         if ( afterText == "sender-company" )
02114             subtype = 4; //VST_COMPANYNAME;
02115         else if ( afterText == "sender-firstname" )
02116             ; // ## This is different from author-name, but the notion of 'sender' is unclear...
02117         else if ( afterText == "sender-lastname" )
02118             ; // ## This is different from author-name, but the notion of 'sender' is unclear...
02119         else if ( afterText == "sender-initials" )
02120             ; // ## This is different from author-initials, but the notion of 'sender' is unclear...
02121         else if ( afterText == "sender-street" )
02122             subtype = 14; // VST_STREET;
02123         else if ( afterText == "sender-country" )
02124             subtype = 9; // VST_COUNTRY;
02125         else if ( afterText == "sender-postal-code" )
02126             subtype = 12; //VST_POSTAL_CODE;
02127         else if ( afterText == "sender-city" )
02128             subtype = 13; // VST_CITY;
02129         else if ( afterText == "sender-title" )
02130             subtype = 15; // VST_AUTHORTITLE; // Small hack (it's supposed to be about the sender, not about the author)
02131         else if ( afterText == "sender-position" )
02132             subtype = 15; // VST_AUTHORTITLE; // TODO separate variable
02133         else if ( afterText == "sender-phone-private" )
02134             subtype = 7; // VST_TELEPHONE;
02135         else if ( afterText == "sender-phone-work" )
02136             subtype = 7; // VST_TELEPHONE; // ### TODO separate type
02137         else if ( afterText == "sender-fax" )
02138             subtype = 8; // VST_FAX;
02139         else if ( afterText == "sender-email" )
02140             subtype = 3; // VST_EMAIL;
02141         if ( subtype != -1 )
02142         {
02143             QDomElement fieldElem = doc.createElement("FIELD");
02144             fieldElem.setAttribute("subtype", subtype);
02145             fieldElem.setAttribute("value", object.text());
02146             appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElem);
02147         }
02148     }
02149     else if ( localName == "variable-set"
02150               || localName == "user-defined" )
02151     {
02152         // We treat both the same. For OO the difference is that
02153         // - variable-set is related to variable-decls (defined in <body>);
02154         //                 its value can change in the middle of the document.
02155         // - user-defined is related to meta::user-defined in meta.xml
02156         QDomElement customElem = doc.createElement( "CUSTOM" );
02157         customElem.setAttribute( "name", object.attributeNS( ooNS::text, "name", QString::null ) );
02158         customElem.setAttribute( "value", object.text() );
02159         appendKWordVariable(doc, outputFormats, object, pos, "STRING", 6, customElem);
02160     }
02161     else
02162     {
02163         kdWarning(30518) << "Unsupported field " << localName << endl;
02164     }
02165 // TODO localName == "page-variable-get", "initial-creator" and many more
02166 }
02167 
02168 void OoWriterImport::appendKWordVariable(QDomDocument& doc, QDomElement& formats, const QDomElement& object, uint pos,
02169     const QString& key, int type, QDomElement& child)
02170 {
02171     QDomElement variableElement ( doc.createElement("VARIABLE") );
02172 
02173     QDomElement typeElement ( doc.createElement("TYPE") );
02174     typeElement.setAttribute("key",key);
02175     typeElement.setAttribute("type",type);
02176     typeElement.setAttribute("text",object.text());
02177     variableElement.appendChild(typeElement); //Append to <VARIABLE>
02178 
02179     variableElement.appendChild(child); //Append to <VARIABLE>
02180 
02181     QDomElement formatElement ( doc.createElement("FORMAT") );
02182     formatElement.setAttribute("id",4); // Variable
02183     formatElement.setAttribute("pos",pos); // Start position
02184     formatElement.setAttribute("len",1);
02185 
02186     formatElement.appendChild(variableElement);
02187 
02188     formats.appendChild(formatElement);
02189 }
02190 
02191 void OoWriterImport::parseTable( QDomDocument &doc, const QDomElement& parent, QDomElement& currentFramesetElement )
02192 {
02193     QString tableName ( parent.attributeNS( ooNS::table, "name", QString::null) ); // TODO: what if empty (non-unique?)
02194     kdDebug(30518) << "Found table " << tableName << endl;
02195 
02196     // In OOWriter a table is never inside a paragraph, in KWord it is always in a paragraph
02197     QDomElement paragraphElementOut (doc.createElement("PARAGRAPH"));
02198     currentFramesetElement.appendChild(paragraphElementOut);
02199 
02200     QDomElement textElementOut(doc.createElement("TEXT"));
02201     textElementOut.appendChild(doc.createTextNode("#"));
02202     paragraphElementOut.appendChild(textElementOut);
02203 
02204     QDomElement formatsPluralElementOut(doc.createElement("FORMATS"));
02205     paragraphElementOut.appendChild(formatsPluralElementOut);
02206 
02207     QDomElement elementFormat(doc.createElement("FORMAT"));
02208     elementFormat.setAttribute("id",6);
02209     elementFormat.setAttribute("pos",0);
02210     elementFormat.setAttribute("len",1);
02211     formatsPluralElementOut.appendChild(elementFormat);
02212 
02213     // ### FIXME: we have no <LAYOUT> element!
02214 
02215     QDomElement elementAnchor(doc.createElement("ANCHOR"));
02216     elementAnchor.setAttribute("type","frameset");
02217     elementAnchor.setAttribute("instance",tableName);
02218     elementFormat.appendChild(elementAnchor);
02219 
02220 
02221     // Left position of the cell/column (similar to RTF's \cellx). The last one defined is the right position of the last cell/column
02222     QMemArray<double> columnLefts(4);
02223     uint maxColumns=columnLefts.size() - 1;
02224 
02225     uint col=0;
02226     columnLefts[0]=0.0; // Initialize left of first cell
02227     QDomElement elem;
02228     forEachElement( elem, parent )
02229     {
02230         if ( elem.localName() == "table-column" && elem.namespaceURI() == ooNS::table )
02231         {
02232             uint repeat = elem.attributeNS( ooNS::table, "number-columns-repeated", "1").toUInt(); // Default 1 time
02233             if (!repeat)
02234                 repeat=1; // At least one column defined!
02235             const QString styleName ( elem.attributeNS( ooNS::table, "style-name", QString::null) );
02236             kdDebug(30518) << "Column " << col << " style " << styleName << endl;
02237             const QDomElement* style=m_styles.find(styleName);
02238             double width=0.0;
02239             if (style)
02240             {
02241                 const QDomElement elemProps( KoDom::namedItemNS( *style, ooNS::style, "properties") );
02242                 if (elemProps.isNull())
02243                 {
02244                     kdWarning(30518) << "Could not find table column style properties!" << endl;
02245                 }
02246                 const QString strWidth ( elemProps.attributeNS( ooNS::style, "column-width", QString::null) );
02247                 kdDebug(30518) << "- raw style width " << strWidth << endl;
02248                 width = KoUnit::parseValue( strWidth );
02249             }
02250             else
02251                 kdWarning(30518) << "Could not find table column style!" << endl;
02252 
02253             if (width < 1.0) // Something is wrong with the width
02254             {
02255                 kdWarning(30518) << "Table column width ridiculous, assuming 1 inch!" << endl;
02256                 width=72.0;
02257             }
02258             else
02259                 kdDebug(30518) << "- style width " << width << endl;
02260 
02261             for (uint j=0; j<repeat; j++)
02262             {
02263                 ++col;
02264                 if (col>=maxColumns)
02265                 {
02266                     // We need more columns
02267                     maxColumns+=4;
02268                     columnLefts.resize(maxColumns+1, QGArray::SpeedOptim);
02269                 }
02270                 columnLefts.at(col) = width + columnLefts.at(col-1);
02271                 kdDebug(30518) << "Cell column " << col-1 << " left " << columnLefts.at(col-1) << " right " << columnLefts.at(col) << endl;
02272             }
02273         }
02274     }
02275 
02276     uint row=0;
02277     uint column=0;
02278     parseInsideOfTable(doc, parent, currentFramesetElement, tableName, columnLefts, row, column);
02279 }
02280 
02281 void OoWriterImport::parseInsideOfTable( QDomDocument &doc, const QDomElement& parent, QDomElement& currentFramesetElement,
02282     const QString& tableName, const QMemArray<double> & columnLefts, uint& row, uint& column )
02283 {
02284     kdDebug(30518) << "parseInsideOfTable: columnLefts.size()=" << columnLefts.size() << endl;
02285     QDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
02286     if (framesetsPluralElement.isNull())
02287     {
02288         kdError(30518) << "Cannot find KWord's <FRAMESETS>! Cannot process table!" << endl;
02289         return;
02290     }
02291 
02292     QDomElement e;
02293     forEachElement( e, parent )
02294     {
02295         m_styleStack.save();
02296         const QString localName = e.localName();
02297         const QString ns = e.namespaceURI();
02298         if ( ns != ooNS::table ) {
02299             kdWarning(30518) << "Skipping element " << e.tagName() << " (in OoWriterImport::parseInsideOfTable)" << endl;
02300             continue;
02301         }
02302 
02303         if ( localName == "table-cell" ) // OOo SPEC 4.8.1 p267
02304         {
02305             const QString frameName(i18n("Frameset name","Table %3, row %1, column %2")
02306                 .arg(row).arg(column).arg(tableName)); // The table name could have a % sequence, so use the table name as last!
02307             kdDebug(30518) << "Trying to create " << frameName << endl;
02308 
02309             // We need to create a frameset for the cell
02310             QDomElement framesetElement(doc.createElement("FRAMESET"));
02311             framesetElement.setAttribute("frameType",1);
02312             framesetElement.setAttribute("frameInfo",0);
02313             framesetElement.setAttribute("visible",1);
02314             framesetElement.setAttribute("name",frameName);
02315             framesetElement.setAttribute("row",row);
02316             framesetElement.setAttribute("col",column);
02317             int rowSpan = e.attributeNS( ooNS::table, "number-rows-spanned", QString::null ).toInt();
02318             framesetElement.setAttribute("rows",rowSpan == 0 ? 1 : rowSpan);
02319             int colSpan = e.attributeNS( ooNS::table, "number-columns-spanned", QString::null ).toInt();
02320             framesetElement.setAttribute("cols",colSpan == 0 ? 1 : colSpan);
02321             framesetElement.setAttribute("grpMgr",tableName);
02322             framesetsPluralElement.appendChild(framesetElement);
02323 
02324             QDomElement frameElementOut(doc.createElement("FRAME"));
02325             frameElementOut.setAttribute("left",columnLefts.at(column));
02326             frameElementOut.setAttribute("right",columnLefts.at(column+1));
02327             frameElementOut.setAttribute("top", 0);
02328             frameElementOut.setAttribute("bottom", 0);
02329             frameElementOut.setAttribute("runaround",1);
02330             frameElementOut.setAttribute("autoCreateNewFrame",0); // Very important for cell growing!
02331             // ### TODO: a few attributes are missing
02332 
02333             m_styleStack.save();
02334             fillStyleStack( e, ooNS::table, "style-name" ); // get the style for the graphics element
02335             importCommonFrameProperties(frameElementOut);
02336             m_styleStack.restore();
02337 
02338             framesetElement.appendChild(frameElementOut);
02339 
02340             parseBodyOrSimilar( doc, e, framesetElement ); // We change the frameset!
02341             column++;
02342         }
02343         else if ( localName == "covered-table-cell" )
02344         {
02345             column++;
02346         }
02347         else if ( localName == "table-row" )
02348         {
02349             column=0;
02350             parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column);
02351             row++;
02352         }
02353         else if ( localName == "table-header-rows" ) // Provisory (###TODO)
02354         {
02355             parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column);
02356         }
02357         else if ( localName == "table-column" )
02358         {
02359             // Allready treated in OoWriterImport::parseTable, we do not need to do anything here!
02360         }
02361         // TODO sub-table
02362         else
02363         {
02364             kdWarning(30518) << "Skipping element " << localName << " (in OoWriterImport::parseInsideOfTable)" << endl;
02365         }
02366 
02367         m_styleStack.restore();
02368     }
02369 }
02370 
02371 void OoWriterImport::appendBookmark( QDomDocument& doc, int paragId, int pos, const QString& name )
02372 {
02373     appendBookmark( doc, paragId, pos, paragId, pos, name );
02374 }
02375 
02376 void OoWriterImport::appendBookmark( QDomDocument& doc, int paragId, int pos, int endParagId, int endPos, const QString& name )
02377 {
02378     Q_ASSERT( !m_currentFrameset.isNull() );
02379     const QString frameSetName = m_currentFrameset.attribute( "name" );
02380     Q_ASSERT( !frameSetName.isEmpty() );
02381     QDomElement bookmarks = doc.documentElement().namedItem( "BOOKMARKS" ).toElement();
02382     if ( bookmarks.isNull() ) {
02383         bookmarks = doc.createElement( "BOOKMARKS" );
02384         doc.documentElement().appendChild( bookmarks );
02385     }
02386     QDomElement bkItem = doc.createElement( "BOOKMARKITEM" );
02387     bkItem.setAttribute( "name", name );
02388     bkItem.setAttribute( "frameset", frameSetName );
02389     bkItem.setAttribute( "startparag", paragId );
02390     bkItem.setAttribute( "cursorIndexStart", pos );
02391     bkItem.setAttribute( "endparag", endParagId );
02392     bkItem.setAttribute( "cursorIndexEnd", endPos );
02393     bookmarks.appendChild( bkItem );
02394 }
02395 
02396 // OOo SPEC: 3.6.1 p146
02397 void OoWriterImport::importFootnotesConfiguration( QDomDocument& doc, const QDomElement& elem, bool endnote )
02398 {
02399     QDomElement docElement( doc.documentElement() );
02400     // can we really be called more than once?
02401     QString elemName = endnote ? "ENDNOTESETTING" : "FOOTNOTESETTING";
02402     Q_ASSERT( docElement.namedItem( elemName ).isNull() );
02403     QDomElement settings = doc.createElement( elemName );
02404     docElement.appendChild( settings );
02405 
02406     // BUG in OO (both 1.0.1 and 1.1). It saves it with an off-by-one (reported to xml@).
02407     // So instead of working around it (which would break with the next version, possibly)
02408     // let's ignore this for now.
02409 #if 0
02410     if ( elem.hasAttributeNS( ooNS::text, "start-value" ) ) {
02411         int startValue = elem.attributeNS( ooNS::text, "start-value", QString::null ).toInt();
02412         settings.setAttribute( "start", startValue );
02413     }
02414 #endif
02415     settings.setAttribute( "type", Conversion::importCounterType( elem.attributeNS( ooNS::style, "num-format", QString::null ) ) );
02416     settings.setAttribute( "lefttext", elem.attributeNS( ooNS::style, "num-prefix", QString::null ) );
02417     settings.setAttribute( "righttext", elem.attributeNS( ooNS::style, "num-suffix", QString::null ) );
02418 }
02419 
02420 void OoWriterImport::appendTOC( QDomDocument& doc, const QDomElement& toc )
02421 {
02422     // table-of-content OOo SPEC 7.5 p452
02423     //fillStyleStack( toc, ooNS::text, "style-name" ); that's the section style
02424 
02425     //QDomElement tocSource = KoDom::namedItemNS( toc, ooNS::text, "table-of-content-source" );
02426     // TODO parse templates and generate "Contents ..." styles from it
02427     //for ( QDomNode n(tocSource.firstChild()); !text.isNull(); text = text.nextSibling() )
02428     //{
02429     //}
02430 
02431     QDomElement tocIndexBody = KoDom::namedItemNS( toc, ooNS::text, "index-body" );
02432     QDomElement t;
02433     forEachElement( t, tocIndexBody )
02434     {
02435         m_styleStack.save();
02436         const QString localName = t.localName();
02437         QDomElement e;
02438         bool isTextNS = e.namespaceURI() == ooNS::text;
02439         if ( isTextNS && localName == "index-title" ) {
02440             parseBodyOrSimilar( doc, t, m_currentFrameset ); // recurse again
02441         } else if ( isTextNS && localName == "p" ) {
02442             fillStyleStack( t, ooNS::text, "style-name" );
02443             e = parseParagraph( doc, t );
02444         }
02445         if ( !e.isNull() )
02446             m_currentFrameset.appendChild( e );
02447         m_styleStack.restore();
02448     }
02449 
02450     // KWord has a special attribute to know if a TOC is present
02451     m_hasTOC = true;
02452 }
02453 
02454 // TODO style:num-format, default number format for page styles,
02455 // used for page numbers (2.3.1)
02456 
02457 void OoWriterImport::finishDocumentContent( QDomDocument& mainDocument )
02458 {
02459     QDomElement attributes = mainDocument.createElement( "ATTRIBUTES" );
02460     QDomElement docElement = mainDocument.documentElement();
02461     docElement.appendChild( attributes );
02462     attributes.setAttribute( "hasTOC", m_hasTOC ? 1 : 0 );
02463     attributes.setAttribute( "hasHeader", m_hasHeader );
02464     attributes.setAttribute( "hasFooter", m_hasFooter );
02465     // TODO unit?, tabStopValue
02466     // TODO activeFrameset, cursorParagraph, cursorIndex
02467 
02468     // Done at the end: write the type of headers/footers,
02469     // depending on which kind of headers and footers we received.
02470     QDomElement paperElement = docElement.namedItem("PAPER").toElement();
02471     Q_ASSERT ( !paperElement.isNull() ); // writePageLayout should have been called!
02472     if ( !paperElement.isNull() )
02473     {
02474         //kdDebug(30513) << k_funcinfo << "m_headerFooters=" << m_headerFooters << endl;
02475         //paperElement.setAttribute("hType", Conversion::headerMaskToHType( m_headerFooters ) );
02476         //paperElement.setAttribute("fType", Conversion::headerMaskToFType( m_headerFooters ) );
02477     }
02478 }
02479 
02480 QString OoWriterImport::kWordStyleName( const QString& ooStyleName )
02481 {
02482     if ( ooStyleName.startsWith( "Contents " ) ) {
02483         QString s( ooStyleName );
02484         return s.replace( 0, 9, QString("Contents Head ") ); // Awful hack for KWord's broken "update TOC" feature
02485     } else {
02486         return ooStyleName;
02487     }
02488 }
02489 
02490 // OOo SPEC: 2.3.3 p59
02491 void OoWriterImport::importHeaderFooter( QDomDocument& doc, const QDomElement& headerFooter, bool hasEvenOdd, QDomElement& style )
02492 {
02493     const QString localName = headerFooter.localName();
02494     QDomElement framesetElement = doc.createElement("FRAMESET");
02495     QDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
02496     framesetElement.setAttribute( "frameType", 1 /* text */);
02497     framesetElement.setAttribute( "frameInfo", Conversion::headerTypeToFrameInfo( localName, hasEvenOdd ) );
02498     framesetElement.setAttribute( "name", Conversion::headerTypeToFramesetName( localName, hasEvenOdd ) );
02499     framesetsPluralElement.appendChild(framesetElement);
02500 
02501     bool isHeader = localName.startsWith( "header" );
02502     if ( isHeader )
02503         m_hasHeader = true;
02504     else
02505         m_hasFooter = true;
02506     QDomElement frameElementOut = createInitialFrame( framesetElement, 29, 798, isHeader?0:567, isHeader?41:567+41, true, Copy );
02507     if ( !style.isNull() )
02508         m_styleStack.push( style );
02509     importCommonFrameProperties( frameElementOut );
02510     if ( !style.isNull() )
02511         m_styleStack.pop(); // don't let it be active when parsing the text
02512 
02513     parseBodyOrSimilar( doc, headerFooter, framesetElement );
02514 }
02515 
02516 #include "oowriterimport.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys