olefilter.cpp
1/* This file is part of the KDE project
2 Copyright (C) 1999 Werner Trobin <trobin@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18
19DESCRIPTION
20
21 When reading, the point of this module is toperform a depth-first traversal
22 of an OLE file. This ensures that a parent object is processed only after
23 its child objects have been processed.
24*/
25
26#include <olefilter.h>
27
28#include <tqfile.h>
29#include <tdetempfile.h>
30#include <kgenericfactory.h>
31#include <kmimetype.h>
32#include <KoFilterChain.h>
33#include <KoDocumentInfo.h>
34
35//#include <excelfilter.h>
36#include <powerpointfilter.h>
37//#include <wordfilter.h>
38//#include <hancomwordfilter.h>
39
40#include <myfile.h>
41
42const int OLEFilter::s_area = 30510;
43
44class OLEFilterFactory : KGenericFactory<OLEFilter, KoFilter>
45{
46public:
47 OLEFilterFactory(void) : KGenericFactory<OLEFilter, KoFilter> ("olefilter")
48 {}
49protected:
50 virtual void setupTranslations( void )
51 {
52 TDEGlobal::locale()->insertCatalogue( "kofficefilters" );
53 }
54};
55
56K_EXPORT_COMPONENT_FACTORY( libolefilter, OLEFilterFactory() )
57
58OLEFilter::OLEFilter(KoFilter *, const char *, const TQStringList&) :
59 KoEmbeddingFilter(), numPic( 0 ),
60 docfile( 0 ), m_embeddeeData( 0 ),
61 m_embeddeeLength( 0 ), success( true )
62{
63 olefile.data=0L;
64}
65
66OLEFilter::~OLEFilter()
67{
68 delete [] olefile.data;
69 delete docfile;
70}
71
72KoFilter::ConversionStatus OLEFilter::convert( const TQCString& from, const TQCString& to )
73{
74 if(to!="application/x-kword" &&
75 to!="application/x-kspread" &&
76 to!="application/x-kpresenter")
77 return KoFilter::NotImplemented;
78 if(from!="application/vnd.ms-word" &&
79 from!="application/vnd.ms-excel" &&
80 from!="application/msword" &&
81 from!="application/msexcel" &&
82 from!="application/mspowerpoint" &&
83 from!="application/x-hancomword")
84 return KoFilter::NotImplemented;
85
86 TQFile in(m_chain->inputFile());
87 if(!in.open(IO_ReadOnly)) {
88 kdError(s_area) << "OLEFilter::filter(): Unable to open input" << endl;
89 in.close();
90 return KoFilter::FileNotFound;
91 }
92
93 // Open the OLE 2 file. [TODO] Is it really the best way to
94 // read all the stuff without buffer?
95 olefile.length=in.size();
96 olefile.data=new unsigned char[olefile.length];
97 in.readBlock((char*)olefile.data, olefile.length);
98 in.close();
99
100 docfile=new KLaola(olefile);
101 if(!docfile->isOk()) {
102 kdError(s_area) << "OLEFilter::filter(): Unable to read input file correctly!" << endl;
103 delete [] olefile.data;
104 olefile.data=0L;
105 return KoFilter::StupidError;
106 }
107
108 // Recursively convert the file
109 convert( "" );
110 if ( success )
111 return KoFilter::OK;
112 else
113 return KoFilter::StupidError;
114}
115
116void OLEFilter::commSlotDelayStream( const char* delay )
117{
118 emit internalCommDelayStream( delay );
119}
120
121void OLEFilter::commSlotShapeID( unsigned int& shapeID )
122{
123 emit internalCommShapeID( shapeID );
124}
125
126void OLEFilter::slotSavePart(
127 const TQString &nameIN,
128 TQString &storageId,
129 TQString &mimeType,
130 const TQString &extension,
131 unsigned int length,
132 const char *data)
133{
134 if(nameIN.isEmpty())
135 return;
136
137 int id = internalPartReference( nameIN );
138
139 if (id != -1)
140 {
141 // The part is already there, this is a lookup operation
142 // -> return the part id.
143 storageId = TQString::number( id );
144 mimeType = internalPartMimeType( nameIN );
145 }
146 else
147 {
148 // Set up the variables for the template method callback
149 m_embeddeeData = data;
150 m_embeddeeLength = length;
151
152 TQString srcMime( KoEmbeddingFilter::mimeTypeByExtension( extension ) );
153 if ( srcMime == KMimeType::defaultMimeType() )
154 kdWarning( s_area ) << "Couldn't determine the mimetype from the extension" << endl;
155
156 KoFilter::ConversionStatus status;
157 TQCString destMime( mimeType.latin1() );
158 storageId = TQString::number( embedPart( srcMime.latin1(), destMime, status, nameIN ) );
159
160 // copy back what the method returned
161 mimeType = destMime;
162
163 // Reset the variables to be on the safe side
164 m_embeddeeData = 0;
165 m_embeddeeLength = 0;
166
167 if ( status != KoFilter::OK )
168 kdDebug(s_area) << "Huh??? Couldn't convert that file" << endl;
169 }
170}
171
172void OLEFilter::slotSaveDocumentInformation(
173 const TQString &fullName,
174 const TQString &title,
175 const TQString &company,
176 const TQString &email,
177 const TQString &telephone,
178 const TQString &fax,
179 const TQString &postalCode,
180 const TQString &country,
181 const TQString &city,
182 const TQString &street,
183 const TQString &docTitle,
184 const TQString &docAbstract)
185{
186 KoDocumentInfo *info = new KoDocumentInfo();
187 KoDocumentInfoAuthor *author = static_cast<KoDocumentInfoAuthor *>(info->page("author"));
188 KoDocumentInfoAbout *about = static_cast<KoDocumentInfoAbout *>(info->page("about"));
189 author->setFullName(fullName);
190 author->setTitle(title);
191 author->setCompany(company);
192 author->setEmail(email);
193 author->setTelephoneHome(telephone);
194 author->setFax(fax);
195 author->setCountry(postalCode);
196 author->setPostalCode(country);
197 author->setCity(city);
198 author->setStreet(street);
199 about->setTitle(docTitle);
200 about->setTitle(docAbstract);
201
202 KoStoreDevice* docInfo = m_chain->storageFile( "documentinfo.xml", KoStore::Write );
203
204 if(!docInfo)
205 {
206 kdError(s_area) << "OLEFilter::slotSaveDocumentInformation(): Could not open documentinfo.xml!" << endl;
207 return;
208 }
209
210 TQCString data = info->save().toCString();
211 // Important: don't use data.length() here. It's slow, and dangerous (in case of a '\0' somewhere)
212 // The -1 is because we don't want to write the final \0.
213 TQ_LONG length = data.size()-1;
214
215 if(docInfo->writeBlock(data, length) != length)
216 kdError(s_area) << "OLEFilter::slotSaveDocumentInformation(): Could not write to KoStore!" << endl;
217}
218
219void OLEFilter::slotSavePic(
220 const TQString &nameIN,
221 TQString &storageId,
222 const TQString &extension,
223 unsigned int length,
224 const char *data)
225{
226 if(nameIN.isEmpty())
227 return;
228
229 TQMap<TQString, TQString>::ConstIterator it = imageMap.find(nameIN);
230
231 if (it != imageMap.end())
232 // The key is already here - return the part id.
233 storageId = it.data();
234 else
235 {
236 // It's not here, so let's generate one.
237 storageId = TQString( "pictures/picture%1.%2" ).arg( numPic++ ).arg( extension );
238 imageMap.insert(nameIN, storageId);
239 KoStoreDevice* pic = m_chain->storageFile( storageId, KoStore::Write );
240 if(!pic)
241 {
242 success = false;
243 kdError(s_area) << "OLEFilter::slotSavePic(): Could not open KoStore!" << endl;
244 return;
245 }
246 // Write it to the gzipped tar file
247 // Let's hope we never have to save images bigger than 2GB :-)
248 bool ret = pic->writeBlock(data, length) == static_cast<int>( length );
249 if (!ret)
250 kdError(s_area) << "OLEFilter::slotSavePic(): Could not write to KoStore!" << endl;
251 }
252}
253
254// ##### Only used for lookup now!
255void OLEFilter::slotPart(
256 const TQString& nameIN,
257 TQString &storageId,
258 TQString &mimeType)
259{
260 if (nameIN.isEmpty())
261 return;
262
263 int id = internalPartReference( nameIN );
264
265 if (id != -1)
266 {
267 // The key is already here - return the part id.
268 storageId = TQString::number( id );
269 mimeType = internalPartMimeType( nameIN );
270 }
271 else
272 kdWarning( s_area ) << "slotPart() can be used for lookup operations only" << endl;
273}
274
275// Don't forget the delete [] the stream.data ptr!
276void OLEFilter::slotGetStream(const int &handle, myFile &stream) {
277 stream=docfile->stream(handle);
278}
279
280// I can't guarantee that you get the right stream as the names
281// in a OLE 2 file are not unique! (searching only the current dir!)
282// Don't forget the delete [] the stream.data ptr!
283void OLEFilter::slotGetStream(const TQString &name, myFile &stream) {
284
285 KLaola::NodeList handle;
286
287 handle=docfile->find(name, true); // search only in current dir!
288
289 if (handle.count()==1)
290 stream=docfile->stream(handle.at(0));
291 else {
292 stream.data=0L;
293 stream.length=0;
294 }
295}
296
297void OLEFilter::savePartContents( TQIODevice* file )
298{
299 if ( m_embeddeeData != 0 && m_embeddeeLength != 0 )
300 file->writeBlock( m_embeddeeData, m_embeddeeLength );
301}
302
303// The recursive method to do all the work
304void OLEFilter::convert( const TQCString& mimeTypeHint )
305{
306 KLaola::NodeList list=docfile->parseCurrentDir();
307 KLaola::OLENode *node;
308 bool onlyDirs=true;
309
310 // Search for the directories
311 for(node=list.first(); node!=0; node=list.next()) {
312 if(node->isDirectory()) { // It's a dir!
313 if(docfile->enterDir(node)) {
314 // Go one level deeper, but don't increase the depth
315 // for ObjectPools.
316 if (node->name() == "ObjectPool")
317 convert( "" );
318 else {
319 // Get the storage name of the part (dirname==key), and associate the
320 // mimeType with it for later use.
321 TQCString mimeHint( mimeTypeHelper() );
322 if ( mimeHint.isEmpty() )
323 mimeHint = "application/x-kword"; // will be converted to a dummy KWord part
324 startInternalEmbedding( node->name(), mimeHint );
325 convert( mimeHint );
326 endInternalEmbedding();
327 }
328 docfile->leaveDir();
329 }
330 }
331 else
332 onlyDirs=false; // To prevent useless looping in the next loop
333 }
334
335 if(!onlyDirs) {
336 TQStringList nodeNames;
337 TQCString mimeType;
338 if ( !mimeTypeHint.isEmpty() )
339 mimeType = mimeTypeHint;
340 else
341 mimeType = mimeTypeHelper();
342
343 FilterBase *myFilter=0L;
344
345 #if 0
346 if ( mimeType == "application/x-kword" ) {
347 // WinWord (or dummy).
348
349 myFile main;
350 KLaola::NodeList tmp;
351 tmp=docfile->find("WordDocument", true);
352
353 if(tmp.count()==1) {
354 // okay, not a dummy
355 main=docfile->stream(tmp.at(0));
356
357 myFile table0, table1, data;
358 tmp=docfile->find("0Table", true);
359 if(tmp.count()==1)
360 table0=docfile->stream(tmp.at(0));
361
362 tmp=docfile->find("1Table", true);
363 if(tmp.count()==1)
364 table1=docfile->stream(tmp.at(0));
365
366 tmp=docfile->find("Data", true);
367 if(tmp.count()==1)
368 data=docfile->stream(tmp.at(0));
369
370 myFilter=new WordFilter(main, table0, table1, data);
371
372 // forward the internal communication calls
373 connect( this, TQ_SIGNAL( internalCommShapeID( unsigned int& ) ), myFilter, TQ_SIGNAL( internalCommShapeID( unsigned int& ) ) );
374 connect( this, TQ_SIGNAL( internalCommDelayStream( const char* ) ), myFilter, TQ_SIGNAL( internalCommDelayStream( const char* ) ) );
375 }
376 }
377 else if ( mimeType == "application/x-kspread" ) {
378 // Excel.
379
380 myFile workbook;
381 KLaola::NodeList tmp;
382
383 tmp = docfile->find( "Workbook", true );
384 if ( tmp.count() == 1 )
385 workbook = docfile->stream( tmp.at( 0 ) );
386 else {
387 tmp = docfile->find( "Book", true );
388 if ( tmp.count() == 1 )
389 workbook = docfile->stream( tmp.at( 0 ) );
390 }
391 myFilter=new ExcelFilter(workbook);
392 }
393 else
394 #endif
395 if ( mimeType == "application/x-kpresenter" ) {
396 // Powerpoint.
397
398 myFile main, currentUser, pictures, summary, documentSummary;
399 KLaola::NodeList tmp;
400
401 tmp=docfile->find("PowerPoint Document", true);
402 if(tmp.count()==1)
403 main=docfile->stream(tmp.at(0));
404
405 tmp=docfile->find("Current User", true);
406 if(tmp.count()==1)
407 currentUser=docfile->stream(tmp.at(0));
408
409 tmp=docfile->find("Pictures", true);
410 if(tmp.count()==1)
411 pictures=docfile->stream(tmp.at(0));
412
413 tmp=docfile->find("SummaryInformation", true);
414 if(tmp.count()==1)
415 summary=docfile->stream(tmp.at(0));
416
417 tmp=docfile->find("DocumentSummaryInformation", true);
418 if(tmp.count()==1)
419 documentSummary=docfile->stream(tmp.at(0));
420
421 myFilter=new PowerPointFilter(main, currentUser, pictures);
422 }
423 #if 0
424 else if ( mimeType == "application/x-hancomword" ) {
425 // HancomWord 6
426 myFile prvText;
427 KLaola::NodeList tmp;
428
429 tmp = docfile->find( "PrvText", true );
430 if( tmp.count() == 1 ) prvText = docfile->stream( tmp.at( 0 ) );
431
432 myFilter = new HancomWordFilter( prvText );
433 }
434 #endif
435
436 if(!myFilter) {
437 // Unknown type. We turn it into a dummy kword document...
438 node = list.first();
439 do {
440 nodeNames.prepend(node->name());
441 node = list.next();
442 } while ( node );
443
444 kdWarning(s_area) << "cannot convert \"" << nodeNames.join(",") << "\"" << endl;
445 myFilter=new FilterBase(nodeNames);
446 }
447
448 // connect SIGNALs&SLOTs
449 connectCommon(&myFilter);
450
451 // Launch the filtering process...
452 success=myFilter->filter();
453 // ...and fetch the file
454 TQCString file;
455 if(!myFilter->plainString()) {
456 const TQDomDocument * const part=myFilter->part();
457 file=part->toCString();
458 }
459 else
460 file=myFilter->CString();
461
462 KoStoreDevice* dev = m_chain->storageFile( "root", KoStore::Write );
463 if(!dev) {
464 success=false;
465 kdError(s_area) << "OLEFilter::convert(): Could not open KoStore!" << endl;
466 return;
467 }
468
469 // Write it to the gzipped tar file
470 bool ret = dev->writeBlock(file.data(), file.size()-1) == static_cast<TQ_LONG>( file.size() - 1 );
471 if (!ret)
472 kdError(s_area) << "OLEFilter::slotSavePic(): Could not write to KoStore!" << endl;
473 delete myFilter;
474 }
475}
476
477void OLEFilter::connectCommon(FilterBase **myFilter) {
478 TQObject::connect(
479 *myFilter,
480 TQ_SIGNAL(signalSaveDocumentInformation(const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &)),
481 this,
482 TQ_SLOT(slotSaveDocumentInformation(const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &, const TQString &)));
483
484 TQObject::connect(
485 *myFilter,
486 TQ_SIGNAL(signalSavePic(const TQString &, TQString &, const TQString &, unsigned int, const char *)),
487 this,
488 TQ_SLOT(slotSavePic(const TQString &, TQString &, const TQString &, unsigned int, const char *)));
489 TQObject::connect(
490 *myFilter,
491 TQ_SIGNAL(signalSavePart(const TQString &, TQString &, TQString &, const TQString &, unsigned int, const char *)),
492 this,
493 TQ_SLOT(slotSavePart(const TQString &, TQString &, TQString &, const TQString &, unsigned int, const char *)));
494 TQObject::connect(*myFilter, TQ_SIGNAL(signalPart(const TQString&, TQString &, TQString &)),
495 this, TQ_SLOT(slotPart(const TQString&, TQString &, TQString &)));
496 TQObject::connect(*myFilter, TQ_SIGNAL(signalGetStream(const int &, myFile &)), this,
497 TQ_SLOT(slotGetStream(const int &, myFile &)));
498 TQObject::connect(*myFilter, TQ_SIGNAL(signalGetStream(const TQString &, myFile &)), this,
499 TQ_SLOT(slotGetStream(const TQString &, myFile &)));
500 TQObject::connect(*myFilter, TQ_SIGNAL(sigProgress(int)), this, TQ_SIGNAL(sigProgress(int)));
501}
502
503TQCString OLEFilter::mimeTypeHelper()
504{
505 KLaola::NodeList list = docfile->parseCurrentDir();
506 KLaola::OLENode* node = list.first();
507
508 // ###### FIXME: Shaheed, please add additional mimetypes here
509 while ( node ) {
510 if ( node->name() == "WordDocument" )
511 return "application/x-kword";
512 else if ( node->name() == "Workbook" || node->name() == "Book" )
513 return "application/x-kspread";
514 else if ( node->name() == "PowerPoint Document" )
515 return "application/x-kpresenter";
516 else if ( node->name() == "PrvText" || node->name() == "BodyText" )
517 return "application/x-hancomword";
518 else
519 node = list.next();
520 }
521 kdWarning( s_area ) << "No known mimetype detected" << endl;
522 return "";
523}
524
525#include <olefilter.moc>