| 
49
 | 
     1 package IMPL::DOM::Schema;
 | 
| 
 | 
     2 use strict;
 | 
| 
 | 
     3 use warnings;
 | 
| 
 | 
     4 
 | 
| 
 | 
     5 require IMPL::DOM::Schema::ComplexNode;
 | 
| 
 | 
     6 require IMPL::DOM::Schema::ComplexType;
 | 
| 
 | 
     7 require IMPL::DOM::Schema::SimpleNode;
 | 
| 
 | 
     8 require IMPL::DOM::Schema::SimpleType;
 | 
| 
 | 
     9 require IMPL::DOM::Schema::Node;
 | 
| 
 | 
    10 require IMPL::DOM::Schema::AnyNode;
 | 
| 
 | 
    11 require IMPL::DOM::Schema::NodeList;
 | 
| 
 | 
    12 require IMPL::DOM::Schema::NodeSet;
 | 
| 
 | 
    13 require IMPL::DOM::Schema::Property;
 | 
| 
 | 
    14 require IMPL::DOM::Schema::SwitchNode;
 | 
| 
101
 | 
    15 require IMPL::DOM::Schema::Validator;
 | 
| 
 | 
    16 require IMPL::DOM::Navigator::Builder;
 | 
| 
 | 
    17 require IMPL::DOM::XMLReader;
 | 
| 
49
 | 
    18 
 | 
| 
 | 
    19 use base qw(IMPL::DOM::Document);
 | 
| 
 | 
    20 use IMPL::Class::Property;
 | 
| 
 | 
    21 use IMPL::Class::Property::Direct;
 | 
| 
101
 | 
    22 use File::Spec;
 | 
| 
49
 | 
    23 
 | 
| 
 | 
    24 our %CTOR = (
 | 
| 
 | 
    25     'IMPL::DOM::Document' => sub { nodeName => 'schema' }
 | 
| 
 | 
    26 );
 | 
| 
 | 
    27 
 | 
| 
 | 
    28 BEGIN {
 | 
| 
 | 
    29     private _direct property _TypesMap => prop_all;
 | 
| 
101
 | 
    30     public _direct property baseDir => prop_all;
 | 
| 
49
 | 
    31     public _direct property BaseSchemas => prop_get | owner_set;
 | 
| 
 | 
    32     private _direct property _Validators => prop_all;
 | 
| 
 | 
    33 }
 | 
| 
 | 
    34 
 | 
| 
 | 
    35 sub resolveType {
 | 
| 
 | 
    36     $_[0]->{$_TypesMap}->{$_[1]};
 | 
| 
 | 
    37 }
 | 
| 
 | 
    38 
 | 
| 
102
 | 
    39 sub CTOR {
 | 
| 
 | 
    40 	my ($this,%args) = @_;
 | 
| 
 | 
    41 	
 | 
| 
 | 
    42 	$this->{$baseDir} = ($args{baseDir} || '.');
 | 
| 
 | 
    43 }
 | 
| 
 | 
    44 
 | 
| 
49
 | 
    45 sub Create {
 | 
| 
 | 
    46     my ($this,$nodeName,$class,$refArgs) = @_;
 | 
| 
 | 
    47     
 | 
| 
100
 | 
    48     die new IMPL::Exception('Invalid node class') unless $class->isa('IMPL::DOM::Node');
 | 
| 
49
 | 
    49     
 | 
| 
101
 | 
    50     if ($class->isa('IMPL::DOM::Schema::Validator')) {
 | 
| 
 | 
    51     	$class = "IMPL::DOM::Schema::Validator::$nodeName";
 | 
| 
 | 
    52     	unless (eval {$class->can('new')}) {
 | 
| 
 | 
    53     		eval "require $class; 1;";
 | 
| 
 | 
    54     		my $e = $@;
 | 
| 
 | 
    55 			die new IMPL::Exception("Invalid validator",$class,$e) if $e;
 | 
| 
 | 
    56     	}
 | 
| 
 | 
    57     }
 | 
| 
 | 
    58     
 | 
| 
 | 
    59     return $this->SUPER::Create($nodeName,$class,$refArgs);
 | 
| 
49
 | 
    60 }
 | 
| 
 | 
    61 
 | 
