File Coverage

blib/lib/WDDX/Recordset.pm
Criterion Covered Total %
statement 6 256 2.3
branch 0 148 0.0
condition 0 48 0.0
subroutine 2 37 5.4
pod n/a
total 8 489 1.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #
3             # $Id: Recordset.pm,v 1.1.1.1 2003/10/28 16:04:37 andy Exp $
4             #
5             # This code is copyright 1999-2000 by Scott Guelich
6             # and is distributed according to the same conditions as Perl itself
7             # Please visit http://www.scripted.com/wddx/ for more information
8             #
9              
10             package WDDX::Recordset;
11              
12             # Auto-inserted by build scripts
13             $VERSION = "1.00";
14              
15 1     1   2986 use strict;
  1         2  
  1         40  
16 1     1   5 use Carp;
  1         1  
  1         4409  
17              
18             require WDDX;
19              
20             my @Data_Types = qw( boolean number string datetime binary null );
21              
22             { my $i_hate_the_w_flag_sometimes = [
23             $WDDX::PACKET_HEADER,
24             $WDDX::PACKET_FOOTER,
25             $WDDX::Recordset::VERSION
26             ] }
27              
28             1;
29              
30              
31             #/-----------------------------------------------------------------------
32             # Public Methods
33             #
34              
35             sub new {
36 0     0     my( $class, $names, $types, $data ) = @_;
37 0           my( @names, @types, $value ) = ();
38            
39 0 0 0       unless ( defined $names and eval { $#$names || 1 } and
  0 0 0        
      0        
40 0 0         defined $types and eval { $#$types || 1 } ) {
41 0           croak "You must supply array refs for names and data types " .
42             "when creating a new $class object";
43             }
44            
45 0 0         croak "Name and type arrays must contain the same number of elements"
46             unless @$names == @$types;
47            
48 0           my $type;
49 0           foreach $type ( @$types ) {
50 0 0         next unless defined $type; # supports deserializing empty recordsets
51 0           $type = lc $type;
52 0 0         die "Unsupported data type: '$type'" unless
53             grep $type eq $_, @Data_Types;
54             }
55            
56 0           my $row;
57 0           my $i = 0;
58 0           foreach $row ( @$data ) {
59 0           $i++;
60 0 0         unless ( ref( $row ) =~ /ARRAY/ ) {
61 0           croak "Third argument must be a ref to an array of array " .
62             "refs (i.e. a table)";
63             }
64 0 0         unless ( @$row == @$names ) {
65 0           croak "The number of fields in row $i does not match the " .
66             "number of declared names";
67             }
68             }
69            
70 0           my @invalid = grep ! /^[_A-Za-z][_.0-9A-Za-z]*$/, @$names;
71 0 0         croak "Invalid field names in recordset: @invalid" if @invalid;
72            
73 0           my $self = {
74             names => $names,
75             types => $types,
76             value => $data,
77             };
78            
79 0           bless $self, $class;
80 0           return $self;
81             }
82              
83              
84             sub type {
85 0     0     return "recordset";
86             }
87              
88              
89             sub as_packet {
90 0     0     my( $self ) = @_;
91 0           my $output = $WDDX::PACKET_HEADER .
92             $self->_serialize .
93             $WDDX::PACKET_FOOTER;
94             }
95              
96              
97             sub as_arrayref {
98 0     0     my( $self ) = @_;
99 0           return $self->_deserialize;
100             }
101              
102              
103             sub as_javascript {
104 0     0     my( $self, $js_var ) = @_;
105 0           my $output = "$js_var=new WddxRecordset();";
106 0           my $types = $self->types;
107            
108 0           for ( my $col = 0; $col < $self->num_columns; $col++ ) {
109 0           my $name = $self->names()->[$col];
110 0           $output .= "$js_var.$name=new Array();";
111 0           for ( my $row = 0; $row < $self->num_rows; $row++ ) {
112 0           my $field = $self->get_element( $col, $row );
113 0           my $var = eval "WDDX::\u$types->[$col]\->new( \$field )";
114 0 0         die "$@\n" if $@;
115 0           $output .= $var->as_javascript( "$js_var.$name\[$row\]" );
116             }
117             }
118 0           return $output;
119             }
120              
121              
122             #/-----------------------------------------------------------------------
123             # Other Public Methods
124             #
125              
126              
127             sub num_rows {
128 0     0     my( $self ) = @_;
129 0           return scalar @{ $self->table };
  0            
130             }
131              
132             sub num_columns {
133 0     0     my( $self ) = @_;
134 0           return scalar @{ $self->{'names'} };
  0            
135             }
136              
137             # Returns an array of the field names
138             sub names {
139 0     0     my( $self, $new_names ) = @_;
140            
141 0 0         if ( defined $new_names ) {
142 0 0         croak "You must supply an array ref when setting names"
143             unless ref $new_names;
144 0           $self->{'names'} = $new_names;
145             }
146            
147 0           return $self->{'names'};
148             }
149              
150             sub types {
151 0     0     my( $self, $new_types ) = @_;
152            
153 0 0         if ( defined $new_types ) {
154 0 0         croak "You must supply an array ref when setting types"
155             unless ref $new_types;
156 0           $self->{'types'} = $new_types;
157             }
158            
159 0           return $self->{'types'};
160             }
161              
162             sub table {
163 0     0     my( $self, $new_value ) = @_;
164            
165 0 0         if ( defined $new_value ) {
166 0 0         croak "You must supply an array ref when setting the table data"
167             unless ref $new_value;
168 0           $self->{'value'} = $new_value;
169             }
170            
171 0           return $self->{value};
172             }
173              
174             # Takes field name or number and returns array ref for that field
175             sub get_column {
176 0     0     my( $self, $label ) = @_;
177 0           my $data = $self->table;
178 0 0         my $index = ( $label =~ /^\d+$/ ? $label : $self->get_index( $label ) );
179            
180 0 0         croak "Invalid column name" unless defined( $index );
181 0 0         croak "Column $index doesn't exist" if $index > $self->num_columns;
182            
183 0           my @result = map $_->[$index], @$data;
184 0           return \@result;
185             }
186              
187             sub set_column {
188 0     0     my( $self, $label, $col ) = @_;
189 0           my $data = $self->table;
190 0 0         my $index = ( $label =~ /^\d+$/ ? $label : $self->get_index( $label ) );
191            
192 0 0         croak "Column not an array reference" unless ref( $col ) =~ /ARRAY/;
193 0 0         croak "Invalid column name: '$label'" unless defined( $index );
194 0 0         croak "Column $index doesn't exist" if $index > $self->num_columns;
195            
196 0           for ( my $i = 0; $i < @$col; $i++ ) {
197 0           $data->[$i][$index] = $col->[$i];
198             }
199             # This fills in the rest of the col with undef if they passed
200             # fewer elements than the number the col currently has
201 0           for ( my $i = @$col; $i < $self->num_rows; $i++ ) {
202 0           $data->[$i][$index] = undef;
203             }
204             }
205              
206             sub add_column {
207 0     0     my( $self, $name, $type, $col ) = @_;
208 0           my $data = $self->table;
209 0           my $names = $self->names;
210 0           my $types = $self->types;
211            
212 0 0         croak "You must supply the name and type of the column" unless @_ >= 4;
213 0 0         croak "Column not an array reference" unless ref( $col ) =~ /ARRAY/;
214 0 0         croak "Duplicate column name: '$name'" if
215             defined( $self->get_index( $name ) );
216            
217 0           push @$names, $name;
218 0           push @$types, $type;
219            
220 0           for ( my $i = 0; $i < @$col; $i++ ) {
221 0           push @{ $data->[$i] }, $col->[$i];
  0            
222             }
223             }
224              
225             sub del_column {
226 0     0     my( $self, $label ) = @_;
227 0           my $data = $self->table;
228 0 0         my $index = ( $label =~ /^\d+$/ ? $label : $self->get_index( $label ) );
229            
230 0 0         croak "Invalid column name: '$label'" unless defined( $index );
231 0 0         croak "Column $index doesn't exist" if $index > $self->num_columns;
232            
233 0           _del_from_array( $self->{'names'}, $index );
234 0           _del_from_array( $self->{'types'}, $index );
235            
236 0           foreach ( @$data ) {
237 0           _del_from_array( $_, $index );
238             }
239             }
240              
241             # Pass array ref and index to delete; deletes array in place
242             sub _del_from_array {
243 0     0     my( $arrayref, $del_idx ) = @_;
244            
245 0 0         return if $del_idx > $#$arrayref;
246 0           for ( my $i = 0; $i < @$arrayref; $i++ ) {
247 0 0         $arrayref->[$i] = $i >= $del_idx ? $arrayref->[$i+1] : $arrayref->[$i];
248             }
249 0           $#$arrayref--;
250             }
251              
252              
253             sub get_row {
254 0     0     my( $self, $row_num ) = @_;
255 0 0         croak "Row $row_num doesn't exist" if $row_num > $self->num_rows;
256 0           return $self->table->[$row_num];
257             }
258              
259             sub set_row {
260 0     0     my( $self, $row_num, $row ) = @_;
261            
262 0 0         croak "Row not an array reference" unless ref( $row ) =~ /ARRAY/;
263 0 0         croak "Row $row_num doesn't exist" if $row_num > $self->num_rows;
264 0 0         croak "Number of elements in row does not match number of columns in " .
265             "recordset" unless @$row == $self->num_columns;
266            
267 0           $self->table->[$row_num] = $row;
268             }
269              
270             sub add_row {
271 0     0     my( $self, $row ) = @_;
272 0           my $data = $self->table;
273            
274 0 0         croak "Row not an array reference" unless ref( $row ) =~ /ARRAY/;
275 0 0         croak "Number of elements in row does not match number of columns in " .
276             "recordset" unless @$row == $self->num_columns;
277            
278 0           push @{ $self->table }, $row;
  0            
279             }
280              
281             sub _check_data_type {
282 0     0     my( $self, $num_rows ) = @_;
283            
284 0 0         if ( @{ $self->types } ) {
  0            
285 0 0         croak "Number of elements in row does not match number of columns in " .
286             "recordset" unless $num_rows == $self->num_columns;
287             }
288             else {
289 0           warn "No data types defined for this recordset; assuming 'string'.\n";
290 0           my @types;
291 0           for ( 1 .. $num_rows ) { push @types, "string"; }
  0            
292 0           $self->{'types'} = \@types;
293             }
294              
295             }
296              
297             sub del_row {
298 0     0     my( $self, $row_num ) = @_;
299 0           my $data = $self->table;
300            
301 0 0         croak "Row $row_num doesn't exist" if $row_num > $self->num_rows;
302            
303 0           _del_from_array( $data, $row_num );
304             }
305              
306              
307             # Deprecated
308             sub get_field {
309 0     0     my( $self, $row_num, $col_num ) = @_;
310 0           my $data = $self->table;
311            
312 0           carp "get_field is deprecated; you should use get_element instead";
313 0 0 0       croak "Field [$row_num,$col_num] doesn't exist" if
314             $row_num > $self->num_rows or $col_num > $self->num_columns;
315            
316 0           return $data->[$row_num][$col_num];
317             }
318              
319             # Deprecated
320             sub set_field {
321 0     0     my( $self, $row_num, $col_num, $value ) = @_;
322 0           my $data = $self->table;
323            
324 0           carp "set_field is deprecated; you should use set_element instead";
325 0 0 0       croak "Field [$row_num,$col_num] doesn't exist" if
326             $row_num > $self->num_rows or $col_num > $self->num_columns;
327            
328 0           $data->[$row_num][$col_num] = $value;
329             }
330              
331              
332             sub get_element {
333 0     0     my( $self, $label, $row_num ) = @_;
334 0           my $data = $self->table;
335 0 0         my $col_num = ( $label =~ /^\d+$/ ? $label : $self->get_index( $label ) );
336            
337 0 0 0       croak "Field [ $label, $row_num ] doesn't exist" if
      0        
338             ! defined( $col_num ) or
339             $row_num >= $self->num_rows or
340             $col_num >= $self->num_columns;
341            
342 0           return $data->[$row_num][$col_num];
343             }
344              
345             sub set_element {
346 0     0     my( $self, $label, $row_num, $value ) = @_;
347 0           my $data = $self->table;
348 0 0         my $col_num = ( $label =~ /^\d+$/ ? $label : $self->get_index( $label ) );
349            
350 0 0 0       croak "Field [ $label, $row_num ] doesn't exist" if
      0        
351             ! defined( $col_num ) or
352             $row_num >= $self->num_rows or
353             $col_num >= $self->num_columns;
354            
355 0           $data->[$row_num][$col_num] = $value;
356             }
357              
358              
359             sub get_index {
360 0     0     my( $self, $name ) = @_;
361            
362 0           for ( my $i = 0; $i < @{ $self->{'names'} }; $i++ ) {
  0            
363 0 0         return $i if lc $name eq lc $self->{'names'}[$i];
364             }
365 0           return undef;
366             }
367              
368              
369              
370             #/-----------------------------------------------------------------------
371             # Private Methods
372             #
373              
374             sub is_parser {
375 0     0     return 0;
376             }
377              
378              
379             sub _serialize {
380 0     0     my( $self ) = @_;
381 0           my $table = $self->table;
382 0           my $names = $self->names;
383 0           my $types = $self->types;
384 0           my $rows = $self->num_rows;
385 0           my $names_str = join ",", @$names;
386 0           my $type;
387            
388             # We don't need to worry about data types if we don't have any data
389 0 0         if ( $self->num_rows ) {
390 0           foreach $type ( @$types ) {
391 0 0         croak "No data types were defined for this recordset" unless
392             defined $type;
393 0 0         die "Unsupported data type: '$_'" unless
394             grep $type eq $_, @Data_Types;
395             }
396             }
397            
398 0           my $output = "";
399            
400 0           for ( my $col_idx = 0; $col_idx < $self->num_columns; $col_idx++ ) {
401 0           $output .= "";
402 0           my $column = $self->get_column( $col_idx );
403 0           my $field;
404 0           foreach $field ( @$column ) {
405 0 0         my $var = defined( $field ) ?
406             eval "WDDX::\u$types->[$col_idx]\->new( \$field )" :
407             new WDDX::Null();
408 0 0         die "$@\n" if $@;
409 0           $output .= $var->_serialize;
410             }
411 0           $output .= "";
412             }
413            
414 0           $output .= "";
415 0           return $output;
416             }
417              
418              
419             sub _deserialize {
420 0     0     my( $self ) = @_;
421 0           return $self;
422             }
423              
424             #/-----------------------------------------------------------------------
425             # Parsing Code
426             #
427              
428             package WDDX::Recordset::Parser;
429              
430              
431             sub new {
432 0     0     my $class = shift;
433            
434 0           my $self = {
435             row_count => 0,
436             names => "",
437             value => [],
438             curr_field => -1,
439             curr_row => -1,
440             parse_var => "",
441             types => [],
442             seen_recordsets => 0,
443             };
444 0           return bless $self, $class;
445             }
446              
447              
448             sub start_tag {
449 0     0     my( $self, $element, $attribs ) = @_;
450 0           my $parse_var = $self->parse_var;
451            
452 0 0 0       if ( $element eq "recordset" and not $self->{seen_recordsets}++ ) {
    0 0        
453 0 0         unless ( $attribs->{rowcount} =~ /^\d+$/ ) {
454 0           die "Invalid value for rowCount attribute in tag\n";
455             }
456            
457 0           my @names = split ",", $attribs->{fieldnames};
458 0 0 0       if ( ! @names or grep ! /^[_A-Za-z][_.0-9A-Za-z]*$/, @names ) {
459 0           die "Invalid fieldNames attribute declared in tag\n";
460             }
461            
462 0           $self->{'names'} = \@names;
463 0           $self->{row_count} = $attribs->{rowcount};
464             }
465             elsif ( $element eq "field" and $self->{seen_recordsets} == 1 ) {
466 0 0         die "No name supplied for field\n" unless $attribs->{name};
467 0 0         die "Cannot nest elements\n" unless $self->{curr_row} < 0;
468            
469 0           my $expected = $self->{'names'}[ ++$self->{curr_field} ];
470 0 0         unless ( $attribs->{name} eq $expected ) {
471 0           die "Expected and found " .
472             "\n";
473             }
474            
475 0           $self->{curr_row} = -1;
476             }
477             else {
478 0 0         unless ( $parse_var ) {
479 0 0         die "<$element> not allowed in Recordset element\n" unless
480             grep $element eq $_, @Data_Types;
481 0 0         $parse_var = WDDX::Parser->create_var( $element ) or
482             die "Expecting some data element (e.g., ), " .
483             "found: <$element>\n"; # shouldn't happen but be safe...
484 0           $self->{'types'}[ $self->{curr_field} ] = $element;
485 0           $self->push( $parse_var );
486             }
487 0           $parse_var->start_tag( $element, $attribs );
488             }
489            
490 0           return $self;
491             }
492              
493              
494             sub end_tag {
495 0     0     my( $self, $element ) = @_;
496 0           my $parse_var = $self->parse_var;
497            
498 0 0 0       if ( $element eq "recordset" and not --$self->{seen_recordsets} ) {
    0 0        
499 0           my @data = map { [ map $_->_deserialize, @$_ ] } @{ $self->{value} };
  0            
  0            
500            
501             # This is kinda a kludge to allow us to deserialize empty recordsets
502             # Since an empty recordset will have no data type tags, we set the
503             # data type of each field to undef
504 0 0         unless ( @data ) {
505 0           $self->{'types'} = [ map undef, ( 1 .. @{ $self->{'names'} } ) ];
  0            
506             }
507            
508 0           $self = new WDDX::Recordset(
509             $self->{'names'},
510             $self->{'types'},
511             \@data
512             );
513             }
514             elsif ( $element eq "field" and $self->{seen_recordsets} == 1 ) {
515 0           my $name = $self->{'names'}[ $self->{curr_field} ];
516 0 0         if ( $self->{curr_row} != $self->{row_count} - 1 ) {
517 0           die "Number of elements in field '$name' doesn't match declared " .
518             "row count\n";
519             }
520 0           $self->{curr_row} = -1;
521             }
522             else {
523 0 0         unless ( $parse_var ) {
524             # XML::Parser should actually catch this
525 0           die "Found before <$element>\n";
526             }
527 0           $self->parse_var( $parse_var->end_tag( $element ) );
528             }
529            
530 0           return $self;
531             }
532              
533              
534             sub append_data {
535 0     0     my( $self, $data ) = @_;
536 0           my $parse_var = $self->parse_var;
537            
538 0 0         if ( $parse_var ) {
    0          
539 0           $parse_var->append_data( $data );
540             }
541             elsif ( $data =~ /\S/ ) {
542 0           die "No loose character data is allowed within elements\n";
543             }
544             }
545              
546              
547             sub is_parser {
548 0     0     return 1;
549             }
550              
551              
552             sub parse_var {
553 0     0     my( $self, $var ) = @_;
554 0           my $curr_field = $self->{curr_field};
555 0           my $curr_row = $self->{curr_row};
556            
557 0 0 0       return "" if $curr_field < 0 or $curr_row < 0;
558            
559 0 0         $self->{value}[$curr_row][$curr_field] = $var if defined $var;
560 0           my $curr_var = $self->{value}[$curr_row][$curr_field];
561 0 0 0       return ( ref $curr_var && $curr_var->is_parser ) ? $curr_var : "";
562             }
563              
564              
565             sub push {
566 0     0     my( $self, $element ) = @_;
567 0           my $curr_field = $self->{curr_field};
568 0           my $curr_row = ++$self->{curr_row};
569 0           my $name = $self->{'names'}[$curr_field];
570            
571 0 0         if ( $curr_field < 0 ) {
572 0           die "Missing tag in recordset\n";
573             }
574 0 0         if ( $self->{curr_row} >= $self->{row_count} ) {
575 0           die "Number of elements in field '$name' exceeds declared row count\n";
576             }
577            
578 0           $self->{value}[$curr_row][$curr_field] = $element;
579             }