Mercurial > pub > Impl
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