kword

KWOasisLoader.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2005 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00016  * Boston, MA 02110-1301, USA.
00017 */
00018 
00019 #include "KWOasisLoader.h"
00020 #include "KWDocument.h"
00021 #include "KWTextDocument.h"
00022 #include "KWTextFrameSet.h"
00023 #include "KWFrameSet.h"
00024 #include "KWPictureFrameSet.h"
00025 #include "KWLoadingInfo.h"
00026 #include "KWTableFrameSet.h"
00027 #include "KWPartFrameSet.h"
00028 #include "KWPageManager.h"
00029 #include "KWFormulaFrameSet.h"
00030 
00031 #include <KoStore.h>
00032 #include <KoOasisContext.h>
00033 #include <KoOasisStyles.h>
00034 #include <KoOasisStore.h>
00035 #include <KoOasisSettings.h>
00036 #include <KoXmlNS.h>
00037 #include <KoDom.h>
00038 #include <KoXmlNS.h>
00039 
00040 #include <kdebug.h>
00041 #include <klocale.h>
00042 
00043 #include <qvaluelist.h>
00044 #include <qdom.h>
00045 
00046 KWOasisLoader::KWOasisLoader( KWDocument* doc )
00047     : m_doc( doc )
00048 {
00049 }
00050 
00051 // Warning, this method has no undo/redo support, it is *called* by the undo/redo commands.
00052 // cursor is set when pasting into a textframesetedit (kwcommand), 0 otherwise.
00053 QValueList<KWFrame *> KWOasisLoader::insertOasisData( KoStore* store, KoTextCursor* cursor )
00054 {
00055     QValueList<KWFrame *> frames;
00056     if ( store->bad() || !store->hasFile( "content.xml" ) )
00057     {
00058         kdError(32001) << "Invalid ZIP store in memory" << endl;
00059         if ( !store->hasFile( "content.xml" ) )
00060             kdError(32001) << "No content.xml file" << endl;
00061         return frames;
00062     }
00063     store->disallowNameExpansion();
00064 
00065     KoOasisStore oasisStore( store );
00066     QDomDocument contentDoc;
00067     QString errorMessage;
00068     bool ok = oasisStore.loadAndParse( "content.xml", contentDoc, errorMessage );
00069     if ( !ok ) {
00070         kdError(32001) << "Error parsing content.xml: " << errorMessage << endl;
00071         return frames;
00072     }
00073 
00074     KoOasisStyles oasisStyles;
00075     QDomDocument stylesDoc;
00076     (void)oasisStore.loadAndParse( "styles.xml", stylesDoc, errorMessage );
00077     // Load styles from style.xml
00078     oasisStyles.createStyleMap( stylesDoc, true );
00079     // Also load styles from content.xml
00080     oasisStyles.createStyleMap( contentDoc, false );
00081 
00082     m_doc->createLoadingInfo();
00083 
00084     QDomElement content = contentDoc.documentElement();
00085 
00086     QDomElement body( KoDom::namedItemNS( content, KoXmlNS::office, "body" ) );
00087     if ( body.isNull() ) {
00088         kdError(32001) << "No office:body found!" << endl;
00089         return frames;
00090     }
00091     // We then want to use whichever element is the child of <office:body>,
00092     // whether that's <office:text> or <office:presentation> or whatever.
00093     QDomElement iter, realBody;
00094     forEachElement( iter, body ) {
00095         realBody = iter;
00096     }
00097     if ( realBody.isNull() ) {
00098         kdError(32001) << "No element found inside office:body!" << endl;
00099         return frames;
00100     }
00101 
00102     KoOasisContext context( m_doc, *m_doc->variableCollection(), oasisStyles, store );
00103     if ( cursor )
00104     {
00105         KWTextDocument * textdoc = static_cast<KWTextDocument *>(cursor->parag()->document());
00106         KWTextFrameSet * textFs = textdoc->textFrameSet();
00107 
00108         *cursor = textFs->textObject()->pasteOasisText( realBody, context, *cursor, m_doc->styleCollection() );
00109 
00110         textFs->textObject()->setNeedSpellCheck( true );
00111     }
00112     else // No cursor available, load only the frames
00113     {
00114         // The main loop from KoTextDocument::loadOasisText but for frames only
00115         // (can't paste text if there is no text-frameset being edited, where would it go?)
00116         QDomElement tag;
00117         forEachElement( tag, realBody )
00118         {
00119             context.styleStack().save();
00120             const QString bodyTagLocalName = tag.localName();
00121             kdDebug() << k_funcinfo << bodyTagLocalName << endl;
00122             if ( bodyTagLocalName == "frame" && tag.namespaceURI() == KoXmlNS::draw )
00123             {
00124                 KWFrame * frame = loadFrame( tag, context, KoPoint( 10, 10 ) /*offset pasted object*/ );
00125                 if ( frame )
00126                 {
00127                     frames.append( frame );
00128                 }
00129             }
00130 #if 0 // TODO OASIS table:table
00131             else if ( bodyTagLocalName == "table" && tag.namespaceURI() == KoXmlNS::table )
00132                 ;
00133 #endif
00134         }
00135     }
00136 
00137     //kdDebug() << "KWOasisLoader::execute calling doc->completePasting" << endl;
00138     m_doc->completeOasisPasting();
00139     m_doc->deleteLoadingInfo();
00140     return frames;
00141 }
00142 
00143 void KWOasisLoader::loadOasisSettings( const QDomDocument& settingsDoc )
00144 {
00145     KoOasisSettings settings( settingsDoc );
00146     KoOasisSettings::Items viewSettings = settings.itemSet( "view-settings" );
00147     if ( !viewSettings.isNull() )
00148     {
00149         m_doc->setUnit( KoUnit::unit(viewSettings.parseConfigItemString("unit")) );
00150     }
00151     loadOasisIgnoreList( settings );
00152     m_doc->variableCollection()->variableSetting()->loadOasis( settings );
00153 }
00154 
00155 static QString headerTypeToFramesetName( const QString& localName, bool hasEvenOdd )
00156 {
00157     if ( localName == "header" )
00158         return hasEvenOdd ? i18n("Odd Pages Header") : i18n( "Header" );
00159     if ( localName == "header-left" )
00160         return i18n("Even Pages Header");
00161     if ( localName == "footer" )
00162         return hasEvenOdd ? i18n("Odd Pages Footer") : i18n( "Footer" );
00163     if ( localName == "footer-left" )
00164         return i18n("Even Pages Footer");
00165     if ( localName == "header-first" ) // NOT OASIS COMPLIANT
00166         return i18n("First Page Header");
00167     if ( localName == "footer-first" ) // NOT OASIS COMPLIANT
00168         return i18n("First Page Footer");
00169     kdWarning(32001) << "Unknown tag in headerTypeToFramesetName: " << localName << endl;
00170     return QString::null;
00171 }
00172 
00173 static KWFrameSet::Info headerTypeToFrameInfo( const QString& localName, bool /*hasEvenOdd*/ )
00174 {
00175     if ( localName == "header" )
00176         return KWFrameSet::FI_ODD_HEADER;
00177     if ( localName == "header-left" )
00178         return KWFrameSet::FI_EVEN_HEADER;
00179     if ( localName == "footer" )
00180         return KWFrameSet::FI_ODD_FOOTER;
00181     if ( localName == "footer-left" )
00182         return KWFrameSet::FI_EVEN_FOOTER;
00183 
00184     // ######## KWord extension, because I'm too lazy.
00185     // TODO: the real solution is a separate page layout for the first page.
00186     if ( localName == "header-first" ) // NOT OASIS COMPLIANT
00187         return KWFrameSet::FI_FIRST_HEADER;
00188     if ( localName == "footer-first" ) // NOT OASIS COMPLIANT
00189         return KWFrameSet::FI_FIRST_FOOTER;
00190     return KWFrameSet::FI_BODY;
00191 }
00192 
00193 void KWOasisLoader::loadOasisHeaderFooter( const QDomElement& headerFooter, bool hasEvenOdd, QDomElement& style, KoOasisContext& context )
00194 {
00195     const QString localName = headerFooter.localName();
00196     bool isHeader = localName.startsWith( "header" );
00197 
00198     KWTextFrameSet *fs = new KWTextFrameSet( m_doc, headerTypeToFramesetName( localName, hasEvenOdd ) );
00199     fs->setFrameSetInfo( headerTypeToFrameInfo( localName, hasEvenOdd ) );
00200     m_doc->addFrameSet( fs, false );
00201 
00202     if ( !style.isNull() )
00203         context.styleStack().push( style );
00204     KWFrame* frame = new KWFrame( fs, 29, isHeader?0:567, 798-29, 41 );
00205     frame->loadCommonOasisProperties( context, fs, "header-footer" );
00206     const QString minHeight = context.styleStack().attributeNS( KoXmlNS::fo, "min-height" );
00207     if ( !minHeight.isEmpty() )
00208         frame->setMinimumFrameHeight( KoUnit::parseValue( minHeight ) );
00209 
00210     frame->setFrameBehavior( KWFrame::AutoExtendFrame );
00211     frame->setNewFrameBehavior( KWFrame::Copy );
00212     fs->addFrame( frame );
00213     if ( !style.isNull() )
00214         context.styleStack().pop(); // don't let it be active when parsing the text
00215 
00216     context.setUseStylesAutoStyles( true ); // use auto-styles from styles.xml, not those from content.xml
00217     fs->loadOasisContent( headerFooter, context );
00218     context.setUseStylesAutoStyles( false );
00219 
00220     if ( isHeader )
00221         m_doc->m_headerVisible = true;
00222     else
00223         m_doc->m_footerVisible = true;
00224 }
00225 
00226 void KWOasisLoader::loadOasisIgnoreList( const KoOasisSettings& settings )
00227 {
00228     KoOasisSettings::Items configurationSettings = settings.itemSet( "configuration-settings" );
00229     if ( !configurationSettings.isNull() )
00230     {
00231         const QString ignorelist = configurationSettings.parseConfigItemString( "SpellCheckerIgnoreList" );
00232         kdDebug()<<" ignorelist :"<<ignorelist<<endl;
00233         m_doc->setSpellCheckIgnoreList( QStringList::split( ',', ignorelist ) );
00234     }
00235 }
00236 
00237 KWFrame* KWOasisLoader::loadFrame( const QDomElement& frameTag, KoOasisContext& context, const KoPoint& offset )
00238 {
00239     KWFrame* frame = 0;
00240     QDomElement elem;
00241     forEachElement( elem, frameTag )
00242     {
00243         if ( elem.namespaceURI() != KoXmlNS::draw )
00244             continue;
00245         const QString localName = elem.localName();
00246         if ( localName == "text-box" )
00247         {
00248             //kdDebug()<<" append text-box\n";
00249             frame = loadOasisTextBox( frameTag, elem, context );
00250             break;
00251         }
00252         else if ( localName == "image" )
00253         {
00254             KWFrameSet* fs = new KWPictureFrameSet( m_doc, frameTag, elem, context );
00255             m_doc->addFrameSet( fs, false );
00256             frame = fs->frame(0);
00257             break;
00258         } else if ( localName == "object" )
00259         {
00260             QDomElement mathElem; // will be set if we find <math:math>
00261             QDomElement childElem;
00262             forEachElement( childElem, elem )
00263             {
00264                 if ( childElem.localName() == "math"
00265                      && childElem.namespaceURI() == KoXmlNS::math ) {
00266                     mathElem = childElem;
00267                 }
00268             }
00269             if ( !mathElem.isNull() ) {
00270                 KWFormulaFrameSet* fs = new KWFormulaFrameSet( m_doc, frameTag, mathElem, context );
00271                 m_doc->addFrameSet( fs, false );
00272                 frame = fs->frame(0);
00273             } else {
00274                 KWPartFrameSet* fs = new KWPartFrameSet( m_doc, frameTag, elem, context );
00275                 m_doc->addFrameSet( fs, false );
00276                 frame = fs->frame(0);
00277             }
00278             break;
00279         }
00280     }
00281     if ( frame ) {
00282         const QString anchorType = frameTag.attributeNS( KoXmlNS::text, "anchor-type", QString::null );
00283         if ( anchorType == "page" ) {
00284             double x = KoUnit::parseValue( frameTag.attributeNS( KoXmlNS::svg, "x", QString::null ) );
00285             double y = KoUnit::parseValue( frameTag.attributeNS( KoXmlNS::svg, "y", QString::null ) );
00286             int pageNum = frameTag.attributeNS( KoXmlNS::text, "anchor-page-number", QString::null ).toInt();
00287             // Ensure that we have enough pages
00288             KWPageManager* pageManager = m_doc->pageManager();
00289             while ( pageNum > pageManager->lastPageNumber() )
00290                 pageManager->appendPage();
00291             frame->moveTopLeft( KoPoint( x, y + pageManager->topOfPage(pageNum) ) );
00292         }
00293         frame->moveBy( offset.x(), offset.y() );
00294     }
00295     return frame;
00296 }
00297 
00298 KWFrame* KWOasisLoader::loadOasisTextBox( const QDomElement& frameTag, const QDomElement& tag,
00299                                           KoOasisContext& context )
00300 {
00301     // Text frame chains. When seeing frame 'B' is chained to this frame A when loading,
00302     // we store 'B' -> A, so that when loading B we can add it to A's frameset.
00303     // If we load B first, no problem: when loading A we can chain.
00304     // This is all made simpler by the fact that we don't have manually configurable order in KWord...
00305     // But it's made more complex by the fact that frames don't have names in KWord (framesets do).
00306     // Hence the framename temporary storage in KWLoadingInfo
00307 
00308     KWLoadingInfo* loadingInfo = m_doc->loadingInfo();
00309     KWTextFrameSet* fs = 0;
00310     QString frameName = frameTag.attributeNS( KoXmlNS::draw, "name", QString::null );
00311     QString chainNextName = tag.attributeNS( KoXmlNS::draw, "chain-next-name", QString::null );
00312     if ( !chainNextName.isEmpty() && loadingInfo ) { // 'B' in the above example
00313         kdDebug(32001) << "Loading " << frameName << " : next-in-chain=" << chainNextName << endl;
00314         // Check if we already loaded that frame (then we need to go 'before' it)
00315         KWFrame* nextFrame = loadingInfo->frameByName( chainNextName );
00316         if ( nextFrame ) {
00317             fs = dynamic_cast<KWTextFrameSet *>( nextFrame->frameSet() );
00318             chainNextName = QString::null; // already found, no need to store it
00319             kdDebug(32001) << "  found " << nextFrame << " -> frameset " << ( fs ? fs->name() : QString::null ) << endl;
00320         }
00321     }
00322     KWFrame* prevFrame = loadingInfo->chainPrevFrame( frameName );
00323     //kdDebug(32001) << "Loading " << frameName << " : chainPrevFrame=" << prevFrame << endl;
00324     if ( prevFrame ) {
00325         if ( fs ) // we are between prevFrame and nextFrame. They'd better be for the same fs!!
00326             Q_ASSERT( fs == prevFrame->frameSet() );
00327         fs = dynamic_cast<KWTextFrameSet *>( prevFrame->frameSet() );
00328         //kdDebug(32001) << "  found " << prevFrame << " -> frameset " << fs->name() << endl;
00329     }
00330     KWFrame* frame = 0;
00331     if ( !fs ) {
00332         fs = new KWTextFrameSet( m_doc, frameTag, context );
00333         m_doc->addFrameSet( fs, false );
00334         frame = fs->loadOasis( frameTag, tag, context );
00335     } else { // Adding frame to existing frameset
00336         context.styleStack().save();
00337         context.fillStyleStack( frameTag, KoXmlNS::draw, "style-name", "graphic" ); // get the style for the graphics element
00338         frame = fs->loadOasisTextFrame( frameTag, tag, context );
00339         context.styleStack().restore();
00340     }
00341 
00342     loadingInfo->storeFrameName( frame, frameName );
00343 
00344     if ( !chainNextName.isEmpty() ) {
00345         loadingInfo->storeNextFrame( frame, chainNextName );
00346     }
00347     return frame;
00348 }
00349 
00350 KWTableFrameSet* KWOasisLoader::loadOasisTable( const QDomElement& tag,
00351                                                 KoOasisContext& context )
00352 {
00353     const QString name = tag.attributeNS( KoXmlNS::table, "name", i18n( "Unnamed Table" ) ); // ### check for unicity?
00354     KWTableFrameSet* table = new KWTableFrameSet( m_doc, name );
00355     table->loadOasis( tag, context );
00356     m_doc->addFrameSet(table, false);
00357     return table;
00358 }
KDE Home | KDE Accessibility Home | Description of Access Keys