filters

amiproparser.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002 Ariya Hidayat <ariyahidayat@yahoo.de>
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#include "amiproparser.h"
21
22#include <tqfile.h>
23#include <tqstring.h>
24#include <tqstringlist.h>
25#include <tqtextstream.h>
26
27const float AmiPro::LS_Single = -1;
28const float AmiPro::LS_OneAndHalf = -1.5;
29const float AmiPro::LS_Double = -2;
30
31// helper function to "unescape" AmiPro string
32static TQString AmiProUnescape( const TQString& str )
33{
34 TQString result;
35
36 for( unsigned i=0; i< str.length(); i++ )
37 {
38 TQChar c = str[i];
39 result.append( c );
40
41 // check for "@@", decoded as '@'
42 if( c == '@' )
43 if( str[i+1] == '@' )
44 i++ ; // eat !
45
46 // a few possible escape sequence
47 if( c == '<' )
48 {
49
50 // check for "<<", decoded as '<'
51 if( str[i+1] == '<' )
52 {
53 result.truncate( result.length() - 1 ); // remove the '<'
54 result.append( '<' );
55 i++;
56 }
57
58 // check for "<;>", decoded as '>'
59 if( str[i+1] == ';' )
60 {
61 result.truncate( result.length() - 1 ); // remove the '<'
62 result.append( '>' );
63 i+=2;
64 }
65
66 // check for "<[>", decoded as '['
67 if( str[i+1] == '[' )
68 {
69 result.truncate( result.length() - 1 ); // remove the '<'
70 result.append( '[' );
71 i+=2;
72 }
73
74 // some special characters
75 if( str[i+1] == '/' )
76 {
77 if( str[i+2] == 'R' )
78 {
79 result.truncate( result.length() - 1 ); // remove the '<'
80 result.append( '\'' ); // </R> decoded as '
81 i += 3;
82 }
83 else
84 {
85 result.truncate( result.length() - 1 ); // remove the '<'
86 result.append( TQChar(str[i+2].unicode() + 0x40 ) );
87 i += 3;
88 }
89 }
90
91 // yet another special characters
92 if( str[i+1] == '\\' )
93 {
94 result.truncate( result.length() - 1 ); // remove the '<'
95 result.append( TQChar(str[i+2].unicode() | 0x80 ) );
96 i += 3;
97 }
98
99 }
100
101 }
102
103 return result;
104}
105
106AmiProParser::AmiProParser()
107{
108 m_result = OK;
109 m_listener = NULL;
110}
111
112AmiProParser::~AmiProParser()
113{
114}
115
116bool AmiProParser::setResult( int result )
117{
118 m_result = result;
119 return m_result == OK;
120}
121
122void AmiProParser::setListener( AmiProListener *listener )
123{
124 m_listener = listener;
125}
126
127bool AmiProParser::process( const TQString& filename )
128{
129 TQString line;
130
131 // open input file
132 TQFile in( filename );
133 if( !in.open( IO_ReadOnly))
134 return setResult( FileError );
135
136 TQTextStream stream;
137 stream.setDevice( &in );
138
139 // the first should be "[ver]"
140 line = stream.readLine();
141 if( line != "[ver]" )
142 return setResult( InvalidFormat );
143
144 // get format version, typically 4
145 line = stream.readLine();
146 int format_version = line.toInt();
147
148 // FIXME is this necessary ?
149 // accept only format version 4
150 if( format_version != 4 )
151 return setResult( InvalidFormat );
152
153 // initialize
154 m_currentFormat = AmiProFormat();
155 m_formatList.clear();
156 m_styleList.clear();
157 m_currentSection = "";
158 TQStringList lines;
159
160 // parse line-by-line
161 for( ;; )
162 {
163
164 line = stream.readLine();
165 if( line.isNull() ) break;
166
167 TQString old_section = m_currentSection;
168 bool enter_new_section = false;
169
170 // new main section ?
171 if( !line.isEmpty() ) if( line[0] == '[' )
172 {
173 enter_new_section = true;
174 m_currentSection = "";
175 for( unsigned i=1; i<line.length(); i++ )
176 if( line[i] == ']' ) break;
177 else m_currentSection += line[i];
178 }
179
180 // leave [tag]
181 if( enter_new_section && ( old_section == "tag" ) )
182 {
183 parseStyle( lines );
184 lines.clear();
185 }
186
187 // leave [edoc]
188 if( enter_new_section && ( old_section == "edoc" ) )
189 {
190 parseParagraph( lines.join(" ") );
191 lines.clear();
192 }
193
194 // still in [tag]
195 if( !enter_new_section && ( old_section == "tag" ) )
196 {
197 lines.append( line );
198 }
199
200 // still in [edoc]
201 if( !enter_new_section && ( old_section == "edoc" ) )
202 {
203 if( line.isEmpty() )
204 {
205 parseParagraph( lines );
206 lines.clear();
207 }
208 lines.append( line );
209 }
210
211 // enter [tag]
212 if( enter_new_section && ( m_currentSection == "tag" ) )
213 {
214 lines.clear();
215 }
216
217 // enter [edoc]
218 if( enter_new_section && ( m_currentSection == "edoc" ) )
219 {
220 processOpenDocument();
221 lines.clear();
222 }
223
224 }
225
226 // in case left-over
227 if( lines.count() > 0 ) parseParagraph( lines.join( " " ) );
228
229 processCloseDocument();
230
231 return true;
232}
233
234bool AmiProParser::processOpenDocument()
235{
236 if( m_listener ) return m_listener->doOpenDocument();
237 return true;
238}
239
240bool AmiProParser::processCloseDocument()
241{
242 if( m_listener )
243 return m_listener->doCloseDocument();
244 return true;
245}
246
247bool AmiProParser::parseParagraph( const TQStringList& lines )
248{
249 m_text = "";
250 m_formatList.clear();
251 m_layout = AmiProLayout();
252
253 // join the lines, up until first char in a line is '>'
254 TQString partext = "";
255 for( unsigned i=0; i<lines.count(); i++ )
256 if( lines[i][0] == '>' ) break;
257 else partext.append( lines[i] + "\n" );
258
259 TQChar ch = partext[partext.length()-1];
260 while( ( ch == '\n' ) || ( ch == '\r' ) )
261 {
262 partext.remove( partext.length()-1, 1 );
263 ch = partext[partext.length()-1];
264 }
265
266 // "unescape", process special chars and such
267 TQString text = AmiProUnescape( partext );
268
269 // apply default style first
270 m_layout.applyStyle( findStyle( "Body Text" ) );
271
272 for( unsigned i=0; i<text.length(); i++ )
273 {
274 TQChar ch = text[i];
275
276 // handle a tag
277 if( ch == '<' )
278 {
279 TQString tag = "";
280 for( i++; (i < text.length()) &&
281 (text[i] != '>'); i++) tag.append( text[i] );
282 handleTag( tag );
283 }
284
285 else
286
287 // handle style change
288 if( ch == '@' )
289 {
290 TQString styleName;
291 for( i++; (i < partext.length()) && (partext[i] != '@'); i++)
292 styleName += partext[i];
293 m_layout.name = styleName;
294 AmiProStyle style = findStyle( styleName );
295 m_currentFormat.applyStyle( style );
296 m_formatList.append( m_currentFormat );
297 m_layout.applyStyle( style );
298 }
299
300 else
301 // normal character
302 m_text.append( ch );
303 }
304
305 // calc length of each format tag
306 for( unsigned j=0; j<m_formatList.count(); j++ )
307 {
308 int nextpos;
309 AmiProFormat& format = m_formatList[j];
310 if( j < m_formatList.count()-1 )
311 {
312 AmiProFormat& nextformat = m_formatList[j+1];
313 nextpos = nextformat.pos;
314 }
315 else nextpos = m_text.length();
316 format.len = nextpos - format.pos;
317 }
318
319 if( m_listener )
320 return m_listener->doParagraph( m_text, m_formatList, m_layout );
321
322 return true;
323}
324
325bool AmiProParser::parseStyle( const TQStringList& lines )
326{
327 AmiProStyle style;
328
329 style.name = AmiProUnescape( lines[0].stripWhiteSpace() );
330 if( style.name.isEmpty() ) return true;
331
332 // font
333 if( lines[2].stripWhiteSpace() != "[fnt]" ) return true;
334 style.fontFamily = lines[3].stripWhiteSpace();
335 style.fontSize = lines[4].stripWhiteSpace().toFloat() / 20.0;
336
337 unsigned color = lines[5].stripWhiteSpace().toUInt();
338 style.fontColor.setRgb( color&255, (color>>8)&255, (color>>16)&255);
339
340 unsigned flag = lines[6].stripWhiteSpace().toUInt();
341 style.bold = flag & 1;
342 style.italic = flag & 2;
343 style.underline = flag & 4;
344 style.word_underline = flag & 8;
345 style.double_underline = flag & 64;
346
347 // alignment
348 if( lines[7].stripWhiteSpace() != "[algn]" ) return true;
349 unsigned align_flag = lines[8].stripWhiteSpace().toUInt();
350 if( align_flag & 1 ) style.align = TQt::AlignLeft;
351 if( align_flag & 2 ) style.align = TQt::AlignRight;
352 if( align_flag & 4 ) style.align = TQt::AlignCenter;
353 if( align_flag & 8 ) style.align = TQt::AlignJustify;
354
355 // linespace
356 if( lines[13].stripWhiteSpace() != "[spc]" ) return true;
357 unsigned ls_flag = lines[14].stripWhiteSpace().toUInt();
358 if( ls_flag & 1 ) style.linespace = AmiPro::LS_Single;
359 if( ls_flag & 2 ) style.linespace = AmiPro::LS_OneAndHalf;
360 if( ls_flag & 4 ) style.linespace = AmiPro::LS_Double;
361 if( ls_flag & 8 )
362 style.linespace = lines[15].stripWhiteSpace().toFloat() / 20.0;
363 style.spaceBefore = lines[17].stripWhiteSpace().toFloat() / 20.0;
364 style.spaceAfter = lines[18].stripWhiteSpace().toFloat() / 20.0;
365
366 m_styleList.append( style );
367
368 // "Style #0", "Style #1" and such are special styles
369 // do not import these styles
370 if( style.name.left( 7 ) != "Style #" )
371 if( m_listener )
372 return m_listener->doDefineStyle( style );
373 return true;
374}
375
376AmiProStyle AmiProParser::findStyle( const TQString& name )
377{
378 AmiProStyleList::iterator it;
379 for( it=m_styleList.begin(); it!=m_styleList.end(); ++it )
380 {
381 AmiProStyle& style = *it;
382 if( style.name == name )
383 return style;
384 }
385 return AmiProStyle();
386}
387
388bool AmiProParser::handleTag( const TQString& tag )
389{
390 // > (actually encoded as <;>)
391 if( tag == ";" )
392 m_text.append( ">" );
393
394 // [ (actually encoded as <[>)
395 if( tag == "[" )
396 m_text.append( "[" );
397
398 // bold on
399 if( tag == "+!" )
400 {
401 m_currentFormat.bold = true;
402 m_currentFormat.pos = m_text.length();
403 m_formatList.append( m_currentFormat );
404 }
405
406 // bold off
407 if( tag == "-!" )
408 {
409 m_currentFormat.bold = false;
410 m_currentFormat.pos = m_text.length();
411 m_formatList.append( m_currentFormat );
412 }
413
414 // italic on
415 if( tag == "+\"" )
416 {
417 m_currentFormat.italic = true;
418 m_currentFormat.pos = m_text.length();
419 m_formatList.append( m_currentFormat );
420 }
421
422 // italic off
423 if( tag == "-\"" )
424 {
425 m_currentFormat.italic = false;
426 m_currentFormat.pos = m_text.length();
427 m_formatList.append( m_currentFormat );
428 }
429
430 // underline on
431 if( tag == "+#" )
432 {
433 m_currentFormat.underline = true;
434 m_currentFormat.pos = m_text.length();
435 m_formatList.append( m_currentFormat );
436 }
437
438 // underline off
439 if( tag == "-#" )
440 {
441 m_currentFormat.underline = false;
442 m_currentFormat.pos = m_text.length();
443 m_formatList.append( m_currentFormat );
444 }
445
446 // double underline on
447 if( tag == "+)" )
448 {
449 m_currentFormat.double_underline = true;
450 m_currentFormat.pos = m_text.length();
451 m_formatList.append( m_currentFormat );
452 }
453
454 // double underline off
455 if( tag == "-)" )
456 {
457 m_currentFormat.double_underline = false;
458 m_currentFormat.pos = m_text.length();
459 m_formatList.append( m_currentFormat );
460 }
461
462 // word underline on
463 if( tag == "+$" )
464 {
465 m_currentFormat.word_underline = true;
466 m_currentFormat.pos = m_text.length();
467 m_formatList.append( m_currentFormat );
468 }
469
470 // word underline off
471 if( tag == "-$" )
472 {
473 m_currentFormat.word_underline = false;
474 m_currentFormat.pos = m_text.length();
475 m_formatList.append( m_currentFormat );
476 }
477
478 // superscript on
479 if( tag == "+&" )
480 {
481 m_currentFormat.superscript = true;
482 m_currentFormat.pos = m_text.length();
483 m_formatList.append( m_currentFormat );
484 }
485
486 // superscript off
487 if( tag == "-&" )
488 {
489 m_currentFormat.superscript = false;
490 m_currentFormat.pos = m_text.length();
491 m_formatList.append( m_currentFormat );
492 }
493
494 // subscript on
495 if( tag == "+'" )
496 {
497 m_currentFormat.subscript = true;
498 m_currentFormat.pos = m_text.length();
499 m_formatList.append( m_currentFormat );
500 }
501
502 // subscript off
503 if( tag == "-'" )
504 {
505 m_currentFormat.subscript = false;
506 m_currentFormat.pos = m_text.length();
507 m_formatList.append( m_currentFormat );
508 }
509
510 // strikethrough on
511 if( tag == "+%" )
512 {
513 m_currentFormat.strikethrough = true;
514 m_currentFormat.pos = m_text.length();
515 m_formatList.append( m_currentFormat );
516 }
517
518 // strikethrough off
519 if( tag == "-%" )
520 {
521 m_currentFormat.strikethrough = false;
522 m_currentFormat.pos = m_text.length();
523 m_formatList.append( m_currentFormat );
524 }
525
526 // paragraph left-align
527 if( tag == "+@" )
528 m_layout.align = TQt::AlignLeft;
529
530 // paragraph right-align
531 if( tag == "+A" )
532 m_layout.align = TQt::AlignRight;
533
534 // paragraph center
535 if( tag == "+B" )
536 m_layout.align = TQt::AlignCenter;
537
538 // paragraph justify
539 if( tag == "+C" )
540 m_layout.align = TQt::AlignJustify;
541
542 // linespace
543 if( tag.left( 3 ) == ":S+" )
544 {
545 float ls = tag.right( tag.length() - 3 ).toFloat();
546 m_layout.linespace = (ls == -1) ? AmiPro::LS_Single :
547 (ls == -2) ? AmiPro::LS_OneAndHalf :
548 (ls == -3) ? AmiPro::LS_Double : ls / 20.0;
549 }
550
551 // font
552 if( tag.left( 2 ) == ":f" )
553 {
554 TQString fontdesc = tag.right( tag.length()-2 );
555 TQStringList desc = TQStringList::split( ",", fontdesc );
556 if( desc.count() > 0 ) m_currentFormat.fontSize = desc[0].toFloat() / 20.0;
557 if( desc.count() > 1 )
558 {
559 TQString fontFamily = desc[1];
560 if( fontFamily[0].isDigit() ) fontFamily.remove( 0, 1 );
561 m_currentFormat.fontFamily = fontFamily;
562 }
563 if( desc.count() > 4 )
564 {
565 unsigned red = desc[2].toUInt();
566 unsigned green = desc[3].toUInt();
567 unsigned blue = desc[4].toUInt();
568 m_currentFormat.fontColor.setRgb( red, green, blue );
569 }
570 m_formatList.append( m_currentFormat );
571 }
572
573 return true;
574}
575
576// text formatting
577AmiProFormat::AmiProFormat()
578{
579 pos = len = 0;
580 bold = italic = underline =
581 word_underline = double_underline =
582 subscript = superscript = strikethrough = false;
583 fontFamily = "";
584 fontSize = 12;
585 fontColor = TQt::black;
586}
587
588void AmiProFormat::assign( const AmiProFormat& f )
589{
590 pos = f.pos;
591 len = f.len;
592 bold = f.bold;
593 italic = f.italic;
594 underline = f.underline;
595 word_underline = f.word_underline;
596 double_underline = f.double_underline;
597 subscript = f.subscript;
598 superscript = f.superscript;
599 strikethrough = f.strikethrough;
600 fontFamily = f.fontFamily;
601 fontSize = f.fontSize;
602 fontColor = f.fontColor;
603}
604
605AmiProFormat::AmiProFormat( const AmiProFormat& f )
606{
607 assign( f );
608}
609
610AmiProFormat& AmiProFormat::operator=( const AmiProFormat& f )
611{
612 assign( f );
613 return *this;
614}
615
616void AmiProFormat::applyStyle( const AmiProStyle& style )
617{
618 fontFamily = style.fontFamily;
619 fontSize = style.fontSize;
620 fontColor = style.fontColor;
621 bold = style.bold;
622 italic = style.italic;
623 underline = style.underline;
624 word_underline = style.word_underline;
625 double_underline = style.double_underline;
626 subscript = style.subscript;
627 superscript = style.superscript;
628 strikethrough = style.strikethrough;
629}
630
631// paragraph layout
632AmiProLayout::AmiProLayout()
633{
634 name = "";
635 fontFamily = "";
636 fontSize = 12;
637 fontColor = TQt::black;
638 bold = italic = underline =
639 word_underline = double_underline =
640 subscript = superscript = strikethrough = false;
641 align = TQt::AlignLeft;
642 linespace = AmiPro::LS_Single;
643 spaceBefore = spaceAfter = 0;
644}
645
646void AmiProLayout::assign( const AmiProLayout &l )
647{
648 name = l.name;
649 fontFamily = l.fontFamily;
650 fontSize = l.fontSize;
651 fontColor = l.fontColor;
652 bold = l.bold;
653 italic = l.italic;
654 underline = l.underline;
655 word_underline = l.word_underline;
656 double_underline = l.double_underline;
657 subscript = l.subscript;
658 superscript = l.superscript;
659 strikethrough = l.strikethrough;
660 align = l.align;
661 linespace = l.linespace;
662 spaceBefore = l.spaceBefore;
663 spaceAfter = l.spaceAfter;
664}
665
666AmiProLayout::AmiProLayout( const AmiProLayout& l )
667{
668 assign( l );
669}
670
671AmiProLayout& AmiProLayout::operator=( const AmiProLayout& l )
672{
673 assign( l );
674 return *this;
675}
676
677void AmiProLayout::applyStyle( const AmiProStyle& style )
678{
679 fontFamily = style.fontFamily;
680 fontSize = style.fontSize;
681 fontColor = style.fontColor;
682 bold = style.bold;
683 italic = style.italic;
684 underline = style.underline;
685 word_underline = style.word_underline;
686 double_underline = style.double_underline;
687 subscript = style.subscript;
688 superscript = style.superscript;
689 strikethrough = style.strikethrough;
690 align = style.align;
691 linespace = style.linespace;
692 spaceBefore = style.spaceBefore;
693 spaceAfter = style.spaceAfter;
694}
695
696// style definition
697AmiProStyle::AmiProStyle()
698{
699 name = "Unnamed";
700 fontFamily = "";
701 fontSize = 12;
702 fontColor = TQt::black;
703 bold = italic = underline =
704 word_underline = double_underline =
705 subscript = superscript = strikethrough = false;
706 linespace = AmiPro::LS_Single;
707 spaceBefore = spaceAfter = 0;
708}
709
710void AmiProStyle::assign( const AmiProStyle& s )
711{
712 name = s.name;
713 fontFamily = s.fontFamily;
714 fontSize = s.fontSize;
715 fontColor = s.fontColor;
716 bold = s.bold;
717 italic = s.italic;
718 underline = s.underline;
719 word_underline = s.word_underline;
720 double_underline = s.double_underline;
721 subscript = s.subscript;
722 superscript = s.superscript;
723 strikethrough = s.strikethrough;
724 align = s.align;
725 linespace = s.linespace;
726 spaceBefore = s.spaceBefore;
727 spaceAfter = s.spaceAfter;
728}
729
730AmiProStyle::AmiProStyle( const AmiProStyle& s )
731{
732 assign( s );
733}
734
735AmiProStyle& AmiProStyle::operator=( const AmiProStyle& s )
736{
737 assign( s );
738 return *this;
739}
740
741// base listener for the parser
742AmiProListener::AmiProListener()
743{
744}
745
746AmiProListener::~AmiProListener()
747{
748}
749
750#define DO_TRUE_DEFINITION(string) \
751 bool AmiProListener::string \
752 {\
753 return true;\
754 }
755
756DO_TRUE_DEFINITION(doOpenDocument())
757DO_TRUE_DEFINITION(doCloseDocument())
758DO_TRUE_DEFINITION(doDefineStyle(const AmiProStyle& style))
759DO_TRUE_DEFINITION(doParagraph(const TQString& text, AmiProFormatList formatList,
760 AmiProLayout& ))