File Coverage

blib/lib/Treex/PML/Schema.pm
Criterion Covered Total %
statement 341 519 65.7
branch 129 280 46.0
condition 113 251 45.0
subroutine 61 82 74.3
pod 31 34 91.1
total 675 1166 57.8


line stmt bran cond sub pod time code
1             # -*- cperl -*-
2             package Treex::PML::Schema;
3              
4              
5 6     6   41 use strict;
  6         12  
  6         181  
6 6     6   28 use warnings;
  6         14  
  6         188  
7 6     6   26 no warnings 'uninitialized';
  6         20  
  6         226  
8              
9 6     6   2299 use UNIVERSAL::DOES;
  6         2781  
  6         257  
10              
11 6     6   40 use Carp;
  6         9  
  6         288  
12 6     6   2128 use Treex::PML::Schema::Constants;
  6         13  
  6         776  
13              
14             BEGIN {
15 6     6   19 our $VERSION = '2.24'; # version template
16 6         29 require Exporter;
17 6         55 import Exporter qw(import);
18 6         25 our @EXPORT = (
19             @Treex::PML::Schema::Constants::EXPORT,
20             qw(PML_VERSION_SUPPORTED),
21             );
22 6         126 our %EXPORT_TAGS = (
23             'constants' => [ @EXPORT ],
24             );
25             } # BEGIN
26              
27 6     6   25 use constant PML_VERSION_SUPPORTED => "1.2";
  6         9  
  6         259  
28              
29 6     6   2277 use Treex::PML::Schema::XMLNode;
  6         19  
  6         192  
30 6     6   2478 use Treex::PML::Schema::Decl;
  6         15  
  6         171  
31 6     6   2249 use Treex::PML::Schema::Root;
  6         14  
  6         161  
32 6     6   2200 use Treex::PML::Schema::Template;
  6         16  
  6         153  
33 6     6   2198 use Treex::PML::Schema::Derive;
  6         16  
  6         175  
34 6     6   2319 use Treex::PML::Schema::Copy;
  6         14  
  6         199  
35 6     6   2265 use Treex::PML::Schema::Import;
  6         18  
  6         166  
36 6     6   2256 use Treex::PML::Schema::Type;
  6         15  
  6         202  
37 6     6   2238 use Treex::PML::Schema::Struct;
  6         16  
  6         172  
38 6     6   2219 use Treex::PML::Schema::Container;
  6         14  
  6         198  
39 6     6   2250 use Treex::PML::Schema::Seq;
  6         14  
  6         162  
40 6     6   2111 use Treex::PML::Schema::List;
  6         15  
  6         160  
41 6     6   2229 use Treex::PML::Schema::Alt;
  6         14  
  6         163  
42 6     6   2205 use Treex::PML::Schema::Choice;
  6         14  
  6         162  
43 6     6   2408 use Treex::PML::Schema::CDATA;
  6         21  
  6         275  
44 6     6   2266 use Treex::PML::Schema::Constant;
  6         16  
  6         165  
45 6     6   1981 use Treex::PML::Schema::Member;
  6         15  
  6         158  
46 6     6   2117 use Treex::PML::Schema::Element;
  6         15  
  6         154  
47 6     6   2448 use Treex::PML::Schema::Attribute;
  6         15  
  6         154  
48 6     6   2250 use Treex::PML::Schema::Reader;
  6         21  
  6         218  
49 6     6   2961 use Treex::PML::IO;
  6         25  
  6         501  
50 6     6   3119 use XML::Writer;
  6         37110  
  6         204  
51              
52 6     6   48 use base qw(Treex::PML::Schema::Template);
  6         15  
  6         767  
53              
54 6     6   44 use Scalar::Util qw(weaken isweak);
  6         14  
  6         3468  
