export class Path{ public static parentId = '^'; public _isRelative: boolean; public _components: Path.Component[]; public _componentsString: string | null; constructor(); constructor(componentsString: string); constructor(head: Path.Component, tail: Path); constructor(head: Path.Component[], relative?: boolean); constructor(){ this._components = []; this._componentsString = null; this._isRelative = false; if (typeof arguments[0] == 'string'){ let componentsString = arguments[0] as string; this.componentsString = componentsString; } else if (arguments[0] instanceof Path.Component && arguments[1] instanceof Path){ let head = arguments[0] as Path.Component; let tail = arguments[1] as Path; this._components.push(head); this._components = this._components.concat(tail._components); } else if (arguments[0] instanceof Array){ let head = arguments[0] as Path.Component[]; let relative = !!arguments[1] as boolean; this._components = this._components.concat(head); this._isRelative = relative; } } get isRelative(){ return this._isRelative; } get componentCount(): number{ return this._components.length; } get head(): Path.Component | null{ if (this._components.length > 0) { return this._components[0]; } else { return null; } } get tail(): Path{ if (this._components.length >= 2) { // careful, the original code uses length-1 here. This is because the second argument of // List.GetRange is a number of elements to extract, wherease Array.slice uses an index let tailComps = this._components.slice(1, this._components.length); return new Path(tailComps); } else { return Path.self; } } get length(): number{ return this._components.length; } get lastComponent(): Path.Component | null{ let lastComponentIdx = this._components.length - 1; if (lastComponentIdx >= 0) { return this._components[lastComponentIdx]; } else { return null; } } get containsNamedComponent(): boolean{ for (let i = 0, l = this._components.length; i < l; i++){ if (!this._components[i].isIndex){ return true; } } return false; } static get self(): Path{ let path = new Path(); path._isRelative = true; return path; } public GetComponent(index: number): Path.Component{ return this._components[index]; } public PathByAppendingPath(pathToAppend: Path): Path{ let p = new Path(); let upwardMoves = 0; for (let i = 0; i < pathToAppend._components.length; ++i) { if (pathToAppend._components[i].isParent) { upwardMoves++; } else { break; } } for (let i = 0; i < this._components.length - upwardMoves; ++i) { p._components.push(this._components[i]); } for (let i = upwardMoves; i < pathToAppend._components.length; ++i) { p._components.push(pathToAppend._components[i]); } return p; } get componentsString(): string{ if (this._componentsString == null) { this._componentsString = this._components.join('.'); if (this.isRelative) this._componentsString = '.' + this._componentsString; } return this._componentsString; } set componentsString(value: string){ this._components.length = 0; this._componentsString = value; if (this._componentsString == null || this._componentsString == '') return; if (this._componentsString[0] == '.') { this._isRelative = true; this._componentsString = this._componentsString.substring(1); } let componentStrings = this._componentsString.split('.'); for (let str of componentStrings) { // we need to distinguish between named components that start with a number, eg "42somewhere", and indexed components // the normal parseInt won't do for the detection because it's too relaxed. // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt if (/^(\-|\+)?([0-9]+|Infinity)$/.test(str)){ this._components.push(new Path.Component(parseInt(str))); } else{ this._components.push(new Path.Component(str)); } } } public toString(): string{ return this.componentsString; } public Equals(otherPath: Path | null): boolean{ if (otherPath == null) return false; if (otherPath._components.length != this._components.length) return false; if (otherPath.isRelative != this.isRelative) return false; // the original code uses SequenceEqual here, so we need to iterate over the components manually. for (let i = 0, l = otherPath._components.length; i < l; i++){ // it's not quite clear whether this test should use Equals or a simple == operator, // see https://github.com/y-lohse/inkjs/issues/22 if (!otherPath._components[i].Equals(this._components[i])) return false; } return true; } public PathByAppendingComponent(c: Path.Component): Path{ let p = new Path(); p._components.push.apply(p._components, this._components); p._components.push(c); return p; } } export namespace Path { export class Component{ public readonly index: number; public readonly name: string | null; constructor(indexOrName: string | number){ this.index = -1; this.name = null; if (typeof indexOrName == 'string'){ this.name = indexOrName; } else{ this.index = indexOrName; } } get isIndex(): boolean{ return this.index >= 0; } get isParent(): boolean{ return this.name == Path.parentId; } public static ToParent(): Component{ return new Component(Path.parentId); } public toString(): string | null{ if (this.isIndex) { return this.index.toString(); } else { return this.name; } } public Equals(otherComp: Component): boolean{ if (otherComp != null && otherComp.isIndex == this.isIndex) { if (this.isIndex) { return this.index == otherComp.index; } else { return this.name == otherComp.name; } } return false; } } }