File Coverage

blib/lib/HTML/Obj2HTML.pm
Criterion Covered Total %
statement 80 416 19.2
branch 45 256 17.5
condition 11 80 13.7
subroutine 15 41 36.5
pod 0 29 0.0
total 151 822 18.3


line stmt bran cond sub pod time code
1             package HTML::Obj2HTML;
2              
3             $HTML::Obj2HTML::VERSION = '0.11';
4              
5 1     1   510 use strict;
  1         2  
  1         24  
6 1     1   4 use warnings;
  1         2  
  1         30  
7              
8 1     1   5 use Carp;
  1         2  
  1         89  
9 1     1   425 use HTML::Entities;
  1         6729  
  1         107  
10 1     1   710 use Text::Markdown;
  1         39284  
  1         72  
11 1     1   538 use Text::Pluralize;
  1         580  
  1         53  
12 1     1   510 use Locale::Currency::Format;
  1         4009  
  1         137  
13 1     1   684 use List::MoreUtils qw(uniq);
  1         11599  
  1         10  
14 1     1   1532 use Module::Pluggable require => 1;
  1         9816  
  1         15  
15              
16             use constant {
17 1         4302 END_TAG_OPTIONAL => 0x0,
18             END_TAG_REQUIRED => 0x1,
19             END_TAG_FORBIDDEN => 0x2,
20             OBSOLETE => 0x4
21 1     1   102 };
  1         2  