55             require Treex::PML;
56              
57             =head1 NAME
58              
59             Treex::PML::Schema - Perl implements a PML schema.
60              
61             =head2 DESCRIPTION
62              
63             This class implements PML schemas. PML schema consists of a set of
64             type declarations of several kinds, represented by objects inheriting
65             from a common base class C.
66              
67             =head2 INHERITANCE
68              
69             This class inherits from L.
70              
71             =head3 Attribute Paths
72              
73             Some methods use so called 'attribute paths' to navigate through
74             nested and referenced type declarations. An attribute path is a
75             '/'-separated sequence of steps, where step can be one of the
76             following:
77              
78             =over 3
79              
80             =item CI
81              
82             '!' followed by name of a named type (this step can only occur
83             as the very first step
84              
85             =item I
86              
87             name (of a member of a structure, element of a sequence or attribute
88             of a container), specifying the type declaration of the specified
89             named component
90              
91             =item C<#content>
92              
93             the string '#content', specifying the content type declaration
94             of a container
95              
96             =item C
97              
98             specifying the type declaration of a list
99              
100             =item C
101              
102             specifying the type declaration of an alt
103              
104             =item C<[>IC<]>
105              
106             where I is a decimal number (ignored) are an equivalent of LM or AM
107              
108             =back
109              
110             Steps of the form LM, AM, and [NNN] (except when occuring at the end
111             of an attribute path) may be omitted.
112              
113             =head2 EXPORT
114              
115             This module exports constants for declaration types.
116              
117             =head2 EXPORT TAGS
118              
119             =over 3
120              
121             =item :constants
122              
123             Export constant symbols (exported by default, too).
124              
125             =back
126              
127             =head2 CONSTANTS
128              
129             See Treex::PML::Schema::Constants.
130              
131             =cut
132              
133             =head1 METHODS
134              
135             =over 3
136              
137             =item Treex::PML::Schema->new ({ option => value, ... })
138              
139             NOTE: Don't call this constructor directly, use Treex::PML::Factory->createPMLSchema() instead!
140              
141             Parses an XML representation of a PML Schema
142             from a string, filehandle, local file, or URL,
143             processing the modular instructions as described in
144              
145             L
146              
147             and returns the corresponding C object.
148              
149             One of the following options must be given:
150              
151             =over 5
152              
153             =item C
154              
155             a XML string to parse
156              
157             =item C
158              
159             a file name or URL
160              
161             =item C
162              
163             a file-handle (IO::File, IO::Pipe, etc.) open for reading
164              
165             =back
166              
167             The following options are optional:
168              
169             =over 5
170              
171             =item C
172              
173             base URL for referred schemas (usefull when parsing from a file-handle or a string)
174              
175             =item C
176              
177             if this option is used with a true value, the parser will attempt to
178             locate referred schemas also in L resource paths.
179              
180             =item C, C, C
181              
182             constraints to the revision number of the schema.
183              
184             =item C
185              
186             if this option is used with a true value, the parser will validate the
187             schema on the fly using a RelaxNG grammar given using the
188             C parameter; if C is not given, the
189             file 'pml_schema_inline.rng' searched for in L resource paths
190             is assumed.
191              
192             =item C
193              
194             a particular RelaxNG grammar to validate against. The value may be an
195             URL or filename for the grammar in the RelaxNG XML format, or a
196             XML::LibXML::RelaxNG object representation. The compact format is not
197             supported.
198              
199             =back
200              
201             =cut
202              
203 0         0 BEGIN{
204             my %parse_opts = (
205             KeyAttr => {
206             "member" => "name",
207             "attribute" => "name",
208             "element" => "name",
209             "type" => "name",
210             "template" => "name",
211             "derive" => "name",
212             "let" => "param",
213             "param" => "name",
214             },
215             TextOnly => {
216             description => 'content',
217             revision => 'content',
218             value => 'content',
219             delete => 'content',
220             constant => 'value',
221             },
222             Stringify => {
223             description => 'content',
224             revision => 'content',
225             value => 'content',
226             delete => 'content',
227             },
228             Solitary => {
229 6     6   120 map { $_ => 1 }
  66         30449  
230             qw(description revision root cdata structure container sequence constant list alt choice)
231             },
232             Bless => {
233             member => 'Treex::PML::Schema::Member',
234             attribute => 'Treex::PML::Schema::Attribute',
235             element => 'Treex::PML::Schema::Element',
236             type => 'Treex::PML::Schema::Type',
237             root => 'Treex::PML::Schema::Root',
238             structure => 'Treex::PML::Schema::Struct',
239             container => 'Treex::PML::Schema::Container',
240             sequence => 'Treex::PML::Schema::Seq',
241             list => 'Treex::PML::Schema::List',
242             alt => 'Treex::PML::Schema::Alt',
243             cdata => 'Treex::PML::Schema::CDATA',
244             constant => 'Treex::PML::Schema::Constant',
245             choice => 'Treex::PML::Schema::Choice',
246             template => 'Treex::PML::Schema::Template',
247             copy => 'Treex::PML::Schema::Copy',
248             import => 'Treex::PML::Schema::Import',
249             derive => 'Treex::PML::Schema::Derive',
250             '*' => 'Treex::PML::Schema::XMLNode',
251             },
252             DefaultNs => PML_SCHEMA_NS,
253             );
254              
255             sub new {
256 63     63 1 360 my ($class,$opts, $more_opts)=@_;
257 63 50       241 if (!ref $opts) {
258             # compatibility with older API
259 0   0     0 $more_opts ||= {};
260 0         0 $opts = { %$more_opts, string => $opts };
261             }
262              
263 63         201 my $file = $opts->{filename};
264              
265 63         150 my $base = $opts->{base_url};
266 63 100 66     357 if (defined $base and length $base) {
    100          
267 16         127 $file = Treex::PML::ResolvePath($base,$file,$opts->{use_resources});
268             } elsif ($opts->{use_resources}) {
269 24         105 $file = Treex::PML::FindInResources($file);
270             }
271 63         249 my $schema;
272             my $revision_opts = {
273 63         185 map { $_ => delete($opts->{$_}) }
  252         813  
274             qw(revision_error revision minimal_revision maximal_revision)
275             };
276 63 50 33     386 if (defined($file) and ref($schema = $opts->{schemas}{$file})) {
277 0 0       0 print STDERR "schema $file already hashed\n" if $Treex::PML::Debug;
278 0         0 $schema->check_revision($revision_opts);
279 0         0 return $schema;
280             }
281 63         780 my $parse_opts = {%parse_opts,%$opts};
282 63         211 $parse_opts->{Bless}{pml_schema}=$class;
283 63         151 $parse_opts->{URL} = $file;
284              
285 63         475 my $pml_reader = Treex::PML::Schema::Reader->new($parse_opts);
286 63         259 my $reader = $pml_reader->reader;
287 63         126 my $version;
288 63         137 eval {
289 63 50       3388 unless ( $reader->nextElement('pml_schema', PML_SCHEMA_NS)==1 ) {
290 0         0 die "Not a PML schema: $file!\n";
291             }
292 63         583 $version = $reader->getAttribute('version');
293 63         227 $reader->moveToElement;
294 63         277 $schema = $pml_reader->parse_element();
295             };
296 63 50       235 if ($@) {
297 0         0 die "Treex::PML::Schema::Reader error while parsing: $file near line ".$reader->lineNumber."\n$@\n";
298 0         0 return;
299             }
300 63 50 33     396 if (defined $version and length $version) {
301 63 50       268 unless (cmp_revisions($version,PML_VERSION_SUPPORTED)<=0) {
302 0         0 die "Unsupported version of PML schema '$file': this module supports versions up to ".PML_VERSION_SUPPORTED."\n";
303             }
304             } else {
305 0         0 warn "WARNING: PML schema '$file' does not specify version! Assuming ".PML_VERSION_SUPPORTED."\n";
306             }
307 63         287 $schema->check_revision($revision_opts);
308 63         173 $schema->{-VERSION}=$Treex::PML::Schema::VERSION;
309 63         315 return $schema;
310             }
311             } # BEGIN
312              
313              
314             =item Treex::PML::Schema->readFrom (filename,opts)
315              
316             An obsolete alias for Treex::PML::Schema->new({%$opts, filename=>$filename}).
317              
318             =cut
319              
320             sub readFrom {
321 0     0 1 0 my ($self,$file,$opts)=@_;
322 0         0 return $self->new({%$opts, filename=>$file});
323             }
324              
325             =item $schema->write ({option => value})
326              
327             This method serializes the Treex::PML::Schema object to XML. See Treex::PML::Schema::XMLNode->write for implementation.
328              
329             IMPORTANT: The resulting schema is simplified, that is all modular instructions
330             are processed and removed from it, see L
331              
332             One of the following options must be given:
333              
334             =over 5
335              
336             =item C
337              
338             a scalar reference to which the XML is to be stored as a string
339              
340             =item C
341              
342             a file name
343              
344             =item C
345              
346             a file-handle (IO::File, IO::Pipe, etc.) open for writing
347              
348             =back
349              
350             One of the following options are optional:
351              
352             =over 5
353              
354             =item C
355              
356             if this option is used with a true value, the writer will not attempt
357             to create backup (tilda) files when overwriting an existing file.
358              
359             =item C
360              
361             if this option is used with a true value, the writer will not add
362             additional newlines and indentatin white-space to the result XML.
363              
364             =back
365              
366             =cut
367              
368             # for implementation see XMLNode.pm
369              
370              
371             =item $schema->get_url ()
372              
373             Return location of the PML schema file.
374              
375             =cut
376              
377 1     1 1 6 sub get_url { return $_[0]->{URL}; }
378              
379             =item $schema->set_url ($URI)
380              
381             Set location of the PML schema file.
382              
383             =cut
384              
385 22     22 1 4215 sub set_url { return $_[0]->{URL} = Treex::PML::IO::make_URI($_[1]) }
386              
387              
388             =item $schema->get_pml_version ()
389              
390             Return PML version the schema conforms to.
391              
392             =cut
393              
394 0     0 1 0 sub get_pml_version { return $_[0]->{version}; }
395              
396              
397             =item $schema->get_revision ()
398              
399             Return PML schema revision.
400              
401             =cut
402              
403 0     0 1 0 sub get_revision { return $_[0]->{revision}; }
404              
405             =item $schema->get_description ()
406              
407             Return PML schema description.
408              
409             =cut
410              
411 0     0 1 0 sub get_description { return $_[0]->{description}; }
412              
413             =item $schema->get_root_decl ()
414              
415             Return the root type declaration (see C).
416              
417             =cut
418              
419 146     146 1 6298 sub get_root_decl { return $_[0]->{root}; }
420              
421             =item $schema->get_root_type ()
422              
423             Like $schema->get_root_decl->get_content_decl.
424              
425             =cut
426              
427             sub get_root_type {
428 0     0 1 0 my ($self,$name) = @_;
429 0         0 return $self->{root}->get_content_decl;
430             }
431             *get_root_type_obj = \&get_root_type;
432              
433              
434 0     0   0 sub _internal_api_version { return $_[0]->{'-api_version'} }
435              
436             =item $decl->get_decl_type ()
437              
438             Return the constant PML_SCHEMA_DECL (for compatibility with the Treex::PML::Schema::Decl interface).
439              
440             =item $decl->get_decl_type_str ()
441              
442             Return the string 'schema' (for compatibility with the Treex::PML::Schema::Decl interface).
443              
444             =cut
445              
446 190     190 1 671 sub get_decl_type { return(PML_SCHEMA_DECL); }
447 0     0 1 0 sub get_decl_type_str { return('schema'); }
448              
449             =item $schema->get_root_name ()
450              
451             Return name of the root element for PML instance.
452              
453             =cut
454              
455             sub get_root_name {
456 1     1 1 4 my $root = $_[0]->{root};
457 1 50       12 return $root ? $root->{name} : undef;
458             }
459              
460             =item $schema->get_type_names ()
461              
462             Return names of all named type declarations.
463              
464             =cut
465              
466             sub get_type_names {
467 0     0 1 0 my $types = $_[0]->{type};
468 0 0       0 return $types ? keys(%$types) : ();
469             }
470              
471             =item $schema->get_named_references ()
472              
473             This method returns a list of HASHrefs containing
474             information about a named references to PML instances
475             (each hash will currently have the keys 'name' and 'readas').
476              
477             =cut
478              
479             sub get_named_references {
480 102     102 1 261 my ($self, $name) = @_;
481 102 100       310 if ($self->{reference}) {
482 50         120 return map { my $r=$_; my $h = { map { ($_=>$r->{$_}) } @{$r->{'-attributes'}} }; $h }
  50         114  
  100         495  
  50         148  
  50         228  
483 50         111 @{$self->{reference}} ;
  50         159  
484             }
485 52         139 return;
486             }
487              
488             =item $schema->get_named_reference_info (name)
489              
490             This method retrieves information about a specific named instance
491             reference as a hash (currently with keys 'name' and 'readas').
492              
493             =cut
494              
495             sub get_named_reference_info {
496 0     0 1 0 my ($self, $name) = @_;
497 0 0       0 if ($self->{reference}) {
498 0         0 return { map { my $r=$_; map { $_=>$r->{$_} } @{$r->{'-attributes'}} }
  0         0  
  0         0  
  0         0  
499 0 0       0 grep { defined($_->{name}) and $_->{name} eq $name } @{$self->{reference}} };
  0         0  
  0         0  
500             }
501 0         0 return;
502             }
503              
504             =item Treex::PML::Schema::cmp_revisions($A, $B)
505              
506             This function compares two schema revision strings according to the
507             ruls described in the PML specification. Returns -1 if revision $A
508             precedes revision $B, 0 if the revisions are equal (equivalent), and 1
509             if revision $A follows revision $B.
510              
511             =cut
512              
513             # compare two revision numbers
514             sub cmp_revisions {
515 74     74 1 225 my ($my_revision,$revision)=@_;
516 74         410 my @my_revision = split(/\./,$my_revision);
517 74         241 my @revision = split(/\./,$revision);
518 74         163 my $cmp=0;
519 74   66     388 while ($cmp==0 and (@my_revision or @revision)) {
      66        
520 150         577 $cmp = (shift(@my_revision) <=> shift(@revision));
521             }
522 74         312 return $cmp;
523             }
524              
525             # compare schema revision number with a given revision number
526             sub _match_revision {
527 11     11   30 my ($self,$revision)=@_;
528 11   50     49 my $my_revision=$self->{revision} || 0;
529 11   50     40 return cmp_revisions($self->{revision} || 0, $revision);
530             }
531              
532             # for internal use only
533             sub _resolve_type {
534 9     9   25 my ($self,$type)=@_;
535 9 50       24 return $type unless ref($type);
536 9         31 my $ref = $type->{type};
537 9 50       23 if ($ref) {
538 9         26 my $rtype = $self->{type}{$ref};
539 9 50       26 if (ref($rtype)) {
540 9         24 return $rtype;
541             } else {
542             # couldn't resolve
543 0         0 warn "No declaration for type '$ref' in schema '".$self->get_url."'\n";
544 0         0 return $type->{type};
545             }
546             } else {
547 0         0 return $type;
548             }
549             }
550              
551             =item $schema->for_each_decl (sub{...})
552              
553             This method traverses all nested declarations and sub-declarations and
554             calls a given subroutine passing the sub-declaration object as a
555             parameter.
556              
557             =cut
558              
559             sub for_each_decl {
560 139     139 1 376 my ($self,$sub) = @_;
561 139 100       523 if (ref $self->{root}) {
562 138         633 $self->{root}->for_each_decl($sub);
563             }
564 139         327 for my $d (qw(template type)) {
565 278 100       715 if (ref $self->{$d}) {
566 125         211 foreach (values %{$self->{$d}}) {
  125         525  
567 877         2077 $_->for_each_decl($sub);
568             }
569             }
570             }
571             }
572              
573             # traverse type data structure and collect types referred via
574             # type="type-name" declarations in the refferred hash
575             sub _get_referred_types {
576 22     22   57 my ($self,$type,$referred) = @_;
577             $type->for_each_decl(
578             sub {
579 93     93   136 my ($type)=@_;
580 93 50       183 return unless ref($type);
581 93 50 66     283 if (defined($type->{type}) and length($type->{type}) and !exists($referred->{$type->{type}})) {
      66        
582             # this type declaration reffers to another type - get it
583 9         34 my $resolved = $self->_resolve_type($type);
584 9         27 $referred->{$type->{type}} = $resolved;
585 9 50       41 $self->_get_referred_types($resolved,$referred) if ref $resolved;
586             }
587 22         128 });
588             }
589              
590             # import given named type and all named types it requires
591             # from src_schema into the current schema (self)
592             sub _import_type {
593 0     0   0 my ($self,$src_schema, $name) = @_;
594 0 0       0 unless (exists $src_schema->{type}{$name}) {
595 0         0 croak "Cannot import type '$name' from '$src_schema->{URL}' to '$self->{URL}': type not declared in the source schema\n";
596             }
597 0         0 my $type = $src_schema->{type}{$name};
598 0         0 my %referred = ($name => $type);
599 0         0 $src_schema->_get_referred_types($type,\%referred);
600 0         0 foreach my $n (keys %referred) {
601 0 0       0 unless (exists $self->{type}{$n}) {
602 0         0 my $parent = $referred{$n}->{-parent};
603 0 0       0 if (defined $parent) {
604 0         0 $self->{type}{$n}=Treex::PML::CloneValue($referred{$n},[$parent], [$self]);
605             } else {
606 0         0 $self->{type}{$n}=Treex::PML::CloneValue($referred{$n});
607             }
608             } else {
609            
610             }
611             }
612             }
613              
614             sub __fmt {
615 0     0   0 my ($string,$fmt) =@_;
616 0         0 $string =~ s{%(.)}{ $1 eq "%" ? "%" :
617 0 0       0 exists($fmt->{$1}) ? $fmt->{$1} : "%$1" }eg;
    0          
618 0         0 return $string;
619             }
620              
621             =item $schema->check_revision({ option=>value })
622              
623             Check that schema revision satisfies given constraints. The following options are suported:
624              
625             C: exact revision number to match
626              
627             C: minimal revision number to match
628              
629             C: maximal revision number to match
630              
631             C: an optional error message format string with %f
632             mark for the schema filename or URL and %e for the error
633             string. Defaults to 'Error: wrong schema revision of %f: %e';
634              
635             =cut
636              
637             sub check_revision {
638 63     63 1 172 my ($self,$opts)=@_;
639              
640 63   100     257 my $error = $opts->{revision_error} || 'Error: wrong schema revision of %f: %e';
641 63 50 66     282 if ($opts->{revision} and
642             $self->_match_revision($opts->{revision})!=0) {
643             croak(__fmt($error, { 'e' => "required $opts->{revision}, got $self->{revision}",
644 0         0 'f' => $self->{URL}}));
645             } else {
646 63 50 66     264 if ($opts->{minimal_revision} and
647             $self->_match_revision($opts->{minimal_revision})<0) {
648             croak(__fmt($error, { 'e' => "required at least $opts->{minimal_revision}, got $self->{revision}",
649 0         0 'f' => $self->{URL}}));
650             }
651 63 50 66     259 if ($opts->{maximal_revision} and
652             $self->_match_revision($opts->{maximal_revision})>0) {
653             croak(__fmt($error, { 'e' => "required at most $opts->{maximal_revision}, got $self->{revision}",
654 0         0 'f' => $self->{URL}}));
655             }
656             }
657             }
658              
659             =item $schema->convert_from_hash
660              
661             Compatibility method building the schema object from a nested hash
662             structure created by XML::Simple which was used in older
663             implementations. This is useful for upgrading objects stored in old
664             binary dumps.
665              
666             =cut
667              
668             sub convert_from_hash {
669 2     2 1 6 my $class = shift;
670 2         5 my $schema_hash;
671 2 50       6 if (ref($class)) {
672 0         0 $schema_hash = $class;
673 0         0 $class = ref( $schema_hash );
674             } else {
675 2         3 $schema_hash = shift;
676 2         6 bless $schema_hash,$class;
677             }
678 2   50     24 $schema_hash->{-api_version} ||= '2.0';
679 2         6 $schema_hash->{'-xml_name'}='pml_schema';
680 2         7 $schema_hash->{-attributes}=[qw(xmlns version)];
681 2 50       14 if (ref $schema_hash->{reference}) {
682 0         0 for my $ref (@{$schema_hash->{reference}}) {
  0         0  
683 0         0 $ref->{'-xml_name'}='reference';
684 0         0 $ref->{'-attributes'}=[qw(name readas)];
685 0         0 bless $ref,'Treex::PML::Schema::XMLNode';
686 0         0 weaken($ref->{-parent}=$schema_hash);
687             }
688             }
689 2         5 my $root = $schema_hash->{root};
690 2 50       7 if (defined($root)) {
691 2         9 bless $root, 'Treex::PML::Schema::Root';
692 2         18 weaken($root->{-parent}=$schema_hash);
693 2         3 $root->{'-xml_name'}='root';
694 2         19 $root->{'-attributes'}=['name','type'];
695 2         16 Treex::PML::Schema::Decl->convert_from_hash($root,
696             $schema_hash,
697             undef # path = '' for root
698             );
699             }
700 2         6 my $types = $schema_hash->{type};
701 2 50       6 if ($types) {
702 2         3 my ($name, $decl);
703 2         10 while (($name, $decl) = each %$types) {
704 2         9 bless $decl, 'Treex::PML::Schema::Type';
705 2         9 $decl->{'-xml_name'}='type';
706 2         6 $decl->{'-attributes'}=['name'];
707 2         10 Treex::PML::Schema::Decl->convert_from_hash($decl,
708             $schema_hash,
709             '!'.$name
710             );
711             }
712             }
713 2         7 return $schema_hash;
714             }
715              
716              
717             =item $schema->find_type_by_path (attribute-path,noresolve,decl)
718              
719             Locate a declaration specified by C starting from
720             declaration C. If C is undefined the root type declaration
721             is used. (Note that attribute paths starting with '/' are always
722             evaluated startng from the root declaration and paths starting with
723             '!' followed by a name of a named type are evaluated starting from
724             that type.) All references to named types are transparently resolved
725             in each step.
726              
727             The caller should pass a true value in C to enforce Member,
728             Attribute, Element, Type, or Root declaration objects to be returned
729             rather than declarations of their content.
730              
731             Attribute path is a '/'-separated sequence of steps (member,
732             attribute, element names or strings matching [\d*]) which identifying
733             a certain nested type declaration. A step of the aforementioned form
734             [\d*] is match the content declaration of a List or Alt. Note however, that
735             named stepsdive into List or Alt declarations automatically, too.
736              
737             =cut
738              
739             sub find_type_by_path {
740 99     99 1 870 my ($schema, $path, $noresolve, $decl) = @_;
741 99 50 33     390 if (defined($path) and length($path)) {
    0          
742 99 100 33     593 if ($path=~s{^!([^/]+)/?}{}) {
    50          
743 2         10 $decl = $schema->get_type_by_name($1);
744 2 50       6 if (defined $decl) {
745 2         10 $decl = $decl->get_content_decl;
746             } else {
747 0         0 return;
748             }
749             } elsif ($path=~s{^/}{} or !$decl) {
750 97         251 $decl = $schema->get_root_decl->get_content_decl;
751             }
752 99         438 for my $step (split /\//, $path,-1) {
753 495 50       907 next if $step eq '.';
754 495 50       862 if (ref($decl)) {
755 495         1076 my $decl_is = $decl->get_decl_type;
756 495 100 66     2300 if ($decl_is == PML_ATTRIBUTE_DECL ||
      100        
      66        
757             $decl_is == PML_MEMBER_DECL ||
758             $decl_is == PML_ELEMENT_DECL ||
759             $decl_is == PML_TYPE_DECL ) {
760 139         341 $decl = $decl->get_knit_content_decl;
761 139 50 33     527 next unless defined($step) and length($step);
762 139         233 redo;
763             }
764 356 100 66     979 if ($decl_is == PML_LIST_DECL ||
765             $decl_is == PML_ALT_DECL ) {
766 54         113 $decl = $decl->get_knit_content_decl;
767 54 50 33     261 next if ($step =~ /^\[[-+]?\d+\]$/ or
    50 0        
      33        
768             (($decl_is == PML_LIST_DECL) ?
769             ($step eq 'LM' or $step eq '[LIST]')
770             :($step eq 'AM' or $step eq '[ALT]')));
771 0         0 redo;
772             }
773 302 100       682 if ($decl_is == PML_STRUCTURE_DECL) {
    100          
    50          
    0          
774 42         114 my $member = $decl->get_member_by_name($step);
775 42 50       86 if ($member) {
776 42         84 $decl = $member;
777             } else {
778 0         0 $member = $decl->get_member_by_name($step.'.rf');
779 0 0       0 return unless $member;
780 0 0       0 if ($member->get_knit_name eq $step) {
781 0         0 $decl = $member;
782             } else {
783 0         0 return;
784             }
785             }
786             } elsif ($decl_is == PML_CONTAINER_DECL) {
787 97 50       180 if ($step eq '#content') {
788 97         187 $decl = $decl->get_content_decl;
789 97         180 next;
790             }
791 0         0 my $attr = $decl->get_attribute_by_name($step);
792 0         0 $decl = $attr;
793             } elsif ($decl_is == PML_SEQUENCE_DECL) {
794 163         253 $step =~ s/^\[\d+\]//; # name must follow
795 163         376 $decl = $decl->get_element_by_name($step);
796             } elsif ($decl_is == PML_ROOT_DECL) {
797 0 0 0     0 if (!(defined($step) and length($step)) or ($step eq $decl->get_name)) {
      0        
798 0         0 $decl = $decl->get_content_decl;
799             } else {
800 0         0 return;
801             }
802             } else {
803 0         0 return;
804             }
805             } else {
806             # warn "Can't follow type path '$path' (step '$step')\n";
807 0         0 return(undef); # ERROR
808             }
809             }
810             } elsif (!$decl) {
811 0   0     0 $decl ||= $schema->get_root_decl->get_content_decl;
812             }
813 99   33     393 my $decl_is = $decl && $decl->get_decl_type;
814 99 100 66     981 return $noresolve ? $decl :
    50          
815             $decl && (
816             $decl_is == PML_ATTRIBUTE_DECL ||
817             $decl_is == PML_MEMBER_DECL ||
818             $decl_is == PML_ELEMENT_DECL ||
819             $decl_is == PML_TYPE_DECL ||
820             $decl_is == PML_ROOT_DECL
821             )
822             ? ($decl->get_knit_content_decl) : $decl;
823             }
824              
825              
826             =item $schema->find_types_by_role (role,start_decls)
827              
828             Return a list of declarations (objects derived from Treex::PML::Schema::Decl)
829             that have role equal to C.
830              
831             If C is specified, it must be an ARRAY reference of
832             declarations; in that case, only declarations nested below the listed
833             ones are considered.
834              
835             =cut
836              
837             sub find_types_by_role {
838 0     0 1 0 my ($self,$role,$start_decls)=@_;
839 0         0 my @decls;
840 0 0   0   0 my $sub = sub { push @decls, $_[0] if $_[0]->{role} eq $role };
  0         0  
841 0 0       0 if (defined($start_decls)) {
842 0         0 for (@$start_decls) {
843 0         0 $_->for_each_decl($sub);
844             }
845             } else {
846 0         0 $self->for_each_decl($sub);
847             }
848 0         0 return @decls;
849             }
850              
851             =item $schema->find_role (role,start_decl,opts)
852              
853             WARINING: this function can be very slow, esp. if the type
854             declarations are recursive.
855              
856             Return a list of attribute paths leading to nested type declarations
857             of C with role equal to C.
858              
859             This is equivalent to
860              
861             $schema->find_decl($decl,sub{ $_[0]->{role} eq $role },$opts);
862              
863             Please, see the documentation for C for more information.
864              
865             =cut
866              
867             sub find_role {
868 22     22 1 78 my ($self, $role, $decl, $opts)=@_;
869 22 50 33     123 if (!$decl and wantarray()) {
870 22   100     164 $self->{-ROLE_CACHE}{$role} ||= [ $self->_find_role($role,$decl,$opts) ];
871 22         101 return @{$self->{-ROLE_CACHE}{$role}};
  22         132  
872             }
873 0         0 return $self->_find_role($role,$decl,$opts);
874             }
875              
876             sub _find_role {
877 12     12   46 my ($self, $role, $decl, $opts)=@_;
878 12 100   1826   129 return $self->find_decl(sub{ defined($_[0]->{role}) and $_[0]->{role} eq $role },$decl,$opts);
  1826         5153  
879             }
880              
881             =item $schema->find_decl (callback,start_decl,opts)
882              
883             WARINING: this function can be very slow, esp. if the type
884             declarations are recursive.
885              
886             Return a list of attribute paths leading to nested type declarations
887             of C for which a given callback returns a true value. The tested
888             type declaration is passed to the callback as the first (and only)
889             argument.
890              
891             If C is specified, it must be an ARRAY reference of
892             declarations; in that case, only declarations nested or referred to
893             from the listed ones are considered.
894              
895             In array context return all matching nested declarations are
896             returned. In scalar context only the first one is returned (with early
897             stopping).
898              
899             The last argument C can be used to pass some flags to the
900             algorithm. Currently only the flag C is available. If
901             true, then the function never recurses into content declaration of
902             declarations with the role #CHILDNODES.
903              
904             =cut
905              
906             sub find_decl {
907 12     12 1 46 my ($self, $sub, $decl, $opts)=@_;
908 12   33     78 $decl ||= $self->{root};
909 12         31 my $first = not(wantarray);
910 12         68 my @res = grep { defined } $self->_find($decl,$sub,$first,{},$opts);
  49         131  
911 12 50       121 return $first ? $res[0] : @res;
912             }
913              
914              
915             sub _find {
916 1859     1859   3462 my ($self, $decl, $test, $first, $cache, $opts)=@_;
917              
918 1859         2399 my @result = ();
919              
920 1859 50       3510 return () unless ref $decl;
921              
922              
923 1859 100       3846 if ($cache->{'#RECURSE'}{ $decl }) {
924             return ()
925 33         82 }
926 1826         4061 local $cache->{'#RECURSE'}{ $decl } = 1;
927              
928 1826 0 66     5612 if ( ref $opts and $opts->{no_childnodes} and defined($decl->{role}) and $decl->{role} eq '#CHILDNODES') {
      33        
      33        
929 0         0 return ();
930             }
931              
932 1826 100       3041 if ( $test->($decl) ) {
933 76 50       154 if ($first) {
934 0         0 return '';
935             } else {
936 76         145 push @result, '';
937             }
938             }
939 1826         4132 my $type_ref = $decl->get_type_ref;
940 1826         4011 my $decl_is = $decl->get_decl_type;
941 1826 50       3071 my $seq_bracket = $opts->{with_Seq_brackets} ? '[0]' : '';
942              
943 1826 100       2906 if ($type_ref) {
944 367         723 my $cached = $cache->{ $type_ref };
945 367 100       663 unless ($cached) {
946 109         252 $cached = $cache->{ $type_ref } = [ $self->_find( $self->get_type_by_name($type_ref),
947             $test, $first, $cache, $opts ) ];
948             }
949 367 50       859 if ($decl_is == PML_CONTAINER_DECL) {
    100          
    100          
950 0 0 0     0 push @result, map { (defined($_) and length($_)) ? '#content/'.$_ : '#content' } @$cached;
  0         0  
951             } elsif ($decl_is == PML_LIST_DECL) {
952 81 100 66     143 push @result, map { (defined($_) and length($_)) ? 'LM/'.$_ : 'LM' } @$cached;
  34         160  
953             } elsif ($decl_is == PML_ALT_DECL) {
954 24 0 0     47 push @result, map { (defined($_) and length($_)) ? 'AM/'.$_ : 'AM' } @$cached;
  0         0  
955             } else {
956 262         401 push @result, @$cached;
957             }
958 367 50 33     772 return $result[0] if ($first and @result);
959             }
960 1826 100 100     9799 if ($decl_is == PML_STRUCTURE_DECL) {
    100 100        
    100 100        
    100 100        
    100          
    100          
961 132         315 foreach my $member ($decl->get_members) {
962 624 50 33     1239 my @res = map { (defined($_) and length($_)) ? $member->get_name.'/'.$_ : $member->get_name }
  37         192  
963             $self->_find($member, $test, $first, $cache, $opts);
964 624 50 33     1221 return $res[0] if ($first and @res);
965 624         1002 push @result,@res;
966             }
967             } elsif ($decl_is == PML_CONTAINER_DECL) {
968 48         103 my $cdecl = $decl->get_content_decl;
969 48         141 foreach my $attr ($decl->get_attributes) {
970 28 0 0     86 my @res = map { (defined($_) and length($_)) ? $attr->get_name.'/'.$_ : $attr->get_name }
  0         0  
971             $self->_find($attr, $test, $first, $cache, $opts);
972 28 50 33     75 return $res[0] if ($first and @res);
973 28         59 push @result,@res;
974             }
975 48 50       115 if ($cdecl) {
976 48 50 33     107 push @result, map { (defined($_) and length($_)) ? '#content/'.$_ : '#content' }
  51         247  
977             $self->_find($cdecl, $test, $first, $cache, $opts);
978 48 50 33     138 return $result[0] if ($first and @result);
979             }
980             } elsif ($decl_is == PML_SEQUENCE_DECL) {
981 34         110 foreach my $element ($decl->get_elements) {
982 55 100 66     118 my @res = map { (defined($_) and length($_)) ? $element->get_name.$seq_bracket.'/'.$_ : $element->get_name.$seq_bracket }
  97         441  
983             $self->_find($element, $test, $first, $cache, $opts);
984 55 50 33     160 return $res[0] if ($first and @res);
985 55         131 push @result,@res;
986             }
987             } elsif ($decl_is == PML_LIST_DECL) {
988 134 100 66     319 push @result, map { (defined($_) and length($_)) ? 'LM/'.$_ : 'LM' }
  34         148  
989             $self->_find($decl->get_content_decl, $test, $first, $cache, $opts);
990             } elsif ($decl_is == PML_ALT_DECL) {
991 28 0 0     82 push @result, map { (defined($_) and length($_)) ? 'AM/'.$_ : 'AM' }
  0         0  
992             $self->_find($decl->get_content_decl, $test, $first, $cache, $opts);
993             } elsif ($decl_is == PML_TYPE_DECL ||
994             $decl_is == PML_ROOT_DECL ||
995             $decl_is == PML_ATTRIBUTE_DECL ||
996             $decl_is == PML_MEMBER_DECL ||
997             $decl_is == PML_ELEMENT_DECL ) {
998 821         1901 push @result, $self->_find($decl->get_content_decl, $test, $first, $cache, $opts);
999             }
1000 1826         2668 my %uniq;
1001             return $first ? (@result ? $result[0] : ())
1002 1826 0       4848 : grep { !$uniq{$_} && ($uniq{$_}=1) } @result;
  633 100       2474  
    50          
1003             }
1004              
1005             =item $schema->node_types ()
1006              
1007             Return a list of all type declarations with the role C<#NODE>.
1008              
1009             =cut
1010              
1011             sub node_types {
1012 0     0 1 0 my ($self) = @_;
1013 0         0 my @result;
1014 0         0 return $self->find_types_by_role('#NODE');
1015             }
1016              
1017              
1018              
1019             =item $schema->get_type_by_name (name)
1020              
1021             Return the declaration of the named type with a given name (see
1022             C).
1023              
1024             =cut
1025              
1026             sub get_type_by_name {
1027 111     111 1 202 my ($self,$name) = @_;
1028 111         380 return $self->{type}{$name};
1029             }
1030             *get_type_by_name_obj = \&get_type_by_name;
1031              
1032              
1033             # OBSOLETE: for backward compatibility only
1034             sub type {
1035 0     0 1 0 my ($self,$decl)=@_;
1036 0 0       0 if (UNIVERSAL::DOES::does($decl,'Treex::PML::Schema::Decl')) {
1037 0         0 return $decl
1038             } else {
1039 0         0 return Treex::PML::Type->new($self,$decl);
1040             }
1041             }
1042              
1043             =item $schema->validate_object (object, type_decl, log, flags)
1044              
1045             Validates the data content of the given object against a specified
1046             type declaration. The type_decl argument must either be an object
1047             derived from the C class or the name of a named
1048             type.
1049              
1050             An array reference may be passed as the optional 3rd argument C
1051             to obtain a detailed report of all validation errors.
1052              
1053             The C argument can specify flags that influance the
1054             validation. The following constants can binary-OR'ed to obtain the
1055             fags:
1056              
1057             PML_VALIDATE_NO_TREES - do not validate nested data with roles
1058             #CHIDLNODES or #TREES and do not require that objects with the role
1059             #NODE implement the Treex::PML::Node role.
1060              
1061             PML_VALIDATE_NO_CHILDNODES - do not validate nested data with the
1062             role #CHIDLNODES.
1063              
1064             Returns: 1 if the content conforms, 0 otherwise.
1065              
1066             =cut
1067              
1068             sub validate_object { # (path, base_type)
1069 0     0 1 0 my ($schema, $object, $type,$log)=@_;
1070 0 0 0     0 if (defined $log and UNIVERSAL::isa($log,'ARRAY')) {
1071 0         0 croak "Treex::PML::Schema::validate_object: log must be an ARRAY reference";
1072             }
1073 0   0     0 $type ||= $schema->get_type_by_name($type);
1074 0 0       0 if (!ref($type)) {
1075 0         0 croak "Treex::PML::Schema::validate_object: Cannot determine data type";
1076             }
1077 0         0 return $type->validate_object($object,{log => $log});
1078             }
1079              
1080              
1081             =item $schema->validate_field (object, attr-path, type, log)
1082              
1083             This method is similar to C, but in this case the
1084             validation is restricted to the data substructure of C
1085             specified by the C argument.
1086              
1087             C is the type of C specified either by the name of a
1088             named type, or as a Treex::PML::Type, or a type declaration.
1089              
1090             An array reference may be passed as the optional 3rd argument C
1091             to obtain a detailed report of all validation errors.
1092              
1093             Returns: 1 if the content conforms, 0 otherwise.
1094              
1095             =cut
1096              
1097             sub validate_field {
1098 0     0 1 0 my ($schema, $object, $path, $type, $log) = @_;
1099 0 0 0     0 if (defined $log and UNIVERSAL::isa($log,'ARRAY')) {
1100 0         0 croak "Treex::PML::Schema::validate_field: log must be an ARRAY reference";
1101             }
1102 0 0       0 if (!ref($type)) {
1103 0         0 my $named_type = $schema->get_type_by_name($type);
1104 0 0       0 croak "Treex::PML::Schema::validate_field: Cannot find type '$type'"
1105             unless $named_type;
1106 0         0 $type = $named_type;
1107             }
1108 0 0 0     0 if (!(defined($path) and length($path))) {
1109 0         0 return $type->validate_object($object, { log => $log });
1110             }
1111 0         0 $type = $type->find($path);
1112 0 0       0 croak "Treex::PML::Schema::validate_field: Cannot determine data type for attribute-path '$path'" unless $type;
1113             return
1114 0         0 $type->validate_object(Treex::PML::Instance::get_data($object,$path),{ path => $path,
1115             log => $log
1116             });
1117             }
1118              
1119              
1120             =item $schema->get_paths_to_atoms (\@decls, \%opts)
1121              
1122             This method returns a list of all non-periodic canonical paths leading
1123             from given types to atomic values. Currently only the following options
1124             are supported:
1125              
1126             no_childnodes => $bool
1127              
1128             If true, the method does not descent to member types with the role
1129             #CHILDNODES.
1130              
1131             no_nodes => $bool
1132              
1133             If true, the method does not descent to member types with the role
1134             #NODE (except for the starting types).
1135              
1136             with_LM => $bool
1137              
1138             If true, the paths will include a LM step for each List type on the path.
1139              
1140             with_AM => $bool
1141              
1142             If true, the paths will include a AM step for each Alt type on the path.
1143              
1144             with_Seq_brackets => $bool
1145              
1146             If true, the paths will append a [0] after each step representing a sequence element
1147              
1148             =cut
1149              
1150             sub get_paths_to_atoms {
1151 0     0 1 0 my ($self,$types,$opts) = @_;
1152             # find node type
1153              
1154 0 0       0 unless (defined $types) {
1155 0         0 $types = [ $self->node_types ];
1156             }
1157 0   0     0 $opts||={};
1158 0         0 return $self->_get_paths_to_atoms($types,{},$opts);
1159             }
1160              
1161             sub _get_paths_to_atoms {
1162 0     0   0 my ($self,$types,$seen,$opts)=@_;
1163 0         0 my @result;
1164 0         0 my $no_children = $opts->{no_childnodes};
1165 0         0 my $no_nodes = $opts->{no_nodes};
1166 0         0 my $with_LM = $opts->{with_LM};
1167 0         0 my $with_AM = $opts->{with_AM};
1168 0         0 my $with_Seq_brackets = $opts->{with_Seq_brackets};
1169 0         0 foreach my $type (@$types) {
1170 0 0       0 next if $seen->{$type};
1171 0         0 my $decl_is = $type->get_decl_type;
1172 0 0 0     0 next if $no_children and $type->get_role eq '#CHILDNODES';
1173 0 0 0     0 if ($decl_is == PML_TYPE_DECL ||
      0        
      0        
      0        
      0        
      0        
      0        
      0        
1174             $decl_is == PML_ROOT_DECL ||
1175             $decl_is == PML_ATTRIBUTE_DECL ||
1176             $decl_is == PML_MEMBER_DECL ||
1177             $decl_is == PML_ELEMENT_DECL ||
1178             (!$with_LM && $decl_is == PML_LIST_DECL) ||
1179             (!$with_AM && $decl_is == PML_ALT_DECL)) {
1180 0         0 $type = $type->get_knit_content_decl;
1181 0 0 0     0 next if $no_nodes and $type->get_role eq '#NODE';
1182 0         0 redo;
1183             }
1184 0 0       0 next unless ref($type);
1185 0         0 my @members;
1186 0 0       0 if ($decl_is == PML_STRUCTURE_DECL) {
    0          
    0          
    0          
    0          
1187 0         0 @members = map { [$_,$_->get_knit_name] } $type->get_members;
  0         0  
1188             } elsif ($decl_is == PML_CONTAINER_DECL) {
1189 0         0 my $cdecl = $type->get_knit_content_decl;
1190 0 0       0 @members = ((map { [ $_, $_->get_name ] } $type->get_attributes),
  0         0  
1191             ($cdecl ? [$cdecl, '#content'] : ()));
1192             } elsif ($decl_is == PML_SEQUENCE_DECL) {
1193 0 0       0 if ($with_Seq_brackets) {
1194 0         0 @members = map { [ $_, $_->get_name.'[0]' ] } $type->get_elements;
  0         0  
1195             } else {
1196 0         0 @members = map { [ $_, $_->get_name ] } $type->get_elements;
  0         0  
1197             }
1198             } elsif ($decl_is == PML_LIST_DECL) {
1199 0         0 @members = [$type->get_knit_content_decl,'LM'];
1200             } elsif ($decl_is == PML_ALT_DECL) {
1201 0         0 @members = [$type->get_knit_content_decl,'AM'];
1202             } else {
1203 0         0 push @result, qq{};
1204             }
1205 0 0       0 if (@members) {
1206 0         0 for my $m (@members) {
1207 0         0 my ($mdecl,$name) = @$m;
1208 0         0 local $seen->{$type}=1;
1209 0 0 0     0 push @result, map { (defined($_) and length($_)) ? $name."/".$_ : $name }
  0         0  
1210             $self->_get_paths_to_atoms([$mdecl],$seen,$opts);
1211             }
1212             }
1213             }
1214 0         0 my %uniq;
1215 0 0       0 return grep { !$uniq{$_} && ($uniq{$_}=1) } @result;
  0         0  
1216             }
1217              
1218              
1219             =item $schema->attributes (decl...)
1220              
1221             This function tries to emulate the behavior of
1222             C<<< Treex::PML::FSFormat->attributes >>> to some extent.
1223              
1224             Return attribute paths to all atomic subtypes of given type
1225             declarations. If no type declaration objects are given, then types
1226             with role C<#NODE> are assumed. This function never descends to
1227             subtypes with role C<#CHILDNODES>.
1228              
1229             =cut
1230              
1231             sub attributes {
1232 0     0 1 0 my ($self,@types) = @_;
1233             # find node type
1234 0 0       0 return $self->get_paths_to_atoms(@types ? \@types : undef, { no_childnodes => 1 });
1235             }
1236              
1237              
1238              
1239             sub init {
1240 63     63 0 197 my ($schema,$opts)=@_;
1241 63         208 $schema->{URL} = $opts->{URL};
1242 63         231 $schema->{-api_version} = '2.0';
1243             }
1244              
1245              
1246             # these functions are used internally by the serializer
1247             sub serialize_exclude_keys {
1248 11     11 0 78 return qw(URL revision description);
1249             }
1250             sub serialize_get_children {
1251 11     11 0 30 my ($self,$opts)=@_;
1252 11         61 my @children = $self->SUPER::serialize_get_children($opts);
1253             return (
1254 22 100       109 (grep { defined($_->[1]) and length($_->[1]) } (
1255             ['revision',$self->{revision}],
1256             ['description',$self->{description}]
1257             )
1258             ),
1259 62         112 (grep { $_->[0] eq 'reference' } @children),
1260 62         110 (grep { $_->[0] eq 'root' } @children),
1261 11         56 (grep { $_->[0] !~ /^(?:root|reference)$/ } @children)
  62         194  
1262             );
1263             }
1264              
1265             =item $schema->post_process($options)
1266              
1267             Auxiliary method used internally by the PML Schema parser. It
1268             simplifies the schema and for each declaration object creates back
1269             references to its parent declaration and schema and pre-computes the
1270             type attribute path returned by $decl->get_decl_path().
1271              
1272             =cut
1273              
1274             sub post_process {
1275 63     63 1 207 my ($schema,$opts)=@_;
1276 63         417 $schema->simplify($opts);
1277             $schema->for_each_decl(sub{
1278 2201     2201   3089 my ($decl)=@_;
1279 2201         5660 weaken( $decl->{-schema} = $schema );
1280 2201         3028 my $parent = $decl->{-parent};
1281 2201         5263 my $decl_is = $decl->get_decl_type;
1282 2201 100 100     16822 if (
    100 100        
      100        
      100        
      100        
      100        
      100        
1283             $decl_is == PML_STRUCTURE_DECL ||
1284             $decl_is == PML_CONTAINER_DECL ||
1285             $decl_is == PML_SEQUENCE_DECL ||
1286             $decl_is == PML_LIST_DECL ||
1287             $decl_is == PML_ALT_DECL ||
1288             $decl_is == PML_CHOICE_DECL ||
1289             $decl_is == PML_CONSTANT_DECL ||
1290             $decl_is == PML_CDATA_DECL
1291             ) {
1292 986         1912 my $parent_is = $parent->get_decl_type;
1293 986 100 100     3506 if ($parent_is == PML_TYPE_DECL) {
    100 100        
    100 66        
    100          
    100          
    50          
1294 369         745 $decl->{-path} = '!'.$parent->get_name;
1295             } elsif ($parent_is == PML_ROOT_DECL) {
1296 27         121 $decl->{-path} = '';
1297             } elsif ($parent_is == PML_ATTRIBUTE_DECL ||
1298             $parent_is == PML_MEMBER_DECL ||
1299             $parent_is == PML_ELEMENT_DECL) {
1300 450         1629 $decl->{-path} = $parent->{-parent}{-path}.'/'.$parent->get_name;
1301             } elsif ($parent_is == PML_CONTAINER_DECL and $decl_is != PML_ATTRIBUTE_DECL) {
1302 92         384 $decl->{-path} = $parent->{-path}.'/#content';
1303             } elsif ($parent_is == PML_LIST_DECL) {
1304 47         202 $decl->{-path} = $parent->{-path}.'/LM';
1305             } elsif ($parent_is == PML_ALT_DECL) {
1306 1         5 $decl->{-path} = $parent->{-path}.'/AM';
1307             }
1308 986 50 100     3091 if ($decl_is == PML_LIST_DECL and !$decl->{-decl} and $decl->{role} eq '#KNIT') {
      66        
1309             # warn ("List $decl->{-path} with role=\"#KNIT\" must have a content type declaration: assuming !\n");
1310 0         0 __fix_knit_type($schema,$decl,$decl->{-path}.'/LM');
1311             }
1312             } elsif ($decl_is == PML_MEMBER_DECL) {
1313 544 50 66     1755 if (!$decl->{-decl} and $decl->{role} eq '#KNIT') {
1314             # warn ("Member $decl->{-parent}{-path}/$decl->{-name} with role=\"#KNIT\" must have a content type declaration: assuming !\n");
1315 0         0 __fix_knit_type($schema,$decl);
1316             }
1317             }
1318 63         701 });
1319             }
1320              
1321             sub __fix_knit_type {
1322 0     0   0 my ($schema,$decl,$path)=@_;
1323 0         0 $decl->{-decl}='cdata';
1324 0         0 my $cdata = $decl->{cdata}= bless {
1325             format => 'PMLREF',
1326             -xml_name => 'cdata',
1327             -attributes => [ 'format' ],
1328             }, 'Treex::PML::Schema::CDATA';
1329 0         0 weaken( $cdata->{-schema} = $schema );
1330 0         0 weaken( $cdata->{-parent} = $decl );
1331 0 0 0     0 if (defined $path) {
    0          
1332 0         0 $cdata->{-path} = $path;
1333             } elsif ($decl->{-parent} and $decl->{-name}) {
1334 0         0 $cdata->{-path} = "$decl->{-parent}{-path}/$decl->{-name}";
1335             }
1336             }
1337              
1338             sub _traverse_data {
1339 707     707   1167 my ($data,$sub,$seen,$hashes_only)=@_;
1340 707         1492 $seen->{$data}=1;
1341 707 100       2067 if (UNIVERSAL::isa($data,'ARRAY')) {
    50          
1342 337 50       565 $sub->($data,0) unless $hashes_only;
1343 337         598 foreach my $val (@$data) {
1344 499 50 33     1179 if (ref($val) and !exists $seen->{$val}) {
1345 0         0 _traverse_data($val,$sub,$seen,$hashes_only);
1346             }
1347             }
1348             } elsif (UNIVERSAL::isa($data,'HASH')) {
1349 370         916 $sub->($data,1);
1350 370         992 foreach my $val (values %$data) {
1351 2948 100 100     7238 if (ref($val) and !exists $seen->{$val}) {
1352 625         1038 _traverse_data($val,$sub,$seen,$hashes_only);
1353             }
1354             }
1355             }
1356             }
1357              
1358              
1359              
1360             =back
1361              
1362             =head1 CLASSES FOR TYPE DECLARATIONS
1363              
1364             =over 3
1365              
1366             =item L
1367              
1368             =item L
1369              
1370             =item L
1371              
1372             =item L
1373              
1374             =item L
1375              
1376             =item L
1377              
1378             =item L
1379              
1380             =item L
1381              
1382             =item L
1383              
1384             =item L
1385              
1386             =item L
1387              
1388             =item L
1389              
1390             =item L
1391              
1392             =item L
1393              
1394             =back
1395              
1396             =cut
1397              
1398             1;
1399              
1400             __END__