Added documentation for --embed-css in the utility script.
[grutatxt.git] / Grutatxt.pm
blob84a6f85f8cdda9a5ab36025cb3ecd2a7177383bd
1 #####################################################################
3 # Grutatxt - A text to HTML (and other things) converter
5 # Copyright (C) 2000/2003 Angel Ortega <angel@triptico.com>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 # http://www.triptico.com
23 #####################################################################
25 package Grutatxt;
27 use locale;
29 $VERSION = '2.0.5';
31 =pod
33 =head1 NAME
35 Grutatxt - Text to HTML (and other formats) converter
37 =head1 SYNOPSIS
39 use Grutatxt;
41 # create a new Grutatxt converter object
42 $grutatxt=new Grutatxt();
44 # process a Grutatxt format string
45 @output=$grutatxt->process($text);
47 # idem for a file
48 @output2=$grutatxt->process_file($file);
50 =head1 DESCRIPTION
52 Grutatxt is a module to process text documents in
53 a special markup format (also called Grutatxt), very
54 similar to plain ASCII text. These documents can be
55 converted to HTML or troff.
57 The markup is designed to be fairly intuitive and
58 straightforward and can include headings, bold and italic
59 text effects, bulleted, numbered and definition lists, URLs,
60 function and variable names, preformatted text, horizontal
61 separators and tables. Special marks can be inserted in the
62 text and a heading-based structural index can be obtained
63 from it.
65 A comprehensive description of the markup is defined in
66 the README file, included with the Grutatxt package (it is
67 written in Grutatxt format itself, so it can be converted
68 using the I<grutatxt> tool to any of the supported formats).
69 The latest version (and more information) can be retrieved
70 from the Grutatxt home page at:
72 http://www.triptico.com/software/grutatxt.html
74 =head1 FUNCTIONS AND METHODS
76 =head2 B<new>
78 $grutatxt=new Grutatxt([ "mode" => $mode, ]
79 [ "title" => \$title, ]
80 [ "marks" => \@marks, ]
81 [ "index" => \@index, ]
82 [ "abstract" => \$abstract, ]
83 [ "strip-parens" => $bool, ]
84 [ "strip-dollars" => $bool, ]
85 [ %driver_specific_arguments ] );
87 Creates a new Grutatxt object instance. All parameters are
88 optional.
90 =over 4
92 =item I<mode>
94 Output format. Can be HTML or troff. HTML is used if not specified.
96 =item I<title>
98 If I<title> is specified as a reference to scalar, the first
99 level 1 heading found in the text is stored inside it.
101 =item I<marks>
103 Marks in the Grutatxt markup are created by inserting the
104 string <-> alone in a line. If I<marks> is specified as a
105 reference to array, it will be filled with the subscripts
106 (relative to the output array) of the lines where the marks
107 are found in the text.
109 =item I<index>
111 If I<index> is specified as a reference to array, it will
112 be filled with strings in the format
114 level,heading
116 This information can be used to build a table of contents
117 of the processed text.
119 =item I<strip-parens>
121 Function names in the Grutatxt markup are strings of
122 alphanumeric characters immediately followed by a pair
123 of open and close parentheses. If this boolean value is
124 set, function names found in the processed text will have
125 their parentheses deleted.
127 =item I<strip-dollars>
129 Variable names in the Grutatxt markup are strings of
130 alphanumeric characters preceded by a dollar sign.
131 If this boolean value is set, variable names found in
132 the processed text will have the dollar sign deleted.
134 =item I<abstract>
136 The I<abstract> of a Grutatxt document is the fragment of text
137 from the beginning of the document to the end of the first
138 paragraph after the title. If I<abstract> is specified as a
139 reference to scalar, it will contain (after each call to the
140 B<process()> method) the subscript of the element of the output
141 array that marks the end of the subject.
143 =back
145 =cut
147 sub new
149 my ($class,%args) = @_;
150 my ($gh);
152 $args{'mode'} ||= 'HTML';
154 $class .= "::" . $args{'mode'};
156 $gh = new $class(%args);
158 return($gh);
162 =head2 B<process>
164 @output=$grutatxt->process($text);
166 Processes a text in Grutatxt format. The result is returned
167 as an array of lines.
169 =cut
171 sub process
173 my ($gh,$content) = @_;
174 my ($p);
176 # clean output
177 @{$gh->{'o'}} = ();
179 # clean title and paragraph numbers
180 $gh->{'-title'} = "";
181 $gh->{'-p'} = 0;
183 # clean marks
184 @{$gh->{'marks'}} = () if ref($gh->{'marks'});
186 # clean index
187 @{$gh->{'index'}} = () if ref($gh->{'index'});
189 # reset abstract line
190 ${$gh->{'abstract'}} = 0 if ref($gh->{'abstract'});
192 # insert prefix
193 $gh->_prefix();
195 $gh->{'-mode'} = undef;
197 foreach my $l (split(/\n/,$content))
199 # inline data (passthrough)
200 if($l =~ /^<<$/ .. $l =~ /^>>$/)
202 $gh->_inline($l);
203 next;
206 # marks
207 if($l =~ /^\s*<\->\s*$/)
209 push(@{$gh->{'marks'}},scalar(@{$gh->{'o'}}))
210 if ref($gh->{'marks'});
212 next;
215 # escape possibly dangerous characters
216 $l = $gh->_escape($l);
218 # empty lines
219 $l =~ s/^\r$//ge;
220 if($l =~ s/^$/$gh->_empty_line()/ge)
222 # mark the abstract end
223 if($gh->{'-title'})
225 $gh->{'-p'}++;
227 # mark abstract if it's the
228 # second paragraph from the title
229 ${$gh->{'abstract'}} = scalar(@{$gh->{'o'}})-1
230 if $gh->{'-p'} == 2;
234 if($gh->{'-process-urls'})
236 # URLs followed by a parenthesized phrase
237 $l =~ s/(https?:\/\/\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
238 $l =~ s/(ftps?:\/\/\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
239 $l =~ s/(file:\/?\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
241 # URLs without phrase
242 $l =~ s/([^=][^\"])(https?:\/\/\S+)/$1.$gh->_url($2)/ge;
243 $l =~ s/([^=][^\"])(ftps?:\/\/\S+)/$1.$gh->_url($2)/ge;
244 $l =~ s/([^=][^\"])(file:\/?\S+)/$1.$gh->_url($2)/ge;
245 $l =~ s/^(https?:\/\/\S+)/$gh->_url($1)/ge;
246 $l =~ s/^(ftps?:\/\/\S+)/$gh->_url($1)/ge;
247 $l =~ s/^(file:\/?\S+)/$gh->_url($1)/ge;
250 # change '''text''' and *text* into strong emphasis
251 $l =~ s/\'\'\'([^\'][^\'][^\']*)\'\'\'/$gh->_strong($1)/ge;
252 $l =~ s/\*(\S[^\*]+\S)\*/$gh->_strong($1)/ge;
253 $l =~ s/\*(\S+)\*/$gh->_strong($1)/ge;
255 # change ''text'' and _text_ into emphasis
256 $l =~ s/\'\'([^\'][^\']*)\'\'/$gh->_em($1)/ge;
257 $l =~ s/\b_(\S[^_]*\S)_\b/$gh->_em($1)/ge;
258 $l =~ s/\b_(\S+)_\b/$gh->_em($1)/ge;
260 # enclose function names
261 if($gh->{'strip-parens'})
263 $l =~ s/(\w+)\(\)/$gh->_funcname($1)/ge;
265 else
267 $l =~ s/(\w+)\(\)/$gh->_funcname($1."()")/ge;
270 # enclose variable names
271 if($gh->{'strip-dollars'})
273 $l =~ s/\$([\w_\.]+)/$gh->_varname($1)/ge;
275 else
277 $l =~ s/(\$[\w_\.]+)/$gh->_varname($1)/ge;
281 # main switch
284 # definition list
285 if($l =~ s/^\s\*\s+([\w\s\-\(\)]+)\:\s+/$gh->_dl($1)/e)
289 # unsorted list
290 elsif($gh->{'-mode'} ne "pre" and
291 ($l =~ s/^(\s+)\*\s+/$gh->_unsorted_list($1)/e or
292 $l =~ s/^(\s+)\-\s+/$gh->_unsorted_list($1)/e))
296 # sorted list
297 elsif($gh->{'-mode'} ne "pre" and
298 ($l =~ s/^(\s+)\#\s+/$gh->_ordered_list($1)/e or
299 $l =~ s/^(\s+)1\s+/$gh->_ordered_list($1)/e))
303 # quoted block
304 elsif($l =~ s/^\s\"/$gh->_blockquote()/e)
308 # table rows
309 elsif($l =~ s/^\s*\|(.*)\|\s*$/$gh->_table_row($1)/e)
313 # table heading / end of row
314 elsif($l =~ s/^\s*(\+[-\+\|]+\+)\s*$/$gh->_table($1)/e)
318 # preformatted text
319 elsif($l =~ s/^(\s.*)$/$gh->_pre($1)/e)
323 # anything else
324 else
326 # back to normal mode
327 $gh->_new_mode(undef);
330 # 1 level heading
331 $l =~ s/^(=+)\s*$/$gh->_process_heading(1,$1)/e;
333 # 2 level heading
334 $l =~ s/^(-+)\s*$/$gh->_process_heading(2,$1)/e;
336 # 3 level heading
337 $l =~ s/^(~+)\s*$/$gh->_process_heading(3,$1)/e;
339 # change ------ into hr
340 $l =~ s/^----*$/$gh->_hr()/e;
342 # push finally
343 $gh->_push($l) if $l;
346 # flush
347 $gh->_new_mode(undef);
349 # postfix
350 $gh->_postfix();
352 # set title
353 ${$gh->{'title'}} = $gh->{'-title'} if ref($gh->{'title'});
355 # set abstract, if not set
356 ${$gh->{'abstract'}} = scalar(@{$gh->{'o'}})
357 if ref($gh->{'abstract'}) and not ${$gh->{'abstract'}};
359 return(@{$gh->{'o'}});
363 =head2 B<process_file>
365 @output=$grutatxt->process_file($filename);
367 Processes a file in Grutatxt format.
369 =cut
371 sub process_file
373 my ($gh,$file) = @_;
375 open F, $file or return(undef);
377 my ($content) = join('',<F>);
378 close F;
380 return($gh->process($content));
384 sub _push
386 my ($gh,$l) = @_;
388 push(@{$gh->{'o'}},$l);
392 sub _process_heading
394 my ($gh,$level,$hd) = @_;
395 my ($l);
397 $l = pop(@{$gh->{'o'}});
399 if($l eq $gh->_empty_line())
401 $gh->_push($l);
402 return($hd);
405 # store title
406 $gh->{'-title'} = $l if $level == 1 and not $gh->{'-title'};
408 # store index
409 if(ref($gh->{'index'}))
411 push(@{$gh->{'index'}},"$level,$l");
414 return($gh->_heading($level,$l));
418 sub _calc_col_span
420 my ($gh,$l) = @_;
421 my (@spans);
423 # strip first + and all -
424 $l =~ s/^\+//;
425 $l =~ s/-//g;
427 my ($t) = 1; @spans = ();
428 for(my $n = 0;$n < length($l);$n++)
430 if(substr($l,$n,1) eq '+')
432 push(@spans,$t);
433 $t = 1;
435 else
437 # it's a colspan mark:
438 # increment
439 $t++;
443 return(@spans);
447 sub _table_row
449 my ($gh,$str) = @_;
451 my @s = split(/\|/,$str);
453 for(my $n = 0;$n < scalar(@s);$n++)
455 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
458 return("");
462 sub _pre
464 my ($gh,$l) = @_;
466 # if any other mode is active, add to it
467 if($gh->{'-mode'} and $gh->{'-mode'} ne "pre")
469 $l =~ s/^\s+//;
471 my ($a) = pop(@{$gh->{'o'}})." ".$l;
472 $gh->_push($a);
473 $l = "";
475 else
477 $gh->_new_mode("pre");
480 return($l);
484 sub _multilevel_list
486 my ($gh, $str, $ind) = @_;
487 my (@l,$level);
489 @l = @{$gh->{$str}};
490 $ind = length($ind);
491 $level = 0;
493 if($l[-1] < $ind)
495 # if last level is less indented, increase
496 # nesting level
497 push(@l, $ind);
498 $level++;
500 elsif($l[-1] > $ind)
502 # if last level is more indented, decrease
503 # levels until the same is found (or back to
504 # the beginning if not)
505 while(pop(@l))
507 $level--;
508 last if $l[-1] == $ind;
512 $gh->{$str} = \@l;
514 return($level);
518 sub _unsorted_list
520 my ($gh, $ind) = @_;
522 return($gh->_ul($gh->_multilevel_list('-ul-levels', $ind)));
526 sub _ordered_list
528 my ($gh, $ind) = @_;
530 return($gh->_ol($gh->_multilevel_list('-ol-levels', $ind)));
534 # empty stubs for falling through the superclass
536 sub _inline { my ($gh,$l) = @_; $l; }
537 sub _escape { my ($gh,$l) = @_; $l; }
538 sub _empty_line { my ($gh) = @_; ""; }
539 sub _url { my ($gh,$url,$label) = @_; ""; }
540 sub _strong { my ($gh,$str) = @_; $str; }
541 sub _em { my ($gh,$str) = @_; $str; }
542 sub _funcname { my ($gh,$str) = @_; $str; }
543 sub _varname { my ($gh,$str) = @_; $str; }
544 sub _new_mode { my ($gh,$mode) = @_; }
545 sub _dl { my ($gh,$str) = @_; $str; }
546 sub _ul { my ($gh,$level) = @_; ""; }
547 sub _ol { my ($gh,$level) = @_; ""; }
548 sub _blockquote { my ($gh,$str) = @_; $str; }
549 sub _hr { my ($gh) = @_; "" }
550 sub _heading { my ($gh,$level,$l) = @_; $l; }
551 sub _table { my ($gh,$str) = @_; $str; }
552 sub _prefix { my ($gh) = @_; }
553 sub _postfix { my ($gh) = @_; }
555 ###########################################################
557 =head1 DRIVER SPECIFIC INFORMATION
559 =cut
561 ###########################################################
562 # HTML Driver
564 package Grutatxt::HTML;
566 @ISA = ("Grutatxt");
568 =head2 HTML Driver
570 The additional parameters for a new Grutatxt object are:
572 =over 4
574 =item I<table-headers>
576 If this boolean value is set, the first row in tables
577 is assumed to be the heading and rendered using <th>
578 instead of <td> tags.
580 =item I<center-tables>
582 If this boolean value is set, tables are centered.
584 =item I<expand-tables>
586 If this boolean value is set, tables are expanded (width 100%).
588 =item I<dl-as-dl>
590 If this boolean value is set, definition lists will be
591 rendered using <dl>, <dt> and <dd> instead of tables.
593 =item I<header-offset>
595 Offset to be summed to the heading level when rendering
596 <h?> tags (default is 0).
598 =item I<class-oddeven>
600 If this boolean value is set, tables will be rendered
601 with an "oddeven" CSS class, and rows alternately classed
602 as "even" or "odd". If it's not set, no CSS class info
603 is added to tables.
605 =back
607 =cut
609 sub new
611 my ($class,%args) = @_;
612 my ($gh);
614 bless(\%args,$class);
615 $gh = \%args;
617 $gh->{'-process-urls'} = 1;
619 return($gh);
623 sub _inline
625 my ($gh,$l) = @_;
627 # accept unnamed and HTML inlines
628 if($l =~ /^<<$/ or $l =~ /^<<\s*html$/i)
630 $gh->{'-inline'} = "HTML";
631 return;
634 if($l =~ /^>>$/)
636 delete $gh->{'-inline'};
637 return;
640 if($gh->{'-inline'} eq "HTML")
642 $gh->_push($l);
647 sub _escape
649 my ($gh,$l) = @_;
651 $l =~ s/&/&amp;/g;
652 $l =~ s/</&lt;/g;
653 $l =~ s/>/&gt;/g;
655 return($l);
659 sub _empty_line
661 my ($gh) = @_;
663 return("<p>");
667 sub _url
669 my ($gh,$url,$label) = @_;
671 $label = $url unless $label;
673 return("<a href=\"$url\">$label</a>");
677 sub _strong
679 my ($gh,$str) = @_;
680 return("<strong class=strong>$str</strong>");
684 sub _em
686 my ($gh,$str) = @_;
687 return("<em class=em>$str</em>");
691 sub _funcname
693 my ($gh,$str) = @_;
694 return("<code class=funcname>$str</code>");
698 sub _varname
700 my ($gh,$str) = @_;
701 return("<code class=var>$str</code>");
705 sub _new_mode
707 my ($gh,$mode,$params) = @_;
709 if($mode ne $gh->{'-mode'})
711 my $tag;
713 # flush previous mode
715 # clean list levels
716 if($gh->{'-mode'} eq "ul")
718 $gh->_push("</ul>" x scalar(@{$gh->{'-ul-levels'}}));
720 elsif($gh->{'-mode'} eq "ol")
722 $gh->_push("</ol>" x scalar(@{$gh->{'-ol-levels'}}));
724 elsif($gh->{'-mode'})
726 $gh->_push("</$gh->{'-mode'}>");
729 # send new one
730 $tag = $params ? "<$mode $params>" : "<$mode>";
731 $gh->_push($tag) if $mode;
733 $gh->{'-mode'} = $mode;
735 # clean previous lists
736 $gh->{'-ul-levels'} = undef;
737 $gh->{'-ol-levels'} = undef;
742 sub _dl
744 my ($gh,$str) = @_;
746 if($gh->{'dl-as-dl'})
748 $gh->_new_mode("dl");
749 return("<dt><strong class=term>$str</strong><dd>");
751 else
753 $gh->_new_mode("table");
754 return("<tr><td valign=top><strong class=term>$1</strong>&nbsp;&nbsp;</td><td valign=top>");
759 sub _ul
761 my ($gh, $levels) = @_;
762 my ($ret);
764 $ret = "";
766 if($levels > 0)
768 $ret = "<ul>";
770 elsif($levels < 0)
772 $ret = "</ul>" x abs($levels);
775 $gh->{'-mode'} = "ul";
777 $ret .= "<li>";
779 return($ret);
783 sub _ol
785 my ($gh, $levels) = @_;
786 my ($ret);
788 $ret = "";
790 if($levels > 0)
792 $ret = "<ol>";
794 elsif($levels < 0)
796 $ret = "</ol>" x abs($levels);
799 $gh->{'-mode'} = "ol";
801 $ret .= "<li>";
803 return($ret);
807 sub _blockquote
809 my ($gh) = @_;
811 $gh->_new_mode("blockquote");
812 return("\"");
816 sub _hr
818 my ($gh) = @_;
820 return("<hr size=1 noshade>");
824 sub _heading
826 my ($gh,$level,$l) = @_;
828 # substitute anchor spaces with underscores
829 my ($a) = lc($l); $a =~ s/\s/_/g;
831 $l = sprintf("<a name=\"%s\"></a>\n<h%d class=level$level>%s</h%d>",
832 $a, $level+$gh->{'header-offset'},
833 $l, $level+$gh->{'header-offset'});
835 return($l);
839 sub _table
841 my ($gh,$str) = @_;
843 if($gh->{'-mode'} eq "table")
845 my ($class) = "";
846 my (@spans) = $gh->_calc_col_span($str);
848 # calculate CSS class, if any
849 if($gh->{'class-oddeven'})
851 $class = ($gh->{'-tbl-row'} & 1) ? "odd" : "even";
854 $str = "<tr $class>";
856 # build columns
857 for(my $n = 0;$n < scalar(@{$gh->{'-table'}});$n++)
859 my ($i,$s);
861 $i = ${$gh->{'-table'}}[$n];
862 $i = "&nbsp;" if $i =~ /^\s*$/;
864 $s = " colspan=$spans[$n]" if $spans[$n] > 1;
866 if($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1)
868 $str .= "<th $class $s>$i</th>";
870 else
872 $str .= "<td $class $s>$i</td>";
876 @{$gh->{'-table'}} = ();
877 $gh->{'-tbl-row'}++;
879 else
881 # new table
882 my ($params);
884 $params = "border=1";
885 $params .= " width='100\%'" if $gh->{'expand-tables'};
886 $params .= " align=center" if $gh->{'center-tables'};
887 $params .= " class=oddeven" if $gh->{'class-oddeven'};
889 $gh->_new_mode("table", $params);
891 @{$gh->{'-table'}} = ();
892 $gh->{'-tbl-row'} = 1;
893 $str = "";
896 return($str);
900 ###########################################################
901 # troff Driver
903 package Grutatxt::troff;
905 @ISA = ("Grutatxt");
907 =head2 troff Driver
909 The troff driver uses the B<-me> macros and B<tbl>. A
910 good way to post-process this output (to PostScript in
911 the example) could be by using
913 groff -t -me -Tps
915 The additional parameters for a new Grutatxt object are:
917 =over 4
919 =item I<normal-size>
921 The point size of normal text. By default is 10.
923 =item I<heading-sizes>
925 This argument must be a reference to an array containing
926 the size in points of the 3 different heading levels. By
927 default, level sizes are [ 20, 18, 15 ].
929 =item I<table-type>
931 The type of table to be rendered by B<tbl>. Can be
932 I<allbox> (all lines rendered; this is the default value),
933 I<box> (only outlined) or I<doublebox> (only outlined by
934 a double line).
936 =back
938 =cut
940 sub new
942 my ($class,%args) = @_;
943 my ($gh);
945 bless(\%args,$class);
946 $gh = \%args;
948 $gh->{'-process-urls'} = 0;
950 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
951 $gh->{'normal-size'} ||= 10;
952 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
954 return($gh);
958 sub _prefix
960 my ($gh) = @_;
962 $gh->_push(".nr pp $gh->{'normal-size'}");
963 $gh->_push(".nh");
967 sub _inline
969 my ($gh,$l) = @_;
971 # accept only troff inlines
972 if($l =~ /^<<\s*troff$/i)
974 $gh->{'-inline'} = "troff";
975 return;
978 if($l =~ /^>>$/)
980 delete $gh->{'-inline'};
981 return;
984 if($gh->{'-inline'} eq "troff")
986 $gh->_push($l);
991 sub _escape
993 my ($gh,$l) = @_;
995 $l =~ s/\\/\\\\/g;
996 $l =~ s/^'/\\&'/;
998 return($l);
1002 sub _empty_line
1004 my ($gh) = @_;
1006 return(".lp");
1010 sub _strong
1012 my ($gh,$str) = @_;
1013 return("\\fB$str\\fP");
1017 sub _em
1019 my ($gh,$str) = @_;
1020 return("\\fI$str\\fP");
1024 sub _funcname
1026 my ($gh,$str) = @_;
1027 return("\\fB$str\\fP");
1031 sub _varname
1033 my ($gh,$str) = @_;
1034 return("\\fI$str\\fP");
1038 sub _new_mode
1040 my ($gh,$mode,$params) = @_;
1042 if($mode ne $gh->{'-mode'})
1044 my $tag;
1046 # flush previous list
1047 if($gh->{'-mode'} eq "pre")
1049 $gh->_push(".)l");
1051 elsif($gh->{'-mode'} eq "table")
1053 chomp($gh->{'-table-head'});
1054 $gh->{'-table-head'} =~ s/\s+$//;
1055 $gh->_push($gh->{'-table-head'} . ".");
1056 $gh->_push($gh->{'-table-body'} . ".TE\n.sp 0.6");
1058 elsif($gh->{'-mode'} eq "blockquote")
1060 $gh->_push(".)q");
1063 # send new one
1064 if($mode eq "pre")
1066 $gh->_push(".(l L");
1068 elsif($mode eq "blockquote")
1070 $gh->_push(".(q");
1073 $gh->{'-mode'} = $mode;
1078 sub _dl
1080 my ($gh,$str) = @_;
1082 $gh->_new_mode("dl");
1083 return(".ip \"$str\"\n");
1087 sub _ul
1089 my ($gh) = @_;
1091 $gh->_new_mode("ul");
1092 return(".bu\n");
1096 sub _ol
1098 my ($gh) = @_;
1100 $gh->_new_mode("ol");
1101 return(".np\n");
1105 sub _blockquote
1107 my ($gh) = @_;
1109 $gh->_new_mode("blockquote");
1110 return("\"");
1114 sub _hr
1116 my ($gh) = @_;
1118 return(".hl");
1122 sub _heading
1124 my ($gh,$level,$l) = @_;
1126 $l = ".sz " . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1128 return($l);
1132 sub _table
1134 my ($gh,$str) = @_;
1136 if($gh->{'-mode'} eq "table")
1138 my ($h,$b);
1139 my (@spans) = $gh->_calc_col_span($str);
1141 # build columns
1142 $h = "";
1143 $b = "";
1144 for(my $n = 0;$n < scalar(@{$gh->{'-table'}});$n++)
1146 my ($i);
1148 if($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1)
1150 $h .= "cB ";
1152 else
1154 $h .= "l ";
1157 # add span columns
1158 $h .= "s " x ($spans[$n] - 1) if $spans[$n] > 1;
1160 $b .= "#" if $n;
1162 $i = ${$gh->{'-table'}}[$n];
1163 $i =~ s/^\s+//;
1164 $i =~ s/\s+$//;
1165 $i =~ s/(\s)+/$1/g;
1166 $b .= $i;
1169 # add a separator
1170 $b .= "\n_" if $gh->{'table-headers'} and
1171 $gh->{'-tbl-row'} == 1 and
1172 $gh->{'table-type'} ne "allbox";
1174 $gh->{'-table-head'} .= "$h\n";
1175 $gh->{'-table-body'} .= "$b\n";
1177 @{$gh->{'-table'}} = ();
1178 $gh->{'-tbl-row'}++;
1180 else
1182 # new table
1183 $gh->_new_mode("table");
1185 @{$gh->{'-table'}} = ();
1186 $gh->{'-tbl-row'} = 1;
1188 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1189 $gh->{'-table-body'} = "";
1192 $str = "";
1193 return($str);
1197 sub _postfix
1199 my ($gh) = @_;
1201 # add to top headings and footers
1202 unshift(@{$gh->{'o'}},".ef '\%' ''");
1203 unshift(@{$gh->{'o'}},".of '' '\%'");
1204 unshift(@{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1205 unshift(@{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1209 =head1 AUTHOR
1211 Angel Ortega angel@triptico.com
1213 =cut