Root/
Source at commit 2903 created 6 years 8 months ago. By ifabio, Comment out message info. | |
---|---|
1 | #!/usr/bin/perl -w␊ |
2 | ␊ |
3 | =encoding UTF-8␊ |
4 | ␊ |
5 | =head1 NAME␊ |
6 | ␊ |
7 | Locale::Po4a::AsciiDoc - convert AsciiDoc documents from/to PO files␊ |
8 | ␊ |
9 | =head1 DESCRIPTION␊ |
10 | ␊ |
11 | The po4a (PO for anything) project goal is to ease translations (and more␊ |
12 | interestingly, the maintenance of translations) using gettext tools on␊ |
13 | areas where they were not expected like documentation.␊ |
14 | ␊ |
15 | Locale::Po4a::AsciiDoc is a module to help the translation of documentation in␊ |
16 | the AsciiDoc format.␊ |
17 | ␊ |
18 | =cut␊ |
19 | ␊ |
20 | package Locale::Po4a::AsciiDoc;␊ |
21 | ␊ |
22 | use 5.010;␊ |
23 | use strict;␊ |
24 | use warnings;␊ |
25 | ␊ |
26 | require Exporter;␊ |
27 | use vars qw(@ISA @EXPORT);␊ |
28 | @ISA = qw(Locale::Po4a::TransTractor);␊ |
29 | @EXPORT = qw();␊ |
30 | ␊ |
31 | use Locale::Po4a::TransTractor;␊ |
32 | use Locale::Po4a::Common;␊ |
33 | ␊ |
34 | =head1 OPTIONS ACCEPTED BY THIS MODULE␊ |
35 | ␊ |
36 | These are this module's particular options:␊ |
37 | ␊ |
38 | =over␊ |
39 | ␊ |
40 | =item B<definitions>␊ |
41 | ␊ |
42 | The name of a file containing definitions for po4a, as defined in the␊ |
43 | B<INLINE CUSTOMIZATION> section.␊ |
44 | You can use this option if it is not possible to put the definitions in␊ |
45 | the document being translated.␊ |
46 | ␊ |
47 | In a definitions file, lines must not start by two slashes, but directly␊ |
48 | by B<po4a:>.␊ |
49 | ␊ |
50 | =item B<entry>␊ |
51 | ␊ |
52 | Space-separated list of attribute entries you want to translate. By default,␊ |
53 | no attribute entries are translatable.␊ |
54 | ␊ |
55 | =item B<macro>␊ |
56 | ␊ |
57 | Space-separated list of macro definitions.␊ |
58 | ␊ |
59 | =item B<style>␊ |
60 | ␊ |
61 | Space-separated list of style definitions.␊ |
62 | ␊ |
63 | =back␊ |
64 | ␊ |
65 | =head1 INLINE CUSTOMIZATION␊ |
66 | ␊ |
67 | The AsciiDoc module can be customized with lines starting by B<//po4a:>.␊ |
68 | These lines are interpreted as commands to the parser.␊ |
69 | The following commands are recognized:␊ |
70 | ␊ |
71 | =over 4␊ |
72 | ␊ |
73 | =item B<//po4a: macro >I<name>B<[>I<attribute list>B<]>␊ |
74 | ␊ |
75 | This permits to describe in detail the parameters of a B<macro>;␊ |
76 | I<name> must be a valid macro name, and it ends with an underscore␊ |
77 | if the target must be translated.␊ |
78 | ␊ |
79 | The I<attribute list> argument is a comma separated list which␊ |
80 | contains informations about translatable arguments. This list contains␊ |
81 | either numbers, to define positional parameters, or named attributes.␊ |
82 | ␊ |
83 | If a plus sign (B<+>) is prepended to I<name>, then the macro and its␊ |
84 | arguments are translated as a whole. There is no need to define␊ |
85 | attribute list in this case, but brackets must be present.␊ |
86 | ␊ |
87 | =item B<//po4a: style >B<[>I<attribute list>B<]>␊ |
88 | ␊ |
89 | This permits to describe in detail which attributes of a style must␊ |
90 | be translated.␊ |
91 | ␊ |
92 | The I<attribute list> argument is a comma separated list which␊ |
93 | contains informations about translatable arguments. This list contains␊ |
94 | either numbers, to define positional parameters, or named attributes.␊ |
95 | The first attribute is the style name, it will not be translated.␊ |
96 | ␊ |
97 | If a plus sign (B<+>) is prepended to the style name, then the␊ |
98 | attribute list is translated as a whole. There is no need to define␊ |
99 | translatable attributes.␊ |
100 | ␊ |
101 | If a minus sign (B<->) is prepended to the style name, then this␊ |
102 | attribute is not translated.␊ |
103 | ␊ |
104 | =item B<//po4a: entry >I<name>␊ |
105 | ␊ |
106 | This declares an attribute entry as being translatable. By default,␊ |
107 | they are not translated.␊ |
108 | ␊ |
109 | =back␊ |
110 | ␊ |
111 | =cut␊ |
112 | ␊ |
113 | my @comments = ();␊ |
114 | ␊ |
115 | my %debug=('split_attributelist' => 0,␊ |
116 | 'join_attributelist' => 0␊ |
117 | );␊ |
118 | ␊ |
119 | sub 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 | ␊ |
175 | sub 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 | ␊ |
189 | sub 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 | ␊ |
208 | sub 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 | ␊ |
215 | sub 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 | ␊ |
223 | sub 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 | ␊ |
235 | sub 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 | ␊ |
249 | my $RE_SECTION_TEMPLATES = "sect1|sect2|sect3|sect4|preface|colophon|dedication|synopsis|index";␊ |
250 | my $RE_STYLE_ADMONITION = "TIP|NOTE|IMPORTANT|WARNING|CAUTION";␊ |
251 | my $RE_STYLE_PARAGRAPH = "normal|literal|verse|quote|listing|abstract|partintro|comment|example|sidebar|source|music|latex|graphviz";␊ |
252 | my $RE_STYLE_NUMBERING = "arabic|loweralpha|upperalpha|lowerroman|upperroman";␊ |
253 | my $RE_STYLE_LIST = "appendix|horizontal|qanda|glossary|bibliography";␊ |
254 | my $RE_STYLES = "$RE_SECTION_TEMPLATES|$RE_STYLE_ADMONITION|$RE_STYLE_PARAGRAPH|$RE_STYLE_NUMBERING|$RE_STYLE_LIST|float";␊ |
255 | ␊ |
256 | BEGIN {␊ |
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 | ␊ |
276 | sub 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 | ␊ |
639 | sub 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␊ |
663 | TEST_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 | ␊ |
720 | sub 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 | ␊ |
737 | sub 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 | ␊ |
763 | sub 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 | ␊ |
784 | sub 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 | ␊ |
804 | sub 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 | ␊ |
837 | sub unquote {␊ |
838 | my ($text) = shift;␊ |
839 | return $text unless $text =~ s/^"(.*)"$/$1/;␊ |
840 | $text =~ s/\\"/"/g;␊ |
841 | return $text;␊ |
842 | }␊ |
843 | ␊ |
844 | sub quote {␊ |
845 | my $text = shift;␊ |
846 | $text =~ s/"/\\"/g;␊ |
847 | return '"'.$text.'"';␊ |
848 | }␊ |
849 | ␊ |
850 | sub quote_space {␊ |
851 | my $text = shift;␊ |
852 | $text =~ s/ /%20/g;␊ |
853 | return $text;␊ |
854 | }␊ |
855 | ␊ |
856 | sub unquote_space {␊ |
857 | my $text = shift;␊ |
858 | $text =~ s/%20/ /g;␊ |
859 | return $text;␊ |
860 | }␊ |
861 | ␊ |
862 | 1;␊ |
863 | ␊ |
864 | =head1 STATUS OF THIS MODULE␊ |
865 | ␊ |
866 | Tested 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 | ␊ |
878 | This program is free software; you may redistribute it and/or modify it␊ |
879 | under the terms of GPL (see the COPYING file).␊ |
880 |