      1 /*
      2  * jQuery CURIE @VERSION
      3  *
      4  * Copyright (c) 2008,2009 Jeni Tennison
      5  * Licensed under the MIT (MIT-LICENSE.txt)
      6  *
      7  * Depends:
      8  *  jquery.uri.js
      9  */
     10 /**
     11  * @fileOverview XML Schema datatype handling
     12  * @author <a href="mailto:jeni@jenitennison.com">Jeni Tennison</a>
     13  * @copyright (c) 2008,2009 Jeni Tennison
     14  * @license MIT license (MIT-LICENSE.txt)
     15  * @version 1.0
     16  * @requires jquery.uri.js
     17  */
     18
     19 (function ($) {
     20
     21   var strip = function (value) {
     22     return value.replace(/[ \t\n\r]+/, ' ').replace(/^ +/, '').replace(/ +$/, '');
     23   };
     24
     25   /**
     26    * Creates a new jQuery.typedValue object. This should be invoked as a method
     27    * rather than constructed using new.
     28    * @class Represents a value with an XML Schema datatype
     29    * @param {String} value The string representation of the value
     30    * @param {String} datatype The XML Schema datatype URI
     31    * @returns {jQuery.typedValue}
     32    * @example intValue = jQuery.typedValue('42', 'http://www.w3.org/2001/XMLSchema#integer');
     33    */
     34   $.typedValue = function (value, datatype) {
     35     return $.typedValue.fn.init(value, datatype);
     36   };
     37
     38   $.typedValue.fn = $.typedValue.prototype = {
     39     /**
     40      * The string representation of the value
     41      * @memberOf jQuery.typedValue#
     42      */
     43     representation: undefined,
     44     /**
     45      * The value as an object. The type of the object will
     46      * depend on the XML Schema datatype URI specified
     47      * in the constructor. The following table lists the mappings
     48      * currently supported:
     49      * <table>
     50      *   <tr>
     51      *   <th>XML Schema Datatype</th>
     52      *   <th>Value type</th>
     53      *   </tr>
     54      *   <tr>
     55      *     <td>http://www.w3.org/2001/XMLSchema#string</td>
     56      *     <td>string</td>
     57      *   </tr>
     58      *   <tr>
     59      *     <td>http://www.w3.org/2001/XMLSchema#token</td>
     60      *     <td>string</td>
     61      *   </tr>
     62      *   <tr>
     63      *     <td>http://www.w3.org/2001/XMLSchema#NCName</td>
     64      *     <td>string</td>
     65      *   </tr>
     66      *   <tr>
     67      *     <td>http://www.w3.org/2001/XMLSchema#boolean</td>
     68      *     <td>bool</td>
     69      *   </tr>
     70      *   <tr>
     71      *     <td>http://www.w3.org/2001/XMLSchema#decimal</td>
     72      *     <td>string</td>
     73      *   </tr>
     74      *   <tr>
     75      *     <td>http://www.w3.org/2001/XMLSchema#integer</td>
     76      *     <td>int</td>
     77      *   </tr>
     78      *   <tr>
     79      *     <td>http://www.w3.org/2001/XMLSchema#int</td>
     80      *     <td>int</td>
     81      *   </tr>
     82      *   <tr>
     83      *     <td>http://www.w3.org/2001/XMLSchema#float</td>
     84      *     <td>float</td>
     85      *   </tr>
     86      *   <tr>
     87      *     <td>http://www.w3.org/2001/XMLSchema#double</td>
     88      *     <td>float</td>
     89      *   </tr>
     90      *   <tr>
     91      *     <td>http://www.w3.org/2001/XMLSchema#dateTime</td>
     92      *     <td>string</td>
     93      *   </tr>
     94      *   <tr>
     95      *     <td>http://www.w3.org/2001/XMLSchema#date</td>
     96      *     <td>string</td>
     97      *   </tr>
     98      *   <tr>
     99      *     <td>http://www.w3.org/2001/XMLSchema#gYear</td>
    100      *     <td>int</td>
    101      *   </tr>
    102      *   <tr>
    103      *     <td>http://www.w3.org/2001/XMLSchema#gMonthDay</td>
    104      *     <td>string</td>
    105      *   </tr>
    106      *   <tr>
    107      *     <td>http://www.w3.org/2001/XMLSchema#anyURI</td>
    108      *     <td>{@link jQuery.uri}</td>
    109      *   </tr>
    110      * </table>
    111      * @memberOf jQuery.typedValue#
    112      */
    113     value: undefined,
    114     /**
    115      * The XML Schema datatype URI for the value's datatype
    116      * @memberOf jQuery.typedValue#
    117      */
    118     datatype: undefined,
    119
    120     init: function (value, datatype) {
    121       var d = $.typedValue.types[datatype];
    122       if ($.typedValue.valid(value, datatype)) {
    123         this.representation = value;
    124         this.datatype = datatype;
    125         this.value = d === undefined ? strip(value) : d.value(d.strip ? strip(value) : value);
    126         return this;
    127       } else {
    128         throw {
    129           name: 'InvalidValue',
    130           message: value + ' is not a valid ' + datatype + ' value'
    131         };
    132       }
    133     }
    134   };
    135
    136   $.typedValue.fn.init.prototype = $.typedValue.fn;
    137
    138   /**
    139    * An object that holds the datatypes supported by the script. The properties of this object are the URIs of the datatypes, and each datatype has four properties:
    140    * <dl>
    141    *   <dt>strip</dt>
    142    *   <dd>A boolean value that indicates whether whitespace should be stripped from the value prior to testing against the regular expression or passing to the value function.</dd>
    143    *   <dt>regex</dt>
    144    *   <dd>A regular expression that valid values of the type must match.</dd>
    145    *   <dt>validate</dt>
    146    *   <dd>Optional. A function that performs further testing on the value.</dd>
    147    *   <dt>value</dt>
    148    *   <dd>A function that returns a Javascript object equivalent for the value.</dd>
    149    * </dl>
    150    * You can add to this object as necessary for your own datatypes, and {@link jQuery.typedValue} and {@link jQuery.typedValue.valid} will work with them.
    151    * @see jQuery.typedValue
    152    * @see jQuery.typedValue.valid
    153    */
    154   $.typedValue.types = {};
    155
    156   $.typedValue.types['http://www.w3.org/2001/XMLSchema#string'] = {
    157     regex: /^.*$/,
    158     strip: false,
    159     /** @ignore */
    160     value: function (v) {
    161       return v;
    162     }
    163   };
    164
    165   $.typedValue.types['http://www.w3.org/2001/XMLSchema#token'] = {
    166     regex: /^.*$/,
    167     strip: true,
    168     /** @ignore */
    169     value: function (v) {
    170       return strip(v);
    171     }
    172   };
    173
    174   $.typedValue.types['http://www.w3.org/2001/XMLSchema#NCName'] = {
    175     regex: /^[a-z_][-\.a-z0-9]+$/i,
    176     strip: true,
    177     /** @ignore */
    178     value: function (v) {
    179       return strip(v);
    180     }
    181   };
    182
    183   $.typedValue.types['http://www.w3.org/2001/XMLSchema#boolean'] = {
    184     regex: /^(?:true|false|1|0)$/,
    185     strip: true,
    186     /** @ignore */
    187     value: function (v) {
    188       return v === 'true' || v === '1';
    189     }
    190   };
    191
    192   $.typedValue.types['http://www.w3.org/2001/XMLSchema#decimal'] = {
    193     regex: /^[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)$/,
    194     strip: true,
    195     /** @ignore */
    196     value: function (v) {
    197       v = v.replace(/^0+/, '')
    198         .replace(/0+$/, '');
    199       if (v === '') {
    200         v = '0.0';
    201       }
    202       if (v.substring(0, 1) === '.') {
    203         v = '0' + v;
    204       }
    205       if (/\.$/.test(v)) {
    206         v = v + '0';
    207       } else if (!/\./.test(v)) {
    208         v = v + '.0';
    209       }
    210       return v;
    211     }
    212   };
    213
    214   $.typedValue.types['http://www.w3.org/2001/XMLSchema#integer'] = {
    215     regex: /^[\-\+]?[0-9]+$/,
    216     strip: true,
    217     /** @ignore */
    218     value: function (v) {
    219       return parseInt(v, 10);
    220     }
    221   };
    222
    223   $.typedValue.types['http://www.w3.org/2001/XMLSchema#int'] = {
    224     regex: /^[\-\+]?[0-9]+$/,
    225     strip: true,
    226     /** @ignore */
    227     value: function (v) {
    228       return parseInt(v, 10);
    229     }
    230   };
    231
    232   $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'] = {
    233     regex: /^(?:[\-\+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)(?:[eE][\-\+]?[0-9]+)?|[\-\+]?INF|NaN)$/,
    234     strip: true,
    235     /** @ignore */
    236     value: function (v) {
    237       if (v === '-INF') {
    238         return -1 / 0;
    239       } else if (v === 'INF' || v === '+INF') {
    240         return 1 / 0;
    241       } else {
    242         return parseFloat(v);
    243       }
    244     }
    245   };
    246
    247   $.typedValue.types['http://www.w3.org/2001/XMLSchema#double'] = {
    248     regex: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].regex,
    249     strip: true,
    250     value: $.typedValue.types['http://www.w3.org/2001/XMLSchema#float'].value
    251   };
    252
    253   $.typedValue.types['http://www.w3.org/2001/XMLSchema#duration'] = {
    254     regex: /^([\-\+])?P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(?:T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+(?:\.[0-9]+)?)?S)?)$/,
    255     /** @ignore */
    256     validate: function (v) {
    257       var m = this.regex.exec(v);
    258       return m[2] || m[3] || m[4] || m[5] || m[6] || m[7];
    259     },
    260     strip: true,
    261     /** @ignore */
    262     value: function (v) {
    263       return v;
    264     }
    265   };
    266
    267   $.typedValue.types['http://www.w3.org/2001/XMLSchema#yearMonthDuration'] = {
    268     regex: /^([\-\+])?P(?:([0-9]+)Y)?(?:([0-9]+)M)?$/,
    269     /** @ignore */
    270     validate: function (v) {
    271       var m = this.regex.exec(v);
    272       return m[2] || m[3];
    273     },
    274     strip: true,
    275     /** @ignore */
    276     value: function (v) {
    277       var m = this.regex.exec(v),
    278         years = m[2] || 0,
    279         months = m[3] || 0;
    280       months += years * 12;
    281       return m[1] === '-' ? -1 * months : months;
    282     }
    283   };
    284
    285   $.typedValue.types['http://www.w3.org/2001/XMLSchema#dateTime'] = {
    286     regex: /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):(([0-9]{2})(\.([0-9]+))?)((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
    287     /** @ignore */
    288     validate: function (v) {
    289       var
    290         m = this.regex.exec(v),
    291         year = parseInt(m[1], 10),
    292         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, ''),
    293         date;
    294       if (year === 0 ||
    295           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
    296         return false;
    297       }
    298       try {
    299         year = year < 100 ? Math.abs(year) + 1000 : year;
    300         month = parseInt(m[2], 10);
    301         day = parseInt(m[3], 10);
    302         if (day > 31) {
    303           return false;
    304         } else if (day > 30 && !(month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12)) {
    305           return false;
    306         } else if (month === 2) {
    307           if (day > 29) {
    308             return false;
    309           } else if (day === 29 && (year % 4 !== 0 || (year % 100 === 0 && year % 400 !== 0))) {
    310             return false;
    311           }
    312         }
    313         date = '' + year + '/' + m[2] + '/' + m[3] + ' ' + m[4] + ':' + m[5] + ':' + m[7] + ' ' + tz;
    314         date = new Date(date);
    315         return true;
    316       } catch (e) {
    317         return false;
    318       }
    319     },
    320     strip: true,
    321     /** @ignore */
    322     value: function (v) {
    323       return v;
    324     }
    325   };
    326
    327   $.typedValue.types['http://www.w3.org/2001/XMLSchema#date'] = {
    328     regex: /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
    329     /** @ignore */
    330     validate: function (v) {
    331       var
    332         m = this.regex.exec(v),
    333         year = parseInt(m[1], 10),
    334         month = parseInt(m[2], 10),
    335         day = parseInt(m[3], 10),
    336         tz = m[10] === undefined || m[10] === 'Z' ? '+0000' : m[10].replace(/:/, '');
    337       if (year === 0 ||
    338           month > 12 ||
    339           day > 31 ||
    340           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
    341         return false;
    342       } else {
    343         return true;
    344       }
    345     },
    346     strip: true,
    347     /** @ignore */
    348     value: function (v) {
    349       return v;
    350     }
    351   };
    352
    353   $.typedValue.types['http://www.w3.org/2001/XMLSchema#gYear'] = {
    354     regex: /^-?([0-9]{4,})$/,
    355     /** @ignore */
    356     validate: function (v) {
    357       var i = parseInt(v, 10);
    358       return i !== 0;
    359     },
    360     strip: true,
    361     /** @ignore */
    362     value: function (v) {
    363       return parseInt(v, 10);
    364     }
    365   };
    366
    367   $.typedValue.types['http://www.w3.org/2001/XMLSchema#gMonthDay'] = {
    368     regex: /^--([0-9]{2})-([0-9]{2})((?:[\-\+]([0-9]{2}):([0-9]{2}))|Z)?$/,
    369     /** @ignore */
    370     validate: function (v) {
    371       var
    372         m = this.regex.exec(v),
    373         month = parseInt(m[1], 10),
    374         day = parseInt(m[2], 10),
    375         tz = m[3] === undefined || m[3] === 'Z' ? '+0000' : m[3].replace(/:/, '');
    376       if (month > 12 ||
    377           day > 31 ||
    378           parseInt(tz, 10) < -1400 || parseInt(tz, 10) > 1400) {
    379         return false;
    380       } else if (month === 2 && day > 29) {
    381         return false;
    382       } else if ((month === 4 || month === 6 || month === 9 || month === 11) && day > 30) {
    383         return false;
    384       } else {
    385         return true;
    386       }
    387     },
    388     strip: true,
    389     /** @ignore */
    390     value: function (v) {
    391       return v;
    392     }
    393   };
    394
    395   $.typedValue.types['http://www.w3.org/2001/XMLSchema#anyURI'] = {
    396     regex: /^.*$/,
    397     strip: true,
    398     /** @ignore */
    399     value: function (v, options) {
    400       var opts = $.extend({}, $.typedValue.defaults, options);
    401       return $.uri.resolve(v, opts.base);
    402     }
    403   };
    404
    405   $.typedValue.defaults = {
    406     base: $.uri.base(),
    407     namespaces: {}
    408   };
    409
    410   /**
    411    * Checks whether a value is valid according to a given datatype. The datatype must be held in the {@link jQuery.typedValue.types} object.
    412    * @param {String} value The value to validate.
    413    * @param {String} datatype The URI for the datatype against which the value will be validated.
    414    * @returns {boolean} True if the value is valid or the datatype is not recognised.
    415    * @example validDate = $.typedValue.valid(date, 'http://www.w3.org/2001/XMLSchema#date');
    416    */
    417   $.typedValue.valid = function (value, datatype) {
    418     var d = $.typedValue.types[datatype];
    419     if (d === undefined) {
    420       return true;
    421     } else {
    422       value = d.strip ? strip(value) : value;
    423       if (d.regex.test(value)) {
    424         return d.validate === undefined ? true : d.validate(value);
    425       } else {
    426         return false;
    427       }
    428     }
    429   };
    430
    431 })(jQuery);
    432