/* This file is part of the KOffice project Copyright (C) 2002 Werner Trobin Copyright (C) 2002 David Faure This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "document.h" #include "conversion.h" #include "texthandler.h" #include "graphicshandler.h" #include "versionmagic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Document::Document( const std::string& fileName, TQDomDocument& mainDocument, TQDomDocument& documentInfo, TQDomElement& framesetsElement, KoFilterChain* chain ) : m_mainDocument( mainDocument ), m_documentInfo ( documentInfo ), m_framesetsElement( framesetsElement ), m_replacementHandler( new KWordReplacementHandler ), m_tableHandler( new KWordTableHandler ), m_pictureHandler( new KWordPictureHandler( this ) ), m_textHandler( 0 ), m_chain( chain ), m_parser( wvWare::ParserFactory::createParser( fileName ) ), m_headerFooters( 0 ), m_bodyFound( false ), m_footNoteNumber( 0 ), m_endNoteNumber( 0 ) { if ( m_parser ) // 0 in case of major error (e.g. unsupported format) { m_textHandler = new KWordTextHandler( m_parser ); connect( m_textHandler, TQT_SIGNAL( subDocFound( const wvWare::FunctorBase*, int ) ), this, TQT_SLOT( slotSubDocFound( const wvWare::FunctorBase*, int ) ) ); connect( m_textHandler, TQT_SIGNAL( tableFound( const KWord::Table& ) ), this, TQT_SLOT( slotTableFound( const KWord::Table& ) ) ); connect( m_textHandler, TQT_SIGNAL( pictureFound( const TQString&, const TQString&, const wvWare::FunctorBase* ) ), this, TQT_SLOT( slotPictureFound( const TQString&, const TQString&, const wvWare::FunctorBase* ) ) ); m_parser->setSubDocumentHandler( this ); m_parser->setTextHandler( m_textHandler ); m_parser->setTableHandler( m_tableHandler ); #ifdef IMAGE_IMPORT m_parser->setPictureHandler( m_pictureHandler ); #endif m_parser->setInlineReplacementHandler( m_replacementHandler ); processStyles(); processAssociatedStrings(); connect( m_tableHandler, TQT_SIGNAL( sigTableCellStart( int, int, int, int, const KoRect&, const TQString&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::SHD& ) ), this, TQT_SLOT( slotTableCellStart( int, int, int, int, const KoRect&, const TQString&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::BRC&, const wvWare::Word97::SHD& ) ) ); connect( m_tableHandler, TQT_SIGNAL( sigTableCellEnd() ), this, TQT_SLOT( slotTableCellEnd() ) ); } } Document::~Document() { delete m_textHandler; delete m_pictureHandler; delete m_tableHandler; delete m_replacementHandler; } void Document::finishDocument() { const wvWare::Word97::DOP& dop = m_parser->dop(); TQDomElement elementDoc = m_mainDocument.documentElement(); TQDomElement element; element = m_mainDocument.createElement("ATTRIBUTES"); element.setAttribute("processing",0); // WP char allHeaders = ( wvWare::HeaderData::HeaderEven | wvWare::HeaderData::HeaderOdd | wvWare::HeaderData::HeaderFirst ); element.setAttribute("hasHeader", m_headerFooters & allHeaders ? 1 : 0 ); char allFooters = ( wvWare::HeaderData::FooterEven | wvWare::HeaderData::FooterOdd | wvWare::HeaderData::FooterFirst ); element.setAttribute("hasFooter", m_headerFooters & allFooters ? 1 : 0 ); //element.setAttribute("unit","mm"); // How to figure out the unit to use? element.setAttribute("tabStopValue", (double)dop.dxaTab / 20.0 ); elementDoc.appendChild(element); element = m_mainDocument.createElement("FOOTNOTESETTING"); elementDoc.appendChild(element); element.setAttribute( "start", dop.nFtn ); // initial footnote number for document. Starts at 1. element.setAttribute( "type", Conversion::numberFormatCode( dop.nfcFtnRef2 ) ); element = m_mainDocument.createElement("ENDNOTESETTING"); elementDoc.appendChild(element); element.setAttribute( "start", dop.nEdn ); // initial endnote number for document. Starts at 1. element.setAttribute( "type", Conversion::numberFormatCode( dop.nfcEdnRef2 ) ); // Done at the end: write the type of headers/footers, // depending on which kind of headers and footers we received. TQDomElement paperElement = elementDoc.namedItem("PAPER").toElement(); Q_ASSERT ( !paperElement.isNull() ); // slotFirstSectionFound should have been called! if ( !paperElement.isNull() ) { kdDebug(30513) << k_funcinfo << "m_headerFooters=" << m_headerFooters << endl; paperElement.setAttribute("hType", Conversion::headerMaskToHType( m_headerFooters ) ); paperElement.setAttribute("fType", Conversion::headerMaskToFType( m_headerFooters ) ); } // Write out tag TQDomElement picturesElem = m_mainDocument.createElement("PICTURES"); elementDoc.appendChild( picturesElem ); for( TQStringList::Iterator it = m_pictureList.begin(); it != m_pictureList.end(); ++it ) { TQDomElement keyElem = m_mainDocument.createElement("KEY"); picturesElem.appendChild( keyElem ); keyElem.setAttribute( "filename", *it ); keyElem.setAttribute( "name", *it ); } } void Document::processAssociatedStrings() { wvWare::AssociatedStrings strings( m_parser->associatedStrings() ); TQDomElement infodoc = m_documentInfo.createElement( "document-info" ); TQDomElement author = m_documentInfo.createElement( "author" ); TQDomElement fullname = m_documentInfo.createElement( "full-name" ); TQDomElement title = m_documentInfo.createElement( "title" ); TQDomElement about = m_documentInfo.createElement( "about" ); m_documentInfo.appendChild(infodoc); if ( !strings.author().isNull()) { fullname.appendChild( m_documentInfo.createTextNode( Conversion::string ( strings.author() ).string())); author.appendChild(fullname); infodoc.appendChild(author); } if ( !strings.title().isNull()) { title.appendChild( m_documentInfo.createTextNode( Conversion::string ( strings.title() ).string())); about.appendChild(title); infodoc.appendChild(about); } } void Document::processStyles() { TQDomElement stylesElem = m_mainDocument.createElement( "STYLES" ); m_mainDocument.documentElement().appendChild( stylesElem ); m_textHandler->setFrameSetElement( stylesElem ); /// ### naming! const wvWare::StyleSheet& styles = m_parser->styleSheet(); unsigned int count = styles.size(); //kdDebug(30513) << k_funcinfo << "styles count=" << count << endl; for ( unsigned int i = 0; i < count ; ++i ) { const wvWare::Style* style = styles.styleByIndex( i ); Q_ASSERT( style ); //kdDebug(30513) << k_funcinfo << "style " << i << " " << style << endl; if ( style && style->type() == wvWare::Style::sgcPara ) { TQDomElement styleElem = m_mainDocument.createElement("STYLE"); stylesElem.appendChild( styleElem ); TQConstString name = Conversion::string( style->name() ); TQDomElement element = m_mainDocument.createElement("NAME"); element.setAttribute( "value", name.string() ); styleElem.appendChild( element ); kdDebug(30513) << k_funcinfo << "Style " << i << ": " << name.string() << endl; const wvWare::Style* followingStyle = styles.styleByID( style->followingStyle() ); if ( followingStyle && followingStyle != style ) { TQConstString followingName = Conversion::string( followingStyle->name() ); element = m_mainDocument.createElement("FOLLOWING"); element.setAttribute( "name", followingName.string() ); styleElem.appendChild( element ); } m_textHandler->paragLayoutBegin(); // new style, reset some vars // It's important to do that one first, for m_shadowTextFound m_textHandler->writeFormat( styleElem, &style->chp(), 0L /*all of it, no ref chp*/, 0, 0, 1, 0L ); m_textHandler->writeLayout( styleElem, style->paragraphProperties(), style ); } // KWord doesn't support character styles yet } } bool Document::parse() { if ( m_parser ) return m_parser->parse(); return false; } void Document::bodyStart() { kdDebug(30513) << k_funcinfo << endl; TQDomElement mainFramesetElement = m_mainDocument.createElement("FRAMESET"); mainFramesetElement.setAttribute("frameType",1); mainFramesetElement.setAttribute("frameInfo",0); // TODO: "name" attribute (needs I18N) m_framesetsElement.appendChild(mainFramesetElement); // Those values are unused. The paper margins make recalcFrames() resize this frame. createInitialFrame( mainFramesetElement, 29, 798, 42, 566, false, Reconnect ); m_textHandler->setFrameSetElement( mainFramesetElement ); connect( m_textHandler, TQT_SIGNAL( firstSectionFound( wvWare::SharedPtr ) ), this, TQT_SLOT( slotFirstSectionFound( wvWare::SharedPtr ) ) ); m_bodyFound = true; } void Document::bodyEnd() { kdDebug(30513) << k_funcinfo << endl; disconnect( m_textHandler, TQT_SIGNAL( firstSectionFound( wvWare::SharedPtr ) ), this, TQT_SLOT( slotFirstSectionFound( wvWare::SharedPtr ) ) ); } void Document::slotFirstSectionFound( wvWare::SharedPtr sep ) { kdDebug(30513) << k_funcinfo << endl; TQDomElement elementDoc = m_mainDocument.documentElement(); TQDomElement elementPaper = m_mainDocument.createElement("PAPER"); bool landscape = (sep->dmOrientPage == 2); double width = (double)sep->xaPage / 20.0; double height = (double)sep->yaPage / 20.0; elementPaper.setAttribute("width", width); elementPaper.setAttribute("height", height); // guessFormat takes millimeters width = POINT_TO_MM( width ); height = POINT_TO_MM( height ); KoFormat paperFormat = KoPageFormat::guessFormat( landscape ? height : width, landscape ? width : height ); elementPaper.setAttribute("format",paperFormat); elementPaper.setAttribute("orientation", landscape ? PG_LANDSCAPE : PG_PORTRAIT ); elementPaper.setAttribute("columns", sep->ccolM1 + 1 ); elementPaper.setAttribute("columnspacing", (double)sep->dxaColumns / 20.0); elementPaper.setAttribute("spHeadBody", (double)sep->dyaHdrTop / 20.0); elementPaper.setAttribute("spFootBody", (double)sep->dyaHdrBottom / 20.0); // elementPaper.setAttribute("zoom",100); // not a doc property in kword elementDoc.appendChild(elementPaper); TQDomElement element = m_mainDocument.createElement("PAPERBORDERS"); element.setAttribute("left", (double)sep->dxaLeft / 20.0); element.setAttribute("top",(double)sep->dyaTop / 20.0); element.setAttribute("right", (double)sep->dxaRight / 20.0); element.setAttribute("bottom", (double)sep->dyaBottom / 20.0); elementPaper.appendChild(element); // TODO apply brcTop/brcLeft etc. to the main FRAME // TODO use sep->fEndNote to set the 'use endnotes or footnotes' flag } void Document::headerStart( wvWare::HeaderData::Type type ) { kdDebug(30513) << "startHeader type=" << type << " (" << Conversion::headerTypeToFramesetName( type ) << ")" << endl; // Werner says the headers are always emitted in the order of the Type enum. TQDomElement framesetElement = m_mainDocument.createElement("FRAMESET"); framesetElement.setAttribute( "frameType", 1 ); framesetElement.setAttribute( "frameInfo", Conversion::headerTypeToFrameInfo( type ) ); framesetElement.setAttribute( "name", Conversion::headerTypeToFramesetName( type ) ); m_framesetsElement.appendChild(framesetElement); bool isHeader = Conversion::isHeader( type ); createInitialFrame( framesetElement, 29, 798, isHeader?0:567, isHeader?41:567+41, true, Copy ); m_textHandler->setFrameSetElement( framesetElement ); m_headerFooters |= type; /*if ( Conversion::isHeader( type ) ) m_hasHeader = true; else m_hasFooter = true;*/ } void Document::headerEnd() { m_textHandler->setFrameSetElement( TQDomElement() ); } void Document::footnoteStart() { // Grab data that was stored with the functor, that triggered this parsing SubDocument subdoc( m_subdocQueue.front() ); int type = subdoc.data; // Create footnote/endnote frameset TQDomElement framesetElement = m_mainDocument.createElement("FRAMESET"); framesetElement.setAttribute( "frameType", 1 /* text */ ); framesetElement.setAttribute( "frameInfo", 7 /* footnote/endnote */ ); if ( type == wvWare::FootnoteData::Endnote ) // Keep name in sync with KWordTextHandler::footnoteFound framesetElement.setAttribute("name", i18n("Endnote %1").arg( ++m_endNoteNumber ) ); else // Keep name in sync with KWordTextHandler::footnoteFound framesetElement.setAttribute("name", i18n("Footnote %1").arg( ++m_footNoteNumber ) ); m_framesetsElement.appendChild(framesetElement); createInitialFrame( framesetElement, 29, 798, 567, 567+41, true, NoFollowup ); m_textHandler->setFrameSetElement( framesetElement ); } void Document::footnoteEnd() { kdDebug(30513) << k_funcinfo << endl; m_textHandler->setFrameSetElement( TQDomElement() ); } void Document::slotTableCellStart( int row, int column, int rowSpan, int columnSpan, const KoRect& cellRect, const TQString& tableName, const wvWare::Word97::BRC& brcTop, const wvWare::Word97::BRC& brcBottom, const wvWare::Word97::BRC& brcLeft, const wvWare::Word97::BRC& brcRight, const wvWare::Word97::SHD& shd ) { // Create footnote/endnote frameset TQDomElement framesetElement = m_mainDocument.createElement("FRAMESET"); framesetElement.setAttribute( "frameType", 1 /* text */ ); framesetElement.setAttribute( "frameInfo", 0 /* normal text */ ); framesetElement.setAttribute( "grpMgr", tableName ); TQString name = i18n("Table_Name Cell row,column", "%1 Cell %2,%3").arg(tableName).arg(row).arg(column); framesetElement.setAttribute( "name", name ); framesetElement.setAttribute( "row", row ); framesetElement.setAttribute( "col", column ); framesetElement.setAttribute( "rows", rowSpan ); framesetElement.setAttribute( "cols", columnSpan ); m_framesetsElement.appendChild(framesetElement); TQDomElement frameElem = createInitialFrame( framesetElement, cellRect.left(), cellRect.right(), cellRect.top(), cellRect.bottom(), true, NoFollowup ); generateFrameBorder( frameElem, brcTop, brcBottom, brcLeft, brcRight, shd ); m_textHandler->setFrameSetElement( framesetElement ); } void Document::slotTableCellEnd() { m_textHandler->setFrameSetElement( TQDomElement() ); } TQDomElement Document::createInitialFrame( TQDomElement& parentFramesetElem, double left, double right, double top, double bottom, bool autoExtend, NewFrameBehavior nfb ) { TQDomElement frameElementOut = parentFramesetElem.ownerDocument().createElement("FRAME"); frameElementOut.setAttribute( "left", left ); frameElementOut.setAttribute( "right", right ); frameElementOut.setAttribute( "top", top ); frameElementOut.setAttribute( "bottom", bottom ); frameElementOut.setAttribute( "runaround", 1 ); // AutoExtendFrame for header/footer/footnote/endnote, AutoCreateNewFrame for body text frameElementOut.setAttribute( "autoCreateNewFrame", autoExtend ? 0 : 1 ); frameElementOut.setAttribute( "newFrameBehavior", nfb ); parentFramesetElem.appendChild( frameElementOut ); return frameElementOut; } void Document::generateFrameBorder( TQDomElement& frameElementOut, const wvWare::Word97::BRC& brcTop, const wvWare::Word97::BRC& brcBottom, const wvWare::Word97::BRC& brcLeft, const wvWare::Word97::BRC& brcRight, const wvWare::Word97::SHD& shd ) { // Frame borders if ( brcTop.cv != 255 && brcTop.dptLineWidth != 255 ) // see tablehandler.cpp Conversion::setBorderAttributes( frameElementOut, brcTop, "t" ); if ( brcBottom.cv != 255 && brcBottom.dptLineWidth != 255 ) // see tablehandler.cpp Conversion::setBorderAttributes( frameElementOut, brcBottom, "b" ); if ( brcLeft.cv != 255 && brcLeft.dptLineWidth != 255 ) // could still be 255, for first column Conversion::setBorderAttributes( frameElementOut, brcLeft, "l" ); if ( brcRight.cv != 255 && brcRight.dptLineWidth != 255 ) // could still be 255, for last column Conversion::setBorderAttributes( frameElementOut, brcRight, "r" ); // Frame background brush (color and fill style) if ( shd.cvFore != 0 || shd.cvBack != 0 ) { // If ipat = 0 (solid fill), icoBack is the background color. // But otherwise, icoFore is the one we need to set as bkColor // (and icoBack is usually white; it's the other colour of the pattern, // something that we can't set in TQt apparently). int bkColor = shd.ipat ? shd.cvFore : shd.cvBack; kdDebug(30513) << "generateFrameBorder: " << " icoFore=" << shd.cvFore << " icoBack=" << shd.cvBack << " ipat=" << shd.ipat << " -> bkColor=" << bkColor << endl; // Reverse-engineer MSWord's own hackery: it models various gray levels // using dithering. But this looks crappy with TQt. So we go back to a TQColor. bool grayHack = ( shd.ipat && shd.cvFore == 1 && shd.cvBack == 8 ); if ( grayHack ) { bool ok; int grayLevel = Conversion::ditheringToGray( shd.ipat, &ok ); if ( ok ) { TQColor color( 0, 0, grayLevel, TQColor::Hsv ); TQString prefix = "bk"; frameElementOut.setAttribute( "bkRed", color.red() ); frameElementOut.setAttribute( "bkBlue", color.blue() ); frameElementOut.setAttribute( "bkGreen", color.green() ); } else grayHack = false; } if ( !grayHack ) { Conversion::setColorAttributes( frameElementOut, bkColor, "bk", true ); // Fill style int brushStyle = Conversion::fillPatternStyle( shd.ipat ); frameElementOut.setAttribute( "bkStyle", brushStyle ); } } } void Document::slotSubDocFound( const wvWare::FunctorBase* functor, int data ) { SubDocument subdoc( functor, data, TQString(), TQString() ); m_subdocQueue.push( subdoc ); } void Document::slotTableFound( const KWord::Table& table ) { m_tableQueue.push( table ); } void Document::slotPictureFound( const TQString& frameName, const TQString& pictureName, const wvWare::FunctorBase* pictureFunctor ) { SubDocument subdoc( pictureFunctor, 0, frameName, pictureName ); m_subdocQueue.push( subdoc ); } void Document::processSubDocQueue() { // Table cells can contain footnotes, and footnotes can contain tables [without footnotes though] // This is why we need to repeat until there's nothing more do to (#79024) while ( !m_subdocQueue.empty() || !m_tableQueue.empty() ) { while ( !m_subdocQueue.empty() ) { SubDocument subdoc( m_subdocQueue.front() ); Q_ASSERT( subdoc.functorPtr ); (*subdoc.functorPtr)(); // call it delete subdoc.functorPtr; // delete it m_subdocQueue.pop(); } while ( !m_tableQueue.empty() ) { KWord::Table& table = m_tableQueue.front(); m_tableHandler->tableStart( &table ); TQValueList &rows = table.rows; for( TQValueList::Iterator it = rows.begin(); it != rows.end(); ++it ) { KWord::TableRowFunctorPtr f = (*it).functorPtr; Q_ASSERT( f ); (*f)(); // call it delete f; // delete it } m_tableHandler->tableEnd(); m_tableQueue.pop(); } } } KoStoreDevice* Document::createPictureFrameSet( const KoSize& size ) { // Grab data that was stored with the functor, that triggered this parsing SubDocument subdoc( m_subdocQueue.front() ); TQDomElement framesetElement = m_mainDocument.createElement("FRAMESET"); framesetElement.setAttribute( "frameType", 2 /*picture*/ ); framesetElement.setAttribute( "frameInfo", 0 ); framesetElement.setAttribute( "name", subdoc.name ); m_framesetsElement.appendChild(framesetElement); // The position doesn't matter as long as the picture is inline // FIXME for non-inline pics #### // To determine the size, look at OOo's filter (WW8PicDesc in ww8graf2.cpp, version 1.50, line 406) // Hint: #i17200#, a bit of guesswork I'm afraid // if (aPic.dxaGoal == 1000 && aPic.mx == 1) //100% hack ? (from ww8graf2.cpp) createInitialFrame( framesetElement, 0, size.width(), 0, size.height(), false, NoFollowup ); TQDomElement pictureElem = m_mainDocument.createElement("PICTURE"); framesetElement.appendChild( pictureElem ); TQDomElement keyElem = m_mainDocument.createElement("KEY"); pictureElem.appendChild( keyElem ); keyElem.setAttribute( "filename", subdoc.extraName ); m_pictureList.append( subdoc.extraName ); kdDebug(30513) << "Preparing to write picture for '" << subdoc.name << "' into " << subdoc.extraName << endl; return m_chain->storageFile( subdoc.extraName, KoStore::Write ); } #include "document.moc"