1 /* AbiWord
2  * Copyright (C) 2001 AbiSource, Inc.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program 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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "ut_string.h"
24 #include "ut_bytebuf.h"
25 #include "ut_base64.h"
26 #include "ut_units.h"
27 #include "ut_wctomb.h"
28 #include "pt_Types.h"
29 #include "ie_exp_HRText.h"
30 #include "pd_Document.h"
31 #include "pp_AttrProp.h"
32 #include "px_ChangeRecord.h"
33 #include "px_CR_Object.h"
34 #include "px_CR_Span.h"
35 #include "px_CR_Strux.h"
36 #include "xap_EncodingManager.h"
37 #include "ut_debugmsg.h"
38 #include "ut_string_class.h"
39 #include "ut_hash.h"
40 #include "xap_Module.h"
41 
42 #ifdef ABI_PLUGIN_BUILTIN
43 #define abi_plugin_register abipgn_hrtext_register
44 #define abi_plugin_unregister abipgn_hrtext_unregister
45 #define abi_plugin_supports_version abipgn_hrtext_supports_version
46 // dll exports break static linking
47 #define ABI_BUILTIN_FAR_CALL extern "C"
48 #else
49 #define ABI_BUILTIN_FAR_CALL ABI_FAR_CALL
50 ABI_PLUGIN_DECLARE("HRText")
51 #endif
52 
53 // our delimiters
54 #define BOLD_DELIM           "*"
55 #define ITALIC_DELIM         "/"
56 #define UNDERLINE_DELIM      "_"
57 #define UNDERLINE_DELIM_CHAR '_'
58 #define LIST_DELIM           "* "
59 #define BLOCK_DELIM          "| "
60 #define PLAIN_DELIM          "| "
61 #define SUPERSCRIPT_DELIM    "^"
62 #define SUBSCRIPT_DELIM      "_"
63 
64 /*****************************************************************/
65 /*****************************************************************/
66 
67 // completely generic code to allow this to be a plugin
68 
69 // we use a reference-counted sniffer
70 static IE_Exp_HRText_Sniffer * m_sniffer = 0;
71 
72 ABI_BUILTIN_FAR_CALL
abi_plugin_register(XAP_ModuleInfo * mi)73 int abi_plugin_register (XAP_ModuleInfo * mi)
74 {
75 
76 	if (!m_sniffer)
77 	{
78 		m_sniffer = new IE_Exp_HRText_Sniffer ();
79 	}
80 
81 	mi->name = "HRText Exporter";
82 	mi->desc = "Export HRText Documents";
83 	mi->version = ABI_VERSION_STRING;
84 	mi->author = "Abi the Ant";
85 	mi->usage = "No Usage";
86 
87 	IE_Exp::registerExporter (m_sniffer);
88 	return 1;
89 }
90 
91 ABI_BUILTIN_FAR_CALL
abi_plugin_unregister(XAP_ModuleInfo * mi)92 int abi_plugin_unregister (XAP_ModuleInfo * mi)
93 {
94 	mi->name = 0;
95 	mi->desc = 0;
96 	mi->version = 0;
97 	mi->author = 0;
98 	mi->usage = 0;
99 
100 	UT_ASSERT (m_sniffer);
101 
102 	IE_Exp::unregisterExporter (m_sniffer);
103 	delete m_sniffer;
104 	m_sniffer = 0;
105 
106 	return 1;
107 }
108 
109 ABI_BUILTIN_FAR_CALL
abi_plugin_supports_version(UT_uint32,UT_uint32,UT_uint32)110 int abi_plugin_supports_version (UT_uint32 /*major*/, UT_uint32 /*minor*/,
111 								 UT_uint32 /*release*/)
112 {
113   return 1;
114 }
115 
116 /*****************************************************************/
117 /*****************************************************************/
118 
IE_Exp_HRText_Sniffer()119 IE_Exp_HRText_Sniffer::IE_Exp_HRText_Sniffer () :
120   IE_ExpSniffer("AbiHRText::Text/human-readable (NWS)")
121 {
122   //
123 }
124 
recognizeSuffix(const char * szSuffix)125 bool IE_Exp_HRText_Sniffer::recognizeSuffix(const char * szSuffix)
126 {
127 	return (!g_ascii_strcasecmp(szSuffix,".nws"));
128 }
129 
constructExporter(PD_Document * pDocument,IE_Exp ** ppie)130 UT_Error IE_Exp_HRText_Sniffer::constructExporter(PD_Document * pDocument,
131 												  IE_Exp ** ppie)
132 {
133 	IE_Exp_HRText * p = new IE_Exp_HRText(pDocument);
134 	*ppie = p;
135 	return UT_OK;
136 }
137 
getDlgLabels(const char ** pszDesc,const char ** pszSuffixList,IEFileType * ft)138 bool IE_Exp_HRText_Sniffer::getDlgLabels(const char ** pszDesc,
139 										 const char ** pszSuffixList,
140 										 IEFileType * ft)
141 {
142 	*pszDesc = "Newsgroup Formatted Text (.nws)";
143 	*pszSuffixList = "*.nws";
144 	*ft = getFileType();
145 	return true;
146 }
147 
148 /*****************************************************************/
149 /*****************************************************************/
150 
IE_Exp_HRText(PD_Document * pDocument)151 IE_Exp_HRText::IE_Exp_HRText(PD_Document * pDocument)
152 	: IE_Exp(pDocument), m_pListener(0)
153 {
154   m_error = UT_OK;
155 }
156 
~IE_Exp_HRText()157 IE_Exp_HRText::~IE_Exp_HRText()
158 {
159 }
160 
161 /*****************************************************************/
162 /*****************************************************************/
163 
164 #define BT_NORMAL		1
165 #define BT_HEADING1		2
166 #define BT_HEADING2		3
167 #define BT_HEADING3		4
168 #define BT_BLOCKTEXT	5
169 #define BT_PLAINTEXT	6
170 #define BT_NUMBEREDLIST	7
171 #define BT_BULLETLIST	8
172 
173 class s_HRText_Listener : public PL_Listener
174 {
175 public:
176 	s_HRText_Listener(PD_Document * pDocument,
177 						IE_Exp_HRText * pie);
178 	virtual ~s_HRText_Listener();
179 
180 	virtual bool		populate(fl_ContainerLayout* sfh,
181 								 const PX_ChangeRecord * pcr);
182 
183 	virtual bool		populateStrux(pf_Frag_Strux* sdh,
184 									  const PX_ChangeRecord * pcr,
185 									  fl_ContainerLayout* * psfh);
186 
187 	virtual bool		change(fl_ContainerLayout* sfh,
188 							   const PX_ChangeRecord * pcr);
189 
190 	virtual bool		insertStrux(fl_ContainerLayout* sfh,
191 									const PX_ChangeRecord * pcr,
192 									pf_Frag_Strux* sdh,
193 									PL_ListenerId lid,
194 									void (* pfnBindHandles)(pf_Frag_Strux* sdhNew,
195 															PL_ListenerId lid,
196 															fl_ContainerLayout* sfhNew));
197 
198 	virtual bool		signal(UT_uint32 iSignal);
199 
200 protected:
201 	void				_closeSection(void);
202 	void				_closeTag(void);
203 	void				_closeSpan(void);
204 	void				_openTag(PT_AttrPropIndex api);
205 	void				_openSection(PT_AttrPropIndex api);
206 	void				_openSpan(PT_AttrPropIndex api);
207 	void				_outputData(const UT_UCSChar * p, UT_uint32 length);
208 	void				_handleDataItems(void);
209 	void				_convertFontSize(char* szDest, const char* pszFontSize);
210 	void				_convertColor(char* szDest, const char* pszColor);
211 
212 	PD_Document *		m_pDocument;
213 	IE_Exp_HRText *		m_pie;
214 	bool				m_bInSection;
215 	bool				m_bInBlock;
216 	bool				m_bInSpan;
217 	bool				m_bNextIsSpace;
218 	bool				m_bInList;
219 	const PP_AttrProp*	m_pAP_Span;
220 
221 	// Need to look up proper type, and place to stick #defines...
222 
223 	char			m_iDecoration;
224 	UT_uint16		m_iBlockType;	// BT_*
225 	UT_uint16		m_iListDepth;	// 0 corresponds to not in a list
226 	UT_Wctomb		m_wctomb;
227 
228 	UT_StringPtrMap	*	m_pList;
229 };
230 
_closeSection(void)231 void s_HRText_Listener::_closeSection(void)
232 {
233 	if (!m_bInSection)
234 	{
235 		return;
236 	}
237 
238 	m_bInSection = false;
239 	return;
240 }
241 
_closeTag(void)242 void s_HRText_Listener::_closeTag(void)
243 {
244 	if (!m_bInBlock)
245 	{
246 		return;
247 	}
248 
249 #ifndef WIN32
250 	m_pie->write("\n\n");
251 #else
252 	m_pie->write("\r\n\r\n");
253 #endif
254 
255 	m_bInBlock = false;
256 	return;
257 }
258 
_openTag(PT_AttrPropIndex api)259 void s_HRText_Listener::_openTag(PT_AttrPropIndex api)
260 {
261 	if (!m_bInSection)
262 	{
263 		return;
264 	}
265 
266 	const PP_AttrProp * pAP = NULL;
267 	bool bHaveProp = m_pDocument->getAttrProp(api,&pAP);
268 	UT_uint16 * piVal;
269 
270 	if (bHaveProp && pAP)
271 	{
272 		const gchar * szValue;
273 		//const gchar * szLevel;
274 		const gchar * szListID;
275 		const gchar * szProps;
276 
277 		if (
278 		   (pAP->getAttribute(static_cast<const gchar*>(PT_STYLE_ATTRIBUTE_NAME), szValue))
279 		   )
280 		{
281 			if(pAP->getAttribute("listid", szListID) &&
282 			   0 != strcmp(szListID, "0"))
283 			{
284 				// we're in a list
285 				// todo: maybe check the list level and insert tabs here?
286 				if(pAP->getProperty("list-style",szProps) &&
287 				0 == strcmp(szProps, "Numbered List"))
288 				{
289 					// it's a numeric list, have we seen it before?
290 					if(!m_pList->pick(static_cast<const char *>(szListID)))
291 					{
292 						//todo: can you set a list number start-value in abiword?
293 						piVal = new UT_uint16(1);
294 						m_pList->insert(static_cast<const char *>(szListID),static_cast<void *>(piVal));
295 					}
296 					UT_uint16 * pTemp = const_cast<UT_uint16 *>(static_cast<const UT_uint16 *>(m_pList->pick(static_cast<const char *>(szListID))));
297 					m_pie->write(UT_String_sprintf("%d", *pTemp).c_str());
298 					*pTemp = *pTemp + 1;
299 				}
300 				else
301 				{
302 					// assume it's a bullet list
303 					m_pie->write(LIST_DELIM);
304 				}
305 			}
306 			else
307 			{
308 				if(0 == strcmp(szValue, "Block Text"))
309 				{
310 					// <p style="Block Text"> ...
311 
312 					m_iBlockType = BT_BLOCKTEXT;
313 					m_pie->write(BLOCK_DELIM);
314 				}
315 				else if(0 == strcmp(szValue, "Plain Text"))
316 				{
317 					// <p style="Plain Text"> ...
318 
319 					m_iBlockType = BT_PLAINTEXT;
320 					m_pie->write(PLAIN_DELIM);
321 				}
322 			}
323 		}
324 	}
325 	m_bInBlock = true;
326 }
327 
_openSection(PT_AttrPropIndex)328 void s_HRText_Listener::_openSection(PT_AttrPropIndex /* api*/)
329 {
330 #ifndef WIN32
331 	m_pie->write("\n");
332 #else
333 	m_pie->write("\r\n");
334 #endif
335 }
336 
_openSpan(PT_AttrPropIndex api)337 void s_HRText_Listener::_openSpan(PT_AttrPropIndex api)
338 {
339 	if (!m_bInBlock)
340 	{
341 		return;
342 	}
343 
344 	const PP_AttrProp * pAP = NULL;
345 	bool bHaveProp = m_pDocument->getAttrProp(api,&pAP);
346 
347 	if (bHaveProp && pAP)
348 	{
349 		const gchar * szValue;
350 
351 		if (
352 			(pAP->getProperty("font-weight", szValue))
353 			&& !strcmp(szValue, "bold")
354 			)
355 		{
356 			m_pie->write(BOLD_DELIM);
357 		}
358 
359 		if (
360 			(pAP->getProperty("font-style", szValue))
361 			&& !strcmp(szValue, "italic")
362 			)
363 		{
364 			m_pie->write(ITALIC_DELIM);
365 		}
366 
367 
368 		if (
369 			(pAP->getProperty("text-decoration", szValue))
370 			)
371 		{
372 		    	const gchar* pszDecor = szValue;
373 
374 		    	gchar* p;
375 		    	if (!(p = g_strdup(pszDecor)))
376 		    	{
377 				  // TODO outofmem
378 		    	}
379 
380 		    	UT_ASSERT(p || !pszDecor);
381 		    	gchar*       q = strtok(p, " ");
382 
383 		    	while (q)
384 		    	{
385 				  if (0 == strcmp(q, "underline"))
386 				  {
387 					  m_iDecoration = UNDERLINE_DELIM_CHAR;
388 					  m_pie->write(UNDERLINE_DELIM);
389 				  }
390 
391 				  q = strtok(NULL, " ");
392 		    }
393 
394 		    FREEP(p);
395 		}
396 
397 		if (pAP->getProperty("text-position", szValue))
398 		{
399 			if (!strcmp("superscript", szValue))
400 			{
401 				m_pie->write(SUPERSCRIPT_DELIM);
402 			}
403 			else if (!strcmp("subscript", szValue))
404 			{
405 				m_pie->write(SUBSCRIPT_DELIM);
406 			}
407 		}
408 
409 		m_bInSpan = true;
410 		m_pAP_Span = pAP;
411 	}
412 }
413 
_closeSpan(void)414 void s_HRText_Listener::_closeSpan(void)
415 {
416 	if (!m_bInSpan)
417 		return;
418 
419 	const PP_AttrProp * pAP = m_pAP_Span;
420 
421 	if (pAP)
422 	{
423 
424 		const gchar * szValue;
425 
426 		if (
427 			(pAP->getProperty("text-decoration", szValue))
428 			&& strcmp(szValue, "none")
429 			)
430 		{
431 			if (m_iDecoration)
432 		  		m_pie->write(&m_iDecoration, 1);
433 		}
434 
435 		if (
436 			(pAP->getProperty("font-style", szValue))
437 			&& !strcmp(szValue, "italic")
438 			)
439 		{
440 		  m_pie->write(ITALIC_DELIM);
441 		}
442 
443 		if (
444 			(pAP->getProperty("font-weight", szValue))
445 			&& !strcmp(szValue, "bold")
446 			)
447 		{
448 		  m_pie->write(BOLD_DELIM);
449 		}
450 
451 		m_pAP_Span = NULL;
452 	}
453 
454 	m_bInSpan = false;
455 	return;
456 }
457 
_outputData(const UT_UCSChar * data,UT_uint32 length)458 void s_HRText_Listener::_outputData(const UT_UCSChar * data, UT_uint32 length)
459 {
460 	UT_String sBuf;
461 	const UT_UCSChar * pData;
462 
463 	int mbLen;
464 	char pC[MB_LEN_MAX];
465 
466 	UT_ASSERT(sizeof(UT_Byte) == sizeof(char));
467 
468 	sBuf.reserve(length);
469 	for (pData=data; (pData<data+length); /**/)
470 	{
471 		if(!m_wctomb.wctomb(pC,mbLen,*pData))
472 		{
473 		    mbLen=1;
474 		    pC[0]='?';
475 		    m_wctomb.initialize();
476 		}
477 		pData++;
478 		if (mbLen>1)
479 		{
480 			sBuf += pC;
481 		}
482 		else
483 		{
484 			sBuf += static_cast<char>(pC[0]);
485 		}
486 	}
487 
488 	m_pie->write(sBuf.c_str(),sBuf.size());
489 }
490 
s_HRText_Listener(PD_Document * pDocument,IE_Exp_HRText * pie)491 s_HRText_Listener::s_HRText_Listener(PD_Document * pDocument,
492 										 IE_Exp_HRText * pie)
493 {
494 	m_pDocument = pDocument;
495 	m_pie = pie;
496 	m_bInSection = false;
497 	m_bInBlock = false;
498 	m_bInSpan = false;
499 	m_bNextIsSpace = false;
500 	m_bInList = false;
501 	m_iListDepth = 0;
502 	m_iDecoration = 0;
503 
504 	m_pList = new UT_StringPtrMap(10);
505 }
506 
~s_HRText_Listener()507 s_HRText_Listener::~s_HRText_Listener()
508 {
509 	_closeSpan();
510 	_closeTag();
511 	_closeSection();
512 	_handleDataItems();
513 
514 
515 	// free memory used to store list bullet numbers
516 	UT_GenericVector<const UT_String*>* pKeyList = m_pList->keys();
517 	if (pKeyList)
518 	{
519 		for(UT_sint32 i = 0; i < pKeyList->getItemCount(); i++)
520 		  delete const_cast<UT_uint16 *>(static_cast<const UT_uint16 *>(m_pList->pick(pKeyList->getLastItem()->c_str())));
521 	}
522 	DELETEP(pKeyList);
523 	DELETEP(m_pList);
524 }
525 
populate(fl_ContainerLayout *,const PX_ChangeRecord * pcr)526 bool s_HRText_Listener::populate(fl_ContainerLayout* /*sfh*/,
527 									  const PX_ChangeRecord * pcr)
528 {
529 	switch (pcr->getType())
530 	{
531 	case PX_ChangeRecord::PXT_InsertSpan:
532 		{
533 			const PX_ChangeRecord_Span * pcrs = static_cast<const PX_ChangeRecord_Span *> (pcr);
534 
535 			PT_AttrPropIndex api = pcr->getIndexAP();
536 			if (api)
537 			{
538 				_openSpan(api);
539 			}
540 
541 			PT_BufIndex bi = pcrs->getBufIndex();
542 			_outputData(m_pDocument->getPointer(bi),pcrs->getLength());
543 
544 			if (api)
545 				_closeSpan();
546 			return true;
547 		}
548 
549 	case PX_ChangeRecord::PXT_InsertObject:
550 		{
551 			return true;
552 		}
553 
554 	case PX_ChangeRecord::PXT_InsertFmtMark:
555 		return true;
556 
557 	default:
558 		UT_ASSERT(0);
559 		return false;
560 	}
561 }
562 
populateStrux(pf_Frag_Strux *,const PX_ChangeRecord * pcr,fl_ContainerLayout ** psfh)563 bool s_HRText_Listener::populateStrux(pf_Frag_Strux* /*sdh*/,
564 										   const PX_ChangeRecord * pcr,
565 										   fl_ContainerLayout* * psfh)
566 {
567 	UT_ASSERT(pcr->getType() == PX_ChangeRecord::PXT_InsertStrux);
568 	const PX_ChangeRecord_Strux * pcrx = static_cast<const PX_ChangeRecord_Strux *> (pcr);
569 	*psfh = 0;							// we don't need it.
570 
571 	switch (pcrx->getStruxType())
572 	{
573 	case PTX_SectionEndnote:
574 	case PTX_SectionHdrFtr:
575 	case PTX_Section:
576 	{
577 		_closeSpan();
578 		_closeTag();
579 		_closeSection();
580 
581 		PT_AttrPropIndex indexAP = pcr->getIndexAP();
582 		const PP_AttrProp* pAP = NULL;
583 		if (m_pDocument->getAttrProp(indexAP, &pAP) && pAP)
584 		{
585 			const gchar* pszSectionType = NULL;
586 			pAP->getAttribute("type", pszSectionType);
587 			if (
588 				!pszSectionType
589 				|| (0 == strcmp(pszSectionType, "doc"))
590 				)
591 			{
592 				_openSection(pcr->getIndexAP());
593 				m_bInSection = true;
594 			}
595 			else
596 			{
597 				m_bInSection = false;
598 			}
599 		}
600 		else
601 		{
602 			m_bInSection = false;
603 		}
604 
605 		return true;
606 	}
607 
608 	case PTX_Block:
609 	{
610 		_closeSpan();
611 		_closeTag();
612 		_openTag(pcr->getIndexAP());
613 		return true;
614 	}
615 
616 
617 	case PTX_SectionTable:
618 	case PTX_EndTable:
619 	case PTX_SectionCell:
620 	case PTX_EndCell:
621 	case PTX_EndFrame:
622 	case PTX_EndMarginnote:
623 	case PTX_EndFootnote:
624 	case PTX_SectionFrame:
625 	case PTX_SectionMarginnote:
626 	case PTX_SectionFootnote:
627 	case PTX_EndEndnote:
628 	default:
629 		UT_ASSERT(UT_TODO);
630 		return true;
631 	}
632 }
633 
change(fl_ContainerLayout *,const PX_ChangeRecord *)634 bool s_HRText_Listener::change(fl_ContainerLayout* /*sfh*/,
635 									const PX_ChangeRecord * /*pcr*/)
636 {
637 	UT_ASSERT(0);						// this function is not used.
638 	return false;
639 }
640 
insertStrux(fl_ContainerLayout *,const PX_ChangeRecord *,pf_Frag_Strux *,PL_ListenerId,void (*)(pf_Frag_Strux *,PL_ListenerId,fl_ContainerLayout *))641 bool s_HRText_Listener::insertStrux(fl_ContainerLayout* /*sfh*/,
642 									 const PX_ChangeRecord * /*pcr*/,
643 									 pf_Frag_Strux* /*sdh*/,
644 									 PL_ListenerId /* lid */,
645 									 void (* /*pfnBindHandles*/)(pf_Frag_Strux* /* sdhNew */,
646 																 PL_ListenerId /* lid */,
647 																 fl_ContainerLayout* /* sfhNew */))
648 {
649 	UT_ASSERT(0);						// this function is not used.
650 	return false;
651 }
652 
signal(UT_uint32)653 bool s_HRText_Listener::signal(UT_uint32 /* iSignal */)
654 {
655 	UT_ASSERT(UT_SHOULD_NOT_HAPPEN);
656 	return false;
657 }
658 
659 
660 /*****************************************************************/
661 /*****************************************************************/
662 
_writeDocument(void)663 UT_Error IE_Exp_HRText::_writeDocument(void)
664 {
665 	m_pListener = new s_HRText_Listener(getDoc(),this);
666 	if (!m_pListener)
667 		return UT_IE_NOMEMORY;
668 	if (!getDoc()->tellListener(static_cast<PL_Listener *>(m_pListener)))
669 		return UT_ERROR;
670 
671 	delete m_pListener;
672 	m_pListener = NULL;
673 
674 	return ((m_error) ? UT_IE_COULDNOTWRITE : UT_OK);
675 }
676 
677 /*****************************************************************/
678 /*****************************************************************/
679 
_handleDataItems(void)680 void s_HRText_Listener::_handleDataItems(void)
681 {
682 	/* Not much we can do with these in text. */
683 }
684