22              
23             # storage is a sort of top-level stash for an object. It doesn't have to be used
24             # you can get away with building your own object variable and passing it to "gen"
25             my $storage = [];
26             # Opts are simple storage that could be referred to in extensions (not typically
27             # in the base class). A good example is "readonly" (true/false)
28             my %opt;
29             # extensions are stored here
30             my %extensions = ();
31             # snippets are stored here
32             my $snippets = {};
33             # A dictionary of substitutions are stored here and can be referenced in _content_
34             my %dictionary;
35              
36             my %dofiles;
37              
38             # Whether or not to close empty tags with /
39             my $mode = "XHTML";
40             # Whether or not to output a warning when something that doesn't look like a valid
41             # html 5 tag is used
42             my $warn_on_unknown_tag = 1;
43             # Whether or not to use HTML::FromArrayref format ("elementname {attributes} content" triplets)
44             my $html_fromarrayref_format = 0;
45             # Default currency to use
46             my $default_currency = "GBP";
47              
48             our $db;
49              
50             # Load up the extensions
51             plugins();
52              
53             sub import {
54 1     1   12 my @extra = ();
55 1         6 while (my $opt = shift) {
56 1 50       10 if ($opt eq "components") {
    50          
    50          
    50          
    50          
57 0         0 my $arg = shift;
58 0         0 foreach my $file (split("\n", `find $arg -name "*.po"`)) {
59 0         0 chomp($file);
60 0         0 my $l = $file;
61 0         0 $l =~ s/$arg\///;
62 0         0 $l =~ s/\.po$//;
63 0         0 $l =~ s/\//::/g;
64             #print STDERR "HTML::Obj2HTML registering component $l\n";
65             HTML::Obj2HTML::register_extension($l, {
66             tag => "",
67             before => sub {
68 0     0   0 my $o = shift;
69 0 0       0 if (ref $o eq "HASH") {
70 0         0 return HTML::Obj2HTML::fetch($file, $o);
71             } else {
72 0         0 return HTML::Obj2HTML::fetch($file, { _ => $o });
73             }
74             }
75 0         0 });
76             }
77             }
78             elsif ($opt eq "default_currency") {
79 0         0 $default_currency = shift;
80             }
81             elsif ($opt eq "mode") {
82 0         0 $mode = shift;
83             }
84             elsif ($opt eq "warn_on_unknown_tag") {
85 0         0 $warn_on_unknown_tag = shift;
86             }
87             elsif ($opt eq "html_fromarrayref_format") {
88 0         0 $html_fromarrayref_format = shift;
89             }
90             else {
91 1         38 push(@extra, $opt);
92             }
93             }
94             }
95              
96             my %tags = (
97             a => END_TAG_REQUIRED,
98             abbr => END_TAG_REQUIRED,
99             acronym => END_TAG_REQUIRED | OBSOLETE,
100             address => END_TAG_REQUIRED,
101             applet => END_TAG_REQUIRED | OBSOLETE,
102             area => END_TAG_FORBIDDEN,
103             article => END_TAG_REQUIRED,
104             aside => END_TAG_REQUIRED,
105             audio => END_TAG_REQUIRED,
106             b => END_TAG_REQUIRED,
107             base => END_TAG_FORBIDDEN,
108             basefont => END_TAG_FORBIDDEN | OBSOLETE,
109             bdi => END_TAG_REQUIRED,
110             bdo => END_TAG_REQUIRED,
111             big => END_TAG_REQUIRED | OBSOLETE,
112             blockquote => END_TAG_REQUIRED,
113             body => END_TAG_OPTIONAL,
114             br => END_TAG_FORBIDDEN,
115             button => END_TAG_REQUIRED,
116             canvas => END_TAG_REQUIRED,
117             caption => END_TAG_REQUIRED,
118             center => END_TAG_REQUIRED,
119             cite => END_TAG_REQUIRED,
120             code => END_TAG_REQUIRED,
121             col => END_TAG_FORBIDDEN,
122             colgroup => END_TAG_REQUIRED,
123             data => END_TAG_REQUIRED,
124             datalist => END_TAG_REQUIRED,
125             dd => END_TAG_OPTIONAL,
126             del => END_TAG_REQUIRED,
127             details => END_TAG_REQUIRED,
128             dfn => END_TAG_REQUIRED,
129             dialog => END_TAG_REQUIRED,
130             dir => END_TAG_REQUIRED | OBSOLETE,
131             div => END_TAG_REQUIRED,
132             dl => END_TAG_REQUIRED,
133             dt => END_TAG_OPTIONAL,
134             em => END_TAG_REQUIRED,
135             embed => END_TAG_FORBIDDEN,
136             fielset => END_TAG_REQUIRED,
137             figcaption => END_TAG_REQUIRED,
138             figure => END_TAG_REQUIRED,
139             font => END_TAG_REQUIRED | OBSOLETE,
140             footer => END_TAG_REQUIRED,
141             form => END_TAG_REQUIRED,
142             frame => END_TAG_FORBIDDEN | OBSOLETE,
143             frameset => END_TAG_REQUIRED | OBSOLETE,
144             head => END_TAG_OPTIONAL,
145             header => END_TAG_REQUIRED,
146             hgroup => END_TAG_REQUIRED,
147             h1 => END_TAG_REQUIRED,
148             h2 => END_TAG_REQUIRED,
149             h3 => END_TAG_REQUIRED,
150             h4 => END_TAG_REQUIRED,
151             h5 => END_TAG_REQUIRED,
152             h6 => END_TAG_REQUIRED,
153             hr => END_TAG_FORBIDDEN,
154             html => END_TAG_OPTIONAL,
155             i => END_TAG_REQUIRED,
156             iframe => END_TAG_REQUIRED,
157             img => END_TAG_FORBIDDEN,
158             input => END_TAG_FORBIDDEN,
159             ins => END_TAG_REQUIRED,
160             kbd => END_TAG_REQUIRED,
161             keygen => END_TAG_FORBIDDEN,
162             label => END_TAG_REQUIRED,
163             legend => END_TAG_REQUIRED,
164             li => END_TAG_REQUIRED,
165             link => END_TAG_FORBIDDEN,
166             main => END_TAG_REQUIRED,
167             map => END_TAG_REQUIRED,
168             mark => END_TAG_REQUIRED,
169             menu => END_TAG_REQUIRED,
170             menuitem => END_TAG_FORBIDDEN,
171             meta => END_TAG_FORBIDDEN,
172             meter => END_TAG_REQUIRED,
173             nav => END_TAG_REQUIRED,
174             noframes => END_TAG_REQUIRED | OBSOLETE,
175             noscript => END_TAG_REQUIRED,
176             object => END_TAG_REQUIRED,
177             ol => END_TAG_REQUIRED,
178             optgroup => END_TAG_REQUIRED,
179             option => END_TAG_OPTIONAL,
180             output => END_TAG_REQUIRED,
181             p => END_TAG_OPTIONAL,
182             param => END_TAG_FORBIDDEN,
183             picture => END_TAG_REQUIRED,
184             pre => END_TAG_REQUIRED,
185             progress => END_TAG_REQUIRED,
186             q => END_TAG_REQUIRED,
187             rp => END_TAG_REQUIRED,
188             rt => END_TAG_REQUIRED,
189             ruby => END_TAG_REQUIRED,
190             s => END_TAG_REQUIRED,
191             samp => END_TAG_REQUIRED,
192             script => END_TAG_REQUIRED,
193             section => END_TAG_REQUIRED,
194             select => END_TAG_REQUIRED,
195             small => END_TAG_REQUIRED,
196             source => END_TAG_FORBIDDEN,
197             span => END_TAG_REQUIRED,
198             strike => END_TAG_REQUIRED | OBSOLETE,
199             strong => END_TAG_REQUIRED,
200             style => END_TAG_REQUIRED,
201             sub => END_TAG_REQUIRED,
202             summary => END_TAG_REQUIRED,
203             sup => END_TAG_REQUIRED,
204             svp => END_TAG_REQUIRED,
205             table => END_TAG_REQUIRED,
206             tbody => END_TAG_REQUIRED,
207             td => END_TAG_REQUIRED,
208             template => END_TAG_REQUIRED,
209             textarea => END_TAG_REQUIRED,
210             tfoot => END_TAG_REQUIRED,
211             th => END_TAG_OPTIONAL,
212             thead => END_TAG_REQUIRED,
213             time => END_TAG_REQUIRED,
214             title => END_TAG_REQUIRED,
215             tr => END_TAG_OPTIONAL,
216             track => END_TAG_FORBIDDEN,
217             tt => END_TAG_REQUIRED | OBSOLETE,
218             u => END_TAG_REQUIRED,
219             ul => END_TAG_REQUIRED,
220             var => END_TAG_REQUIRED,
221             video => END_TAG_REQUIRED,
222             wbr => END_TAG_FORBIDDEN
223             );
224              
225             sub flush {
226 0     0 0 0 %opt = ();
227 0         0 %dictionary = ();
228 0         0 $snippets = {};
229             }
230             sub set_opt {
231 0     0 0 0 my $key = shift;
232 0         0 my $val = shift;
233 0         0 $opt{$key} = $val;
234             }
235             sub get_opt {
236 0     0 0 0 my $key = shift;
237 0         0 return $opt{$key};
238             }
239              
240             sub set_dbh {
241 0     0 0 0 $db = shift;
242             }
243              
244             sub set_dictionary {
245 0     0 0 0 my $hashref = shift;
246 0         0 %dictionary = %{$hashref};
  0         0  
247             }
248             sub add_dictionary_items {
249 0     0 0 0 my $hashref = shift;
250 0         0 %dictionary = (%dictionary, %{$hashref});
  0         0  
251             }
252              
253             sub set_snippet {
254 0     0 0 0 my $name = shift;
255 0         0 my $obj = shift;
256 0 0       0 if (!ref $obj) {
257 0         0 my $args = shift;
258 0         0 $obj = fetch($obj, $args);
259             }
260 0         0 $snippets->{$name} = $obj;
261             }
262             sub get_snippet {
263 0     0 0 0 my $name = shift;
264 0         0 return $snippets->{$name};
265             }
266             sub append_snippet {
267 0     0 0 0 my $name = shift;
268 0         0 my $obj = shift;
269 0 0       0 if (!defined $snippets->{$name}) {
    0          
270 0         0 $snippets->{$name} = [];
271             } elsif (ref $snippets->{$name} ne "ARRAY") {
272 0         0 return;
273             }
274 0 0       0 if (!ref $obj) {
275 0         0 my $args = shift;
276 0         0 $obj = fetch($obj, $args);
277             }
278 0         0 push(@{$snippets->{$name}}, $obj);
  0         0  
279             }
280              
281              
282              
283              
284             sub do {
285 0     0 0 0 HTML::Obj2HTML::print($storage);
286             }
287              
288             sub init {
289 0     0 0 0 $storage = shift;
290             }
291              
292             sub sort {
293 0     0 0 0 my $parentblock = shift;
294 0         0 my $sortsub = shift;
295 0         0 my $arr = shift;
296 0         0 my @ret = ();
297              
298 0         0 foreach my $c (sort { $sortsub->($a,$b) } @$arr) {
  0         0  
299 0         0 push(@ret, $parentblock, $c);
300             }
301 0         0 return \@ret;
302             }
303              
304             sub iterate {
305 0     0 0 0 my $parentblock = shift;
306 0         0 my $arr = shift;
307 0   0     0 my $collapsearrayrefs = shift || 0;
308 0         0 my @ret = ();
309              
310 0         0 foreach my $c (@$arr) {
311 0 0 0     0 if (ref($c) eq "ARRAY" && $collapsearrayrefs) {
    0          
312 0         0 my $itr = iterate($parentblock, $c);
313 0         0 push(@ret, @{$itr});
  0         0  
314             } elsif (defined $c) {
315 0         0 push(@ret, $parentblock, $c);
316             }
317             }
318 0         0 return \@ret;
319             }
320              
321             sub fetchraw {
322 0     0 0 0 my $f = shift;
323             # If we want an absolute path, use document root
324 0 0       0 if ($f =~ /\//) {
325 0         0 $f = $ENV{DOCUMENT_ROOT}.$f;
326             }
327             # And don't allow back-tracking through the file system!
328 0         0 $f =~ s|\/[\.\/]+|\/|;
329 0         0 my $rawfile;
330 0 0       0 if (-e $f) {
331 0         0 local($/);
332 0         0 open(RAWFILE, $f);
333 0         0 $rawfile = ;
334 0         0 close(RAWFILE);
335             }
336 0         0 return $rawfile;
337             }
338             sub fetch {
339 0     0 0 0 my $f = shift;
340 0         0 our $args = shift;
341 0         0 my $fetch;
342 0 0       0 if ($f !~ /^[\.\/]/) { $f = "./".$f; }
  0         0  
343 0 0       0 if (-e $f) {
344 0         0 $fetch = do($f);
345 0 0       0 if (!$fetch) {
346 0 0       0 if ($@) {
347 0         0 carp "Do failed for $f at error: $@\n";
348             }
349 0 0       0 if ($!) {
350 0         0 carp "Do failed for $f bang error: $!\n";
351             }
352             }
353 0 0       0 if (ref $fetch eq "CODE") {
354 0         0 $fetch = $fetch->($args);
355             }
356              
357             } else {
358 0         0 my $pwd = `pwd`;
359 0         0 chomp($pwd);
360 0         0 carp "Couldn't find $f ($pwd)\n";
361 0         0 return [];
362             }
363 0         0 return $fetch;
364             }
365             sub display {
366 0     0 0 0 my $f = shift;
367 0         0 my $args = shift;
368 0         0 my $ret = fetch($f, $args);
369 0         0 print gen($ret);
370             }
371             sub push {
372 0     0 0 0 my $arr = shift;
373 0         0 my $f = shift;
374 0         0 my $arg = shift;
375 0         0 my $ret = fetch($f,$arg);
376 0         0 push(@{$arr}, @{$ret});
  0         0  
  0         0  
377             }
378             sub append {
379 0     0 0 0 my $insertpoint = shift;
380 0         0 my $inserto = shift;
381 0         0 my $args = shift;
382 0 0 0     0 if (!ref $inserto && $inserto =~ /staticfile:(.*)/) {
    0 0        
383 0         0 $inserto = HTML::Obj2HTML::fetchraw($1);
384             } elsif (!ref $inserto && $inserto =~ /file:(.*)/) {
385 0         0 $inserto = fetch($1, $args);
386             }
387 0         0 my $o = find($storage, $insertpoint);
388 0         0 foreach my $e (@{$o}) {
  0         0  
389             # convert to common format
390 0 0       0 if (!ref $e->[1]) {
    0          
391 0         0 $e->[1] = { _ => [ _ => $e->[1] ] };
392             } elsif (ref $e->[1] eq "ARRAY") {
393 0         0 $e->[1] = { _ => $e->[1] };
394             }
395 0         0 CORE::push(@{$e->[1]->{_}}, @{$inserto});
  0         0  
  0         0  
396             }
397             }
398             sub prepend {
399 0     0 0 0 my $insertpoint = shift;
400 0         0 my $inserto = shift;
401 0         0 my $args = shift;
402 0 0 0     0 if (!ref $inserto && $inserto =~ /staticfile:(.*)/) {
    0 0        
403 0         0 $inserto = HTML::Obj2HTML::fetchraw($1);
404             } elsif (!ref $inserto && $inserto =~ /file:(.*)/) {
405 0         0 $inserto = fetch($1, $args);
406             }
407 0         0 my $o = find($storage, $insertpoint);
408 0         0 foreach my $e (@{$o}) {
  0         0  
409             # convert to common format
410 0 0       0 if (!ref $e->[1]) {
    0          
411 0         0 $e->[1] = { _ => [ _ => $e->[1] ] };
412             } elsif (ref $e->[1] eq "ARRAY") {
413 0         0 $e->[1] = { _ => $e->[1] };
414             }
415 0         0 unshift(@{$e->[1]->{_}}, @{$inserto});
  0         0  
  0         0  
416             }
417             }
418              
419             sub find {
420 0     0 0 0 my $o = shift;
421 0         0 my $query = shift;
422 0   0     0 my $ret = shift || [];
423              
424 0         0 my @tags = @{$o};
  0         0  
425 0         0 while (@tags) {
426 0         0 my $tag = shift(@tags);
427 0         0 my $attr = shift(@tags);
428              
429 0 0       0 if (ref $attr eq "ARRAY") {
    0          
430 0         0 find($attr, $query, $ret);
431             } elsif (ref $attr eq "HASH") {
432 0         0 my %attrs = %{$attr};
  0         0  
433 0         0 my $content;
434 0 0       0 if ($attrs{_}) {
435 0         0 find($attrs{_}, $query, $ret);
436             }
437 0 0 0     0 if ($query =~ /\#(.*)/ && $attrs{id} eq $1) {
    0 0        
438 0         0 CORE::push(@{$ret}, [$tag, $attr]);
  0         0  
439             } elsif ($query =~ /^([^\#\.]\S*)/ && $tag eq $1) {
440 0         0 CORE::push(@{$ret}, [$tag, $attr]);
  0         0  
441             }
442             }
443             }
444 0         0 return $ret;
445             }
446              
447             sub gen {
448 4     4 0 90 my $o = shift;
449 4         6 my $ret = "";
450              
451 4 50       11 if (!ref $o) {
452 0         0 $o = web_escape($o);
453 0         0 return $o;
454             }
455 4 50       9 if (ref $o eq "HASH") {
456 0         0 carp "HTML::Obj2HTML::gen called with a hash reference!";
457 0         0 return "";
458             }
459 4 50       9 if (ref $o eq "CODE") {
460 0         0 eval {
461 0         0 $ret = HTML::Obj2HTML::gen($o->());
462             };
463 0 0       0 if ($@) { carp "Error parsing HTML::Obj2HTML objects when calling code ref: $@\n"; }
  0         0  
464 0         0 return $ret;
465             }
466 4 50       8 if (ref $o ne "ARRAY") {
467 0         0 return "";
468             }
469              
470 4         6 my @tags = @{$o};
  4         9  
471 4         10 while (@tags) {
472 6         8 my $tag = shift(@tags);
473 6 50       13 if (!defined $tag) {
474 0         0 next;
475             }
476 6 50       12 if (ref $tag eq "ARRAY") {
477 0         0 $ret .= HTML::Obj2HTML::gen($tag);
478 0         0 next;
479             }
480 6 50       13 if (ref $tag eq "CODE") {
481 0         0 eval {
482 0         0 $ret .= HTML::Obj2HTML::gen($tag->());
483             };
484 0 0       0 if ($@) { carp "Error parsing HTML::Obj2HTML objects when calling code ref: $@\n"; }
  0         0  
485 0         0 next;
486             }
487 6 50       12 if ($tag =~ /_(.+)/) {
488 0         0 $ret .= HTML::Obj2HTML::gen(get_snippet($1));
489 0         0 next;
490             }
491             # If the tag has a space it's not a valid tag, so output it as content instead
492 6 50       14 if ($tag =~ /\s/) {
493 0         0 $ret .= $tag;
494 0         0 next;
495             }
496              
497 6         8 my $attr = shift(@tags);
498 6 50       11 if ($html_fromarrayref_format) {
499             # Make this module behave more like HTML::FromArrayref, where you have elementname, { attributes }, content
500             # This should be considered for backward compatibility; The find routine would struggle with this...
501 0 0 0     0 if (ref $attr eq "HASH" && ($tags{$tag} & END_TAG_FORBIDDEN) == 0) {
502 0         0 $attr->{"_"} = shift(@tags);
503             }
504             }
505             # Typically linking to another file will return an arrayref, but could equally return a hashref to also set the
506             # attributes of the element calling it
507 6 50 66     42 if (!ref $attr && $attr =~ /staticfile:(.+)/) {
    50 66        
    50 66        
508 0         0 $attr = HTML::Obj2HTML::fetchraw($1);
509             } elsif (!ref $attr && $attr =~ /file:(.+)/) {
510 0         0 $attr = HTML::Obj2HTML::fetch($1);
511             } elsif (!ref $attr && $attr =~ /raw:(.+)/) {
512 0         0 $attr = HTML::Obj2HTML::fetchraw($1);
513             }
514              
515             # Run the current tag through extentions
516 6         8 my $origtag = $tag;
517 6 50       14 if (defined $extensions{$origtag}) {
518 0 0 0     0 if (defined $extensions{$origtag}->{scalarattr} && !ref $attr) { $attr = { $extensions{$origtag}->{scalarattr} => $attr }; }
  0         0  
519              
520 0 0       0 if (defined $extensions{$origtag}->{before}) {
521 0         0 my $o = $extensions{$origtag}->{before}($attr);
522 0 0       0 if (ref $o eq "ARRAY") {
    0          
523 0         0 $ret .= HTML::Obj2HTML::gen($o);
524             } elsif (ref $o eq "") {
525 0         0 $ret .= $o;
526             }
527             }
528              
529 0 0       0 if (defined $extensions{$origtag}->{tag}) {
530 0         0 $tag = $extensions{$origtag}->{tag};
531             }
532 0 0       0 if (defined $extensions{$origtag}->{attr}) {
533 0 0       0 if (ref $attr ne "HASH") {
534 0         0 $attr = { _ => $attr };
535             }
536 0         0 foreach my $k (keys %{$extensions{$origtag}->{attr}}) {
  0         0  
537 0 0       0 if (defined $attr->{$k}) {
538 0         0 $attr->{$k} = $extensions{$origtag}->{attr}->{$k}." ".$attr->{$k};
539 0 0       0 if ($k eq "class") {
540 0         0 $attr->{$k} = join(" ", uniq(split(/\s+/, $attr->{$k})));
541             }
542             } else {
543 0         0 $attr->{$k} = $extensions{$origtag}->{attr}->{$k};
544             }
545             }
546             }
547              
548 0 0       0 if (defined $extensions{$origtag}->{replace}) {
549 0         0 my $o = HTML::Obj2HTML::gen($extensions{$origtag}->{replace}($attr));
550 0 0       0 if (ref $o eq "HASH") {
    0          
551 0         0 $ret .= HTML::Obj2HTML::gen($o);
552             } elsif (ref $o eq "") {
553 0         0 $ret .= $o;
554             }
555 0         0 $tag = "";
556             }
557             }
558              
559             # Non-HTML functions
560 6 50 33     55 if ($tag eq "_") {
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
561 0 0       0 if (ref $attr) {
562 0         0 carp("HTML::Obj2HTML: _ element called, but attr wasn't a scalar.");
563             } else {
564 0         0 $ret .= web_escape($attr);
565             }
566              
567             } elsif ($tag eq "raw") {
568 0 0       0 if (ref $attr) {
569 0         0 carp("HTML::Obj2HTML: raw element called, but attr wasn't a scalar.");
570             } else {
571 0         0 $ret .= "$attr";
572             }
573              
574             } elsif ($tag eq "if") {
575 0 0       0 if (ref $attr eq "HASH") {
    0          
576 0 0 0     0 if ($attr->{cond} && $attr->{true}) {
    0 0        
577 0         0 $ret .= HTML::Obj2HTML::gen($attr->{true});
578             } elsif (!$attr->{cond} && $attr->{false}) {
579 0         0 $ret .= HTML::Obj2HTML::gen($attr->{false});
580             }
581             } elsif (ref $attr eq "ARRAY") {
582 0         0 for (my $i = 0; $i<$#{$attr}; $i+=2) {
  0         0  
583 0 0       0 if ($attr->[$i]) {
584 0         0 $ret .= HTML::Obj2HTML::gen($attr->[$i+1]);
585 0         0 last;
586             }
587             }
588             } else {
589 0         0 carp("HTML::Obj2HTML: if element called, but attr wasn't a hash ref or array ref.");
590             }
591             } elsif ($tag eq "switch") {
592 0 0       0 if (ref $attr eq "HASH") {
593 0 0       0 if (defined $attr->{$attr->{val}}) {
    0          
    0          
594 0         0 $ret .= HTML::Obj2HTML::gen($attr->{$attr->{val}});
595             } elsif (defined $attr->{"_default"}) {
596 0         0 $ret .= HTML::Obj2HTML::gen($attr->{"_default"});
597             } elsif (defined $attr->{"_"}) {
598 0         0 $ret .= HTML::Obj2HTML::gen($attr->{"_"});
599             }
600             } else {
601 0         0 carp("HTML::Obj2HTML: switch element called, but attr wasn't a hash ref.");
602             }
603              
604             } elsif ($tag eq "md") {
605 0 0       0 if (ref $attr) {
606 0         0 carp("HTML::Obj2HTML: md element called, but attr wasn't a scalar.");
607             } else {
608 0         0 $ret .= markdown($attr);
609             }
610              
611             } elsif ($tag eq "plain") {
612 0 0       0 if (ref $attr) {
613 0         0 carp("HTML::Obj2HTML: plain element called, but attr wasn't a scalar.");
614             } else {
615 0         0 $ret .= plain($attr);
616             }
617              
618             } elsif ($tag eq "currency") {
619 0 0       0 if (ref $attr eq "HASH") {
    0          
620 0   0     0 $ret .= web_escape(currency_format($attr->{currency} || $default_currency, $attr->{"_"}, FMT_SYMBOL));
621             } elsif (!ref $attr) {
622 0         0 $ret .= web_escape(currency_format($default_currency, $attr, FMT_SYMBOL));
623             } else {
624 0         0 carp("HTML::Obj2HTML: currency called, but attr wasn't a hash ref or plain scalar.");
625             }
626              
627             } elsif ($tag eq "pluralize") {
628 0 0       0 if (ref $attr eq "ARRAY") {
629 0         0 $ret .= pluralize($attr->[0], $attr->[1]);
630             } else {
631 0         0 carp("HTML::Obj2HTML: pluralize called, but attr wasn't a array ref");
632             }
633              
634             } elsif ($tag eq "include") {
635 0         0 $ret .= HTML::Obj2HTML::gen(HTML::Obj2HTML::fetch($o->{src}.".po", $attr));
636              
637             } elsif ($tag eq "javascript") {
638 0         0 $ret .= "";
639              
640             } elsif ($tag eq "includejs") {
641 0 0       0 if (!ref $attr) {
    0          
642 0         0 $ret .= "";
643             } elsif (ref $attr == "HASH") {
644 0         0 $ret .= "";
652             }
653              
654              
655             } elsif ($tag eq "includecss") {
656 0         0 $ret .= "";
657              
658             } elsif ($tag eq "doctype") {
659 1         5 $ret .= "";
660              
661             } elsif (ref $attr eq "HASH" && defined $attr->{removeif} && $attr->{removeif}) {
662 0         0 $ret .= HTML::Obj2HTML::gen($attr->{_});
663              
664             # Finally through all the non-HTML elements ;)
665             } elsif ($tag) {
666              
667             # It's perfectly allowed to omit content from a tag where the end tag was forbidden
668             # If we have content, we have to assume that it should appear after the
669             # tag - not discard it, or show it within
670             # If we've got a hash ref though, we have attributes :)
671             # Note that this has to go here, because the attribute might have been a staticfile: or similar
672             # to execute some additional code
673 5 50 33     17 if ($tags{$tag} & END_TAG_FORBIDDEN && ref $attr ne "HASH") {
674 0         0 unshift(@tags, $attr);
675 0         0 $attr = undef;
676             }
677              
678 5 50 33     17 if ($warn_on_unknown_tag && !defined $tags{$tag}) {
679 0         0 carp "Warning: Unknown tag $tag in HTML::Obj2HTML\n";
680             }
681              
682 5         9 $ret .= "<$tag";
683 5 50       20 if (!defined $attr) {
    100          
    50          
    50          
    50          
684 0 0       0 if ($tags{$tag} & END_TAG_FORBIDDEN) {
    0          
685 0 0       0 if ($mode eq "XHTML") {
686 0         0 $ret .= " />";
687             } else {
688 0         0 $ret .= ">";
689             }
690             } elsif ($tags{$tag} & END_TAG_REQUIRED) {
691 0         0 $ret .= ">";
692             }
693              
694             } elsif (ref $attr eq "ARRAY") {
695 3         5 $ret .= ">";
696 3         40 $ret .= HTML::Obj2HTML::gen($attr);
697 3         6 $ret .= "";
698              
699             } elsif (ref $attr eq "HASH") {
700 0         0 my %attrs = %{$attr};
  0         0  
701 0         0 my $content;
702 0         0 foreach my $k (keys(%attrs)) {
703 0 0       0 if (ref $k eq "ARRAY") {
    0          
    0          
    0          
704 0         0 $content = $k;
705              
706             } elsif (ref $attrs{$k} eq "ARRAY") {
707             # shorthand, you can defined the content within the classname, e.g. div => { "ui segment" => [ _ => "Content" ] }
708 0 0       0 if ($k ne "_") {
709 0         0 $ret .= format_attr("class", $k);
710             }
711 0   0     0 $content = $attrs{$k} || '';
712              
713             } elsif (ref $attrs{$k} eq "HASH") {
714              
715 0 0       0 if ($k eq "style") {
    0          
    0          
716 0         0 my @styles = ();
717 0         0 while (my ($csskey, $cssval) = each(%{$attrs{$k}})) {
  0         0  
718 0         0 CORE::push(@styles, $csskey.":".$cssval.";");
719             }
720 0         0 $ret .= format_attr("style", join("",@styles));
721             } elsif ($k eq "if") {
722 0         0 my $val = $attrs{$k};
723 0 0 0     0 if ($val->{cond} && $val->{true}) {
    0 0        
724 0         0 foreach my $newk (keys(%{$val->{true}})) {
  0         0  
725 0         0 $ret .= format_attr($newk, $val->{true}->{$newk})
726             }
727             } elsif (!$val->{cond} && $val->{false}) {
728 0         0 foreach my $newk (keys(%{$val->{false}})) {
  0         0  
729 0         0 $ret .= format_attr($newk, $val->{false}->{$newk})
730             }
731             }
732             } elsif (defined $attrs{$k}->{if}) {
733 0 0 0     0 if ($attrs{$k}->{if} && defined $attrs{$k}->{true}) {
    0 0        
734 0         0 $ret .= format_attr($k, $attrs{$k}->{true});
735             } elsif (!$attrs{$k}->{if} && defined $attrs{$k}->{false}) {
736 0         0 $ret .= format_attr($k, $attrs{$k}->{false});
737             }
738             }
739              
740             } elsif ($k eq "_") {
741 0   0     0 $content = $attrs{$k} || '';
742              
743             } else {
744 0         0 $ret .= format_attr($k, $attrs{$k});
745             }
746             }
747 0 0       0 if ($tags{$tag} & END_TAG_FORBIDDEN) {
    0          
    0          
748             # content is also forbidden!
749 0 0       0 if ($mode eq "XHTML") {
750 0         0 $ret .= " />";
751             } else {
752 0         0 $ret .= ">";
753             }
754             } elsif (defined $content) {
755 0         0 $ret .= ">";
756 0         0 $ret .= HTML::Obj2HTML::gen($content);
757 0         0 $ret .= "";
758             } elsif ($tags{$tag} & END_TAG_REQUIRED) {
759 0         0 $ret .= ">";
760             } else {
761 0 0       0 if ($mode eq "XHTML") {
762 0         0 $ret .= " />";
763             } else {
764 0         0 $ret .= ">";
765             }
766             }
767             } elsif (ref $attr eq "CODE") {
768 0         0 $ret .= ">";
769 0         0 eval {
770 0         0 $ret .= gen($attr->());
771             };
772 0         0 $ret .= "";
773 0 0       0 if ($@) { warn "Error parsing HTML::Obj2HTML objects when calling code ref: $@\n"; }
  0         0  
774             } elsif (ref $attr eq "") {
775 2         13 my $val = web_escape($attr);
776 2         8 $ret .= ">$val";
777             }
778             }
779              
780 6 50 33     20 if (defined $extensions{$origtag} && defined $extensions{$origtag}->{after}) {
781 0         0 $ret .= $extensions{$origtag}->{after}($attr);
782             }
783              
784             }
785 4         9 return $ret;
786             }
787              
788             sub format_attr {
789 0     0 0 0 my $k = shift;
790 0         0 my $val = shift;
791 0         0 $val = web_escape($val);
792 0 0       0 if (defined $val) {
793 0         0 return " $k=\"$val\"";
794             }
795 0         0 return "";
796             }
797             sub substitute_dictionary {
798 2     2 0 28 my $val = shift;
799 2         4 $val =~ s/%([A-Za-z0-9]+)%/$dictionary{$1}/g;
800 2         3 return $val;
801             }
802             sub web_escape {
803 2     2 0 5 my $val = shift;
804 2         9 $val = HTML::Entities::encode($val);
805 2         37 $val = substitute_dictionary($val);
806 2         19 return $val;
807             }
808             sub plain {
809 0     0 0 0 my $txt = shift;
810 0         0 $txt = web_escape($txt);
811 0         0 $txt =~ s|\n|
|g;
812 0         0 return $txt;
813             }
814             sub markdown {
815 0     0 0 0 my $txt = shift;
816 0         0 $txt = substitute_dictionary($txt);
817 0         0 my $m = new Text::Markdown;
818 0         0 my $val = $m->markdown($txt);
819 0         0 return $val;
820             }
821              
822             sub print {
823 0     0 0 0 my $o = shift;
824 0         0 print gen($o);
825             }
826              
827             sub format {
828 0     0 0 0 my $plain = shift;
829 0         0 $plain =~ s/\n/
/g;
830 0         0 return [ raw => $plain ];
831             }
832              
833             sub register_extension {
834 39     39 0 55 my $tag = shift;
835 39         43 my $def = shift;
836 39         37 my $flags = shift;
837 39         79 $extensions{$tag} = $def;
838 39 50       58 if (defined $flags) {
839 0         0 $tags{$tag} = $flags;
840             } else {
841 39         71 $tags{$tag} = END_TAG_OPTIONAL;
842             }
843             }
844              
845             1;
846             __END__