read.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  1. import {
  2. forEach,
  3. find,
  4. assign
  5. } from 'min-dash';
  6. import {
  7. Parser as SaxParser
  8. } from 'saxen';
  9. import Moddle from 'moddle';
  10. import {
  11. parseName as parseNameNs
  12. } from 'moddle/lib/ns';
  13. import {
  14. coerceType,
  15. isSimple as isSimpleType
  16. } from 'moddle/lib/types';
  17. import {
  18. XSI_TYPE,
  19. serializeAsType,
  20. hasLowerCaseAlias
  21. } from './common';
  22. function capitalize(str) {
  23. return str.charAt(0).toUpperCase() + str.slice(1);
  24. }
  25. function aliasToName(aliasNs, pkg) {
  26. if (!hasLowerCaseAlias(pkg)) {
  27. return aliasNs.name;
  28. }
  29. return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
  30. }
  31. function prefixedToName(nameNs, pkg) {
  32. var name = nameNs.name,
  33. localName = nameNs.localName;
  34. var typePrefix = pkg.xml && pkg.xml.typePrefix;
  35. if (typePrefix && localName.indexOf(typePrefix) === 0) {
  36. return nameNs.prefix + ':' + localName.slice(typePrefix.length);
  37. } else {
  38. return name;
  39. }
  40. }
  41. function normalizeXsiTypeName(name, model) {
  42. var nameNs = parseNameNs(name);
  43. var pkg = model.getPackage(nameNs.prefix);
  44. return prefixedToName(nameNs, pkg);
  45. }
  46. function error(message) {
  47. return new Error(message);
  48. }
  49. /**
  50. * Get the moddle descriptor for a given instance or type.
  51. *
  52. * @param {ModdleElement|Function} element
  53. *
  54. * @return {Object} the moddle descriptor
  55. */
  56. function getModdleDescriptor(element) {
  57. return element.$descriptor;
  58. }
  59. function defer(fn) {
  60. setTimeout(fn, 0);
  61. }
  62. /**
  63. * A parse context.
  64. *
  65. * @class
  66. *
  67. * @param {Object} options
  68. * @param {ElementHandler} options.rootHandler the root handler for parsing a document
  69. * @param {boolean} [options.lax=false] whether or not to ignore invalid elements
  70. */
  71. export function Context(options) {
  72. /**
  73. * @property {ElementHandler} rootHandler
  74. */
  75. /**
  76. * @property {Boolean} lax
  77. */
  78. assign(this, options);
  79. this.elementsById = {};
  80. this.references = [];
  81. this.warnings = [];
  82. /**
  83. * Add an unresolved reference.
  84. *
  85. * @param {Object} reference
  86. */
  87. this.addReference = function(reference) {
  88. this.references.push(reference);
  89. };
  90. /**
  91. * Add a processed element.
  92. *
  93. * @param {ModdleElement} element
  94. */
  95. this.addElement = function(element) {
  96. if (!element) {
  97. throw error('expected element');
  98. }
  99. var elementsById = this.elementsById;
  100. var descriptor = getModdleDescriptor(element);
  101. var idProperty = descriptor.idProperty,
  102. id;
  103. if (idProperty) {
  104. id = element.get(idProperty.name);
  105. if (id) {
  106. // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
  107. if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
  108. throw new Error('illegal ID <' + id + '>');
  109. }
  110. if (elementsById[id]) {
  111. throw error('duplicate ID <' + id + '>');
  112. }
  113. elementsById[id] = element;
  114. }
  115. }
  116. };
  117. /**
  118. * Add an import warning.
  119. *
  120. * @param {Object} warning
  121. * @param {String} warning.message
  122. * @param {Error} [warning.error]
  123. */
  124. this.addWarning = function(warning) {
  125. this.warnings.push(warning);
  126. };
  127. }
  128. function BaseHandler() {}
  129. BaseHandler.prototype.handleEnd = function() {};
  130. BaseHandler.prototype.handleText = function() {};
  131. BaseHandler.prototype.handleNode = function() {};
  132. /**
  133. * A simple pass through handler that does nothing except for
  134. * ignoring all input it receives.
  135. *
  136. * This is used to ignore unknown elements and
  137. * attributes.
  138. */
  139. function NoopHandler() { }
  140. NoopHandler.prototype = Object.create(BaseHandler.prototype);
  141. NoopHandler.prototype.handleNode = function() {
  142. return this;
  143. };
  144. function BodyHandler() {}
  145. BodyHandler.prototype = Object.create(BaseHandler.prototype);
  146. BodyHandler.prototype.handleText = function(text) {
  147. this.body = (this.body || '') + text;
  148. };
  149. function ReferenceHandler(property, context) {
  150. this.property = property;
  151. this.context = context;
  152. }
  153. ReferenceHandler.prototype = Object.create(BodyHandler.prototype);
  154. ReferenceHandler.prototype.handleNode = function(node) {
  155. if (this.element) {
  156. throw error('expected no sub nodes');
  157. } else {
  158. this.element = this.createReference(node);
  159. }
  160. return this;
  161. };
  162. ReferenceHandler.prototype.handleEnd = function() {
  163. this.element.id = this.body;
  164. };
  165. ReferenceHandler.prototype.createReference = function(node) {
  166. return {
  167. property: this.property.ns.name,
  168. id: ''
  169. };
  170. };
  171. function ValueHandler(propertyDesc, element) {
  172. this.element = element;
  173. this.propertyDesc = propertyDesc;
  174. }
  175. ValueHandler.prototype = Object.create(BodyHandler.prototype);
  176. ValueHandler.prototype.handleEnd = function() {
  177. var value = this.body || '',
  178. element = this.element,
  179. propertyDesc = this.propertyDesc;
  180. value = coerceType(propertyDesc.type, value);
  181. if (propertyDesc.isMany) {
  182. element.get(propertyDesc.name).push(value);
  183. } else {
  184. element.set(propertyDesc.name, value);
  185. }
  186. };
  187. function BaseElementHandler() {}
  188. BaseElementHandler.prototype = Object.create(BodyHandler.prototype);
  189. BaseElementHandler.prototype.handleNode = function(node) {
  190. var parser = this,
  191. element = this.element;
  192. if (!element) {
  193. element = this.element = this.createElement(node);
  194. this.context.addElement(element);
  195. } else {
  196. parser = this.handleChild(node);
  197. }
  198. return parser;
  199. };
  200. /**
  201. * @class Reader.ElementHandler
  202. *
  203. */
  204. export function ElementHandler(model, typeName, context) {
  205. this.model = model;
  206. this.type = model.getType(typeName);
  207. this.context = context;
  208. }
  209. ElementHandler.prototype = Object.create(BaseElementHandler.prototype);
  210. ElementHandler.prototype.addReference = function(reference) {
  211. this.context.addReference(reference);
  212. };
  213. ElementHandler.prototype.handleText = function(text) {
  214. var element = this.element,
  215. descriptor = getModdleDescriptor(element),
  216. bodyProperty = descriptor.bodyProperty;
  217. if (!bodyProperty) {
  218. throw error('unexpected body text <' + text + '>');
  219. }
  220. BodyHandler.prototype.handleText.call(this, text);
  221. };
  222. ElementHandler.prototype.handleEnd = function() {
  223. var value = this.body,
  224. element = this.element,
  225. descriptor = getModdleDescriptor(element),
  226. bodyProperty = descriptor.bodyProperty;
  227. if (bodyProperty && value !== undefined) {
  228. value = coerceType(bodyProperty.type, value);
  229. element.set(bodyProperty.name, value);
  230. }
  231. };
  232. /**
  233. * Create an instance of the model from the given node.
  234. *
  235. * @param {Element} node the xml node
  236. */
  237. ElementHandler.prototype.createElement = function(node) {
  238. var attributes = node.attributes,
  239. Type = this.type,
  240. descriptor = getModdleDescriptor(Type),
  241. context = this.context,
  242. instance = new Type({}),
  243. model = this.model,
  244. propNameNs;
  245. forEach(attributes, function(value, name) {
  246. var prop = descriptor.propertiesByName[name],
  247. values;
  248. if (prop && prop.isReference) {
  249. if (!prop.isMany) {
  250. context.addReference({
  251. element: instance,
  252. property: prop.ns.name,
  253. id: value
  254. });
  255. } else {
  256. // IDREFS: parse references as whitespace-separated list
  257. values = value.split(' ');
  258. forEach(values, function(v) {
  259. context.addReference({
  260. element: instance,
  261. property: prop.ns.name,
  262. id: v
  263. });
  264. });
  265. }
  266. } else {
  267. if (prop) {
  268. value = coerceType(prop.type, value);
  269. } else
  270. if (name !== 'xmlns') {
  271. propNameNs = parseNameNs(name, descriptor.ns.prefix);
  272. // check whether attribute is defined in a well-known namespace
  273. // if that is the case we emit a warning to indicate potential misuse
  274. if (model.getPackage(propNameNs.prefix)) {
  275. context.addWarning({
  276. message: 'unknown attribute <' + name + '>',
  277. element: instance,
  278. property: name,
  279. value: value
  280. });
  281. }
  282. }
  283. instance.set(name, value);
  284. }
  285. });
  286. return instance;
  287. };
  288. ElementHandler.prototype.getPropertyForNode = function(node) {
  289. var name = node.name;
  290. var nameNs = parseNameNs(name);
  291. var type = this.type,
  292. model = this.model,
  293. descriptor = getModdleDescriptor(type);
  294. var propertyName = nameNs.name,
  295. property = descriptor.propertiesByName[propertyName],
  296. elementTypeName,
  297. elementType;
  298. // search for properties by name first
  299. if (property) {
  300. if (serializeAsType(property)) {
  301. elementTypeName = node.attributes[XSI_TYPE];
  302. // xsi type is optional, if it does not exists the
  303. // default type is assumed
  304. if (elementTypeName) {
  305. // take possible type prefixes from XML
  306. // into account, i.e.: xsi:type="t{ActualType}"
  307. elementTypeName = normalizeXsiTypeName(elementTypeName, model);
  308. elementType = model.getType(elementTypeName);
  309. return assign({}, property, {
  310. effectiveType: getModdleDescriptor(elementType).name
  311. });
  312. }
  313. }
  314. // search for properties by name first
  315. return property;
  316. }
  317. var pkg = model.getPackage(nameNs.prefix);
  318. if (pkg) {
  319. elementTypeName = aliasToName(nameNs, pkg);
  320. elementType = model.getType(elementTypeName);
  321. // search for collection members later
  322. property = find(descriptor.properties, function(p) {
  323. return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
  324. });
  325. if (property) {
  326. return assign({}, property, {
  327. effectiveType: getModdleDescriptor(elementType).name
  328. });
  329. }
  330. } else {
  331. // parse unknown element (maybe extension)
  332. property = find(descriptor.properties, function(p) {
  333. return !p.isReference && !p.isAttribute && p.type === 'Element';
  334. });
  335. if (property) {
  336. return property;
  337. }
  338. }
  339. throw error('unrecognized element <' + nameNs.name + '>');
  340. };
  341. ElementHandler.prototype.toString = function() {
  342. return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
  343. };
  344. ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
  345. return new ValueHandler(propertyDesc, element);
  346. };
  347. ElementHandler.prototype.referenceHandler = function(propertyDesc) {
  348. return new ReferenceHandler(propertyDesc, this.context);
  349. };
  350. ElementHandler.prototype.handler = function(type) {
  351. if (type === 'Element') {
  352. return new GenericElementHandler(this.model, type, this.context);
  353. } else {
  354. return new ElementHandler(this.model, type, this.context);
  355. }
  356. };
  357. /**
  358. * Handle the child element parsing
  359. *
  360. * @param {Element} node the xml node
  361. */
  362. ElementHandler.prototype.handleChild = function(node) {
  363. var propertyDesc, type, element, childHandler;
  364. propertyDesc = this.getPropertyForNode(node);
  365. element = this.element;
  366. type = propertyDesc.effectiveType || propertyDesc.type;
  367. if (isSimpleType(type)) {
  368. return this.valueHandler(propertyDesc, element);
  369. }
  370. if (propertyDesc.isReference) {
  371. childHandler = this.referenceHandler(propertyDesc).handleNode(node);
  372. } else {
  373. childHandler = this.handler(type).handleNode(node);
  374. }
  375. var newElement = childHandler.element;
  376. // child handles may decide to skip elements
  377. // by not returning anything
  378. if (newElement !== undefined) {
  379. if (propertyDesc.isMany) {
  380. element.get(propertyDesc.name).push(newElement);
  381. } else {
  382. element.set(propertyDesc.name, newElement);
  383. }
  384. if (propertyDesc.isReference) {
  385. assign(newElement, {
  386. element: element
  387. });
  388. this.context.addReference(newElement);
  389. } else {
  390. // establish child -> parent relationship
  391. newElement.$parent = element;
  392. }
  393. }
  394. return childHandler;
  395. };
  396. /**
  397. * An element handler that performs special validation
  398. * to ensure the node it gets initialized with matches
  399. * the handlers type (namespace wise).
  400. *
  401. * @param {Moddle} model
  402. * @param {String} typeName
  403. * @param {Context} context
  404. */
  405. function RootElementHandler(model, typeName, context) {
  406. ElementHandler.call(this, model, typeName, context);
  407. }
  408. RootElementHandler.prototype = Object.create(ElementHandler.prototype);
  409. RootElementHandler.prototype.createElement = function(node) {
  410. var name = node.name,
  411. nameNs = parseNameNs(name),
  412. model = this.model,
  413. type = this.type,
  414. pkg = model.getPackage(nameNs.prefix),
  415. typeName = pkg && aliasToName(nameNs, pkg) || name;
  416. // verify the correct namespace if we parse
  417. // the first element in the handler tree
  418. //
  419. // this ensures we don't mistakenly import wrong namespace elements
  420. if (!type.hasType(typeName)) {
  421. throw error('unexpected element <' + node.originalName + '>');
  422. }
  423. return ElementHandler.prototype.createElement.call(this, node);
  424. };
  425. function GenericElementHandler(model, typeName, context) {
  426. this.model = model;
  427. this.context = context;
  428. }
  429. GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);
  430. GenericElementHandler.prototype.createElement = function(node) {
  431. var name = node.name,
  432. ns = parseNameNs(name),
  433. prefix = ns.prefix,
  434. uri = node.ns[prefix + '$uri'],
  435. attributes = node.attributes;
  436. return this.model.createAny(name, uri, attributes);
  437. };
  438. GenericElementHandler.prototype.handleChild = function(node) {
  439. var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
  440. element = this.element;
  441. var newElement = handler.element,
  442. children;
  443. if (newElement !== undefined) {
  444. children = element.$children = element.$children || [];
  445. children.push(newElement);
  446. // establish child -> parent relationship
  447. newElement.$parent = element;
  448. }
  449. return handler;
  450. };
  451. GenericElementHandler.prototype.handleEnd = function() {
  452. if (this.body) {
  453. this.element.$body = this.body;
  454. }
  455. };
  456. /**
  457. * A reader for a meta-model
  458. *
  459. * @param {Object} options
  460. * @param {Model} options.model used to read xml files
  461. * @param {Boolean} options.lax whether to make parse errors warnings
  462. */
  463. export function Reader(options) {
  464. if (options instanceof Moddle) {
  465. options = {
  466. model: options
  467. };
  468. }
  469. assign(this, { lax: false }, options);
  470. }
  471. /**
  472. * Parse the given XML into a moddle document tree.
  473. *
  474. * @param {String} xml
  475. * @param {ElementHandler|Object} options or rootHandler
  476. * @param {Function} done
  477. */
  478. Reader.prototype.fromXML = function(xml, options, done) {
  479. var rootHandler = options.rootHandler;
  480. if (options instanceof ElementHandler) {
  481. // root handler passed via (xml, { rootHandler: ElementHandler }, ...)
  482. rootHandler = options;
  483. options = {};
  484. } else {
  485. if (typeof options === 'string') {
  486. // rootHandler passed via (xml, 'someString', ...)
  487. rootHandler = this.handler(options);
  488. options = {};
  489. } else if (typeof rootHandler === 'string') {
  490. // rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
  491. rootHandler = this.handler(rootHandler);
  492. }
  493. }
  494. var model = this.model,
  495. lax = this.lax;
  496. var context = new Context(assign({}, options, { rootHandler: rootHandler })),
  497. parser = new SaxParser({ proxy: true }),
  498. stack = createStack();
  499. rootHandler.context = context;
  500. // push root handler
  501. stack.push(rootHandler);
  502. /**
  503. * Handle error.
  504. *
  505. * @param {Error} err
  506. * @param {Function} getContext
  507. * @param {boolean} lax
  508. *
  509. * @return {boolean} true if handled
  510. */
  511. function handleError(err, getContext, lax) {
  512. var ctx = getContext();
  513. var line = ctx.line,
  514. column = ctx.column,
  515. data = ctx.data;
  516. // we receive the full context data here,
  517. // for elements trim down the information
  518. // to the tag name, only
  519. if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
  520. data = data.slice(0, data.indexOf(' ')) + '>';
  521. }
  522. var message =
  523. 'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' +
  524. 'line: ' + line + '\n\t' +
  525. 'column: ' + column + '\n\t' +
  526. 'nested error: ' + err.message;
  527. if (lax) {
  528. context.addWarning({
  529. message: message,
  530. error: err
  531. });
  532. return true;
  533. } else {
  534. throw error(message);
  535. }
  536. }
  537. function handleWarning(err, getContext) {
  538. // just like handling errors in <lax=true> mode
  539. return handleError(err, getContext, true);
  540. }
  541. /**
  542. * Resolve collected references on parse end.
  543. */
  544. function resolveReferences() {
  545. var elementsById = context.elementsById;
  546. var references = context.references;
  547. var i, r;
  548. for (i = 0; (r = references[i]); i++) {
  549. var element = r.element;
  550. var reference = elementsById[r.id];
  551. var property = getModdleDescriptor(element).propertiesByName[r.property];
  552. if (!reference) {
  553. context.addWarning({
  554. message: 'unresolved reference <' + r.id + '>',
  555. element: r.element,
  556. property: r.property,
  557. value: r.id
  558. });
  559. }
  560. if (property.isMany) {
  561. var collection = element.get(property.name),
  562. idx = collection.indexOf(r);
  563. // we replace an existing place holder (idx != -1) or
  564. // append to the collection instead
  565. if (idx === -1) {
  566. idx = collection.length;
  567. }
  568. if (!reference) {
  569. // remove unresolvable reference
  570. collection.splice(idx, 1);
  571. } else {
  572. // add or update reference in collection
  573. collection[idx] = reference;
  574. }
  575. } else {
  576. element.set(property.name, reference);
  577. }
  578. }
  579. }
  580. function handleClose() {
  581. stack.pop().handleEnd();
  582. }
  583. var PREAMBLE_START_PATTERN = /^<\?xml /i;
  584. var ENCODING_PATTERN = / encoding="([^"]+)"/i;
  585. var UTF_8_PATTERN = /^utf-8$/i;
  586. function handleQuestion(question) {
  587. if (!PREAMBLE_START_PATTERN.test(question)) {
  588. return;
  589. }
  590. var match = ENCODING_PATTERN.exec(question);
  591. var encoding = match && match[1];
  592. if (!encoding || UTF_8_PATTERN.test(encoding)) {
  593. return;
  594. }
  595. context.addWarning({
  596. message:
  597. 'unsupported document encoding <' + encoding + '>, ' +
  598. 'falling back to UTF-8'
  599. });
  600. }
  601. function handleOpen(node, getContext) {
  602. var handler = stack.peek();
  603. try {
  604. stack.push(handler.handleNode(node));
  605. } catch (err) {
  606. if (handleError(err, getContext, lax)) {
  607. stack.push(new NoopHandler());
  608. }
  609. }
  610. }
  611. function handleCData(text, getContext) {
  612. try {
  613. stack.peek().handleText(text);
  614. } catch (err) {
  615. handleWarning(err, getContext);
  616. }
  617. }
  618. function handleText(text, getContext) {
  619. // strip whitespace only nodes, i.e. before
  620. // <!CDATA[ ... ]> sections and in between tags
  621. text = text.trim();
  622. if (!text) {
  623. return;
  624. }
  625. handleCData(text, getContext);
  626. }
  627. var uriMap = model.getPackages().reduce(function(uriMap, p) {
  628. uriMap[p.uri] = p.prefix;
  629. return uriMap;
  630. }, {});
  631. parser
  632. .ns(uriMap)
  633. .on('openTag', function(obj, decodeStr, selfClosing, getContext) {
  634. // gracefully handle unparsable attributes (attrs=false)
  635. var attrs = obj.attrs || {};
  636. var decodedAttrs = Object.keys(attrs).reduce(function(d, key) {
  637. var value = decodeStr(attrs[key]);
  638. d[key] = value;
  639. return d;
  640. }, {});
  641. var node = {
  642. name: obj.name,
  643. originalName: obj.originalName,
  644. attributes: decodedAttrs,
  645. ns: obj.ns
  646. };
  647. handleOpen(node, getContext);
  648. })
  649. .on('question', handleQuestion)
  650. .on('closeTag', handleClose)
  651. .on('cdata', handleCData)
  652. .on('text', function(text, decodeEntities, getContext) {
  653. handleText(decodeEntities(text), getContext);
  654. })
  655. .on('error', handleError)
  656. .on('warn', handleWarning);
  657. // deferred parse XML to make loading really ascnchronous
  658. // this ensures the execution environment (node or browser)
  659. // is kept responsive and that certain optimization strategies
  660. // can kick in
  661. defer(function() {
  662. var err;
  663. try {
  664. parser.parse(xml);
  665. resolveReferences();
  666. } catch (e) {
  667. err = e;
  668. }
  669. var element = rootHandler.element;
  670. // handle the situation that we could not extract
  671. // the desired root element from the document
  672. if (!err && !element) {
  673. err = error('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
  674. }
  675. done(err, err ? undefined : element, context);
  676. });
  677. };
  678. Reader.prototype.handler = function(name) {
  679. return new RootElementHandler(this.model, name);
  680. };
  681. // helpers //////////////////////////
  682. function createStack() {
  683. var stack = [];
  684. Object.defineProperty(stack, 'peek', {
  685. value: function() {
  686. return this[this.length - 1];
  687. }
  688. });
  689. return stack;
  690. }