@tripsuite/cxml
Version:
Advanced schema-aware streaming XML parser
224 lines (223 loc) • 9.22 kB
JavaScript
// This file is part of cxml, copyright (c) 2016 BusFaster Ltd.
// Released under the MIT license, see LICENSE.
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var MemberRef_1 = require("./MemberRef");
var Type_1 = require("./Type");
var Item_1 = require("./Item");
/** Parse name from schema in serialized JSON format.
* If name used in XML is not a valid JavaScript identifier, the schema
* definition will be in format <cleaned up name for JavaScript>:<XML name>. */
function parseName(name) {
var splitPos = name.indexOf(':');
var safeName;
if (splitPos >= 0) {
safeName = name.substr(0, splitPos);
name = name.substr(splitPos + 1);
}
else
safeName = name;
return ({
name: name,
safeName: safeName
});
}
exports.parseName = parseName;
/** Create a new data object inheriting default values from another. */
function inherit(parentObject) {
function Proxy() { }
Proxy.prototype = parentObject;
return (new Proxy());
}
function defineSubstitute(substitute, proxy) {
var ref = new MemberRef_1.MemberRef([substitute, 0, substitute.safeName], substitute.namespace, proxy);
return (ref);
}
/** Type specification defining attributes and children. */
var TypeSpec = (function () {
function TypeSpec(spec, namespace, name) {
// Initialize helper containing data and methods also applicable to members.
this.optionalList = [];
this.requiredList = [];
this.item = new Item_1.ItemBase(this);
if (name) {
var parts = parseName(name);
this.name = parts.name;
this.safeName = parts.safeName;
}
this.namespace = namespace;
this.flags = spec[0];
this.item.parentNum = spec[1];
this.childSpecList = spec[2];
this.attributeSpecList = spec[3];
}
TypeSpec.prototype.getProto = function () { return (this.proto); };
TypeSpec.prototype.getType = function () { return (this.type); };
TypeSpec.prototype.define = function () {
// This function hasn't been called for this type yet by setParent,
// but something must by now have called it for the parent type.
var parent = (this.item.parent && this.item.parent != this) ? this.item.parent.proto : Type_1.TypeInstance;
this.proto = (function (_super) {
__extends(XmlType, _super);
function XmlType() {
return _super !== null && _super.apply(this, arguments) || this;
}
return XmlType;
}(parent));
var instanceProto = this.proto.prototype;
instanceProto._exists = true;
instanceProto._namespace = this.namespace.name;
this.placeHolder = new this.proto();
this.placeHolder._exists = false;
this.type = new Type_1.Type(this.proto);
this.proto.type = this.type;
this.type.namespace = this.namespace;
if (this.item.parent) {
this.type.childTbl = inherit(this.item.parent.type.childTbl);
this.type.attributeTbl = inherit(this.item.parent.type.attributeTbl);
}
else {
this.type.attributeTbl = {};
this.type.childTbl = {};
}
this.type.isPrimitive = !!(this.flags & TypeSpec.primitiveFlag);
this.type.isPlainPrimitive = !!(this.flags & TypeSpec.plainPrimitiveFlag);
this.type.isList = !!(this.flags & TypeSpec.listFlag);
if (this.type.isPrimitive) {
var primitiveType = this;
var next;
while ((next = primitiveType.item.parent) && next != primitiveType)
primitiveType = next;
this.type.primitiveType = primitiveType.safeName;
}
return (this.type);
};
TypeSpec.prototype.defineMember = function (ref) {
var typeSpec = ref.member.typeSpec;
var proxySpec = ref.member.proxySpec;
if (proxySpec) {
if (ref.max > 1) {
typeSpec = proxySpec;
}
else {
proxySpec = this;
typeSpec = null;
}
TypeSpec.addSubstitutesToProxy(ref.member, proxySpec.proto.prototype);
}
if (typeSpec) {
var memberType = typeSpec.placeHolder;
var type = (this.proto.prototype);
type[ref.safeName] = (ref.max > 1) ? [memberType] : memberType;
if (ref.min < 1)
this.optionalList.push(ref.safeName);
else
this.requiredList.push(ref.safeName);
}
return (ref);
};
TypeSpec.prototype.getSubstitutes = function () {
return (this.substituteList);
};
TypeSpec.prototype.defineMembers = function () {
var spec;
for (var _i = 0, _a = this.childSpecList; _i < _a.length; _i++) {
spec = _a[_i];
var memberRef = new MemberRef_1.MemberRef(spec, this.namespace);
this.addChild(memberRef);
this.defineMember(memberRef);
}
for (var _b = 0, _c = this.attributeSpecList; _b < _c.length; _b++) {
spec = _c[_b];
var attributeRef = new MemberRef_1.MemberRef(spec, this.namespace);
if (attributeRef.member.typeSpec)
this.type.addAttribute(attributeRef);
this.defineMember(attributeRef);
}
};
TypeSpec.prototype.addSubstitutes = function (headRef, proxy) {
headRef.member.containingTypeList.push({
type: this,
head: headRef,
proxy: proxy
});
headRef.member.proxySpec.item.define();
for (var _i = 0, _a = headRef.member.proxySpec.getSubstitutes(); _i < _a.length; _i++) {
var substitute = _a[_i];
if (substitute == headRef.member) {
this.type.addChild(headRef);
}
else {
var substituteRef = defineSubstitute(substitute, proxy);
this.addChild(substituteRef, proxy);
}
}
};
TypeSpec.prototype.addChild = function (memberRef, proxy) {
if (memberRef.member.proxySpec)
this.addSubstitutes(memberRef, proxy || memberRef);
else if (!memberRef.member.isAbstract)
this.type.addChild(memberRef);
};
TypeSpec.prototype.addSubstitute = function (head, substitute) {
if (this.item.defined && head.containingTypeList.length) {
// The element's proxy type has already been defined
// so we need to patch other types containing the element.
for (var _i = 0, _a = head.containingTypeList; _i < _a.length; _i++) {
var spec = _a[_i];
var ref = defineSubstitute(substitute, spec.proxy);
spec.type.addChild(ref, spec.proxy);
if (spec.head.max <= 1) {
TypeSpec.addSubstituteToProxy(substitute, spec.type.proto.prototype);
}
}
// Add the substitution to proxy type of the group head,
// and loop if the head further substitutes something else.
while (head) {
TypeSpec.addSubstituteToProxy(substitute, head.proxySpec.proto.prototype);
head = head.item.parent;
}
}
this.substituteList.push(substitute);
};
/** Remove placeholders from instance prototype. They allow dereferencing
* contents of missing optional child elements without throwing errors.
* @param strict Also remove placeholders for mandatory child elements. */
TypeSpec.prototype.cleanPlaceholders = function (strict) {
var type = (this.proto.prototype);
var nameList = this.optionalList;
if (strict)
nameList = nameList.concat(this.requiredList);
for (var _i = 0, nameList_1 = nameList; _i < nameList_1.length; _i++) {
var name = nameList_1[_i];
delete (type[name]);
}
};
TypeSpec.addSubstituteToProxy = function (substitute, type, head) {
if (substitute == head || !substitute.proxySpec) {
if (!substitute.isAbstract)
type[substitute.safeName] = substitute.typeSpec.placeHolder;
}
else {
TypeSpec.addSubstitutesToProxy(substitute, type);
}
};
TypeSpec.addSubstitutesToProxy = function (member, type) {
for (var _i = 0, _a = member.proxySpec.getSubstitutes(); _i < _a.length; _i++) {
var substitute = _a[_i];
TypeSpec.addSubstituteToProxy(substitute, type, member);
}
};
return TypeSpec;
}());
/** Type contains text that gets parsed to JavaScript primitives. */
TypeSpec.primitiveFlag = 1;
/** Type only contains text, no wrapper object is needed to hold its attributes. */
TypeSpec.plainPrimitiveFlag = 2;
/** Type contains text with a list of whitespace-separated items. */
TypeSpec.listFlag = 4;
exports.TypeSpec = TypeSpec;