File Coverage

blib/lib/RTF/HTML/Converter.pm
Criterion Covered Total %
statement 51 68 75.0
branch 4 10 40.0
condition n/a
subroutine 13 16 81.2
pod 2 3 66.6
total 70 97 72.1


$text$char_props$N"; $N$text$N$N"; $N";
line stmt bran cond sub pod time code
1             # Philippe Verdret 1998-1999
2 3     3   23566 use strict;
  3         6  
  3         169  
3              
4             package RTF::HTML::Converter;
5             $RTF::HTML::Converter::VERSION = '1.12';
6 3     3   1514 use RTF::Control;
  3         10  
  3         583  
7 3     3   1760 use RTF::HTML::Converter::ansi;
  3         6  
  3         74  
8 3     3   1668 use RTF::HTML::Converter::charmap;
  3         9  
  3         132  
9              
10             @RTF::HTML::Converter::ISA = qw(RTF::Control);
11              
12 3     3   18 use constant TRACE => 0;
  3         5  
  3         174  
13 3     3   14 use constant LIST_TRACE => 0;
  3         5  
  3         118  
14 3     3   16 use constant SHOW_STYLE_NOT_PROCESSED => 1;
  3         4  
  3         134  
15 3     3   15 use constant SHOW_STYLE => 0; # insert style name in the output
  3         5  
  3         116  
16 3     3   13 use constant SHOW_RTF_LINE_NUMBER => 0;
  3         5  
  3         139  
17              
18 3     3   14 use constant RTF_DEBUG => 0;
  3         5  
  3         8859  
