constructor.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. 'use strict';
  2. var $$ = require('./common');
  3. var _errors = require('./errors');
  4. var _nodes = require('./nodes');
  5. function ConstructorError() {
  6. _errors.MarkedYAMLError.apply(this, arguments);
  7. this.name = 'ConstructorError';
  8. }
  9. $$.inherits(ConstructorError, _errors.MarkedYAMLError);
  10. var BOOL_VALUES = {
  11. 'y': true,
  12. 'yes': true,
  13. 'n': false,
  14. 'no': false,
  15. 'true': true,
  16. 'false': false,
  17. 'on': true,
  18. 'off': false
  19. };
  20. var TIMESTAMP_REGEXP = new RegExp(
  21. '^([0-9][0-9][0-9][0-9])' + // [1] year
  22. '-([0-9][0-9]?)' + // [2] month
  23. '-([0-9][0-9]?)' + // [3] day
  24. '(?:(?:[Tt]|[ \\t]+)' + // ...
  25. '([0-9][0-9]?)' + // [4] hour
  26. ':([0-9][0-9])' + // [5] minute
  27. ':([0-9][0-9])' + // [6] second
  28. '(?:\\.([0-9]*))?' + // [7] fraction
  29. '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour
  30. '(?::([0-9][0-9]))?))?)?$' // [11] tz_minute
  31. );
  32. function BaseConstructor() {
  33. this.constructedObjects = new $$.Hash();
  34. this.recursiveObjects = new $$.Hash();
  35. this.statePopulators = []; // was state_generators
  36. this.deepConstruct = false;
  37. this.yamlConstructors = BaseConstructor.yamlConstructors;
  38. }
  39. BaseConstructor.yamlConstructors = {};
  40. BaseConstructor.addConstructor = function addConstructor(tag, constructor) {
  41. this.yamlConstructors[tag] = constructor;
  42. };
  43. BaseConstructor.prototype.checkData = function checkData() {
  44. return this.checkNode();
  45. };
  46. BaseConstructor.prototype.getData = function getData() {
  47. if (this.checkNode()) {
  48. return this.constructDocument(this.getNode());
  49. }
  50. };
  51. BaseConstructor.prototype.getSingleData = function getSingleData() {
  52. var node = this.getSingleNode();
  53. if (null !== node) {
  54. return this.constructDocument(node);
  55. }
  56. return null;
  57. };
  58. BaseConstructor.prototype.constructDocument = function constructDocument(node) {
  59. var data = this.constructObject(node),
  60. stateIterator, statePopulators;
  61. stateIterator = function (populator) { populator.execute(); };
  62. while (!!this.statePopulators.length) {
  63. statePopulators = this.statePopulators;
  64. this.statePopulators = [];
  65. statePopulators.forEach(stateIterator);
  66. }
  67. this.constructedObjects = new $$.Hash();
  68. this.recursiveObjects = new $$.Hash();
  69. this.deepConstruct = false;
  70. return data;
  71. };
  72. BaseConstructor.prototype.constructObject = function constructObject(node, deep) {
  73. var data, old_deep, constructor, populator;
  74. if (this.constructedObjects.hasKey(node)) {
  75. return this.constructedObjects.get(node);
  76. }
  77. if (!!deep) {
  78. old_deep = this.deepConstruct;
  79. this.deepConstruct = true;
  80. }
  81. if (this.recursiveObjects.hasKey(node)) {
  82. throw new ConstructorError(null, null,
  83. "found unconstructable recursive node",
  84. node.startMark);
  85. }
  86. this.recursiveObjects.store(node, null);
  87. if (undefined !== this.yamlConstructors[node.tag]) {
  88. constructor = this.yamlConstructors[node.tag];
  89. } else {
  90. if (undefined !== this.yamlConstructors[null]) {
  91. constructor = this.yamlConstructors[null];
  92. } else {
  93. throw new ConstructorError(null, null,
  94. "can't find any constructor for tag=" + node.tag,
  95. node.startMark);
  96. }
  97. }
  98. data = constructor.call(this, node);
  99. if (data instanceof $$.Populator) {
  100. populator = data;
  101. data = populator.data;
  102. if (this.deepConstruct) {
  103. populator.execute();
  104. } else {
  105. this.statePopulators.push(populator);
  106. }
  107. }
  108. this.constructedObjects.store(node, data);
  109. this.recursiveObjects.remove(node);
  110. if (deep) {
  111. this.deepConstruct = old_deep;
  112. }
  113. return data;
  114. };
  115. BaseConstructor.prototype.constructScalar = function constructScalar(node) {
  116. if (!$$.isInstanceOf(node, _nodes.ScalarNode)) {
  117. throw new ConstructorError(null, null,
  118. "expected a scalar node, but found " + node.id,
  119. node.startMark);
  120. }
  121. return node.value;
  122. };
  123. BaseConstructor.prototype.constructSequence = function constructSequence(node, deep) {
  124. if (!$$.isInstanceOf(node, _nodes.SequenceNode)) {
  125. throw new ConstructorError(null, null,
  126. "expected a sequence node, but found " + node.id,
  127. node.startMark);
  128. }
  129. return node.value.map(function (child) {
  130. return this.constructObject(child, deep);
  131. }, this);
  132. };
  133. BaseConstructor.prototype.constructMapping = function constructMapping(node, deep) {
  134. var mapping;
  135. if (!$$.isInstanceOf(node, _nodes.MappingNode)) {
  136. throw new ConstructorError(null, null,
  137. "expected a mapping node, but found " + node.id,
  138. node.startMark);
  139. }
  140. mapping = {};
  141. $$.each(node.value, function (pair) {
  142. var key_node = pair[0], value_node = pair[1], key, value;
  143. key = this.constructObject(key_node, deep);
  144. // TODO: Do we need to check
  145. if (undefined === key_node.hash) {
  146. throw new ConstructorError("while constructing a mapping", key_node.startMark,
  147. "found unhashable key", key_node.startMark);
  148. }
  149. value = this.constructObject(value_node, deep);
  150. mapping[key] = value;
  151. }, this);
  152. return mapping;
  153. };
  154. BaseConstructor.prototype.constructPairs = function constructPairs(node, deep) {
  155. var pairs;
  156. if (!$$.isInstanceOf(node, _nodes.MappingNode)) {
  157. throw new ConstructorError(null, null,
  158. "expected a mapping node, but found " + node.id,
  159. node.startMark);
  160. }
  161. pairs = [];
  162. $$.each(node.value, function (pair) {
  163. var key, value;
  164. key = this.constructObject(pair[0], deep);
  165. value = this.constructObject(pair[1], deep);
  166. pairs.store(key, value);
  167. }, this);
  168. return pairs;
  169. };
  170. function SafeConstructor() {
  171. BaseConstructor.apply(this);
  172. this.yamlConstructors = SafeConstructor.yamlConstructors;
  173. }
  174. $$.inherits(SafeConstructor, BaseConstructor);
  175. SafeConstructor.yamlConstructors = $$.extend({}, BaseConstructor.yamlConstructors);
  176. SafeConstructor.addConstructor = BaseConstructor.addConstructor;
  177. SafeConstructor.prototype.constructScalar = function constructScalar(node) {
  178. var result;
  179. if ($$.isInstanceOf(node, _nodes.MappingNode)) {
  180. $$.each(node.value, function (pair) {
  181. var key_node = pair[0], value_node = pair[1], value;
  182. if ('tag:yaml.org,2002:value' === key_node.tag) {
  183. result = this.constructScalar(value_node);
  184. }
  185. }, this);
  186. if (undefined !== result) {
  187. return result;
  188. }
  189. }
  190. return BaseConstructor.prototype.constructScalar.call(this, node);
  191. };
  192. SafeConstructor.prototype.flattenMapping = function flattenMapping(node) {
  193. var self = this, merge = [], index = 0, keyNode, valueNode, submerge,
  194. pushSingleValue, pushMultipleValues, submergeIterator;
  195. pushSingleValue = function (value) {
  196. merge.push(value);
  197. };
  198. pushMultipleValues = function (values) {
  199. values.forEach(pushSingleValue);
  200. };
  201. submergeIterator = function (subnode) {
  202. if (!$$.isInstanceOf(subnode, _nodes.MappingNode)) {
  203. throw new ConstructorError("while constructing a mapping", node.startMark,
  204. "expected a mapping for merging, but found " + subnode.id,
  205. subnode.startMark);
  206. }
  207. self.flattenMapping(subnode);
  208. submerge.push(subnode.value);
  209. };
  210. while (index < node.value.length) {
  211. keyNode = node.value[index][0];
  212. valueNode = node.value[index][1];
  213. if ('tag:yaml.org,2002:merge' === keyNode.tag) {
  214. node.value.splice(index, 1);
  215. if ($$.isInstanceOf(valueNode, _nodes.MappingNode)) {
  216. self.flattenMapping(valueNode);
  217. $$.each(valueNode.value, pushSingleValue);
  218. } else if ($$.isInstanceOf(valueNode, _nodes.SequenceNode)) {
  219. submerge = [];
  220. $$.each(valueNode.value, submergeIterator);
  221. $$.reverse(submerge).forEach(pushMultipleValues);
  222. } else {
  223. throw new ConstructorError("while constructing a mapping", node.startMark,
  224. "expected a mapping or list of mappings for merging, but found " + valueNode.id,
  225. valueNode.startMark);
  226. }
  227. } else if ('tag:yaml.org,2002:value' === keyNode.tag) {
  228. keyNode.tag = 'tag:yaml.org,2002:str';
  229. index += 1;
  230. } else {
  231. index += 1;
  232. }
  233. }
  234. if (!!merge.length) {
  235. $$.each(node.value, function (value) { merge.push(value); });
  236. node.value = merge;
  237. }
  238. };
  239. SafeConstructor.prototype.constructMapping = function constructMapping(node, deep) {
  240. if ($$.isInstanceOf(node, _nodes.MappingNode)) {
  241. this.flattenMapping(node);
  242. }
  243. return BaseConstructor.prototype.constructMapping.call(this, node);
  244. };
  245. SafeConstructor.prototype.constructYamlNull = function constructYamlNull(node) {
  246. this.constructScalar(node);
  247. return null;
  248. };
  249. SafeConstructor.prototype.constructYamlBool = function constructYamlBool(node) {
  250. var value = this.constructScalar(node);
  251. return BOOL_VALUES[value.toLowerCase()];
  252. };
  253. SafeConstructor.prototype.constructYamlInt = function constructYamlInt(node) {
  254. var value = this.constructScalar(node).replace(/_/g, ''),
  255. sign = ('-' === value[0]) ? -1 : 1,
  256. base, digits = [];
  257. if (0 <= '+-'.indexOf(value[0])) {
  258. value = value.slice(1);
  259. }
  260. if ('0' === value) {
  261. return 0;
  262. } else if (/^0b/.test(value)) {
  263. return sign * parseInt(value.slice(2), 2);
  264. } else if (/^0x/.test(value)) {
  265. return sign * parseInt(value, 16);
  266. } else if ('0' === value[0]) {
  267. return sign * parseInt(value, 8);
  268. } else if (0 <= value.indexOf(':')) {
  269. value.split(':').forEach(function (v) {
  270. digits.unshift(parseInt(v, 10));
  271. });
  272. value = 0;
  273. base = 1;
  274. digits.forEach(function (d) {
  275. value += (d * base);
  276. base *= 60;
  277. });
  278. return sign * value;
  279. } else {
  280. return sign * parseInt(value, 10);
  281. }
  282. };
  283. SafeConstructor.prototype.constructYamlFloat = function constructYamlFloat(node) {
  284. var value = this.constructScalar(node).replace(/_/g, ''),
  285. sign = ('-' === value[0]) ? -1 : 1,
  286. base, digits = [];
  287. if (0 <= '+-'.indexOf(value[0])) {
  288. value = value.slice(1);
  289. }
  290. if ('.inf' === value) {
  291. return (1 === sign) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
  292. } else if ('.nan' === value) {
  293. return NaN;
  294. } else if (0 <= value.indexOf(':')) {
  295. value.split(':').forEach(function (v) {
  296. digits.unshift(parseFloat(v, 10));
  297. });
  298. value = 0.0;
  299. base = 1;
  300. digits.forEach(function (d) {
  301. value += d * base;
  302. base *= 60;
  303. });
  304. return sign * value;
  305. } else {
  306. return sign * parseFloat(value, 10);
  307. }
  308. };
  309. SafeConstructor.prototype.constructYamlBinary = function constructYamlBinary(node) {
  310. try {
  311. return $$.decodeBase64(this.constructScalar(node));
  312. } catch (err) {
  313. throw new ConstructorError(null, null,
  314. "failed to decode base64 data: " + err.toString(), node.startMark);
  315. }
  316. };
  317. SafeConstructor.prototype.constructYamlTimestamp = function constructYamlTimestamp(node) {
  318. var match, year, month, day, hour, minute, second, fraction = 0,
  319. delta = null, tz_hour, tz_minute, data;
  320. match = TIMESTAMP_REGEXP.exec(this.constructScalar(node));
  321. // match: [1] year [2] month [3] day
  322. year = +(match[1]);
  323. month = +(match[2]) - 1; // JS month starts with 0
  324. day = +(match[3]);
  325. if (!match[4]) { // no hour
  326. return new Date(year, month, day);
  327. }
  328. // match: [4] hour [5] minute [6] second [7] fraction
  329. hour = +(match[4]);
  330. minute = +(match[5]);
  331. second = +(match[6]);
  332. if (!!match[7]) {
  333. fraction = match[7].slice(0,3);
  334. while (fraction.length < 3) { // milli-seconds
  335. fraction += '0';
  336. }
  337. fraction = +fraction;
  338. }
  339. // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute
  340. if (!!match[9]) {
  341. tz_hour = +(match[10]);
  342. tz_minute = +(match[11] || 0);
  343. delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds
  344. if ('-' === match[9]) {
  345. delta = -delta;
  346. }
  347. }
  348. data = new Date(year, month, day, hour, minute, second, fraction);
  349. if (!!delta) {
  350. data.setTime(data.getTime() - delta);
  351. }
  352. return data;
  353. };
  354. SafeConstructor.prototype.constructYamlOmap = function constructYamlOmap(node) {
  355. var self = this, omap = [];
  356. return $$.Populator(omap, function () {
  357. if (!$$.isInstanceOf(node, _nodes.SequenceNode)) {
  358. throw new ConstructorError("while constructing an ordered map", node.startMark,
  359. "expected a sequence, but found " + node.id, node.startMark);
  360. }
  361. node.value.forEach(function (subnode) {
  362. var data, key, value;
  363. if (!$$.isInstanceOf(subnode, _nodes.MappingNode)) {
  364. throw new ConstructorError("while constructing an ordered map", node.startMark,
  365. "expected a mapping of length 1, but found " + subnode.id,
  366. subnode.startMark);
  367. }
  368. if (1 !== subnode.value.length) {
  369. throw new ConstructorError("while constructing an ordered map", node.startMark,
  370. "expected a single mapping item, but found " + subnode.value.length + " items",
  371. subnode.startMark);
  372. }
  373. key = self.constructObject(subnode.value[0][0]);
  374. value = self.constructObject(subnode.value[0][1]);
  375. data = Object.create(null);
  376. data[key] = value;
  377. omap.push(data);
  378. });
  379. });
  380. };
  381. SafeConstructor.prototype.constructYamlPairs = function constructYamlPairs(node) {
  382. var self = this, pairs = [];
  383. return $$.Populator(pairs, function () {
  384. if (!$$.isInstanceOf(node, _nodes.SequenceNode)) {
  385. throw new ConstructorError("while constructing pairs", node.startMark,
  386. "expected a sequence, but found " + node.id, node.startMark);
  387. }
  388. node.value.forEach(function (subnode) {
  389. var key, value;
  390. if (!$$.isInstanceOf(subnode, _nodes.MappingNode)) {
  391. throw new ConstructorError("while constructing pairs", node.startMark,
  392. "expected a mapping of length 1, but found " + subnode.id,
  393. subnode.startMark);
  394. }
  395. if (1 !== subnode.value.length) {
  396. throw new ConstructorError("while constructing pairs", node.startMark,
  397. "expected a single mapping item, but found " + subnode.value.length + " items",
  398. subnode.startMark);
  399. }
  400. key = self.constructObject(subnode.value[0][0]);
  401. value = self.constructObject(subnode.value[0][1]);
  402. pairs.push([key, value]);
  403. });
  404. });
  405. };
  406. SafeConstructor.prototype.constructYamlSet = function constructYamlSet(node) {
  407. var data = {};
  408. return $$.Populator(data, function () {
  409. $$.extend(data, this.constructMapping(node));
  410. }, this);
  411. };
  412. SafeConstructor.prototype.constructYamlStr = function constructYamlStr(node) {
  413. return this.constructScalar(node);
  414. };
  415. SafeConstructor.prototype.constructYamlSeq = function constructYamlSeq(node) {
  416. var data = [];
  417. return $$.Populator(data, function () {
  418. this.constructSequence(node).forEach(function (value) {
  419. data.push(value);
  420. });
  421. }, this);
  422. };
  423. SafeConstructor.prototype.constructYamlMap = function constructYamlMap(node) {
  424. var data = {};
  425. return $$.Populator(data, function () {
  426. $$.extend(data, this.constructMapping(node, true));
  427. }, this);
  428. };
  429. SafeConstructor.prototype.constructUndefined = function constructUndefined(node) {
  430. throw new ConstructorError(null, null,
  431. "could not determine constructor for the tag " + node.tag,
  432. node.startMark);
  433. };
  434. SafeConstructor.addConstructor(
  435. 'tag:yaml.org,2002:null',
  436. SafeConstructor.prototype.constructYamlNull);
  437. SafeConstructor.addConstructor(
  438. 'tag:yaml.org,2002:bool',
  439. SafeConstructor.prototype.constructYamlBool);
  440. SafeConstructor.addConstructor(
  441. 'tag:yaml.org,2002:int',
  442. SafeConstructor.prototype.constructYamlInt);
  443. SafeConstructor.addConstructor(
  444. 'tag:yaml.org,2002:float',
  445. SafeConstructor.prototype.constructYamlFloat);
  446. SafeConstructor.addConstructor(
  447. 'tag:yaml.org,2002:binary',
  448. SafeConstructor.prototype.constructYamlBinary);
  449. SafeConstructor.addConstructor(
  450. 'tag:yaml.org,2002:timestamp',
  451. SafeConstructor.prototype.constructYamlTimestamp);
  452. SafeConstructor.addConstructor(
  453. 'tag:yaml.org,2002:omap',
  454. SafeConstructor.prototype.constructYamlOmap);
  455. SafeConstructor.addConstructor(
  456. 'tag:yaml.org,2002:pairs',
  457. SafeConstructor.prototype.constructYamlPairs);
  458. SafeConstructor.addConstructor(
  459. 'tag:yaml.org,2002:set',
  460. SafeConstructor.prototype.constructYamlSet);
  461. SafeConstructor.addConstructor(
  462. 'tag:yaml.org,2002:str',
  463. SafeConstructor.prototype.constructYamlStr);
  464. SafeConstructor.addConstructor(
  465. 'tag:yaml.org,2002:seq',
  466. SafeConstructor.prototype.constructYamlSeq);
  467. SafeConstructor.addConstructor(
  468. 'tag:yaml.org,2002:map',
  469. SafeConstructor.prototype.constructYamlMap);
  470. SafeConstructor.addConstructor(
  471. null,
  472. SafeConstructor.prototype.constructUndefined);
  473. function Constructor() {
  474. SafeConstructor.apply(this);
  475. this.yamlConstructors = Constructor.yamlConstructors;
  476. }
  477. $$.inherits(Constructor, SafeConstructor);
  478. Constructor.yamlConstructors = $$.extend({}, SafeConstructor.yamlConstructors);
  479. Constructor.addConstructor = SafeConstructor.addConstructor;
  480. Constructor.prototype.constructJavascriptRegExp = function constructJavascriptRegExp(node) {
  481. var regexp = this.constructScalar(node),
  482. tail =/\/([gim]*)$/.exec(regexp),
  483. modifiers;
  484. // `/foo/gim` - tail can be maximum 4 chars
  485. if ('/' === regexp[0] && !!tail && 4 >= tail[0].length) {
  486. regexp = regexp.slice(1, regexp.length - tail[0].length);
  487. modifiers = tail[1];
  488. }
  489. return new RegExp(regexp, modifiers);
  490. };
  491. Constructor.prototype.constructJavascriptUndefined = function constructJavascriptUndefined(node) {
  492. var undef;
  493. return undef;
  494. };
  495. Constructor.prototype.constructJavascriptFunction = function constructJavascriptFunction(node) {
  496. /*jslint evil:true*/
  497. var func = new Function('return ' + this.constructScalar(node));
  498. return func();
  499. };
  500. Constructor.addConstructor(
  501. 'tag:yaml.org,2002:js/undefined',
  502. Constructor.prototype.constructJavascriptUndefined);
  503. Constructor.addConstructor(
  504. 'tag:yaml.org,2002:js/regexp',
  505. Constructor.prototype.constructJavascriptRegExp);
  506. Constructor.addConstructor(
  507. 'tag:yaml.org,2002:js/function',
  508. Constructor.prototype.constructJavascriptFunction);
  509. module.exports.BaseConstructor = BaseConstructor;
  510. module.exports.SafeConstructor = SafeConstructor;
  511. module.exports.Constructor = Constructor;
  512. ////////////////////////////////////////////////////////////////////////////////
  513. // vim:ts=2:sw=2
  514. ////////////////////////////////////////////////////////////////////////////////