filters

ExportFilter.cc

00001 /*
00002    This file is part of the KDE project
00003    Copyright (C) 2001, 2002, 2004 Nicolas GOUTTE <goutte@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include <qstring.h>
00022 #include <qtextcodec.h>
00023 #include <qfile.h>
00024 #include <qfileinfo.h>
00025 #include <qdir.h>
00026 #include <qpicture.h>
00027 
00028 #include <klocale.h>
00029 #include <kdebug.h>
00030 
00031 #include <KWEFUtil.h>
00032 #include <KWEFBaseWorker.h>
00033 
00034 #include "ExportFilter.h"
00035 
00036 QString HtmlWorker::escapeHtmlText(const QString& strText) const
00037 {
00038     // Escape quotes (needed in attributes)
00039     // Do not escape apostrophs (only allowed in XHTML!)
00040     return KWEFUtil::EscapeSgmlText(getCodec(),strText,true,false);
00041 }
00042 
00043 bool HtmlWorker::makeTable(const FrameAnchor& anchor)
00044 {
00045     *m_streamOut << "<table>\n";
00046     *m_streamOut << "<tbody>\n";
00047 
00048     QValueList<TableCell>::ConstIterator itCell;
00049 
00050     int rowCurrent=0;
00051     *m_streamOut << "<tr>\n";
00052 
00053 
00054     for (itCell=anchor.table.cellList.begin();
00055         itCell!=anchor.table.cellList.end(); itCell++)
00056     {
00057         if (rowCurrent!=(*itCell).row)
00058         {
00059             rowCurrent=(*itCell).row;
00060             *m_streamOut << "</tr>\n<tr>\n";
00061         }
00062 
00063         *m_streamOut << "<td";
00064         if ( (*itCell).m_rows > 1 )
00065             *m_streamOut << " rowspan=\"" << (*itCell).m_rows << "\"";
00066         if ( (*itCell).m_cols > 1 )
00067             *m_streamOut << " colspan=\"" << (*itCell).m_cols << "\"";
00068         *m_streamOut << ">\n";
00069 
00070         if (!doFullAllParagraphs(*(*itCell).paraList))
00071         {
00072             return false;
00073         }
00074 
00075         *m_streamOut << "</td>\n";
00076     }
00077 
00078     *m_streamOut << "</tr>\n";
00079     *m_streamOut << "</tbody>\n";
00080     *m_streamOut << "</table>\n";
00081 
00082     return true;
00083 }
00084 
00085 QString HtmlWorker::getAdditionalFileName(const QString& additionalName)
00086 {
00087     kdDebug(30503) << "HtmlWorker::getAdditionalFileName " << additionalName << endl;
00088 
00089     QDir dir(m_strFileDir);
00090     kdDebug(30503) << "Base directory: " << m_strFileDir << endl;
00091 
00092     if (!dir.exists(m_strSubDirectoryName))
00093     {
00094         // Make the directory, as it does not exist yet!
00095         kdDebug(30503) << "Creating directory: " << m_strSubDirectoryName << endl;
00096         dir.mkdir(m_strSubDirectoryName);
00097     }
00098 
00099     QString strFileName(m_strSubDirectoryName);
00100     strFileName+="/";
00101     const int result=additionalName.findRev("/");
00102     if (result>=0)
00103     {
00104         strFileName+=additionalName.mid(result+1);
00105     }
00106     else
00107     {
00108         strFileName+=additionalName;
00109     }
00110 
00111     // Now, we have to create a backup file.
00112 
00113     QString strBackupName(strFileName);
00114     strBackupName+="~";
00115     kdDebug(30503) << "Remove backup file: " << strBackupName << endl;
00116     // We need to remove the backup file, as not all filesystems or ports can do it themselves on a rename.
00117     dir.remove(strBackupName);
00118     kdDebug(30503) << "Moving file: " << additionalName << " => " << strBackupName << endl;
00119     dir.rename(strFileName,strBackupName);
00120 
00121     return strFileName;
00122 }
00123 
00124 bool HtmlWorker::makeImage(const FrameAnchor& anchor)
00125 {
00126     const QString strImageName(getAdditionalFileName(anchor.picture.koStoreName));
00127 
00128     QString strImagePath(m_strFileDir);
00129     strImagePath+='/';
00130     strImagePath+=strImageName;
00131 
00132     QByteArray image;
00133 
00134     kdDebug(30503) << "Image " << anchor.picture.koStoreName << " will be written in " << strImageName << endl;
00135 
00136     if (loadSubFile(anchor.picture.koStoreName,image))
00137     {
00138         bool writePicture = false;
00139 
00140         const double height = anchor.frame.bottom - anchor.frame.top;
00141         const double width  = anchor.frame.right  - anchor.frame.left;
00142 
00143         const int pos = anchor.picture.koStoreName.findRev( '.' );
00144         QString extension;
00145         if ( pos > -1 )
00146             extension = anchor.picture.koStoreName.mid( pos+1 ).lower();
00147 
00148         if ( extension == "png" || extension == "jpeg" || extension == "jpg" || extension == "gif"
00149             || extension == "bmp" ) // A few file types known by all HTML user agents
00150         {
00151             *m_streamOut << "<img "; // This is an empty element!
00152             *m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" ";
00153             *m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\"";
00154             *m_streamOut << (isXML()?"/>":">");
00155             writePicture = true;
00156         }
00157         else if ( extension == "svg" )
00158         {
00159             // Save picture as SVG
00160             *m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\"";
00161             *m_streamOut << " type=\"image/svg+xml\"";
00162             *m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n";
00163             *m_streamOut << "</object>"; // <object> is *not* an empty element in HTML!
00164             writePicture = true;
00165         }
00166         else if ( extension == "qpic" )
00167         {
00168 
00169             QPicture picture;
00170 
00171             QIODevice* io=getSubFileDevice(anchor.picture.koStoreName);
00172             if (!io)
00173             {
00174                 // NO message error, as there must be already one
00175                 return false;
00176             }
00177 
00178             // TODO: if we have alreasy SVG, do *not* go through QPicture!
00179             if (picture.load(io))
00180             {
00181 
00182                 // Save picture as SVG
00183                 *m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\"";
00184                 *m_streamOut << " type=\"image/svg+xml\"";
00185                 *m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n";
00186                 *m_streamOut << "</object>"; // <object> is *not* an empty element in HTML!
00187                 // TODO: other props for image
00188 
00189                 kdDebug(30506) << "Trying to save clipart to " << strImageName << endl;
00190                 if (!picture.save(strImagePath,"svg"))
00191                 {
00192                     kdError(30506) << "Could not save clipart: "  << anchor.picture.koStoreName
00193                         << " to " << strImageName << endl;
00194                     return false;
00195                 }
00196 
00197             }
00198         }
00199         else
00200         {
00201             // ### TODO: avoid loading the picture 2 times
00202             image.resize( 0 );
00203             if ( ! loadAndConvertToImage( anchor.picture.koStoreName, extension, "PNG", image ) )
00204             {
00205                 kdWarning(30503) << "Could not convert picture to PNG!" << endl;
00206                 return false;
00207             }
00208             *m_streamOut << "<img "; // This is an empty element!
00209             *m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" ";
00210             *m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\"";
00211             *m_streamOut << (isXML()?"/>":">");
00212             writePicture = true;
00213         }
00214 
00215         // Do we still need to write the original picture?
00216         if ( writePicture )
00217         {
00218             QFile file(strImagePath);
00219 
00220             if ( !file.open (IO_WriteOnly) )
00221             {
00222                 kdError(30503) << "Unable to open image output file!" << endl;
00223                 return false;
00224             }
00225 
00226             file.writeBlock(image);
00227             file.close();
00228         }
00229     }
00230     else
00231     {
00232         kdWarning(30503) << "Unable to load picture " << anchor.picture.koStoreName << endl;
00233     }
00234 
00235     return true;
00236 }
00237 
00238 void HtmlWorker::formatTextParagraph(const QString& strText,
00239  const FormatData& formatOrigin, const FormatData& format)
00240 {
00241     QString strEscaped(escapeHtmlText(strText));
00242 
00243     // Replace line feeds by line breaks
00244     int pos;
00245     QString strBr(isXML()?QString("<br />"):QString("<br>"));
00246     while ((pos=strEscaped.find(QChar(10)))>-1)
00247     {
00248         strEscaped.replace(pos,1,strBr);
00249     }
00250 
00251     if (!format.text.missing)
00252     {
00253         // Opening elements
00254         openSpan(formatOrigin,format);
00255     }
00256 
00257     // TODO: first and last characters of partialText should not be a space (white space problems!)
00258     // TODO: replace multiples spaces by non-breaking spaces!
00259 
00260     if (strText==" ")
00261     {//Just a space as text. Therefore we must use a non-breaking space.
00262         *m_streamOut << "&nbsp;";
00263         // TODO/FIXME: only needed for <p>&nbsp;</p>, but not for </span> <span>
00264     }
00265     else
00266     {
00267         *m_streamOut << strEscaped;
00268     }
00269 
00270     if (!format.text.missing)
00271     {
00272         // Closing elements
00273         closeSpan(formatOrigin,format);
00274     }
00275 }
00276 
00277 void HtmlWorker::ProcessParagraphData (const QString& strTag, const QString &paraText,
00278     const LayoutData& layout, const ValueListFormatData &paraFormatDataList)
00279 {
00280     if (paraText.isEmpty() && paraFormatDataList.first().id != 6)
00281     {
00282         openParagraph(strTag,layout);
00283         *m_streamOut << "&nbsp;" ; // A paragraph can never be empty in HTML
00284         closeParagraph(strTag,layout);
00285     }
00286     else
00287     {
00288         bool paragraphNotOpened=true;
00289 
00290         ValueListFormatData::ConstIterator  paraFormatDataIt;
00291 
00292         QString partialText;
00293 
00294         for ( paraFormatDataIt = paraFormatDataList.begin ();
00295               paraFormatDataIt != paraFormatDataList.end ();
00296               paraFormatDataIt++ )
00297         {
00298             if (1==(*paraFormatDataIt).id)
00299             {
00300                 //Retrieve text
00301                 partialText=paraText.mid ( (*paraFormatDataIt).pos, (*paraFormatDataIt).len );
00302                 // For normal text, we need an opened paragraph
00303                 if (paragraphNotOpened)
00304                 {
00305                     openParagraph(strTag,layout,partialText.ref(0).direction());
00306                     paragraphNotOpened=false;
00307                 }
00308                 formatTextParagraph(partialText,layout.formatData,*paraFormatDataIt);
00309             }
00310             else if (4==(*paraFormatDataIt).id)
00311             {
00312                 // For variables, we need an opened paragraph
00313                 if (paragraphNotOpened)
00314                 {
00315                     openParagraph(strTag,layout);
00316                     paragraphNotOpened=false;
00317                 }
00318                 if (9==(*paraFormatDataIt).variable.m_type)
00319                 {
00320                     // A link
00321                     *m_streamOut << "<a href=\""
00322                         << escapeHtmlText((*paraFormatDataIt).variable.getHrefName())
00323                         << "\">"
00324                         << escapeHtmlText((*paraFormatDataIt).variable.getLinkName())
00325                         << "</a>";
00326                 }
00327                 else
00328                 {
00329                     // Generic variable
00330                     *m_streamOut << escapeHtmlText((*paraFormatDataIt).variable.m_text);
00331                 }
00332             }
00333             else if (6==(*paraFormatDataIt).id)
00334             {
00335                 // We have an image, a clipart or a table
00336 
00337                 if (6==(*paraFormatDataIt).frameAnchor.type)
00338                 {
00339                     // We have a table
00340                     // But first, we must sure that the paragraph is not opened.
00341                     if (!paragraphNotOpened)
00342                     {
00343                         // The paragraph was opened, so close it.
00344                         closeParagraph(strTag,layout);
00345                     }
00346                     makeTable((*paraFormatDataIt).frameAnchor);
00347                     // The paragraph will need to be opened again
00348                     paragraphNotOpened=true;
00349 
00350                 }
00351 
00352                 else if ( ( 2 == (*paraFormatDataIt).frameAnchor.type )
00353                     || ( 5 == (*paraFormatDataIt).frameAnchor.type ) )
00354                 {
00355                     // <img> and <object> need to be in a paragraph
00356                     if (paragraphNotOpened)
00357                     {
00358                         openParagraph( strTag, layout,partialText.ref(0). direction() );
00359                         paragraphNotOpened=false;
00360                     }
00361                     makeImage((*paraFormatDataIt).frameAnchor);
00362                 }
00363                 else
00364                 {
00365                     kdWarning(30503) << "Unknown anchor type: "
00366                         << (*paraFormatDataIt).frameAnchor.type << endl;
00367                 }
00368             }
00369         }
00370         if (!paragraphNotOpened)
00371         {
00372             // The paragraph was opened, so close it.
00373             closeParagraph(strTag,layout);
00374         }
00375     }
00376 }
00377 
00378 bool HtmlWorker::doFullParagraph(const QString& paraText,
00379     const LayoutData& layout, const ValueListFormatData& paraFormatDataList)
00380 {
00381     kdDebug(30503) << "Entering HtmlWorker::doFullParagraph" << endl << paraText << endl;
00382     QString strParaText=paraText;
00383     QString strTag; // Tag that will be written.
00384 
00385     if ( layout.counter.numbering == CounterData::NUM_LIST )
00386     {
00387         const uint layoutDepth=layout.counter.depth+1; // Word's depth starts at 0!
00388         const uint listDepth=m_listStack.size();
00389         // We are in a list, but has it the right depth?
00390         if (layoutDepth>listDepth)
00391         {
00392             ListInfo newList;
00393             newList.m_typeList=layout.counter.style;
00394             for (uint i=listDepth; i<layoutDepth; i++)
00395             {
00396                 *m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList);
00397                 m_listStack.push(newList);
00398             }
00399         }
00400         else if (layoutDepth<listDepth)
00401         {
00402             for (uint i=listDepth; i>layoutDepth; i--)
00403             {
00404                 ListInfo oldList=m_listStack.pop();
00405                 if (oldList.m_orderedList)
00406                 {
00407                     *m_streamOut << "</ol>\n";
00408                 }
00409                 else
00410                 {
00411                     *m_streamOut << "</ul>\n";
00412                 }
00413             }
00414         }
00415 
00416         // We have a list but does it have the right type?
00417         if ( layout.counter.style!=m_listStack.top().m_typeList)
00418         {
00419             // No, then close the previous list
00420             ListInfo oldList=m_listStack.pop();
00421             if (oldList.m_orderedList)
00422             {
00423                 *m_streamOut << "</ol>\n";
00424             }
00425             else
00426             {
00427                 *m_streamOut << "</ul>\n";
00428             }
00429             ListInfo newList;
00430             *m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList);
00431             newList.m_typeList=layout.counter.style;
00432             m_listStack.push(newList);
00433         }
00434 
00435         // TODO: with Cascaded Style Sheet, we could add the exact counter type that we want
00436         strTag="li";
00437     }
00438     else
00439     {
00440         // Close all open lists first
00441         if (!m_listStack.isEmpty())
00442         {
00443             for (uint i=m_listStack.size(); i>0; i--)
00444             {
00445                 ListInfo oldList=m_listStack.pop();
00446                 if (oldList.m_orderedList)
00447                 {
00448                     *m_streamOut << "</ol>\n";
00449                 }
00450                 else
00451                 {
00452                     *m_streamOut << "</ul>\n";
00453                 }
00454             }
00455         }
00456         if ( (layout.counter.numbering == CounterData::NUM_CHAPTER)
00457             && (layout.counter.depth<6) )
00458         {
00459             strTag=QString("h%1").arg(layout.counter.depth + 1); // H1 ... H6
00460         }
00461         else
00462         {
00463             strTag="p";
00464         }
00465     }
00466 
00467     ProcessParagraphData(strTag, strParaText, layout, paraFormatDataList);
00468 
00469     kdDebug(30503) << "Quiting HtmlWorker::doFullParagraph" << endl;
00470     return true;
00471 }
00472 
00473 bool HtmlWorker::doOpenFile(const QString& filenameOut, const QString& /*to*/)
00474 {
00475     m_ioDevice=new QFile(filenameOut);
00476 
00477     if (!m_ioDevice)
00478     {
00479         kdError(30503) << "No output file! Aborting!" << endl;
00480         return false;
00481     }
00482 
00483     if ( !m_ioDevice->open (IO_WriteOnly) )
00484     {
00485         kdError(30503) << "Unable to open output file!" << endl;
00486         return false;
00487     }
00488 
00489     m_streamOut=new QTextStream(m_ioDevice);
00490 
00491     if (!getCodec())
00492     {
00493         kdError(30503) << "Could not create QTextCodec! Aborting" << endl;
00494         return false;
00495     }
00496 
00497     kdDebug(30503) << "Charset used: " << getCodec()->name() << endl;
00498 
00499     m_streamOut->setCodec( getCodec() );
00500 
00501     m_fileName=filenameOut;
00502     QFileInfo base(m_fileName);
00503     m_strFileDir=base.dirPath();
00504     m_strTitle=base.fileName();
00505     m_strSubDirectoryName=base.fileName();
00506     m_strSubDirectoryName+=".dir";
00507 
00508     return true;
00509 }
00510 
00511 bool HtmlWorker::doCloseFile(void)
00512 {
00513     kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl;
00514     delete m_streamOut;
00515     m_streamOut=NULL;
00516     if (m_ioDevice)
00517         m_ioDevice->close();
00518     return true;
00519 }
00520 
00521 void HtmlWorker::writeDocType(void)
00522 {
00523     // write <!DOCTYPE
00524     *m_streamOut << "<!DOCTYPE ";
00525     if (isXML())
00526     {
00527         *m_streamOut << "html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n";
00528     }
00529     else
00530     {
00531         *m_streamOut << "HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
00532     }
00533 }
00534 
00535 bool HtmlWorker::doOpenDocument(void)
00536 {
00537     // Make the file header
00538 
00539     if (isXML())
00540     {   //Write out the XML declaration
00541         *m_streamOut << "<?xml version=\"1.0\" encoding=\""
00542             << getCodec()->mimeName() << "\"?>" << endl;
00543     }
00544 
00545     // write <!DOCTYPE
00546     writeDocType();
00547 
00548     // No "lang" or "xml:lang" attribute for <html>, as we do not know in which language the document is!
00549     *m_streamOut << "<html";
00550     if (isXML())
00551     {
00552         // XHTML has an extra attribute defining its namespace (in the <html> opening tag)
00553         *m_streamOut << " xmlns=\"http://www.w3.org/1999/xhtml\"";
00554     }
00555     *m_streamOut << ">\n";
00556     return true;
00557 }
00558 
00559 bool HtmlWorker::doCloseDocument(void)
00560 {
00561     kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl;
00562     *m_streamOut << "</html>\n";
00563     return true;
00564 }
00565 
00566 bool HtmlWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo)
00567 {
00568     QString strText=docInfo.title;
00569     if (!strText.isEmpty())
00570     {
00571         m_strTitle=strText; // Set title only if it is not empty!
00572         kdDebug(30503) << "Found new title " << m_strTitle << endl;
00573     }
00574     return true;
00575 }
00576 
00577 bool HtmlWorker::doOpenHead(void)
00578 {
00579     *m_streamOut << "<head>" << endl;
00580 
00581     // Declare what charset we are using
00582     *m_streamOut << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
00583     *m_streamOut << getCodec()->mimeName() << '"';
00584     *m_streamOut << (isXML()?" /":"") << ">\n" ;
00585 
00586     // Say who we are (with the CVS revision number) in case we have a bug in our filter output!
00587     QString strVersion("$Revision: 466447 $");
00588     // Eliminate the dollar signs
00589     //  (We don't want that the version number changes if the HTML file is itself put in a CVS storage.)
00590     *m_streamOut << "<meta name=\"Generator\" content=\"KWord HTML Export Filter Version"
00591               << strVersion.mid( 10 ).remove( '$' )
00592               << "\""<< (isXML()?" /":"") // X(HT)ML closes empty elements, HTML not!
00593               << ">\n";
00594 
00595     if (m_strTitle.isEmpty())
00596     {
00597         // Somehow we have still an empty title (this should not happen!)
00598         kdWarning(30503) << "Title still empty! (HtmlWorker::doOpenHead)" << endl;
00599         m_strTitle=i18n("Untitled Document");
00600     }
00601     *m_streamOut << "<title>"<< escapeHtmlText(m_strTitle) <<"</title>\n";  // <TITLE> is mandatory!
00602 
00603     if( !customCSSURL().isEmpty() )
00604     {
00605       *m_streamOut << "<link ref=\"stylesheet\" type=\"text/css\" href=\"" << customCSSURL() << "\" title=\"Style\" >\n" << endl;
00606     }
00607 
00608     //TODO: transform documentinfo.xml into many <META> elements (at least the author!)
00609 
00610     return true;
00611 }
00612 
00613 bool HtmlWorker::doCloseHead(void)
00614 {
00615     *m_streamOut << "</head>\n";
00616     return true;
00617 }
00618 
00619 bool HtmlWorker::doOpenBody(void)
00620 {
00621     *m_streamOut << "<body>\n";
00622     return true;
00623 }
00624 
00625 bool HtmlWorker::doCloseBody(void)
00626 {
00627     *m_streamOut << "</body>\n";
00628     return true;
00629 }
00630 
00631 bool HtmlWorker::doOpenTextFrameSet(void)
00632 {
00633     return true;
00634 }
00635 
00636 bool HtmlWorker::doCloseTextFrameSet(void)
00637 {
00638     if (!m_listStack.isEmpty())
00639     {
00640         for (uint i=m_listStack.size(); i>0; i--)
00641         {
00642             ListInfo oldList=m_listStack.pop();
00643             if (oldList.m_orderedList)
00644             {
00645                 *m_streamOut << "</ol>\n";
00646             }
00647             else
00648             {
00649                 *m_streamOut << "</ul>\n";
00650             }
00651         }
00652     }
00653     return true;
00654 }
KDE Home | KDE Accessibility Home | Description of Access Keys