NAME

WDDX.pm - Module for reading and writing WDDX packets

VERSION

Version 1.02

$Header: /home/cvs/wddx/WDDX.pm,v 1.4 2003/12/02 03:41:10 andy Exp $

NAME

SYNOPSIS

use WDDX;
my $wddx = new WDDX;

# Serialization example

my $wddx_hash = $wddx->hash( {
        str     =>  $wddx->string( "Welcome to WDDX!\n" ),
        num     =>  $wddx->number( -12.456 ),
        date    =>  $wddx->datetime( date ),
        bool    =>  $wddx->boolean( 1 ),
        arr     =>  $wddx->array( [
                    $wddx->boolean( 0 ),
                    $wddx->number( 10 ),
                    $wddx->string( "third element" ),
                ] ),
        rec     =>  $wddx->recordset(
                    [ "NAME", "AGE" ],
                    [ "string", "number" ],
                    [
                        [ "John Doe", 34 ],
                        [ "Jane Doe", 25 ],
                        [ "Fred Doe", 90 ],
                    ]
                ),
        obj     =>  $wddx->hash( {
                    str => $wddx->string( "a string" ),
                    num => $wddx->number( 3.14159 ),
                } ),
        bin     => $wddx->binary( $img_data ),
        null    => $wddx->null(),
    } );

print $wddx->header;
print $wddx->serialize( $wddx_hash );

# Deserialization example

my $wddx_request = $wddx->deserialize( $packet );

# Assume that our code expects an array
$wddx_request->type eq "array" or die "Invalid request";
my $array_ref = $wddx_request->as_arrayref;

DESCRIPTION

About WDDX

From http://www.wddx.org/:

    The Web Distributed Data Exchange, or WDDX, is a free, open XML-based technology that allows Web applications created with any platform to easily exchange data with one another over the Web.

WDDX and Perl

WDDX defines basic data types that mirror the data types available in other common programming languages. Many of these data types don't have corresponding data types in Perl. To Perl, strings, numbers, booleans, and dates are just scalars. However, in order to communicate effectively with other languages (and this is the point of WDDX), you do have to learn the basic WDDX data types. Here is a table that maps the WDDX data type to Perl, along with the intermediate object WDDX.pm represents it as:

WDDX Type      WDDX.pm Data Object      Perl Type
---------      -------------------      ---------
String         WDDX::String             Scalar
Number         WDDX::Number             Scalar
Boolean        WDDX::Boolean            Scalar (1 or "")
Datetime       WDDX::Datetime           Scalar (seconds since epoch)
Null           WDDX::Null               Scalar (undef)
Binary         WDDX::Binary             Scalar
Array          WDDX::Array              Array
Struct         WDDX::Struct             Hash
Recordset      WDDX::Recordset          WDDX::Recordset

In languages that have data types similar to the WDDX data types, the WDDX modules allow you to convert directly from a variable to a WDDX packet and vice versa. This Perl implementation is different; here you must always go through an intermediate stage where the data is represented by an object with a corresponding data type. These objects can be converted to a WDDX packet, converted to a basic Perl type, or converted to JavaScript code (which will recreate the data for you in JavaScript). We will refer to these objects as data objects throughout this documentation.

Requirements

This module requires XML::Parser and MIME::Base64, which are both available on CPAN at http://www.cpan.org/. Windows users note: These modules use compiled code, but I have been told that they are both included with recent distributions of ActiveState Perl.

METHODS

new

This creates a new WDDX object. You need one of these to do pretty much anything else. It doesn't take any arguments.

$wddx->deserialize( $string_or_filehandle )

This method deserializes a WDDX packet and returns a data object. Note that you can pass either a string or a reference to an open filehandle containing a packet (XML::Parser is flexible this way):

$wddx_obj = $wddx->deserialize( $packet );     # OR
$wddx_obj = $wddx->deserialize( \*HANDLE );

If WDDX.pm or the underlying XML::Parser finds any errors with the structure of the WDDX packet, then it will die with an error message that identifies the problem. If you don't want this to terminate your script, you will have to place this call within an eval block to trap the die.

$wddx->serialize( $wddx_obj )

This accepts a data object as an argument and returns a WDDX packet. This method calls the as_packet() method on the data object it receives. However, this method does provide one feature that as_packet() does not. If $WDDX::INDENT is set to a defined value, then the generated WDDX packet is indented using $WDDX::INDENT as the unit of indentation. Otherwise packets are generated without extra whitespace.

Note that the generated packet is not a valid XML document without the header, see below.

$wddx->header

