├── .eslintignore ├── src ├── valueParsers │ ├── valueParsers.js │ ├── parsers │ │ ├── StringParser.js │ │ ├── IntParser.js │ │ ├── FloatParser.js │ │ ├── NullParser.js │ │ ├── BoolParser.js │ │ └── ValueParser.js │ └── ValueParserStore.js ├── valueFormatters │ ├── valueFormatters.js │ └── formatters │ │ ├── StringFormatter.js │ │ ├── NullFormatter.js │ │ └── ValueFormatter.js ├── values │ ├── NumberValue.js │ ├── UnknownValue.js │ ├── BoolValue.js │ ├── StringValue.js │ ├── GlobeCoordinateValue.js │ ├── MonolingualTextValue.js │ ├── MultilingualTextValue.js │ ├── UnDeserializableValue.js │ ├── QuantityValue.js │ ├── DecimalValue.js │ └── TimeValue.js ├── DataValue.js └── dataValues.js ├── .gitignore ├── tests ├── .eslintrc.json ├── src │ ├── values │ │ ├── BoolValue.tests.js │ │ ├── NumberValue.tests.js │ │ ├── StringValue.tests.js │ │ ├── GlobeCoordinateValue.tests.js │ │ ├── DecimalValue.tests.js │ │ ├── UnknownValue.tests.js │ │ ├── QuantityValue.tests.js │ │ ├── MonolingualTextValue.tests.js │ │ ├── MultilingualTextValue.tests.js │ │ ├── UnDeserializableValue.tests.js │ │ └── TimeValue.tests.js │ ├── valueParsers │ │ ├── parsers │ │ │ ├── IntParser.tests.js │ │ │ ├── StringParser.tests.js │ │ │ ├── FloatParser.tests.js │ │ │ ├── NullParser.tests.js │ │ │ └── BoolParser.tests.js │ │ ├── valueParsers.tests.js │ │ └── ValueParserStore.tests.js │ ├── valueFormatters │ │ ├── formatters │ │ │ ├── StringFormatter.tests.js │ │ │ └── NullFormatter.tests.js │ │ └── valueFormatters.tests.js │ ├── dataValues.tests.js │ └── dataValues.DataValue.tests.js └── lib │ └── globeCoordinate │ └── globeCoordinate.GlobeCoordinate.tests.js ├── lib ├── globeCoordinate │ ├── globeCoordinate.js │ └── globeCoordinate.GlobeCoordinate.js └── util │ └── util.inherit.js ├── .eslintrc.json ├── .github └── workflows │ └── ci.yaml ├── package.json ├── karma.conf.js ├── README.md └── COPYING /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | vendor/** 3 | -------------------------------------------------------------------------------- /src/valueParsers/valueParsers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | this.valueParsers = this.valueParsers || {}; 5 | -------------------------------------------------------------------------------- /src/valueFormatters/valueFormatters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ignore 3 | */ 4 | this.valueFormatters = this.valueFormatters || {}; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.kate-swp 3 | 4 | composer.lock 5 | 6 | !.* 7 | .idea/ 8 | 9 | node_modules/ 10 | vendor/ 11 | build/ 12 | -------------------------------------------------------------------------------- /tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "env": { 4 | "qunit": true 5 | }, 6 | "rules": { 7 | "dot-notation": "off" 8 | } 9 | } -------------------------------------------------------------------------------- /lib/globeCoordinate/globeCoordinate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Globe coordinate module 3 | * @class globeCoordinate 4 | * @singleton 5 | * @license GPL-2.0+ 6 | */ 7 | this.globeCoordinate = {}; 8 | -------------------------------------------------------------------------------- /src/valueFormatters/formatters/StringFormatter.js: -------------------------------------------------------------------------------- 1 | ( function( $, vf, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = vf.ValueFormatter; 5 | 6 | /** 7 | * String formatter. 8 | * @class valueFormatters.StringFormatter 9 | * @extends valueFormatters.ValueFormatter 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author H. Snater < mediawiki@snater.com > 13 | * 14 | * @constructor 15 | */ 16 | vf.StringFormatter = util.inherit( PARENT, { 17 | /** 18 | * @inheritdoc 19 | */ 20 | format: function( dataValue ) { 21 | var deferred = $.Deferred(); 22 | 23 | deferred.resolve( dataValue.toJSON(), dataValue ); 24 | 25 | return deferred.promise(); 26 | } 27 | } ); 28 | 29 | }( jQuery, valueFormatters, util ) ); 30 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/StringParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, dv, $, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = vp.ValueParser; 5 | 6 | /** 7 | * Constructor for string parsers. 8 | * @class valueParsers.StringParser 9 | * @extends valueParsers.ValueParser 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 13 | * 14 | * @constructor 15 | */ 16 | vp.StringParser = util.inherit( PARENT, { 17 | /** 18 | * @inheritdoc 19 | * 20 | * @param {string} rawValue 21 | */ 22 | parse: function( rawValue ) { 23 | return $.Deferred().resolve( 24 | rawValue === '' ? null : new dv.StringValue( rawValue ) 25 | ).promise(); 26 | } 27 | } ); 28 | 29 | }( valueParsers, dataValues, jQuery, util ) ); 30 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "wikimedia", 3 | "env": { 4 | "browser": true, 5 | "jquery": true 6 | }, 7 | "globals": { 8 | "dataValues": false, 9 | "define": false, 10 | "globeCoordinate": false, 11 | "module": "false", 12 | "QUnit": false, 13 | "util": false, 14 | "valueFormatters": false, 15 | "valueParsers": false, 16 | "wikibase": false 17 | }, 18 | "rules": { 19 | "computed-property-spacing": "off", 20 | "indent": "off", 21 | "keyword-spacing": "off", 22 | "no-loop-func": "off", 23 | "no-underscore-dangle": "off", 24 | "no-unused-vars": "off", 25 | "one-var": "off", 26 | "operator-linebreak": "off", 27 | "quote-props": "off", 28 | "space-before-function-paren": "off", 29 | "space-infix-ops": "off", 30 | "valid-jsdoc": "off", 31 | "vars-on-top": "off" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/src/values/BoolValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the boolean DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.BoolValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.BoolValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ true ], 32 | [ false ] 33 | ]; 34 | } 35 | 36 | } ); 37 | 38 | var test = new dv.tests.BoolValueTest(); 39 | 40 | test.runTests( 'dataValues.BoolValue' ); 41 | 42 | }( dataValues, util ) ); 43 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/IntParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, dv, util, $ ) { 2 | 'use strict'; 3 | 4 | var PARENT = vp.ValueParser; 5 | 6 | /** 7 | * Constructor for string-to-float parsers. 8 | * @class valueParsers.IntParser 9 | * @extends valueParsers.ValueParser 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author H. Snater < mediawiki@snater.com > 13 | * 14 | * @constructor 15 | */ 16 | vp.IntParser = util.inherit( PARENT, { 17 | /** 18 | * @inheritdoc 19 | * 20 | * @param {string} rawValue 21 | */ 22 | parse: function( rawValue ) { 23 | var deferred = $.Deferred(); 24 | 25 | // TODO: Localization, option to set integer base 26 | if( /^(-)?\d+$/.test( rawValue ) ) { 27 | deferred.resolve( new dv.NumberValue( parseInt( rawValue, 10 ) ) ); 28 | } 29 | 30 | deferred.reject( 'Unable to parse "' + rawValue + '"' ); 31 | 32 | return deferred.promise(); 33 | } 34 | } ); 35 | 36 | }( valueParsers, dataValues, util, jQuery ) ); 37 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/FloatParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, dv, util, $ ) { 2 | 'use strict'; 3 | 4 | var PARENT = vp.ValueParser; 5 | 6 | /** 7 | * Constructor for string-to-float parsers. 8 | * @class valueParsers.FloatParser 9 | * @extends valueParsers.ValueParser 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author H. Snater < mediawiki@snater.com > 13 | * 14 | * @constructor 15 | */ 16 | vp.FloatParser = util.inherit( PARENT, { 17 | /** 18 | * @inheritdoc 19 | * 20 | * @param {string} rawValue 21 | */ 22 | parse: function( rawValue ) { 23 | var deferred = $.Deferred(); 24 | 25 | // TODO: Localization 26 | if( !isNaN( parseFloat( rawValue ) ) && isFinite( rawValue ) ) { 27 | deferred.resolve( new dv.NumberValue( parseFloat( rawValue ) ) ); 28 | } 29 | 30 | deferred.reject( 'Unable to parse "' + rawValue + '"' ); 31 | 32 | return deferred.promise(); 33 | } 34 | } ); 35 | 36 | }( valueParsers, dataValues, util, jQuery ) ); 37 | -------------------------------------------------------------------------------- /tests/src/values/NumberValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the number DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.NumberValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.NumberValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ 0 ], 32 | [ 42 ], 33 | [ 4.2 ], 34 | [ -42 ], 35 | [ -4.2 ] 36 | ]; 37 | } 38 | 39 | } ); 40 | 41 | var test = new dv.tests.NumberValueTest(); 42 | 43 | test.runTests( 'dataValues.NumberValue' ); 44 | 45 | }( dataValues, util ) ); 46 | -------------------------------------------------------------------------------- /tests/src/values/StringValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the string DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.StringValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.StringValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ '' ], 32 | [ 'foo' ], 33 | [ ' foo bar baz foo bar baz. foo bar baz ' ] 34 | ]; 35 | } 36 | 37 | } ); 38 | 39 | var test = new dv.tests.StringValueTest(); 40 | 41 | test.runTests( 'dataValues.StringValue' ); 42 | 43 | }( dataValues, util ) ); 44 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/NullParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, dv, $, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = vp.ValueParser; 5 | 6 | /** 7 | * Constructor for null parsers. 8 | * Null parser will take any value for parsing. The parsed value 9 | * will be an UnknownValue data value except if null got passed in or a DataValue got passed in. 10 | * In those cases, the value given to the parse function will be the parse result. 11 | * @class valueParsers.NullParser 12 | * @extends valueParsers.ValueParser 13 | * @since 0.1 14 | * @license GPL-2.0+ 15 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 16 | * 17 | * @constructor 18 | */ 19 | vp.NullParser = util.inherit( PARENT, { 20 | /** 21 | * @inheritdoc 22 | */ 23 | parse: function( rawValue ) { 24 | var deferred = $.Deferred(), 25 | value = rawValue; 26 | 27 | if( value !== null && !( value instanceof dv.DataValue ) ) { 28 | value = new dv.UnknownValue( value ); 29 | } 30 | 31 | return deferred.resolve( value ).promise(); 32 | } 33 | } ); 34 | 35 | }( valueParsers, dataValues, jQuery, util ) ); 36 | -------------------------------------------------------------------------------- /tests/src/valueParsers/parsers/IntParser.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( vp, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vp.tests.ValueParserTest; 9 | 10 | /** 11 | * Constructor for creating a test object holding tests for the IntParser. 12 | * 13 | * @constructor 14 | * @extends dv.tests.ValueParserTest 15 | * @since 0.1 16 | */ 17 | vp.tests.IntParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vp.IntParser; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getParseArguments: function() { 30 | return [ 31 | // TODO: replace test stub 32 | [ '4', new dv.NumberValue( 4 ) ], 33 | [ '42', new dv.NumberValue( 42 ) ], 34 | [ '0', new dv.NumberValue( 0 ) ], 35 | [ '9001', new dv.NumberValue( 9001 ) ] 36 | ]; 37 | } 38 | 39 | } ); 40 | 41 | var test = new vp.tests.IntParserTest(); 42 | 43 | test.runTests( 'valueParsers.IntParser' ); 44 | 45 | }( valueParsers, dataValues, util ) ); 46 | -------------------------------------------------------------------------------- /tests/src/values/GlobeCoordinateValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( dv, util, GlobeCoordinate ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the globe coordinate DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.GlobeCoordinateValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.GlobeCoordinateValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ new GlobeCoordinate( { latitude: 1.5, longitude: 1.25, precision: 0.01 } ) ], 32 | [ new GlobeCoordinate( { latitude: -50, longitude: -20, precision: 1 } ) ] 33 | ]; 34 | } 35 | 36 | } ); 37 | 38 | var test = new dv.tests.GlobeCoordinateValueTest(); 39 | 40 | test.runTests( 'dataValues.GlobeCoordinateValue' ); 41 | 42 | }( dataValues, util, globeCoordinate.GlobeCoordinate ) ); 43 | -------------------------------------------------------------------------------- /tests/src/valueParsers/parsers/StringParser.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 4 | */ 5 | ( function( vp, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vp.tests.ValueParserTest; 9 | 10 | /** 11 | * Constructor for creating a test object holding tests for the StringParser. 12 | * 13 | * @constructor 14 | * @extends dv.tests.ValueParserTest 15 | * @since 0.1 16 | */ 17 | vp.tests.StringParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vp.StringParser; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getParseArguments: function() { 30 | return [ 31 | [ '42', new dv.StringValue( '42' ) ], 32 | [ ' foo ', new dv.StringValue( ' foo ' ) ], 33 | [ ' Baa', new dv.StringValue( ' Baa' ) ], 34 | [ 'xXx ', new dv.StringValue( 'xXx ' ) ], 35 | [ '', null ] 36 | ]; 37 | } 38 | 39 | } ); 40 | 41 | var test = new vp.tests.StringParserTest(); 42 | 43 | test.runTests( 'valueParsers.StringParser' ); 44 | 45 | }( valueParsers, dataValues, util ) ); 46 | -------------------------------------------------------------------------------- /tests/src/valueFormatters/formatters/StringFormatter.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( vf, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vf.tests.ValueFormatterTest; 9 | 10 | /** 11 | * Constructor for creating a test object containing tests for the StringFormatter. 12 | * 13 | * @constructor 14 | * @extends valueFormatters.tests.ValueFormatterTest 15 | * @since 0.1 16 | */ 17 | vf.tests.StringFormatterTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vf.StringFormatter; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getFormatArguments: function() { 30 | return [ 31 | [ new dv.StringValue( 'some string' ), 'some string' ], 32 | [ new dv.StringValue( ' foo ' ), ' foo ' ], 33 | [ new dv.StringValue( ' xXx' ), ' xXx' ], 34 | [ new dv.StringValue( 'xXx ' ), 'xXx ' ] 35 | ]; 36 | } 37 | 38 | } ); 39 | 40 | var test = new vf.tests.StringFormatterTest(); 41 | 42 | test.runTests( 'valueFormatters.StringFormatter' ); 43 | 44 | }( valueFormatters, dataValues, util ) ); 45 | -------------------------------------------------------------------------------- /tests/src/values/DecimalValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the DecimalValue DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.DecimalValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.DecimalValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ 0 ], 32 | [ 1 ], 33 | [ 1e30 ], 34 | [ 1.5e30 ], 35 | [ 1.5e-30 ], 36 | [ -1 ], 37 | [ -1.5e-30 ], 38 | [ '+0' ], 39 | [ '+1' ], 40 | [ '-0' ], 41 | [ '-1' ], 42 | [ '+100000000000000000' ], 43 | [ '-100000000000000000' ], 44 | [ '-0.1' ], 45 | [ '+0.1' ] 46 | ]; 47 | } 48 | 49 | } ); 50 | 51 | var test = new dv.tests.DecimalValueTest(); 52 | 53 | test.runTests( 'dataValues.DecimalValueTest' ); 54 | 55 | }( dataValues, util ) ); 56 | -------------------------------------------------------------------------------- /tests/src/values/UnknownValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the unknown DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.UnknownValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.UnknownValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ '' ], 32 | [ 'foo' ], 33 | [ ' foo bar baz foo bar baz. foo bar baz ' ], 34 | [ 0 ], 35 | [ 42 ], 36 | [ -4.2 ], 37 | [ { 'a': 'b' } ], 38 | [ [ 'foo', 9001, { 'bar': 'baz', 5: 5 } ] ], 39 | [ new Date() ], 40 | [ false ], 41 | [ true ], 42 | [ null ], 43 | [ [] ], 44 | [ {} ] 45 | ]; 46 | } 47 | 48 | } ); 49 | 50 | var test = new dv.tests.UnknownValueTest(); 51 | 52 | test.runTests( 'dataValues.UnknownValue' ); 53 | 54 | }( dataValues, util ) ); 55 | -------------------------------------------------------------------------------- /tests/src/valueParsers/parsers/FloatParser.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 4 | */ 5 | ( function( vp, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vp.tests.ValueParserTest; 9 | 10 | /** 11 | * Constructor for creating a test object holding tests for the FloatParser. 12 | * 13 | * @constructor 14 | * @extends dv.tests.ValueParserTest 15 | * @since 0.1 16 | */ 17 | vp.tests.FloatParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vp.FloatParser; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getParseArguments: function() { 30 | return [ 31 | [ '0', new dv.NumberValue( 0 ) ], 32 | [ '-0', new dv.NumberValue( 0 ) ], 33 | [ '42', new dv.NumberValue( 42 ) ], 34 | [ '4.2', new dv.NumberValue( 4.2 ) ], 35 | [ '-42', new dv.NumberValue( -42 ) ], 36 | [ '-4.2', new dv.NumberValue( -4.2 ) ], 37 | [ '-9000.2', new dv.NumberValue( -9000.2 ) ] 38 | ]; 39 | } 40 | 41 | } ); 42 | 43 | var test = new vp.tests.FloatParserTest(); 44 | 45 | test.runTests( 'valueParsers.FloatParser' ); 46 | 47 | }( valueParsers, dataValues, util ) ); 48 | -------------------------------------------------------------------------------- /tests/src/valueParsers/parsers/NullParser.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( vp, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vp.tests.ValueParserTest; 9 | 10 | /** 11 | * Constructor for creating a test object holding tests for the NullParser. 12 | * 13 | * @constructor 14 | * @extends dv.tests.ValueParserTest 15 | * @since 0.1 16 | */ 17 | vp.tests.NullParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vp.NullParser; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getParseArguments: function() { 30 | var date = new Date(), 31 | list = [ true, false, null ], 32 | dataValue = new dv.UnknownValue( 'foo' ); 33 | 34 | return [ 35 | [ dataValue, dataValue ], 36 | [ null, null ], 37 | [ '42', new dv.UnknownValue( '42' ) ], 38 | [ -4.2, new dv.UnknownValue( -4.2 ) ], 39 | [ date, new dv.UnknownValue( date ) ], 40 | [ list, new dv.UnknownValue( list ) ] 41 | ]; 42 | } 43 | 44 | } ); 45 | 46 | var test = new vp.tests.NullParserTest(); 47 | 48 | test.runTests( 'valueParsers.NullParser' ); 49 | 50 | }( valueParsers, dataValues, util ) ); 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: ['20.x', '22.x', '23.x'] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | - name: Setup Chrome 22 | uses: browser-actions/setup-chrome@latest 23 | with: 24 | chrome-version: stable 25 | 26 | - name: Configure Chrome Sandbox 27 | run: | 28 | sudo chown root:root /opt/hostedtoolcache/setup-chrome/chromium/stable/x64/chrome-sandbox 29 | sudo chmod 4755 /opt/hostedtoolcache/setup-chrome/chromium/stable/x64/chrome-sandbox 30 | 31 | - name: Install dependencies 32 | run: npm install 33 | 34 | - name: Run ESLint and tests 35 | run: npm test 36 | env: 37 | CHROME_BIN: chrome 38 | CHROME_FLAGS: --no-sandbox --headless --disable-gpu 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wikibase-data-values", 3 | "description": "JavaScript implementations of all basic DataValue classes, associated parsers and formatters", 4 | "keywords": [ 5 | "datavalues", 6 | "wikidata" 7 | ], 8 | "version": "0.10.0", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/wmde/DataValuesJavaScript" 12 | }, 13 | "contributors": [ 14 | { 15 | "name": "Daniel Werner", 16 | "url": "https://www.mediawiki.org/wiki/User:Danwe" 17 | }, 18 | { 19 | "name": "H. Snater", 20 | "url": "http://www.snater.com" 21 | }, 22 | { 23 | "name": "Jeroen De Dauw", 24 | "email": "jeroendedauw@gmail.com", 25 | "homepage": "http://jeroendedauw.com" 26 | } 27 | ], 28 | "bugs": { 29 | "url": "https://phabricator.wikimedia.org/" 30 | }, 31 | "license": "GPL-2.0+", 32 | "dependencies": { 33 | "jquery": "^3.2.1" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^4.18.2", 37 | "eslint-config-wikimedia": "0.4.0", 38 | "karma": "^6.1.0", 39 | "karma-cli": "^1.0.1", 40 | "karma-chrome-launcher": "^3.1.0", 41 | "karma-qunit": "^4.1.2", 42 | "qunit": "^2.14.0" 43 | }, 44 | "scripts": { 45 | "test": "npm run eslint && npm run run-tests", 46 | "eslint": "eslint .", 47 | "run-tests": "karma start --single-run" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/values/QuantityValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the QuantityValue DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.QuantityValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.QuantityValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ new dv.DecimalValue( 0 ), 'some unit', new dv.DecimalValue( 0 ), new dv.DecimalValue( 0 ) ], 32 | [ new dv.DecimalValue( 0 ), 'some unit', new dv.DecimalValue( -1 ), new dv.DecimalValue( 1 ) ], 33 | [ new dv.DecimalValue( 5 ), 'some unit', new dv.DecimalValue( 4 ), new dv.DecimalValue( 6 ) ], 34 | [ new dv.DecimalValue( 6 ), 'some unit', null, null ], 35 | [ new dv.DecimalValue( 7 ), 'some unit' ] 36 | ]; 37 | } 38 | 39 | } ); 40 | 41 | var test = new dv.tests.QuantityValueTest(); 42 | 43 | test.runTests( 'dataValues.QuantityValueTest' ); 44 | }( dataValues, util ) ); 45 | -------------------------------------------------------------------------------- /tests/src/values/MonolingualTextValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the MonolingualTextValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.MonolingualTextValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.MonolingualTextValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ 'en', '' ], 32 | [ 'de', 'foo' ], 33 | [ 'nl', ' foo bar baz foo bar baz. foo bar baz ' ] 34 | ]; 35 | }, 36 | 37 | /** 38 | * @see dataValues.tests.DataValueTest.createGetterTest 39 | */ 40 | testGetText: PARENT.createGetterTest( 1, 'getText' ), 41 | 42 | /** 43 | * @see dataValues.tests.DataValuesTest.createGetterTest 44 | */ 45 | testGetLanguageCode: PARENT.createGetterTest( 0, 'getLanguageCode' ) 46 | 47 | } ); 48 | 49 | var test = new dv.tests.MonolingualTextValueTest(); 50 | 51 | test.runTests( 'dataValues.MonolingualTextValue' ); 52 | 53 | }( dataValues, util ) ); 54 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/BoolParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, dv, util, $ ) { 2 | 'use strict'; 3 | 4 | var PARENT = vp.ValueParser; 5 | 6 | /** 7 | * Constructor for string-to-BoolValue parsers. 8 | * @class valueParsers.BoolParser 9 | * @extends valueParsers.ValueParser 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author H. Snater < mediawiki@snater.com > 13 | * 14 | * @constructor 15 | */ 16 | vp.BoolParser = util.inherit( PARENT, { 17 | /** 18 | * @inheritdoc 19 | * 20 | * @param {string} rawValue 21 | */ 22 | parse: function( rawValue ) { 23 | var deferred = $.Deferred(), 24 | lowerCaseRawValue = rawValue.toLowerCase(); 25 | 26 | for( var value in this.constructor.values ) { 27 | if( value === lowerCaseRawValue ) { 28 | deferred.resolve( new dv.BoolValue( this.constructor.values[value] ) ); 29 | break; 30 | } 31 | } 32 | 33 | deferred.reject( 'Unable to parse "' + rawValue + '"' ); 34 | 35 | return deferred.promise(); 36 | } 37 | } ); 38 | 39 | /** 40 | * Enum featuring the strings detecting a boolean value. 41 | * @property {Object} 42 | * @static 43 | */ 44 | vp.BoolParser.values = { 45 | 'yes': true, 46 | 'on': true, 47 | '1': true, 48 | 'true': true, 49 | 'no': false, 50 | 'off': false, 51 | '0': false, 52 | 'false': false 53 | }; 54 | 55 | }( valueParsers, dataValues, util, jQuery ) ); 56 | -------------------------------------------------------------------------------- /tests/src/valueFormatters/formatters/NullFormatter.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( vf, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vf.tests.ValueFormatterTest; 9 | 10 | /** 11 | * Constructor for creating a test object containing tests for the NullFormatter. 12 | * 13 | * @constructor 14 | * @extends valueFormatters.tests.ValueFormatterTest 15 | * @since 0.1 16 | */ 17 | vf.tests.NullParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vf.NullFormatter; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getFormatArguments: function() { 30 | var date = new Date(), 31 | list = [ true, false, null ]; 32 | 33 | return [ 34 | [ new dv.UnknownValue( 'foo' ), 'foo' ], 35 | [ null, null ], 36 | [ 'plain string', [ 'plain string', new dv.UnknownValue( 'plain string' ) ] ], 37 | [ -99.9, [ '-99.9', new dv.UnknownValue( -99.9 ) ] ], 38 | [ date, [ String( date ), new dv.UnknownValue( date ) ] ], 39 | [ list, [ String( list ), new dv.UnknownValue( list ) ] ] 40 | ]; 41 | } 42 | 43 | } ); 44 | 45 | var test = new vf.tests.NullParserTest(); 46 | 47 | test.runTests( 'valueFormatters.NullFormatter' ); 48 | 49 | }( valueFormatters, dataValues, util ) ); 50 | -------------------------------------------------------------------------------- /tests/src/valueParsers/parsers/BoolParser.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( vp, dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = vp.tests.ValueParserTest; 9 | 10 | /** 11 | * Constructor for creating a test object holding tests for the BoolParser. 12 | * 13 | * @constructor 14 | * @extends dv.tests.ValueParserTest 15 | * @since 0.1 16 | */ 17 | vp.tests.BoolParserTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return vp.BoolParser; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getParseArguments: function() { 30 | var validValues = { 31 | 'yes': true, 32 | 'on': true, 33 | '1': true, 34 | 'true': true, 35 | 'no': false, 36 | 'off': false, 37 | '0': false, 38 | 'false': false 39 | }; 40 | 41 | var argLists = []; 42 | 43 | // build a list with arrays as entries, [0] is parser input, [1] expected output: 44 | for ( var rawValue in validValues ) { 45 | if ( validValues.hasOwnProperty( rawValue ) ) { 46 | argLists.push( [ rawValue, new dv.BoolValue( validValues[rawValue] ) ] ); 47 | } 48 | } 49 | 50 | return argLists; 51 | } 52 | 53 | } ); 54 | 55 | var test = new vp.tests.BoolParserTest(); 56 | 57 | test.runTests( 'valueParsers.BoolParser' ); 58 | 59 | }( valueParsers, dataValues, util ) ); 60 | -------------------------------------------------------------------------------- /tests/src/values/MultilingualTextValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the MultilingualTextValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.MultilingualTextValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.MultilingualTextValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ [ new dv.MonolingualTextValue( 'en', '' ) ] ], 32 | [ [ new dv.MonolingualTextValue( 'de', 'foo' ) ] ], 33 | [ [ new dv.MonolingualTextValue( 'nl', ' foo bar baz foo bar baz. foo bar baz ' ) ] ], 34 | [ [ 35 | new dv.MonolingualTextValue( 'en', '' ), 36 | new dv.MonolingualTextValue( 'de', 'foo' ), 37 | new dv.MonolingualTextValue( 'nl', ' foo bar baz foo bar baz. foo bar baz ' ) 38 | ] ] 39 | ]; 40 | }, 41 | 42 | /** 43 | * @see dataValues.tests.DataValuesTest.createGetterTest 44 | */ 45 | testGetTexts: PARENT.createGetterTest( 0, 'getTexts' ) 46 | 47 | } ); 48 | 49 | var test = new dv.tests.MultilingualTextValueTest(); 50 | 51 | test.runTests( 'dataValues.MultilingualTextValue' ); 52 | 53 | }( dataValues, util ) ); 54 | -------------------------------------------------------------------------------- /src/valueFormatters/formatters/NullFormatter.js: -------------------------------------------------------------------------------- 1 | ( function( $, vf, dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = vf.ValueFormatter; 5 | 6 | /** 7 | * Null Formatter. 8 | * The Null Formatter formats any DataValue instance and may be used as a fallback for DataValue 9 | * instances that cannot be identified (e.g. due to missing implementation). The formatted value 10 | * will be the string casted result of the data value's toJSON() function. 11 | * If the data value could not be identified, the data value passed on to the $.Promise returned by 12 | * the format function will be an UnknownValue DataValue instance. 13 | * @class valueFormatters.NullFormatter 14 | * @extends valueFormatters.ValueFormatter 15 | * @since 0.1 16 | * @license GPL-2.0+ 17 | * @author H. Snater < mediawiki@snater.com > 18 | * 19 | * @constructor 20 | */ 21 | vf.NullFormatter = util.inherit( PARENT, { 22 | /** 23 | * @inheritdoc 24 | */ 25 | format: function( dataValue ) { 26 | var deferred = $.Deferred(); 27 | 28 | if( dataValue === null ) { 29 | return deferred.resolve( null, null ).promise(); 30 | } 31 | 32 | if( !( dataValue instanceof dv.DataValue ) ) { 33 | dataValue = new dv.UnknownValue( dataValue ); 34 | } 35 | 36 | var formatted = dataValue.toJSON(); 37 | 38 | if( formatted !== null ) { 39 | formatted = String( formatted ); 40 | } 41 | 42 | return deferred.resolve( formatted, dataValue ).promise(); 43 | } 44 | 45 | } ); 46 | 47 | }( jQuery, valueFormatters, dataValues, util ) ); 48 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( config ) { 2 | config.set( { 3 | frameworks: [ 'qunit' ], 4 | 5 | files: [ 6 | 'node_modules/jquery/dist/jquery.js', 7 | 'lib/util/util.inherit.js', 8 | 'lib/globeCoordinate/globeCoordinate.js', 9 | 'lib/globeCoordinate/globeCoordinate.GlobeCoordinate.js', 10 | 'src/dataValues.js', 11 | 'src/DataValue.js', 12 | 'src/values/*.js', 13 | 'src/valueFormatters/valueFormatters.js', 14 | 'src/valueFormatters/formatters/ValueFormatter.js', 15 | 'src/valueFormatters/formatters/*.js', 16 | 'src/valueParsers/valueParsers.js', 17 | 'src/valueParsers/ValueParserStore.js', 18 | 'src/valueParsers/parsers/ValueParser.js', 19 | 'src/valueParsers/parsers/*.js', 20 | 'tests/lib/globeCoordinate/*.js', 21 | 'tests/src/dataValues.tests.js', 22 | 'tests/src/dataValues.DataValue.tests.js', 23 | 'tests/src/values/*.js', 24 | 'tests/src/valueFormatters/valueFormatters.tests.js', 25 | 'tests/src/valueFormatters/formatters/*.js', 26 | 'tests/src/valueParsers/valueParsers.tests.js', 27 | 'tests/src/valueParsers/ValueParserStore.tests.js', 28 | 'tests/src/valueParsers/parsers/*.js' 29 | ], 30 | 31 | port: 9876, 32 | 33 | logLevel: config.LOG_INFO, 34 | browsers: [ 'ChromeHeadless' ], 35 | customLaunchers: { 36 | ChromeHeadless: { 37 | base: 'Chrome', 38 | flags: [ 39 | '--no-sandbox', 40 | '--headless', 41 | '--disable-gpu', 42 | '--disable-dev-shm-usage', 43 | '--disable-software-rasterizer' 44 | ] 45 | } 46 | } 47 | } ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/values/NumberValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value representing a number. 8 | * @class dataValues.NumberValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 13 | * 14 | * @constructor 15 | * 16 | * @param {number} value 17 | */ 18 | var SELF = dv.NumberValue = util.inherit( 'DvNumberValue', PARENT, function( value ) { 19 | // TODO: validate 20 | this._value = value; 21 | }, { 22 | /** 23 | * @property {number} 24 | * @private 25 | */ 26 | _value: null, 27 | 28 | /** 29 | * @inheritdoc 30 | * 31 | * @return {number} 32 | */ 33 | getValue: function() { 34 | return this._value; 35 | }, 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | equals: function( value ) { 41 | if ( !( value instanceof dv.NumberValue ) ) { 42 | return false; 43 | } 44 | 45 | return this.getValue() === value.getValue(); 46 | }, 47 | 48 | /** 49 | * @inheritdoc 50 | * 51 | * @return {number} 52 | */ 53 | toJSON: function() { 54 | return this._value; 55 | } 56 | } ); 57 | 58 | /** 59 | * @inheritdoc 60 | * 61 | * @return {dataValues.NumberValue} 62 | */ 63 | SELF.newFromJSON = function( json ) { 64 | return new SELF( json ); 65 | }; 66 | 67 | /** 68 | * @inheritdoc 69 | * @property {string} [TYPE='number'] 70 | * @static 71 | */ 72 | SELF.TYPE = 'number'; 73 | 74 | // make this data value available in the store: 75 | dv.registerDataValue( SELF ); 76 | 77 | }( dataValues, util ) ); 78 | -------------------------------------------------------------------------------- /src/values/UnknownValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value holding a value of unknown nature. 8 | * @class dataValues.UnknownValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 13 | * 14 | * @constructor 15 | * 16 | * @param {*} value 17 | */ 18 | var SELF = dv.UnknownValue = util.inherit( 'DvUnknownValue', PARENT, function( value ) { 19 | // TODO: validate 20 | this._value = value; 21 | }, { 22 | /** 23 | * @property {*} 24 | */ 25 | _value: null, 26 | 27 | /** 28 | * @inheritdoc 29 | * 30 | * @return {*} 31 | */ 32 | getValue: function() { 33 | return this._value; 34 | }, 35 | 36 | /** 37 | * Since the type of value is not known, it's not possible to perform a comparison always 38 | * correct and meaningful. Therefore, false negatives might be returned. 39 | * @inheritdoc 40 | */ 41 | equals: function( value ) { 42 | if ( !( value instanceof dv.UnknownValue ) ) { 43 | return false; 44 | } 45 | 46 | return this.getValue() === value.getValue(); 47 | }, 48 | 49 | /** 50 | * @inheritdoc 51 | * 52 | * @return {*} 53 | */ 54 | toJSON: function() { 55 | return this._value; 56 | } 57 | 58 | } ); 59 | 60 | /** 61 | * @inheritdoc 62 | * 63 | * @return {dataValues.UnknownValue} 64 | */ 65 | SELF.newFromJSON = function( json ) { 66 | return new SELF( json ); 67 | }; 68 | 69 | /** 70 | * @inheritdoc 71 | * @property {string} [TYPE='unknown'] 72 | * @static 73 | */ 74 | SELF.TYPE = 'unknown'; 75 | 76 | dv.registerDataValue( SELF ); 77 | 78 | }( dataValues, util ) ); 79 | -------------------------------------------------------------------------------- /src/values/BoolValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value representing a boolean. 8 | * @class dataValues.BoolValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 13 | * 14 | * @constructor 15 | * 16 | * @param {boolean} value 17 | * 18 | * @throws {Error} if value is not of type boolean. 19 | */ 20 | var SELF = dv.BoolValue = util.inherit( 'DvBoolValue', PARENT, function( value ) { 21 | if( typeof value !== 'boolean' ) { 22 | throw new Error( 'A boolean value has to be given' ); 23 | } 24 | this._value = value; 25 | }, { 26 | /** 27 | * @property {boolean} 28 | * @private 29 | */ 30 | _value: null, 31 | 32 | /** 33 | * @inheritdoc 34 | * 35 | * @return {boolean} 36 | */ 37 | getValue: function() { 38 | return this._value; 39 | }, 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | equals: function( value ) { 45 | if ( !( value instanceof dv.BoolValue ) ) { 46 | return false; 47 | } 48 | 49 | return this.getValue() === value.getValue(); 50 | }, 51 | 52 | /** 53 | * @inheritdoc 54 | * 55 | * @return {boolean} 56 | */ 57 | toJSON: function() { 58 | return this._value; 59 | } 60 | 61 | } ); 62 | 63 | /** 64 | * @inheritdoc 65 | * @static 66 | * 67 | * @return {dataValues.BoolValue} 68 | */ 69 | SELF.newFromJSON = function( json ) { 70 | return new dv.BoolValue( json ); 71 | }; 72 | 73 | /** 74 | * @inheritdoc 75 | * @property {string} [TYPE='boolean'] 76 | * @static 77 | */ 78 | SELF.TYPE = 'boolean'; 79 | 80 | dv.registerDataValue( SELF ); 81 | 82 | }( dataValues, util ) ); 83 | -------------------------------------------------------------------------------- /src/values/StringValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value representing a string. 8 | * @class dataValues.StringValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Daniel Werner 13 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 14 | * 15 | * @constructor 16 | * 17 | * @param {string} value 18 | * 19 | * @throws {Error} if value is not a string. 20 | */ 21 | var SELF = dv.StringValue = util.inherit( 'DvStringValue', PARENT, function( value ) { 22 | if( typeof value !== 'string' ) { 23 | throw new Error( 'A string value has to be given' ); 24 | } 25 | this._value = value; 26 | }, { 27 | /** 28 | * @property {string} 29 | * @private 30 | */ 31 | _value: null, 32 | 33 | /** 34 | * @inheritdoc 35 | * 36 | * @return {string} 37 | */ 38 | getValue: function() { 39 | return this._value; 40 | }, 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | equals: function( value ) { 46 | if ( !( value instanceof dv.StringValue ) ) { 47 | return false; 48 | } 49 | 50 | return this.getValue() === value.getValue(); 51 | }, 52 | 53 | /** 54 | * @inheritdoc 55 | * 56 | * @return {string} 57 | */ 58 | toJSON: function() { 59 | return this._value; 60 | } 61 | 62 | } ); 63 | 64 | /** 65 | * @inheritdoc 66 | * 67 | * @return {dataValues.StringValue} 68 | */ 69 | SELF.newFromJSON = function( json ) { 70 | return new SELF( json ); 71 | }; 72 | 73 | /** 74 | * @inheritdoc 75 | * @property {string} [TYPE='string'] 76 | * @static 77 | */ 78 | SELF.TYPE = 'string'; 79 | 80 | dv.registerDataValue( SELF ); 81 | 82 | }( dataValues, util ) ); 83 | -------------------------------------------------------------------------------- /src/valueFormatters/formatters/ValueFormatter.js: -------------------------------------------------------------------------------- 1 | ( function( $, vf, util ) { 2 | 'use strict'; 3 | 4 | /** 5 | * Base constructor for objects representing a value formatter. 6 | * @class valueFormatters.ValueFormatter 7 | * @abstract 8 | * @since 0.1 9 | * @license GPL-2.0+ 10 | * @author H. Snater < mediawiki@snater.com > 11 | * 12 | * @constructor 13 | */ 14 | var SELF = vf.ValueFormatter = function VpValueFormatter() { 15 | }; 16 | 17 | /** 18 | * @class valueFormatters.ValueFormatter 19 | */ 20 | $.extend( SELF.prototype, { 21 | 22 | /** 23 | * Formats a value. Will return a jQuery.Promise which will be resolved if formatting is 24 | * successful or rejected if it fails. There are various reasons why formatting could fail, e.g. 25 | * the formatter is using an API and the API cannot be reached. In case of success, the 26 | * callbacks will be passed a dataValues.DataValue object. In case of failure, the callback's 27 | * parameter will be an error object of some sort (not implemented yet!). 28 | * @abstract 29 | * 30 | * @param {dataValues.DataValue} dataValue 31 | * @return {Object} jQuery.Promise 32 | * @return {Function} return.done 33 | * @return {string|null} return.done.formatted Formatted DataValue. 34 | * @return {dataValues.DataValue|null} return.done.dataValue DataValue object that has been 35 | * formatted. 36 | * @return {Function} return.fail 37 | * @return {string} return.fail.message HTML error message. 38 | */ 39 | format: util.abstractMember 40 | // TODO: Specify Error object for formatter failure. Consider different error scenarios e.g. 41 | // API can not be reached or real formatting issues. 42 | // TODO: Think about introducing formatter warnings or a status object in done() callbacks. 43 | 44 | } ); 45 | 46 | }( jQuery, valueFormatters, util ) ); 47 | -------------------------------------------------------------------------------- /tests/src/values/UnDeserializableValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 4 | */ 5 | ( function( dv, util, $ ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the ununserializable DataValue. 12 | * 13 | * @constructor 14 | * @extends dv.tests.DataValueTest 15 | * @since 0.1 16 | */ 17 | dv.tests.UnDeserializableValueTest = util.inherit( PARENT, { 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | getConstructor: function() { 23 | return dv.UnDeserializableValue; 24 | }, 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | getConstructorArguments: function() { 30 | return [ 31 | [ {}, 'sometype', 'some error' ], 32 | [ { foo: 'bar' }, 'another-type', 'another error' ] 33 | ]; 34 | }, 35 | 36 | /** 37 | * Tests the getStructure method. 38 | * 39 | * @since 0.1 40 | * 41 | * @param {QUnit.assert} assert 42 | */ 43 | testGetStructure: function( assert ) { 44 | var instances = this.getInstances(), 45 | i, 46 | structure; 47 | 48 | for ( i in instances ) { 49 | structure = instances[i].getStructure(); 50 | 51 | assert.ok( 52 | $.isPlainObject( structure ), 53 | 'return value is plain object' 54 | ); 55 | 56 | assert.ok( 57 | structure !== instances[i].getStructure(), 58 | 'return value not returned by reference' 59 | ); 60 | } 61 | }, 62 | 63 | /** 64 | * @see dv.tests.DataValueTest.testJsonRoundtripping 65 | * 66 | * skip 67 | * TODO: activate after equals is implemented according to TODO in the data value's file 68 | */ 69 | testEquals: null 70 | } ); 71 | 72 | var test = new dv.tests.UnDeserializableValueTest(); 73 | 74 | test.runTests( 'dataValues.UnDeserializableValue' ); 75 | 76 | }( dataValues, util, jQuery ) ); 77 | -------------------------------------------------------------------------------- /tests/src/dataValues.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, $, QUnit ) { 6 | 'use strict'; 7 | 8 | QUnit.module( 'dataValues.js' ); 9 | 10 | QUnit.test( 'getDataValues', function( assert ) { 11 | var dvs = dv.getDataValues(); 12 | assert.expect( dvs.length * 2 + 1 ); 13 | 14 | assert.ok( Array.isArray( dvs ), 'Returns an array' ); 15 | 16 | for ( var i = 0, s = dvs.length; i < s; i++ ) { 17 | assert.ok( 18 | typeof dvs[i] === 'string', 19 | 'Returned DV type "' + dvs[i] + '" is a string' 20 | ); 21 | 22 | assert.ok( 23 | dv.hasDataValue( dvs[i] ), 24 | 'Returned DV type "' + dvs[i] + '" is present according to hasDataValue' 25 | ); 26 | } 27 | } ); 28 | 29 | QUnit.test( 'hasDataValue', function( assert ) { 30 | assert.expect( 2 ); 31 | // Already partially tested in getDataValues 32 | 33 | assert.strictEqual( 34 | dv.hasDataValue( 'in your code, being silly' ), 35 | false, 36 | 'Non existing DV type is not present' 37 | ); 38 | 39 | var dvs = dv.getDataValues(); 40 | 41 | assert.strictEqual( 42 | dv.hasDataValue( dvs.pop() ), 43 | true, 44 | 'Existing DV type is present' 45 | ); 46 | } ); 47 | 48 | QUnit.test( 'newDataValue', function( assert ) { 49 | assert.expect( 2 ); 50 | // This test needs dv.MonolingualTextValue to be loaded and registered 51 | 52 | var dataValue = dv.newDataValue( 53 | 'monolingualtext', 54 | { 55 | 'language': 'en', 56 | 'text': '~=[,,_,,]:3' 57 | } 58 | ); 59 | 60 | assert.strictEqual( 61 | dataValue.getText(), 62 | '~=[,,_,,]:3', 63 | 'Value was constructed and the text was set correctly' 64 | ); 65 | 66 | assert.strictEqual( 67 | dataValue.getLanguageCode(), 68 | 'en', 69 | 'Value was constructed and the language code was set correctly' 70 | ); 71 | } ); 72 | 73 | }( dataValues, jQuery, QUnit ) ); 74 | -------------------------------------------------------------------------------- /src/DataValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, $, util ) { 2 | 'use strict'; 3 | 4 | /** 5 | * Base constructor for objects representing a data value. DataValue objects are immutable, meaning 6 | * that the actual value can't be altered. 7 | * @class dataValues.DataValue 8 | * @abstract 9 | * @since 0.1 10 | * @license GPL-2.0+ 11 | * @author Daniel Werner 12 | * 13 | * @constructor 14 | * 15 | * @throws {Error} if no static TYPE is defined on the DataValue constructor. 16 | */ 17 | var SELF = dv.DataValue = function DvDataValue() { 18 | if( !this.constructor.TYPE ) { 19 | throw new Error( 'Can not create abstract DataValue of no specific type' ); 20 | } 21 | }; 22 | 23 | /** 24 | * Type of the DataValue. A static definition of the type like this has to be defined for all 25 | * DataValue implementations. 26 | * @property {string} [TYPE='null'] 27 | * @static 28 | */ 29 | SELF.TYPE = null; 30 | 31 | /** 32 | * @class dataValues.DataValue 33 | */ 34 | $.extend( SELF.prototype, { 35 | 36 | /** 37 | * Returns the most basic representation of this Object's value. 38 | * @abstract 39 | * 40 | * @return {*} 41 | */ 42 | getValue: util.abstractMember, 43 | 44 | /** 45 | * Returns a simple JSON structure representing this data value. 46 | * @abstract 47 | * 48 | * @return {*} 49 | */ 50 | toJSON: util.abstractMember, 51 | 52 | /** 53 | * Returns whether this value equals some other given value. 54 | * @abstract 55 | * 56 | * @param {*} dataValue 57 | * @return {boolean} 58 | */ 59 | equals: util.abstractMember, 60 | 61 | /** 62 | * Returns the type identifier for this data value. 63 | * 64 | * @return {string} 65 | */ 66 | getType: function() { 67 | return this.constructor.TYPE; 68 | } 69 | } ); 70 | 71 | /** 72 | * Instantiates a DataValue object from provided JSON. 73 | * @static 74 | * 75 | * @param {*} json 76 | * @return {dataValues.DataValue} 77 | */ 78 | SELF.newFromJSON = util.abstractMember; 79 | 80 | }( dataValues, jQuery, util ) ); 81 | -------------------------------------------------------------------------------- /src/valueParsers/parsers/ValueParser.js: -------------------------------------------------------------------------------- 1 | ( function( vp, $, util ) { 2 | 'use strict'; 3 | 4 | /** 5 | * Base constructor for objects representing a value parser. 6 | * @class valueParsers.ValueParser 7 | * @abstract 8 | * @since 0.1 9 | * @license GPL-2.0+ 10 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 11 | * 12 | * @constructor 13 | * 14 | * @param {Object} options 15 | */ 16 | var SELF = vp.ValueParser = function VpValueParser( options ) { 17 | this._options = $.extend( {}, options || {} ); 18 | }; 19 | 20 | /** 21 | * @class valueParsers.ValueParser 22 | */ 23 | $.extend( SELF.prototype, { 24 | /** 25 | * Parser options. 26 | * @property {Object} 27 | * @private 28 | */ 29 | _options: {}, 30 | 31 | /** 32 | * Returns the parser's options as set in the constructor. 33 | * 34 | * @return {Object} 35 | */ 36 | getOptions: function() { 37 | return $.extend( {}, this._options ); 38 | }, 39 | 40 | /** 41 | * Parses a value. Will return a jQuery.Promise which will be resolved if the parsing is 42 | * successful or rejected if it fails. There can be various reasons for the parsing to fail, 43 | * e.g. the parser is using the API and the API can't be reached. In case of success, the 44 | * callbacks will get a dataValues.DataValue object. In case of failure, the callback's 45 | * parameter will be an error object of some sort (not implemented yet!). 46 | * @abstract 47 | * 48 | * @param {*} rawValue 49 | * @return {Object} jQuery.Promise 50 | * @return {Function} return.done 51 | * @return {dataValues.DataValue|null} return.done.dataValue Parsed DataValue object or "null" 52 | * if empty. 53 | * @return {Function} return.fail 54 | * @return {string} return.fail.message HTML error message. 55 | */ 56 | parse: util.abstractMember 57 | // TODO: Specify Error object for parser failure. Consider different error scenarios e.g. 58 | // API can not be reached or real parsing issues. 59 | // TODO: Think about introducing parser warnings or a status object in done() callbacks. 60 | 61 | } ); 62 | 63 | }( valueParsers, jQuery, util ) ); 64 | -------------------------------------------------------------------------------- /src/dataValues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global 'dataValues' object. 3 | * @class dataValues 4 | * @singleton 5 | * @since 0.1 6 | * @license GPL-2.0+ 7 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 8 | */ 9 | this.dataValues = new ( function Dv() { 10 | 'use strict'; 11 | 12 | var dvs = []; 13 | 14 | /** 15 | * Returns the constructor associated with the provided DataValue type. 16 | * @ignore 17 | * 18 | * @param {string} dataValueType 19 | * @return {dataValues.DataValue} 20 | * 21 | * @throws {Error} if the data value type is unknown. 22 | */ 23 | function getDataValueConstructor( dataValueType ) { 24 | if ( dvs[dataValueType] !== undefined ) { 25 | return dvs[dataValueType]; 26 | } 27 | 28 | throw new Error( 'Unknown data value type "' + dataValueType + '" has no associated ' 29 | + 'DataValue class' ); 30 | } 31 | 32 | /** 33 | * Constructs and returns a new DataValue of specified type with the provided data. 34 | * 35 | * @param {string} dataValueType 36 | * @param {*} data 37 | * @return {dataValues.DataValue} 38 | */ 39 | this.newDataValue = function( dataValueType, data ) { 40 | return getDataValueConstructor( dataValueType ).newFromJSON( data ); 41 | }; 42 | 43 | /** 44 | * Returns the types of the registered DataValues. 45 | * 46 | * @return {string[]} 47 | */ 48 | this.getDataValues = function() { 49 | var keys = []; 50 | 51 | for ( var key in dvs ) { 52 | if ( dvs.hasOwnProperty( key ) ) { 53 | keys.push( key ); 54 | } 55 | } 56 | 57 | return keys; 58 | }; 59 | 60 | /** 61 | * Returns if there is a DataValue with the provided type. 62 | * 63 | * @param {string} dataValueType 64 | * @return {boolean} 65 | */ 66 | this.hasDataValue = function( dataValueType ) { 67 | return dvs[dataValueType] !== undefined; 68 | }; 69 | 70 | /** 71 | * Registers a data value. 72 | * If a data value with the provided name is registered already, the registration will be 73 | * overwritten with the newly provided data. 74 | * 75 | * @param {dataValues.DataValue} dataValueConstructor 76 | */ 77 | this.registerDataValue = function( dataValueConstructor ) { 78 | dvs[dataValueConstructor.TYPE] = dataValueConstructor; 79 | }; 80 | 81 | return this; 82 | 83 | } )(); 84 | -------------------------------------------------------------------------------- /src/values/GlobeCoordinateValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util, GlobeCoordinate ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value representing a globe coordinate. 8 | * @class dataValues.GlobeCoordinateValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author H. Snater < mediawiki@snater.com > 13 | * 14 | * @constructor 15 | * 16 | * @param {globeCoordinate.GlobeCoordinate} value 17 | * 18 | * @throws {Error} if value is not a globeCoordinate.GlobeCoordinate instance. 19 | */ 20 | var SELF 21 | = dv.GlobeCoordinateValue 22 | = util.inherit( 'DvGlobeCoordinateValue', PARENT, function( value ) { 23 | if( !( value instanceof GlobeCoordinate ) ) { 24 | throw new Error( 'The given value has to be a globeCoordinate.GlobeCoordinate ' 25 | + 'object' ); 26 | } 27 | 28 | this._value = value; 29 | }, 30 | { 31 | /** 32 | * @property {globeCoordinate.GlobeCoordinate} 33 | * @private 34 | */ 35 | _value: null, 36 | 37 | /** 38 | * @inheritdoc 39 | * 40 | * @return {globeCoordinate.GlobeCoordinate} 41 | */ 42 | getValue: function() { 43 | return this._value; 44 | }, 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | equals: function( value ) { 50 | if ( !( value instanceof SELF ) ) { 51 | return false; 52 | } 53 | return this.getValue().equals( value.getValue() ); 54 | }, 55 | 56 | /** 57 | * @inheritdoc 58 | * 59 | * @return {Object} 60 | */ 61 | toJSON: function() { 62 | var globeCoordinate = this.getValue(); 63 | 64 | return { 65 | latitude: globeCoordinate.getLatitude(), 66 | longitude: globeCoordinate.getLongitude(), 67 | globe: globeCoordinate.getGlobe(), 68 | precision: globeCoordinate.getPrecision() 69 | }; 70 | } 71 | } ); 72 | 73 | /** 74 | * @inheritdoc 75 | * 76 | * @return {dataValues.GlobeCoordinateValue} 77 | */ 78 | SELF.newFromJSON = function( json ) { 79 | var gc = new GlobeCoordinate( { 80 | latitude: json.latitude, 81 | longitude: json.longitude, 82 | globe: json.globe, 83 | precision: json.precision 84 | } ); 85 | 86 | return new SELF( gc ); 87 | }; 88 | 89 | /** 90 | * @inheritdoc 91 | * @property {string} [TYPE='globecoordinate'] 92 | * @static 93 | */ 94 | SELF.TYPE = 'globecoordinate'; 95 | 96 | dv.registerDataValue( SELF ); 97 | 98 | }( dataValues, util, globeCoordinate.GlobeCoordinate ) ); 99 | -------------------------------------------------------------------------------- /src/values/MonolingualTextValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a monolingual text value. A monolingual text is a string which is 8 | * dedicated to one specific language. 9 | * @class dataValues.MonolingualTextValue 10 | * @extends dataValues.DataValue 11 | * @since 0.1 12 | * @license GPL-2.0+ 13 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 14 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 15 | * 16 | * @constructor 17 | * 18 | * @param {string} languageCode 19 | * @param {string} text 20 | */ 21 | var SELF 22 | = dv.MonolingualTextValue 23 | = util.inherit( 'DvMonolingualTextValue', PARENT, function( languageCode, text ) { 24 | // TODO: validate 25 | this._languageCode = languageCode; 26 | this._text = text; 27 | }, 28 | { 29 | /** 30 | * @property {string} 31 | * @private 32 | */ 33 | _languageCode: null, 34 | 35 | /** 36 | * @property {string} 37 | * @private 38 | */ 39 | _text: null, 40 | 41 | /** 42 | * @inheritdoc 43 | * 44 | * @return {dataValues.MonolingualTextValue} 45 | */ 46 | getValue: function() { 47 | return this; 48 | }, 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | equals: function( value ) { 54 | if ( !( value instanceof dv.MonolingualTextValue ) ) { 55 | return false; 56 | } 57 | 58 | return this.getText() === value.getText() && this._languageCode === value.getLanguageCode(); 59 | }, 60 | 61 | /** 62 | * @inheritdoc 63 | * 64 | * @return {Object} 65 | */ 66 | toJSON: function() { 67 | return { 68 | 'text': this._text, 69 | 'language': this._languageCode 70 | }; 71 | }, 72 | 73 | /** 74 | * Returns the text. 75 | * 76 | * @return {string} 77 | */ 78 | getText: function() { 79 | return this._text; 80 | }, 81 | 82 | /** 83 | * Returns the language code of the value's language. 84 | * 85 | * @return {string} 86 | */ 87 | getLanguageCode: function() { 88 | return this._languageCode; 89 | } 90 | 91 | } ); 92 | 93 | /** 94 | * @inheritdoc 95 | * 96 | * @return {dataValues.MonolingualTextValue} 97 | */ 98 | SELF.newFromJSON = function( json ) { 99 | return new SELF( json.language, json.text ); 100 | }; 101 | 102 | /** 103 | * @inheritdoc 104 | * @property {string} [TYPE='monolingualtext'] 105 | * @static 106 | */ 107 | SELF.TYPE = 'monolingualtext'; 108 | 109 | dv.registerDataValue( SELF ); 110 | 111 | }( dataValues, util ) ); 112 | -------------------------------------------------------------------------------- /src/values/MultilingualTextValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a multilingual text value. A multilingual text is a collection of 8 | * monolingual text values with the same meaning in different languages. 9 | * @class dataValues.MultilingualTextValue 10 | * @extends dataValues.DataValue 11 | * @since 0.1 12 | * @license GPL-2.0+ 13 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 14 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 15 | * 16 | * @constructor 17 | * 18 | * @param {dataValues.MonolingualTextValue[]} monoLingualValues 19 | */ 20 | var SELF 21 | = dv.MultilingualTextValue 22 | = util.inherit( 'DvMultilingualTextValue', PARENT, function( monoLingualValues ) { 23 | // TODO: validate 24 | this._texts = monoLingualValues; 25 | }, 26 | { 27 | /** 28 | * @property {dataValues.MonolingualTextValue[]} 29 | * @private 30 | */ 31 | _texts: null, 32 | 33 | /** 34 | * @inheritdoc 35 | * 36 | * @return {dataValues.MultilingualTextValue} 37 | */ 38 | getValue: function() { 39 | return this; 40 | }, 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | equals: function( value ) { 46 | if ( !( value instanceof dv.MultilingualTextValue ) ) { 47 | return false; 48 | } 49 | 50 | var a = this.toJSON(), 51 | b = value.toJSON(); 52 | 53 | return !( a > b || b < a ); 54 | }, 55 | 56 | /** 57 | * @inheritdoc 58 | * 59 | * @return {Object} 60 | */ 61 | toJSON: function() { 62 | var texts = {}; 63 | 64 | for ( var i in this._texts ) { 65 | texts[ this._texts[i].getLanguageCode() ] = this._texts[i].getText(); 66 | } 67 | 68 | return texts; 69 | }, 70 | 71 | /** 72 | * Returns the text in all languages available. 73 | * 74 | * @return {string[]} 75 | */ 76 | getTexts: function() { 77 | return this._texts; 78 | } 79 | 80 | } ); 81 | 82 | /** 83 | * @inheritdoc 84 | * @return {dataValues.MultilingualTextValue} 85 | */ 86 | SELF.newFromJSON = function( json ) { 87 | var monolingualValues = []; 88 | 89 | for ( var languageCode in json ) { 90 | if ( json.hasOwnProperty( languageCode ) ) { 91 | monolingualValues.push( 92 | new dv.MonolingualTextValue( languageCode, json[languageCode] ) 93 | ); 94 | } 95 | } 96 | 97 | return new SELF( monolingualValues ); 98 | }; 99 | 100 | /** 101 | * @inheritdoc 102 | * @property {string} [TYPE='multilingualtext'] 103 | * @static 104 | */ 105 | SELF.TYPE = 'multilingualtext'; 106 | 107 | dv.registerDataValue( SELF ); 108 | 109 | }( dataValues, util ) ); 110 | -------------------------------------------------------------------------------- /tests/src/valueParsers/valueParsers.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 5 | */ 6 | ( function( vp, dv, util, $, QUnit ) { 7 | 'use strict'; 8 | 9 | vp.tests = {}; 10 | 11 | /** 12 | * Base constructor for ValueParser object tests. 13 | * 14 | * @constructor 15 | * @abstract 16 | * @since 0.1 17 | */ 18 | vp.tests.ValueParserTest = function() {}; 19 | vp.tests.ValueParserTest.prototype = { 20 | 21 | /** 22 | * Data provider that provides valid parse arguments. 23 | * 24 | * @since 0.1 25 | * 26 | * @return array 27 | */ 28 | getParseArguments: util.abstractMember, 29 | 30 | /** 31 | * Returns the ValueParser constructor to be tested. 32 | * 33 | * @since 0.1 34 | * 35 | * @return {Function} 36 | */ 37 | getConstructor: util.abstractMember, 38 | 39 | /** 40 | * Returns the ValueParser instance to be tested. 41 | * 42 | * @since 0.1 43 | * 44 | * @param {Array} constructorArguments 45 | * 46 | * @return {valueParsers.ValueParser} 47 | */ 48 | getInstance: function( constructorArguments ) { 49 | constructorArguments = constructorArguments || this.getDefaultConstructorArgs(); 50 | 51 | var 52 | self = this, 53 | ValueParserConstructor = function( constructorArguments ) { 54 | self.getConstructor().apply( this, constructorArguments ); 55 | }; 56 | 57 | ValueParserConstructor.prototype = this.getConstructor().prototype; 58 | return new ValueParserConstructor( constructorArguments ); 59 | }, 60 | 61 | getDefaultConstructorArgs: function() { 62 | return []; 63 | }, 64 | 65 | /** 66 | * Runs the tests. 67 | * 68 | * @since 0.1 69 | * 70 | * @param {string} moduleName 71 | */ 72 | runTests: function( moduleName ) { 73 | QUnit.module( moduleName ); 74 | 75 | var self = this; 76 | 77 | $.each( this, function( property, value ) { 78 | if ( property.substring( 0, 4 ) === 'test' && typeof self[property] === 'function' ) { 79 | QUnit.test( 80 | property, 81 | function( assert ) { 82 | self[property]( assert ); 83 | } 84 | ); 85 | } 86 | } ); 87 | }, 88 | 89 | /** 90 | * Tests the parse method. 91 | * 92 | * @since 0.1 93 | * 94 | * @param {QUnit.assert} assert 95 | */ 96 | testParse: function( assert ) { 97 | var parseArguments = this.getParseArguments(), 98 | parser = this.getInstance(); 99 | 100 | $.each( parseArguments, function( i, args ) { 101 | var parseInput = args[0], 102 | expected = args[1], 103 | inputDetailMsg = typeof parseInput === 'string' 104 | ? 'for input "' + parseInput + '" ' 105 | : '', 106 | done = assert.async(); 107 | 108 | parser.parse( parseInput ) 109 | .done( function( dataValue ) { 110 | // promise resolved, so no error has occured 111 | assert.ok( true, 'parsing succeeded' ); 112 | 113 | assert.ok( 114 | dataValue === null || ( dataValue instanceof dv.DataValue ), 115 | 'result ' + inputDetailMsg + 'is instanceof DataValue or null' 116 | ); 117 | 118 | if( expected !== undefined ) { 119 | assert.ok( 120 | dataValue === expected || dataValue.equals( expected ), 121 | 'result ' + inputDetailMsg + 'is equal to the expected DataValue' 122 | ); 123 | } 124 | } ) 125 | .fail( function( errorMessage ) { 126 | assert.ok( false, 'parsing ' + inputDetailMsg + 'failed: ' + errorMessage ); 127 | } ) 128 | .always( done ); 129 | } ); 130 | } 131 | 132 | }; 133 | 134 | }( valueParsers, dataValues, util, jQuery, QUnit ) ); 135 | -------------------------------------------------------------------------------- /src/values/UnDeserializableValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util, $ ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for creating a data value representing a value which could not have been unserialized 8 | * for some reason. Holds the serialized value which can not be unserialized as well as an error 9 | * object describing the reason why the value can not be unserialized properly. 10 | * @class dataValues.UnDeserializableValue 11 | * @extends dataValues.DataValue 12 | * @since 0.1 13 | * @license GPL-2.0+ 14 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 15 | * 16 | * @constructor 17 | * 18 | * @param {string} ofType The data value type the structure should have been deserialized to. 19 | * @param {Object} unDeserializableStructure Plain object assumingly representing some data value 20 | * but the responsible deserializer was not able to deserialize it. 21 | * @param {string} deserializeError The error thrown during the attempt to deserialize the given 22 | * structure. 23 | */ 24 | var SELF = dv.UnDeserializableValue = util.inherit( 25 | 'DvUnDeserializableValue', 26 | PARENT, 27 | function( unDeserializableStructure, ofType, deserializeError ) { 28 | if( !$.isPlainObject( unDeserializableStructure ) ) { 29 | throw new Error( 'The undeserializable structure has to be a plain object.' ); 30 | } 31 | if( typeof ofType !== 'string' ) { 32 | throw new Error( 'The undeserializable type must be a string.' ); 33 | } 34 | if( typeof deserializeError !== 'string' ) { 35 | throw new Error( 'The undeserializable error param must be a string.' ); 36 | } 37 | this._unDeserializableStructure = $.extend( {}, unDeserializableStructure ); 38 | this._targetType = ofType; 39 | this._deserializeError = deserializeError; 40 | }, 41 | { 42 | /** 43 | * @property {Object} 44 | * @private 45 | */ 46 | _unDeserializableStructure: null, 47 | 48 | /** 49 | * @property {string} 50 | * @private 51 | */ 52 | _targetType: null, 53 | 54 | /** 55 | * @property {string} 56 | * @private 57 | */ 58 | _deserializeError: null, 59 | 60 | /** 61 | * @inheritdoc 62 | * 63 | * @return {dataValues.UnDeserializableValue} 64 | */ 65 | getValue: function() { 66 | return this; 67 | }, 68 | 69 | /** 70 | * Returns the structure not possible to deserialize. 71 | * 72 | * @return {Object} 73 | */ 74 | getStructure: function() { 75 | return $.extend( {}, this._unDeserializableStructure ); 76 | }, 77 | 78 | /** 79 | * Returns the data value type into which the structure should have been deserialized. 80 | * 81 | * @return {string} 82 | */ 83 | getTargetType: function() { 84 | return this._targetType; 85 | }, 86 | 87 | /** 88 | * Returns the error object stating why some deserializer was not able to deserialize the 89 | * structure. 90 | * 91 | * @return {string} 92 | */ 93 | getReason: function() { 94 | return this._deserializeError; 95 | }, 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | equals: function( other ) { 101 | if( !( other instanceof SELF ) ) { 102 | return false; 103 | } 104 | 105 | return JSON.stringify( this.toJSON() ) === JSON.stringify( other.toJSON() ); 106 | }, 107 | 108 | /** 109 | * @inheritdoc 110 | */ 111 | toJSON: function() { 112 | return { 113 | value: this.getStructure(), 114 | type: this.getTargetType(), 115 | error: this.getReason() 116 | }; 117 | } 118 | } ); 119 | 120 | /** 121 | * @inheritdoc 122 | */ 123 | SELF.newFromJSON = function( json ) { 124 | return new SELF( 125 | json.value, 126 | json.type, 127 | json.error 128 | ); 129 | }; 130 | 131 | /** 132 | * @inheritdoc 133 | * @property {string} [TYPE='undeserializable'] 134 | * @static 135 | */ 136 | SELF.TYPE = 'undeserializable'; 137 | 138 | // NOTE: we don't have to register this one globally since this one is constructed on demand rather 139 | // than being provided by some store or builder. 140 | dv.registerDataValue( SELF ); 141 | 142 | }( dataValues, util, jQuery ) ); 143 | -------------------------------------------------------------------------------- /lib/globeCoordinate/globeCoordinate.GlobeCoordinate.js: -------------------------------------------------------------------------------- 1 | ( function( globeCoordinate ) { 2 | 'use strict'; 3 | 4 | /** 5 | * Globe coordinate object. 6 | * @class globeCoordinate.GlobeCoordinate 7 | * @license GPL-2.0+ 8 | * @author H. Snater < mediawiki@snater.com > 9 | * 10 | * @constructor 11 | * 12 | * @param {Object} gcDef Needs the following attributes: 13 | * - {number} latitude 14 | * - {number} longitude 15 | * - {number|null} [precision] 16 | * - {string|null} [globe] Defaults to http://www.wikidata.org/entity/Q2. 17 | * 18 | * @throws {Error} when latitude is greater than 360. 19 | * @throws {Error} when longitude is greater than 360. 20 | */ 21 | var SELF = globeCoordinate.GlobeCoordinate = function GlobeCoordinate( gcDef ) { 22 | if( !gcDef || typeof gcDef !== 'object' 23 | || gcDef.latitude === undefined 24 | || gcDef.longitude === undefined 25 | ) { 26 | throw new Error( 'No proper globe coordinate definition given' ); 27 | } 28 | 29 | this._latitude = gcDef.latitude; 30 | this._longitude = gcDef.longitude; 31 | this._precision = gcDef.precision || null; 32 | 33 | // TODO: Implement globe specific restrictions. The restrictions below 34 | // allow coordinates for Mars and other globes. 35 | if( Math.abs( this._latitude ) > 360 ) { 36 | throw new Error( 'Latitude (' + this._latitude + ') is out of bounds' ); 37 | } 38 | if( Math.abs( this._longitude ) > 360 ) { 39 | throw new Error( 'Longitude (' + this._longitude + ') is out of bounds' ); 40 | } 41 | 42 | this._globe = gcDef.globe || 'http://www.wikidata.org/entity/Q2'; 43 | }; 44 | 45 | /** 46 | * @class globeCoordinate.GlobeCoordinate 47 | */ 48 | SELF.prototype = { 49 | // Don't forget about "constructor" since we are overwriting the whole prototype here: 50 | constructor: SELF, 51 | 52 | /** 53 | * Globe URI 54 | * @property {string} 55 | * @private 56 | */ 57 | _globe: null, 58 | 59 | /** 60 | * Latitude (decimal) 61 | * @property {number} 62 | * @private 63 | */ 64 | _latitude: null, 65 | 66 | /** 67 | * Longitude (decimal) 68 | * @property {number} 69 | * @private 70 | */ 71 | _longitude: null, 72 | 73 | /** 74 | * Precision 75 | * @property {number|null} 76 | * @private 77 | */ 78 | _precision: null, 79 | 80 | /** 81 | * Returns the coordinate's globe URI. 82 | * 83 | * @return {string} 84 | */ 85 | getGlobe: function() { 86 | return this._globe; 87 | }, 88 | 89 | /** 90 | * Returns the decimal latitude. 91 | * 92 | * @return {number} 93 | */ 94 | getLatitude: function() { return this._latitude; }, 95 | 96 | /** 97 | * Returns the decimal longitude. 98 | * 99 | * @return {number} 100 | */ 101 | getLongitude: function() { return this._longitude; }, 102 | 103 | /** 104 | * Returns the precision. 105 | * 106 | * @return {number|null} 107 | */ 108 | getPrecision: function() { return this._precision; }, 109 | 110 | /** 111 | * Compares the object to another GlobeCoordinate object and returns whether both represent 112 | * the same information. 113 | * 114 | * @param {globeCoordinate.GlobeCoordinate} otherGlobeCoordinate 115 | * @return {boolean} 116 | */ 117 | equals: function( otherGlobeCoordinate ) { 118 | if ( !( otherGlobeCoordinate instanceof SELF ) 119 | || otherGlobeCoordinate._globe !== this._globe 120 | ) { 121 | return false; 122 | } 123 | 124 | // 0.00000001° corresponds to approx. 1 mm on Earth and can always be considered equal. 125 | var oneMillimeter = 0.00000001, 126 | epsilon = Math.max( 127 | // A change worth 1/2 precision might already become a visible change 128 | Math.min( this._precision, otherGlobeCoordinate._precision ) / 2, 129 | oneMillimeter 130 | ); 131 | 132 | return Math.abs( otherGlobeCoordinate._precision - this._precision ) < oneMillimeter 133 | && Math.abs( otherGlobeCoordinate._latitude - this._latitude ) < epsilon 134 | && Math.abs( otherGlobeCoordinate._longitude - this._longitude ) < epsilon; 135 | } 136 | }; 137 | 138 | }( globeCoordinate ) ); 139 | -------------------------------------------------------------------------------- /src/values/QuantityValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | var PARENT = dv.DataValue; 5 | 6 | /** 7 | * Constructor for a data value representing a quantity. 8 | * @class dataValues.QuantityValue 9 | * @extends dataValues.DataValue 10 | * @since 0.1 11 | * @license GPL-2.0+ 12 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 13 | * @author H. Snater < mediawiki@snater.com > 14 | * 15 | * @constructor 16 | * 17 | * @param {dataValues.DecimalValue} amount Numeric string or a number. 18 | * @param {string} unit A unit identifier. Must not be empty, use "1" for unit-less quantities. 19 | * @param {dataValues.DecimalValue|null} [upperBound] The upper bound of the quantity, inclusive. 20 | * @param {dataValues.DecimalValue|null} [lowerBound] The lower bound of the quantity, inclusive. 21 | * 22 | * @throws {Error} if constructor parameters are invalid. 23 | */ 24 | var SELF 25 | = dv.QuantityValue 26 | = util.inherit( 'DvQuantityValue', PARENT, function( amount, unit, upperBound, lowerBound ) { 27 | if ( !amount || !( amount instanceof dv.DecimalValue ) ) { 28 | throw new Error( 'amount needs to be a DecimalValue object' ); 29 | } 30 | 31 | if ( typeof unit !== 'string' || unit === '' ) { 32 | throw new Error( 'unit must be a non-empty string (use "1" for unit-less quantities)' ); 33 | } 34 | 35 | // Both can be null/undefined. But if one is set, both must be set. 36 | if ( upperBound || lowerBound ) { 37 | if ( !( upperBound instanceof dv.DecimalValue ) 38 | || !( lowerBound instanceof dv.DecimalValue ) 39 | ) { 40 | throw new Error( 'upperBound and lowerBound must both be defined or both undefined' ); 41 | } 42 | } 43 | 44 | this._amount = amount; 45 | this._unit = unit; 46 | this._lowerBound = lowerBound || null; 47 | this._upperBound = upperBound || null; 48 | }, 49 | { 50 | /** 51 | * @property {dataValues.DecimalValue} 52 | * @private 53 | */ 54 | _amount: null, 55 | 56 | /** 57 | * @property {string} 58 | * @private 59 | */ 60 | _unit: null, 61 | 62 | /** 63 | * @property {dataValues.DecimalValue|null} 64 | * @private 65 | */ 66 | _lowerBound: null, 67 | 68 | /** 69 | * @property {dataValues.DecimalValue|null} 70 | * @private 71 | */ 72 | _upperBound: null, 73 | 74 | /** 75 | * @inheritdoc 76 | * 77 | * @return {dataValues.QuantityValue} 78 | */ 79 | getValue: function() { 80 | return this; 81 | }, 82 | 83 | /** 84 | * Returns the unit held by this quantity. Returns null in case of unit-less quantities. 85 | * 86 | * @return {string|null} 87 | */ 88 | getUnit: function() { 89 | return this._unit; 90 | }, 91 | 92 | /** 93 | * @inheritdoc 94 | */ 95 | equals: function( that ) { 96 | if ( !( that instanceof this.constructor ) ) { 97 | return false; 98 | } 99 | 100 | return this._amount.equals( that._amount ) 101 | && this._unit === that._unit 102 | && ( this._upperBound === that._upperBound 103 | || ( this._upperBound && this._upperBound.equals( that._upperBound ) ) ) 104 | && ( this._lowerBound === that._lowerBound 105 | || ( this._lowerBound && this._lowerBound.equals( that._lowerBound ) ) ); 106 | }, 107 | 108 | /** 109 | * @inheritdoc 110 | * 111 | * @return {Object} 112 | */ 113 | toJSON: function() { 114 | var json = { 115 | amount: this._amount.toJSON(), 116 | unit: this._unit 117 | }; 118 | if ( this._upperBound && this._lowerBound ) { 119 | json.upperBound = this._upperBound.toJSON(); 120 | json.lowerBound = this._lowerBound.toJSON(); 121 | } 122 | return json; 123 | } 124 | } ); 125 | 126 | /** 127 | * @inheritdoc 128 | * 129 | * @return {dataValues.QuantityValue} 130 | */ 131 | SELF.newFromJSON = function( json ) { 132 | return new SELF( 133 | new dv.DecimalValue( json.amount ), 134 | json.unit, 135 | json.upperBound ? new dv.DecimalValue( json.upperBound ) : null, 136 | json.lowerBound ? new dv.DecimalValue( json.lowerBound ) : null 137 | ); 138 | }; 139 | 140 | /** 141 | * @inheritdoc 142 | * @property {string} [TYPE='quantity'] 143 | * @static 144 | */ 145 | SELF.TYPE = 'quantity'; 146 | 147 | dv.registerDataValue( SELF ); 148 | 149 | }( dataValues, util ) ); 150 | -------------------------------------------------------------------------------- /src/valueParsers/ValueParserStore.js: -------------------------------------------------------------------------------- 1 | ( function( $, vp ) { 2 | 'use strict'; 3 | 4 | /** 5 | * @ignore 6 | * 7 | * @param {Function} Parser 8 | * 9 | * @throws {Error} if the provided argument is not a valueParsers.ValueParser constructor. 10 | */ 11 | function assertIsValueParserConstructor( Parser ) { 12 | if( !( typeof Parser === 'function' && Parser.prototype instanceof vp.ValueParser ) ) { 13 | throw new Error( 'Invalid ValueParser constructor' ); 14 | } 15 | } 16 | 17 | /** 18 | * Store managing ValueParser instances. 19 | * @class valueParsers.ValueParserStore 20 | * @since 0.1 21 | * @license GPL-2.0+ 22 | * @author H. Snater < mediawiki@snater.com > 23 | * 24 | * @constructor 25 | * 26 | * @param {Function|null} [DefaultParser=null] Constructor of a default parser that shall be 27 | * returned when no parser is registered for a specific purpose. 28 | */ 29 | var SELF = vp.ValueParserStore = function VpValueParserStore( DefaultParser ) { 30 | this._DefaultParser = DefaultParser || null; 31 | this._parsersForDataTypes = {}; 32 | this._parsersForDataValueTypes = {}; 33 | }; 34 | 35 | /** 36 | * @class valueParsers.ValueParserStore 37 | */ 38 | $.extend( SELF.prototype, { 39 | /** 40 | * Default parser constructor to be returned when no parser is registered for a specific 41 | * purpose. 42 | * @property {Function|null} 43 | * @private 44 | */ 45 | _DefaultParser: null, 46 | 47 | /** 48 | * @property {Object} 49 | * @private 50 | */ 51 | _parsersForDataTypes: null, 52 | 53 | /** 54 | * @property {Object} 55 | * @private 56 | */ 57 | _parsersForDataValueTypes: null, 58 | 59 | /** 60 | * Registers a parser for a certain data type. 61 | * 62 | * @param {Function} Parser 63 | * @param {string} dataTypeId 64 | * 65 | * @throws {Error} if no data type id is specified. 66 | * @throws {Error} if a parser for the specified data type id is registered already. 67 | */ 68 | registerDataTypeParser: function( Parser, dataTypeId ) { 69 | assertIsValueParserConstructor( Parser ); 70 | 71 | if( dataTypeId === undefined ) { 72 | throw new Error( 'No proper data type id provided to register the parser for' ); 73 | } 74 | 75 | if( this._parsersForDataTypes[dataTypeId] ) { 76 | throw new Error( 'Parser for data type "' + dataTypeId + '" is registered ' 77 | + 'already' ); 78 | } 79 | 80 | this._parsersForDataTypes[dataTypeId] = Parser; 81 | }, 82 | 83 | /** 84 | * Registers a parser for a certain data value type. 85 | * 86 | * @param {Function} Parser 87 | * @param {string} dataValueType 88 | * 89 | * @throws {Error} if no data value type is specified. 90 | * @throws {Error} if a parser for the specified data value type is registered already. 91 | */ 92 | registerDataValueParser: function( Parser, dataValueType ) { 93 | assertIsValueParserConstructor( Parser ); 94 | 95 | if( dataValueType === undefined ) { 96 | throw new Error( 'No proper data value type provided to register the parser for' ); 97 | } 98 | 99 | if( this._parsersForDataValueTypes[dataValueType] ) { 100 | throw new Error( 'Parser for DataValue type "' + dataValueType + '" is registered ' 101 | + 'already' ); 102 | } 103 | 104 | this._parsersForDataValueTypes[dataValueType] = Parser; 105 | }, 106 | 107 | /** 108 | * Returns the ValueParser constructor registered for the specified purpose or the default 109 | * parser if no ValueParser is registered for that purpose. 110 | * 111 | * @param {string} dataValueType 112 | * @param {string} [dataTypeId] 113 | * @return {Function|null} 114 | * 115 | * @throws {Error} if no proper purpose is provided to retrieve a parser. 116 | */ 117 | getParser: function( dataValueType, dataTypeId ) { 118 | var parser; 119 | 120 | if( typeof dataTypeId === 'string' ) { 121 | parser = this._parsersForDataTypes[dataTypeId]; 122 | } 123 | 124 | if( !parser && typeof dataValueType === 'string' ) { 125 | parser = this._parsersForDataValueTypes[dataValueType]; 126 | } else if( !parser ) { 127 | throw new Error( 'No sufficient purpose provided for choosing a parser' ); 128 | } 129 | 130 | return parser || this._DefaultParser; 131 | } 132 | } ); 133 | 134 | }( jQuery, valueParsers ) ); 135 | -------------------------------------------------------------------------------- /tests/src/valueFormatters/valueFormatters.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( vf, util, $, QUnit ) { 6 | 'use strict'; 7 | 8 | vf.tests = {}; 9 | 10 | /** 11 | * Base constructor for ValueFormatter object tests 12 | * 13 | * @constructor 14 | * @abstract 15 | * @since 0.1 16 | */ 17 | vf.tests.ValueFormatterTest = function() {}; 18 | vf.tests.ValueFormatterTest.prototype = { 19 | 20 | /** 21 | * Data provider that provides valid format arguments. 22 | * @since 0.1 23 | * 24 | * @return {*[]} 25 | */ 26 | getFormatArguments: util.abstractMember, 27 | 28 | /** 29 | * Returns the ValueFormatter constructor to be tested. 30 | * @since 0.1 31 | * 32 | * @return {Function} 33 | */ 34 | getConstructor: util.abstractMember, 35 | 36 | /** 37 | * Returns the ValueFormatter instance to be tested. 38 | * @since 0.1 39 | * 40 | * @param {*[]} constructorArguments 41 | * @return {valueFormatters.ValueFormatter} 42 | */ 43 | getInstance: function( constructorArguments ) { 44 | constructorArguments = constructorArguments || this.getDefaultConstructorArgs(); 45 | 46 | var self = this; 47 | 48 | var ValueFormatterConstructor = function( constructorArguments ) { 49 | self.getConstructor().apply( this, constructorArguments ); 50 | }; 51 | 52 | ValueFormatterConstructor.prototype = this.getConstructor().prototype; 53 | return new ValueFormatterConstructor( constructorArguments ); 54 | }, 55 | 56 | getDefaultConstructorArgs: function() { 57 | return []; 58 | }, 59 | 60 | /** 61 | * Runs the tests. 62 | * @since 0.1 63 | * 64 | * @param {string} moduleName 65 | */ 66 | runTests: function( moduleName ) { 67 | QUnit.module( moduleName ); 68 | 69 | var self = this; 70 | 71 | $.each( this, function( property, value ) { 72 | if( property.substring( 0, 4 ) === 'test' && typeof self[property] === 'function' ) { 73 | QUnit.test( 74 | property, 75 | function( assert ) { 76 | self[property]( assert ); 77 | } 78 | ); 79 | } 80 | } ); 81 | }, 82 | 83 | /** 84 | * Tests the format method. 85 | * @since 0.1 86 | * 87 | * @param {QUnit.assert} assert 88 | */ 89 | testFormat: function( assert ) { 90 | var formatArguments = this.getFormatArguments(), 91 | formatter = this.getInstance(); 92 | 93 | $.each( formatArguments, function( i, args ) { 94 | var formatInput = args[0], 95 | expected = args[1], 96 | expectedValue = expected, 97 | expectedDataValue, 98 | inputDetailMsg = typeof formatInput === 'string' 99 | ? 'for input "' + formatInput + '" ' 100 | : '', 101 | done = assert.async(); 102 | 103 | if( Array.isArray( expected ) ) { 104 | expectedValue = expected[0]; 105 | expectedDataValue = expected[1]; 106 | } else { 107 | expectedDataValue = formatInput; 108 | } 109 | 110 | formatter.format( formatInput ) 111 | .done( function( formattedValue, dataValue ) { 112 | assert.ok( true, 'Formatting succeeded.' ); 113 | 114 | if( formattedValue === null ) { 115 | assert.strictEqual( 116 | formattedValue, 117 | null, 118 | 'Formatting result is null.' 119 | ); 120 | } else { 121 | assert.strictEqual( 122 | typeof formattedValue, 123 | 'string', 124 | 'Formatting result is a string: ' + formattedValue 125 | ); 126 | } 127 | 128 | assert.ok( 129 | expectedValue === formattedValue, 130 | 'Formatting result ' + inputDetailMsg + 'matches the expected result.' 131 | ); 132 | 133 | assert.ok( 134 | dataValue === expectedDataValue || dataValue.equals( expectedDataValue ), 135 | 'Returned DataValue ' + inputDetailMsg + 'is equal to the expected ' 136 | + 'DataValue.' 137 | ); 138 | } ) 139 | .fail( function( errorMessage ) { 140 | assert.ok( 141 | false, 142 | 'Formatting ' + inputDetailMsg + 'failed: ' + errorMessage 143 | ); 144 | } ) 145 | .always( done ); 146 | } ); 147 | } 148 | 149 | }; 150 | 151 | }( valueFormatters, util, jQuery, QUnit ) ); 152 | -------------------------------------------------------------------------------- /src/values/DecimalValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util ) { 2 | 'use strict'; 3 | 4 | /** 5 | * Regular expression for matching decimal strings that conform to the format defined for 6 | * DecimalValue. 7 | * @ignore 8 | */ 9 | var DECIMAL_VALUE_PATTERN = /^[+-]?(?:[1-9]\d*|\d)(?:\.\d+)?$/; 10 | 11 | /** 12 | * Checks whether a string conforms to the DecimalValue definition. 13 | * @ignore 14 | * 15 | * @param {string} decimalString 16 | * 17 | * @throws {Error} if string does not conform to the DecimalValue definition. 18 | */ 19 | function assertDecimalString( decimalString ) { 20 | if( typeof decimalString !== 'string' ) { 21 | throw new Error( 'Designated decimal string (' + decimalString + ') is not of type ' 22 | + 'string' ); 23 | } 24 | 25 | if( !DECIMAL_VALUE_PATTERN.test( decimalString ) ) { 26 | throw new Error( 'Designated decimal string (' + decimalString + ' does not match the ' 27 | + 'pattern for numeric values' ); 28 | } 29 | 30 | if( decimalString.length > 127 ) { 31 | throw new Error( 'Designated decimal string (' + decimalString + ') is longer than 127 ' 32 | + 'characters' ); 33 | } 34 | } 35 | 36 | /** 37 | * Converts a number of a string. This involves resolving the exponent (if any). 38 | * @ignore 39 | * 40 | * @param {number} number 41 | * @return {string} 42 | */ 43 | function convertNumberToString( number ) { 44 | var string = number.toString( 10 ), 45 | matches = string.match( /^(\d+)(\.(\d+))?e([-+]?)(\d+)$/i ); 46 | 47 | if( !matches ) { 48 | return string; 49 | } 50 | 51 | var integerPart = Math.abs( matches[1] ), 52 | fractionalPart = matches[3] || '', 53 | sign = matches[4], 54 | exponent = matches[5], 55 | numberOfZeros = ( sign === '-' ) ? exponent - 1 : exponent - fractionalPart.length, 56 | zerosToPad = ''; 57 | 58 | while( numberOfZeros-- ) { 59 | zerosToPad += '0'; 60 | } 61 | 62 | string = ( sign === '-' ) 63 | ? '0.' + zerosToPad + integerPart + fractionalPart 64 | : integerPart + fractionalPart + zerosToPad; 65 | 66 | if( number < 0 ) { 67 | string = '-' + string; 68 | } 69 | 70 | return string; 71 | } 72 | 73 | /** 74 | * Converts a number to a string confirming to the DecimalValue definition. 75 | * @ignore 76 | * 77 | * @param {number} number 78 | * @return {string} 79 | * 80 | * @throws {Error} if number is invalid 81 | */ 82 | function convertToDecimalString( number ) { 83 | if( typeof number !== 'number' || !isFinite( number ) ) { 84 | throw new Error( 'Number is invalid (NaN or not finite)' ); 85 | } 86 | 87 | var decimal = convertNumberToString( Math.abs( number ) ); 88 | decimal = ( ( number < 0 ) ? '-' : '+' ) + decimal; 89 | 90 | assertDecimalString( decimal ); 91 | 92 | return decimal; 93 | } 94 | 95 | var PARENT = dv.DataValue; 96 | 97 | /** 98 | * Constructor for a data value representing a decimal value. 99 | * @class dataValues.DecimalValue 100 | * @extends dataValues.DataValue 101 | * @since 0.1 102 | * @license GPL-2.0+ 103 | * @author H. Snater < mediawiki@snater.com > 104 | * 105 | * @param {string|number} value 106 | */ 107 | var SELF = dv.DecimalValue = util.inherit( 'DvDecimalValue', PARENT, function( value ) { 108 | if( typeof value === 'number' ) { 109 | value = convertToDecimalString( value ); 110 | } 111 | 112 | assertDecimalString( value ); 113 | 114 | this._value = value; 115 | }, { 116 | /** 117 | * @property {number} 118 | * @private 119 | */ 120 | _value: null, 121 | 122 | /** 123 | * @inheritdoc 124 | * 125 | * @return {string} 126 | */ 127 | getValue: function() { 128 | return this._value; 129 | }, 130 | 131 | /** 132 | * @inheritdoc 133 | */ 134 | equals: function( value ) { 135 | if ( !( value instanceof this.constructor ) ) { 136 | return false; 137 | } 138 | 139 | return this._value === value.getValue(); 140 | }, 141 | 142 | /** 143 | * @inheritdoc 144 | * 145 | * @return {string} 146 | */ 147 | toJSON: function() { 148 | return this._value; 149 | } 150 | } ); 151 | 152 | /** 153 | * @inheritdoc 154 | * 155 | * @return {dataValues.DecimalValue} 156 | */ 157 | SELF.newFromJSON = function( json ) { 158 | return new SELF( json ); 159 | }; 160 | 161 | /** 162 | * @inheritdoc 163 | * @property {string} [TYPE='decimal'] 164 | * @static 165 | */ 166 | SELF.TYPE = 'decimal'; 167 | 168 | dataValues.registerDataValue( SELF ); 169 | 170 | }( dataValues, util ) ); 171 | -------------------------------------------------------------------------------- /lib/util/util.inherit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class util 3 | * @singleton 4 | */ 5 | this.util = this.util || {}; 6 | 7 | ( function( util ) { 8 | 'use strict'; 9 | 10 | /** 11 | * Extends an object with the attributes of another object. 12 | * @ignore 13 | * 14 | * @param {Object} target 15 | * @param {Object} source 16 | * @return {Object} 17 | */ 18 | function extend( target, source ) { 19 | for( var v in source ) { 20 | if( source.hasOwnProperty( v ) ) { 21 | target[v] = source[v]; 22 | } 23 | } 24 | return target; 25 | } 26 | 27 | /** 28 | * Helper to create a function which will execute a given function. 29 | * @ignore 30 | * 31 | * @param {Function} [originalFn=function() {}] Optional function which will be executed by new 32 | * function. 33 | * @return {Function} 34 | */ 35 | function createFunction( originalFn ) { 36 | return originalFn 37 | ? function() { originalFn.apply( this, arguments ); } 38 | : function() {}; 39 | } 40 | 41 | /** 42 | * Helper for prototypical inheritance. 43 | * @license GPL-2.0+ 44 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 45 | * 46 | * @param {string|Function} [nameOrBase] The name of the new constructor (currently not used). 47 | * This is handy for debugging purposes since instances of the constructor might be 48 | * displayed under that name. 49 | * If a function is provided, it is assumed to be the constructor to be used for the 50 | * prototype chain (see next argument). 51 | * @param {Function} [baseOrConstructor] Constructor which will be used for the prototype 52 | * chain. This function will not be the constructor returned by the function but will be 53 | * called by it. 54 | * If no name is provided, this argument is assumed to be a constructor (see next 55 | * argument). 56 | * @param {Function|Object} [constructorOrMembers] Constructor to overwriting the base 57 | * constructor with. 58 | * If no name is provided, this argument is assumed to be an object with new prototype 59 | * members (see next argument). 60 | * @param {Object} [members={}] Properties overwriting or extending those of the base. 61 | * @return {Function} Constructor of the new, extended type. 62 | * 63 | * @throws {Error} in case a malicious function name is given or a reserved word is used. 64 | */ 65 | util.inherit = function( nameOrBase, baseOrConstructor, constructorOrMembers, members ) { 66 | var base = baseOrConstructor, 67 | constructor = constructorOrMembers; 68 | 69 | if( typeof nameOrBase !== 'string' ) { 70 | members = constructorOrMembers; 71 | constructor = baseOrConstructor; 72 | base = nameOrBase; 73 | } 74 | 75 | // allow to omit constructor since it can be inherited directly. But if given, require it as 76 | // second parameter for readability. If no constructor, second parameter is the prototype 77 | // extension object. 78 | if( !members ) { 79 | if( typeof constructor === 'function' ) { 80 | members = {}; 81 | } else { 82 | members = constructor || {}; 83 | constructor = false; 84 | } 85 | } 86 | 87 | // function we execute in our real constructor 88 | var NewConstructor = createFunction( constructor || base ); 89 | 90 | // new constructor for avoiding direct use of base constructor and its potential 91 | // side-effects 92 | var NewPrototype = createFunction(); 93 | NewPrototype.prototype = base.prototype; 94 | 95 | NewConstructor.prototype = extend( 96 | new NewPrototype(), 97 | members 98 | ); 99 | 100 | // Set "constructor" property properly, allow explicit overwrite via member definition. 101 | // NOTE: in IE < 9, overwritten "constructor" properties are still set as not enumerable, 102 | // so don't do this as part of the extend above. 103 | NewConstructor.prototype.constructor = 104 | members.hasOwnProperty( 'constructor' ) ? members.constructor : NewConstructor; 105 | 106 | return NewConstructor; 107 | }; 108 | 109 | /** 110 | * Throw a kind of meaningful error whenever the function should be overwritten when inherited. 111 | * 112 | * @example 113 | * SomethingAbstract.prototype = { 114 | * someFunc: function( a, b ) { doSomething() }, 115 | * someAbstractFunc: util.abstractFunction 116 | * }; 117 | * 118 | * @throws {Error} when called. 119 | */ 120 | util.abstractMember = function() { 121 | throw new Error( 'Call to undefined abstract function' ); 122 | }; 123 | 124 | }( util ) ); 125 | -------------------------------------------------------------------------------- /tests/src/values/TimeValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Daniel Werner < daniel.a.r.werner@gmail.com > 4 | */ 5 | ( function( dv, util ) { 6 | 'use strict'; 7 | 8 | var PARENT = dv.tests.DataValueTest; 9 | 10 | /** 11 | * Constructor for creating a test object for the `Time` `DataValue`. 12 | * @see dataValues.TimeValue 13 | * @class dataValues.tests.TimeValueTest 14 | * @extends dataValues.tests.DataValueTest 15 | * @since 0.1 16 | * 17 | * @constructor 18 | */ 19 | dv.tests.TimeValueTest = util.inherit( PARENT, { 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | getConstructor: function() { 25 | return dv.TimeValue; 26 | }, 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | getConstructorArguments: function() { 32 | return [ 33 | [ '+0000000000001942-04-01T00:00:00Z' ], 34 | 35 | // Optional parts 36 | [ '+0000000000001942-04-01T00:00:00' ], 37 | [ '+0000000000001942-04-01T00:00' ], 38 | [ '+0000000000001942-04-01T' ], 39 | [ '0000000000001942-04-01T' ], 40 | [ '1942-04-01T' ], 41 | 42 | // Minimal and maximal length 43 | [ '+1-1-1T1:1:1Z' ], 44 | [ '+9999999999999999-12-31T23:59:59Z' ], 45 | 46 | // Options 47 | [ '+0000000000001400-01-01T00:00:00Z', { 48 | calendarModel: 'http://www.wikidata.org/entity/Q1985786' 49 | } ], 50 | [ '-0000000000000042-00-00T00:00:00Z', { 51 | precision: 9 52 | } ] 53 | ]; 54 | }, 55 | 56 | /** 57 | * Tests if the constructor fails as expected for invalid and unsupported timestamp values. 58 | * 59 | * @since 0.7 60 | * 61 | * @param {QUnit.assert} assert 62 | */ 63 | testConstructorThrowsException: function( assert ) { 64 | var invalidTimestamps = [ 65 | // Non-strings 66 | undefined, 67 | null, 68 | 1, 69 | 0.1, 70 | 71 | // The "T" is required 72 | '', 73 | '1', 74 | '1942-04-01', 75 | '+0000000000002015-01-01 01:01:01Z', 76 | 77 | // Unsupported time zones 78 | '+0000000000002015-01-01T01:01:01A', 79 | '+0000000000002015-01-01T01:01:01+0000', 80 | '+0000000000002015-01-01T01:01:01+00:00' 81 | ]; 82 | var i, invalidTimestamp; 83 | 84 | for ( i = 0; i < invalidTimestamps.length; i++ ) { 85 | invalidTimestamp = invalidTimestamps[i]; 86 | 87 | assert.throws( 88 | function() { 89 | dv.TimeValue( invalidTimestamp ); 90 | }, 91 | '"' + invalidTimestamp + '" is not a valid TimeValue timestamp' 92 | ); 93 | } 94 | }, 95 | 96 | /** 97 | * Tests if the equals method is able to return false. 98 | * 99 | * @since 0.7 100 | * 101 | * @param {QUnit.assert} assert 102 | */ 103 | testNotEquals: function( assert ) { 104 | var timeValue1 = new dv.TimeValue( '2015-12-30T00:00:00Z' ), 105 | timeValue2 = new dv.TimeValue( '2015-12-31T00:00:00Z' ); 106 | 107 | assert.ok( 108 | !timeValue1.equals( timeValue2 ), 109 | 'instances encapsulating different values are not equal' 110 | ); 111 | }, 112 | 113 | /** 114 | * Tests the effect of the private pad() function, relevant in toJSON(). 115 | * 116 | * @since 0.7 117 | * 118 | * @param {QUnit.assert} assert 119 | */ 120 | testPad: function( assert ) { 121 | var testCases = { 122 | // Without leading zeros 123 | '-9000000000000000-12-31T23:59:59Z': [ 124 | '-9000000000000000-12-31T23:59:59Z' 125 | ], 126 | '-123456789012-00-00T00:00:00Z': [ 127 | '-123456789012-00-00T00:00:00Z' 128 | ], 129 | '-12345678901-00-00T00:00:00Z': [ 130 | '-12345678901-00-00T00:00:00Z' 131 | ], 132 | '-1234-1-1T01:01:01Z': [ 133 | '-1234-01-01T01:01:01Z' 134 | ], 135 | '-123-1-1T01:01:01Z': [ 136 | '-0123-01-01T01:01:01Z' 137 | ], 138 | '-12-1-1T01:01:01Z': [ 139 | '-0012-01-01T01:01:01Z' 140 | ], 141 | '-1-1-1T01:01:01Z': [ 142 | '-0001-01-01T01:01:01Z' 143 | ], 144 | '0-1-1T01:01:01Z': [ 145 | '+0000-01-01T01:01:01Z' 146 | ], 147 | '1-1-1T01:01:01Z': [ 148 | '+0001-01-01T01:01:01Z' 149 | ], 150 | '12-00-00T00:00:00Z': [ 151 | '+0012-00-00T00:00:00Z' 152 | ], 153 | '123-00-00T00:00:00Z': [ 154 | '+0123-00-00T00:00:00Z' 155 | ], 156 | '1234-00-00T00:00:00Z': [ 157 | '+1234-00-00T00:00:00Z' 158 | ], 159 | '1234567890-00-00T00:00:00Z': [ 160 | '+1234567890-00-00T00:00:00Z' 161 | ], 162 | '12345678901-00-00T00:00:00Z': [ 163 | '+12345678901-00-00T00:00:00Z' 164 | ], 165 | '123456789012-00-00T00:00:00Z': [ 166 | '+123456789012-00-00T00:00:00Z' 167 | ], 168 | '1234567890123456-00-00T00:00:00Z': [ 169 | '+1234567890123456-00-00T00:00:00Z' 170 | ], 171 | '9000000000000000-12-31T23:59:59Z': [ 172 | '+9000000000000000-12-31T23:59:59Z' 173 | ], 174 | 175 | // With leading zeros 176 | '-0900000000000000-12-31T23:59:59Z': [ 177 | '-900000000000000-12-31T23:59:59Z' 178 | ], 179 | '-0000000000000123-01-01T01:01:01Z': [ 180 | '-0123-01-01T01:01:01Z' 181 | ], 182 | '+0000000000000000-01-01T01:01:01Z': [ 183 | '+0000-01-01T01:01:01Z' 184 | ], 185 | '+0000000000000001-01-01T01:01:01Z': [ 186 | '+0001-01-01T01:01:01Z' 187 | ], 188 | '+0900000000000000-12-31T23:59:59Z': [ 189 | '+900000000000000-12-31T23:59:59Z' 190 | ], 191 | 192 | // Year would become 10000000000000000 when parsed as a number 193 | '-9999999999999999-12-31T23:59:59Z': [ 194 | '-9999999999999999-12-31T23:59:59Z' 195 | ], 196 | '9999999999999999-12-31T23:59:59Z': [ 197 | '+9999999999999999-12-31T23:59:59Z' 198 | ] 199 | }; 200 | 201 | for( var timestamp in testCases ) { 202 | var timeValue = new dv.TimeValue( timestamp ), 203 | json = timeValue.toJSON().time, 204 | expectedJSON = testCases[timestamp][0]; 205 | 206 | assert.ok( 207 | json === expectedJSON, 208 | 'Expected toJSON().time to return "' + expectedJSON + '", got "' + json + '"' 209 | ); 210 | } 211 | } 212 | 213 | } ); 214 | 215 | var test = new dv.tests.TimeValueTest(); 216 | 217 | test.runTests( 'dataValues.TimeValue' ); 218 | 219 | }( dataValues, util ) ); 220 | -------------------------------------------------------------------------------- /tests/lib/globeCoordinate/globeCoordinate.GlobeCoordinate.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | * @author Thiemo Mättig 5 | */ 6 | ( function( globeCoordinate, $, QUnit ) { 7 | 'use strict'; 8 | 9 | QUnit.module( 'globeCoordinate.GlobeCoordinate.js' ); 10 | 11 | QUnit.test( 'Basic checks', function( assert ) { 12 | assert.expect( 8 ); 13 | var c; 14 | 15 | assert.throws( 16 | function() { c = new globeCoordinate.GlobeCoordinate( '' ); }, 17 | 'Trying to instantiate with an empty value throws an error.' 18 | ); 19 | 20 | assert.throws( 21 | function() { c = new globeCoordinate.GlobeCoordinate( 'some string' ); }, 22 | 'Trying to instantiate with an invalid value (some string) throws an error.' 23 | ); 24 | 25 | assert.throws( 26 | function() { c = new globeCoordinate.GlobeCoordinate( '190° 30" 1.123\'' ); }, 27 | 'Trying to instantiate with an invalid value (190° 30" 1.123\') throws an error.' 28 | ); 29 | 30 | assert.throws( 31 | function() { c = new globeCoordinate.GlobeCoordinate( { latitude: 20 } ); }, 32 | 'Trying to instantiate with an invalid value ({ latitude: 20 }) throws an error.' 33 | ); 34 | 35 | c = new globeCoordinate.GlobeCoordinate( { latitude: 1.5, longitude: 1.5, precision: 0.1 } ); 36 | 37 | // Since most methods are just plain getters, just doing plain verification: 38 | 39 | assert.equal( 40 | c.getLatitude(), 41 | 1.5, 42 | 'Verified getLatitude()' 43 | ); 44 | 45 | assert.equal( 46 | c.getLongitude(), 47 | 1.5, 48 | 'Verified getLongitude()' 49 | ); 50 | 51 | assert.equal( 52 | c.getPrecision(), 53 | 0.1, 54 | 'Verified getPrecision()' 55 | ); 56 | 57 | assert.equal( 58 | c.getGlobe(), 59 | 'http://www.wikidata.org/entity/Q2', 60 | 'Verified getGlobe()' 61 | ); 62 | } ); 63 | 64 | QUnit.test( 'Precision defaults to null', function( assert ) { 65 | assert.expect( 2 ); 66 | var c = new globeCoordinate.GlobeCoordinate( { latitude: 0, longitude: 0 } ); 67 | 68 | assert.ok( 69 | c.getPrecision() === null, 70 | 'Verified getPrecision()' 71 | ); 72 | 73 | assert.ok( 74 | c.equals( c ), 75 | 'Validated equality' 76 | ); 77 | } ); 78 | 79 | QUnit.test( 'Costum globe', function( assert ) { 80 | assert.expect( 1 ); 81 | var c = new globeCoordinate.GlobeCoordinate( { 82 | latitude: 20, 83 | longitude: 25.5, 84 | globe: 'http://www.wikidata.org/entity/Q313' 85 | } ); 86 | 87 | assert.equal( 88 | c.getGlobe(), 89 | 'http://www.wikidata.org/entity/Q313', 90 | 'Verified getGlobe()' 91 | ); 92 | } ); 93 | 94 | QUnit.test( 'Strict (in)equality', function( assert ) { 95 | assert.expect( 169 ); 96 | var gcDefs = [ 97 | { latitude: 0, longitude: 0, precision: 1 }, 98 | { latitude: -3, longitude: 2, precision: 1 }, 99 | { latitude: 1.1, longitude: 2, precision: 0.1 }, 100 | { latitude: 11.92, longitude: 255.92, precision: 0.1 }, 101 | { latitude: 90, longitude: 30.1, precision: 0.01 }, 102 | { latitude: 0.1, longitude: 0.0075, precision: 1 / 3600 }, 103 | { latitude: -0.1, longitude: 0, precision: 1 / 60 }, 104 | { latitude: 1.00028, longitude: 0, precision: 1 / 3600 }, 105 | { latitude: 1.0005, longitude: 0, precision: 1 / 36000 }, 106 | { latitude: 89.9, longitude: -0.00031, precision: 1 / 3600000 }, 107 | { latitude: 5, longitude: -0.00292, precision: 1 / 36000 }, 108 | { latitude: 5, longitude: 2, precision: 1, globe: 'http://www.wikidata.org/entity/Q2' }, 109 | { latitude: 5, longitude: 2, precision: 1, globe: 'http://www.wikidata.org/entity/Q313' } 110 | ], 111 | c1, c2; 112 | 113 | $.each( gcDefs, function( i1, gcDef1 ) { 114 | c1 = new globeCoordinate.GlobeCoordinate( gcDef1 ); 115 | 116 | $.each( gcDefs, function( i2, gcDef2 ) { 117 | c2 = new globeCoordinate.GlobeCoordinate( gcDef2 ); 118 | 119 | if( gcDef1.latitude === gcDef2.latitude 120 | && gcDef1.longitude === gcDef2.longitude 121 | && gcDef1.precision === gcDef2.precision 122 | && ( gcDef1.globe || 'http://www.wikidata.org/entity/Q2' ) === 123 | ( gcDef2.globe || 'http://www.wikidata.org/entity/Q2' ) 124 | ) { 125 | assert.ok( 126 | c1.equals( c2 ), 127 | 'Validated equality for data set #' + i1 + '.' 128 | ); 129 | } else { 130 | assert.ok( 131 | !c1.equals( c2 ), 132 | 'Validated inequality of data set #' + i1 + ' to #' + i2 + '.' 133 | ); 134 | } 135 | } ); 136 | } ); 137 | 138 | } ); 139 | 140 | QUnit.test( 'Loose equality', function( assert ) { 141 | assert.expect( 7 ); 142 | var gcDefs = [ 143 | { latitude: 0, longitude: 0, precision: 1 }, 144 | { latitude: 0.01, longitude: 0, precision: 1 }, 145 | { latitude: 0.1, longitude: 0, precision: 1 }, 146 | { latitude: 0, longitude: 0.01, precision: 1 }, 147 | { latitude: 0, longitude: 0.1, precision: 1 }, 148 | { latitude: 0, longitude: 0, precision: 1.000000001 }, 149 | { latitude: 0, longitude: 0, precision: 1.00000001 } 150 | ], 151 | c1 = new globeCoordinate.GlobeCoordinate( gcDefs[0] ); 152 | 153 | $.each( gcDefs, function( i2, gcDef2 ) { 154 | var c2 = new globeCoordinate.GlobeCoordinate( gcDef2 ); 155 | assert.ok( 156 | c1.equals( c2 ), 157 | 'Validated equality of data set #0 to #' + i2 + '.' 158 | ); 159 | } ); 160 | 161 | } ); 162 | 163 | QUnit.test( 'Loose inequality', function( assert ) { 164 | assert.expect( 4 ); 165 | var c1 = new globeCoordinate.GlobeCoordinate( 166 | { latitude: 0, longitude: 0, precision: 1 / 3600 } 167 | ), 168 | gcDefs = [ 169 | { latitude: 0.0002, longitude: 0, precision: 1 / 3600 }, 170 | { latitude: 0, longitude: 0.0002, precision: 1 / 3600 }, 171 | { latitude: 0, longitude: 0, precision: 1 / 3600 + 0.0000001 }, 172 | { latitude: 0, longitude: 0, precision: 1 / 3600 - 0.0000001 } 173 | ]; 174 | 175 | $.each( gcDefs, function( i2, gcDef2 ) { 176 | var c2 = new globeCoordinate.GlobeCoordinate( gcDef2 ); 177 | assert.ok( 178 | !c1.equals( c2 ), 179 | 'Validated inequality to data set #' + i2 + '.' 180 | ); 181 | } ); 182 | 183 | } ); 184 | 185 | }( globeCoordinate, jQuery, QUnit ) ); 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DataValues JavaScript 2 | 3 | This library contains JavaScript implementations of all basic DataValue classes used in the 4 | [Wikibase software](http://wikiba.se/), along with associated parsers and formatters. This library 5 | mirrors most of the PHP implementations of DataValue classes as specified in the 6 | [DataValues set of libraries](https://github.com/DataValues) (most notably the 7 | [basic](https://github.com/DataValues/DataValues), 8 | [Geo](https://github.com/DataValues/Geo), 9 | [Number](https://github.com/DataValues/Number), and 10 | [Time](https://github.com/DataValues/Time) libraries). 11 | 12 | [![Build Status](https://github.com/wmde/DataValuesJavaScript/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/wmde/DataValuesJavaScript/actions/workflows/ci.yaml) 13 | 14 | ## Release notes 15 | ### 0.11.0 (dev) 16 | * Removed `globeCoordinate.GlobeCoordinate.getDecimal` 17 | * Removed `getLowerBound`, `getUpperBound`, `getAmount` from QuantityValue 18 | * Removed support for Node.js versions older than v10 19 | ### 0.10.0 (2017-10-06) 20 | * The library is now a pure JavaScript library. 21 | * Removed MediaWiki ResourceLoader module definitions. 22 | * Removed `globeCoordinate.Formatter`. 23 | * Removed the `globeCoordinate` utility class. 24 | 25 | ### 0.9.0 (2017-09-06) 26 | * Removed `valueFormatters.ValueFormatterStore`. 27 | * Removed the `options` constructor parameter as well as the `getOptions` method from 28 | `valueFormatters.ValueFormatter`. 29 | * Removed `dataValues.DataValue.getSortKey` from the interface and all implementations. 30 | * Removed `dataValues.TimeValue.getYear`, `getMonth`, `getDay`, `getHour`, `getMinute`, and 31 | `getSecond`. 32 | * Removed `globeCoordinate.GlobeCoordinate.iso6709`. 33 | * Declared `globeCoordinate.GlobeCoordinate.getDecimal` private. 34 | 35 | ### 0.8.4 (2017-07-18) 36 | * Updated JSDoc tags mistakenly requiring objects. 37 | * Removed an unused dependency on `composer/installers`. 38 | * Raised required PHP version from 5.3 to 5.5.9. 39 | 40 | ### 0.8.3 (2016-11-07) 41 | * `QuantityValue` now supports unknown upper and lower bounds, required for compatibility with 42 | DataValues Number 0.8.0. 43 | 44 | ### 0.8.2 (2016-04-12) 45 | * Added 1/10000 of an arcsecond as a known precision to `globeCoordinate.Formatter`. 46 | * Fixed `globeCoordinate.Formatter.PRECISIONTEXT` not properly supporting precisions lower than 47 | 1/1000 of an arcsecond. 48 | 49 | ### 0.8.1 (2016-04-08) 50 | * Added support for undefined `precision` to `globeCoordinate.GlobeCoordinate`. 51 | * Added `globe` support to `globeCoordinate.GlobeCoordinate`. 52 | 53 | ### 0.8.0 (2016-01-07) 54 | 55 | #### Breaking changes 56 | * `valueParsers.StringParser.parse` now resolves empty strings to `null`. 57 | * Renamed string `id`s for the 0 to 6 `dataValues.TimeValue.PRECISIONS` to be identical to the PHP 58 | constants in DataValues Time. 59 | 60 | ### 0.7.0 (2015-06-03) 61 | 62 | #### Breaking changes 63 | * Renamed `dataValues.UnUnserializableValue` to `dataValues.UnDeserializableValue`. 64 | * Changed constructor parameter order of `dataValues.UnDeserializableValue` (formerly 65 | `dataValues.UnUnserializableValue`). 66 | * Removed `time.js` legacy code, including `time.Time` and `time.Parser`. Every "vital" 67 | functionality has been ported to `dataValues.TimeValue` which now may be interacted with directly 68 | instead of having to retrieve the encapsulated `time.Time` object first. 69 | * Removed obsolete `valueParsers.TimeParser`. Back-end parser is to be used via API. 70 | * Removed obsolete `mw.ext.dataValues` module as it was just overwriting the obsolete `time.js` 71 | settings. Dependencies should be updated to point directly to the `dataValues.values` module. 72 | 73 | #### Enhancements 74 | * Implemented `toJSON` and `newFromJSON` in `dataValues.UnDeserializableValue`. 75 | * Consolidated code structure, updated and added code documentation to allow generating a proper 76 | documentation using JSDuck. 77 | 78 | ### 0.6.3 (2015-04-01) 79 | * Remove explicit resource loader dependency on jquery.qunit. 80 | 81 | ### 0.6.1 (2014-11-07) 82 | 83 | #### Enhancements 84 | * `Time` object's month and day attributes default to 0 instead of 1 now. 85 | * Fixed `Time.newFromIso8601()`. 86 | * Improved PhantomJS Testrunner, outputs failed assertions on the console now. 87 | * Improved `globeCoordinate.GlobeCoordinate.equals()` 88 | 89 | ### 0.6.0 (2014-09-01) 90 | 91 | #### Breaking changes 92 | 93 | * #40 Removed the arbitrary list of precisions for globe coordinates 94 | 95 | #### Enhancements 96 | 97 | * #44 Fixed comparing time values 98 | * #42 Removed 'to a degree' label, now shown as '±1°' 99 | * #45 Removed constructor naming debugging feature 100 | 101 | #### Bugfixes 102 | 103 | * Remove ResourceLoader dependencies on jquery and mediawiki (bug 69468) 104 | 105 | ### 0.5.1 (2014-06-04) 106 | 107 | #### Bugfixes 108 | 109 | * Don't limit precisions of globe coordinates in the UI (allows display of values with a non 110 | predefined precision) 111 | 112 | ### 0.5.0 (2014-03-28) 113 | 114 | #### Breaking changes 115 | 116 | * Renamed ValueFormatterFactory to ValueFormatterStore. 117 | * Renamed ValueParserFactory to ValueParserStore. 118 | * Removed mw.ext.valueFormatters and mw.ext.valueParsers. 119 | 120 | #### Enhancements 121 | 122 | * Defined parameters of the promises returned by ValueFormatter's and ValueParser's format/parse 123 | functions. 124 | 125 | ### 0.4.0 (2014-03-24) 126 | 127 | #### Breaking changes 128 | 129 | * mw.ext.valueParsers does not register valueParsers.TimeParser anymore 130 | * mw.ext.valueFormatters does not register valueFormatters.StringFormatter anymore 131 | * Renamed ValueFormatterFactory to ValueFormatterStore. 132 | * Renamed ValueParserFactory to ValueParserStore. 133 | 134 | #### Bugfixes 135 | 136 | * Fixed definitions of ResourceLoader test modules. 137 | * Accept timestamp strings with zeroes as months and days 138 | * Always return a string in time.writeYear and time.writeDay 139 | 140 | ### 0.3.1 (2014-02-03) 141 | 142 | #### Bugfixes 143 | 144 | * Fixed valueParsers ResourceLoader module definition template. 145 | 146 | ### 0.3.0 (2014-01-30) 147 | 148 | #### Breaking changes 149 | 150 | * Renamed "valueFormatters.factory" Resource Loader module to 151 | "valueFormatters.ValueFormatterFactory". 152 | * Renamed "valueParsers.factory" Resource Loader module to "valueParsers.ValueParserFactory". 153 | * Removed ValueView dependency from "mw.ext.valueFormatters" module and "mw.ext.valueParsers" 154 | module. 155 | 156 | ### 0.2.0 (2014-01-24) 157 | 158 | #### Breaking changes 159 | 160 | * #8 Removed dataValues.util.Notifier 161 | * #10 Renamed dataValues.util.inherit to util.inherit 162 | * #13 Removed vp.GlobeCoordinateParser and vp.QuantityParser 163 | * #15 Removed the ParseValue API module 164 | 165 | #### Enhancements 166 | 167 | * #14 Decoupled the QUnit tests from the MediaWiki resource loader 168 | * #16 Have the tests run on TravisCI using PhantomJS 169 | * #18 Provided QUnit test runner using requireJS 170 | 171 | ### 0.1.0 (2013-12-23) 172 | 173 | Initial release. 174 | -------------------------------------------------------------------------------- /tests/src/dataValues.DataValue.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author Jeroen De Dauw < jeroendedauw@gmail.com > 4 | */ 5 | ( function( dv, util, $, QUnit ) { 6 | 'use strict'; 7 | 8 | dv.tests = {}; 9 | 10 | /** 11 | * Base constructor for DataValue object tests. 12 | * 13 | * @constructor 14 | * @abstract 15 | * @since 0.1 16 | */ 17 | var SELF = dv.tests.DataValueTest = function DvTestsDataValueTest() {}; 18 | 19 | $.extend( SELF.prototype, { 20 | /** 21 | * Data provider that provides valid constructor arguments. 22 | * 23 | * @since 0.1 24 | * 25 | * @return array 26 | */ 27 | getConstructorArguments: util.abstractMember, 28 | 29 | /** 30 | * Returns the dataValue constructor to be tested (ie dv.StringValue). 31 | * 32 | * @since 0.1 33 | * 34 | * @return Function 35 | */ 36 | getConstructor: util.abstractMember, 37 | 38 | /** 39 | * Returns the dataValue object to be tested (ie dv.StringValue). 40 | * 41 | * @since 0.1 42 | * 43 | * @param {Array} constructorArguments 44 | * 45 | * @return dv.DataValue 46 | */ 47 | getInstance: function( constructorArguments ) { 48 | var self = this, 49 | OriginalConstructor = self.getConstructor(), 50 | DataValueConstructor = function( constructorArguments ) { 51 | OriginalConstructor.apply( this, constructorArguments ); 52 | }; 53 | 54 | DataValueConstructor.prototype = OriginalConstructor.prototype; 55 | 56 | return new DataValueConstructor( constructorArguments ); 57 | }, 58 | 59 | /** 60 | * Returns the dataValue object to be tested (ie dv.StringValue). 61 | * 62 | * @since 0.1 63 | * 64 | * @return dv.DataValue 65 | */ 66 | getInstances: function() { 67 | var self = this; 68 | 69 | return this.getConstructorArguments().map( function( constructorArguments ) { 70 | return self.getInstance( constructorArguments ); 71 | } ); 72 | }, 73 | 74 | /** 75 | * Runs the tests. 76 | * 77 | * @since 0.1 78 | * 79 | * @param {string} moduleName 80 | */ 81 | runTests: function( moduleName ) { 82 | QUnit.module( moduleName ); 83 | 84 | var self = this; 85 | 86 | $.each( this, function( property, value ) { 87 | if ( property.substring( 0, 4 ) === 'test' && typeof self[property] === 'function' ) { 88 | QUnit.test( 89 | property, 90 | function( assert ) { 91 | self[property]( assert ); 92 | } 93 | ); 94 | } 95 | } ); 96 | }, 97 | 98 | /** 99 | * Tests the constructor. 100 | * 101 | * @since 0.1 102 | * 103 | * @param {QUnit.assert} assert 104 | */ 105 | testConstructor: function( assert ) { 106 | var constructorArgs = this.getConstructorArguments(), 107 | i, 108 | instance; 109 | 110 | for ( i in constructorArgs ) { 111 | instance = this.getInstance( constructorArgs[i] ); 112 | 113 | assert.ok( 114 | instance instanceof dv.DataValue, 115 | 'is instance of DataValue' 116 | ); 117 | assert.ok( 118 | instance instanceof this.getConstructor(), 119 | 'is instance of actual data value implementation\'s constructor' 120 | ); 121 | assert.equal( 122 | typeof instance.getType(), 123 | 'string', 124 | 'getType method is present and returns string' 125 | ); 126 | } 127 | }, 128 | 129 | /** 130 | * Tests whether the data value's constructor has a newFromJSON function. 131 | * 132 | * @since 0.1 133 | * 134 | * @param {QUnit.assert} assert 135 | */ 136 | testNewFromJSON: function( assert ) { 137 | var fnNewFromJSON = this.getConstructor().newFromJSON; 138 | 139 | assert.ok( 140 | typeof fnNewFromJSON === 'function', 141 | 'has a related newFromJSON function' 142 | ); 143 | }, 144 | 145 | /** 146 | * Tests the toJSON method. 147 | * 148 | * @since 0.1 149 | * 150 | * @param {QUnit.assert} assert 151 | */ 152 | testToJSON: function( assert ) { 153 | var instances = this.getInstances(), 154 | i, 155 | jsonValue; 156 | 157 | for ( i in instances ) { 158 | jsonValue = instances[i].toJSON(); 159 | 160 | assert.ok( 161 | jsonValue !== undefined, 162 | 'toJSON() returned some value' 163 | ); 164 | } 165 | }, 166 | 167 | /** 168 | * Gets a data values JSON, constructs a new data value from it by using the newFromJSON 169 | * and checks whether the two are equal. 170 | * 171 | * @param {QUnit.assert} assert 172 | */ 173 | testJsonRoundtripping: function( assert ) { 174 | var instances = this.getInstances(), 175 | fnNewFromJSON = this.getConstructor().newFromJSON, 176 | i, 177 | value1, 178 | value2, 179 | jsonValue; 180 | 181 | for ( i in instances ) { 182 | value1 = instances[i]; 183 | jsonValue = value1.toJSON(); 184 | value2 = fnNewFromJSON( jsonValue ); 185 | 186 | assert.ok( 187 | value1.equals( value2 ) && value2.equals( value1 ), 188 | 'DataValue created from another DataValue\'s JSON is equal to its donor (' 189 | + JSON.stringify( jsonValue ) + ' -> ' 190 | + JSON.stringify( value2.toJSON() ) + ').' 191 | ); 192 | } 193 | }, 194 | 195 | /** 196 | * Tests the getValue method. 197 | * 198 | * @since 0.1 199 | * 200 | * @param {QUnit.assert} assert 201 | */ 202 | testGetValue: function( assert ) { 203 | var instances = this.getInstances(), 204 | i, 205 | value; 206 | 207 | for ( i in instances ) { 208 | value = instances[i].getValue(); 209 | assert.ok( true ); // TODO: add meaningful assertion 210 | } 211 | }, 212 | 213 | /** 214 | * Tests the equals method. 215 | * 216 | * @since 0.1 217 | * 218 | * @param {QUnit.assert} assert 219 | */ 220 | testEquals: function( assert ) { 221 | var instances = this.getInstances(), 222 | instances2 = this.getInstances(), 223 | i; 224 | 225 | for ( i in instances ) { 226 | assert.ok( 227 | instances[i].equals( instances[i] ), 228 | 'instance is equal to itself' 229 | ); 230 | 231 | if ( instances[i].getType() !== 'unknown' ) { 232 | assert.ok( 233 | instances[i] !== instances2[i] && instances[i].equals( instances2[i] ), 234 | 'instances is equal to another instance encapsulating the same value' 235 | ); 236 | } 237 | } 238 | } 239 | 240 | } ); 241 | 242 | /** 243 | * Creates and returns a test method for a getter. 244 | * Only works for simpler getters, ie those that return one of the arguments provided to the constructor. 245 | * 246 | * @since 0.1 247 | * 248 | * @param {number} argNumber 249 | * @param {string} functionName 250 | * 251 | * @return {Function} 252 | */ 253 | SELF.createGetterTest = function( argNumber, functionName ) { 254 | return function() { 255 | var 256 | constructorArgs = this.getConstructorArguments(), 257 | i, 258 | instance; 259 | 260 | for ( i in constructorArgs ) { 261 | instance = this.getInstance( constructorArgs[i] ); 262 | 263 | QUnit.assert.strictEqual( 264 | instance[functionName](), 265 | constructorArgs[i][argNumber], 266 | functionName + ' must return the value that was provided as argument ' + argNumber + ' in the constructor' 267 | ); 268 | } 269 | }; 270 | }; 271 | 272 | }( dataValues, util, jQuery, QUnit ) ); 273 | -------------------------------------------------------------------------------- /src/values/TimeValue.js: -------------------------------------------------------------------------------- 1 | ( function( dv, util, $ ) { 2 | 'use strict'; 3 | 4 | /** 5 | * @ignore 6 | * 7 | * @param {number|string} number 8 | * @param {number} digits 9 | * @return {string} 10 | */ 11 | function pad( number, digits ) { 12 | if( typeof number !== 'string' ) { 13 | number = String( number ); 14 | } 15 | 16 | // Strip sign characters. 17 | number = number.replace( /^[-+]/, '' ); 18 | 19 | if ( number.length >= digits ) { 20 | return number; 21 | } 22 | 23 | return new Array( digits - number.length + 1 ).join( '0' ) + number; 24 | } 25 | 26 | var PARENT = dv.DataValue; 27 | 28 | /** 29 | * `DataValue` for time values. 30 | * @class dataValues.TimeValue 31 | * @extends dataValues.DataValue 32 | * @since 0.1 33 | * @license GPL-2.0+ 34 | * @author H. Snater < mediawiki@snater.com > 35 | * @author Thiemo Mättig 36 | * 37 | * @constructor 38 | * 39 | * @param {string} timestamp 40 | * @param {Object} [options] 41 | * @param {string} [options.calendarModel=dataValues.TimeValue.CALENDARS.GREGORIAN] 42 | * Wikidata URL of the calendar model. 43 | * @param {number} [options.precision=dataValues.TimeValue.PRECISIONS.DAY] 44 | * @param {number} [options.before=0] 45 | * @param {number} [options.after=0] 46 | * @param {number} [options.timezone=0] 47 | * 48 | * @throws {Error} if `timestamp` is not a valid YMD-ordered timestamp string resembling ISO 8601. 49 | */ 50 | var SELF = dv.TimeValue = util.inherit( 'DvTimeValue', PARENT, function( timestamp, options ) { 51 | try { 52 | var matches = /^([-+]?\d+)-(\d+)-(\d+)T(?:(\d+):(\d+)(?::(\d+))?Z?)?$/.exec( timestamp ); 53 | 54 | // Strip additional leading zeros from the year, but keep 4 digits. 55 | var year = matches[1].replace( /\b0+(?=\d{4})/, '' ), 56 | month = parseInt( matches[2], 10 ), 57 | day = parseInt( matches[3], 10 ), 58 | hour = parseInt( matches[4] || 0, 10 ), 59 | minute = parseInt( matches[5] || 0, 10 ), 60 | second = parseInt( matches[6] || 0, 10 ); 61 | 62 | this.timestamp = ( year.charAt( 0 ) === '-' ? '-' : '+' ) 63 | + pad( year, 4 ) + '-' 64 | + pad( month, 2 ) + '-' 65 | + pad( day, 2 ) + 'T' 66 | + pad( hour, 2 ) + ':' 67 | + pad( minute, 2 ) + ':' 68 | + pad( second, 2 ) + 'Z'; 69 | } catch( e ) { 70 | throw new Error( 'Unable to process timestamp "' + timestamp + '"' ); 71 | } 72 | 73 | this._options = { 74 | calendarModel: SELF.CALENDARS.GREGORIAN, 75 | precision: SELF.getPrecisionById( 'DAY' ), 76 | before: 0, 77 | after: 0, 78 | timezone: 0 79 | }; 80 | 81 | var self = this; 82 | 83 | $.each( options || {}, function( key, value ) { 84 | self._setOption( key, value ); 85 | } ); 86 | }, { 87 | /** 88 | * @property {string} 89 | * @private 90 | */ 91 | timestamp: null, 92 | 93 | /** 94 | * @property {Object} 95 | * @private 96 | */ 97 | _options: null, 98 | 99 | /** 100 | * @protected 101 | * 102 | * @param {string} key 103 | * @param {*} value 104 | * 105 | * @throws {Error} if a value to set is not specified properly. 106 | */ 107 | _setOption: function( key, value ) { 108 | if( key === 'calendarModel' && !SELF.getCalendarModelKeyByUri( value ) ) { 109 | throw new Error( 'Setting ' + key + ': No valid calendar model URI provided' ); 110 | } 111 | if( $.inArray( key, [ 'precision', 'before', 'after', 'timezone' ] ) !== -1 112 | && typeof value !== 'number' 113 | ) { 114 | throw new Error( 'Setting ' + key + ': Expected "number" type' ); 115 | } 116 | if( key === 'precision' && ( value < 0 || value > SELF.PRECISIONS.length ) ) { 117 | throw new Error( 'Setting ' + key + ': No valid precision provided' ); 118 | } 119 | 120 | this._options[key] = value; 121 | }, 122 | 123 | /** 124 | * @since 0.7 125 | * 126 | * @param {string} key 127 | * @return {*} 128 | */ 129 | getOption: function( key ) { 130 | return this._options[key]; 131 | }, 132 | 133 | /** 134 | * @inheritdoc 135 | * 136 | * @return {Object} 137 | */ 138 | getValue: function() { 139 | return this.toJSON(); 140 | }, 141 | 142 | /** 143 | * @inheritdoc 144 | */ 145 | equals: function( value ) { 146 | if( !( value instanceof SELF ) ) { 147 | return false; 148 | } 149 | 150 | var valueJSON = value.toJSON(), 151 | match = true; 152 | 153 | $.each( this.toJSON(), function( key, value ) { 154 | if( valueJSON[key] !== value ) { 155 | match = false; 156 | } 157 | return match; 158 | } ); 159 | 160 | return match; 161 | }, 162 | 163 | /** 164 | * @inheritdoc 165 | * 166 | * @return {Object} 167 | */ 168 | toJSON: function() { 169 | return { 170 | after: this._options.after, 171 | before: this._options.before, 172 | calendarmodel: this._options.calendarModel, 173 | precision: this._options.precision, 174 | time: this.timestamp, 175 | timezone: this._options.timezone 176 | }; 177 | } 178 | 179 | } ); 180 | 181 | /** 182 | * @inheritdoc 183 | * 184 | * @return {dataValues.TimeValue} 185 | */ 186 | SELF.newFromJSON = function( json ) { 187 | return new SELF( json.time, { 188 | after: json.after, 189 | before: json.before, 190 | calendarModel: json.calendarmodel, 191 | precision: json.precision, 192 | timezone: json.timezone 193 | } ); 194 | }; 195 | 196 | /** 197 | * @inheritdoc 198 | * @property {string} [TYPE='time'] 199 | * @static 200 | */ 201 | SELF.TYPE = 'time'; 202 | 203 | // TODO: Inject configurations... 204 | /** 205 | * Known calendar model URIs. 206 | * @property {Object} 207 | * @static 208 | * @since 0.7 209 | */ 210 | SELF.CALENDARS = { 211 | GREGORIAN: 'http://www.wikidata.org/entity/Q1985727', 212 | JULIAN: 'http://www.wikidata.org/entity/Q1985786' 213 | }; 214 | 215 | /** 216 | * Retrieves a lower-cased calendar model key string, e.g. "gregorian", by its URI. 217 | * @static 218 | * @since 0.7 219 | * 220 | * @param {string} uri 221 | * @return {string|null} 222 | */ 223 | SELF.getCalendarModelKeyByUri = function( uri ) { 224 | var key = null; 225 | 226 | $.each( SELF.CALENDARS, function( knownKey, knownUri ) { 227 | if ( uri === knownUri ) { 228 | key = knownKey.toLowerCase(); 229 | return false; 230 | } 231 | 232 | return true; 233 | } ); 234 | 235 | return key; 236 | }; 237 | 238 | /** 239 | * Precision configuration. 240 | * @property {Object} 241 | * @static 242 | * @since 0.8 243 | */ 244 | SELF.PRECISIONS = [ 245 | { id: 'YEAR1G', text: 'billion years' }, 246 | { id: 'YEAR100M', text: 'hundred million years' }, 247 | { id: 'YEAR10M', text: 'ten million years' }, 248 | { id: 'YEAR1M', text: 'million years' }, 249 | { id: 'YEAR100K', text: '100,000 years' }, 250 | { id: 'YEAR10K', text: '10,000 years' }, 251 | { id: 'YEAR1K', text: 'millenium' }, 252 | { id: 'YEAR100', text: 'century' }, 253 | { id: 'YEAR10', text: 'decade' }, 254 | { id: 'YEAR', text: 'year' }, 255 | { id: 'MONTH', text: 'month' }, 256 | { id: 'DAY', text: 'day' }, 257 | { id: 'HOUR', text: 'hour' }, 258 | { id: 'MINUTE', text: 'minute' }, 259 | { id: 'SECOND', text: 'second' } 260 | ]; 261 | 262 | /** 263 | * Retrieves a numeric precision value by its descriptive string id. 264 | * @static 265 | * @since 0.7 266 | * 267 | * @param {string} id 268 | * @return {number|null} 269 | */ 270 | SELF.getPrecisionById = function( id ) { 271 | for( var i = SELF.PRECISIONS.length - 1; i--; ) { 272 | if( SELF.PRECISIONS[i].id === id ) { 273 | return i; 274 | } 275 | } 276 | 277 | return null; 278 | }; 279 | 280 | dv.registerDataValue( SELF ); 281 | 282 | }( dataValues, util, jQuery ) ); 283 | -------------------------------------------------------------------------------- /tests/src/valueParsers/ValueParserStore.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license GPL-2.0+ 3 | * @author H. Snater < mediawiki@snater.com > 4 | */ 5 | ( function( vp, dv, $, QUnit ) { 6 | 'use strict'; 7 | 8 | var DataTypeMock = function( dataTypeId, DataValue ) { 9 | this._dataTypeId = dataTypeId; 10 | this._dataValueType = DataValue.TYPE; 11 | }; 12 | $.extend( DataTypeMock.prototype, { 13 | getId: function() { 14 | return this._dataTypeId; 15 | }, 16 | getDataValueType: function() { 17 | return this._dataValueType; 18 | } 19 | } ); 20 | 21 | /** 22 | * Returns a descriptive string to be used as id when registering a ValueParser in a 23 | * ValueParserStore. 24 | * 25 | * @param {DataTypeMock|Function} purpose 26 | * @return {string} 27 | */ 28 | function getTypeInfo( purpose ) { 29 | if( purpose instanceof DataTypeMock ) { 30 | return 'DataType with data value type "' + purpose.getDataValueType() + '"'; 31 | } 32 | return 'constructor for DataValue of type "' + purpose.TYPE + '"'; 33 | } 34 | 35 | var StringValue = dv.StringValue, 36 | UnknownValue = dv.UnknownValue, 37 | stringType = new DataTypeMock( 'somestringtype', StringValue ), 38 | numberType = new DataTypeMock( 'somenumbertype', dv.NumberValue ), 39 | StringParser = vp.StringParser, 40 | NullParser = vp.NullParser; 41 | 42 | QUnit.module( 'valueParsers.ValueParserStore' ); 43 | 44 | QUnit.test( 'Constructor', function( assert ) { 45 | var parserStore = new vp.ValueParserStore(); 46 | 47 | assert.ok( 48 | parserStore instanceof vp.ValueParserStore, 49 | 'Instantiated ValueParserStore.' 50 | ); 51 | } ); 52 | 53 | QUnit.test( 'registerDataTypeParser(): Error handling', function( assert ) { 54 | var parserStore = new vp.ValueParserStore(); 55 | 56 | assert.throws( 57 | function() { 58 | parserStore.registerDataTypeParser( 'invalid', stringType.getId() ); 59 | }, 60 | 'Failed trying to register an invalid parser constructor.' 61 | ); 62 | 63 | parserStore.registerDataTypeParser( StringParser, stringType.getId() ); 64 | 65 | assert.throws( 66 | function() { 67 | parserStore.getParser( stringType ); 68 | }, 69 | 'Failed trying to get a parser with an invalid purpose.' 70 | ); 71 | } ); 72 | 73 | QUnit.test( 'registerDataValueParser(): Error handling', function( assert ) { 74 | var parserStore = new vp.ValueParserStore(); 75 | 76 | assert.throws( 77 | function() { 78 | parserStore.registerDataValueParser( 'invalid', StringValue.TYPE ); 79 | }, 80 | 'Failed trying to register an invalid parser constructor.' 81 | ); 82 | 83 | parserStore.registerDataValueParser( StringParser, StringValue.TYPE ); 84 | 85 | assert.throws( 86 | function() { 87 | parserStore.getParser( StringValue ); 88 | }, 89 | 'Failed trying to get a parser with an invalid purpose.' 90 | ); 91 | } ); 92 | 93 | QUnit.test( 'Return default parser on getParser()', function( assert ) { 94 | var parserStore = new vp.ValueParserStore( NullParser ); 95 | 96 | assert.equal( 97 | parserStore.getParser( StringValue.TYPE ), 98 | NullParser, 99 | 'Returning default parser if no parser is registered for a specific data value.' 100 | ); 101 | 102 | assert.equal( 103 | parserStore.getParser( stringType.getDataValueType(), stringType.getId() ), 104 | NullParser, 105 | 'Returning default parser if no parser is registered for a specific data type.' 106 | ); 107 | 108 | parserStore.registerDataValueParser( StringParser, StringValue.TYPE ); 109 | 110 | assert.equal( 111 | parserStore.getParser( StringValue.TYPE ), 112 | StringParser, 113 | 'Returning specific parser if a parser is registered for a specific data value.' 114 | ); 115 | 116 | assert.equal( 117 | parserStore.getParser( UnknownValue.TYPE ), 118 | NullParser, 119 | 'Still returning default parser if no parser is registered for a specific data value.' 120 | ); 121 | 122 | assert.equal( 123 | parserStore.getParser( numberType.getDataValueType(), numberType.getId() ), 124 | NullParser, 125 | 'Still returning default parser if no parser is registered for a specific data type.' 126 | ); 127 | } ); 128 | 129 | // Tests regarding registration of parsers: 130 | 131 | /** 132 | * Array of test definitions used as provider for "valueParserStoreRegistrationTest". 133 | * @property {Object[]} 134 | */ 135 | var valueParserStoreRegistrationTestCases = [ 136 | { 137 | title: 'Empty ValueParserStore', 138 | register: [], 139 | expect: [ 140 | [ StringValue, null ], 141 | [ stringType, null ] 142 | ] 143 | }, 144 | { 145 | title: 'Store with parser for string DataValue which is also suitable for string ' 146 | + 'DataType', 147 | register: [ 148 | [ StringValue, StringParser ] 149 | ], 150 | expect: [ 151 | [ StringValue, StringParser ], 152 | [ stringType, StringParser ], // data type uses value type 153 | [ UnknownValue, null ], 154 | [ numberType, null ] 155 | ] 156 | }, 157 | { 158 | title: 'Store for string DataType. String DataValue can\'t use this potentially more ' 159 | + 'specialized parser', 160 | register: [ 161 | [ stringType, StringParser ] 162 | ], 163 | expect: [ 164 | [ StringValue, null ], 165 | [ stringType, StringParser ] 166 | ] 167 | }, 168 | { 169 | title: 'Store with two parsers: For DataValue and for DataType using that DataValue ' 170 | + 'type', 171 | register: [ 172 | [ StringValue, StringParser ], 173 | [ stringType, StringParser ] 174 | ], 175 | expect: [ 176 | [ StringValue, StringParser ], 177 | [ stringType, StringParser ], 178 | [ UnknownValue, null ] 179 | ] 180 | }, 181 | { 182 | title: 'Store with two parsers for two different DataValue types', 183 | register: [ 184 | [ StringValue, StringParser ], 185 | [ UnknownValue, NullParser ] 186 | ], 187 | expect: [ 188 | [ StringValue, StringParser ], 189 | [ UnknownValue, NullParser ], 190 | [ numberType, null ] 191 | ] 192 | } 193 | ]; 194 | 195 | /** 196 | * Test for registration of ValueParsers to ValueParserStore and expected conditions 197 | * afterwards. 198 | * 199 | * @param {QUnit.assert} assert 200 | * @param {Array[]} toRegister Array containing arrays where each one tells a ValueParserStore 201 | * what parsers to register. The inner array has to consist out of two objects, a parser 202 | * constructor and a DataValue constructor or a DataTypeMock object. 203 | * @param {Array[]} toExpect Array containing arrays where each one states one expected 204 | * condition of the ValueParserStore after registration of what is given in the first 205 | * parameter. Each inner array should contain a data type, data value or data value 206 | * constructor and a ValueParser which is expected to be registered for it. 207 | */ 208 | function valueParserStoreRegistrationTest( assert, toRegister, toExpect ) { 209 | var parserStore = new vp.ValueParserStore(); 210 | 211 | // Register ValueParsers as per definition: 212 | $.each( toRegister, function( i, registerPair ) { 213 | var purpose = registerPair[0], 214 | Parser = registerPair[1]; 215 | 216 | if( purpose instanceof DataTypeMock ) { 217 | parserStore.registerDataTypeParser( Parser, purpose.getId() ); 218 | } else { 219 | parserStore.registerDataValueParser( Parser, purpose.TYPE ); 220 | } 221 | 222 | assert.ok( 223 | true, 224 | 'Registered parser for ' + getTypeInfo( purpose ) 225 | ); 226 | } ); 227 | 228 | // Check for expected conditions: 229 | $.each( toExpect, function( i, expectPair ) { 230 | var purpose = expectPair[0], 231 | Parser = expectPair[1], 232 | RetrievedParser; 233 | 234 | if( purpose instanceof DataTypeMock ) { 235 | RetrievedParser = parserStore.getParser( 236 | purpose.getDataValueType(), purpose.getId() 237 | ); 238 | } else { 239 | RetrievedParser = parserStore.getParser( purpose.TYPE ); 240 | } 241 | 242 | assert.strictEqual( 243 | RetrievedParser, 244 | Parser, 245 | 'Requesting parser for ' + getTypeInfo( purpose ) + 246 | ( Parser !== null ? ' returns expected parser' : ' returns null' ) 247 | ); 248 | } ); 249 | } 250 | 251 | QUnit.test( 252 | 'registerDataTypeParser() / registerDataValueParser() & getParser(): ', 253 | function( assert ) { 254 | for ( var i = 0; i < valueParserStoreRegistrationTestCases.length; i++ ) { 255 | var params = valueParserStoreRegistrationTestCases[ i ]; 256 | valueParserStoreRegistrationTest( assert, params.register, params.expect ); 257 | } 258 | } 259 | ); 260 | 261 | }( valueParsers, dataValues, jQuery, QUnit ) ); 262 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The license text below "----" applies to all files within this distribution, other 2 | than those that are in a directory which contains files named "LICENCE", "LICENSE" or 3 | "COPYING", or a subdirectory thereof. For those files, the license text contained in 4 | said file overrides any license information contained in directories of smaller depth. 5 | Alternative licenses are typically used for software that is provided by external 6 | parties, and merely packaged with this software for convenience. 7 | ---- 8 | 9 | GNU GENERAL PUBLIC LICENSE 10 | Version 2, June 1991 11 | 12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 13 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 14 | Everyone is permitted to copy and distribute verbatim copies 15 | of this license document, but changing it is not allowed. 16 | 17 | Preamble 18 | 19 | The licenses for most software are designed to take away your 20 | freedom to share and change it. By contrast, the GNU General Public 21 | License is intended to guarantee your freedom to share and change free 22 | software--to make sure the software is free for all its users. This 23 | General Public License applies to most of the Free Software 24 | Foundation's software and to any other program whose authors commit to 25 | using it. (Some other Free Software Foundation software is covered by 26 | the GNU Lesser General Public License instead.) You can apply it to 27 | your programs, too. 28 | 29 | When we speak of free software, we are referring to freedom, not 30 | price. Our General Public Licenses are designed to make sure that you 31 | have the freedom to distribute copies of free software (and charge for 32 | this service if you wish), that you receive source code or can get it 33 | if you want it, that you can change the software or use pieces of it 34 | in new free programs; and that you know you can do these things. 35 | 36 | To protect your rights, we need to make restrictions that forbid 37 | anyone to deny you these rights or to ask you to surrender the rights. 38 | These restrictions translate to certain responsibilities for you if you 39 | distribute copies of the software, or if you modify it. 40 | 41 | For example, if you distribute copies of such a program, whether 42 | gratis or for a fee, you must give the recipients all the rights that 43 | you have. You must make sure that they, too, receive or can get the 44 | source code. And you must show them these terms so they know their 45 | rights. 46 | 47 | We protect your rights with two steps: (1) copyright the software, and 48 | (2) offer you this license which gives you legal permission to copy, 49 | distribute and/or modify the software. 50 | 51 | Also, for each author's protection and ours, we want to make certain 52 | that everyone understands that there is no warranty for this free 53 | software. If the software is modified by someone else and passed on, we 54 | want its recipients to know that what they have is not the original, so 55 | that any problems introduced by others will not reflect on the original 56 | authors' reputations. 57 | 58 | Finally, any free program is threatened constantly by software 59 | patents. We wish to avoid the danger that redistributors of a free 60 | program will individually obtain patent licenses, in effect making the 61 | program proprietary. To prevent this, we have made it clear that any 62 | patent must be licensed for everyone's free use or not licensed at all. 63 | 64 | The precise terms and conditions for copying, distribution and 65 | modification follow. 66 | 67 | GNU GENERAL PUBLIC LICENSE 68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 69 | 70 | 0. This License applies to any program or other work which contains 71 | a notice placed by the copyright holder saying it may be distributed 72 | under the terms of this General Public License. The "Program", below, 73 | refers to any such program or work, and a "work based on the Program" 74 | means either the Program or any derivative work under copyright law: 75 | that is to say, a work containing the Program or a portion of it, 76 | either verbatim or with modifications and/or translated into another 77 | language. (Hereinafter, translation is included without limitation in 78 | the term "modification".) Each licensee is addressed as "you". 79 | 80 | Activities other than copying, distribution and modification are not 81 | covered by this License; they are outside its scope. The act of 82 | running the Program is not restricted, and the output from the Program 83 | is covered only if its contents constitute a work based on the 84 | Program (independent of having been made by running the Program). 85 | Whether that is true depends on what the Program does. 86 | 87 | 1. You may copy and distribute verbatim copies of the Program's 88 | source code as you receive it, in any medium, provided that you 89 | conspicuously and appropriately publish on each copy an appropriate 90 | copyright notice and disclaimer of warranty; keep intact all the 91 | notices that refer to this License and to the absence of any warranty; 92 | and give any other recipients of the Program a copy of this License 93 | along with the Program. 94 | 95 | You may charge a fee for the physical act of transferring a copy, and 96 | you may at your option offer warranty protection in exchange for a fee. 97 | 98 | 2. You may modify your copy or copies of the Program or any portion 99 | of it, thus forming a work based on the Program, and copy and 100 | distribute such modifications or work under the terms of Section 1 101 | above, provided that you also meet all of these conditions: 102 | 103 | a) You must cause the modified files to carry prominent notices 104 | stating that you changed the files and the date of any change. 105 | 106 | b) You must cause any work that you distribute or publish, that in 107 | whole or in part contains or is derived from the Program or any 108 | part thereof, to be licensed as a whole at no charge to all third 109 | parties under the terms of this License. 110 | 111 | c) If the modified program normally reads commands interactively 112 | when run, you must cause it, when started running for such 113 | interactive use in the most ordinary way, to print or display an 114 | announcement including an appropriate copyright notice and a 115 | notice that there is no warranty (or else, saying that you provide 116 | a warranty) and that users may redistribute the program under 117 | these conditions, and telling the user how to view a copy of this 118 | License. (Exception: if the Program itself is interactive but 119 | does not normally print such an announcement, your work based on 120 | the Program is not required to print an announcement.) 121 | 122 | These requirements apply to the modified work as a whole. If 123 | identifiable sections of that work are not derived from the Program, 124 | and can be reasonably considered independent and separate works in 125 | themselves, then this License, and its terms, do not apply to those 126 | sections when you distribute them as separate works. But when you 127 | distribute the same sections as part of a whole which is a work based 128 | on the Program, the distribution of the whole must be on the terms of 129 | this License, whose permissions for other licensees extend to the 130 | entire whole, and thus to each and every part regardless of who wrote it. 131 | 132 | Thus, it is not the intent of this section to claim rights or contest 133 | your rights to work written entirely by you; rather, the intent is to 134 | exercise the right to control the distribution of derivative or 135 | collective works based on the Program. 136 | 137 | In addition, mere aggregation of another work not based on the Program 138 | with the Program (or with a work based on the Program) on a volume of 139 | a storage or distribution medium does not bring the other work under 140 | the scope of this License. 141 | 142 | 3. You may copy and distribute the Program (or a work based on it, 143 | under Section 2) in object code or executable form under the terms of 144 | Sections 1 and 2 above provided that you also do one of the following: 145 | 146 | a) Accompany it with the complete corresponding machine-readable 147 | source code, which must be distributed under the terms of Sections 148 | 1 and 2 above on a medium customarily used for software interchange; or, 149 | 150 | b) Accompany it with a written offer, valid for at least three 151 | years, to give any third party, for a charge no more than your 152 | cost of physically performing source distribution, a complete 153 | machine-readable copy of the corresponding source code, to be 154 | distributed under the terms of Sections 1 and 2 above on a medium 155 | customarily used for software interchange; or, 156 | 157 | c) Accompany it with the information you received as to the offer 158 | to distribute corresponding source code. (This alternative is 159 | allowed only for noncommercial distribution and only if you 160 | received the program in object code or executable form with such 161 | an offer, in accord with Subsection b above.) 162 | 163 | The source code for a work means the preferred form of the work for 164 | making modifications to it. For an executable work, complete source 165 | code means all the source code for all modules it contains, plus any 166 | associated interface definition files, plus the scripts used to 167 | control compilation and installation of the executable. However, as a 168 | special exception, the source code distributed need not include 169 | anything that is normally distributed (in either source or binary 170 | form) with the major components (compiler, kernel, and so on) of the 171 | operating system on which the executable runs, unless that component 172 | itself accompanies the executable. 173 | 174 | If distribution of executable or object code is made by offering 175 | access to copy from a designated place, then offering equivalent 176 | access to copy the source code from the same place counts as 177 | distribution of the source code, even though third parties are not 178 | compelled to copy the source along with the object code. 179 | 180 | 4. You may not copy, modify, sublicense, or distribute the Program 181 | except as expressly provided under this License. Any attempt 182 | otherwise to copy, modify, sublicense or distribute the Program is 183 | void, and will automatically terminate your rights under this License. 184 | However, parties who have received copies, or rights, from you under 185 | this License will not have their licenses terminated so long as such 186 | parties remain in full compliance. 187 | 188 | 5. You are not required to accept this License, since you have not 189 | signed it. However, nothing else grants you permission to modify or 190 | distribute the Program or its derivative works. These actions are 191 | prohibited by law if you do not accept this License. Therefore, by 192 | modifying or distributing the Program (or any work based on the 193 | Program), you indicate your acceptance of this License to do so, and 194 | all its terms and conditions for copying, distributing or modifying 195 | the Program or works based on it. 196 | 197 | 6. Each time you redistribute the Program (or any work based on the 198 | Program), the recipient automatically receives a license from the 199 | original licensor to copy, distribute or modify the Program subject to 200 | these terms and conditions. You may not impose any further 201 | restrictions on the recipients' exercise of the rights granted herein. 202 | You are not responsible for enforcing compliance by third parties to 203 | this License. 204 | 205 | 7. If, as a consequence of a court judgment or allegation of patent 206 | infringement or for any other reason (not limited to patent issues), 207 | conditions are imposed on you (whether by court order, agreement or 208 | otherwise) that contradict the conditions of this License, they do not 209 | excuse you from the conditions of this License. If you cannot 210 | distribute so as to satisfy simultaneously your obligations under this 211 | License and any other pertinent obligations, then as a consequence you 212 | may not distribute the Program at all. For example, if a patent 213 | license would not permit royalty-free redistribution of the Program by 214 | all those who receive copies directly or indirectly through you, then 215 | the only way you could satisfy both it and this License would be to 216 | refrain entirely from distribution of the Program. 217 | 218 | If any portion of this section is held invalid or unenforceable under 219 | any particular circumstance, the balance of the section is intended to 220 | apply and the section as a whole is intended to apply in other 221 | circumstances. 222 | 223 | It is not the purpose of this section to induce you to infringe any 224 | patents or other property right claims or to contest validity of any 225 | such claims; this section has the sole purpose of protecting the 226 | integrity of the free software distribution system, which is 227 | implemented by public license practices. Many people have made 228 | generous contributions to the wide range of software distributed 229 | through that system in reliance on consistent application of that 230 | system; it is up to the author/donor to decide if he or she is willing 231 | to distribute software through any other system and a licensee cannot 232 | impose that choice. 233 | 234 | This section is intended to make thoroughly clear what is believed to 235 | be a consequence of the rest of this License. 236 | 237 | 8. If the distribution and/or use of the Program is restricted in 238 | certain countries either by patents or by copyrighted interfaces, the 239 | original copyright holder who places the Program under this License 240 | may add an explicit geographical distribution limitation excluding 241 | those countries, so that distribution is permitted only in or among 242 | countries not thus excluded. In such case, this License incorporates 243 | the limitation as if written in the body of this License. 244 | 245 | 9. The Free Software Foundation may publish revised and/or new versions 246 | of the General Public License from time to time. Such new versions will 247 | be similar in spirit to the present version, but may differ in detail to 248 | address new problems or concerns. 249 | 250 | Each version is given a distinguishing version number. If the Program 251 | specifies a version number of this License which applies to it and "any 252 | later version", you have the option of following the terms and conditions 253 | either of that version or of any later version published by the Free 254 | Software Foundation. If the Program does not specify a version number of 255 | this License, you may choose any version ever published by the Free Software 256 | Foundation. 257 | 258 | 10. If you wish to incorporate parts of the Program into other free 259 | programs whose distribution conditions are different, write to the author 260 | to ask for permission. For software which is copyrighted by the Free 261 | Software Foundation, write to the Free Software Foundation; we sometimes 262 | make exceptions for this. Our decision will be guided by the two goals 263 | of preserving the free status of all derivatives of our free software and 264 | of promoting the sharing and reuse of software generally. 265 | 266 | NO WARRANTY 267 | 268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 276 | REPAIR OR CORRECTION. 277 | 278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 286 | POSSIBILITY OF SUCH DAMAGES. 287 | 288 | END OF TERMS AND CONDITIONS 289 | 290 | How to Apply These Terms to Your New Programs 291 | 292 | If you develop a new program, and you want it to be of the greatest 293 | possible use to the public, the best way to achieve this is to make it 294 | free software which everyone can redistribute and change under these terms. 295 | 296 | To do so, attach the following notices to the program. It is safest 297 | to attach them to the start of each source file to most effectively 298 | convey the exclusion of warranty; and each file should have at least 299 | the "copyright" line and a pointer to where the full notice is found. 300 | 301 | 302 | Copyright (C) 303 | 304 | This program is free software; you can redistribute it and/or modify 305 | it under the terms of the GNU General Public License as published by 306 | the Free Software Foundation; either version 2 of the License, or 307 | (at your option) any later version. 308 | 309 | This program is distributed in the hope that it will be useful, 310 | but WITHOUT ANY WARRANTY; without even the implied warranty of 311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 312 | GNU General Public License for more details. 313 | 314 | You should have received a copy of the GNU General Public License along 315 | with this program; if not, write to the Free Software Foundation, Inc., 316 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 317 | 318 | Also add information on how to contact you by electronic and paper mail. 319 | 320 | If the program is interactive, make it output a short notice like this 321 | when it starts in an interactive mode: 322 | 323 | Gnomovision version 69, Copyright (C) year name of author 324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 325 | This is free software, and you are welcome to redistribute it 326 | under certain conditions; type `show c' for details. 327 | 328 | The hypothetical commands `show w' and `show c' should show the appropriate 329 | parts of the General Public License. Of course, the commands you use may 330 | be called something other than `show w' and `show c'; they could even be 331 | mouse-clicks or menu items--whatever suits your program. 332 | 333 | You should also get your employer (if you work as a programmer) or your 334 | school, if any, to sign a "copyright disclaimer" for the program, if 335 | necessary. Here is a sample; alter the names: 336 | 337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 338 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 339 | 340 | , 1 April 1989 341 | Ty Coon, President of Vice 342 | 343 | This General Public License does not permit incorporating your program into 344 | proprietary programs. If your program is a subroutine library, you may 345 | consider it more useful to permit linking proprietary applications with the 346 | library. If this is what you want to do, use the GNU Lesser General 347 | Public License instead of this License. 348 | --------------------------------------------------------------------------------