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 8     8   180 use 5.008;
  8         35  
4 8     8   75 use strict;
  8         19  
  8         552  
5 8     8   49 use warnings;
  8         17  
  8         516  
6 8     8   53 use Carp;
  8         37  
  8         641  
7 8     8   55 use Cwd;
  8         28  
  8         770  
8              
9             BEGIN {
10              
11 8     8   74 require Exporter;
12 8         395 import Exporter qw(import);
13              
14             }
15              
16 8     8   47 use Scalar::Util qw(weaken blessed);
  8         16  
  8         522  
17 8     8   52 use UNIVERSAL::DOES;
  8         23  
  8         435  
18 8     8   4410 use Treex::PML::Instance::Common qw(:all);
  8         27  
  8         1901  
19 8     8   65 use Treex::PML::Schema;
  8         22  
  8         1000  
20 8     8   57 use Encode;
  8         17  
  8         844  
21 8     8   59 use File::Spec;
  8         15  
  8         202  
22 8     8   38 use URI;
  8         16  
  8         249  
23 8     8   36 use URI::file;
  8         33  
  8         481  
24             our $DEFAULT_ENCODING = 'utf-8';
25              
26       8     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.28'; # version template
79              
80 0         0 BEGIN {
81 8     8   2540 require Treex::PML::IO;
82 8         253 require Treex::PML;
83              
84              
85             # FIELDS:
86 8         63 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 8     8   4548 );
  8         15130  
122              
123             } # BEGIN
124              
125 8     8   7247 use Treex::PML::Instance::Reader;
  8         41  
  8         494  
