diff lib/IMPL/DOM/Transform/ObjectToDOM.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/Transform/ObjectToDOM.pm	Fri Sep 04 19:40:23 2015 +0300
@@ -0,0 +1,255 @@
+package IMPL::DOM::Transform::ObjectToDOM;
+use strict;
+
+use IMPL::Const qw(:prop :access);
+use IMPL::declare {
+    require => {
+        PropertyInfo => 'IMPL::Class::PropertyInfo',
+        Builder => 'IMPL::DOM::Navigator::Builder',
+        Exception => 'IMPL::Exception',
+        ArgumentException => '-IMPL::InvalidArgumentException',
+        OperationException => '-IMPL::InvalidOperationException'
+    },
+    base => [
+        'IMPL::Transform' => sub {
+            -plain => 'TransformPlain',
+            HASH => 'TransformHash',
+            -default => 'TransformDefault'
+        }
+    ],
+    props => [
+        documentSchema => PROP_RO,
+        _schema => PROP_RW,
+        _navi => PROP_RW
+    ]
+};
+
+use constant {
+    SchemaNode => 'IMPL::DOM::Schema::Node',
+    ComplexNode => 'IMPL::DOM::Schema::ComplexNode'
+};
+
+sub CTOR {
+    my ($this,$docName,$docSchema,$transforms) = @_;
+    
+    my $docNodeSchema = $docSchema->selectSingleNode(sub { $_->isa(SchemaNode) and $_->name eq $docName } )
+        or die OperationException->new("Can't find a node schema for the document '$docName'");
+       
+    my $docClass = ($docNodeSchema->can('nativeType') ? $docNodeSchema->nativeType : undef) || 'IMPL::DOM::Document';
+    
+    $this->documentSchema($docNodeSchema);
+    
+    $this->_navi(
+        Builder->new(
+            $docClass,
+            $docSchema,
+            ignoreUndefined => 1
+        )
+    );
+    $this->_schema($docSchema);
+    
+    $this->_navi->NavigateCreate($docName);
+    $this->currentNode->nodeProperty(schemaDocument => $docSchema);
+}
+
+sub TransformPlain {
+    my ($this,$data) = @_;
+    
+    $this->_navi->Current->nodeValue( $data );
+    return $this->_navi->Current;
+}
+
+sub currentNode {
+    shift->_navi->Current;
+}
+
+sub TransformHash {
+    my ($this,$data) = @_;
+    
+    die ArgumentException->new(data => 'A HASH reference is required')
+        unless ref $data eq 'HASH';
+        
+    return $this->StoreObject($this->currentNode,$data)
+        if !$this->currentNode->schemaType->isa(ComplexNode);
+
+    KEYLOOP: foreach my $key (keys %$data) {
+        my $value = $data->{$key};
+        
+        if (ref $value eq 'ARRAY') {
+            foreach my $subval (grep $_, @$value) {
+                
+                $this->_navi->saveState();
+                
+                my $node = $this->_navi->NavigateCreate($key);
+                
+                unless(defined $node) {
+                    #$this->_navi->Back();
+                    $this->_navi->restoreState();
+                    next KEYLOOP;
+                }
+                
+                $this->_navi->applyState();
+                
+                $this->Transform($subval);
+                
+                $this->_navi->Back();
+            }
+        } else {
+            $this->_navi->saveState();
+            my $node = $this->_navi->NavigateCreate($key);
+
+            unless(defined $node) {
+                #$this->_navi->Back();
+                $this->_navi->restoreState();
+                next KEYLOOP;
+            }
+            
+            $this->_navi->applyState();
+            
+            $this->Transform($value);
+            
+            $this->_navi->Back();            
+        }
+    }
+    return $this->_navi->Current;
+}
+
+# this method handles situatuions when a complex object must be stored in a
+# simple node.
+sub StoreObject {
+    my ($this,$node,$data) = @_;
+    
+    $node->nodeValue($data);
+    
+    return $node;
+}
+
+sub TransformDefault {
+    my ($this,$data) = @_;
+    
+    return $this->StoreObject($this->currentNode,$data)
+        if !$this->currentNode->schemaType->isa(ComplexNode);
+    
+    if ( ref $data and eval { $data->can('GetMeta') } ) {
+        my %props = map {
+            $_->name, 1
+        } $data->GetMeta(PropertyInfo, sub { $_->access == ACCESS_PUBLIC }, 1 );
+        
+        
+        my %values = map {
+            $_,
+            scalar($data->$_())
+        } keys %props;
+        
+        return $this->Transform(\%values);
+    } else {
+        die OperationException->new("Don't know how to transform $data");
+    }
+    
+    return $this->_navi->Current;
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+C<IMPL::DOM::Transform::ObjectToDOM> -преобразование объекта в DOM документ.
+
+=head1 SYNOPSIS 
+
+=begin code
+
+use IMPL::require {
+    Schema => 'IMPL::DOM::Schema',
+    Config => 'IMPL::Config'
+}
+
+my $data = {
+    id => '12313-232',
+    name => 'Peter',
+    age => 20
+};
+
+my $schema = Schema->LoadSchema(Config->AppBase('schemas','person.xml'));
+my $transorm = IMPL::DOM::Transform::ObjectToDOM->new('edit', $schema);
+
+my $form = $transform->Transform($data);
+
+my @errors;
+    
+push @errors, $schema->Validate($doc);
+
+=end code
+
+=head1 DESCRIPTION
+
+Наследует C<IMPL::Transform>. Определяет базовые преобразования для хешей и
+объектов, поддерживающих метаданные.
+
+Результатом выполнения преобразования является DOM документ. При построении
+документа используется навигатор C<IMPL::DOM::Navigator::Builder> для
+сопоставления схемы и свойств преобразуемого объекта. Элементы полученного
+документа имеют ссылки на соответствующие им элементы схемы.
+
+После того, как документ построен и преобразование будет очищено, не останется
+объектов, которые бы ссылались на документ со схемой, поскольку элементы схемы
+имеют слабые ссылки на саму схему и не могут предотвратить ее удаление.
+Для предотвращения очитски документа схемы, ссылка на него сохраняется в
+атрибуте документа C<schemaDocument>, что обеспечит жизнь схемы на протяжении
+жизни документа.
+
+Преобразование происходит рекурсивно, сначала используется метод
+C<NavigateCreate> для создания элемента соответсвующего свойству объекта,
+затем вызывается метод C<Transform> для преобразования значения свойства, при
+этом C<currentNode> указывает на только что созданный элемент документа.
+
+Для изменения поведения преобразования можно добавлять новые обработчики, как
+в случае со стандартным преобразованием, а также можно унаследовать текущий
+класс для переопределения его некоторых методов.
+
+=head1 MEMBERS
+
+=head2 C<CTOR($docName,$schema)>
+
+Создает преобразование, при этом будет создан документ состоящий только из
+корневого элемента с именем C<$docName> и будет найдена подходящий для него
+элемент схемы C<$schema>. 
+
+=over
+
+=item * C<$docName>
+
+Имя корневого узла документа, которое будет использовано для поиска
+соответствующего элемента схемы C<$schema>
+
+=item * C<$schema>
+
+Схема, содержащая описание документа. Если в данной схеме нет описания корневого
+элемента с именем C<$docName>, будет вызвано исключение.
+
+=back
+
+=head2 C<[get]documentSchema>
+
+Элемент схемы C<ComplexNode> соответствующий документу. Определяется в
+конструкторе исходя из имени документа. 
+
+=head2 C<[get]currentNode>
+
+Текущий элемент документа. После создания преобразования - это сам документ.
+Данное свойство использется внутри преобразования для работы с текущим
+элементом.
+
+=head2 C<[virtual]StoreObject($node,$data)>
+
+Метод, который вызывается преобразованием в случае если текущий узел документа
+является простым, а значени которое ему соответсвует является объектом (ссылкой).
+
+По-умолчанию будет выполнено присваивание C<< $node->nodeValue($data) >>, однако
+это можно заменить, например, на преобразование в строку.
+
+=cut
\ No newline at end of file