File Coverage

blib/lib/Treex/PML/Schema/Decl.pm
Criterion Covered Total %
statement 103 197 52.2
branch 44 100 44.0
condition 9 29 31.0
subroutine 18 33 54.5
pod 22 24 91.6
total 196 383 51.1


line stmt bran cond sub pod time code
1             package Treex::PML::Schema::Decl;
2              
3             ########################################################################
4             # PML Schema type declaration
5             ########################################################################
6              
7 6     6   37 use strict;
  6         14  
  6         184  
8 6     6   29 use warnings;
  6         11  
  6         148  
9              
10 6     6   29 use vars qw($VERSION);
  6         10  
  6         260  
11             BEGIN {
12 6     6   111 $VERSION='2.24'; # version template
13             }
14 6     6   30 no warnings 'uninitialized';
  6         9  
  6         241  
15 6     6   35 use Scalar::Util qw( weaken );
  6         11  
  6         288  
16 6     6   40 use Carp;
  6         10  
  6         296  
17 6     6   55 use Treex::PML::Schema::Constants;
  6         17  
  6         559  
18 6     6   38 use base qw(Treex::PML::Schema::XMLNode);
  6         9  
  6         12676  
19              
20             =head1 NAME
21              
22             Treex::PML::Schema::Decl - implements PML schema type declaration
23              
24             =head1 DESCRIPTION
25              
26             This is an abstract class from which all specific type declaration
27             classes inherit.
28              
29             =head1 INHERITANCE
30              
31             This class inherits from L.
32              
33             =head1 METHODS
34              
35             =over 3
36              
37             =cut
38              
39 0     0 0 0 sub new { croak("Can't create ".__PACKAGE__) }
40              
41             # compatibility with old Treex::PML::Type
42              
43 0     0 0 0 sub type_decl { return $_[0] };
44              
45             =item $decl->get_schema ()
46              
47             =item $decl->schema ()
48              
49             Return C the declaration belongs to.
50              
51             =cut
52              
53 0     0 1 0 sub schema { return $_[0]->{-schema} }
54              
55             =item $decl->get_schema ()
56              
57             Same as C<< $decl->schema() >>.
58              
59             =cut
60              
61 0     0 1 0 sub get_schema { return $_[0]->schema }
62              
63             =item $decl->get_decl_type ()
64              
65             Return the type of declaration as an integer constant (see
66             L).
67              
68             =item $decl->get_decl_type_str ()
69              
70             Return the type of declaration as string; one of: type, root,
71             structure, container, sequence, list, alt, cdata, choice, constant,
72             attribute, member, element.
73              
74             =cut
75              
76 0     0 1 0 sub get_decl_type { return(undef); } # VIRTUAL
77 0     0 1 0 sub get_decl_type_str { return(undef); } # VIRTUAL
78              
79             =item $decl->is_atomic ()
80              
81             Return 1 if the declaration is of atomic type (cdata, choice,
82             constant), 0 if it is a structured type (structure, container,
83             sequence, list, alt), or undef, if it is an auxiliary declaration
84             (root, type, attribute, member, element).
85              
86             =cut
87              
88 0     0 1 0 sub is_atomic { croak "is_atomic(): UNKNOWN TYPE"; } # VIRTUAL
89              
90             =item $decl->get_content_decl ()
91              
92             For declarations with content (type, root, container, list, alt,
93             attribute, member, element), return the content declaration; return
94             undef for other declarations. This method transparently resolves
95             references to named types.
96              
97             =cut
98              
99             sub get_content_decl {
100 2889     2889 1 4951 my $self = shift;
101 2889         3599 my $no_resolve = shift;
102 2889 100       6398 if ($self->{-decl}) {
    100          
    50          
103 1819         6201 return $self->{ $self->{-decl} };
104             } elsif (my $resolved = $self->{-resolved}) {
105 827         2256 return $resolved;
106             } elsif (my $type_ref = $self->{type}) {
107 243         369 my $schema = $self->{-schema};
108 243 50       428 if ($schema) {
109 243         505 my $type = $schema->{type}{ $type_ref };
110 243 50       502 if ($no_resolve) {
    50          
111 0         0 return $type;
112             } elsif ($type) {
113 243         480 weaken($self->{-resolved} = $type->get_content_decl);
114 243         711 return $self->{-resolved};
115             } else {
116 0         0 return undef;
117             }
118             } else {
119 0         0 croak "Declaration not associated with a schema";
120             }
121             }
122 0         0 return(undef);
123             }
124              
125             =item $decl->get_knit_content_decl ()
126              
127             If the data type has a role '#KNIT', return a type declaration for the
128             knitted content (Note: PML 1.1.2 allows role '#KNIT' role on list,
129             element, and member declarations, but element knitting is not
130             currenlty implemented). Otherwise return the same as get_content_decl.
131              
132             =cut
133              
134              
135             sub get_knit_content_decl {
136 276     276 1 413 my $self = shift;
137 276 100 100     1125 return (defined($self->{role}) and $self->{role} eq '#KNIT') ?
138             $self->get_type_ref_decl
139             : $self->get_content_decl;
140             }
141              
142             =item $decl->get_type_ref ()
143              
144             If the declaration has content and the content is specified via a
145             reference to a named type, return the name of the referred type.
146             Otherwise return undef.
147              
148             =cut
149              
150             sub get_type_ref {
151 1826     1826 1 3357 return $_[0]->{type};
152             }
153              
154             =item $decl->get_type_ref_decl ()
155              
156             Retrun content declaration object (if any), but only if it is
157             specified via a reference to a named type. In all other cases, return
158             undef.
159              
160             =cut
161              
162             sub get_type_ref_decl {
163 17     17 1 38 my $self = shift;
164 17         34 my $no_resolve = shift;
165 17 100       104 if (my $resolved = $self->{-resolved}) {
    50          
166 7         22 return $resolved;
167             } elsif (my $type_ref = $self->{type}) {
168 10         24 my $schema = $self->{-schema};
169 10 50       33 if ($schema) {
170 10         32 my $type = $schema->{type}{ $type_ref };
171             return $no_resolve ? $type
172             : $type ?
173 10 50       52 ($self->{-resolved} = $type->get_content_decl)
    50          
174             : undef ;
175             }
176             }
177 0         0 return(undef);
178             }
179              
180             =item $decl->get_base_type_name ()
181              
182             If the declaration is a nested (even deeply) part of a named type
183             declaration, return the name of that named type.
184              
185             =cut
186              
187             sub get_base_type_name {
188 0     0 1 0 my $path = $_[0]->{-path};
189 0 0       0 if ($path=~m{^!([^/]+)}) {
190 0         0 return $1;
191             } else {
192 0         0 return(undef);
193             }
194             }
195              
196             =item $decl->get_parent_decl ()
197              
198             If this declaration is nested, return its parent declaration.
199              
200             =cut
201              
202 583     583 1 1124 sub get_parent_decl { return $_[0]->{-parent} }
203              
204             =item $decl->get_decl_path ()
205              
206             Return a cannonical attribute path leading to the declaration
207             (starting either at a named type or the root type declaration).
208              
209             =cut
210              
211 2621     2621 1 6620 sub get_decl_path { return $_[0]->{-path}; }
212              
213             =item $decl->get_role
214              
215             If the declaration is associated with a role, return it.
216              
217             =cut
218              
219 1256   100 1256 1 6287 sub get_role { return $_[0]->{role}||'' }
220              
221              
222             =item $decl->find (attribute-path,noresolve)
223              
224             Locate a nested declaration specified by C starting
225             from the current type. See C<$schema-Efind_type_by_path> for details
226             about locating declarations.
227              
228             =cut
229              
230             sub find {
231 0     0 1 0 my ($self, $path,$noresolve) = @_;
232             # find node type
233 0         0 my $type = $self->type_decl;
234 0         0 return $self->schema->find_type_by_path($path,$noresolve,$type);
235             }
236              
237             =item $decl->find_role (role, opts)
238              
239             Search declarations with a given role nested within this declaration.
240             In scalar context, return the first declaration that matches, in array
241             context return all such declarations.
242              
243             The last argument C can be used to pass some flags to the
244             algorithm. Currently only the flag C is available. If
245             true, then the function never recurses into content declaration of
246             declarations with the role #CHILDNODES.
247              
248             =cut
249              
250             sub find_role {
251 0     0 1 0 my ($self, $role, $opts) = @_;
252 0         0 return $self->schema->find_role($role,$self->type_decl,$opts);
253             }
254              
255             =item $decl->convert_from_hash (class, hash, schema, path)
256              
257             Compatibility method building the schema object from a nested hash
258             structure created by XML::Simple which was used in older
259             implementations. This is useful for upgrading objects stored in old
260             binary dumps. Not to be used directly.
261              
262             =cut
263              
264             sub convert_from_hash {
265 212     212 1 293 my ($class, $decl, $schema, $path) = @_;
266 212         225 my $sub;
267             my $decl_type;
268 212 100       574 if ($sub = $decl->{structure}) {
    50          
    50          
    100          
    100          
    100          
    100          
    50          
269 4         5 $decl_type = 'structure';
270 4         12 bless $sub, 'Treex::PML::Schema::Struct';
271 4         24 $sub->{'-attributes'}=[qw(role name type)];
272 4 50       11 if (my $members = $sub->{member}) {
273 4         7 my ($name, $mdecl);
274 4         15 while (($name, $mdecl) = each %$members) {
275 104         137 bless $mdecl, 'Treex::PML::Schema::Member';
276 104         140 $mdecl->{'-xml_name'}='member';
277 104         209 $mdecl->{'-attributes'}=[qw(name required as_attribute type role)];
278 104         212 weaken($mdecl->{-parent}=$sub);
279 104         195 weaken($mdecl->{-schema}=$schema);
280 104         297 $class->convert_from_hash($mdecl,
281             $schema,
282             $path.'/'.$name
283             );
284 104 50 33     311 if (!$mdecl->{-decl} and $mdecl->{role} eq '#KNIT') {
285             # warn("Member $decl->{-parent}{-path}/$decl->{-name} with role=\"#KNIT\" must have a content type declaration: assuming !\n");
286 0         0 Treex::PML::Schema::__fix_knit_type($schema,$mdecl);
287             }
288             }
289             }
290             } elsif ($sub = $decl->{container}) {
291 0         0 $decl_type = 'container';
292 0         0 bless $sub, 'Treex::PML::Schema::Container';
293 0         0 $sub->{'-attributes'}=[qw(role name type)];
294 0 0       0 if (my $members = $sub->{attribute}) {
295 0         0 my ($name, $mdecl);
296 0         0 while (($name, $mdecl) = each %$members) {
297 0         0 bless $mdecl, 'Treex::PML::Schema::Attribute';
298 0         0 $mdecl->{'-xml_name'}='attribute';
299 0         0 $mdecl->{'-attributes'}=[qw(name required type role)];
300 0         0 weaken($mdecl->{-schema}=$schema);
301 0         0 weaken($mdecl->{-parent}=$sub);
302 0         0 $class->convert_from_hash($mdecl,
303             $schema,
304             $path.'/'.$name
305             );
306             }
307             }
308 0         0 $class->convert_from_hash($sub, $schema, $path.'/#content');
309             } elsif ($sub = $decl->{sequence}) {
310 0         0 $decl_type = 'sequence';
311 0         0 bless $sub, 'Treex::PML::Schema::Seq';
312 0         0 $sub->{'-attributes'}=[qw(role content_pattern type)];
313 0 0       0 if (my $members = $sub->{element}) {
314 0         0 my ($name, $mdecl);
315 0         0 while (($name, $mdecl) = each %$members) {
316 0         0 bless $mdecl, 'Treex::PML::Schema::Element';
317 0         0 $mdecl->{'-xml_name'}='element';
318 0         0 $mdecl->{'-attributes'}=[qw(name type role)];
319 0         0 weaken($mdecl->{-schema}=$schema);
320 0         0 weaken($mdecl->{-parent}=$sub);
321 0         0 $class->convert_from_hash($mdecl,
322             $schema,
323             $path.'/'.$name
324             );
325             }
326             }
327             } elsif ($sub = $decl->{list}) {
328 4         7 $decl_type = 'list';
329 4         15 bless $sub, 'Treex::PML::Schema::List';
330 4         16 $sub->{'-attributes'}=[qw(role ordered type)];
331 4         18 $class->convert_from_hash($sub, $schema, $path.'/LM');
332 4 50 33     23 if (!$sub->{-decl} and $sub->{role} eq '#KNIT') {
333             # warn("List $sub->{-name} with role=\"#KNIT\" must have a content type declaration: assuming !\n");
334 0         0 Treex::PML::Schema::__fix_knit_type($schema,$sub,$path.'/LM');
335             }
336             } elsif ($sub = $decl->{alt}) {
337 100         109 $decl_type = 'alt';
338 100         125 bless $sub, 'Treex::PML::Schema::Alt';
339 100         200 $sub->{'-attributes'}=[qw(role type)];
340 100         209 $class->convert_from_hash($sub, $schema, $path.'/AM');
341             } elsif ($sub = $decl->{choice}) {
342 2         4 $decl_type = 'choice';
343             # convert from an ARRAY to a hash
344 2 50       7 if (ref($sub) eq 'ARRAY') {
    0          
345             $sub = $decl->{choice} = bless { values => [
346             map {
347 2 50       6 ref($_) eq 'HASH' ? $_->{content} : $_
  226         403  
348             } @$sub
349             ],
350             }, 'Treex::PML::Schema::Choice';
351             } elsif (ref($sub)) {
352 0         0 bless $sub, 'Treex::PML::Schema::Choice';
353 0 0       0 if (ref($sub->{value}) eq 'ARRAY') {
354             $sub->{values} = [
355 0         0 map { $_->{content} } @{$sub->{value}}
  0         0  
  0         0  
356             ];
357 0         0 delete $sub->{value};
358             }
359             } else {
360 0         0 croak __PACKAGE__.": Invalid element in type '$path'?\n";
361             }
362             } elsif ($sub = $decl->{cdata}) {
363 98         103 $decl_type = 'cdata';
364 98         114 bless $sub, 'Treex::PML::Schema::CDATA';
365 98         158 $sub->{'-attributes'}=['format'];
366             } elsif (exists $decl->{constant}) { # can be 0
367 0         0 $sub = $decl->{constant};
368 0         0 $decl_type = 'constant';
369 0 0       0 unless (ref($sub)) {
370 0         0 $sub = $decl->{constant} = bless { value => $sub }, 'Treex::PML::Schema::Constant';
371             }
372             ## this is just a scalar value
373             # bless $sub, 'Treex::PML::Schema::Constant';
374             }
375 212         301 $sub->{'-xml_name'}=$decl_type;
376 212         422 weaken( $decl->{-schema} = $schema );
377 212         256 $decl->{-decl} = $decl_type;
378 212 50 66     420 unless (exists($sub->{-schema}) and exists($sub->{-parent})) {
379 212 100       390 weaken( $sub->{-schema} = $schema ) unless $sub->{-schema};
380 212 50       525 weaken( $sub->{-parent} = $decl ) unless $sub->{-parent};
381 212         325 $sub->{-path} = $path;
382             }
383 212         274 return $decl;
384             }
385              
386              
387             =item $decl->get_normal_fields ()
388              
389             This method is provided for convenience.
390              
391             For a structure type, return names of its members, for a container
392             return names of its attributes plus the name '#content' referring to
393             the container's content value. In both cases, eliminate fields of
394             values with role C<#CHILDNODES> and strip a possible C<.rf> suffix of
395             fields with role C<#KNIT>.
396              
397             =cut
398              
399             sub get_normal_fields {
400 0     0 1 0 my ($self,$path)=@_;
401 0 0       0 my $type = defined($path) ? $self->find($path) : $self;
402 0         0 my $struct;
403             my $members;
404 0 0       0 return unless ref $type;
405 0         0 my $decl_is = $type->get_decl_type;
406 0 0 0     0 if ($decl_is == PML_TYPE_DECL ||
      0        
      0        
      0        
407             $decl_is == PML_ROOT_DECL ||
408             $decl_is == PML_ATTRIBUTE_DECL ||
409             $decl_is == PML_MEMBER_DECL ||
410             $decl_is == PML_ELEMENT_DECL ) {
411 0 0       0 if ($type = $type->get_content_decl) {
412 0         0 $decl_is = $type->get_decl_type;
413             } else {
414 0         0 return ();
415             }
416             }
417 0         0 my @members = ();
418 0 0       0 if ($decl_is == PML_STRUCTURE_DECL) {
    0          
419             @members =
420 0         0 map { $_->get_knit_name }
421 0         0 grep { $_->get_role ne '#CHILDNODES' }
  0         0  
422             $type->get_members;
423             } elsif ($decl_is == PML_CONTAINER_DECL) {
424 0         0 my $cdecl = $type->get_content_decl;
425 0 0 0     0 @members = ($type->get_attribute_names,
426             ($cdecl && $type->get_role ne '#CHILDNODES') ? '#content' : ());
427             }
428             }
429              
430             =item $decl->get_childnodes_decls ()
431              
432             If the $decl has the role #NODE, this method locates a sub-declaration
433             with role #CHILDNODES and returns a list of declarations of the child
434             nodes.
435              
436             =cut
437              
438             sub get_childnodes_decls {
439 0     0 1 0 my ($self) = @_;
440 0 0       0 if ($self->get_decl_type == PML_ELEMENT_DECL) {
441 0         0 $self = $self->get_content_decl;
442             }
443 0 0       0 return unless $self->get_role eq '#NODE';
444 0         0 my ($ch) = $self->find_members_by_role('#CHILDNODES');
445 0 0       0 if ($ch) {
446 0         0 my $ch_is = $ch->get_decl_type;
447 0 0       0 if ($ch_is == PML_MEMBER_DECL) {
448 0         0 $ch = $ch->get_content_decl;
449 0         0 $ch_is = $ch->get_decl_type;
450             }
451 0 0       0 if ($ch_is == PML_SEQUENCE_DECL) {
    0          
452 0         0 return $ch->get_elements;
453             } elsif ($ch_is == PML_LIST_DECL) {
454 0         0 return $ch->get_content_decl;
455             }
456             }
457 0         0 return;
458             }
459              
460              
461             =item $decl->get_attribute_paths (\%opts)
462              
463             Return attribute paths leading from this declaration to all (possibly
464             deeply) nested declarations of atomic type. This method is an alias for
465              
466             $decl->schema->get_paths_to_atoms([$decl],\%opts)
467              
468             See L for details.
469              
470             =cut
471              
472             sub get_attribute_paths { # OLD NAME
473 0     0 1 0 my ($self,$opts)=@_;
474 0         0 return $self->get_paths_to_atoms($opts);
475             }
476              
477             =item $decl->get_paths_to_atoms (\%opts)
478              
479             Same as
480              
481             $decl->schema->get_paths_to_atoms([$decl],\%opts)
482              
483             See L for details.
484              
485             =cut
486              
487             sub get_paths_to_atoms {
488 0     0 1 0 my ($self,$opts)=@_;
489 0         0 return $self->schema->get_paths_to_atoms([$self],$opts);
490             }
491              
492             =item $decl->validate_object($object);
493              
494             See C method of L.
495              
496             =cut
497              
498             sub validate_object {
499 0     0 1 0 croak "Not implemented for the class ".__PACKAGE__;
500             }
501              
502             =item $decl->for_each_decl (sub{ ... })
503              
504             This method traverses all nested sub-declarations and calls a given
505             subroutine passing the sub-declaration object as a parameter.
506              
507             =cut
508              
509             sub for_each_decl {
510 5446     5446 1 8877 my ($self,$sub)=@_;
511 5446         12094 $sub->($self);
512             # (a container or #KNIT member can have both type and children)
513             # traverse descendant type declarations
514 5446         9090 for my $d (qw(member attribute element)) {
515 15321 100       28825 if (ref $self->{$d}) {
516 781         1026 foreach (values %{$self->{$d}}) {
  781         2278  
517 1976         4067 $_->for_each_decl($sub);
518             }
519 781 100       1677 last if $d eq 'attribute'; # there may be content
520 618         1004 return; # otherwise
521             }
522             }
523 4828         7274 for my $d (qw(list alt structure container sequence),
524             qw(cdata choice constant)) {
525 30705 100       52439 if (exists $self->{$d}) {
526 2433         6684 $self->{$d}->for_each_decl($sub);
527 2433         4732 return;
528             }
529             }
530             }
531              
532             =item $decl->write ({option => value})
533              
534             This method serializes a declaration to XML. See Treex::PML::Schema->write for
535             details and Treex::PML::Schema::XMLNode->write for implementation.
536              
537             =cut
538              
539              
540             =back
541              
542             =cut
543              
544              
545             1;
546             __END__