vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
11 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. var util = require('../util');
  2. let errorFound = false;
  3. let allOptions;
  4. let printStyle = 'background: #FFeeee; color: #dd0000';
  5. /**
  6. * Used to validate options.
  7. */
  8. class Validator {
  9. constructor() {
  10. }
  11. /**
  12. * Main function to be called
  13. * @param options
  14. * @param subObject
  15. * @returns {boolean}
  16. */
  17. static validate(options, referenceOptions, subObject) {
  18. errorFound = false;
  19. allOptions = referenceOptions;
  20. let usedOptions = referenceOptions;
  21. if (subObject !== undefined) {
  22. usedOptions = referenceOptions[subObject];
  23. }
  24. Validator.parse(options, usedOptions, []);
  25. return errorFound;
  26. }
  27. /**
  28. * Will traverse an object recursively and check every value
  29. * @param options
  30. * @param referenceOptions
  31. * @param path
  32. */
  33. static parse(options, referenceOptions, path) {
  34. for (let option in options) {
  35. if (options.hasOwnProperty(option)) {
  36. Validator.check(option, options, referenceOptions, path);
  37. }
  38. }
  39. }
  40. /**
  41. * Check every value. If the value is an object, call the parse function on that object.
  42. * @param option
  43. * @param options
  44. * @param referenceOptions
  45. * @param path
  46. */
  47. static check(option, options, referenceOptions, path) {
  48. if (referenceOptions[option] === undefined && referenceOptions.__any__ === undefined) {
  49. Validator.getSuggestion(option, referenceOptions, path);
  50. }
  51. else if (referenceOptions[option] === undefined && referenceOptions.__any__ !== undefined) {
  52. // __any__ is a wildcard. Any value is accepted and will be further analysed by reference.
  53. if (Validator.getType(options[option]) === 'object') {
  54. Validator.checkFields(option, options, referenceOptions, '__any__', referenceOptions['__any__'].__type__, path);
  55. }
  56. }
  57. else {
  58. // Since all options in the reference are objects, we can check whether they are supposed to be object to look for the __type__ field.
  59. if (referenceOptions[option].__type__ !== undefined) {
  60. // if this should be an object, we check if the correct type has been supplied to account for shorthand options.
  61. Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option].__type__, path);
  62. }
  63. else {
  64. Validator.checkFields(option, options, referenceOptions, option, referenceOptions[option], path);
  65. }
  66. }
  67. }
  68. /**
  69. *
  70. * @param {String} option | the option property
  71. * @param {Object} options | The supplied options object
  72. * @param {Object} referenceOptions | The reference options containing all options and their allowed formats
  73. * @param {String} referenceOption | Usually this is the same as option, except when handling an __any__ tag.
  74. * @param {String} refOptionType | This is the type object from the reference options
  75. * @param {Array} path | where in the object is the option
  76. */
  77. static checkFields(option, options, referenceOptions, referenceOption, refOptionObj, path) {
  78. let optionType = Validator.getType(options[option]);
  79. let refOptionType = refOptionObj[optionType];
  80. if (refOptionType !== undefined) {
  81. // if the type is correct, we check if it is supposed to be one of a few select values
  82. if (Validator.getType(refOptionType) === 'array') {
  83. if (refOptionType.indexOf(options[option]) === -1) {
  84. console.log('%cInvalid option detected in "' + option + '".' +
  85. ' Allowed values are:' + Validator.print(refOptionType) + ' not "' + options[option] + '". ' + Validator.printLocation(path, option), printStyle);
  86. errorFound = true;
  87. }
  88. else if (optionType === 'object') {
  89. path = util.copyAndExtendArray(path, option);
  90. Validator.parse(options[option], referenceOptions[referenceOption], path);
  91. }
  92. }
  93. else if (optionType === 'object') {
  94. path = util.copyAndExtendArray(path, option);
  95. Validator.parse(options[option], referenceOptions[referenceOption], path);
  96. }
  97. }
  98. else {
  99. if (refOptionObj['undef'] !== undefined && optionType === 'undefined') {
  100. // item is undefined, which is allowed
  101. }
  102. else if (refOptionObj['fn'] !== undefined && optionType === 'function') {
  103. // item is a function, which is allowed
  104. }
  105. else if (refOptionObj['any'] === undefined) {
  106. // type of the field is incorrect and the field cannot be any
  107. console.log('%cInvalid type received for "' + option + '". Expected: ' + Validator.print(Object.keys(refOptionObj)) + '. Received [' + optionType + '] "' + options[option] + '"' + Validator.printLocation(path, option), printStyle);
  108. errorFound = true;
  109. }
  110. }
  111. }
  112. static getType(object) {
  113. var type = typeof object;
  114. if (type === 'object') {
  115. if (object === null) {
  116. return 'null';
  117. }
  118. if (object instanceof Boolean) {
  119. return 'boolean';
  120. }
  121. if (object instanceof Number) {
  122. return 'number';
  123. }
  124. if (object instanceof String) {
  125. return 'string';
  126. }
  127. if (Array.isArray(object)) {
  128. return 'array';
  129. }
  130. if (object instanceof Date) {
  131. return 'date';
  132. }
  133. if (object.nodeType !== undefined) {
  134. return 'dom';
  135. }
  136. if (object._isAMomentObject === true) {
  137. return 'moment';
  138. }
  139. return 'object';
  140. }
  141. else if (type === 'number') {
  142. return 'number';
  143. }
  144. else if (type === 'boolean') {
  145. return 'boolean';
  146. }
  147. else if (type === 'string') {
  148. return 'string';
  149. }
  150. else if (type === undefined) {
  151. return 'undefined';
  152. }
  153. return type;
  154. }
  155. static getSuggestion(option, options, path) {
  156. let localSearch = Validator.findInOptions(option,options,path,false);
  157. let globalSearch = Validator.findInOptions(option,allOptions,[],true);
  158. let localSearchThreshold = 8;
  159. let globalSearchThreshold = 4;
  160. if (localSearch.indexMatch !== undefined) {
  161. console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option,'') + 'Perhaps it was incomplete? Did you mean: "' + localSearch.indexMatch + '"?\n\n', printStyle);
  162. }
  163. else if (globalSearch.distance <= globalSearchThreshold && localSearch.distance > globalSearch.distance) {
  164. console.log('%cUnknown option detected: "' + option + '" in ' + Validator.printLocation(localSearch.path, option,'') + 'Perhaps it was misplaced? Matching option found at: ' + Validator.printLocation(globalSearch.path, globalSearch.closestMatch,''), printStyle);
  165. }
  166. else if (localSearch.distance <= localSearchThreshold) {
  167. console.log('%cUnknown option detected: "' + option + '". Did you mean "' + localSearch.closestMatch + '"?' + Validator.printLocation(localSearch.path, option), printStyle);
  168. }
  169. else {
  170. console.log('%cUnknown option detected: "' + option + '". Did you mean one of these: ' + Validator.print(Object.keys(options)) + Validator.printLocation(path, option), printStyle);
  171. }
  172. errorFound = true;
  173. }
  174. /**
  175. * traverse the options in search for a match.
  176. * @param option
  177. * @param options
  178. * @param path
  179. * @param recursive
  180. * @returns {{closestMatch: string, path: Array, distance: number}}
  181. */
  182. static findInOptions(option, options, path, recursive = false) {
  183. let min = 1e9;
  184. let closestMatch = '';
  185. let closestMatchPath = [];
  186. let lowerCaseOption = option.toLowerCase();
  187. let indexMatch = undefined;
  188. for (let op in options) {
  189. let distance;
  190. if (options[op].__type__ !== undefined && recursive === true) {
  191. let result = Validator.findInOptions(option, options[op], util.copyAndExtendArray(path,op));
  192. if (min > result.distance) {
  193. closestMatch = result.closestMatch;
  194. closestMatchPath = result.path;
  195. min = result.distance;
  196. indexMatch = result.indexMatch;
  197. }
  198. }
  199. else {
  200. if (op.toLowerCase().indexOf(lowerCaseOption) !== -1) {
  201. indexMatch = op;
  202. }
  203. distance = Validator.levenshteinDistance(option, op);
  204. if (min > distance) {
  205. closestMatch = op;
  206. closestMatchPath = util.copyArray(path);
  207. min = distance;
  208. }
  209. }
  210. }
  211. return {closestMatch:closestMatch, path:closestMatchPath, distance:min, indexMatch: indexMatch};
  212. }
  213. static printLocation(path, option, prefix = 'Problem value found at: \n') {
  214. let str = '\n\n' + prefix + 'options = {\n';
  215. for (let i = 0; i < path.length; i++) {
  216. for (let j = 0; j < i + 1; j++) {
  217. str += ' ';
  218. }
  219. str += path[i] + ': {\n'
  220. }
  221. for (let j = 0; j < path.length + 1; j++) {
  222. str += ' ';
  223. }
  224. str += option + '\n';
  225. for (let i = 0; i < path.length + 1; i++) {
  226. for (let j = 0; j < path.length - i; j++) {
  227. str += ' ';
  228. }
  229. str += '}\n'
  230. }
  231. return str + '\n\n';
  232. }
  233. static print(options) {
  234. return JSON.stringify(options).replace(/(\")|(\[)|(\])|(,"__type__")/g, "").replace(/(\,)/g, ', ')
  235. }
  236. // Compute the edit distance between the two given strings
  237. // http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript
  238. /*
  239. Copyright (c) 2011 Andrei Mackenzie
  240. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  241. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  242. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  243. */
  244. static levenshteinDistance(a, b) {
  245. if (a.length === 0) return b.length;
  246. if (b.length === 0) return a.length;
  247. var matrix = [];
  248. // increment along the first column of each row
  249. var i;
  250. for (i = 0; i <= b.length; i++) {
  251. matrix[i] = [i];
  252. }
  253. // increment each column in the first row
  254. var j;
  255. for (j = 0; j <= a.length; j++) {
  256. matrix[0][j] = j;
  257. }
  258. // Fill in the rest of the matrix
  259. for (i = 1; i <= b.length; i++) {
  260. for (j = 1; j <= a.length; j++) {
  261. if (b.charAt(i - 1) == a.charAt(j - 1)) {
  262. matrix[i][j] = matrix[i - 1][j - 1];
  263. } else {
  264. matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
  265. Math.min(matrix[i][j - 1] + 1, // insertion
  266. matrix[i - 1][j] + 1)); // deletion
  267. }
  268. }
  269. }
  270. return matrix[b.length][a.length];
  271. }
  272. ;
  273. }
  274. export default Validator;
  275. export {printStyle}