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 9     9   146 use 5.008;
  9         33  
4 9     9   39 use strict;
  9         19  
  9         186  
5 9     9   30 use warnings;
  9         16  
  9         364  
6 9     9   55 use Carp;
  9         14  
  9         631  
7 9     9   47 use Cwd;
  9         15  
  9         778  
8              
9             BEGIN {
10              
11 9     9   61 require Exporter;
12 9         361 import Exporter qw(import);
13              
14             }
15              
16 9     9   41 use Scalar::Util qw(weaken blessed);
  9         18  
  9         443  
17 9     9   59 use UNIVERSAL::DOES;
  9         20  
  9         334  
18 9     9   3584 use Treex::PML::Instance::Common qw(:all);
  9         51  
  9         1741  
19 9     9   56 use Treex::PML::Schema;
  9         20  
  9         850  
20 9     9   48 use Encode;
  9         15  
  9         774  
21 9     9   50 use File::Spec;
  9         13  
  9         199  
22 9     9   33 use URI;
  9         15  
  9         168  
23 9     9   49 use URI::file;
  9         15  
  9         439  
24             our $DEFAULT_ENCODING = 'utf-8';
25              
26       9     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.29'; # version template
79              
80 0         0 BEGIN {
81 9     9   1836 require Treex::PML::IO;
82 9         263 require Treex::PML;
83              
84              
85             # FIELDS:
86 9         43 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 9     9   3840 );
  9         13023  
122              
123             } # BEGIN
124              
125 9     9   19836 use Treex::PML::Instance::Reader;
  9         29  
  9         363  
