view Lib/IMPL/DOM/Navigator/SchemaNavigator.pm @ 368:010ceafd0c5a

form metadata + tests
author cin
date Wed, 04 Dec 2013 17:31:53 +0400
parents 70658970af15
children
line wrap: on
line source

package IMPL::DOM::Navigator::SchemaNavigator;
use strict;
use warnings;

use IMPL::Class::Property;

require IMPL::DOM::Schema::ComplexType;
require IMPL::DOM::Schema::NodeSet;
require IMPL::DOM::Schema::AnyNode;

use IMPL::declare {
    base => [
        'IMPL::DOM::Navigator' => '@_'
    ]
};

BEGIN {
    public _direct property Schema => prop_get;
    private _direct property _historySteps => prop_all;
}

sub CTOR {
    my ($this,$schema) = @_;
    
    $this->{$Schema} = $schema->isa('IMPL::DOM::Schema::ComplexNode') ? $schema->document : $schema;
    
    die new IMPL::InvalidArgumentException("A schema object is required") unless ref $this->{$Schema} && eval { $this->{$Schema}->isa('IMPL::DOM::Schema') };
}

my $schemaAnyNode = IMPL::DOM::Schema::ComplexType->new(type => '::AnyNodeType', nativeType => 'IMPL::DOM::ComplexNode')->appendRange(
    IMPL::DOM::Schema::NodeSet->new()->appendRange(
        IMPL::DOM::Schema::AnyNode->new()
    )
);

sub NavigateName {
    my ($this,$name) = @_;
    
    die new IMPL::InvalidArgumentException('name is required') unless defined $name;
    
    # perform a safe navigation
    #return dosafe $this sub {
        my $steps = 0;
        # if we are currently in a ComplexNode, first go to it's content
        if ($this->Current->isa('IMPL::DOM::Schema::ComplexNode')) {
            # navigate to it's content
            # ComplexNode
            $this->internalNavigateNodeSet($this->Current->content);
            $steps ++;
        }
        
        # navigate to node
        if (
            my $node = $this->Navigate( sub {
                $_->isa('IMPL::DOM::Schema::Node') and (
                    $_->name eq $name
                    or
                    $_->nodeName eq 'AnyNode'
                    or
                    ( $_->nodeName eq 'SwitchNode' and $_->selectNodes( sub { $_->name eq $name } ) )
                )
            })
        ) {
            $steps ++;
            if ($node->nodeName eq 'AnyNode') {
                # if we navigate to the anynode
                # assume it to be ComplexType by default
                $node = $node->type ? $this->{$Schema}->resolveType($node->type) : $schemaAnyNode;
                $this->internalNavigateNodeSet($node);
                $steps ++;
            } elsif ($node->nodeName eq 'SwitchNode') {
                # if we are in the switchnode
                # navigate to the target node
                $node = $this->Navigate(sub { $_->name eq $name });
                $steps ++;
            }
            
            die IMPL::Exception->new("A node is expected")
                unless $node;
            if ($node->nodeName eq 'Node') {
                # if we navigate to a reference
                # resolve it
                $node = $this->{$Schema}->resolveType($node->type);
                $this->internalNavigateNodeSet($node);
                $steps++;
            } 
            
            push @{$this->{$_historySteps}},$steps;
            
            # return found node schema
            return $node;
        } else {
            return; # abort navigation
        }
    #}
}

sub SchemaBack {
    my ($this) = @_;
    
    $this->Back(pop @{$this->{$_historySteps}}) if $this->{$_historySteps};
}

sub SourceSchemaNode {
    my ($this) = @_;
    
    if ($this->Current->isa('IMPL::DOM::Schema::SimpleType') or
        $this->Current->isa('IMPL::DOM::Schema::ComplexType')
    ) {
        # we are redirected
        return $this->GetNodeFromHistory(-1);
    } else {
        return $this->Current;
    }
}

sub schema {
	goto &Schema;
}

1;
__END__

=pod

=head1 DESCRIPTION

Помимо стандартных методов навигации позволяет переходить по элементам документа,
который данной схемой описывается.

=head1 METHODS

=over

=item C<NavigateName($name)>

Переходит на схему узла с указанным именем. Тоесть использует свойство C<name>.

=item C<SchemaBack>

Возвращается на позицию до последней операции C<NavigateName>. Данный метод нужен
посокольку операция навигации по элементам описываемым схемой может приводить к
нескольким операциям навигации по самой схеме.

=item C<SourceSchemaNode>

Получает схему узла из которого было выполнено перенаправление, например, C<IMPL::DOM::Schema::Node>.
В остальных случаях совпадает со свойством C<Current>.

=back

=cut