File Coverage

blib/lib/Treex/PML/Instance/Reader.pm
Criterion Covered Total %
statement 493 703 70.1
branch 197 384 51.3
condition 126 329 38.3
subroutine 41 49 83.6
pod 0 14 0.0
total 857 1479 57.9


line stmt bran cond sub pod time code
1             package Treex::PML::Instance::Reader;
2             {
3 8     8   154 use 5.008;
  8         34  
4 8     8   52 use strict;
  8         27  
  8         300  
5 8     8   42 use warnings;
  8         16  
  8         508  
6 8     8   62 no warnings qw(recursion);
  8         16  
  8         462  
7 8     8   67 use Scalar::Util qw(blessed);
  8         21  
  8         610  
8 8     8   77 use UNIVERSAL::DOES;
  8         15  
  8         403  
9              
10 8     8   49 use Carp;
  8         15  
  8         524  
11 8     8   47 use Data::Dumper;
  8         30  
  8         679  
12              
13             BEGIN {
14 8     8   238 our $VERSION = '2.28'; # version template
15             }
16 8     8   53 use List::Util qw(first);
  8         15  
  8         667  
17 8     8   49 use Scalar::Util qw(weaken);
  8         16  
  8         487  
18 8     8   47 use Treex::PML::Instance::Common qw(:diagnostics :constants);
  8         30  
  8         1354  
19 8     8   57 use Treex::PML::Schema;
  8         21  
  8         1136  
20 8     8   63 use XML::LibXML::Reader;
  8         17  
  8         1444  
21 8     8   60 use Treex::PML::IO qw(open_uri close_uri rename_uri);
  8         18  
  8         543  
22 8     8   47 use Encode;
  8         17  
  8         963  
23              
24             use constant {
25 8         1366 XAT_TYPE => 0,
26             XAT_NAME => 1,
27             XAT_VALUE => 1,
28             XAT_NS => 2,
29             XAT_ATTRS => 3,
30             XAT_CHILDREN => 5,
31             XAT_LINE => 4,
32 8     8   53 };
  8         19  
33              
34             our $STRICT =1;
35             our $XTC_FLAGS;
36 8     8   61 use vars qw( $HAVE_XS );
  8         20  
  8         1789  
37             BEGIN {
38 8 50 33 8   106 if (!$ENV{PML_COMPILE_NO_XS} && eval {
39 8         1219 require XML::CompactTree::XS;
40 0         0 import XML::CompactTree::XS;
41 0         0 $HAVE_XS = 1;
42 0         0 1;
43             }) {
44             # print STDERR "Using XML::CompactTree::XS\n" if $HAVE_XS;
45 0         0 $XTC_FLAGS = XML::CompactTree::XS::XCT_ATTRIBUTE_ARRAY()|
46             XML::CompactTree::XS::XCT_LINE_NUMBERS()|
47             XML::CompactTree::XS::XCT_IGNORE_COMMENTS();
48             } else {
49 8         4446 require XML::CompactTree;
50 8         17901 import XML::CompactTree;
51 8         59 $XTC_FLAGS = XML::CompactTree::XCT_ATTRIBUTE_ARRAY()|
52             XML::CompactTree::XCT_LINE_NUMBERS()|
53             XML::CompactTree::XCT_IGNORE_COMMENTS();
54 8         135718 $HAVE_XS = 0;
55             }
56             }
57              
58             my (%handlers,%src,
59             %handler_cache,@handler_cache,
60             %schema_cache,@schema_cache
61             );
62              
63             # TODO:
64             # - create one handler per cdata+format type
65             # - test inline schemas
66              
67             our $CACHE_HANDLERS=1;
68             our $CACHE_SCHEMAS=1;
69             our $MAX_SCHEMA_CACHE_SIZE=50;
70              
71             our $VALIDATE_CDATA=0;
72             our $VALIDATE_SEQUENCES=1;
73             our $BUILD_TREES = 1;
74             our $LOAD_REFFILES = 1;
75             our $KNIT = 1;
76              
77             our $READER_OPTS = {
78             no_cdata => 1,
79             clean_namespaces => 1,
80             expand_entities => 1,
81             expand_xinclude => 1,
82             no_xinclude_nodes => 1,
83             };
84              
85             require Treex::PML;
86              
87             sub _get_handlers_cache_key {
88 64     64   175 my ($schema)=@_;
89 64         254 my $key="$schema"; $key=~s/.*=//; # strip class
  64         624  
90             return
91             [
92 64   50     1179 $key,
      50        
      50        
      50        
      50        
93             join ',',
94             $key,
95             $VALIDATE_CDATA || 0,
96             $VALIDATE_SEQUENCES || 0,
97             $BUILD_TREES || 0,
98             $LOAD_REFFILES || 0,
99             $KNIT || 0,
100             $Treex::PML::Node::TYPE,
101             $Treex::PML::Node::lbrother,
102             $Treex::PML::Node::rbrother,
103             $Treex::PML::Node::parent,
104             $Treex::PML::Node::firstson,
105             ];
106             }
107              
108             sub _get_schema_cache_key {
109 64     64   174 my ($schema_file)=@_;
110 64 50 33     710 if ((blessed($schema_file) and $schema_file->isa('URI'))) { # assume URI
111 64 50 50     265 if (($schema_file->scheme||'') eq 'file') {
112 64         1917 $schema_file = $schema_file->file
113             } else {
114 0         0 return '0 '.$schema_file;
115             }
116             }
117 64 50       10908 if (-f $schema_file) {
118 64         862 my $mtime = (stat $schema_file)[9];
119 64         381 return $mtime.' '.$schema_file;
120             }
121             }
122              
123             sub get_cached_schema {
124 64     64 0 201 my ($schema_file)=@_;
125 64 50       220 return unless defined $schema_file;
126 64         218 my $cached = $schema_cache{$schema_file};
127 64 100 100     448 if ($cached and $schema_cache[-1] ne $schema_file) {
128             # move the last retrieved schema to the end of the queue
129 14         52 @schema_cache = ((grep { $_ ne $schema_file } @schema_cache),$schema_file);
  168         481  
130             }
131 64         891 return $cached;
132             }
133              
134             sub cache_schema {
135 27     27 0 73 my ($key,$schema)=@_;
136 27         73 push @schema_cache,$key;
137 27         105 $schema_cache{$key} = $schema;
138 27 50       493 if (@schema_cache > $MAX_SCHEMA_CACHE_SIZE) {
139 0         0 my $del = delete $schema_cache{ shift @schema_cache };
140 0         0 delete $handler_cache{ $del }; # delete also from the handler cache
141 0         0 @handler_cache = grep { $_->[0] ne $del } @handler_cache;
  0         0  
142 0 0       0 if (exists &Treex::PML::Instance::Writer::forget_schema) {
143 0         0 Treex::PML::Instance::Writer::forget_schema($schema);
144             }
145             }
146             }
147              
148             sub get_cached_handlers {
149 64     64 0 175 my ($key)=@_;
150 64         181 my $subkey = $key->[1];
151 64         342 my $cached = $handler_cache{ $key->[0] }{ $subkey };
152 64 100 100     452 if ($cached and $handler_cache[-1][1] ne $subkey) {
153             # move the last retrieved schema to the end of the queue
154 14         49 @handler_cache = ((grep { $_->[1] ne $subkey } @handler_cache),$key);
  168         607  
155             }
156 64         175 return $cached;
157             }
158              
159             sub cache_handlers {
160 27     27 0 90 my ($key,$handlers)=@_;
161 27         78 my $subkey = $key->[1];
162 27         72 push @handler_cache,$key;
163 27         142 $handler_cache{$key->[0]}{$subkey} = $handlers;
164 27 50       128 if (@handler_cache > $MAX_SCHEMA_CACHE_SIZE) {
165 0         0 my $del = shift @handler_cache;
166 0         0 delete $handler_cache{ $del->[0] }{ $del->[1] };
167             }
168             }
169              
170             sub load {
171 64     64 0 212 my $ctxt = shift;
172 64         135 my $opts = shift;
173 64 50       384 if (ref($opts) ne 'HASH') {
174 0         0 croak("Usage: Treex::PML::Instance->load({option=>value,...})\n");
175             }
176 64 100       257 if (!ref($ctxt)) {
177 36         400 $ctxt = Treex::PML::Factory->createPMLInstance;
178             }
179 64         38302 my $config = $opts->{config};
180 64 50 66     451 if ($config and ref(my $load_opts = $config->get_data('options/load'))) {
181 0         0 $opts = {%$load_opts, %$opts};
182             }
183 64 50 66     544 $Treex::PML::Instance::DEBUG = $config->get_data('options/debug') if (!$Treex::PML::Instance::DEBUG and $config and defined($config->get_data('options/debug')));
      66        
184              
185 64 50       402 local $READER_OPTS = { %$READER_OPTS, %{$opts->{parser_options} || {}} };
  64         635  
186              
187 64 50       348 if (exists $opts->{filename}) {
188             $ctxt->set_filename( $opts->{use_resources}
189             ? Treex::PML::FindInResourcePaths($opts->{filename})
190             : $opts->{filename}
191 64 100       567 );
192             }
193 64         11860 my $reader;
194             my $fh_to_close;
195             # print Dumper($opts),"\n";
196 64 100       601 if (defined $opts->{dom}) {
    100          
    100          
    50          
197 1         13 $reader = XML::LibXML::Reader->new(DOM => delete $opts->{dom}, %$READER_OPTS);
198             } elsif (defined $opts->{fh}) {
199             $reader = XML::LibXML::Reader->new(IO => $opts->{fh}, %$READER_OPTS,
200 22         397 URI => $ctxt->{'_filename'},
201             %$READER_OPTS
202             );
203             } elsif (defined $opts->{string}) {
204             $reader = XML::LibXML::Reader->new(string => $opts->{string}, %$READER_OPTS,
205 8         167 URI => $ctxt->{'_filename'},
206             %$READER_OPTS
207             );
208             } elsif (defined $ctxt->{_filename}) {
209 33 50       161 if ($ctxt->{_filename} eq '-') {
210 0         0 $reader = XML::LibXML::Reader->new(FD => \*STDIN,
211             %$READER_OPTS,
212             );
213             } else {
214 33         341 $fh_to_close = open_uri($ctxt->{_filename});
215             $reader = XML::LibXML::Reader->new(FD => $fh_to_close,
216             URI => $ctxt->{_filename},
217 33         608 %$READER_OPTS,
218             );
219             }
220             } else {
221 0         0 croak("Treex::PML::Instance->load: at least one of filename, fh, string, or dom arguments are required!");
222             }
223 64         14344 eval {
224             # check NS
225 64         5132 $reader->nextElement();
226             my @transform_map =
227             grep {
228 0         0 my $id = $_->{id};
229 0 0 0     0 if (defined($id) and length($id)) {
230 0         0 $_
231             } else {
232 0         0 warn(__PACKAGE__.": Skipping PML transform in ".$config->get_url." (required attribute id missing):".Dumper($_));
233             ()
234 0         0 }
235             }
236 64         408 (eval {
237             ($config and $config->get_root) ? $config->get_root->{transform_map}->values : ()
238 64 100 66     511 });
239 64         573 my $root_element = $reader->localName;
240 64   50     456 my $root_ns = $reader->namespaceURI || '';
241 64 50 33     537 if ($root_ns ne PML_NS
242 0 0 0     0 or grep { (($_->{ns}||'') eq PML_NS and ($_->{root}||'') eq $root_element) } @transform_map) {
      0        
243 0 0 0     0 if ($config and $config->get_root) {
244             # TRANSFORM
245 0         0 $reader->preserveNode;
246 0         0 $reader->finish;
247 0         0 my $dom = $reader->document;
248 0         0 foreach my $transform (@transform_map) {
249 0         0 my $id = $transform->{'id'};
250 0         0 my ($in_xsl) = $transform->{in};
251 0   0     0 my $type = $in_xsl && $in_xsl->{'type'};
252 0 0 0     0 next unless ($type and $type =~ /^(?:xslt|perl|pipe|shell)$/);
253 0         0 my $test = $transform->{'test'};
254 0         0 _debug("Trying transformation rule '$id'");
255 0 0 0     0 if (($test or $transform->{ns} or $transform->{root})
      0        
      0        
      0        
      0        
      0        
      0        
256             and (!$transform->{ns} or $transform->{ns} eq $root_ns)
257             and (!$transform->{root} or $transform->{root} eq $root_element)
258 0         0 and !$test or eval { $dom->find($test) }) {
259 0 0 0     0 if ($type eq 'xslt') {
    0          
    0          
260 0 0       0 die "Buggy libxslt version 10127\n" if XSLT_BUG;
261 0 0       0 if (eval { require XML::LibXSLT; 1 }) {
  0         0  
  0         0  
262 0         0 my $in_xsl_href = URI->new(Encode::encode_utf8($in_xsl->get_member('href')));
263 0 0       0 next unless $in_xsl_href;
264 0         0 _debug("Transforming to PML with XSLT '$in_xsl_href'");
265 0         0 $ctxt->{'_transform_id'} = $id;
266 0         0 my $params = $in_xsl->content;
267 0         0 my %params;
268 0 0       0 %params = map { $_->{'name'} => $_->value } $params->values if $params;
  0         0  
269 0         0 $in_xsl_href = Treex::PML::ResolvePath($config->{'_filename'}, $in_xsl_href, 1);
270 0         0 my $xslt = XML::LibXSLT->new;
271 0   0     0 my $in_xsl_parsed = $xslt->parse_stylesheet_file($in_xsl_href)
272             || die("Cannot locate XSL stylesheet '$in_xsl_href' for transformation $id\n");
273 0         0 $dom = $in_xsl_parsed->transform($dom,%params);
274 0 0 0     0 $dom->setBaseURI($ctxt->{'_filename'}) if $dom and $dom->can('setBaseURI');
275 0 0 0     0 $dom->setURI($ctxt->{'_filename'}) if $dom and $dom->can('setURI');
276 0         0 $reader = XML::LibXML::Reader->new(DOM => $dom);
277 0         0 $reader->nextElement();
278 0         0 last;
279             } else {
280 0         0 warn "Cannot use XML::LibXSLT for transformation!\n";
281             }
282             } elsif ($type eq 'perl') {
283 0         0 my $code = $in_xsl->get_member('command');
284 0 0       0 next unless $code;
285 0         0 _debug("Transforming to PML with Perl code: $code");
286 0         0 $ctxt->{'_transform_id'} = $id;
287 0         0 my $params = $in_xsl->content;
288 0         0 my %params;
289 0 0       0 %params = map { $_->{'name'} => $_->value } $params->values if $params;
  0         0  
290 0         0 $dom = perl_transform($code, $dom, %params);
291 0 0       0 die("Perl-based transformation '$id' failed: $@") if $@;
292 0 0 0     0 die("Perl-based transformation didn't return a XML::LibXML::Document object!\n") unless
293             (blessed($dom) and $dom->isa('XML::LibXML::Document'));
294 0 0 0     0 $dom->setBaseURI($ctxt->{'_filename'}) if $dom and $dom->can('setBaseURI');
295 0 0 0     0 $dom->setURI($ctxt->{'_filename'}) if $dom and $dom->can('setURI');
296 0         0 $reader = XML::LibXML::Reader->new(DOM => $dom);
297 0         0 $reader->nextElement();
298 0         0 last;
299             } elsif ($type eq 'pipe' or $type eq 'shell') {
300 0         0 my $code = $in_xsl->get_member('command');
301 0 0       0 next unless $code;
302 0         0 _debug("Transforming to PML with $type code: $code");
303 0         0 $ctxt->{'_transform_id'} = $id;
304 0         0 my $params = $in_xsl->content;
305 0         0 my @params;
306 0 0       0 @params = grep {defined and length } map { $_->{'name'} => $_->value } $params->values if $params;
  0 0       0  
  0         0  
307 0         0 my $tmp_file_in;
308 0 0       0 if ($type eq 'pipe') {
309 0         0 (my $fh, $tmp_file_in) = File::Temp::tempfile();
310 0         0 $dom->toFH($fh);
311 0         0 close $fh;
312             } else {
313 0         0 push @params, $dom->URI;
314             }
315 0         0 my $tmp_file_out;
316             {
317 0         0 local *OLDIN;
318 0         0 local *OLDOUT;
319 0         0 open OLDOUT,"<&STDOUT";
320 0         0 open OLDIN,"<&STDIN";
321              
322 0 0       0 if ($type eq 'pipe') {
323 0         0 open STDIN, '<', $tmp_file_in;
324             } else {
325 0         0 close STDIN;
326             }
327 0         0 (undef, $tmp_file_out) = File::Temp::tempfile();
328 0         0 open STDOUT, '>', $tmp_file_out;
329 0         0 system($code,@params);
330 0 0       0 unlink $tmp_file_in if $tmp_file_in;
331 0         0 open STDIN,"<&OLDIN";
332 0         0 open STDOUT,">&OLDOUT";
333             }
334             {
335 0 0       0 open(my $fh, '<', $tmp_file_out) or die("Failed to read output from pipe transformation: $code\n");
  0         0  
  0         0  
336 0 0       0 unlink $tmp_file_out if $tmp_file_out;
337 0         0 $reader = XML::LibXML::Reader->new(IO => $fh, URI => $ctxt->{'_filename'});
338             }
339 0         0 $reader->nextElement();
340 0         0 last;
341             }
342             } else {
343 0         0 _debug("failed");
344             }
345             }
346             }
347 0 0 0     0 if (($reader->namespaceURI||'') ne PML_NS) {
348 0   0     0 my $f = $ctxt->{'_filename'} || '';
349 0   0     0 die("Root element of '$f' isn't in PML namespace: '".($reader->localName()||'')."' ".($reader->namespaceURI()||''))
      0        
350             }
351             }
352              
353 64         348 $ctxt->{_root} = read_header($ctxt,$reader,$opts);
354 64         246 my $schema = $ctxt->{'_schema'};
355 64 50       205 unless (ref($schema)) {
356 0         0 die("Instance doesn't provide PML schema!");
357             }
358 64 50 50     425 unless (length($schema->{version}||'')) {
359 0         0 die("PML Schema file ".$ctxt->{'_schema-url'}." does not specify version!");
360             }
361 64 50       403 if (index(SUPPORTED_PML_VERSIONS," ".$schema->{version}." ")<0) {
362 0         0 die("Unsupported PML Schema version ".$schema->{version}." in ".$ctxt->{'_schema-url'});
363             }
364              
365             {
366             # preprocess the options selected_references and selected_keys:
367             # we map the reffile names to reffile id's
368 64         142 my $sel_knit = ($ctxt->{_selected_knits} =
369 64         249 $opts->{selected_knits});
370             my $sel_refs = ($ctxt->{_selected_references} =
371 64         180 $opts->{selected_references});
372 64 50 33     260 croak("Treex::PML::Instance->load: selected_knits must be a Hash ref!")
373             if defined($sel_knit) && ref($sel_knit) ne 'HASH';
374 64 50 33     267 croak("Treex::PML::Instance->load: selected_references must be a Hash ref!")
375             if defined($sel_refs) && ref($sel_refs) ne 'HASH';
376             ($ctxt->{'_selected_knits_ids'},
377             $ctxt->{'_selected_references_ids'}) = map {
378 64         183 my $sel = $_;
  128         228  
379             my $ret = {
380             (defined($sel) ?
381             (map {
382 128 50       352 my $ids = $ctxt->{'_refnames'}->{$_};
  0         0  
383 0         0 my $val = $sel->{$_};
384 0 0       0 map { $_=>$val }
  0 0       0  
385             defined($ids) ? (ref($ids) ? @$ids : ($ids)) : ()
386             } keys %$sel) : ())
387             };
388 128         433 $ret
389             } ($sel_knit,$sel_refs);
390             }
391              
392 64         691 $ctxt->read_reffiles({use_resources=>$opts->{use_resources}});
393 64         318 $ctxt->{'_no_read_trees'} = $opts->{no_trees};
394 64 50       302 local $BUILD_TREES = $opts->{no_trees} ? 0 : 1;
395 64 50       254 local $LOAD_REFFILES = $opts->{no_references} ? 0 : 1;
396 64 50       254 local $KNIT = $opts->{no_knit} ? 0 : $LOAD_REFFILES;
397 64 50       211 local $VALIDATE_CDATA =$opts->{validate_cdata} ? 1 : 0;
398 64 50       211 local $VALIDATE_SEQUENCES =$opts->{ignore_content_patterns} ? 0 : 1;
399 64         175 $ctxt->{'_id-hash'}={};
400              
401 64         305 prepare_handlers($ctxt);
402 64 50 33     607 dump_handlers($ctxt) if $opts->{dump_handlers} or $ENV{PML_COMPILE_DUMP};
403 64         325 load_data($ctxt,$reader,$opts);
404 64         492 while ($reader->read) {
405 0 0       0 if ($reader->nodeType == XML_READER_TYPE_PROCESSING_INSTRUCTION) {
406 0         0 push @{$ctxt->{'_pi'}}, [ $reader->name,$reader->value ];
  0         0  
407             }
408             }
409              
410 64         432 $handlers{'#initialize'}->($ctxt);
411 64         386 $ctxt->{_root} = $handlers{'#root'}->($ctxt->{_root});
412             };
413 64   33 0   99736 ($handlers{'#cleanup'}||sub{})->();
414 64         1015 %handlers=();
415 64 100       532 close_uri($fh_to_close) if defined $fh_to_close;
416 64 50       344 die $@ if $@;
417 64         269 $ctxt->{'_parser'} = undef;
418 64         681 return $ctxt;
419             }
420              
421             ######################################################
422             # $ctxt
423              
424             sub _reader_address {
425 0     0   0 my ($ctxt,$reader)=@_;
426 0         0 my $line_number=$reader->lineNumber;
427 0         0 return " at ".$ctxt->{'_filename'}." line ".$line_number."\n";
428             }
429              
430             sub read_header {
431 64     64 0 226 my ($ctxt,$reader,$opts)=@_;
432              
433             # manually extract the root node
434 64         352 my $root = [XML_READER_TYPE_ELEMENT,
435             $reader->localName,
436             undef,
437             ];
438             # read root node attributes
439 64         278 $root->[XAT_LINE] = 0;
440 64         337 $root->[XAT_ATTRS] = readAttributes($reader);
441 64         163 my $found_head = 0;
442 64         470 while ($reader->read == 1) {
443 93         331 my $type = $reader->nodeType;
444 93 50       428 if ($type == XML_READER_TYPE_TEXT) { # no CDATA
    100          
445 0         0 die "Unexpected content of a root element preceding "._reader_address($ctxt,$reader);
446             } elsif ($type == XML_READER_TYPE_ELEMENT) {
447 64 50 33     660 if ($reader->localName eq 'head' and $reader->namespaceURI eq PML_NS) {
448             # we have head!
449 64         142 $found_head = 1;
450 64         158 last;
451             } else {
452 0         0 die "Unexpected element '".$reader->name."' precedes PML header "._reader_address($ctxt,$reader);
453             }
454             }
455             }
456 64 50       257 unless ($found_head) {
457 0         0 die "Did not find PML element: the document '".$ctxt->{_filename}."' is not a PML instance!";
458             }
459              
460 64         188 my (%references,%named_references);
461 64         287 while ($reader->read == 1) {
462 188 100       884 last if $reader->depth<=1;
463 124         372 my $type = $reader->nodeType;
464 124 100 66     732 if ($type == XML_READER_TYPE_ELEMENT and $reader->namespaceURI eq PML_NS) {
465 90         342 my $name = $reader->localName;
466 90 100       382 if ($name eq 'schema') {
    50          
467 64 50       256 if ($ctxt->{'_schema'}) {
468 0         0 warn "Multiple elements in a PML !";
469 0 0       0 $reader->nextSibling || last;
470 0         0 redo;
471             }
472             # read schema here:
473 64 50       124 my %a = @{ readAttributes($reader) || [] };
  64         196  
474 64         282 my $schema_file = delete $a{href};
475 64 50 33     337 if (defined $schema_file and length $schema_file) {
476 64         767 $schema_file = URI->new(Encode::encode_utf8($schema_file));
477             # print "$schema_file\n";
478 64         4196 $ctxt->{'_schema-url'} = $schema_file; # store the original URL, not the resolved one!
479 64         433 my $schema_path = Treex::PML::ResolvePath($ctxt->{'_filename'},$schema_file,1);
480 64         890 my $key = _get_schema_cache_key($schema_path);
481 64 100       244 if (!($ctxt->{'_schema'}=get_cached_schema($key))) {
482             # print "loading schema $schema_path\n";
483             $ctxt->{'_schema'} =
484             Treex::PML::Factory->createPMLSchema({
485             filename => $schema_path,
486             use_resources => 1,
487             revision_error =>
488 27         167 "Error: ".$ctxt->{'_filename'}." requires different revision of PML schema %f: %e\n",
489             %a, # revision_opts
490             });
491 27 50       1482 cache_schema($key, $ctxt->{'_schema'}) if $CACHE_SCHEMAS;
492             }
493             } else {
494             # inline schema
495             $ctxt->{'_schema'} = Treex::PML::Factory->createPMLSchema({
496             reader=>$reader,
497             base_url => $ctxt->{'_filename'},
498             use_resources => 1,
499             revision_error =>
500 0   0     0 "Error: ".($ctxt->{'_filename'}||'document')." requires different revision of PML schema %f: %e\n",
501             %a, # revision_opts
502             });
503             }
504             } elsif ($name eq 'references') {
505 26 50       209 if ($reader->read) {
506 26         131 while ($reader->depth==3) {
507 41 100 66     366 if ($reader->localName eq 'reffile' and
508             $reader->namespaceURI eq PML_NS) {
509 33 50       78 my %a = @{ readAttributes($reader) || [] };
  33         132  
510 33         209 my ($id,$name,$href) = @a{qw(id name href)};
511 33 50 33     357 if (defined($id) and length($id) and
      33        
      33        
512             defined($href) and length($href)) {
513 33 50 33     168 if (defined $name and length $name) {
514 33         128 my $prev_ids = $named_references{ $name };
515 33 50       121 if (defined $prev_ids) {
516 0 0       0 if (ref($prev_ids)) {
517 0         0 push @$prev_ids,$id;
518             } else {
519 0         0 $named_references{ $name }=Treex::PML::Factory->createAlt([$prev_ids,$id],1);
520             }
521             } else {
522 33         128 $named_references{ $name } = $id;
523             }
524             }
525             # Encode: all filenames must(!) be bytes
526             $references{$id} = Treex::PML::ResolvePath
527             ($ctxt->{'_filename'},
528             URI->new(Encode::encode_utf8($href)),
529 33         363 $opts->{use_resources});
530             # Resources are not used for non-readas references,
531             # though, they must be handled manually.
532             } else {
533 0         0 warn "Missing id or href attribute on a : ignoring\n";
534             }
535             }
536 41 100       1299 $reader->nextSibling || last;
537             }
538             }
539             }
540             }
541             }
542             $ctxt->{'_schema'} or
543 64 50       304 die "Did not find element in PML : the document '".$ctxt->{_filename}."' is not a valid PML instance!";
544 64         224 $ctxt->{'_references'} = \%references;
545 64         194 $ctxt->{'_refnames'} = \%named_references;
546 64         328 return $root;
547             }
548              
549             sub prepare_handlers {
550 64     64 0 222 my ($ctxt,$opts)=@_;
551 64         162 %handlers=();
552 64         197 my $schema = $ctxt->{'_schema'};
553 64         286 my $key=_get_handlers_cache_key($schema);
554 64         288 my $cached = get_cached_handlers($key);
555 64 100       232 if ($cached) {
556 37         1009 %handlers= @$cached;
557             } else {
558 27         147 compile_schema($schema);
559 27 50       1276 cache_handlers($key,[%handlers]) if $CACHE_HANDLERS;
560             }
561             }
562              
563             sub dump_handlers {
564 0     0 0 0 my $dir = '.pml_compile.d';
565 0 0 0     0 (-d $dir) || mkdir($dir) || die "Can't dump to $dir: $!\n";
566             # print "created $dir\n";
567 0         0 for my $f (keys %src) {
568 0         0 my $dump_file = File::Spec->catfile($dir,$f);
569 0 0       0 open (my $fh, '>:utf8', $dump_file)
570             || die "Can't write to $dump_file: $!\n";
571 0         0 my $sub = $src{$f};
572 0         0 $sub=~s/^\s*#line[^\n]*\n//;
573 0         0 print $fh ($sub);
574 0         0 close $fh;
575             }
576             }
577              
578             sub load_data {
579 64     64 0 174 my ($ctxt,$reader)=@_;
580 64         227 my $root = $ctxt->{_root};
581 64         131 my ($children);
582 64 50       1002 $reader->read if $reader->nodeType == XML_READER_TYPE_END_ELEMENT;
583 64 50       256 if ($HAVE_XS) {
584 0         0 my %ns;
585 0         0 $children = XML::CompactTree::XS::readLevelToPerl(
586             $reader,
587             $XTC_FLAGS,
588             \%ns
589             );
590 0   0     0 $root->[XAT_NS]=$ns{(PML_NS)} || -1;
591             } else {
592 64         125 my %ns;
593 64         416 $children = XML::CompactTree::readLevelToPerl(
594             $reader,
595             $XTC_FLAGS,
596             \%ns
597             );
598 64   50     6040566 $root->[XAT_NS]=$ns{(PML_NS)} || -1;
599             }
600              
601 64         290 $root->[XAT_CHILDREN]=$children;
602             # print Dumper($root);
603              
604             # print Dumper({references => $ctxt->{'_references'},
605             # refnames => $ctxt->{'_refnames'}});
606 64         201 return $root;
607             }
608              
609             sub _set_trees_seq {
610 30     30   3941 my ($ctxt,$type,$data)=@_;
611 30         118 $ctxt->{'_pml_trees_type'} = $type;
612 30   33     233 my $trees = $ctxt->{'_trees'} ||= Treex::PML::Factory->createList;
613 30   33     200 my $prolog = $ctxt->{'_pml_prolog'} ||= Treex::PML::Factory->createSeq;
614 30   33     170 my $epilog = $ctxt->{'_pml_epilog'} ||= Treex::PML::Factory->createSeq;
615 30         71 my $phase = 0; # prolog
616 30         90 foreach my $element (@$data) {
617 495         906 my $val = $element->[1];
618 495 100       995 if (UNIVERSAL::DOES::does($val,'Treex::PML::Node')) {
619 476 100       6211 if ($phase == 0) {
620 30         74 $phase = 1;
621             }
622 476 50       774 if ($phase == 1) {
623 476         1094 $val->{'#name'} = $element->[0]; # manually delegate_name on this element
624 476         1022 push @$trees, $val;
625             } else {
626 0         0 $prolog->push_element_obj($element);
627             }
628             } else {
629 19 50       696 if ($phase == 1) {
630 0         0 $phase = 2; # start epilog
631             }
632 19 50       62 if ($phase == 0) {
633 19         87 $prolog->push_element_obj($element);
634             } else {
635 0         0 $epilog->push_element_obj($element);
636             }
637             }
638             }
639             }
640              
641             sub readAttributes {
642 161     161 0 411 my ($r)=@_;
643 161         277 my @attrs;
644 161         289 my ($prefix,$name);
645 161 50       760 if ($r->moveToFirstAttribute==1) {
646 161         294 do {{
647 227         403 $prefix = $r->prefix;
  227         648  
648 227         656 $name = $r->localName;
649 227 100 33     2399 push @attrs, ($name,$r->value) unless ($prefix and $prefix eq 'xmlns') or (!$prefix and $name eq 'xmlns');
      66        
      66        
650             }} while ($r->moveToNextAttribute==1);
651 161         441 $r->moveToElement;
652             }
653 161         997 \@attrs;
654             }
655              
656              
657             sub _paste_last_code {
658 17     17   70 my ($node,$prev,$p)=@_;
659 17         140 return qq`
660             #$node\->{'$Treex::PML::Node::rbrother'}=undef;
661             $prev\->{'$Treex::PML::Node::rbrother'}=$node;
662             weaken( $node\->{'$Treex::PML::Node::lbrother'} = $prev );
663             weaken( $node\->{'$Treex::PML::Node::parent'} = $p );
664             `;
665             }
666             sub _paste_first_code {
667 17     17   47 my ($node,$p)=@_;
668 17         159 return qq`
669             #$node\->{'$Treex::PML::Node::rbrother'}=undef;
670             #$node\->{'$Treex::PML::Node::lbrother'}=undef;
671             $p\->{'$Treex::PML::Node::firstson'}=$node;
672             weaken( $node\->{'$Treex::PML::Node::parent'} = $p );
673             `;
674             }
675              
676             sub hash_id_code {
677 47     47 0 136 my ($key,$value)=@_;
678 47         271 return q`
679             for (`.$key.q`) {
680             if (defined and length) {
681             if (exists($ID_HASH->{$ID_PREFIX.$_}) and
682             $ID_HASH->{$ID_PREFIX.$_} != `.$value.q`) {
683             warn("Duplicated ID '$_'");
684             }
685             weaken( $ID_HASH->{$ID_PREFIX.$_} = `.$value.q` );
686             }
687             }`
688             }
689              
690             sub _fix_id_member {
691 24     24   59 my ($decl)=@_;
692 24 50       88 return unless $decl;
693 24         103 my ($idM) = $decl->find_members_by_role('#ID');
694 24 50       97 if ($idM) {
695             # what follows is a hack fixing buggy PDT 2.0 schemas
696 24         89 my $cdecl = $idM->get_content_decl(1); # no_resolve
697 24 100 33     105 if ($cdecl and $cdecl->get_decl_type == PML_CDATA_DECL and $cdecl->get_format eq 'ID') {
    50 66        
698 6         24 $cdecl->set_format('PMLREF');
699             } elsif ($cdecl = $idM->get_content_decl()) {
700 18 50 33     67 if ($cdecl and $cdecl->get_decl_type == PML_CDATA_DECL and $cdecl->get_format eq 'ID') {
      33        
701 0         0 warn "Trying to knit object of type '".$decl->get_decl_path."' which has an #ID-attribute ".
702             "'".$idM->get_name."' declared as . ".
703             "Note that the data-type for #ID-attributes in objects knitted as DOM should be ".
704             " (Hint: redeclare with for imported types).";
705             }
706             }
707             }
708 24         96 return $idM;
709             }
710              
711             sub knit_code {
712 10     10 0 34 my ($decl,$assign,$fail)=@_;
713 10         23 my $sub = q`
714             if ($ref) {
715             $ref =~ s/^(?:(.*?)\#)//;
716             my $file_id = $1||'';
717             my $do_knit=$selected_knits->{$file_id};
718             unless (defined($do_knit) and $do_knit==0) {
719             my $target;
720             if (length $file_id) {
721             my $f = $parsed_reffile->{ $file_id };
722             if (ref $f) {
723             if (UNIVERSAL::DOES::does($f,'Treex::PML::Instance')) {
724             $target = $f->{'_id-hash'}->{$ref};
725             $target->{'#knit_prefix'}=$file_id;
726             } else { # DOM`;
727 10 50       33 if ($decl) {
728 10         37 my $idM = _fix_id_member($decl);
729 10   33     49 my $idM_name = $idM && $idM->get_name;
730 10         28 my $decl_path = $decl->get_decl_path; $decl_path =~ s/^!//;
  10         47  
731 10 50       98 $sub .= q`
732             my $dom_node = $ref_index->{$file_id}{$ref} || $f->getElementsById($ref);
733             if (defined $dom_node) {
734             $target = $ID_HASH->{$ID_PREFIX.$file_id.'#'.$ref};
735             if (!defined $target) {
736             my $p = $ID_PREFIX;
737             $ID_PREFIX.=$file_id.'#';
738             my $r = XML::LibXML::Reader->new(string=>''.$dom_node->toString.'');
739             $r->nextElement;
740             # print $r, $dom_node->toString,"\n";
741             my %ns;
742             my $tree = XML::CompactTree`.($HAVE_XS ? '::XS' : '').q`::readSubtreeToPerl($r,`.$XTC_FLAGS.q`,\%ns);
743             my $index = $pml_ns_index;
744             $pml_ns_index = $ns{'`.PML_NS.q`'} || -1;
745             # print "index: $pml_ns_index\n";
746             # print Dumper($tree->[0][XAT_CHILDREN][0]);
747             $target = $handlers{'`.$decl_path.q`'}->($tree->[XAT_CHILDREN][0]);`;
748 10 50       39 if ($idM) {
749 10         49 $sub .= q`
750             $target->{`.$idM_name.q`}=$file_id.'#'.$target->{`.$idM_name.q`} if $target;`;
751             }
752 10         22 $sub .= q`
753             $pml_ns_index = $index;
754             $weaken=0;
755             $ID_PREFIX=$p;
756             }
757             }`;
758             } else {
759 0         0 $sub .= q`
760             warn("DOM knit error: knit content type not declared in the schema!\n");`;
761             }
762 10         36 $sub.=q`
763             }
764             } else {
765             warn("warning: KNIT failed: document '$file_id' not loaded\n");
766             }
767             } else {
768             $target = $ID_HASH->{$ID_PREFIX.$ref};
769             }
770             if (ref $target) {`.$assign.q`
771             } else {
772             warn("warning: KNIT failed: ID $ref not found in reffile '$file_id'\n");`.$fail.q`
773             }
774             }
775             }
776             `;
777 10         101 return $sub;
778             }
779              
780             sub _report_error {
781 0     0   0 my ($err)=@_;
782 0 0       0 if ($STRICT) {die $err} else {warn $err};
  0         0  
  0         0  
783             }
784             sub _unhandled {
785 0     0   0 my ($what,$pml_file,$el,$path)=@_;
786 0         0 _report_error( "Error: $what not declared for type '$path' at ".$pml_file." line ".$el->[XAT_LINE] );
787 0     0   0 return sub{};
788             }
789              
790             sub compile_schema {
791 27     27 0 62 my ($schema)=@_;
792 27         157 my $schema_name = $schema->get_root_decl->get_name;
793 27         75 my ($ctxt,$pml_file,$pml_ns_index,$ID_HASH,$ID_PREFIX,$selected_knits,$ref_index,$parsed_reffile,$trees_type,$have_trees);
794             $handlers{'#cleanup'}= sub {
795 64     64   442 undef $_ for ($ctxt,$pml_file,$pml_ns_index,$ID_HASH,$ID_PREFIX,$selected_knits,$ref_index,$parsed_reffile);
796 27         245 };
797             $handlers{'#initialize'}= sub {
798 64     64   172 my ($instance)=@_;
799 64         191 $ctxt = $instance;
800 64         235 $pml_file = $instance->{'_filename'};
801 64         173 $pml_ns_index = $instance->{_root}->[XAT_NS];
802 64         163 $selected_knits = $instance->{_selected_knits_ids};
803 64         170 $ref_index = $instance->{'_ref-index'};
804 64         183 $ID_HASH = $instance->{'_id-hash'};
805 64   50     376 $ID_PREFIX = $instance->{'_id_prefix'} || '';
806 64         163 $parsed_reffile=$instance->{'_ref'};
807 64         185 $have_trees = 0;
808 27         206 };
809             $schema->for_each_decl(sub {
810 1244     1244   2160 my ($decl)=@_;
811             # no warnings 'uninitialized';
812 1244         3610 my $decl_type=$decl->get_decl_type;
813 1244         3225 my $path = $decl->get_decl_path;
814 1244 100       4069 $path =~ s/^!// if $path;
815 1244 100 100     6095 return if $decl_type == PML_ATTRIBUTE_DECL ||
      100        
      100        
816             $decl_type == PML_MEMBER_DECL ||
817             $decl_type == PML_TYPE_DECL ||
818             $decl_type == PML_ELEMENT_DECL;
819 555 100       2568 if ($decl_type == PML_ROOT_DECL) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
820 27         128 my $name = $decl->get_name;
821 27         153 my $cpath = $decl->get_content_decl->get_decl_path;
822 27         176 $cpath =~ s/^!//;
823 27         86 my $src = $schema_name.'__generated_read_root';
824 27         91 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
825             sub {
826             my ($p)=@_;
827             unless (ref($p) and
828             $p->[XAT_TYPE] == XML_READER_TYPE_ELEMENT and
829             $p->[XAT_NS] == $pml_ns_index and
830             $p->[XAT_NAME] eq '`.$name.q`'
831             ) {
832             die q(Did not find expected root element '`.$name.q` in ').$pml_file;
833             }
834             return ($handlers{ '`.$cpath.q`' })->($p);
835             }`;
836 27         215 $src{$src}=$sub;
837 27 50       11437 $handlers{'#root'}=eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  27         618  
838             } elsif ($decl_type == PML_STRUCTURE_DECL) {
839             # print $path,"\n";
840 100         230 my $src = $schema_name.'__generated_read_structure@'.$path;
841 100         282 $src=~y{/}{@};
842 100         233 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
843             sub {
844             my ($p)=@_;
845             my $a=$p->[XAT_ATTRS];
846             my $c=$p->[XAT_CHILDREN];
847             # print join(",",map {defined($_) ? $_ : 'undef'} $p->[XAT_NAME],$p->[XAT_LINE],@$p)."\n";
848             my (%s,$k,$v);`;
849 100 50       260 if ($VALIDATE_CDATA) {
850 0         0 $sub .= q`
851             if ($a) {
852             while (@$a) {
853             $k=shift @$a;
854             $v=shift @$a;
855             $s{ $k } = ($handlers{ '`.$path.q`/'.$k }||_unhandled("attribute member '$k'",$pml_file,$p,'`.$path.q`'))->( $v );
856             }
857             }`;
858             } else {
859 100         222 $sub .= q`
860             %s = @$a if $a;`;
861             }
862 100         324 $sub .= q`
863             if ($c) {
864             for my $el (@$c) {
865             unless (ref($el) and $el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT
866             and $el->[XAT_NS] == $pml_ns_index) {
867             if (!ref($el) || $el->[XAT_TYPE] == XML_READER_TYPE_TEXT || $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
868             warn q(Ignoring unexpected text content ').$el->[XAT_VALUE].q(' in a structure '`.$path.q`');
869             } elsif ($el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT) {
870             warn q(Ignoring unexpected element ').$el->[XAT_NAME].q(' in a structure '`.$path.q`');
871             }
872             next;
873             }
874             $k = $el->[XAT_NAME];
875             $s{ $k } = ($handlers{ '`.$path.q`/'.$k }||_unhandled("member '$k'",$pml_file,$el,'`.$path.q`'))->($el);
876             }
877             }`;
878 100         170 my ($id, $children_member);
879 100         355 for my $member ($decl->get_members) {
880 377         926 my $mdecl = $member->get_content_decl;
881 377 100 66     884 if ($member->is_required) {
    100          
882 87         218 my $name = $member->get_name;
883 87 50 33     404 if ($mdecl && $mdecl->get_role eq '#TREES') {
884             # this is a bit of a hack:
885             # in this case, if the trees have been read from the member, the member handler returns
886             # a stub value '#TREES' that will get deleted
887 0         0 $sub.=q`
888             ref or ($_ eq '#TREES' and delete($s{'`.$name.q`'})) or warn q(Missing required member '`.$name.q`' in structure '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE] for $s{'`.$name.q`'};`;
889             } else {
890 87         227 $sub.=q`
891             ref or defined and length or warn q(Missing required member '`.$name.q`' in structure '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE] for $s{'`.$name.q`'};`;
892             }
893             } elsif ($mdecl and $mdecl->get_decl_type == PML_CONSTANT_DECL) {
894             $sub.=q`
895 2         14 defined or $_="`.quotemeta($mdecl->{value}).q`" for $s{'`.$member->get_name.q`'};`;
896             }
897 377         796 my $role = $member->get_role;
898 377 100 66     1068 if ($KNIT and !$role) {
899 324   33     620 $mdecl ||= $member->get_content_decl;
900 324 100 66     794 if ($mdecl and $mdecl->get_decl_type == PML_LIST_DECL and
      100        
901             $mdecl->get_role eq '#KNIT') {
902 9         29 my $mname = $member->get_name;
903 9         48 my $knit_name = $mname; $knit_name=~s/\.rf$//;
  9         79  
904             # warn("#KNIT on list not yet implemented: ".$member->get_name."\n");
905 9         71 $sub .=q`
906             my $ref_list = $s{'`.$mname.q`'};
907             if ($ref_list) {
908             my (@knit_list,@weaken,$weaken);
909             for my $ref (@$ref_list) {
910             $weaken=1;`
911             .knit_code($mdecl->get_knit_content_decl(),q`
912             push @knit_list, $target;
913             push @weaken, $weaken;`,
914             q`undef $ref_list; last;`)
915             .q`
916             }
917             if (defined $ref_list) {
918             my $i=0;
919             for (@knit_list) {
920             weaken($_) if $weaken[$i++];
921             }
922             $s{'`.$knit_name.q`'}=Treex::PML::Factory->createList(\@knit_list);`;
923 9 50       32 if ($mname ne $knit_name) {
924 9         30 $sub .= q`delete $s{'`.$mname.q`'};`;
925             }
926 9         21 $sub .= q`
927             } else {
928             warn("KNIT failed on list '`.$mname.q`'");
929             }
930             }`;
931 9         28 next;
932             }
933             }
934 368 100 100     1913 if ($role eq '#ID') {
    100 66        
    100 66        
    100          
935 37         98 $id = $member->get_name;
936             } elsif (!$trees_type and $role eq '#TREES' and $BUILD_TREES) {
937 3   33     11 $mdecl ||= $member->get_content_decl;
938 3         12 my $mtype = $mdecl->get_decl_type;
939 3 50       10 if ($mtype == PML_LIST_DECL) {
    0          
940             # check that content type is of role #NODE
941 3         9 my $cmdecl = $mdecl->get_content_decl;
942 3         9 my $cmdecl_type = $cmdecl->get_decl_type;
943 3 50 50     14 unless ($cmdecl && ($cmdecl->get_role||'') eq '#NODE' &&
      33        
      33        
      33        
944             ($cmdecl_type == PML_STRUCTURE_DECL or
945             $cmdecl_type == PML_CONTAINER_DECL)) {
946 0         0 _report_error("List '$path' with role #TREES may only contain structures or containers with role #NODE in schema ".
947             $decl->get_schema->get_url."\n");
948             }
949 3         7 $trees_type = $mdecl;
950 3         10 $sub .= q`
951             unless ($have_trees) {
952             $ctxt->{'_pml_trees_type'} = $trees_type;
953             $have_trees=1;
954             $ctxt->{'_trees'} = delete $s{'`.$member->get_name.q`'};
955             }`;
956             } elsif ($mtype == PML_SEQUENCE_DECL) {
957 0         0 $trees_type = $mdecl;
958 0         0 $sub .= q`
959             unless ($have_trees) {
960             $have_trees=1;
961             defined($_) && _set_trees_seq($ctxt,$trees_type,$_->elements_list) for (delete $s{'`.$member->get_name.q`'});
962             }`;
963             } else {
964 0         0 _report_error("#TREES member '$path/".$member->get_name."' is neither a list nor a sequence in schema ".$member->get_schema->get_url."\n");
965             }
966             } elsif ($role eq '#CHILDNODES') {
967 7 50       23 if ($children_member) {
968 0         0 _report_error("#CHILDNODES role defined on multiple members of type '$path': '$children_member' and '".$member->get_name."' in schema ".$member->get_schema->get_url."\n");
969             } else {
970 7         25 $children_member=$member->get_name;
971             }
972             } elsif ($role eq '#KNIT' and $KNIT) {
973 1         4 my $mname = $member->get_name;
974 1         3 my $knit_name = $mname; $knit_name=~s/\.rf$//;
  1         9  
975 1 50       5 $sub .= q`
976             my $ref = $s{'`.$mname.q`'}; my $weaken = 1;`
977             .knit_code($member->get_knit_content_decl,q`
978             if ($weaken) {
979             weaken( $s{'`.$knit_name.q`'}=$target );
980             } else {
981             $s{'`.$knit_name.q`'}=$target;
982             } `.
983             ($mname ne $knit_name ? q`delete $s{'`.$mname.q`'};` : ''), '');
984             }
985             }
986 100 100 66     431 if ($decl->get_role eq '#NODE' and $BUILD_TREES) {
987 7         23 $sub .= q`
988             my $node = Treex::PML::Factory->createTypedNode($decl,\%s,1);
989             # my $node = bless \%s, 'Treex::PML::Node';
990             # $node->{`.$Treex::PML::Node::TYPE.q`}=$decl;`;
991 7 50       20 if ($children_member) {
992 7         34 my $cdecl = $decl->get_member_by_name($children_member)->get_content_decl;
993 7         23 my $ctype = $cdecl->get_decl_type;
994 7 50       18 if ($ctype == PML_LIST_DECL) {
    0          
995 7         21 my $cmdecl = $cdecl->get_content_decl;
996 7         23 my $cmdecl_type = $cmdecl->get_decl_type;
997 7 50 33     18 unless ($cmdecl->get_role eq '#NODE' &&
      33        
998             ($cmdecl_type == PML_STRUCTURE_DECL or
999             $cmdecl_type == PML_CONTAINER_DECL)) {
1000 0         0 _report_error("List '$path' with role #CHILDNODES may only contain structures or containers with role #NODE in schema '".
1001             $decl->get_schema->get_url."'; got ".$cmdecl->get_decl_type_str." (".$cmdecl->get_decl_path.") with role '".$cmdecl->get_role."' instead!\n");
1002             }
1003 7         35 $sub .= q`
1004             my $content = delete $node->{'`.$children_member.q`'};
1005             if ($content) {
1006             my $prev;
1007             foreach my $son (@{ $content }) {
1008             if ($prev) {
1009             `._paste_last_code(qw($son $prev $node)).q`
1010             } else {
1011             `._paste_first_code(qw($son $node)).q`
1012             }
1013             $prev = $son;
1014             }
1015             }`;
1016             } elsif ($ctype == PML_SEQUENCE_DECL) {
1017 0         0 for my $edecl ($cdecl->get_elements) {
1018 0         0 my $cmdecl = $edecl->get_content_decl;
1019 0         0 my $cmdecl_type = $cmdecl->get_decl_type;
1020 0 0 0     0 unless ($cmdecl->get_role eq '#NODE' &&
      0        
1021             ($cmdecl_type == PML_STRUCTURE_DECL or
1022             $cmdecl_type == PML_CONTAINER_DECL)) {
1023 0         0 _report_error("Sequence '$path' with role #CHILDNODES may only contain structures or containers with role #NODE in schema '".
1024             $decl->get_schema->get_url."'; got ".$cmdecl->get_decl_type_str." (".$cmdecl->get_decl_path.") with role '".$cmdecl->get_role."' instead!\n");
1025             }
1026             }
1027 0         0 $sub .= q`
1028             my $content = delete $node->{'`.$children_member.q`'};
1029             if ($content) {
1030             # $content->delegate_names('#name');
1031             foreach my $element (@{$content->[0]}) { # manually delegate
1032             $element->[1]{'#name'} = $element->[0]; # store element's name in key $key of its value
1033             }
1034             my $prev;
1035             foreach my $son (map $_->[1], @{$content->[0]}) { # $content->values
1036             if ($prev) {
1037             `._paste_last_code(qw($son $prev $node)).q`
1038             } else {
1039             `._paste_first_code(qw($son $node)).q`
1040             }
1041             $prev = $son;
1042             }
1043             }`;
1044             } else {
1045 0         0 _report_error("Role #CHILDNODES can only occur on a structure member of type list or sequence, not on ".$cdecl->get_decl_type_str." '$path' in schema ".$cdecl->get_schema->get_url."\n");
1046             }
1047             }
1048             } else {
1049 93         248 $sub.=q`
1050             my $node = Treex::PML::Factory->createStructure(\%s,1);
1051             # my $node = bless \%s, 'Treex::PML::Struct';
1052             `;
1053             }
1054 100 100       242 if (defined $id) {
1055 37         193 $sub.=hash_id_code(qq(\$s{'$id'}),'$node');
1056             }
1057 100         195 $sub.=q`
1058             return $node;
1059             }`;
1060             # print $sub;
1061 100         650 $src{$src}=$sub;
1062 100 50       69185 $handlers{$path} = eval($sub); die _nl($sub)."\n".$@.' ' if $@;
  100         2392  
1063             } elsif ($decl_type == PML_CONTAINER_DECL) {
1064 39         71 my %attributes;
1065 39         179 @attributes{ map $_->get_name, $decl->get_attributes } = ();
1066 39         197 my $cdecl = $decl->get_content_decl;
1067 39   33     217 my $cpath = $cdecl && $cdecl->get_decl_path;
1068 39 50       261 $cpath=~s/^!// if $cpath;
1069 39         116 my $src = $schema_name.'__generated_read_container@'.$path;
1070 39         104 $src=~y{/}{@};
1071 39         87 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1072             sub {
1073             my ($p)=@_;
1074             my $a=$p->[XAT_ATTRS];
1075             my $c=$p->[XAT_CHILDREN];
1076             my (%s,$k,$v,$content,@a_rest);
1077             if ($a) {
1078             while (@$a) {
1079             $k=shift @$a;
1080             $v=shift @$a;
1081             if (exists $attributes{$k}) {`;
1082 39 50       114 if ($VALIDATE_CDATA) {
1083 0         0 $sub .= q`
1084             $s{ $k } = ($handlers{ '`.$path.q`/'.$k }||_unhandled("attribute '$k'",$pml_file,$p,'`.$path.q`'))->( $v );`;
1085             } else {
1086 39         111 $sub .= q`
1087             $s{ $k } = $v;`;
1088             }
1089 39         113 $sub .= q`
1090             } else {
1091             push @a_rest, $k, $v;
1092             }
1093             }
1094             }
1095             $p->[XAT_ATTRS]=\@a_rest;`;
1096 39 50       105 if ($cdecl) {
1097 39         90 $sub .= q`
1098             $content = $handlers{ '`.$cpath.q`' }->($p);`;
1099             } else {
1100 0         0 $sub .= q`
1101             !$c or !grep { !($_->[XAT_TYPE] == XML_READER_TYPE_WHITESPACE or $_->[XAT_TYPE] == XML_READER_TYPE_SIGNIFICANT_WHITESPACE) } @$c or _report_error(qq(Unexpected content of an empty container type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);`;
1102             }
1103 39         83 my $id;
1104 39         115 for my $member ($decl->get_attributes) {
1105 50 100       158 if ($member->is_required) {
1106 18         58 my $name = $member->get_name;
1107 18         87 $sub.=q`
1108             ref or defined and length or _report_error(q(missing required attribute '`.$name.q`' in container '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]) for $s{'`.$name.q`'};`;
1109             }
1110 50 100       186 if ($member->get_role eq '#ID') {
1111 10         37 $id = $member->get_name;
1112             }
1113             }
1114 39 100 66     162 if ($decl->get_role eq '#NODE' and $BUILD_TREES) {
1115 17         45 $sub .= q`
1116             my $node = Treex::PML::Factory->createTypedNode($decl,\%s,1);
1117             # my $node = bless \%s, 'FSNode';
1118             # $node->{`.$Treex::PML::Node::TYPE.q`}=$decl;`;
1119 17 100 100     98 if ($cdecl and ($cdecl->get_role||'') eq '#CHILDNODES') {
    50 66        
1120 10         47 my $ctype = $cdecl->get_decl_type;
1121 10 100       50 if ($ctype == PML_LIST_DECL) {
    50          
1122 2         11 my $cmdecl = $cdecl->get_content_decl;
1123 2         15 my $cmdecl_type = $cmdecl->get_decl_type;
1124 2 50 33     10 unless ($cmdecl->get_role eq '#NODE' &&
      33        
1125             ($cmdecl_type == PML_STRUCTURE_DECL or
1126             $cmdecl_type == PML_CONTAINER_DECL)) {
1127 0         0 _report_error("List '$path' with role #CHILDNODES may only contain structures or containers with role #NODE in schema '".
1128             $decl->get_schema->get_url."'; got ".$cmdecl->get_decl_type_str." (".$cmdecl->get_decl_path.") with role '".$cmdecl->get_role."' instead!\n");
1129             }
1130 2         12 $sub .= q`
1131             if ($content) {
1132             my $prev;
1133             foreach my $son (@{ $content }) {
1134             if ($prev) {
1135             `._paste_last_code(qw($son $prev $node)).q`
1136             } else {
1137             `._paste_first_code(qw($son $node)).q`
1138             }
1139             $prev = $son;
1140             }
1141             }`;
1142             } elsif ($ctype == PML_SEQUENCE_DECL) {
1143 8         37 for my $edecl ($cdecl->get_elements) {
1144 12 50       39 my $cmdecl = $edecl->get_content_decl or
1145             _report_error("Element '".$edecl->get_name."' of sequence '$path' has no content type declaration");
1146 12         31 my $cmdecl_type = $cmdecl->get_decl_type;
1147 12 50 33     45 unless ($cmdecl->get_role eq '#NODE' &&
      33        
1148             ($cmdecl_type == PML_STRUCTURE_DECL or
1149             $cmdecl_type == PML_CONTAINER_DECL)) {
1150 0         0 _report_error("Sequence '$path' with role #CHILDNODES may only contain structures or containers with role #NODE in schema '".
1151             $decl->get_schema->get_url."'; got ".$cmdecl->get_decl_type_str." (".$cmdecl->get_decl_path.") with role '".$cmdecl->get_role."' instead!\n");
1152             }
1153             }
1154 8         37 $sub .= q`
1155             if ($content) {
1156             # $content->delegate_names('#name');
1157             foreach my $element (@{$content->[0]}) { # manually delegate
1158             $element->[1]{'#name'} = $element->[0]; # store element's name in key $key of its value
1159             }
1160             my $prev;
1161             foreach my $son (map $_->[1], @{$content->[0]}) { # $content->values
1162             if ($prev) {
1163             `._paste_last_code(qw($son $prev $node)).q`
1164             } else {
1165             `._paste_first_code(qw($son $node)).q`
1166             }
1167             $prev = $son;
1168             }
1169             }`;
1170             } else {
1171 0         0 _report_error("Role #CHILDNODES can only occur on a container content type if it is a list or sequence, not on a ".$cdecl->get_decl_type_str." '".$path."' in schema ".$cdecl->get_schema->get_url."\n");
1172             }
1173             } elsif ($cdecl) {
1174 7         17 $sub .= q`
1175             $node->{'#content'} = $content if $content;`;
1176             }
1177             } else {
1178 22         53 $sub.=q`
1179             my $node = Treex::PML::Factory->createContainer($content,\%s,1);
1180             # $s{'#content'}=$content if $content;
1181             # my $node = bless \%s, 'Treex::PML::Container';`;
1182             }
1183 39 100       113 if (defined $id) {
1184 10         50 $sub.=hash_id_code(qq(\$s{'$id'}),'$node');
1185             }
1186 39         83 $sub.=q`
1187             return $node;
1188             }`;
1189             # print $sub;
1190 39         189 $src{$src}=$sub;
1191 39 50       19648 $handlers{$path} = eval($sub); die _nl($sub)."\n".$@.' ' if $@;
  39         926  
1192             } elsif ($decl_type == PML_SEQUENCE_DECL) {
1193 39         112 my $src = $schema_name.'__generated_read_sequence@'.$path;
1194 39         103 $src=~y{/}{@};
1195 39         138 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1196             sub {
1197             my ($p)=@_;
1198             my $c=$p->[XAT_CHILDREN];
1199             return undef unless $c and @$c;
1200             my @seq;
1201             my $k;
1202             for my $el (@$c) {
1203             if (ref($el) and $el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT
1204             and $el->[XAT_NS] == $pml_ns_index) {
1205             # print "element: $el->[XAT_NAME]\n";
1206             $k = $el->[XAT_NAME];
1207             push @seq, bless [$k, ($handlers{ '`.$path.q`/'.$k }||_unhandled("element '$k'",$pml_file,$el,'`.$path.q`'))->($el)], 'Treex::PML::Seq::Element';`;
1208 39 100       142 if ($decl->is_mixed) {
1209 1         4 $sub .= q`
1210             } elsif (!ref($el)) {`;
1211 1         6 $sub .= q`
1212             push @seq, bless ['#TEXT',$el], 'Treex::PML::Seq::Element';
1213             } elsif ($el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA
1214             or $el->[XAT_TYPE] == XML_READER_TYPE_WHITESPACE or $el->[XAT_TYPE] == XML_READER_TYPE_SIGNIFICANT_WHITESPACE) {
1215             push @seq, bless ['#TEXT',$el->[XAT_VALUE]], 'Treex::PML::Seq::Element';
1216             }`;
1217             } else {
1218 38         91 $sub .= q`
1219             } elsif (!ref($el) or $el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
1220             _report_error(q(Unexpected text content in a non-mixed sequence '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1221             }`;
1222             }
1223 39         109 $sub .= q`
1224             }`;
1225 39         125 my $content_pattern = $decl->get_content_pattern;
1226 39 100 66     230 if ($VALIDATE_SEQUENCES and $content_pattern) {
1227 9         61 my $re = Treex::PML::Seq::content_pattern2regexp($content_pattern);
1228 9         36 $sub .= q`
1229             unless (join('',map '<'.$_->[0].'>',@seq) =~ m{^`.$re.q`$}ox) {
1230             warn("Sequence content (".join(",",map $_->[0], @seq).") does not follow the pattern `.quotemeta($content_pattern).q` in ".$pml_file.' line '.$p->[XAT_LINE]);
1231             }`;
1232             }
1233 39 50 100     230 if (!$trees_type and $decl->get_role eq '#TREES' and $BUILD_TREES) {
      66        
1234 10         22 $trees_type = $decl;
1235 10         61 $sub .= q`
1236             unless ($have_trees) {
1237             $have_trees=1;
1238             _set_trees_seq($ctxt,$trees_type,\@seq);
1239             return;
1240             }`;
1241             }
1242 39 100       114 if ($content_pattern) {
1243 9         39 $sub .= q`
1244             return Treex::PML::Factory->createSeq(\@seq, "`.quotemeta($content_pattern).q`",1);
1245             }`;
1246             } else {
1247 30         60 $sub .= q`
1248             return Treex::PML::Factory->createSeq(\@seq, undef, 1);
1249             }`;
1250             }
1251 39         162 $src{$src}=$sub;
1252 39 50       20388 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  39         847  
1253             } elsif ($decl_type == PML_LIST_DECL) {
1254             # print $path."\t@".$decl->get_decl_type_str,"\n";
1255 45 50       187 my $cdecl = $decl->get_content_decl
1256             or croak("Invalid PML Schema: list type without content: ",$decl->get_decl_path);
1257 45         152 my $cpath = $cdecl->get_decl_path;
1258 45         186 $cpath=~s/^!//;
1259 45         118 my $src = $schema_name.'__generated_read_list@'.$path;
1260 45         110 $src=~y{/}{@};
1261 45         157 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1262             sub {
1263             my ($p)=@_;
1264             my $c=$p->[XAT_CHILDREN];
1265             my $a=$p->[XAT_ATTRS];
1266             return undef unless $c and @$c or $a and @$a;
1267             my @list;
1268             my $singleton = $a && @$a ? 1 : 0;
1269             unless ($singleton) {
1270             for my $el (@$c) {
1271             if (!ref($el) or $el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
1272             $singleton = 1;
1273             last;
1274             } elsif ($el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT) {
1275             $singleton = 1 if $el->[XAT_NAME] ne 'LM' and $el->[XAT_NS] == $pml_ns_index;
1276             last;
1277             }
1278             }}
1279             if ($singleton) {
1280             @list = ($handlers{ '`.$cpath.q`' }->($p));
1281             } else {
1282             for my $el (@$c) {
1283             if (ref($el) and $el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT and $el->[XAT_NS] == $pml_ns_index) {
1284             $el->[XAT_NAME] eq 'LM' or _report_error(q(Unexpected non-LM element ').$el->[XAT_NAME].q(' in a list: '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1285             push @list, $handlers{ '`.$cpath.q`' }->($el);
1286             } elsif (!ref($el) or $el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
1287             _report_error(q(Unexpected text content in a list '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1288             }
1289             }
1290             }`;
1291 45 0 66     199 if (!$trees_type and $decl->get_role eq '#TREES' and $BUILD_TREES) {
      33        
1292 0         0 my $cdecl_type = $cdecl->get_decl_type;
1293 0 0 0     0 unless ($cdecl && ($cdecl->get_role||'') eq '#NODE' &&
      0        
      0        
      0        
1294             ($cdecl_type == PML_STRUCTURE_DECL or
1295             $cdecl_type == PML_CONTAINER_DECL)) {
1296 0         0 _report_error("List '$path' with role #TREES may only contain structures or containers with role #NODE in schema ".
1297             $decl->get_schema->get_url."\n");
1298             }
1299 0         0 $trees_type = $decl;
1300 0         0 $sub .= q`
1301             unless ($have_trees) {
1302             $have_trees = 1;
1303             $ctxt->{'_pml_trees_type'} = $trees_type;
1304             $ctxt->{'_trees'} = Treex::PML::Factory->createList(\@list,1);
1305             return;
1306             }`;
1307             }
1308 45         98 $sub .= q`
1309             return Treex::PML::Factory->createList(\@list,1);
1310             }`;
1311             # print $sub;
1312 45         249 $src{$src}=$sub;
1313 45 50       31902 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  45         1188  
1314             } elsif ($decl_type == PML_ALT_DECL) {
1315             # print $path."\t@".$decl->get_decl_type_str,"\n";
1316 8         46 my $cpath = $decl->get_content_decl->get_decl_path;
1317 8         38 $cpath=~s/^!//;
1318 8         20 my $src = $schema_name.'__generated_read_alt@'.$path;
1319 8         19 $src=~y{/}{@};
1320 8         49 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1321             sub {
1322             my ($p)=@_;
1323             my $c=$p->[XAT_CHILDREN];
1324             my $a=$p->[XAT_ATTRS];
1325             return undef unless $c and @$c or $a and @$a;
1326             my $singleton = $a && @$a ? 1 : 0;
1327             unless ($singleton) {
1328             for my $el (@$c) {
1329             if (!ref($el) or $el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
1330             $singleton = 1;
1331             last;
1332             } elsif ($el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT and $el->[XAT_NS] == $pml_ns_index) {
1333             $singleton = 1 if $el->[XAT_NAME] ne 'AM';
1334             last;
1335             }
1336             }
1337             }
1338             if ($singleton) {
1339             return $handlers{ '`.$cpath.q`' }->($p);
1340             } else {
1341             my @alt;
1342             for my $el (@$c) {
1343             if (ref($el) and $el->[XAT_TYPE] == XML_READER_TYPE_ELEMENT and $el->[XAT_NS] == $pml_ns_index) {
1344             $el->[XAT_NAME] eq 'AM' or _report_error(q(Unexpected non-AM element ').$el->[XAT_NAME].q(' in an alt: '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1345             push @alt, $handlers{ '`.$cpath.q`' }->($el);
1346             } elsif (!ref($el) or $el->[XAT_TYPE] == XML_READER_TYPE_TEXT or $el->[XAT_TYPE] == XML_READER_TYPE_CDATA) {
1347             _report_error(q(Unexpected text content in an alt: '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1348             }
1349             }
1350             return @alt == 0 ? undef : @alt == 1 ? $alt[0] :
1351             #return bless \@alt, 'Treex::PML::Alt';
1352             Treex::PML::Factory->createAlt(\@alt,1);
1353             }
1354             }
1355             `;
1356             # print $sub;
1357 8         23 $src{$src}=$sub;
1358 8 50       4444 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  8         185  
1359              
1360             } elsif ($decl_type == PML_CDATA_DECL) {
1361 235         517 my $src = $schema_name.'__generated_read_cdata@'.$path;
1362 235         456 $src=~y{/}{@};
1363 235         485 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1364             sub {
1365             my ($p)=@_;
1366             my $text;
1367             if (ref($p)) {
1368             my $c = $p->[XAT_CHILDREN];
1369             return undef unless $c and @$c;
1370             my $type;
1371             $text = join '',
1372             map {
1373             if (ref($_)) {
1374             $type = $_->[XAT_TYPE];
1375             if ($type == XML_READER_TYPE_TEXT ||
1376             $type == XML_READER_TYPE_WHITESPACE ||
1377             $type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE ||
1378             $type == XML_READER_TYPE_CDATA) {
1379             $_->[XAT_VALUE]
1380             } elsif ($type == XML_READER_TYPE_ELEMENT) {
1381             _report_error(q(Element found where only character data were expected in element <).$_->[XAT_NAME].q(> of CDATA type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1382             }
1383             } else {
1384             $_
1385             }
1386             } @$c;`;
1387 235         311 my $format_checker;
1388 235 50 33     646 if ($VALIDATE_CDATA and $decl->get_format ne 'any') {
1389 0         0 $sub .=q`
1390             } else {
1391             $text = $p;
1392             }`;
1393 0         0 $format_checker = $decl->_get_format_checker();
1394 0 0       0 if (defined $format_checker) {
1395 0 0       0 if (ref($format_checker) eq 'CODE') {
1396 0         0 $sub .= q`
1397             if (defined $text and length $text and !$format_checker->($text)) {`;
1398             } else {
1399 0         0 $sub .= q`
1400             if (defined $text and length $text and $text !~ $format_checker) {`;
1401             }
1402 0         0 $sub .= q`
1403             warn("CDATA value '$text' does not conform to format '`.$decl->get_format.q`' at ".$pml_file.' line '.$p->[XAT_LINE]);
1404             }`;
1405             }
1406 0         0 $sub .= q`
1407             return $text;
1408             }`;
1409             } else {
1410 235         414 $sub .=q`
1411             return $text;
1412             } else {
1413             return $p;
1414             }
1415             }`;
1416             }
1417             # print $sub;
1418 235         1066 $src{$src}=$sub;
1419 235 50       105140 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  235         5202  
1420             } elsif ($decl_type == PML_CHOICE_DECL) {
1421             # print $path,"\n";
1422 60         201 my $value_hash = $decl->{value_hash};
1423 60 50       165 unless ($value_hash) {
1424 60         147 $value_hash={};
1425 60         97 @{$value_hash}{@{$decl->{values}}}=();
  60         628  
  60         205  
1426 60         172 $decl->{value_hash}=$value_hash;
1427             }
1428 60         190 my $src = $schema_name.'__generated_read_choice@'.$path;
1429 60         140 $src=~y{/}{@};
1430 60         271 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1431             sub {
1432             my ($p)=@_;
1433             my $text;
1434             if (ref($p)) {
1435             my $c = $p->[XAT_CHILDREN];
1436             return undef unless @$c;
1437             $c=$c->[0];
1438             if (ref($c)) {
1439             my $type = $c->[XAT_TYPE];
1440             if ($type == XML_READER_TYPE_TEXT ||
1441             $type == XML_READER_TYPE_WHITESPACE ||
1442             $type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE ||
1443             $type == XML_READER_TYPE_CDATA) {
1444             $text = $c->[XAT_VALUE]
1445             } elsif ($type == XML_READER_TYPE_ELEMENT) {
1446             _report_error(q(Element found where only character data were expected in element <).$p->[XAT_NAME].q(> of choice type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1447             }
1448             } else {
1449             $text = $c;
1450             }
1451             } else {
1452             $text=$p;
1453             }
1454             return undef unless defined $text;
1455             exists($value_hash->{$text}) or _report_error(qq(Invalid value '$text' in element <).$p->[XAT_NAME].q(> of choice type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1456             return $text;
1457             }`;
1458             # print $sub;
1459 60         199 $src{$src}=$sub;
1460 60 50       33381 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  60         1411  
1461             } elsif ($decl_type == PML_CONSTANT_DECL) {
1462             # print $path,"\n";
1463 2         7 my $value = quotemeta($decl->{value});
1464 2         5 my $src = $schema_name.'__generated_read_constant@'.$path;
1465 2         8 $src=~y{/}{@};
1466 2         35 my $sub = q`#line 0 ".pml_compile.d/`.$src.q`"
1467             sub {
1468             my ($p)=@_;
1469             my $text;
1470             if (ref($p)) {
1471             my $c = $p->[XAT_CHILDREN];
1472             return undef unless $c and @$c;
1473             $c=$c->[0];
1474             if (ref($c)) {
1475             my $type = $c->[XAT_TYPE];
1476             if ($type == XML_READER_TYPE_TEXT ||
1477             $type == XML_READER_TYPE_WHITESPACE ||
1478             $type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE ||
1479             $type == XML_READER_TYPE_CDATA) {
1480             $text = $c->[XAT_VALUE]
1481             } elsif ($type == XML_READER_TYPE_ELEMENT) {
1482             _report_error(q(Unexpected element occurrence in element <).$p->[XAT_NAME].q(> of constant type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1483             }
1484             } else {
1485             $text = $c;
1486             }
1487             } else {
1488             $text=$p;
1489             }
1490             !(defined($text) and length($text)) or ($text eq "`.$value.q`") or
1491             _report_error(qq(Invalid value '$text' in element <).$p->[XAT_NAME].q(> of constant type '`.$path.q`' at ).$pml_file.' line '.$p->[XAT_LINE]);
1492             return $text;
1493             }`;
1494             # print $sub;
1495 2         8 $src{$src}=$sub;
1496 2 50       1100 $handlers{$path} = eval $sub; die _nl($sub)."\n".$@.' ' if $@;
  2         43  
1497             }
1498             # print "@_\n";
1499 27         2327 });
1500             $schema->for_each_decl(
1501             sub {
1502 1244     1244   1775 my ($decl)=@_;
1503 1244         2801 my $decl_type=$decl->get_decl_type;
1504 1244 100 100     4712 if ($decl_type == PML_ATTRIBUTE_DECL ||
      100        
1505             $decl_type == PML_MEMBER_DECL ||
1506             $decl_type == PML_ELEMENT_DECL
1507             ) {
1508 482         1148 my $parent = $decl->get_parent_decl;
1509 482         993 my $path = $parent->get_decl_path . '/'. $decl->get_name;
1510 482 50       1647 $path =~ s/^!// if $path;
1511 482         700 my $mdecl;
1512 482 100 100     1076 if ($decl_type == PML_MEMBER_DECL and $decl->is_required) {
1513             # a hack that fixes missing content of a required member
1514             # containing a construct with the role #TREES
1515             #
1516             # the modified handler returns string '#TREES' instead
1517             # and the value gets deleted in the structure handler
1518 87         236 $mdecl = $decl->get_content_decl;
1519 87 50 33     243 if ($mdecl->get_role eq '#TREES' and $mdecl==$trees_type) {
1520 0         0 my $mpath = $mdecl->get_decl_path;
1521 0 0       0 $mpath =~ s/^!// if $mpath;
1522 0         0 my $handler = $handlers{$mpath};
1523             $handlers{$path}=sub {
1524 0 0 0     0 if (!$have_trees and $BUILD_TREES) {
1525 0         0 my $ret = &$handler;
1526 0 0 0     0 return '#TREES' if $have_trees and !defined($ret);
1527 0         0 return $ret;
1528             } else {
1529 0         0 return &$handler;
1530             }
1531 0         0 };
1532 0         0 return;
1533             }
1534             }
1535             # print "$path\n";
1536 482 100       1299 if (!exists($handlers{$path})) {
1537 234   66     763 $mdecl ||= $decl->get_content_decl;
1538 234   33     592 my $mpath = $mdecl && $mdecl->get_decl_path;
1539 234 50       416 if ($mpath) {
1540 234         571 $mpath =~ s/^!//;
1541             # print "mapping $path -> $mpath ... $handlers{$mpath}\n";
1542 234         861 $handlers{$path} = $handlers{$mpath};
1543             }
1544             }
1545             }
1546 27         334 });
1547             }
1548              
1549             sub _nl {
1550 0     0     my ($str)=@_;
1551 0           my $i=0;
1552 0           return join "\n", map sprintf("%4d\t",$i++).$_, split /\n/, $str;
1553             }
1554              
1555             }
1556              
1557             {
1558             # outside the main blog so that we leak no lexicals other than $dom
1559             sub perl_transform {
1560 0     0 0   return eval shift();
1561             }
1562             }
1563              
1564             1;
1565             __END__