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