19              
20             =head1 NAME
21              
22             RTF::HTML::Converter - Perl extension for converting RTF into HTML
23              
24             =head1 VERSION
25              
26             version 1.12
27              
28             =head1 DESCRIPTION
29              
30             Perl extension for converting RTF into HTML
31              
32             =head1 SYNOPSIS
33              
34             use strict;
35             use RTF::HTML::Converter;
36              
37             my $object = RTF::HTML::Converter->new(
38              
39             output => \*STDOUT
40              
41             );
42              
43             $object->parse_stream( \*RTF_FILE );
44              
45             OR
46              
47             use strict;
48             use RTF::HTML::Converter;
49              
50             my $object = RTF::HTML::Converter->new(
51              
52             output => \$string
53              
54             );
55              
56             $object->parse_string( $rtf_data );
57              
58             =head1 METHODS
59              
60             =head2 new()
61              
62             Constructor method. Currently takes one named parameter, C,
63             which can either be a reference to a filehandle, or a reference to
64             a string. This is where our HTML will end up.
65              
66             =head2 parse_stream()
67              
68             Read RTF in from a filehandle, and start processing it. Pass me
69             a reference to a filehandle.
70              
71             =head2 parse_string()
72              
73             Read RTF in from a string, and start processing it. Pass me a string.
74              
75             =head1 JUST SO YOU KNOW
76              
77             You can mix-and-match your output and input methods - nothing to stop
78             you outputting to a string when you've read from a filehandle...
79              
80             =head1 AUTHOR
81              
82             Peter Sergeant C, originally by Philippe Verdret
83              
84             =head1 COPYRIGHT
85              
86             Copyright 2004 B.
87              
88             This program is free software; you can redistribute it and/or modify it under
89             the same terms as Perl itself.
90              
91             =head1 CREDITS
92              
93             This work was carried out under a grant generously provided by The Perl Foundation -
94             give them money!
95              
96              
97             =cut
98              
99             # Symbol exported by the RTF::Ouptut module:
100             # %info: informations of the {\info ...}
101             # %par_props: paragraph properties
102             # $style: name of the current style or pseudo-style
103             # $event: start and end on the 'document' event
104             # $text: text associated to the current style
105             # %symbol: symbol translations
106             # %do_on_control: routines associated to RTF controls
107             # %do_on_event: routines associated to events
108             # output(): a stack oriented output routine (don't use print())
109              
110             my $START_NEW_PARA = 1; # some actions to do at the beginning of a new para
111              
112             ###########################################################################
113             my $N = "\n"; # Pretty-printing
114             # some output parameters
115             my $TITLE_FLAG = 0;
116             my $LANG = 'en';
117             my $TABLE_BORDER = 1;
118              
119             my $CURRENT_LI = 0; # current list indent
120             my @LIST_STACK = (); # stack of opened lists
121             my %LI_LEVEL = (); # li -> list level
122              
123             my %charmap_defaults = map( { sprintf( "%02x", $_ ) => "&#$_;" } ( 0 .. 255 ) );
124              
125             my %tag_counter = (); # Attempt to only close tags that might be open
126              
127             my %PAR_ALIGN = qw(
128             qc CENTER
129             ql LEFT
130             qr RIGHT
131             qj LEFT
132             );
133             # here put your style mappings
134             my %STYLES = (
135             'Normal' => 'p',
136             'Abstract' => 'Blockquote',
137             'PACSCode' => 'Code',
138             #'AuthGrp' => '',
139             'Section' => 'H1',
140             'heading 1' => 'H1',
141             'heading 2' => 'H2',
142             'heading 3' => 'H3',
143             'heading 4' => 'H4',
144             'heading 5' => 'H5',
145             'heading 6' => 'H6',
146             'Code' => 'pre',
147             'par' => 'p', # default value
148             );
149             # list names -> level
150             my %UL_STYLES = (
151             'toc 1' => 1,
152             'toc 2' => 2,
153             'toc 3' => 3,
154             'toc 4' => 4,
155             'toc 5' => 5, );
156              
157             # not used
158             my %UL_TYPES = qw(b7 disk
159             X square
160             Y circle
161             );
162              
163             my %OL_STYLES = ();
164             # not used
165             my %OL_TYPES = (
166             'pncard' => '1', # Cardinal numbering: One, Two, Three
167             'pndec' => '1', # Decimal numbering: 1, 2, 3
168             'pnucltr' => 'A', # Uppercase alphabetic numbering
169             'pnlcltr' => 'a', # lowercase alphabetic numbering
170             'pnucrm' => 'I', # Uppercase roman numbering
171             'pnlcrm' => 'i', # Lowercase roman numbering
172             );
173             my $in_Field = -1; # nested links are illegal, not used
174             my $in_Bookmark = -1; # nested links are illegal, not used
175              
176             # This is truly nasty, but it's slightly nicer than what was there before
177             my $make_tag_handler = sub {
178             my $tag_ = shift;
179             return sub {
180             $style = $tag_;
181             if ( $event eq 'end' ) {
182             if ( $tag_counter{ $style } ) {
183             $tag_counter{ $style }--;
184             output ""
185             }
186             } else {
187             $tag_counter{ $style }++;
188             output "<$style>";
189             }
190             }
191             };
192              
193             %do_on_event = (
194             'document' => sub { # Special action
195             %tag_counter = ();
196             if ( $event eq 'start' ) {
197             output
198             qq@$N$N$N@;
199             } else {
200             my $author = $info{author};
201             my $creatim = $info{creatim};
202             my $revtim = $info{revtim};
203              
204             my $tag;
205             while (@LIST_STACK) {
206             $tag = pop @LIST_STACK;
207             output "" . $N;
208             }
209             $style = 'p';
210              
211             if ( $LANG eq 'fr' ) {
212             output "<$style>Auteur : $author\n" if $author;
213             output "<$style>Date de création : $creatim\n"
214             if $creatim;
215             output
216             "<$style>Date de modification : $revtim\n"
217             if $revtim;
218             } else { # Default
219             output "<$style>Author : $author\n" if $author;
220             output "<$style>Creation date: $creatim\n"
221             if $creatim;
222             output "<$style>Modification date: $revtim\n"
223             if $revtim;
224             }
225             output "\n\n";
226             }
227             },
228             # Table processing
229             'table' => sub { # end of table
230             if ( $event eq 'end' ) {
231             #print STDERR "end of table\n";
232             $TABLE_BORDER ? output "$N$text
$N" :
233             output "$N$text
$N";
234             } else {
235             #print STDERR "start of table\n";
236             my $end;
237             while (@LIST_STACK) {
238             $end .= '' . $N;
239             }
240             output($end);
241             }
242             },
243             'row' => sub { # end of row
244             #my $char_props = $_[SELF]->force_char_props('end');
245             #output "$N
246             if ( $event eq 'end' ) {
247             output "$N
248             } else {
249             # not defined
250             }
251             },
252             'cell' => sub { # end of cell
253             if ( $event eq 'end' ) {
254             my $char_props = $_[SELF]->force_char_props('end');
255             my $end;
256             while (@LIST_STACK) {
257             $end .= '' . $N;
258             }
259             output "$text$char_props$end
260             } else {
261             # not defined
262             }
263             },
264             # PARAGRAPH STYLES
265             #'Normal' => sub {}, # create one entry per style name???
266             'par' => sub { # Default rule: if no entry for a paragraph style
267             # Paragraph styles
268             #print STDERR "$style\n" if LIST_TRACE;
269             return output($text) unless $text =~ /\S/;
270             my ( $tag_start, $tag_end, $before ) = ( '', '', '' );
271              
272             if ( defined( my $level = $UL_STYLES{$style} ) )
273             { # registered list styles
274             if ( $level > @LIST_STACK ) {
275             my $tag;
276             push @LIST_STACK, $tag = 'UL';
277             if (SHOW_STYLE) {
278             $before = "<$tag>[$style]" . $N;
279             } else {
280             $before = "<$tag>" . $N;
281             }
282             $tag_start = $tag_end = 'LI';
283             } else {
284             $level = @LIST_STACK - $level;
285             while ( $level-- > 0 ) {
286             $before .= '' . $N;
287             }
288             $tag_start = $tag_end = 'LI';
289             }
290             } else {
291             }
292              
293             if ( $tag_start eq '' ) { # end of list
294             while (@LIST_STACK) {
295             $before .= '' . $N;
296             }
297             $tag_start = $tag_end = $STYLES{$style} || do {
298             if (SHOW_STYLE_NOT_PROCESSED) {
299 3     3   34 use vars qw/%style_not_processed/;
  3         5  
  3         3457  
300             # todo: add count
301             unless ( exists $style_not_processed{$style} ) {
302             print STDERR "style not defined '$style'\n"
303             if SHOW_STYLE_NOT_PROCESSED;
304             $style_not_processed{$style} = '';
305             }
306             }
307             $STYLES{'par'};
308             };
309             foreach (qw(qj qc ql qr)) { # for some html elements...
310             if ( $par_props{$_} ) {
311             $tag_start .= " ALIGN=$PAR_ALIGN{$_}";
312             }
313             }
314             }
315              
316             $_[SELF]->trace("$tag_start-$tag_end: $text") if TRACE;
317             my $char_props = $_[SELF]->force_char_props('end');
318             if (SHOW_RTF_LINE_NUMBER) {
319             output "$N$before<$tag_start>[$.]$text$char_props$N";
320             } else {
321             output "$N$before<$tag_start>$text$char_props$N";
322             }
323             $START_NEW_PARA = 1;
324             },
325             # Hypertextuel links
326             # 'bookmark' => sub {
327             # $_[SELF]->trace("bookmark $event $text") if TRACE;
328             # if ($event eq 'end') {
329             # return if $in_Bookmark--;
330             # output("");
331             # } else {
332             # return if ++$in_Bookmark;
333             # output("");
334             # }
335             # },
336             # 'field' => sub {
337             # my $id = $_[0];
338             # $_[SELF]->trace("field $event $text") if TRACE;
339             # if ($event eq 'end') {
340             # return if $in_Field--;
341             # output("$text");
342             # } else {
343             # return if ++$in_Field;
344             # output(""); # doesn't work!
345             # }
346             # },
347             # CHAR properties
348             b => $make_tag_handler->('b'),
349             i => $make_tag_handler->('i'),
350             ul => $make_tag_handler->('u'),
351             sub => $make_tag_handler->('sub'),
352             super => $make_tag_handler->('sup'),
353             strike => $make_tag_handler->('strike'),
354             );
355              
356             ###############################################################################
357             # If you have an &; in your RTF document and if
358             # is a character entity, you'll see "&;" in the RTF document
359             # and the corresponding glyphe in the HTML document
360             # How to give a new definition to a control registered in %do_on_control:
361             # - method redefinition (could be the purist's solution)
362             # - $Control::do_on_control{control_word} = sub {};
363             # - when %do_on_control is exported write:
364             $do_on_control{'ansi'} = # callback redefinition
365             sub {
366             # RTF: \'
367             # HTML: &#;
368              
369 1     1   7 my @charmap_data = $_[SELF]->charmap_reader( $_[CONTROL] );
370              
371 35         47 my %charset = ( # general rule
372             %charmap_defaults,
373             # and some specific defs
374 1         75 map( { s/^\s+//;
375 35         252 split /\s+/
376             } @charmap_data ) );
377             *char = sub {
378 0     0     my $char_props;
379 0 0         if ($START_NEW_PARA) {
380 0           $char_props = $_[SELF]->force_char_props('start');
381 0           $START_NEW_PARA = 0;
382             } else {
383 0           $char_props = $_[SELF]->process_char_props();
384             }
385 0           output $char_props . $charset{ $_[1] };
386             }
387 1         24 };
388              
389             # symbol processing
390             # RTF: \~
391             # named chars
392             # RTF: \ldblquote, \rdblquote
393             $symbol{'~'} = ' ';
394             $symbol{'tab'} = ' '; #'        ';
395             $symbol{'ldblquote'} = '«';
396             $symbol{'rdblquote'} = '»';
397             $symbol{'line'} = '
';
398              
399             sub symbol {
400              
401 0     0 1 0 debug( 'symbol', @_ ) if RTF_DEBUG > 5;
402              
403 0         0 my $char_props;
404 0 0       0 if ($START_NEW_PARA) {
405 0         0 $char_props = $_[SELF]->force_char_props('start');
406 0         0 $START_NEW_PARA = 0;
407             } else {
408 0         0 $char_props = $_[SELF]->process_char_props();
409             }
410 0 0       0 if ( defined( my $sym = $symbol{ $_[1] } ) ) {
411 0         0 output $char_props . $sym;
412             } else {
413 0         0 output $char_props . $_[1]; # as it
414             }
415             }
416             # Text
417             # certainly do the same thing with the char() method
418             sub text { # parser callback redefinition
419              
420 19     19 1 19 debug( 'text', @_ ) if RTF_DEBUG > 5;
421              
422 19         29 my $text = $_[1];
423 19         23 my $char_props = '';
424 19 100       123 if ($START_NEW_PARA) {
425 3         23 $char_props = $_[SELF]->force_char_props('start');
426 3         6 $START_NEW_PARA = 0;
427             } else {
428 16         58 $char_props = $_[SELF]->process_char_props();
429             }
430 19         38 $text =~ s/&/&/g;
431 19         23 $text =~ s/
432 19         26 $text =~ s/>/>/g;
433 19 100       34 if ( defined $char_props ) {
434 12         41 output("$char_props$text");
435             } else {
436 7         26 output("$text");
437             }
438             }
439              
440             sub debug {
441              
442 0     0 0 0 my $function = shift;
443              
444 0         0 print STDERR "[RTF::HTML::Converter::$function]" . ( join '|', @_ ), "\n";
445              
446             }
447              
448             1;
449             __END__