filters

abiwordexport.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001, 2002, 2003, 2004 Nicolas GOUTTE <goutte@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 as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 /*
00021    This file is based on the old file:
00022     /home/kde/koffice/filters/kword/ascii/asciiexport.cc
00023 
00024    The old file was copyrighted by
00025     Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
00026     Copyright (c) 2000 ID-PRO Deutschland GmbH. All rights reserved.
00027                        Contact: Wolf-Michael Bolle <Bolle@ID-PRO.de>
00028 
00029    The old file was licensed under the terms of the GNU Library General Public
00030    License version 2.
00031 */
00032 
00033 #include <qmap.h>
00034 #include <qiodevice.h>
00035 #include <qtextstream.h>
00036 #include <qdom.h>
00037 
00038 #include <kdebug.h>
00039 #include <kmdcodec.h>
00040 #include <kfilterdev.h>
00041 #include <kgenericfactory.h>
00042 #include <kimageio.h>
00043 
00044 #include <KoPageLayout.h>
00045 #include <KoFilterChain.h>
00046 #include <KoPictureKey.h>
00047 
00048 #include <KWEFStructures.h>
00049 #include <KWEFUtil.h>
00050 #include <KWEFBaseWorker.h>
00051 #include <KWEFKWordLeader.h>
00052 
00053 #include <abiwordexport.h>
00054 #include <abiwordexport.moc>
00055 
00056 class ABIWORDExportFactory : KGenericFactory<ABIWORDExport, KoFilter>
00057 {
00058 public:
00059     ABIWORDExportFactory(void) : KGenericFactory<ABIWORDExport, KoFilter> ("kwordabiwordexport")
00060     {}
00061 protected:
00062     virtual void setupTranslations( void )
00063     {
00064         KGlobal::locale()->insertCatalogue( "kofficefilters" );
00065     }
00066 };
00067 
00068 K_EXPORT_COMPONENT_FACTORY( libabiwordexport, ABIWORDExportFactory() )
00069 
00070 class StyleMap : public QMap<QString,LayoutData>
00071 {
00072 public:
00073     StyleMap(void) {}
00074     ~StyleMap(void) {}
00075 };
00076 
00077 class AbiWordWorker : public KWEFBaseWorker
00078 {
00079 public:
00080     AbiWordWorker(void);
00081     virtual ~AbiWordWorker(void) { delete m_streamOut; delete m_ioDevice; }
00082 public:
00083     virtual bool doOpenFile(const QString& filenameOut, const QString& to);
00084     virtual bool doCloseFile(void); // Close file in normal conditions
00085     virtual bool doOpenDocument(void);
00086     virtual bool doCloseDocument(void);
00087     virtual bool doFullParagraph(const QString& paraText, const LayoutData& layout,
00088         const ValueListFormatData& paraFormatDataList);
00089     virtual bool doOpenTextFrameSet(void); // AbiWord's <section>
00090     virtual bool doCloseTextFrameSet(void); // AbiWord's </section>
00091     virtual bool doFullPaperFormat(const int format,
00092         const double width, const double height, const int orientation); // Calc AbiWord's <papersize>
00093     virtual bool doFullPaperBorders (const double top, const double left,
00094         const double bottom, const double right); // Like KWord's <PAPERBORDERS>
00095     virtual bool doCloseHead(void); // Write <papersize>
00096     virtual bool doOpenStyles(void); // AbiWord's <styles>
00097     virtual bool doCloseStyles(void); // AbiWord's </styles>
00098     virtual bool doFullDefineStyle(LayoutData& layout); // AbiWord's <s></s>
00099     virtual bool doOpenSpellCheckIgnoreList (void); // AbiWord's <ignorewords>
00100     virtual bool doCloseSpellCheckIgnoreList (void); // AbiWord's </ignorewords>
00101     virtual bool doFullSpellCheckIgnoreWord (const QString& ignoreword); // AbiWord's <iw>
00102     virtual bool doFullDocumentInfo(const KWEFDocumentInfo& docInfo); // AbiWord's <metadata>
00103 private:
00104     void processParagraphData (const QString& paraText,
00105         const TextFormatting& formatLayout,
00106         const ValueListFormatData& paraFormatDataList);
00107     void processNormalText ( const QString& paraText,
00108         const TextFormatting& formatLayout,
00109         const FormatData& formatData);
00110     void processVariable ( const QString& paraText,
00111         const TextFormatting& formatLayout,
00112         const FormatData& formatData);
00113     void processAnchor ( const QString& paraText,
00114         const TextFormatting& formatLayout,
00115         const FormatData& formatData);
00116     QString textFormatToAbiProps(const TextFormatting& formatOrigin,
00117         const TextFormatting& formatData, const bool force) const;
00118     QString layoutToCss(const LayoutData& layoutOrigin,
00119         const LayoutData& layout, const bool force) const;
00120     QString escapeAbiWordText(const QString& strText) const;
00121     bool makeTable(const FrameAnchor& anchor);
00122     bool makePicture(const FrameAnchor& anchor);
00123     void writeAbiProps(const TextFormatting& formatLayout, const TextFormatting& format);
00124     void writePictureData(const QString& koStoreName, const QString& keyName);
00125     QString transformToTextDate(const QDateTime& dt);
00126 private:
00127     QIODevice* m_ioDevice;
00128     QTextStream* m_streamOut;
00129     QString m_pagesize; // Buffer for the <pagesize> tag
00130     QMap<QString,KoPictureKey> m_mapPictureData;
00131     StyleMap m_styleMap;
00132     double m_paperBorderTop,m_paperBorderLeft,m_paperBorderBottom,m_paperBorderRight;
00133     bool m_inIgnoreWords; // true if <ignorewords> has been written
00134     KWEFDocumentInfo m_docInfo; // document information
00135 };
00136 
00137 AbiWordWorker::AbiWordWorker(void) : m_ioDevice(NULL), m_streamOut(NULL),
00138     m_paperBorderTop(0.0),m_paperBorderLeft(0.0),
00139     m_paperBorderBottom(0.0),m_paperBorderRight(0.0)
00140 {
00141 }
00142 
00143 QString AbiWordWorker::escapeAbiWordText(const QString& strText) const
00144 {
00145     // Escape quotes (needed in attributes)
00146     // Escape apostrophs (allowed by XML)
00147     return KWEFUtil::EscapeSgmlText(NULL,strText,true,true);
00148 }
00149 
00150 bool AbiWordWorker::doOpenFile(const QString& filenameOut, const QString& )
00151 {
00152     kdDebug(30506) << "Opening file: " << filenameOut
00153         << " (in AbiWordWorker::doOpenFile)" << endl;
00154     //Find the last extension
00155     QString strExt;
00156     const int result=filenameOut.findRev('.');
00157     if (result>=0)
00158     {
00159         strExt=filenameOut.mid(result);
00160     }
00161 
00162     QString strMimeType; // Mime type of the compressor
00163 
00164     if ((strExt==".gz")||(strExt==".GZ")        //in case of .abw.gz (logical extension)
00165         ||(strExt==".zabw")||(strExt==".ZABW")) //in case of .zabw (extension used prioritary with AbiWord)
00166     {
00167         // Compressed with gzip
00168         strMimeType="application/x-gzip";
00169     }
00170     else if ((strExt==".bz2")||(strExt==".BZ2") //in case of .abw.bz2 (logical extension)
00171         ||(strExt==".bzabw")||(strExt==".BZABW")) //in case of .bzabw (extension used prioritary with AbiWord)
00172     {
00173         // Compressed with bzip2
00174         strMimeType="application/x-bzip2";
00175     }
00176     else
00177     {
00178         // No compression
00179         strMimeType="text/plain";
00180     }
00181 
00182     kdDebug(30506) << "Compression: " << strMimeType << endl;
00183 
00184     m_ioDevice = KFilterDev::deviceForFile(filenameOut,strMimeType);
00185 
00186     if (!m_ioDevice)
00187     {
00188         kdError(30506) << "No output file! Aborting!" << endl;
00189         return false;
00190     }
00191 
00192     if ( !m_ioDevice->open (IO_WriteOnly) )
00193     {
00194         kdError(30506) << "Unable to open output file! Aborting!" << endl;
00195         return false;
00196     }
00197 
00198     m_streamOut=new QTextStream(m_ioDevice);
00199 
00200     // We only export in UTF-8 (are there AbiWord ports that cannot read UTF-8? Be careful SVG uses UTF-8 too!)
00201     m_streamOut->setEncoding( QTextStream::UnicodeUTF8 );
00202     return true;
00203 }
00204 
00205 bool AbiWordWorker::doCloseFile(void)
00206 {
00207     delete m_streamOut;
00208     m_streamOut=NULL;
00209     if (m_ioDevice)
00210         m_ioDevice->close();
00211     return (m_ioDevice);
00212 }
00213 
00214 bool AbiWordWorker::doOpenDocument(void)
00215 {
00216     kdDebug(30506)<< "AbiWordWorker::doOpenDocument" << endl;
00217     // Make the file header
00218 
00219     // First the XML header in UTF-8 version
00220     // (AbiWord and QT handle UTF-8 well, so we stay with this encoding!)
00221     *m_streamOut << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
00222 
00223     // NOTE: AbiWord CVS 2002-02-?? has a new DOCTYPE
00224     *m_streamOut << "<!DOCTYPE abiword PUBLIC \"-//ABISOURCE//DTD AWML 1.0 Strict//EN\"";
00225     *m_streamOut << " \"http://www.abisource.com/awml.dtd\">\n";
00226 
00227     // First magic: "<abiword"
00228     *m_streamOut << "<abiword";
00229     // AbiWord CVS 2002-02-23 defines a default namespace.
00230     *m_streamOut << " xmlns=\"http://www.abisource.com/awml.dtd\"";
00231     // As we do not use xmlns:awml, do we need to define it?
00232     // *m_streamOut << " xmlns:awml=\"http://www.abisource.com/awml.dtd\"";
00233     *m_streamOut << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
00234     // AbiWord CVS 2002-02-22 defines other namespaces, which we are not using.
00235     // AbiWord CVS 2002-12-23 has no fileformat attribute anymore
00236     // ### TODO: add document language and document direction of writing.
00237     *m_streamOut << " xml:space=\"preserve\" version=\"1.1.2\" template=\"false\" styles=\"unlocked\">\n";
00238     // Second magic: "<!-- This file is an AbiWord document."
00239     // TODO/FIXME: write as much spaces as AbiWord does for the following line.
00240     *m_streamOut << "<!-- This file is an AbiWord document. -->\n";
00241     // We have chosen NOT to have the full comment header that AbiWord files normally have.
00242     // ### TODO: perhaps we should add the comment: do not edit the file
00243     *m_streamOut << "\n";
00244 
00245 
00246     return true;
00247 }
00248 
00249 void AbiWordWorker::writePictureData(const QString& koStoreName, const QString& keyName)
00250 {
00251     kdDebug(30506) << "AbiWordWorker::writeImageData" << endl;
00252 
00253     QByteArray image;
00254 
00255     QString strExtension(koStoreName.lower());
00256     const int result=koStoreName.findRev(".");
00257     if (result>=0)
00258     {
00259         strExtension=koStoreName.mid(result+1);
00260     }
00261 
00262     bool isImageLoaded=false;
00263 
00264     if (strExtension=="png")
00265     {
00266         isImageLoaded=loadSubFile(koStoreName,image);
00267     }
00268     else
00269     {
00270         // All other picture types must be converted to PNG
00271         //   (yes, even JPEG, SVG or WMF!)
00272         isImageLoaded=loadAndConvertToImage(koStoreName,strExtension,"PNG",image);
00273     }
00274 
00275     if (isImageLoaded)
00276     {
00277         *m_streamOut << "<d name=\"" << keyName << "\""
00278             << " base64=\"yes\""
00279             << " mime=\"image/png\">\n";
00280 
00281         QCString base64=KCodecs::base64Encode(image,true);
00282 
00283         *m_streamOut << base64 << "\n"; // QCString is taken as Latin1 by QTextStream
00284 
00285         *m_streamOut << "</d>\n";
00286     }
00287     else
00288     {
00289         kdWarning(30506) << "Unable to load picture: " << koStoreName << endl;
00290     }
00291 }
00292 
00293 bool AbiWordWorker::doCloseDocument(void)
00294 {
00295     // Before writing the <data> element,
00296     //  we must be sure that we have data and that we can retrieve it.
00297 
00298     if (m_kwordLeader && !m_mapPictureData.isEmpty())
00299     {
00300         *m_streamOut << "<data>\n";
00301 
00302         QMap<QString,KoPictureKey>::ConstIterator it;
00303         QMap<QString,KoPictureKey>::ConstIterator end(m_mapPictureData.end());
00304         // all images first
00305         for (it=m_mapPictureData.begin(); it!=end; ++it)
00306         {
00307             // Warning: do not mix up KWord's key and the iterator's key!
00308             writePictureData(it.key(),it.data().filename());
00309         }
00310 
00311         *m_streamOut << "</data>\n";
00312     }
00313 
00314     *m_streamOut << "</abiword>\n"; //Close the file for XML
00315     return true;
00316 }
00317 
00318 bool AbiWordWorker::doOpenTextFrameSet(void)
00319 {
00320     *m_streamOut << "<section props=\"";
00321     *m_streamOut << "page-margin-top: ";
00322     *m_streamOut << m_paperBorderTop;
00323     *m_streamOut << "pt; ";
00324     *m_streamOut << "page-margin-left: ";
00325     *m_streamOut << m_paperBorderLeft;
00326     *m_streamOut << "pt; ";
00327     *m_streamOut << "page-margin-bottom: ";
00328     *m_streamOut << m_paperBorderBottom;
00329     *m_streamOut << "pt; ";
00330     *m_streamOut << "page-margin-right: ";
00331     *m_streamOut << m_paperBorderRight;
00332     *m_streamOut << "pt"; // Last one, so no semi-comma
00333     *m_streamOut << "\">\n";
00334     return true;
00335 }
00336 
00337 bool AbiWordWorker::doCloseTextFrameSet(void)
00338 {
00339     *m_streamOut << "</section>\n";
00340     return true;
00341 }
00342 
00343 bool AbiWordWorker::doOpenStyles(void)
00344 {
00345     *m_streamOut << "<styles>\n";
00346     return true;
00347 }
00348 
00349 bool AbiWordWorker::doCloseStyles(void)
00350 {
00351     *m_streamOut << "</styles>\n";
00352     return true;
00353 }
00354 
00355 QString AbiWordWorker::textFormatToAbiProps(const TextFormatting& formatOrigin,
00356     const TextFormatting& formatData, const bool force) const
00357 {
00358     // TODO: rename variable formatData
00359     QString strElement; // TODO: rename this variable
00360 
00361     // Font name
00362     QString fontName = formatData.fontName;
00363     if ( !fontName.isEmpty()
00364         && (force || (formatOrigin.fontName!=formatData.fontName)))
00365     {
00366         strElement+="font-family: ";
00367         strElement+= escapeAbiWordText(fontName); // TODO: add alternative font names
00368         strElement+="; ";
00369     }
00370 
00371     if (force || (formatOrigin.italic!=formatData.italic))
00372     {
00373         // Font style
00374         strElement+="font-style: ";
00375         if ( formatData.italic )
00376         {
00377             strElement+="italic";
00378         }
00379         else
00380         {
00381             strElement+="normal";
00382         }
00383         strElement+="; ";
00384     }
00385 
00386     if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75)))
00387     {
00388         strElement+="font-weight: ";
00389         if ( formatData.weight >= 75 )
00390         {
00391             strElement+="bold";
00392         }
00393         else
00394         {
00395             strElement+="normal";
00396         }
00397         strElement+="; ";
00398     }
00399 
00400     if (force || (formatOrigin.fontSize!=formatData.fontSize))
00401     {
00402         const int size=formatData.fontSize;
00403         if (size>0)
00404         {
00405             // We use absolute font sizes.
00406             strElement+="font-size: ";
00407             strElement+=QString::number(size,10);
00408             strElement+="pt; ";
00409         }
00410     }
00411 
00412     if (force || (formatOrigin.fgColor!=formatData.fgColor))
00413     {
00414         if ( formatData.fgColor.isValid() )
00415         {
00416             // Give colour
00417             strElement+="color: ";
00418 
00419             // No leading # (unlike CSS2)
00420             // We must have two hex digits for each colour channel!
00421             const int red=formatData.fgColor.red();
00422             strElement += QString::number((red&0xf0)>>4,16);
00423             strElement += QString::number(red&0x0f,16);
00424 
00425             const int green=formatData.fgColor.green();
00426             strElement += QString::number((green&0xf0)>>4,16);
00427             strElement += QString::number(green&0x0f,16);
00428 
00429             const int blue=formatData.fgColor.blue();
00430             strElement += QString::number((blue&0xf0)>>4,16);
00431             strElement += QString::number(blue&0x0f,16);
00432 
00433             strElement+="; ";
00434         }
00435     }
00436 
00437     if (force || (formatOrigin.bgColor!=formatData.bgColor))
00438     {
00439         if ( formatData.bgColor.isValid() )
00440         {
00441             // Give background colour
00442             strElement+="bgcolor: ";
00443 
00444             // No leading # (unlike CSS2)
00445             // We must have two hex digits for each colour channel!
00446             const int red=formatData.bgColor.red();
00447             strElement += QString::number((red&0xf0)>>4,16);
00448             strElement += QString::number(red&0x0f,16);
00449 
00450             const int green=formatData.bgColor.green();
00451             strElement += QString::number((green&0xf0)>>4,16);
00452             strElement += QString::number(green&0x0f,16);
00453 
00454             const int blue=formatData.bgColor.blue();
00455             strElement += QString::number((blue&0xf0)>>4,16);
00456             strElement += QString::number(blue&0x0f,16);
00457 
00458             strElement+="; ";
00459         }
00460     }
00461 
00462     if (force || (formatOrigin.underline!=formatData.underline)
00463         || (formatOrigin.strikeout!=formatData.strikeout))
00464     {
00465         strElement+="text-decoration: ";
00466         if ( formatData.underline )
00467         {
00468             strElement+="underline";
00469         }
00470         else if ( formatData.strikeout )
00471         {
00472             strElement+="line-through";
00473         }
00474         else
00475         {
00476             strElement+="none";
00477         }
00478         strElement+="; ";
00479     }
00480 
00481     return strElement;
00482 }
00483 
00484 bool AbiWordWorker::makeTable(const FrameAnchor& anchor)
00485 {
00486 #if 0
00487     *m_streamOut << "</p>\n"; // Close previous paragraph ### TODO: do it correctly like for HTML
00488     *m_streamOut << "<table>\n";
00489 #endif
00490 
00491     QValueList<TableCell>::ConstIterator itCell;
00492     for (itCell=anchor.table.cellList.begin();
00493         itCell!=anchor.table.cellList.end(); itCell++)
00494     {
00495 #if 0
00496         // ### TODO: rowspan, colspan
00497 
00498         // AbiWord seems to work by attaching to the cell borders
00499         *m_streamOut << "<cell props=\"";
00500         *m_streamOut << "left-attach:" << (*itCell).col << "; ";
00501         *m_streamOut << "right-attach:" << (*itCell).col + 1 << "; ";
00502         *m_streamOut << "top-attach:" << (*itCell).row << "; ";
00503         *m_streamOut << "bot-attach:" << (*itCell).row + 1;
00504         *m_streamOut << "\">\n";
00505 #endif
00506         if (!doFullAllParagraphs(*(*itCell).paraList))
00507         {
00508             return false;
00509         }
00510 #if 0
00511         *m_streamOut << "</cell>\n";
00512 #endif
00513     }
00514 #if 0
00515     *m_streamOut << "</table>\n";
00516     *m_streamOut << "<p>\n"; // Re-open the "previous" paragraph ### TODO: do it correctly like for HTML
00517 #endif
00518     return true;
00519 }
00520 
00521 bool AbiWordWorker::makePicture(const FrameAnchor& anchor)
00522 {
00523     kdDebug(30506) << "New image/clipart: " << anchor.picture.koStoreName
00524         << " , " << anchor.picture.key.toString() << endl;
00525 
00526     const double height=anchor.frame.bottom - anchor.frame.top;
00527     const double width =anchor.frame.right  - anchor.frame.left;
00528 
00529     // TODO: we are only using the filename, not the rest of the key
00530     // TODO:  (bad if there are two images of the same name, but of a different key)
00531     *m_streamOut << "<image dataid=\"" << anchor.picture.key.filename() << "\"";
00532     *m_streamOut << " props= \"height:" << height << "pt;width:" << width << "pt\"";
00533     *m_streamOut << "/>"; // NO end of line!
00534     // TODO: other props for image
00535 
00536     m_mapPictureData[anchor.picture.koStoreName]=anchor.picture.key;
00537 
00538     return true;
00539 }
00540 
00541 void AbiWordWorker::writeAbiProps (const TextFormatting& formatLayout, const TextFormatting& format)
00542 {
00543     QString abiprops=textFormatToAbiProps(formatLayout,format,false);
00544 
00545     // Erase the last semi-comma (as in CSS2, semi-commas only separate instructions and do not terminate them)
00546     const int result=abiprops.findRev(";");
00547 
00548     if (result>=0)
00549     {
00550         // Remove the last semi-comma and the space thereafter
00551         abiprops.remove(result,2);
00552     }
00553 
00554     if (!abiprops.isEmpty())
00555     {
00556         *m_streamOut << " props=\"" << abiprops << "\"";
00557     }
00558 }
00559 
00560 void AbiWordWorker::processNormalText ( const QString &paraText,
00561     const TextFormatting& formatLayout,
00562     const FormatData& formatData)
00563 {
00564     // Retrieve text and escape it
00565     QString partialText=escapeAbiWordText(paraText.mid(formatData.pos,formatData.len));
00566 
00567     // Replace line feeds by line breaks
00568     int pos;
00569     while ((pos=partialText.find(QChar(10)))>-1)
00570     {
00571         partialText.replace(pos,1,"<br/>");
00572     }
00573 
00574     if (formatData.text.missing)
00575     {
00576         // It's just normal text, so we do not need a <c> element!
00577         *m_streamOut << partialText;
00578     }
00579     else
00580     { // Text with properties, so use a <c> element!
00581         *m_streamOut << "<c";
00582         writeAbiProps(formatLayout,formatData.text);
00583         *m_streamOut << ">" << partialText << "</c>";
00584     }
00585 }
00586 
00587 void AbiWordWorker::processVariable ( const QString&,
00588     const TextFormatting& formatLayout,
00589     const FormatData& formatData)
00590 {
00591     if (0==formatData.variable.m_type)
00592     {
00593         // As AbiWord's field is inflexible, we cannot make the date custom
00594         *m_streamOut << "<field type=\"date_ntdfl\"";
00595         writeAbiProps(formatLayout,formatData.text);
00596         *m_streamOut << "/>";
00597     }
00598     else if (2==formatData.variable.m_type)
00599     {
00600         // As AbiWord's field is inflexible, we cannot make the time custom
00601         *m_streamOut << "<field type=\"time\"";
00602         writeAbiProps(formatLayout,formatData.text);
00603         *m_streamOut << "/>";
00604     }
00605     else if (4==formatData.variable.m_type)
00606     {
00607         // As AbiWord's field is inflexible, we cannot make the time custom
00608         QString strFieldType;
00609         if (formatData.variable.isPageNumber())
00610         {
00611             strFieldType="page_number";
00612         }
00613         else if (formatData.variable.isPageCount())
00614         {
00615             strFieldType="page_count";
00616         }
00617         if (strFieldType.isEmpty())
00618         {
00619             // Unknown subtype, therefore write out the result
00620             *m_streamOut << formatData.variable.m_text;
00621         }
00622         else
00623         {
00624             *m_streamOut << "<field type=\"" << strFieldType <<"\"";
00625             writeAbiProps(formatLayout,formatData.text);
00626             *m_streamOut << "/>";
00627         }
00628     }
00629     else if (9==formatData.variable.m_type)
00630     {
00631         // A link
00632         *m_streamOut << "<a xlink:href=\""
00633             << escapeAbiWordText(formatData.variable.getHrefName())
00634             << "\"><c";  // In AbiWord, an anchor <a> has always a <c> child
00635         writeAbiProps(formatLayout,formatData.text);
00636         *m_streamOut << ">"
00637             << escapeAbiWordText(formatData.variable.getLinkName())
00638             << "</c></a>";
00639     }
00640 #if 0
00641                 else if (11==(*paraFormatDataIt).variable.m_type)
00642                 {
00643                     // Footnote
00644                     QString value = (*paraFormatDataIt).variable.getFootnoteValue();
00645                     bool automatic = (*paraFormatDataIt).variable.getFootnoteAuto();
00646                     QValueList<ParaData> *paraList = (*paraFormatDataIt).variable.getFootnotePara();
00647                     if( paraList )
00648                     {
00649                         QString fstr;
00650                         QValueList<ParaData>::ConstIterator it;
00651                         for (it=paraList->begin();it!=paraList->end();it++)
00652                             fstr += ProcessParagraphData( (*it).text, (*it).layout,(*it).formattingList);
00653                         str += "{\\super ";
00654                         str += automatic ? "\\chftn " : value;
00655                         str += "{\\footnote ";
00656                         str += "{\\super ";
00657                         str += automatic ? "\\chftn " : value;
00658                         str += fstr;
00659                         str += " }";
00660                         str += " }";
00661                         str += " }";
00662                     }
00663                 }
00664 #endif
00665     else
00666     {
00667         // Generic variable
00668         *m_streamOut << formatData.variable.m_text;
00669     }
00670 }
00671 
00672 void AbiWordWorker::processAnchor ( const QString&,
00673     const TextFormatting& /*formatLayout*/, //TODO
00674     const FormatData& formatData)
00675 {
00676     // We have an image or a table
00677     if ( (2==formatData.frameAnchor.type) // <IMAGE> or <PICTURE>
00678         || (5==formatData.frameAnchor.type) ) // <CLIPART>
00679     {
00680         makePicture(formatData.frameAnchor);
00681     }
00682     else if (6==formatData.frameAnchor.type)
00683     {
00684         makeTable(formatData.frameAnchor);
00685     }
00686     else
00687     {
00688         kdWarning(30506) << "Unsupported anchor type: "
00689             << formatData.frameAnchor.type << endl;
00690     }
00691 }
00692 
00693 void AbiWordWorker::processParagraphData ( const QString &paraText,
00694     const TextFormatting& formatLayout,
00695     const ValueListFormatData &paraFormatDataList)
00696 {
00697     if ( paraText.length () > 0 )
00698     {
00699         ValueListFormatData::ConstIterator  paraFormatDataIt;
00700 
00701         for ( paraFormatDataIt = paraFormatDataList.begin ();
00702               paraFormatDataIt != paraFormatDataList.end ();
00703               paraFormatDataIt++ )
00704         {
00705             if (1==(*paraFormatDataIt).id)
00706             {
00707                 processNormalText(paraText, formatLayout, (*paraFormatDataIt));
00708             }
00709             else if (4==(*paraFormatDataIt).id)
00710             {
00711                 processVariable(paraText, formatLayout, (*paraFormatDataIt));
00712             }
00713             else if (6==(*paraFormatDataIt).id)
00714             {
00715                 processAnchor(paraText, formatLayout, (*paraFormatDataIt));
00716             }
00717         }
00718     }
00719 }
00720 
00721 QString AbiWordWorker::layoutToCss(const LayoutData& layoutOrigin,
00722     const LayoutData& layout, const bool force) const
00723 {
00724     QString props;
00725 
00726     if (force || (layoutOrigin.alignment!=layout.alignment))
00727     {
00728         // Check if the current alignment is a valid one for AbiWord.
00729         if ((layout.alignment == "left") || (layout.alignment == "right")
00730             || (layout.alignment == "center")  || (layout.alignment == "justify"))
00731         {
00732             props += "text-align:";
00733             props += layout.alignment;
00734             props += "; ";
00735         }
00736         else if (layout.alignment == "auto")
00737         {
00738             // We assume a left alignment as AbiWord is not really bi-di (and this filter even less.)
00739             props += "text-align:left; ";
00740         }
00741         else
00742         {
00743             kdWarning(30506) << "Unknown alignment: " << layout.alignment << endl;
00744         }
00745     }
00746 
00747     // TODO/FIXME: what if all tabulators must be erased?
00748 #if 0
00749     // DEPRECATED!
00750     if (!layout.tabulator.isEmpty()
00751         && (force || (layoutOrigin.tabulator!=layout.tabulator)))
00752     {
00753         props += "tabstops:";
00754         props += layout.tabulator;
00755         props += "; ";
00756     }
00757 #endif
00758     if (!layout.tabulatorList.isEmpty()
00759         && (force || (layoutOrigin.tabulatorList!=layout.tabulatorList) ))
00760     {
00761         props += "tabstops:";
00762         bool first=true;
00763         TabulatorList::ConstIterator it;
00764         TabulatorList::ConstIterator end(layout.tabulatorList.end());
00765         for (it=layout.tabulatorList.begin();it!=end;++it)
00766         {
00767             if (first)
00768             {
00769                 first=false;
00770             }
00771             else
00772             {
00773                 props += ",";
00774             }
00775             props += QString::number((*it).m_ptpos);
00776             props += "pt";
00777 
00778             switch ((*it).m_type)
00779             {
00780                 case 0:  props += "/L"; break;
00781                 case 1:  props += "/C"; break;
00782                 case 2:  props += "/R"; break;
00783                 case 3:  props += "/D"; break;
00784                 default: props += "/L";
00785             }
00786 
00787             props += "0"; // No filling
00788         }
00789         props += "; ";
00790     }
00791 
00792     if ((layout.indentLeft>=0.0)
00793         && (force || (layoutOrigin.indentLeft!=layout.indentLeft)))
00794     {
00795         props += QString("margin-left:%1pt; ").arg(layout.indentLeft);
00796     }
00797 
00798     if ((layout.indentRight>=0.0)
00799         && (force || (layoutOrigin.indentRight!=layout.indentRight)))
00800     {
00801         props += QString("margin-right:%1pt; ").arg(layout.indentRight);
00802     }
00803 
00804     if (force || (layoutOrigin.indentLeft!=layout.indentLeft))
00805     {
00806         props += "text-indent: ";
00807         props += QString::number(layout.indentFirst);
00808         props += "pt; ";
00809     }
00810 
00811     if ((layout.marginBottom>=0.0)
00812         && ( force || ( layoutOrigin.marginBottom != layout.marginBottom ) ) )
00813     {
00814        props += QString("margin-bottom:%1pt; ").arg(layout.marginBottom);
00815     }
00816 
00817     if ((layout.marginTop>=0.0)
00818         && ( force || ( layoutOrigin.marginTop != layout.marginTop ) ) )
00819     {
00820        props += QString("margin-top:%1pt; ").arg(layout.marginTop);
00821     }
00822 
00823     if (force
00824         || ( layoutOrigin.lineSpacingType != layout.lineSpacingType )
00825         || ( layoutOrigin.lineSpacing != layout.lineSpacing ) )
00826     {
00827         switch ( layout.lineSpacingType )
00828         {
00829         case LayoutData::LS_CUSTOM:
00830             {
00831                 // We have a custom line spacing (in points). However AbiWord cannot do it, so transform in "at-least"
00832                 props += "line-height=:";
00833                 props += QString::number( layout.lineSpacing ); // ### TODO: rounding?
00834                 props += "pt+; ";
00835                 break;
00836             }
00837         case LayoutData::LS_SINGLE:
00838             {
00839                 props += "line-height:1.0; "; // One
00840                 break;
00841             }
00842         case LayoutData::LS_ONEANDHALF:
00843             {
00844                 props += "line-height:1.5; "; // One-and-half
00845                 break;
00846             }
00847         case LayoutData::LS_DOUBLE:
00848             {
00849                 props += "line-height:2.0; "; // Two
00850                 break;
00851             }
00852         case LayoutData::LS_MULTIPLE:
00853             {
00854                 props += "line-height:";
00855                 props += QString::number( layout.lineSpacing ); // ### TODO: rounding?
00856                 props += "; ";
00857                 break;
00858             }
00859         case LayoutData::LS_FIXED:
00860             {
00861                 // We have a fixed line height (in points)
00862                 props += "line-height:";
00863                 props += QString::number( layout.lineSpacing ); // ### TODO: rounding?
00864                 props += "pt; ";
00865                 break;
00866             }
00867         case LayoutData::LS_ATLEAST:
00868             {
00869                 // We have an "at-least" line height (in points)
00870                 props += "line-height=:";
00871                 props += QString::number( layout.lineSpacing ); // ### TODO: rounding?
00872                 props += "pt+; "; // The + makes the difference
00873                 break;
00874             }
00875         default:
00876             {
00877                 kdWarning(30506) << "Unsupported lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl;
00878                 break;
00879             }
00880         }
00881     }
00882 
00883     // Add all AbiWord properties collected in the <FORMAT> element
00884     props += textFormatToAbiProps(layoutOrigin.formatData.text,layout.formatData.text,force);
00885 
00886     return props;
00887 }
00888 
00889 bool AbiWordWorker::doFullParagraph(const QString& paraText, const LayoutData& layout,
00890     const ValueListFormatData& paraFormatDataList)
00891 {
00892     QString style=layout.styleName;
00893 
00894     const LayoutData& styleLayout=m_styleMap[style];
00895 
00896     QString props=layoutToCss(styleLayout,layout,false);
00897 
00898     *m_streamOut << "<p";
00899     if (!style.isEmpty())
00900     {
00901         *m_streamOut << " style=\"" << EscapeXmlText(style,true,true) << "\"";
00902     }
00903     if (!props.isEmpty())
00904     {
00905         // Find the last semi-comma
00906         // Note: as in CSS2, semi-commas only separates instructions (like in PASCAL) and do not terminate them (like in C)
00907         const int result=props.findRev(";");
00908         if (result>=0)
00909         {
00910             // Remove the last semi-comma and the space thereafter
00911             props.remove(result,2);
00912         }
00913 
00914         *m_streamOut << " props=\"" << props << "\"";
00915     }
00916     *m_streamOut << ">";  //Warning: No trailing white space or else it's in the text!!!
00917 
00918     // Before processing the text, test if we have a page break
00919     if (layout.pageBreakBefore)
00920     {
00921         // We have a page break before the paragraph
00922         *m_streamOut << "<pbr/>";
00923     }
00924 
00925     processParagraphData(paraText, layout.formatData.text, paraFormatDataList);
00926 
00927     // Before closing the paragraph, test if we have a page break
00928     if (layout.pageBreakAfter)
00929     {
00930         // We have a page break after the paragraph
00931         *m_streamOut << "<pbr/>";
00932     }
00933 
00934     *m_streamOut << "</p>\n";
00935     return true;
00936 }
00937 
00938 bool AbiWordWorker::doFullDefineStyle(LayoutData& layout)
00939 {
00940     //Register style in the style map
00941     m_styleMap[layout.styleName]=layout;
00942 
00943     *m_streamOut << "<s";
00944 
00945     // TODO: cook the style name to the standard style names in AbiWord
00946     *m_streamOut << " name=\"" << EscapeXmlText(layout.styleName,true,true) << "\"";
00947     *m_streamOut << " followedby=\"" << EscapeXmlText(layout.styleFollowing,true,true) << "\"";
00948 
00949     if ( (layout.counter.numbering == CounterData::NUM_CHAPTER)
00950         && (layout.counter.depth<10) )
00951     {
00952         *m_streamOut << " level=\"";
00953         *m_streamOut << QString::number(layout.counter.depth+1,10);
00954         *m_streamOut << "\"";
00955     }
00956 
00957     QString abiprops=layoutToCss(layout,layout,true);
00958 
00959     const int result=abiprops.findRev(";");
00960     if (result>=0)
00961     {
00962         // Remove the last semi-comma and the space thereafter
00963         abiprops.remove(result,2);
00964     }
00965 
00966     *m_streamOut << " props=\"" << abiprops << "\"";
00967 
00968     *m_streamOut << "/>\n";
00969 
00970     return true;
00971 }
00972 
00973 bool AbiWordWorker::doFullPaperFormat(const int format,
00974             const double width, const double height, const int orientation)
00975 {
00976     QString outputText = "<pagesize ";
00977 
00978     switch (format)
00979     {
00980         // ISO A formats
00981         case PG_DIN_A0: // ISO A0
00982         case PG_DIN_A1: // ISO A1
00983         case PG_DIN_A2: // ISO A2
00984         case PG_DIN_A3: // ISO A3
00985         case PG_DIN_A4: // ISO A4
00986         case PG_DIN_A5: // ISO A5
00987         case PG_DIN_A6: // ISO A6
00988         // ISO B formats
00989         case PG_DIN_B0: // ISO B0
00990         case PG_DIN_B1: // ISO B1
00991         case PG_DIN_B2: // ISO B2
00992         case PG_DIN_B3: // ISO B3
00993         case PG_DIN_B4: // ISO B4
00994         case PG_DIN_B5: // ISO B5
00995         case PG_DIN_B6: // ISO B6
00996         // American formats
00997         case PG_US_LETTER: // US Letter
00998         case PG_US_LEGAL:  // US Legal
00999         {
01000             QString pagetype=KoPageFormat::formatString(KoFormat(format));
01001             outputText+="pagetype=\"";
01002             outputText+=pagetype;
01003 
01004             QString strWidth, strHeight, strUnits;
01005             KWEFUtil::GetNativePaperFormat(format, strWidth, strHeight, strUnits);
01006             outputText+="\" width=\"";
01007             outputText+=strWidth;
01008             outputText+="\" height=\"";
01009             outputText+=strHeight;
01010             outputText+="\" units=\"";
01011             outputText+=strUnits;
01012             outputText+="\" ";
01013             break;
01014         }
01015         case PG_US_EXECUTIVE: // US Executive (does not exists in AbiWord!)
01016         {
01017             // FIXME/TODO: AbiWord (CVS 2001-04-25) seems not to like custom formats, so avoid them for now!
01018 #if 0
01019             outputText += "pagetype=\"Custom\" width=\"7.5\" height=\"10.0\" units=\"inch\" ";
01020 #else
01021             // As replacement, use the slightly bigger "letter" format.
01022             outputText += "pagetype=\"Letter\" width=\"8.5\" height=\"11.0\" units=\"inch\" ";
01023 #endif
01024             break;
01025         }
01026         // Other format not supported yet by AbiWord CVS 2001-04-25)
01027         case PG_DIN_A7: // ISO A7
01028         case PG_DIN_A8: // ISO A8
01029         case PG_DIN_A9: // ISO A9
01030         case PG_DIN_B10: // ISO B10
01031         // Other formats
01032         case PG_SCREEN: // Screen
01033         case PG_CUSTOM: // Custom
01034         default:
01035         {
01036              // FIXME/TODO: AbiWord (CVS 2001-04-25) seems not to like custom formats, so avoid them for now!
01037             if ((width<=1.0) || (height<=1.0) || true)
01038             {
01039                 // Height or width is ridiculous, so assume A4 format
01040                 outputText += "pagetype=\"A4\" width=\"21.0\" height=\"29.7\" units=\"cm\" ";
01041             }
01042             else
01043             {   // We prefer to use inches, as to limit rounding errors (page size is in points!)
01044                 outputText += QString("pagetype=\"Custom\" width=\"%1\" height=\"%2\" units=\"inch\" ").arg(width/72.0).arg(height/72.0);
01045             }
01046             break;
01047         }
01048     }
01049 
01050     outputText += "orientation=\"";
01051     if (1==orientation)
01052     {
01053         outputText += "landscape";
01054     }
01055     else
01056     {
01057         outputText += "portrait";
01058     }
01059     outputText += "\" ";
01060 
01061     outputText += "page-scale=\"1.0\"/>\n"; // KWord has no page scale, so assume 100%
01062 
01063     m_pagesize=outputText;
01064     return true;
01065 }
01066 
01067 bool AbiWordWorker::doFullPaperBorders (const double top, const double left,
01068     const double bottom, const double right)
01069 {
01070     m_paperBorderTop=top;
01071     m_paperBorderLeft=left;
01072     m_paperBorderBottom=bottom;
01073     m_paperBorderRight=right;
01074     return true;
01075 }
01076 
01077 bool AbiWordWorker::doCloseHead(void)
01078 {
01079     if (!m_pagesize.isEmpty())
01080     {
01081         *m_streamOut << m_pagesize;
01082     }
01083     return true;
01084 }
01085 
01086 bool AbiWordWorker::doOpenSpellCheckIgnoreList (void)
01087 {
01088     kdDebug(30506) << "AbiWordWorker::doOpenSpellCheckIgnoreList" << endl;
01089     m_inIgnoreWords=false; // reset
01090     return true;
01091 }
01092 
01093 bool AbiWordWorker::doCloseSpellCheckIgnoreList (void)
01094 {
01095     kdDebug(30506) << "AbiWordWorker::doCloseSpellCheckIgnoreList" << endl;
01096     if (m_inIgnoreWords)
01097         *m_streamOut << "</ignorewords>\n";
01098     return true;
01099 }
01100 
01101 bool AbiWordWorker::doFullSpellCheckIgnoreWord (const QString& ignoreword)
01102 {
01103     kdDebug(30506) << "AbiWordWorker::doFullSpellCheckIgnoreWord: " << ignoreword << endl;
01104     if (!m_inIgnoreWords)
01105     {
01106         *m_streamOut << "<ignorewords>\n";
01107         m_inIgnoreWords=true;
01108     }
01109     *m_streamOut << " <iw>" << ignoreword << "</iw>\n";
01110     return true;
01111 }
01112 
01113 // Similar to QDateTime::toString, but guaranteed *not* to be translated
01114 QString AbiWordWorker::transformToTextDate(const QDateTime& dt)
01115 {
01116     if (dt.isValid())
01117     {
01118         QString result;
01119 
01120         const QDate date(dt.date());
01121 
01122         const char* dayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
01123         const int dow = date.dayOfWeek() - 1;
01124         if ((dow<0) || (dow>6))
01125             result += "Mon"; // Unknown day, rename it Monday.
01126         else
01127             result += dayName[dow];
01128         result += ' ';
01129 
01130         const char* monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
01131         const int month = date.month() - 1;
01132         if ((month<0) || (month>11))
01133             result += "Jan"; // Unknown month, rename it January
01134         else
01135             result += monthName[month];
01136         result += ' ';
01137 
01138         QString temp;
01139 
01140         temp = "00";
01141         temp += QString::number(date.day(), 10);
01142         result += temp.right(2);
01143         result += ' ';
01144 
01145         const QTime time(dt.time());
01146 
01147         temp = "00";
01148         temp += QString::number(time.hour(), 10);
01149         result += temp.right(2);
01150         result += ':';
01151 
01152         temp = "00";
01153         temp += QString::number(time.minute(), 10);
01154         result += temp.right(2);
01155         result += ':';
01156 
01157         temp = "00";
01158         temp += QString::number(time.second(), 10);
01159         result += temp.right(2);
01160         result += ' ';
01161 
01162         temp = "0000";
01163         temp += QString::number(date.year(), 10);
01164         result += temp.right(4);
01165 
01166         return result;
01167     }
01168     else
01169     {
01170         // Invalid, so give back 1970-01-01
01171         return "Thu Jan 01 00:00:00 1970";
01172     }
01173 }
01174 
01175 bool AbiWordWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo)
01176 {
01177     m_docInfo=docInfo;
01178 
01179     *m_streamOut << "<metadata>\n";
01180 
01181     // First all Dublin Core data
01182     *m_streamOut << "<m key=\"dc.format\">application/x-abiword</m>\n";
01183     if (!m_docInfo.title.isEmpty())
01184     {
01185         *m_streamOut << "<m key=\"dc.title\">" << escapeAbiWordText(m_docInfo.title) << "</m>\n";
01186     }
01187     if (!m_docInfo.abstract.isEmpty())
01188     {
01189         *m_streamOut << "<m key=\"dc.description\">" << escapeAbiWordText(m_docInfo.abstract) << "</m>\n";
01190     }
01191 
01192     if ( !m_docInfo.keywords.isEmpty() )
01193     {
01194         *m_streamOut << "<m key=\"abiword.keywords\">" << escapeAbiWordText(m_docInfo.keywords) << "</m>\n";
01195     }
01196     if ( !m_docInfo.subject.isEmpty() )
01197     {
01198         *m_streamOut << "<m key=\"dc.subject\">" << escapeAbiWordText(m_docInfo.subject) << "</m>\n";
01199     }
01200 
01201     // Say who we are (with the CVS revision number) in case we have a bug in our filter output!
01202     *m_streamOut << "<m key=\"abiword.generator\">KWord Export Filter";
01203 
01204     QString strVersion("$Revision: 508787 $");
01205     // Remove the dollar signs
01206     //  (We don't want that the version number changes if the AbiWord file is itself put in a CVS storage.)
01207     *m_streamOut << strVersion.mid(10).remove('$');
01208 
01209     *m_streamOut << "</m>\n";
01210 
01211     QDateTime now (QDateTime::currentDateTime(Qt::UTC)); // current time in UTC
01212     *m_streamOut << "<m key=\"abiword.date_last_changed\">"
01213          << escapeAbiWordText(transformToTextDate(now))
01214          << "</m>\n";
01215 
01216     *m_streamOut << "</metadata>\n";
01217 
01218     return true;
01219 }
01220 
01221 
01222 // ==========================================================================================
01223 
01224 ABIWORDExport::ABIWORDExport(KoFilter */*parent*/, const char */*name*/, const QStringList &) :
01225                      KoFilter() {
01226 }
01227 
01228 KoFilter::ConversionStatus ABIWORDExport::convert( const QCString& from, const QCString& to )
01229 {
01230     if ( to != "application/x-abiword" || from != "application/x-kword" )
01231     {
01232         return KoFilter::NotImplemented;
01233     }
01234 
01235     // We need KimageIO's help in AbiWordWorker::convertUnknownImage
01236     KImageIO::registerFormats();
01237 
01238     AbiWordWorker* worker=new AbiWordWorker();
01239 
01240     if (!worker)
01241     {
01242         kdError(30506) << "Cannot create Worker! Aborting!" << endl;
01243         return KoFilter::StupidError;
01244     }
01245 
01246     KWEFKWordLeader* leader=new KWEFKWordLeader(worker);
01247 
01248     if (!leader)
01249     {
01250         kdError(30506) << "Cannot create Worker! Aborting!" << endl;
01251         delete worker;
01252         return KoFilter::StupidError;
01253     }
01254 
01255     KoFilter::ConversionStatus result=leader->convert(m_chain,from,to);
01256 
01257     delete leader;
01258     delete worker;
01259 
01260     return result;
01261 }
KDE Home | KDE Accessibility Home | Description of Access Keys