| 
 | 
    62 sub Process {
 | 
| 
 | 
    63     my ($this) = @_;
 | 
| 
 | 
    64     
 | 
| 
101
 | 
    65     # process instructions
 | 
| 
 | 
    66     $this->Include($_) foreach map $_->nodeProperty('source'), $this->selectNodes('Include');
 | 
| 
 | 
    67     
 | 
| 
 | 
    68     # build types map
 | 
| 
49
 | 
    69     $this->{$_TypesMap} = { map { $_->type, $_ } $this->selectNodes(sub { $_[0]->nodeName eq 'ComplexType' || $_[0]->nodeName eq 'SimpleType' } ) };
 | 
| 
 | 
    70 }
 | 
| 
 | 
    71 
 | 
| 
101
 | 
    72 sub Include {
 | 
| 
 | 
    73 	my ($this,$file) = @_;
 | 
| 
 | 
    74 	
 | 
| 
102
 | 
    75 	my $schema = $this->LoadSchema(File::Spec->catfile($this->baseDir, $file));
 | 
| 
101
 | 
    76 	
 | 
| 
 | 
    77 	$this->appendRange( $schema->childNodes );
 | 
| 
 | 
    78 }
 | 
| 
 | 
    79 
 | 
| 
 | 
    80 sub LoadSchema {
 | 
| 
102
 | 
    81 	my ($this,$file) = @_;
 | 
| 
 | 
    82 	
 | 
| 
 | 
    83 	$file = File::Spec->rel2abs($file);
 | 
| 
101
 | 
    84 	
 | 
| 
 | 
    85 	my $class = ref $this || $this;
 | 
| 
 | 
    86 	
 | 
| 
 | 
    87 	my $reader = new IMPL::DOM::XMLReader(
 | 
| 
 | 
    88 		Navigator => new IMPL::DOM::Navigator::Builder(
 | 
| 
 | 
    89 			$class,
 | 
| 
 | 
    90 			$class->MetaSchema
 | 
| 
 | 
    91 		)
 | 
| 
 | 
    92 	);
 | 
| 
 | 
    93 		
 | 
| 
102
 | 
    94 	$reader->ParseFile($file);
 | 
| 
101
 | 
    95 	
 | 
| 
 | 
    96 	my $schema = $reader->Navigator->Document;
 | 
| 
 | 
    97 	
 | 
| 
 | 
    98 	my ($vol,$dir) = File::Spec->splitpath($file);
 | 
| 
 | 
    99 	
 | 
| 
 | 
   100 	$schema->baseDir($dir);
 | 
| 
 | 
   101 	
 | 
| 
 | 
   102 	my @errors = $class->MetaSchema->Validate($schema);
 | 
| 
 | 
   103 	
 | 
| 
 | 
   104 	die new IMPL::Exception("Schema is invalid",$file,map( $_->Message, @errors ) ) if @errors;
 | 
| 
 | 
   105 	
 | 
| 
 | 
   106 	$schema->Process;
 | 
| 
 | 
   107 	
 | 
| 
 | 
   108 	return $schema;
 | 
| 
 | 
   109 }
 | 
| 
 | 
   110 
 | 
| 
49
 | 
   111 sub Validate {
 | 
| 
 | 
   112     my ($this,$node) = @_;
 | 
| 
 | 
   113     
 | 
| 
 | 
   114     if ( my ($schemaNode) = $this->selectNodes(sub { $_[0]->name eq $node->nodeName })) {
 | 
| 
 | 
   115         $schemaNode->Validate($node);
 | 
| 
 | 
   116     } else {
 | 
| 
 | 
   117         return new IMPL::DOM::Schema::ValidationError(Message=> "A specified document doesn't match the schema");
 | 
| 
 | 
   118     }
 | 
| 
 | 
   119 }
 | 
| 
 | 
   120 
 | 
| 
 | 
   121 my $schema;
 | 
| 
 | 
   122 
 | 
