# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be 
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: Elements.pm 3125 2008-06-17 15:44:02Z josh $

package BigMed::Elements;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;

my %handler = ();
reg_defaults();

sub reg_defaults {
    BigMed::Elements->reg_element(
        name           => 'body_position',
        pack           => \&string_pack,
        can_multiple   => 0,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) },
        options        => [qw(gallery above block below other hidden)],
        labels         => {
            'gallery' => 'POS_Image gallery',
            'above'   => 'POS_Above text',
            'block'   => 'POS_Align with paragraph:',
            'below'   => 'POS_Below text',
            'other'   => 'POS_Other',
            'hidden'  => 'POS_Do not display',
        },
    );

   BigMed::Elements->reg_element(
        name           => 'boolean',
        pack           => \&boolean_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'dir_path',
        pack           => \&clean_dir,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'dir_url',
        pack           => \&clean_dir,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'document',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'email',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'id',
        pack           => \&positive_integer_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 0,
        can_line_break => 0,
        validator      => sub { is_number(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'image_file',
        pack           => \&key_value_pack,
        unpack         => \&key_value_unpack,
        can_multiple   => 0,
        can_line_break => 0,
        array          => 1,
        validator      => sub { is_key_value(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'key_boolean',
        pack           => \&key_value_pack,
        unpack         => \&key_value_unpack,
        can_multiple   => 0,
        array          => 1,
        can_line_break => 1,
        validator      => sub { is_key_value(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'key_value',
        pack           => \&key_value_pack,
        unpack         => \&key_value_unpack,
        can_multiple   => 0,
        array          => 1,
        can_line_break => 1,
        validator      => sub { is_key_value(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'kilobytes',
        pack           => \&not_negative_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_number(@_) || is_array_ref(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'link_elements',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        array          => 1,
        validator      => sub { is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'number',
        pack           => \&number_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_number(@_) || is_array_ref(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'number_integer_positive',
        pack           => \&positive_integer_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_number(@_) || is_array_ref(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'number_zeroplus_integer',
        pack           => \&zeroplus_integer_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_number(@_) || is_array_ref(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'password',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'raw_text',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 1,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'rich_text',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 1,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'rich_text_brief',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 1,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'rich_text_public',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 1,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'rich_text_inline',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'select_section',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'simple_text',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'sort_order',
        pack           => \&string_pack,
        can_multiple   => 0,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) },
        options =>
          ['', 'priority', 'pub_time', 'mod_time', 'chron_time', 'title'],
        labels => {
            'priority'   => 'SORT_priority',
            'pub_time'   => 'SORT_pub_time',
            'chron_time' => 'SORT_chron_time',
            'mod_time'   => 'SORT_mod_time',
            'title'      => 'SORT_alphatitle',
        },
    );
    BigMed::Elements->reg_element(
        name           => 'system_id',
        pack           => \&zeroplus_integer_pack,
#        unpack         => \&number_unpack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_number(@_) || is_array_ref(@_) },
        numeric        => 1,
    );
    BigMed::Elements->reg_element(
        name           => 'system_time',
        pack           => \&time_pack,
#        unpack         => \&time_unpack,
        can_multiple   => 0,
        can_line_break => 0,
        validator      => sub { is_time(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'time_offset',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'url',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'url_safe',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'username',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'value_check',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'value_list',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'value_freeform',
        pack           => \&string_pack,
        can_multiple   => 1,
        array          => 1,
        can_line_break => 0,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
    BigMed::Elements->reg_element(
        name           => 'value_several',
        pack           => \&string_pack,
        can_multiple   => 1,
        can_line_break => 0,
        array          => 1,
        validator      => sub { is_scalar(@_) || is_array_ref(@_) },
    );
}

sub reg_element {
    my $class = shift;
    @_ % 2
      && croak "Poorly formatted set_element request (odd "
      . "number of parameters)";
    my %param = @_;
    my $name  = $param{name}
      or croak "Must provide a name when registering an element type.";
    
    my %attr = $handler{$name} ? %{ $handler{$name} } : ();
    $attr{pack} = $param{pack} if ref $param{pack} eq 'CODE';
    $attr{unpack} = $param{unpack} if ref $param{unpack} eq 'CODE';

    foreach my $a (
        'can_multiple', 'can_line_break', 'validator', 'numeric',
        'client_error', 'options',        'labels',    'array'
      )
    {
        $attr{$a} = $param{$a} if exists $param{$a};
    }
    $handler{$name} = \%attr;
}

sub element_exists {
    defined $handler{$_[1]};
}

sub must_unpack {
    my $rtype = $handler{$_[1]};
    defined $rtype->{unpack} || $rtype->{can_line_break};
}

sub property {
    my $class = shift;
    my $name = shift or croak 'No element type provided in property request';
    my $property = shift or croak 'No property name provided in property request';
    return undef if !$handler{$name};
    return $handler{$name}->{$property};
}

###########################################################
# ROUTE TO THE HANDLER ROUTINES
###########################################################

#pack and unpack get their own routines for a slight speed edge
# @_ = [0]class, [1]element_name, [2]data_value, [3]\%param
sub unpack {
    defined ( my $data = $_[2] ) or return undef;
    my $rtype = $handler{$_[1]};
    $data =~ s/\|&&\|/\n/g if $rtype->{can_line_break};
    return $rtype->{unpack} ? $rtype->{unpack}->( $data, $_[3] )
      : !$_[3]->{multiple} ? $data
      : $data ne q{} ? [split( /_!!_/, $data, -1 )]
      : [];
}

sub pack {
    my $class = shift;
    my %param = @_;
    my @value =
      ref( $param{data} ) eq "ARRAY"
      ? @{ $param{data} }
      : ( $param{data} );
    @value = ( $value[0] )
      unless $param{param}->{multiple}
      && $handler{ $param{name} }->{can_multiple};
    my $scrub = $handler{ $param{name} }->{can_line_break}
      ? \&scrub_replace_line_breaks
      : \&scrub_value;
    foreach my $item (@value) {
        $item = $handler{ $param{name} }->{pack}->( $item, $scrub );
    }
    join( "_!!_", @value );
}

sub is_numeric { $handler{ $_[1] }->{numeric} }

###########################################################
# NOTES ABOUT PACKING AND UNPACKING CONVENTIONS
###########################################################

# The divider _!!_ is used to split primary values within
# a field that holds complex data. For example, the array
# data type uses _!!_ as the delimiter between values.
#
# the divider |&| is used to split secondary values within
# a field that holds complex data. For example, the key_array
# data type uses |&| as the delimiter between values in
# the array for each key.
#
# the divider |&&| is used to represent a line break within
# a value.
#
# Individual drivers may also use _!!!_ as a delimiter
# between fields to combine multiple fields into a single
# string. (The File driver does this for its primary index,
# for example).
#
# The pack and unpack drivers scrub all values to make sure
# that they cannot include these strings within values.
# (See the scrub_value routine).

###########################################################
# MULTIPURPOSE ROUTINES FOR BOTH 'PACK' AND 'UNPACK'
###########################################################

sub clean_dir {
    $_[0] = &scrub_value( $_[0] );

    #no trailing/leading space, trailing slash
    $_[0] =~ s/^\s+//;
    $_[0] =~ s#[\s/\\]+$##;
    $_[0];
}

###########################################################
# ELEMENT 'PACK' ROUTINES
###########################################################

#pack routines receive the data to pack (either a scalar or
#a reference to a data structure) and a reference to the
#appropriate data-scrubbing routine. the returned value is a
#string suitable for storing in the database.

# my $packed = &string_pack($data, \&scrub_value);

sub boolean_pack {
    return $_[0] ? 1 : '';
}

sub string_pack {
    return $_[1]->( $_[0] );
}

sub number_pack {
    return &is_number( $_[0] ) ? $_[0] + 0 : "";
}

sub time_pack {
    return "" unless &is_time( $_[0] );
    chomp $_[0];
    $_[0];
}

sub positive_integer_pack {
    if (&is_number( $_[0] ) && int( $_[0] ) == $_[0] && $_[0] > 0) {
        return $_[0] + 0;
    }
    return '';
}

sub zeroplus_integer_pack {
    return &is_number( $_[0] ) && int( $_[0] ) >= 0 ? int( $_[0] ) : "";
}

sub not_negative_pack {
    return &is_number( $_[0] ) && $_[0] >= 0 ? $_[0] + 0 : "";
}

sub key_array_pack {
    my ( $hash, $scrub ) = @_;
    return "" unless ref($hash) eq "HASH";

    my @value;
    foreach my $key ( keys %$hash ) {
        ( my $item = &scrub_value($key) ) =~ s/:+//g;
        $item .= ":";
        $item .= join( '|&|', map { $scrub->($_) } @{ $$hash{$key} } )
          if ref( $$hash{$key} ) eq "ARRAY";
        push( @value, $item );
    }
    join( "_!!_", @value );
}

sub key_value_pack {
    my ( $hash, $scrub ) = @_;
    return "" unless ref($hash) eq "HASH";

    my @value;
    foreach my $key ( keys %$hash ) {
        next unless defined $$hash{$key};
        my $item = $key;
        ( $item = &scrub_value($item) ) =~ s/:+//g;
        push( @value, $item . ":" . $scrub->( $$hash{$key} ) );
    }
    join( "_!!_", @value );
}

###########################################################
# ELEMENT 'UNPACK' ROUTINES
###########################################################

#unpacking do the reverse of what packing routines do; they
#translate a data element in its string form and unpack it
#into its full data structure.

#unpack routines accept the BigMed object, a string of the data
#to be unpacked, and an optional reference to a hash of parameters/
#preferences.

#the returned value is a string -- either a plain string
#for elements that are defined as simple strings, or as
#a reference to a more complex data structure.

sub number_unpack {
    $_[1]->{multiple}
      ? [map { $_ ne "" ? $_ : undef } split( /_!!_/, $_[0], -1 )]
      : $_[0] ne ""     ? $_[0]
      : undef;
}

sub time_unpack {
    $_[0] || undef;
}

sub key_value_unpack {
    my %value;
    foreach my $item ( split( /_!!_/, $_[0], -1 ) ) {
        $value{$1} = $item if $item =~ s/\A(.*?)://;
    }
    \%value;
}

###########################################################
# CONTENT TYPE VALIDATION
###########################################################

sub validate {
    ( $_[0] && exists $handler{ $_[0] }->{validator} )
      or croak "validate: No validator for element type $_[0]";
    return undef unless exists $_[1];
    $handler{ $_[0] }->{validator}->( $_[1] );
}

sub is_scalar {    #returns true for undefined or scalar value
    !defined $_[0] || !ref $_[0] ? 1 : undef;
}

sub is_array_ref {    #returns true for undefined or array ref
    !defined $_[0] || ref $_[0] eq "ARRAY" ? 1 : undef;
}

sub is_hash_ref {     #returns true for undefined or array ref
    !defined $_[0] || ref $_[0] eq "HASH" ? 1 : undef;
}

sub is_number {

    #regex needs /s modifier to knock out strings with line feeds
    if (   defined $_[0]
        && $_[0] =~ /\A\s*[\+\-]?\d*\.?\d*([eE][\+\-]?\d+)?\s*\z/s
        && $_[0] =~ /\d/ )
    {
        return 1;
    }
    return undef;
}

sub is_time {
    return defined $_[0]
      && $_[0] =~ /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/s;
}

sub is_key_value {
    return undef unless is_hash_ref( $_[0] );
    my $rhash = $_[0];
    foreach my $key ( keys %$rhash ) {
        return undef unless is_scalar( $rhash->{$key} );
    }
    1;
}

###########################################################
# HELPERS
###########################################################

sub no_trailing_slash {
    return "" unless defined $_[0];
    $_[0] =~ s#[/\\]+$##;
    $_[0];
}

sub scrub_replace_line_breaks {
    return "" unless defined $_[0];
    $_[0] =~ s/\|&&\|/\|&amp;&amp;\|/g;
    $_[0] =~ s/\n|(\r(\n)?)/\|&&\|/g;
    $_[0] = &scrub_value( $_[0] );
    $_[0];
}

sub scrub_value {

    #prevents values from containing line breaks or divider strings
    return "" unless defined $_[0];
    $_[0] =~ s/[\n\r]+/ /g;
    $_[0] =~ s/\|&(?=\||$)/\|&amp;/g;
    $_[0] =~ s/_!+(?=_|$)/_!/g;
    $_[0];
}

sub pack_hash_as_array {
    my ( $rhash, $scrub, $rkeys ) = @_;
    return ""
      unless ref $rhash eq "HASH"
      && keys %$rhash > 0
      && ref $rkeys eq "ARRAY"
      && @$rkeys > 0;
    my @item;
    foreach my $key (@$rkeys) {
        my $data = $$rhash{$key};    #avoid scrubbing original data
        push( @item, $scrub->($data) );
    }
    join( '|&|', @item );
}

sub unpack_array_of_hashes {
    my %param = ref( $_[2] ) eq "HASH" ? %{ $_[2] } : ();
    return undef
      unless ref( $param{keys} ) eq "ARRAY" && @{ $param{keys} } > 0;

    my @value;
    foreach my $set ( split( /_!!_/, $_[0], -1 ) ) {
        my %hash;
        my @val = split( /\|&\|/, $set, -1 );
        foreach my $key ( @{ $param{keys} } ) {
            $hash{$key} = shift(@val);
        }
        push( @value, \%hash );
    }

    ref $_[1] eq "HASH" && $_[1]->{multiple}
      ? \@value
      : $value[0] ? $value[0] : {};
}

1;

__END__

=head1 BigMed::Elements

Defines basic Big Medium data types

=head1 Description

Big Medium's data objects (which belong to BigMed::Data subclasses) are
composed of data columns, each of which belongs to a data type.
BigMed::Elements defines these data types, which determine how the
data is packed (marshaled) and unpacked for database storage and retrieval.
These type names are also used by BigMed::App's Parse and Prompt classes
to determine how to display edit prompts and how to parse submitted
data when editing data columns in a BigMed::App interface.

=head1 Synopsis

    #register an element type
    BigMed::Elements->reg_element(
        name           => 'type_name',
        pack           => \&pack_routine,
        unpack         => \&unpack_routine,
        can_multiple   => 0,
        can_line_break => 0,
        numeric        => 0,
        validator      => sub { is_scalar(@_) },
        options        => ['above','block','below','hidden'],
        labels         => {
            'above'  => 'POS_Above text',
            'block'  => 'POS_Align with paragraph:',
            'below'  => 'POS_Below text',
            'hidden' => 'POS_Do not display',
          },
    );
    
    #get a property of an element type
    my $clb = BigMed::Elements->property('body_position', 'can_line_break');
    
    #check if it exists
    my $exists = BigMed::Elements->element_exists('body_position');
    
    #pack data for the database
    my $packed = BigMed::Elements->pack(
        name => 'body_position'   # element type name
        data => $value,           # value to pack
        param => {multiple => 1}  # optional hash of parameters        
    );
    
    #unpack data from database
    my $packed = BigMed::Elements->unpack(
        name => 'body_position'   # element type name
        data => $value,           # value to unpack
        param => {multiple => 1}  # optional hash of parameters        
    );
    
    #check if data is valid for type
    my $is_valid = BigMed::Elements::validate('body_position', $value);

=head1 Methods

=head2 reg_element( %parameter )

Registers an element type in the Big Medium system. Calling this method
more than once with the same name attribute will update the existing
registration with new attributes for the values submitted.

The reg_element method accepts a hash of arguments with the following
key/value pairs (only name, pack and unpack are required):

=over 4

=item * C<<name => 'element_name'>>

Required. The name of the element type. This is the internal identifier used
throughout the Big Medium system for this element type.

=item * C<<pack => \&pack_routine>>

Required. A code reference to the routine that packs the submitted value
into a flat string for insertion into the database. The routine receives
two arguments:

    pack_routine($value, $scrub_ref);

The first argument is the value to pack.  The second argument is a coderef
to a routine used by Big Medium to scrub each value of bad characters. If
the value is a scalar, just pass the value to the coderef. If the value
is a reference to a more complex data structure, pass each element of the
data structure to the coderef for scrubbing.

The return value should be the packed string.

=item * C<<unpack => \&unpack_routine>>

Optional. A code reference to the routine that unpacks a database
string into the appropriate format for use by Big Medium data objects.
The routine receives two arguments:

    unpack_routine($value, \%param);

The first argument is the value to unpack.  The second argument is a
hashref of parameter values (rarely used).
The return value of the callback should be the unpacked value.

If no unpack value is specified, the element will be unpacked as a string
(or for multiple items, as an array of strings).

=item * C<<validator => \&validator>>

Optional coderef to a routine that validates whether the value fits
the expected format for this element type. The routine receives a single
argument, the value to validate. Should return a true value if valid,
false if not.

=item * C<<can_multiple => 1>>

If true, indicates that the element type can hold multiple values.
Default is false.

=item * C<<can_line_break => 1>>

If true, indicates that line breaks can make the round trip from the
data base.  If false, line breaks are stripped and replaced with spaces.
Default is false.

=item * C<<numeric => 1>>

If true, indicates that the value is numeric. Useful to help data drivers
sort out how they should store and sort the value.

=item * C<<array => 1>>

A true value indicates that the driver should always return an array-
formatted value for the getter accessor for this element type, no matter
whether the can_multiple is true or not. Useful for hash-formatted
element types like key_value.

=item * C<<options => \@constrained_options>>

An optional array reference of values for types that should constrain their
values to a limited set of options. This is an advisory attribute and is not
enforced at pack/unpack. It may be overrided with different values by
BigMed::Data subclasses when defining data columns.  These values are most
typically used in Prompt and Parse data classes.

=item * C<<labels => { 'option1' => 'label1', 'option2' => 'label2' }>>

An optional hash reference indicating display labels for the values in
the C<options> array. The keys are the values from the C<options> parameter
above, and the values are the (unlocalized and unescaped) display labels.
As with C<options>, this is an advisory attribute that may be overrided
by BigMed::Data subclasses when defining data columns.

=back

=head2 C<<property($element_type, $attribute)

Returns the value of an element type's attribute. The name of the element
type is the first argument, and the attribute name is the second.

    my $clb = BigMed::Elements('body_position', 'can_line_break');

=head2 C<<element_exists($element_type)>>

Returns true if an element type has been registered with the name in the
first argument.

    BigMed::Elements->pack( name => $_[1], data => $_[2], param => $_[3] );

=head2 C<<is_numeric($element_type)>>

    my $numeric = BigMed::Elements->is_numeric($element_type);

=head2 C<<must_unpack($element_type)>>

    BigMed::Elements->is_numeric($element_type);
    
Returns true if the element type has its own custom unpack routine
or may contain line breaks that need to be unescaped.

=head2 C<<BigMed::Elements::validate($element_type, $value)>>

Note that this is a procedural routine, not a class method.

Returns true if $value passes the validator registered for the $element_type.
Throws an exception if no validator is registered for the element type.

=head1 Packing and Unpacking Element Values

The C<pack> and C<unpack> routines are typically used internally by
BigMed::Driver subclasses and you will likely never need to use these
methods outside of those contexts. (Values get packed and unpacked
automatically via BigMed::Data's save and fetch methods.)

=head2 C<<pack( %parameters )>>

Returns a packed string representation of a data value, suitable for storage
in the database. Accepts a hash of arguments with the following key/value
pairs:

    BigMed::Elements->pack( name => $_[1], data => $_[2], param => $_[3] );

=over 4

=item * C<<name => 'element_type'>>

The name of the element type to pack.

=item * C<<data => $value>>

The value to pack.

=item * C<<param => \%optional_parameters>>

Individual element types may treat data differently based on received
parameters.

The only common behavior is the "multiple" flag. If
C<<{ multiple => 1 }>> is the parameter value (and the specific
element type has a C<can_multiple> attribute), an array reference will
be packed with all of its values. If multiple is not true, only a scalar
value or the first value of an array reference will be packed.

=back

=head2 C<<unpack( %parameters )>>

Returns an unpacked data structure from a packed data string.
Accepts a hash of arguments with the following key/value
pairs:

    BigMed::Elements->unpack( name => $_[1], data => $_[2], param => $_[3] );

=over 4

=item * C<<name => 'element_type'>>

The name of the element type to unpack.

=item * C<<data => $value>>

The value to unpack.

=item * C<<param => \%optional_parameters>>

Individual element types may treat data differently based on received
parameters. None of the built-in element types use these parameters.

=back

=head1 See Also

=over 4

=item * BigMed::Data

=item * BigMed::Driver

=item * BigMed::App::Web::Prompt

=item * BigMed::App::Web::Parse

=back

=head1 Author & Copyrights

This module and all Big Medium modules are copyright Josh Clark
and Global Moxie. All rights reserved.

Use of this module and the Big Medium content
management system are governed by Global Moxie's software licenses
and may not be used outside of the terms and conditions outlined
there.

For more information, visit the Global Moxie website at
L<http://globalmoxie.com/>.

Big Medium and Global Moxie are service marks of Global Moxie
and Josh Clark. All rights reserved.

=cut


