File Coverage

blib/lib/Treex/PML/Backend/FS.pm
Criterion Covered Total %
statement 239 284 84.1
branch 67 112 59.8
condition 16 41 39.0
subroutine 20 21 95.2
pod 1 12 8.3
total 343 470 72.9


line stmt bran cond sub pod time code
1              
2             ############################################################
3             #
4             # Treex::PML::Backend::FS
5             # =========
6             #
7              
8             package Treex::PML::Backend::FS;
9              
10 6     6   41 use Carp;
  6         15  
  6         347  
11 6     6   36 use vars qw($CheckListValidity $emulatePML);
  6         10  
  6         240  
12 6     6   31 use strict;
  6         15  
  6         121  
13              
14 6     6   27 use vars qw($VERSION);
  6         13  
  6         195  
15             BEGIN {
16 6     6   129 $VERSION='2.24'; # version template
17             }
18 6     6   635 use Treex::PML::IO qw(open_backend close_backend);
  6         16  
  6         889  
19 6     6   57 use Treex::PML::Factory;
  6         25  
  6         159  
20              
21 6     6   29 use UNIVERSAL::DOES;
  6         10  
  6         17858  
22              
23             sub DOES {
24 0     0 0 0 my ($self,$role)=@_;
25 0 0 0     0 if ($role eq 'FSBackend' or $role eq __PACKAGE__) {
26 0         0 return 1;
27             } else {
28 0         0 return $self->SUPER::DOES($role);
29             }
30             }
31              
32              
33             =pod
34              
35             =head2 NAME
36              
37             Treex::PML::Backend::FS - IO backend for reading/writing FS files.
38              
39             =head1 SYNOPSIS
40              
41             use Treex::PML;
42             Treex::PML::AddBackends(qw(FS))
43              
44             my $document = Treex::PML::Factory->createDocumentFromFile('input.fs');
45             ...
46             $document->save();
47              
48             =head1 DESCRIPTION
49              
50             This module implements a Treex::PML input/output backend which accepts
51             reads/writes documents in the FS format.
52              
53             =head1 REFERENCE
54              
55             =over 4
56              
57             =item Treex::PML::Backend::FS::$emulatePML
58              
59             This variable controls whether a simple PML schema should be created
60             for FS files (default is 1 - yes). Attribute whose name contains one
61             or more slashes is represented as a (possibly nested) structure where
62             each slash represents one level of nesting. Attributes sharing a
63             common name-part followed by a slash are represented as members of
64             the same structure. For example, attributes C, C, C and
65             C result in the following structure:
66              
67             C<{a => value_of_a,
68             b => { u => { x => value_of_a/u/x },
69             v => { x => value_of_a/v/x,
70             y => value_of_a/v/y }
71             }
72             }>
73              
74             In the PML schema emulation mode, it is forbidden to have both C
75             and C attributes. In such a case the parser reverts to
76             non-emulation mode.
77              
78             =cut
79              
80             $emulatePML=1;
81              
82              
83             sub test {
84 4     4 0 11 my ($f,$encoding)=@_;
85 4 50       16 if (ref($f) eq 'ARRAY') {
    100          
86 0         0 return $f->[0]=~/^@/;
87             } elsif (ref($f)) {
88 2 50       9 binmode $f unless UNIVERSAL::DOES::does($f,'IO::Zlib');
89 2         127 my $test = ($f->getline()=~/^@/);
90 2         120 return $test;
91             } else {
92 2         9 my $fh = open_backend($f,"r");
93 2   33     22 my $test = $fh && test($fh);
94 2         10 close_backend($fh);
95 2         15 return $test;
96             }
97             }
98              
99              
100             sub _fs2members {
101 2     2   4 my ($fs)=@_;
102 2         5 my $mbr = {};
103 2         6 my $defs = $fs->defs;
104             # sort, so that possible short parts go first
105 2         8 foreach my $attr (sort $fs->attributes) {
106 100         120 my $m = $mbr;
107             # check that no short attr exists
108 100         150 my @parts = split /\//,$attr;
109 100         128 my $short=$parts[0];
110 100         171 for (my $i=1;$i<@parts;$i++) {
111 0 0       0 if ($defs->{$short}) {
112 0         0 warn "Can't emulate PML schema: attribute name conflict between $short and $attr: falling back to non-emulation mode\n";
113             }
114 0         0 $short .= '/'.$parts[$i];
115             }
116 100         134 for my $part (@parts) {
117 100         277 $m->{structure}{member}{$part}{-name} = $part;
118 100         142 $m=$m->{structure}{member}{$part};
119             }
120             # allow ``alt'' values concatenated with |
121 100 100       188 if ($fs->isList($attr)) {
122             $m->{alt} = {
123 2         9 -flat => 1,
124             choice => [ $fs->listValues($attr) ]
125             };
126             } else {
127             $m->{alt} = {
128 98         299 -flat => 1,
129             cdata => { format =>'any' }
130             };
131             }
132             }
133 2         10 return $mbr->{structure}{member};
134             }
135              
136             sub read {
137 2     2 0 6 my ($fileref,$fsfile) = @_;
138 2 50       7 return unless ref($fsfile);
139 2         11 my $FS = Treex::PML::Factory->createFSFormat();
140 2 50       9 $FS->readFrom($fileref) || return 0;
141 2         13 $fsfile->changeFS( $FS );
142              
143 2         4 my $emu_schema_type;
144 2 50       6 if ($emulatePML) {
145             # fake a PML Schema:
146 2         9 my $members = _fs2members($fsfile->FS);
147 2         14 $members->{'#childnodes'}={
148             role => '#CHILDNODES',
149             list => {
150             ordered => 1,
151             type => 'fs-node.type',
152             },
153             };
154 2         10 my $node_type = {
155             name => 'fs-node',
156             role => '#NODE',
157             member => $members,
158             };
159 2         38 my $schema= Treex::PML::Schema->convert_from_hash({
160             description => 'PML schema generated from FS header',
161             root => { name => 'fs-data',
162             structure => {
163             member => {
164             trees => {
165             -name => 'trees',
166             role => '#TREES',
167             required => 1,
168             list => {
169             ordered => 1,
170             type => 'fs-node.type'
171             }
172             }
173             }
174             }
175             },
176             type => {
177             'fs-node.type' => {
178             -name => 'fs-node.type',
179             structure => $node_type,
180             }
181             }
182             });
183 2 50       9 if (defined($node_type->{member})) {
184 2         3 $emu_schema_type = $node_type;
185 2         11 $fsfile->changeMetaData('schema',$schema);
186             }
187             }
188              
189 2         5 my ($root,$l,@rest);
190 2         9 $fsfile->changeTrees();
191              
192             # this could give us some speedup.
193 2         4 my $ordhash;
194             {
195 2         2 my $i = 0;
  2         3  
196 2         7 $ordhash = { map { $_ => $i++ } $fsfile->FS->attributes };
  100         209  
197             }
198              
199 2         18 while ($l=ReadEscapedLine($fileref)) {
200 12 100       29 if ($l=~/^\[/) {
201 4         15 $root=ParseFSTree($fsfile->FS,$l,$ordhash,$emu_schema_type);
202 4 50       11 push @{$fsfile->treeList}, $root if $root;
  4         14  
203 8         13 } else { push @rest, $l; }
204             }
205 2         9 $fsfile->changeTail(@rest);
206              
207             #parse Rest
208 2         4 my @patterns;
209 2         6 foreach ($fsfile->tail) {
210 8 100       34 if (/^\/\/Tred:Custom-Attribute:(.*\S)\s*$/) {
    50          
    50          
211 4         9 push @patterns,$1;
212             } elsif (/^\/\/Tred:Custom-AttributeCont:(.*\S)\s*$/) {
213 0         0 $patterns[$#patterns].="\n".$1;
214             } elsif (/^\/\/FS-REQUIRE:\s*(\S+)\s+(\S+)=\"([^\"]+)\"\s*$/) {
215 0   0     0 my $requires = $fsfile->metaData('fs-require') || $fsfile->changeMetaData('fs-require',[]);
216 0         0 push @$requires,[$2,$3];
217 0   0     0 my $refnames = $fsfile->metaData('refnames') || $fsfile->changeMetaData('refnames',{});
218 0         0 $refnames->{$1} = $2;
219             }
220             }
221 2         10 $fsfile->changePatterns(@patterns);
222 2 50       13 unless (@patterns) {
223 0         0 my ($peep)=$fsfile->tail;
224 0         0 $fsfile->changePatterns( map { "\$\{".$fsfile->FS->atno($_)."\}" }
  0         0  
225             ($peep=~/[,\(]([0-9]+)/g));
226             }
227             $fsfile->changeHint(join "\n",
228 2 100       10 map { /^\/\/Tred:Balloon-Pattern:(.*\S)\s*$/ ? $1 : () } $fsfile->tail);
  8         42  
229 2         13 return 1;
230             }
231              
232              
233             sub write {
234 1     1 0 3 my ($fileref,$fsfile) = @_;
235 1 50       4 return unless ref($fsfile);
236              
237             # print $fileref @{$fsfile->FS->unparsed};
238             {
239 1         2 my $encoding = $fsfile->encoding;
  1         4  
240 1 50       4 if (defined $encoding) {
241 0         0 print $fileref '@E '."$encoding\n";
242             }
243             }
244 1         3 $fsfile->FS->writeTo($fileref);
245 1 50       5 PrintFSFile($fileref,
246             $fsfile->FS,
247             $fsfile->treeList,
248             ref($fsfile->metaData('schema')) ? 1 : 0
249             );
250              
251             ## Tredish custom attributes:
252             $fsfile->changeTail(
253 4         29 (grep { $_!~/\/\/Tred:(?:Custom-Attribute(?:Cont)?|Balloon-Pattern):/ } $fsfile->tail),
254 2         17 (map {"//Tred:Custom-Attribute:$_\n"}
255             map {
256 2         9 join "\n//Tred:Custom-AttributeCont:",
257             split /\n/,$_
258             } $fsfile->patterns),
259 1         8 (map {"//Tred:Balloon-Pattern:$_\n"}
  1         7  
260             split /\n/,$fsfile->hint),
261             );
262 1         5 print $fileref $fsfile->tail;
263 1 50       10 if (ref($fsfile->metaData('fs-require'))) {
264 0   0     0 my $refnames = $fsfile->metaData('refnames') || {};
265 0         0 foreach my $req ( @{ $fsfile->metaData('fs-require') } ) {
  0         0  
266 0         0 my ($name) = grep { $refnames->{$_} eq $req->[0] } keys(%$refnames);
  0         0  
267 0         0 print $fileref "//FS-REQUIRE:$name $req->[0]=\"$req->[1]\"\n";
268             }
269             }
270 1         6 return 1;
271             }
272              
273             sub Print ($$) {
274             my (
275 461     461 0 594 $output, # filehandle or string
276             $text # text
277             )=@_;
278 461 50       648 if (ref($output) eq 'SCALAR') {
279 0         0 $$output.=$text;
280             } else {
281 461         700 print $output $text;
282             }
283             }
284              
285             sub PrintFSFile {
286 1     1 0 3 my ($fh,$fsformat,$trees,$emu_schema)=@_;
287 1         3 foreach my $tree (@$trees) {
288 2         5 PrintFSTree($tree,$fsformat,$fh,$emu_schema);
289             }
290             }
291              
292             sub PrintFSTree {
293 2     2 0 5 my ($root, # a reference to the root-node
294             $fsformat, # FSFormat object
295             $fh,
296             $emu_schema
297             )=@_;
298              
299 2 50       5 $fh=\*STDOUT unless $fh;
300 2         4 my $node=$root;
301 2         11 while ($node) {
302 14         28 PrintFSNode($node,$fsformat,$fh,$emu_schema);
303 14 100       32 if ($node->{$Treex::PML::Node::firstson}) {
304 8         15 Print($fh, "(");
305 8         12 $node = $node->{$Treex::PML::Node::firstson};
306 8         12 redo;
307             }
308 6   66     34 while ($node && $node != $root && !($node->{$Treex::PML::Node::rbrother})) {
      100        
309 8         13 Print($fh, ")");
310 8         29 $node = $node->{$Treex::PML::Node::parent};
311             }
312 6 50       13 croak "Error: NULL-node within the node while printing\n" if !$node;
313 6 100 66     17 last if ($node == $root || !$node);
314 4         9 Print($fh, ",");
315 4         6 $node = $node->{$Treex::PML::Node::rbrother};
316 4         5 redo;
317             }
318 2         6 Print($fh, "\n");
319             }
320              
321             sub PrintFSNode {
322 14     14 0 22 my ($node, # a reference to the root-node
323             $fsformat,
324             $output, # output stream
325             $emu_schema
326             )=@_;
327 14         16 my $v;
328 14         16 my $lastprinted=1;
329              
330 14         33 my $defs = $fsformat->defs;
331 14         26 my $attrs = $fsformat->list;
332 14         22 my $attr_count = $#$attrs+1;
333              
334 14 50       26 if ($node) {
335 14         26 Print($output, "[");
336 14         34 for (my $n=0; $n<$attr_count; $n++) {
337 700 50       1308 $v=$emu_schema ? $node->attr($attrs->[$n]) : $node->{$attrs->[$n]};
338 700 100       1207 $v=~s/([,\[\]=\\\n])/\\$1/go if (defined($v));
339 700 100 66     1738 if (index($defs->{$attrs->[$n]}, " O")>=0) {
    100          
340 56 100       105 Print($output,",") if $n;
341 56 50 33     162 unless ($lastprinted && index($defs->{$attrs->[$n]}," P")>=0) # N could match here too probably
342 0         0 { Print($output, $attrs->[$n]."="); }
343 56 50 33     143 $v='-' if ($v eq '' or not defined($v));
344 56         96 Print($output,$v);
345 56         101 $lastprinted=1;
346             } elsif (defined($v) and length($v)) {
347 116 50       253 Print($output,",") if $n;
348 116 100 66     248 unless ($lastprinted && index($defs->{$attrs->[$n]}," P")>=0) # N could match here too probably
349 81         162 { Print($output,$attrs->[$n]."="); }
350 116         215 Print($output,$v);
351 116         196 $lastprinted=1;
352             } else {
353 528         827 $lastprinted=0;
354             }
355             }
356 14         23 Print($output,"]");
357             } else {
358 0         0 Print($output,"<>");
359             }
360             }
361              
362             =item Treex::PML::Backend::FS::ParseFSTree ($fsformat,$line,$ordhash)
363              
364             Parse a given string (line) in FS format and return the root of the
365             resulting FS tree as a node object.
366              
367             =cut
368              
369             sub ParseFSTree {
370 4     4 1 10 my ($fsformat,$l,$ordhash,$emu_schema_type)=@_;
371 4 50       10 return unless ref($fsformat);
372 4         8 my $root;
373             my $curr;
374 4         0 my $c;
375              
376 4 50       10 unless ($ordhash) {
377 0         0 my $i = 0;
378 0         0 $ordhash = { map { $_ => $i++ } @{$fsformat->list} };
  0         0  
  0         0  
379             }
380              
381 4 50       13 if ($l=~/^\[/o) {
382 4         10 $l=~s/&/&/g;
383 4         9 $l=~s/\\\\/&backslash;/g;
384 4         18 $l=~s/\\,/,/g;
385 4         10 $l=~s/\\\[/[/g;
386 4         7 $l=~s/\\]/]/g;
387 4         7 $l=~s/\\=/&eq;/g;
388 4         5 $l=~s/\\//g;
389 4         7 $l=~s/\r//g;
390 4         11 $curr=$root=ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type); # create Root
391              
392 4         7 while ($l) {
393 40         51 $c = substr($l,0,1);
394 40         60 $l = substr($l,1);
395 40 100       65 if ( $c eq '(' ) { # Create son (go down)
396 16         35 my $first_son = $curr->{$Treex::PML::Node::firstson} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
397 16         34 $first_son->{$Treex::PML::Node::parent}=$curr;
398 16         18 $curr=$first_son;
399 16         32 next;
400             }
401 24 100       37 if ( $c eq ')' ) { # Return to parent (go up)
402 16 50       32 croak "Error paring tree" if ($curr eq $root);
403 16         21 $curr=$curr->{$Treex::PML::Node::parent};
404 16         27 next;
405             }
406 8 50       15 if ( $c eq ',' ) { # Create right brother (go right);
407 8         14 my $rb = $curr->{$Treex::PML::Node::rbrother} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
408 8         28 $rb->set_lbrother( $curr );
409 8         27 $rb->set_parent( $curr->{$Treex::PML::Node::parent} );
410 8         9 $curr=$rb;
411 8         17 next;
412             }
413 0         0 croak "Unexpected token... `$c'!\n$l\n";
414             }
415 4 50       8 croak "Error: Closing brackets do not lead to root of the tree.\n" if ($curr != $root);
416             }
417 4         10 return $root;
418             }
419              
420              
421             sub ParseFSNode {
422 28     28 0 48 my ($fsformat,$lr,$ordhash,$emu_schema_type) = @_;
423 28         39 my $n = 0;
424 28         33 my $node;
425 28         34 my @ats=();
426 28         30 my $pos = 1;
427 28         34 my $a=0;
428 28         30 my $v=0;
429 28         76 my $tmp;
430             my @lv;
431 28         0 my $nd;
432 28         0 my $i;
433 28         0 my $w;
434              
435 28         58 my $defs = $fsformat->defs;
436 28         61 my $attrs = $fsformat->list;
437 28         44 my $attr_count = $#$attrs+1;
438 28 50       46 unless ($ordhash) {
439 0         0 my $i = 0;
440 0         0 $ordhash = { map { $_ => $i++ } @$attrs };
  0         0  
441             }
442              
443 28 50       68 $node = $emu_schema_type
444             ? Treex::PML::Factory->createTypedNode($emu_schema_type)
445             : Treex::PML::Factory->createNode();
446 28 50       89 if ($$lr=~/^\[/) {
447 28         58 chomp $$lr;
448 28         52 $i=index($$lr,']');
449 28         56 $nd=substr($$lr,1,$i-1);
450 28         67 $$lr=substr($$lr,$i+1);
451 28         136 @ats=split(',',$nd);
452 28         66 while (@ats) {
453 344         442 $w=shift @ats;
454 344         411 $i=index($w,'=');
455 344 100       452 if ($i>=0) {
456 163         199 $a=substr($w,0,$i);
457 163         201 $v=substr($w,$i+1);
458 163         197 $tmp=$ordhash->{$a};
459 163 50       233 $n = $tmp if (defined($tmp));
460             } else {
461 181         204 $v=$w;
462 181   33     616 $n++ while ( $n<$attr_count and $defs->{$attrs->[$n]}!~/ [PNW]/);
463 181 50       272 if ($n>$attr_count) {
464 0         0 croak "No more positional attribute $n for value $v at position in:\n".$n."\n";
465             }
466 181         249 $a=$attrs->[$n];
467             }
468 344 50       430 if ($CheckListValidity) {
469 0 0       0 if ($fsformat->isList($a)) {
470 0         0 @lv=$fsformat->listValues($a);
471 0         0 foreach $tmp (split /\|/,$v) {
472 0 0       0 print("Invalid list value $v of atribute $a no in @lv:\n$nd\n" ) unless (defined(Index(\@lv,$tmp)));
473             }
474             }
475             }
476 344         333 $n++;
477 344         390 $v=~s/,/,/g;
478 344         351 $v=~s/[/[/g;
479 344         354 $v=~s/]/]/g;
480 344         347 $v=~s/&eq;/=/g;
481 344         340 $v=~s/&backslash;/\\/g;
482 344         357 $v=~s/&/&/g;
483 344 50 33     752 if ($emu_schema_type and $a=~/\//) {
484 0         0 $node->set_attr($a,$v);
485             } else {
486             # speed optimized version
487             # $node->setAttribute($a,$v);
488 344         740 $node->{$a}=$v;
489             }
490             }
491 0         0 } else { croak $$lr," not node!\n"; }
492 28         92 return $node;
493             }
494              
495             sub ReadLine {
496 128     128 0 151 my ($handle)=@_;
497 128         130 local $_;
498 128 50       180 if (ref($handle) eq 'ARRAY') {
499 0         0 $_=shift @$handle;
500 128         287 } else { $_=<$handle>;
501 128         308 return $_; }
502 0         0 return $_;
503             }
504              
505             sub ReadEscapedLine {
506 128     128 0 172 my ($handle)=@_; # file handle or array reference
507 128         144 my $l="";
508 128         129 local $_;
509 128         163 while ($_=ReadLine($handle)) {
510 126 50       240 if (s/\\\r*\n?$//og) {
511 0         0 $l.=$_; next;
  0         0  
512             } # if backslashed eol, concatenate
513 126         194 $l.=$_;
514             # use Devel::Peek;
515             # Dump($l);
516 126         141 last; # else we have the whole tree
517             }
518 128         287 return $l;
519             }
520              
521              
522             =back
523              
524             =cut
525              
526             1;
527              
528              
529             __END__