| 
 | 
   123 sub MetaSchema {
 | 
| 
 | 
   124     
 | 
| 
 | 
   125     return $schema if $schema;
 | 
| 
 | 
   126     
 | 
| 
 | 
   127     $schema = new IMPL::DOM::Schema;
 | 
| 
 | 
   128     
 | 
| 
 | 
   129     $schema->appendRange(
 | 
| 
 | 
   130         IMPL::DOM::Schema::ComplexNode->new(name => 'schema')->appendRange(
 | 
| 
 | 
   131             IMPL::DOM::Schema::NodeSet->new()->appendRange(
 | 
| 
 | 
   132                 IMPL::DOM::Schema::Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   133                 IMPL::DOM::Schema::Node->new(name => 'ComplexType', type => 'ComplexType', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   134                 IMPL::DOM::Schema::Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   135                 IMPL::DOM::Schema::Node->new(name => 'SimpleType', type => 'SimpleType', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   136                 IMPL::DOM::Schema::SimpleNode->new(name => 'Node', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   137                 IMPL::DOM::Schema::SimpleNode->new(name => 'Include', minOccur => 0, maxOccur=>'unbounded')->appendRange(
 | 
| 
 | 
   138                     IMPL::DOM::Schema::Property->new(name => 'source')
 | 
| 
 | 
   139                 )
 | 
| 
 | 
   140             ),
 | 
| 
 | 
   141         ),
 | 
| 
 | 
   142         IMPL::DOM::Schema::ComplexType->new(type => 'NodeSet', nativeType => 'IMPL::DOM::Schema::NodeSet')->appendRange(
 | 
| 
 | 
   143             IMPL::DOM::Schema::NodeSet->new()->appendRange(
 | 
| 
 | 
   144                 IMPL::DOM::Schema::Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   145                 IMPL::DOM::Schema::Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   146                 IMPL::DOM::Schema::SimpleNode->new(name => 'Node', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   147                 IMPL::DOM::Schema::SwitchNode->new(minOccur => 0, maxOccur => 1)->appendRange(
 | 
| 
 | 
   148                     IMPL::DOM::Schema::SimpleNode->new(name => 'AnyNode'),
 | 
| 
 | 
   149                     IMPL::DOM::Schema::Node->new(name => 'SwitchNode',type => 'SwitchNode')
 | 
| 
 | 
   150                 )
 | 
| 
 | 
   151             )
 | 
| 
 | 
   152         ),
 | 
| 
 | 
   153         IMPL::DOM::Schema::ComplexType->new(type => 'SwitchNode', nativeType => 'IMPL::DOM::Schema::SwitchNode')->appendRange(
 | 
| 
 | 
   154             IMPL::DOM::Schema::NodeSet->new()->appendRange(
 | 
| 
 | 
   155                 IMPL::DOM::Schema::Node->new(name => 'ComplexNode', type=>'ComplexNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   156                 IMPL::DOM::Schema::Node->new(name => 'SimpleNode', type=>'SimpleNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   157                 IMPL::DOM::Schema::SimpleNode->new(name => 'Node', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   158             )
 | 
| 
 | 
   159         ),
 | 
| 
 | 
   160         IMPL::DOM::Schema::ComplexType->new(type => 'NodeList', nativeType => 'IMPL::DOM::Schema::NodeList')->appendRange(
 | 
| 
 | 
   161             IMPL::DOM::Schema::NodeSet->new()->appendRange(
 | 
| 
 | 
   162                 IMPL::DOM::Schema::Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   163                 IMPL::DOM::Schema::Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   164                 IMPL::DOM::Schema::Node->new(name => 'SwitchNode',type => 'SwitchNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   165                 IMPL::DOM::Schema::SimpleNode->new(name => 'Node', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   166                 IMPL::DOM::Schema::SimpleNode->new(name => 'AnyNode', minOccur => 0, maxOccur=>'unbounded'),
 | 
| 
 | 
   167             )
 | 
| 
 | 
   168         ),
 | 
| 
 | 
   169         IMPL::DOM::Schema::ComplexType->new(type => 'ComplexType', nativeType => 'IMPL::DOM::Schema::ComplexType')->appendRange(
 | 
| 
 | 
   170             IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   171                 IMPL::DOM::Schema::SwitchNode->new()->appendRange(
 | 
| 
 | 
   172                     IMPL::DOM::Schema::Node->new(name => 'NodeSet', type => 'NodeSet'),
 | 
| 
 | 
   173                     IMPL::DOM::Schema::Node->new(name => 'NodeList',type => 'NodeList'),
 | 
| 
 | 
   174                 ),
 | 
| 
102
 | 
   175                 IMPL::DOM::Schema::Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0),
 | 
| 
49
 | 
   176                 IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator')
 | 
| 
 | 
   177             ),
 | 
| 
 | 
   178             new IMPL::DOM::Schema::Property(name => 'type')
 | 
| 
 | 
   179         ),
 | 
| 
 | 
   180         IMPL::DOM::Schema::ComplexType->new(type => 'ComplexNode', nativeType => 'IMPL::DOM::Schema::ComplexNode')->appendRange(
 | 
| 
 | 
   181             IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   182                 IMPL::DOM::Schema::SwitchNode->new()->appendRange(
 | 
| 
 | 
   183                     IMPL::DOM::Schema::Node->new(name => 'NodeSet', type => 'NodeSet'),
 | 
| 
 | 
   184                     IMPL::DOM::Schema::Node->new(name => 'NodeList',type => 'NodeList'),
 | 
| 
 | 
   185                 ),
 | 
| 
102
 | 
   186                 IMPL::DOM::Schema::Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0),
 | 
| 
49
 | 
   187                 IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator')
 | 
| 
 | 
   188             ),
 | 
| 
 | 
   189             new IMPL::DOM::Schema::Property(name => 'name')
 | 
| 
 | 
   190         ),
 | 
| 
 | 
   191         IMPL::DOM::Schema::ComplexType->new(type => 'SimpleType', nativeType => 'IMPL::DOM::Schema::SimpleType')->appendRange(
 | 
| 
102
 | 
   192             IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   193             	IMPL::DOM::Schema::Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0),
 | 
| 
49
 | 
   194                 IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator')
 | 
| 
 | 
   195             ),
 | 
| 
 | 
   196             new IMPL::DOM::Schema::Property(name => 'type')
 | 
| 
 | 
   197         ),
 | 
| 
 | 
   198         IMPL::DOM::Schema::ComplexType->new(type => 'SimpleNode', nativeType => 'IMPL::DOM::Schema::SimpleNode')->appendRange(
 | 
| 
102
 | 
   199             IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   200             	IMPL::DOM::Schema::Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0),
 | 
| 
49
 | 
   201                 IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator')
 | 
| 
 | 
   202             ),
 | 
| 
 | 
   203             new IMPL::DOM::Schema::Property(name => 'name')
 | 
| 
 | 
   204         ),
 | 
| 
 | 
   205         IMPL::DOM::Schema::ComplexType->new(type => 'Validator', nativeType => 'IMPL::DOM::Schema::Validator')->appendRange(
 | 
| 
 | 
   206             IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   207                 IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0)
 | 
| 
100
 | 
   208             )
 | 
