1 #####################################################################
3 # Grutatxt - A text to HTML (and other things) converter
5 # Copyright (C) 2000/2010 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.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://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) = @_;
474 $l = pop(@
{$gh->{'o'}});
476 if ($l eq $gh->_empty_line()) {
482 $gh->{'-title'} = $l if $level == 1 and not $gh->{'-title'};
485 if (ref($gh->{'index'})) {
486 push(@
{$gh->{'index'}}, [ $level, $l ]);
489 return $gh->_heading($level,$l);
498 # strip first + and all -
502 my ($t) = 1; @spans = ();
503 for (my $n = 0; $n < length($l); $n++) {
504 if (substr($l, $n, 1) eq '+') {
509 # it's a colspan mark:
523 my @s = split(/\|/,$str);
525 for (my $n = 0; $n < scalar(@s); $n++) {
526 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
529 push(@
{$gh->{'-table-raw'}}, $str);
539 # if any other mode is active, add to it
540 if ($gh->{'-mode'} and $gh->{'-mode'} ne 'pre') {
543 my ($a) = pop(@
{$gh->{'o'}})." ".$l;
548 # tabs to spaces if a non-zero tabsize is given (only in LaTex)
549 $l =~ s/\t/' ' x $gh->{'tabsize'}/ge if $gh->{'tabsize'} > 0;
551 $gh->_new_mode('pre');
560 my ($gh, $str, $ind) = @_;
568 # if last level is less indented, increase
573 elsif ($l[-1] > $ind) {
574 # if last level is more indented, decrease
575 # levels until the same is found (or back to
576 # the beginning if not)
579 last if $l[-1] == $ind;
593 return $gh->_ul($gh->_multilevel_list('-ul-levels', $ind));
601 return $gh->_ol($gh->_multilevel_list('-ol-levels', $ind));
605 # empty stubs for falling through the superclass
607 sub _inline
{ my ($gh, $l) = @_; $l; }
608 sub _escape
{ my ($gh, $l) = @_; $l; }
609 sub _escape_post
{ my ($gh, $l) = @_; $l; }
610 sub _empty_line
{ my ($gh) = @_; ''; }
611 sub _url
{ my ($gh, $url, $label) = @_; ''; }
612 sub _strong
{ my ($gh, $str) = @_; $str; }
613 sub _em
{ my ($gh, $str) = @_; $str; }
614 sub _code
{ my ($gh, $str) = @_; $str; }
615 sub _funcname
{ my ($gh, $str) = @_; $str; }
616 sub _varname
{ my ($gh, $str) = @_; $str; }
617 sub _new_mode
{ my ($gh, $mode) = @_; }
618 sub _dl
{ my ($gh, $str) = @_; $str; }
619 sub _ul
{ my ($gh, $level) = @_; ''; }
620 sub _ol
{ my ($gh, $level) = @_; ''; }
621 sub _blockquote
{ my ($gh, $str) = @_; $str; }
622 sub _hr
{ my ($gh) = @_; ''; }
623 sub _heading
{ my ($gh, $level, $l) = @_; $l; }
624 sub _table
{ my ($gh, $str) = @_; $str; }
625 sub _prefix
{ my ($gh) = @_; }
626 sub _postfix
{ my ($gh) = @_; }
627 sub _toc
{ my ($gh) = @_; return (); }
629 ###########################################################
631 =head1 DRIVER SPECIFIC INFORMATION
635 ###########################################################
638 package Grutatxt
::HTML
;
644 The additional parameters for a new Grutatxt object are:
648 =item I<table-headers>
650 If this boolean value is set, the first row in tables
651 is assumed to be the heading and rendered using 'th'
652 instead of 'td' tags.
654 =item I<center-tables>
656 If this boolean value is set, tables are centered.
658 =item I<expand-tables>
660 If this boolean value is set, tables are expanded (width 100%).
664 If this boolean value is set, definition lists will be
665 rendered using 'dl', 'dt' and 'dd' instead of tables.
667 =item I<header-offset>
669 Offset to be summed to the heading level when rendering
670 'h?' tags (default is 0).
672 =item I<class-oddeven>
674 If this boolean value is set, tables will be rendered
675 with an "oddeven" CSS class, and rows alternately classed
676 as "even" or "odd". If it's not set, no CSS class info
679 =item I<url-label-max>
681 If an URL without label is given (that is, the URL itself
682 is used as the label), it's trimmed to have as much
683 characters as this value says. By default it's 80.
691 my ($class, %args) = @_;
694 bless(\
%args, $class);
697 $gh->{'-process-urls'} = 1;
698 $gh->{'url-label-max'} ||= 80;
708 # accept unnamed and HTML inlines
709 if ($l =~ /^<<$/ or $l =~ /^<<\s*html$/i) {
710 $gh->{'-inline'} = 'HTML';
715 delete $gh->{'-inline'};
719 if ($gh->{'-inline'} eq 'HTML') {
747 my ($gh, $url, $label) = @_;
752 if (length($label) > $gh->{'url-label-max'}) {
753 $label = substr($label, 0,
754 $gh->{'url-label-max'}) . '...';
758 return "<a href = \"$url\">$label</a>";
765 return "<strong>$str</strong>";
772 return "<em>$str</em>";
779 return "<code class = 'literal'>$str</code>";
786 return "<code class = 'funcname'>$str</code>";
793 return "<code class = 'var'>$str</code>";
799 my ($gh, $mode, $params) = @_;
801 if ($mode ne $gh->{'-mode'}) {
805 if ($gh->{'-mode'} eq 'ul') {
806 $gh->_push('</li>' . '</ul>' x
scalar(@
{$gh->{'-ul-levels'}}));
808 elsif ($gh->{'-mode'} eq 'ol') {
809 $gh->_push('</li>' . '</ol>' x
scalar(@
{$gh->{'-ol-levels'}}));
811 elsif ($gh->{'-mode'}) {
812 $gh->_push("</$gh->{'-mode'}>");
816 $tag = $params ?
"<$mode $params>" : "<$mode>";
817 $gh->_push($tag) if $mode;
819 $gh->{'-mode'} = $mode;
820 $gh->{'-mode-elems'} = 0;
822 # clean previous lists
823 $gh->{'-ul-levels'} = undef;
824 $gh->{'-ol-levels'} = undef;
834 if ($gh->{'dl-as-dl'}) {
835 $gh->_new_mode('dl');
836 $ret .= "<dt><strong class = 'term'>$str</strong><dd>";
839 $gh->_new_mode('table');
840 $ret .= "<tr><td valign = 'top'><strong class = 'term'>$1</strong> </td><td valign = 'top'>";
849 my ($gh, $levels) = @_;
857 elsif ($levels < 0) {
858 $ret .= '</li></ul>' x
abs($levels);
861 if ($gh->{'-mode'} ne 'ul') {
862 $gh->{'-mode'} = 'ul';
865 $ret .= '</li>' if $levels <= 0;
876 my ($gh, $levels) = @_;
884 elsif ($levels < 0) {
885 $ret .= '</li></ol>' x
abs($levels);
888 if ($gh->{'-mode'} ne 'ol') {
889 $gh->{'-mode'} = 'ol';
892 $ret .= '</li>' if $levels <= 0;
905 $gh->_new_mode('blockquote');
914 return "<hr size = '1' noshade = 'noshade'>";
934 my ($gh, $level, $l) = @_;
936 # creates a valid anchor
937 my $a = $gh->__mkanchor($l);
939 $l = sprintf("<a name = '%s'></a>\n<h%d class = 'level$level'>%s</h%d>",
940 $a, $level+$gh->{'header-offset'},
941 $l, $level+$gh->{'header-offset'});
951 if ($gh->{'-mode'} eq 'table') {
953 my (@spans) = $gh->_calc_col_span($str);
955 # calculate CSS class, if any
956 if ($gh->{'class-oddeven'}) {
957 $class = "class = '" . ($gh->{'-tbl-row'} & 1) ?
"odd'" : "even'";
960 $str = "<tr $class>";
963 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
966 $i = ${$gh->{'-table'}}[$n];
967 $i = " " if $i =~ /^\s*$/;
969 $s = " colspan = '$spans[$n]'" if $spans[$n] > 1;
971 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
972 $str .= "<th $class $s>$i</th>";
975 $str .= "<td $class $s>$i</td>";
981 @
{$gh->{'-table'}} = ();
988 $params = "border = '1'";
989 $params .= " width = '100\%'" if $gh->{'expand-tables'};
990 $params .= " align = 'center'" if $gh->{'center-tables'};
991 $params .= " class = 'oddeven'" if $gh->{'class-oddeven'};
993 $gh->_new_mode('table', $params);
995 @
{$gh->{'-table'}} = ();
996 $gh->{'-tbl-row'} = 1;
1009 push(@t, "<div class = 'TOC'>");
1013 foreach my $e (@
{$gh->{index}}) {
1014 # ignore level 1 headings
1022 elsif ($l > $e->[0]) {
1028 push(@t, sprintf("<li><a href = '#%s'>%s</a></li>",
1029 $gh->__mkanchor($e->[1]), $e->[1]));
1041 ###########################################################
1044 package Grutatxt
::troff
;
1046 @ISA = ("Grutatxt");
1050 The troff driver uses the B<-me> macros and B<tbl>. A
1051 good way to post-process this output (to PostScript in
1052 the example) could be by using
1056 The additional parameters for a new Grutatxt object are:
1060 =item I<normal-size>
1062 The point size of normal text. By default is 10.
1064 =item I<heading-sizes>
1066 This argument must be a reference to an array containing
1067 the size in points of the 3 different heading levels. By
1068 default, level sizes are [ 20, 18, 15 ].
1072 The type of table to be rendered by B<tbl>. Can be
1073 I<allbox> (all lines rendered; this is the default value),
1074 I<box> (only outlined) or I<doublebox> (only outlined by
1083 my ($class, %args) = @_;
1086 bless(\
%args,$class);
1089 $gh->{'-process-urls'} = 0;
1091 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
1092 $gh->{'normal-size'} ||= 10;
1093 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
1103 $gh->_push(".nr pp $gh->{'normal-size'}");
1112 # accept only troff inlines
1113 if ($l =~ /^<<\s*troff$/i) {
1114 $gh->{'-inline'} = 'troff';
1119 delete $gh->{'-inline'};
1123 if ($gh->{'-inline'} eq 'troff') {
1150 my ($gh, $str) = @_;
1151 return "\\fB$str\\fP";
1157 my ($gh, $str) = @_;
1158 return "\\fI$str\\fP";
1164 my ($gh, $str) = @_;
1165 return "\\fI$str\\fP";
1171 my ($gh, $str) = @_;
1172 return "\\fB$str\\fP";
1178 my ($gh, $str) = @_;
1179 return "\\fI$str\\fP";
1185 my ($gh, $mode, $params) = @_;
1187 if ($mode ne $gh->{'-mode'}) {
1190 # flush previous list
1191 if ($gh->{'-mode'} eq 'pre') {
1194 elsif ($gh->{'-mode'} eq 'table') {
1195 chomp($gh->{'-table-head'});
1196 $gh->{'-table-head'} =~ s/\s+$//;
1197 $gh->_push($gh->{'-table-head'} . '.');
1198 $gh->_push($gh->{'-table-body'} . '.TE\n.sp 0.6');
1200 elsif ($gh->{'-mode'} eq 'blockquote') {
1205 if ($mode eq 'pre') {
1206 $gh->_push('.(l L');
1208 elsif ($mode eq 'blockquote') {
1212 $gh->{'-mode'} = $mode;
1219 my ($gh, $str) = @_;
1221 $gh->_new_mode('dl');
1222 return ".ip \"$str\"\n";
1230 $gh->_new_mode('ul');
1239 $gh->_new_mode('ol');
1248 $gh->_new_mode('blockquote');
1263 my ($gh, $level, $l) = @_;
1265 $l = '.sz ' . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1273 my ($gh, $str) = @_;
1275 if ($gh->{'-mode'} eq 'table') {
1277 my (@spans) = $gh->_calc_col_span($str);
1282 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1285 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
1293 $h .= 's ' x
($spans[$n] - 1) if $spans[$n] > 1;
1297 $i = ${$gh->{'-table'}}[$n];
1305 $b .= "\n_" if $gh->{'table-headers'} and
1306 $gh->{'-tbl-row'} == 1 and
1307 $gh->{'table-type'} ne "allbox";
1309 $gh->{'-table-head'} .= "$h\n";
1310 $gh->{'-table-body'} .= "$b\n";
1312 @
{$gh->{'-table'}} = ();
1313 $gh->{'-tbl-row'}++;
1317 $gh->_new_mode('table');
1319 @
{$gh->{'-table'}} = ();
1320 $gh->{'-tbl-row'} = 1;
1322 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1323 $gh->{'-table-body'} = '';
1335 # add to top headings and footers
1336 unshift(@
{$gh->{'o'}},".ef '\%' ''");
1337 unshift(@
{$gh->{'o'}},".of '' '\%'");
1338 unshift(@
{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1339 unshift(@
{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1343 ###########################################################
1346 package Grutatxt
::man
;
1348 @ISA = ("Grutatxt::troff", "Grutatxt");
1352 The man driver is used to generate Unix-like man pages. Note that
1353 all headings have the same level with this output driver.
1355 The additional parameters for a new Grutatxt object are:
1361 The man page section (see man documentation). By default is 1.
1365 The name of the page. This is usually the name of the program
1366 or function the man page is documenting and will be shown in the
1367 page header. By default is the empty string.
1375 my ($class, %args) = @_;
1378 bless(\
%args,$class);
1381 $gh->{'-process-urls'} = 0;
1383 $gh->{'section'} ||= 1;
1384 $gh->{'page-name'} ||= "";
1394 $gh->_push(".TH \"$gh->{'page-name'}\" \"$gh->{'section'}\" \"" . localtime() . "\"");
1402 # accept only man markup inlines
1403 if ($l =~ /^<<\s*man$/i) {
1404 $gh->{'-inline'} = 'man';
1409 delete $gh->{'-inline'};
1413 if ($gh->{'-inline'} eq 'man') {
1429 my ($gh,$mode,$params) = @_;
1431 if ($mode ne $gh->{'-mode'}) {
1434 # flush previous list
1435 if ($gh->{'-mode'} eq 'pre' or
1436 $gh->{'-mode'} eq 'table') {
1440 if ($gh->{'-mode'} eq 'blockquote') {
1444 if ($gh->{'-mode'} eq 'ul') {
1445 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ul-levels'}}));
1448 if ($gh->{'-mode'} eq 'ol') {
1449 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ol-levels'}}));
1453 if ($mode eq 'pre' or $mode eq 'table') {
1457 if ($mode eq 'blockquote') {
1458 $gh->_push('.RS 4');
1461 $gh->{'-mode'} = $mode;
1468 my ($gh, $str) = @_;
1470 $gh->_new_mode('dl');
1471 return ".TP\n.B \"$str\"\n";
1477 my ($gh, $levels) = @_;
1483 elsif ($levels < 0) {
1484 $ret = ".RE\n" x
abs($levels);
1487 $gh->_new_mode('ul');
1488 return $ret . ".TP 4\n\\(bu\n";
1494 my ($gh, $levels) = @_;
1495 my $l = @
{$gh->{'-ol-levels'}};
1498 $gh->{'-ol-level'} += $levels;
1503 $l[$gh->{'-ol-level'}] = 1;
1505 elsif ($levels < 0) {
1506 $ret = ".RE\n" x
abs($levels);
1509 $gh->_new_mode('ol');
1510 $ret .= ".TP 4\n" . $l[$gh->{'-ol-level'}]++ . ".\n";
1526 my ($gh, $level, $l) = @_;
1528 # all headers are the same depth in man pages
1529 return ".SH \"" . uc($l) . "\"";
1535 my ($gh, $str) = @_;
1537 if ($gh->{'-mode'} eq 'table') {
1538 foreach my $r (@
{$gh->{'-table-raw'}}) {
1543 $gh->_new_mode('table');
1546 @
{$gh->{'-table'}} = ();
1547 @
{$gh->{'-table-raw'}} = ();
1560 ###########################################################
1563 package Grutatxt
::latex
;
1565 @ISA = ("Grutatxt");
1569 The additional parameters for a new Grutatxt object are:
1575 The LaTeX document class. By default is 'report'. You can also use
1576 'article' or 'book' (consult your LaTeX documentation for details).
1580 The paper size to be used in the document. By default is 'a4paper'.
1584 The character encoding used in the document. By default is 'latin1'.
1588 Note that you can't nest further than 4 levels in LaTeX; if you do,
1589 LaTeX will choke in the generated code with a 'Too deeply nested' error.
1595 my ($class, %args) = @_;
1598 bless(\
%args,$class);
1601 $gh->{'-process-urls'} = 0;
1603 $gh->{'-docclass'} ||= 'report';
1604 $gh->{'-papersize'} ||= 'a4paper';
1605 $gh->{'-encoding'} ||= 'latin1';
1615 $gh->_push("\\documentclass[$gh->{'-papersize'}]{$gh->{-docclass}}");
1616 $gh->_push("\\usepackage[$gh->{'-encoding'}]{inputenc}");
1618 $gh->_push("\\begin{document}");
1626 # accept only latex inlines
1627 if ($l =~ /^<<\s*latex$/i) {
1628 $gh->{'-inline'} = 'latex';
1633 delete $gh->{'-inline'};
1637 if ($gh->{'-inline'} eq 'latex') {
1647 $l =~ s/ _ / \\_ /g;
1648 $l =~ s/ ~ / \\~ /g;
1649 $l =~ s/ & / \\& /g;
1659 $l =~ s/ # / \\# /g;
1661 $l =~ s/([^\s_])_([^\s_])/$1\\_$2/g;
1677 my ($gh, $str) = @_;
1678 return "\\textbf{$str}";
1684 my ($gh, $str) = @_;
1685 return "\\emph{$str}";
1691 my ($gh, $str) = @_;
1692 return "{\\tt $str}";
1698 my ($gh, $str) = @_;
1699 return "{\\tt $str}";
1705 my ($gh, $str) = @_;
1707 $str =~ s/^\$/\\\$/;
1709 return "{\\tt $str}";
1715 my ($gh, $mode, $params) = @_;
1719 'pre' => 'verbatim',
1720 'blockquote' => 'quote',
1721 'table' => 'tabular',
1722 'dl' => 'description',
1727 if ($mode ne $gh->{'-mode'}) {
1728 # close previous mode
1729 if ($gh->{'-mode'} eq 'ul') {
1730 $gh->_push("\\end{itemize}" x
scalar(@
{$gh->{'-ul-levels'}}));
1732 elsif ($gh->{'-mode'} eq 'ol') {
1733 $gh->_push("\\end{enumerate}" x
scalar(@
{$gh->{'-ol-levels'}}));
1735 elsif ($gh->{'-mode'} eq 'table') {
1736 $gh->_push("\\end{tabular}\n");
1739 $gh->_push("\\end{" . $latex_modes{$gh->{'-mode'}} . "}")
1744 $gh->_push("\\begin{" . $latex_modes{$mode} . "}" . $params)
1747 $gh->{'-mode'} = $mode;
1749 $gh->{'-ul-levels'} = undef;
1750 $gh->{'-ol-levels'} = undef;
1757 my ($gh, $str) = @_;
1759 $gh->_new_mode('dl');
1760 return "\\item[$str]\n";
1766 my ($gh, $levels) = @_;
1772 $ret .= "\\begin{itemize}\n";
1774 elsif ($levels < 0) {
1775 $ret .= "\\end{itemize}\n" x
abs($levels);
1778 $gh->{'-mode'} = 'ul';
1788 my ($gh, $levels) = @_;
1794 $ret .= "\\begin{enumerate}\n";
1796 elsif ($levels < 0) {
1797 $ret .= "\\end{enumerate}\n" x
abs($levels);
1800 $gh->{'-mode'} = 'ol';
1812 $gh->_new_mode('blockquote');
1821 return "------------\n";
1827 my ($gh, $level, $l) = @_;
1829 my @latex_headings = ( "\\section*{", "\\subsection*{",
1830 "\\subsubsection*{");
1832 $l = "\n" . $latex_headings[$level - 1] . $l . "}";
1842 if ($gh->{'-mode'} eq 'table') {
1844 my (@spans) = $gh->_calc_col_span($str);
1850 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1853 $i = ${$gh->{'-table'}}[$n];
1854 $i = " " if $i =~ /^\s*$/;
1856 # $s = " colspan='$spans[$n]'" if $spans[$n] > 1;
1859 $i = "\\multicolumn{$spans[$n]}{|l|}{$i}"
1869 $str .= join('&', @cols) . "\\\\\n\\hline";
1871 # $str .= "\n\\hline" if $gh->{'-tbl-row'} == 1;
1873 @
{$gh->{'-table'}} = ();
1874 $gh->{'-tbl-row'}++;
1879 # count the number of columns
1881 my $params = "{" . "|l" x
(length($str) - 1) . "|}\n\\hline";
1884 $gh->_new_mode('table', $params);
1886 @
{$gh->{'-table'}} = ();
1887 $gh->{'-tbl-row'} = 1;
1899 $gh->_push("\\end{document}");
1903 ###########################################################
1906 package Grutatxt
::rtf
;
1908 @ISA = ("Grutatxt");
1912 The additional parameters for a new Grutatxt object are:
1916 =item I<normal-size>
1918 The point size of normal text. By default is 20.
1920 =item I<heading-sizes>
1922 This argument must be a reference to an array containing
1923 the size in points of the 3 different heading levels. By
1924 default, level sizes are [ 34, 30, 28 ].
1932 my ($class, %args) = @_;
1935 bless(\
%args, $class);
1938 $gh->{'-process-urls'} = 0;
1940 $gh->{'heading-sizes'} ||= [ 34, 30, 28 ];
1941 $gh->{'normal-size'} ||= 20;
1951 $gh->_push('{\rtf1\ansi {\plain \fs' . $gh->{'normal-size'} . ' \sa227');
1965 my ($gh, $level, $l) = @_;
1967 return '{\b \fs' . $gh->{'heading-sizes'}->[$level] . ' ' . $l . '}';
1973 my ($gh, $str) = @_;
1974 return "{\\b $str}";
1980 my ($gh, $str) = @_;
1981 return "{\\i $str}";
1987 my ($gh, $str) = @_;
1988 return "{\\tt $str}";
1994 my ($gh, $levels) = @_;
1996 $gh->_new_mode('ul');
1997 return "{{\\bullet \\li" . $levels . ' ';
2003 my ($gh, $str) = @_;
2005 $gh->_new_mode('dl');
2006 return "{{\\b $str \\par} {\\li566 ";
2012 my ($gh, $mode, $params) = @_;
2014 if ($mode ne $gh->{'-mode'}) {
2015 if ($gh->{'-mode'} =~ /^(dl|ul)$/) {
2019 $gh->{'-mode'} = $mode;
2021 $gh->{'-ul-levels'} = undef;
2022 $gh->{'-ol-levels'} = undef;
2025 if ($mode =~ /^(dl|ul)$/) {
2026 $gh->_push('}\par}');
2036 @
{$gh->{o
}} = map { $_ . ' '; } @
{$gh->{o
}};
2044 Angel Ortega angel@triptico.com