view Lib/IMPL/DOM/Schema.pm @ 383:2f16f13b000c

DOM localization
author cin
date Thu, 23 Jan 2014 17:26:34 +0400
parents 99ac2e19c0cc
children 5aff94ba842f
line wrap: on
line source

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
	    InflateFactory => 'IMPL::DOM::Schema::InflateFactory',
	    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->Append( $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'),
            Property->new(name => 'inflator', optional => 1, inflator => 'IMPL::DOM::Schema::InflateFactory')
        ),
        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'),
            Property->new(name => 'inflator', optional => 1, inflator => 'IMPL::DOM::Schema::InflateFactory')
        ),
        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'),
            Property->new(name => 'inflator', optional => 1, inflator => 'IMPL::DOM::Schema::InflateFactory')
        ),
        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