diff options
Diffstat (limited to 'filters/kword/abiword/abiwordimport.cpp')
-rw-r--r-- | filters/kword/abiword/abiwordimport.cpp | 1875 |
1 files changed, 1875 insertions, 0 deletions
diff --git a/filters/kword/abiword/abiwordimport.cpp b/filters/kword/abiword/abiwordimport.cpp new file mode 100644 index 000000000..bd6ea1e38 --- /dev/null +++ b/filters/kword/abiword/abiwordimport.cpp @@ -0,0 +1,1875 @@ +/* This file is part of the KDE project + Copyright 2001, 2002, 2003, 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 <config.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <tqmap.h> +#include <tqbuffer.h> +#include <tqpicture.h> +#include <tqxml.h> +#include <tqdom.h> +#include <tqdatetime.h> + +#include <kdebug.h> +#include <kmdcodec.h> +#include <kfilterdev.h> +#include <kgenericfactory.h> +#include <tdemessagebox.h> + +#include <KoPageLayout.h> +#include <KoStore.h> +#include <KoFilterChain.h> + +#include "ImportHelpers.h" +#include "ImportFormatting.h" +#include "ImportStyle.h" +#include "ImportField.h" + +#include "abiwordimport.h" + +class ABIWORDImportFactory : KGenericFactory<ABIWORDImport, KoFilter> +{ +public: + ABIWORDImportFactory(void) : KGenericFactory<ABIWORDImport, KoFilter> ("kwordabiwordimport") + {} +protected: + virtual void setupTranslations( void ) + { + TDEGlobal::locale()->insertCatalogue( "kofficefilters" ); + } +}; + +K_EXPORT_COMPONENT_FACTORY( libabiwordimport, ABIWORDImportFactory() ) + +// *Note for the reader of this code* +// Tags in lower case (e.g. <c>) are AbiWord's ones. +// Tags in upper case (e.g. <TEXT>) are KWord's ones. + +// enum StackItemElementType is now in the file ImportFormatting.h + +class StructureParser : public TQXmlDefaultHandler +{ +public: + StructureParser(KoFilterChain* chain) + : m_chain(chain), m_pictureNumber(0), m_pictureFrameNumber(0), m_tableGroupNumber(0), + m_timepoint(TQDateTime::currentDateTime(Qt::UTC)), m_fatalerror(false) + { + createDocument(); + structureStack.setAutoDelete(true); + StackItem *stackItem=new(StackItem); + stackItem->elementType=ElementTypeBottom; + stackItem->m_frameset=mainFramesetElement; // The default frame set. + stackItem->stackElementText=mainFramesetElement; // This is more for DEBUG + structureStack.push(stackItem); //Security item (not to empty the stack) + } + virtual ~StructureParser() + { + structureStack.clear(); + } +public: + virtual bool startDocument(void); + virtual bool endDocument(void); + virtual bool startElement( const TQString&, const TQString&, const TQString& name, const TQXmlAttributes& attributes); + virtual bool endElement( const TQString&, const TQString& , const TQString& qName); + virtual bool characters ( const TQString & ch ); + virtual bool warning(const TQXmlParseException& exception); + virtual bool error(const TQXmlParseException& exception); + virtual bool fatalError(const TQXmlParseException& exception); +public: + inline TQDomDocument getDocInfo(void) const { return m_info; } + inline TQDomDocument getDocument(void) const { return mainDocument; } + inline bool wasFatalError(void) const { return m_fatalerror; } +protected: + bool clearStackUntilParagraph(StackItemStack& auxilaryStack); + bool complexForcedPageBreak(StackItem* stackItem); +private: + // The methods that would need too much parameters are private instead of being static outside the class + bool StartElementC(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes); + bool StartElementA(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes); + bool StartElementImage(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes); + bool EndElementD (StackItem* stackItem); + bool EndElementM (StackItem* stackItem); + bool StartElementSection(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes); + bool StartElementFoot(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes); + bool StartElementTable(StackItem* stackItem, StackItem* stackCurrent, const TQXmlAttributes& attributes); + bool StartElementCell(StackItem* stackItem, StackItem* stackCurrent,const TQXmlAttributes& attributes); +private: + void createDocument(void); + void createDocInfo(void); + TQString indent; //DEBUG + StackItemStack structureStack; + TQDomDocument mainDocument; + TQDomDocument m_info; + TQDomElement framesetsPluralElement; // <FRAMESETS> + TQDomElement mainFramesetElement; // The main <FRAMESET> where the body text will be under. + TQDomElement m_picturesElement; // <PICTURES> + TQDomElement m_paperElement; // <PAPER> + TQDomElement m_paperBordersElement; // <PAPERBORDER> + TQDomElement m_ignoreWordsElement; // <SPELLCHECKIGNORELIST> + StyleDataMap styleDataMap; + KoFilterChain* m_chain; + uint m_pictureNumber; // unique: increment *before* use + uint m_pictureFrameNumber; // unique: increment *before* use + uint m_tableGroupNumber; // unique: increment *before* use + TQMap<TQString,TQString> m_metadataMap; // Map for <m> elements + TQDateTime m_timepoint; // Date/time (for pictures) + bool m_fatalerror; // Did a XML parsing fatal error happened? +}; + +// Element <c> + +bool StructureParser::StartElementC(StackItem* stackItem, StackItem* stackCurrent, const TQXmlAttributes& attributes) +{ + // <c> elements can be nested in <p> elements, in <a> elements or in other <c> elements + // AbiWord does not nest <c> elements in other <c> elements, but explicitly allows external programs to do it! + + // <p> or <c> (not child of <a>) + if ((stackCurrent->elementType==ElementTypeParagraph)||(stackCurrent->elementType==ElementTypeContent)) + { + // Contents can have styles, however KWord cannot have character style. + // Therefore we use the style if it exist, but we do not create it if not. + TQString strStyleProps; + TQString strStyleName=attributes.value("style").stripWhiteSpace(); + if (!strStyleName.isEmpty()) + { + StyleDataMap::Iterator it=styleDataMap.find(strStyleName); + if (it!=styleDataMap.end()) + { + strStyleProps=it.data().m_props; + } + } + + AbiPropsMap abiPropsMap; + PopulateProperties(stackItem,strStyleProps,attributes,abiPropsMap,true); + + stackItem->elementType=ElementTypeContent; + stackItem->stackElementParagraph=stackCurrent->stackElementParagraph; // <PARAGRAPH> + stackItem->stackElementText=stackCurrent->stackElementText; // <TEXT> + stackItem->stackElementFormatsPlural=stackCurrent->stackElementFormatsPlural; // <FORMATS> + stackItem->pos=stackCurrent->pos; //Propagate the position + } + // <a> or <c> when child of <a> + else if ((stackCurrent->elementType==ElementTypeAnchor)||(stackCurrent->elementType==ElementTypeAnchorContent)) + { + stackItem->elementType=ElementTypeAnchorContent; + } + else + {//we are not nested correctly, so consider it a parse error! + kdError(30506) << "parse error <c> tag nested neither in <p> nor in <c> nor in <a> but in " + << stackCurrent->itemName << endl; + return false; + } + return true; +} + +bool charactersElementC (StackItem* stackItem, TQDomDocument& mainDocument, const TQString & ch) +{ + if (stackItem->elementType==ElementTypeContent) + { // Normal <c> + TQDomElement elementText=stackItem->stackElementText; + TQDomElement elementFormatsPlural=stackItem->stackElementFormatsPlural; + elementText.appendChild(mainDocument.createTextNode(ch)); + + TQDomElement formatElementOut=mainDocument.createElement("FORMAT"); + formatElementOut.setAttribute("id",1); // Normal text! + formatElementOut.setAttribute("pos",stackItem->pos); // Start position + formatElementOut.setAttribute("len",ch.length()); // Start position + elementFormatsPlural.appendChild(formatElementOut); //Append to <FORMATS> + stackItem->pos+=ch.length(); // Adapt new starting position + + AddFormat(formatElementOut, stackItem, mainDocument); + } + else if (stackItem->elementType==ElementTypeAnchorContent) + { + // Add characters to the link name + stackItem->strTemp2+=ch; + // TODO: how can we care about the text format? + } + else + { + kdError(30506) << "Internal error (in charactersElementC)" << endl; + } + + return true; +} + +bool EndElementC (StackItem* stackItem, StackItem* stackCurrent) +{ + if (stackItem->elementType==ElementTypeContent) + { + stackItem->stackElementText.normalize(); + stackCurrent->pos=stackItem->pos; //Propagate the position back to the parent element + } + else if (stackItem->elementType==ElementTypeAnchorContent) + { + stackCurrent->strTemp2+=stackItem->strTemp2; //Propagate the link name back to the parent element + } + else + { + kdError(30506) << "Wrong element type!! Aborting! (</c> in StructureParser::endElement)" << endl; + return false; + } + return true; +} + +// Element <a> +bool StructureParser::StartElementA(StackItem* stackItem, StackItem* stackCurrent, const TQXmlAttributes& attributes) +{ + // <a> elements can be nested in <p> elements + if (stackCurrent->elementType==ElementTypeParagraph) + { + + //AbiPropsMap abiPropsMap; + //PopulateProperties(stackItem,TQString(),attributes,abiPropsMap,true); + + stackItem->elementType=ElementTypeAnchor; + stackItem->stackElementParagraph=stackCurrent->stackElementParagraph; // <PARAGRAPH> + stackItem->stackElementText=stackCurrent->stackElementText; // <TEXT> + stackItem->stackElementFormatsPlural=stackCurrent->stackElementFormatsPlural; // <FORMATS> + stackItem->pos=stackCurrent->pos; //Propagate the position + stackItem->strTemp1=attributes.value("xlink:href").stripWhiteSpace(); // link reference + stackItem->strTemp2=TQString(); // link name + + // We must be careful: AbiWord permits anchors to bookmarks. + // However, KWord does not know what a bookmark is. + if (stackItem->strTemp1[0]=='#') + { + kdWarning(30506) << "Anchor <a> to bookmark: " << stackItem->strTemp1 << endl + << " Processing <a> like <c>" << endl; + // We have a reference to a bookmark. Therefore treat it as a normal content <c> + return StartElementC(stackItem, stackCurrent, attributes); + } + } + else + {//we are not nested correctly, so consider it a parse error! + kdError(30506) << "parse error <a> tag not a child of <p> but of " + << stackCurrent->itemName << endl; + return false; + } + return true; +} + +static bool charactersElementA (StackItem* stackItem, const TQString & ch) +{ + // Add characters to the link name + stackItem->strTemp2+=ch; + return true; +} + +static bool EndElementA (StackItem* stackItem, StackItem* stackCurrent, TQDomDocument& mainDocument) +{ + if (!stackItem->elementType==ElementTypeAnchor) + { + kdError(30506) << "Wrong element type!! Aborting! (</a> in StructureParser::endElement)" << endl; + return false; + } + + TQDomElement elementText=stackItem->stackElementText; + elementText.appendChild(mainDocument.createTextNode("#")); + + TQDomElement formatElement=mainDocument.createElement("FORMAT"); + formatElement.setAttribute("id",4); // Variable + formatElement.setAttribute("pos",stackItem->pos); // Start position + formatElement.setAttribute("len",1); // Start position + + TQDomElement variableElement=mainDocument.createElement("VARIABLE"); + formatElement.appendChild(variableElement); + + TQDomElement typeElement=mainDocument.createElement("TYPE"); + typeElement.setAttribute("key","STRING"); + typeElement.setAttribute("type",9); // link + typeElement.setAttribute("text",stackItem->strTemp2); + variableElement.appendChild(typeElement); //Append to <VARIABLE> + + TQDomElement linkElement=mainDocument.createElement("LINK"); + linkElement.setAttribute("hrefName",stackItem->strTemp1); + linkElement.setAttribute("linkName",stackItem->strTemp2); + variableElement.appendChild(linkElement); //Append to <VARIABLE> + + // Now work on stackCurrent + stackCurrent->stackElementFormatsPlural.appendChild(formatElement); + stackCurrent->pos++; //Propagate the position back to the parent element + + return true; +} + +// Element <p> + +bool StartElementP(StackItem* stackItem, StackItem* stackCurrent, + TQDomDocument& mainDocument, + StyleDataMap& styleDataMap, const TQXmlAttributes& attributes) +{ + // We must prepare the style + TQString strStyle=attributes.value("style"); + if (strStyle.isEmpty()) + { + strStyle="Normal"; + } + StyleDataMap::ConstIterator it=styleDataMap.useOrCreateStyle(strStyle); + + TQString strLevel=attributes.value("level"); + int level; + if (strLevel.isEmpty()) + { + // We have not "level" attribute, so we must use the style's level. + level=it.data().m_level; + } + else + { + // We have a "level" attribute, so it overrides the style's level. + level=strStyle.toInt(); + } + + TQDomElement elementText=stackCurrent->stackElementText; + TQDomElement paragraphElementOut=mainDocument.createElement("PARAGRAPH"); + stackCurrent->m_frameset.appendChild(paragraphElementOut); + + TQDomElement textElementOut=mainDocument.createElement("TEXT"); + paragraphElementOut.appendChild(textElementOut); + TQDomElement formatsPluralElementOut=mainDocument.createElement("FORMATS"); + paragraphElementOut.appendChild(formatsPluralElementOut); + + AbiPropsMap abiPropsMap; + PopulateProperties(stackItem,it.data().m_props,attributes,abiPropsMap,false); + + stackItem->elementType=ElementTypeParagraph; + stackItem->stackElementParagraph=paragraphElementOut; // <PARAGRAPH> + stackItem->stackElementText=textElementOut; // <TEXT> + stackItem->stackElementFormatsPlural=formatsPluralElementOut; // <FORMATS> + stackItem->pos=0; // No text characters yet + + // Now we populate the layout + TQDomElement layoutElement=mainDocument.createElement("LAYOUT"); + paragraphElementOut.appendChild(layoutElement); + + AddLayout(strStyle,layoutElement, stackItem, mainDocument, abiPropsMap, level, false); + + return true; +} + +bool charactersElementP (StackItem* stackItem, TQDomDocument& mainDocument, const TQString & ch) +{ + TQDomElement elementText=stackItem->stackElementText; + + elementText.appendChild(mainDocument.createTextNode(ch)); + + stackItem->pos+=ch.length(); // Adapt new starting position + + return true; +} + +bool EndElementP (StackItem* stackItem) +{ + if (!stackItem->elementType==ElementTypeParagraph) + { + kdError(30506) << "Wrong element type!! Aborting! (in endElementP)" << endl; + return false; + } + stackItem->stackElementText.normalize(); + return true; +} + +static bool StartElementField(StackItem* stackItem, StackItem* stackCurrent, + TQDomDocument& mainDocument, const TQXmlAttributes& attributes) +{ + // <field> element elements can be nested in <p> + if (stackCurrent->elementType==ElementTypeParagraph) + { + TQString strType=attributes.value("type").stripWhiteSpace(); + kdDebug(30506)<<"<field> type:"<<strType<<endl; + + AbiPropsMap abiPropsMap; + PopulateProperties(stackItem,TQString(),attributes,abiPropsMap,true); + + stackItem->elementType=ElementTypeEmpty; + + // We create a format element + TQDomElement variableElement=mainDocument.createElement("VARIABLE"); + + if (!ProcessField(mainDocument, variableElement, strType, attributes)) + { + // The field type was not recognised, + // therefore write the field type in red as normal text + kdWarning(30506) << "Unknown <field> type: " << strType << endl; + TQDomElement formatElement=mainDocument.createElement("FORMAT"); + formatElement.setAttribute("id",1); // Variable + formatElement.setAttribute("pos",stackItem->pos); // Start position + formatElement.setAttribute("len",strType.length()); + + formatElement.appendChild(variableElement); + + // Now work on stackCurrent + stackCurrent->stackElementFormatsPlural.appendChild(formatElement); + stackCurrent->stackElementText.appendChild(mainDocument.createTextNode(strType)); + stackCurrent->pos+=strType.length(); // Adjust position + + // Add formating (use stackItem) + stackItem->fgColor.setRgb(255,0,0); + AddFormat(formatElement, stackItem, mainDocument); + return true; + } + + // We create a format element + TQDomElement formatElement=mainDocument.createElement("FORMAT"); + formatElement.setAttribute("id",4); // Variable + formatElement.setAttribute("pos",stackItem->pos); // Start position + formatElement.setAttribute("len",1); + + formatElement.appendChild(variableElement); + + // Now work on stackCurrent + stackCurrent->stackElementFormatsPlural.appendChild(formatElement); + stackCurrent->stackElementText.appendChild(mainDocument.createTextNode("#")); + stackCurrent->pos++; // Adjust position + + // Add formating (use stackItem) + AddFormat(formatElement, stackItem, mainDocument); + + } + else + {//we are not nested correctly, so consider it a parse error! + kdError(30506) << "parse error <field> tag not nested in <p> but in " + << stackCurrent->itemName << endl; + return false; + } + return true; +} + +// <s> (style) +static bool StartElementS(StackItem* stackItem, StackItem* /*stackCurrent*/, + const TQXmlAttributes& attributes, StyleDataMap& styleDataMap) +{ + // We do not assume when we are called. + // We also do not care if a style is defined multiple times. + stackItem->elementType=ElementTypeEmpty; + + TQString strStyleName=attributes.value("name").stripWhiteSpace(); + + if (strStyleName.isEmpty()) + { + kdWarning(30506) << "Style has no name!" << endl; + } + else + { + TQString strLevel=attributes.value("level"); + int level; + if (strLevel.isEmpty()) + level=-1; //TODO/FIXME: might be wrong if the style is based on another + else + level=strLevel.toInt(); + TQString strBasedOn=attributes.value("basedon").simplifyWhiteSpace(); + styleDataMap.defineNewStyleFromOld(strStyleName,strBasedOn,level,attributes.value("props")); + kdDebug(30506) << " Style name: " << strStyleName << endl + << " Based on: " << strBasedOn << endl + << " Level: " << level << endl + << " Props: " << attributes.value("props") << endl; + } + + return true; +} + +// <image> +bool StructureParser::StartElementImage(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes) +{ + // <image> elements can be nested in <p> or <c> elements + if ((stackCurrent->elementType!=ElementTypeParagraph) && (stackCurrent->elementType!=ElementTypeContent)) + {//we are not nested correctly, so consider it a parse error! + kdError(30506) << "parse error <image> tag nested neither in <p> nor in <c> but in " + << stackCurrent->itemName << endl; + return false; + } + stackItem->elementType=ElementTypeEmpty; + + TQString strDataId=attributes.value("dataid").stripWhiteSpace(); + + AbiPropsMap abiPropsMap; + abiPropsMap.splitAndAddAbiProps(attributes.value("props")); + + double height=ValueWithLengthUnit(abiPropsMap["height"].getValue()); + double width =ValueWithLengthUnit(abiPropsMap["width" ].getValue()); + + kdDebug(30506) << "Image: " << strDataId << " height: " << height << " width: " << width << endl; + + // TODO: image properties + + if (strDataId.isEmpty()) + { + kdWarning(30506) << "Image has no data id!" << endl; + } + else + { + kdDebug(30506) << "Image: " << strDataId << endl; + } + + TQString strPictureFrameName(i18n("Frameset name","Picture %1").arg(++m_pictureFrameNumber)); + + // Create the frame set of the image + + TQDomElement framesetElement=mainDocument.createElement("FRAMESET"); + framesetElement.setAttribute("frameType",2); + framesetElement.setAttribute("frameInfo",0); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",strPictureFrameName); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut=mainDocument.createElement("FRAME"); + frameElementOut.setAttribute("left",0); + frameElementOut.setAttribute("top",0); + frameElementOut.setAttribute("bottom",height); + frameElementOut.setAttribute("right" ,width ); + frameElementOut.setAttribute("runaround",1); + // TODO: a few attributes are missing + framesetElement.appendChild(frameElementOut); + + TQDomElement element=mainDocument.createElement("PICTURE"); + element.setAttribute("keepAspectRatio","true"); + framesetElement.setAttribute("frameType",2); // Picture + framesetElement.appendChild(element); + + TQDomElement key=mainDocument.createElement("KEY"); + key.setAttribute("filename",strDataId); + key.setAttribute("year",m_timepoint.date().year()); + key.setAttribute("month",m_timepoint.date().month()); + key.setAttribute("day",m_timepoint.date().day()); + key.setAttribute("hour",m_timepoint.time().hour()); + key.setAttribute("minute",m_timepoint.time().minute()); + key.setAttribute("second",m_timepoint.time().second()); + key.setAttribute("msec",m_timepoint.time().msec()); + element.appendChild(key); + + // Now use the image's frame set + TQDomElement elementText=stackItem->stackElementText; + TQDomElement elementFormatsPlural=stackItem->stackElementFormatsPlural; + elementText.appendChild(mainDocument.createTextNode("#")); + + TQDomElement formatElementOut=mainDocument.createElement("FORMAT"); + formatElementOut.setAttribute("id",6); // Normal text! + formatElementOut.setAttribute("pos",stackItem->pos); // Start position + formatElementOut.setAttribute("len",1); // Start position + elementFormatsPlural.appendChild(formatElementOut); //Append to <FORMATS> + + // WARNING: we must change the position in stackCurrent! + stackCurrent->pos++; // Adapt new starting position + + TQDomElement anchor=mainDocument.createElement("ANCHOR"); + // No name attribute! + anchor.setAttribute("type","frameset"); + anchor.setAttribute("instance",strPictureFrameName); + formatElementOut.appendChild(anchor); + + return true; +} + +// <d> +static bool StartElementD(StackItem* stackItem, StackItem* /*stackCurrent*/, + const TQXmlAttributes& attributes) +{ + // We do not assume when we are called or if we are or not a child of <data> + // However, we assume that we are after all <image> elements + stackItem->elementType=ElementTypeRealData; + + TQString strName=attributes.value("name").stripWhiteSpace(); + kdDebug(30506) << "Data: " << strName << endl; + + TQString strBase64=attributes.value("base64").stripWhiteSpace(); + TQString strMime=attributes.value("mime").stripWhiteSpace(); + + if (strName.isEmpty()) + { + kdWarning(30506) << "Data has no name!" << endl; + stackItem->elementType=ElementTypeEmpty; + return true; + } + + if (strMime.isEmpty()) + { + // Old AbiWord files had no mime types for images but the data were base64-coded PNG + strMime="image/png"; + strBase64="yes"; + } + + stackItem->fontName=strName; // Store the data name as font name. + stackItem->bold=(strBase64=="yes"); // Store base64-coded as bold + stackItem->strTemp1=strMime; // Mime type + stackItem->strTemp2=TQString(); // Image data + + return true; +} + +static bool CharactersElementD (StackItem* stackItem, TQDomDocument& /*mainDocument*/, const TQString & ch) +{ + // As we have no guarantee to have the whole stream in one call, we must store the data. + stackItem->strTemp2+=ch; + return true; +} + +bool StructureParser::EndElementD (StackItem* stackItem) +{ + if (!stackItem->elementType==ElementTypeRealData) + { + kdError(30506) << "Wrong element type!! Aborting! (in endElementD)" << endl; + return false; + } + if (!m_chain) + { + kdError(30506) << "No filter chain! Aborting! (in endElementD)" << endl; + return false; + } + + bool isSvg=false; // SVG ? + + TQString extension; + + // stackItem->strTemp1 contains the mime type + if (stackItem->strTemp1=="image/png") + { + extension=".png"; + } + else if (stackItem->strTemp1=="image/jpeg") // ### FIXME: in fact it does not exist in AbiWord + { + extension=".jpeg"; + } + else if (stackItem->strTemp1=="image/svg-xml") //Yes it is - not + + { + extension=".svg"; + isSvg=true; + } + else + { + kdWarning(30506) << "Unknown or unsupported mime type: " + << stackItem->strTemp1 << endl; + return true; + } + + TQString strStoreName; + strStoreName="pictures/picture"; + strStoreName+=TQString::number(++m_pictureNumber); + strStoreName+=extension; + + TQString strDataId=stackItem->fontName; // AbiWord's data id + TQDomElement key=mainDocument.createElement("KEY"); + key.setAttribute("filename",strDataId); + key.setAttribute("year",m_timepoint.date().year()); + key.setAttribute("month",m_timepoint.date().month()); + key.setAttribute("day",m_timepoint.date().day()); + key.setAttribute("hour",m_timepoint.time().hour()); + key.setAttribute("minute",m_timepoint.time().minute()); + key.setAttribute("second",m_timepoint.time().second()); + key.setAttribute("msec",m_timepoint.time().msec()); + key.setAttribute("name",strStoreName); + m_picturesElement.appendChild(key); + + KoStoreDevice* out=m_chain->storageFile(strStoreName, KoStore::Write); + if(!out) + { + kdError(30506) << "Unable to open output file for: " << stackItem->fontName << " Storage: " << strStoreName << endl; + return false; + } + + if (stackItem->bold) // Is base64-coded? + { + kdDebug(30506) << "Decode and write base64 stream: " << stackItem->fontName << endl; + // We need to decode the base64 stream + // However KCodecs has no TQString to TQByteArray decoder! + TQByteArray base64Stream=stackItem->strTemp2.utf8(); // Use utf8 to avoid corruption of data + TQByteArray binaryStream; + KCodecs::base64Decode(base64Stream, binaryStream); + out->writeBlock(binaryStream, binaryStream.count()); + } + else + { + // Unknown text format! + kdDebug(30506) << "Write character stream: " << stackItem->fontName << endl; + // We strip the white space in front to avoid white space before a XML declaration + TQCString strOut=stackItem->strTemp2.stripWhiteSpace().utf8(); + out->writeBlock(strOut,strOut.length()); + } + + return true; +} + +// <m> +static bool StartElementM(StackItem* stackItem, StackItem* /*stackCurrent*/, + const TQXmlAttributes& attributes) +{ + // We do not assume when we are called or if we are or not a child of <metadata> + stackItem->elementType=ElementTypeRealMetaData; + + TQString strKey=attributes.value("key").stripWhiteSpace(); + kdDebug(30506) << "Metadata key: " << strKey << endl; + + if (strKey.isEmpty()) + { + kdWarning(30506) << "Metadata has no key!" << endl; + stackItem->elementType=ElementTypeIgnore; + return true; + } + + stackItem->strTemp1=strKey; // Key + stackItem->strTemp2=TQString(); // Meta data + + return true; +} + +static bool CharactersElementM (StackItem* stackItem, const TQString & ch) +{ + // As we have no guarantee to have the whole data in one call, we must store the data. + stackItem->strTemp2+=ch; + return true; +} + +bool StructureParser::EndElementM (StackItem* stackItem) +{ + if (!stackItem->elementType==ElementTypeRealData) + { + kdError(30506) << "Wrong element type!! Aborting! (in endElementM)" << endl; + return false; + } + + if (stackItem->strTemp1.isEmpty()) + { + // Probably an internal error! + kdError(30506) << "Key name was erased! Aborting! (in endElementM)" << endl; + return false; + } + + // Just add it to the metadata map, we do not do something special with the values. + m_metadataMap[stackItem->strTemp1]=stackItem->strTemp2; + + return true; +} + +// <br> (forced line break) +static bool StartElementBR(StackItem* stackItem, StackItem* stackCurrent, + TQDomDocument& mainDocument) +{ + // <br> elements are mostly in <c> but can also be in <p> + if ((stackCurrent->elementType==ElementTypeParagraph) + || (stackCurrent->elementType==ElementTypeContent)) + { + stackItem->elementType=ElementTypeEmpty; + + // Now work on stackCurrent + + if (stackCurrent->elementType==ElementTypeContent) + { + // Child <c>, so we have to add formating of <c> + TQDomElement formatElement=mainDocument.createElement("FORMAT"); + formatElement.setAttribute("id",1); // Normal text! + formatElement.setAttribute("pos",stackCurrent->pos); // Start position + formatElement.setAttribute("len",1); + AddFormat(formatElement, stackCurrent, mainDocument); // Add the format of the parent <c> + stackCurrent->stackElementFormatsPlural.appendChild(formatElement); //Append to <FORMATS> + } + + stackCurrent->stackElementText.appendChild(mainDocument.createTextNode(TQChar(10))); // Add a LINE FEED + stackCurrent->pos++; // Adjust position + + } + else + {//we are not nested correctly, so consider it a parse error! + kdError(30506) << "parse error <br> tag not nested in <p> or <c> but in " + << stackCurrent->itemName << endl; + return false; + } + return true; +} + +// <cbr> (forced column break, not supported) +// <pbr> (forced page break) +static bool StartElementPBR(StackItem* /*stackItem*/, StackItem* stackCurrent, + TQDomDocument& mainDocument) +{ + // We are sure to be the child of a <p> element + + // The following code is similar to the one in StartElementP + // We use mainFramesetElement here not to be dependant that <section> has happened before + TQDomElement paragraphElementOut=mainDocument.createElement("PARAGRAPH"); + stackCurrent->m_frameset.appendChild(paragraphElementOut); + TQDomElement textElementOut=mainDocument.createElement("TEXT"); + paragraphElementOut.appendChild(textElementOut); + TQDomElement formatsPluralElementOut=mainDocument.createElement("FORMATS"); + paragraphElementOut.appendChild(formatsPluralElementOut); + + // We must now copy/clone the layout of elementText. + + TQDomNodeList nodeList=stackCurrent->stackElementParagraph.elementsByTagName("LAYOUT"); + + if (!nodeList.count()) + { + kdError(30506) << "Unable to find <LAYOUT> element! Aborting! (in StartElementPBR)" <<endl; + return false; + } + + // Now clone it + TQDomNode newNode=nodeList.item(0).cloneNode(true); // We make a deep cloning of the first element/node + if (newNode.isNull()) + { + kdError(30506) << "Unable to clone <LAYOUT> element! Aborting! (in StartElementPBR)" <<endl; + return false; + } + paragraphElementOut.appendChild(newNode); + + // We need a page break! + TQDomElement oldLayoutElement=nodeList.item(0).toElement(); + if (oldLayoutElement.isNull()) + { + kdError(30506) << "Cannot find old <LAYOUT> element! Aborting! (in StartElementPBR)" <<endl; + return false; + } + // We have now to add a element <PAGEBREAKING> + // TODO/FIXME: what if there is already one? + TQDomElement pagebreakingElement=mainDocument.createElement("PAGEBREAKING"); + pagebreakingElement.setAttribute("linesTogether","false"); + pagebreakingElement.setAttribute("hardFrameBreak","false"); + pagebreakingElement.setAttribute("hardFrameBreakAfter","true"); + oldLayoutElement.appendChild(pagebreakingElement); + + // Now that we have done with the old paragraph, + // we can write stackCurrent with the data of the new one! + // NOTE: The following code is similar to the one in StartElementP but we are working on stackCurrent! + stackCurrent->elementType=ElementTypeParagraph; + stackCurrent->stackElementParagraph=paragraphElementOut; // <PARAGRAPH> + stackCurrent->stackElementText=textElementOut; // <TEXT> + stackCurrent->stackElementFormatsPlural=formatsPluralElementOut; // <FORMATS> + stackCurrent->pos=0; // No text character yet + + return true; +} + +// <pagesize> +static bool StartElementPageSize(TQDomElement& paperElement, const TQXmlAttributes& attributes) +{ + if (attributes.value("page-scale").toDouble()!=1.0) + { + kdWarning(30506) << "Ignoring unsupported page scale: " << attributes.value("page-scale") << endl; + } + + int kwordOrientation; + TQString strOrientation=attributes.value("orientation").stripWhiteSpace(); + + if (strOrientation=="portrait") + { + kwordOrientation=0; + } + else if (strOrientation=="landscape") + { + kwordOrientation=1; + } + else + { + kdWarning(30506) << "Unknown page orientation: " << strOrientation << "! Ignoring! " << endl; + kwordOrientation=0; + } + + double kwordHeight; + double kwordWidth; + + TQString strPageType=attributes.value("pagetype").stripWhiteSpace(); + + // Do we know the page size or do we need to measure? + // For page formats that KWord knows, use our own values in case the values in the file would be wrong. + + KoFormat kwordFormat = KoPageFormat::formatFromString(strPageType); + + if (kwordFormat==PG_CUSTOM) + { + kdDebug(30506) << "Custom or other page format found: " << strPageType << endl; + + double height = attributes.value("height").toDouble(); + double width = attributes.value("width" ).toDouble(); + + TQString strUnits = attributes.value("units").stripWhiteSpace(); + + kdDebug(30506) << "Explicit page size: " + << height << " " << strUnits << " x " << width << " " << strUnits + << endl; + + if (strUnits=="cm") + { + kwordHeight = CentimetresToPoints(height); + kwordWidth = CentimetresToPoints(width); + } + else if (strUnits=="inch") + { + kwordHeight = InchesToPoints(height); + kwordWidth = InchesToPoints(width); + } + else if (strUnits=="mm") + { + kwordHeight = MillimetresToPoints(height); + kwordWidth = MillimetresToPoints(width); + } + else + { + kwordHeight = 0.0; + kwordWidth = 0.0; + kdWarning(30506) << "Unknown unit type: " << strUnits << endl; + } + } + else + { + // We have a format known by KOffice, so use KOffice's functions + kwordHeight = MillimetresToPoints(KoPageFormat::height(kwordFormat,PG_PORTRAIT)); + kwordWidth = MillimetresToPoints(KoPageFormat::width (kwordFormat,PG_PORTRAIT)); + } + + if ((kwordHeight <= 1.0) || (kwordWidth <= 1.0)) + // At least one of the two values is ridiculous + { + kdWarning(30506) << "Page width or height is too small: " + << kwordHeight << "x" << kwordWidth << endl; + // As we have no correct page size, we assume we have A4 + kwordFormat = PG_DIN_A4; + kwordHeight = CentimetresToPoints(29.7); + kwordWidth = CentimetresToPoints(21.0); + } + + // Now that we have gathered all the page size data, put it in the right element! + + if (paperElement.isNull()) + { + kdError(30506) << "<PAPER> element cannot be accessed! Aborting!" << endl; + return false; + } + + paperElement.setAttribute("format",kwordFormat); + paperElement.setAttribute("width",kwordWidth); + paperElement.setAttribute("height",kwordHeight); + paperElement.setAttribute("orientation",kwordOrientation); + + return true; +} + + +bool StructureParser::complexForcedPageBreak(StackItem* stackItem) +{ + // We are not a child of a <p> element, so we cannot use StartElementPBR directly + + StackItemStack auxilaryStack; + + if (!clearStackUntilParagraph(auxilaryStack)) + { + kdError(30506) << "Could not clear stack until a paragraph!" << endl; + return false; + } + + // Now we are a child of a <p> element! + + bool success=StartElementPBR(stackItem,structureStack.current(),mainDocument); + + // Now restore the stack + + StackItem* stackCurrent=structureStack.current(); + StackItem* item; + while (auxilaryStack.count()>0) + { + item=auxilaryStack.pop(); + // We cannot put back the item on the stack like that. + // We must set a few values for each item. + item->pos=0; // Start at position 0 + item->stackElementParagraph=stackCurrent->stackElementParagraph; // new <PARAGRAPH> + item->stackElementText=stackCurrent->stackElementText; // new <TEXT> + item->stackElementFormatsPlural=stackCurrent->stackElementFormatsPlural; // new <FORMATS> + structureStack.push(item); + } + + return success; +} + + +// <section> +bool StructureParser::StartElementSection(StackItem* stackItem, StackItem* /*stackCurrent*/, + const TQXmlAttributes& attributes) +{ + //TODO: non main text sections (e.g. footers) + stackItem->elementType=ElementTypeSection; + + AbiPropsMap abiPropsMap; + // Treat the props attributes in the two available flavors: lower case and upper case. + kdDebug(30506)<< "========== props=\"" << attributes.value("props") << "\"" << endl; + abiPropsMap.splitAndAddAbiProps(attributes.value("props")); + abiPropsMap.splitAndAddAbiProps(attributes.value("PROPS")); // PROPS is deprecated + + // TODO: only the first main text section should change the page margins + // TODO; (as KWord does not allow different page sizes/margins for the same document) + if (true && (!m_paperBordersElement.isNull())) + { + TQString str; + str=abiPropsMap["page-margin-top"].getValue(); + if (!str.isEmpty()) + { + m_paperBordersElement.setAttribute("top",ValueWithLengthUnit(str)); + } + str=abiPropsMap["page-margin-left"].getValue(); + if (!str.isEmpty()) + { + m_paperBordersElement.setAttribute("left",ValueWithLengthUnit(str)); + } + str=abiPropsMap["page-margin-bottom"].getValue(); + if (!str.isEmpty()) + { + m_paperBordersElement.setAttribute("bottom",ValueWithLengthUnit(str)); + } + str=abiPropsMap["page-margin-right"].getValue(); + if (!str.isEmpty()) + { + m_paperBordersElement.setAttribute("right",ValueWithLengthUnit(str)); + } + } + return true; +} + +// <iw> + +static bool EndElementIW(StackItem* stackItem, StackItem* /*stackCurrent*/, + TQDomDocument& mainDocument, TQDomElement& m_ignoreWordsElement) +{ + if (!stackItem->elementType==ElementTypeIgnoreWord) + { + kdError(30506) << "Wrong element type!! Aborting! (in endElementIW)" << endl; + return false; + } + TQDomElement wordElement=mainDocument.createElement("SPELLCHECKIGNOREWORD"); + wordElement.setAttribute("word",stackItem->strTemp2.stripWhiteSpace()); + m_ignoreWordsElement.appendChild(wordElement); + return true; +} + +// <foot> +bool StructureParser::StartElementFoot(StackItem* stackItem, StackItem* /*stackCurrent*/, + const TQXmlAttributes& /*attributes*/) +{ +#if 0 + stackItem->elementType=ElementTypeFoot; + + const TQString id(attributes.value("endnote-id").stripWhiteSpace()); + kdDebug(30506) << "Foot note id: " << id << endl; + + if (id.isEmpty()) + { + kdWarning(30506) << "Footnote has no id!" << endl; + stackItem->elementType=ElementTypeIgnore; + return true; + } + + // We need to create a frameset for the foot note. + TQDomElement framesetElement(mainDocument.createElement("FRAMESET")); + framesetElement.setAttribute("frameType",1); + framesetElement.setAttribute("frameInfo",7); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",getFootnoteFramesetName(id)); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut(mainDocument.createElement("FRAME")); + //frameElementOut.setAttribute("left",28); + //frameElementOut.setAttribute("top",42); + //frameElementOut.setAttribute("bottom",566); + //frameElementOut.setAttribute("right",798); + frameElementOut.setAttribute("runaround",1); + // ### TODO: a few attributes are missing + framesetElement.appendChild(frameElementOut); + + stackItem->m_frameset=framesetElement; +#else + stackItem->elementType=ElementTypeIgnore; +#endif + return true; +} + +// Element <table> +bool StructureParser::StartElementTable(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes) +{ +#if 1 + // In KWord, inline tables are inside a paragraph. + // In AbiWord, tables are outside any paragraph. + + TQStringList widthList; + widthList.split('/', attributes.value("table-column-props"), false); + const uint columns = widthList.size(); + stackItem->m_doubleArray.detach(); // Be sure not to modify parents + stackItem->m_doubleArray.resize(columns+1); // All left positions but the last right one + stackItem->m_doubleArray[0] = 0.0; + TQStringList::ConstIterator it; + uint i; + for ( i=0, it=widthList.begin(); i<columns; ++i, ++it ) + { + kdDebug(30506) << "Column width: " << (*it) << " cooked " << ValueWithLengthUnit(*it) << endl; + stackItem->m_doubleArray[i+1] = ValueWithLengthUnit(*it) + stackItem->m_doubleArray[i]; + } + // ### TODO: in case of automatic column widths, we have not any width given by AbiWord + + const uint tableNumber(++m_tableGroupNumber); + const TQString tableName(i18n("Table %1").arg(tableNumber)); + + TQDomElement elementText=stackCurrent->stackElementText; + TQDomElement paragraphElementOut=mainDocument.createElement("PARAGRAPH"); + stackCurrent->m_frameset.appendChild(paragraphElementOut); + + TQDomElement textElementOut(mainDocument.createElement("TEXT")); + textElementOut.appendChild(mainDocument.createTextNode("#")); + paragraphElementOut.appendChild(textElementOut); + + TQDomElement formatsPluralElementOut=mainDocument.createElement("FORMATS"); + paragraphElementOut.appendChild(formatsPluralElementOut); + + TQDomElement elementFormat(mainDocument.createElement("FORMAT")); + elementFormat.setAttribute("id",6); + elementFormat.setAttribute("pos",0); + elementFormat.setAttribute("len",1); + formatsPluralElementOut.appendChild(elementFormat); + + TQDomElement elementAnchor(mainDocument.createElement("ANCHOR")); + elementAnchor.setAttribute("type","frameset"); + elementAnchor.setAttribute("instance",tableName); + elementFormat.appendChild(elementAnchor); + + stackItem->elementType=ElementTypeTable; + stackItem->stackElementParagraph=paragraphElementOut; // <PARAGRAPH> + stackItem->stackElementText=textElementOut; // <TEXT> + stackItem->stackElementFormatsPlural=formatsPluralElementOut; // <FORMATS> + stackItem->strTemp1=tableName; + stackItem->strTemp2=TQString::number(tableNumber); // needed as I18N does not allow adding phrases + stackItem->pos=1; // Just # + + // Now we populate the layout + TQDomElement layoutElement=mainDocument.createElement("LAYOUT"); + paragraphElementOut.appendChild(layoutElement); + + AbiPropsMap abiPropsMap; + styleDataMap.useOrCreateStyle("Normal"); // We might have to create the "Normal" style. + AddLayout("Normal", layoutElement, stackItem, mainDocument, abiPropsMap, 0, false); +#else + stackItem->elementType=ElementTypeIgnore; +#endif + return true; +} + +// <cell> +bool StructureParser::StartElementCell(StackItem* stackItem, StackItem* stackCurrent, + const TQXmlAttributes& attributes) +{ +#if 1 + if (stackCurrent->elementType!=ElementTypeTable) + { + kdError(30506) << "Wrong element type!! Aborting! (in StructureParser::endElementCell)" << endl; + return false; + } + + stackItem->elementType=ElementTypeCell; + + const TQString tableName(stackCurrent->strTemp1); + kdDebug(30506) << "Table name: " << tableName << endl; + + if (tableName.isEmpty()) + { + kdError(30506) << "Table name is empty! Aborting!" << endl; + return false; + } + + AbiPropsMap abiPropsMap; + abiPropsMap.splitAndAddAbiProps(attributes.value("props")); // Do not check PROPS + + // We abuse the attach number to know the row and col numbers. + const uint row=abiPropsMap["top-attach"].getValue().toUInt(); + const uint col=abiPropsMap["left-attach"].getValue().toUInt(); + + if ( col >= stackItem->m_doubleArray.size() ) + { + // We do not know the right position of this column, so improvise. (### TODO) + // We play on the fact that TQByteArray uses shallow copies by default. + // (We do want that the change is known at <table> level) + stackItem->m_doubleArray.resize( stackItem->m_doubleArray.size() + 1, TQGArray::SpeedOptim ); + stackItem->m_doubleArray[col+1] = stackItem->m_doubleArray[col] + 72; // Try 1 inch + } + + const TQString frameName(i18n("Frameset name","Table %3, row %1, column %2") + .arg(row).arg(col).arg(stackCurrent->strTemp2)); // As the stack could be wrong, be careful and use the string as last! + + // We need to create a frameset for the cell + TQDomElement framesetElement(mainDocument.createElement("FRAMESET")); + framesetElement.setAttribute("frameType",1); + framesetElement.setAttribute("frameInfo",0); + framesetElement.setAttribute("visible",1); + framesetElement.setAttribute("name",frameName); + framesetElement.setAttribute("row",row); + framesetElement.setAttribute("col",col); + framesetElement.setAttribute("rows",1); // ### TODO: rowspan + framesetElement.setAttribute("cols",1); // ### TODO: colspan + framesetElement.setAttribute("grpMgr",tableName); + framesetsPluralElement.appendChild(framesetElement); + + TQDomElement frameElementOut(mainDocument.createElement("FRAME")); + frameElementOut.setAttribute( "left", stackItem->m_doubleArray[col] ); + frameElementOut.setAttribute( "right", stackItem->m_doubleArray[col+1] ); + frameElementOut.setAttribute("top",0); + frameElementOut.setAttribute("bottom",0); + frameElementOut.setAttribute("runaround",1); + frameElementOut.setAttribute("autoCreateNewFrame",0); // Very important for cell growing! + // ### TODO: a few attributes are missing + framesetElement.appendChild(frameElementOut); + + stackItem->m_frameset=framesetElement; + TQDomElement nullDummy; + stackItem->stackElementParagraph=nullDummy; // <PARAGRAPH> + stackItem->stackElementText=nullDummy; // <TEXT> + stackItem->stackElementFormatsPlural=nullDummy; // <FORMATS> + +#else + stackItem->elementType=ElementTypeIgnore; +#endif + return true; +} + +// Parser for SAX2 + +bool StructureParser :: startElement( const TQString&, const TQString&, const TQString& name, const TQXmlAttributes& attributes) +{ + //Warning: be careful that some element names can be lower case or upper case (not very XML) + kdDebug(30506) << indent << " <" << name << ">" << endl; //DEBUG + indent += "*"; //DEBUG + + if (structureStack.isEmpty()) + { + kdError(30506) << "Stack is empty!! Aborting! (in StructureParser::startElement)" << endl; + return false; + } + + // Create a new stack element copying the top of the stack. + StackItem *stackItem=new StackItem(*structureStack.current()); + + if (!stackItem) + { + kdError(30506) << "Could not create Stack Item! Aborting! (in StructureParser::startElement)" << endl; + return false; + } + + stackItem->itemName=name; + + bool success=false; + + if ((name=="c")||(name=="C")) + { + success=StartElementC(stackItem,structureStack.current(),attributes); + } + else if ((name=="p")||(name=="P")) + { + success=StartElementP(stackItem,structureStack.current(),mainDocument, + styleDataMap,attributes); + } + else if ((name=="section")||(name=="SECTION")) + { + success=StartElementSection(stackItem,structureStack.current(),attributes); + } + else if (name=="a") + { + success=StartElementA(stackItem,structureStack.current(),attributes); + } + else if (name=="br") // NOTE: Not sure if it only exists in lower case! + { + // We have a forced line break + StackItem* stackCurrent=structureStack.current(); + success=StartElementBR(stackItem,stackCurrent,mainDocument); + } + else if (name=="cbr") // NOTE: Not sure if it only exists in lower case! + { + // We have a forced column break (not supported by KWord) + stackItem->elementType=ElementTypeEmpty; + StackItem* stackCurrent=structureStack.current(); + if (stackCurrent->elementType==ElementTypeContent) + { + kdWarning(30506) << "Forced column break found! Transforming to forced page break" << endl; + success=complexForcedPageBreak(stackItem); + } + else if (stackCurrent->elementType==ElementTypeParagraph) + { + kdWarning(30506) << "Forced column break found! Transforming to forced page break" << endl; + success=StartElementPBR(stackItem,stackCurrent,mainDocument); + } + else + { + kdError(30506) << "Forced column break found out of turn! Aborting! Parent: " + << stackCurrent->itemName <<endl; + success=false; + } + } + else if (name=="pbr") // NOTE: Not sure if it only exists in lower case! + { + // We have a forced page break + stackItem->elementType=ElementTypeEmpty; + StackItem* stackCurrent=structureStack.current(); + if (stackCurrent->elementType==ElementTypeContent) + { + success=complexForcedPageBreak(stackItem); + } + else if (stackCurrent->elementType==ElementTypeParagraph) + { + success=StartElementPBR(stackItem,stackCurrent,mainDocument); + } + else + { + kdError(30506) << "Forced page break found out of turn! Aborting! Parent: " + << stackCurrent->itemName <<endl; + success=false; + } + } + else if (name=="pagesize") + // Does only exist as lower case tag! + { + stackItem->elementType=ElementTypeEmpty; + stackItem->stackElementText=structureStack.current()->stackElementText; // TODO: reason? + success=StartElementPageSize(m_paperElement,attributes); + } + else if ((name=="field") //TODO: upper-case? + || (name=="f")) // old deprecated name for <field> + { + success=StartElementField(stackItem,structureStack.current(),mainDocument,attributes); + } + else if (name=="s") // Seems only to exist as lower case + { + success=StartElementS(stackItem,structureStack.current(),attributes,styleDataMap); + } + else if ((name=="image") //TODO: upper-case? + || (name=="i")) // old deprecated name for <image> + { + success=StartElementImage(stackItem,structureStack.current(),attributes); + } + else if (name=="d") // TODO: upper-case? + { + success=StartElementD(stackItem,structureStack.current(),attributes); + } + else if (name=="iw") // No upper-case + { + stackItem->elementType=ElementTypeIgnoreWord; + success=true; + } + else if (name=="m") // No upper-case + { + success=StartElementM(stackItem,structureStack.current(),attributes); + } + else if (name=="foot") // No upper-case + { + success=StartElementFoot(stackItem,structureStack.current(),attributes); + } + else if (name=="table") // No upper-case + { + success=StartElementTable(stackItem,structureStack.current(), attributes); + } + else if (name=="cell") // No upper-case + { + success=StartElementCell(stackItem,structureStack.current(),attributes); + } + else + { + stackItem->elementType=ElementTypeUnknown; + stackItem->stackElementText=structureStack.current()->stackElementText; // TODO: reason? + success=true; + } + if (success) + { + structureStack.push(stackItem); + } + else + { // We have a problem so destroy our resources. + delete stackItem; + } + return success; +} + +bool StructureParser :: endElement( const TQString&, const TQString& , const TQString& name) +{ + indent.remove( 0, 1 ); // DEBUG + kdDebug(30506) << indent << " </" << name << ">" << endl; + + if (structureStack.isEmpty()) + { + kdError(30506) << "Stack is empty!! Aborting! (in StructureParser::endElement)" << endl; + return false; + } + + bool success=false; + + StackItem *stackItem=structureStack.pop(); + if ((name=="c")||(name=="C")) + { + success=EndElementC(stackItem,structureStack.current()); + } + else if ((name=="p")||(name=="P")) + { + success=EndElementP(stackItem); + } + else if (name=="a") + { + if (stackItem->elementType==ElementTypeContent) + { + // Anchor to a bookmark (not supported by KWord)) + success=EndElementC(stackItem,structureStack.current()); + } + else + { + // Normal anchor + success=EndElementA(stackItem,structureStack.current(), mainDocument); + } + } + else if (name=="d") + { + success=EndElementD(stackItem); + } + else if (name=="iw") // No upper-case + { + success=EndElementIW(stackItem,structureStack.current(), mainDocument, m_ignoreWordsElement); + } + else if (name=="m") // No upper-case + { + success=EndElementM(stackItem); + } + else + { + success=true; // No problem, so authorisation to continue parsing + } + if (!success) + { + // If we have no success, then it was surely a tag mismatch. Help debugging! + kdError(30506) << "Found tag name: " << name + << " expected: " << stackItem->itemName << endl; + } + delete stackItem; + return success; +} + +bool StructureParser :: characters ( const TQString & ch ) +{ + // DEBUG start + if (ch=="\n") + { + kdDebug(30506) << indent << " (LINEFEED)" << endl; + } + else if (ch.length()> 40) + { // 40 characters are enough (especially for image data) + kdDebug(30506) << indent << " :" << ch.left(40) << "..." << endl; + } + else + { + kdDebug(30506) << indent << " :" << ch << ":" << endl; + } + // DEBUG end + if (structureStack.isEmpty()) + { + kdError(30506) << "Stack is empty!! Aborting! (in StructureParser::characters)" << endl; + return false; + } + + bool success=false; + + StackItem *stackItem=structureStack.current(); + + if ((stackItem->elementType==ElementTypeContent) + || (stackItem->elementType==ElementTypeAnchorContent)) + { // <c> + success=charactersElementC(stackItem,mainDocument,ch); + } + else if (stackItem->elementType==ElementTypeParagraph) + { // <p> + success=charactersElementP(stackItem,mainDocument,ch); + } + else if (stackItem->elementType==ElementTypeAnchor) + { // <a> + success=charactersElementA(stackItem,ch); + } + else if (stackItem->elementType==ElementTypeEmpty) + { + success=ch.stripWhiteSpace().isEmpty(); + if (!success) + { + // We have a parsing error, so abort! + kdError(30506) << "Empty element "<< stackItem->itemName + <<" is not empty! Aborting! (in StructureParser::characters)" << endl; + } + } + else if (stackItem->elementType==ElementTypeRealData) + { + success=CharactersElementD(stackItem,mainDocument,ch); + } + else if (stackItem->elementType==ElementTypeIgnoreWord) + { + stackItem->strTemp2+=ch; // Just collect the data + success=true; + } + else if (stackItem->elementType==ElementTypeRealMetaData) + { + success=CharactersElementM(stackItem,ch); + } + else + { + success=true; + } + + return success; +} + +bool StructureParser::startDocument(void) +{ + indent = TQString(); //DEBUG + styleDataMap.defineDefaultStyles(); + return true; +} + +void StructureParser::createDocInfo(void) +{ + TQDomImplementation implementation; + TQDomDocument doc(implementation.createDocumentType("document-info", + "-//KDE//DTD document-info 1.2//EN", "http://www.koffice.org/DTD/document-info-1.2.dtd")); + + m_info=doc; + + m_info.appendChild( + mainDocument.createProcessingInstruction( + "xml","version=\"1.0\" encoding=\"UTF-8\"")); + + TQDomElement elementDoc(mainDocument.createElement("document-info")); + elementDoc.setAttribute("xmlns","http://www.koffice.org/DTD/document-info"); + m_info.appendChild(elementDoc); + + TQDomElement about(mainDocument.createElement("about")); + elementDoc.appendChild(about); + + TQDomElement abstract(mainDocument.createElement("abstract")); + about.appendChild(abstract); + abstract.appendChild(mainDocument.createTextNode(m_metadataMap["dc.description"])); + + TQDomElement title(mainDocument.createElement("title")); + about.appendChild(title); + title.appendChild(mainDocument.createTextNode(m_metadataMap["dc.title"])); + + TQDomElement keyword(mainDocument.createElement("keyword")); + about.appendChild(keyword); + keyword.appendChild(mainDocument.createTextNode(m_metadataMap["abiword.keywords"])); + + TQDomElement subject(mainDocument.createElement("subject")); + about.appendChild(subject); + subject.appendChild(mainDocument.createTextNode(m_metadataMap["dc.subject"])); +} + +bool StructureParser::endDocument(void) +{ + TQDomElement stylesPluralElement=mainDocument.createElement("STYLES"); + // insert before <PICTURES>, as <PICTURES> must remain last. + mainDocument.documentElement().insertBefore(stylesPluralElement,m_picturesElement); + + kdDebug(30506) << "###### Start Style List ######" << endl; + StyleDataMap::ConstIterator it; + + // At first, we put the Normal style + it=styleDataMap.find("Normal"); + if (it!=styleDataMap.end()) + { + kdDebug(30506) << "\"" << it.key() << "\" => " << it.data().m_props << endl; + TQDomElement styleElement=mainDocument.createElement("STYLE"); + stylesPluralElement.appendChild(styleElement); + AddStyle(styleElement, it.key(),it.data(),mainDocument); + } + else + kdWarning(30506) << "No 'Normal' style" << endl; + + for (it=styleDataMap.begin();it!=styleDataMap.end();++it) + { + if (it.key()=="Normal") + continue; + + kdDebug(30506) << "\"" << it.key() << "\" => " << it.data().m_props << endl; + + TQDomElement styleElement=mainDocument.createElement("STYLE"); + stylesPluralElement.appendChild(styleElement); + + AddStyle(styleElement, it.key(),it.data(),mainDocument); + } + kdDebug(30506) << "###### End Style List ######" << endl; + + createDocInfo(); + + return true; +} + +bool StructureParser::warning(const TQXmlParseException& exception) +{ + kdWarning(30506) << "XML parsing warning: line " << exception.lineNumber() + << " col " << exception.columnNumber() << " message: " << exception.message() << endl; + return true; +} + +bool StructureParser::error(const TQXmlParseException& exception) +{ + // A XML error is recoverable, so it is only a KDE warning + kdWarning(30506) << "XML parsing error: line " << exception.lineNumber() + << " col " << exception.columnNumber() << " message: " << exception.message() << endl; + return true; +} + +bool StructureParser::fatalError (const TQXmlParseException& exception) +{ + kdError(30506) << "XML parsing fatal error: line " << exception.lineNumber() + << " col " << exception.columnNumber() << " message: " << exception.message() << endl; + m_fatalerror=true; + KMessageBox::error(NULL, i18n("An error has occurred while parsing the AbiWord file.\nAt line: %1, column %2\nError message: %3") + .arg(exception.lineNumber()).arg(exception.columnNumber()) + .arg( i18n( "TQXml", exception.message().utf8() ) ), + i18n("AbiWord Import Filter"),0); + return false; // Stop parsing now, we do not need further errors. +} + +void StructureParser :: createDocument(void) +{ + TQDomImplementation implementation; + TQDomDocument doc(implementation.createDocumentType("DOC", + "-//KDE//DTD kword 1.2//EN", "http://www.koffice.org/DTD/kword-1.2.dtd")); + + mainDocument=doc; + + mainDocument.appendChild( + mainDocument.createProcessingInstruction( + "xml","version=\"1.0\" encoding=\"UTF-8\"")); + + TQDomElement elementDoc; + elementDoc=mainDocument.createElement("DOC"); + elementDoc.setAttribute("xmlns","http://www.koffice.org/DTD/kword"); + elementDoc.setAttribute("editor","AbiWord Import Filter"); + elementDoc.setAttribute("mime","application/x-kword"); + elementDoc.setAttribute( "syntaxVersion", 3 ); + mainDocument.appendChild(elementDoc); + + TQDomElement element; + element=mainDocument.createElement("ATTRIBUTES"); + element.setAttribute("processing",0); + element.setAttribute("standardpage",1); + element.setAttribute("hasHeader",0); + element.setAttribute("hasFooter",0); + //element.setAttribute("unit","mm"); // use KWord default instead + element.setAttribute("tabStopValue",36); // AbiWord has a default of 0.5 inch tab stops + elementDoc.appendChild(element); + + // <PAPER> will be partialy changed by an AbiWord <pagesize> element. + // Default paper format of AbiWord is "Letter" + m_paperElement=mainDocument.createElement("PAPER"); + m_paperElement.setAttribute("format",PG_US_LETTER); + m_paperElement.setAttribute("width",MillimetresToPoints(KoPageFormat::width (PG_US_LETTER,PG_PORTRAIT))); + m_paperElement.setAttribute("height",MillimetresToPoints(KoPageFormat::height(PG_US_LETTER,PG_PORTRAIT))); + m_paperElement.setAttribute("orientation",PG_PORTRAIT); + m_paperElement.setAttribute("columns",1); + m_paperElement.setAttribute("columnspacing",2); + m_paperElement.setAttribute("hType",0); + m_paperElement.setAttribute("fType",0); + m_paperElement.setAttribute("spHeadBody",9); + m_paperElement.setAttribute("spFootBody",9); + m_paperElement.setAttribute("zoom",100); + elementDoc.appendChild(m_paperElement); + + m_paperBordersElement=mainDocument.createElement("PAPERBORDERS"); + m_paperBordersElement.setAttribute("left",28); + m_paperBordersElement.setAttribute("top",42); + m_paperBordersElement.setAttribute("right",28); + m_paperBordersElement.setAttribute("bottom",42); + m_paperElement.appendChild(m_paperBordersElement); + + framesetsPluralElement=mainDocument.createElement("FRAMESETS"); + mainDocument.documentElement().appendChild(framesetsPluralElement); + + mainFramesetElement=mainDocument.createElement("FRAMESET"); + mainFramesetElement.setAttribute("frameType",1); + mainFramesetElement.setAttribute("frameInfo",0); + mainFramesetElement.setAttribute("visible",1); + mainFramesetElement.setAttribute("name",i18n("Frameset name","Main Text Frameset")); + framesetsPluralElement.appendChild(mainFramesetElement); + + TQDomElement frameElementOut=mainDocument.createElement("FRAME"); + frameElementOut.setAttribute("left",28); + frameElementOut.setAttribute("top",42); + frameElementOut.setAttribute("bottom",566); + frameElementOut.setAttribute("right",798); + frameElementOut.setAttribute("runaround",1); + // TODO: a few attributes are missing + mainFramesetElement.appendChild(frameElementOut); + + // As we are manipulating the document, create a few particular elements + m_ignoreWordsElement=mainDocument.createElement("SPELLCHECKIGNORELIST"); + mainDocument.documentElement().appendChild(m_ignoreWordsElement); + m_picturesElement=mainDocument.createElement("PICTURES"); + mainDocument.documentElement().appendChild(m_picturesElement); +} + +bool StructureParser::clearStackUntilParagraph(StackItemStack& auxilaryStack) +{ + for (;;) + { + StackItem* item=structureStack.pop(); + switch (item->elementType) + { + case ElementTypeContent: + { + // Push the item on the auxilary stack + auxilaryStack.push(item); + break; + } + case ElementTypeParagraph: + { + // Push back the item on this stack and then stop loop + structureStack.push(item); + return true; + } + default: + { + // Something has gone wrong! + kdError(30506) << "Cannot clear this element: " + << item->itemName << endl; + return false; + } + } + } +} + +ABIWORDImport::ABIWORDImport(KoFilter */*parent*/, const char */*name*/, const TQStringList &) : + KoFilter() { +} + +KoFilter::ConversionStatus ABIWORDImport::convert( const TQCString& from, const TQCString& to ) +{ + if ((to != "application/x-kword") || (from != "application/x-abiword")) + return KoFilter::NotImplemented; + + kdDebug(30506)<<"AbiWord to KWord Import filter"<<endl; + + StructureParser handler(m_chain); + + //We arbitrarily decide that TQt can handle the encoding in which the file was written!! + TQXmlSimpleReader reader; + reader.setContentHandler( &handler ); + reader.setErrorHandler( &handler ); + + //Find the last extension + TQString strExt; + TQString fileIn = m_chain->inputFile(); + const int result=fileIn.findRev('.'); + if (result>=0) + { + strExt=fileIn.mid(result); + } + + kdDebug(30506) << "File extension: -" << strExt << "-" << endl; + + TQString strMime; // Mime type of the compressor (default: unknown) + + if ((strExt==".gz")||(strExt==".GZ") //in case of .abw.gz (logical extension) + ||(strExt==".zabw")||(strExt==".ZABW")) //in case of .zabw (extension used prioritary with AbiWord) + { + // Compressed with gzip + strMime="application/x-gzip"; + kdDebug(30506) << "Compression: gzip" << endl; + } + else if ((strExt==".bz2")||(strExt==".BZ2") //in case of .abw.bz2 (logical extension) + ||(strExt==".bzabw")||(strExt==".BZABW")) //in case of .bzabw (extension used prioritary with AbiWord) + { + // Compressed with bzip2 + strMime="application/x-bzip2"; + kdDebug(30506) << "Compression: bzip2" << endl; + } + + TQIODevice* in = KFilterDev::deviceForFile(fileIn,strMime); + + if ( !in ) + { + kdError(30506) << "Cannot create device for uncompressing! Aborting!" << endl; + return KoFilter::FileNotFound; // ### TODO: better error? + } + + if (!in->open(IO_ReadOnly)) + { + kdError(30506) << "Cannot open file for uncompressing! Aborting!" << endl; + delete in; + return KoFilter::FileNotFound; + } + + TQXmlInputSource source(in); // Read the file + + in->close(); + + if (!reader.parse( source )) + { + kdError(30506) << "Import: Parsing unsuccessful. Aborting!" << endl; + delete in; + if (!handler.wasFatalError()) + { + // As the parsing was stopped for something else than a fatal error, we have not yet get an error message. (Can it really happen?) + KMessageBox::error(NULL, i18n("An error occurred during the load of the AbiWord file: %1").arg(from.data()), + i18n("AbiWord Import Filter"),0); + } + return KoFilter::ParsingError; + } + delete in; + + TQCString strOut; + KoStoreDevice* out; + + kdDebug(30506) << "Creating documentinfo.xml" << endl; + out=m_chain->storageFile( "documentinfo.xml", KoStore::Write ); + if(!out) + { + kdError(30506) << "AbiWord Import unable to open output file! (Documentinfo)" << endl; + KMessageBox::error(NULL, i18n("Unable to save document information."),i18n("AbiWord Import Filter"),0); + return KoFilter::StorageCreationError; + } + + //Write the document information! + strOut=handler.getDocInfo().toCString(); // UTF-8 + // WARNING: we cannot use KoStore::write(const TQByteArray&) because it writes an extra NULL character at the end. + out->writeBlock(strOut,strOut.length()); + + kdDebug(30506) << "Creating maindoc.xml" << endl; + out=m_chain->storageFile( "root", KoStore::Write ); + if(!out) + { + kdError(30506) << "AbiWord Import unable to open output file! (Root)" << endl; + KMessageBox::error(NULL, i18n("Unable to save main document."),i18n("AbiWord Import Filter"),0); + return KoFilter::StorageCreationError; + } + + //Write the document! + strOut=handler.getDocument().toCString(); // UTF-8 + // WARNING: we cannot use KoStore::write(const TQByteArray&) because it writes an extra NULL character at the end. + out->writeBlock(strOut,strOut.length()); + +#if 0 + kdDebug(30506) << documentOut.toString(); +#endif + + kdDebug(30506) << "Now importing to KWord!" << endl; + + return KoFilter::OK; +} + +#include "abiwordimport.moc" |