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 8     8   67 use Carp;
  8         20  
  8         722  
11 8     8   51 use vars qw($CheckListValidity $emulatePML);
  8         17  
  8         456  
12 8     8   44 use strict;
  8         14  
  8         259  
13              
14 8     8   50 use vars qw($VERSION);
  8         17  
  8         378  
15             BEGIN {
16 8     8   238 $VERSION='2.28'; # version template
17             }
18 8     8   80 use Treex::PML::IO qw(open_backend close_backend);
  8         16  
  8         946  
19 8     8   57 use Treex::PML::Factory;
  8         18  
  8         266  
20              
21 8     8   53 use UNIVERSAL::DOES;
  8         18  
  8         36625  
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 22 my ($f,$encoding)=@_;
85 4 50       14 if (ref($f) eq 'ARRAY') {
    100          
86 0         0 return $f->[0]=~/^@/;
87             } elsif (ref($f)) {
88 2 50       10 binmode $f unless UNIVERSAL::DOES::does($f,'IO::Zlib');
89 2         186 my $test = ($f->getline()=~/^@/);
90 2         12 return $test;
91             } else {
92 2         8 my $fh = open_backend($f,"r");
93 2   33     25 my $test = $fh && test($fh);
94 2         12 close_backend($fh);
95 2         36 return $test;
96             }
97             }
98              
99              
100             sub _fs2members {
101 2     2   4 my ($fs)=@_;
102 2         4 my $mbr = {};
103 2         4 my $defs = $fs->defs;
104             # sort, so that possible short parts go first
105 2         10 foreach my $attr (sort $fs->attributes) {
106 100         100 my $m = $mbr;
107             # check that no short attr exists
108 100         107 my @parts = split /\//,$attr;
109 100         92 my $short=$parts[0];
110 100         122 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         100 for my $part (@parts) {
117 100         189 $m->{structure}{member}{$part}{-name} = $part;
118 100         112 $m=$m->{structure}{member}{$part};
119             }
120             # allow ``alt'' values concatenated with |
121 100 100       121 if ($fs->isList($attr)) {
122             $m->{alt} = {
123 2         6 -flat => 1,
124             choice => [ $fs->listValues($attr) ]
125             };
126             } else {
127             $m->{alt} = {
128 98         255 -flat => 1,
129             cdata => { format =>'any' }
130             };
131             }
132             }
133 2         14 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         13 my $FS = Treex::PML::Factory->createFSFormat();
140 2 50       9 $FS->readFrom($fileref) || return 0;
141 2         13 $fsfile->changeFS( $FS );
142              
143 2         2 my $emu_schema_type;
144 2 50       5 if ($emulatePML) {
145             # fake a PML Schema:
146 2         6 my $members = _fs2members($fsfile->FS);
147 2         13 $members->{'#childnodes'}={
148             role => '#CHILDNODES',
149             list => {
150             ordered => 1,
151             type => 'fs-node.type',
152             },
153             };
154 2         8 my $node_type = {
155             name => 'fs-node',
156             role => '#NODE',
157             member => $members,
158             };
159 2         121 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       7 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         4 my ($root,$l,@rest);
190 2         7 $fsfile->changeTrees();
191              
192             # this could give us some speedup.
193 2         3 my $ordhash;
194             {
195 2         3 my $i = 0;
  2         2  
196 2         7 $ordhash = { map { $_ => $i++ } $fsfile->FS->attributes };
  100         152  
197             }
198              
199 2         17 while ($l=ReadEscapedLine($fileref)) {
200 12 100       21 if ($l=~/^\[/) {
201 4         9 $root=ParseFSTree($fsfile->FS,$l,$ordhash,$emu_schema_type);
202 4 50       9 push @{$fsfile->treeList}, $root if $root;
  4         14  
203 8         11 } else { push @rest, $l; }
204             }
205 2         8 $fsfile->changeTail(@rest);
206              
207             #parse Rest
208 2         3 my @patterns;
209 2         6 foreach ($fsfile->tail) {
210 8 100       27 if (/^\/\/Tred:Custom-Attribute:(.*\S)\s*$/) {
    50          
    50          
211 4         18 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         8 $fsfile->changePatterns(@patterns);
222 2 50       5 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       5 map { /^\/\/Tred:Balloon-Pattern:(.*\S)\s*$/ ? $1 : () } $fsfile->tail);
  8         28  
229 2         14 return 1;
230             }
231              
232              
233             sub write {
234 1     1 0 2 my ($fileref,$fsfile) = @_;
235 1 50       3 return unless ref($fsfile);
236              
237             # print $fileref @{$fsfile->FS->unparsed};
238             {
239 1         1 my $encoding = $fsfile->encoding;
  1         4  
240 1 50       3 if (defined $encoding) {
241 0         0 print $fileref '@E '."$encoding\n";
242             }
243             }
244 1         3 $fsfile->FS->writeTo($fileref);
245 1 50       4 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         19 (grep { $_!~/\/\/Tred:(?:Custom-Attribute(?:Cont)?|Balloon-Pattern):/ } $fsfile->tail),
254 2         8 (map {"//Tred:Custom-Attribute:$_\n"}
255             map {
256 2         7 join "\n//Tred:Custom-AttributeCont:",
257             split /\n/,$_
258             } $fsfile->patterns),
259 1         9 (map {"//Tred:Balloon-Pattern:$_\n"}
  1         5  
260             split /\n/,$fsfile->hint),
261             );
262 1         3 print $fileref $fsfile->tail;
263 1 50       4 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         5 return 1;
271             }
272              
273             sub Print ($$) {
274             my (
275 461     461 0 526 $output, # filehandle or string
276             $text # text
277             )=@_;
278 461 50       515 if (ref($output) eq 'SCALAR') {
279 0         0 $$output.=$text;
280             } else {
281 461         534 print $output $text;
282             }
283             }
284              
285             sub PrintFSFile {
286 1     1 0 3 my ($fh,$fsformat,$trees,$emu_schema)=@_;
287 1         2 foreach my $tree (@$trees) {
288 2         4 PrintFSTree($tree,$fsformat,$fh,$emu_schema);
289             }
290             }
291              
292             sub PrintFSTree {
293 2     2 0 4 my ($root, # a reference to the root-node
294             $fsformat, # FSFormat object
295             $fh,
296             $emu_schema
297             )=@_;
298              
299 2 50       4 $fh=\*STDOUT unless $fh;
300 2         3 my $node=$root;
301 2         4 while ($node) {
302 14         23 PrintFSNode($node,$fsformat,$fh,$emu_schema);
303 14 100       26 if ($node->{$Treex::PML::Node::firstson}) {
304 8         17 Print($fh, "(");
305 8         9 $node = $node->{$Treex::PML::Node::firstson};
306 8         11 redo;
307             }
308 6   66     30 while ($node && $node != $root && !($node->{$Treex::PML::Node::rbrother})) {
      100        
309 8         18 Print($fh, ")");
310 8         35 $node = $node->{$Treex::PML::Node::parent};
311             }
312 6 50       11 croak "Error: NULL-node within the node while printing\n" if !$node;
313 6 100 66     30 last if ($node == $root || !$node);
314 4         9 Print($fh, ",");
315 4         7 $node = $node->{$Treex::PML::Node::rbrother};
316 4         6 redo;
317             }
318 2         4 Print($fh, "\n");
319             }
320              
321             sub PrintFSNode {
322 14     14 0 20 my ($node, # a reference to the root-node
323             $fsformat,
324             $output, # output stream
325             $emu_schema
326             )=@_;
327 14         13 my $v;
328 14         14 my $lastprinted=1;
329              
330 14         33 my $defs = $fsformat->defs;
331 14         25 my $attrs = $fsformat->list;
332 14         26 my $attr_count = $#$attrs+1;
333              
334 14 50       22 if ($node) {
335 14         21 Print($output, "[");
336 14         22 for (my $n=0; $n<$attr_count; $n++) {
337 700 50       1017 $v=$emu_schema ? $node->attr($attrs->[$n]) : $node->{$attrs->[$n]};
338 700 100       917 $v=~s/([,\[\]=\\\n])/\\$1/go if (defined($v));
339 700 100 66     1302 if (index($defs->{$attrs->[$n]}, " O")>=0) {
    100          
340 56 100       107 Print($output,",") if $n;
341 56 50 33     152 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     116 $v='-' if ($v eq '' or not defined($v));
344 56         82 Print($output,$v);
345 56         75 $lastprinted=1;
346             } elsif (defined($v) and length($v)) {
347 116 50       209 Print($output,",") if $n;
348 116 100 66     192 unless ($lastprinted && index($defs->{$attrs->[$n]}," P")>=0) # N could match here too probably
349 81         133 { Print($output,$attrs->[$n]."="); }
350 116         187 Print($output,$v);
351 116         151 $lastprinted=1;
352             } else {
353 528         660 $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 7 my ($fsformat,$l,$ordhash,$emu_schema_type)=@_;
371 4 50       8 return unless ref($fsformat);
372 4         8 my $root;
373             my $curr;
374 4         0 my $c;
375              
376 4 50       36 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       10 if ($l=~/^\[/o) {
382 4         5 $l=~s/&/&/g;
383 4         7 $l=~s/\\\\/&backslash;/g;
384 4         16 $l=~s/\\,/,/g;
385 4         7 $l=~s/\\\[/[/g;
386 4         4 $l=~s/\\]/]/g;
387 4         10 $l=~s/\\=/&eq;/g;
388 4         5 $l=~s/\\//g;
389 4         5 $l=~s/\r//g;
390 4         12 $curr=$root=ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type); # create Root
391              
392 4         9 while ($l) {
393 40         38 $c = substr($l,0,1);
394 40         49 $l = substr($l,1);
395 40 100       59 if ( $c eq '(' ) { # Create son (go down)
396 16         21 my $first_son = $curr->{$Treex::PML::Node::firstson} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
397 16         23 $first_son->{$Treex::PML::Node::parent}=$curr;
398 16         15 $curr=$first_son;
399 16         27 next;
400             }
401 24 100       29 if ( $c eq ')' ) { # Return to parent (go up)
402 16 50       35 croak "Error paring tree" if ($curr eq $root);
403 16         18 $curr=$curr->{$Treex::PML::Node::parent};
404 16         22 next;
405             }
406 8 50       14 if ( $c eq ',' ) { # Create right brother (go right);
407 8         15 my $rb = $curr->{$Treex::PML::Node::rbrother} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
408 8         21 $rb->set_lbrother( $curr );
409 8         20 $rb->set_parent( $curr->{$Treex::PML::Node::parent} );
410 8         9 $curr=$rb;
411 8         11 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         7 return $root;
418             }
419              
420              
421             sub ParseFSNode {
422 28     28 0 39 my ($fsformat,$lr,$ordhash,$emu_schema_type) = @_;
423 28         28 my $n = 0;
424 28         29 my $node;
425 28         29 my @ats=();
426 28         24 my $pos = 1;
427 28         27 my $a=0;
428 28         22 my $v=0;
429 28         64 my $tmp;
430             my @lv;
431 28         0 my $nd;
432 28         0 my $i;
433 28         0 my $w;
434              
435 28         48 my $defs = $fsformat->defs;
436 28         43 my $attrs = $fsformat->list;
437 28         31 my $attr_count = $#$attrs+1;
438 28 50       44 unless ($ordhash) {
439 0         0 my $i = 0;
440 0         0 $ordhash = { map { $_ => $i++ } @$attrs };
  0         0  
441             }
442              
443 28 50       67 $node = $emu_schema_type
444             ? Treex::PML::Factory->createTypedNode($emu_schema_type)
445             : Treex::PML::Factory->createNode();
446 28 50       72 if ($$lr=~/^\[/) {
447 28         35 chomp $$lr;
448 28         32 $i=index($$lr,']');
449 28         42 $nd=substr($$lr,1,$i-1);
450 28         62 $$lr=substr($$lr,$i+1);
451 28         103 @ats=split(',',$nd);
452 28         36 while (@ats) {
453 344         347 $w=shift @ats;
454 344         317 $i=index($w,'=');
455 344 100       348 if ($i>=0) {
456 163         155 $a=substr($w,0,$i);
457 163         179 $v=substr($w,$i+1);
458 163         161 $tmp=$ordhash->{$a};
459 163 50       202 $n = $tmp if (defined($tmp));
460             } else {
461 181         163 $v=$w;
462 181   33     447 $n++ while ( $n<$attr_count and $defs->{$attrs->[$n]}!~/ [PNW]/);
463 181 50       218 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         172 $a=$attrs->[$n];
467             }
468 344 50       394 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         312 $n++;
477 344         317 $v=~s/,/,/g;
478 344         322 $v=~s/[/[/g;
479 344         301 $v=~s/]/]/g;
480 344         339 $v=~s/&eq;/=/g;
481 344         285 $v=~s/&backslash;/\\/g;
482 344         311 $v=~s/&/&/g;
483 344 50 33     623 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         585 $node->{$a}=$v;
489             }
490             }
491 0         0 } else { croak $$lr," not node!\n"; }
492 28         89 return $node;
493             }
494              
495             sub ReadLine {
496 128     128 0 126 my ($handle)=@_;
497 128         113 local $_;
498 128 50       158 if (ref($handle) eq 'ARRAY') {
499 0         0 $_=shift @$handle;
500 128         254 } else { $_=<$handle>;
501 128         268 return $_; }
502 0         0 return $_;
503             }
504              
505             sub ReadEscapedLine {
506 128     128 0 132 my ($handle)=@_; # file handle or array reference
507 128         143 my $l="";
508 128         127 local $_;
509 128         146 while ($_=ReadLine($handle)) {
510 126 50       206 if (s/\\\r*\n?$//og) {
511 0         0 $l.=$_; next;
  0         0  
512             } # if backslashed eol, concatenate
513 126         163 $l.=$_;
514             # use Devel::Peek;
515             # Dump($l);
516 126         118 last; # else we have the whole tree
517             }
518 128         220 return $l;
519             }
520              
521              
522             =back
523              
524             =cut
525              
526             1;
527              
528              
529             __END__