Mercurial > pub > Impl
diff lib/IMPL/DOM/Schema.pm @ 407:c6e90e02dd17 ref20150831
renamed Lib->lib
author | cin |
---|---|
date | Fri, 04 Sep 2015 19:40:23 +0300 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/IMPL/DOM/Schema.pm Fri Sep 04 19:40:23 2015 +0300 @@ -0,0 +1,371 @@ +package IMPL::DOM::Schema; +use strict; +use warnings; + +use File::Spec; +use IMPL::Const qw(:prop); +use IMPL::declare { + require => { + ComplexNode => 'IMPL::DOM::Schema::ComplexNode', + ComplexType => 'IMPL::DOM::Schema::ComplexType', + SimpleNode => 'IMPL::DOM::Schema::SimpleNode', + SimpleType => 'IMPL::DOM::Schema::SimpleType', + Node => 'IMPL::DOM::Schema::Node', + AnyNode => 'IMPL::DOM::Schema::AnyNode', + NodeList => 'IMPL::DOM::Schema::NodeList', + NodeSet => 'IMPL::DOM::Schema::NodeSet', + Property => 'IMPL::DOM::Schema::Property', + SwitchNode => 'IMPL::DOM::Schema::SwitchNode', + Validator => 'IMPL::DOM::Schema::Validator', + Builder => 'IMPL::DOM::Navigator::Builder', + XMLReader => 'IMPL::DOM::XMLReader', # XMLReader references Schema + Loader => 'IMPL::Code::Loader', + StringMap => 'IMPL::Resources::StringLocaleMap' + }, + base => [ + 'IMPL::DOM::Document' => sub { + nodeName => 'schema' + } + ], + props => [ + _typesMap => PROP_RW | PROP_DIRECT, + baseDir => PROP_RW | PROP_DIRECT, + schemaName => PROP_RW | PROP_DIRECT, + baseSchemas => PROP_RO | PROP_LIST | PROP_DIRECT, + stringMap => { + get => '_getStringMap', + direct => 1 + } + ] +}; + +my $validatorLoader = Loader->new(prefix => Validator, verifyNames => 1); + +#TODO rename and remove +sub resolveType { + goto &ResolveType; +} + +sub CTOR { + my ($this,%args) = @_; + + $this->{$baseDir} = ($args{baseDir} || '.'); +} + +# compat +sub ResolveType { + my ($this,$typeName) = @_; + + my $type = $this->{$_typesMap}{$typeName}; + return $type if $type; + + foreach my $base ($this->baseSchemas) { + last if $type = $base->ResolveType($typeName); + } + + die IMPL::KeyNotFoundException->new($typeName) + unless $type; + return $this->{$_typesMap}{$typeName} = $type; +} + +sub Create { + my ($this,$nodeName,$class,$refArgs) = @_; + + die new IMPL::Exception('Invalid node class') unless $class->isa('IMPL::DOM::Node'); + + if ($class->isa('IMPL::DOM::Schema::Validator')) { + $class = $validatorLoader->GetFullName($nodeName); + unless (eval {$class->can('new')}) { + eval { + $validatorLoader->Require($nodeName); + }; + my $e = $@; + die new IMPL::Exception("Invalid validator",$class,$e) if $e; + } + } + + return $this->SUPER::Create($nodeName,$class,$refArgs); +} + +sub _getStringMap { + my ($this) = @_; + + return $this->{$stringMap} + if $this->{$stringMap}; + + my $map = StringMap->new(); + if ($this->baseDir and $this->schemaName) { + + $map->paths( File::Spec->catdir($this->baseDir,'locale') ); + $map->name( $this->schemaName ); + } + + return $this->{$stringMap} = $map; +} + +sub Process { + my ($this) = @_; + + # process instructions + $this->Include($_) foreach map $_->nodeProperty('source'), $this->selectNodes('Include'); + + # build types map + $this->{$_typesMap} = { map { $_->type, $_ } $this->selectNodes(sub { $_[0]->nodeName eq 'ComplexType' || $_[0]->nodeName eq 'SimpleType' } ) }; +} + +sub Include { + my ($this,$file) = @_; + + my $schema = $this->LoadSchema(File::Spec->catfile($this->baseDir, $file)); + + $this->baseSchemas->Push( $schema ); +} + +sub LoadSchema { + my ($this,$file) = @_; + + $file = File::Spec->rel2abs($file); + + my $class = ref $this || $this; + + my $reader = XMLReader->new( + Navigator => Builder->new( + $class, + $class->MetaSchema + ), + SkipWhitespace => 1 + ); + + $reader->ParseFile($file); + + my $schema = $reader->Navigator->Document; + + my ($vol,$dir,$name) = File::Spec->splitpath($file); + + $name =~ s/\.xml$//; + + $schema->baseDir($dir); + $schema->schemaName($name); + + my @errors = $class->MetaSchema->Validate($schema); + + die new IMPL::Exception("Schema is invalid",$file,map( $_->message, @errors ) ) if @errors; + + $schema->Process; + + return $schema; +} + +sub Validate { + my ($this,$node) = @_; + + if ( my ($schemaNode) = $this->selectNodes(sub { $_->isa(Node) and $_[0]->name eq $node->nodeName })) { + $schemaNode->Validate($node); + } else { + return new IMPL::DOM::Schema::ValidationError(node => $node, message=> "A specified document (%Node.nodeName%) doesn't match the schema"); + } +} + +my $schema; + +sub MetaSchema { + + return $schema if $schema; + + $schema = __PACKAGE__->new(); + + $schema->appendRange( + ComplexNode->new(name => 'schema')->appendRange( + NodeSet->new()->appendRange( + Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'ComplexType', type => 'ComplexType', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SimpleType', type => 'SimpleType', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'Node', type => 'Node', minOccur => 0, maxOccur=>'unbounded'), + SimpleNode->new(name => 'Include', minOccur => 0, maxOccur=>'unbounded')->appendRange( + Property->new(name => 'source') + ) + ), + ), + ComplexType->new(type => 'NodeSet', nativeType => 'IMPL::DOM::Schema::NodeSet')->appendRange( + NodeSet->new()->appendRange( + Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'Node', type=>'Node', minOccur => 0, maxOccur=>'unbounded'), + SwitchNode->new(minOccur => 0, maxOccur => 1)->appendRange( + Node->new(name => 'AnyNode', type => 'AnyNode'), + Node->new(name => 'SwitchNode',type => 'SwitchNode') + ) + ) + ), + ComplexType->new(type => 'SwitchNode', nativeType => 'IMPL::DOM::Schema::SwitchNode')->appendRange( + NodeSet->new()->appendRange( + Node->new(name => 'ComplexNode', type=>'ComplexNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SimpleNode', type=>'SimpleNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'Node', type=>'Node', minOccur => 0, maxOccur=>'unbounded'), + ) + ), + ComplexType->new(type => 'NodeList', nativeType => 'IMPL::DOM::Schema::NodeList')->appendRange( + NodeSet->new()->appendRange( + Node->new(name => 'ComplexNode', type => 'ComplexNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SimpleNode', type => 'SimpleNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'SwitchNode',type => 'SwitchNode', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'Node', type => 'Node', minOccur => 0, maxOccur=>'unbounded'), + Node->new(name => 'AnyNode', type => 'AnyNode', minOccur => 0, maxOccur=>'unbounded'), + ) + ), + ComplexType->new(type => 'ComplexType', nativeType => 'IMPL::DOM::Schema::ComplexType')->appendRange( + NodeList->new()->appendRange( + SwitchNode->new()->appendRange( + Node->new(name => 'NodeSet', type => 'NodeSet'), + Node->new(name => 'NodeList',type => 'NodeList'), + ), + Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0), + AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator') + ), + Property->new(name => 'type') + ), + ComplexType->new(type => 'ComplexNode', nativeType => 'IMPL::DOM::Schema::ComplexNode')->appendRange( + NodeList->new()->appendRange( + SwitchNode->new()->appendRange( + Node->new(name => 'NodeSet', type => 'NodeSet'), + Node->new(name => 'NodeList',type => 'NodeList'), + ), + Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0), + AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator') + ), + Property->new(name => 'name') + ), + ComplexType->new(type => 'SimpleType', nativeType => 'IMPL::DOM::Schema::SimpleType')->appendRange( + NodeList->new()->appendRange( + Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0), + AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator') + ), + Property->new(name => 'type') + ), + ComplexType->new(type => 'SimpleNode', nativeType => 'IMPL::DOM::Schema::SimpleNode')->appendRange( + NodeList->new()->appendRange( + Node->new(name => 'Property', type=>'Property', maxOccur=>'unbounded', minOccur=>0), + AnyNode->new(maxOccur => 'unbounded', minOccur => 0, type=>'Validator') + ), + Property->new(name => 'name') + ), + ComplexType->new(type => 'Validator', nativeType => 'IMPL::DOM::Schema::Validator')->appendRange( + NodeList->new()->appendRange( + AnyNode->new(maxOccur => 'unbounded', minOccur => 0) + ) + ), + ComplexType->new(type => 'Property', nativeType => 'IMPL::DOM::Schema::Property' )->appendRange( + NodeList->new()->appendRange( + AnyNode->new(maxOccur => 'unbounded', minOccur => 0) + ), + Property->new(name => 'name') + ), + SimpleType->new(type => 'Node', nativeType => 'IMPL::DOM::Schema::Node')->appendRange( + Property->new(name => 'name'), + Property->new(name => 'type') + ), + SimpleType->new(type => 'AnyNode', nativeType => 'IMPL::DOM::Schema::AnyNode') + ); + + $schema->Process; + + return $schema; +} + +1; + +__END__ + +=pod + +=head1 NAME + +C<IMPL::DOM::Schema> - Схема документа. + +=head1 DESCRIPTION + +C<use parent qw(IMPL::DOM::Document)> + +DOM схема - это документ, состоящий из определенных узлов, описывающая структуру +других документов. + +=head1 METHODS + +=over + +=item C<< $obj->Process() >> + +Обновляет таблицу типов из содержимого. + +=item C<< $obj->ResolveType($typeName) >> + +Возвращает схему типа c именем C<$typeName>. + +=back + +=head1 META SCHEMA + +Схема для описания схемы, эта схема используется для постороения других схем, выглядит приблизительно так + +=begin code xml + +<schema> + <ComplexNode name="schema"> + <NodeSet> + <Node minOcuur="0" maxOccur="unbounded" name="ComplexNode" type="ComplexNode"/> + <Node minOcuur="0" maxOccur="unbounded" name="SimpleNode" type="SimpleNode"/> + <Node minOcuur="0" maxOccur="unbounded" name="ComplexType" type="ComplexType"/> + <Node minOcuur="0" maxOccur="unbounded" name="SimpleType" type="SimpleType"/> + <SimpleNode minOcuur="0" maxOccur="unbounded" name="Node"/> + <SimpleNode minOcuur="0" maxOccur="unbounded" name="Include"/> + </NodeSet> + </ComplexNode> + + <ComplexType type="NodeContainer"> + <NodeSet> + <Node minOcuur="0" maxOccur="unbounded" name="ComplexNode" type="ComplexNode"/> + <Node minOcuur="0" maxOccur="unbounded" name="SimpleNode" type="SimpleNode"/> + <SimpleNode minOcuur="0" maxOccur="unbounded" name="Node"/> + </NodeSet> + </ComplexType> + + <ComplexType type="ComplexType"> + <NodeList> + <Node name="NodeSet" type="NodeContainer" minOcuur=0/> + <Node name="NodeList" type="NodeContainer" minOccur=0/> + <AnyNode minOccur="0" maxOccur="unbounded" type="Validator"/> + </NodeList> + </ComplexType> + + <ComplexType type="ComplexNode"> + <NodeList> + <Node name="NodeSet" type="NodeContainer" minOcuur=0/> + <Node name="NodeList" type="NodeContainer" minOccur=0/> + <AnyNode minOccur="0" maxOccur="unbounded" type="Validator"/> + </NodeList> + </ComplexType> + + <ComplexType type="SimpleNode"> + <NodeSet> + <AnyNode minOccur=0 maxOccur="unbounded" type="Validator"/> + </NodeSet> + </ComplexType> + + <ComplexType type="SimpleType"> + <NodeSet> + <AnyNode minOccur=0 maxOccur="unbounded" type="Validator"/> + </NodeSet> + </ComplexType> + + <ComplexType type="Validator"> + <NodeSet> + <AnyNode minOccur=0 maxOccur="unbounded"/> + </NodeSet> + </ComplexType> + +</schema> + +=end code xml + +=cut