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