236
|
1 package IMPL::DOM::Transform::ObjectToDOM;
|
|
2 use strict;
|
|
3
|
|
4 use IMPL::Const qw(:prop :access);
|
|
5 use IMPL::declare {
|
|
6 require => {
|
|
7 PropertyInfo => 'IMPL::Class::PropertyInfo',
|
|
8 Builder => 'IMPL::DOM::Navigator::Builder',
|
|
9 Exception => 'IMPL::Exception',
|
|
10 ArgumentException => '-IMPL::InvalidArgumentException',
|
|
11 OperationException => '-IMPL::InvalidOperationException'
|
|
12 },
|
|
13 base => [
|
|
14 'IMPL::Transform' => sub {
|
250
|
15 -plain => 'TransformPlain',
|
|
16 HASH => 'TransformHash',
|
|
17 -default => 'TransformDefault'
|
236
|
18 }
|
|
19 ],
|
|
20 props => [
|
|
21 documentSchema => PROP_RO,
|
|
22 _schema => PROP_RW,
|
|
23 _navi => PROP_RW
|
|
24 ]
|
|
25 };
|
|
26
|
246
|
27 use constant {
|
250
|
28 SchemaNode => 'IMPL::DOM::Schema::Node',
|
|
29 ComplexNode => 'IMPL::DOM::Schema::ComplexNode'
|
246
|
30 };
|
|
31
|
236
|
32 sub CTOR {
|
237
|
33 my ($this,$docName,$docSchema,$transforms) = @_;
|
236
|
34
|
246
|
35 my $docNodeSchema = $docSchema->selectSingleNode(sub { $_->isa(SchemaNode) and $_->name eq $docName } )
|
236
|
36 or die OperationException->new("Can't find a node schema for the document '$docName'");
|
|
37
|
|
38 my $docClass = ($docNodeSchema->can('nativeType') ? $docNodeSchema->nativeType : undef) || 'IMPL::DOM::Document';
|
|
39
|
|
40 $this->documentSchema($docNodeSchema);
|
|
41
|
|
42 $this->_navi(
|
|
43 Builder->new(
|
|
44 $docClass,
|
|
45 $docSchema,
|
|
46 ignoreUndefined => 1
|
|
47 )
|
|
48 );
|
|
49 $this->_schema($docSchema);
|
|
50
|
|
51 $this->_navi->NavigateCreate($docName);
|
263
|
52 $this->currentNode->nodeProperty(schemaDocument => $docSchema);
|
236
|
53 }
|
|
54
|
|
55 sub TransformPlain {
|
|
56 my ($this,$data) = @_;
|
|
57
|
250
|
58 $this->_navi->Current->nodeValue( $data );
|
236
|
59 return $this->_navi->Current;
|
|
60 }
|
|
61
|
250
|
62 sub currentNode {
|
|
63 shift->_navi->Current;
|
|
64 }
|
|
65
|
|
66 sub inflateNodeValue {
|
|
67 shift->_navi->inflateValue(shift);
|
|
68 }
|
|
69
|
236
|
70 sub TransformHash {
|
|
71 my ($this,$data) = @_;
|
|
72
|
|
73 die ArgumentException->new(data => 'A HASH reference is required')
|
|
74 unless ref $data eq 'HASH';
|
250
|
75
|
|
76 return $this->StoreObject($this->currentNode,$data)
|
|
77 if !$this->currentNode->schema->isa(ComplexNode);
|
236
|
78
|
|
79 KEYLOOP: foreach my $key (keys %$data) {
|
|
80 my $value = $data->{$key};
|
|
81
|
|
82 if (ref $value eq 'ARRAY') {
|
257
|
83 #TODO: collapse empty values only if needed
|
|
84 foreach my $subval (grep $_, @$value) {
|
236
|
85
|
250
|
86 $this->_navi->saveState();
|
|
87
|
236
|
88 my $node = $this->_navi->NavigateCreate($key);
|
|
89
|
|
90 unless(defined $node) {
|
250
|
91 #$this->_navi->Back();
|
|
92 $this->_navi->restoreState();
|
236
|
93 next KEYLOOP;
|
|
94 }
|
|
95
|
250
|
96 $this->_navi->applyState();
|
|
97
|
236
|
98 $this->Transform($subval);
|
|
99
|
|
100 $this->_navi->Back();
|
|
101 }
|
|
102 } else {
|
250
|
103 $this->_navi->saveState();
|
236
|
104 my $node = $this->_navi->NavigateCreate($key);
|
250
|
105
|
236
|
106 unless(defined $node) {
|
250
|
107 #$this->_navi->Back();
|
|
108 $this->_navi->restoreState();
|
236
|
109 next KEYLOOP;
|
|
110 }
|
|
111
|
250
|
112 $this->_navi->applyState();
|
|
113
|
236
|
114 $this->Transform($value);
|
|
115
|
|
116 $this->_navi->Back();
|
|
117 }
|
|
118 }
|
|
119 return $this->_navi->Current;
|
|
120 }
|
|
121
|
250
|
122 # this method handles situatuions when a complex object must be stored in a
|
|
123 # simple node.
|
|
124 sub StoreObject {
|
|
125 my ($this,$node,$data) = @_;
|
|
126
|
|
127 $node->nodeValue($data);
|
|
128
|
|
129 return $node;
|
|
130 }
|
|
131
|
236
|
132 sub TransformDefault {
|
|
133 my ($this,$data) = @_;
|
|
134
|
250
|
135 return $this->StoreObject($this->currentNode,$data)
|
|
136 if !$this->currentNode->schema->isa(ComplexNode);
|
|
137
|
236
|
138 if ( ref $data and eval { $data->can('GetMeta') } ) {
|
|
139 my %props = map {
|
|
140 $_->name, 1
|
|
141 } $data->GetMeta(PropertyInfo, sub { $_->access == ACCESS_PUBLIC }, 1 );
|
|
142
|
329
|
143
|
236
|
144 my %values = map {
|
|
145 $_,
|
329
|
146 scalar($data->$_())
|
236
|
147 } keys %props;
|
|
148
|
|
149 return $this->Transform(\%values);
|
|
150 } else {
|
|
151 die OperationException->new("Don't know how to transform $data");
|
|
152 }
|
|
153
|
|
154 return $this->_navi->Current;
|
|
155 }
|
|
156
|
|
157 sub buildErrors {
|
|
158 my ($this) = @_;
|
|
159
|
|
160 return $this->_navi->buildErrors;
|
|
161 }
|
|
162
|
|
163 1;
|
|
164
|
|
165 __END__
|
|
166
|
|
167 =pod
|
|
168
|
|
169 =head1 NAME
|
|
170
|
263
|
171 C<IMPL::DOM::Transform::ObjectToDOM> -преобразование объекта в DOM документ.
|
236
|
172
|
|
173 =head1 SYNOPSIS
|
|
174
|
263
|
175 =begin code
|
|
176
|
|
177 use IMPL::require {
|
|
178 Schema => 'IMPL::DOM::Schema',
|
|
179 Config => 'IMPL::Config'
|
|
180 }
|
|
181
|
|
182 my $data = {
|
|
183 id => '12313-232',
|
|
184 name => 'Peter',
|
|
185 age => 20
|
|
186 };
|
|
187
|
|
188 my $schema = Schema->LoadSchema(Config->AppBase('schemas','person.xml'));
|
|
189 my $transorm = IMPL::DOM::Transform::ObjectToDOM->new('edit', $schema);
|
|
190
|
|
191 my $form = $transform->Transform($data);
|
|
192
|
|
193 my @errors;
|
|
194
|
|
195 push @errors, $transform->buildErrors;
|
|
196 push @errors, $schema->Validate($doc);
|
|
197
|
|
198 =end code
|
|
199
|
|
200 =head1 DESCRIPTION
|
|
201
|
|
202 Наследует C<IMPL::Transform>. Определяет базовые преобразования для хешей и
|
|
203 объектов, поддерживающих метаданные.
|
|
204
|
|
205 Результатом выполнения преобразования является DOM документ. При построении
|
|
206 документа используется навигатор C<IMPL::DOM::Navigator::Builder> для
|
|
207 сопоставления схемы и свойств преобразуемого объекта. Элементы полученного
|
|
208 документа имеют ссылки на соответствующие им элементы схемы.
|
|
209
|
|
210 После того, как документ построен и преобразование будет очищено, не останется
|
|
211 объектов, которые бы ссылались на документ со схемой, поскольку элементы схемы
|
|
212 имеют слабые ссылки на саму схему и не могут предотвратить ее удаление.
|
|
213 Для предотвращения очитски документа схемы, ссылка на него сохраняется в
|
|
214 атрибуте документа C<schemaDocument>, что обеспечит жизнь схемы на протяжении
|
|
215 жизни документа.
|
|
216
|
|
217 Преобразование происходит рекурсивно, сначала используется метод
|
|
218 C<NavigateCreate> для создания элемента соответсвующего свойству объекта,
|
|
219 затем вызывается метод C<Transform> для преобразования значения свойства, при
|
|
220 этом C<currentNode> указывает на только что созданный элемент документа.
|
|
221
|
264
|
222 Для изменения поведения преобразования можно добавлять новые обработчики, как
|
|
223 в случае со стандартным преобразованием, а также можно унаследовать текущий
|
|
224 класс для переопределения его некоторых методов.
|
|
225
|
263
|
226 =head1 MEMBERS
|
|
227
|
|
228 =head2 C<CTOR($docName,$schema)>
|
|
229
|
|
230 Создает преобразование, при этом будет создан документ состоящий только из
|
|
231 корневого элемента с именем C<$docName> и будет найдена подходящий для него
|
|
232 элемент схемы C<$schema>.
|
|
233
|
|
234 =over
|
|
235
|
|
236 =item * C<$docName>
|
|
237
|
|
238 Имя корневого узла документа, которое будет использовано для поиска
|
|
239 соответствующего элемента схемы C<$schema>
|
|
240
|
|
241 =item * C<$schema>
|
|
242
|
|
243 Схема, содержащая описание документа. Если в данной схеме нет описания корневого
|
|
244 элемента с именем C<$docName>, будет вызвано исключение.
|
|
245
|
|
246 =back
|
|
247
|
|
248 =head2 C<[get]documentSchema>
|
|
249
|
|
250 Элемент схемы C<ComplexNode> соответствующий документу. Определяется в
|
|
251 конструкторе исходя из имени документа.
|
|
252
|
|
253 =head2 C<[get]currentNode>
|
|
254
|
|
255 Текущий элемент документа. После создания преобразования - это сам документ.
|
|
256 Данное свойство использется внутри преобразования для работы с текущим
|
|
257 элементом.
|
|
258
|
|
259 =head2 C<[virtual]StoreObject($node,$data)>
|
|
260
|
|
261 Метод, который вызывается преобразованием в случае если текущий узел документа
|
|
262 является простым, а значени которое ему соответсвует является объектом (ссылкой).
|
|
263
|
|
264 По-умолчанию будет выполнено присваивание C<< $node->nodeValue($data) >>, однако
|
|
265 это можно заменить, например, на преобразование в строку.
|
|
266
|
|
267 =head2 C<inflateNodeValue($data)>
|
|
268
|
|
269 Метод который используется для преобразования значений к правильным типам,
|
|
270 используя атрибут C<inflator> элемента схемы. Этот метод можно использовать для
|
|
271 C<TransformPlain>, однако по-умолчанию он не используется, поскольку
|
|
272 предполагается, что входной объект имеет уже преобразованные значения в своих
|
|
273 свойствах.
|
|
274
|
236
|
275 =cut |