1 /* -*- c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- */
2
3 /* AbiWord
4 * Copyright (C) 2001 AbiSource, Inc.
5 * Copyright (C) 2001 Dom Lachowicz
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 * 02110-1301 USA.
21 */
22
23 #include <string.h>
24 #include "ut_string.h"
25 #include "ut_bytebuf.h"
26 #include "ut_base64.h"
27 #include "pt_Types.h"
28 #include "ie_exp_Applix.h"
29 #include "pd_Document.h"
30 #include "pp_AttrProp.h"
31 #include "px_ChangeRecord.h"
32 #include "px_CR_Object.h"
33 #include "px_CR_Span.h"
34 #include "px_CR_Strux.h"
35 #include "ut_wctomb.h"
36 #include "xap_EncodingManager.h"
37 #include "ut_string_class.h"
38 #include "ie_impexp_Applix.h"
39
40 /**
41 * TODO:
42 *
43 * All that this export filter handles now is plain (unformatted) text
44 * It shouldn't be too hard to add paragraph and text formatting though
45 * (rather trivial, actually). Also needed is a modified outputdata
46 * method to support applix-special charaters (signified by ^blah)
47 * Image support would also be nice, as would better handling of styles
48 *
49 * This would make a great POW. When you do the POW, please remove this TODO
50 */
51
52 #define APPLIX_LINE 80 // Applix only allows 80 chars per line
53
54 /*****************************************************************/
55 /*****************************************************************/
56
IE_Exp_Applix_Sniffer(const char * _name)57 IE_Exp_Applix_Sniffer::IE_Exp_Applix_Sniffer (const char * _name) :
58 IE_ExpSniffer(_name)
59 {
60 //
61 }
62
supportsMIME(const char * szMIME)63 UT_Confidence_t IE_Exp_Applix_Sniffer::supportsMIME (const char * szMIME)
64 {
65 if (strcmp (szMIME, IE_MIMETYPE_Applix) == 0)
66 {
67 return UT_CONFIDENCE_GOOD;
68 }
69 return UT_CONFIDENCE_ZILCH;
70 }
71
recognizeSuffix(const char * szSuffix)72 bool IE_Exp_Applix_Sniffer::recognizeSuffix(const char * szSuffix)
73 {
74 return (g_ascii_strcasecmp(szSuffix,".aw") == 0);
75 }
76
constructExporter(PD_Document * pDocument,IE_Exp ** ppie)77 UT_Error IE_Exp_Applix_Sniffer::constructExporter(PD_Document * pDocument,
78 IE_Exp ** ppie)
79 {
80 IE_Exp_Applix * p = new IE_Exp_Applix(pDocument);
81 *ppie = p;
82 return UT_OK;
83 }
84
getDlgLabels(const char ** pszDesc,const char ** pszSuffixList,IEFileType * ft)85 bool IE_Exp_Applix_Sniffer::getDlgLabels(const char ** pszDesc,
86 const char ** pszSuffixList,
87 IEFileType * ft)
88 {
89 *pszDesc = "Applix Words (.aw)";
90 *pszSuffixList = "*.aw";
91 *ft = getFileType();
92 return true;
93 }
94
95 /*****************************************************************/
96 /*****************************************************************/
97
98 //////////////////////////////////////////////////////////////////
99 // a private listener class to help us translate the document
100 // into a Applix stream. code is at the bottom of this file.
101 //////////////////////////////////////////////////////////////////
102
103 class s_Applix_Listener : public PL_Listener
104 {
105 public:
106 s_Applix_Listener(PD_Document * pDocument,
107 IE_Exp_Applix * pie);
108 virtual ~s_Applix_Listener();
109
110 virtual bool populate(fl_ContainerLayout* sfh,
111 const PX_ChangeRecord * pcr);
112
113 virtual bool populateStrux(pf_Frag_Strux* sdh,
114 const PX_ChangeRecord * pcr,
115 fl_ContainerLayout* * psfh);
116
117 virtual bool change(fl_ContainerLayout* sfh,
118 const PX_ChangeRecord * pcr);
119
120 virtual bool insertStrux(fl_ContainerLayout* sfh,
121 const PX_ChangeRecord * pcr,
122 pf_Frag_Strux* sdh,
123 PL_ListenerId lid,
124 void (* pfnBindHandles)(pf_Frag_Strux* sdhNew,
125 PL_ListenerId lid,
126 fl_ContainerLayout* sfhNew));
127
128 virtual bool signal(UT_uint32 iSignal);
129
130 protected:
131 void _closeBlock(void);
132 void _outputData(const UT_UCSChar * p, UT_uint32 length);
133 void _write (const char *);
134 void _write (const char * src, int len);
135 void _writeln (const char *);
136 void _openTag (const char *);
137 void _closeTag (void);
138 void _flush (void);
139
140 void _openParagraph (PT_AttrPropIndex api);
141 void _openSpan (PT_AttrPropIndex api);
142 void _closeSpan (PT_AttrPropIndex api);
143
144 void _writePreamble (void);
145 void _writePostamble (void);
146 void _resetBuffer (void);
147
148 PD_Document * m_pDocument;
149 IE_Exp_Applix * m_pie;
150 bool m_bInBlock;
151 char m_buf[APPLIX_LINE + 1]; // not evil, applix does 80 chars per line
152 int m_pos;
153 bool m_bInSpan;
154 };
155
156 /*****************************************************************/
157 /*****************************************************************/
158
IE_Exp_Applix(PD_Document * pDocument)159 IE_Exp_Applix::IE_Exp_Applix(PD_Document * pDocument)
160 : IE_Exp(pDocument)
161 {
162 m_error = 0;
163 m_pListener = NULL;
164 }
165
~IE_Exp_Applix()166 IE_Exp_Applix::~IE_Exp_Applix()
167 {
168 }
169
170 /*****************************************************************/
171 /*****************************************************************/
172
_writeDocument(void)173 UT_Error IE_Exp_Applix::_writeDocument(void)
174 {
175 m_pListener = new s_Applix_Listener(getDoc(),this);
176 if (!m_pListener)
177 return UT_IE_NOMEMORY;
178
179 if (getDocRange())
180 getDoc()->tellListenerSubset(static_cast<PL_Listener *>(m_pListener),getDocRange());
181 else
182 getDoc()->tellListener(static_cast<PL_Listener *>(m_pListener));
183 DELETEP(m_pListener);
184
185 return ((m_error) ? UT_IE_COULDNOTWRITE : UT_OK);
186 }
187
188 /*****************************************************************/
189 /*****************************************************************/
190
_outputData(const UT_UCSChar * data,UT_uint32 length)191 void s_Applix_Listener::_outputData(const UT_UCSChar * data, UT_uint32 length)
192 {
193 const UT_UCSChar * pData;
194 UT_String sBuf;
195
196 UT_ASSERT(sizeof(char) == sizeof(UT_Byte));
197
198 if (!m_bInBlock)
199 {
200 return;
201 }
202
203 sBuf.reserve(length);
204 for (pData=data; (pData<data+length); /**/)
205 {
206 switch (*pData)
207 {
208
209 default:
210 if (*pData > 0x007f)
211 {
212 /*
213 Try to convert to native encoding and if
214 character fits into byte, output raw byte. This
215 is somewhat essential for single-byte non-latin
216 languages like russian or polish - since
217 tools like grep and sed can be used then for
218 these files without any problem.
219 Networks and mail transfers are 8bit clean
220 these days. - VH
221 */
222 UT_UCSChar c = XAP_EncodingManager::get_instance()->try_UToNative(*pData);
223 if (c==0 || c>255)
224 {
225 sBuf += UT_String_sprintf("&#x%x;",*pData++);
226 }
227 else
228 {
229 sBuf += static_cast<char>(c);
230 pData++;
231 }
232 }
233 else
234 {
235 sBuf += static_cast<char>(*pData++);
236 }
237 break;
238 }
239 }
240
241 _write(sBuf.c_str(),sBuf.size());
242 }
243
s_Applix_Listener(PD_Document * pDocument,IE_Exp_Applix * pie)244 s_Applix_Listener::s_Applix_Listener(PD_Document * pDocument,
245 IE_Exp_Applix * pie)
246 {
247 m_pDocument = pDocument;
248 m_pie = pie;
249
250 // when we are going to the clipboard, we should implicitly
251 // assume that we are starting in the middle of a block.
252 // when going to a file we should not.
253 m_bInBlock = false;
254 m_bInSpan = false;
255
256 _resetBuffer (); // initialize the buffer
257 _writePreamble ();
258 }
259
~s_Applix_Listener()260 s_Applix_Listener::~s_Applix_Listener()
261 {
262 _closeBlock();
263 _writePostamble ();
264 _flush ();
265 }
266
populate(fl_ContainerLayout *,const PX_ChangeRecord * pcr)267 bool s_Applix_Listener::populate(fl_ContainerLayout* /*sfh*/,
268 const PX_ChangeRecord * pcr)
269 {
270 switch (pcr->getType())
271 {
272 case PX_ChangeRecord::PXT_InsertSpan:
273 {
274 const PX_ChangeRecord_Span * pcrs = static_cast<const PX_ChangeRecord_Span *> (pcr);
275
276 PT_AttrPropIndex api = pcr->getIndexAP();
277 _openSpan(api);
278
279 PT_BufIndex bi = pcrs->getBufIndex();
280 _outputData(m_pDocument->getPointer(bi),pcrs->getLength());
281
282 _closeSpan(api);
283
284 return true;
285 }
286
287 case PX_ChangeRecord::PXT_InsertObject:
288 {
289 #if 0
290 // TODO decide how to indicate objects in Applix output.
291
292 const PX_ChangeRecord_Object * pcro = static_cast<const PX_ChangeRecord_Object *> (pcr);
293 PT_AttrPropIndex api = pcr->getIndexAP();
294 switch (pcro->getObjectType())
295 {
296 case PTO_Image:
297 return true;
298
299 case PTO_Field:
300 return true;
301
302 // todo: support these
303 case PTO_Hyperlink:
304 case PTO_Bookmark:
305 return true;
306
307 default:
308 UT_ASSERT(0);
309 return false;
310 }
311 #else
312 return true;
313 #endif
314 }
315
316 case PX_ChangeRecord::PXT_InsertFmtMark:
317 return true;
318
319 default:
320 UT_ASSERT(0);
321 return false;
322 }
323 }
324
populateStrux(pf_Frag_Strux *,const PX_ChangeRecord * pcr,fl_ContainerLayout ** psfh)325 bool s_Applix_Listener::populateStrux(pf_Frag_Strux* /*sdh*/,
326 const PX_ChangeRecord * pcr,
327 fl_ContainerLayout* * psfh)
328 {
329 UT_ASSERT(pcr->getType() == PX_ChangeRecord::PXT_InsertStrux);
330 const PX_ChangeRecord_Strux * pcrx = static_cast<const PX_ChangeRecord_Strux *> (pcr);
331 *psfh = 0; // we don't need it.
332
333 switch (pcrx->getStruxType())
334 {
335 case PTX_SectionEndnote:
336 case PTX_SectionHdrFtr:
337 case PTX_Section:
338 {
339 return true;
340 }
341
342 case PTX_Block:
343 {
344 _closeBlock();
345 _openParagraph (pcr->getIndexAP());
346 m_bInBlock = true;
347 return true;
348 }
349
350 case PTX_SectionTable:
351 case PTX_EndTable:
352 case PTX_SectionCell:
353 case PTX_EndCell:
354 return true;
355
356 case PTX_EndFrame:
357 case PTX_EndMarginnote:
358 case PTX_EndFootnote:
359 case PTX_SectionFrame:
360 case PTX_SectionMarginnote:
361 case PTX_SectionFootnote:
362 case PTX_EndEndnote:
363 default:
364 UT_ASSERT_NOT_REACHED();
365 return false;
366 }
367 }
368
369 // this method has been very carefully hand-crafted
370 // to produce 80 chars per line output. don't mess with it
371 // -Dom
_write(const char * src,int len)372 void s_Applix_Listener::_write (const char * src, int len)
373 {
374 if (!src || !len) // short-circuit
375 return;
376
377 for (int i = 0; i < len; i++)
378 {
379 if (src[i] == '\n')
380 {
381 _flush (); // flush and reset the buffer
382 m_pie->write ("\n", 1); // write the newline
383 }
384 else // not a newline
385 {
386 if (m_pos < (APPLIX_LINE - 2)) // plenty of room to append
387 {
388 m_buf[m_pos++] = src[i];
389 }
390 else // (m_pos == (APPLIX_LINE - 1))
391 {
392 if (i < (len - 1)) // more chars to write
393 {
394 m_buf[m_pos++] = src[i]; // append the character
395 m_buf[m_pos++] = '\\'; // append a trailing '\'
396 _flush (); // flush the buffer
397 m_pie->write ("\n", 1); // append a newline
398 m_buf[m_pos++] = ' '; // append a space
399 }
400 else
401 {
402 m_buf[m_pos++] = src[i];
403 }
404 }
405 }
406 }
407 }
408
_flush(void)409 void s_Applix_Listener::_flush (void)
410 {
411 // flush the internal buffers
412 m_pie->write (m_buf, m_pos); // write out the contents of the buffer
413 _resetBuffer (); // reset the buffer and count
414 }
415
_write(const char * src)416 void s_Applix_Listener::_write (const char * src)
417 {
418 if (src)
419 _write (src, strlen (src));
420 }
421
_writeln(const char * src)422 void s_Applix_Listener::_writeln (const char * src)
423 {
424 _write (src);
425 _write ("\n");
426 }
427
_openTag(const char * tag)428 void s_Applix_Listener::_openTag(const char * tag)
429 {
430 _write ("<");
431 _write (tag);
432 _write (" ");
433 }
434
_closeTag(void)435 void s_Applix_Listener::_closeTag (void)
436 {
437 _writeln (">");
438 }
439
change(fl_ContainerLayout *,const PX_ChangeRecord *)440 bool s_Applix_Listener::change(fl_ContainerLayout* /*sfh*/,
441 const PX_ChangeRecord * /*pcr*/)
442 {
443 UT_ASSERT(0); // this function is not used.
444 return false;
445 }
446
insertStrux(fl_ContainerLayout *,const PX_ChangeRecord *,pf_Frag_Strux *,PL_ListenerId,void (*)(pf_Frag_Strux *,PL_ListenerId,fl_ContainerLayout *))447 bool s_Applix_Listener::insertStrux(fl_ContainerLayout* /*sfh*/,
448 const PX_ChangeRecord * /*pcr*/,
449 pf_Frag_Strux* /*sdh*/,
450 PL_ListenerId /* lid */,
451 void (* /*pfnBindHandles*/)(pf_Frag_Strux* /* sdhNew */,
452 PL_ListenerId /* lid */,
453 fl_ContainerLayout* /* sfhNew */))
454 {
455 UT_ASSERT(0); // this function is not used.
456 return false;
457 }
458
signal(UT_uint32)459 bool s_Applix_Listener::signal(UT_uint32 /* iSignal */)
460 {
461 UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
462 return false;
463 }
464
_writePreamble(void)465 void s_Applix_Listener::_writePreamble(void)
466 {
467 // global stuff
468 _writeln ("*BEGIN WORDS VERSION=430/320 ENCODING=7BIT");
469 _writeln ("<Applix Words>");
470 _writeln ("<Globals levelIndent:0 hyphMethod:0 headerMargin:500 footerMargin:394 changeBar Pos:0>");
471
472 // styles - TODO: auto-generate these based on our styles
473 _writeln ("<start_styles>");
474 _write ("<style \"Normal\" nextStyle \"Normal\" no-pageBreak no-keepWith no-block justifyLeft "
475 "indentToLevel spellcheck firstIndent:0 leftIndent:0 rightIndent:0 lineSpacing:0 ");
476 _write ("preParaSpacing:0 postParaSpacing:0 level:0 hyphZone:0 hyphMinFrag:0 no-bold "
477 "no-italic no-strikethru no-hidden no-caps no-underline hyphenate color:\"Black\" ");
478 _write ("face:\"Palatino\" size:12 position:0 tag:\"\" lB:0:0:\"\" rB:0:0:\"\" tB:0:0:\"\" "
479 "bB:0:0:\"\" hB:0:0:\"\" vB:0:0:\"\" shading:18:\"\":\"\":\"\" horizontalMargin:0 ");
480 _writeln ("verticalMargin:0 dropShadow:0 localTabs lT:394 xposColumnRelative xpos:0 "
481 "yposParaRelative ypos:1 leftFrameMargin:126 rightFrameMargin:126 topFrameMargin:0 "
482 "bottomFrameMargin:0 >");
483 _writeln ("<style \"footer\" parent \"Normal\" nextStyle \"footer\" indentToLevel level:0 "
484 "color:\"Black\" localTabs cT:3347 rT:6299 >");
485 _writeln ("<style \"header\" parent \"Normal\" nextStyle \"header\" indentToLevel level:0 "
486 "color:\"Black\" localTabs cT:3347 rT:6299 >");
487 _writeln ("<style \"heading 1\" parent \"Normal\" nextStyle \"heading_1\" indentToLevel "
488 "preParaSpacing:167 level:0 bold >");
489 _writeln ("<style \"heading 2\" parent \"heading 1\" nextStyle \"heading_2\" indentToLevel "
490 "level:0 size:14 >");
491 _writeln ("<style \"heading 3\" parent \"Normal\" nextStyle \"Normal indent\" indentToLevel "
492 "level:0 bold >");
493 _writeln ("<style \"Normal indent\" parent \"Normal\" nextStyle \"Normal indent\" "
494 "indentToLevel firstIndent:394 leftIndent:394 level:0 color:\"Black\" >");
495 _writeln ("<style \"heading_1\" parent \"Normal\" >");
496
497 // colors - these are usually localized to the user's environment
498 // eg. - Schwarz, Blau, Wiess, Gelb, etc... we don't need to do that
499 _writeln ("<color \"Black0\":0:0:0:255>");
500 _writeln ("<color \"Black\":0:0:0:255>");
501 _writeln ("<color \"Blue\":255:255:0:0>");
502 _writeln ("<color \"Cyan\":255:0:0:0>");
503 _writeln ("<color \"Green\":255:0:255:0>");
504 _writeln ("<color \"Magenta\":0:255:0:0>");
505 _writeln ("<color \"Red\":0:255:255:0>");
506 _writeln ("<color \"Yellow\":0:0:255:0>");
507 _writeln ("<color \"White\":0:0:0:0>");
508 _writeln ("<color \"Dark Blue\":127:127:0:128>");
509 _writeln ("<color \"Dark Cyan\":127:0:0:128>");
510 _writeln ("<color \"Dark Green\":127:0:127:128>");
511 _writeln ("<color \"Dark Magenta\":0:127:0:128>");
512 _writeln ("<color \"Dark Red\":0:127:127:128>");
513 _writeln ("<color \"Dark Yellow\":0:0:127:128>");
514 _writeln ("<color \"Dark Gray\":0:0:0:128>");
515 _writeln ("<color \"Light Gray\":0:0:0:63>");
516 _writeln ("<color \"HtmlLinkDefault@\":255:255:0:0>");
517
518 // end styles
519 _writeln ("<end_styles>");
520
521 // begin the document
522 _writeln ("<start_flow>");
523 _writeln ("<WP400 \"This file must be filtered to be read in WP 3.11\">");
524 }
525
_writePostamble(void)526 void s_Applix_Listener::_writePostamble(void)
527 {
528 // end of document
529 _writeln ("<end_flow>");
530
531 // this might have to get more interesting
532 _writeln ("<start_vars>");
533 _writeln ("<end_vars>");
534
535 _writeln ("<end_document>");
536 _writeln ("*END WORDS");
537 }
538
_resetBuffer(void)539 void s_Applix_Listener::_resetBuffer (void)
540 {
541 memset (m_buf, 0, sizeof (m_buf));
542 m_pos = 0;
543 }
544
_openSpan(PT_AttrPropIndex)545 void s_Applix_Listener::_openSpan(PT_AttrPropIndex /* always ignored */)
546 {
547 _openTag ("T");
548 _write ("\""); // begin text
549 m_bInSpan = true;
550 }
551
_openParagraph(PT_AttrPropIndex)552 void s_Applix_Listener::_openParagraph (PT_AttrPropIndex /*api*/)
553 {
554 // TODO: this should get more complex, but this is a 1st rev
555 _openTag ("P");
556 _closeTag ();
557 }
558
_closeSpan(PT_AttrPropIndex api)559 void s_Applix_Listener::_closeSpan(PT_AttrPropIndex api)
560 {
561 _write ("\""); // end text
562
563 if (!api)
564 _closeTag(); // just close the "<T" tag
565 else
566 {
567 // TODO: this should get more interesting if api != 0
568 _closeTag ();
569 }
570
571 m_bInSpan = false;
572 }
573
_closeBlock(void)574 void s_Applix_Listener::_closeBlock(void)
575 {
576 if (m_bInBlock)
577 m_bInBlock = false;
578 }
579