| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Data::Validate::XSD; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 4 |  |  | 4 |  | 141094 | use strict; | 
|  | 4 |  |  |  |  | 11 |  | 
|  | 4 |  |  |  |  | 306 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | =head1 NAME | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | Data::Validate::XSD - Validate complex structures by definition | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | use Data::Validate::XSD; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | my $validator = Data::Validate::XSD->new( \%definition ); | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | $errors = $validator->validate( \%data ); | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | warn Dumper($errors) if $errors; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | Based on xsd and xml validation, this is an attempt to provide those functions | 
| 22 |  |  |  |  |  |  | without either xml or the hidous errors given out by modules like XPath. | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | The idea behind the error reporting is that the errors can reflect the structure | 
| 25 |  |  |  |  |  |  | of the original structure replacing each variable with an error code and message. | 
| 26 |  |  |  |  |  |  | It is possible to work out a one dimention error reporting scheme too which I may | 
| 27 |  |  |  |  |  |  | work on next. | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | =head1 INVITATION | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | If you find an example where the W3C definitions and this module differ then | 
| 32 |  |  |  |  |  |  | please email the author and a new version with fixes can be released. | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | If you find there is a certain type that your always using then let me know | 
| 35 |  |  |  |  |  |  | I can consider adding the type to the default set and make the module more useful. | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | =head1 EXAMPLES | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | =head2 Definitions | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | A definition is a hash containing information like an xml node containing children. | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | An example definition for registering a user on a website: | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | $def = { | 
| 47 |  |  |  |  |  |  | root => [ | 
| 48 |  |  |  |  |  |  | { name => 'input', type => 'newuser' }, | 
| 49 |  |  |  |  |  |  | { name => 'foo',   type => 'string'  }, | 
| 50 |  |  |  |  |  |  | ], | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | simpleTypes => [ | 
| 53 |  |  |  |  |  |  | confirm  => { base => 'id',   match => '/input/password' }, | 
| 54 |  |  |  |  |  |  | rname    => { base => 'name', minLength => 1 }, | 
| 55 |  |  |  |  |  |  | password => { base => 'id',   minLength => 6 }, | 
| 56 |  |  |  |  |  |  | ], | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | complexTypes => { | 
| 59 |  |  |  |  |  |  | newuser => [ | 
| 60 |  |  |  |  |  |  | { name => 'username',     type => 'token'                                 }, | 
| 61 |  |  |  |  |  |  | { name => 'password',     type => 'password'                              }, | 
| 62 |  |  |  |  |  |  | { name => 'confirm',      type => 'confirm'                               }, | 
| 63 |  |  |  |  |  |  | { name => 'firstName',    type => 'rname'                                 }, | 
| 64 |  |  |  |  |  |  | { name => 'familyName',   type => 'name',  minOccurs => 0                 }, | 
| 65 |  |  |  |  |  |  | { name => 'nickName',     type => 'name',  minOccurs => 0                 }, | 
| 66 |  |  |  |  |  |  | { name => 'emailAddress', type => 'email', minOccurs => 1, maxOccurs => 3 }, | 
| 67 |  |  |  |  |  |  | [ | 
| 68 |  |  |  |  |  |  | { name => 'aim',    type => 'index'  }, | 
| 69 |  |  |  |  |  |  | { name => 'msn',    type => 'email'  }, | 
| 70 |  |  |  |  |  |  | { name => 'jabber', type => 'email'  }, | 
| 71 |  |  |  |  |  |  | { name => 'irc',    type => 'string' }, | 
| 72 |  |  |  |  |  |  | ] | 
| 73 |  |  |  |  |  |  | ], | 
| 74 |  |  |  |  |  |  | }, | 
| 75 |  |  |  |  |  |  | }; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | =head2 Data | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | And this is an example of the data that would validate against it: | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | $data = { | 
| 84 |  |  |  |  |  |  | input => { | 
| 85 |  |  |  |  |  |  | username     => 'abcdef', | 
| 86 |  |  |  |  |  |  | password     => '1234567', | 
| 87 |  |  |  |  |  |  | confirm      => '1234567', | 
| 88 |  |  |  |  |  |  | firstName    => 'test', | 
| 89 |  |  |  |  |  |  | familyName   => 'user', | 
| 90 |  |  |  |  |  |  | nickName     => 'foobar', | 
| 91 |  |  |  |  |  |  | emailAddress => [ 'foo@bar.com', 'some@other.or', 'great@nice.con' ], | 
| 92 |  |  |  |  |  |  | msn          => 'foo@msn.com', | 
| 93 |  |  |  |  |  |  | }, | 
| 94 |  |  |  |  |  |  | foo => 'extra content', | 
| 95 |  |  |  |  |  |  | }; | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | We are asking for a username, a password typed twice, some real names, a nick name, | 
| 99 |  |  |  |  |  |  | between 1 and 3 email addresses and at least one instant message account, foo is an | 
| 100 |  |  |  |  |  |  | extra string of information to show that the level is arbitary. bellow the definition | 
| 101 |  |  |  |  |  |  | and all options are explained. | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | =head2 Results | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | The first result you get is a structure the second is a boolean, the boolean explains the total stuctures pass or fail status. | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | The structure that is returned is almost a mirror structure of the input: | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | $errors = { | 
| 110 |  |  |  |  |  |  | input => { | 
| 111 |  |  |  |  |  |  | username     => 0, | 
| 112 |  |  |  |  |  |  | password     => 0, | 
| 113 |  |  |  |  |  |  | confirm      => 0, | 
| 114 |  |  |  |  |  |  | firstName    => 0, | 
| 115 |  |  |  |  |  |  | familyName   => 0, | 
| 116 |  |  |  |  |  |  | nickName     => 0, | 
| 117 |  |  |  |  |  |  | emailAddress => 0, | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  | }, | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | =head1 DETAILED DEFINITION | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | =head2 Definition Root | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | root         - The very first level of all structures, it should contain the first | 
| 126 |  |  |  |  |  |  | level complex type (see below). The data by default is a hash since | 
| 127 |  |  |  |  |  |  | all xml have at least one level of xml tags names. | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | import       - A list of file names, local to perl that should be loaded to include | 
| 130 |  |  |  |  |  |  | further and shared simple and complex types. Supported formats are | 
| 131 |  |  |  |  |  |  | "perl code", xml and yml. | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | simpleTypes  - A hash reference containing each simple definition which tests a | 
| 134 |  |  |  |  |  |  | scalar type (see below for format of each definition) | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | complexTypes - A hash reference containing each complex definition which tests a | 
| 138 |  |  |  |  |  |  | structure (see below for definition). | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | =head2 Simple Types | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | A simple type is a definition which will validate data directly, it will never validate | 
| 144 |  |  |  |  |  |  | arrays, hashes or any future wacky structural types. In perl parlance it will only validate | 
| 145 |  |  |  |  |  |  | SCALAR types. These options should match the w3c simple types definition: | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | base           - The name of another simple type to first test the value against. | 
| 148 |  |  |  |  |  |  | fixed          - The value should match this exactly. | 
| 149 |  |  |  |  |  |  | pattern        - Should be a regular expresion reference which matchs the value i.e qr/\w/ | 
| 150 |  |  |  |  |  |  | minLength      - The minimum length of a string value. | 
| 151 |  |  |  |  |  |  | maxLength      - The maximum length of a string value. | 
| 152 |  |  |  |  |  |  | match          - An XPath link to another data node it should match. | 
| 153 |  |  |  |  |  |  | notMatch       - An XPath link to another data node it should NOT match. | 
| 154 |  |  |  |  |  |  | enumeration    - An array reference of possible values of which value should be one. | 
| 155 |  |  |  |  |  |  | custom         - Should contain a CODE reference which will be called upon to validate the value. | 
| 156 |  |  |  |  |  |  | minInclusive   - The minimum value of a number value inclusive, i.e greater than or eq to (>=). | 
| 157 |  |  |  |  |  |  | maxInclusive   - The maximum value of a number value inclusive, i.e less than of eq to (<=). | 
| 158 |  |  |  |  |  |  | minExclusive   - The minimum value of a number value exlusive, i.e more than (>). | 
| 159 |  |  |  |  |  |  | maxExclusive   - The maximum value of a number value exlusive, i.e less than (<). | 
| 160 |  |  |  |  |  |  | fractionDigits - The maximum number of digits on a fractional number. | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =head2 Complex Types | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | A complex type is a definition which will validate a hash reference, the very first structure, | 
| 165 |  |  |  |  |  |  | 'root' is a complex definition and follows the same syntax as all complex types. each complex | 
| 166 |  |  |  |  |  |  | type is a list of data which should all occur in the hash, when a list entry is a hash; it | 
| 167 |  |  |  |  |  |  | equates to one named entry in the hash data and has the following options: | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | name      - Required name of the entry in the hash data. | 
| 170 |  |  |  |  |  |  | minOccurs - The minimum number of the named that this data should have in it. | 
| 171 |  |  |  |  |  |  | maxOccurs - The maximum number of the named that this data should have in it. | 
| 172 |  |  |  |  |  |  | type      - The type definition which validates the contents of the data. | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | Where the list entry is an array, it will toggle the combine mode and allow further list entries | 
| 175 |  |  |  |  |  |  | With in it; this allows for parts of the sturcture to be optional only if different parts of the | 
| 176 |  |  |  |  |  |  | stucture exist. | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | =head1 INBUILT TYPES | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | By default these types are available to all definitions as base types. | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | string           - /^.*$/ | 
| 183 |  |  |  |  |  |  | integer          - /^[\-]{0,1}\d+$/ | 
| 184 |  |  |  |  |  |  | index            - /^\d+$/ | 
| 185 |  |  |  |  |  |  | double           - /^[0-9\-\.]*$/ | 
| 186 |  |  |  |  |  |  | token            - /^\w+$/ | 
| 187 |  |  |  |  |  |  | boolean          - /^1|0|true|false$/ | 
| 188 |  |  |  |  |  |  | email            - /^.+@.+\..+$/ | 
| 189 |  |  |  |  |  |  | date             - /^\d\d\d\d-\d\d-\d\d$/ + datetime | 
| 190 |  |  |  |  |  |  | 'time'           - /^\d\d:\d\d$/ + datetime | 
| 191 |  |  |  |  |  |  | datetime         - /^(\d\d\d\d-\d\d-\d\d)?[T ]?(\d\d:\d\d)?$/ + valid_date method | 
| 192 |  |  |  |  |  |  | percentage       - minInclusive == 0 + maxInclusive == 100 + double | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | =cut | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 4 |  |  | 4 |  | 26 | use Carp; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 409 |  | 
| 197 | 4 |  |  | 4 |  | 25 | use Scalar::Util qw/looks_like_number/; | 
|  | 4 |  |  |  |  | 11 |  | 
|  | 4 |  |  |  |  | 403 |  | 
| 198 | 4 |  |  | 4 |  | 3902 | use Date::Parse qw/str2time/; | 
|  | 4 |  |  |  |  | 40145 |  | 
|  | 4 |  |  |  |  | 13554 |  | 
| 199 |  |  |  |  |  |  | our $VERSION = "1.05"; | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | # Error codes | 
| 202 |  |  |  |  |  |  | my $NOERROR             = 0x00; | 
| 203 |  |  |  |  |  |  | my $INVALID_TYPE        = 0x01; | 
| 204 |  |  |  |  |  |  | my $INVALID_PATTERN     = 0x02; | 
| 205 |  |  |  |  |  |  | my $INVALID_MINLENGTH   = 0x03; | 
| 206 |  |  |  |  |  |  | my $INVALID_MAXLENGTH   = 0x04; | 
| 207 |  |  |  |  |  |  | my $INVALID_MATCH       = 0x05; | 
| 208 |  |  |  |  |  |  | my $INVALID_VALUE       = 0x06; | 
| 209 |  |  |  |  |  |  | my $INVALID_NODE        = 0x07; | 
| 210 |  |  |  |  |  |  | my $INVALID_ENUMERATION = 0x08; | 
| 211 |  |  |  |  |  |  | my $INVALID_MIN_RANGE   = 0x09; | 
| 212 |  |  |  |  |  |  | my $INVALID_MAX_RANGE   = 0x0A; | 
| 213 |  |  |  |  |  |  | my $INVALID_NUMBER      = 0x0B; | 
| 214 |  |  |  |  |  |  | my $INVALID_COMPLEX     = 0x0C; | 
| 215 |  |  |  |  |  |  | my $INVALID_EXIST       = 0x0D; | 
| 216 |  |  |  |  |  |  | my $INVALID_MIN_OCCURS  = 0x0E; | 
| 217 |  |  |  |  |  |  | my $INVALID_MAX_OCCURS  = 0x0F; | 
| 218 |  |  |  |  |  |  | my $INVALID_CUSTOM      = 0x10; | 
| 219 |  |  |  |  |  |  | my $CRITICAL            = 0x11; | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | my @errors = ( | 
| 222 |  |  |  |  |  |  | 0, | 
| 223 |  |  |  |  |  |  | 'Invalid Node Type', | 
| 224 |  |  |  |  |  |  | 'Invalid Pattern: Regex Pattern failed', | 
| 225 |  |  |  |  |  |  | 'Invalid MinLength: Not enough nodes present', | 
| 226 |  |  |  |  |  |  | 'Invalid MaxLength: Too many nodes present', | 
| 227 |  |  |  |  |  |  | 'Invalid Match: Node to Node match failed', | 
| 228 |  |  |  |  |  |  | 'Invalid Value, Fixed string did not match', | 
| 229 |  |  |  |  |  |  | 'Invalid Node: Required data does not exist for this node', | 
| 230 |  |  |  |  |  |  | 'Invalid Enum: Data not equal to any values supplied', | 
| 231 |  |  |  |  |  |  | 'Invalid Number: Less than allowable range', | 
| 232 |  |  |  |  |  |  | 'Invalid Number: Greater than allowable range', | 
| 233 |  |  |  |  |  |  | 'Invalid Number: Data is not a real number', | 
| 234 |  |  |  |  |  |  | 'Invalid Complex Type: Failed to validate Complex Type', | 
| 235 |  |  |  |  |  |  | 'Invalid Exists: Data didn\'t exist, and should.', | 
| 236 |  |  |  |  |  |  | 'Invalid Occurs: Minium number of occurances not met', | 
| 237 |  |  |  |  |  |  | 'Invalid Occurs: Maxium number of occurances exceeded', | 
| 238 |  |  |  |  |  |  | 'Invalid Custom Filter: Method returned false', | 
| 239 |  |  |  |  |  |  | 'Critical Problem:', | 
| 240 |  |  |  |  |  |  | ); | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | my %complex_types = (); | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | my %simple_types = ( | 
| 245 |  |  |  |  |  |  | string     => { pattern => qr/.*/ }, | 
| 246 |  |  |  |  |  |  | integer    => { pattern => qr/[\-]{0,1}\d+/ }, | 
| 247 |  |  |  |  |  |  | 'index'    => { pattern => qr/\d+/ }, | 
| 248 |  |  |  |  |  |  | double     => { pattern => qr/[0-9\-\.]*/ }, | 
| 249 |  |  |  |  |  |  | token      => { base    => 'string', pattern => qr/\w+/ }, | 
| 250 |  |  |  |  |  |  | boolean    => { pattern => qr/1|0|true|false/ }, | 
| 251 |  |  |  |  |  |  | email      => { pattern => qr/.+@.+\..+/ }, | 
| 252 |  |  |  |  |  |  | date       => { pattern => qr/\d\d\d\d-\d\d-\d\d/, base => 'datetime' }, | 
| 253 |  |  |  |  |  |  | 'time'     => { pattern => qr/\d\d:\d\d/,          base => 'datetime' }, | 
| 254 |  |  |  |  |  |  | datetime   => { pattern => qr/(\d\d\d\d-\d\d-\d\d)?[T ]?(\d\d:\d\d)?/, custom => sub { _test_datetime(@_) } }, | 
| 255 |  |  |  |  |  |  | percentage => { base => 'double', minInclusive => 0, maxInclusive => 100 }, | 
| 256 |  |  |  |  |  |  | ); | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | =head1 METHODS | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =head2 $class->new( $definition ) | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | Create a new validation object, debug will cause | 
| 263 |  |  |  |  |  |  | All error codes to be replaced by error strings. | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =cut | 
| 266 |  |  |  |  |  |  | sub new { | 
| 267 | 3 |  |  | 3 | 1 | 175 | my ($class, $definition) = @_; | 
| 268 |  |  |  |  |  |  |  | 
| 269 | 3 |  |  |  |  | 14 | my $self = bless { strict => 1 }, $class; | 
| 270 |  |  |  |  |  |  |  | 
| 271 | 3 |  |  |  |  | 17 | $self->setDefinition( $definition ); | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 3 |  |  |  |  | 50 | return $self; | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | =head2 $class->newFromFile( $path, $filename, $debug ) | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | Create a new definition from a dumped perl file. | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | =cut | 
| 281 |  |  |  |  |  |  | sub newFromFile { | 
| 282 | 2 |  |  | 2 | 1 | 404 | my ($class, $filename, @a) = @_; | 
| 283 |  |  |  |  |  |  |  | 
| 284 | 2 | 50 |  |  |  | 40 | if(-f $filename) { | 
| 285 | 2 |  |  |  |  | 9 | my $definition = $class->_load_file( $filename, 1 ); | 
| 286 | 1 |  |  |  |  | 5 | return $class->new( $definition, @a ); | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 0 |  |  |  |  | 0 | croak("Validation Error: Could not find Validate Configuration '$filename'"); | 
| 289 |  |  |  |  |  |  | } | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | =head2 I<$validator>->validate( $data ) | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | Validate a set of data against this validator. | 
| 294 |  |  |  |  |  |  | Returns an $errors structure or 0 if there were no errors. | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | =cut | 
| 297 |  |  |  |  |  |  | sub validate { | 
| 298 | 6 |  |  | 6 | 1 | 2736 | my ($self, $data) = @_; | 
| 299 | 6 |  |  |  |  | 13 | my $def = $self->{'definition'}; | 
| 300 |  |  |  |  |  |  |  | 
| 301 | 6 | 50 | 33 |  |  | 46 | if(defined($def->{'root'}) and defined($data)) { | 
| 302 | 6 |  |  |  |  | 25 | return $self->_validate_elements( definition => $def->{'root'}, data => $data ); | 
| 303 |  |  |  |  |  |  | } else { | 
| 304 | 0 | 0 |  |  |  | 0 | croak("VAL Error: No root document definition") if not defined($def->{'root'}); | 
| 305 | 0 | 0 |  |  |  | 0 | croak("VAL Error: No data provided")            if not defined($data); | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | =head2 I<$validator>->validateFile( $filename ) | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | Validate a file against this validator. | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =cut | 
| 314 |  |  |  |  |  |  | sub validateFile { | 
| 315 | 2 |  |  | 2 | 1 | 1093 | my ($self, $filename, @a) = @_; | 
| 316 |  |  |  |  |  |  |  | 
| 317 | 2 | 50 |  |  |  | 35 | if(-f $filename) { | 
| 318 | 2 |  |  |  |  | 6 | my $data = $self->_load_file( $filename ); | 
| 319 | 2 |  |  |  |  | 7 | return $self->validate( $data, @a ); | 
| 320 |  |  |  |  |  |  | } | 
| 321 | 0 |  |  |  |  | 0 | croak("Validation Error: Could not find data to validate: '$filename'"); | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | =head2 I<$validator>->setStrict( $bool ) | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | Should missing data be considered an error. | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | =cut | 
| 330 |  |  |  |  |  |  | sub setStrict { | 
| 331 | 0 |  |  | 0 | 1 | 0 | my ($self, $bool) = @_; | 
| 332 | 0 |  |  |  |  | 0 | $self->{'strict'} = $bool; | 
| 333 |  |  |  |  |  |  | } | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =head2 I<$validator>->setDefinition( $definition ) | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | Set the validators definition, will load it (used internally too) | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | =cut | 
| 340 |  |  |  |  |  |  | sub setDefinition { | 
| 341 | 3 |  |  | 3 | 1 | 8 | my ($self, $definition) = @_; | 
| 342 | 3 |  |  |  |  | 15 | $self->{'definition'} = $self->_load_definition( $definition ); | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | =head2 I<$validator>->getErrorString( $error_code ) | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | Return a human readable string for each error code. | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | =cut | 
| 350 |  |  |  |  |  |  | sub getErrorString { | 
| 351 | 0 |  |  | 0 | 1 | 0 | my ($self, $e) = @_; | 
| 352 | 0 | 0 | 0 |  |  | 0 | if($e>0 and $e<=$#errors) { | 
| 353 | 0 |  |  |  |  | 0 | return $errors[$e]; | 
| 354 |  |  |  |  |  |  | } | 
| 355 | 0 |  |  |  |  | 0 | return 'Invalid error code'; | 
| 356 |  |  |  |  |  |  | } | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | =head1 INTERNAL METHODS | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | Only read on if you are interesting in knowing some extra stuff about | 
| 361 |  |  |  |  |  |  | the internals of this module. | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | =head2 I<$validator>->_load_definition( $definition ) | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | Internal method for loading a definition into the validator | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | =cut | 
| 368 |  |  |  |  |  |  | sub _load_definition | 
| 369 |  |  |  |  |  |  | { | 
| 370 | 3 |  |  | 3 |  | 5 | my ($self, $definition) = @_; | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 3 | 50 |  |  |  | 28 | $definition->{'simpleTypes'} = { %simple_types, %{$definition->{'simpleTypes'} || {}} }; | 
|  | 3 |  |  |  |  | 94 |  | 
| 373 | 3 | 50 |  |  |  | 14 | $definition->{'complexTypes'} = { %complex_types, %{$definition->{'complexTypes'} || {}} }; | 
|  | 3 |  |  |  |  | 38 |  | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 3 | 50 |  |  |  | 59 | if(defined($definition->{'include'})) { | 
| 376 | 0 | 0 |  |  |  | 0 | if(ref($definition->{'include'}) eq "ARRAY") { | 
| 377 | 0 |  |  |  |  | 0 | foreach my $include (@{$definition->{'include'}}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 378 |  |  |  |  |  |  |  | 
| 379 | 0 | 0 |  |  |  | 0 | my $def = ref($include) ? $self->_load_definition( $include ) : $self->_load_definition_from_file( $include ); | 
| 380 |  |  |  |  |  |  |  | 
| 381 | 0 | 0 |  |  |  | 0 | if(defined($def->{'simpleTypes'})) { | 
| 382 | 0 |  |  |  |  | 0 | $self->_push_hash($definition->{'simpleTypes'}, $def->{'simpleTypes'}); | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  |  | 
| 385 | 0 | 0 |  |  |  | 0 | if(defined($def->{'complexTypes'})) { | 
| 386 | 0 |  |  |  |  | 0 | $self->_push_hash($definition->{'complexTypes'}, $def->{'complexTypes'}); | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  | } else { | 
| 390 | 0 |  |  |  |  | 0 | croak("Validator Error: include format needs to be an Array []"); | 
| 391 |  |  |  |  |  |  | } | 
| 392 |  |  |  |  |  |  | } | 
| 393 | 3 |  |  |  |  | 25 | return $definition; | 
| 394 |  |  |  |  |  |  | } | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | =head2 I<$validator>->_load_definition_from_file( $filename ) | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | Internal method for loading a definition from a file | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =cut | 
| 401 |  |  |  |  |  |  | sub _load_definition_from_file { | 
| 402 | 0 |  |  | 0 |  | 0 | my ($self, $filename) = @_; | 
| 403 | 0 |  |  |  |  | 0 | my $definition = $self->_load_file( $filename ); | 
| 404 | 0 |  |  |  |  | 0 | return $self->_load_definition( $definition ); | 
| 405 |  |  |  |  |  |  | } | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | =head2 I<$validator>->_validate_elements( %p ) | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | Internal method for validating a list of elements; | 
| 410 |  |  |  |  |  |  | p: definition, data, mode | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | =cut | 
| 413 |  |  |  |  |  |  | sub _validate_elements | 
| 414 |  |  |  |  |  |  | { | 
| 415 | 24 |  |  | 24 |  | 72 | my ($self, %p) = @_; | 
| 416 |  |  |  |  |  |  |  | 
| 417 | 24 |  |  |  |  | 36 | my $definition  = $p{'definition'}; | 
| 418 | 24 |  |  |  |  | 35 | my $data        = $p{'data'}; | 
| 419 | 24 |  |  |  |  | 38 | my $errors      = {}; | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # This should be AND or OR and controls the logic flow of the data varify | 
| 422 | 24 |  | 100 |  |  | 96 | my $mode = $p{'mode'} || 'AND'; | 
| 423 |  |  |  |  |  |  |  | 
| 424 | 24 | 50 |  |  |  | 97 | if(not UNIVERSAL::isa($definition, 'ARRAY')) { | 
| 425 | 0 |  |  |  |  | 0 | croak("definition is not in the correct format: expected array"); | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 24 |  |  |  |  | 35 | foreach my $element (@{$definition}) { | 
|  | 24 |  |  |  |  | 52 |  | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | # Element data check | 
| 431 | 59 | 100 |  |  |  | 177 | if(UNIVERSAL::isa($element, 'HASH')) { | 
|  |  | 50 |  |  |  |  |  | 
| 432 |  |  |  |  |  |  |  | 
| 433 | 53 |  |  |  |  | 89 | my $name = $element->{'name'}; | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | # Skip element if it's not defined | 
| 436 | 53 | 50 |  |  |  | 114 | next if(not $name); | 
| 437 |  |  |  |  |  |  |  | 
| 438 | 53 | 100 |  |  |  | 137 | $element->{'minOccurs'} = 1 if not defined($element->{'minOccurs'}); | 
| 439 | 53 | 100 |  |  |  | 124 | $element->{'maxOccurs'} = 1 if not defined($element->{'maxOccurs'}); | 
| 440 | 53 | 50 |  |  |  | 123 | $element->{'type'} = 'string' if not defined($element->{'type'}); | 
| 441 |  |  |  |  |  |  |  | 
| 442 | 53 |  |  |  |  | 212 | my $terrors = $self->_validate_element( | 
| 443 |  |  |  |  |  |  | definition => $element, | 
| 444 |  |  |  |  |  |  | data       => $data->{$name}, | 
| 445 |  |  |  |  |  |  | name       => $name, | 
| 446 |  |  |  |  |  |  | ); | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | # Fill Errors with required results. | 
| 449 | 53 | 100 |  |  |  | 204 | $errors->{$name} = $terrors if $terrors; | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | } elsif(UNIVERSAL::isa($element, 'ARRAY')) { | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  |  | 
| 454 | 6 |  |  |  |  | 13 | my $subr = {}; | 
| 455 | 6 | 100 |  |  |  | 27 | $subr = $self->_validate_elements( | 
| 456 |  |  |  |  |  |  | definition => $element, | 
| 457 |  |  |  |  |  |  | data       => $data, | 
| 458 |  |  |  |  |  |  | mode       => $mode eq 'OR' ? 'AND' : 'OR', | 
| 459 |  |  |  |  |  |  | ); | 
| 460 |  |  |  |  |  |  |  | 
| 461 | 6 | 100 | 66 |  |  | 35 | map { $errors->{$_} = $subr->{$_} } keys(%{$subr}) if $subr and ref($subr); | 
|  | 3 |  |  |  |  | 24 |  | 
|  | 2 |  |  |  |  | 7 |  | 
| 462 |  |  |  |  |  |  | } else { | 
| 463 | 0 |  |  |  |  | 0 | carp "This is a complex type, but it doesn't look like one: $element"; | 
| 464 |  |  |  |  |  |  | } | 
| 465 |  |  |  |  |  |  | } | 
| 466 |  |  |  |  |  |  |  | 
| 467 | 24 | 100 |  |  |  | 71 | if($mode eq 'OR') { | 
| 468 |  |  |  |  |  |  | # Only invalidate parent if all elements have errored | 
| 469 | 3 |  |  |  |  | 5 | foreach (%{$errors}) { | 
|  | 3 |  |  |  |  | 10 |  | 
| 470 | 6 | 100 |  |  |  | 29 | return 0 if not $errors->{$_}; | 
| 471 |  |  |  |  |  |  | } | 
| 472 | 0 |  |  |  |  | 0 | return $errors; | 
| 473 |  |  |  |  |  |  | } | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 21 | 100 |  |  |  | 27 | return %{$errors} ? $errors : 0; | 
|  | 21 |  |  |  |  | 133 |  | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =head2 I<$validator>->_validate_element( %p ) | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | Internal method for validating a single element | 
| 481 |  |  |  |  |  |  | p: data, definition, mode | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | =cut | 
| 484 |  |  |  |  |  |  | sub _validate_element { | 
| 485 | 53 |  |  | 53 |  | 173 | my ($self, %p) = @_; | 
| 486 |  |  |  |  |  |  |  | 
| 487 | 53 |  |  |  |  | 83 | my $definition = $p{'definition'}; | 
| 488 | 53 |  |  |  |  | 77 | my $data       = $p{'data'}; | 
| 489 | 53 |  |  |  |  | 66 | my $name       = $p{'name'}; | 
| 490 |  |  |  |  |  |  |  | 
| 491 | 53 |  |  |  |  | 60 | my @results; | 
| 492 | 53 |  |  |  |  | 66 | my $proped = 0; | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 53 | 100 | 100 |  |  | 271 | if(ref($data) ne "ARRAY" and defined($data)) { | 
| 495 | 36 |  |  |  |  | 46 | $proped = 1; | 
| 496 | 36 |  |  |  |  | 91 | $data = [$data]; | 
| 497 |  |  |  |  |  |  | } | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | # minOccurs checking | 
| 500 | 53 | 100 |  |  |  | 135 | if($definition->{'minOccurs'} >= 1) { | 
| 501 | 49 | 100 |  |  |  | 110 | if(defined($data)) { | 
| 502 | 40 | 50 |  |  |  | 51 | if($definition->{'minOccurs'} > @{$data}) { | 
|  | 40 |  |  |  |  | 124 |  | 
| 503 | 0 |  |  |  |  | 0 | return $INVALID_MIN_OCCURS; | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  | } else { | 
| 506 | 9 |  |  |  |  | 33 | return $INVALID_EXIST; | 
| 507 |  |  |  |  |  |  | } | 
| 508 |  |  |  |  |  |  | } | 
| 509 |  |  |  |  |  |  |  | 
| 510 | 44 | 100 |  |  |  | 112 | if(defined($data)) { | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | # maxOccurs Checking | 
| 513 | 42 | 100 |  |  |  | 122 | if($definition->{'maxOccurs'} ne 'unbounded') { | 
| 514 | 40 | 100 |  |  |  | 53 | if($definition->{'maxOccurs'} < @{$data}) { | 
|  | 40 |  |  |  |  | 108 |  | 
| 515 | 4 |  |  |  |  | 14 | return $INVALID_MAX_OCCURS; | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  | } | 
| 518 |  |  |  |  |  |  |  | 
| 519 | 38 |  |  |  |  | 54 | foreach my $element (@{$data}) { | 
|  | 38 |  |  |  |  | 73 |  | 
| 520 |  |  |  |  |  |  | # fixed and default checking | 
| 521 | 40 | 50 |  |  |  | 96 | if(defined($definition->{'fixed'})) { | 
| 522 | 0 | 0 | 0 |  |  | 0 | if(ref($element) ne "" or ($element and $element ne $definition->{'fixed'})) { | 
|  |  |  | 0 |  |  |  |  | 
| 523 | 0 |  |  |  |  | 0 | push @results, $INVALID_VALUE; | 
| 524 | 0 |  |  |  |  | 0 | next; | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  |  | 
| 528 | 40 | 50 |  |  |  | 107 | if(defined($definition->{'default'})) { | 
| 529 | 0 | 0 |  |  |  | 0 | $element = $definition->{'default'} if not defined($element); | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  |  | 
| 532 | 40 |  |  |  |  | 53 | my %po; | 
| 533 | 40 |  |  |  |  | 78 | foreach ('minLength', 'maxLength') { | 
| 534 | 80 | 100 |  |  |  | 257 | $po{$_} = $definition->{$_} if defined($definition->{$_}); | 
| 535 |  |  |  |  |  |  | } | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | # Element type checking | 
| 538 | 40 |  |  |  |  | 189 | my ($result, $te) = $self->_validate_type( | 
| 539 |  |  |  |  |  |  | type => $definition->{'type'}, | 
| 540 |  |  |  |  |  |  | data => $element, | 
| 541 |  |  |  |  |  |  | %po, #Passable Options | 
| 542 |  |  |  |  |  |  | ); | 
| 543 |  |  |  |  |  |  |  | 
| 544 | 40 | 100 |  |  |  | 211 | push @results, $result if $result; | 
| 545 |  |  |  |  |  |  | } | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  |  | 
| 548 | 40 | 100 |  |  |  | 107 | if(@results > 0) { | 
| 549 | 8 | 50 |  |  |  | 37 | return ($proped ? $results[0] : \@results); | 
| 550 |  |  |  |  |  |  | } | 
| 551 | 32 |  |  |  |  | 132 | return 0; | 
| 552 |  |  |  |  |  |  | } | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | =head2 I<$validator>->_validate_type( %p ) | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | Internal method for validating a single data type | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | =cut | 
| 559 |  |  |  |  |  |  | sub _validate_type { | 
| 560 | 42 |  |  | 42 |  | 124 | my ($self, %p) = @_; | 
| 561 |  |  |  |  |  |  |  | 
| 562 | 42 |  |  |  |  | 79 | my $data       = delete($p{'data'}); | 
| 563 | 42 |  |  |  |  | 78 | my $type       = delete($p{'type'}); | 
| 564 | 42 |  |  |  |  | 72 | my $definition = $self->{'definition'}; | 
| 565 | 42 |  |  |  |  | 82 | my %pdef       = %p; | 
| 566 |  |  |  |  |  |  |  | 
| 567 | 42 | 100 |  |  |  | 138 | if(defined($definition->{'simpleTypes'}->{$type})) { | 
|  |  | 50 |  |  |  |  |  | 
| 568 |  |  |  |  |  |  |  | 
| 569 | 30 |  |  |  |  | 40 | my $typedef = { %{$definition->{'simpleTypes'}->{$type}}, %pdef }; | 
|  | 30 |  |  |  |  | 127 |  | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | # Base type check | 
| 572 | 30 | 100 |  |  |  | 93 | if(defined($typedef->{'base'})) { | 
| 573 | 2 |  |  |  |  | 15 | my $err = $self->_validate_type( | 
| 574 |  |  |  |  |  |  | type => $typedef->{'base'}, | 
| 575 |  |  |  |  |  |  | data => $data, | 
| 576 |  |  |  |  |  |  | ); | 
| 577 | 2 | 50 |  |  |  | 16 | return $err if $err; | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | # Pattern type check | 
| 581 | 30 | 50 | 33 |  |  | 167 | if(defined($typedef->{'pattern'}) and ref($typedef->{'pattern'}) eq 'REGEX') { | 
| 582 | 0 | 0 |  |  |  | 0 | if($data !~ $typedef->{'pattern'}) { | 
| 583 | 0 |  |  |  |  | 0 | return $INVALID_PATTERN; | 
| 584 |  |  |  |  |  |  | } | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | # Custom method check | 
| 588 | 30 | 100 |  |  |  | 82 | if(defined($typedef->{'custom'})) { | 
| 589 | 6 |  |  |  |  | 9 | my $method = $typedef->{'custom'}; | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 6 | 50 | 33 |  |  | 30 | if(ref($method) ne 'CODE' or not $method->($data, $typedef)) { | 
| 592 | 0 |  |  |  |  | 0 | return $INVALID_CUSTOM; | 
| 593 |  |  |  |  |  |  | } | 
| 594 |  |  |  |  |  |  | } | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | # Length checks | 
| 597 | 30 | 100 |  |  |  | 91 | if(defined($typedef->{'maxLength'})) { | 
| 598 | 6 | 100 |  |  |  | 21 | if(length($data) > $typedef->{'maxLength'}) { | 
| 599 | 2 |  |  |  |  | 10 | return $INVALID_MAXLENGTH; | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  | } | 
| 602 |  |  |  |  |  |  |  | 
| 603 | 28 | 100 |  |  |  | 67 | if(defined($typedef->{'minLength'})) { | 
| 604 | 4 | 100 |  |  |  | 14 | if(length($data) < $typedef->{'minLength'}) { | 
| 605 | 2 |  |  |  |  | 10 | return $INVALID_MINLENGTH; | 
| 606 |  |  |  |  |  |  | } | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | # Match another node | 
| 610 | 26 | 50 | 33 |  |  | 140 | if(defined($typedef->{'match'}) or defined($typedef->{'notMatch'})) { | 
| 611 | 0 |  | 0 |  |  | 0 | my $path   = $typedef->{'match'} || $typedef->{'notMatch'}; | 
| 612 | 0 |  |  |  |  | 0 | my $result = $self->_find_value( path => $path, data => $data ); | 
| 613 | 0 | 0 | 0 |  |  | 0 | if( ($data ne $result and $typedef->{'match'}) | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 614 |  |  |  |  |  |  | or ($data eq $result and $typedef->{'notMatch'})) { | 
| 615 | 0 |  |  |  |  | 0 | return $INVALID_MATCH; | 
| 616 |  |  |  |  |  |  | } | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  |  | 
| 619 | 26 | 50 |  |  |  | 63 | if(defined($typedef->{'enumeration'})) { | 
| 620 | 0 | 0 |  |  |  | 0 | if(ref($typedef->{'enumeration'}) ne 'ARRAY') { | 
| 621 | 0 |  |  |  |  | 0 | croak("Validator Error: Enumberation not of the correct type"); | 
| 622 |  |  |  |  |  |  | } | 
| 623 | 0 |  |  |  |  | 0 | my $found = 0; | 
| 624 | 0 |  |  |  |  | 0 | foreach (@{$typedef->{'enumeration'}}) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 625 | 0 | 0 |  |  |  | 0 | $found = 1 if $_ eq $data; | 
| 626 |  |  |  |  |  |  | } | 
| 627 | 0 | 0 |  |  |  | 0 | return $INVALID_ENUMERATION if not $found; | 
| 628 |  |  |  |  |  |  | } | 
| 629 |  |  |  |  |  |  |  | 
| 630 | 26 | 100 | 33 |  |  | 415 | if(looks_like_number($data)) { | 
|  |  | 50 | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 631 | 3 | 50 | 33 |  |  | 13 | return $INVALID_MIN_RANGE if defined($typedef->{'minInclusive'}) and $data < $typedef->{'minInclusive'}; | 
| 632 | 3 | 50 | 33 |  |  | 10 | return $INVALID_MAX_RANGE if defined($typedef->{'maxInclusive'}) and $data > $typedef->{'maxInclusive'}; | 
| 633 | 3 | 50 | 33 |  |  | 20 | return $INVALID_MIN_RANGE if defined($typedef->{'minExclusive'}) and $data <= $typedef->{'minExclusive'}; | 
| 634 | 3 | 50 | 33 |  |  | 15 | return $INVALID_MAX_RANGE if defined($typedef->{'maxExclusive'}) and $data >= $typedef->{'maxExclusive'}; | 
| 635 |  |  |  |  |  |  |  | 
| 636 |  |  |  |  |  |  | #     return $INVALID_FRACTION if defined($typedef->{'fractionDigits'}) and $data !~ /\.(\d{})$/; | 
| 637 |  |  |  |  |  |  |  | 
| 638 |  |  |  |  |  |  | } elsif(defined($typedef->{'minInclusive'}) or defined($typedef->{'maxInclusive'}) or | 
| 639 |  |  |  |  |  |  | defined($typedef->{'minExclusive'}) or defined($typedef->{'maxExclusive'}) or | 
| 640 |  |  |  |  |  |  | defined($typedef->{'fractionDigits'})) { | 
| 641 | 0 |  |  |  |  | 0 | return $INVALID_NUMBER; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | } elsif(defined($definition->{'complexTypes'}->{$type})) { | 
| 645 | 12 |  |  |  |  | 24 | my $typedef = $definition->{'complexTypes'}->{$type}; | 
| 646 | 12 | 50 |  |  |  | 34 | if(ref($data) eq "HASH") { | 
| 647 | 12 |  |  |  |  | 48 | return $self->_validate_elements( definition => $typedef, data => $data ); | 
| 648 |  |  |  |  |  |  | } else { | 
| 649 | 0 |  |  |  |  | 0 | return $INVALID_COMPLEX; | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  | } else { | 
| 652 | 0 |  |  |  |  | 0 | croak("Validator Error: Can not find type definition '$type'"); | 
| 653 | 0 |  |  |  |  | 0 | return $CRITICAL; | 
| 654 |  |  |  |  |  |  | } | 
| 655 |  |  |  |  |  |  |  | 
| 656 | 26 |  |  |  |  | 79 | return $NOERROR; | 
| 657 |  |  |  |  |  |  | } | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  | =head2 I<$validator>->_find_value( %p ) | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | Internal method for finding a value match (basic xpath) | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | =cut | 
| 664 |  |  |  |  |  |  | sub _find_value | 
| 665 |  |  |  |  |  |  | { | 
| 666 | 0 |  |  | 0 |  | 0 | my ($self, %p) = @_; | 
| 667 |  |  |  |  |  |  | # Remove root path, and stop localisation | 
| 668 | 0 | 0 |  |  |  | 0 | if($p{'path'} =~ s/^\///){ $p{'data'} = $self->{'data'}; } | 
|  | 0 |  |  |  |  | 0 |  | 
| 669 |  |  |  |  |  |  |  | 
| 670 | 0 |  |  |  |  | 0 | my @paths = split('/', $p{'path'}); | 
| 671 | 0 |  |  |  |  | 0 | my $data  = $p{'data'}; | 
| 672 |  |  |  |  |  |  |  | 
| 673 | 0 |  |  |  |  | 0 | foreach my $path (@paths) { | 
| 674 | 0 | 0 |  |  |  | 0 | if(UNIVERSAL::isa($data, 'HASH')) { | 
| 675 | 0 | 0 |  |  |  | 0 | if(defined($data->{$path})) { | 
| 676 | 0 |  |  |  |  | 0 | $data = $data->{$path}; | 
| 677 |  |  |  |  |  |  | } else { | 
| 678 | 0 | 0 |  |  |  | 0 | carp "Validator Error: Can't find nodes for '$p{'path'}' in _find_value\n" if $self->{'debug'}; | 
| 679 |  |  |  |  |  |  | } | 
| 680 |  |  |  |  |  |  | } else { | 
| 681 | 0 | 0 |  |  |  | 0 | carp "Validator Error: Can't find nodes for '$p{'path'}' in _find_value\n" if $self->{'debug'}; | 
| 682 |  |  |  |  |  |  | } | 
| 683 |  |  |  |  |  |  | } | 
| 684 | 0 |  |  |  |  | 0 | return $data; | 
| 685 |  |  |  |  |  |  | } | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | =head2 I<$validator>->_push_hash( $dest, $source ) | 
| 688 |  |  |  |  |  |  |  | 
| 689 |  |  |  |  |  |  | Internal method for copying a hash to another | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | =cut | 
| 692 |  |  |  |  |  |  | sub _push_hash | 
| 693 |  |  |  |  |  |  | { | 
| 694 | 0 |  |  | 0 |  | 0 | my($self, $dest, $source) = @_; | 
| 695 |  |  |  |  |  |  |  | 
| 696 | 0 |  |  |  |  | 0 | foreach my $key (keys(%{$source})) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 697 | 0 | 0 |  |  |  | 0 | if(not $dest->{$key}) { | 
| 698 | 0 |  |  |  |  | 0 | $dest->{$key} = $source->{$key}; | 
| 699 |  |  |  |  |  |  | } | 
| 700 |  |  |  |  |  |  | } | 
| 701 | 0 |  |  |  |  | 0 | return $dest; | 
| 702 |  |  |  |  |  |  | } | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | =head2 I<$validator>->_load_file( $file ) | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | Internal method for loading a file, must be valid perl syntax. | 
| 707 |  |  |  |  |  |  | Yep that's right, be bloody careful when loading from files. | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  | =cut | 
| 710 |  |  |  |  |  |  | sub _load_file { | 
| 711 | 4 |  |  | 4 |  | 7 | my ($self, $filename, $def) = @_; | 
| 712 | 4 |  |  |  |  | 114 | open( VALIDATE, $filename ); | 
| 713 | 4 |  |  |  |  | 124 | my $content = join('', ); | 
| 714 | 4 |  |  |  |  | 43 | close( VALIDATE ); | 
| 715 |  |  |  |  |  |  |  | 
| 716 | 4 |  |  |  |  | 6 | my $data; | 
| 717 | 4 | 100 |  |  |  | 15 | if($content =~ /^<\?xml/) { | 
| 718 |  |  |  |  |  |  | # XML File | 
| 719 | 1 |  |  | 1 |  | 72 | eval("use Data::Validate::XSD::ParseXML"); | 
|  | 1 |  |  |  |  | 683 |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 720 | 1 | 50 |  |  |  | 409 | croak("Did you forget to install XML::SAX? ($@)") if $@; | 
| 721 | 0 |  |  |  |  | 0 | my $parser = Data::Validate::XSD::ParseXML->new( $content ); | 
| 722 | 0 | 0 | 0 |  |  | 0 | if($def and $content =~ /XMLSchema/) { | 
| 723 | 0 |  |  |  |  | 0 | $data = $parser->definition(); | 
| 724 |  |  |  |  |  |  | } else { | 
| 725 | 0 |  |  |  |  | 0 | $data = $parser->data(); | 
| 726 |  |  |  |  |  |  | } | 
| 727 |  |  |  |  |  |  | } else { | 
| 728 | 3 |  |  |  |  | 289 | $data = eval('{ '.$content.' }'); | 
| 729 | 3 | 50 |  |  |  | 16 | croak("Validator Error! $@") if $@; | 
| 730 |  |  |  |  |  |  | } | 
| 731 | 3 |  |  |  |  | 10 | return $data; | 
| 732 |  |  |  |  |  |  | } | 
| 733 |  |  |  |  |  |  |  | 
| 734 |  |  |  |  |  |  | =head2 $validate->_test_datetime( $typedef ) | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | Test a date time range is a valid date. | 
| 737 |  |  |  |  |  |  |  | 
| 738 |  |  |  |  |  |  | =cut | 
| 739 |  |  |  |  |  |  | sub _test_datetime { | 
| 740 | 6 |  |  | 6 |  | 10 | my ($data, $typedef) = @_; | 
| 741 | 6 | 50 |  |  |  | 16 | if($data) { | 
| 742 | 6 |  |  |  |  | 21 | my $epoch = str2time( $data ); | 
| 743 | 6 | 50 |  |  |  | 1556 | if($epoch) { | 
| 744 | 6 |  |  |  |  | 33 | return 1; | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  | } | 
| 747 | 0 |  |  |  |  | 0 | return undef; | 
| 748 |  |  |  |  |  |  | } | 
| 749 |  |  |  |  |  |  |  | 
| 750 |  |  |  |  |  |  | =head1 KNOWN BUGS | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | * XML and YML suport not added yet. | 
| 753 |  |  |  |  |  |  | * Fraction Didgets test doesn't work yet. | 
| 754 |  |  |  |  |  |  |  | 
| 755 |  |  |  |  |  |  | =head1 AUTHOR | 
| 756 |  |  |  |  |  |  |  | 
| 757 |  |  |  |  |  |  | Copyright, Martin Owens 2007-2008, Affero General Public License (AGPL) | 
| 758 |  |  |  |  |  |  |  | 
| 759 |  |  |  |  |  |  | http://www.fsf.org/licensing/licenses/agpl-3.0.html | 
| 760 |  |  |  |  |  |  |  | 
| 761 |  |  |  |  |  |  | =cut | 
| 762 |  |  |  |  |  |  | 1; |