126 9     9   5480 use Treex::PML::Instance::Writer;
  9         27  
  9         58830  
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 74     74 1 295 $_[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 107     107 1 581 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 5228 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 81 sub get_status { $_[0]->{'_status'}; }
202             #sub set_status { $_[0]->{'_status'} = $_[1]; }
203              
204             sub get_reffiles {
205 107     107 1 195 my ($ctxt)=@_;
206 107         476 my $references = [$ctxt->{'_schema'}->get_named_references];
207 107         185 my @refs;
208 107 50       241 if ($references) {
209 107         239 foreach my $reference (@$references) {
210 56         166 my $refids = $ctxt->{'_refnames'}->{$reference->{name}};
211 56 50       115 if ($refids) {
212 56 50       153 foreach my $refid (ref($refids) ? @$refids : ($refids)) {
213 56         114 my $href = $ctxt->{'_references'}->{$refid};
214 56 50       320 if ($href) {
215 56         559 _debug("Found '$reference->{name}' as $refid# = '$href'");
216             push @refs,{
217             readas => $reference->{readas},
218             name => $reference->{name},
219 56         436 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 107         317 return @refs;
232             }
233              
234             sub read_reffiles {
235 65     65 0 165 my ($ctxt,$opts) = @_;
236 65         205 foreach my $ref ($ctxt->get_reffiles()) {
237 28         59 my $id = $ref->{id};
238 28         49 my $selected = $ctxt->{'_selected_references_ids'}{$id};
239 28 50       95 next if (defined($selected) ? $selected==0 : $ctxt->{'_no_references'});
    50          
240 28         49 my $readas = $ref->{readas};
241 28 50       67 if (defined $readas) {
242 28 100       93 if ($readas eq 'dom') {
    100          
    50          
    0          
243 20         98 $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         27 $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 16 my ($ctxt,$refid,$href,$opts)=@_;
258             # embed PML documents
259 6         13 my $ref_data;
260 6         21 _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       83 ($opts ? %$opts : ()),
268             });
269 6   50     377 $ctxt->{'_ref'} ||= {};
270 6         24 $ctxt->{'_ref'}->{$refid}=$pml;
271 6   50     34 $ctxt->{'_ref-index'} ||= {};
272 6         18 weaken( $ctxt->{'_ref-index'}->{$refid} = $pml->{'_id-hash'} );
273 6         33 1;
274             }
275              
276             # $ctxt, $refid, $href
277             sub readas_dom {
278 32     32 0 87 my ($ctxt,$refid,$href,$opts)=@_;
279             # embed DOM documents
280 32         41 my $ref_data;
281             # if ($opts and $opts->{use_resources}) {
282             # $href = Treex::PML::FindInResourcePaths($href);
283             # }
284              
285 32         114 my ($local_file,$remove_file) = Treex::PML::IO::fetch_file($href);
286 32         136 my $ref_fh = Treex::PML::IO::open_uri($local_file);
287 32 50       108 _die("Cannot open $href for reading") unless $ref_fh;
288 32         525 _debug("readas_dom: $refid => $href");
289 32   66     254 my $parser = $ctxt->{'_parser'} || $ctxt->_xml_parser();
290 32 50       90 if ($ref_fh){
291 32         59 eval {
292 32         175 $ref_data = $parser->parse_fh($ref_fh, $href);
293             };
294 32 50       35201 _die("Error parsing $href $ref_fh $local_file ($@)") if $@;
295 32 50 33     283 $ref_data->setBaseURI($href) if $ref_data and $ref_data->can('setBaseURI');;
296 32         801 $parser->process_xincludes($ref_data);
297 32         4269 Treex::PML::IO::close_uri($ref_fh);
298 32   100     161 $ctxt->{'_ref'} ||= {};
299 32         3738 $ctxt->{'_ref'}->{$refid}=$ref_data;
300 32   100     156 $ctxt->{'_ref-index'} ||= {};
301 32         161 $ctxt->{'_ref-index'}->{$refid}=_index_by_id($ref_data);
302 32 100 100     346 if ($href ne $local_file and $remove_file) {
303 10         121 local $!;
304 10   33     1576 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         441 1;
314             }
315              
316             sub _xml_parser {
317 40     40   94 my ($self,$opts) = @_;
318 40         373 my $parser = XML::LibXML->new();
319 40         711 $parser->keep_blanks(0);
320 40         885 $parser->line_numbers(1);
321 40         340 $parser->load_ext_dtd(0);
322 40         478 $parser->validation(0);
323 40 50 33     387 if (ref($opts) and $parser->can('set_options')) {
324 0         0 $parser->set_options($opts);
325             }
326 40         115 return $parser;
327             }
328              
329             ###################################
330             # CONSTRUCTOR
331             ####################################
332              
333             sub new {
334 75     75 1 142 my $class = shift;
335 75 50       165 _die('Usage: ' . __PACKAGE__ . '->new()') if ref($class);
336 75         356 return fields::new($class);
337             }
338              
339              
340              
341             ###################################
342             # LOAD
343             ###################################
344              
345             sub load {
346 65     65 1 8067 return &Treex::PML::Instance::Reader::load;
347             }
348              
349             sub save {
350 21     21 1 4307 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   74 my ($dom) = @_;
380 32         49 my %index;
381 32         1211 $dom->indexElements;
382 32         48 for my $node (@{$dom->findnodes('//*/@*[name()="id" or name()="xml:id"]')}) {
  32         159  
383 8322         39067 $index{ $node->value }=$node->ownerElement;
384             }
385 32         3648 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 1203 my ($node,$path, $strict) = @_;
404 757 100       1268 if (UNIVERSAL::DOES::does($node,'Treex::PML::Instance')) {
405 54         824 $node = $node->get_root;
406             }
407 757         11099 my $val = $node;
408 757 50       1212 if (!defined $path) {
409 0         0 carp("Treex::PML::Instance::get_data : undefined attribute path!");
410 0         0 return;
411             }
412 757         1546 for my $step (split /\//, $path) {
413 815 50       1333 next if $step eq '.';
414 815         1292 my $is_list = UNIVERSAL::DOES::does($val,'Treex::PML::List');
415 815 100 66     12122 if ($is_list or UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
    50          
    100          
    50          
416 1 50       5 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         3 $val = $val->[0];
425 1 50       4 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         21899 $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         609 return undef;
455             }
456             }
457 703         1498 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 52 my ($ctxt,$fsfile,$opts)=@_;
864              
865 22         53 my $schema = $ctxt->{'_schema'};
866 22   50     117 $opts||={};
867              
868 22 50       95 unless (ref($fsfile)) {
869 0         0 $fsfile = Treex::PML::Factory->createDocument({ backend => 'PML' } );
870             }
871              
872 22         148 $fsfile->changeURL( $ctxt->{'_filename'} );
873 22         102 $fsfile->changeEncoding($DEFAULT_ENCODING);
874              
875 22 50 33     193 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         413 $fsfile->changeMetaData( 'schema', $schema );
881 22         62 $fsfile->changeMetaData( 'schema-url', $ctxt->{'_schema-url'} );
882 22         101 $fsfile->changeMetaData( 'schema-inline', $ctxt->{'_schema-inline'} );
883 22         97 $fsfile->changeMetaData( 'pml_transform', $ctxt->{'_transform_id'} );
884 22         82 $fsfile->changeMetaData( 'references', $ctxt->{'_references'} );
885 22         63 $fsfile->changeMetaData( 'refnames', $ctxt->{'_refnames'} );
886             $fsfile->changeMetaData( 'fs-require',
887 2         8 [ map { [$_->{id},$_->{href}] }
888 22         91 grep { $_->{readas} eq 'trees' } $ctxt->get_reffiles() ]
  16         91  
889             );
890              
891 22   100     141 $fsfile->changeAppData( 'ref', $ctxt->{'_ref'} || {} );
892             # $fsfile->changeAppData( 'ref-index', $ctxt->{'_ref-index'} || {} );
893 22         81 $fsfile->changeAppData( 'id-hash', $ctxt->{'_id-hash'} );
894              
895 22         53 $fsfile->changeMetaData( 'pml_root', $ctxt->{'_root'} );
896 22         59 $fsfile->changeMetaData( 'pml_trees_type', $ctxt->{'_pml_trees_type'} );
897 22         71 $fsfile->changeMetaData( 'pml_prolog', $ctxt->{'_pml_prolog'} );
898 22         60 $fsfile->changeMetaData( 'pml_epilog', $ctxt->{'_pml_epilog'} );
899            
900 22 50       62 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       61 $fsfile->changeTrees( @{$ctxt->{'_trees'}} ) if $ctxt->{'_trees'};
  22         153  
914              
915 22         108 my @nodes = $ctxt->{'_schema'}->find_role('#NODE');
916 22         42 my (@order,@hide);
917 22         45 for my $path (@nodes) {
918 97         247 my $node_decl = $schema->find_type_by_path($path);
919 97 50       146 $node_decl or die "Type-path $path does not lead to anything\n";
920              
921 97         224 push @order, map { $_->get_name } $node_decl->find_members_by_role('#ORDER');
  19         49  
922 97         190 push @hide, map { $_->get_name } $node_decl->find_members_by_role('#HIDE' );
  0         0  
923             }
924 22         38 my %uniq;
925 22 100       44 @order = grep { !$uniq{$_} && ($uniq{$_}=1) } @order;
  19         69  
926 22         50 %uniq=();
927 22 0       42 @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       44 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         104 my $defs = $fsfile->FS->defs;
937 22 100       63 $defs->{$order[0]} = ' N' if @order;
938 22 50       41 $defs->{$hide[0]} = ' H' if @hide;
939              
940 22         93 return $fsfile;
941             }
942              
943             ##########################################
944             # Convert from Treex::PML::Document
945             ##########################################
946              
947             sub convert_from_fsfile {
948 10     10 1 25 my ($ctxt,$fsfile)=@_;
949              
950 10 50       52 unless (ref($ctxt)) {
951 10         44 $ctxt = $ctxt->new();
952             }
953              
954 10         1976 $ctxt->{'_transform_id'} = $fsfile->metaData('pml_transform');
955 10         44 $ctxt->{'_filename'} = $fsfile->filename;
956 10         597 $ctxt->{'_schema'} = $fsfile->metaData('schema');
957 10         28 $ctxt->{'_root'} = $fsfile->metaData('pml_root');
958 10         24 $ctxt->{'_schema-inline'} = $fsfile->metaData('schema-inline'); # not used anymore
959 10         24 $ctxt->{'_schema-url'} = $fsfile->metaData('schema-url');
960 10         29 $ctxt->{'_references'} = $fsfile->metaData('references');
961 10         28 $ctxt->{'_refnames'} = $fsfile->metaData('refnames');
962 10         22 $ctxt->{'_pml_trees_type'} = $fsfile->metaData('pml_trees_type');
963 10         28 $ctxt->{'_pml_prolog'} = $fsfile->metaData('pml_prolog');
964 10         21 $ctxt->{'_pml_epilog'} = $fsfile->metaData('pml_epilog');
965 10         45 $ctxt->{'_trees'} = Treex::PML::Factory->createList( $fsfile->treeList );
966              
967 10         39 $ctxt->{'_refs_save'} = $fsfile->appData('refs_save');
968              
969 10         22 $ctxt->{'_ref'} = $fsfile->appData('ref');
970             # $ctxt->{'_ref-index'} = $fsfile->appData('ref-index');
971 10         24 $ctxt->{'_id-hash'} = $fsfile->appData('id-hash');
972              
973 10         24 my $PIs = $ctxt->{'_pi'} = [];
974 10         56 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         43 my $hint = $fsfile->hint;
981 10 50 33     40 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         30 return $ctxt;
989             }
990              
991              
992             1;
993             __END__