1 #####################################################################
3 # Grutatxt - A text to HTML (and other things) converter
5 # Copyright (C) 2000/2009 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.16-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
67 A comprehensive description of the markup is defined in
68 the README file, included with the Grutatxt package (it is
69 written in Grutatxt format itself, so it can be converted
70 using the I<grutatxt> tool to any of the supported formats).
71 The latest version (and more information) can be retrieved
72 from the Grutatxt home page at:
74 http://www.triptico.com/software/grutatxt.html
76 =head1 FUNCTIONS AND METHODS
80 $grutatxt = new Grutatxt([ "mode" => $mode, ]
81 [ "title" => \$title, ]
82 [ "marks" => \@marks, ]
83 [ "index" => \@index, ]
84 [ "abstract" => \$abstract, ]
85 [ "strip-parens" => $bool, ]
86 [ "strip-dollars" => $bool, ]
87 [ %driver_specific_arguments ] );
89 Creates a new Grutatxt object instance. All parameters are
96 Output format. Can be HTML, troff or man. HTML is used if not specified.
100 If I<title> is specified as a reference to scalar, the first
101 level 1 heading found in the text is stored inside it.
105 Marks in the Grutatxt markup are created by inserting the
106 string <-> alone in a line. If I<marks> is specified as a
107 reference to array, it will be filled with the subscripts
108 (relative to the output array) of the lines where the marks
109 are found in the text.
113 If I<index> is specified as a reference to array, it will
114 be filled with two element arrayrefs with the level as first
115 argument and the heading as second.
117 This information can be used to build a table of contents
118 of the processed text.
120 =item I<strip-parens>
122 Function names in the Grutatxt markup are strings of
123 alphanumeric characters immediately followed by a pair
124 of open and close parentheses. If this boolean value is
125 set, function names found in the processed text will have
126 their parentheses deleted.
128 =item I<strip-dollars>
130 Variable names in the Grutatxt markup are strings of
131 alphanumeric characters preceded by a dollar sign.
132 If this boolean value is set, variable names found in
133 the processed text will have the dollar sign deleted.
137 The I<abstract> of a Grutatxt document is the fragment of text
138 from the beginning of the document to the end of the first
139 paragraph after the title. If I<abstract> is specified as a
140 reference to scalar, it will contain (after each call to the
141 B<process()> method) the subscript of the element of the output
142 array that marks the end of the subject.
144 =item I<no-pure-verbatim>
146 Since version 2.0.15, text effects as italics and bold are not
147 processed in I<verbatim> (preformatted) mode. If you want to
148 revert to the old behaviour, use this option.
152 If set, a table of contents will be generated after the abstract.
153 The table of contents will be elaborated using headings from 2
162 my ($class, %args) = @_;
165 $args{'mode'} ||= 'HTML';
167 $class .= "::" . $args{'mode'};
169 $gh = new
$class(%args);
176 # escapes special characters, ignoring passthrough code
180 # splits between << and >>
181 my (@l) = split(/(<<|>>)/, $l);
186 # escape only text outside << and >>
187 unless ($l eq '<<' .. $l eq '>>') {
188 $l = $gh->_escape($l);
194 # join again, stripping << and >>
195 $l = join('', grep(!/^(<<|>>)$/, @l));
203 @output = $grutatxt->process($text);
205 Processes a text in Grutatxt format. The result is returned
206 as an array of lines.
212 my ($gh, $content) = @_;
218 # clean title and paragraph numbers
219 $gh->{'-title'} = '';
224 $gh->{marks
} = \
$gh->{_marks
};
231 $gh->{index} = \
$gh->{_index
};
236 # reset abstract line
237 if (!$gh->{abstract
}) {
238 $gh->{abstract
} = \
$gh->{_abstract
};
241 ${$gh->{'abstract'}} = 0;
246 $gh->{'-mode'} = undef;
248 foreach my $l (split(/\n/,$content)) {
249 # inline data (passthrough)
250 if ($l =~ /^<<$/ .. $l =~ /^>>$/) {
256 if ($l =~ /^\s*<\->\s*$/) {
257 push(@
{$gh->{'marks'}},scalar(@
{$gh->{'o'}}))
258 if ref($gh->{'marks'});
264 if ($l =~ /^\s*<\?>\s*$/) {
265 $gh->{toc
} = $gh->{_toc_pos
} = scalar(@
{$gh->{o
}});
269 # escape possibly dangerous characters
270 $l = $gh->escape($l);
274 if ($l =~ s/^$/$gh->_empty_line()/ge) {
275 # mark the abstract end
276 if ($gh->{'-title'}) {
279 # mark abstract if it's the
280 # second paragraph from the title
281 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})-1
286 # line-mutating process
289 if ($gh->{'-process-urls'}) {
290 # URLs followed by a parenthesized phrase
291 $l =~ s/(https?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
292 $l =~ s/(ftps?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
293 $l =~ s/(file:\/?\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
294 $l =~ s
|(\s
+)\
./(\S
+)\s
+\
(([^\
)]+)\
)|$1.$gh->_url($2,$3)|ge;
295 $l =~ s
|^\
./(\S
+)\s
+\
(([^\
)]+)\
)|$gh->_url($1,$2)|ge;
297 # URLs without phrase
298 $l =~ s/([^=][^\"])(https?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
299 $l =~ s/([^=][^\"])(ftps?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
300 $l =~ s/([^=][^\"])(file:\/?\S+)/$1.$gh->_url($2)/ge;
301 $l =~ s
|(\s
+)\
./(\S
+)|$1.$gh->_url($2)|ge;
303 $l =~ s/^(https?:\/\/\S
+)/$gh->_url($1)/ge
;
304 $l =~ s/^(ftps?:\/\/\S
+)/$gh->_url($1)/ge
;
305 $l =~ s/^(file:\/?\S+)/$gh->_url($1)/ge;
306 $l =~ s
|^\
./(\S
+)|$gh->_url($1)|ge;
309 # change '''text''' and *text* into strong emphasis
310 $l =~ s/\'\'\'([^\'][^\'][^\']*)\'\'\'/$gh->_strong($1)/ge;
311 $l =~ s/\*(\S[^\*]+\S)\*/$gh->_strong($1)/ge;
312 $l =~ s/\*(\S+)\*/$gh->_strong($1)/ge;
314 # change ''text'' and _text_ into emphasis
315 $l =~ s/\'\'([^\'][^\']*)\'\'/$gh->_em($1)/ge;
316 $l =~ s/\b_(\S[^_]*\S)_\b/$gh->_em($1)/ge;
317 $l =~ s/\b_(\S+)_\b/$gh->_em($1)/ge;
319 # change `text' into code
320 $l =~ s/`([^\']*)\'/$gh->_code($1)/ge;
322 # enclose function names
323 if ($gh->{'strip-parens'}) {
324 $l =~ s/(\w+)\(\)/$gh->_funcname($1)/ge;
327 $l =~ s/(\w+)\(\)/$gh->_funcname($1."()")/ge;
330 # enclose variable names
331 if ($gh->{'strip-dollars'}) {
332 $l =~ s/\$([\w_\.]+)/$gh->_varname($1)/ge;
335 $l =~ s/(\$[\w_\.]+)/$gh->_varname($1)/ge;
343 if ($l =~ /^\s\*\s+/ && $l =~ s/^\s\*\s+([^:\.,;]+)\:\s+/$gh->_dl($1)/e) {
344 $gh->{'-mode-elems'} ++;
348 elsif ($gh->{'-mode'} ne 'pre' and
349 ($l =~ s/^(\s+)\*\s+/$gh->_unsorted_list($1)/e or
350 $l =~ s/^(\s+)\-\s+/$gh->_unsorted_list($1)/e)) {
351 $gh->{'-mode-elems'} ++;
355 elsif ($gh->{'-mode'} ne 'pre' and
356 ($l =~ s/^(\s+)\#\s+/$gh->_ordered_list($1)/e or
357 $l =~ s/^(\s+)1\s+/$gh->_ordered_list($1)/e)) {
358 $gh->{'-mode-elems'} ++;
362 elsif ($gh->{'-mode'} ne 'pre' and
363 $l =~ s/^\s\"/$gh->_blockquote()/e) {
367 elsif ($l =~ s/^\s*\|(.*)\|\s*$/$gh->_table_row($1)/e) {
368 $gh->{'-mode-elems'} ++;
371 # table heading / end of row
372 elsif ($l =~ s/^\s*(\+[-\+\|]+\+)\s*$/$gh->_table($1)/e) {
376 elsif ($l =~ s/^(\s.*)$/$gh->_pre($1)/e) {
377 if ($gh->{'-mode'} eq 'pre' &&
378 !$gh->{'no-pure-verbatim'}) {
379 # set line back to original
386 # back to normal mode
387 $gh->_new_mode(undef);
391 $l =~ s/^(=+)\s*$/$gh->_process_heading(1,$1)/e;
394 $l =~ s/^(-+)\s*$/$gh->_process_heading(2,$1)/e;
397 $l =~ s/^(~+)\s*$/$gh->_process_heading(3,$1)/e;
399 # change ------ into hr
400 $l =~ s/^----*$/$gh->_hr()/e;
403 $gh->_push($l) if $l;
407 $gh->_new_mode(undef);
413 ${$gh->{'title'}} = $gh->{'-title'} if ref($gh->{'title'});
415 # set abstract, if not set
416 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})
417 if ref($gh->{'abstract'}) and not ${$gh->{'abstract'}};
419 # travel all lines again, post-escaping
420 @
{$gh->{'o'}} = map { $_ = $gh->_escape_post($_); } @
{$gh->{'o'}};
422 # add TOC after first paragraph
423 if ($gh->{toc
} && @
{$gh->{o
}}) {
424 my $p = $gh->{_toc_pos
} ||
428 @
{$gh->{o
}} = (@
{$gh->{o
}}[0 .. $p],
430 @
{$gh->{o
}}[$p + 1 ..
431 scalar(@
{$gh->{o
}})]);
434 return @
{$gh->{'o'}};
440 @output = $grutatxt->process_file($filename);
442 Processes a file in Grutatxt format.
448 my ($gh, $file) = @_;
450 open F
, $file or return(undef);
452 my ($content) = join('',<F
>);
455 return $gh->process($content);
463 push(@
{$gh->{'o'}},$l);
469 my ($gh, $level, $hd) = @_;
472 $l = pop(@
{$gh->{'o'}});
474 if ($l eq $gh->_empty_line()) {
480 $gh->{'-title'} = $l if $level == 1 and not $gh->{'-title'};
483 if (ref($gh->{'index'})) {
484 push(@
{$gh->{'index'}}, [ $level, $l ]);
487 return $gh->_heading($level,$l);
496 # strip first + and all -
500 my ($t) = 1; @spans = ();
501 for (my $n = 0; $n < length($l); $n++) {
502 if (substr($l, $n, 1) eq '+') {
507 # it's a colspan mark:
521 my @s = split(/\|/,$str);
523 for (my $n = 0; $n < scalar(@s); $n++) {
524 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
527 push(@
{$gh->{'-table-raw'}}, $str);
537 # if any other mode is active, add to it
538 if ($gh->{'-mode'} and $gh->{'-mode'} ne 'pre') {
541 my ($a) = pop(@
{$gh->{'o'}})." ".$l;
546 # tabs to spaces if a non-zero tabsize is given (only in LaTex)
547 $l =~ s/\t/' ' x $gh->{'tabsize'}/ge if $gh->{'tabsize'} > 0;
549 $gh->_new_mode('pre');
558 my ($gh, $str, $ind) = @_;
566 # if last level is less indented, increase
571 elsif ($l[-1] > $ind) {
572 # if last level is more indented, decrease
573 # levels until the same is found (or back to
574 # the beginning if not)
577 last if $l[-1] == $ind;
591 return $gh->_ul($gh->_multilevel_list('-ul-levels', $ind));
599 return $gh->_ol($gh->_multilevel_list('-ol-levels', $ind));
603 # empty stubs for falling through the superclass
605 sub _inline
{ my ($gh, $l) = @_; $l; }
606 sub _escape
{ my ($gh, $l) = @_; $l; }
607 sub _escape_post
{ my ($gh, $l) = @_; $l; }
608 sub _empty_line
{ my ($gh) = @_; ''; }
609 sub _url
{ my ($gh, $url, $label) = @_; ''; }
610 sub _strong
{ my ($gh, $str) = @_; $str; }
611 sub _em
{ my ($gh, $str) = @_; $str; }
612 sub _code
{ my ($gh, $str) = @_; $str; }
613 sub _funcname
{ my ($gh, $str) = @_; $str; }
614 sub _varname
{ my ($gh, $str) = @_; $str; }
615 sub _new_mode
{ my ($gh, $mode) = @_; }
616 sub _dl
{ my ($gh, $str) = @_; $str; }
617 sub _ul
{ my ($gh, $level) = @_; ''; }
618 sub _ol
{ my ($gh, $level) = @_; ''; }
619 sub _blockquote
{ my ($gh, $str) = @_; $str; }
620 sub _hr
{ my ($gh) = @_; ''; }
621 sub _heading
{ my ($gh, $level, $l) = @_; $l; }
622 sub _table
{ my ($gh, $str) = @_; $str; }
623 sub _prefix
{ my ($gh) = @_; }
624 sub _postfix
{ my ($gh) = @_; }
625 sub _toc
{ my ($gh) = @_; return (); }
627 ###########################################################
629 =head1 DRIVER SPECIFIC INFORMATION
633 ###########################################################
636 package Grutatxt
::HTML
;
642 The additional parameters for a new Grutatxt object are:
646 =item I<table-headers>
648 If this boolean value is set, the first row in tables
649 is assumed to be the heading and rendered using 'th'
650 instead of 'td' tags.
652 =item I<center-tables>
654 If this boolean value is set, tables are centered.
656 =item I<expand-tables>
658 If this boolean value is set, tables are expanded (width 100%).
662 If this boolean value is set, definition lists will be
663 rendered using 'dl', 'dt' and 'dd' instead of tables.
665 =item I<header-offset>
667 Offset to be summed to the heading level when rendering
668 'h?' tags (default is 0).
670 =item I<class-oddeven>
672 If this boolean value is set, tables will be rendered
673 with an "oddeven" CSS class, and rows alternately classed
674 as "even" or "odd". If it's not set, no CSS class info
677 =item I<url-label-max>
679 If an URL without label is given (that is, the URL itself
680 is used as the label), it's trimmed to have as much
681 characters as this value says. By default it's 80.
689 my ($class, %args) = @_;
692 bless(\
%args, $class);
695 $gh->{'-process-urls'} = 1;
696 $gh->{'url-label-max'} ||= 80;
706 # accept unnamed and HTML inlines
707 if ($l =~ /^<<$/ or $l =~ /^<<\s*html$/i) {
708 $gh->{'-inline'} = 'HTML';
713 delete $gh->{'-inline'};
717 if ($gh->{'-inline'} eq 'HTML') {
745 my ($gh, $url, $label) = @_;
750 if (length($label) > $gh->{'url-label-max'}) {
751 $label = substr($label, 0,
752 $gh->{'url-label-max'}) . '...';
756 return "<a href = \"$url\">$label</a>";
763 return "<strong>$str</strong>";
770 return "<em>$str</em>";
777 return "<code class = 'literal'>$str</code>";
784 return "<code class = 'funcname'>$str</code>";
791 return "<code class = 'var'>$str</code>";
797 my ($gh, $mode, $params) = @_;
799 if ($mode ne $gh->{'-mode'}) {
803 if ($gh->{'-mode'} eq 'ul') {
804 $gh->_push('</li>' . '</ul>' x
scalar(@
{$gh->{'-ul-levels'}}));
806 elsif ($gh->{'-mode'} eq 'ol') {
807 $gh->_push('</li>' . '</ol>' x
scalar(@
{$gh->{'-ol-levels'}}));
809 elsif ($gh->{'-mode'}) {
810 $gh->_push("</$gh->{'-mode'}>");
814 $tag = $params ?
"<$mode $params>" : "<$mode>";
815 $gh->_push($tag) if $mode;
817 $gh->{'-mode'} = $mode;
818 $gh->{'-mode-elems'} = 0;
820 # clean previous lists
821 $gh->{'-ul-levels'} = undef;
822 $gh->{'-ol-levels'} = undef;
832 if ($gh->{'dl-as-dl'}) {
833 $gh->_new_mode('dl');
834 $ret .= "<dt><strong class = 'term'>$str</strong><dd>";
837 $gh->_new_mode('table');
838 $ret .= "<tr><td valign = 'top'><strong class = 'term'>$1</strong> </td><td valign = 'top'>";
847 my ($gh, $levels) = @_;
855 elsif ($levels < 0) {
856 $ret .= '</li></ul>' x
abs($levels);
859 if ($gh->{'-mode'} ne 'ul') {
860 $gh->{'-mode'} = 'ul';
863 $ret .= '</li>' if $levels <= 0;
874 my ($gh, $levels) = @_;
882 elsif ($levels < 0) {
883 $ret .= '</li></ol>' x
abs($levels);
886 if ($gh->{'-mode'} ne 'ol') {
887 $gh->{'-mode'} = 'ol';
890 $ret .= '</li>' if $levels <= 0;
903 $gh->_new_mode('blockquote');
912 return "<hr size = '1' noshade = 'noshade'>";
932 my ($gh, $level, $l) = @_;
934 # creates a valid anchor
935 my $a = $gh->__mkanchor($l);
937 $l = sprintf("<a name = '%s'></a>\n<h%d class = 'level$level'>%s</h%d>",
938 $a, $level+$gh->{'header-offset'},
939 $l, $level+$gh->{'header-offset'});
949 if ($gh->{'-mode'} eq 'table') {
951 my (@spans) = $gh->_calc_col_span($str);
953 # calculate CSS class, if any
954 if ($gh->{'class-oddeven'}) {
955 $class = "class = '" . ($gh->{'-tbl-row'} & 1) ?
"odd'" : "even'";
958 $str = "<tr $class>";
961 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
964 $i = ${$gh->{'-table'}}[$n];
965 $i = " " if $i =~ /^\s*$/;
967 $s = " colspan = '$spans[$n]'" if $spans[$n] > 1;
969 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
970 $str .= "<th $class $s>$i</th>";
973 $str .= "<td $class $s>$i</td>";
979 @
{$gh->{'-table'}} = ();
986 $params = "border = '1'";
987 $params .= " width = '100\%'" if $gh->{'expand-tables'};
988 $params .= " align = 'center'" if $gh->{'center-tables'};
989 $params .= " class = 'oddeven'" if $gh->{'class-oddeven'};
991 $gh->_new_mode('table', $params);
993 @
{$gh->{'-table'}} = ();
994 $gh->{'-tbl-row'} = 1;
1007 push(@t, '<!-- TOC -->');
1011 foreach my $e (@
{$gh->{index}}) {
1012 # ignore level 1 headings
1020 elsif ($l > $e->[0]) {
1026 push(@t, sprintf("<li><a href = '#%s'>%s</a></li>",
1027 $gh->__mkanchor($e->[1]), $e->[1]));
1037 ###########################################################
1040 package Grutatxt
::troff
;
1042 @ISA = ("Grutatxt");
1046 The troff driver uses the B<-me> macros and B<tbl>. A
1047 good way to post-process this output (to PostScript in
1048 the example) could be by using
1052 The additional parameters for a new Grutatxt object are:
1056 =item I<normal-size>
1058 The point size of normal text. By default is 10.
1060 =item I<heading-sizes>
1062 This argument must be a reference to an array containing
1063 the size in points of the 3 different heading levels. By
1064 default, level sizes are [ 20, 18, 15 ].
1068 The type of table to be rendered by B<tbl>. Can be
1069 I<allbox> (all lines rendered; this is the default value),
1070 I<box> (only outlined) or I<doublebox> (only outlined by
1079 my ($class, %args) = @_;
1082 bless(\
%args,$class);
1085 $gh->{'-process-urls'} = 0;
1087 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
1088 $gh->{'normal-size'} ||= 10;
1089 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
1099 $gh->_push(".nr pp $gh->{'normal-size'}");
1108 # accept only troff inlines
1109 if ($l =~ /^<<\s*troff$/i) {
1110 $gh->{'-inline'} = 'troff';
1115 delete $gh->{'-inline'};
1119 if ($gh->{'-inline'} eq 'troff') {
1146 my ($gh, $str) = @_;
1147 return "\\fB$str\\fP";
1153 my ($gh, $str) = @_;
1154 return "\\fI$str\\fP";
1160 my ($gh, $str) = @_;
1161 return "\\fI$str\\fP";
1167 my ($gh, $str) = @_;
1168 return "\\fB$str\\fP";
1174 my ($gh, $str) = @_;
1175 return "\\fI$str\\fP";
1181 my ($gh, $mode, $params) = @_;
1183 if ($mode ne $gh->{'-mode'}) {
1186 # flush previous list
1187 if ($gh->{'-mode'} eq 'pre') {
1190 elsif ($gh->{'-mode'} eq 'table') {
1191 chomp($gh->{'-table-head'});
1192 $gh->{'-table-head'} =~ s/\s+$//;
1193 $gh->_push($gh->{'-table-head'} . '.');
1194 $gh->_push($gh->{'-table-body'} . '.TE\n.sp 0.6');
1196 elsif ($gh->{'-mode'} eq 'blockquote') {
1201 if ($mode eq 'pre') {
1202 $gh->_push('.(l L');
1204 elsif ($mode eq 'blockquote') {
1208 $gh->{'-mode'} = $mode;
1215 my ($gh, $str) = @_;
1217 $gh->_new_mode('dl');
1218 return ".ip \"$str\"\n";
1226 $gh->_new_mode('ul');
1235 $gh->_new_mode('ol');
1244 $gh->_new_mode('blockquote');
1259 my ($gh, $level, $l) = @_;
1261 $l = '.sz ' . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1269 my ($gh, $str) = @_;
1271 if ($gh->{'-mode'} eq 'table') {
1273 my (@spans) = $gh->_calc_col_span($str);
1278 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1281 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
1289 $h .= 's ' x
($spans[$n] - 1) if $spans[$n] > 1;
1293 $i = ${$gh->{'-table'}}[$n];
1301 $b .= "\n_" if $gh->{'table-headers'} and
1302 $gh->{'-tbl-row'} == 1 and
1303 $gh->{'table-type'} ne "allbox";
1305 $gh->{'-table-head'} .= "$h\n";
1306 $gh->{'-table-body'} .= "$b\n";
1308 @
{$gh->{'-table'}} = ();
1309 $gh->{'-tbl-row'}++;
1313 $gh->_new_mode('table');
1315 @
{$gh->{'-table'}} = ();
1316 $gh->{'-tbl-row'} = 1;
1318 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1319 $gh->{'-table-body'} = '';
1331 # add to top headings and footers
1332 unshift(@
{$gh->{'o'}},".ef '\%' ''");
1333 unshift(@
{$gh->{'o'}},".of '' '\%'");
1334 unshift(@
{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1335 unshift(@
{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1339 ###########################################################
1342 package Grutatxt
::man
;
1344 @ISA = ("Grutatxt::troff", "Grutatxt");
1348 The man driver is used to generate Unix-like man pages. Note that
1349 all headings have the same level with this output driver.
1351 The additional parameters for a new Grutatxt object are:
1357 The man page section (see man documentation). By default is 1.
1361 The name of the page. This is usually the name of the program
1362 or function the man page is documenting and will be shown in the
1363 page header. By default is the empty string.
1371 my ($class, %args) = @_;
1374 bless(\
%args,$class);
1377 $gh->{'-process-urls'} = 0;
1379 $gh->{'section'} ||= 1;
1380 $gh->{'page-name'} ||= "";
1390 $gh->_push(".TH \"$gh->{'page-name'}\" \"$gh->{'section'}\" \"" . localtime() . "\"");
1398 # accept only man markup inlines
1399 if ($l =~ /^<<\s*man$/i) {
1400 $gh->{'-inline'} = 'man';
1405 delete $gh->{'-inline'};
1409 if ($gh->{'-inline'} eq 'man') {
1425 my ($gh,$mode,$params) = @_;
1427 if ($mode ne $gh->{'-mode'}) {
1430 # flush previous list
1431 if ($gh->{'-mode'} eq 'pre' or
1432 $gh->{'-mode'} eq 'table') {
1436 if ($gh->{'-mode'} eq 'blockquote') {
1440 if ($gh->{'-mode'} eq 'ul') {
1441 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ul-levels'}}));
1444 if ($gh->{'-mode'} eq 'ol') {
1445 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ol-levels'}}));
1449 if ($mode eq 'pre' or $mode eq 'table') {
1453 if ($mode eq 'blockquote') {
1454 $gh->_push('.RS 4');
1457 $gh->{'-mode'} = $mode;
1464 my ($gh, $str) = @_;
1466 $gh->_new_mode('dl');
1467 return ".TP\n.B \"$str\"\n";
1473 my ($gh, $levels) = @_;
1479 elsif ($levels < 0) {
1480 $ret = ".RE\n" x
abs($levels);
1483 $gh->_new_mode('ul');
1484 return $ret . ".TP 4\n\\(bu\n";
1490 my ($gh, $levels) = @_;
1491 my $l = @
{$gh->{'-ol-levels'}};
1494 $gh->{'-ol-level'} += $levels;
1499 $l[$gh->{'-ol-level'}] = 1;
1501 elsif ($levels < 0) {
1502 $ret = ".RE\n" x
abs($levels);
1505 $gh->_new_mode('ol');
1506 $ret .= ".TP 4\n" . $l[$gh->{'-ol-level'}]++ . ".\n";
1522 my ($gh, $level, $l) = @_;
1524 # all headers are the same depth in man pages
1525 return ".SH \"" . uc($l) . "\"";
1531 my ($gh, $str) = @_;
1533 if ($gh->{'-mode'} eq 'table') {
1534 foreach my $r (@
{$gh->{'-table-raw'}}) {
1539 $gh->_new_mode('table');
1542 @
{$gh->{'-table'}} = ();
1543 @
{$gh->{'-table-raw'}} = ();
1556 ###########################################################
1559 package Grutatxt
::latex
;
1561 @ISA = ("Grutatxt");
1565 The additional parameters for a new Grutatxt object are:
1571 The LaTeX document class. By default is 'report'. You can also use
1572 'article' or 'book' (consult your LaTeX documentation for details).
1576 The paper size to be used in the document. By default is 'a4paper'.
1580 The character encoding used in the document. By default is 'latin1'.
1584 Note that you can't nest further than 4 levels in LaTeX; if you do,
1585 LaTeX will choke in the generated code with a 'Too deeply nested' error.
1591 my ($class, %args) = @_;
1594 bless(\
%args,$class);
1597 $gh->{'-process-urls'} = 0;
1599 $gh->{'-docclass'} ||= 'report';
1600 $gh->{'-papersize'} ||= 'a4paper';
1601 $gh->{'-encoding'} ||= 'latin1';
1611 $gh->_push("\\documentclass[$gh->{'-papersize'}]{$gh->{-docclass}}");
1612 $gh->_push("\\usepackage[$gh->{'-encoding'}]{inputenc}");
1614 $gh->_push("\\begin{document}");
1622 # accept only latex inlines
1623 if ($l =~ /^<<\s*latex$/i) {
1624 $gh->{'-inline'} = 'latex';
1629 delete $gh->{'-inline'};
1633 if ($gh->{'-inline'} eq 'latex') {
1643 $l =~ s/ _ / \\_ /g;
1644 $l =~ s/ ~ / \\~ /g;
1645 $l =~ s/ & / \\& /g;
1655 $l =~ s/ # / \\# /g;
1657 $l =~ s/([^\s_])_([^\s_])/$1\\_$2/g;
1673 my ($gh, $str) = @_;
1674 return "\\textbf{$str}";
1680 my ($gh, $str) = @_;
1681 return "\\emph{$str}";
1687 my ($gh, $str) = @_;
1688 return "{\\tt $str}";
1694 my ($gh, $str) = @_;
1695 return "{\\tt $str}";
1701 my ($gh, $str) = @_;
1703 $str =~ s/^\$/\\\$/;
1705 return "{\\tt $str}";
1711 my ($gh, $mode, $params) = @_;
1715 'pre' => 'verbatim',
1716 'blockquote' => 'quote',
1717 'table' => 'tabular',
1718 'dl' => 'description',
1723 if ($mode ne $gh->{'-mode'}) {
1724 # close previous mode
1725 if ($gh->{'-mode'} eq 'ul') {
1726 $gh->_push("\\end{itemize}" x
scalar(@
{$gh->{'-ul-levels'}}));
1728 elsif ($gh->{'-mode'} eq 'ol') {
1729 $gh->_push("\\end{enumerate}" x
scalar(@
{$gh->{'-ol-levels'}}));
1731 elsif ($gh->{'-mode'} eq 'table') {
1732 $gh->_push("\\end{tabular}\n");
1735 $gh->_push("\\end{" . $latex_modes{$gh->{'-mode'}} . "}")
1740 $gh->_push("\\begin{" . $latex_modes{$mode} . "}" . $params)
1743 $gh->{'-mode'} = $mode;
1745 $gh->{'-ul-levels'} = undef;
1746 $gh->{'-ol-levels'} = undef;
1753 my ($gh, $str) = @_;
1755 $gh->_new_mode('dl');
1756 return "\\item[$str]\n";
1762 my ($gh, $levels) = @_;
1768 $ret .= "\\begin{itemize}\n";
1770 elsif ($levels < 0) {
1771 $ret .= "\\end{itemize}\n" x
abs($levels);
1774 $gh->{'-mode'} = 'ul';
1784 my ($gh, $levels) = @_;
1790 $ret .= "\\begin{enumerate}\n";
1792 elsif ($levels < 0) {
1793 $ret .= "\\end{enumerate}\n" x
abs($levels);
1796 $gh->{'-mode'} = 'ol';
1808 $gh->_new_mode('blockquote');
1817 return "------------\n";
1823 my ($gh, $level, $l) = @_;
1825 my @latex_headings = ( "\\section*{", "\\subsection*{",
1826 "\\subsubsection*{");
1828 $l = "\n" . $latex_headings[$level - 1] . $l . "}";
1838 if ($gh->{'-mode'} eq 'table') {
1840 my (@spans) = $gh->_calc_col_span($str);
1846 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1849 $i = ${$gh->{'-table'}}[$n];
1850 $i = " " if $i =~ /^\s*$/;
1852 # $s = " colspan='$spans[$n]'" if $spans[$n] > 1;
1855 $i = "\\multicolumn{$spans[$n]}{|l|}{$i}"
1865 $str .= join('&', @cols) . "\\\\\n\\hline";
1867 # $str .= "\n\\hline" if $gh->{'-tbl-row'} == 1;
1869 @
{$gh->{'-table'}} = ();
1870 $gh->{'-tbl-row'}++;
1875 # count the number of columns
1877 my $params = "{" . "|l" x
(length($str) - 1) . "|}\n\\hline";
1880 $gh->_new_mode('table', $params);
1882 @
{$gh->{'-table'}} = ();
1883 $gh->{'-tbl-row'} = 1;
1895 $gh->_push("\\end{document}");
1901 Angel Ortega angel@triptico.com