File Coverage

blib/lib/Treex/PML/Instance.pm
Criterion Covered Total %
statement 211 562 37.5
branch 49 342 14.3
condition 22 191 11.5
subroutine 35 72 48.6
pod 39 43 90.7
total 356 1210 29.4


line stmt bran cond sub pod time code
1             package Treex::PML::Instance;
2              
3 6     6   104 use 5.008;
  6         21  
4 6     6   30 use strict;
  6         12  
  6         126  
5 6     6   27 use warnings;
  6         10  
  6         132  
6 6     6   28 use Carp;
  6         13  
  6         331  
7 6     6   38 use Cwd;
  6         13  
  6         409  
8              
9             BEGIN {
10              
11 6     6   41 require Exporter;
12 6         180 import Exporter qw(import);
13              
14             }
15              
16 6     6   30 use Scalar::Util qw(weaken blessed);
  6         24  
  6         334  
17 6     6   50 use UNIVERSAL::DOES;
  6         15  
  6         259  
18 6     6   2173 use Treex::PML::Instance::Common qw(:all);
  6         15  
  6         1103  
19 6     6   42 use Treex::PML::Schema;
  6         13  
  6         470  
20 6     6   34 use Encode;
  6         9  
  6         507  
21 6     6   37 use File::Spec;
  6         12  
  6         153  
22 6     6   33 use URI;
  6         9  
  6         121  
23 6     6   34 use URI::file;
  6         14  
  6         222  
24             our $DEFAULT_ENCODING = 'utf-8';
25              
26       6     BEGIN {
27              
28             =begin comment
29              
30             TODO
31              
32             Note: correct writing with XSLT requires XML::LibXML >= 1.59 (!!!)
33              
34             (GENERAL):
35              
36             - improve reading/writing trees (use the live object)
37             Postponing because:
38             1/ sequences of tree/no-tree objects are problematic
39             2/ changing this would break binary compatibility
40              
41             - Treex::PML:
42             find_role_in_data,
43             traverse_data($node, $decl, sub($data,$decl,$decl_resolved))
44              
45             - readas DOM => readas PML, where #KNITting means pointing to the same data (if possible).
46             test implementation: breaks old Undo/Redo, but ok with the new "object-preserving" one
47              
48             (XSLT):
49              
50             - support for external xslt processors (maybe a common wrapper)
51             - with LibXSLT, cache the parsed stylesheets
52              
53             DONE:
54              
55             - hash by #ID into appData('id-hash')/{'id-hash'} (knitted instances could be hashed with prefix#,
56             knitted-knitted instances with prefix1#prefix2#...)
57             (this is temporary)
58              
59             =end comment
60              
61             =cut
62              
63             }
64              
65             our %EXPORT_TAGS = (
66             'functions' => [ qw( get_data set_data count_matches for_each_match get_all_matches ) ],
67             'constants' => $Treex::PML::Instance::Common::EXPORT_TAGS{constants},
68             'diagnostics' => $Treex::PML::Instance::Common::EXPORT_TAGS{diagnostics},
69             );
70             $EXPORT_TAGS{'all'} = [ @{ $EXPORT_TAGS{'constants'} },
71             @{ $EXPORT_TAGS{'diagnostics'} },
72             @{ $EXPORT_TAGS{'functions'} },
73             qw( $DEBUG )
74             ];
75              
76             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
77             our @EXPORT = qw( );
78             our $VERSION = '2.24'; # version template
79              
80 0         0 BEGIN {
81 6     6   1477 require Treex::PML::IO;
82 6         137 require Treex::PML;
83              
84              
85             # FIELDS:
86 6         26 use fields qw(
87             _schema
88             _schema-url
89             _schema-inline
90             _types
91             _dom
92             _root
93             _parser
94             _writer
95             _filename
96             _transform_id
97             _status
98             _readas-trees
99             _references
100             _refnames
101             _ref
102             _ref-index
103             _pml_trees_type
104             _no_read_trees
105             _no_references
106             _no_knit
107             _selected_references
108             _selected_references_ids
109             _selected_knits
110             _selected_knits_ids
111             _trees
112             _pml_prolog
113             _pml_epilog
114             _id-hash
115             _log
116             _id_prefix
117             _trees_written
118             _refs_save
119             _save_flags
120             _pi
121 6     6   2462 );
  6         8483  
122              
123             } # BEGIN
124              
125 6     6   3284 use Treex::PML::Instance::Reader;
  6         19  
  6         237  
126 6     6   3325 use Treex::PML::Instance::Writer;
  6         18  
  6         36834  
