Chameleon

Chameleon Svn Source Tree

Root/branches/zenith432/package/bin/po4a/lib/Locale/Po4a/AsciiDoc.pm

1#!/usr/bin/perl -w
2
3=encoding UTF-8
4
5=head1 NAME
6
7Locale::Po4a::AsciiDoc - convert AsciiDoc documents from/to PO files
8
9=head1 DESCRIPTION
10
11The po4a (PO for anything) project goal is to ease translations (and more
12interestingly, the maintenance of translations) using gettext tools on
13areas where they were not expected like documentation.
14
15Locale::Po4a::AsciiDoc is a module to help the translation of documentation in
16the AsciiDoc format.
17
18=cut
19
20package Locale::Po4a::AsciiDoc;
21
22use 5.010;
23use strict;
24use warnings;
25
26require Exporter;
27use vars qw(@ISA @EXPORT);
28@ISA = qw(Locale::Po4a::TransTractor);
29@EXPORT = qw();
30
31use Locale::Po4a::TransTractor;
32use Locale::Po4a::Common;
33
34=head1 OPTIONS ACCEPTED BY THIS MODULE
35
36These are this module's particular options:
37
38=over
39
40=item B<definitions>
41
42The name of a file containing definitions for po4a, as defined in the
43B<INLINE CUSTOMIZATION> section.
44You can use this option if it is not possible to put the definitions in
45the document being translated.
46
47In a definitions file, lines must not start by two slashes, but directly
48by B<po4a:>.
49
50=item B<entry>
51
52Space-separated list of attribute entries you want to translate. By default,
53no attribute entries are translatable.
54
55=item B<macro>
56
57Space-separated list of macro definitions.
58
59=item B<style>
60
61Space-separated list of style definitions.
62
63=back
64
65=head1 INLINE CUSTOMIZATION
66
67The AsciiDoc module can be customized with lines starting by B<//po4a:>.
68These lines are interpreted as commands to the parser.
69The following commands are recognized:
70
71=over 4
72
73=item B<//po4a: macro >I<name>B<[>I<attribute list>B<]>
74
75This permits to describe in detail the parameters of a B<macro>;
76I<name> must be a valid macro name, and it ends with an underscore
77if the target must be translated.
78
79The I<attribute list> argument is a comma separated list which
80contains informations about translatable arguments. This list contains
81either numbers, to define positional parameters, or named attributes.
82
83If a plus sign (B<+>) is prepended to I<name>, then the macro and its
84arguments are translated as a whole. There is no need to define
85attribute list in this case, but brackets must be present.
86
87=item B<//po4a: style >B<[>I<attribute list>B<]>
88
89This permits to describe in detail which attributes of a style must
90be translated.
91
92The I<attribute list> argument is a comma separated list which
93contains informations about translatable arguments. This list contains
94either numbers, to define positional parameters, or named attributes.
95The first attribute is the style name, it will not be translated.
96
97If a plus sign (B<+>) is prepended to the style name, then the
98attribute list is translated as a whole. There is no need to define
99translatable attributes.
100
101If a minus sign (B<->) is prepended to the style name, then this
102attribute is not translated.
103
104=item B<//po4a: entry >I<name>
105
106This declares an attribute entry as being translatable. By default,
107they are not translated.
108
109=back
110
111=cut
112
113my @comments = ();
114
115my %debug=('split_attributelist' => 0,
116 'join_attributelist' => 0
117 );
118
119sub initialize {
120 my $self = shift;
121 my %options = @_;
122
123 $self->{options}{'nobullets'} = 1;
124 $self->{options}{'debug'}='';
125 $self->{options}{'verbose'} = 1;
126 $self->{options}{'entry'}='';
127 $self->{options}{'macro'}='';
128 $self->{options}{'style'}='';
129 $self->{options}{'definitions'}='';
130
131 foreach my $opt (keys %options) {
132 die wrap_mod("po4a::asciidoc",
133 dgettext("po4a", "Unknown option: %s"), $opt)
134 unless exists $self->{options}{$opt};
135 $self->{options}{$opt} = $options{$opt};
136 }
137
138 if ($options{'debug'}) {
139 foreach ($options{'debug'}) {
140 $debug{$_} = 1;
141 }
142 }
143
144 $self->{translate} = {
145 macro => {},
146 style => {},
147 entry => {}
148 };
149
150 $self->register_attributelist('[verse,2,3,attribution,citetitle]');
151 $self->register_attributelist('[quote,2,3,attribution,citetitle]');
152 $self->register_attributelist('[icon]');
153 $self->register_attributelist('[caption]');
154 $self->register_attributelist('[-icons,caption]');
155 $self->register_macro('image_[1,alt,title,link]');
156
157 if ($self->{options}{'definitions'}) {
158 $self->parse_definition_file($self->{options}{'definitions'})
159 }
160 $self->{options}{entry} =~ s/^\s*//;
161 foreach my $attr (split(/\s+/, $self->{options}{entry})) {
162 $self->{translate}->{entry}->{$attr} = 1;
163 }
164 $self->{options}{macro} =~ s/^\s*//;
165 foreach my $attr (split(/\s+/, $self->{options}{macro})) {
166 $self->register_macro($attr);
167 }
168 $self->{options}{style} =~ s/^\s*//;
169 foreach my $attr (split(/\s+/, $self->{options}{style})) {
170 $self->register_attributelist($attr);
171 }
172
173}
174
175sub register_attributelist {
176 my $self = shift;
177 my $list = shift;
178 my $type = shift || 'style';
179 $list =~ s/^\[//;
180 $list =~ s/\]$//;
181 $list =~ s/\s+//;
182 $list = ",".$list.",";
183 $list =~ m/^,([-+]?)([^,]*)/;
184 my $command = $2;
185 $self->{translate}->{$type}->{$command} = $list;
186 print STDERR "Definition: $type $command: $list\n" if $debug{definitions};
187}
188
189sub register_macro {
190 my $self = shift;
191 my $text = shift;
192 die wrap_mod("po4a::asciidoc",
193 dgettext("po4a", "Unable to parse macro definition: %s"), $text)
194 unless $text =~ m/^(\+?)([\w\d][\w\d-]*?)(_?)\[(.*)\]$/;
195 my $macroplus = $1;
196 my $macroname = $2;
197 my $macrotarget = $3;
198 my $macroparam = $macroname.",".$4;
199 $self->register_attributelist($macroparam, 'macro');
200 if ($macrotarget eq '_') {
201 $self->{translate}->{macro}->{$macroname} .= '_';
202 }
203 if ($macroplus eq '+') {
204 $self->{translate}->{macro}->{$macroname} =~ s/^,/,+/;
205 }
206}
207
208sub is_translated_target {
209 my $self = shift;
210 my $macroname = shift;
211 return defined($self->{translate}->{macro}->{$macroname}) &&
212 $self->{translate}->{macro}->{$macroname} =~ m/_$/;
213}
214
215sub is_unsplitted_attributelist {
216 my $self = shift;
217 my $name = shift;
218 my $type = shift;
219 return defined($self->{translate}->{$type}->{$name}) &&
220 $self->{translate}->{$type}->{$name} =~ m/^,\+/;
221}
222
223sub process_definition {
224 my $self = shift;
225 my $command = shift;
226 if ($command =~ m/^po4a: macro\s+(.*\[.*\])\s*$/) {
227 $self->register_macro($1);
228 } elsif ($command =~ m/^po4a: style\s*(\[.*\])\s*$/) {
229 $self->register_attributelist($1);
230 } elsif ($command =~ m/^po4a: entry\s+(.+?)\s*$/) {
231 $self->{translate}->{entry}->{$1} = 1;
232 }
233}
234
235sub parse_definition_file {
236 my $self = shift;
237 my $filename = shift;
238 if (! open (IN,"<", $filename)) {
239 die wrap_mod("po4a::asciidoc",
240 dgettext("po4a", "Can't open %s: %s"), $filename, $!);
241 }
242 while (<IN>) {
243 chomp;
244 process_definition($self, $_);
245 }
246 close IN;
247}
248
249my $RE_SECTION_TEMPLATES = "sect1|sect2|sect3|sect4|preface|colophon|dedication|synopsis|index";
250my $RE_STYLE_ADMONITION = "TIP|NOTE|IMPORTANT|WARNING|CAUTION";
251my $RE_STYLE_PARAGRAPH = "normal|literal|verse|quote|listing|abstract|partintro|comment|example|sidebar|source|music|latex|graphviz";
252my $RE_STYLE_NUMBERING = "arabic|loweralpha|upperalpha|lowerroman|upperroman";
253my $RE_STYLE_LIST = "appendix|horizontal|qanda|glossary|bibliography";
254my $RE_STYLES = "$RE_SECTION_TEMPLATES|$RE_STYLE_ADMONITION|$RE_STYLE_PARAGRAPH|$RE_STYLE_NUMBERING|$RE_STYLE_LIST|float";
255
256BEGIN {
257 my $UnicodeGCString_available = 0;
258 $UnicodeGCString_available = 1 if (eval { require Unicode::GCString });
259 eval {
260 sub columns($$$) {
261 my $text = shift;
262 my $encoder = shift;
263 $text = $encoder->decode($text) if (defined($encoder) && $encoder->name ne "ascii");
264 if ($UnicodeGCString_available) {
265 return Unicode::GCString->new($text)->columns();
266 } else {
267 $text =~ s/\n$//s;
268 return length($text) if !(defined($encoder) && $encoder->name ne "ascii");
269 die wrap_mod("po4a::asciidoc",
270 dgettext("po4a", "Detection of two line titles failed at %s\nInstall the Unicode::GCString module!"), shift)
271 }
272 }
273 };
274}
275
276sub parse {
277 my $self = shift;
278 my ($line,$ref);
279 my $paragraph="";
280 my $wrapped_mode = 1;
281 ($line,$ref)=$self->shiftline();
282 my $file = $ref;
283 $file =~ s/:[0-9]+$// if defined($line);
284 while (defined($line)) {
285 $ref =~ m/^(.*):[0-9]+$/;
286 if ($1 ne $file) {
287 $file = $1;
288 do_paragraph($self,$paragraph,$wrapped_mode);
289 $paragraph="";
290 $wrapped_mode = 1;
291 }
292
293 chomp($line);
294 $self->{ref}="$ref";
295 if ((defined $self->{verbatim}) and ($self->{verbatim} == 3)) {
296 # Untranslated blocks
297 $self->pushline($line."\n");
298 if ($line =~ m/^~{4,}$/) {
299 undef $self->{verbatim};
300 undef $self->{type};
301 $wrapped_mode = 1;
302 }
303 } elsif ((defined $self->{verbatim}) and ($self->{verbatim} == 2)) {
304 # CommentBlock
305 if ($line =~ m/^\/{4,}$/) {
306 undef $self->{verbatim};
307 undef $self->{type};
308 $wrapped_mode = 1;
309 } else {
310 push @comments, $line;
311 }
312 } elsif ((not defined($self->{verbatim})) and ($line =~ m/^(\+|--)$/)) {
313 # List Item Continuation or List Block
314 do_paragraph($self,$paragraph,$wrapped_mode);
315 $paragraph="";
316 $self->pushline($line."\n");
317 } elsif ((not defined($self->{verbatim})) and
318 ($line =~ m/^(={2,}|-{2,}|~{2,}|\^{2,}|\+{2,})$/) and
319 (defined($paragraph) )and
320 ($paragraph =~ m/^[^\n]*\n$/s) and
321 (columns($paragraph, $self->{TT}{po_in}{encoder}, $ref) == (length($line)))) {
322 # Found title
323 $wrapped_mode = 0;
324 my $level = $line;
325 $level =~ s/^(.).*$/$1/;
326 $paragraph =~ s/\n$//s;
327 my $t = $self->translate($paragraph,
328 $self->{ref},
329 "Title $level",
330 "comment" => join("\n", @comments),
331 "wrap" => 0);
332 $self->pushline($t."\n");
333 $paragraph="";
334 @comments=();
335 $wrapped_mode = 1;
336 $self->pushline(($level x (columns($t, $self->{TT}{po_in}{encoder}, $ref)))."\n");
337 } elsif ($line =~ m/^(={1,5})( +)(.*?)( +\1)?$/) {
338 my $titlelevel1 = $1;
339 my $titlespaces = $2;
340 my $title = $3;
341 my $titlelevel2 = $4||"";
342 # Found one line title
343 do_paragraph($self,$paragraph,$wrapped_mode);
344 $wrapped_mode = 0;
345 $paragraph="";
346 my $t = $self->translate($title,
347 $self->{ref},
348 "Title $titlelevel1",
349 "comment" => join("\n", @comments),
350 "wrap" => 0);
351 $self->pushline($titlelevel1.$titlespaces.$t.$titlelevel2."\n");
352 @comments=();
353 $wrapped_mode = 1;
354 } elsif ($line =~ m/^(\/{4,}|\+{4,}|-{4,}|\.{4,}|\*{4,}|_{4,}|={4,}|~{4,}|\|={4,})$/) {
355 # Found one delimited block
356 my $t = $line;
357 $t =~ s/^(.).*$/$1/;
358 my $type = "delimited block $t";
359 if (defined $self->{verbatim} and ($self->{type} ne $type)) {
360 $paragraph .= "$line\n";
361 } else {
362 do_paragraph($self,$paragraph,$wrapped_mode);
363 if ( (defined $self->{type})
364 and ($self->{type} eq $type)) {
365 undef $self->{type};
366 undef $self->{verbatim};
367 $wrapped_mode = 1;
368 } else {
369 if ($t eq "\/") {
370 # CommentBlock, should not be treated
371 $self->{verbatim} = 2;
372 } elsif ($t eq "+") {
373 # PassthroughBlock
374 $wrapped_mode = 0;
375 $self->{verbatim} = 1;
376 } elsif ($t eq "-" or $t eq "|") {
377 # ListingBlock
378 $wrapped_mode = 0;
379 $self->{verbatim} = 1;
380 } elsif ($t eq ".") {
381 # LiteralBlock
382 $wrapped_mode = 0;
383 $self->{verbatim} = 1;
384 } elsif ($t eq "*") {
385 # SidebarBlock
386 $wrapped_mode = 1;
387 } elsif ($t eq "_") {
388 # QuoteBlock
389 if ( (defined $self->{type})
390 and ($self->{type} eq "verse")) {
391 $wrapped_mode = 0;
392 $self->{verbatim} = 1;
393 } else {
394 $wrapped_mode = 1;
395 }
396 } elsif ($t eq "=") {
397 # ExampleBlock
398 $wrapped_mode = 1;
399 } elsif ($t eq "~") {
400 # Filter blocks, TBC: not translated
401 $wrapped_mode = 0;
402 $self->{verbatim} = 3;
403 }
404 $self->{type} = $type;
405 }
406 $paragraph="";
407 $self->pushline($line."\n") unless defined($self->{verbatim}) && $self->{verbatim} == 2;
408 }
409 } elsif ((not defined($self->{verbatim})) and ($line =~ m/^\/\/(.*)/)) {
410 my $comment = $1;
411 if ($comment =~ m/^po4a: /) {
412 # Po4a command line
413 $self->process_definition($comment);
414 } else {
415 # Comment line
416 push @comments, $comment;
417 }
418 } elsif (not defined $self->{verbatim} and
419 ($line =~ m/^\[\[([^\]]*)\]\]$/)) {
420 # Found BlockId
421 do_paragraph($self,$paragraph,$wrapped_mode);
422 $paragraph="";
423 $wrapped_mode = 1;
424 $self->pushline($line."\n");
425 undef $self->{bullet};
426 undef $self->{indent};
427 } elsif (not defined $self->{verbatim} and
428 ($paragraph eq "") and
429 ($line =~ m/^((?:$RE_STYLE_ADMONITION):\s+)(.*)$/)) {
430 my $type = $1;
431 my $text = $2;
432 do_paragraph($self,$paragraph,$wrapped_mode);
433 $paragraph=$text."\n";
434 $wrapped_mode = 1;
435 $self->pushline($type);
436 undef $self->{bullet};
437 undef $self->{indent};
438 } elsif (not defined $self->{verbatim} and
439 ($line =~ m/^\[($RE_STYLES)\]$/)) {
440 my $type = $1;
441 do_paragraph($self,$paragraph,$wrapped_mode);
442 $paragraph="";
443 $wrapped_mode = 1;
444 $self->pushline($line."\n");
445 if ($type eq "verse") {
446 $wrapped_mode = 0;
447 }
448 undef $self->{bullet};
449 undef $self->{indent};
450 } elsif (not defined $self->{verbatim} and
451 ($line =~ m/^\[.*\]$/)) {
452 do_paragraph($self,$paragraph,$wrapped_mode);
453 $paragraph="";
454 my $t = $self->parse_style($line);
455 $self->pushline("$t\n");
456 @comments=();
457 $wrapped_mode = 1;
458 if ($line =~ m/^\[(['"]?)(verse|quote)\1,/) {
459 $wrapped_mode = 0 if $2 eq 'verse';
460 $self->{type} = $2;
461 }
462 undef $self->{bullet};
463 undef $self->{indent};
464 } elsif (not defined $self->{verbatim} and
465 ($line =~ m/^(\s*)([*_+`'#[:alnum:]].*)((?:::|;;|\?\?|:-)(?: *\\)?)$/)) {
466 my $indent = $1;
467 my $label = $2;
468 my $labelend = $3;
469 # Found labeled list
470 do_paragraph($self,$paragraph,$wrapped_mode);
471 $paragraph="";
472 $wrapped_mode = 1;
473 $self->{bullet} = "";
474 $self->{indent} = $indent;
475 my $t = $self->translate($label,
476 $self->{ref},
477 "Labeled list",
478 "comment" => join("\n", @comments),
479 "wrap" => 0);
480 $self->pushline("$indent$t$labelend\n");
481 @comments=();
482 } elsif (not defined $self->{verbatim} and
483 ($line =~ m/^(\s*)(\S.*)((?:::|;;)\s+)(.*)$/)) {
484 my $indent = $1;
485 my $label = $2;
486 my $labelend = $3;
487 my $labeltext = $4;
488 # Found Horizontal Labeled Lists
489 do_paragraph($self,$paragraph,$wrapped_mode);
490 $paragraph=$labeltext."\n";
491 $wrapped_mode = 1;
492 $self->{bullet} = "";
493 $self->{indent} = $indent;
494 my $t = $self->translate($label,
495 $self->{ref},
496 "Labeled list",
497 "comment" => join("\n", @comments),
498 "wrap" => 0);
499 $self->pushline("$indent$t$labelend");
500 @comments=();
501 } elsif (not defined $self->{verbatim} and
502 ($line =~ m/^\:(\S.*?)(:\s*)(.*)$/)) {
503 my $attrname = $1;
504 my $attrsep = $2;
505 my $attrvalue = $3;
506 while ($attrvalue =~ s/ \+$//s) {
507 ($line,$ref)=$self->shiftline();
508 $ref =~ m/^(.*):[0-9]+$/;
509 $line =~ s/^\s+//;
510 $attrvalue .= $line;
511 }
512 # Found an Attribute entry
513 do_paragraph($self,$paragraph,$wrapped_mode);
514 $paragraph="";
515 $wrapped_mode = 1;
516 undef $self->{bullet};
517 undef $self->{indent};
518 if (defined($self->{translate}->{entry}->{$attrname})) {
519 my $t = $self->translate($attrvalue,
520 $self->{ref},
521 "Attribute :$attrname:",
522 "comment" => join("\n", @comments),
523 "wrap" => 0);
524 $self->pushline(":$attrname$attrsep$t\n");
525 } else {
526 $self->pushline(":$attrname$attrsep$attrvalue\n");
527 }
528 @comments=();
529 } elsif (not defined $self->{verbatim} and
530 ($line =~ m/^([\w\d][\w\d-]*)(::)(\S+)\[(.*)\]$/)) {
531 my $macroname = $1;
532 my $macrotype = $2;
533 my $macrotarget = $3;
534 my $macroparam = $4;
535 # Found a macro
536 if ($macrotype eq '::') {
537 do_paragraph($self,$paragraph,$wrapped_mode);
538 $paragraph="";
539 $wrapped_mode = 1;
540 undef $self->{bullet};
541 undef $self->{indent};
542 }
543 my $t = $self->parse_macro($macroname, $macrotype, $macrotarget, $macroparam);
544 $self->pushline("$t\n");
545 @comments=();
546 } elsif (not defined $self->{verbatim} and
547 ($line !~ m/^\.\./) and ($line =~ m/^\.(\S.*)$/)) {
548 my $title = $1;
549 # Found block title
550 do_paragraph($self,$paragraph,$wrapped_mode);
551 $paragraph="";
552 $wrapped_mode = 1;
553 undef $self->{bullet};
554 undef $self->{indent};
555 my $t = $self->translate($title,
556 $self->{ref},
557 "Block title",
558 "comment" => join("\n", @comments),
559 "wrap" => 0);
560 $self->pushline(".$t\n");
561 @comments=();
562 } elsif (not defined $self->{verbatim} and
563 ($line =~ m/^(\s*)((?:[-*o+]|(?:[0-9]+[.\)])|(?:[a-z][.\)])|\([0-9]+\)|\.|\.\.)\s+)(.*)$/)) {
564 my $indent = $1||"";
565 my $bullet = $2;
566 my $text = $3;
567 do_paragraph($self,$paragraph,$wrapped_mode);
568 $paragraph = $text."\n";
569 $self->{indent} = $indent;
570 $self->{bullet} = $bullet;
571 } elsif (not defined $self->{verbatim} and
572 ($line =~ m/^((?:<?[0-9]+)?> +)(.*)$/)) {
573 my $bullet = $1;
574 my $text = $2;
575 do_paragraph($self,$paragraph,$wrapped_mode);
576 $paragraph = $text."\n";
577 $self->{indent} = "";
578 $self->{bullet} = $bullet;
579 } elsif (not defined $self->{verbatim} and
580 (defined $self->{bullet} and $line =~ m/^(\s+)(.*)$/)) {
581 my $indent = $1;
582 my $text = $2;
583 if (not defined $self->{indent}) {
584 $paragraph .= $text."\n";
585 $self->{indent} = $indent;
586 } elsif (length($paragraph) and (length($self->{bullet}) + length($self->{indent}) == length($indent))) {
587 $paragraph .= $text."\n";
588 } else {
589 do_paragraph($self,$paragraph,$wrapped_mode);
590 $paragraph = $text."\n";
591 $self->{indent} = $indent;
592 $self->{bullet} = "";
593 }
594 } elsif ($line =~ /^\s*$/) {
595 # Break paragraphs on lines containing only spaces
596 do_paragraph($self,$paragraph,$wrapped_mode);
597 $paragraph="";
598 $wrapped_mode = 1 unless defined($self->{verbatim});
599 $self->pushline($line."\n");
600 undef $self->{controlkey};
601 } elsif ($line =~ /^-- $/) {
602 # Break paragraphs on email signature hint
603 do_paragraph($self,$paragraph,$wrapped_mode);
604 $paragraph="";
605 $wrapped_mode = 1;
606 $self->pushline($line."\n");
607 } elsif ( $line =~ /^=+$/
608 or $line =~ /^_+$/
609 or $line =~ /^-+$/) {
610 $wrapped_mode = 0;
611 $paragraph .= $line."\n";
612 do_paragraph($self,$paragraph,$wrapped_mode);
613 $paragraph="";
614 $wrapped_mode = 1;
615 } else {
616 if ($line =~ /^\s/) {
617 # A line starting by a space indicates a non-wrap
618 # paragraph
619 $wrapped_mode = 0;
620 }
621 undef $self->{bullet};
622 undef $self->{indent};
623 # TODO: comments
624 $paragraph .= $line."\n";
625 }
626 # paragraphs starting by a bullet, or numbered
627 # or paragraphs with a line containing many consecutive spaces
628 # (more than 3)
629 # are considered as verbatim paragraphs
630 $wrapped_mode = 0 if ( $paragraph =~ m/^(\*|[0-9]+[.)] )/s
631 or $paragraph =~ m/[ \t][ \t][ \t]/s);
632 ($line,$ref)=$self->shiftline();
633 }
634 if (length $paragraph) {
635 do_paragraph($self,$paragraph,$wrapped_mode);
636 }
637}
638
639sub do_paragraph {
640 my ($self, $paragraph, $wrap) = (shift, shift, shift);
641 my $type = shift || $self->{type} || "Plain text";
642 return if ($paragraph eq "");
643
644# DEBUG
645# my $b;
646# if (defined $self->{bullet}) {
647# $b = $self->{bullet};
648# } else {
649# $b = "UNDEF";
650# }
651# $type .= " verbatim: '".($self->{verbatim}||"NONE")."' bullet: '$b' indent: '".($self->{indent}||"NONE")."' type: '".($self->{type}||"NONE")."'";
652
653 if (not $wrap and not defined $self->{verbatim}) {
654 # Detect bullets
655 # | * blah blah
656 # |<spaces> blah
657 # | ^-- aligned
658 # <empty line>
659 #
660 # Other bullets supported:
661 # - blah o blah + blah
662 # 1. blah 1) blah (1) blah
663TEST_BULLET:
664 if ($paragraph =~ m/^(\s*)((?:[-*o+]|([0-9]+[.\)])|\([0-9]+\))\s+)([^\n]*\n)(.*)$/s) {
665 my $para = $5;
666 my $bullet = $2;
667 my $indent1 = $1;
668 my $indent2 = "$1".(' ' x length $bullet);
669 my $text = $4;
670 while ($para !~ m/$indent2(?:[-*o+]|([0-9]+[.\)])|\([0-9]+\))\s+/
671 and $para =~ s/^$indent2(\S[^\n]*\n)//s) {
672 $text .= $1;
673 }
674 # TODO: detect if a line starts with the same bullet
675 if ($text !~ m/\S[ \t][ \t][ \t]+\S/s) {
676 my $bullet_regex = quotemeta($indent1.$bullet);
677 $bullet_regex =~ s/[0-9]+/\\d\+/;
678 if ($para eq '' or $para =~ m/^$bullet_regex\S/s) {
679 my $trans = $self->translate($text,
680 $self->{ref},
681 "Bullet: '$indent1$bullet'",
682 "wrap" => 1,
683 "wrapcol" => - (length $indent2));
684 $trans =~ s/^/$indent1$bullet/s;
685 $trans =~ s/\n(.)/\n$indent2$1/sg;
686 $self->pushline( $trans."\n" );
687 if ($para eq '') {
688 return;
689 } else {
690 # Another bullet
691 $paragraph = $para;
692 goto TEST_BULLET;
693 }
694 }
695 }
696 }
697 }
698
699 my $end = "";
700 if ($wrap) {
701 $paragraph =~ s/^(.*?)(\n*)$/$1/s;
702 $end = $2 || "";
703 }
704 my $t = $self->translate($paragraph,
705 $self->{ref},
706 $type,
707 "comment" => join("\n", @comments),
708 "wrap" => $wrap);
709 @comments = ();
710 if (defined $self->{bullet}) {
711 my $bullet = $self->{bullet};
712 my $indent1 = $self->{indent};
713 my $indent2 = $indent1.(' ' x length($bullet));
714 $t =~ s/^/$indent1$bullet/s;
715 $t =~ s/\n(.)/\n$indent2$1/sg;
716 }
717 $self->pushline( $t.$end );
718}
719
720sub parse_style {
721 my ($self, $text) = (shift, shift);
722 $text =~ s/^\[//;
723 $text =~ s/\]$//;
724 $text =~ m/^([^=,]+)/;
725 if (defined($1) && $self->is_unsplitted_attributelist($1, 'style')) {
726 my $t = $self->translate($text,
727 $self->{ref},
728 "Unsplitted AttributeList",
729 "comment" => join("\n", @comments),
730 "wrap" => 0);
731 return "[$t]";
732 }
733 my @attributes = $self->split_attributelist($text);
734 return "[".join(", ", $self->join_attributelist("style", @attributes))."]";
735}
736
737sub parse_macro {
738 my ($self, $macroname, $macrotype, $macrotarget, $macroparam) = (shift, shift, shift, shift, shift);
739 if ($self->is_unsplitted_attributelist($macroname, 'macro')) {
740 my $t = $self->translate("$macroname$macrotype$macrotarget\[$macroparam\]",
741 $self->{ref},
742 "Unsplitted macro call",
743 "comment" => join("\n", @comments),
744 "wrap" => 0);
745 return $t;
746 }
747 my @attributes = $self->split_attributelist($macroparam);
748 unshift @attributes, $macroname;
749 my @translated_attributes = $self->join_attributelist("macro", @attributes);
750 shift @translated_attributes;
751 if ($self->is_translated_target($macroname)) {
752 my $target = unquote_space($macrotarget);
753 my $t = $self->translate($target,
754 $self->{ref},
755 "Target for macro $macroname",
756 "comment" => join("\n", @comments),
757 "wrap" => 0);
758 $macrotarget = quote_space($t);
759 }
760 return "$macroname$macrotype$macrotarget\[".join(", ", @translated_attributes)."]";
761}
762
763sub split_attributelist {
764 my ($self, $text) = (shift, shift);
765
766 print STDERR "Splitting attributes in: $text\n" if $debug{split_attributelist};
767 my @attributes = ();
768 while ($text =~ m/\G(
769 [^\W\d][-\w]*="(?:[^"\\]++|\\.)*+" # named attribute
770 | [^\W\d][-\w]*=None # undefined named attribute
771 | [^\W\d][-\w]*=\S+ # invalid, but accept it anyway
772 | "(?:[^"\\]++|\\.)*+" # quoted attribute
773 | (?:[^,\\]++|\\.)++ # unquoted attribute
774 )(?:,\s*+)?/gx) {
775 print STDERR " -> $1\n" if $debug{split_attributelist};
776 push @attributes, $1;
777 }
778 die wrap_mod("po4a::asciidoc",
779 dgettext("po4a", "Unable to parse attribute list: [%s]"), $text)
780 unless scalar(@attributes);
781 return @attributes;
782}
783
784sub join_attributelist {
785 my ($self, $type) = (shift, shift);
786 my @attributes = @_;
787 my $command = shift(@attributes);
788 my $position = 1;
789 my @text = ($command);
790 if ($command =~ m/=/) {
791 my $attr = $command;
792 $command =~ s/=.*//;
793 @text = ();
794 push @text, $self->translate_attributelist($type, $command, $position, $attr);
795 }
796 foreach my $attr (@attributes) {
797 $position++;
798 push @text, $self->translate_attributelist($type, $command, $position, $attr);
799 }
800 print STDERR "Joined attributes: ".join(", ", @text)."\n" if $debug{join_attributelist};
801 return @text;
802}
803
804sub translate_attributelist {
805 my ($self, $type, $command, $count, $attr) = (shift, shift, shift, shift, shift);
806 return $attr unless defined $self->{translate}->{$type}->{$command};
807 if ($attr =~ m/^([^\W\d][-\w]*)=(.*)/) {
808 my $attrname = $1;
809 my $attrvalue = $2;
810 if ($self->{translate}->{$type}->{$command} =~ m/,$attrname,/) {
811 my $value = unquote($attrvalue);
812 my $t = $self->translate($value,
813 $self->{ref},
814 "Named '$attrname' AttributeList argument for $type '$command'",
815 "comment" => join("\n", @comments),
816 "wrap" => 0);
817 if ($attrvalue eq 'None' && $t eq 'None') {
818 $attr = $attrname."=None";
819 } else {
820 $attr = $attrname."=".quote($t);
821 }
822 }
823 } else {
824 if ($self->{translate}->{$type}->{$command} =~ m/,$count,/) {
825 my $attrvalue = unquote($attr);
826 my $t = $self->translate($attrvalue,
827 $self->{ref},
828 "Positional (\$$count) AttributeList argument for $type '$command'",
829 "comment" => join("\n", @comments),
830 "wrap" => 0);
831 $attr = quote($t);
832 }
833 }
834 return $attr;
835}
836
837sub unquote {
838 my ($text) = shift;
839 return $text unless $text =~ s/^"(.*)"$/$1/;
840 $text =~ s/\\"/"/g;
841 return $text;
842}
843
844sub quote {
845 my $text = shift;
846 $text =~ s/"/\\"/g;
847 return '"'.$text.'"';
848}
849
850sub quote_space {
851 my $text = shift;
852 $text =~ s/ /%20/g;
853 return $text;
854}
855
856sub unquote_space {
857 my $text = shift;
858 $text =~ s/%20/ /g;
859 return $text;
860}
861
8621;
863
864=head1 STATUS OF THIS MODULE
865
866Tested successfully on simple AsciiDoc files.
867
868=head1 AUTHORS
869
870 Nicolas François <nicolas.francois@centraliens.net>
871 Denis Barbier <barbier@linuxfr.org>
872
873=head1 COPYRIGHT AND LICENSE
874
875 Copyright 2005-2008 by Nicolas FRANÇOIS <nicolas.francois@centraliens.net>.
876 Copyright 2012 by Denis BARBIER <barbier@linuxfr.org>.
877
878This program is free software; you may redistribute it and/or modify it
879under the terms of GPL (see the COPYING file).
880

Archive Download this file

Revision: 2805