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