diff lib/IMPL/DOM/Node.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/Node.pm	Fri Sep 04 19:40:23 2015 +0300
@@ -0,0 +1,505 @@
+package IMPL::DOM::Node;
+use strict;
+use warnings;
+
+use Scalar::Util qw(weaken);
+
+use IMPL::lang;
+use IMPL::Object::List;
+
+use IMPL::Exception();
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        PropertyInfo => '-IMPL::Class::PropertyInfo'
+    },
+    base => [
+        'IMPL::Object' => undef
+    ],
+    props => [
+        nodeName => PROP_RO | PROP_DIRECT,
+        document => PROP_RO | PROP_DIRECT,
+        isComplex => { get => \&_getIsComplex },
+        nodeValue => PROP_RW | PROP_DIRECT,
+        childNodes => { get => \&_getChildNodes, isList => 1, direct => 1 },
+        parentNode => PROP_RO | PROP_DIRECT,
+        schemaNode => PROP_RO | PROP_DIRECT,
+        schemaType => PROP_RO | PROP_DIRECT,
+        _propertyMap => PROP_RW | PROP_DIRECT
+    ]
+};
+
+our %Axes = (
+    parent => \&selectParent,
+    siblings => \&selectSiblings,
+    child => \&childNodes,
+    document => \&selectDocument,
+    ancestor => \&selectAncestors,
+    descendant => \&selectDescendant
+);
+
+sub CTOR {
+    my ($this,%args) = @_;
+    
+    $this->{$nodeName} = delete $args{nodeName} or die new IMPL::InvalidArgumentException("A name is required");
+    $this->{$nodeValue} = delete $args{nodeValue} if exists $args{nodeValue};
+    if ( exists $args{document} ) {
+        $this->{$document} = delete $args{document};
+        weaken($this->{$document});
+    }
+    
+    while ( my ($key,$value) = each %args ) {
+        $this->nodeProperty($key,$value);
+    }
+}
+
+sub insertNode {
+    my ($this,$node,$pos) = @_;
+    
+    die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node;
+    
+    $node->{$parentNode}->removeNode($node) if ($node->{$parentNode});
+    
+    $this->childNodes->InsertAt($pos,$node);
+    
+    $node->_setParent( $this );
+    
+    return $node;
+}
+
+sub appendChild {
+    my ($this,$node) = @_;
+    
+    die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node;
+    
+    $node->{$parentNode}->removeNode($node) if ($node->{$parentNode});
+    
+    my $children = $this->childNodes;
+    $children->Push($node);
+    
+    $node->_setParent( $this );
+    
+    return $node;
+}
+
+sub appendNode {
+    goto &appendChild;
+}
+
+sub appendRange {
+    my ($this,@range) = @_;
+    
+    die new IMPL::InvalidOperationException("You can't insert the node to itselft") if grep $_ == $this, @range;
+    
+    foreach my $node (@range) {
+        $node->{$parentNode}->removeNode($node) if ($node->{$parentNode});
+        $node->_setParent( $this );
+    }
+    
+    $this->childNodes->Push(@range);
+    
+    return $this;
+}
+
+sub _getChildNodes {
+    my ($this) = @_;
+    
+    $this->{$childNodes} = new IMPL::Object::List() unless $this->{$childNodes};
+    return wantarray ? @{ $this->{$childNodes} } : $this->{$childNodes};
+}
+
+sub childNodesRef {
+    my ($this) = @_;
+    return scalar $this->_getChildNodes;
+}
+
+sub removeNode {
+    my ($this,$node) = @_;
+    
+    if ($this == $node->{$parentNode}) {
+        $this->childNodes->RemoveItem($node);
+        $node->_setParent(undef);
+        return $node;
+    } else {
+        die new IMPL::InvalidOperationException("The specified node isn't belong to this node");
+    }
+}
+
+sub replaceNodeAt {
+    my ($this,$index,$node) = @_;
+    
+    my $nodeOld = $this->childNodes->[$index];
+        
+    die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node;
+        
+    # unlink node from previous parent
+    $node->{$parentNode}->removeNode($node) if ($node->{$parentNode});
+        
+    # replace (or set) old node
+    $this->childNodes->[$index] = $node;
+        
+    # set new parent
+    $node->_setParent( $this );
+        
+    # unlink old node if we have one
+    $nodeOld->_setParent(undef) if $nodeOld;
+        
+    # return old node
+    return $nodeOld;
+}
+
+sub removeAt {
+    my ($this,$pos) = @_;
+    
+    if ( my $node = $this->childNodes->RemoveAt($pos) ) {
+        $node->_setParent(undef);
+        return $node;
+    } else {
+        return undef;
+    }
+}
+
+sub removeLast {
+    my ($this) = @_;
+    
+    if ( my $node = $this->{$childNodes} ? $this->{$childNodes}->RemoveLast() : undef) {
+        $node->_setParent(undef);
+        return $node;
+    } else {
+        return undef;
+    }
+}
+
+sub removeSelected {
+    my ($this,$query) = @_;
+    
+    my @newSet;
+    my @result;
+    
+    if (ref $query eq 'CODE') {
+        &$query($_) ? push @result, $_ : push @newSet, $_ foreach @{$this->childNodes};
+    } elsif (defined $query) {
+        $_->nodeName eq $query ? push @result, $_ : push @newSet, $_ foreach @{$this->childNodes};
+    } else {
+        my $children = $this->childNodes;
+        $_->_setParent(undef) foreach @$children;
+        delete $this->{$childNodes};
+        return wantarray ? @$children : $children;
+    }
+    
+    $_->_setParent(undef) foreach @result;
+    
+    $this->{$childNodes} = @newSet ? bless \@newSet ,'IMPL::Object::List' : undef;
+    
+    return wantarray ? @result : \@result;
+}
+
+sub resolveAxis {
+    my ($this,$axis) = @_;
+    return $Axes{$axis}->($this)
+}
+
+sub selectNodes {
+    my $this = shift;
+    my $path;
+    
+    if (@_ == 1) {
+        $path = $this->translatePath($_[0]);
+    } else {
+        $path = [@_];
+    }
+    
+    my @set = ($this);
+    
+    while (@$path) {
+        my $query = shift @$path;
+        @set = map $_->selectNodesAxis($query), @set;
+    }
+    
+    return wantarray ? @set : \@set;   
+}
+
+sub selectSingleNode {
+    my $this = shift;
+    my @result = $this->selectNodes(@_);
+    return $result[0];
+}
+
+sub selectNodesRef {
+    my $this = shift;
+    
+    my @result = $this->selectNodes(@_);
+    return \@result;
+}
+
+sub translatePath {
+    my ($this,$path) = @_;
+    
+    # TODO: Move path compilation here from IMPL::DOM::Schema::Validator::Compare
+    return [$path];
+}
+
+sub selectNodesAxis {
+     my ($this,$query,$axis) = @_;
+    
+    $axis ||= 'child';
+    
+    die new IMPL::InvalidOperationException('Unknown axis',$axis) unless exists $Axes{$axis};
+    
+    my $nodes = $this->resolveAxis($axis);
+    
+    my @result;
+    
+    if (ref $query eq 'CODE') {
+        @result = grep &$query($_), @{$nodes};
+    } elsif (ref $query eq 'ARRAY' ) {
+        my %keys = map (($_,1),@$query);
+        @result = grep $keys{$_->nodeName}, @{$nodes};
+    } elsif (ref $query eq 'HASH') {
+        while( my ($axis,$filter) = each %$query ) {
+            push @result, $this->selectNodesAxis($filter,$axis);
+        }
+    } elsif (defined $query) {
+        @result = grep $_->nodeName eq $query, @{$nodes};
+    } else {
+        return wantarray ? @{$nodes} : $nodes;
+    }
+    
+    return wantarray ? @result : \@result;
+}
+
+sub selectParent {
+    my ($this) = @_;
+    
+    if ($this->parentNode) {
+        return wantarray ? $this->parentNode : [$this->parentNode];
+    } else {
+        return wantarray ? () : [];
+    }
+}
+
+sub selectSiblings {
+    my ($this) = @_;
+    
+    if ($this->parentNode) {
+        return $this->parentNode->selectNodes( sub { $_ != $this } );
+    } else {
+        return wantarray ? () : [];
+    }
+}
+
+sub selectDocument {
+    my ($this) = @_;
+    
+    if ($this->document) {
+        return wantarray ? $this->document : [$this->document];
+    } else {
+        return wantarray ? () : [];
+    }
+}
+
+sub selectDescendant {
+    wantarray ?
+        map $_->selectAll(), $_[0]->childNodes :
+        [map $_->selectAll(), $_[0]->childNodes]
+}
+
+sub selectAll {
+    map(selectAll($_),@{$_[0]->childNodes}) , $_[0]
+}
+
+sub selectAncestors {
+    my $parent = $_[0]->parentNode;
+    
+    wantarray ?
+        ($parent ? ($parent->selectAncestors,$parent) : ()) :
+        [$parent ? ($parent->selectAncestors,$parent) : ()]
+}
+
+sub firstChild {
+    @_ >=2 ? $_[0]->replaceNodeAt(0,$_[1]) : $_[0]->childNodes->[0];
+}
+
+sub _getIsComplex {
+    ($_[0]->{$childNodes} and $_[0]->{$childNodes}->Count) ? 1 : 0;
+}
+
+sub _updateDocRefs {
+    my ($this) = @_;
+    
+    # this method is called by the parent node on his children, so we need no to check parent
+    $this->{$document} = $this->{$parentNode}->document;
+    
+    # prevent cyclic
+    weaken($this->{$document}) if $this->{$document};
+    
+    map $_->_updateDocRefs, @{$this->{$childNodes}} if $this->{$childNodes};
+}
+
+sub _setParent {
+    my ($this,$node) = @_;
+    
+   
+    if (($node || 0) != ($this->{$parentNode} || 0)) {
+        my $newOwner;
+        if ($node) {
+            $this->{$parentNode} = $node;
+            $newOwner = $node->document || 0;
+            
+            # prevent from creating cyclicreferences
+            weaken($this->{$parentNode});
+
+        } else {
+            delete $this->{$parentNode};
+            
+            #keep document
+            $newOwner = $this->{$document};
+        }
+        
+        if (($this->{$document}||0) != $newOwner) {
+            $this->{$document} = $newOwner;
+            weaken($this->{$document}) if $newOwner;
+            $_->_updateDocRefs foreach @{$this->childNodes};
+        }
+    }
+}
+
+sub text {
+    my ($this) = @_;
+    
+    join ('', $this->nodeValue || '', map ($_->text || '', @{$this->childNodes}));
+}
+
+sub nodeProperty {
+    my $this = shift;
+    my $name = shift;
+    
+    return unless defined $name;
+    
+    if (my $method = $this->can($name)) {
+        unshift @_,$this;
+        # use goto to preserve calling context
+        goto &$method;
+    }
+    # dynamic property
+    if (@_) {
+        # set
+        return $this->{$_propertyMap}{$name} = shift;
+    } else {
+        return $this->{$_propertyMap}{$name};
+    }
+}
+
+sub listProperties {
+    my ($this) = @_;
+    
+    my %props = map {$_->name, 1} $this->GetMeta(PropertyInfo, sub { $_->attributes->{dom} },1);
+    
+    return (keys %props,keys %{$this->{$_propertyMap}});
+}
+
+sub save {
+    my ($this,$writer) = @_;
+    
+    if ( not ( $this->isComplex or defined $this->{$nodeValue} ) ) {
+        $writer->emptyTag(
+            $this->{$nodeName},
+            map {
+                $_,
+                $this->nodeProperty($_)
+            } grep defined $this->nodeProperty($_), $this->listProperties
+        );
+    } else {
+        $writer->startTag(
+            $this->{$nodeName},
+            map {
+                $_,
+                $this->nodeProperty($_)
+            } grep defined $this->nodeProperty($_), $this->listProperties
+        );
+        $writer->characters($this->{$nodeValue}) if $this->{$nodeValue};
+        
+        $_->save($writer) foreach $this->childNodes;
+        
+        $writer->endTag($this->{$nodeName});
+    }
+}
+
+sub qname {
+    $_[0]->{$nodeName};
+}
+
+sub path {
+    my ($this) = @_;
+    
+    if ($this->parentNode) {
+        return $this->parentNode->path.'.'.$this->qname;
+    } else {
+        return $this->qname;
+    }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+C<IMPL::DOM::Node> Элемент DOM модели
+
+=head1 DESCRIPTION
+
+Базовый узел DOM модели. От него можно наследовать другие элементы DOM модели.
+
+=head1 MEMBERS
+
+=head2 PROPERTIES
+
+=over
+
+=item C<[get] nodeName>
+
+Имя узла. Задается при создании.
+
+=item C<[get] document>
+
+Документ к которому принадлежит узел. Задается при поздании узла.
+
+=item C<[get] isComplex>
+
+Определяет является ли узел сложным (тоесть есть ли дети).
+
+C<true> - есть, C<false> - нет.
+
+=item C<[get,set] nodeValue>
+
+Значение узла, обычно простой скаляр, но ничто не мешает туда
+устанавливать любое значение.
+
+=item C<[get,list] childNodes>
+
+Список детей, является списокм C<IMPL::Object::List>.
+
+=item C<[get] parentNode>
+
+Ссылка на родительский элемент, если таковой имеется.
+
+=item C<[get] schemaType>
+
+Ссылка на узел из C<IMPL::DOM::Schema>, представляющий схему данных текущего узла. Может быть C<undef>.
+
+=item C<[get] schemaNode>
+
+Ссылка на узел из C<IMPL::DOM::Schema>, представляющий элемент схемы, объявляющий данный узел. Может быть C<undef>.
+
+Отличается от свойства C<schemaType> тем, что узел в случае ссылки на тип узла, данной свойство будет содержать
+описание ссылки C<IMPL::DOM::Schema::Node>, а свойство C<schema> например будет ссылаться на
+C<IMPL::DOM::Schema::ComplexType>.
+
+=back 
+
+=head2 METHODS
+
+=cut