| 
102
 | 
   209         ),
 | 
| 
 | 
   210         IMPL::DOM::Schema::ComplexType->new(type => 'Property', nativeType => 'IMPL::DOM::Schema::Property' )->appendRange(
 | 
| 
 | 
   211         	IMPL::DOM::Schema::NodeList->new()->appendRange(
 | 
| 
 | 
   212         		IMPL::DOM::Schema::AnyNode->new(maxOccur => 'unbounded', minOccur => 0)
 | 
| 
 | 
   213         	),
 | 
| 
 | 
   214         	IMPL::DOM::Schema::Property->new(name => 'name')
 | 
| 
49
 | 
   215         )
 | 
| 
 | 
   216     );
 | 
| 
 | 
   217     
 | 
| 
 | 
   218     $schema->Process;
 | 
| 
 | 
   219     
 | 
| 
 | 
   220     return $schema;
 | 
| 
 | 
   221 }
 | 
| 
 | 
   222 
 | 
| 
 | 
   223 1;
 | 
| 
 | 
   224 
 | 
| 
 | 
   225 __END__
 | 
| 
 | 
   226 
 | 
| 
 | 
   227 =pod
 | 
| 
 | 
   228 
 | 
| 
98
 | 
   229 =head1 NAME
 | 
| 
 | 
   230 
 | 
| 
 | 
   231 C<IMPL::DOM::Schema> - Схема документа.
 | 
| 
 | 
   232 
 | 
| 
49
 | 
   233 =head1 DESCRIPTION
 | 
| 
 | 
   234 
 | 
| 
98
 | 
   235 C<use base qw(IMPL::DOM::Document)>
 | 
| 
 | 
   236 
 | 
| 
 | 
   237 DOM схема - это документ, состоящий из определенных узлов, описывающая структуру
 | 
| 
 | 
   238 других документов.
 | 
| 
49
 | 
   239 
 | 
| 
 | 
   240 =head1 METHODS
 | 
| 
 | 
   241 
 | 
| 
 | 
   242 =over
 | 
| 
 | 
   243 
 | 
| 
 | 
   244 =item C<< $obj->Process() >>
 | 
| 
 | 
   245 
 | 
| 
 | 
   246 Обновляет таблицу типов из содержимого.
 | 
| 
 | 
   247 
 | 
| 
 | 
   248 =item C<< $obj->ResolveType($typeName) >>
 | 
| 
 | 
   249 
 | 
| 
 | 
   250 Возвращает схему типа c именем C<$typeName>.
 | 
| 
 | 
   251 
 | 
| 
 | 
   252 =back
 | 
| 
 | 
   253 
 | 
| 
 | 
   254 =head1 META SCHEMA
 | 
| 
 | 
   255 
 | 
| 
100
 | 
   256 Схема для описания схемы, эта схема используется для постороения других схем, выглядит приблизительно так
 | 
