1 #####################################################################
3 # Grutatxt - A text to HTML (and other things) converter
5 # Copyright (C) 2000/2008 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 #####################################################################
29 $VERSION = '2.0.15-dev';
35 Grutatxt - Text to HTML (and other formats) converter
41 # create a new Grutatxt converter object
42 $grutatxt = new Grutatxt();
44 # process a Grutatxt format string
45 @output = $grutatxt->process($text);
48 @output2 = $grutatxt->process_file($file);
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, troff or man.
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
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
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
94 Output format. Can be HTML, troff or man. HTML is used if not specified.
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.
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.
111 If I<index> is specified as a reference to array, it will
112 be filled with strings in the format
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.
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.
149 my ($class, %args) = @_;
152 $args{'mode'} ||= 'HTML';
154 $class .= "::" . $args{'mode'};
156 $gh = new
$class(%args);
163 # escapes special characters, ignoring passthrough code
167 # splits between << and >>
168 my (@l) = split(/(<<|>>)/, $l);
173 # escape only text outside << and >>
174 unless ($l eq '<<' .. $l eq '>>') {
175 $l = $gh->_escape($l);
181 # join again, stripping << and >>
182 $l = join('', grep(!/^(<<|>>)$/, @l));
190 @output = $grutatxt->process($text);
192 Processes a text in Grutatxt format. The result is returned
193 as an array of lines.
199 my ($gh, $content) = @_;
205 # clean title and paragraph numbers
206 $gh->{'-title'} = '';
210 @
{$gh->{'marks'}} = () if ref($gh->{'marks'});
213 @
{$gh->{'index'}} = () if ref($gh->{'index'});
215 # reset abstract line
216 ${$gh->{'abstract'}} = 0 if ref($gh->{'abstract'});
221 $gh->{'-mode'} = undef;
223 foreach my $l (split(/\n/,$content)) {
224 # inline data (passthrough)
225 if ($l =~ /^<<$/ .. $l =~ /^>>$/) {
231 if ($l =~ /^\s*<\->\s*$/) {
232 push(@
{$gh->{'marks'}},scalar(@
{$gh->{'o'}}))
233 if ref($gh->{'marks'});
238 # escape possibly dangerous characters
239 $l = $gh->escape($l);
243 if ($l =~ s/^$/$gh->_empty_line()/ge) {
244 # mark the abstract end
245 if ($gh->{'-title'}) {
248 # mark abstract if it's the
249 # second paragraph from the title
250 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})-1
255 if ($gh->{'-process-urls'}) {
256 # URLs followed by a parenthesized phrase
257 $l =~ s/(https?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
258 $l =~ s/(ftps?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
259 $l =~ s/(file:\/?\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
260 $l =~ s
|(\s
+)\
./(\S
+)\s
+\
(([^\
)]+)\
)|$1.$gh->_url($2,$3)|ge;
261 $l =~ s
|^\
./(\S
+)\s
+\
(([^\
)]+)\
)|$gh->_url($1,$2)|ge;
263 # URLs without phrase
264 $l =~ s/([^=][^\"])(https?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
265 $l =~ s/([^=][^\"])(ftps?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
266 $l =~ s/([^=][^\"])(file:\/?\S+)/$1.$gh->_url($2)/ge;
267 $l =~ s
|(\s
+)\
./(\S
+)|$1.$gh->_url($2)|ge;
269 $l =~ s/^(https?:\/\/\S
+)/$gh->_url($1)/ge
;
270 $l =~ s/^(ftps?:\/\/\S
+)/$gh->_url($1)/ge
;
271 $l =~ s/^(file:\/?\S+)/$gh->_url($1)/ge;
272 $l =~ s
|^\
./(\S
+)|$gh->_url($1)|ge;
275 # change '''text''' and *text* into strong emphasis
276 $l =~ s/\'\'\'([^\'][^\'][^\']*)\'\'\'/$gh->_strong($1)/ge;
277 $l =~ s/\*(\S[^\*]+\S)\*/$gh->_strong($1)/ge;
278 $l =~ s/\*(\S+)\*/$gh->_strong($1)/ge;
280 # change ''text'' and _text_ into emphasis
281 $l =~ s/\'\'([^\'][^\']*)\'\'/$gh->_em($1)/ge;
282 $l =~ s/\b_(\S[^_]*\S)_\b/$gh->_em($1)/ge;
283 $l =~ s/\b_(\S+)_\b/$gh->_em($1)/ge;
285 # change `text' into code
286 $l =~ s/`([^\']*)\'/$gh->_code($1)/ge;
288 # enclose function names
289 if ($gh->{'strip-parens'}) {
290 $l =~ s/(\w+)\(\)/$gh->_funcname($1)/ge;
293 $l =~ s/(\w+)\(\)/$gh->_funcname($1."()")/ge;
296 # enclose variable names
297 if ($gh->{'strip-dollars'}) {
298 $l =~ s/\$([\w_\.]+)/$gh->_varname($1)/ge;
301 $l =~ s/(\$[\w_\.]+)/$gh->_varname($1)/ge;
309 if ($l =~ /^\s\*\s+/ && $l =~ s/^\s\*\s+([^:\.,;]+)\:\s+/$gh->_dl($1)/e) {
310 $gh->{'-mode-elems'} ++;
314 elsif ($gh->{'-mode'} ne 'pre' and
315 ($l =~ s/^(\s+)\*\s+/$gh->_unsorted_list($1)/e or
316 $l =~ s/^(\s+)\-\s+/$gh->_unsorted_list($1)/e)) {
317 $gh->{'-mode-elems'} ++;
321 elsif ($gh->{'-mode'} ne 'pre' and
322 ($l =~ s/^(\s+)\#\s+/$gh->_ordered_list($1)/e or
323 $l =~ s/^(\s+)1\s+/$gh->_ordered_list($1)/e)) {
324 $gh->{'-mode-elems'} ++;
328 elsif ($l =~ s/^\s\"/$gh->_blockquote()/e) {
332 elsif ($l =~ s/^\s*\|(.*)\|\s*$/$gh->_table_row($1)/e) {
333 $gh->{'-mode-elems'} ++;
336 # table heading / end of row
337 elsif ($l =~ s/^\s*(\+[-\+\|]+\+)\s*$/$gh->_table($1)/e) {
341 elsif ($l =~ s/^(\s.*)$/$gh->_pre($1)/e) {
346 # back to normal mode
347 $gh->_new_mode(undef);
351 $l =~ s/^(=+)\s*$/$gh->_process_heading(1,$1)/e;
354 $l =~ s/^(-+)\s*$/$gh->_process_heading(2,$1)/e;
357 $l =~ s/^(~+)\s*$/$gh->_process_heading(3,$1)/e;
359 # change ------ into hr
360 $l =~ s/^----*$/$gh->_hr()/e;
363 $gh->_push($l) if $l;
367 $gh->_new_mode(undef);
373 ${$gh->{'title'}} = $gh->{'-title'} if ref($gh->{'title'});
375 # set abstract, if not set
376 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})
377 if ref($gh->{'abstract'}) and not ${$gh->{'abstract'}};
379 # travel all lines again, post-escaping
380 @
{$gh->{'o'}} = map { $_ = $gh->_escape_post($_); } @
{$gh->{'o'}};
382 return @
{$gh->{'o'}};
386 =head2 B<process_file>
388 @output = $grutatxt->process_file($filename);
390 Processes a file in Grutatxt format.
396 my ($gh, $file) = @_;
398 open F
, $file or return(undef);
400 my ($content) = join('',<F
>);
403 return $gh->process($content);
411 push(@
{$gh->{'o'}},$l);
417 my ($gh, $level, $hd) = @_;
420 $l = pop(@
{$gh->{'o'}});
422 if ($l eq $gh->_empty_line()) {
428 $gh->{'-title'} = $l if $level == 1 and not $gh->{'-title'};
431 if (ref($gh->{'index'})) {
432 push(@
{$gh->{'index'}},"$level,$l");
435 return $gh->_heading($level,$l);
444 # strip first + and all -
448 my ($t) = 1; @spans = ();
449 for (my $n = 0; $n < length($l); $n++) {
450 if (substr($l, $n, 1) eq '+') {
455 # it's a colspan mark:
469 my @s = split(/\|/,$str);
471 for (my $n = 0; $n < scalar(@s); $n++) {
472 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
475 push(@
{$gh->{'-table-raw'}}, $str);
485 # if any other mode is active, add to it
486 if ($gh->{'-mode'} and $gh->{'-mode'} ne 'pre') {
489 my ($a) = pop(@
{$gh->{'o'}})." ".$l;
494 # tabs to spaces if a non-zero tabsize is given (only in LaTex)
495 $l =~ s/\t/' ' x $gh->{'tabsize'}/ge if $gh->{'tabsize'} > 0;
497 $gh->_new_mode('pre');
506 my ($gh, $str, $ind) = @_;
514 # if last level is less indented, increase
519 elsif ($l[-1] > $ind) {
520 # if last level is more indented, decrease
521 # levels until the same is found (or back to
522 # the beginning if not)
525 last if $l[-1] == $ind;
539 return $gh->_ul($gh->_multilevel_list('-ul-levels', $ind));
547 return $gh->_ol($gh->_multilevel_list('-ol-levels', $ind));
551 # empty stubs for falling through the superclass
553 sub _inline
{ my ($gh, $l) = @_; $l; }
554 sub _escape
{ my ($gh, $l) = @_; $l; }
555 sub _escape_post
{ my ($gh, $l) = @_; $l; }
556 sub _empty_line
{ my ($gh) = @_; ''; }
557 sub _url
{ my ($gh, $url, $label) = @_; ''; }
558 sub _strong
{ my ($gh, $str) = @_; $str; }
559 sub _em
{ my ($gh, $str) = @_; $str; }
560 sub _code
{ my ($gh, $str) = @_; $str; }
561 sub _funcname
{ my ($gh, $str) = @_; $str; }
562 sub _varname
{ my ($gh, $str) = @_; $str; }
563 sub _new_mode
{ my ($gh, $mode) = @_; }
564 sub _dl
{ my ($gh, $str) = @_; $str; }
565 sub _ul
{ my ($gh, $level) = @_; ''; }
566 sub _ol
{ my ($gh, $level) = @_; ''; }
567 sub _blockquote
{ my ($gh, $str) = @_; $str; }
568 sub _hr
{ my ($gh) = @_; ''; }
569 sub _heading
{ my ($gh, $level, $l) = @_; $l; }
570 sub _table
{ my ($gh, $str) = @_; $str; }
571 sub _prefix
{ my ($gh) = @_; }
572 sub _postfix
{ my ($gh) = @_; }
574 ###########################################################
576 =head1 DRIVER SPECIFIC INFORMATION
580 ###########################################################
583 package Grutatxt
::HTML
;
589 The additional parameters for a new Grutatxt object are:
593 =item I<table-headers>
595 If this boolean value is set, the first row in tables
596 is assumed to be the heading and rendered using 'th'
597 instead of 'td' tags.
599 =item I<center-tables>
601 If this boolean value is set, tables are centered.
603 =item I<expand-tables>
605 If this boolean value is set, tables are expanded (width 100%).
609 If this boolean value is set, definition lists will be
610 rendered using 'dl', 'dt' and 'dd' instead of tables.
612 =item I<header-offset>
614 Offset to be summed to the heading level when rendering
615 'h?' tags (default is 0).
617 =item I<class-oddeven>
619 If this boolean value is set, tables will be rendered
620 with an "oddeven" CSS class, and rows alternately classed
621 as "even" or "odd". If it's not set, no CSS class info
624 =item I<url-label-max>
626 If an URL without label is given (that is, the URL itself
627 is used as the label), it's trimmed to have as much
628 characters as this value says. By default it's 80.
636 my ($class, %args) = @_;
639 bless(\
%args, $class);
642 $gh->{'-process-urls'} = 1;
643 $gh->{'url-label-max'} ||= 80;
653 # accept unnamed and HTML inlines
654 if ($l =~ /^<<$/ or $l =~ /^<<\s*html$/i) {
655 $gh->{'-inline'} = 'HTML';
660 delete $gh->{'-inline'};
664 if ($gh->{'-inline'} eq 'HTML') {
692 my ($gh, $url, $label) = @_;
697 if (length($label) > $gh->{'url-label-max'}) {
698 $label = substr($label, 0,
699 $gh->{'url-label-max'}) . '...';
703 return "<a href = \"$url\">$label</a>";
710 return "<strong>$str</strong>";
717 return "<em>$str</em>";
724 return "<code class = 'literal'>$str</code>";
731 return "<code class = 'funcname'>$str</code>";
738 return "<code class = 'var'>$str</code>";
744 my ($gh, $mode, $params) = @_;
746 if ($mode ne $gh->{'-mode'}) {
750 if ($gh->{'-mode'} eq 'ul') {
751 $gh->_push('</li>' . '</ul>' x
scalar(@
{$gh->{'-ul-levels'}}));
753 elsif ($gh->{'-mode'} eq 'ol') {
754 $gh->_push('</li>' . '</ol>' x
scalar(@
{$gh->{'-ol-levels'}}));
756 elsif ($gh->{'-mode'}) {
757 $gh->_push("</$gh->{'-mode'}>");
761 $tag = $params ?
"<$mode $params>" : "<$mode>";
762 $gh->_push($tag) if $mode;
764 $gh->{'-mode'} = $mode;
765 $gh->{'-mode-elems'} = 0;
767 # clean previous lists
768 $gh->{'-ul-levels'} = undef;
769 $gh->{'-ol-levels'} = undef;
779 if ($gh->{'dl-as-dl'}) {
780 $gh->_new_mode('dl');
781 $ret .= "<dt><strong class = 'term'>$str</strong><dd>";
784 $gh->_new_mode('table');
785 $ret .= "<tr><td valign = 'top'><strong class = 'term'>$1</strong> </td><td valign = 'top'>";
794 my ($gh, $levels) = @_;
802 elsif ($levels < 0) {
803 $ret .= '</li></ul>' x
abs($levels);
806 if ($gh->{'-mode'} ne 'ul') {
807 $gh->{'-mode'} = 'ul';
810 $ret .= '</li>' if $levels <= 0;
821 my ($gh, $levels) = @_;
829 elsif ($levels < 0) {
830 $ret .= '</li></ol>' x
abs($levels);
833 if ($gh->{'-mode'} ne 'ol') {
834 $gh->{'-mode'} = 'ol';
837 $ret .= '</li>' if $levels <= 0;
850 $gh->_new_mode('blockquote');
859 return "<hr size = '1' noshade = 'noshade'>";
865 my ($gh, $level, $l) = @_;
867 # creates a valid anchor
874 $l = sprintf("<a name = '%s'></a>\n<h%d class = 'level$level'>%s</h%d>",
875 $a, $level+$gh->{'header-offset'},
876 $l, $level+$gh->{'header-offset'});
886 if ($gh->{'-mode'} eq 'table') {
888 my (@spans) = $gh->_calc_col_span($str);
890 # calculate CSS class, if any
891 if ($gh->{'class-oddeven'}) {
892 $class = "class = '" . ($gh->{'-tbl-row'} & 1) ?
"odd'" : "even'";
895 $str = "<tr $class>";
898 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
901 $i = ${$gh->{'-table'}}[$n];
902 $i = " " if $i =~ /^\s*$/;
904 $s = " colspan = '$spans[$n]'" if $spans[$n] > 1;
906 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
907 $str .= "<th $class $s>$i</th>";
910 $str .= "<td $class $s>$i</td>";
916 @
{$gh->{'-table'}} = ();
923 $params = "border = '1'";
924 $params .= " width = '100\%'" if $gh->{'expand-tables'};
925 $params .= " align = 'center'" if $gh->{'center-tables'};
926 $params .= " class = 'oddeven'" if $gh->{'class-oddeven'};
928 $gh->_new_mode('table', $params);
930 @
{$gh->{'-table'}} = ();
931 $gh->{'-tbl-row'} = 1;
939 ###########################################################
942 package Grutatxt
::troff
;
948 The troff driver uses the B<-me> macros and B<tbl>. A
949 good way to post-process this output (to PostScript in
950 the example) could be by using
954 The additional parameters for a new Grutatxt object are:
960 The point size of normal text. By default is 10.
962 =item I<heading-sizes>
964 This argument must be a reference to an array containing
965 the size in points of the 3 different heading levels. By
966 default, level sizes are [ 20, 18, 15 ].
970 The type of table to be rendered by B<tbl>. Can be
971 I<allbox> (all lines rendered; this is the default value),
972 I<box> (only outlined) or I<doublebox> (only outlined by
981 my ($class, %args) = @_;
984 bless(\
%args,$class);
987 $gh->{'-process-urls'} = 0;
989 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
990 $gh->{'normal-size'} ||= 10;
991 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
1001 $gh->_push(".nr pp $gh->{'normal-size'}");
1010 # accept only troff inlines
1011 if ($l =~ /^<<\s*troff$/i) {
1012 $gh->{'-inline'} = 'troff';
1017 delete $gh->{'-inline'};
1021 if ($gh->{'-inline'} eq 'troff') {
1048 my ($gh, $str) = @_;
1049 return "\\fB$str\\fP";
1055 my ($gh, $str) = @_;
1056 return "\\fI$str\\fP";
1062 my ($gh, $str) = @_;
1063 return "\\fI$str\\fP";
1069 my ($gh, $str) = @_;
1070 return "\\fB$str\\fP";
1076 my ($gh, $str) = @_;
1077 return "\\fI$str\\fP";
1083 my ($gh, $mode, $params) = @_;
1085 if ($mode ne $gh->{'-mode'}) {
1088 # flush previous list
1089 if ($gh->{'-mode'} eq 'pre') {
1092 elsif ($gh->{'-mode'} eq 'table') {
1093 chomp($gh->{'-table-head'});
1094 $gh->{'-table-head'} =~ s/\s+$//;
1095 $gh->_push($gh->{'-table-head'} . '.');
1096 $gh->_push($gh->{'-table-body'} . '.TE\n.sp 0.6');
1098 elsif ($gh->{'-mode'} eq 'blockquote') {
1103 if ($mode eq 'pre') {
1104 $gh->_push('.(l L');
1106 elsif ($mode eq 'blockquote') {
1110 $gh->{'-mode'} = $mode;
1117 my ($gh, $str) = @_;
1119 $gh->_new_mode('dl');
1120 return ".ip \"$str\"\n";
1128 $gh->_new_mode('ul');
1137 $gh->_new_mode('ol');
1146 $gh->_new_mode('blockquote');
1161 my ($gh, $level, $l) = @_;
1163 $l = '.sz ' . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1171 my ($gh, $str) = @_;
1173 if ($gh->{'-mode'} eq 'table') {
1175 my (@spans) = $gh->_calc_col_span($str);
1180 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1183 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
1191 $h .= 's ' x
($spans[$n] - 1) if $spans[$n] > 1;
1195 $i = ${$gh->{'-table'}}[$n];
1203 $b .= "\n_" if $gh->{'table-headers'} and
1204 $gh->{'-tbl-row'} == 1 and
1205 $gh->{'table-type'} ne "allbox";
1207 $gh->{'-table-head'} .= "$h\n";
1208 $gh->{'-table-body'} .= "$b\n";
1210 @
{$gh->{'-table'}} = ();
1211 $gh->{'-tbl-row'}++;
1215 $gh->_new_mode('table');
1217 @
{$gh->{'-table'}} = ();
1218 $gh->{'-tbl-row'} = 1;
1220 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1221 $gh->{'-table-body'} = '';
1233 # add to top headings and footers
1234 unshift(@
{$gh->{'o'}},".ef '\%' ''");
1235 unshift(@
{$gh->{'o'}},".of '' '\%'");
1236 unshift(@
{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1237 unshift(@
{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1241 ###########################################################
1244 package Grutatxt
::man
;
1246 @ISA = ("Grutatxt::troff", "Grutatxt");
1250 The man driver is used to generate Unix-like man pages. Note that
1251 all headings have the same level with this output driver.
1253 The additional parameters for a new Grutatxt object are:
1259 The man page section (see man documentation). By default is 1.
1263 The name of the page. This is usually the name of the program
1264 or function the man page is documenting and will be shown in the
1265 page header. By default is the empty string.
1273 my ($class, %args) = @_;
1276 bless(\
%args,$class);
1279 $gh->{'-process-urls'} = 0;
1281 $gh->{'section'} ||= 1;
1282 $gh->{'page-name'} ||= "";
1292 $gh->_push(".TH \"$gh->{'page-name'}\" \"$gh->{'section'}\" \"" . localtime() . "\"");
1300 # accept only man markup inlines
1301 if ($l =~ /^<<\s*man$/i) {
1302 $gh->{'-inline'} = 'man';
1307 delete $gh->{'-inline'};
1311 if ($gh->{'-inline'} eq 'man') {
1327 my ($gh,$mode,$params) = @_;
1329 if ($mode ne $gh->{'-mode'}) {
1332 # flush previous list
1333 if ($gh->{'-mode'} eq 'pre' or
1334 $gh->{'-mode'} eq 'table') {
1338 if ($gh->{'-mode'} eq 'blockquote') {
1342 if ($gh->{'-mode'} eq 'ul') {
1343 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ul-levels'}}));
1346 if ($gh->{'-mode'} eq 'ol') {
1347 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ol-levels'}}));
1351 if ($mode eq 'pre' or $mode eq 'table') {
1355 if ($mode eq 'blockquote') {
1356 $gh->_push('.RS 4');
1359 $gh->{'-mode'} = $mode;
1366 my ($gh, $str) = @_;
1368 $gh->_new_mode('dl');
1369 return ".TP\n.B \"$str\"\n";
1375 my ($gh, $levels) = @_;
1381 elsif ($levels < 0) {
1382 $ret = ".RE\n" x
abs($levels);
1385 $gh->_new_mode('ul');
1386 return $ret . ".TP 4\n\\(bu\n";
1392 my ($gh, $levels) = @_;
1393 my $l = @
{$gh->{'-ol-levels'}};
1396 $gh->{'-ol-level'} += $levels;
1401 $l[$gh->{'-ol-level'}] = 1;
1403 elsif ($levels < 0) {
1404 $ret = ".RE\n" x
abs($levels);
1407 $gh->_new_mode('ol');
1408 $ret .= ".TP 4\n" . $l[$gh->{'-ol-level'}]++ . ".\n";
1424 my ($gh, $level, $l) = @_;
1426 # all headers are the same depth in man pages
1427 return ".SH \"" . uc($l) . "\"";
1433 my ($gh, $str) = @_;
1435 if ($gh->{'-mode'} eq 'table') {
1436 foreach my $r (@
{$gh->{'-table-raw'}}) {
1441 $gh->_new_mode('table');
1444 @
{$gh->{'-table'}} = ();
1445 @
{$gh->{'-table-raw'}} = ();
1458 ###########################################################
1461 package Grutatxt
::latex
;
1463 @ISA = ("Grutatxt");
1467 The additional parameters for a new Grutatxt object are:
1473 The LaTeX document class. By default is 'report'. You can also use
1474 'article' or 'book' (consult your LaTeX documentation for details).
1478 The paper size to be used in the document. By default is 'a4paper'.
1482 The character encoding used in the document. By default is 'latin1'.
1486 Note that you can't nest further than 4 levels in LaTeX; if you do,
1487 LaTeX will choke in the generated code with a 'Too deeply nested' error.
1493 my ($class, %args) = @_;
1496 bless(\
%args,$class);
1499 $gh->{'-process-urls'} = 0;
1501 $gh->{'-docclass'} ||= 'report';
1502 $gh->{'-papersize'} ||= 'a4paper';
1503 $gh->{'-encoding'} ||= 'latin1';
1513 $gh->_push("\\documentclass[$gh->{'-papersize'}]{$gh->{-docclass}}");
1514 $gh->_push("\\usepackage[$gh->{'-encoding'}]{inputenc}");
1516 $gh->_push("\\begin{document}");
1524 # accept only latex inlines
1525 if ($l =~ /^<<\s*latex$/i) {
1526 $gh->{'-inline'} = 'latex';
1531 delete $gh->{'-inline'};
1535 if ($gh->{'-inline'} eq 'latex') {
1545 $l =~ s/ _ / \\_ /g;
1546 $l =~ s/ ~ / \\~ /g;
1547 $l =~ s/ & / \\& /g;
1557 $l =~ s/ # / \\# /g;
1559 $l =~ s/([^\s_])_([^\s_])/$1\\_$2/g;
1575 my ($gh, $str) = @_;
1576 return "\\textbf{$str}";
1582 my ($gh, $str) = @_;
1583 return "\\emph{$str}";
1589 my ($gh, $str) = @_;
1590 return "{\\tt $str}";
1596 my ($gh, $str) = @_;
1597 return "{\\tt $str}";
1603 my ($gh, $str) = @_;
1605 $str =~ s/^\$/\\\$/;
1607 return "{\\tt $str}";
1613 my ($gh, $mode, $params) = @_;
1617 'pre' => 'verbatim',
1618 'blockquote' => 'quote',
1619 'table' => 'tabular',
1620 'dl' => 'description',
1625 if ($mode ne $gh->{'-mode'}) {
1626 # close previous mode
1627 if ($gh->{'-mode'} eq 'ul') {
1628 $gh->_push("\\end{itemize}" x
scalar(@
{$gh->{'-ul-levels'}}));
1630 elsif ($gh->{'-mode'} eq 'ol') {
1631 $gh->_push("\\end{enumerate}" x
scalar(@
{$gh->{'-ol-levels'}}));
1633 elsif ($gh->{'-mode'} eq 'table') {
1634 $gh->_push("\\end{tabular}\n");
1637 $gh->_push("\\end{" . $latex_modes{$gh->{'-mode'}} . "}")
1642 $gh->_push("\\begin{" . $latex_modes{$mode} . "}" . $params)
1645 $gh->{'-mode'} = $mode;
1647 $gh->{'-ul-levels'} = undef;
1648 $gh->{'-ol-levels'} = undef;
1655 my ($gh, $str) = @_;
1657 $gh->_new_mode('dl');
1658 return "\\item[$str]\n";
1664 my ($gh, $levels) = @_;
1670 $ret .= "\\begin{itemize}\n";
1672 elsif ($levels < 0) {
1673 $ret .= "\\end{itemize}\n" x
abs($levels);
1676 $gh->{'-mode'} = 'ul';
1686 my ($gh, $levels) = @_;
1692 $ret .= "\\begin{enumerate}\n";
1694 elsif ($levels < 0) {
1695 $ret .= "\\end{enumerate}\n" x
abs($levels);
1698 $gh->{'-mode'} = 'ol';
1710 $gh->_new_mode('blockquote');
1719 return "------------\n";
1725 my ($gh, $level, $l) = @_;
1727 my @latex_headings = ( "\\section*{", "\\subsection*{",
1728 "\\subsubsection*{");
1730 $l = "\n" . $latex_headings[$level - 1] . $l . "}";
1740 if ($gh->{'-mode'} eq 'table') {
1742 my (@spans) = $gh->_calc_col_span($str);
1748 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1751 $i = ${$gh->{'-table'}}[$n];
1752 $i = " " if $i =~ /^\s*$/;
1754 # $s = " colspan='$spans[$n]'" if $spans[$n] > 1;
1757 $i = "\\multicolumn{$spans[$n]}{|l|}{$i}"
1767 $str .= join('&', @cols) . "\\\\\n\\hline";
1769 # $str .= "\n\\hline" if $gh->{'-tbl-row'} == 1;
1771 @
{$gh->{'-table'}} = ();
1772 $gh->{'-tbl-row'}++;
1777 # count the number of columns
1779 my $params = "{" . "|l" x
(length($str) - 1) . "|}\n\\hline";
1782 $gh->_new_mode('table', $params);
1784 @
{$gh->{'-table'}} = ();
1785 $gh->{'-tbl-row'} = 1;
1797 $gh->_push("\\end{document}");
1803 Angel Ortega angel@triptico.com