1 #####################################################################
3 # Grutatxt - A text to HTML (and other things) converter
5 # Copyright (C) 2000/2011 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.
23 #####################################################################
29 $VERSION = '2.0.17-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://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'} = '';
223 if (!defined $gh->{marks
}) {
227 @
{$gh->{'marks'}} = ();
234 @
{$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;
296 $l =~ s/(mailto:\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
298 # URLs without phrase
299 $l =~ s/([^=][^\"])(https?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
300 $l =~ s/([^=][^\"])(ftps?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
301 $l =~ s/([^=][^\"])(file:\/?\S+)/$1.$gh->_url($2)/ge;
302 $l =~ s
|(\s
+)\
./(\S
+)|$1.$gh->_url($2)|ge;
303 $l =~ s/([^=][^\"])(mailto:)(\S+)/$1.$gh->_url($2.$3,$3)/ge;
305 $l =~ s/^(https?:\/\/\S
+)/$gh->_url($1)/ge
;
306 $l =~ s/^(ftps?:\/\/\S
+)/$gh->_url($1)/ge
;
307 $l =~ s/^(file:\/?\S+)/$gh->_url($1)/ge;
308 $l =~ s
|^\
./(\S
+)|$gh->_url($1)|ge;
311 # change '''text''' and *text* into strong emphasis
312 $l =~ s/\'\'\'([^\'][^\'][^\']*)\'\'\'/$gh->_strong($1)/ge;
313 $l =~ s/\*(\S[^\*]+\S)\*/$gh->_strong($1)/ge;
314 $l =~ s/\*(\S+)\*/$gh->_strong($1)/ge;
316 # change ''text'' and _text_ into emphasis
317 $l =~ s/\'\'([^\'][^\']*)\'\'/$gh->_em($1)/ge;
318 $l =~ s/\b_(\S[^_]*\S)_\b/$gh->_em($1)/ge;
319 $l =~ s/\b_(\S+)_\b/$gh->_em($1)/ge;
321 # change `text' into code
322 $l =~ s/`([^\']*)\'/$gh->_code($1)/ge;
324 # enclose function names
325 if ($gh->{'strip-parens'}) {
326 $l =~ s/(\w+)\(\)/$gh->_funcname($1)/ge;
329 $l =~ s/(\w+)\(\)/$gh->_funcname($1."()")/ge;
332 # enclose variable names
333 if ($gh->{'strip-dollars'}) {
334 $l =~ s/\$([\w_\.]+)/$gh->_varname($1)/ge;
337 $l =~ s/(\$[\w_\.]+)/$gh->_varname($1)/ge;
345 if ($l =~ /^\s\*\s+/ && $l =~ s/^\s\*\s+([^:\.,;]+)\:\s+/$gh->_dl($1)/e) {
346 $gh->{'-mode-elems'} ++;
350 elsif ($gh->{'-mode'} ne 'pre' and
351 ($l =~ s/^(\s+)\*\s+/$gh->_unsorted_list($1)/e or
352 $l =~ s/^(\s+)\-\s+/$gh->_unsorted_list($1)/e)) {
353 $gh->{'-mode-elems'} ++;
357 elsif ($gh->{'-mode'} ne 'pre' and
358 ($l =~ s/^(\s+)\#\s+/$gh->_ordered_list($1)/e or
359 $l =~ s/^(\s+)1\s+/$gh->_ordered_list($1)/e)) {
360 $gh->{'-mode-elems'} ++;
364 elsif ($gh->{'-mode'} ne 'pre' and
365 $l =~ s/^\s\"/$gh->_blockquote()/e) {
369 elsif ($l =~ s/^\s*\|(.*)\|\s*$/$gh->_table_row($1)/e) {
370 $gh->{'-mode-elems'} ++;
373 # table heading / end of row
374 elsif ($l =~ s/^\s*(\+[-\+\|]+\+)\s*$/$gh->_table($1)/e) {
378 elsif ($l =~ s/^(\s.*)$/$gh->_pre($1)/e) {
379 if ($gh->{'-mode'} eq 'pre' &&
380 !$gh->{'no-pure-verbatim'}) {
381 # set line back to original
388 # back to normal mode
389 $gh->_new_mode(undef);
393 $l =~ s/^(=+)\s*$/$gh->_process_heading(1,$1)/e;
396 $l =~ s/^(-+)\s*$/$gh->_process_heading(2,$1)/e;
399 $l =~ s/^(~+)\s*$/$gh->_process_heading(3,$1)/e;
401 # change ------ into hr
402 $l =~ s/^----*$/$gh->_hr()/e;
405 $gh->_push($l) if $l;
409 $gh->_new_mode(undef);
415 ${$gh->{'title'}} = $gh->{'-title'} if ref($gh->{'title'});
417 # set abstract, if not set
418 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})
419 if ref($gh->{'abstract'}) and not ${$gh->{'abstract'}};
421 # travel all lines again, post-escaping
422 @
{$gh->{'o'}} = map { $_ = $gh->_escape_post($_); } @
{$gh->{'o'}};
424 # add TOC after first paragraph
425 if ($gh->{toc
} && @
{$gh->{o
}}) {
426 my $p = $gh->{_toc_pos
} ||
430 @
{$gh->{o
}} = (@
{$gh->{o
}}[0 .. $p],
432 @
{$gh->{o
}}[$p + 1 ..
433 scalar(@
{$gh->{o
}})]);
436 return @
{$gh->{'o'}};
442 @output = $grutatxt->process_file($filename);
444 Processes a file in Grutatxt format.
450 my ($gh, $file) = @_;
452 open F
, $file or return(undef);
454 my ($content) = join('',<F
>);
457 return $gh->process($content);
465 push(@
{$gh->{'o'}},$l);
471 my ($gh, $level, $hd) = @_;
475 $l = pop(@
{$gh->{'o'}});
477 if ($l eq $gh->_empty_line()) {
483 if ($level == 1 and not $gh->{'-title'}) {
484 $gh->{'-title'} = $l;
489 if (ref($gh->{'index'})) {
490 push(@
{$gh->{'index'}}, [ $level, $l ]);
493 return $gh->_heading($level, $l, $is_title);
502 # strip first + and all -
506 my ($t) = 1; @spans = ();
507 for (my $n = 0; $n < length($l); $n++) {
508 if (substr($l, $n, 1) eq '+') {
513 # it's a colspan mark:
527 my @s = split(/\|/,$str);
529 for (my $n = 0; $n < scalar(@s); $n++) {
530 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
533 push(@
{$gh->{'-table-raw'}}, $str);
543 # if any other mode is active, add to it
544 if ($gh->{'-mode'} and $gh->{'-mode'} ne 'pre') {
547 my ($a) = pop(@
{$gh->{'o'}})." ".$l;
552 # tabs to spaces if a non-zero tabsize is given (only in LaTex)
553 $l =~ s/\t/' ' x $gh->{'tabsize'}/ge if $gh->{'tabsize'} > 0;
555 $gh->_new_mode('pre');
564 my ($gh, $str, $ind) = @_;
572 # if last level is less indented, increase
577 elsif ($l[-1] > $ind) {
578 # if last level is more indented, decrease
579 # levels until the same is found (or back to
580 # the beginning if not)
583 last if $l[-1] == $ind;
597 return $gh->_ul($gh->_multilevel_list('-ul-levels', $ind));
605 return $gh->_ol($gh->_multilevel_list('-ol-levels', $ind));
609 # empty stubs for falling through the superclass
611 sub _inline
{ my ($gh, $l) = @_; $l; }
612 sub _escape
{ my ($gh, $l) = @_; $l; }
613 sub _escape_post
{ my ($gh, $l) = @_; $l; }
614 sub _empty_line
{ my ($gh) = @_; ''; }
615 sub _url
{ my ($gh, $url, $label) = @_; ''; }
616 sub _strong
{ my ($gh, $str) = @_; $str; }
617 sub _em
{ my ($gh, $str) = @_; $str; }
618 sub _code
{ my ($gh, $str) = @_; $str; }
619 sub _funcname
{ my ($gh, $str) = @_; $str; }
620 sub _varname
{ my ($gh, $str) = @_; $str; }
621 sub _new_mode
{ my ($gh, $mode) = @_; }
622 sub _dl
{ my ($gh, $str) = @_; $str; }
623 sub _ul
{ my ($gh, $level) = @_; ''; }
624 sub _ol
{ my ($gh, $level) = @_; ''; }
625 sub _blockquote
{ my ($gh, $str) = @_; $str; }
626 sub _hr
{ my ($gh) = @_; ''; }
627 sub _heading
{ my ($gh, $level, $l) = @_; $l; }
628 sub _table
{ my ($gh, $str) = @_; $str; }
629 sub _prefix
{ my ($gh) = @_; }
630 sub _postfix
{ my ($gh) = @_; }
631 sub _toc
{ my ($gh) = @_; return (); }
633 ###########################################################
635 =head1 DRIVER SPECIFIC INFORMATION
639 ###########################################################
642 package Grutatxt
::HTML
;
648 The additional parameters for a new Grutatxt object are:
652 =item I<table-headers>
654 If this boolean value is set, the first row in tables
655 is assumed to be the heading and rendered using 'th'
656 instead of 'td' tags.
658 =item I<center-tables>
660 If this boolean value is set, tables are centered.
662 =item I<expand-tables>
664 If this boolean value is set, tables are expanded (width 100%).
668 If this boolean value is set, definition lists will be
669 rendered using 'dl', 'dt' and 'dd' instead of tables.
671 =item I<header-offset>
673 Offset to be summed to the heading level when rendering
674 'h?' tags (default is 0).
676 =item I<class-oddeven>
678 If this boolean value is set, tables will be rendered
679 with an "oddeven" CSS class, and rows alternately classed
680 as "even" or "odd". If it's not set, no CSS class info
683 =item I<url-label-max>
685 If an URL without label is given (that is, the URL itself
686 is used as the label), it's trimmed to have as much
687 characters as this value says. By default it's 80.
695 my ($class, %args) = @_;
698 bless(\
%args, $class);
701 $gh->{'-process-urls'} = 1;
702 $gh->{'url-label-max'} ||= 80;
712 # accept unnamed and HTML inlines
713 if ($l =~ /^<<$/ or $l =~ /^<<\s*html$/i) {
714 $gh->{'-inline'} = 'HTML';
719 delete $gh->{'-inline'};
723 if ($gh->{'-inline'} eq 'HTML') {
751 my ($gh, $url, $label) = @_;
756 if (length($label) > $gh->{'url-label-max'}) {
757 $label = substr($label, 0,
758 $gh->{'url-label-max'}) . '...';
762 return "<a href = \"$url\">$label</a>";
769 return "<strong>$str</strong>";
776 return "<em>$str</em>";
783 return "<code class = 'literal'>$str</code>";
790 return "<code class = 'funcname'>$str</code>";
797 return "<code class = 'var'>$str</code>";
803 my ($gh, $mode, $params) = @_;
805 if ($mode ne $gh->{'-mode'}) {
809 if ($gh->{'-mode'} eq 'ul') {
810 $gh->_push('</li>' . '</ul>' x
scalar(@
{$gh->{'-ul-levels'}}));
812 elsif ($gh->{'-mode'} eq 'ol') {
813 $gh->_push('</li>' . '</ol>' x
scalar(@
{$gh->{'-ol-levels'}}));
815 elsif ($gh->{'-mode'}) {
816 $gh->_push("</$gh->{'-mode'}>");
820 $tag = $params ?
"<$mode $params>" : "<$mode>";
821 $gh->_push($tag) if $mode;
823 $gh->{'-mode'} = $mode;
824 $gh->{'-mode-elems'} = 0;
826 # clean previous lists
827 $gh->{'-ul-levels'} = undef;
828 $gh->{'-ol-levels'} = undef;
838 if ($gh->{'dl-as-dl'}) {
839 $gh->_new_mode('dl');
840 $ret .= "<dt><strong class = 'term'>$str</strong><dd>";
843 $gh->_new_mode('table');
844 $ret .= "<tr><td valign = 'top'><strong class = 'term'>$1</strong> </td><td valign = 'top'>";
853 my ($gh, $levels) = @_;
861 elsif ($levels < 0) {
862 $ret .= '</li></ul>' x
abs($levels);
865 if ($gh->{'-mode'} ne 'ul') {
866 $gh->{'-mode'} = 'ul';
869 $ret .= '</li>' if $levels <= 0;
880 my ($gh, $levels) = @_;
888 elsif ($levels < 0) {
889 $ret .= '</li></ol>' x
abs($levels);
892 if ($gh->{'-mode'} ne 'ol') {
893 $gh->{'-mode'} = 'ol';
896 $ret .= '</li>' if $levels <= 0;
909 $gh->_new_mode('blockquote');
918 return "<hr size = '1' noshade = 'noshade'>";
938 my ($gh, $level, $l, $title) = @_;
940 # creates a valid anchor
941 my $a = $gh->__mkanchor($l);
944 "<a %s name = '%s'></a>\n<h%d class = 'level$level'>%s</h%d>",
945 $title ?
"class = 'title'" : '',
947 $level + $gh->{'header-offset'},
949 $level + $gh->{'header-offset'}
960 if ($gh->{'-mode'} eq 'table') {
962 my (@spans) = $gh->_calc_col_span($str);
964 # calculate CSS class, if any
965 if ($gh->{'class-oddeven'}) {
966 $class = "class = '" . ($gh->{'-tbl-row'} & 1) ?
"odd'" : "even'";
969 $str = "<tr $class>";
972 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
975 $i = ${$gh->{'-table'}}[$n];
976 $i = " " if $i =~ /^\s*$/;
978 $s = " colspan = '$spans[$n]'" if $spans[$n] > 1;
980 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
981 $str .= "<th $class $s>$i</th>";
984 $str .= "<td $class $s>$i</td>";
990 @
{$gh->{'-table'}} = ();
997 $params = "border = '1'";
998 $params .= " width = '100\%'" if $gh->{'expand-tables'};
999 $params .= " align = 'center'" if $gh->{'center-tables'};
1000 $params .= " class = 'oddeven'" if $gh->{'class-oddeven'};
1002 $gh->_new_mode('table', $params);
1004 @
{$gh->{'-table'}} = ();
1005 $gh->{'-tbl-row'} = 1;
1018 push(@t, "<div class = 'TOC'>");
1022 foreach my $e (@
{$gh->{index}}) {
1023 # ignore level 1 headings
1031 elsif ($l > $e->[0]) {
1037 push(@t, sprintf("<li><a href = '#%s'>%s</a></li>",
1038 $gh->__mkanchor($e->[1]), $e->[1]));
1050 ###########################################################
1053 package Grutatxt
::troff
;
1055 @ISA = ("Grutatxt");
1059 The troff driver uses the B<-me> macros and B<tbl>. A
1060 good way to post-process this output (to PostScript in
1061 the example) could be by using
1065 The additional parameters for a new Grutatxt object are:
1069 =item I<normal-size>
1071 The point size of normal text. By default is 10.
1073 =item I<heading-sizes>
1075 This argument must be a reference to an array containing
1076 the size in points of the 3 different heading levels. By
1077 default, level sizes are [ 20, 18, 15 ].
1081 The type of table to be rendered by B<tbl>. Can be
1082 I<allbox> (all lines rendered; this is the default value),
1083 I<box> (only outlined) or I<doublebox> (only outlined by
1092 my ($class, %args) = @_;
1095 bless(\
%args,$class);
1098 $gh->{'-process-urls'} = 0;
1100 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
1101 $gh->{'normal-size'} ||= 10;
1102 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
1112 $gh->_push(".nr pp $gh->{'normal-size'}");
1121 # accept only troff inlines
1122 if ($l =~ /^<<\s*troff$/i) {
1123 $gh->{'-inline'} = 'troff';
1128 delete $gh->{'-inline'};
1132 if ($gh->{'-inline'} eq 'troff') {
1159 my ($gh, $str) = @_;
1160 return "\\fB$str\\fP";
1166 my ($gh, $str) = @_;
1167 return "\\fI$str\\fP";
1173 my ($gh, $str) = @_;
1174 return "\\fI$str\\fP";
1180 my ($gh, $str) = @_;
1181 return "\\fB$str\\fP";
1187 my ($gh, $str) = @_;
1188 return "\\fI$str\\fP";
1194 my ($gh, $mode, $params) = @_;
1196 if ($mode ne $gh->{'-mode'}) {
1199 # flush previous list
1200 if ($gh->{'-mode'} eq 'pre') {
1203 elsif ($gh->{'-mode'} eq 'table') {
1204 chomp($gh->{'-table-head'});
1205 $gh->{'-table-head'} =~ s/\s+$//;
1206 $gh->_push($gh->{'-table-head'} . '.');
1207 $gh->_push($gh->{'-table-body'} . '.TE\n.sp 0.6');
1209 elsif ($gh->{'-mode'} eq 'blockquote') {
1214 if ($mode eq 'pre') {
1215 $gh->_push('.(l L');
1217 elsif ($mode eq 'blockquote') {
1221 $gh->{'-mode'} = $mode;
1228 my ($gh, $str) = @_;
1230 $gh->_new_mode('dl');
1231 return ".ip \"$str\"\n";
1239 $gh->_new_mode('ul');
1248 $gh->_new_mode('ol');
1257 $gh->_new_mode('blockquote');
1272 my ($gh, $level, $l) = @_;
1274 $l = '.sz ' . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1282 my ($gh, $str) = @_;
1284 if ($gh->{'-mode'} eq 'table') {
1286 my (@spans) = $gh->_calc_col_span($str);
1291 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1294 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
1302 $h .= 's ' x
($spans[$n] - 1) if $spans[$n] > 1;
1306 $i = ${$gh->{'-table'}}[$n];
1314 $b .= "\n_" if $gh->{'table-headers'} and
1315 $gh->{'-tbl-row'} == 1 and
1316 $gh->{'table-type'} ne "allbox";
1318 $gh->{'-table-head'} .= "$h\n";
1319 $gh->{'-table-body'} .= "$b\n";
1321 @
{$gh->{'-table'}} = ();
1322 $gh->{'-tbl-row'}++;
1326 $gh->_new_mode('table');
1328 @
{$gh->{'-table'}} = ();
1329 $gh->{'-tbl-row'} = 1;
1331 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1332 $gh->{'-table-body'} = '';
1344 # add to top headings and footers
1345 unshift(@
{$gh->{'o'}},".ef '\%' ''");
1346 unshift(@
{$gh->{'o'}},".of '' '\%'");
1347 unshift(@
{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1348 unshift(@
{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1352 ###########################################################
1355 package Grutatxt
::man
;
1357 @ISA = ("Grutatxt::troff", "Grutatxt");
1361 The man driver is used to generate Unix-like man pages. Note that
1362 all headings have the same level with this output driver.
1364 The additional parameters for a new Grutatxt object are:
1370 The man page section (see man documentation). By default is 1.
1374 The name of the page. This is usually the name of the program
1375 or function the man page is documenting and will be shown in the
1376 page header. By default is the empty string.
1384 my ($class, %args) = @_;
1387 bless(\
%args,$class);
1390 $gh->{'-process-urls'} = 0;
1392 $gh->{'section'} ||= 1;
1393 $gh->{'page-name'} ||= "";
1403 $gh->_push(".TH \"$gh->{'page-name'}\" \"$gh->{'section'}\" \"" . localtime() . "\"");
1411 # accept only man markup inlines
1412 if ($l =~ /^<<\s*man$/i) {
1413 $gh->{'-inline'} = 'man';
1418 delete $gh->{'-inline'};
1422 if ($gh->{'-inline'} eq 'man') {
1438 my ($gh,$mode,$params) = @_;
1440 if ($mode ne $gh->{'-mode'}) {
1443 # flush previous list
1444 if ($gh->{'-mode'} eq 'pre' or
1445 $gh->{'-mode'} eq 'table') {
1449 if ($gh->{'-mode'} eq 'blockquote') {
1453 if ($gh->{'-mode'} eq 'ul') {
1454 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ul-levels'}}));
1457 if ($gh->{'-mode'} eq 'ol') {
1458 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ol-levels'}}));
1462 if ($mode eq 'pre' or $mode eq 'table') {
1466 if ($mode eq 'blockquote') {
1467 $gh->_push('.RS 4');
1470 $gh->{'-mode'} = $mode;
1477 my ($gh, $str) = @_;
1479 $gh->_new_mode('dl');
1480 return ".TP\n.B \"$str\"\n";
1486 my ($gh, $levels) = @_;
1492 elsif ($levels < 0) {
1493 $ret = ".RE\n" x
abs($levels);
1496 $gh->_new_mode('ul');
1497 return $ret . ".TP 4\n\\(bu\n";
1503 my ($gh, $levels) = @_;
1504 my $l = @
{$gh->{'-ol-levels'}};
1507 $gh->{'-ol-level'} += $levels;
1512 $l[$gh->{'-ol-level'}] = 1;
1514 elsif ($levels < 0) {
1515 $ret = ".RE\n" x
abs($levels);
1518 $gh->_new_mode('ol');
1519 $ret .= ".TP 4\n" . $l[$gh->{'-ol-level'}]++ . ".\n";
1535 my ($gh, $level, $l) = @_;
1537 # all headers are the same depth in man pages
1538 return ".SH \"" . uc($l) . "\"";
1544 my ($gh, $str) = @_;
1546 if ($gh->{'-mode'} eq 'table') {
1547 foreach my $r (@
{$gh->{'-table-raw'}}) {
1552 $gh->_new_mode('table');
1555 @
{$gh->{'-table'}} = ();
1556 @
{$gh->{'-table-raw'}} = ();
1569 ###########################################################
1572 package Grutatxt
::latex
;
1574 @ISA = ("Grutatxt");
1578 The additional parameters for a new Grutatxt object are:
1584 The LaTeX document class. By default is 'report'. You can also use
1585 'article' or 'book' (consult your LaTeX documentation for details).
1589 The paper size to be used in the document. By default is 'a4paper'.
1593 The character encoding used in the document. By default is 'latin1'.
1597 Note that you can't nest further than 4 levels in LaTeX; if you do,
1598 LaTeX will choke in the generated code with a 'Too deeply nested' error.
1604 my ($class, %args) = @_;
1607 bless(\
%args,$class);
1610 $gh->{'-process-urls'} = 0;
1612 $gh->{'-docclass'} ||= 'report';
1613 $gh->{'-papersize'} ||= 'a4paper';
1614 $gh->{'-encoding'} ||= 'latin1';
1624 $gh->_push("\\documentclass[$gh->{'-papersize'}]{$gh->{-docclass}}");
1625 $gh->_push("\\usepackage[$gh->{'-encoding'}]{inputenc}");
1627 $gh->_push("\\begin{document}");
1635 # accept only latex inlines
1636 if ($l =~ /^<<\s*latex$/i) {
1637 $gh->{'-inline'} = 'latex';
1642 delete $gh->{'-inline'};
1646 if ($gh->{'-inline'} eq 'latex') {
1656 $l =~ s/ _ / \\_ /g;
1657 $l =~ s/ ~ / \\~ /g;
1658 $l =~ s/ & / \\& /g;
1668 $l =~ s/ # / \\# /g;
1670 $l =~ s/([^\s_])_([^\s_])/$1\\_$2/g;
1686 my ($gh, $str) = @_;
1687 return "\\textbf{$str}";
1693 my ($gh, $str) = @_;
1694 return "\\emph{$str}";
1700 my ($gh, $str) = @_;
1701 return "{\\tt $str}";
1707 my ($gh, $str) = @_;
1708 return "{\\tt $str}";
1714 my ($gh, $str) = @_;
1716 $str =~ s/^\$/\\\$/;
1718 return "{\\tt $str}";
1724 my ($gh, $mode, $params) = @_;
1728 'pre' => 'verbatim',
1729 'blockquote' => 'quote',
1730 'table' => 'tabular',
1731 'dl' => 'description',
1736 if ($mode ne $gh->{'-mode'}) {
1737 # close previous mode
1738 if ($gh->{'-mode'} eq 'ul') {
1739 $gh->_push("\\end{itemize}" x
scalar(@
{$gh->{'-ul-levels'}}));
1741 elsif ($gh->{'-mode'} eq 'ol') {
1742 $gh->_push("\\end{enumerate}" x
scalar(@
{$gh->{'-ol-levels'}}));
1744 elsif ($gh->{'-mode'} eq 'table') {
1745 $gh->_push("\\end{tabular}\n");
1748 $gh->_push("\\end{" . $latex_modes{$gh->{'-mode'}} . "}")
1753 $gh->_push("\\begin{" . $latex_modes{$mode} . "}" . $params)
1756 $gh->{'-mode'} = $mode;
1758 $gh->{'-ul-levels'} = undef;
1759 $gh->{'-ol-levels'} = undef;
1766 my ($gh, $str) = @_;
1768 $gh->_new_mode('dl');
1769 return "\\item[$str]\n";
1775 my ($gh, $levels) = @_;
1781 $ret .= "\\begin{itemize}\n";
1783 elsif ($levels < 0) {
1784 $ret .= "\\end{itemize}\n" x
abs($levels);
1787 $gh->{'-mode'} = 'ul';
1797 my ($gh, $levels) = @_;
1803 $ret .= "\\begin{enumerate}\n";
1805 elsif ($levels < 0) {
1806 $ret .= "\\end{enumerate}\n" x
abs($levels);
1809 $gh->{'-mode'} = 'ol';
1821 $gh->_new_mode('blockquote');
1830 return "------------\n";
1836 my ($gh, $level, $l) = @_;
1838 my @latex_headings = ( "\\section*{", "\\subsection*{",
1839 "\\subsubsection*{");
1841 $l = "\n" . $latex_headings[$level - 1] . $l . "}";
1851 if ($gh->{'-mode'} eq 'table') {
1853 my (@spans) = $gh->_calc_col_span($str);
1859 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1862 $i = ${$gh->{'-table'}}[$n];
1863 $i = " " if $i =~ /^\s*$/;
1865 # $s = " colspan='$spans[$n]'" if $spans[$n] > 1;
1868 $i = "\\multicolumn{$spans[$n]}{|l|}{$i}"
1878 $str .= join('&', @cols) . "\\\\\n\\hline";
1880 # $str .= "\n\\hline" if $gh->{'-tbl-row'} == 1;
1882 @
{$gh->{'-table'}} = ();
1883 $gh->{'-tbl-row'}++;
1888 # count the number of columns
1890 my $params = "{" . "|l" x
(length($str) - 1) . "|}\n\\hline";
1893 $gh->_new_mode('table', $params);
1895 @
{$gh->{'-table'}} = ();
1896 $gh->{'-tbl-row'} = 1;
1908 $gh->_push("\\end{document}");
1912 ###########################################################
1915 package Grutatxt
::rtf
;
1917 @ISA = ("Grutatxt");
1921 The additional parameters for a new Grutatxt object are:
1925 =item I<normal-size>
1927 The point size of normal text. By default is 20.
1929 =item I<heading-sizes>
1931 This argument must be a reference to an array containing
1932 the size in points of the 3 different heading levels. By
1933 default, level sizes are [ 34, 30, 28 ].
1941 my ($class, %args) = @_;
1944 bless(\
%args, $class);
1947 $gh->{'-process-urls'} = 0;
1949 $gh->{'heading-sizes'} ||= [ 34, 30, 28 ];
1950 $gh->{'normal-size'} ||= 20;
1960 $gh->_push('{\rtf1\ansi {\plain \fs' . $gh->{'normal-size'} . ' \sa227');
1974 my ($gh, $level, $l) = @_;
1976 return '{\b \fs' . $gh->{'heading-sizes'}->[$level] . ' ' . $l . '}';
1982 my ($gh, $str) = @_;
1983 return "{\\b $str}";
1989 my ($gh, $str) = @_;
1990 return "{\\i $str}";
1996 my ($gh, $str) = @_;
1997 return "{\\tt $str}";
2003 my ($gh, $levels) = @_;
2005 $gh->_new_mode('ul');
2006 return "{{\\bullet \\li" . $levels . ' ';
2012 my ($gh, $str) = @_;
2014 $gh->_new_mode('dl');
2015 return "{{\\b $str \\par} {\\li566 ";
2021 my ($gh, $mode, $params) = @_;
2023 if ($mode ne $gh->{'-mode'}) {
2024 if ($gh->{'-mode'} =~ /^(dl|ul)$/) {
2028 $gh->{'-mode'} = $mode;
2030 $gh->{'-ul-levels'} = undef;
2031 $gh->{'-ol-levels'} = undef;
2034 if ($mode =~ /^(dl|ul)$/) {
2035 $gh->_push('}\par}');
2045 @
{$gh->{o
}} = map { $_ . ' '; } @
{$gh->{o
}};
2053 Angel Ortega angel@triptico.com