| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ############################################################################### | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | # This file copyright (c) 2006-2008 by Randy J. Ray, all rights reserved | 
| 4 |  |  |  |  |  |  | # | 
| 5 |  |  |  |  |  |  | # See "LICENSE" in the documentation for licensing and redistribution terms. | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | ############################################################################### | 
| 8 |  |  |  |  |  |  | # | 
| 9 |  |  |  |  |  |  | #   $Id: Categories.pm 49 2008-04-06 10:45:43Z  $ | 
| 10 |  |  |  |  |  |  | # | 
| 11 |  |  |  |  |  |  | #   Description:    This is an extension of the API base-class that provides | 
| 12 |  |  |  |  |  |  | #                   the information specific to categories. | 
| 13 |  |  |  |  |  |  | # | 
| 14 |  |  |  |  |  |  | #   Functions:      BUILD | 
| 15 |  |  |  |  |  |  | #                   copy | 
| 16 |  |  |  |  |  |  | #                   new | 
| 17 |  |  |  |  |  |  | #                   set_id | 
| 18 |  |  |  |  |  |  | #                   get_parent | 
| 19 |  |  |  |  |  |  | #                   get_sub_categories | 
| 20 |  |  |  |  |  |  | #                   set_sub_categories | 
| 21 |  |  |  |  |  |  | #                   find | 
| 22 |  |  |  |  |  |  | #                   normalize_args | 
| 23 |  |  |  |  |  |  | # | 
| 24 |  |  |  |  |  |  | #   Libraries:      Class::Std | 
| 25 |  |  |  |  |  |  | #                   Error | 
| 26 |  |  |  |  |  |  | #                   WebService::ISBNDB::API | 
| 27 |  |  |  |  |  |  | # | 
| 28 |  |  |  |  |  |  | #   Global Consts:  $VERSION | 
| 29 |  |  |  |  |  |  | # | 
| 30 |  |  |  |  |  |  | ############################################################################### | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | package WebService::ISBNDB::API::Categories; | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 3 |  |  | 3 |  | 11346 | use 5.006; | 
|  | 3 |  |  |  |  | 11 |  | 
|  | 3 |  |  |  |  | 107 |  | 
| 35 | 3 |  |  | 3 |  | 15 | use strict; | 
|  | 3 |  |  |  |  | 52 |  | 
|  | 3 |  |  |  |  | 104 |  | 
| 36 | 3 |  |  | 3 |  | 15 | use warnings; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 104 |  | 
| 37 | 3 |  |  | 3 |  | 12 | no warnings 'redefine'; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 110 |  | 
| 38 | 3 |  |  | 3 |  | 13 | use vars qw($VERSION); | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 131 |  | 
| 39 | 3 |  |  | 3 |  | 18 | use base 'WebService::ISBNDB::API'; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 278 |  | 
| 40 |  |  |  |  |  |  |  | 
| 41 | 3 |  |  | 3 |  | 13 | use Class::Std; | 
|  | 3 |  |  |  |  | 11 |  | 
|  | 3 |  |  |  |  | 19 |  | 
| 42 | 3 |  |  | 3 |  | 274 | use Error; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 24 |  | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | $VERSION = "0.21"; | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | my %id             : ATTR(:init_arg     :get     :default<>); | 
| 47 |  |  |  |  |  |  | my %parent         : ATTR(:init_arg :set :default<>); | 
| 48 |  |  |  |  |  |  | my %name           : ATTR(:name                    :default<>); | 
| 49 |  |  |  |  |  |  | my %summary        : ATTR(:name                 :default<>); | 
| 50 |  |  |  |  |  |  | my %depth          : ATTR(:name                   :default<>); | 
| 51 |  |  |  |  |  |  | my %element_count  : ATTR(:name           :default<>); | 
| 52 |  |  |  |  |  |  | my %sub_categories : ATTR(:init_arg      :default<>); | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | ############################################################################### | 
| 55 |  |  |  |  |  |  | # | 
| 56 |  |  |  |  |  |  | #   Sub Name:       new | 
| 57 |  |  |  |  |  |  | # | 
| 58 |  |  |  |  |  |  | #   Description:    Pass off to the super-class constructor, which handles | 
| 59 |  |  |  |  |  |  | #                   the special cases for arguments. | 
| 60 |  |  |  |  |  |  | # | 
| 61 |  |  |  |  |  |  | ############################################################################### | 
| 62 |  |  |  |  |  |  | sub new | 
| 63 |  |  |  |  |  |  | { | 
| 64 | 3 |  |  | 3 |  | 836 | shift->SUPER::new(@_); | 
| 65 |  |  |  |  |  |  | } | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | ############################################################################### | 
| 68 |  |  |  |  |  |  | # | 
| 69 |  |  |  |  |  |  | #   Sub Name:       BUILD | 
| 70 |  |  |  |  |  |  | # | 
| 71 |  |  |  |  |  |  | #   Description:    Builder for this class. See Class::Std. | 
| 72 |  |  |  |  |  |  | # | 
| 73 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 74 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 75 |  |  |  |  |  |  | #                   $id       in      scalar    This object's unique ID | 
| 76 |  |  |  |  |  |  | #                   $args     in      hashref   The set of arguments currently | 
| 77 |  |  |  |  |  |  | #                                                 being considered for the | 
| 78 |  |  |  |  |  |  | #                                                 constructor. | 
| 79 |  |  |  |  |  |  | # | 
| 80 |  |  |  |  |  |  | #   Returns:        Success:    void | 
| 81 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 82 |  |  |  |  |  |  | # | 
| 83 |  |  |  |  |  |  | ############################################################################### | 
| 84 |  |  |  |  |  |  | sub BUILD | 
| 85 |  |  |  |  |  |  | { | 
| 86 | 2 |  |  | 2 | 1 | 158 | my ($self, $id, $args) = @_; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 2 |  |  |  |  | 11 | $self->set_type('Categories'); | 
| 89 |  |  |  |  |  |  |  | 
| 90 | 2 | 50 |  |  |  | 6 | if ($args->{sub_categories}) | 
| 91 |  |  |  |  |  |  | { | 
| 92 | 0 | 0 |  |  |  | 0 | throw Error::Simple("'sub_categories' must be a list-reference") | 
| 93 |  |  |  |  |  |  | unless (ref($args->{sub_categories}) eq 'ARRAY'); | 
| 94 | 0 |  |  |  |  | 0 | $args->{sub_categories} = [ @{$args->{sub_categories}} ]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 2 |  |  |  |  | 6 | return; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | ############################################################################### | 
| 101 |  |  |  |  |  |  | # | 
| 102 |  |  |  |  |  |  | #   Sub Name:       copy | 
| 103 |  |  |  |  |  |  | # | 
| 104 |  |  |  |  |  |  | #   Description:    Copy the Categories-specific attributes over from target | 
| 105 |  |  |  |  |  |  | #                   object to caller. | 
| 106 |  |  |  |  |  |  | # | 
| 107 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 108 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 109 |  |  |  |  |  |  | #                   $target   in      ref       Object of the same class | 
| 110 |  |  |  |  |  |  | # | 
| 111 |  |  |  |  |  |  | #   Globals:        %id | 
| 112 |  |  |  |  |  |  | #                   %parent | 
| 113 |  |  |  |  |  |  | #                   %name | 
| 114 |  |  |  |  |  |  | #                   %summary | 
| 115 |  |  |  |  |  |  | #                   %depth | 
| 116 |  |  |  |  |  |  | #                   %element_count | 
| 117 |  |  |  |  |  |  | #                   %sub_categories | 
| 118 |  |  |  |  |  |  | # | 
| 119 |  |  |  |  |  |  | #   Returns:        Success:    void | 
| 120 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 121 |  |  |  |  |  |  | # | 
| 122 |  |  |  |  |  |  | ############################################################################### | 
| 123 |  |  |  |  |  |  | sub copy : CUMULATIVE | 
| 124 |  |  |  |  |  |  | { | 
| 125 | 0 |  |  | 0 | 1 | 0 | my ($self, $target) = @_; | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 0 | 0 |  |  |  | 0 | throw Error::Simple("Argument to 'copy' must be the same class as caller") | 
| 128 |  |  |  |  |  |  | unless (ref($self) eq ref($target)); | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 0 |  |  |  |  | 0 | my $id1 = ident $self; | 
| 131 | 0 |  |  |  |  | 0 | my $id2 = ident $target; | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | # Do the simple (scalar) attributes first | 
| 134 | 0 |  |  |  |  | 0 | $id{$id1}            = $id{$id2}; | 
| 135 | 0 |  |  |  |  | 0 | $parent{$id1}        = $parent{$id2}; | 
| 136 | 0 |  |  |  |  | 0 | $name{$id1}          = $name{$id2}; | 
| 137 | 0 |  |  |  |  | 0 | $summary{$id1}       = $summary{$id2}; | 
| 138 | 0 |  |  |  |  | 0 | $depth{$id1}         = $depth{$id2}; | 
| 139 | 0 |  |  |  |  | 0 | $element_count{$id1} = $element_count{$id2}; | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | # This must be tested and copied by value | 
| 142 | 0 | 0 |  |  |  | 0 | $sub_categories{$id1}  = [ @{$sub_categories{$id2}}  ] | 
|  | 0 |  |  |  |  | 0 |  | 
| 143 |  |  |  |  |  |  | if ref($sub_categories{$id2}); | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 0 |  |  |  |  | 0 | return; | 
| 146 | 3 |  |  | 3 |  | 1275 | } | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 18 |  | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | ############################################################################### | 
| 149 |  |  |  |  |  |  | # | 
| 150 |  |  |  |  |  |  | #   Sub Name:       set_id | 
| 151 |  |  |  |  |  |  | # | 
| 152 |  |  |  |  |  |  | #   Description:    Set the ID attribute on the object. Done manually so that | 
| 153 |  |  |  |  |  |  | #                   we can restrict it to this package. | 
| 154 |  |  |  |  |  |  | # | 
| 155 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 156 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 157 |  |  |  |  |  |  | #                   $id       in      scalar    ID, taken from isbndb.com data | 
| 158 |  |  |  |  |  |  | # | 
| 159 |  |  |  |  |  |  | #   Globals:        %id | 
| 160 |  |  |  |  |  |  | # | 
| 161 |  |  |  |  |  |  | #   Returns:        $self | 
| 162 |  |  |  |  |  |  | # | 
| 163 |  |  |  |  |  |  | ############################################################################### | 
| 164 |  |  |  |  |  |  | sub set_id : RESTRICTED | 
| 165 |  |  |  |  |  |  | { | 
| 166 | 0 |  |  | 0 | 1 | 0 | my ($self, $id) = @_; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 0 |  |  |  |  | 0 | $id{ident $self} = $id; | 
| 169 | 0 |  |  |  |  | 0 | $self; | 
| 170 | 3 |  |  | 3 |  | 733 | } | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 10 |  | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | ############################################################################### | 
| 173 |  |  |  |  |  |  | # | 
| 174 |  |  |  |  |  |  | #   Sub Name:       get_parent | 
| 175 |  |  |  |  |  |  | # | 
| 176 |  |  |  |  |  |  | #   Description:    Return a Categories object for the parent. If the current | 
| 177 |  |  |  |  |  |  | #                   value in the attribute is a scalar, convert it to an | 
| 178 |  |  |  |  |  |  | #                   object and replace it before returning. | 
| 179 |  |  |  |  |  |  | # | 
| 180 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 181 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 182 |  |  |  |  |  |  | # | 
| 183 |  |  |  |  |  |  | #   Globals:        %parent | 
| 184 |  |  |  |  |  |  | # | 
| 185 |  |  |  |  |  |  | #   Returns:        Success:    Categories object | 
| 186 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 187 |  |  |  |  |  |  | # | 
| 188 |  |  |  |  |  |  | ############################################################################### | 
| 189 |  |  |  |  |  |  | sub get_parent | 
| 190 |  |  |  |  |  |  | { | 
| 191 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 0 |  |  |  |  | 0 | my $parent = $parent{ident $self}; | 
| 194 | 0 | 0 | 0 |  |  | 0 | if ($parent and not ref($parent)) | 
| 195 |  |  |  |  |  |  | { | 
| 196 | 0 |  |  |  |  | 0 | my $class = $self->class_for_type('Categories'); | 
| 197 |  |  |  |  |  |  |  | 
| 198 | 0 | 0 |  |  |  | 0 | throw Error::Simple("No category found for ID '$parent'") | 
| 199 |  |  |  |  |  |  | unless (ref($parent = $class->new($parent))); | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 0 |  |  |  |  | 0 | $parent{ident $self} = $parent; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 0 |  |  |  |  | 0 | $parent; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | ############################################################################### | 
| 208 |  |  |  |  |  |  | # | 
| 209 |  |  |  |  |  |  | #   Sub Name:       set_sub_categories | 
| 210 |  |  |  |  |  |  | # | 
| 211 |  |  |  |  |  |  | #   Description:    Set the list of Categories objects for this instance. The | 
| 212 |  |  |  |  |  |  | #                   list will initially be a list of IDs, taken from the | 
| 213 |  |  |  |  |  |  | #                   attributes of the XML. Only upon read-access (via | 
| 214 |  |  |  |  |  |  | #                   get_sub_categories) will the list be turned into real | 
| 215 |  |  |  |  |  |  | #                   objects. | 
| 216 |  |  |  |  |  |  | # | 
| 217 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 218 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 219 |  |  |  |  |  |  | #                   $list     in      ref       List-reference of category data | 
| 220 |  |  |  |  |  |  | # | 
| 221 |  |  |  |  |  |  | #   Globals:        %sub_categories | 
| 222 |  |  |  |  |  |  | # | 
| 223 |  |  |  |  |  |  | #   Returns:        Success:    $self | 
| 224 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 225 |  |  |  |  |  |  | # | 
| 226 |  |  |  |  |  |  | ############################################################################### | 
| 227 |  |  |  |  |  |  | sub set_sub_categories | 
| 228 |  |  |  |  |  |  | { | 
| 229 | 0 |  |  | 0 | 1 | 0 | my ($self, $list) = @_; | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 0 | 0 |  |  |  | 0 | throw Error::Simple("Argument to 'set_sub_categories' must be a list " . | 
| 232 |  |  |  |  |  |  | "reference") | 
| 233 |  |  |  |  |  |  | unless (ref($list) eq 'ARRAY'); | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | # Make a copy of the list | 
| 236 | 0 |  |  |  |  | 0 | $sub_categories{ident $self} = [ @$list ]; | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 0 |  |  |  |  | 0 | $self; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | ############################################################################### | 
| 242 |  |  |  |  |  |  | # | 
| 243 |  |  |  |  |  |  | #   Sub Name:       get_sub_categories | 
| 244 |  |  |  |  |  |  | # | 
| 245 |  |  |  |  |  |  | #   Description:    Return a list-reference of the sub-Categories. If this is | 
| 246 |  |  |  |  |  |  | #                   the first such request, then the category values are going | 
| 247 |  |  |  |  |  |  | #                   to be scalars, not objects, and must be converted to | 
| 248 |  |  |  |  |  |  | #                   objects before being returned. | 
| 249 |  |  |  |  |  |  | # | 
| 250 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 251 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 252 |  |  |  |  |  |  | # | 
| 253 |  |  |  |  |  |  | #   Globals:        %sub_categories | 
| 254 |  |  |  |  |  |  | # | 
| 255 |  |  |  |  |  |  | #   Returns:        Success:    list-reference of data | 
| 256 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 257 |  |  |  |  |  |  | # | 
| 258 |  |  |  |  |  |  | ############################################################################### | 
| 259 |  |  |  |  |  |  | sub get_sub_categories | 
| 260 |  |  |  |  |  |  | { | 
| 261 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 0 |  |  |  |  | 0 | my $sub_categories = $sub_categories{ident $self}; | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | # If any element is not a reference, we need to transform the list | 
| 266 | 0 | 0 |  |  |  | 0 | if (grep(! ref($_), @$sub_categories)) | 
| 267 |  |  |  |  |  |  | { | 
| 268 | 0 |  |  |  |  | 0 | my $class = $self->class_for_type('Categories'); | 
| 269 |  |  |  |  |  |  | # Make sure it's loaded | 
| 270 | 0 |  |  |  |  | 0 | eval "require $class;"; | 
| 271 | 0 |  |  |  |  | 0 | my $cat_id; | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 0 |  |  |  |  | 0 | for (0 .. $#$sub_categories) | 
| 274 |  |  |  |  |  |  | { | 
| 275 | 0 | 0 |  |  |  | 0 | unless (ref($cat_id = $sub_categories->[$_])) | 
| 276 |  |  |  |  |  |  | { | 
| 277 | 0 | 0 |  |  |  | 0 | throw Error::Simple("No category found for ID '$cat_id'") | 
| 278 |  |  |  |  |  |  | unless ref($sub_categories->[$_] = | 
| 279 |  |  |  |  |  |  | $class->find({ id => $cat_id })); | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | # Make a copy, so the real reference doesn't get altered | 
| 285 | 0 |  |  |  |  | 0 | [ @$sub_categories ]; | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | ############################################################################### | 
| 289 |  |  |  |  |  |  | # | 
| 290 |  |  |  |  |  |  | #   Sub Name:       find | 
| 291 |  |  |  |  |  |  | # | 
| 292 |  |  |  |  |  |  | #   Description:    Find a single record using the passed-in search criteria. | 
| 293 |  |  |  |  |  |  | #                   Most of the work is done by the super-class: this method | 
| 294 |  |  |  |  |  |  | #                   turns a single-argument call into a proper hashref, and/or | 
| 295 |  |  |  |  |  |  | #                   turns user-supplied arguments into those recognized by the | 
| 296 |  |  |  |  |  |  | #                   API. | 
| 297 |  |  |  |  |  |  | # | 
| 298 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 299 |  |  |  |  |  |  | #                   $self     in      ref       Object | 
| 300 |  |  |  |  |  |  | #                   $args     in      variable  See text | 
| 301 |  |  |  |  |  |  | # | 
| 302 |  |  |  |  |  |  | #   Returns:        Success:    result from SUPER::find | 
| 303 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 304 |  |  |  |  |  |  | # | 
| 305 |  |  |  |  |  |  | ############################################################################### | 
| 306 |  |  |  |  |  |  | sub find | 
| 307 |  |  |  |  |  |  | { | 
| 308 | 1 |  |  | 1 | 1 | 3 | my ($self, $args) = @_; | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | # First, see if we were passed a single scalar for an argument. If so, it | 
| 311 |  |  |  |  |  |  | # needs to become the id argument | 
| 312 | 1 | 50 |  |  |  | 6 | $args = { category_id => $args } unless (ref $args); | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 1 |  |  |  |  | 10 | $self->SUPER::find($args); | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | ############################################################################### | 
| 318 |  |  |  |  |  |  | # | 
| 319 |  |  |  |  |  |  | #   Sub Name:       normalize_args | 
| 320 |  |  |  |  |  |  | # | 
| 321 |  |  |  |  |  |  | #   Description:    Normalize the contents of the $args hash reference, turning | 
| 322 |  |  |  |  |  |  | #                   the user-visible (and user-friendlier) arguments into the | 
| 323 |  |  |  |  |  |  | #                   arguments that the API expects. | 
| 324 |  |  |  |  |  |  | # | 
| 325 |  |  |  |  |  |  | #                   Also adds some "results" values, to tailor the returned | 
| 326 |  |  |  |  |  |  | #                   content. | 
| 327 |  |  |  |  |  |  | # | 
| 328 |  |  |  |  |  |  | #   Arguments:      NAME      IN/OUT  TYPE      DESCRIPTION | 
| 329 |  |  |  |  |  |  | #                   $class    in      scalar    Object ref or class name | 
| 330 |  |  |  |  |  |  | #                   $args     in      hashref   Reference to the arguments hash | 
| 331 |  |  |  |  |  |  | # | 
| 332 |  |  |  |  |  |  | #   Returns:        Success:    $args (changed) | 
| 333 |  |  |  |  |  |  | #                   Failure:    throws Error::Simple | 
| 334 |  |  |  |  |  |  | # | 
| 335 |  |  |  |  |  |  | ############################################################################### | 
| 336 |  |  |  |  |  |  | sub normalize_args | 
| 337 |  |  |  |  |  |  | { | 
| 338 | 1 |  |  | 1 | 1 | 4 | my ($class, $args) = @_; | 
| 339 |  |  |  |  |  |  |  | 
| 340 | 1 |  |  |  |  | 3 | my ($key, $value, @keys, $count, $results, %seen); | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | # Turn the collection of arguments into a set that the isbndb.com API can | 
| 343 |  |  |  |  |  |  | # use. Each key/value pair has to become a pair of the form "indexX" and | 
| 344 |  |  |  |  |  |  | # "valueX". Some keys, like author and publisher, have to be handled with | 
| 345 |  |  |  |  |  |  | # more attention. | 
| 346 | 1 |  |  |  |  | 4 | @keys = keys %$args; | 
| 347 | 1 |  |  |  |  | 3 | $count = 0; # Used to gradually increment the "indexX" and "valueX" keys | 
| 348 | 1 |  |  |  |  | 2 | foreach $key (@keys) | 
| 349 |  |  |  |  |  |  | { | 
| 350 |  |  |  |  |  |  | # If we see "api_key", it means that WebService::ISBNDB::API::search | 
| 351 |  |  |  |  |  |  | # curried it into the arglist due to the type-level search being | 
| 352 |  |  |  |  |  |  | # called as a static method. | 
| 353 | 1 | 50 |  |  |  | 4 | next if $key eq 'api_key'; | 
| 354 | 1 |  |  |  |  | 4 | $value = $args->{$key}; | 
| 355 | 1 |  |  |  |  | 2 | delete $args->{$key}; | 
| 356 | 1 |  |  |  |  | 2 | $count++; | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | # A key of "id" needs to be translated as "subject_id" | 
| 359 | 1 | 50 |  |  |  | 3 | if ($key eq 'id') | 
| 360 |  |  |  |  |  |  | { | 
| 361 | 0 |  |  |  |  | 0 | $args->{"index$count"} = 'category_id'; | 
| 362 | 0 |  |  |  |  | 0 | $args->{"value$count"} = $value; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 0 |  |  |  |  | 0 | next; | 
| 365 |  |  |  |  |  |  | } | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | # A key of "parent" should become "parent_id". If it is a plain | 
| 368 |  |  |  |  |  |  | # scalar, the value carries over. If it is a Categories object, use | 
| 369 |  |  |  |  |  |  | # the "id" method. | 
| 370 | 1 | 50 |  |  |  | 6 | if ($key eq 'parent') | 
| 371 |  |  |  |  |  |  | { | 
| 372 | 0 |  |  |  |  | 0 | $args->{"index$count"} = 'parent_id'; | 
| 373 | 0 | 0 | 0 |  |  | 0 | $args->{"value$count"} = | 
| 374 |  |  |  |  |  |  | (ref $value and | 
| 375 |  |  |  |  |  |  | $value->isa('WebService::ISBNDB::API::Categories')) ? | 
| 376 |  |  |  |  |  |  | $value->id : $value; | 
| 377 |  |  |  |  |  |  |  | 
| 378 | 0 |  |  |  |  | 0 | next; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | # These are the only other allowed search-key(s) | 
| 382 | 1 | 50 |  |  |  | 10 | if ($key =~ /^(:?name|category_id|parent_id)$/) | 
| 383 |  |  |  |  |  |  | { | 
| 384 | 1 |  |  |  |  | 5 | $args->{"index$count"} = $key; | 
| 385 | 1 |  |  |  |  | 3 | $args->{"value$count"} = $value; | 
| 386 |  |  |  |  |  |  |  | 
| 387 | 1 |  |  |  |  | 3 | next; | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  |  | 
| 390 | 0 |  |  |  |  | 0 | throw Error::Simple("'$key' is not a valid search-key for publishers"); | 
| 391 |  |  |  |  |  |  | } | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | # Add the "results" values that we want | 
| 394 | 1 |  |  |  |  | 5 | $args->{results} = [ qw(details subcategories) ]; | 
| 395 |  |  |  |  |  |  |  | 
| 396 | 1 |  |  |  |  | 11 | $args; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | 1; | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | =pod | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | =head1 NAME | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | WebService::ISBNDB::API::Categories - Data class for category information | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | use WebService::ISBNDB::API::Categories; | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | $ray_authors = WebService::ISBNDB::API::Categories-> | 
| 412 |  |  |  |  |  |  | search({ name => 'alphabetically.authors.r.a.y' }); | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | The B class extends the | 
| 417 |  |  |  |  |  |  | B class to add attributes specific to the data | 
| 418 |  |  |  |  |  |  | B provides on categories. | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | =head1 METHODS | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | The following methods are specific to this class, or overridden from the | 
| 423 |  |  |  |  |  |  | super-class. | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | =head2 Constructor | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | The constructor for this class may take a single scalar argument in lieu of a | 
| 428 |  |  |  |  |  |  | hash reference: | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | =over 4 | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | =item new($CATEGORY_ID|$ARGS) | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | This constructs a new object and returns a referent to it. If the parameter | 
| 435 |  |  |  |  |  |  | passed is a hash reference, it is handled as normal, per B | 
| 436 |  |  |  |  |  |  | mechanics. If the value is a scalar, it is assumed to be the category's ID | 
| 437 |  |  |  |  |  |  | within the system, and is looked up by that. | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | If the argument is the hash-reference form, then a new object is always | 
| 440 |  |  |  |  |  |  | constructed; to perform searches see the search() and find() methods. Thus, | 
| 441 |  |  |  |  |  |  | the following two lines are in fact different: | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | $book = WebService::ISBNDB::API::Categories-> | 
| 444 |  |  |  |  |  |  | new({ id => "arts.music" }); | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | $book = WebService::ISBNDB::API::Categories->new('arts.music'); | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | The first creates a new object that has only the C attribute set. The | 
| 449 |  |  |  |  |  |  | second returns a new object that represents the category named C, | 
| 450 |  |  |  |  |  |  | with all data present. | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | =back | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | The class also defines: | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | =over 4 | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | =item copy($TARGET) | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | Copies the target object into the calling object. All attributes (including | 
| 461 |  |  |  |  |  |  | the ID) are copied. This method is marked "CUMULATIVE" (see L), | 
| 462 |  |  |  |  |  |  | and any sub-class of this class should provide their own copy() and also mark | 
| 463 |  |  |  |  |  |  | it "CUMULATIVE", to ensure that all attributes at all levels are copied. | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | =back | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | See the copy() method in L. | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | =head2 Accessors | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | The following attributes are used to maintain the content of a category | 
| 472 |  |  |  |  |  |  | object: | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | =over 4 | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | =item id | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | The unique ID within the B system for this category. | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | =item name | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | The name of the category. | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =item parent | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | The parent category, if there is one, that this category falls under. | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | =item summary | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | A brief summary of the category, if available. | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | =item depth | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | The depth of the category in the hierarchy. Top-level categories are at a | 
| 495 |  |  |  |  |  |  | depth of 0. C, for example, is at a depth of 3. | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | =item element_count | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | Not documented in the B API; appears be the number of books in | 
| 500 |  |  |  |  |  |  | the category and all of its sub-categories. | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | =item sub_categories | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | A list of category objects for the sub-categories that fall below this one. | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | =back | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | The following accessors are provided to manage these attributes: | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | =over 4 | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | =item get_id | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | Return the category ID. | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | =item set_id($ID) | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | Sets the category ID. This method is restricted to this class, and cannot be | 
| 519 |  |  |  |  |  |  | called outside of it. In general, you shouldn't need to set the ID after the | 
| 520 |  |  |  |  |  |  | object is created, since B is a read-only source. | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | =item get_name | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | Return the category's name. | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | =item set_name($NAME) | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | Set the name to the value in C<$NAME>. | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | =item get_parent | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | Return the B object that represents this | 
| 533 |  |  |  |  |  |  | category's parent. If this is a top-level category, then the method returns | 
| 534 |  |  |  |  |  |  | C. | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | =item set_parent($PARENT) | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | Set the category's parent to the value in C<$PARENT>. This may be an object, | 
| 539 |  |  |  |  |  |  | or it may be a category ID. If the value is not an object, the next call to | 
| 540 |  |  |  |  |  |  | get_parent() will attempt to convert it to one by calling the service. | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | =item get_summary | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | Get the category summary. | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | =item set_summary($SUMMARY) | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | Set the category summary to C<$SUMMARY>. | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | =item get_depth | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | Get the category depth. | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | =item set_depth($DEPTH) | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | Set the category depth to C<$DEPTH>. | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | =item get_element_count | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | Get the count of elements. | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | =item set_element_count($COUNT) | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | Set the element count. | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | =item get_sub_categories | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | Return a list-reference of the sub-categories for the category. Each element | 
| 569 |  |  |  |  |  |  | of the list will be an instance of B. | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | =item set_sub_categories($CATEGORIES) | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | Set the sub-categories to the list-reference given in C<$CATEGORIES>. When the | 
| 574 |  |  |  |  |  |  | category object is first created from the XML data, this list is populated | 
| 575 |  |  |  |  |  |  | with the IDs of the sub-categories. They are not converted to objects until | 
| 576 |  |  |  |  |  |  | requested (via get_sub_categories()) by the user. | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | =back | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | =head2 Utility Methods | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | Besides the constructor and the accessors, the following methods are provided | 
| 583 |  |  |  |  |  |  | for utility: | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | =over 4 | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | =item find($ARG|$ARGS) | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | This is a specialization of find() from the parent class. It allows the | 
| 590 |  |  |  |  |  |  | argument passed in to be a scalar in place of the usual hash reference. If the | 
| 591 |  |  |  |  |  |  | value is a scalar, it is searched as though it were the ID. If the value is a | 
| 592 |  |  |  |  |  |  | hash reference, it is passed to the super-class method. | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | =item normalize_args($ARGS) | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | This method maps the user-visible arguments as defined for find() and search() | 
| 597 |  |  |  |  |  |  | into the actual arguments that must be passed to the service itself. In | 
| 598 |  |  |  |  |  |  | addition, some arguments are added to the request to make the service return | 
| 599 |  |  |  |  |  |  | extra data used for retrieving categories, location, etc. The | 
| 600 |  |  |  |  |  |  | method changes C<$ARGS> in place, and also returns C<$ARGS> as the value from | 
| 601 |  |  |  |  |  |  | the method. | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | =back | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | See the next section for an explanation of the available keys for searches. | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | =head1 SEARCHING | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | Both find() and search() allow the user to look up data in the B | 
| 610 |  |  |  |  |  |  | database. The allowable search fields are limited to a certain set, however. | 
| 611 |  |  |  |  |  |  | When either of find() or search() are called, the argument to the method | 
| 612 |  |  |  |  |  |  | should be a hash reference of key/value pairs to be passed as arguments for | 
| 613 |  |  |  |  |  |  | the search (the exception being that find() can accept a single string, which | 
| 614 |  |  |  |  |  |  | has special meaning as detailed earlier). | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | Searches in the text fields are done in a case-insensitive manner. | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | The available search keys are: | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | =over 4 | 
| 621 |  |  |  |  |  |  |  | 
| 622 |  |  |  |  |  |  | =item name | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | The value should be a text string. The search returns categories whose name | 
| 625 |  |  |  |  |  |  | matches the string. | 
| 626 |  |  |  |  |  |  |  | 
| 627 |  |  |  |  |  |  | =item id|category_id | 
| 628 |  |  |  |  |  |  |  | 
| 629 |  |  |  |  |  |  | The value should be a text string. The search returns the category whose ID | 
| 630 |  |  |  |  |  |  | in the system matches the value. | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | =item parent|parent_id | 
| 633 |  |  |  |  |  |  |  | 
| 634 |  |  |  |  |  |  | You can also search by the parent. The search-key C will accept either | 
| 635 |  |  |  |  |  |  | a string (taken as the ID) or a Categories object, in which case the ID is | 
| 636 |  |  |  |  |  |  | derived from it. If the key used is C, the value is assumed to be | 
| 637 |  |  |  |  |  |  | the ID. | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | =back | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | Note that the names above may not be the same as the corresponding parameters | 
| 642 |  |  |  |  |  |  | to the service. The names are chosen to match the related attributes as | 
| 643 |  |  |  |  |  |  | closely as possible, for ease of understanding. | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | =head1 EXAMPLES | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | Get the record for the ID C: | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | $science = WebService::ISBNDB::API::Categories->find('science'); | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | Find all category records that are sub-categories of C: | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | $science2 = WebService::ISBNDB::API::Categories-> | 
| 654 |  |  |  |  |  |  | search({ parent => $science }); | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | =head1 CAVEATS | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | The data returned by this class is only as accurate as the data retrieved from | 
| 659 |  |  |  |  |  |  | B. | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | The list of results from calling search() is currently limited to 10 items. | 
| 662 |  |  |  |  |  |  | This limit will be removed in an upcoming release, when iterators are | 
| 663 |  |  |  |  |  |  | implemented. | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | L | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | =head1 AUTHOR | 
| 670 |  |  |  |  |  |  |  | 
| 671 |  |  |  |  |  |  | Randy J. Ray Erjray@blackperl.comE | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | =head1 LICENSE | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | This module and the code within are | 
| 676 |  |  |  |  |  |  | released under the terms of the Artistic License 2.0 | 
| 677 |  |  |  |  |  |  | (http://www.opensource.org/licenses/artistic-license-2.0.php). This | 
| 678 |  |  |  |  |  |  | code may be redistributed under either the Artistic License or the GNU | 
| 679 |  |  |  |  |  |  | Lesser General Public License (LGPL) version 2.1 | 
| 680 |  |  |  |  |  |  | (http://www.opensource.org/licenses/lgpl-license.php). | 
| 681 |  |  |  |  |  |  |  | 
| 682 |  |  |  |  |  |  | =cut |