filters

abiwordexport.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001, 2002, 2003, 2004 Nicolas GOUTTE <goutte@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*/
19
20/*
21 This file is based on the old file:
22 /home/kde/koffice/filters/kword/ascii/asciiexport.cpp
23
24 The old file was copyrighted by
25 Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
26 Copyright (c) 2000 ID-PRO Deutschland GmbH. All rights reserved.
27 Contact: Wolf-Michael Bolle <Bolle@ID-PRO.de>
28
29 The old file was licensed under the terms of the GNU Library General Public
30 License version 2.
31*/
32
33#include <tqmap.h>
34#include <tqiodevice.h>
35#include <tqtextstream.h>
36#include <tqdom.h>
37
38#include <kdebug.h>
39#include <kmdcodec.h>
40#include <kfilterdev.h>
41#include <kgenericfactory.h>
42#include <kimageio.h>
43
44#include <KoPageLayout.h>
45#include <KoFilterChain.h>
46#include <KoPictureKey.h>
47
48#include <KWEFStructures.h>
49#include <KWEFUtil.h>
50#include <KWEFBaseWorker.h>
51#include <KWEFKWordLeader.h>
52
53#include <abiwordexport.h>
54#include <abiwordexport.moc>
55
56class ABIWORDExportFactory : KGenericFactory<ABIWORDExport, KoFilter>
57{
58public:
59 ABIWORDExportFactory(void) : KGenericFactory<ABIWORDExport, KoFilter> ("kwordabiwordexport")
60 {}
61protected:
62 virtual void setupTranslations( void )
63 {
64 TDEGlobal::locale()->insertCatalogue( "kofficefilters" );
65 }
66};
67
68K_EXPORT_COMPONENT_FACTORY( libabiwordexport, ABIWORDExportFactory() )
69
70class StyleMap : public TQMap<TQString,LayoutData>
71{
72public:
73 StyleMap(void) {}
74 ~StyleMap(void) {}
75};
76
77class AbiWordWorker : public KWEFBaseWorker
78{
79public:
80 AbiWordWorker(void);
81 virtual ~AbiWordWorker(void) { delete m_streamOut; delete m_ioDevice; }
82public:
83 virtual bool doOpenFile(const TQString& filenameOut, const TQString& to);
84 virtual bool doCloseFile(void); // Close file in normal conditions
85 virtual bool doOpenDocument(void);
86 virtual bool doCloseDocument(void);
87 virtual bool doFullParagraph(const TQString& paraText, const LayoutData& layout,
88 const ValueListFormatData& paraFormatDataList);
89 virtual bool doOpenTextFrameSet(void); // AbiWord's <section>
90 virtual bool doCloseTextFrameSet(void); // AbiWord's </section>
91 virtual bool doFullPaperFormat(const int format,
92 const double width, const double height, const int orientation); // Calc AbiWord's <papersize>
93 virtual bool doFullPaperBorders (const double top, const double left,
94 const double bottom, const double right); // Like KWord's <PAPERBORDERS>
95 virtual bool doCloseHead(void); // Write <papersize>
96 virtual bool doOpenStyles(void); // AbiWord's <styles>
97 virtual bool doCloseStyles(void); // AbiWord's </styles>
98 virtual bool doFullDefineStyle(LayoutData& layout); // AbiWord's <s></s>
99 virtual bool doOpenSpellCheckIgnoreList (void); // AbiWord's <ignorewords>
100 virtual bool doCloseSpellCheckIgnoreList (void); // AbiWord's </ignorewords>
101 virtual bool doFullSpellCheckIgnoreWord (const TQString& ignoreword); // AbiWord's <iw>
102 virtual bool doFullDocumentInfo(const KWEFDocumentInfo& docInfo); // AbiWord's <metadata>
103private:
104 void processParagraphData (const TQString& paraText,
105 const TextFormatting& formatLayout,
106 const ValueListFormatData& paraFormatDataList);
107 void processNormalText ( const TQString& paraText,
108 const TextFormatting& formatLayout,
109 const FormatData& formatData);
110 void processVariable ( const TQString& paraText,
111 const TextFormatting& formatLayout,
112 const FormatData& formatData);
113 void processAnchor ( const TQString& paraText,
114 const TextFormatting& formatLayout,
115 const FormatData& formatData);
116 TQString textFormatToAbiProps(const TextFormatting& formatOrigin,
117 const TextFormatting& formatData, const bool force) const;
118 TQString layoutToCss(const LayoutData& layoutOrigin,
119 const LayoutData& layout, const bool force) const;
120 TQString escapeAbiWordText(const TQString& strText) const;
121 bool makeTable(const FrameAnchor& anchor);
122 bool makePicture(const FrameAnchor& anchor);
123 void writeAbiProps(const TextFormatting& formatLayout, const TextFormatting& format);
124 void writePictureData(const TQString& koStoreName, const TQString& keyName);
125 TQString transformToTextDate(const TQDateTime& dt);
126private:
127 TQIODevice* m_ioDevice;
128 TQTextStream* m_streamOut;
129 TQString m_pagesize; // Buffer for the <pagesize> tag
130 TQMap<TQString,KoPictureKey> m_mapPictureData;
131 StyleMap m_styleMap;
132 double m_paperBorderTop,m_paperBorderLeft,m_paperBorderBottom,m_paperBorderRight;
133 bool m_inIgnoreWords; // true if <ignorewords> has been written
134 KWEFDocumentInfo m_docInfo; // document information
135};
136
137AbiWordWorker::AbiWordWorker(void) : m_ioDevice(NULL), m_streamOut(NULL),
138 m_paperBorderTop(0.0),m_paperBorderLeft(0.0),
139 m_paperBorderBottom(0.0),m_paperBorderRight(0.0)
140{
141}
142
143TQString AbiWordWorker::escapeAbiWordText(const TQString& strText) const
144{
145 // Escape quotes (needed in attributes)
146 // Escape apostrophs (allowed by XML)
147 return KWEFUtil::EscapeSgmlText(NULL,strText,true,true);
148}
149
150bool AbiWordWorker::doOpenFile(const TQString& filenameOut, const TQString& )
151{
152 kdDebug(30506) << "Opening file: " << filenameOut
153 << " (in AbiWordWorker::doOpenFile)" << endl;
154 //Find the last extension
155 TQString strExt;
156 const int result=filenameOut.findRev('.');
157 if (result>=0)
158 {
159 strExt=filenameOut.mid(result);
160 }
161
162 TQString strMimeType; // Mime type of the compressor
163
164 if ((strExt==".gz")||(strExt==".GZ") //in case of .abw.gz (logical extension)
165 ||(strExt==".zabw")||(strExt==".ZABW")) //in case of .zabw (extension used prioritary with AbiWord)
166 {
167 // Compressed with gzip
168 strMimeType="application/x-gzip";
169 }
170 else if ((strExt==".bz2")||(strExt==".BZ2") //in case of .abw.bz2 (logical extension)
171 ||(strExt==".bzabw")||(strExt==".BZABW")) //in case of .bzabw (extension used prioritary with AbiWord)
172 {
173 // Compressed with bzip2
174 strMimeType="application/x-bzip2";
175 }
176 else
177 {
178 // No compression
179 strMimeType="text/plain";
180 }
181
182 kdDebug(30506) << "Compression: " << strMimeType << endl;
183
184 m_ioDevice = KFilterDev::deviceForFile(filenameOut,strMimeType);
185
186 if (!m_ioDevice)
187 {
188 kdError(30506) << "No output file! Aborting!" << endl;
189 return false;
190 }
191
192 if ( !m_ioDevice->open (IO_WriteOnly) )
193 {
194 kdError(30506) << "Unable to open output file! Aborting!" << endl;
195 return false;
196 }
197
198 m_streamOut=new TQTextStream(m_ioDevice);
199
200 // We only export in UTF-8 (are there AbiWord ports that cannot read UTF-8? Be careful SVG uses UTF-8 too!)
201 m_streamOut->setEncoding( TQTextStream::UnicodeUTF8 );
202 return true;
203}
204
205bool AbiWordWorker::doCloseFile(void)
206{
207 delete m_streamOut;
208 m_streamOut=NULL;
209 if (m_ioDevice)
210 m_ioDevice->close();
211 return (m_ioDevice);
212}
213
214bool AbiWordWorker::doOpenDocument(void)
215{
216 kdDebug(30506)<< "AbiWordWorker::doOpenDocument" << endl;
217 // Make the file header
218
219 // First the XML header in UTF-8 version
220 // (AbiWord and TQt handle UTF-8 well, so we stay with this encoding!)
221 *m_streamOut << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
222
223 // NOTE: AbiWord CVS 2002-02-?? has a new DOCTYPE
224 *m_streamOut << "<!DOCTYPE abiword PUBLIC \"-//ABISOURCE//DTD AWML 1.0 Strict//EN\"";
225 *m_streamOut << " \"http://www.abisource.com/awml.dtd\">\n";
226
227 // First magic: "<abiword"
228 *m_streamOut << "<abiword";
229 // AbiWord CVS 2002-02-23 defines a default namespace.
230 *m_streamOut << " xmlns=\"http://www.abisource.com/awml.dtd\"";
231 // As we do not use xmlns:awml, do we need to define it?
232 // *m_streamOut << " xmlns:awml=\"http://www.abisource.com/awml.dtd\"";
233 *m_streamOut << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
234 // AbiWord CVS 2002-02-22 defines other namespaces, which we are not using.
235 // AbiWord CVS 2002-12-23 has no fileformat attribute anymore
236 // ### TODO: add document language and document direction of writing.
237 *m_streamOut << " xml:space=\"preserve\" version=\"1.1.2\" template=\"false\" styles=\"unlocked\">\n";
238 // Second magic: "<!-- This file is an AbiWord document."
239 // TODO/FIXME: write as much spaces as AbiWord does for the following line.
240 *m_streamOut << "<!-- This file is an AbiWord document. -->\n";
241 // We have chosen NOT to have the full comment header that AbiWord files normally have.
242 // ### TODO: perhaps we should add the comment: do not edit the file
243 *m_streamOut << "\n";
244
245
246 return true;
247}
248
249void AbiWordWorker::writePictureData(const TQString& koStoreName, const TQString& keyName)
250{
251 kdDebug(30506) << "AbiWordWorker::writeImageData" << endl;
252
253 TQByteArray image;
254
255 TQString strExtension(koStoreName.lower());
256 const int result=koStoreName.findRev(".");
257 if (result>=0)
258 {
259 strExtension=koStoreName.mid(result+1);
260 }
261
262 bool isImageLoaded=false;
263
264 if (strExtension=="png")
265 {
266 isImageLoaded=loadSubFile(koStoreName,image);
267 }
268 else
269 {
270 // All other picture types must be converted to PNG
271 // (yes, even JPEG, SVG or WMF!)
272 isImageLoaded=loadAndConvertToImage(koStoreName,strExtension,"PNG",image);
273 }
274
275 if (isImageLoaded)
276 {
277 *m_streamOut << "<d name=\"" << keyName << "\""
278 << " base64=\"yes\""
279 << " mime=\"image/png\">\n";
280
281 TQCString base64=KCodecs::base64Encode(image,true);
282
283 *m_streamOut << base64 << "\n"; // TQCString is taken as Latin1 by TQTextStream
284
285 *m_streamOut << "</d>\n";
286 }
287 else
288 {
289 kdWarning(30506) << "Unable to load picture: " << koStoreName << endl;
290 }
291}
292
293bool AbiWordWorker::doCloseDocument(void)
294{
295 // Before writing the <data> element,
296 // we must be sure that we have data and that we can retrieve it.
297
298 if (m_kwordLeader && !m_mapPictureData.isEmpty())
299 {
300 *m_streamOut << "<data>\n";
301
302 TQMap<TQString,KoPictureKey>::ConstIterator it;
303 TQMap<TQString,KoPictureKey>::ConstIterator end(m_mapPictureData.end());
304 // all images first
305 for (it=m_mapPictureData.begin(); it!=end; ++it)
306 {
307 // Warning: do not mix up KWord's key and the iterator's key!
308 writePictureData(it.key(),it.data().filename());
309 }
310
311 *m_streamOut << "</data>\n";
312 }
313
314 *m_streamOut << "</abiword>\n"; //Close the file for XML
315 return true;
316}
317
318bool AbiWordWorker::doOpenTextFrameSet(void)
319{
320 *m_streamOut << "<section props=\"";
321 *m_streamOut << "page-margin-top: ";
322 *m_streamOut << m_paperBorderTop;
323 *m_streamOut << "pt; ";
324 *m_streamOut << "page-margin-left: ";
325 *m_streamOut << m_paperBorderLeft;
326 *m_streamOut << "pt; ";
327 *m_streamOut << "page-margin-bottom: ";
328 *m_streamOut << m_paperBorderBottom;
329 *m_streamOut << "pt; ";
330 *m_streamOut << "page-margin-right: ";
331 *m_streamOut << m_paperBorderRight;
332 *m_streamOut << "pt"; // Last one, so no semi-comma
333 *m_streamOut << "\">\n";
334 return true;
335}
336
337bool AbiWordWorker::doCloseTextFrameSet(void)
338{
339 *m_streamOut << "</section>\n";
340 return true;
341}
342
344{
345 *m_streamOut << "<styles>\n";
346 return true;
347}
348
350{
351 *m_streamOut << "</styles>\n";
352 return true;
353}
354
355TQString AbiWordWorker::textFormatToAbiProps(const TextFormatting& formatOrigin,
356 const TextFormatting& formatData, const bool force) const
357{
358 // TODO: rename variable formatData
359 TQString strElement; // TODO: rename this variable
360
361 // Font name
362 TQString fontName = formatData.fontName;
363 if ( !fontName.isEmpty()
364 && (force || (formatOrigin.fontName!=formatData.fontName)))
365 {
366 strElement+="font-family: ";
367 strElement+= escapeAbiWordText(fontName); // TODO: add alternative font names
368 strElement+="; ";
369 }
370
371 if (force || (formatOrigin.italic!=formatData.italic))
372 {
373 // Font style
374 strElement+="font-style: ";
375 if ( formatData.italic )
376 {
377 strElement+="italic";
378 }
379 else
380 {
381 strElement+="normal";
382 }
383 strElement+="; ";
384 }
385
386 if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75)))
387 {
388 strElement+="font-weight: ";
389 if ( formatData.weight >= 75 )
390 {
391 strElement+="bold";
392 }
393 else
394 {
395 strElement+="normal";
396 }
397 strElement+="; ";
398 }
399
400 if (force || (formatOrigin.fontSize!=formatData.fontSize))
401 {
402 const int size=formatData.fontSize;
403 if (size>0)
404 {
405 // We use absolute font sizes.
406 strElement+="font-size: ";
407 strElement+=TQString::number(size,10);
408 strElement+="pt; ";
409 }
410 }
411
412 if (force || (formatOrigin.fgColor!=formatData.fgColor))
413 {
414 if ( formatData.fgColor.isValid() )
415 {
416 // Give colour
417 strElement+="color: ";
418
419 // No leading # (unlike CSS2)
420 // We must have two hex digits for each colour channel!
421 const int red=formatData.fgColor.red();
422 strElement += TQString::number((red&0xf0)>>4,16);
423 strElement += TQString::number(red&0x0f,16);
424
425 const int green=formatData.fgColor.green();
426 strElement += TQString::number((green&0xf0)>>4,16);
427 strElement += TQString::number(green&0x0f,16);
428
429 const int blue=formatData.fgColor.blue();
430 strElement += TQString::number((blue&0xf0)>>4,16);
431 strElement += TQString::number(blue&0x0f,16);
432
433 strElement+="; ";
434 }
435 }
436
437 if (force || (formatOrigin.bgColor!=formatData.bgColor))
438 {
439 if ( formatData.bgColor.isValid() )
440 {
441 // Give background colour
442 strElement+="bgcolor: ";
443
444 // No leading # (unlike CSS2)
445 // We must have two hex digits for each colour channel!
446 const int red=formatData.bgColor.red();
447 strElement += TQString::number((red&0xf0)>>4,16);
448 strElement += TQString::number(red&0x0f,16);
449
450 const int green=formatData.bgColor.green();
451 strElement += TQString::number((green&0xf0)>>4,16);
452 strElement += TQString::number(green&0x0f,16);
453
454 const int blue=formatData.bgColor.blue();
455 strElement += TQString::number((blue&0xf0)>>4,16);
456 strElement += TQString::number(blue&0x0f,16);
457
458 strElement+="; ";
459 }
460 }
461
462 if (force || (formatOrigin.underline!=formatData.underline)
463 || (formatOrigin.strikeout!=formatData.strikeout))
464 {
465 strElement+="text-decoration: ";
466 if ( formatData.underline )
467 {
468 strElement+="underline";
469 }
470 else if ( formatData.strikeout )
471 {
472 strElement+="line-through";
473 }
474 else
475 {
476 strElement+="none";
477 }
478 strElement+="; ";
479 }
480
481 return strElement;
482}
483
484bool AbiWordWorker::makeTable(const FrameAnchor& anchor)
485{
486#if 0
487 *m_streamOut << "</p>\n"; // Close previous paragraph ### TODO: do it correctly like for HTML
488 *m_streamOut << "<table>\n";
489#endif
490
491 TQValueList<TableCell>::ConstIterator itCell;
492 for (itCell=anchor.table.cellList.begin();
493 itCell!=anchor.table.cellList.end(); itCell++)
494 {
495#if 0
496 // ### TODO: rowspan, colspan
497
498 // AbiWord seems to work by attaching to the cell borders
499 *m_streamOut << "<cell props=\"";
500 *m_streamOut << "left-attach:" << (*itCell).col << "; ";
501 *m_streamOut << "right-attach:" << (*itCell).col + 1 << "; ";
502 *m_streamOut << "top-attach:" << (*itCell).row << "; ";
503 *m_streamOut << "bot-attach:" << (*itCell).row + 1;
504 *m_streamOut << "\">\n";
505#endif
506 if (!doFullAllParagraphs(*(*itCell).paraList))
507 {
508 return false;
509 }
510#if 0
511 *m_streamOut << "</cell>\n";
512#endif
513 }
514#if 0
515 *m_streamOut << "</table>\n";
516 *m_streamOut << "<p>\n"; // Re-open the "previous" paragraph ### TODO: do it correctly like for HTML
517#endif
518 return true;
519}
520
521bool AbiWordWorker::makePicture(const FrameAnchor& anchor)
522{
523 kdDebug(30506) << "New image/clipart: " << anchor.picture.koStoreName
524 << " , " << anchor.picture.key.toString() << endl;
525
526 const double height=anchor.frame.bottom - anchor.frame.top;
527 const double width =anchor.frame.right - anchor.frame.left;
528
529 // TODO: we are only using the filename, not the rest of the key
530 // TODO: (bad if there are two images of the same name, but of a different key)
531 *m_streamOut << "<image dataid=\"" << anchor.picture.key.filename() << "\"";
532 *m_streamOut << " props= \"height:" << height << "pt;width:" << width << "pt\"";
533 *m_streamOut << "/>"; // NO end of line!
534 // TODO: other props for image
535
536 m_mapPictureData[anchor.picture.koStoreName]=anchor.picture.key;
537
538 return true;
539}
540
541void AbiWordWorker::writeAbiProps (const TextFormatting& formatLayout, const TextFormatting& format)
542{
543 TQString abiprops=textFormatToAbiProps(formatLayout,format,false);
544
545 // Erase the last semi-comma (as in CSS2, semi-commas only separate instructions and do not terminate them)
546 const int result=abiprops.findRev(";");
547
548 if (result>=0)
549 {
550 // Remove the last semi-comma and the space thereafter
551 abiprops.remove(result,2);
552 }
553
554 if (!abiprops.isEmpty())
555 {
556 *m_streamOut << " props=\"" << abiprops << "\"";
557 }
558}
559
560void AbiWordWorker::processNormalText ( const TQString &paraText,
561 const TextFormatting& formatLayout,
562 const FormatData& formatData)
563{
564 // Retrieve text and escape it
565 TQString partialText=escapeAbiWordText(paraText.mid(formatData.pos,formatData.len));
566
567 // Replace line feeds by line breaks
568 int pos;
569 while ((pos=partialText.find(TQChar(10)))>-1)
570 {
571 partialText.replace(pos,1,"<br/>");
572 }
573
574 if (formatData.text.missing)
575 {
576 // It's just normal text, so we do not need a <c> element!
577 *m_streamOut << partialText;
578 }
579 else
580 { // Text with properties, so use a <c> element!
581 *m_streamOut << "<c";
582 writeAbiProps(formatLayout,formatData.text);
583 *m_streamOut << ">" << partialText << "</c>";
584 }
585}
586
587void AbiWordWorker::processVariable ( const TQString&,
588 const TextFormatting& formatLayout,
589 const FormatData& formatData)
590{
591 if (0==formatData.variable.m_type)
592 {
593 // As AbiWord's field is inflexible, we cannot make the date custom
594 *m_streamOut << "<field type=\"date_ntdfl\"";
595 writeAbiProps(formatLayout,formatData.text);
596 *m_streamOut << "/>";
597 }
598 else if (2==formatData.variable.m_type)
599 {
600 // As AbiWord's field is inflexible, we cannot make the time custom
601 *m_streamOut << "<field type=\"time\"";
602 writeAbiProps(formatLayout,formatData.text);
603 *m_streamOut << "/>";
604 }
605 else if (4==formatData.variable.m_type)
606 {
607 // As AbiWord's field is inflexible, we cannot make the time custom
608 TQString strFieldType;
609 if (formatData.variable.isPageNumber())
610 {
611 strFieldType="page_number";
612 }
613 else if (formatData.variable.isPageCount())
614 {
615 strFieldType="page_count";
616 }
617 if (strFieldType.isEmpty())
618 {
619 // Unknown subtype, therefore write out the result
620 *m_streamOut << formatData.variable.m_text;
621 }
622 else
623 {
624 *m_streamOut << "<field type=\"" << strFieldType <<"\"";
625 writeAbiProps(formatLayout,formatData.text);
626 *m_streamOut << "/>";
627 }
628 }
629 else if (9==formatData.variable.m_type)
630 {
631 // A link
632 *m_streamOut << "<a xlink:href=\""
633 << escapeAbiWordText(formatData.variable.getHrefName())
634 << "\"><c"; // In AbiWord, an anchor <a> has always a <c> child
635 writeAbiProps(formatLayout,formatData.text);
636 *m_streamOut << ">"
637 << escapeAbiWordText(formatData.variable.getLinkName())
638 << "</c></a>";
639 }
640#if 0
641 else if (11==(*paraFormatDataIt).variable.m_type)
642 {
643 // Footnote
644 TQString value = (*paraFormatDataIt).variable.getFootnoteValue();
645 bool automatic = (*paraFormatDataIt).variable.getFootnoteAuto();
646 TQValueList<ParaData> *paraList = (*paraFormatDataIt).variable.getFootnotePara();
647 if( paraList )
648 {
649 TQString fstr;
650 TQValueList<ParaData>::ConstIterator it;
651 for (it=paraList->begin();it!=paraList->end();it++)
652 fstr += ProcessParagraphData( (*it).text, (*it).layout,(*it).formattingList);
653 str += "{\\super ";
654 str += automatic ? "\\chftn " : value;
655 str += "{\\footnote ";
656 str += "{\\super ";
657 str += automatic ? "\\chftn " : value;
658 str += fstr;
659 str += " }";
660 str += " }";
661 str += " }";
662 }
663 }
664#endif
665 else
666 {
667 // Generic variable
668 *m_streamOut << formatData.variable.m_text;
669 }
670}
671
672void AbiWordWorker::processAnchor ( const TQString&,
673 const TextFormatting& /*formatLayout*/, //TODO
674 const FormatData& formatData)
675{
676 // We have an image or a table
677 if ( (2==formatData.frameAnchor.type) // <IMAGE> or <PICTURE>
678 || (5==formatData.frameAnchor.type) ) // <CLIPART>
679 {
680 makePicture(formatData.frameAnchor);
681 }
682 else if (6==formatData.frameAnchor.type)
683 {
684 makeTable(formatData.frameAnchor);
685 }
686 else
687 {
688 kdWarning(30506) << "Unsupported anchor type: "
689 << formatData.frameAnchor.type << endl;
690 }
691}
692
693void AbiWordWorker::processParagraphData ( const TQString &paraText,
694 const TextFormatting& formatLayout,
695 const ValueListFormatData &paraFormatDataList)
696{
697 if ( paraText.length () > 0 )
698 {
699 ValueListFormatData::ConstIterator paraFormatDataIt;
700
701 for ( paraFormatDataIt = paraFormatDataList.begin ();
702 paraFormatDataIt != paraFormatDataList.end ();
703 paraFormatDataIt++ )
704 {
705 if (1==(*paraFormatDataIt).id)
706 {
707 processNormalText(paraText, formatLayout, (*paraFormatDataIt));
708 }
709 else if (4==(*paraFormatDataIt).id)
710 {
711 processVariable(paraText, formatLayout, (*paraFormatDataIt));
712 }
713 else if (6==(*paraFormatDataIt).id)
714 {
715 processAnchor(paraText, formatLayout, (*paraFormatDataIt));
716 }
717 }
718 }
719}
720
721TQString AbiWordWorker::layoutToCss(const LayoutData& layoutOrigin,
722 const LayoutData& layout, const bool force) const
723{
724 TQString props;
725
726 if (force || (layoutOrigin.alignment!=layout.alignment))
727 {
728 // Check if the current alignment is a valid one for AbiWord.
729 if ((layout.alignment == "left") || (layout.alignment == "right")
730 || (layout.alignment == "center") || (layout.alignment == "justify"))
731 {
732 props += "text-align:";
733 props += layout.alignment;
734 props += "; ";
735 }
736 else if (layout.alignment == "auto")
737 {
738 // We assume a left alignment as AbiWord is not really bi-di (and this filter even less.)
739 props += "text-align:left; ";
740 }
741 else
742 {
743 kdWarning(30506) << "Unknown alignment: " << layout.alignment << endl;
744 }
745 }
746
747 // TODO/FIXME: what if all tabulators must be erased?
748#if 0
749 // DEPRECATED!
750 if (!layout.tabulator.isEmpty()
751 && (force || (layoutOrigin.tabulator!=layout.tabulator)))
752 {
753 props += "tabstops:";
754 props += layout.tabulator;
755 props += "; ";
756 }
757#endif
758 if (!layout.tabulatorList.isEmpty()
759 && (force || (layoutOrigin.tabulatorList!=layout.tabulatorList) ))
760 {
761 props += "tabstops:";
762 bool first=true;
763 TabulatorList::ConstIterator it;
764 TabulatorList::ConstIterator end(layout.tabulatorList.end());
765 for (it=layout.tabulatorList.begin();it!=end;++it)
766 {
767 if (first)
768 {
769 first=false;
770 }
771 else
772 {
773 props += ",";
774 }
775 props += TQString::number((*it).m_ptpos);
776 props += "pt";
777
778 switch ((*it).m_type)
779 {
780 case 0: props += "/L"; break;
781 case 1: props += "/C"; break;
782 case 2: props += "/R"; break;
783 case 3: props += "/D"; break;
784 default: props += "/L";
785 }
786
787 props += "0"; // No filling
788 }
789 props += "; ";
790 }
791
792 if ((layout.indentLeft>=0.0)
793 && (force || (layoutOrigin.indentLeft!=layout.indentLeft)))
794 {
795 props += TQString("margin-left:%1pt; ").arg(layout.indentLeft);
796 }
797
798 if ((layout.indentRight>=0.0)
799 && (force || (layoutOrigin.indentRight!=layout.indentRight)))
800 {
801 props += TQString("margin-right:%1pt; ").arg(layout.indentRight);
802 }
803
804 if (force || (layoutOrigin.indentLeft!=layout.indentLeft))
805 {
806 props += "text-indent: ";
807 props += TQString::number(layout.indentFirst);
808 props += "pt; ";
809 }
810
811 if ((layout.marginBottom>=0.0)
812 && ( force || ( layoutOrigin.marginBottom != layout.marginBottom ) ) )
813 {
814 props += TQString("margin-bottom:%1pt; ").arg(layout.marginBottom);
815 }
816
817 if ((layout.marginTop>=0.0)
818 && ( force || ( layoutOrigin.marginTop != layout.marginTop ) ) )
819 {
820 props += TQString("margin-top:%1pt; ").arg(layout.marginTop);
821 }
822
823 if (force
824 || ( layoutOrigin.lineSpacingType != layout.lineSpacingType )
825 || ( layoutOrigin.lineSpacing != layout.lineSpacing ) )
826 {
827 switch ( layout.lineSpacingType )
828 {
829 case LayoutData::LS_CUSTOM:
830 {
831 // We have a custom line spacing (in points). However AbiWord cannot do it, so transform in "at-least"
832 props += "line-height=:";
833 props += TQString::number( layout.lineSpacing ); // ### TODO: rounding?
834 props += "pt+; ";
835 break;
836 }
837 case LayoutData::LS_SINGLE:
838 {
839 props += "line-height:1.0; "; // One
840 break;
841 }
842 case LayoutData::LS_ONEANDHALF:
843 {
844 props += "line-height:1.5; "; // One-and-half
845 break;
846 }
847 case LayoutData::LS_DOUBLE:
848 {
849 props += "line-height:2.0; "; // Two
850 break;
851 }
852 case LayoutData::LS_MULTIPLE:
853 {
854 props += "line-height:";
855 props += TQString::number( layout.lineSpacing ); // ### TODO: rounding?
856 props += "; ";
857 break;
858 }
859 case LayoutData::LS_FIXED:
860 {
861 // We have a fixed line height (in points)
862 props += "line-height:";
863 props += TQString::number( layout.lineSpacing ); // ### TODO: rounding?
864 props += "pt; ";
865 break;
866 }
867 case LayoutData::LS_ATLEAST:
868 {
869 // We have an "at-least" line height (in points)
870 props += "line-height=:";
871 props += TQString::number( layout.lineSpacing ); // ### TODO: rounding?
872 props += "pt+; "; // The + makes the difference
873 break;
874 }
875 default:
876 {
877 kdWarning(30506) << "Unsupported lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl;
878 break;
879 }
880 }
881 }
882
883 // Add all AbiWord properties collected in the <FORMAT> element
884 props += textFormatToAbiProps(layoutOrigin.formatData.text,layout.formatData.text,force);
885
886 return props;
887}
888
889bool AbiWordWorker::doFullParagraph(const TQString& paraText, const LayoutData& layout,
890 const ValueListFormatData& paraFormatDataList)
891{
892 TQString style=layout.styleName;
893
894 const LayoutData& styleLayout=m_styleMap[style];
895
896 TQString props=layoutToCss(styleLayout,layout,false);
897
898 *m_streamOut << "<p";
899 if (!style.isEmpty())
900 {
901 *m_streamOut << " style=\"" << EscapeXmlText(style,true,true) << "\"";
902 }
903 if (!props.isEmpty())
904 {
905 // Find the last semi-comma
906 // Note: as in CSS2, semi-commas only separates instructions (like in PASCAL) and do not terminate them (like in C)
907 const int result=props.findRev(";");
908 if (result>=0)
909 {
910 // Remove the last semi-comma and the space thereafter
911 props.remove(result,2);
912 }
913
914 *m_streamOut << " props=\"" << props << "\"";
915 }
916 *m_streamOut << ">"; //Warning: No trailing white space or else it's in the text!!!
917
918 // Before processing the text, test if we have a page break
919 if (layout.pageBreakBefore)
920 {
921 // We have a page break before the paragraph
922 *m_streamOut << "<pbr/>";
923 }
924
925 processParagraphData(paraText, layout.formatData.text, paraFormatDataList);
926
927 // Before closing the paragraph, test if we have a page break
928 if (layout.pageBreakAfter)
929 {
930 // We have a page break after the paragraph
931 *m_streamOut << "<pbr/>";
932 }
933
934 *m_streamOut << "</p>\n";
935 return true;
936}
937
938bool AbiWordWorker::doFullDefineStyle(LayoutData& layout)
939{
940 //Register style in the style map
941 m_styleMap[layout.styleName]=layout;
942
943 *m_streamOut << "<s";
944
945 // TODO: cook the style name to the standard style names in AbiWord
946 *m_streamOut << " name=\"" << EscapeXmlText(layout.styleName,true,true) << "\"";
947 *m_streamOut << " followedby=\"" << EscapeXmlText(layout.styleFollowing,true,true) << "\"";
948
949 if ( (layout.counter.numbering == CounterData::NUM_CHAPTER)
950 && (layout.counter.depth<10) )
951 {
952 *m_streamOut << " level=\"";
953 *m_streamOut << TQString::number(layout.counter.depth+1,10);
954 *m_streamOut << "\"";
955 }
956
957 TQString abiprops=layoutToCss(layout,layout,true);
958
959 const int result=abiprops.findRev(";");
960 if (result>=0)
961 {
962 // Remove the last semi-comma and the space thereafter
963 abiprops.remove(result,2);
964 }
965
966 *m_streamOut << " props=\"" << abiprops << "\"";
967
968 *m_streamOut << "/>\n";
969
970 return true;
971}
972
973bool AbiWordWorker::doFullPaperFormat(const int format,
974 const double width, const double height, const int orientation)
975{
976 TQString outputText = "<pagesize ";
977
978 switch (format)
979 {
980 // ISO A formats
981 case PG_DIN_A0: // ISO A0
982 case PG_DIN_A1: // ISO A1
983 case PG_DIN_A2: // ISO A2
984 case PG_DIN_A3: // ISO A3
985 case PG_DIN_A4: // ISO A4
986 case PG_DIN_A5: // ISO A5
987 case PG_DIN_A6: // ISO A6
988 // ISO B formats
989 case PG_DIN_B0: // ISO B0
990 case PG_DIN_B1: // ISO B1
991 case PG_DIN_B2: // ISO B2
992 case PG_DIN_B3: // ISO B3
993 case PG_DIN_B4: // ISO B4
994 case PG_DIN_B5: // ISO B5
995 case PG_DIN_B6: // ISO B6
996 // American formats
997 case PG_US_LETTER: // US Letter
998 case PG_US_LEGAL: // US Legal
999 {
1000 TQString pagetype=KoPageFormat::formatString(KoFormat(format));
1001 outputText+="pagetype=\"";
1002 outputText+=pagetype;
1003
1004 TQString strWidth, strHeight, strUnits;
1005 KWEFUtil::GetNativePaperFormat(format, strWidth, strHeight, strUnits);
1006 outputText+="\" width=\"";
1007 outputText+=strWidth;
1008 outputText+="\" height=\"";
1009 outputText+=strHeight;
1010 outputText+="\" units=\"";
1011 outputText+=strUnits;
1012 outputText+="\" ";
1013 break;
1014 }
1015 case PG_US_EXECUTIVE: // US Executive (does not exists in AbiWord!)
1016 {
1017 // FIXME/TODO: AbiWord (CVS 2001-04-25) seems not to like custom formats, so avoid them for now!
1018#if 0
1019 outputText += "pagetype=\"Custom\" width=\"7.5\" height=\"10.0\" units=\"inch\" ";
1020#else
1021 // As replacement, use the slightly bigger "letter" format.
1022 outputText += "pagetype=\"Letter\" width=\"8.5\" height=\"11.0\" units=\"inch\" ";
1023#endif
1024 break;
1025 }
1026 // Other format not supported yet by AbiWord CVS 2001-04-25)
1027 case PG_DIN_A7: // ISO A7
1028 case PG_DIN_A8: // ISO A8
1029 case PG_DIN_A9: // ISO A9
1030 case PG_DIN_B10: // ISO B10
1031 // Other formats
1032 case PG_SCREEN: // Screen
1033 case PG_CUSTOM: // Custom
1034 default:
1035 {
1036 // FIXME/TODO: AbiWord (CVS 2001-04-25) seems not to like custom formats, so avoid them for now!
1037 if ((width<=1.0) || (height<=1.0) || true)
1038 {
1039 // Height or width is ridiculous, so assume A4 format
1040 outputText += "pagetype=\"A4\" width=\"21.0\" height=\"29.7\" units=\"cm\" ";
1041 }
1042 else
1043 { // We prefer to use inches, as to limit rounding errors (page size is in points!)
1044 outputText += TQString("pagetype=\"Custom\" width=\"%1\" height=\"%2\" units=\"inch\" ").arg(width/72.0).arg(height/72.0);
1045 }
1046 break;
1047 }
1048 }
1049
1050 outputText += "orientation=\"";
1051 if (1==orientation)
1052 {
1053 outputText += "landscape";
1054 }
1055 else
1056 {
1057 outputText += "portrait";
1058 }
1059 outputText += "\" ";
1060
1061 outputText += "page-scale=\"1.0\"/>\n"; // KWord has no page scale, so assume 100%
1062
1063 m_pagesize=outputText;
1064 return true;
1065}
1066
1067bool AbiWordWorker::doFullPaperBorders (const double top, const double left,
1068 const double bottom, const double right)
1069{
1070 m_paperBorderTop=top;
1071 m_paperBorderLeft=left;
1072 m_paperBorderBottom=bottom;
1073 m_paperBorderRight=right;
1074 return true;
1075}
1076
1078{
1079 if (!m_pagesize.isEmpty())
1080 {
1081 *m_streamOut << m_pagesize;
1082 }
1083 return true;
1084}
1085
1087{
1088 kdDebug(30506) << "AbiWordWorker::doOpenSpellCheckIgnoreList" << endl;
1089 m_inIgnoreWords=false; // reset
1090 return true;
1091}
1092
1094{
1095 kdDebug(30506) << "AbiWordWorker::doCloseSpellCheckIgnoreList" << endl;
1096 if (m_inIgnoreWords)
1097 *m_streamOut << "</ignorewords>\n";
1098 return true;
1099}
1100
1101bool AbiWordWorker::doFullSpellCheckIgnoreWord (const TQString& ignoreword)
1102{
1103 kdDebug(30506) << "AbiWordWorker::doFullSpellCheckIgnoreWord: " << ignoreword << endl;
1104 if (!m_inIgnoreWords)
1105 {
1106 *m_streamOut << "<ignorewords>\n";
1107 m_inIgnoreWords=true;
1108 }
1109 *m_streamOut << " <iw>" << ignoreword << "</iw>\n";
1110 return true;
1111}
1112
1113// Similar to TQDateTime::toString, but guaranteed *not* to be translated
1114TQString AbiWordWorker::transformToTextDate(const TQDateTime& dt)
1115{
1116 if (dt.isValid())
1117 {
1118 TQString result;
1119
1120 const TQDate date(dt.date());
1121
1122 const char* dayName[7] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
1123 const int dow = date.dayOfWeek() - 1;
1124 if ((dow<0) || (dow>6))
1125 result += "Mon"; // Unknown day, rename it Monday.
1126 else
1127 result += dayName[dow];
1128 result += ' ';
1129
1130 const char* monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1131 const int month = date.month() - 1;
1132 if ((month<0) || (month>11))
1133 result += "Jan"; // Unknown month, rename it January
1134 else
1135 result += monthName[month];
1136 result += ' ';
1137
1138 TQString temp;
1139
1140 temp = "00";
1141 temp += TQString::number(date.day(), 10);
1142 result += temp.right(2);
1143 result += ' ';
1144
1145 const TQTime time(dt.time());
1146
1147 temp = "00";
1148 temp += TQString::number(time.hour(), 10);
1149 result += temp.right(2);
1150 result += ':';
1151
1152 temp = "00";
1153 temp += TQString::number(time.minute(), 10);
1154 result += temp.right(2);
1155 result += ':';
1156
1157 temp = "00";
1158 temp += TQString::number(time.second(), 10);
1159 result += temp.right(2);
1160 result += ' ';
1161
1162 temp = "0000";
1163 temp += TQString::number(date.year(), 10);
1164 result += temp.right(4);
1165
1166 return result;
1167 }
1168 else
1169 {
1170 // Invalid, so give back 1970-01-01
1171 return "Thu Jan 01 00:00:00 1970";
1172 }
1173}
1174
1175bool AbiWordWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo)
1176{
1177 m_docInfo=docInfo;
1178
1179 *m_streamOut << "<metadata>\n";
1180
1181 // First all Dublin Core data
1182 *m_streamOut << "<m key=\"dc.format\">application/x-abiword</m>\n";
1183 if (!m_docInfo.title.isEmpty())
1184 {
1185 *m_streamOut << "<m key=\"dc.title\">" << escapeAbiWordText(m_docInfo.title) << "</m>\n";
1186 }
1187 if (!m_docInfo.abstract.isEmpty())
1188 {
1189 *m_streamOut << "<m key=\"dc.description\">" << escapeAbiWordText(m_docInfo.abstract) << "</m>\n";
1190 }
1191
1192 if ( !m_docInfo.keywords.isEmpty() )
1193 {
1194 *m_streamOut << "<m key=\"abiword.keywords\">" << escapeAbiWordText(m_docInfo.keywords) << "</m>\n";
1195 }
1196 if ( !m_docInfo.subject.isEmpty() )
1197 {
1198 *m_streamOut << "<m key=\"dc.subject\">" << escapeAbiWordText(m_docInfo.subject) << "</m>\n";
1199 }
1200
1201 // Say who we are (with the CVS revision number) in case we have a bug in our filter output!
1202 *m_streamOut << "<m key=\"abiword.generator\">KWord Export Filter";
1203
1204 TQString strVersion("$Revision: 508787 $");
1205 // Remove the dollar signs
1206 // (We don't want that the version number changes if the AbiWord file is itself put in a CVS storage.)
1207 *m_streamOut << strVersion.mid(10).remove('$');
1208
1209 *m_streamOut << "</m>\n";
1210
1211 TQDateTime now (TQDateTime::currentDateTime(TQt::UTC)); // current time in UTC
1212 *m_streamOut << "<m key=\"abiword.date_last_changed\">"
1213 << escapeAbiWordText(transformToTextDate(now))
1214 << "</m>\n";
1215
1216 *m_streamOut << "</metadata>\n";
1217
1218 return true;
1219}
1220
1221
1222// ==========================================================================================
1223
1224ABIWORDExport::ABIWORDExport(KoFilter */*parent*/, const char */*name*/, const TQStringList &) :
1225 KoFilter() {
1226}
1227
1228KoFilter::ConversionStatus ABIWORDExport::convert( const TQCString& from, const TQCString& to )
1229{
1230 if ( to != "application/x-abiword" || from != "application/x-kword" )
1231 {
1232 return KoFilter::NotImplemented;
1233 }
1234
1235 // We need KimageIO's help in AbiWordWorker::convertUnknownImage
1236 KImageIO::registerFormats();
1237
1238 AbiWordWorker* worker=new AbiWordWorker();
1239
1240 if (!worker)
1241 {
1242 kdError(30506) << "Cannot create Worker! Aborting!" << endl;
1243 return KoFilter::StupidError;
1244 }
1245
1246 KWEFKWordLeader* leader=new KWEFKWordLeader(worker);
1247
1248 if (!leader)
1249 {
1250 kdError(30506) << "Cannot create Worker! Aborting!" << endl;
1251 delete worker;
1252 return KoFilter::StupidError;
1253 }
1254
1255 KoFilter::ConversionStatus result=leader->convert(m_chain,from,to);
1256
1257 delete leader;
1258 delete worker;
1259
1260 return result;
1261}
@ NUM_CHAPTER
Numbered as a heading.
This is basically <FRAMESET> tag.
Base worker of the libexport system.
virtual bool doCloseSpellCheckIgnoreList(void)
like AbiWord's </ignorewords> and Kwords </SPELLCHECKIGNORELIST>
virtual bool doOpenSpellCheckIgnoreList(void)
like AbiWord's <ignorewords> and KWord's <SPELLCHECKIGNORELIST>
virtual bool doCloseHead(void)
Like HTML's </HEAD>.
virtual bool doOpenStyles(void)
Like HTML's <style>.
virtual bool doCloseStyles(void)
Like HTML's </style>.
Paragraph layout.
double lineSpacing
Space between lines in pt, if lineSpacingType==0.
TabulatorList tabulatorList
List of tabulators.
double indentFirst
indentation correction of first line (can be negative!)
double indentRight
right indentation (a negative value means invalid)
double indentLeft
left indentation (a negative value means invalid)
TQString alignment
left, right, center, justify
int lineSpacingType
0=custom, 10=one line, 15=one and half lines, 20= 2 lines
double marginTop
space before the paragraph (a negative value means invalid)
double marginBottom
space after the paragraph (a negative value means invalid)
TextFormatting is a container for data retrieved from the <FORMAT> tag and its subtags to be used in ...
bool underline
Any underline? (If true, we do not know if simple or double or what else).
bool strikeout
true if strikeout
bool missing
true if this TextFormatting object does not correspond to a real <FORMAT> element
TQString getHrefName(void) const
Reference of link (attribute "hrefName" of <LINK>).
TQString getLinkName(void) const
Name of link (attribute "linkName" of <LINK>).