Mercurial > pub > Impl
view Lib/IMPL/DOM/Transform/ObjectToDOM.pm @ 347:3eafa6fefa9f
sync
author | cin |
---|---|
date | Mon, 30 Sep 2013 17:36:17 +0400 |
parents | 50ff1595bd62 |
children | f116cd9fe7d9 |
line wrap: on
line source
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 inflateNodeValue { shift->_navi->inflateValue(shift); } 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->schema->isa(ComplexNode); KEYLOOP: foreach my $key (keys %$data) { my $value = $data->{$key}; if (ref $value eq 'ARRAY') { #TODO: collapse empty values only if needed 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->schema->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; } sub buildErrors { my ($this) = @_; return $this->_navi->buildErrors; } 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, $transform->buildErrors; 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) >>, однако это можно заменить, например, на преобразование в строку. =head2 C<inflateNodeValue($data)> Метод который используется для преобразования значений к правильным типам, используя атрибут C<inflator> элемента схемы. Этот метод можно использовать для C<TransformPlain>, однако по-умолчанию он не используется, поскольку предполагается, что входной объект имеет уже преобразованные значения в своих свойствах. =cut