File Coverage

blib/lib/Treex/PML/Backend/FS.pm
Criterion Covered Total %
statement 22 284 7.7
branch 0 112 0.0
condition 0 41 0.0
subroutine 8 21 38.1
pod 1 12 8.3
total 31 470 6.6


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 1     1   1166 use Carp;
  1         1  
  1         47  
11 1     1   4 use vars qw($CheckListValidity $emulatePML);
  1         0  
  1         33  
12 1     1   3 use strict;
  1         1  
  1         14  
13              
14 1     1   2 use vars qw($VERSION);
  1         1  
  1         26  
15             BEGIN {
16 1     1   12 $VERSION='2.21'; # version template
17             }
18 1     1   472 use Treex::PML::IO qw(open_backend close_backend);
  1         2  
  1         74  
19 1     1   5 use Treex::PML::Factory;
  1         2  
  1         16  
20              
21 1     1   2 use UNIVERSAL::DOES;
  1         2  
  1         2151  
22              
23             sub DOES {
24 0     0 0   my ($self,$role)=@_;
25 0 0 0       if ($role eq 'FSBackend' or $role eq __PACKAGE__) {
26 0           return 1;
27             } else {
28 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 0     0 0   my ($f,$encoding)=@_;
85 0 0         if (ref($f) eq 'ARRAY') {
    0          
86 0           return $f->[0]=~/^@/;
87             } elsif (ref($f)) {
88 0 0         binmode $f unless UNIVERSAL::DOES::does($f,'IO::Zlib');
89 0           my $test = ($f->getline()=~/^@/);
90 0           return $test;
91             } else {
92 0           my $fh = open_backend($f,"r");
93 0   0       my $test = $fh && test($fh);
94 0           close_backend($fh);
95 0           return $test;
96             }
97             }
98              
99              
100             sub _fs2members {
101 0     0     my ($fs)=@_;
102 0           my $mbr = {};
103 0           my $defs = $fs->defs;
104             # sort, so that possible short parts go first
105 0           foreach my $attr (sort $fs->attributes) {
106 0           my $m = $mbr;
107             # check that no short attr exists
108 0           my @parts = split /\//,$attr;
109 0           my $short=$parts[0];
110 0           for (my $i=1;$i<@parts;$i++) {
111 0 0         if ($defs->{$short}) {
112 0           warn "Can't emulate PML schema: attribute name conflict between $short and $attr: falling back to non-emulation mode\n";
113             }
114 0           $short .= '/'.$parts[$i];
115             }
116 0           for my $part (@parts) {
117 0           $m->{structure}{member}{$part}{-name} = $part;
118 0           $m=$m->{structure}{member}{$part};
119             }
120             # allow ``alt'' values concatenated with |
121 0 0         if ($fs->isList($attr)) {
122             $m->{alt} = {
123 0           -flat => 1,
124             choice => [ $fs->listValues($attr) ]
125             };
126             } else {
127             $m->{alt} = {
128 0           -flat => 1,
129             cdata => { format =>'any' }
130             };
131             }
132             }
133 0           return $mbr->{structure}{member};
134             }
135              
136             sub read {
137 0     0 0   my ($fileref,$fsfile) = @_;
138 0 0         return unless ref($fsfile);
139 0           my $FS = Treex::PML::Factory->createFSFormat();
140 0 0         $FS->readFrom($fileref) || return 0;
141 0           $fsfile->changeFS( $FS );
142              
143 0           my $emu_schema_type;
144 0 0         if ($emulatePML) {
145             # fake a PML Schema:
146 0           my $members = _fs2members($fsfile->FS);
147 0           $members->{'#childnodes'}={
148             role => '#CHILDNODES',
149             list => {
150             ordered => 1,
151             type => 'fs-node.type',
152             },
153             };
154 0           my $node_type = {
155             name => 'fs-node',
156             role => '#NODE',
157             member => $members,
158             };
159 0           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 0 0         if (defined($node_type->{member})) {
184 0           $emu_schema_type = $node_type;
185 0           $fsfile->changeMetaData('schema',$schema);
186             }
187             }
188              
189 0           my ($root,$l,@rest);
190 0           $fsfile->changeTrees();
191              
192             # this could give us some speedup.
193 0           my $ordhash;
194             {
195 0           my $i = 0;
  0            
196 0           $ordhash = { map { $_ => $i++ } $fsfile->FS->attributes };
  0            
197             }
198              
199 0           while ($l=ReadEscapedLine($fileref)) {
200 0 0         if ($l=~/^\[/) {
201 0           $root=ParseFSTree($fsfile->FS,$l,$ordhash,$emu_schema_type);
202 0 0         push @{$fsfile->treeList}, $root if $root;
  0            
203 0           } else { push @rest, $l; }
204             }
205 0           $fsfile->changeTail(@rest);
206              
207             #parse Rest
208 0           my @patterns;
209 0           foreach ($fsfile->tail) {
210 0 0         if (/^\/\/Tred:Custom-Attribute:(.*\S)\s*$/) {
    0          
    0          
211 0           push @patterns,$1;
212             } elsif (/^\/\/Tred:Custom-AttributeCont:(.*\S)\s*$/) {
213 0           $patterns[$#patterns].="\n".$1;
214             } elsif (/^\/\/FS-REQUIRE:\s*(\S+)\s+(\S+)=\"([^\"]+)\"\s*$/) {
215 0   0       my $requires = $fsfile->metaData('fs-require') || $fsfile->changeMetaData('fs-require',[]);
216 0           push @$requires,[$2,$3];
217 0   0       my $refnames = $fsfile->metaData('refnames') || $fsfile->changeMetaData('refnames',{});
218 0           $refnames->{$1} = $2;
219             }
220             }
221 0           $fsfile->changePatterns(@patterns);
222 0 0         unless (@patterns) {
223 0           my ($peep)=$fsfile->tail;
224 0           $fsfile->changePatterns( map { "\$\{".$fsfile->FS->atno($_)."\}" }
  0            
225             ($peep=~/[,\(]([0-9]+)/g));
226             }
227             $fsfile->changeHint(join "\n",
228 0 0         map { /^\/\/Tred:Balloon-Pattern:(.*\S)\s*$/ ? $1 : () } $fsfile->tail);
  0            
229 0           return 1;
230             }
231              
232              
233             sub write {
234 0     0 0   my ($fileref,$fsfile) = @_;
235 0 0         return unless ref($fsfile);
236              
237             # print $fileref @{$fsfile->FS->unparsed};
238             {
239 0           my $encoding = $fsfile->encoding;
  0            
240 0 0         if (defined $encoding) {
241 0           print $fileref '@E '."$encoding\n";
242             }
243             }
244 0           $fsfile->FS->writeTo($fileref);
245 0 0         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 0           (grep { $_!~/\/\/Tred:(?:Custom-Attribute(?:Cont)?|Balloon-Pattern):/ } $fsfile->tail),
254 0           (map {"//Tred:Custom-Attribute:$_\n"}
255             map {
256 0           join "\n//Tred:Custom-AttributeCont:",
257             split /\n/,$_
258             } $fsfile->patterns),
259 0           (map {"//Tred:Balloon-Pattern:$_\n"}
  0            
260             split /\n/,$fsfile->hint),
261             );
262 0           print $fileref $fsfile->tail;
263 0 0         if (ref($fsfile->metaData('fs-require'))) {
264 0   0       my $refnames = $fsfile->metaData('refnames') || {};
265 0           foreach my $req ( @{ $fsfile->metaData('fs-require') } ) {
  0            
266 0           my ($name) = grep { $refnames->{$_} eq $req->[0] } keys(%$refnames);
  0            
267 0           print $fileref "//FS-REQUIRE:$name $req->[0]=\"$req->[1]\"\n";
268             }
269             }
270 0           return 1;
271             }
272              
273             sub Print ($$) {
274             my (
275 0     0 0   $output, # filehandle or string
276             $text # text
277             )=@_;
278 0 0         if (ref($output) eq 'SCALAR') {
279 0           $$output.=$text;
280             } else {
281 0           print $output $text;
282             }
283             }
284              
285             sub PrintFSFile {
286 0     0 0   my ($fh,$fsformat,$trees,$emu_schema)=@_;
287 0           foreach my $tree (@$trees) {
288 0           PrintFSTree($tree,$fsformat,$fh,$emu_schema);
289             }
290             }
291              
292             sub PrintFSTree {
293 0     0 0   my ($root, # a reference to the root-node
294             $fsformat, # FSFormat object
295             $fh,
296             $emu_schema
297             )=@_;
298              
299 0 0         $fh=\*STDOUT unless $fh;
300 0           my $node=$root;
301 0           while ($node) {
302 0           PrintFSNode($node,$fsformat,$fh,$emu_schema);
303 0 0         if ($node->{$Treex::PML::Node::firstson}) {
304 0           Print($fh, "(");
305 0           $node = $node->{$Treex::PML::Node::firstson};
306 0           redo;
307             }
308 0   0       while ($node && $node != $root && !($node->{$Treex::PML::Node::rbrother})) {
      0        
309 0           Print($fh, ")");
310 0           $node = $node->{$Treex::PML::Node::parent};
311             }
312 0 0         croak "Error: NULL-node within the node while printing\n" if !$node;
313 0 0 0       last if ($node == $root || !$node);
314 0           Print($fh, ",");
315 0           $node = $node->{$Treex::PML::Node::rbrother};
316 0           redo;
317             }
318 0           Print($fh, "\n");
319             }
320              
321             sub PrintFSNode {
322 0     0 0   my ($node, # a reference to the root-node
323             $fsformat,
324             $output, # output stream
325             $emu_schema
326             )=@_;
327 0           my $v;
328 0           my $lastprinted=1;
329              
330 0           my $defs = $fsformat->defs;
331 0           my $attrs = $fsformat->list;
332 0           my $attr_count = $#$attrs+1;
333              
334 0 0         if ($node) {
335 0           Print($output, "[");
336 0           for (my $n=0; $n<$attr_count; $n++) {
337 0 0         $v=$emu_schema ? $node->attr($attrs->[$n]) : $node->{$attrs->[$n]};
338 0 0         $v=~s/([,\[\]=\\\n])/\\$1/go if (defined($v));
339 0 0 0       if (index($defs->{$attrs->[$n]}, " O")>=0) {
    0          
340 0 0         Print($output,",") if $n;
341 0 0 0       unless ($lastprinted && index($defs->{$attrs->[$n]}," P")>=0) # N could match here too probably
342 0           { Print($output, $attrs->[$n]."="); }
343 0 0 0       $v='-' if ($v eq '' or not defined($v));
344 0           Print($output,$v);
345 0           $lastprinted=1;
346             } elsif (defined($v) and length($v)) {
347 0 0         Print($output,",") if $n;
348 0 0 0       unless ($lastprinted && index($defs->{$attrs->[$n]}," P")>=0) # N could match here too probably
349 0           { Print($output,$attrs->[$n]."="); }
350 0           Print($output,$v);
351 0           $lastprinted=1;
352             } else {
353 0           $lastprinted=0;
354             }
355             }
356 0           Print($output,"]");
357             } else {
358 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 0     0 1   my ($fsformat,$l,$ordhash,$emu_schema_type)=@_;
371 0 0         return unless ref($fsformat);
372 0           my $root;
373             my $curr;
374 0           my $c;
375              
376 0 0         unless ($ordhash) {
377 0           my $i = 0;
378 0           $ordhash = { map { $_ => $i++ } @{$fsformat->list} };
  0            
  0            
379             }
380              
381 0 0         if ($l=~/^\[/o) {
382 0           $l=~s/&/&/g;
383 0           $l=~s/\\\\/&backslash;/g;
384 0           $l=~s/\\,/,/g;
385 0           $l=~s/\\\[/[/g;
386 0           $l=~s/\\]/]/g;
387 0           $l=~s/\\=/&eq;/g;
388 0           $l=~s/\\//g;
389 0           $l=~s/\r//g;
390 0           $curr=$root=ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type); # create Root
391              
392 0           while ($l) {
393 0           $c = substr($l,0,1);
394 0           $l = substr($l,1);
395 0 0         if ( $c eq '(' ) { # Create son (go down)
396 0           my $first_son = $curr->{$Treex::PML::Node::firstson} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
397 0           $first_son->{$Treex::PML::Node::parent}=$curr;
398 0           $curr=$first_son;
399 0           next;
400             }
401 0 0         if ( $c eq ')' ) { # Return to parent (go up)
402 0 0         croak "Error paring tree" if ($curr eq $root);
403 0           $curr=$curr->{$Treex::PML::Node::parent};
404 0           next;
405             }
406 0 0         if ( $c eq ',' ) { # Create right brother (go right);
407 0           my $rb = $curr->{$Treex::PML::Node::rbrother} = ParseFSNode($fsformat,\$l,$ordhash,$emu_schema_type);
408 0           $rb->set_lbrother( $curr );
409 0           $rb->set_parent( $curr->{$Treex::PML::Node::parent} );
410 0           $curr=$rb;
411 0           next;
412             }
413 0           croak "Unexpected token... `$c'!\n$l\n";
414             }
415 0 0         croak "Error: Closing brackets do not lead to root of the tree.\n" if ($curr != $root);
416             }
417 0           return $root;
418             }
419              
420              
421             sub ParseFSNode {
422 0     0 0   my ($fsformat,$lr,$ordhash,$emu_schema_type) = @_;
423 0           my $n = 0;
424 0           my $node;
425 0           my @ats=();
426 0           my $pos = 1;
427 0           my $a=0;
428 0           my $v=0;
429 0           my $tmp;
430             my @lv;
431 0           my $nd;
432 0           my $i;
433 0           my $w;
434              
435 0           my $defs = $fsformat->defs;
436 0           my $attrs = $fsformat->list;
437 0           my $attr_count = $#$attrs+1;
438 0 0         unless ($ordhash) {
439 0           my $i = 0;
440 0           $ordhash = { map { $_ => $i++ } @$attrs };
  0            
441             }
442              
443 0 0         $node = $emu_schema_type
444             ? Treex::PML::Factory->createTypedNode($emu_schema_type)
445             : Treex::PML::Factory->createNode();
446 0 0         if ($$lr=~/^\[/) {
447 0           chomp $$lr;
448 0           $i=index($$lr,']');
449 0           $nd=substr($$lr,1,$i-1);
450 0           $$lr=substr($$lr,$i+1);
451 0           @ats=split(',',$nd);
452 0           while (@ats) {
453 0           $w=shift @ats;
454 0           $i=index($w,'=');
455 0 0         if ($i>=0) {
456 0           $a=substr($w,0,$i);
457 0           $v=substr($w,$i+1);
458 0           $tmp=$ordhash->{$a};
459 0 0         $n = $tmp if (defined($tmp));
460             } else {
461 0           $v=$w;
462 0   0       $n++ while ( $n<$attr_count and $defs->{$attrs->[$n]}!~/ [PNW]/);
463 0 0         if ($n>$attr_count) {
464 0           croak "No more positional attribute $n for value $v at position in:\n".$n."\n";
465             }
466 0           $a=$attrs->[$n];
467             }
468 0 0         if ($CheckListValidity) {
469 0 0         if ($fsformat->isList($a)) {
470 0           @lv=$fsformat->listValues($a);
471 0           foreach $tmp (split /\|/,$v) {
472 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 0           $n++;
477 0           $v=~s/,/,/g;
478 0           $v=~s/[/[/g;
479 0           $v=~s/]/]/g;
480 0           $v=~s/&eq;/=/g;
481 0           $v=~s/&backslash;/\\/g;
482 0           $v=~s/&/&/g;
483 0 0 0       if ($emu_schema_type and $a=~/\//) {
484 0           $node->set_attr($a,$v);
485             } else {
486             # speed optimized version
487             # $node->setAttribute($a,$v);
488 0           $node->{$a}=$v;
489             }
490             }
491 0           } else { croak $$lr," not node!\n"; }
492 0           return $node;
493             }
494              
495             sub ReadLine {
496 0     0 0   my ($handle)=@_;
497 0           local $_;
498 0 0         if (ref($handle) eq 'ARRAY') {
499 0           $_=shift @$handle;
500 0           } else { $_=<$handle>;
501 0           return $_; }
502 0           return $_;
503             }
504              
505             sub ReadEscapedLine {
506 0     0 0   my ($handle)=@_; # file handle or array reference
507 0           my $l="";
508 0           local $_;
509 0           while ($_=ReadLine($handle)) {
510 0 0         if (s/\\\r*\n?$//og) {
511 0           $l.=$_; next;
  0            
512             } # if backslashed eol, concatenate
513 0           $l.=$_;
514             # use Devel::Peek;
515             # Dump($l);
516 0           last; # else we have the whole tree
517             }
518 0           return $l;
519             }
520              
521              
522             =back
523              
524             =cut
525              
526             1;
527              
528              
529             __END__