This returns a header that should accompany every serialized packet you send.

WDDX DATA OBJECTS

Common Methods

All of the WDDX data objects share the following common methods:

$wddx_obj->type

This returns the data type of the object. It is lowercase and maps to the package name without the WDDX prefix. For example, type will return "string" for WDDX::String objects, "datetime" for WDDX::Datetime objects, etc.

$wddx_obj->as_packet

This returns a WDDX packet for the object. You can also do this by passing the object to the $wddx-serialize> method. See the warning in $wddx-header>.

$wddx_obj->as_javascript( $js_varname )

This method takes the name of a JavaScript variable and returns the actual JavaScript code to assign this data object to the given JavaScript variable. No temporary variables are created to avoid any danger of variable name collisions.

Example:

$options[0] = $wddx->string( "First Choice" );
$options[1] = $wddx->string( "Second Choice" );
$options[2] = $wddx->string( "Third Choice" );
$w_array    = $wddx->array( \@options );
print $w_array->as_javascript( "myArray" );

This prints the text (new lines added for readability):

myArray=new Array();
myArray[0]="First Choice";
myArray[1]="Second Choice";
myArray[2]="Third Choice";

All data types are supported, and arrays and hashes (structs) can nest to any level. Recordset and binary objects require the JavaScript WddxRecordset and WddxBinary constructors. The easiest way to include these is to add a reference to the wddx.js file:

<SCRIPT NAME="javascript" SRC="wddx.js"></SCRIPT>

wddx.js is the WDDX library for JavaScript. It is available as part of the WDDX SDK at http://www.wddx.org/.

WDDX::String

$wddx->string( 'Just a bunch of text...' )

This creates a WDDX string object. Strings contain 8 bit characters, can be any length, and should not include embedded nulls. However, control characters and characters that have special meaning for XML (like <, >, and &) are safely encoded for you.

$w_string->as_scalar

This returns the value of the WDDX::String as a Perl scalar.

WDDX::Number

$wddx->number( 3.14159 )

This creates a WDDX number object. Numbers are restricted to +/-1.7e308 and if you exceed these bounds this method dies with an error. Floating point numbers are restricted to 15 digits of accuracy past the decimal. If you exceed this then the number is truncated to 15 digits with a warning. If you pass a non-numeric scalar to this, then it is simply treated as a number: Perl will attempt to translate it, will probably use zero, and will issue a warning.

$w_number->as_scalar

This returns the value of the WDDX::Number as a Perl scalar.

WDDX::Boolean

$wddx->boolean( 1 )

This creates a WDDX boolean object. It simply tests the argument in a boolean context, so "0" and "" are false and anything else is true.

$w_boolean->as_scalar

This returns the value of the WDDX::Boolean as a Perl scalar. True is represented by 1 and false is represented by an empty string.

WDDX::Datetime

$wddx->datetime

This creates a WDDX Datetime object.

$w_datetime->use_timezone_info( 1 )

This sets or reads the flag that says whether to include the timezone info (local hour and minute offset from UTC) in WDDX packets created from this object. By default this is turned on for new objects. You can turn it off by passing a false (but not undef) argument to this method.

When a WDDX::Datetime object is deserialized from a packet, this method will indicate whether timezone information was present in that packet.

$w_datetime->as_scalar

