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