| 
49
 | 
   257 
 | 
| 
98
 | 
   258 =begin code xml
 | 
| 
 | 
   259 
 | 
| 
49
 | 
   260 <schema>
 | 
| 
 | 
   261     <ComplexNode name="schema">
 | 
| 
 | 
   262         <NodeSet>
 | 
| 
 | 
   263             <Node minOcuur="0" maxOccur="unbounded" name="ComplexNode" type="ComplexNode"/>
 | 
| 
 | 
   264             <Node minOcuur="0" maxOccur="unbounded" name="SimpleNode" type="SimpleNode"/>
 | 
| 
 | 
   265             <Node minOcuur="0" maxOccur="unbounded" name="ComplexType" type="ComplexType"/>
 | 
| 
 | 
   266             <Node minOcuur="0" maxOccur="unbounded" name="SimpleType" type="SimpleType"/>
 | 
| 
 | 
   267             <SimpleNode minOcuur="0" maxOccur="unbounded" name="Node"/>
 | 
| 
 | 
   268             <SimpleNode minOcuur="0" maxOccur="unbounded" name="Include"/>
 | 
| 
 | 
   269         </NodeSet>
 | 
| 
 | 
   270     </ComplexNode>
 | 
| 
 | 
   271     
 | 
| 
 | 
   272     <ComplexType type="NodeContainer">
 | 
| 
 | 
   273         <NodeSet>
 | 
| 
 | 
   274             <Node minOcuur="0" maxOccur="unbounded" name="ComplexNode" type="ComplexNode"/>
 | 
| 
 | 
   275             <Node minOcuur="0" maxOccur="unbounded" name="SimpleNode" type="SimpleNode"/>
 | 
| 
 | 
   276             <SimpleNode minOcuur="0" maxOccur="unbounded" name="Node"/>
 | 
| 
 | 
   277         </NodeSet>
 | 
| 
 | 
   278     </ComplexType>
 | 
| 
 | 
   279     
 | 
| 
 | 
   280     <ComplexType type="ComplexType">
 | 
| 
 | 
   281         <NodeList>
 | 
| 
 | 
   282             <Node name="NodeSet" type="NodeContainer" minOcuur=0/>
 | 
| 
 | 
   283             <Node name="NodeList" type="NodeContainer" minOccur=0/>
 | 
| 
 | 
   284             <AnyNode minOccur="0" maxOccur="unbounded"  type="Validator"/>
 | 
| 
 | 
   285         </NodeList>
 | 
| 
 | 
   286     </ComplexType>
 | 
| 
 | 
   287     
 | 
| 
 | 
   288     <ComplexType type="ComplexNode">
 | 
| 
 | 
   289         <NodeList>
 | 
| 
 | 
   290             <Node name="NodeSet" type="NodeContainer" minOcuur=0/>
 | 
| 
 | 
   291             <Node name="NodeList" type="NodeContainer" minOccur=0/>
 | 
| 
 | 
   292             <AnyNode minOccur="0" maxOccur="unbounded"  type="Validator"/>
 | 
| 
 | 
   293         </NodeList>
 | 
| 
 | 
   294     </ComplexType>
 | 
| 
 | 
   295     
 | 
| 
 | 
   296     <ComplexType type="SimpleNode">
 | 
| 
 | 
   297         <NodeSet>
 | 
| 
 | 
   298             <AnyNode minOccur=0 maxOccur="unbounded" type="Validator"/>
 | 
| 
 | 
   299         </NodeSet>
 | 
| 
 | 
   300     </ComplexType>
 | 
| 
 | 
   301     
 | 
| 
 | 
   302     <ComplexType type="SimpleType">
 | 
| 
 | 
   303         <NodeSet>
 | 
| 
 | 
   304             <AnyNode minOccur=0 maxOccur="unbounded" type="Validator"/>
 | 
| 
 | 
   305         </NodeSet>
 | 
| 
 | 
   306     </ComplexType>
 | 
| 
 | 
   307     
 | 
| 
 | 
   308     <ComplexType type="Validator">
 | 
| 
 | 
   309         <NodeSet>
 | 
| 
 | 
   310             <AnyNode minOccur=0 maxOccur="unbounded"/>
 | 
| 
 | 
   311         </NodeSet>
 | 
| 
 | 
   312     </ComplexType>
 | 
| 
 | 
   313     
 | 
| 
 | 
   314 </schema>
 | 
| 
 | 
   315 
 | 
| 
98
 | 
   316 =end code xml
 | 
| 
 | 
   317 
 | 
| 
49
 | 
   318 =cut
 |