This returns the value of the WDDX::Datetime as a Perl scalar. It contains the number of seconds since the epoch localized for the current machine (like Perl's built-in time function). This number can be passed into Perl's localtime function.

WDDX::Null

$wddx->null()

This creates a WDDX null object. This is roughly the equivalent of undef in Perl. It takes no arguments.

$w_datetime->as_scalar

This simply returns undef (this was a hard one to code :).

WDDX::Binary

$wddx->binary( $binary_data )

This creates a WDDX binary object. It takes a scalar containing any data, which will be base64 encoded before being serialized into the packet.

WDDX::Array

$wddx->array( [ $wddx_obj1, $wddx_obj2, ... ] )

This creates a WDDX::Array object. It takes a reference to an array containing data objects. You must construct a WDDX data object for each element of an array before adding them to the array. WDDX::Arrays can contain any other WDDX data type and do not need to be of a uniform type, so one array can contain a WDDX::String, a WDDX::Number, and a WDDX::Struct, for example.

If you need to create an array of uniform types, Perl's built-in map function makes this easy. If you have a standard Perl array called @array, you can generate a WDDX::Array of WDDX::String objects like this:

my @obj_array = map $wddx->number( $_ ), @array;
my $wddx_array = $wddx->array( \@obj_array );

If you need to serialize more complicated array structures, refer to array2wddx in the UTILITY METHODS section.

$wddx_array->as_arrayref()

This returns a reference to a Perl array. Every element in the WDDX::Array is recursively deserialized to Perl data structures. Only WDDX::Recordsets remain as WDDX data objects.

$wddx_array->get_element( $i )
$wddx_array->get( $i )

This allows you to get an element of a WDDX::Array as a data object instead of having it deserialized to Perl.

$wddx_array->set( $i => $wddx_obj );

This allows you to set an element in a WDDX::Array. Note that $wddx_obj should be a WDDX data object of some type.

$wddx_array->splice( $offset, $length, $wddx_obj1, $wddx_obj2, ... );
$wddx_array->splice( $offset, $length );
$wddx_array->splice( $offset );

This allows you to insert or delete elements in a WDDX::Array using the syntax of Perl's built-in splice function.

$wddx_array->length();

This returns the number of elements in the WDDX::Array object.

$wddx_array->push( $wddx_obj1, $wddx_obj2, ... );

This will push the given elements onto the WDDX::Array object.

$wddx_array->pop();

This will pop the last element off the WDDX::Array object and return it.

$wddx_array->unshift( $wddx_obj1, $wddx_obj2, ... );

This will unshift the given elements onto the WDDX::Array object.

$wddx_array->shift();

This will shift the first element off the WDDX::Array object and return it.

WDDX::Struct

$wddx->struct( { key1 => $wddx_obj1, key2 => $wddx_obj2, ... } )
$wddx->hash ( { key1 => $wddx_obj1, key2 => $wddx_obj2, ... } )

This creates a WDDX::Struct object. To WDDX, a struct is simply what Perl refers to as a hash (or associative array). These two methods are aliases so you can use whichever name you prefer.

There are no restrictions on keys, but values must be WDDX data types. Just like with WDDX::Arrays, you have to create a WDDX data type for each value you want to insert into a WDDX::Struct.

Here's how to use Perl's built-in map function to generate a WDDX::Struct if all of your values have the same data type. If you have a standard Perl hash called %hash, you can generate a WDDX::Struct of WDDX::String objects like this:

my %obj_hash = map { $_ => $wddx->number( $hash{$_} } keys %hash;
my $wddx_hash = $wddx->hash( \@obj_hash );

If you need to serialize more complicated hash structures, refer to hash2wddx in the UTILITY METHODS section.

$wddx_array->as_hashref()

This returns a reference to a Perl hash. Every element in the hash is recursively deserialized to Perl data structures. Only WDDX::Recordsets remain as data objects.

$wddx_hash->get_element( $key );
$wddx_hash->get( $key );

This allows you to get an element of a WDDX::Struct as a data object instead of having it deserialized to Perl.

$wddx_hash->set( $key => $wddx_obj );

This allows you to set a key/value pair in a WDDX::Struct. Note that $wddx_obj should be a WDDX data object of some type.

$wddx_hash->delete( $key );

This allows you to delete a key from a WDDX::Struct.

$wddx_hash->keys();

This will return a list of keys for the WDDX::Struct object or the number of keys (if called in a scalar context).

$wddx_hash->values();

This will return a list of values for the WDDX::Struct object or the number of values (if called in a scalar context). Note that each one of these values should be a WDDX data object of some type.

WDDX::Recordset

$wddx->recordset( [ NAME_A, NAME_B, ... ], [ TYPE_A, TYPE_B, ... ], [ DATA ] )

This creates a WDDX::Recordset object. Recordsets hold tabular data. There is no corresponding data type in Perl, but it corresponds with the type of output you would receive from a SQL query.

The first argument when constructing a recordset should be a reference to an array containing the names of each of the fields. The second argument an reference to an array containing the types of each of the fields. Field types must be simple, so the valid types are "string", "number", "boolean", or "datetime". The last argument is an optional reference to an array of arrays -- in other words a table of data. Note that this table of data contains plain old Perl scalars; you should not create WDDX objects for each value as you would for an array or a hash.

$wddx_rec = $wddx->recordset( [ NAME_A, NAME_B, ... ], 
                              [ TYPE_A, TYPE_B, ... ], 
                              [ [ $val_a1, $val_b1, ... ], 
                                [ $val_b1, $val_b2, ... ],
                                ... 
                              ] )

This is simple to use with DBI:

$data = $dbh->selectall_arrayref( "SELECT NAME, AGE FROM TABLE" ) or
  die $dbh->errstr;
$wddx_rec = $wddx->recordset( [ "NAME", "AGE" ],
                              [ "string", "number" ],
                              $data );

Recordsets that are within arrays or hashes are not automatically deserialized for you when you deserialize the array or hash. They remain as recordset objects. You can use the methods below to access the data.

Note: It is possible to receive a packet for a recordset that does not contain any records. In WDDX, the data type for each field is determined by looking at how the data in the field has been tagged; so if there is no data, then there is no data type information. Thus if you deserialize an empty recordset packet, add data to the resulting recordset object, and attempt to serialize it back into a packet, you will get an error because WDDX.pm will not know what data type to assign to the data you added. To avoid this, you should call the types() method to set the data types before you serialize a recordset object that was created by deserializing a packet. (If this explanation makes no sense, reread it a few times; if it still doesn't make sense, email me and let me know. :)

$wddx_rec->names

Returns a reference to an array of the field names. You can also pass a reference to an array to set the names.

$wddx_rec->types

Returns a reference to an array of the field data types. You can also pass a reference to an array to set the data types.

$wddx_rec->table

Returns a reference to an array of rows, each containing an array of fields. You can also pass a reference to an array to set all the data at once.

$wddx_rec->num_rows

Returns the number of rows.

$wddx_rec->num_columns

Returns the number of columns (or fields in a row).

$wddx_rec->get_row( $row_num )

Takes an row index (0 base) and returns a reference to an array for that row.

$wddx_rec->add_row( [ ARRAY ] )

Takes a reference to an array and adds this row to the bottom of the rows.

$wddx_rec->del_row( $row_num )

Takes a row index and deletes that row.

$wddx_rec->set_row( $row_num, [ ARRAY ] )

Takes a row index and a reference to an array. It replaces that row with this new array.

$wddx_rec->get_column( $col_name )

Takes a column name or index (0 base) and returns a reference to an array for that column.

$wddx_rec->add_column( 'NAME', 'TYPE', [ ARRAY ] )

Takes a column name, type, and a reference to an array and adds the column to the end of the columns.

$wddx_rec->set_column( 'NAME', [ ARRAY ] )

Takes a column name or index (0 base) and a reference to an array. Replaces the column with the values from this array.

$wddx_rec->del_column( $name )

Takes a column name or index (0 base) and deletes the column.

$wddx_rec->get_element( $col_name, $row_num )

Takes a column name or index (0 base) and row number and returns the value of the intersecting cell.

$wddx_rec->set_element( $col_name, $row_num, 'New value' )

Takes a row number, a column number, and a value and sets the value of the intersecting cell.

$wddx_rec->get_field( $row_num, $col_num )

DEPRECATED! Takes a row number and column number and returns the value of the intersecting cell.

This method is deprecated. Because WDDX often refers to columns in a recordset as fields, this method name may be confusing. It has been replaced by get_element() and will be removed in a future version.

$wddx_rec->set_field( $row_num, $col_num, 'New value' )

DEPRECATED! Takes a row number, a column number, and a value and sets the value of the intersecting cell.

This method is deprecated. Because WDDX often refers to columns in a recordset as fields, this method name may be confusing. It has been replaced by set_element() and will be removed in a future version.

Utility Methods

These methods make it easier to go from Perl to WDDX data objects and vice versa.

$wddx->wddx2perl( $wddx_obj );

This takes a WDDX data object and returns a scalar if it is a simple data type, an array reference if it is an array, a hash reference if it is a struct, and a WDDX::Recordset object if it is a recordset.

$wddx->scalar2wddx( $scalar, [ $type ] );

This method takes a scalar and a data type and returns the scalar as a WDDX data object of the given type. Type should be one of the simple WDDX data types (i.e. string, number, boolean, datetime, null, or binary), and if it is not supplied, then string is assumed.

This method is convenient if you have the type stored in a variable, since it avoids you having to do a bunch of if/else statements to call the corresponding data object constructor.

$wddx->array2wddx( $arrayref, [ $coderef ] );
$wddx->hash2wddx( $hashref, [ $coderef ] );

These methods attempt to provide a way for you to generate complex WDDX data types from complicated Perl structures. In their simplest form, they will generate a corresponding WDDX data object by serializing all scalars as strings. This may be sufficient for your needs, but it likely will not. Thus, these methods also allow you to determine the type for each scalar. To do so, you must provide a reference to a sub.

Your sub will be called for each value within the array or a hash you supply, as well as each value within any nested arrays or hashes. Thus your sub may need to support both hashes and arrays.

If your sub is called within an array, it will receive the following arguments:

1. the index of the current element
2. the value of the current element
3. the text "ARRAY"

If your sub is called within a hash, it will receive the following arguments:

1. the key of the current pair
2. the value of the current pair
3. the text "HASH"

You must return the type of the data object to construct (e.g. "number") or a false value if you want to let the element continue to the next rule. The rules for converting elements into WDDX data objects are as follows:

    1. If the element is already a WDDX data object, then it is left alone.

    2. Your subroutine is called (if provided). If a true value is not returned, then we skip to rule 3. If you return an invalid data type, then this method dies with an error. If you return a valid data type then:

      a. If the current element is a scalar then this element is converted to a WDDX data object of the type you specified.

      b. If the current element is a reference to a hash or an array, then this hash or array is converted to a WDDX data object with each element having the type you specified (this applies to all nested arrays and hashes too).

    3. If the current element is a reference to a hash or an array then wddx2array or wddx2hash is called on it and your sub (if provided) propagates.

    4. Any scalars that have not been handled by a previous rule are treated as strings.

Here is an example. Assume that you have the following data structure in Perl:

$weather_data = {
   title       => "Weather Conditions",
   region      => "San Francisco Bay Area",
   current     => {
     temp      => 72,
     sky       => "mostly clear",
     precip    => undef,
     wind      => 12
   },
   tomorrow    => {
     temps     => [ 62 => 75 ],
     sky       => "partly cloudy",
     precip    => undef,
     winds     => [ 5  => 10 ]
   }
};

To convert this to a WDDX object you could create a handler and use it to create a WDDX object like this:

$type_sub = sub {
   my( $name, $val, $mode ) = @_;
   ! defined( $val )   and return "null";
   $name =~ /temp/     and return "number";
   $name =~ /wind/     and return "number";
};

my $wddx_weather = $wddx->hash2wddx( $weather_data, $type_sub );

Then you can easily serialize the WDDX object to a packet:

$WDDX::INDENT = "   ";
print $wddx->serialize( $wddx_weather );

This prints:

<wddxPacket version='1.0'>
   <header/>
   <data>
      <struct>
         <var name='tomorrow'>
            <struct>
               <var name='temps'>
                  <array length='2'>
                     <number>0</number>
                     <number>1</number>
                  </array>
               </var>
               <var name='precip'>
                  <null/>
               </var>
               <var name='winds'>
                  <array length='2'>
                     <number>0</number>
                     <number>1</number>
                  </array>
               </var>
               <var name='sky'>
                  <string>partly cloudy</string>
               </var>
            </struct>
         </var>
         <var name='title'>
            <string>Weather Conditions</string>
         </var>
         <var name='current'>
            <struct>
               <var name='wind'>
                  <number>12</number>
               </var>
               <var name='precip'>
                  <null/>
               </var>
               <var name='temp'>
                  <number>72</number>
               </var>
               <var name='sky'>
                  <string>mostly clear</string>
               </var>
            </struct>
         </var>
         <var name='region'>
            <string>San Francisco Bay Area</string>
         </var>
      </struct>
   </data>
</wddxPacket>

Of course, the handler you construct will vary depending on each particular data structure.

EXAMPLES

I pulled the examples out of here when I realized that this POD was over 50 screenfuls on a standard term! For more lengthy examples, please visit http://www.scripted.com/wddx/ or http://www.wddx.org/.

BUGS

WDDX does not support 16 bit character sets (at least not without encoding them as binary objects).

Every element of data must be encoded as an object. This increases memory usage somewhat, and it also means any data you transfer must fit in memory.

This is actually a non-bug: XML::Parser untaints data as it parses it. This is dangerous. WDDX.pm retaints the data it receives from XML::Parser so you should be safe if you are running in taint mode. Note: WDDX.pm uses $0 to retaint data, so if you untaint $0 then any subsequent WDDX.pm data will be untainted too. Taint is explained perlsec.

CREDITS

Nate Weiss, the man behind the WDDX SDK, has been an especially huge help.

David Medinets started an earlier version of a Perl and WDDX module available at http://www.codebits.com/wddx/.

The following people have helped provide feedback, bug reports, etc. for this module:

Thomas R. Hall
David J. MacKenzie
Jon Sala
Wolfgang ???
James Ritter
Miguel Marques
Vadim Geshel
Adolfo Garcia
Sean McGeever
Allie Rogers
Ziying Sherwin

AUTHOR

Origianally by Scott Guelich <scott@scripted.com>, now maintained by Andy Lester <andy@petdance.com>.