File Coverage

blib/lib/Text/Tiki.pm
Criterion Covered Total %
statement 6 378 1.5
branch 0 138 0.0
condition 0 58 0.0
subroutine 2 61 3.2
pod 14 59 23.7
total 22 694 3.1


'); \n".$row."\n";
line stmt bran cond sub pod time code
1             #
2             # Copyright 2003-4 Timothy Appnel.
3             # This code is released under the Artistic License.
4             #
5              
6             package Text::Tiki;
7              
8 1     1   1018 use strict;
  1         4  
  1         50  
9              
10 1     1   6 use vars qw( $VERSION );
  1         2  
  1         23740  
11              
12             $VERSION = 0.73;
13              
14             # Explaination from HTML::FromText docs.
15             # This list of protocols is taken from RFC 1630: "Universal Resource
16             # Identifiers in WWW". The protocol "file" is omitted because
17             # experience suggests that it results in many false positives; "https
18             # postdates RFC 1630. The protocol "mailto" is handled separately, by
19             # the email address matching code.
20             my $protocol = join '|', qw(afs cid ftp gopher http https mid news nntp prospero telnet wais);
21              
22             my %Map = ( '&' => '&', '"' => '"', '<' => '<', '>' => '>', "'" => ''' );
23             my $RE = join '|', keys %Map;
24              
25             my $punc = '[.?!,:;\]\)\}]';
26             my $inline_start_boundry='[\s\t\[\(\{]'; # this sets off the search for a handler
27             my $term= qr/(?=$punc*?(\s|$))/; # checks that the following space to be sure the character isn't something inline
28             my $not_in_markup='(?:[^<>]*|<[^<>]*>)*?';
29             my $macro= qr/%%.*?%%/;
30              
31             # __LT__ & __GT__ is the unfortunate side effect of not confusing the parser during processing.
32             my %cell_alignments = ( '__LT__' => 'left', '^' => 'center', '__GT__' => 'right' );
33             my $cell_alignment = '\\'.join '|\\', keys %cell_alignments;
34              
35             my %block_handlers = (
36             '_p' => \&hdlr_paragraph,
37             '>' => \&hdlr_blockquote,
38             '_pre' => \&hdlr_pre,
39             '%' => \&hdlr_code_block,
40             '-' => \&hdlr_hr,
41             '_h1' => \&hdlr_h1,
42             '_h2' => \&hdlr_h2,
43             '_h3' => \&hdlr_h3,
44             '_h4' => \&hdlr_h4,
45             '_h5' => \&hdlr_h5,
46             '_h6' => \&hdlr_h6,
47             '*' => \&hdlr_ul,
48             '#' => \&hdlr_ol,
49             '_dl' => \&hdlr_dl,
50             '|' => \&hdlr_table,
51             '_macro' => \&hdlr_macro_block # for default block handler
52             );
53            
54             my %inline_handlers = (
55             '*' => \&hdlr_strong,
56             '/' => \&hdlr_emp,
57             '+' => \&hdlr_insert,
58             '-' => \&hdlr_delete,
59             '~' => \&hdlr_subscript,
60             '^' => \&hdlr_superscript,
61             '"' => \&hdlr_quote,
62             '%' => \&hdlr_code_inline,
63             '@' => \&hdlr_cite,
64             '[' => \&hdlr_hyperlink,
65             '{' => \&hdlr_image,
66             );
67            
68             #--- external methods
69              
70             sub new {
71 0     0 1   my $class = shift;
72 0           my $tiki = bless {}, $class;
73 0           $tiki->init(@_);
74 0           $tiki->clear_handlers(@_);
75 0           return $tiki;
76             }
77              
78             sub init {
79 0     0 1   my $tiki = shift;
80 0           $tiki->{__heading_names}={};
81 0           $tiki->{__block_format_depth}=0;
82 0           $tiki->{__inline_format_depth}=0;
83 0           $tiki->{__macro_processing}=0;
84 0           $tiki->{__wiki_implicit_links}=0;
85 0           $tiki->{__wiki_prefix}='';
86 0           $tiki->{__interwiki_links}=0;
87 0           $tiki->{__interwiki_table}={ };
88 0           $tiki->{__typographic_processing}=1;
89             # should we store the original?
90             # should we store the finished product?
91             }
92              
93             sub format {
94 0     0 1   my $tiki = shift;
95 0           my $content = shift;
96 0 0         unless ( ref($content) eq "ARRAY") {
97 0           $content=~s/\r//g;
98 0           my @lines = split(/\n/, $content);
99 0           $content = \@lines;
100             }
101 0           return $tiki->block_format($content);
102             }
103              
104             sub format_line {
105 0     0 1   my $tiki = shift;
106 0           my $line = shift;
107 0           $tiki->inline_format(\$line);
108 0           return $line
109             }
110              
111             sub stash {
112 0     0 1   my $tiki = shift;
113 0           my $key = shift;
114 0 0         $tiki->{__stash}->{$key} = shift if @_;
115 0           return $tiki->{__stash}->{$key};
116             }
117              
118             sub clear_handlers {
119 0     0 1   $_[0]->{__wiki_links_handler} = \&wiki_link_default_processor;
120 0           $_[0]->{__interwiki_links_handler} = \&interwiki_link_default_processor;
121 0           $_[0]->{__macro_handlers} = undef;
122             }
123              
124             #--- internal "workhorse" methods
125              
126             sub block_format {
127 0     0 0   my $tiki = shift;
128 0           my $content=shift;
129 0           my $index=0;
130 0           my $buffer;
131             my $out;
132 0           my @pre_tail=();
133 0 0         return '' unless ( @{ $content } );
  0            
134 0           $tiki->{__block_format_depth}++;
135 0           foreach my $line (@{ $content }) {
  0            
136 0           $line =~s/^[\s\t]*$//; # cleans out false negatives linebreaks.
137 0 0         unless ( length($line) ) {
138 0 0         next unless( defined( $buffer->[$index]->{type} ) );
139 0 0         if ( $buffer->[$index]->{type}!~/(_pre|%)/ ) {
140 0 0         if ( @{ $buffer->[$index]->{buffer} } ) {
  0            
141 0           $index++; $buffer->[$index]->{buffer} = ();
  0            
142 0           next;
143             }
144 0           } else { push(@pre_tail,''); next; }
  0            
145             } else {
146 0           my $symbol=substr($line,0,1);
147 0 0         if ($symbol=~/^[\s\t]/) {
148 0           $symbol='_pre';
149 0           push(@{ $buffer->[$index]->{buffer} },@pre_tail);
  0            
150 0           @pre_tail=();
151             } else {
152 0 0         if ( @pre_tail ) { $index++; $buffer->[$index]->{buffer} = (); @pre_tail=(); }
  0            
  0            
  0            
153 0 0 0       if ( $symbol eq '!'
    0 0        
    0 0        
    0 0        
    0 0        
    0          
154             && substr($line,1,2)=~/^[1-6]?[\s\t]/ ) {
155 0           $line=~s/^!([1-6]?)[\s\t]//;
156 0 0         $symbol = '_h'. ($1?$1:1);
157             } elsif ($symbol eq '-') {
158 0 0         if ($line=~/^----/) { $line=''; }
  0            
159 0           else { $symbol = '_p'; }
160             } elsif ($symbol eq '|') {
161             # avoids further processing for now
162             } elsif ($symbol=~/[:;]/) {
163 0           $symbol = '_dl';
164             # we don't strip the symbol here so the dl handler knows what is a
165             # and what is a
.
166             } elsif ($tiki->{__macro_processing} && $symbol eq '%' && $line=~/^$macro[\s\t]*$/) { # should use more precise regex that only accepts if alone on line.
167 0           $symbol = '_macro';
168             } elsif ( defined( $block_handlers{$symbol} )
169             && ( ( $symbol=~/[#*]/ && substr($line,1,1)=~/[#*\s\t]/ ) || # Handles nested lists shortcut
170             length($line) == 1 || # blank line in a block
171             substr($line,1,1)=~/^[$symbol\s\t]/ ) ) {
172 0           $line=~s/^[$symbol][\s\t]?//;
173 0           } else { $symbol = '_p'; }
174             }
175 0 0         $buffer->[$index]->{type} = $symbol unless ( defined ( $buffer->[$index]->{type} ) );
176             }
177             # keeps encoder from getting confused during processing.
178 0           $line=~s//__GT__/g;
  0            
179 0           push(@{ $buffer->[$index]->{buffer} },$line);
  0            
180 0 0         if ($buffer->[$index]->{type} eq '_macro') { $index++; $buffer->[$index]->{buffer} = (); }
  0            
  0            
181             }
182 0 0 0       pop( @{ $buffer } ) and $index-- unless ( $buffer->[-1]->{type} );
  0            
183 0           foreach my $block ( @{ $buffer } ) {
  0            
184 0           $out .= $block_handlers{ $block->{type} }->( $tiki, $block->{buffer} );
185             }
186 0 0 0       if ( ! ($tiki->{__block_format_depth}-1) && $tiki->{__macro_processing} ) {
187 0           $out=~s/($macro)[\s\t]*/$tiki->hdlr_macro_block_postprocess($1)/meg; # should only work at start of the line
  0            
188             }
189 0           $tiki->{__block_format_depth}--;
190 0 0         return $out?$out:'';
191             }
192              
193             sub inline_format {
194 0     0 0   my $tiki = shift;
195 0           my $in = shift;
196 0           my $out='';
197 0           $tiki->{__inline_format_depth}++;
198 0   0       while (defined($$in) && length($$in) > 2) {
199 0           my $symbol = substr($$in,0,1);
200 0 0 0       if ( $tiki->{__inline_format_depth}==1 && $symbol eq '%' && $tiki->{__macro_processing} ) {
      0        
201 0           $$in=~s/^($macro)/$tiki->hdlr_macro_inline($1)/e;
  0            
202             #macro was passed through.
203 0 0 0       if ($$in=~s/^($macro)// && $1) {
204 0           $out .= $1; next;
  0            
205             }
206 0           $symbol = substr($$in,0,1);
207             }
208 0 0 0       if ( defined( $inline_handlers{$symbol} ) &&
      0        
209             (! length($out) || substr($out, length($out)-1 )=~/$inline_start_boundry/ ) ) {
210 0           my ($start_tag,$enclosure,$end_tag) = $inline_handlers{ $symbol }->( $tiki, $in );
211 0 0         if ( defined( $start_tag ) ) {
212 0           $out .= $start_tag . $tiki->inline_format(\$enclosure) . $end_tag;
213 0           next;
214             }
215             }
216 0           $$in=~s/(.)//; $out.=$1;
  0            
217             }
218 0 0         $out .= defined($$in)?$$in:'';
219 0 0         unless ( $tiki->{__inline_format_depth}-1 ) {
220 0           $tiki->hdlr_autolink(\$out);
221 0 0         $tiki->hdlr_wiki_link(\$out) if ( $tiki->{__wiki_implicit_links} );
222 0 0         $tiki->hdlr_interwiki_link(\$out) if ( $tiki->{__interwiki_links} );
223 0           $tiki->hdlr_acronym(\$out);
224 0           encoding_processor(\$out, { typographic_processing => $tiki->{__typographic_processing} } );
225 0 0         if ( $tiki->{__macro_processing} ) {
226 0           $out=~s/($macro)/$tiki->hdlr_macro_inline_literal($1)/eg;
  0            
227             }
228             }
229 0           $tiki->{__inline_format_depth}--;
230 0           return $out;
231             }
232              
233             #-- wiki handling routines.
234              
235 0     0 1   sub wiki_implicit_links { $_[0]->{__wiki_implicit_links}=$_[1]; }
236 0     0 1   sub wiki_prefix { $_[0]->{__wiki_prefix}=$_[1]; }
237 0     0 1   sub interwiki_links { $_[0]->{__interwiki_links}=$_[1]; }
238 0     0 1   sub interwiki_table { $_[0]->{__interwiki_table}=$_[1]; }
239              
240 0     0 1   sub wiki_links_handler { $_[0]->{__wiki_links_handler}=$_[1]; }
241 0     0 1   sub interwiki_links_handler { $_[0]->{__interwiki_links_handler}=$_[1]; }
242              
243             sub hdlr_wiki_link { # regex borrowed from Text::WikiFormat
244 0     0 0   my $tiki = shift;
245 0           my $in = shift;
246 0   0       $tiki->{__wiki_links_handler} ||= \&wiki_link_default_processor;
247 0           $$in =~ s|(?=])\b([A-Za-z]+(?:[A-Z]\w+)+)|$tiki->{__wiki_links_handler}->($tiki,$1)|eg;
  0            
248             }
249              
250             sub hdlr_interwiki_link {
251 0     0 0   my $tiki = shift;
252 0           my $in = shift;
253 0   0       $tiki->{__interwiki_links_handler} ||= \&interwiki_link_default_processor;
254 0           $$in =~ s|\[\[([:\w]+?)(?:\s+([^\]{2}]+?))?\]\]|$tiki->{__interwiki_links_handler}->($tiki,$1,$2)|eg;
  0            
255             }
256              
257             sub wiki_link_default_processor {
258 0     0 0   return qq|$_[1]|;
259             }
260              
261             sub interwiki_link_default_processor {
262 0     0 0   my $tiki = shift;
263 0           my $link = shift;
264 0           my $word = shift;
265 0           $link =~ m/^(\w+?):(.*)$/;
266 0   0       my $interwiki = $tiki->{__interwiki_table}->{$1} || '';
267 0   0       return qq{}.($word||$2).qq{};
268             }
269              
270             #-- macro handling routines
271              
272 0     0 1   sub macros { $_[0]->{__macro_processing}=$_[1]; }
273              
274             {
275             my %hdlr_types = ( 'block' =>1, 'block_post' =>1, 'inline'=>1, 'inline_literal'=>1 );
276             sub macro_handler {
277 0     0 1   my ($tiki, $name, $code, $type) = @_;
278 0 0         unless ( defined( $hdlr_types{ $type } ) ) {
279 0           my @types = keys %hdlr_types;
280 0           die "Unknown macro handler type: $type\n Valid types: @types";
281             }
282 0           $tiki->{__macro_handlers}->{ $name } = { code => $code, type => $type };
283             }
284             }
285              
286 0     0 0   sub hdlr_macro_block { return $_[0]->macro_processor($_[1],'block'); }
287 0     0 0   sub hdlr_macro_block_postprocess { return $_[0]->macro_processor($_[1],'block_post'); }
288 0     0 0   sub hdlr_macro_inline { return $_[0]->inline_format( \$_[0]->macro_processor($_[1],'inline') ); }
289 0     0 0   sub hdlr_macro_inline_literal { return $_[0]->macro_processor($_[1],'inline_literal'); }
290              
291             sub macro_processor {
292 0     0 0   my $tiki = shift;
293 0           my $in = shift;
294 0           my $type = shift;
295 0           my $out;
296 0 0         $in = join ('', @{ $in } ) if ( ref($in) eq "ARRAY");
  0            
297 0           $in=~/^%%(.*)%%/;
298 0           my ($name, $attrib) = split(/\s/,$1,2);
299 0 0 0       if ( defined( $tiki->{__macro_handlers}->{ $name } ) &&
300             $tiki->{__macro_handlers}->{ $name }->{type} eq $type ) {
301 0           $out = $tiki->{__macro_handlers}->{ $name }->{code}->($tiki, $name, $attrib);
302             }
303 0 0 0       return ($out || $in).($type=~/^block/?"\n":'');
304             }
305              
306             #--- block handlers
307             # They get an array and pass decide whether to pass them on to be encoded or autolinked.
308              
309             sub hdlr_paragraph {
310 0     0 0   foreach my $line ( @{ $_[1] } ) {
  0            
311 0           $line = $_[0]->inline_format( \$line );
312             }
313 0           return '

'.join("
",@{ $_[1] } )."

\n";
  0            
314             }
315              
316 0     0 0   sub hdlr_blockquote { return "
\n" . $_[0]->block_format($_[1]) . "
\n"; }
317              
318             sub hdlr_pre {
319 0     0 0   my $tiki = shift;
320 0           foreach my $line ( @{ $_[0] } ) {
  0            
321 0           $line = $tiki->inline_format( \$line );
322             }
323 0           return "
\n". join("\n",@{ $_[0] }) ."\n
\n";
  0            
324             }
325              
326             sub hdlr_code_block {
327 0     0 0   my $line = join("\n",@{ $_[1] });
  0            
328 0           xml_encode( \$line );
329 0           return "
\n".$line."\n
\n";
330             }
331              
332 0     0 0   sub hdlr_hr { return '
'.join("
",@{ $_[1] } ); }
  0            
333 0     0 0   sub hdlr_h1 { return $_[0]->heading_processor($_[1]); }
334 0     0 0   sub hdlr_h2 { return $_[0]->heading_processor($_[1],'h2'); }
335 0     0 0   sub hdlr_h3 { return $_[0]->heading_processor($_[1],'h3'); }
336 0     0 0   sub hdlr_h4 { return $_[0]->heading_processor($_[1],'h4'); }
337 0     0 0   sub hdlr_h5 { return $_[0]->heading_processor($_[1],'h5'); }
338 0     0 0   sub hdlr_h6 { return $_[0]->heading_processor($_[1],'h6'); }
339 0     0 0   sub hdlr_ul { return $_[0]->list_processor($_[1]); }
340 0     0 0   sub hdlr_ol { return $_[0]->list_processor($_[1],'ol'); }
341              
342             sub hdlr_dl {
343 0     0 0   my $tiki = shift;
344 0           my $content = shift;
345 0           my $out="
\n";
346 0           foreach my $line ( @{ $content } ) {
  0            
347 0           $line=~s/([;:])[\s\t]//;
348             # Unlike other block handlers we do the striping because we have
349             # two types of elements within this type.
350 0 0         if ( $1 eq ':' ) {
351 0           my @array = ( $line);
352 0           $out .= "
\n".$tiki->block_format( \@array )."
\n";
353             } else {
354 0           $out .= '
'.$tiki->inline_format( \$line )."
\n";
355             }
356             }
357 0           return $out."\n";
358             } # http://www.w3.org/TR/2003/WD-xhtml2-20030506/mod-list.html#s_listmodule
359              
360             sub heading_processor {
361 0     0 0   my $tiki = shift;
362 0           my $heading = join("
", @{ $_[0] } );
  0            
363 0           my $id = join('', @{ $_[0] } );
  0            
364 0   0       my $level = $_[1] || 'h1';
365 0           $id =~ s/[^\s\w\d]+//g; # remove non alpha/ws characters
366 0           $id =~ s/\b([a-z])/\u\L$1/g; # proper case all lower case characters at start of words
367             # Using WikiWord format instead of Camel Case
368             # $id =~ s/^\s*([A-Z]+)/\L$1/; # but first word is all lowercase
369 0           $id =~ s/\s+//g; # Remove whitespace
370 0 0         if ($tiki->{__heading_names}->{$id}) {
371 0           my $count=2; # one is assumed.
372 0           while ($tiki->{__heading_names}->{$id.$count}) { $count++; }
  0            
373 0           $id.=$count;
374             }
375 0           $tiki->{__heading_names}->{$id}++;
376 0           return "<$level>".$tiki->inline_format(\$heading)."\n";
377             }
378              
379             sub list_processor {
380 0     0 0   my $tiki = shift;
381 0           my $content = shift;
382 0   0       my $list_type = shift || 'ul';
383 0           my $buffer;
384 0           my $index = 0;
385 0           $buffer->[$index]->{type}='_li';
386 0           foreach my $line ( @{ $content } ) {
  0            
387 0           my $symbol;
388 0 0         unless ( $line=~s/^([*#])([\s\t]|(?=[*#]))// ) {
389 0           $symbol = '_li';
390 0           } else { $symbol = $1; }
391 0 0         unless( $symbol eq $buffer->[$index]->{type} ) {
392 0           $index++; $buffer->[$index]->{buffer}=(); $buffer->[$index]->{type}=$symbol;
  0            
  0            
393             }
394 0           push(@{ $buffer->[$index]->{buffer} },$line);
  0            
395             }
396 0           my $out = "<$list_type>\n
  • ";
  • 397 0           my $started = 0;
    398 0           foreach my $buffer ( @{ $buffer } ) {
      0            
    399 0 0         if ($buffer->{type} eq '_li') {
    400 0           foreach my $line ( @{ $buffer->{buffer} } ) {
      0            
    401 0 0         $out .= $started ? "\n
  • " : '';
  • 402 0           $started++;
    403 0           $out .= $tiki->inline_format( \$line );
    404             }
    405 0 0         } else { $out .= "\n".$tiki->list_processor( $buffer->{buffer}, ( $buffer->{type} eq '*'?'ul':'ol' ) ); }
    406             }
    407 0           $out .= "\n";
    408 0           return $out."\n";
    409             }
    410              
    411             sub hdlr_table {
    412 0     0 0   my $tiki = shift;
    413 0           my $content = shift;
    414 0           my $out;
    415 0           foreach my $line (@{ $content }) {
      0            
    416 0           my $row='';
    417 0           my $colspan = 1;
    418 0 0         my $is_heading = 1 if $line=~s/^\|!/\|/;
    419 0           $line=~s/\|/ \|/g; # quick hack that inserts a space and get "blanks cells" to register.
    420 0           my @cells = split(/\|/,$line);
    421 0           foreach my $cell (reverse @cells) { # work backward to calculate colspans.
    422 0           $cell=~s!^($cell_alignment)!!;
    423 0 0         my $alignment = $cell_alignments{$1} if $1;
    424 0 0         if ( $cell ) { $cell=~s/^\s*//; $cell=~s/\s*$//; }# clean out leading and tailing whitespace.
      0            
      0            
    425 0 0         unless( $cell ) { $colspan++; }
      0            
    426             else {
    427 0 0         my $encoded = ( defined($is_heading)?'
        0          
        0          
    428             ( defined($alignment)?" align=\"$alignment\"":'' ) .
    429             ( $colspan>1?" colspan=\"$colspan\"":'' ) .
    430             '>';
    431 0           $encoded .= $tiki->inline_format( \$cell );
    432 0 0         $encoded .= (defined($is_heading)?'':'
    433 0           $row = $encoded . "\n" . $row;
    434 0           $colspan = 1;
    435             }
    436             }
    437 0           $out .= "
    438             }
    439 0           return "\n".$out."
    \n";
    440             }
    441              
    442              
    443             #--- inline handlers
    444             # Handler functions gets scalar (reference?) and is assumed to parse the string from the start and strip
    445             # is entirely from the referenced string. If nothing is found return undef in the first position.)
    446              
    447 0 0   0 0   sub hdlr_strong { return ${$_[1]}=~s/^\*(.+?)\*${term}//?('', $1,''):undef; }
      0            
    448 0 0   0 0   sub hdlr_emp { return ${$_[1]}=~s!^/(.+?)/${term}!!?('', $1,''):undef; }
      0            
    449 0 0   0 0   sub hdlr_insert { return ${$_[1]}=~s/^\+(.+?)\+${term}//?('', $1,''):undef; }
      0            
    450 0 0   0 0   sub hdlr_delete { return ${$_[1]}=~s/^\-(.+?)\b\-${term}//?('', $1,''):undef; } # added \b to fix "greedy problem."
      0            
    451 0 0   0 0   sub hdlr_subscript { return ${$_[1]}=~s/^\~(.+?)\~${term}//?('', $1,''):undef; }
      0            
    452 0 0   0 0   sub hdlr_superscript { return ${$_[1]}=~s/^\^(.+?)\^${term}//?('', $1,''):undef; }
      0            
    453 0 0   0 0   sub hdlr_quote { return ${$_[1]}=~s/^\"(.+?)\"${term}//?('', $1,''):undef; }
      0            
    454 0 0   0 0   sub hdlr_code_inline { return ${$_[1]}=~s/^\%(.+?)\%${term}//?('', $1,''):undef; }
      0            
    455 0 0   0 0   sub hdlr_cite { return ${$_[1]}=~s/^\@(.+?)\@${term}//?('', $1,''):undef; }
      0            
    456              
    457 0 0   0 0   sub hdlr_hyperlink { return ${$_[1]}=~s!^\[([^\]]*)\]:(.*?)${term}!!?("",$1,''):undef; }
      0            
    458 0 0   0 0   sub hdlr_image { return ${$_[1]}=~s!^\{([^\}]*)\}:(.*?)${term}!!?("\"$1\"/",'',''):undef; }
      0            
    459              
    460             sub hdlr_autolink {
    461 0     0 0   ${$_[1]}=~s#(?:^|(?<=\s))((?:$protocol):\S+[\w/])(?=${term})#$1#go;
      0            
    462 0           while ( ${$_[1]}=~m/(?:^|(?<=\s))((?:mailto:)?)([^\s",\@]*\@[^\s.,]+\.[^\s,]+)(?=${term})/ ) {
      0            
    463 0           my $matched = "$1$2";
    464 0           my ($prefix, $mailto) = ($1, $2 );
    465 0           $mailto=~s/\@/@/; $mailto=~s/\././g;
      0            
    466 0           my $email = $mailto;
    467 0           $email=~s/&/__AMP__/g; #uses amp markers to avoid encoding confusion in display.
    468 0           ${$_[1]}=~s!$matched!$prefix$email!gi
      0            
    469             }
    470             }
    471              
    472             sub hdlr_acronym {
    473 0     0 0   ${$_[1]}=~s!\b([A-Z][A-Z0-9]*)\(([^\)]+?)\)${term}!$1!go; # Needs to be more inclusive?
      0            
    474             }
    475              
    476             #--- encoding
    477              
    478             sub encoding_processor {
    479 0     0 0   my $line = shift;
    480 0           my $attribs = shift;
    481 0           my $c;
    482 0 0         my $tp = defined( $attribs->{typographic_processing} )?$attribs->{typographic_processing}:1;
    483 0           while ( $$line=~m/($not_in_markup)/g ) { #|<[^>]*\/>
    484 0           my $t=$1;
    485 0 0         unless ($t=~/^
    486 0           xml_encode(\$t);
    487 0 0         typographic_encode(\$t) if ($tp);
    488             }
    489 0           $c.=$t;
    490             }
    491 0           $$line=$c;
    492             }
    493              
    494             sub xml_encode { # Splitting this out is particularly helpful to CODE blocks.
    495 0     0 0   ${$_[0]}=~s/__LT__//g; # reverse markers.
      0            
      0            
      0            
    496 0           ${$_[0]}=~s!($RE)!$Map{$1}!g;
      0            
    497 0           ${$_[0]}=~s{([\xC0-\xDF].|[\xE0-\xEF]..|[\xF0-\xFF]...)}{xml_utf8_decode($1)}egs;
      0            
      0            
    498 0           ${$_[0]}=~s/__AMP__/&/g; #reverse amp spam protect markers
      0            
    499 0           return ${$_[0]}; # hack for CODE blocks.
      0            
    500             }
    501              
    502             sub xml_utf8_decode {
    503 0     0 0   my ($str, $hex) = @_;
    504 0           my $len = length ($str);
    505 0           my $n;
    506 0 0         if ($len == 2) {
        0          
        0          
        0          
    507 0           my @n = unpack "C2", $str;
    508 0           $n = (($n[0] & 0x3f) << 6) + ($n[1] & 0x3f);
    509             } elsif ($len == 3) {
    510 0           my @n = unpack "C3", $str;
    511 0           $n = (($n[0] & 0x1f) << 12) + (($n[1] & 0x3f) << 6) + ($n[2] & 0x3f);
    512             } elsif ($len == 4) {
    513 0           my @n = unpack "C4", $str;
    514 0           $n = (($n[0] & 0x0f) << 18) + (($n[1] & 0x3f) << 12)
    515             + (($n[2] & 0x3f) << 6) + ($n[3] & 0x3f);
    516 0           } elsif ($len == 1) { $n = ord ($str); # just to be complete...
    517 0           } else { warn "bad value [$str] for xml_utf8_decode"; }
    518 0 0         return $hex ? sprintf ("&#x%x;", $n) : "&#$n;";
    519             }
    520              
    521             sub typographic_encode {
    522 0     0 0   ${$_[0]}=~s!(^|(?<=\s))---(?=\s?)!—!go;
      0            
    523 0           ${$_[0]}=~s!(^|(?<=\s))--(?=\s?)!–!go;
      0            
    524 0           ${$_[0]}=~s!(^|\B)\.\.\.\b!…!go;
      0            
    525 0           ${$_[0]}=~s!\b\.\.\.(?=[\s\t]|$)!…!go;
      0            
    526 0           ${$_[0]}=~s!\(R\)(?=\s?)!®!go;
      0            
    527 0           ${$_[0]}=~s!\(TM\)(?=${term})!™!go;
      0            
    528 0           ${$_[0]}=~s!\(C\)(?=${term})!©!go;
      0            
    529 0           ${$_[0]}=~s!(^|(?<=\s))1/4(?=${term})!¼!go;
      0            
    530 0           ${$_[0]}=~s!(^|(?<=\s))1/2(?=${term})!½!go;
      0            
    531 0           ${$_[0]}=~s!(^|(?<=\s))3/4(?=${term})!¾!go;
      0            
    532 0           ${$_[0]}=~s!\b(\d+)\s?x\s?(\d+)\b!$1×$2!go;
      0            
    533             }
    534              
    535             1;
    536              
    537             __END__