127              
128             # PML Instance File
129             sub get_filename {
130 0     0 1 0 my $filename= $_[0]->{'_filename'};
131 0 0 0     0 if (blessed($filename) and $filename->isa('URI')
      0        
132             and $filename->scheme eq 'file') {
133 0         0 return $filename->file;
134             }
135 0         0 return $filename;
136             }
137             sub get_url {
138 0     0 1 0 my $filename= $_[0]->{'_filename'};
139 0 0 0     0 if ($filename and not (blessed($filename) and $filename->isa('URI'))) {
      0        
140 0         0 return Treex::PML::IO::make_URI($filename);
141             }
142 0         0 return $filename;
143             }
144              
145             sub set_filename {
146 70     70 1 330 $_[0]->{'_filename'} = Treex::PML::IO::make_abs_URI($_[1]); # 1K faster than cwd
147             }
148 0     0 1 0 sub get_transform_id { $_[0]->{'_transform_id'}; }
149 0     0 1 0 sub set_transform_id { $_[0]->{'_transform_id'} = $_[1]; }
150              
151             # Schema
152 0     0 0 0 sub schema { $_[0]->{'_schema'} }
153             *get_schema = \&schema;
154 0     0 1 0 sub set_schema { $_[0]->{'_schema'} = $_[1] }
155 0     0 1 0 sub get_schema_url { $_[0]->{'_schema-url'} }
156 0     0 1 0 sub set_schema_url { $_[0]->{'_schema-url'} = $_[1]; }
157              
158             # Data
159 104     104 1 520 sub get_root { $_[0]->{'_root'}; }
160 0     0 1 0 sub set_root { $_[0]->{'_root'} = $_[1]; }
161 0     0 1 0 sub get_trees { $_[0]->{'_trees'}; }
162             #sub set_trees { $_[0]->{'_trees'} = $_[1]; }
163 0     0 1 0 sub get_trees_prolog { $_[0]->{'_pml_prolog'}; }
164             #sub set_trees_prolog { $_[0]->{'_pml_prolog'} = $_[1]; }
165 0     0 1 0 sub get_trees_epilog { $_[0]->{'_pml_epilog'}; }
166             #sub set_trees_epilog { $_[0]->{'_pml_epilog'} = $_[1]; }
167 0     0 1 0 sub get_trees_type { $_[0]->{'_pml_trees_type'}; }
168             #sub set_trees_type { $_[0]->{'_pml_trees_type'} = $_[1]; }
169              
170             # References
171             sub get_references_hash {
172 18   50 18 1 6190 return ($_[0]->{'_references'}||={});
173             }
174 0     0 1 0 sub set_references_hash { $_[0]->{'_references'} = $_[1]; }
175             sub get_ref_ids_by_name {
176 0     0 1 0 my ($self,$name)=@_;
177 0         0 my $refs = $self->get_refname_hash->{$name};
178 0 0       0 return ref($refs) ? @$refs : ($refs);
179             }
180             sub get_refs_by_name {
181 0     0 1 0 my ($self,$name)=@_;
182 0         0 return map {$self->get_ref($_)} $self->get_ref_ids_by_name;
  0         0  
183             }
184             sub get_refname_hash {
185 0   0 0 1 0 return ($_[0]->{'_refnames'}||={});
186             }
187 0     0 1 0 sub set_refname_hash { $_[0]->{'_refnames'} = $_[1]; }
188             sub get_ref {
189 0     0 1 0 my ($self,$id)=@_;
190 0         0 my $refs = $self->{'_ref'};
191 0 0       0 return $refs ? $refs->{$id} : undef;
192             }
193             sub set_ref {
194 0     0 1 0 my ($self,$id,$obj)=@_;
195 0         0 my $refs = $self->{'_ref'};
196 0 0       0 $self->{'_ref'} = $refs = {} unless ($refs);
197 0         0 return $refs->{$id}=$obj;
198             }
199              
200             # Status=1 (if parsed fine)
201 22     22 1 67 sub get_status { $_[0]->{'_status'}; }
202             #sub set_status { $_[0]->{'_status'} = $_[1]; }
203              
204             sub get_reffiles {
205 102     102 1 232 my ($ctxt)=@_;
206 102         512 my $references = [$ctxt->{'_schema'}->get_named_references];
207 102         197 my @refs;
208 102 50       263 if ($references) {
209 102         288 foreach my $reference (@$references) {
210 50         215 my $refids = $ctxt->{'_refnames'}->{$reference->{name}};
211 50 50       145 if ($refids) {
212 50 50       167 foreach my $refid (ref($refids) ? @$refids : ($refids)) {
213 50         149 my $href = $ctxt->{'_references'}->{$refid};
214 50 50       188 if ($href) {
215 50         614 _debug("Found '$reference->{name}' as $refid# = '$href'");
216             push @refs,{
217             readas => $reference->{readas},
218             name => $reference->{name},
219 50         420 id => $refid,
220             href => $href
221             };
222             } else {
223 0         0 _die("No href for $refid# ($reference->{name})")
224             }
225             }
226             } else {
227 0         0 _warn("Didn't find any reference to '".$reference->{name}."'\n");
228             }
229             }
230             }
231 102         405 return @refs;
232             }
233              
234             sub read_reffiles {
235 61     61 0 181 my ($ctxt,$opts) = @_;
236 61         240 foreach my $ref ($ctxt->get_reffiles()) {
237 25         64 my $id = $ref->{id};
238 25         125 my $selected = $ctxt->{'_selected_references_ids'}{$id};
239 25 50       124 next if (defined($selected) ? $selected==0 : $ctxt->{'_no_references'});
    50          
240 25         64 my $readas = $ref->{readas};
241 25 50       77 if (defined $readas) {
242 25 100       112 if ($readas eq 'dom') {
    100          
    50          
    0          
243 17         88 $ctxt->readas_dom($id,$ref->{href},$opts);
244             } elsif($readas eq 'trees') {
245             # when translating to Treex::PML::Document,
246             # push to fs-require [$id,$ref->{href}];
247             } elsif($readas eq 'pml') {
248 6         36 $ctxt->readas_pml($id,$ref->{href},$opts);
249             } elsif (length($readas)) {
250 0         0 _warn("Ignoring references with unknown readas method: '$readas' for reffile id='$id', href='$ref->{href}'\n");
251             }
252             }
253             }
254             }
255              
256             sub readas_pml {
257 6     6 0 72 my ($ctxt,$refid,$href,$opts)=@_;
258             # embed PML documents
259 6         15 my $ref_data;
260 6         37 _debug("readas_pml: $refid => $href");
261             my $pml = Treex::PML::Instance->load({
262             filename => $href,
263             no_knit => $ctxt->{_no_knit},
264             selected_knits => $ctxt->{_selected_knits},
265             no_references => $ctxt->{_no_references},
266             selected_references => $ctxt->{_selected_references},
267 6 50       149 ($opts ? %$opts : ()),
268             });
269 6   50     470 $ctxt->{'_ref'} ||= {};
270 6         32 $ctxt->{'_ref'}->{$refid}=$pml;
271 6   50     49 $ctxt->{'_ref-index'} ||= {};
272 6         42 weaken( $ctxt->{'_ref-index'}->{$refid} = $pml->{'_id-hash'} );
273 6         37 1;
274             }
275              
276             # $ctxt, $refid, $href
277             sub readas_dom {
278 26     26 0 111 my ($ctxt,$refid,$href,$opts)=@_;
279             # embed DOM documents
280 26         44 my $ref_data;
281             # if ($opts and $opts->{use_resources}) {
282             # $href = Treex::PML::FindInResourcePaths($href);
283             # }
284              
285 26         108 my ($local_file,$remove_file) = Treex::PML::IO::fetch_file($href);
286 26         111 my $ref_fh = Treex::PML::IO::open_uri($local_file);
287 26 50       103 _die("Cannot open $href for reading") unless $ref_fh;
288 26         210 _debug("readas_dom: $refid => $href");
289 26   66     170 my $parser = $ctxt->{'_parser'} || $ctxt->_xml_parser();
290 26 50       84 if ($ref_fh){
291 26         58 eval {
292 26         133 $ref_data = $parser->parse_fh($ref_fh, $href);
293             };
294 26 50       34911 _die("Error parsing $href $ref_fh $local_file ($@)") if $@;
295 26 50 33     229 $ref_data->setBaseURI($href) if $ref_data and $ref_data->can('setBaseURI');;
296 26         714 $parser->process_xincludes($ref_data);
297 26         4142 Treex::PML::IO::close_uri($ref_fh);
298 26   100     161 $ctxt->{'_ref'} ||= {};
299 26         2756 $ctxt->{'_ref'}->{$refid}=$ref_data;
300 26   100     160 $ctxt->{'_ref-index'} ||= {};
301 26         110 $ctxt->{'_ref-index'}->{$refid}=_index_by_id($ref_data);
302 26 100 100     364 if ($href ne $local_file and $remove_file) {
303 10         141 local $!;
304 10   33     933 unlink $local_file || _warn("couldn't unlink tmp file $local_file: $!\n");
305             }
306             } else {
307 0 0 0     0 if ($href ne $local_file and $remove_file) {
308 0         0 local $!;
309 0   0     0 unlink $local_file || _warn("couldn't unlink tmp file $local_file: $!\n");
310             }
311 0         0 _die("Couldn't open '".$href."': $!");
312             }
313 26         381 1;
314             }
315              
316             sub _xml_parser {
317 36     36   138 my ($self,$opts) = @_;
318 36         288 my $parser = XML::LibXML->new();
319 36         773 $parser->keep_blanks(0);
320 36         951 $parser->line_numbers(1);
321 36         389 $parser->load_ext_dtd(0);
322 36         559 $parser->validation(0);
323 36 50 33     485 if (ref($opts) and $parser->can('set_options')) {
324 0         0 $parser->set_options($opts);
325             }
326 36         136 return $parser;
327             }
328              
329             ###################################
330             # CONSTRUCTOR
331             ####################################
332              
333             sub new {
334 71     71 1 156 my $class = shift;
335 71 50       211 _die('Usage: ' . __PACKAGE__ . '->new()') if ref($class);
336 71         373 return fields::new($class);
337             }
338              
339              
340              
341             ###################################
342             # LOAD
343             ###################################
344              
345             sub load {
346 61     61 1 9212 return &Treex::PML::Instance::Reader::load;
347             }
348              
349             sub save {
350 19     19 1 5396 return &Treex::PML::Instance::Writer::save;
351             }
352              
353             sub lookup_id {
354 0     0 1 0 my ($ctxt,$id)=@_;
355 0   0     0 my $hash = $ctxt->{'_id-hash'} ||= {};
356 0         0 return $hash->{ $id };
357             }
358              
359             sub hash_id {
360 0     0 1 0 my ($ctxt,$id,$object,$check_uniq) = @_;
361 0 0 0     0 return unless defined($id) and length($id);
362 0   0     0 my $prefix = $ctxt->{'_id_prefix'} || '';
363 0         0 $id = $prefix . $id;
364 0   0     0 my $hash = $ctxt->{'_id-hash'} ||= {};
365 0 0       0 if ($check_uniq) { # and $prefix eq ''
366 0         0 my $current = $hash->{$id};
367 0 0 0     0 if (defined $current and $current != $object) {
368 0         0 _warn("Duplicated ID '$id'");
369             }
370             }
371 0 0       0 if (ref($object)) {
372 0         0 weaken( $hash->{$id} = $object );
373             } else {
374 0         0 $hash->{$id} = $object;
375             }
376             }
377              
378             sub _index_by_id {
379 26     26   117 my ($dom) = @_;
380 26         52 my %index;
381 26         911 $dom->indexElements;
382 26         54 for my $node (@{$dom->findnodes('//*/@*[name()="id" or name()="xml:id"]')}) {
  26         137  
383 8322         48440 $index{ $node->value }=$node->ownerElement;
384             }
385 26         2766 return \%index;
386             }
387              
388             ##########################################
389             # Validation
390             #########################################
391              
392             sub validate_object {
393 0     0 1 0 my ($ctxt, $object, $type, $opts)=@_;
394 0         0 $type->validate_object($object,$opts);
395             }
396              
397             ##########################################
398             # Data pulling
399             #########################################
400              
401              
402             sub get_data {
403 757     757 1 1108 my ($node,$path, $strict) = @_;
404 757 100       1206 if (UNIVERSAL::DOES::does($node,'Treex::PML::Instance')) {
405 54         942 $node = $node->get_root;
406             }
407 757         10637 my $val = $node;
408 757 50       1187 if (!defined $path) {
409 0         0 carp("Treex::PML::Instance::get_data : undefined attribute path!");
410 0         0 return;
411             }
412 757         1425 for my $step (split /\//, $path) {
413 815 50       1258 next if $step eq '.';
414 815         1240 my $is_list = UNIVERSAL::DOES::does($val,'Treex::PML::List');
415 815 100 66     12486 if ($is_list or UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
    50          
    100          
    50          
416 1 50       8 if ($step =~ /^\[([-+]?\d+)\]/) {
    50          
417 0 0       0 $val =
    0          
418             $1>0 ? $val->[$1-1] :
419             $1<0 ? $val->[$1] : undef;
420             } elsif ($strict) {
421             # warn "Can't follow attribute path '$path' (step '$step')\n";
422 0         0 return; # ERROR
423             } else {
424 1         4 $val = $val->[0];
425 1 50       7 redo unless $step eq ($is_list ? LM : AM);
    50          
426             }
427             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Seq')) {
428 0 0       0 if ($step =~ /^\[([-+]?\d+)\](.*)/) {
    0          
429 0 0       0 $val =
    0          
430             $1>0 ? $val->elements_list->[$1-1] :
431             $1<0 ? $val->elements_list->[$1] : undef; # element
432 0 0       0 if ($val) {
433 0 0 0     0 if (defined $2 and length $2) { # optional name test
434 0 0       0 return if $val->[0] ne $2; # ERROR
435             }
436 0         0 $val = $val->[1]; # value
437             }
438             } elsif ($step =~ /^([^\[]+)(?:\[([-+]?\d+)\])?/) {
439 0         0 my $i = $2;
440 0         0 $val = $val->values($1);
441 0 0       0 if ($i ne q{}) {
442 0 0       0 $val = $i>0 ? $val->[$i-1] :
    0          
443             $i<0 ? $val->[$i] : undef;
444             }
445             } else {
446 0         0 return; # ERROR
447             }
448             } elsif (ref($val)) {
449 760         22753 $val = $val->{$step};
450             } elsif (defined($val)) {
451             # warn "Can't follow attribute path '$path' (step '$step')\n";
452 0         0 return; # ERROR
453             } else {
454 54         805 return undef;
455             }
456             }
457 703         1253 return $val;
458             }
459              
460             sub get_all {
461 0     0 1 0 my ($node,$path) = @_;
462 0 0       0 if (UNIVERSAL::DOES::does($node,'Treex::PML::Instance')) {
463 0         0 $node = $node->get_root;
464             }
465 0         0 my @vals = ($node);
466 0         0 my $val;
467 0         0 my $redo=0;
468 0         0 my $dot;
469 0 0       0 for my $step (ref($path) ? @$path : (split /\//, $path)) {
470 0         0 $dot= ($step eq '.');
471 0 0       0 next if $dot;
472 0         0 $redo=0;
473             @vals = map {
474 0         0 $val=$_;
  0         0  
475 0         0 my $is_list = UNIVERSAL::DOES::does($val,'Treex::PML::List');
476 0 0 0     0 if ($is_list or UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
    0          
    0          
477 0 0       0 if ($step =~ /^\[([-+]?\d+)\]/) {
478 0 0       0 $1>0 ? $val->[$1-1] :
    0          
479             $1<0 ? $val->[$1] : ();
480             } else {
481 0 0       0 $redo=1 unless $step eq ($is_list ? 'LM' : 'AM');
    0          
482 0         0 @$val
483             }
484             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Seq')) {
485             # grep { $_->[0] eq $step } @{$val->[0]}
486 0 0       0 if ($step =~ /^\[([-+]?\d+)\](.*)/) {
    0          
487 0 0       0 $val =
    0          
488             $1>0 ? $val->elements_list->[$1-1] :
489             $1<0 ? $val->elements_list->[$1] : undef; # element
490 0 0 0     0 $val ?
    0          
    0          
491             (defined $2 and length $2) ?
492             ($val->[0] eq $2) ? ($val->[1]) : ()
493             : $val->[1]
494             :()
495             } elsif ($step =~ /^([^\[]+)(?:\[([-+]?\d+)\])?/) {
496 0         0 my $i = $2;
497 0         0 $val = $val->values($1);
498 0 0 0     0 if (defined $i and length $i) {
499 0 0       0 $i>0 ? $val->[$i-1] :
    0          
500             $i<0 ? $val->[$i] : ();
501             } else {
502 0         0 @$val
503             }
504 0         0 } else { () }
505             } elsif (ref($val)) {
506 0         0 ($val->{$step});
507             } else {
508             ()
509 0         0 }
510             } @vals;
511 0 0       0 redo if $redo;
512             }
513 0 0       0 return @vals if $dot; # a path may end with a /. to prevent expanding trailing lists and alts
514 0 0 0     0 return map { (UNIVERSAL::DOES::does($_,'Treex::PML::List') or UNIVERSAL::DOES::does($_,'Treex::PML::Alt')) ? __expand_list_alt($_) : ($_) } @vals;
  0         0  
515             }
516             sub __expand_list_alt {
517 0 0 0 0   0 return map { (UNIVERSAL::DOES::does($_,'Treex::PML::List') or UNIVERSAL::DOES::does($_,'Treex::PML::Alt')) ? _expand_list_alt($_) : ($_) } @{$_[0]};
  0         0  
  0         0  
518             }
519              
520             sub set_data {
521 0     0 1 0 my ($node,$path, $value, $strict) = @_;
522 0 0       0 if (UNIVERSAL::DOES::does($node,'Treex::PML::Instance')) {
523 0         0 $node = $node->get_root;
524             }
525 0         0 my $val = $node;
526 0         0 my @steps = split /\//, $path;
527 0         0 while (@steps) {
528 0         0 my $step = shift @steps;
529 0 0 0     0 if (UNIVERSAL::DOES::does($val,'Treex::PML::List') or UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
    0          
    0          
    0          
530 0 0       0 if ($step =~ /^\[([-+]?\d+)\]/) {
    0          
531 0 0       0 if (@steps) {
532 0 0       0 $val =
    0          
533             $1>0 ? $val->[$1-1] :
534             $1<0 ? $val->[$1] : undef;
535             } else {
536             return
537 0 0       0 $1>0 ? ($val->[$1-1]=$value) :
    0          
538             $1<0 ? ($val->[$1]=$value) : undef;
539             }
540             } elsif ($strict) {
541 0         0 my $msg = "Can't follow attribute path '$path' (step '$step')";
542 0 0       0 croak $msg if ($strict==2);
543 0         0 warn $msg."\n";
544 0         0 return; # ERROR
545             } else {
546 0 0       0 if (@steps) {
547 0         0 $val = $val->[0]{$step};
548             } else {
549 0         0 $val->[0]{$step} = $value;
550 0         0 return $value;
551             }
552             }
553             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Seq')) {
554 0 0       0 if ($step =~ /^\[([-+]?\d+)\](.*)/) {
    0          
555 0         0 my $i = $1;
556 0 0       0 my $el = $i>0 ? $val->elements_list->[$i-1] :
    0          
557             $i<0 ? $val->elements_list->[$i] : undef; # element
558 0 0 0     0 if (defined $el and defined $2 and length $2 and $el->[0] ne $2) { # optional name test
      0        
      0        
559 0         0 my $msg = "Can't follow attribute path '$path' (step '$step')";
560 0 0       0 croak $msg if ($strict==2);
561 0         0 warn $msg."\n";
562 0         0 return; # ERROR
563             }
564 0 0       0 if (@steps) {
565 0         0 $val = $el->[1];
566             } else {
567 0 0       0 if (UNIVERSAL::DOES::does($value,'Treex::PML::Seq::Element')) {
    0          
568 0         0 $val = $val->elements_list;
569             return
570 0 0       0 $i>0 ? ($val->[$i-1]=$value) :
    0          
571             $i<0 ? ($val->[$i]=$value) : undef;
572             } elsif (ref $val->[$i-1]) {
573 0         0 $el->[1]=$value;
574 0         0 return $value;
575             } else {
576 0         0 my $msg = "Can't follow attribute path '$path' (no sequence element found at step '$step')";
577 0 0       0 croak $msg if ($strict==2);
578 0         0 warn $msg."\n";
579 0         0 return; # ERROR
580             }
581             }
582             } elsif ($step =~ /^([^\[]+)(?:\[([-+]?\d+)\])?/) {
583 0         0 my $i = $2;
584 0         0 $val = $val->values($1);
585 0 0       0 unless (@steps) {
586 0 0       0 $val = $1>0 ? $val->[$1-1] :
    0          
587             $1<0 ? $val->[$1] : undef;
588 0 0       0 if (defined $val) {
589 0 0       0 if (UNIVERSAL::DOES::does($value,'Treex::PML::Seq::Element')) {
590 0         0 $val->[0]=$value->[0];
591 0         0 $val->[1]=$value->[1];
592 0         0 return $val;
593             } else {
594 0         0 $val->[1]=$value;
595 0         0 return $value;
596             }
597             } else {
598 0         0 my $msg = "Can't follow attribute path '$path' (no sequence element found at step '$step')";
599 0 0       0 croak $msg if ($strict==2);
600 0         0 warn $msg."\n";
601 0         0 return; # ERROR
602             }
603             }
604             } else {
605 0         0 return; # ERROR
606             }
607             } elsif (ref($val)) {
608 0 0       0 if (@steps) {
609 0 0 0     0 if (!defined($val->{$step}) and $steps[0]!~/^\[/) {
610 0         0 $val->{$step}=Treex::PML::Factory->createStructure();
611             }
612 0         0 $val = $val->{$step};
613             } else {
614 0         0 $val->{$step} = $value;
615 0         0 return $value;
616             }
617             } elsif (defined($val)) {
618 0         0 my $msg = "Can't follow attribute path '$path' (step '$step')";
619 0 0       0 croak $msg if ($strict==2);
620 0         0 warn $msg."\n";
621 0         0 return; # ERROR
622             } else {
623 0         0 return '';
624             }
625             }
626 0         0 return;
627             }
628              
629              
630              
631             sub __match_path {
632 0     0   0 my ($match_paths, $step)=@_;
633 0         0 my @r;
634 0         0 my $s = $step;
635 0         0 $s =~ s/^\[\d+\]//;
636 0         0 foreach my $m (@$match_paths) {
637 0         0 my ($m_step,@rest) = @{$m->[0]};
  0         0  
638 0 0 0     0 if (defined $m_step and length($m_step)==0) {
    0 0        
    0          
639             # handle //
640 0         0 push @r,$m, [\@rest=>$m->[1]];
641             } elsif ($m_step eq $step or $m_step eq '*') {
642 0         0 push @r,[\@rest=>$m->[1]];
643             } elsif ($m_step !~ /^\[/) {
644 0 0       0 if (!length($s)) {
    0          
645 0         0 push @r,$m;
646             } elsif ($s eq $m_step) {
647 0         0 push @r,[\@rest=>$m->[1]];
648             }
649             }
650             }
651 0         0 return \@r;
652             }
653              
654             sub __split_path {
655 0     0   0 my @p = split m{/}, $_[0];
656 0 0 0     0 if (@p>0 and length($p[0])==0) { shift @p; }
  0         0  
657 0         0 return \@p;
658             }
659              
660             sub for_each_match {
661 0     0 1 0 my ($obj,$paths,$opts) = @_;
662 0   0     0 $opts||={};
663 0         0 my @match_paths;
664 0 0       0 if (UNIVERSAL::isa($paths,'HASH')) {
665 0         0 @match_paths = map { [ __split_path($_) => $paths->{$_} ] } keys %$paths;
  0         0  
666             } else {
667 0         0 croak("Usage: \$pml->for_each_match( { path1 => callback1, path2 => callback2,...} )\n".
668             " or: Treex::PML::Instance::for_each_match( \$obj, { path1 => callback1, ... } )\n");
669             }
670 0         0 my $type;
671 0 0       0 if (UNIVERSAL::DOES::does($obj,'Treex::PML::Instance')) {
    0          
672 0 0       0 if (exists $opts->{type}) {
673             $type = $opts->{type}
674 0         0 } else {
675 0         0 $type = $obj->get_schema->get_root_type
676             }
677 0         0 $obj = $obj->get_root;
678             } elsif (exists $opts->{type}) {
679 0         0 $type = $opts->{type};
680             }
681 0 0       0 __for_each_match_dispatch('','',\@match_paths,$obj,$type) if @match_paths;
682             }
683              
684             sub __for_each_match_dispatch {
685 0     0   0 my ($path, $step, $match_paths, $v, $type)=@_;
686 0 0       0 $path .= $path eq '/' ? $step : '/'.$step;
687 0         0 my $match = __match_path($match_paths,$step);
688 0         0 my @m;
689 0 0       0 if (defined $type) {
690 0         0 my $dt = $type->get_decl_type;
691 0 0 0     0 if ($dt==PML_ATTRIBUTE_DECL ||
      0        
692             $dt==PML_MEMBER_DECL ||
693             $dt==PML_ELEMENT_DECL) {
694 0         0 $type = $type->get_content_decl;
695             }
696             }
697 0         0 for my $m (@$match) {
698 0 0       0 if ( @{$m->[0]}>0 ) {
  0         0  
699 0         0 push @m, $m;
700             } else {
701 0         0 my $cb = $m->[1];
702 0         0 my @args;
703 0 0       0 if (UNIVERSAL::isa($cb,'ARRAY')) {
704 0         0 ($cb,@args) = @$cb;
705             }
706 0         0 $cb->({path => $path, value => $v, type=>$type},@args);
707             }
708             }
709 0 0       0 __for_each_match($path,$v,\@m,$type) if (@m);
710             }
711              
712             sub __for_each_match {
713 0     0   0 my ($p, $val, $match_paths,$type)=@_;
714 0 0       0 if ($val) {
715 0   0     0 my $dt = (defined($type)||undef) && $type->get_decl_type;
716 0 0 0     0 if (defined($type) and $dt == PML_ALT_DECL and !UNIVERSAL::DOES::does($val, 'Treex::PML::Alt')) {
      0        
717 0         0 $type=$type->get_content_decl;
718 0         0 $dt=$type->get_decl_type;
719             }
720 0 0 0     0 if ((UNIVERSAL::DOES::does($val, 'Treex::PML::List') or UNIVERSAL::DOES::does($val, 'Treex::PML::Alt'))
    0 0        
    0 0        
      0        
      0        
      0        
      0        
721             and (!defined($dt) ||
722             $dt == PML_LIST_DECL ||
723             $dt == PML_ALT_DECL)) {
724 0         0 my $no = 1;
725 0   0     0 my $content_type =(defined($type)||undef) && $type->get_content_decl;
726 0         0 foreach my $v (@$val) {
727 0         0 __for_each_match_dispatch($p,"[$no]",$match_paths,
728             $v,$content_type);
729 0         0 $no++;
730             }
731             } elsif ((UNIVERSAL::DOES::does($val, 'Treex::PML::Seq'))
732             and (!defined($dt) || $dt == PML_SEQUENCE_DECL)) {
733 0         0 my $no = 1;
734 0         0 foreach my $e ($val->elements) {
735 0         0 my $name = $e->name;
736 0   0     0 my $content_type = (defined($type)||undef) && $type->get_element_by_name($name);
737 0 0 0     0 if (!defined($type) || defined($content_type)) {
738 0         0 __for_each_match_dispatch($p,"[$no]$name",$match_paths,
739             $e->value, $content_type);
740             }
741 0         0 $no++;
742             }
743             } elsif (UNIVERSAL::isa($val,'HASH')
744             and (!defined($dt)
745             || $dt == PML_STRUCTURE_DECL
746             || $dt == PML_CONTAINER_DECL)) {
747 0         0 foreach my $name (keys %$val) {
748 0   0     0 my $content_type = (defined($type)||undef) && $type->get_member_by_name($name);
749 0 0 0     0 if (!defined($type) || defined($content_type)) {
750 0         0 __for_each_match_dispatch($p,$name,$match_paths,$val->{$name},
751             $content_type);
752             }
753             }
754             }
755             }
756             }
757             sub get_all_matches {
758 0     0 1 0 my ($obj,$path_list,$opts) = @_;
759              
760 0 0       0 unless (UNIVERSAL::isa($path_list,"ARRAY")) {
761 0 0       0 if (ref($path_list)) {
762 0         0 die "Usage: ".__PACKAGE__."::get_all_matches: expected a string or a list, got $path_list";
763             } else {
764 0         0 $path_list = [$path_list];
765             }
766             }
767 0         0 my @matches;
768 0     0   0 my $sub = sub { push @matches, $_[0] };
  0         0  
769 0         0 for_each_match($obj, { map { $_=>$sub } @$path_list }, $opts);
  0         0  
770 0 0       0 return wantarray ? @matches : \@matches;
771             }
772             sub count_matches {
773 0     0 1 0 my ($obj,$path_list,$opts) = @_;
774              
775 0 0       0 unless (UNIVERSAL::isa($path_list,"ARRAY")) {
776 0 0       0 if (ref($path_list)) {
777 0         0 die "Usage: ".__PACKAGE__."::get_all_matches: expected a string or a list, got $path_list";
778             } else {
779 0         0 $path_list = [$path_list];
780             }
781             }
782 0         0 my $matches;
783 0     0   0 my $sub = sub { $matches++ };
  0         0  
784 0         0 for_each_match($obj, { map { $_=>$sub } @$path_list }, $opts);
  0         0  
785 0         0 return $matches;
786             }
787              
788              
789             sub traverse_data {
790 0     0 1 0 my ($value,$decl,$callback,$opts)=@_;
791 0   0     0 $opts||={};
792 0 0 0     0 die "Usage: traverse_data(\$data,\$type_decl,\$callback,\$option_hash)"
      0        
      0        
793             unless blessed($value) and $decl->isa('Treex::PML::Schema::Decl')
794             and ref($callback) eq 'CODE'
795             and ref($opts) eq 'HASH';
796 0         0 return _traverse_data($value,$decl,$callback,$opts);
797             }
798              
799             sub _traverse_data {
800 0     0   0 my ($value,$decl,$callback,$opts)=@_;
801 0         0 my $decl_is = $decl->get_decl_type;
802 0         0 my $desc;
803 0         0 $callback->($value,$decl,$opts->{data});
804 0 0 0     0 if ($decl_is == PML_STRUCTURE_DECL) {
    0 0        
    0 0        
    0          
    0          
805 0         0 my @members = $decl->get_members;
806 0 0       0 if ($opts->{no_childnodes}) {
807             @members = grep {
808 0         0 my $role = $_->get_role;
  0         0  
809 0 0       0 !defined($role) or $role ne '#CHILDNODES'
810             } $decl->get_members;
811             }
812 0 0       0 if ($opts->{no_trees}) {
813             @members = grep {
814 0         0 my $role = $_->get_role;
  0         0  
815 0 0       0 !defined($role) or $role ne '#TREES'
816             } $decl->get_members;
817             }
818 0         0 for (@members) {
819 0         0 my $n = $_->get_knit_name;
820 0         0 my $v = $value->{$n};
821 0 0       0 _traverse_data($v,$_->get_knit_content_decl,$callback,$opts) if defined $v;
822             }
823             } elsif ($decl_is == PML_CONTAINER_DECL) {
824 0         0 my @attrs = $decl->get_attributes;
825 0         0 for (@attrs) {
826 0         0 my $n = $_->get_name;
827 0         0 my $v = $value->{$n};
828 0 0       0 _traverse_data($v,$_->get_content_decl,$callback,$opts) if defined $v;
829             }
830 0         0 my $content_decl = $decl->get_knit_content_decl;
831 0         0 my $v = $value->{'#content'};
832 0 0       0 _traverse_data($v,$content_decl,$callback,$opts) if $v;
833             } elsif ($decl_is == PML_SEQUENCE_DECL) {
834 0         0 my @elems = $decl->get_elements;
835 0         0 for (@{$value->elements_list}) {
  0         0  
836 0         0 my $n = $_->name;
837 0         0 my $v = $_->value;
838 0         0 my $e = $decl->get_element_by_name($n);
839 0 0 0     0 _traverse_data($v,$e,$callback,$opts) if $v and $e;
840             }
841             } elsif ($decl_is == PML_LIST_DECL || $decl_is == PML_ALT_DECL) {
842 0 0 0     0 if ($decl_is == PML_ALT_DECL and not UNIVERSAL::DOES::does($value,'Treex::PML::Alt')) {
843 0         0 $value=Treex::PML::Factory->createAlt([$value],1);
844             }
845 0         0 my $content_decl=$decl->get_knit_content_decl;
846 0         0 for my $v ($value->values) {
847 0 0       0 _traverse_data($v,$content_decl,$callback,$opts) if defined($v);
848             }
849             } elsif ($decl_is == PML_CHOICE_DECL || $decl_is == PML_CONSTANT_DECL || $decl_is == PML_CDATA_DECL) {
850             } else {
851 0         0 die "unhandled data type: $decl\n";
852             }
853 0         0 return;
854             }
855              
856              
857              
858             ##########################################
859             # Convert to Treex::PML::Document
860             #########################################
861              
862             sub convert_to_fsfile {
863 22     22 1 78 my ($ctxt,$fsfile,$opts)=@_;
864              
865 22         66 my $schema = $ctxt->{'_schema'};
866 22   50     206 $opts||={};
867              
868 22 50       123 unless (ref($fsfile)) {
869 0         0 $fsfile = Treex::PML::Factory->createDocument({ backend => 'PML' } );
870             }
871              
872 22         140 $fsfile->changeURL( $ctxt->{'_filename'} );
873 22         112 $fsfile->changeEncoding($DEFAULT_ENCODING);
874              
875 22 50 33     965 if ($schema->isa('Treex::PML::Schema') and not UNIVERSAL::DOES::does($schema, 'Treex::PML::Schema')) {
876             # rebless
877 0         0 require Treex::PML::Schema;
878 0         0 bless $schema, 'Treex::PML::Schema';
879             }
880 22         502 $fsfile->changeMetaData( 'schema', $schema );
881 22         87 $fsfile->changeMetaData( 'schema-url', $ctxt->{'_schema-url'} );
882 22         121 $fsfile->changeMetaData( 'schema-inline', $ctxt->{'_schema-inline'} );
883 22         104 $fsfile->changeMetaData( 'pml_transform', $ctxt->{'_transform_id'} );
884 22         88 $fsfile->changeMetaData( 'references', $ctxt->{'_references'} );
885 22         78 $fsfile->changeMetaData( 'refnames', $ctxt->{'_refnames'} );
886             $fsfile->changeMetaData( 'fs-require',
887 2         12 [ map { [$_->{id},$_->{href}] }
888 22         113 grep { $_->{readas} eq 'trees' } $ctxt->get_reffiles() ]
  16         94  
889             );
890              
891 22   100     159 $fsfile->changeAppData( 'ref', $ctxt->{'_ref'} || {} );
892             # $fsfile->changeAppData( 'ref-index', $ctxt->{'_ref-index'} || {} );
893 22         84 $fsfile->changeAppData( 'id-hash', $ctxt->{'_id-hash'} );
894              
895 22         76 $fsfile->changeMetaData( 'pml_root', $ctxt->{'_root'} );
896 22         76 $fsfile->changeMetaData( 'pml_trees_type', $ctxt->{'_pml_trees_type'} );
897 22         95 $fsfile->changeMetaData( 'pml_prolog', $ctxt->{'_pml_prolog'} );
898 22         92 $fsfile->changeMetaData( 'pml_epilog', $ctxt->{'_pml_epilog'} );
899            
900 22 50       77 if ($ctxt->{'_pi'}) {
901 0         0 my @patterns = map { $_->[1] } grep { $_->[0] eq 'tred-pattern' } @{$ctxt->{'_pi'}};
  0         0  
  0         0  
  0         0  
902 0         0 my ($hint) = map { $_->[1] } grep { $_->[0] eq 'tred-hint' } @{$ctxt->{'_pi'}} ;
  0         0  
  0         0  
  0         0  
903 0         0 for (@patterns, $hint) {
904 0 0       0 next unless defined;
905 0         0 s/</
906 0         0 s/>/>/g;
907 0         0 s/&/&/g;
908             }
909 0         0 $fsfile->changePatterns( @patterns );
910 0         0 $fsfile->changeHint( $hint );
911             }
912              
913 22 50       81 $fsfile->changeTrees( @{$ctxt->{'_trees'}} ) if $ctxt->{'_trees'};
  22         155  
914              
915 22         142 my @nodes = $ctxt->{'_schema'}->find_role('#NODE');
916 22         62 my (@order,@hide);
917 22         68 for my $path (@nodes) {
918 97         307 my $node_decl = $schema->find_type_by_path($path);
919 97 50       220 $node_decl or die "Type-path $path does not lead to anything\n";
920              
921 97         300 push @order, map { $_->get_name } $node_decl->find_members_by_role('#ORDER');
  19         75  
922 97         263 push @hide, map { $_->get_name } $node_decl->find_members_by_role('#HIDE' );
  0         0  
923             }
924 22         51 my %uniq;
925 22 100       58 @order = grep { !$uniq{$_} && ($uniq{$_}=1) } @order;
  19         80  
926 22         75 %uniq=();
927 22 0       58 @hide = grep { !$uniq{$_} && ($uniq{$_}=1) } @hide;
  0         0  
928 22 50       69 if (@order>1) {
929 0         0 _warn("Treex::PML::Document only supports #ORDER members/attributes with a same name: found {",
930             join(',',@order),"}, using $order[0]!");
931             }
932 22 50       73 if (@hide>1) {
933 0         0 _warn("Treex::PML::Document only supports #HIDE members/attributes with a same name: found {",
934             join(',',@hide),"} $hide[0]!");
935             }
936 22         115 my $defs = $fsfile->FS->defs;
937 22 100       78 $defs->{$order[0]} = ' N' if @order;
938 22 50       64 $defs->{$hide[0]} = ' H' if @hide;
939              
940 22         101 return $fsfile;
941             }
942              
943             ##########################################
944             # Convert from Treex::PML::Document
945             ##########################################
946              
947             sub convert_from_fsfile {
948 10     10 1 32 my ($ctxt,$fsfile)=@_;
949              
950 10 50       45 unless (ref($ctxt)) {
951 10         51 $ctxt = $ctxt->new();
952             }
953              
954 10         2326 $ctxt->{'_transform_id'} = $fsfile->metaData('pml_transform');
955 10         45 $ctxt->{'_filename'} = $fsfile->filename;
956 10         616 $ctxt->{'_schema'} = $fsfile->metaData('schema');
957 10         35 $ctxt->{'_root'} = $fsfile->metaData('pml_root');
958 10         32 $ctxt->{'_schema-inline'} = $fsfile->metaData('schema-inline'); # not used anymore
959 10         46 $ctxt->{'_schema-url'} = $fsfile->metaData('schema-url');
960 10         36 $ctxt->{'_references'} = $fsfile->metaData('references');
961 10         34 $ctxt->{'_refnames'} = $fsfile->metaData('refnames');
962 10         34 $ctxt->{'_pml_trees_type'} = $fsfile->metaData('pml_trees_type');
963 10         33 $ctxt->{'_pml_prolog'} = $fsfile->metaData('pml_prolog');
964 10         32 $ctxt->{'_pml_epilog'} = $fsfile->metaData('pml_epilog');
965 10         39 $ctxt->{'_trees'} = Treex::PML::Factory->createList( $fsfile->treeList );
966              
967 10         58 $ctxt->{'_refs_save'} = $fsfile->appData('refs_save');
968              
969 10         28 $ctxt->{'_ref'} = $fsfile->appData('ref');
970             # $ctxt->{'_ref-index'} = $fsfile->appData('ref-index');
971 10         35 $ctxt->{'_id-hash'} = $fsfile->appData('id-hash');
972              
973 10         34 my $PIs = $ctxt->{'_pi'} = [];
974 10         46 for my $pattern ($fsfile->patterns) {
975 0         0 $pattern =~ s/&/&/g;
976 0         0 $pattern =~ s/
977 0         0 $pattern =~ s/>/>/g;
978 0         0 push @$PIs, ['tred-pattern', $pattern];
979             }
980 10         45 my $hint = $fsfile->hint;
981 10 50 33     45 if (defined $hint and length $hint) {
982 0         0 $hint =~ s/&/&/g;
983 0         0 $hint =~ s/
984 0         0 $hint =~ s/>/>/g;
985 0         0 push @$PIs, [ 'tred-hint', $hint ];
986             }
987            
988 10         31 return $ctxt;
989             }
990              
991              
992             1;
993             __END__