ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Ext/DataForm.pm
Revision: 1.3
Committed: Fri Jul 6 22:22:21 2007 UTC (19 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.2: +477 -2 lines
Log Message:
implemented dataforms - phew! that was a bullet of work

File Contents

# Content
1 package Net::XMPP2::Ext::DataForm;
2 use strict;
3 use Net::XMPP2::Namespaces qw/xmpp_ns/;
4
5 =head1 NAME
6
7 Net::XMPP2::Ext::DataForm - XEP-0004 DataForm
8
9 =head1 SYNOPSIS
10
11 package foo;
12
13 =head1 DESCRIPTION
14
15 This module represents a Data Form as specified in XEP-0004.
16
17 =head1 METHODS
18
19 =over 4
20
21 =item B<new (%args)>
22
23 =cut
24
25 sub new {
26 my $this = shift;
27 my $class = ref($this) || $this;
28 my $self = bless { @_ }, $class;
29 $self->init;
30 $self
31 }
32
33 sub init {
34 my ($self) = @_;
35 $self->{fields} = [];
36 $self->{field_var} = {};
37 $self->{items} = [];
38 $self->{reported} = [];
39 delete $self->{type};
40 delete $self->{title};
41 delete $self->{instructions};
42 }
43
44 =item B<append_field ($field)>
45
46 This method appends a field to the form.
47 C<$field> must have the structure as described in L<FIELD STRUCTURE> below.
48
49 =cut
50
51 sub append_field {
52 my ($self, $field) = @_;
53 $self->{fields} = [] unless $self->{fields};
54 $self->{field_var} = {} unless $self->{field_var};
55 push @{$self->{fields}}, $field;
56 $self->{field_var}->{$field->{var}} = $field if defined $field->{var};
57 }
58
59 =item B<from_node ($node)>
60
61 This method interprets the L<Net::XMPP2::Node> object in C<$node> as
62 data form XML node and reads out the fields and all associated information.
63
64 (C<$node> must be the XML node of the <x xmlns='jabber:x:data'> tag).
65
66 =cut
67
68 sub _extract_field {
69 my ($field) = @_;
70
71 my $fo = {
72 label => $field->attr ('label'),
73 var => $field->attr ('var'),
74 type => $field->attr ('type'),
75 };
76
77 my ($desc) = $field->find_all ([qw/data_form desc/]);
78 if ($desc) {
79 $fo->{desc} = $desc->text;
80 }
81 if ($field->find_all ([qw/data_form required/])) {
82 $fo->{required} = 1;
83 }
84 my (@vals) = $field->find_all ([qw/data_form value/]);
85 $fo->{values} = [];
86 for (@vals) {
87 push @{$fo->{values}}, $_->text;
88 }
89 my (@opts) = $field->find_all ([qw/data_form option/]);
90 $fo->{options} = [];
91 for my $o (@opts) {
92 my (@v) = $o->find_all ([qw/data_form value/]);
93 my $vals = [];
94 for my $val (@v) {
95 push @$vals, $val->text;
96 }
97 push @{$fo->{options}}, [$o->attr ('label'), $vals];
98 }
99
100 $fo
101 }
102
103 sub from_node {
104 my ($self, $node) = @_;
105
106 $self->init;
107
108 my ($title) = $node->find_all ([qw/data_form title/]);
109 my ($instr) = $node->find_all ([qw/data_form instructions/]);
110
111 $self->{type} = $node->attr ('type');
112 $self->{title} = $title->text if $title;
113 $self->{instructions} = $instr->text if $instr;
114
115 for my $field ($node->find_all ([qw/data_form field/])) {
116 my $fo = _extract_field ($field);
117 $self->append_field ($fo);
118 }
119
120 my ($rep) = $node->find_all ([qw/data_form reported/]);
121 if ($rep) {
122 for my $field ($rep->find_all ([qw/data_form field/])) {
123 my $fo = {
124 label => $field->attr ('label'),
125 var => $field->attr ('var'),
126 type => $field->attr ('type'),
127 };
128 push @{$self->{reported}}, $fo;
129 }
130 }
131
132 for my $item ($node->find_all ([qw/data_form item/])) {
133 my $flds = [];
134 for my $field ($item->find_all ([qw/data_form field/])) {
135 my $fo = _extract_field ($field);
136 push @$flds, $fo;
137 }
138 push @{$self->{items}}, $flds;
139 }
140 }
141
142 =item B<make_answer_form ($request_form)>
143
144 This method initializes this form with default answers and
145 other neccessary fields from C<$request_form>, which must be
146 of type L<Net::XMPP2::Ext::DataForm> or compatible.
147
148 The result will be a form with a copy of all fields which are not of
149 type C<fixed>. The fields will also have the default value copied over.
150
151 The form type will be set to C<submit>.
152
153 The idea is: this creates a template answer form from C<$request_form>.
154
155 To strip out the unneccessary fields later you don't need call the
156 C<clear_empty_fields> method.
157
158 =cut
159
160 sub make_answer_form {
161 my ($self, $reqform) = @_;
162
163 $self->set_form_type ('submit');
164
165 for my $field ($reqform->get_fields) {
166 next if $field->{type} eq 'fixed';
167
168 my $fo = {
169 var => $field->{var},
170 type => $field->{type},
171 values => [ @{$field->{values}} ],
172 options => [],
173 };
174
175 $self->append_field ($fo);
176 }
177 }
178
179 =item B<clear_empty_fields>
180
181 This method removes all fields that have no values and options.
182
183 =cut
184
185 sub clear_empty_fields {
186 my ($self) = @_;
187
188 my @dead;
189 for ($self->get_fields) {
190 unless (@{$_->{values}} || @{$_->{options}}) {
191 push @dead, $_;
192 }
193 }
194 $self->remove_field ($_) for @dead;
195 }
196
197 =item B<remove_field ($field_or_var)>
198
199 This method removes a field either by it's unique name or
200 by reference. C<$field_or_var> can either be the unique name or
201 the actual field hash reference you get from C<get_field> or C<get_fields>.
202
203 =cut
204
205 sub remove_field {
206 my ($self, $field) = @_;
207 unless (ref $field) {
208 $field = $self->get_field ($field) or return;
209 }
210 @{$self->{fields}} = grep { $_ ne $field } @{$self->{fields}};
211 if (defined $field->{var}) {
212 delete $self->{field_var}->{$field->{var}};
213 }
214 }
215
216 =item B<set_form_type ($type)>
217
218 This method sets the type of the form, which must be one of:
219
220 form, submit, cancel, result
221
222 =cut
223
224 sub set_form_type {
225 my ($self, $type) = @_;
226 $self->{type} = $type;
227 }
228
229 =item B<form_type>
230
231 This method returns the type of the form, which is one of the
232 options described in C<set_form_type> above or undef if no type
233 was yet set.
234
235 =cut
236
237 sub form_type { return $_[0]->{type} }
238
239 =item B<get_reported_fields>
240
241 If this is a search result this method returns more than one element
242 here. The returned list consists of fields as described in L<FIELD STRUCTURE>,
243 only that they lack values and options.
244
245 See also the C<get_items> method.
246
247 =cut
248
249 sub get_reported_fields {
250 my ($self) = @_;
251 @{$self->{reported}}
252 }
253
254 =item B<get_items>
255
256 If this form is a search result this method returns the list of
257 items of that search.
258
259 An item is a array ref of fields (field structure is described in L<FIELD STRUCTURE>).
260 This method returns a list of items.
261
262 =cut
263
264 sub get_items {
265 my ($self) = @_;
266 @{$self->{items}};
267 }
268
269 =item B<get_fields>
270
271 This method returns a list of fields. Each field has the structure as described
272 in L<FIELD STRUCTURE>.
273
274 =cut
275
276 sub get_fields {
277 my ($self) = @_;
278 @{$self->{fields}}
279 }
280
281 =item B<get_field ($var)>
282
283 Returns the field with the unique field name C<$var> or
284 undef if no such field is in this form.
285
286 =cut
287
288 sub get_field {
289 my ($self, $var) = @_;
290 $self->{field_var}->{$var}
291 }
292
293 =item B<set_field_value ($var, $value)>
294
295 This method sets the value of the field with the unique name C<$var>.
296 If the field has supports multiple values all values will be removed
297 and only C<$value> will be added, if C<$value> is undefined the field's
298 value will be deleted.
299
300 =cut
301
302 sub set_field_value {
303 my ($self, $var, $val) = @_;
304 my $f = $self->get_field ($var) or return;
305 $f->{values} = defined $val ? [ $val ] : [];
306 }
307
308 =item B<add_field_value ($var, $value)>
309
310 This method adds the C<$value> to the field with the unique name C<$var>.
311 If the field doesn't support multiple values this method has the same
312 effect as C<set_field_value>.
313
314 =cut
315
316 sub add_field_value {
317 my ($self, $var, $val) = @_;
318 my $f = $self->get_field ($var) or return;
319 if (grep { $f->{type} eq $_ } qw/jid-multi list-multi text-multi/) {
320 push @{$f->{values}}, $val;
321 } else {
322 $self->set_field_value ($var, $val);
323 }
324 }
325
326 =item B<to_simxml>
327
328 This method converts the form to a data strcuture
329 that you can pass as C<node> argument to the C<simxml>
330 function which is documented in L<Net::XMPP2::Util>.
331
332 Example call might be:
333
334 my $node = $form->to_simxml;
335 simxml ($w, defns => $node->{ns}, node => $node);
336
337 =cut
338
339 sub _field_to_simxml {
340 my ($f) = @_;
341
342 my $ofa = [];
343 my $ofc = [];
344 my $of = {
345 ns => 'data_form',
346 name => 'field',
347 attrs => $ofa,
348 childs => $ofc,
349 };
350
351 push @$ofa, (label => $f->{label}) if defined $f->{label};
352 push @$ofa, (var => $f->{var}) if defined $f->{var};
353 push @$ofa, (type => $f->{type}) if defined $f->{type};
354
355 for (@{$f->{values}}) {
356 push @$ofc, { ns => 'data_form', name => 'value', childs => [ $_ ] }
357 }
358
359 for (@{$f->{options}}) {
360 my $at = [];
361 my $chlds = [];
362 push @$ofc, {
363 ns => 'data_form', name => 'option',
364 attrs => $at, childs => $chlds
365 };
366 for (@{$_->[1]}) {
367 push @$chlds, { name => 'value', childs => [ $_ ] }
368 }
369 if (defined $_->[0]) { push @$at, (label => $_->[0]) }
370 }
371
372 if ($f->{desc}) {
373 push @$ofc, { ns => 'data_form', name => 'desc', childs => [ $f->{desc} ] }
374 }
375
376 if ($f->{required}) {
377 push @$ofc, { ns => 'data_form', name => 'required' }
378 }
379
380 $of
381 }
382
383 sub to_simxml {
384 my ($self) = @_;
385
386 my $fields = [];
387 my $top = {
388 ns => 'data_form',
389 name => 'x',
390 attrs => [],
391 childs => $fields,
392 };
393
394 push @{$top->{attrs}}, ( type => $self->{type} );
395
396 if (defined $self->{title}) {
397 push @$fields, {
398 name => 'title', childs => [ $self->{title} ]
399 }
400 }
401
402 if (defined $self->{instructions}) {
403 push @$fields, {
404 name => 'instructions', childs => [ $self->{instructions} ]
405 }
406 }
407
408 for my $f ($self->get_fields) {
409 push @$fields, _field_to_simxml ($f);
410 }
411
412 my $repchld = [];
413 for my $rf ($self->get_reported_fields) {
414 push @$repchld, _field_to_simxml ($rf);
415 }
416
417 if (@$repchld) {
418 push @$fields, {
419 name => 'reported',
420 childs => $repchld
421 };
422 }
423
424 for my $itf ($self->get_items) {
425 my $itfields = [];
426
427 for my $f (@$itf) {
428 push @$itfields, _field_to_simxml ($f);
429 }
430
431 push @$fields, {
432 name => 'item',
433 childs => $itfields
434 }
435 }
436
437 $top
438 }
439
440 =item B<as_debug_string>
441
442 This method returns a string that represents the form.
443 Only for debugging purposes.
444
445 =cut
446
447 sub as_debug_string {
448 my ($self) = @_;
449
450 my $str;
451 $str .= "title: $self->{title}\n"
452 ."instructions: $self->{instructions}\n"
453 ."type: $self->{type}\n";
454 for my $f ($self->get_fields) {
455 $str .= sprintf "- var : %-50s label: %s\n type: %-10s required: %d\n",
456 $f->{var}, $f->{label}, $f->{type}, $f->{required};
457 for (@{$f->{values}}) {
458 $str .= sprintf " * val : %s\n", $_
459 }
460 for (@{$f->{options}}) {
461 $str .= sprintf " * opt lbl: %-50s text: %s\n", @$_
462 }
463 }
464
465 $str .= "reported:\n";
466 for my $f (@{$self->{reported}}) {
467 $str .= sprintf "- var: %-50s label: %-30s type: %-10s %d\n",
468 $f->{var}, $f->{label}, $f->{type};
469 }
470
471 $str .= "items:\n";
472 for my $i (@{$self->{items}}) {
473 $str .= "-" x 60 . "\n";
474 for my $f (@$i) {
475 $str .= sprintf "- var : %-50s\n", $f->{var};
476 for (@{$f->{values}}) {
477 $str .= sprintf " * val : %s\n", $_
478 }
479 for (@{$f->{options}}) {
480 $str .= sprintf " * opt lbl: %-50s text: %s\n", @$_
481 }
482 }
483 }
484
485 $str
486 }
487
488 =back
489
490 =head1 FIELD STRUCTURE
491
492 {
493 label => 'field label',
494 type => 'field type',
495 var => '(unique) field name'
496 required => true or false value,
497 values => [
498 'value text',
499 ...
500 ],
501 options => [
502 ['option label', 'option text'],
503 ...
504 ]
505 }
506
507 For the semantics of all fields please consult XEP 0004.
508
509 =head1 SEE ALSO
510
511 XEP 0004
512
513 =head1 AUTHOR
514
515 Robin Redeker, C<< <elmex at ta-sa.org> >>, JID: C<< <elmex at jabber.org> >>
516
517 =head1 COPYRIGHT & LICENSE
518
519 Copyright 2007 Robin Redeker, all rights reserved.
520
521 This program is free software; you can redistribute it and/or modify it
522 under the same terms as Perl itself.
523
524 =cut
525
526 1;