diff options
Diffstat (limited to 'filters/kword/html/export/ExportFilter.cpp')
-rw-r--r-- | filters/kword/html/export/ExportFilter.cpp | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/filters/kword/html/export/ExportFilter.cpp b/filters/kword/html/export/ExportFilter.cpp new file mode 100644 index 000000000..96f0b6c5b --- /dev/null +++ b/filters/kword/html/export/ExportFilter.cpp @@ -0,0 +1,654 @@ +/* + This file is part of the KDE project + Copyright (C) 2001, 2002, 2004 Nicolas GOUTTE <goutte@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <tqstring.h> +#include <tqtextcodec.h> +#include <tqfile.h> +#include <tqfileinfo.h> +#include <tqdir.h> +#include <tqpicture.h> + +#include <tdelocale.h> +#include <kdebug.h> + +#include <KWEFUtil.h> +#include <KWEFBaseWorker.h> + +#include "ExportFilter.h" + +TQString HtmlWorker::escapeHtmlText(const TQString& strText) const +{ + // Escape quotes (needed in attributes) + // Do not escape apostrophs (only allowed in XHTML!) + return KWEFUtil::EscapeSgmlText(getCodec(),strText,true,false); +} + +bool HtmlWorker::makeTable(const FrameAnchor& anchor) +{ + *m_streamOut << "<table>\n"; + *m_streamOut << "<tbody>\n"; + + TQValueList<TableCell>::ConstIterator itCell; + + int rowCurrent=0; + *m_streamOut << "<tr>\n"; + + + for (itCell=anchor.table.cellList.begin(); + itCell!=anchor.table.cellList.end(); itCell++) + { + if (rowCurrent!=(*itCell).row) + { + rowCurrent=(*itCell).row; + *m_streamOut << "</tr>\n<tr>\n"; + } + + *m_streamOut << "<td"; + if ( (*itCell).m_rows > 1 ) + *m_streamOut << " rowspan=\"" << (*itCell).m_rows << "\""; + if ( (*itCell).m_cols > 1 ) + *m_streamOut << " colspan=\"" << (*itCell).m_cols << "\""; + *m_streamOut << ">\n"; + + if (!doFullAllParagraphs(*(*itCell).paraList)) + { + return false; + } + + *m_streamOut << "</td>\n"; + } + + *m_streamOut << "</tr>\n"; + *m_streamOut << "</tbody>\n"; + *m_streamOut << "</table>\n"; + + return true; +} + +TQString HtmlWorker::getAdditionalFileName(const TQString& additionalName) +{ + kdDebug(30503) << "HtmlWorker::getAdditionalFileName " << additionalName << endl; + + TQDir dir(m_strFileDir); + kdDebug(30503) << "Base directory: " << m_strFileDir << endl; + + if (!dir.exists(m_strSubDirectoryName)) + { + // Make the directory, as it does not exist yet! + kdDebug(30503) << "Creating directory: " << m_strSubDirectoryName << endl; + dir.mkdir(m_strSubDirectoryName); + } + + TQString strFileName(m_strSubDirectoryName); + strFileName+="/"; + const int result=additionalName.findRev("/"); + if (result>=0) + { + strFileName+=additionalName.mid(result+1); + } + else + { + strFileName+=additionalName; + } + + // Now, we have to create a backup file. + + TQString strBackupName(strFileName); + strBackupName+="~"; + kdDebug(30503) << "Remove backup file: " << strBackupName << endl; + // We need to remove the backup file, as not all filesystems or ports can do it themselves on a rename. + dir.remove(strBackupName); + kdDebug(30503) << "Moving file: " << additionalName << " => " << strBackupName << endl; + dir.rename(strFileName,strBackupName); + + return strFileName; +} + +bool HtmlWorker::makeImage(const FrameAnchor& anchor) +{ + const TQString strImageName(getAdditionalFileName(anchor.picture.koStoreName)); + + TQString strImagePath(m_strFileDir); + strImagePath+='/'; + strImagePath+=strImageName; + + TQByteArray image; + + kdDebug(30503) << "Image " << anchor.picture.koStoreName << " will be written in " << strImageName << endl; + + if (loadSubFile(anchor.picture.koStoreName,image)) + { + bool writePicture = false; + + const double height = anchor.frame.bottom - anchor.frame.top; + const double width = anchor.frame.right - anchor.frame.left; + + const int pos = anchor.picture.koStoreName.findRev( '.' ); + TQString extension; + if ( pos > -1 ) + extension = anchor.picture.koStoreName.mid( pos+1 ).lower(); + + if ( extension == "png" || extension == "jpeg" || extension == "jpg" || extension == "gif" + || extension == "bmp" ) // A few file types known by all HTML user agents + { + *m_streamOut << "<img "; // This is an empty element! + *m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" "; + *m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\""; + *m_streamOut << (isXML()?"/>":">"); + writePicture = true; + } + else if ( extension == "svg" ) + { + // Save picture as SVG + *m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\""; + *m_streamOut << " type=\"image/svg+xml\""; + *m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n"; + *m_streamOut << "</object>"; // <object> is *not* an empty element in HTML! + writePicture = true; + } + else if ( extension == "qpic" ) + { + + TQPicture picture; + + TQIODevice* io=getSubFileDevice(anchor.picture.koStoreName); + if (!io) + { + // NO message error, as there must be already one + return false; + } + + // TODO: if we have alreasy SVG, do *not* go through TQPicture! + if (picture.load(io)) + { + + // Save picture as SVG + *m_streamOut << "<object data=\"" << escapeHtmlText(strImageName) << "\""; + *m_streamOut << " type=\"image/svg+xml\""; + *m_streamOut << " height=\"" << height << "\" width=\"" << width << "\">\n"; + *m_streamOut << "</object>"; // <object> is *not* an empty element in HTML! + // TODO: other props for image + + kdDebug(30506) << "Trying to save clipart to " << strImageName << endl; + if (!picture.save(strImagePath,"svg")) + { + kdError(30506) << "Could not save clipart: " << anchor.picture.koStoreName + << " to " << strImageName << endl; + return false; + } + + } + } + else + { + // ### TODO: avoid loading the picture 2 times + image.resize( 0 ); + if ( ! loadAndConvertToImage( anchor.picture.koStoreName, extension, "PNG", image ) ) + { + kdWarning(30503) << "Could not convert picture to PNG!" << endl; + return false; + } + *m_streamOut << "<img "; // This is an empty element! + *m_streamOut << "src=\"" << escapeHtmlText(strImageName) << "\" "; + *m_streamOut << "alt=\"" << escapeHtmlText(anchor.picture.key.filename()) << "\""; + *m_streamOut << (isXML()?"/>":">"); + writePicture = true; + } + + // Do we still need to write the original picture? + if ( writePicture ) + { + TQFile file(strImagePath); + + if ( !file.open (IO_WriteOnly) ) + { + kdError(30503) << "Unable to open image output file!" << endl; + return false; + } + + file.writeBlock(image); + file.close(); + } + } + else + { + kdWarning(30503) << "Unable to load picture " << anchor.picture.koStoreName << endl; + } + + return true; +} + +void HtmlWorker::formatTextParagraph(const TQString& strText, + const FormatData& formatOrigin, const FormatData& format) +{ + TQString strEscaped(escapeHtmlText(strText)); + + // Replace line feeds by line breaks + int pos; + TQString strBr(isXML()?TQString("<br />"):TQString("<br>")); + while ((pos=strEscaped.find(TQChar(10)))>-1) + { + strEscaped.replace(pos,1,strBr); + } + + if (!format.text.missing) + { + // Opening elements + openSpan(formatOrigin,format); + } + + // TODO: first and last characters of partialText should not be a space (white space problems!) + // TODO: replace multiples spaces by non-breaking spaces! + + if (strText==" ") + {//Just a space as text. Therefore we must use a non-breaking space. + *m_streamOut << " "; + // TODO/FIXME: only needed for <p> </p>, but not for </span> <span> + } + else + { + *m_streamOut << strEscaped; + } + + if (!format.text.missing) + { + // Closing elements + closeSpan(formatOrigin,format); + } +} + +void HtmlWorker::ProcessParagraphData (const TQString& strTag, const TQString ¶Text, + const LayoutData& layout, const ValueListFormatData ¶FormatDataList) +{ + if (paraText.isEmpty() && paraFormatDataList.first().id != 6) + { + openParagraph(strTag,layout); + *m_streamOut << " " ; // A paragraph can never be empty in HTML + closeParagraph(strTag,layout); + } + else + { + bool paragraphNotOpened=true; + + ValueListFormatData::ConstIterator paraFormatDataIt; + + TQString partialText; + + for ( paraFormatDataIt = paraFormatDataList.begin (); + paraFormatDataIt != paraFormatDataList.end (); + paraFormatDataIt++ ) + { + if (1==(*paraFormatDataIt).id) + { + //Retrieve text + partialText=paraText.mid ( (*paraFormatDataIt).pos, (*paraFormatDataIt).len ); + // For normal text, we need an opened paragraph + if (paragraphNotOpened) + { + openParagraph(strTag,layout,partialText.ref(0).direction()); + paragraphNotOpened=false; + } + formatTextParagraph(partialText,layout.formatData,*paraFormatDataIt); + } + else if (4==(*paraFormatDataIt).id) + { + // For variables, we need an opened paragraph + if (paragraphNotOpened) + { + openParagraph(strTag,layout); + paragraphNotOpened=false; + } + if (9==(*paraFormatDataIt).variable.m_type) + { + // A link + *m_streamOut << "<a href=\"" + << escapeHtmlText((*paraFormatDataIt).variable.getHrefName()) + << "\">" + << escapeHtmlText((*paraFormatDataIt).variable.getLinkName()) + << "</a>"; + } + else + { + // Generic variable + *m_streamOut << escapeHtmlText((*paraFormatDataIt).variable.m_text); + } + } + else if (6==(*paraFormatDataIt).id) + { + // We have an image, a clipart or a table + + if (6==(*paraFormatDataIt).frameAnchor.type) + { + // We have a table + // But first, we must sure that the paragraph is not opened. + if (!paragraphNotOpened) + { + // The paragraph was opened, so close it. + closeParagraph(strTag,layout); + } + makeTable((*paraFormatDataIt).frameAnchor); + // The paragraph will need to be opened again + paragraphNotOpened=true; + + } + + else if ( ( 2 == (*paraFormatDataIt).frameAnchor.type ) + || ( 5 == (*paraFormatDataIt).frameAnchor.type ) ) + { + // <img> and <object> need to be in a paragraph + if (paragraphNotOpened) + { + openParagraph( strTag, layout,partialText.ref(0). direction() ); + paragraphNotOpened=false; + } + makeImage((*paraFormatDataIt).frameAnchor); + } + else + { + kdWarning(30503) << "Unknown anchor type: " + << (*paraFormatDataIt).frameAnchor.type << endl; + } + } + } + if (!paragraphNotOpened) + { + // The paragraph was opened, so close it. + closeParagraph(strTag,layout); + } + } +} + +bool HtmlWorker::doFullParagraph(const TQString& paraText, + const LayoutData& layout, const ValueListFormatData& paraFormatDataList) +{ + kdDebug(30503) << "Entering HtmlWorker::doFullParagraph" << endl << paraText << endl; + TQString strParaText=paraText; + TQString strTag; // Tag that will be written. + + if ( layout.counter.numbering == CounterData::NUM_LIST ) + { + const uint layoutDepth=layout.counter.depth+1; // Word's depth starts at 0! + const uint listDepth=m_listStack.size(); + // We are in a list, but has it the right depth? + if (layoutDepth>listDepth) + { + ListInfo newList; + newList.m_typeList=layout.counter.style; + for (uint i=listDepth; i<layoutDepth; i++) + { + *m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList); + m_listStack.push(newList); + } + } + else if (layoutDepth<listDepth) + { + for (uint i=listDepth; i>layoutDepth; i--) + { + ListInfo oldList=m_listStack.pop(); + if (oldList.m_orderedList) + { + *m_streamOut << "</ol>\n"; + } + else + { + *m_streamOut << "</ul>\n"; + } + } + } + + // We have a list but does it have the right type? + if ( layout.counter.style!=m_listStack.top().m_typeList) + { + // No, then close the previous list + ListInfo oldList=m_listStack.pop(); + if (oldList.m_orderedList) + { + *m_streamOut << "</ol>\n"; + } + else + { + *m_streamOut << "</ul>\n"; + } + ListInfo newList; + *m_streamOut << getStartOfListOpeningTag(layout.counter.style,newList.m_orderedList); + newList.m_typeList=layout.counter.style; + m_listStack.push(newList); + } + + // TODO: with Cascaded Style Sheet, we could add the exact counter type that we want + strTag="li"; + } + else + { + // Close all open lists first + if (!m_listStack.isEmpty()) + { + for (uint i=m_listStack.size(); i>0; i--) + { + ListInfo oldList=m_listStack.pop(); + if (oldList.m_orderedList) + { + *m_streamOut << "</ol>\n"; + } + else + { + *m_streamOut << "</ul>\n"; + } + } + } + if ( (layout.counter.numbering == CounterData::NUM_CHAPTER) + && (layout.counter.depth<6) ) + { + strTag=TQString("h%1").arg(layout.counter.depth + 1); // H1 ... H6 + } + else + { + strTag="p"; + } + } + + ProcessParagraphData(strTag, strParaText, layout, paraFormatDataList); + + kdDebug(30503) << "Quiting HtmlWorker::doFullParagraph" << endl; + return true; +} + +bool HtmlWorker::doOpenFile(const TQString& filenameOut, const TQString& /*to*/) +{ + m_ioDevice=TQT_TQIODEVICE(new TQFile(filenameOut)); + + if (!m_ioDevice) + { + kdError(30503) << "No output file! Aborting!" << endl; + return false; + } + + if ( !m_ioDevice->open (IO_WriteOnly) ) + { + kdError(30503) << "Unable to open output file!" << endl; + return false; + } + + m_streamOut=new TQTextStream(m_ioDevice); + + if (!getCodec()) + { + kdError(30503) << "Could not create TQTextCodec! Aborting" << endl; + return false; + } + + kdDebug(30503) << "Charset used: " << getCodec()->name() << endl; + + m_streamOut->setCodec( getCodec() ); + + m_fileName=filenameOut; + TQFileInfo base(m_fileName); + m_strFileDir=base.dirPath(); + m_strTitle=base.fileName(); + m_strSubDirectoryName=base.fileName(); + m_strSubDirectoryName+=".dir"; + + return true; +} + +bool HtmlWorker::doCloseFile(void) +{ + kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl; + delete m_streamOut; + m_streamOut=NULL; + if (m_ioDevice) + m_ioDevice->close(); + return true; +} + +void HtmlWorker::writeDocType(void) +{ + // write <!DOCTYPE + *m_streamOut << "<!DOCTYPE "; + if (isXML()) + { + *m_streamOut << "html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"; + } + else + { + *m_streamOut << "HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"; + } +} + +bool HtmlWorker::doOpenDocument(void) +{ + // Make the file header + + if (isXML()) + { //Write out the XML declaration + *m_streamOut << "<?xml version=\"1.0\" encoding=\"" + << getCodec()->mimeName() << "\"?>" << endl; + } + + // write <!DOCTYPE + writeDocType(); + + // No "lang" or "xml:lang" attribute for <html>, as we do not know in which language the document is! + *m_streamOut << "<html"; + if (isXML()) + { + // XHTML has an extra attribute defining its namespace (in the <html> opening tag) + *m_streamOut << " xmlns=\"http://www.w3.org/1999/xhtml\""; + } + *m_streamOut << ">\n"; + return true; +} + +bool HtmlWorker::doCloseDocument(void) +{ + kdDebug(30503) << __FILE__ << ":" << __LINE__ << endl; + *m_streamOut << "</html>\n"; + return true; +} + +bool HtmlWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo) +{ + TQString strText=docInfo.title; + if (!strText.isEmpty()) + { + m_strTitle=strText; // Set title only if it is not empty! + kdDebug(30503) << "Found new title " << m_strTitle << endl; + } + return true; +} + +bool HtmlWorker::doOpenHead(void) +{ + *m_streamOut << "<head>" << endl; + + // Declare what charset we are using + *m_streamOut << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset="; + *m_streamOut << getCodec()->mimeName() << '"'; + *m_streamOut << (isXML()?" /":"") << ">\n" ; + + // Say who we are (with the CVS revision number) in case we have a bug in our filter output! + TQString strVersion("$Revision: 466447 $"); + // Eliminate the dollar signs + // (We don't want that the version number changes if the HTML file is itself put in a CVS storage.) + *m_streamOut << "<meta name=\"Generator\" content=\"KWord HTML Export Filter Version" + << strVersion.mid( 10 ).remove( '$' ) + << "\""<< (isXML()?" /":"") // X(HT)ML closes empty elements, HTML not! + << ">\n"; + + if (m_strTitle.isEmpty()) + { + // Somehow we have still an empty title (this should not happen!) + kdWarning(30503) << "Title still empty! (HtmlWorker::doOpenHead)" << endl; + m_strTitle=i18n("Untitled Document"); + } + *m_streamOut << "<title>"<< escapeHtmlText(m_strTitle) <<"</title>\n"; // <TITLE> is mandatory! + + if( !customCSSURL().isEmpty() ) + { + *m_streamOut << "<link ref=\"stylesheet\" type=\"text/css\" href=\"" << customCSSURL() << "\" title=\"Style\" >\n" << endl; + } + + //TODO: transform documentinfo.xml into many <META> elements (at least the author!) + + return true; +} + +bool HtmlWorker::doCloseHead(void) +{ + *m_streamOut << "</head>\n"; + return true; +} + +bool HtmlWorker::doOpenBody(void) +{ + *m_streamOut << "<body>\n"; + return true; +} + +bool HtmlWorker::doCloseBody(void) +{ + *m_streamOut << "</body>\n"; + return true; +} + +bool HtmlWorker::doOpenTextFrameSet(void) +{ + return true; +} + +bool HtmlWorker::doCloseTextFrameSet(void) +{ + if (!m_listStack.isEmpty()) + { + for (uint i=m_listStack.size(); i>0; i--) + { + ListInfo oldList=m_listStack.pop(); + if (oldList.m_orderedList) + { + *m_streamOut << "</ol>\n"; + } + else + { + *m_streamOut << "</ul>\n"; + } + } + } + return true; +} |