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
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 strings in the format
118 This information can be used to build a table of contents
119 of the processed text.
121 =item I<strip-parens>
123 Function names in the Grutatxt markup are strings of
124 alphanumeric characters immediately followed by a pair
125 of open and close parentheses. If this boolean value is
126 set, function names found in the processed text will have
127 their parentheses deleted.
129 =item I<strip-dollars>
131 Variable names in the Grutatxt markup are strings of
132 alphanumeric characters preceded by a dollar sign.
133 If this boolean value is set, variable names found in
134 the processed text will have the dollar sign deleted.
138 The I<abstract> of a Grutatxt document is the fragment of text
139 from the beginning of the document to the end of the first
140 paragraph after the title. If I<abstract> is specified as a
141 reference to scalar, it will contain (after each call to the
142 B<process()> method) the subscript of the element of the output
143 array that marks the end of the subject.
151 my ($class, %args) = @_;
154 $args{'mode'} ||= 'HTML';
156 $class .= "::" . $args{'mode'};
158 $gh = new
$class(%args);
165 # escapes special characters, ignoring passthrough code
169 # splits between << and >>
170 my (@l) = split(/(<<|>>)/, $l);
175 # escape only text outside << and >>
176 unless ($l eq '<<' .. $l eq '>>') {
177 $l = $gh->_escape($l);
183 # join again, stripping << and >>
184 $l = join('', grep(!/^(<<|>>)$/, @l));
192 @output = $grutatxt->process($text);
194 Processes a text in Grutatxt format. The result is returned
195 as an array of lines.
201 my ($gh, $content) = @_;
207 # clean title and paragraph numbers
208 $gh->{'-title'} = '';
212 @
{$gh->{'marks'}} = () if ref($gh->{'marks'});
215 @
{$gh->{'index'}} = () if ref($gh->{'index'});
217 # reset abstract line
218 ${$gh->{'abstract'}} = 0 if ref($gh->{'abstract'});
223 $gh->{'-mode'} = undef;
225 foreach my $l (split(/\n/,$content)) {
226 # inline data (passthrough)
227 if ($l =~ /^<<$/ .. $l =~ /^>>$/) {
233 if ($l =~ /^\s*<\->\s*$/) {
234 push(@
{$gh->{'marks'}},scalar(@
{$gh->{'o'}}))
235 if ref($gh->{'marks'});
240 # escape possibly dangerous characters
241 $l = $gh->escape($l);
245 if ($l =~ s/^$/$gh->_empty_line()/ge) {
246 # mark the abstract end
247 if ($gh->{'-title'}) {
250 # mark abstract if it's the
251 # second paragraph from the title
252 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})-1
257 # line-mutating process
260 if ($gh->{'-process-urls'}) {
261 # URLs followed by a parenthesized phrase
262 $l =~ s/(https?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
263 $l =~ s/(ftps?:\/\/\S
+)\s
+\
(([^\
)]+)\
)/$gh->_url($1,$2)/ge
;
264 $l =~ s/(file:\/?\S+)\s+\(([^\)]+)\)/$gh->_url($1,$2)/ge;
265 $l =~ s
|(\s
+)\
./(\S
+)\s
+\
(([^\
)]+)\
)|$1.$gh->_url($2,$3)|ge;
266 $l =~ s
|^\
./(\S
+)\s
+\
(([^\
)]+)\
)|$gh->_url($1,$2)|ge;
268 # URLs without phrase
269 $l =~ s/([^=][^\"])(https?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
270 $l =~ s/([^=][^\"])(ftps?:\/\/\S
+)/$1.$gh->_url($2)/ge
;
271 $l =~ s/([^=][^\"])(file:\/?\S+)/$1.$gh->_url($2)/ge;
272 $l =~ s
|(\s
+)\
./(\S
+)|$1.$gh->_url($2)|ge;
274 $l =~ s/^(https?:\/\/\S
+)/$gh->_url($1)/ge
;
275 $l =~ s/^(ftps?:\/\/\S
+)/$gh->_url($1)/ge
;
276 $l =~ s/^(file:\/?\S+)/$gh->_url($1)/ge;
277 $l =~ s
|^\
./(\S
+)|$gh->_url($1)|ge;
280 # change '''text''' and *text* into strong emphasis
281 $l =~ s/\'\'\'([^\'][^\'][^\']*)\'\'\'/$gh->_strong($1)/ge;
282 $l =~ s/\*(\S[^\*]+\S)\*/$gh->_strong($1)/ge;
283 $l =~ s/\*(\S+)\*/$gh->_strong($1)/ge;
285 # change ''text'' and _text_ into emphasis
286 $l =~ s/\'\'([^\'][^\']*)\'\'/$gh->_em($1)/ge;
287 $l =~ s/\b_(\S[^_]*\S)_\b/$gh->_em($1)/ge;
288 $l =~ s/\b_(\S+)_\b/$gh->_em($1)/ge;
290 # change `text' into code
291 $l =~ s/`([^\']*)\'/$gh->_code($1)/ge;
293 # enclose function names
294 if ($gh->{'strip-parens'}) {
295 $l =~ s/(\w+)\(\)/$gh->_funcname($1)/ge;
298 $l =~ s/(\w+)\(\)/$gh->_funcname($1."()")/ge;
301 # enclose variable names
302 if ($gh->{'strip-dollars'}) {
303 $l =~ s/\$([\w_\.]+)/$gh->_varname($1)/ge;
306 $l =~ s/(\$[\w_\.]+)/$gh->_varname($1)/ge;
314 if ($l =~ /^\s\*\s+/ && $l =~ s/^\s\*\s+([^:\.,;]+)\:\s+/$gh->_dl($1)/e) {
315 $gh->{'-mode-elems'} ++;
319 elsif ($gh->{'-mode'} ne 'pre' and
320 ($l =~ s/^(\s+)\*\s+/$gh->_unsorted_list($1)/e or
321 $l =~ s/^(\s+)\-\s+/$gh->_unsorted_list($1)/e)) {
322 $gh->{'-mode-elems'} ++;
326 elsif ($gh->{'-mode'} ne 'pre' and
327 ($l =~ s/^(\s+)\#\s+/$gh->_ordered_list($1)/e or
328 $l =~ s/^(\s+)1\s+/$gh->_ordered_list($1)/e)) {
329 $gh->{'-mode-elems'} ++;
333 elsif ($l =~ s/^\s\"/$gh->_blockquote()/e) {
337 elsif ($l =~ s/^\s*\|(.*)\|\s*$/$gh->_table_row($1)/e) {
338 $gh->{'-mode-elems'} ++;
341 # table heading / end of row
342 elsif ($l =~ s/^\s*(\+[-\+\|]+\+)\s*$/$gh->_table($1)/e) {
346 elsif ($l =~ s/^(\s.*)$/$gh->_pre($1)/e) {
347 if ($gh->{'-mode'} eq 'pre' &&
348 !$gh->{'no-pure-verbatim'}) {
349 # set line back to original
356 # back to normal mode
357 $gh->_new_mode(undef);
361 $l =~ s/^(=+)\s*$/$gh->_process_heading(1,$1)/e;
364 $l =~ s/^(-+)\s*$/$gh->_process_heading(2,$1)/e;
367 $l =~ s/^(~+)\s*$/$gh->_process_heading(3,$1)/e;
369 # change ------ into hr
370 $l =~ s/^----*$/$gh->_hr()/e;
373 $gh->_push($l) if $l;
377 $gh->_new_mode(undef);
383 ${$gh->{'title'}} = $gh->{'-title'} if ref($gh->{'title'});
385 # set abstract, if not set
386 ${$gh->{'abstract'}} = scalar(@
{$gh->{'o'}})
387 if ref($gh->{'abstract'}) and not ${$gh->{'abstract'}};
389 # travel all lines again, post-escaping
390 @
{$gh->{'o'}} = map { $_ = $gh->_escape_post($_); } @
{$gh->{'o'}};
392 return @
{$gh->{'o'}};
398 @output = $grutatxt->process_file($filename);
400 Processes a file in Grutatxt format.
406 my ($gh, $file) = @_;
408 open F
, $file or return(undef);
410 my ($content) = join('',<F
>);
413 return $gh->process($content);
421 push(@
{$gh->{'o'}},$l);
427 my ($gh, $level, $hd) = @_;
430 $l = pop(@
{$gh->{'o'}});
432 if ($l eq $gh->_empty_line()) {
438 $gh->{'-title'} = $l if $level == 1 and not $gh->{'-title'};
441 if (ref($gh->{'index'})) {
442 push(@
{$gh->{'index'}},"$level,$l");
445 return $gh->_heading($level,$l);
454 # strip first + and all -
458 my ($t) = 1; @spans = ();
459 for (my $n = 0; $n < length($l); $n++) {
460 if (substr($l, $n, 1) eq '+') {
465 # it's a colspan mark:
479 my @s = split(/\|/,$str);
481 for (my $n = 0; $n < scalar(@s); $n++) {
482 ${$gh->{'-table'}}[$n] .= ' ' . $s[$n];
485 push(@
{$gh->{'-table-raw'}}, $str);
495 # if any other mode is active, add to it
496 if ($gh->{'-mode'} and $gh->{'-mode'} ne 'pre') {
499 my ($a) = pop(@
{$gh->{'o'}})." ".$l;
504 # tabs to spaces if a non-zero tabsize is given (only in LaTex)
505 $l =~ s/\t/' ' x $gh->{'tabsize'}/ge if $gh->{'tabsize'} > 0;
507 $gh->_new_mode('pre');
516 my ($gh, $str, $ind) = @_;
524 # if last level is less indented, increase
529 elsif ($l[-1] > $ind) {
530 # if last level is more indented, decrease
531 # levels until the same is found (or back to
532 # the beginning if not)
535 last if $l[-1] == $ind;
549 return $gh->_ul($gh->_multilevel_list('-ul-levels', $ind));
557 return $gh->_ol($gh->_multilevel_list('-ol-levels', $ind));
561 # empty stubs for falling through the superclass
563 sub _inline
{ my ($gh, $l) = @_; $l; }
564 sub _escape
{ my ($gh, $l) = @_; $l; }
565 sub _escape_post
{ my ($gh, $l) = @_; $l; }
566 sub _empty_line
{ my ($gh) = @_; ''; }
567 sub _url
{ my ($gh, $url, $label) = @_; ''; }
568 sub _strong
{ my ($gh, $str) = @_; $str; }
569 sub _em
{ my ($gh, $str) = @_; $str; }
570 sub _code
{ my ($gh, $str) = @_; $str; }
571 sub _funcname
{ my ($gh, $str) = @_; $str; }
572 sub _varname
{ my ($gh, $str) = @_; $str; }
573 sub _new_mode
{ my ($gh, $mode) = @_; }
574 sub _dl
{ my ($gh, $str) = @_; $str; }
575 sub _ul
{ my ($gh, $level) = @_; ''; }
576 sub _ol
{ my ($gh, $level) = @_; ''; }
577 sub _blockquote
{ my ($gh, $str) = @_; $str; }
578 sub _hr
{ my ($gh) = @_; ''; }
579 sub _heading
{ my ($gh, $level, $l) = @_; $l; }
580 sub _table
{ my ($gh, $str) = @_; $str; }
581 sub _prefix
{ my ($gh) = @_; }
582 sub _postfix
{ my ($gh) = @_; }
584 ###########################################################
586 =head1 DRIVER SPECIFIC INFORMATION
590 ###########################################################
593 package Grutatxt
::HTML
;
599 The additional parameters for a new Grutatxt object are:
603 =item I<table-headers>
605 If this boolean value is set, the first row in tables
606 is assumed to be the heading and rendered using 'th'
607 instead of 'td' tags.
609 =item I<center-tables>
611 If this boolean value is set, tables are centered.
613 =item I<expand-tables>
615 If this boolean value is set, tables are expanded (width 100%).
619 If this boolean value is set, definition lists will be
620 rendered using 'dl', 'dt' and 'dd' instead of tables.
622 =item I<header-offset>
624 Offset to be summed to the heading level when rendering
625 'h?' tags (default is 0).
627 =item I<class-oddeven>
629 If this boolean value is set, tables will be rendered
630 with an "oddeven" CSS class, and rows alternately classed
631 as "even" or "odd". If it's not set, no CSS class info
634 =item I<url-label-max>
636 If an URL without label is given (that is, the URL itself
637 is used as the label), it's trimmed to have as much
638 characters as this value says. By default it's 80.
646 my ($class, %args) = @_;
649 bless(\
%args, $class);
652 $gh->{'-process-urls'} = 1;
653 $gh->{'url-label-max'} ||= 80;
663 # accept unnamed and HTML inlines
664 if ($l =~ /^<<$/ or $l =~ /^<<\s*html$/i) {
665 $gh->{'-inline'} = 'HTML';
670 delete $gh->{'-inline'};
674 if ($gh->{'-inline'} eq 'HTML') {
702 my ($gh, $url, $label) = @_;
707 if (length($label) > $gh->{'url-label-max'}) {
708 $label = substr($label, 0,
709 $gh->{'url-label-max'}) . '...';
713 return "<a href = \"$url\">$label</a>";
720 return "<strong>$str</strong>";
727 return "<em>$str</em>";
734 return "<code class = 'literal'>$str</code>";
741 return "<code class = 'funcname'>$str</code>";
748 return "<code class = 'var'>$str</code>";
754 my ($gh, $mode, $params) = @_;
756 if ($mode ne $gh->{'-mode'}) {
760 if ($gh->{'-mode'} eq 'ul') {
761 $gh->_push('</li>' . '</ul>' x
scalar(@
{$gh->{'-ul-levels'}}));
763 elsif ($gh->{'-mode'} eq 'ol') {
764 $gh->_push('</li>' . '</ol>' x
scalar(@
{$gh->{'-ol-levels'}}));
766 elsif ($gh->{'-mode'}) {
767 $gh->_push("</$gh->{'-mode'}>");
771 $tag = $params ?
"<$mode $params>" : "<$mode>";
772 $gh->_push($tag) if $mode;
774 $gh->{'-mode'} = $mode;
775 $gh->{'-mode-elems'} = 0;
777 # clean previous lists
778 $gh->{'-ul-levels'} = undef;
779 $gh->{'-ol-levels'} = undef;
789 if ($gh->{'dl-as-dl'}) {
790 $gh->_new_mode('dl');
791 $ret .= "<dt><strong class = 'term'>$str</strong><dd>";
794 $gh->_new_mode('table');
795 $ret .= "<tr><td valign = 'top'><strong class = 'term'>$1</strong> </td><td valign = 'top'>";
804 my ($gh, $levels) = @_;
812 elsif ($levels < 0) {
813 $ret .= '</li></ul>' x
abs($levels);
816 if ($gh->{'-mode'} ne 'ul') {
817 $gh->{'-mode'} = 'ul';
820 $ret .= '</li>' if $levels <= 0;
831 my ($gh, $levels) = @_;
839 elsif ($levels < 0) {
840 $ret .= '</li></ol>' x
abs($levels);
843 if ($gh->{'-mode'} ne 'ol') {
844 $gh->{'-mode'} = 'ol';
847 $ret .= '</li>' if $levels <= 0;
860 $gh->_new_mode('blockquote');
869 return "<hr size = '1' noshade = 'noshade'>";
875 my ($gh, $level, $l) = @_;
877 # creates a valid anchor
884 $l = sprintf("<a name = '%s'></a>\n<h%d class = 'level$level'>%s</h%d>",
885 $a, $level+$gh->{'header-offset'},
886 $l, $level+$gh->{'header-offset'});
896 if ($gh->{'-mode'} eq 'table') {
898 my (@spans) = $gh->_calc_col_span($str);
900 # calculate CSS class, if any
901 if ($gh->{'class-oddeven'}) {
902 $class = "class = '" . ($gh->{'-tbl-row'} & 1) ?
"odd'" : "even'";
905 $str = "<tr $class>";
908 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
911 $i = ${$gh->{'-table'}}[$n];
912 $i = " " if $i =~ /^\s*$/;
914 $s = " colspan = '$spans[$n]'" if $spans[$n] > 1;
916 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
917 $str .= "<th $class $s>$i</th>";
920 $str .= "<td $class $s>$i</td>";
926 @
{$gh->{'-table'}} = ();
933 $params = "border = '1'";
934 $params .= " width = '100\%'" if $gh->{'expand-tables'};
935 $params .= " align = 'center'" if $gh->{'center-tables'};
936 $params .= " class = 'oddeven'" if $gh->{'class-oddeven'};
938 $gh->_new_mode('table', $params);
940 @
{$gh->{'-table'}} = ();
941 $gh->{'-tbl-row'} = 1;
949 ###########################################################
952 package Grutatxt
::troff
;
958 The troff driver uses the B<-me> macros and B<tbl>. A
959 good way to post-process this output (to PostScript in
960 the example) could be by using
964 The additional parameters for a new Grutatxt object are:
970 The point size of normal text. By default is 10.
972 =item I<heading-sizes>
974 This argument must be a reference to an array containing
975 the size in points of the 3 different heading levels. By
976 default, level sizes are [ 20, 18, 15 ].
980 The type of table to be rendered by B<tbl>. Can be
981 I<allbox> (all lines rendered; this is the default value),
982 I<box> (only outlined) or I<doublebox> (only outlined by
991 my ($class, %args) = @_;
994 bless(\
%args,$class);
997 $gh->{'-process-urls'} = 0;
999 $gh->{'heading-sizes'} ||= [ 20, 18, 15 ];
1000 $gh->{'normal-size'} ||= 10;
1001 $gh->{'table-type'} ||= "allbox"; # box, allbox, doublebox
1011 $gh->_push(".nr pp $gh->{'normal-size'}");
1020 # accept only troff inlines
1021 if ($l =~ /^<<\s*troff$/i) {
1022 $gh->{'-inline'} = 'troff';
1027 delete $gh->{'-inline'};
1031 if ($gh->{'-inline'} eq 'troff') {
1058 my ($gh, $str) = @_;
1059 return "\\fB$str\\fP";
1065 my ($gh, $str) = @_;
1066 return "\\fI$str\\fP";
1072 my ($gh, $str) = @_;
1073 return "\\fI$str\\fP";
1079 my ($gh, $str) = @_;
1080 return "\\fB$str\\fP";
1086 my ($gh, $str) = @_;
1087 return "\\fI$str\\fP";
1093 my ($gh, $mode, $params) = @_;
1095 if ($mode ne $gh->{'-mode'}) {
1098 # flush previous list
1099 if ($gh->{'-mode'} eq 'pre') {
1102 elsif ($gh->{'-mode'} eq 'table') {
1103 chomp($gh->{'-table-head'});
1104 $gh->{'-table-head'} =~ s/\s+$//;
1105 $gh->_push($gh->{'-table-head'} . '.');
1106 $gh->_push($gh->{'-table-body'} . '.TE\n.sp 0.6');
1108 elsif ($gh->{'-mode'} eq 'blockquote') {
1113 if ($mode eq 'pre') {
1114 $gh->_push('.(l L');
1116 elsif ($mode eq 'blockquote') {
1120 $gh->{'-mode'} = $mode;
1127 my ($gh, $str) = @_;
1129 $gh->_new_mode('dl');
1130 return ".ip \"$str\"\n";
1138 $gh->_new_mode('ul');
1147 $gh->_new_mode('ol');
1156 $gh->_new_mode('blockquote');
1171 my ($gh, $level, $l) = @_;
1173 $l = '.sz ' . ${$gh->{'heading-sizes'}}[$level - 1] . "\n$l\n.sp 0.6";
1181 my ($gh, $str) = @_;
1183 if ($gh->{'-mode'} eq 'table') {
1185 my (@spans) = $gh->_calc_col_span($str);
1190 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1193 if ($gh->{'table-headers'} and $gh->{'-tbl-row'} == 1) {
1201 $h .= 's ' x
($spans[$n] - 1) if $spans[$n] > 1;
1205 $i = ${$gh->{'-table'}}[$n];
1213 $b .= "\n_" if $gh->{'table-headers'} and
1214 $gh->{'-tbl-row'} == 1 and
1215 $gh->{'table-type'} ne "allbox";
1217 $gh->{'-table-head'} .= "$h\n";
1218 $gh->{'-table-body'} .= "$b\n";
1220 @
{$gh->{'-table'}} = ();
1221 $gh->{'-tbl-row'}++;
1225 $gh->_new_mode('table');
1227 @
{$gh->{'-table'}} = ();
1228 $gh->{'-tbl-row'} = 1;
1230 $gh->{'-table-head'} = ".TS\n$gh->{'table-type'} tab (#);\n";
1231 $gh->{'-table-body'} = '';
1243 # add to top headings and footers
1244 unshift(@
{$gh->{'o'}},".ef '\%' ''");
1245 unshift(@
{$gh->{'o'}},".of '' '\%'");
1246 unshift(@
{$gh->{'o'}},".eh '$gh->{'-title'}' ''");
1247 unshift(@
{$gh->{'o'}},".oh '' '$gh->{'-title'}'");
1251 ###########################################################
1254 package Grutatxt
::man
;
1256 @ISA = ("Grutatxt::troff", "Grutatxt");
1260 The man driver is used to generate Unix-like man pages. Note that
1261 all headings have the same level with this output driver.
1263 The additional parameters for a new Grutatxt object are:
1269 The man page section (see man documentation). By default is 1.
1273 The name of the page. This is usually the name of the program
1274 or function the man page is documenting and will be shown in the
1275 page header. By default is the empty string.
1283 my ($class, %args) = @_;
1286 bless(\
%args,$class);
1289 $gh->{'-process-urls'} = 0;
1291 $gh->{'section'} ||= 1;
1292 $gh->{'page-name'} ||= "";
1302 $gh->_push(".TH \"$gh->{'page-name'}\" \"$gh->{'section'}\" \"" . localtime() . "\"");
1310 # accept only man markup inlines
1311 if ($l =~ /^<<\s*man$/i) {
1312 $gh->{'-inline'} = 'man';
1317 delete $gh->{'-inline'};
1321 if ($gh->{'-inline'} eq 'man') {
1337 my ($gh,$mode,$params) = @_;
1339 if ($mode ne $gh->{'-mode'}) {
1342 # flush previous list
1343 if ($gh->{'-mode'} eq 'pre' or
1344 $gh->{'-mode'} eq 'table') {
1348 if ($gh->{'-mode'} eq 'blockquote') {
1352 if ($gh->{'-mode'} eq 'ul') {
1353 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ul-levels'}}));
1356 if ($gh->{'-mode'} eq 'ol') {
1357 $gh->_push(".RE\n" x
scalar(@
{$gh->{'-ol-levels'}}));
1361 if ($mode eq 'pre' or $mode eq 'table') {
1365 if ($mode eq 'blockquote') {
1366 $gh->_push('.RS 4');
1369 $gh->{'-mode'} = $mode;
1376 my ($gh, $str) = @_;
1378 $gh->_new_mode('dl');
1379 return ".TP\n.B \"$str\"\n";
1385 my ($gh, $levels) = @_;
1391 elsif ($levels < 0) {
1392 $ret = ".RE\n" x
abs($levels);
1395 $gh->_new_mode('ul');
1396 return $ret . ".TP 4\n\\(bu\n";
1402 my ($gh, $levels) = @_;
1403 my $l = @
{$gh->{'-ol-levels'}};
1406 $gh->{'-ol-level'} += $levels;
1411 $l[$gh->{'-ol-level'}] = 1;
1413 elsif ($levels < 0) {
1414 $ret = ".RE\n" x
abs($levels);
1417 $gh->_new_mode('ol');
1418 $ret .= ".TP 4\n" . $l[$gh->{'-ol-level'}]++ . ".\n";
1434 my ($gh, $level, $l) = @_;
1436 # all headers are the same depth in man pages
1437 return ".SH \"" . uc($l) . "\"";
1443 my ($gh, $str) = @_;
1445 if ($gh->{'-mode'} eq 'table') {
1446 foreach my $r (@
{$gh->{'-table-raw'}}) {
1451 $gh->_new_mode('table');
1454 @
{$gh->{'-table'}} = ();
1455 @
{$gh->{'-table-raw'}} = ();
1468 ###########################################################
1471 package Grutatxt
::latex
;
1473 @ISA = ("Grutatxt");
1477 The additional parameters for a new Grutatxt object are:
1483 The LaTeX document class. By default is 'report'. You can also use
1484 'article' or 'book' (consult your LaTeX documentation for details).
1488 The paper size to be used in the document. By default is 'a4paper'.
1492 The character encoding used in the document. By default is 'latin1'.
1496 Note that you can't nest further than 4 levels in LaTeX; if you do,
1497 LaTeX will choke in the generated code with a 'Too deeply nested' error.
1503 my ($class, %args) = @_;
1506 bless(\
%args,$class);
1509 $gh->{'-process-urls'} = 0;
1511 $gh->{'-docclass'} ||= 'report';
1512 $gh->{'-papersize'} ||= 'a4paper';
1513 $gh->{'-encoding'} ||= 'latin1';
1523 $gh->_push("\\documentclass[$gh->{'-papersize'}]{$gh->{-docclass}}");
1524 $gh->_push("\\usepackage[$gh->{'-encoding'}]{inputenc}");
1526 $gh->_push("\\begin{document}");
1534 # accept only latex inlines
1535 if ($l =~ /^<<\s*latex$/i) {
1536 $gh->{'-inline'} = 'latex';
1541 delete $gh->{'-inline'};
1545 if ($gh->{'-inline'} eq 'latex') {
1555 $l =~ s/ _ / \\_ /g;
1556 $l =~ s/ ~ / \\~ /g;
1557 $l =~ s/ & / \\& /g;
1567 $l =~ s/ # / \\# /g;
1569 $l =~ s/([^\s_])_([^\s_])/$1\\_$2/g;
1585 my ($gh, $str) = @_;
1586 return "\\textbf{$str}";
1592 my ($gh, $str) = @_;
1593 return "\\emph{$str}";
1599 my ($gh, $str) = @_;
1600 return "{\\tt $str}";
1606 my ($gh, $str) = @_;
1607 return "{\\tt $str}";
1613 my ($gh, $str) = @_;
1615 $str =~ s/^\$/\\\$/;
1617 return "{\\tt $str}";
1623 my ($gh, $mode, $params) = @_;
1627 'pre' => 'verbatim',
1628 'blockquote' => 'quote',
1629 'table' => 'tabular',
1630 'dl' => 'description',
1635 if ($mode ne $gh->{'-mode'}) {
1636 # close previous mode
1637 if ($gh->{'-mode'} eq 'ul') {
1638 $gh->_push("\\end{itemize}" x
scalar(@
{$gh->{'-ul-levels'}}));
1640 elsif ($gh->{'-mode'} eq 'ol') {
1641 $gh->_push("\\end{enumerate}" x
scalar(@
{$gh->{'-ol-levels'}}));
1643 elsif ($gh->{'-mode'} eq 'table') {
1644 $gh->_push("\\end{tabular}\n");
1647 $gh->_push("\\end{" . $latex_modes{$gh->{'-mode'}} . "}")
1652 $gh->_push("\\begin{" . $latex_modes{$mode} . "}" . $params)
1655 $gh->{'-mode'} = $mode;
1657 $gh->{'-ul-levels'} = undef;
1658 $gh->{'-ol-levels'} = undef;
1665 my ($gh, $str) = @_;
1667 $gh->_new_mode('dl');
1668 return "\\item[$str]\n";
1674 my ($gh, $levels) = @_;
1680 $ret .= "\\begin{itemize}\n";
1682 elsif ($levels < 0) {
1683 $ret .= "\\end{itemize}\n" x
abs($levels);
1686 $gh->{'-mode'} = 'ul';
1696 my ($gh, $levels) = @_;
1702 $ret .= "\\begin{enumerate}\n";
1704 elsif ($levels < 0) {
1705 $ret .= "\\end{enumerate}\n" x
abs($levels);
1708 $gh->{'-mode'} = 'ol';
1720 $gh->_new_mode('blockquote');
1729 return "------------\n";
1735 my ($gh, $level, $l) = @_;
1737 my @latex_headings = ( "\\section*{", "\\subsection*{",
1738 "\\subsubsection*{");
1740 $l = "\n" . $latex_headings[$level - 1] . $l . "}";
1750 if ($gh->{'-mode'} eq 'table') {
1752 my (@spans) = $gh->_calc_col_span($str);
1758 for (my $n = 0; $n < scalar(@
{$gh->{'-table'}}); $n++) {
1761 $i = ${$gh->{'-table'}}[$n];
1762 $i = " " if $i =~ /^\s*$/;
1764 # $s = " colspan='$spans[$n]'" if $spans[$n] > 1;
1767 $i = "\\multicolumn{$spans[$n]}{|l|}{$i}"
1777 $str .= join('&', @cols) . "\\\\\n\\hline";
1779 # $str .= "\n\\hline" if $gh->{'-tbl-row'} == 1;
1781 @
{$gh->{'-table'}} = ();
1782 $gh->{'-tbl-row'}++;
1787 # count the number of columns
1789 my $params = "{" . "|l" x
(length($str) - 1) . "|}\n\\hline";
1792 $gh->_new_mode('table', $params);
1794 @
{$gh->{'-table'}} = ();
1795 $gh->{'-tbl-row'} = 1;
1807 $gh->_push("\\end{document}");
1813 Angel Ortega angel@triptico.com