126 8     8   6372 use Treex::PML::Instance::Writer;
  8         33  
  8         71676  
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 73     73 1 831 $_[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 106     106 1 763 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 7372 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 89 sub get_status { $_[0]->{'_status'}; }
202             #sub set_status { $_[0]->{'_status'} = $_[1]; }
203              
204             sub get_reffiles {
205 106     106 1 278 my ($ctxt)=@_;
206 106         678 my $references = [$ctxt->{'_schema'}->get_named_references];
207 106         236 my @refs;
208 106 50       329 if ($references) {
209 106         381 foreach my $reference (@$references) {
210 56         257 my $refids = $ctxt->{'_refnames'}->{$reference->{name}};
211 56 50       160 if ($refids) {
212 56 50       232 foreach my $refid (ref($refids) ? @$refids : ($refids)) {
213 56         188 my $href = $ctxt->{'_references'}->{$refid};
214 56 50       466 if ($href) {
215 56         800 _debug("Found '$reference->{name}' as $refid# = '$href'");
216             push @refs,{
217             readas => $reference->{readas},
218             name => $reference->{name},
219 56         609 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 106         470 return @refs;
232             }
233              
234             sub read_reffiles {
235 64     64 0 231 my ($ctxt,$opts) = @_;
236 64         315 foreach my $ref ($ctxt->get_reffiles()) {
237 28         80 my $id = $ref->{id};
238 28         110 my $selected = $ctxt->{'_selected_references_ids'}{$id};
239 28 50       141 next if (defined($selected) ? $selected==0 : $ctxt->{'_no_references'});
    50          
240 28         65 my $readas = $ref->{readas};
241 28 50       87 if (defined $readas) {
242 28 100       135 if ($readas eq 'dom') {
    100          
    50          
    0          
243 20         109 $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         57 $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 23 my ($ctxt,$refid,$href,$opts)=@_;
258             # embed PML documents
259 6         18 my $ref_data;
260 6         52 _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       194 ($opts ? %$opts : ()),
268             });
269 6   50     449 $ctxt->{'_ref'} ||= {};
270 6         26 $ctxt->{'_ref'}->{$refid}=$pml;
271 6   50     49 $ctxt->{'_ref-index'} ||= {};
272 6         26 weaken( $ctxt->{'_ref-index'}->{$refid} = $pml->{'_id-hash'} );
273 6         52 1;
274             }
275              
276             # $ctxt, $refid, $href
277             sub readas_dom {
278 32     32 0 107 my ($ctxt,$refid,$href,$opts)=@_;
279             # embed DOM documents
280 32         50 my $ref_data;
281             # if ($opts and $opts->{use_resources}) {
282             # $href = Treex::PML::FindInResourcePaths($href);
283             # }
284              
285 32         156 my ($local_file,$remove_file) = Treex::PML::IO::fetch_file($href);
286 32         162 my $ref_fh = Treex::PML::IO::open_uri($local_file);
287 32 50       583 _die("Cannot open $href for reading") unless $ref_fh;
288 32         280 _debug("readas_dom: $refid => $href");
289 32   66     272 my $parser = $ctxt->{'_parser'} || $ctxt->_xml_parser();
290 32 50       141 if ($ref_fh){
291 32         65 eval {
292 32         197 $ref_data = $parser->parse_fh($ref_fh, $href);
293             };
294 32 50       43278 _die("Error parsing $href $ref_fh $local_file ($@)") if $@;
295 32 50 33     367 $ref_data->setBaseURI($href) if $ref_data and $ref_data->can('setBaseURI');;
296 32         1118 $parser->process_xincludes($ref_data);
297 32         5827 Treex::PML::IO::close_uri($ref_fh);
298 32   100     199 $ctxt->{'_ref'} ||= {};
299 32         4474 $ctxt->{'_ref'}->{$refid}=$ref_data;
300 32   100     209 $ctxt->{'_ref-index'} ||= {};
301 32         146 $ctxt->{'_ref-index'}->{$refid}=_index_by_id($ref_data);
302 32 100 100     522 if ($href ne $local_file and $remove_file) {
303 10         156 local $!;
304 10   33     2130 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 32         565 1;
314             }
315              
316             sub _xml_parser {
317 40     40   127 my ($self,$opts) = @_;
318 40         395 my $parser = XML::LibXML->new();
319 40         895 $parser->keep_blanks(0);
320 40         1119 $parser->line_numbers(1);
321 40         434 $parser->load_ext_dtd(0);
322 40         620 $parser->validation(0);
323 40 50 33     534 if (ref($opts) and $parser->can('set_options')) {
324 0         0 $parser->set_options($opts);
325             }
326 40         176 return $parser;
327             }
328              
329             ###################################
330             # CONSTRUCTOR
331             ####################################
332              
333             sub new {
334 74     74 1 180 my $class = shift;
335 74 50       273 _die('Usage: ' . __PACKAGE__ . '->new()') if ref($class);
336 74         532 return fields::new($class);
337             }
338              
339              
340              
341             ###################################
342             # LOAD
343             ###################################
344              
345             sub load {
346 64     64 1 11085 return &Treex::PML::Instance::Reader::load;
347             }
348              
349             sub save {
350 21     21 1 6308 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 32     32   96 my ($dom) = @_;
380 32         72 my %index;
381 32         1355 $dom->indexElements;
382 32         63 for my $node (@{$dom->findnodes('//*/@*[name()="id" or name()="xml:id"]')}) {
  32         175  
383 8322         51744 $index{ $node->value }=$node->ownerElement;
384             }
385 32         4346 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 959 my ($node,$path, $strict) = @_;
404 757 100       1041 if (UNIVERSAL::DOES::does($node,'Treex::PML::Instance')) {
405 54         1201 $node = $node->get_root;
406             }
407 757         7658 my $val = $node;
408 757 50       941 if (!defined $path) {
409 0         0 carp("Treex::PML::Instance::get_data : undefined attribute path!");
410 0         0 return;
411             }
412 757         1194 for my $step (split /\//, $path) {
413 815 50       1141 next if $step eq '.';
414 815         1024 my $is_list = UNIVERSAL::DOES::does($val,'Treex::PML::List');
415 815 100 66     9800 if ($is_list or UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
    50          
    100          
    50          
416 1 50       9 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       8 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         17397 $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         1014 return undef;
455             }
456             }
457 703         1024 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 79 my ($ctxt,$fsfile,$opts)=@_;
864              
865 22         69 my $schema = $ctxt->{'_schema'};
866 22   50     203 $opts||={};
867              
868 22 50       160 unless (ref($fsfile)) {
869 0         0 $fsfile = Treex::PML::Factory->createDocument({ backend => 'PML' } );
870             }
871              
872 22         214 $fsfile->changeURL( $ctxt->{'_filename'} );
873 22         127 $fsfile->changeEncoding($DEFAULT_ENCODING);
874              
875 22 50 33     258 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         575 $fsfile->changeMetaData( 'schema', $schema );
881 22         105 $fsfile->changeMetaData( 'schema-url', $ctxt->{'_schema-url'} );
882 22         124 $fsfile->changeMetaData( 'schema-inline', $ctxt->{'_schema-inline'} );
883 22         112 $fsfile->changeMetaData( 'pml_transform', $ctxt->{'_transform_id'} );
884 22         93 $fsfile->changeMetaData( 'references', $ctxt->{'_references'} );
885 22         91 $fsfile->changeMetaData( 'refnames', $ctxt->{'_refnames'} );
886             $fsfile->changeMetaData( 'fs-require',
887 2         16 [ map { [$_->{id},$_->{href}] }
888 22         143 grep { $_->{readas} eq 'trees' } $ctxt->get_reffiles() ]
  16         107  
889             );
890              
891 22   100     206 $fsfile->changeAppData( 'ref', $ctxt->{'_ref'} || {} );
892             # $fsfile->changeAppData( 'ref-index', $ctxt->{'_ref-index'} || {} );
893 22         107 $fsfile->changeAppData( 'id-hash', $ctxt->{'_id-hash'} );
894              
895 22         106 $fsfile->changeMetaData( 'pml_root', $ctxt->{'_root'} );
896 22         86 $fsfile->changeMetaData( 'pml_trees_type', $ctxt->{'_pml_trees_type'} );
897 22         98 $fsfile->changeMetaData( 'pml_prolog', $ctxt->{'_pml_prolog'} );
898 22         90 $fsfile->changeMetaData( 'pml_epilog', $ctxt->{'_pml_epilog'} );
899            
900 22 50       161 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       96 $fsfile->changeTrees( @{$ctxt->{'_trees'}} ) if $ctxt->{'_trees'};
  22         184  
914              
915 22         150 my @nodes = $ctxt->{'_schema'}->find_role('#NODE');
916 22         60 my (@order,@hide);
917 22         85 for my $path (@nodes) {
918 97         502 my $node_decl = $schema->find_type_by_path($path);
919 97 50       271 $node_decl or die "Type-path $path does not lead to anything\n";
920              
921 97         399 push @order, map { $_->get_name } $node_decl->find_members_by_role('#ORDER');
  19         94  
922 97         365 push @hide, map { $_->get_name } $node_decl->find_members_by_role('#HIDE' );
  0         0  
923             }
924 22         54 my %uniq;
925 22 100       60 @order = grep { !$uniq{$_} && ($uniq{$_}=1) } @order;
  19         119  
926 22         59 %uniq=();
927 22 0       65 @hide = grep { !$uniq{$_} && ($uniq{$_}=1) } @hide;
  0         0  
928 22 50       81 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       75 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         150 my $defs = $fsfile->FS->defs;
937 22 100       90 $defs->{$order[0]} = ' N' if @order;
938 22 50       69 $defs->{$hide[0]} = ' H' if @hide;
939              
940 22         185 return $fsfile;
941             }
942              
943             ##########################################
944             # Convert from Treex::PML::Document
945             ##########################################
946              
947             sub convert_from_fsfile {
948 10     10 1 35 my ($ctxt,$fsfile)=@_;
949              
950 10 50       59 unless (ref($ctxt)) {
951 10         55 $ctxt = $ctxt->new();
952             }
953              
954 10         2887 $ctxt->{'_transform_id'} = $fsfile->metaData('pml_transform');
955 10         78 $ctxt->{'_filename'} = $fsfile->filename;
956 10         703 $ctxt->{'_schema'} = $fsfile->metaData('schema');
957 10         29 $ctxt->{'_root'} = $fsfile->metaData('pml_root');
958 10         33 $ctxt->{'_schema-inline'} = $fsfile->metaData('schema-inline'); # not used anymore
959 10         36 $ctxt->{'_schema-url'} = $fsfile->metaData('schema-url');
960 10         30 $ctxt->{'_references'} = $fsfile->metaData('references');
961 10         35 $ctxt->{'_refnames'} = $fsfile->metaData('refnames');
962 10         35 $ctxt->{'_pml_trees_type'} = $fsfile->metaData('pml_trees_type');
963 10         31 $ctxt->{'_pml_prolog'} = $fsfile->metaData('pml_prolog');
964 10         30 $ctxt->{'_pml_epilog'} = $fsfile->metaData('pml_epilog');
965 10         54 $ctxt->{'_trees'} = Treex::PML::Factory->createList( $fsfile->treeList );
966              
967 10         48 $ctxt->{'_refs_save'} = $fsfile->appData('refs_save');
968              
969 10         52 $ctxt->{'_ref'} = $fsfile->appData('ref');
970             # $ctxt->{'_ref-index'} = $fsfile->appData('ref-index');
971 10         36 $ctxt->{'_id-hash'} = $fsfile->appData('id-hash');
972              
973 10         36 my $PIs = $ctxt->{'_pi'} = [];
974 10         42 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         44 my $hint = $fsfile->hint;
981 10 50 33     52 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         45 return $ctxt;
989             }
990              
991              
992             1;
993             __END__