You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/filters/kword/mswrite/mswriteimport.cc

1485 lines
40 KiB

/* This file is part of the KDE project
Copyright (C) 2001-2003 Clarence Dang <dang@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 Version 2 as published by the Free Software Foundation.
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 Version 2 for more details.
You should have received a copy of the GNU Library General Public License
Version 2 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.
*/
#ifndef NDEBUG
//#define DEBUG_XML_OUTPUT
#endif
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <tqfile.h>
#include <tqobject.h>
#include <tqstring.h>
#include <tqtextcodec.h>
#include <tqtextstream.h>
#include <kdebug.h>
#include <kgenericfactory.h>
#include <KoFilterChain.h>
#include <KoStore.h>
#include "list.h"
#include "libmswrite.h"
#include "ImportDialog.h"
#include "mswriteimport.h"
class MSWriteImportFactory : KGenericFactory <MSWriteImport, KoFilter>
{
public:
MSWriteImportFactory () : KGenericFactory <MSWriteImport, KoFilter> ("kwordmswriteimport")
{
}
protected:
virtual void setupTranslations (void)
{
TDEGlobal::locale()->insertCatalogue ("kofficefilters");
}
};
K_EXPORT_COMPONENT_FACTORY (libmswriteimport, MSWriteImportFactory ())
//
// Device that reads from .WRI file
//
class WRIDevice : public MSWrite::Device
{
private:
FILE *m_infp;
public:
WRIDevice () : m_infp (NULL)
{
}
virtual ~WRIDevice ()
{
closeFile ();
}
bool openFile (const char *fileName)
{
m_infp = fopen (fileName, "rb");
if (!m_infp)
{
error (MSWrite::Error::FileError, "could not open file for reading\n");
return false;
}
return true;
}
bool closeFile (void)
{
if (m_infp)
{
if (fclose (m_infp))
{
error (MSWrite::Error::FileError, "could not close input file\n");
return false;
}
m_infp = NULL;
}
return true;
}
bool read (MSWrite::Byte *buf, const MSWrite::DWord numBytes)
{
if (fread (buf, 1, (size_t) numBytes, m_infp) != (size_t) numBytes)
{
error (MSWrite::Error::FileError, "could not read from input file\n");
return false;
}
return true;
}
bool write (const MSWrite::Byte *, const MSWrite::DWord)
{
error (MSWrite::Error::InternalError, "writing to an input file?\n");
return false;
}
bool seek (const long offset, const int whence)
{
if (fseek (m_infp, offset, whence))
{
error (MSWrite::Error::InternalError, "could not seek input file\n");
return false;
}
return true;
}
long tell (void)
{
return ftell (m_infp);
}
void debug (const char *s)
{
kdDebug (30509) << s;
}
void debug (const int i)
{
kdDebug (30509) << i;
}
void error (const int errorCode, const char *message,
const char * /*file*/ = "", const int /*lineno*/ = 0,
MSWrite::DWord /*tokenValue*/ = NoToken)
{
if (errorCode == MSWrite::Error::Warn)
kdWarning (30509) << message;
else
{
m_error = errorCode;
kdError (30509) << message;
}
}
};
//
// Generator that creates the KWord file
//
class KWordGenerator : public MSWrite::Generator, public MSWrite::NeedsDevice
{
private:
// KoStore can only have 1 file open at a time
// so we store objects in this structure temporarily
class WRIObject
{
private:
WRIObject (const WRIObject &rhs);
public:
MSWrite::Byte *m_data;
MSWrite::DWord m_dataLength;
MSWrite::DWord m_dataUpto;
TQString m_nameInStore;
WRIObject () : m_data (NULL), m_dataLength (0), m_dataUpto (0)
{
}
~WRIObject ()
{
delete [] m_data;
}
WRIObject operator= (const WRIObject &rhs)
{
delete [] m_data;
m_dataLength = rhs.m_dataLength;
m_dataUpto = rhs.m_dataUpto;
m_nameInStore = rhs.m_nameInStore;
if (rhs.m_data)
{
m_data = new MSWrite::Byte [m_dataLength];
if (m_data)
memcpy (m_data, rhs.m_data, m_dataLength);
// remember to check m_data before use
}
else
m_data = NULL;
return *this;
}
};
// page/margin dimensions
int m_pageWidth, m_pageHeight;
int m_left, m_right, m_top, m_bottom; // describing border of Text Frameset (position, not magnitude)
int m_leftMargin, m_rightMargin, m_topMargin, m_bottomMargin;
int m_headerFromTop, m_footerFromTop;
bool m_hasHeader, m_isHeaderOnFirstPage;
bool m_hasFooter, m_isFooterOnFirstPage;
bool m_writeHeaderFirstTime, m_writeFooterFirstTime;
int inWhat;
enum inWhatPossiblities
{
Nothing,
Header,
Footer,
Body
};
int m_startingPageNumber;
KoFilterChain *m_chain;
KoStoreDevice *m_outfile;
// for charset conversion
TQTextCodec *m_codec;
TQTextDecoder *m_decoder;
// import options (compensate for differences between KWord and MS Write)
bool m_simulateLineSpacing;
bool m_simulateImageOffset;
// formatting
TQString m_formatOutput;
int m_charInfoCountStart, m_charInfoCountLen;
bool m_pageBreak, m_needAnotherParagraph;
int m_pageBreakOffset;
int m_lineSpacingFromAbove;
// picture counters
int m_numPictures;
TQString m_pictures;
TQString m_objectFrameset;
MSWrite::List <WRIObject> m_objectList;
double m_objectHorizOffset;
bool m_paraIsImage;
MSWriteImport *m_koLink;
// XML output that is held back until after "Text Frameset 1" is output
// (i.e. header & footer must come after the Body in KWord)
bool m_delayOutput;
TQString m_heldOutput;
void delayOutput (const bool yes)
{
m_delayOutput = yes;
}
bool delayOutputFlush (void)
{
TQCString strUtf8 = m_heldOutput.utf8 ();
int strLength = strUtf8.length ();
if (m_outfile->writeBlock (strUtf8, strLength) != strLength)
ErrorAndQuit (MSWrite::Error::FileError, "could not write delayed output\n");
m_heldOutput = "";
return true;
}
public:
KWordGenerator () : m_hasHeader (false), m_isHeaderOnFirstPage (false),
m_hasFooter (false), m_isFooterOnFirstPage (false),
m_writeHeaderFirstTime (true), m_writeFooterFirstTime (true),
inWhat (Nothing),
m_decoder (NULL),
m_simulateLineSpacing (false),
m_simulateImageOffset (true),
m_pageBreak (false), m_needAnotherParagraph (false),
m_lineSpacingFromAbove (0),
m_numPictures (0)
{
delayOutput (false);
// just select windows-1252 until the "Select Encoding" dialog works
m_codec = TQTextCodec::codecForName ("CP 1252");
if (m_codec)
m_decoder = m_codec->makeDecoder();
else
kdWarning (30509) << "Cannot convert from Win Charset!" << endl;
}
virtual ~KWordGenerator ()
{
delete m_decoder;
}
void setFilterChain (KoFilterChain *chain)
{
m_chain = chain;
}
bool writeDocumentBegin (const MSWrite::Word,
const MSWrite::PageLayout *pageLayout)
{
kdDebug (30509) << "writeDocumentBegin()" << endl;
// open maindoc.xml
m_outfile = m_chain->storageFile ("root", KoStore::Write);
if (!m_outfile)
ErrorAndQuit (MSWrite::Error::FileError, "could not open root in store\n");
//
// store page dimensions for now
//
// page width & height
m_pageWidth = Twip2Point (pageLayout->getPageWidth ());
m_pageHeight = Twip2Point (pageLayout->getPageHeight ());
// offset of margins
m_left = Twip2Point (pageLayout->getLeftMargin ());
m_right = m_left + Twip2Point (pageLayout->getTextWidth ()) - 1;
m_top = Twip2Point (pageLayout->getTopMargin ());
m_bottom = m_top + Twip2Point (pageLayout->getTextHeight ()) - 1;
// size of margins
m_leftMargin = m_left;
m_rightMargin = Twip2Point (pageLayout->getRightMargin ());
m_topMargin = m_top;
m_bottomMargin = Twip2Point (pageLayout->getBottomMargin ());
kdDebug (30509) << "leftMargin: " << m_leftMargin << endl;
kdDebug (30509) << "rightMargin: " << m_rightMargin << endl;
kdDebug (30509) << "topMargin: " << m_topMargin << endl;
kdDebug (30509) << "bottomMargin: " << m_bottomMargin << endl;
// offset of header & footer
m_headerFromTop = Twip2Point (pageLayout->getHeaderFromTop ());
m_footerFromTop = Twip2Point (pageLayout->getFooterFromTop ());
kdDebug (30509) << "headerFromTop: " << m_headerFromTop
<< " footerFromTop: " << m_footerFromTop << endl;
m_startingPageNumber = pageLayout->getPageNumberStart ();
return true;
}
bool writeDocumentBeginForReal (void)
{
kdDebug (30509) << "writeDocumentBeginForReal()" << endl;
// adjust margins/PAPERBORDERS to ensure that the header & footer are
// within them
// TODO: stop header & footer from changing body's location
// TODO: recompute offset of margins after recomputing margins
if (m_hasHeader)
if (m_headerFromTop < m_topMargin)
m_topMargin = m_headerFromTop;
if (m_hasFooter)
if (m_pageHeight - m_footerFromTop < m_bottomMargin)
m_bottomMargin = m_pageHeight - m_footerFromTop;
kdDebug (30509) << "adjusted::: leftMargin: " << m_leftMargin
<< " rightMargin: " << m_rightMargin
<< " topMargin: " << m_topMargin
<< " bottomMargin: " << m_bottomMargin
<< endl;
// start document
// TODO: error checking
writeTextInternal ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writeTextInternal ("<!DOCTYPE DOC PUBLIC \"-//KDE//DTD kword 1.3//EN\" "
"\"http://www.koffice.org/DTD/kword-1.3.dtd\">");
writeTextInternal ("<DOC xmlns=\"http://www.koffice.org/DTD/kword\" "
"mime=\"application/x-kword\" "
"syntaxVersion=\"3\" editor=\"KWord\">");
writeTextInternal ("<PAPER format=\"1\" "
"width=\"%i\" height=\"%i\" "
"orientation=\"0\" columns=\"1\" "
"hType=\"%i\" fType=\"%i\">",
m_pageWidth, m_pageHeight,
m_isHeaderOnFirstPage ? 0 : 2,
m_isFooterOnFirstPage ? 0 : 2);
writeTextInternal ("<PAPERBORDERS left=\"%i\" right=\"%i\" "
"top=\"%i\" bottom=\"%i\"/>",
m_leftMargin, m_rightMargin,
m_topMargin, m_bottomMargin);
writeTextInternal ("</PAPER>");
writeTextInternal ("<ATTRIBUTES processing=\"0\" "
"tabStopValue=\"%lf\" "
"hasHeader=\"%i\" hasFooter=\"%i\"/>",
Inch2Point (0.5),
m_hasHeader ? 1 : 0, m_hasFooter ? 1 : 0);
// handle page numbering not starting from 1
if (m_startingPageNumber != 1)
writeTextInternal ("<VARIABLESETTINGS startingPageNumber=\"%i\"/>",
m_startingPageNumber);
writeTextInternal ("<FRAMESETS>");
return true;
}
bool writeDocumentEnd (const MSWrite::Word, const MSWrite::PageLayout *)
{
kdDebug (30509) << "writeDocumentEnd()" << endl;
// write framesets for the objects
writeTextInternal (m_objectFrameset);
writeTextInternal ("</FRAMESETS>");
writeTextInternal ("<STYLES>");
writeTextInternal ("<STYLE>");
writeTextInternal ("<NAME value=\"Standard\"/>");
writeTextInternal ("<FLOW align=\"left\"/>");
writeTextInternal ("<INDENTS first=\"0\" left=\"0\" right=\"0\"/>");
writeTextInternal ("<OFFSETS before=\"0\" after=\"0\"/>");
writeTextInternal ("<FORMAT id=\"1\">");
writeTextInternal ("<COLOR blue=\"0\" red=\"0\" green=\"0\"/>");
writeTextInternal ("<FONT name=\"helvetica\"/>");
writeTextInternal ("<SIZE value=\"12\"/>");
writeTextInternal ("<WEIGHT value=\"50\"/>");
writeTextInternal ("<ITALIC value=\"0\"/>");
writeTextInternal ("<UNDERLINE value=\"0\"/>");
writeTextInternal ("<STRIKEOUT value=\"0\"/>");
writeTextInternal ("<VERTALIGN value=\"0\"/>");
writeTextInternal ("</FORMAT>");
writeTextInternal ("<FOLLOWING name=\"Standard\"/>");
writeTextInternal ("</STYLE>");
writeTextInternal ("</STYLES>");
// write out image keys
writeTextInternal ("<PICTURES>");
writeTextInternal (m_pictures);
writeTextInternal ("</PICTURES>");
// end document
writeTextInternal ("</DOC>");
// close maindoc.xml
m_outfile->close ();
m_outfile = (KoStoreDevice *) NULL;
//
// output object data
/*if (m_objectUpto != getNumObjects ())
warning ("m_objectUpto (%i) != getNumObjects() (%i) -- this is probably because OLE is unimplemented\n",
m_objectUpto, getNumObjects ());*/
MSWrite::List <WRIObject>::Iterator it;
MSWrite::List <WRIObject>::Iterator end(m_objectList.end ());
for (it = m_objectList.begin (); it != end; ++it)
{
kdDebug (30509) << "outputting object \'" << (*it).m_nameInStore
<< "\' (length: " << (*it).m_dataLength << ")"
<< endl;
if (!(*it).m_data)
ErrorAndQuit (MSWrite::Error::InternalError, "image data not initialised\n");
// open file for object in store
m_outfile = m_chain->storageFile ((*it).m_nameInStore, KoStore::Write);
if (!m_outfile)
ErrorAndQuit (MSWrite::Error::FileError, "could not open image in store\n");
if (m_outfile->writeBlock ((const char *) (*it).m_data, (*it).m_dataLength)
!= (TQ_LONG) (*it).m_dataLength)
ErrorAndQuit (MSWrite::Error::FileError, "could not write image to store\n");
// close object in store
m_outfile->close ();
m_outfile = NULL;
}
return true;
}
bool writeFooterBegin (void)
{
kdDebug (30509) << "writeFooterBegin()" << endl;
inWhat = Footer;
m_hasFooter = true;
// footers must go after body in KWord
delayOutput (true);
// footer frameset will be written in writeParaInfoBegin()
return true;
}
bool writeFooterEnd (void)
{
kdDebug (30509) << "writeFooterEnd()" << endl;
inWhat = Nothing;
if (!m_writeFooterFirstTime)
writeTextInternal ("</FRAMESET>");
delayOutput (false);
return true;
}
bool writeHeaderBegin (void)
{
kdDebug (30509) << "writeHeaderBegin()" << endl;
inWhat = Header;
m_hasHeader = true;
// headers must go after body in KWord
delayOutput (true);
// header frameset will be written in writeParaInfoBegin()
return true;
}
bool writeHeaderEnd (void)
{
kdDebug (30509) << "writeHeaderEnd()" << endl;
inWhat = Nothing;
if (!m_writeHeaderFirstTime)
writeTextInternal ("</FRAMESET>");
delayOutput (false);
return true;
}
bool writeBodyBegin (void)
{
kdDebug (30509) << "writeBodyBegin()" << endl;
inWhat = Body;
// writeFooterBegin() and writeHeaderBegin() have been called by now
// so we have enough information to actually write about them
writeDocumentBeginForReal ();
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"0\" name=\"Text Frameset 1\" visible=\"1\">");
// TODO: runaround?
writeTextInternal ("<FRAME runaround=\"1\" autoCreateNewFrame=\"1\" newFrameBehavior=\"0\" copy=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_top, m_bottom, m_left, m_right);
return true;
}
bool writeBodyEnd (void)
{
kdDebug (30509) << "writeBodyEnd()" << endl;
inWhat = Nothing;
// <PAGEBREAKING hardFrameBreakAfter=\"true\"/>" may have been in the last paragraph
// and for "hardFrameBreakAfter" to do its work, we need one more final paragraph!
if (m_needAnotherParagraph)
{
kdDebug (30509) << "needAnotherParagraph in bodyEndWrite()" << endl;
writeTextInternal ("<PARAGRAPH><TEXT></TEXT><LAYOUT></LAYOUT></PARAGRAPH>");
m_needAnotherParagraph = false;
}
writeTextInternal ("</FRAMESET>");
// since "Text Frameset 1" has ended, we can output the header & footer, now
delayOutputFlush ();
return true;
}
bool writeParaInfoBegin (const MSWrite::FormatParaProperty *paraProperty,
const MSWrite::OLE *ole,
const MSWrite::Image *image)
{
//kdDebug (30509) << "writeParaInfoBegin()" << endl;
// reset charInfo counters
m_charInfoCountStart = 0;
m_charInfoCountLen = 0;
if (inWhat == Header)
{
m_isHeaderOnFirstPage = paraProperty->getIsOnFirstPage ();
if (m_writeHeaderFirstTime)
{
// dummy header frames
//
// except, if the header is NOT on the first page, then make an empty "First Page Header"
// by setting "visible=1"
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"1\" name=\"First Page Header\" visible=\"%i\">",
m_isHeaderOnFirstPage ? 1 : 0);
writeTextInternal ("<FRAME runaround=\"1\" copy=\"0\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_headerFromTop, m_headerFromTop, m_left, m_right);
writeTextInternal ("</FRAMESET>");
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"2\" name=\"Even Pages Header\" visible=\"0\">");
writeTextInternal ("<FRAME runaround=\"1\" copy=\"0\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_headerFromTop, m_headerFromTop, m_left, m_right);
writeTextInternal ("</FRAMESET>");
// real header frame
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"3\" name=\"Odd Pages Header\" visible=\"1\">");
writeTextInternal ("<FRAME runaround=\"1\" copy=\"1\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_headerFromTop, m_headerFromTop, m_left, m_right);
m_writeHeaderFirstTime = false;
}
}
else if (inWhat == Footer)
{
m_isFooterOnFirstPage = paraProperty->getIsOnFirstPage ();
if (m_writeFooterFirstTime)
{
// dummy footer frames
//
// except, if the footer is NOT on the first page, then make an empty "First Page Footer"
// by setting "visible=1"
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"4\" name=\"First Page Footer\" visible=\"%i\">",
m_isFooterOnFirstPage ? 1 : 0);
writeTextInternal ("<FRAME runaround=\"1\" copy=\"0\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_footerFromTop, m_footerFromTop, m_left, m_right);
writeTextInternal ("</FRAMESET>");
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"5\" name=\"Even Pages Footer\" visible=\"0\">");
writeTextInternal ("<FRAME runaround=\"1\" copy=\"0\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_footerFromTop, m_footerFromTop, m_left, m_right);
writeTextInternal ("</FRAMESET>");
// real footer frame
writeTextInternal ("<FRAMESET frameType=\"1\" frameInfo=\"6\" name=\"Odd Pages Footer\" visible=\"1\">");
writeTextInternal ("<FRAME runaround=\"1\" copy=\"1\" newFrameBehavior=\"2\" autoCreateNewFrame=\"0\""
" top=\"%i\" bottom=\"%i\" left=\"%i\" right=\"%i\"/>",
m_footerFromTop, m_footerFromTop, m_left, m_right);
m_writeFooterFirstTime = false;
}
}
if (!writeTextInternal ("<PARAGRAPH><TEXT>")) return false;
if (image)
{
kdDebug (30509) << "Paragraph is an image!" << endl;
TQString imageName;
TQString fileInStore;
// give picture a name
//
imageName = "Picture ";
imageName += TQString::number (m_numPictures + 1); // image numbers start at 1...
// give picture a filename
//
fileInStore = "pictures/picture" + TQString::number (m_numPictures + 1);
kdDebug (30509) << "\tGetting type..." << endl;
// append extension
if (image->getIsBMP ())
fileInStore += ".bmp";
else if (image->getIsWMF () )
fileInStore += ".wmf";
else
ErrorAndQuit (MSWrite::Error::InternalError, "unsupported picture type\n");
// indicate anchored image in formatting
//
kdDebug (30509) << "\tIndicating anchored image in formatting" << endl;
if (!writeTextInternal ("#")) return false;
m_formatOutput += "<FORMAT id=\"6\" pos=\"0\" len=\"1\">";
m_formatOutput += "<ANCHOR type=\"frameset\" instance=\"";
m_formatOutput += imageName;
m_formatOutput += "\"/>";
m_formatOutput += "</FORMAT>";
// write framesets (truly written in documentEndWrite())
//
kdDebug (30509) << "\tWriting framesets!" << endl;
m_objectFrameset += "<FRAMESET frameType=\"2\" frameInfo=\"0\" name=\"";
m_objectFrameset += imageName;
m_objectFrameset += "\" visible=\"1\">";
m_objectFrameset += "<FRAME runaround=\"1\" copy=\"0\" newFrameBehavior=\"1\"";
const double imageLeft = double (m_left) + Twip2Point (double (image->getIndent ()));
m_objectFrameset += " left=\"";
m_objectFrameset += TQString::number (imageLeft);
m_objectFrameset += "\"";
const double imageWidth = Twip2Point (double (image->getDisplayedWidth ()));
m_objectFrameset += " right=\"";
m_objectFrameset += TQString::number (imageLeft + imageWidth - 1);
m_objectFrameset += "\"";
m_objectFrameset += " top=\"";
m_objectFrameset += TQString::number (m_top);
m_objectFrameset += "\"";
const double imageHeight = Twip2Point (double (image->getDisplayedHeight ()));
m_objectFrameset += " bottom=\"";
m_objectFrameset += TQString::number (double (m_top) + imageHeight - 1);
m_objectFrameset += "\"/>";
m_objectFrameset += "<PICTURE keepAspectRatio=\"false\">";
m_objectFrameset += "<KEY msec=\"0\" hour=\"0\" second=\"0\" minute=\"0\" day=\"1\" month=\"1\" year=\"1970\"";
m_objectFrameset += " filename=\"";
m_objectFrameset += fileInStore;
m_objectFrameset += "\"/>";
m_objectFrameset += "</PICTURE>";
m_objectFrameset += "</FRAMESET>";
#ifdef DEBUG_XML_OUTPUT
m_objectFrameset += "\n";
#endif
m_pictures += "<KEY msec=\"0\" hour=\"0\" second=\"0\" minute=\"0\" day=\"1\" month=\"1\" year=\"1970\"";
m_pictures += " name=\"";
m_pictures += fileInStore;
m_pictures += "\"";
m_pictures += " filename=\"";
m_pictures += fileInStore;
m_pictures += "\"/>";
m_numPictures++;
// store object properties
//
kdDebug (30509) << "\tStoring object" << endl;
if (!m_objectList.addToBack ())
ErrorAndQuit (MSWrite::Error::OutOfMemory, "could not allocate memory for object\n");
WRIObject &obj = *m_objectList.begin (false);
obj.m_nameInStore = fileInStore;
obj.m_dataLength = image->getExternalImageSize ();
obj.m_data = new MSWrite::Byte [obj.m_dataLength];
if (!obj.m_data)
ErrorAndQuit (MSWrite::Error::OutOfMemory, "could not allocate memory for object data\n");
// if anchored images could be positioned properly, this wouldn't be needed
m_objectHorizOffset = double (Twip2Point (image->getIndent ()));
m_paraIsImage = true;
}
else
{
if (ole)
{
if (!writeTextInternal ("[OLE unsupported]")) return false;
}
m_paraIsImage = false;
}
return true;
}
bool writeParaInfoEnd (const MSWrite::FormatParaProperty *paraProperty,
const MSWrite::OLE * /*ole*/,
const MSWrite::Image *image)
{
//kdDebug (30509) << "writeParaInfoEnd()" << endl;
if (image)
{
WRIObject &obj = *m_objectList.begin (false);
// consistency check: wrote exactly the right amount of data?
if (obj.m_dataUpto != obj.m_dataLength)
kdWarning (30509) << "obj.dataUpto (" << obj.m_dataUpto
<< ") != obj.dataLength (" << obj.m_dataLength
<< ")" << endl;
}
TQString output;
output += "</TEXT>";
output += "<LAYOUT>";
output += "<NAME value=\"Standard\"/>";
int align = paraProperty->getAlignment ();
if (align != MSWrite::Alignment::Left)
{
output += "<FLOW align=\"";
switch (align)
{
/*case MSWrite::Alignment::Left:
output += "left";
break;*/
case MSWrite::Alignment::Centre:
output += "center";
break;
case MSWrite::Alignment::Right:
output += "right";
break;
case MSWrite::Alignment::Justify:
output += "justify";
break;
}
output += "\"/>";
}
double indentFirst = Twip2Point (double (paraProperty->getLeftIndentFirstLine ()));
double indentLeft = Twip2Point (double (paraProperty->getLeftIndent ()));
double indentRight = Twip2Point (double (paraProperty->getRightIndent ()));
#if 0
debug ("raw indent: first: %i left: %i right: %i\n",
indentFirst, indentLeft, indentRight);
#endif
if (m_paraIsImage /*paraProperty->isObject ()*/ && m_objectHorizOffset)
{
if (align == MSWrite::Align::Center)
{
// TODO: I don't know what m_objectHorizOffset is relative to!
kdDebug (30509) << "ignoring image offset with centred image" << endl;
m_objectHorizOffset = 0;
}
else
{
// MSWrite does not add the horizontal offset of the image from the left margin to the Left Indent
// -- instead, it selects the bigger one
// TODO: proper image positioning (see doc IMPERFECT)
if (m_simulateImageOffset && m_objectHorizOffset > indentLeft)
{
kdDebug (30509) << "image is further away from left margin by itself, rather than using indentLeft ("
<< m_objectHorizOffset << " > " << indentLeft << ")" << endl;
indentLeft = m_objectHorizOffset;
}
}
}
// hopefully these test operations will be cheaper than the XML ones :)
if (indentFirst || indentLeft || indentRight)
{
output += "<INDENTS";
if (indentFirst) output += " first=\"" + TQString::number (indentFirst) + "\"";
if (indentLeft) output += " left=\"" + TQString::number (indentLeft) + "\"";
if (indentRight) output += " right=\"" + TQString::number (indentRight) + "\"";
output += "/>";
}
MSWrite::Word lineSpacing = paraProperty->getLineSpacing ();
if (lineSpacing != MSWrite::LineSpacing::Single)
{
#if 1
output += "<LINESPACING type=\"atleast\" spacingvalue=\"" + TQString::number (Twip2Point (lineSpacing)) + "\"/>";
#else // old way
output += "<LINESPACING type=\"";
switch (lineSpacing)
{
//case MSWrite:;LineSpacing::Single:
// break;
case MSWrite::LineSpacing::OneAndAHalf:
output += "oneandhalf";
break;
case MSWrite::LineSpacing::Double:
output += "double";
break;
default:
kdWarning (30509) << "non-\"standard\" linespacing value: " << lineSpacing << endl;
output += "atleast\" ";
output += "spacingvalue=\"";
output += TQString::number (Twip2Point (lineSpacing));
break;
}
output += "\"/>";
#endif
}
// Do we want the linespacing to _look_ like it does in Write?
// (this adds extra space before each paragraph)
if (m_simulateLineSpacing)
{
// emulate Write's linespacing (aligned to bottom)
// by using varying amounts of space before the paragraph
// TODO: test if it works nicely enough (what if you have several different sized fonts in paragraph?)
if (lineSpacing != MSWrite::LineSpacing::Single) // if not normal linespacing...
{
output += "<OFFSETS before=\"";
int amount = 0;
switch (lineSpacing)
{
/*case MSWrite::LineSpacing::Single:
break;*/
case MSWrite::LineSpacing::OneAndAHalf:
amount = 7;
break;
case MSWrite::LineSpacing::Double:
amount = 14;
break;
default:
kdWarning (30509) << "unknown linespacing value: " << lineSpacing << endl;
break;
}
// subtract the amount of trailing linespace from last paragraph
amount -= m_lineSpacingFromAbove;
if (amount <= 0) amount = 0; // no emulation can be perfect...
output += TQString::number (amount);
output += "\" />";
}
// GUESS (TODO: fix) the amount of trailing linespace
switch (lineSpacing)
{
case MSWrite::LineSpacing::Single:
m_lineSpacingFromAbove = 0;
break;
case MSWrite::LineSpacing::OneAndAHalf:
m_lineSpacingFromAbove = 7;
break;
case MSWrite::LineSpacing::Double:
m_lineSpacingFromAbove = 14;
break;
default: // unknown
m_lineSpacingFromAbove = 0;
break;
}
} // if (m_simulateLineSpacing) {
if (m_pageBreak)
{
#if 0
debug ("\tpagebrk: output: offset: %i chars in paragraph: %i\n",
m_pageBreakOffset, m_charInfoCountStart + m_charInfoCountLen);
#endif
// page break before all the text
if (m_pageBreakOffset == 0 && m_charInfoCountStart + m_charInfoCountLen > 0)
{
output += "<PAGEBREAKING hardFrameBreak=\"true\"/>";
m_needAnotherParagraph = false; // this paragraph is on first page so we don't need another one
}
// we assume that the pageBreak was after all the text (TODO: don't assume this)
else
{
output += "<PAGEBREAKING hardFrameBreakAfter=\"true\"/>";
m_needAnotherParagraph = true; // need another paragraph for hardFrameBreakAfter to work
}
m_pageBreak = false; // reset flag
}
else
m_needAnotherParagraph = false;
// Tabulators
for (int i = 0; i < paraProperty->getNumTabulator (); i++)
{
const MSWrite::FormatParaPropertyTabulator *tab = paraProperty->getTabulator (i);
if (tab->getIsDummy ()) break;
output += "<TABULATOR";
if (tab->getIsDecimal ())
output += " type=\"3\" alignchar=\".\"";
else
output += " type=\"0\"";
output += " ptpos=\"" + TQString::number (Twip2Point (double (tab->getIndent ()))) + "\"/>";
//debug ("Tab: isNormal: %i ptPos: %i\n",
// paraProperty->tbd [i].isTabNormal (), paraProperty->tbd [i].getTabNumPoints ());
}
output += "</LAYOUT>";
#ifdef DEBUG_XML_OUTPUT
output += "\n";
#endif
output += "<FORMATS>";
#ifdef DEBUG_XML_OUTPUT
output += "\n";
#endif
// output all the charInfo for this paragraph
output += m_formatOutput; m_formatOutput = "";
output += "</FORMATS>";
#ifdef DEBUG_XML_OUTPUT
output += "\n";
#endif
output += "</PARAGRAPH>";
#ifdef DEBUG_XML_OUTPUT
output += "\n";
#endif
if (!writeTextInternal (output)) return false;
return true;
}
bool writeCharInfoBegin (const MSWrite::FormatCharProperty * /*charProperty*/)
{
//kdDebug (30509) << "writeCharInfoBegin()" << endl;
return true;
}
// outputs character formatting tags
bool writeCharInfoEnd (const MSWrite::FormatCharProperty *charProperty,
const bool = false)
{
//kdDebug (30509) << "writeCharInfoEnd()" << endl;
// output type of format information (page number or normal text)
m_formatOutput += "<FORMAT id=\"";
if (charProperty->getIsPageNumber ())
m_formatOutput += "4";
else
m_formatOutput += "1";
m_formatOutput += "\" ";
m_formatOutput += "pos=\""; m_formatOutput += TQString::number (m_charInfoCountStart); m_formatOutput += "\" ";
m_formatOutput += "len=\""; m_formatOutput += TQString::number (m_charInfoCountLen); m_formatOutput += "\">";
m_charInfoCountStart += m_charInfoCountLen;
m_charInfoCountLen = 0;
if (charProperty->getIsPageNumber ())
{
m_formatOutput += "<VARIABLE>";
m_formatOutput += "<TYPE key=\"NUMBER\" type=\"4\"/>";
m_formatOutput += "<PGNUM subtype=\"0\" value=\"1\"/>";
m_formatOutput += "</VARIABLE>";
}
m_formatOutput += "<FONT name=\"";
m_formatOutput += (const char *) (charProperty->getFont ()->getName ());
m_formatOutput += "\"/>";
m_formatOutput += "<SIZE value=\"";
m_formatOutput += TQString::number (charProperty->getFontSize ());
m_formatOutput += "\"/>";
if (charProperty->getIsBold ())
m_formatOutput += "<WEIGHT value=\"75\"/>";
//else
// m_formatOutput += "<WEIGHT value=\"50\" />";
if (charProperty->getIsItalic ())
m_formatOutput += "<ITALIC value=\"1\"/>";
// else
// m_formatOutput += "<ITALIC value=\"0\" />";
if (charProperty->getIsUnderlined ())
m_formatOutput += "<UNDERLINE value=\"1\"/>";
// else
// m_formatOutput += "<UNDERLINE value=\"0\" />";
/*if (charProperty->isNormalPosition ())
m_formatOutput += "<VERTALIGN value=\"0\" />";
else*/ if (charProperty->getIsSubscript ())
m_formatOutput += "<VERTALIGN value=\"1\"/>";
else if (charProperty->getIsSuperscript ())
m_formatOutput += "<VERTALIGN value=\"2\"/>";
/*else
error ("unknown valign\n");*/
m_formatOutput += "</FORMAT>";
return true;
}
bool writeBinary (const MSWrite::Byte *buffer, const MSWrite::DWord length)
{
kdDebug (30509) << "writeBinary()" << endl;
// must be OLE, TODO: implement OLE properly
if (!m_paraIsImage)
return true;
WRIObject &obj = *m_objectList.begin (false);
if (!obj.m_data)
ErrorAndQuit (MSWrite::Error::InternalError, "object data not initialised\n");
// consistency check: aren't going to write past end of array?
if (obj.m_dataUpto + length > obj.m_dataLength)
{
kdDebug (30509) << "object image overrun: "
<< obj.m_dataUpto << " + " << length
<< " > " << obj.m_dataLength << endl;
ErrorAndQuit (MSWrite::Error::InternalError, "object image overrun\n");
}
memcpy (obj.m_data + obj.m_dataUpto, buffer, length);
obj.m_dataUpto += length;
return true;
}
//
// text output functions
//
bool writeText (const MSWrite::Byte *string)
{
// from Win Character Set...
TQString strUnicode;
// there is a codec, therefore there is a decoder...
if (m_codec)
{
// output Unicode (UTF8)
strUnicode = m_decoder->toUnicode ((const char *) string, strlen ((const char *) string));
}
else
{
// output a plain string still in wrong Character Set
// (hopefully the user won't notice)
strUnicode = (const char *) string;
}
// update character information counter (after charset conversion)
m_charInfoCountLen += strUnicode.length ();
// make string XML-friendly (TODO: speed up)
strUnicode.replace ('&', "&amp;");
strUnicode.replace ('<', "&lt;");
strUnicode.replace ('>', "&gt;");
strUnicode.replace ('\"', "&quot;");
strUnicode.replace ('\'', "&apos;");
return writeTextInternal (strUnicode);
}
bool writeTextInternal (const MSWrite::Byte *str)
{
#if 0
return textWrite_lowLevel (TQString (str));
#else // while this is code duplication (of below func), this ensures that no
// characters are mysteriously converted (this makes writeOptionalHyphen () work)
if (m_delayOutput)
{
// header/footer must be written after main body
m_heldOutput += (const char *) str;
return true;
}
else
{
int strLength = strlen ((const char *) str);
if (m_outfile->writeBlock ((const char *) str, strLength) != strLength)
{
ErrorAndQuit (MSWrite::Error::FileError, "could not write to maindoc.xml\n");
}
else
return true;
}
#endif
}
bool writeTextInternal (const TQString &str)
{
if (m_delayOutput)
{
// header/footer must be written after main body
m_heldOutput += str;
return true;
}
else
{
TQCString strUtf8 = str.utf8 ();
int strLength = strUtf8.length ();
if (m_outfile->writeBlock (strUtf8, strLength) != strLength)
{
ErrorAndQuit (MSWrite::Error::FileError, "could not write to maindoc.xml (2)\n");
}
else
return true;
}
}
bool writeTextInternal (const int num)
{
return writeTextInternal (TQString::number (num));
}
bool writeTextInternal (const char *format, ...)
{
va_list list;
va_start (list, format);
bool ret;
// This function is mainly for outputting tags (where XML characters are
// already escaped and the text is in the right character set...ASCII
// = UTF-8 for alphanumeric chars I hope). So _don't_ pass user text
// to this function (that's what writeText() is for); otherwise you might
// exceed this 1024 limit.
char string [1024];
vsnprintf (string, sizeof (string) - 1, format, list);
string [sizeof (string) - 1] = '\0';
ret = writeTextInternal ((const MSWrite::Byte *) string);
va_end (list);
return ret;
}
// writePageNew() is called for the pageTable
// -- however, pageTable can be very inaccurate, so we ignore it
bool writePageNew (const int)
{
return true;
}
// handles explicit page breaks
bool writePageBreak (void)
{
// later used in paraEndWrite
m_pageBreak = true;
m_pageBreakOffset = m_charInfoCountStart + m_charInfoCountLen;
return true;
}
// handle "(page)" number
bool writePageNumber (void)
{
m_charInfoCountLen++; // not incremented by writeTextInternal()
return writeTextInternal ("#");
}
bool writeCarriageReturn (void)
{
return true; // ignore CR
}
// write newline unless end-of-paragraph
// (this is the support for paragraphs with multiple newlines)
bool writeNewLine (const bool endOfParagraph)
{
if (!endOfParagraph)
{
m_charInfoCountLen++; // not incremented by writeTextInternal()
return writeTextInternal ("\n");
}
else
return true;
}
// aka "soft hyphen"
bool writeOptionalHyphen (void)
{
m_charInfoCountLen++; // not incremented by writeTextInternal()
return writeTextInternal ("\xC2\xAD");
}
void setKOfficeLink (MSWriteImport *kofficeLink)
{
m_koLink = kofficeLink;
}
void sigProgress (const int value)
{
m_koLink->sigProgress (value);
}
};
//
// KoFilter
//
MSWriteImport::MSWriteImport (KoFilter *, const char *, const TQStringList &)
: m_device (NULL), m_parser (NULL), m_generator (NULL)
{
}
MSWriteImport::~MSWriteImport ()
{
delete m_generator;
delete m_parser;
delete m_device;
}
KoFilter::ConversionStatus MSWriteImport::convert (const TQCString &from, const TQCString &to)
{
kdDebug (30509) << "MSWriteImport $Date: 2006-02-12 19:28:12 +0100 (Sun, 12 Feb 2006) $ using LibMSWrite "
<< MSWrite::Version << endl;
if (to != "application/x-kword" || from != "application/x-mswrite")
{
kdError (30509) << "Internal error! Filter not implemented?" << endl;
return KoFilter::NotImplemented;
}
#if 0
//MSWriteImportDialog *dialog = new MSWriteImportDialog ();
MSWriteImportDialog dialog;
/*debug ("DIALOG check alloc\n");
if (!dialog)
{
error ("Could not allocate memory for dialog\n");
return KoFilter::StupidError;
}*/
debug ("DIALOG EXEC!!!\n");
if (!dialog.exec ())
{
error ("Dialog was aborted! Aborting filter!\n");
return KoFilter::UserCancelled;
}
debug ("DIALOG GET!!!\n");
// read settings from dialog
m_codec = dialog.getCodec ();
m_simulateLinespacing = dialog.getSimulateLinespacing ();
m_simulateImageOffset = dialog.getSimulateImageOffset ();
debug ("Import options: simulateLinespacing: %i\tsimulateImageOffset: %i\n",
m_simulateLinespacing, m_simulateImageOffset);
debug ("DIALOG DELETE\n");
//delete dialog;
#endif
// create the Device that will read from the .WRI file
m_device = new WRIDevice;
if (!m_device)
{
kdError (30509) << "Could not allocate memory for device" << endl;
return KoFilter::OutOfMemory;
}
// open the .WRI file
if (!m_device->openFile (TQFile::encodeName (m_chain->inputFile ())))
{
kdError (30509) << "Could not open \'" << m_chain->inputFile () << "\'" << endl;
return KoFilter::FileNotFound;
}
// create Parser that will interpret the .WRI file and call the Generator
m_parser = new MSWrite::InternalParser;
if (!m_parser)
{
kdError (30509) << "Could not allocate memory for parser" << endl;
return KoFilter::OutOfMemory;
}
// tell the Parser to use the Device to read from the .WRI file
m_parser->setDevice (m_device);
// create Generator that will produce the .KWD file
m_generator = new KWordGenerator;
if (!m_generator)
{
kdError (30509) << "Could not allocate memory for generator" << endl;
return KoFilter::OutOfMemory;
}
// give the Generator the Device for error-handling purposes
m_generator->setDevice (m_device);
// give the Generator the chain
m_generator->setFilterChain (m_chain);
// hand over sigProgess to give some feedback to the user
m_generator->setKOfficeLink (this);
// hook up Generator to Parser
m_parser->setGenerator (m_generator);
// filter!
if (!m_parser->parse ())
{
// try to return somewhat more meaningful errors than StupidError
// for the day that KOffice actually reports them to the user properly
int errorCode = m_device->bad ();
switch (errorCode)
{
case MSWrite::Error::Ok:
kdDebug (30509) << "Error::Ok but aborted???" << endl;
return KoFilter::InternalError;
case MSWrite::Error::Warn:
kdDebug (30509) << "Error::Warn" << endl;
return KoFilter::InternalError; // warnings should _never_ set m_error
case MSWrite::Error::InvalidFormat:
kdDebug (30509) << "Error::InvalidFormat" << endl;
return KoFilter::WrongFormat;
case MSWrite::Error::OutOfMemory:
kdDebug (30509) << "Error::OutOfMemory" << endl;
return KoFilter::OutOfMemory;
case MSWrite::Error::InternalError:
kdDebug (30509) << "Error::InternalError" << endl;
return KoFilter::InternalError;
case MSWrite::Error::Unsupported:
kdDebug (30509) << "Error::Unsupported" << endl;
return KoFilter::InternalError;
case MSWrite::Error::FileError:
kdDebug (30509) << "Error::FileError" << endl;
return KoFilter::StupidError; // got a better return value?
}
kdWarning (30509) << "Unknown error: " << errorCode << endl;
return KoFilter::StupidError;
}
return KoFilter::OK;
}
#include <mswriteimport.moc>