summaryrefslogtreecommitdiff
path: root/perl/Locale/Po4a/Text.pm
blob: 8f703c894d17d335f9d3318775cebb030a9fc196 (plain)
  1. #!/usr/bin/perl -w
  2. # Po4a::Text.pm
  3. # extract and translate translatable strings from a text documents
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc.,
  18. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19. #
  20. ########################################################################
  21. =encoding UTF-8
  22. =head1 NAME
  23. Locale::Po4a::Text - convert text documents from/to PO files
  24. =head1 DESCRIPTION
  25. The po4a (PO for anything) project goal is to ease translations (and more
  26. interestingly, the maintenance of translations) using gettext tools on
  27. areas where they were not expected like documentation.
  28. Locale::Po4a::Text is a module to help the translation of text documents into
  29. other [human] languages.
  30. Paragraphs are split on empty lines (or lines containing only spaces or
  31. tabulations).
  32. If a paragraph contains a line starting by a space (or tabulation), this
  33. paragraph won't be rewrapped.
  34. =cut
  35. package Locale::Po4a::Text;
  36. use 5.006;
  37. use strict;
  38. use warnings;
  39. require Exporter;
  40. use vars qw(@ISA @EXPORT);
  41. @ISA = qw(Locale::Po4a::TransTractor);
  42. @EXPORT qw();
  43. use Locale::Po4a::TransTractor;
  44. use Locale::Po4a::Common;
  45. =head1 OPTIONS ACCEPTED BY THIS MODULE
  46. These are this module's particular options:
  47. =over
  48. =item B<nobullets>
  49. Deactivate detection of bullets.
  50. By default, when a bullet is detected, the bullet paragraph is not considered
  51. as a verbatim paragraph (with the no-wrap flag in the PO file), but the module
  52. rewrap this paragraph in the generated PO file and in the translation.
  53. =cut
  54. my $bullets 1;
  55. =item B<tabs>=I<mode>
  56. Specify how tabulations shall be handled. The I<mode> can be any of:
  57. =over
  58. =item B<split>
  59. Lines with tabulations introduce breaks in the current paragraph.
  60. =item B<verbatim>
  61. Paragraph containing tabulations will not be re-wrapped.
  62. =back
  63. By default, tabulations are considered as spaces.
  64. =cut
  65. my $tabs "";
  66. =item B<breaks>=I<regex>
  67. A regular expression matching lines which introduce breaks.
  68. The regular expression will be anchored so that the whole line must match.
  69. =cut
  70. my $breaks;
  71. =item B<debianchangelog>
  72. Handle the header and footer of
  73. released versions, which only contain non translatable informations.
  74. =cut
  75. my $debianchangelog 0;
  76. =item B<fortunes>
  77. Handle the fortunes format, which separate fortunes with a line which
  78. consists in '%' or '%%', and use '%%' as the beginning of a comment.
  79. =cut
  80. my $fortunes 0;
  81. =item B<markdown>
  82. Handle some special markup in Markdown-formatted texts.
  83. =cut
  84. my $markdown 0;
  85. =item B<asciidoc>
  86. Handle documents in the asciidoc format.
  87. =cut
  88. my $asciidoc 0;
  89. =back
  90. =cut
  91. sub initialize {
  92.     my $self shift;
  93.     my %options @_;
  94.     $self->{options}{'asciidoc'} = 1;
  95.     $self->{options}{'breaks'} = 1;
  96.     $self->{options}{'debianchangelog'} = 1;
  97.     $self->{options}{'debug'} = 1;
  98.     $self->{options}{'fortunes'} = 1;
  99.     $self->{options}{'markdown'} = 1;
  100.     $self->{options}{'nobullets'} = 1;
  101.     $self->{options}{'tabs'} = 1;
  102.     $self->{options}{'verbose'} = 1;
  103.     foreach my $opt (keys %options) {
  104.         die wrap_mod("po4a::text",
  105.                      dgettext("po4a""Unknown option: %s"), $opt)
  106.             unless exists $self->{options}{$opt};
  107.         $self->{options}{$opt} = $options{$opt};
  108.     }
  109.     if (defined $options{'nobullets'}) {
  110.         $bullets 0;
  111.     }
  112.     if (defined $options{'tabs'}) {
  113.         $tabs $options{'tabs'};
  114.     }
  115.     if (defined $options{'breaks'}) {
  116.         $breaks $options{'breaks'};
  117.     }
  118.     if (defined $options{'debianchangelog'}) {
  119.         $debianchangelog=1;
  120.     }
  121.     if (defined $options{'fortunes'}) {
  122.         $fortunes=1;
  123.     }
  124.     if (defined $options{'markdown'}) {
  125.         $markdown=1;
  126.     }
  127.     $asciidoc=if (defined $options{'asciidoc'});
  128. }
  129. sub parse {
  130.     my $self shift;
  131.     my ($line,$ref);
  132.     my $paragraph="";
  133.     my $wrapped_mode 1;
  134.     my $expect_header 1;
  135.     my $end_of_paragraph 0;
  136.     ($line,$ref)=$self->shiftline();
  137.     my $file $ref;
  138.     $file =~ s/:[0-9]+$// if defined($line);
  139.     while (defined($line)) {
  140.         $ref =~ m/^(.*):[0-9]+$/;
  141.         if ($1 ne $file) {
  142.             $file $1;
  143.             do_paragraph($self,$paragraph,$wrapped_mode);
  144.             $paragraph="";
  145.             $wrapped_mode 1;
  146.             $expect_header 1;
  147.         }
  148.         chomp($line);
  149.         $self->{ref}="$ref";
  150.         if ($debianchangelog and
  151.             $expect_header and
  152.             $line =~ /^(\w[-+0-9a-z.]*)\ \(([^\(\\t]+)\# src, version
  153.                        \s+([-+0-9a-z.]+);                 # distribution
  154.                        \s*urgency\s*\=\s*(.*\S)\s*$/ix) { #
  155.             do_paragraph($self,$paragraph,$wrapped_mode);
  156.             $paragraph="";
  157.             $self->pushline("$line\n");
  158.             $expect_header=0;
  159.         elsif ($debianchangelog and
  160.                  $line =~ m/^ \-\- (.*) <(.*)> ((\w+\,\s*)?\d{1,2}\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?)$/) {
  161.             # Found trailer
  162.             do_paragraph($self,$paragraph,$wrapped_mode);
  163.             $paragraph="";
  164.             $self->pushline("$line\n");
  165.             $expect_header=1;
  166.         elsif ($fortunes and
  167.                  $line =~ m/^%%?\s*$/) {
  168.             # Found end of fortune
  169.             do_paragraph($self,$paragraph,$wrapped_mode);
  170.             $self->pushline("\n"unless (   $wrapped_mode == 0
  171.                                           or $paragraph eq "");
  172.             $paragraph="";
  173.             $wrapped_mode 1;
  174.             $self->pushline("$line\n");
  175.         elsif (    (defined $self->{verbatim})
  176.                  and ($self->{verbatim} == 2)) {
  177.             # Untranslated blocks
  178.             $self->pushline($line."\n");
  179.             if ($asciidoc and
  180.                 ($line =~ m/^(\/{4,}|~{4,})$/)) {
  181.                 undef $self->{verbatim};
  182.                 undef $self->{type};
  183.                 $wrapped_mode 1;
  184.             }
  185.         elsif (   ($line =~ /^\s*$/)
  186.                  or (    defined $breaks
  187.                      and $line =~ m/^$breaks$/)) {
  188.             # Break paragraphs on lines containing only spaces
  189.             do_paragraph($self,$paragraph,$wrapped_mode);
  190.             $paragraph="";
  191.             $wrapped_mode unless defined($self->{verbatim});
  192.             $self->pushline($line."\n");
  193.         elsif ($asciidoc and (not defined($self->{verbatim})) and
  194.                  ($line =~ m/^(\+|--)$/)) {
  195.             # List Item Continuation or List Block
  196.             do_paragraph($self,$paragraph,$wrapped_mode);
  197.             $paragraph="";
  198.             $self->pushline($line."\n");
  199.         elsif ($asciidoc and (not defined($self->{verbatim})) and
  200.                  ($line =~ m/^(={4,}|-{4,}|~{4,}|\^{4,}|\+{4,})$/and
  201.                  (defined($paragraph) )and
  202.                  ($paragraph =~ m/^[^\n]*\n$/sand
  203.                  (length($paragraph) == (length($line)+1))) {
  204.             # Found title
  205.             $wrapped_mode 0;
  206.             my $level $line;
  207.             $level =~ s/^(.).*$/$1/;
  208.             my $t $self->translate($paragraph,
  209.                                      $self->{ref},
  210.                                      "Title $level",
  211.                                      "wrap" => 0);
  212.             $self->pushline($t);
  213.             $paragraph="";
  214.             $wrapped_mode 1;
  215.             $self->pushline(($level (length($t)-1))."\n");
  216.         elsif ($asciidoc and
  217.                  ($line =~ m/^(={1,5})( +)(.*?)( +\1)?$/)) {
  218.             my $titlelevel1 $1;
  219.             my $titlespaces $2;
  220.             my $title $3;
  221.             my $titlelevel2 $4||"";
  222.             # Found one line title
  223.             do_paragraph($self,$paragraph,$wrapped_mode);
  224.             $wrapped_mode 0;
  225.             $paragraph="";
  226.             my $t $self->translate($title,
  227.                                      $self->{ref},
  228.                                      "Title $titlelevel1",
  229.                                      "wrap" => 0);
  230.             $self->pushline($titlelevel1.$titlespaces.$t.$titlelevel2."\n");
  231.             $wrapped_mode 1;
  232.         elsif ($asciidoc and
  233.                  ($line =~ m/^(\/{4,}|\+{4,}|-{4,}|\.{4,}|\*{4,}|_{4,}|={4,}|~{4,})$/)) {
  234.             # Found one delimited block
  235.             my $t $line;
  236.             $t =~ s/^(.).*$/$1/;
  237.             my $type "delimited block $t";
  238.             if (defined $self->{verbatimand ($self->{typene $type)) {
  239.                 $paragraph .= "$line\n";
  240.             else {
  241.             do_paragraph($self,$paragraph,$wrapped_mode);
  242.             if (    (defined $self->{type})
  243.                 and ($self->{typeeq $type)) {
  244.                 undef $self->{type};
  245.                 undef $self->{verbatim};
  246.                 $wrapped_mode 1;
  247.             else {
  248.                 if ($t eq "\/") {
  249.                     # CommentBlock, should not be treated
  250.                     $self->{verbatim} = 2;
  251.                 elsif ($t eq "+") {
  252.                     # PassthroughBlock
  253.                     $wrapped_mode 0;
  254.                     $self->{verbatim} = 1;
  255.                 elsif ($t eq "-") {
  256.                     # ListingBlock
  257.                     $wrapped_mode 0;
  258.                     $self->{verbatim} = 1;
  259.                 elsif ($t eq ".") {
  260.                     # LiteralBlock
  261.                     $wrapped_mode 0;
  262.                     $self->{verbatim} = 1;
  263.                 elsif ($t eq "*") {
  264.                     # SidebarBlock
  265.                     $wrapped_mode 1;
  266.                 elsif ($t eq "_") {
  267.                     # QuoteBlock
  268.                     if (    (defined $self->{type})
  269.                         and ($self->{typeeq "verse")) {
  270.                         $wrapped_mode 0;
  271.                         $self->{verbatim} = 1;
  272.                     else {
  273.                         $wrapped_mode 1;
  274.                     }
  275.                 elsif ($t eq "=") {
  276.                     # ExampleBlock
  277.                     $wrapped_mode 1;
  278.                 elsif ($t eq "~") {
  279.                     # Filter blocks, TBC: not translated
  280.                     $wrapped_mode 0;
  281.                     $self->{verbatim} = 2;
  282.                 
  283.                 $self->{type} = $type;
  284.             }
  285.             $paragraph="";
  286.             $self->pushline($line."\n");
  287.             }
  288.         elsif ($asciidoc and not defined $self->{verbatimand
  289.                  ($line =~ m/^\[\[([^\]]*)\]\]$/)) {
  290.             # Found BlockId
  291.             do_paragraph($self,$paragraph,$wrapped_mode);
  292.             $paragraph="";
  293.             $wrapped_mode 1;
  294.             $self->pushline($line."\n");
  295.             undef $self->{bullet};
  296.             undef $self->{indent};
  297.         elsif ($asciidoc and not defined $self->{verbatimand
  298.                  ($paragraph eq ""and
  299.                  ($line =~ m/^((?:NOTE|TIP|IMPORTANT|WARNING|CAUTION):\s+)(.*)$/)) {
  300.             my $type $1;
  301.             my $text $2;
  302.             do_paragraph($self,$paragraph,$wrapped_mode);
  303.             $paragraph=$text."\n";
  304.             $wrapped_mode 1;
  305.             $self->pushline($type);
  306.             undef $self->{bullet};
  307.             undef $self->{indent};
  308.         elsif ($asciidoc and not defined $self->{verbatimand
  309.                  ($line =~ m/^\[(NOTE|TIP|IMPORTANT|WARNING|CAUTION|verse|quote)\]$/)) {
  310.             my $type $1;
  311.             do_paragraph($self,$paragraph,$wrapped_mode);
  312.             $paragraph="";
  313.             $wrapped_mode 1;
  314.             $self->pushline($line."\n");
  315.             if ($type  eq "verse") {
  316.                 $wrapped_mode 0;
  317.             }
  318.             undef $self->{bullet};
  319.             undef $self->{indent};
  320.         elsif ($asciidoc and not defined $self->{verbatimand
  321.                  ($line =~ m/^\[(verse|quote), +(.*)\]$/)) {
  322.             my $type $1;
  323.             my $arg $2;
  324.             do_paragraph($self,$paragraph,$wrapped_mode);
  325.             $paragraph="";
  326.             my $t $self->translate($arg,
  327.                                      $self->{ref},
  328.                                      "$type",
  329.                                      "wrap" => 0);
  330.             $self->pushline("[$type$t]\n");
  331.             $wrapped_mode 1;
  332.             if ($type  eq "verse") {
  333.                 $wrapped_mode 0;
  334.             }
  335.             $self->{type} = $type;
  336.             undef $self->{bullet};
  337.             undef $self->{indent};
  338.         elsif ($asciidoc and not defined $self->{verbatimand
  339.                  ($line =~ m/^\[icon="(.*)"\]$/)) {
  340.             my $arg $1;
  341.             do_paragraph($self,$paragraph,$wrapped_mode);
  342.             $paragraph="";
  343.             my $t $self->translate($arg,
  344.                                      $self->{ref},
  345.                                      "icon",
  346.                                      "wrap" => 0);
  347.             $self->pushline("[icon=\"$t\"]\n");
  348.             $wrapped_mode 1;
  349.             undef $self->{bullet};
  350.             undef $self->{indent};
  351.         elsif ($asciidoc and not defined $self->{verbatimand
  352.                  ($line =~ m/^\[icons=None, +caption="(.*)"\]$/)) {
  353.             my $arg $1;
  354.             do_paragraph($self,$paragraph,$wrapped_mode);
  355.             $paragraph="";
  356.             my $t $self->translate($arg,
  357.                                      $self->{ref},
  358.                                      "caption",
  359.                                      "wrap" => 0);
  360.             $self->pushline("[icons=None, caption=\"$t\"]\n");
  361.             $wrapped_mode 1;
  362.             undef $self->{bullet};
  363.             undef $self->{indent};
  364.         elsif ($asciidoc and not defined $self->{verbatimand
  365.                  ($line =~ m/^(\s*)([*_+`'#[:alnum:]].*)((?:::|;;|\?\?|:-)(?: *\\)?)$/)) {
  366.             my $indent $1;
  367.             my $label $2;
  368.             my $labelend $3;
  369.             # Found labeled list
  370.             do_paragraph($self,$paragraph,$wrapped_mode);
  371.             $paragraph="";
  372.             $wrapped_mode 1;
  373.             $self->{bullet} = "";
  374.             $self->{indent} = $indent;
  375.             my $t $self->translate($label,
  376.                                      $self->{ref},
  377.                                      "Labeled list",
  378.                                      "wrap" => 0);
  379.             $self->pushline("$indent$t$labelend\n");
  380.         elsif ($asciidoc and not defined $self->{verbatimand
  381.                  ($line =~ m/^(\s*)(\S.*)((?:::|;;)\s+)(.*)$/)) {
  382.             my $indent $1;
  383.             my $label $2;
  384.             my $labelend $3;
  385.             my $labeltext $4;
  386.             # Found Horizontal Labeled Lists
  387.             do_paragraph($self,$paragraph,$wrapped_mode);
  388.             $paragraph=$labeltext."\n";
  389.             $wrapped_mode 1;
  390.             $self->{bullet} = "";
  391.             $self->{indent} = $indent;
  392.             my $t $self->translate($label,
  393.                                      $self->{ref},
  394.                                      "Labeled list",
  395.                                      "wrap" => 0);
  396.             $self->pushline("$indent$t$labelend");
  397.         elsif ($asciidoc and not defined $self->{verbatimand
  398.                  ($line =~ m/^\:(\S.*?)(:\s*)(.*)$/)) {
  399.             my $attrname $1;
  400.             my $attrsep $2;
  401.             my $attrvalue $3;
  402.             # Found a Attribute entry
  403.             do_paragraph($self,$paragraph,$wrapped_mode);
  404.             $paragraph="";
  405.             $wrapped_mode 1;
  406.             undef $self->{bullet};
  407.             undef $self->{indent};
  408.             my $t $self->translate($attrvalue,
  409.                                      $self->{ref},
  410.                                      "Attribute :$attrname:",
  411.                                      "wrap" => 0);
  412.             $self->pushline(":$attrname$attrsep$t\n");
  413.         elsif ($asciidoc and not defined $self->{verbatimand
  414.                  ($line !~ m/^\.\./and ($line =~ m/^\.(\S.*)$/)) {
  415.             my $title $1;
  416.             # Found block title
  417.             do_paragraph($self,$paragraph,$wrapped_mode);
  418.             $paragraph="";
  419.             $wrapped_mode 1;
  420.             undef $self->{bullet};
  421.             undef $self->{indent};
  422.             my $t $self->translate($title,
  423.                                      $self->{ref},
  424.                                      "Block title",
  425.                                      "wrap" => 0);
  426.             $self->pushline(".$t\n");
  427.         elsif ($asciidoc and not defined $self->{verbatimand
  428.                  ($line =~ m/^(\s*)((?:[-*o+]|(?:[0-9]+[.\)])|(?:[a-z][.\)])|\([0-9]+\)|\.|\.\.)\s+)(.*)$/)) {
  429.             my $indent $1||"";
  430.             my $bullet $2;
  431.             my $text $3;
  432.             do_paragraph($self,$paragraph,$wrapped_mode);
  433.             $paragraph $text."\n";
  434.             $self->{indent} = $indent;
  435.             $self->{bullet} = $bullet;
  436.         elsif ($asciidoc and not defined $self->{verbatimand
  437.                  ($line =~ m/^((?:<?[0-9]+)?> +)(.*)$/)) {
  438.             my $bullet $1;
  439.             my $text $2;
  440.             do_paragraph($self,$paragraph,$wrapped_mode);
  441.             $paragraph $text."\n";
  442.             $self->{indent} = "";
  443.             $self->{bullet} = $bullet;
  444.         elsif ($asciidoc and not defined $self->{verbatimand
  445.                  (defined $self->{bulletand $line =~ m/^(\s+)(.*)$/)) {
  446.             my $indent $1;
  447.             my $text $2;
  448.             if (not defined $self->{indent}) {
  449.                 $paragraph .= $text."\n";
  450.                 $self->{indent} = $indent;
  451.             elsif (length($paragraphand (length($self->{bullet}) + length($self->{indent}) == length($indent))) {
  452.                 $paragraph .= $text."\n";
  453.             else {
  454.                 do_paragraph($self,$paragraph,$wrapped_mode);
  455.                 $paragraph $text."\n";
  456.                 $self->{indent} = $indent;
  457.                 $self->{bullet} = "";
  458.             }
  459.         elsif ($markdown and
  460.                  (not defined($self->{verbatim})) and
  461.                  ($line =~ m/^(={4,}|-{4,})$/and
  462.                  (defined($paragraph) )and
  463.                  ($paragraph =~ m/^[^\n]*\n$/sand
  464.                  (length($paragraph) == (length($line)+1))) {
  465.             # XXX: There can be any number of underlining according
  466.             #      to the documentation. This detection, which avoid
  467.             #      translating the formatting, is only supported if
  468.             #      the underlining has the same size as the herder text.
  469.             # Found title
  470.             $wrapped_mode 0;
  471.             my $level $line;
  472.             $level =~ s/^(.).*$/$1/;
  473.             my $t $self->translate($paragraph,
  474.                                      $self->{ref},
  475.                                      "Title $level",
  476.                                      "wrap" => 0);
  477.             $self->pushline($t);
  478.             $paragraph="";
  479.             $wrapped_mode 1;
  480.             $self->pushline(($level (length($t)-1))."\n");
  481.         elsif ($markdown and
  482.                  ($line =~ m/^(#{1,6})( +)(.*?)( +\1)?$/)) {
  483.             my $titlelevel1 $1;
  484.             my $titlespaces $2;
  485.             my $title $3;
  486.             my $titlelevel2 $4||"";
  487.             # Found one line title
  488.             do_paragraph($self,$paragraph,$wrapped_mode);
  489.             $wrapped_mode 0;
  490.             $paragraph="";
  491.             my $t $self->translate($title,
  492.                                      $self->{ref},
  493.                                      "Title $titlelevel1",
  494.                                      "wrap" => 0);
  495.             $self->pushline($titlelevel1.$titlespaces.$t.$titlelevel2."\n");
  496.             $wrapped_mode 1;
  497.         elsif ($markdown and
  498.                  ($paragraph eq ""and
  499.                  ($line =~ /^((\*\s*){3,}|(-\s*){3,}|(_\s*){3,})$/)) {
  500.             # Horizontal rule
  501.             $wrapped_mode 1;
  502.             $self->pushline($line."\n");
  503.         elsif ($line =~ /^-- $/) {
  504.             # Break paragraphs on email signature hint
  505.             do_paragraph($self,$paragraph,$wrapped_mode);
  506.             $paragraph="";
  507.             $wrapped_mode 1;
  508.             $self->pushline($line."\n");
  509.         elsif (   $line =~ /^=+$/
  510.                  or $line =~ /^_+$/
  511.                  or $line =~ /^-+$/) {
  512.             $wrapped_mode 0;
  513.             $paragraph .= $line."\n";
  514.             do_paragraph($self,$paragraph,$wrapped_mode);
  515.             $paragraph="";
  516.             $wrapped_mode 1;
  517.         elsif ($markdown and
  518.                  (   $line =~ /^\s*\[\[\!\S+\s*$/     # macro begin
  519.                  or $line =~ /^\s*"""\s*\]\]\s*$/)) { # """ textblock inside macro end
  520.             # Avoid translating Markdown lines containing only markup
  521.             do_paragraph($self,$paragraph,$wrapped_mode);
  522.             $paragraph="";
  523.             $wrapped_mode 1;
  524.             $self->pushline("$line\n");
  525.         elsif ($markdown and
  526.                  (   $line =~ /^#/            # headline
  527.                   or $line =~ /^\s*\[\[\!\S[^\]]*\]\]\s*$/)) { # sole macro
  528.             # Preserve some Markdown markup as a single line
  529.             do_paragraph($self,$paragraph,$wrapped_mode);
  530.             $paragraph="$line\n";
  531.             $wrapped_mode 0;
  532.             $end_of_paragraph 1;
  533.         elsif ($markdown and
  534.                  (   $line =~ /^"""/)) { # """ textblock inside macro end
  535.             # Markdown markup needing separation _before_ this line
  536.             do_paragraph($self,$paragraph,$wrapped_mode);
  537.             $paragraph="$line\n";
  538.             $wrapped_mode 1;
  539.         elsif ($tabs eq "split" and $line =~ m/\t/ and $paragraph !~ m/\t/s) {
  540.             $wrapped_mode 0;
  541.             do_paragraph($self,$paragraph,$wrapped_mode);
  542.             $paragraph "$line\n";
  543.             $wrapped_mode 0;
  544.         elsif ($tabs eq "split" and $line !~ m/\t/ and $paragraph =~ m/\t/s) {
  545.             do_paragraph($self,$paragraph,$wrapped_mode);
  546.             $paragraph "$line\n";
  547.             $wrapped_mode 1;
  548.         else {
  549.             if ($line =~ /^\s/) {
  550.                 # A line starting by a space indicates a non-wrap
  551.                 # paragraph
  552.                 $wrapped_mode 0;
  553.             }
  554.             if ($markdown and
  555.                      (   $line =~ /\S $/    # explicit newline
  556.                       or $line =~ /"""$/)) { # """ textblock inside macro begin
  557.                 # Markdown markup needing separation _after_ this line
  558.                 $end_of_paragraph 1;
  559.             else {
  560.                 undef $self->{bullet};
  561.                 undef $self->{indent};
  562.             }
  563.             if ($fortunes) {
  564.                 $line =~ s/%%(.*)$//;
  565.             }
  566. # TODO: comments
  567.             $paragraph .= $line."\n";
  568.         }
  569.         # paragraphs starting by a bullet, or numbered
  570.         # or paragraphs with a line containing many consecutive spaces
  571.         # (more than 3)
  572.         # are considered as verbatim paragraphs
  573.         $wrapped_mode if (   $paragraph =~ m/^(\*|[0-9]+[.)] )/s
  574.                           or $paragraph =~ m/[ \t][ \t][ \t]/s);
  575.         $wrapped_mode if (    $tabs eq "verbatim"
  576.                               and $paragraph =~ m/\t/s);
  577.         if ($markdown) {
  578.             # Some Markdown markup can (or might) not survive wrapping
  579.             $wrapped_mode if (
  580.                    $paragraph =~ /^>/ms                  # blockquote
  581.                 or $paragraph =~ /^( {8}|\t)/ms          # monospaced
  582.                 or $paragraph =~ /^\$(\S+[{}]\S*\s*)+/ms # Xapian macro
  583.                 or $paragraph =~ /<(?![a-z]+[:@])/ms     # maybe html (tags but not wiki <URI>)
  584.                 or $paragraph =~ /^[^<]+>/ms             # maybe html (tag with vertical space)
  585.                 or $paragraph =~ /\[\[\!\S[^\]]+$/ms     # macro begin
  586.             );
  587.         }
  588.         if ($end_of_paragraph) {
  589.             do_paragraph($self,$paragraph,$wrapped_mode);
  590.             $paragraph="";
  591.             $wrapped_mode 1;
  592.             $end_of_paragraph 0;
  593.         }
  594.         ($line,$ref)=$self->shiftline();
  595.     }
  596.     if (length $paragraph) {
  597.         do_paragraph($self,$paragraph,$wrapped_mode);
  598.     }
  599. }
  600. sub do_paragraph {
  601.     my ($self$paragraph$wrap) = (shiftshiftshift);
  602.     my $type shift || $self->{type} || "Plain text";
  603.     return if ($paragraph eq "");
  604. # DEBUG
  605. #    my $b;
  606. #    if (defined $self->{bullet}) {
  607. #            $b = $self->{bullet};
  608. #    } else {
  609. #            $b = "UNDEF";
  610. #    }
  611. #    $type .= " verbatim: '".($self->{verbatim}||"NONE")."' bullet: '$b' indent: '".($self->{indent}||"NONE")."' type: '".($self->{type}||"NONE")."'";
  612.     if ($bullets and not $wrap and not defined $self->{verbatim}) {
  613.         # Detect bullets
  614.         # |        * blah blah
  615.         # |<spaces>  blah
  616.         # |          ^-- aligned
  617.         # <empty line>
  618.         #
  619.         # Other bullets supported:
  620.         # - blah         o blah         + blah
  621.         # 1. blah       1) blah       (1) blah
  622. TEST_BULLET:
  623.         if ($paragraph =~ m/^(\s*)((?:[-*o+]|([0-9]+[.\)])|\([0-9]+\))\s+)([^\n]*\n)(.*)$/s) {
  624.             my $para $5;
  625.             my $bullet $2;
  626.             my $indent1 $1;
  627.             my $indent2 "$1".(' ' x length $bullet);
  628.             my $text $4;
  629.             while ($para !~ m/$indent2(?:[-*o+]|([0-9]+[.\)])|\([0-9]+\))\s+/
  630.                    and $para =~ s/^$indent2(\S[^\n]*\n)//s) {
  631.                 $text .= $1;
  632.             }
  633.             # TODO: detect if a line starts with the same bullet
  634.             if ($text !~ m/\S[ \t][ \t][ \t]+\S/s) {
  635.                 my $bullet_regex quotemeta($indent1.$bullet);
  636.                 $bullet_regex =~ s/[0-9]+/\\d\+/;
  637.                 if ($para eq '' or $para =~ m/^$bullet_regex\S/s) {
  638.                     my $trans $self->translate($text,
  639.                                                  $self->{ref},
  640.                                                  "Bullet: '$indent1$bullet'",
  641.                                                  "wrap" => 1,
  642.                                                  "wrapcol" => - (length $indent2));
  643.                     $trans =~ s/^/$indent1$bullet/s;
  644.                     $trans =~ s/\n(.)/\n$indent2$1/sg;
  645.                     $self->pushline$trans."\n" );
  646.                     if ($para eq '') {
  647.                         return;
  648.                     else {
  649.                         # Another bullet
  650.                         $paragraph $para;
  651.                         goto TEST_BULLET;
  652.                     }
  653.                 }
  654.             }
  655.         }
  656.     }
  657.     my $end "";
  658.     if ($wrap) {
  659.         $paragraph =~ s/^(.*?)(\n*)$/$1/s;
  660.         $end $2 || "";
  661.     }
  662.     my $t $self->translate($paragraph,
  663.                              $self->{ref},
  664.                              $type,
  665.                              "wrap" => $wrap);
  666.     if (defined $self->{bullet}) {
  667.         my $bullet $self->{bullet};
  668.         my $indent1 $self->{indent};
  669.         my $indent2 $indent1.(' ' x length($bullet));
  670.         $t =~ s/^/$indent1$bullet/s;
  671.         $t =~ s/\n(.)/\n$indent2$1/sg;
  672.     }
  673.     $self->pushline$t.$end );
  674. }
  675. 1;
  676. =head1 STATUS OF THIS MODULE
  677. Tested successfully on simple text files and NEWS.Debian files.
  678. =head1 AUTHORS
  679.  Nicolas François <nicolas.francois@centraliens.net>
  680. =head1 COPYRIGHT AND LICENSE
  681.  Copyright 2005-2008 by Nicolas FRANÇOIS <nicolas.francois@centraliens.net>.
  682. This program is free software; you may redistribute it and/or modify it
  683. under the terms of GPL (see the COPYING file).