| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 | 'use strict';/** * @module symbol-tree * @author Joris van der Wel <joris@jorisvanderwel.com> */const SymbolTreeNode = require('./SymbolTreeNode');const TreePosition = require('./TreePosition');const TreeIterator = require('./TreeIterator');function returnTrue() {        return true;}function reverseArrayIndex(array, reverseIndex) {        return array[array.length - 1 - reverseIndex]; // no need to check `index >= 0`}class SymbolTree {        /**         * @constructor         * @alias module:symbol-tree         * @param {string} [description='SymbolTree data'] Description used for the Symbol         */        constructor(description) {                this.symbol = Symbol(description || 'SymbolTree data');        }        /**         * You can use this function to (optionally) initialize an object right after its creation,         * to take advantage of V8's fast properties. Also useful if you would like to         * freeze your object.         *         * `O(1)`         *         * @method         * @alias module:symbol-tree#initialize         * @param {Object} object         * @return {Object} object         */        initialize(object) {                this._node(object);                return object;        }        _node(object) {                if (!object) {                        return null;                }                const node = object[this.symbol];                if (node) {                        return node;                }                return (object[this.symbol] = new SymbolTreeNode());        }        /**         * Returns `true` if the object has any children. Otherwise it returns `false`.         *         * * `O(1)`         *         * @method hasChildren         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Boolean}         */        hasChildren(object) {                return this._node(object).hasChildren;        }        /**         * Returns the first child of the given object.         *         * * `O(1)`         *         * @method firstChild         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        firstChild(object) {                return this._node(object).firstChild;        }        /**         * Returns the last child of the given object.         *         * * `O(1)`         *         * @method lastChild         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        lastChild(object) {                return this._node(object).lastChild;        }        /**         * Returns the previous sibling of the given object.         *         * * `O(1)`         *         * @method previousSibling         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        previousSibling(object) {                return this._node(object).previousSibling;        }        /**         * Returns the next sibling of the given object.         *         * * `O(1)`         *         * @method nextSibling         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        nextSibling(object) {                return this._node(object).nextSibling;        }        /**         * Return the parent of the given object.         *         * * `O(1)`         *         * @method parent         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        parent(object) {                return this._node(object).parent;        }        /**         * Find the inclusive descendant that is last in tree order of the given object.         *         * * `O(n)` (worst case) where `n` is the depth of the subtree of `object`         *         * @method lastInclusiveDescendant         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object}         */        lastInclusiveDescendant(object) {                let lastChild;                let current = object;                while ((lastChild = this._node(current).lastChild)) {                        current = lastChild;                }                return current;        }        /**         * Find the preceding object (A) of the given object (B).         * An object A is preceding an object B if A and B are in the same tree         * and A comes before B in tree order.         *         * * `O(n)` (worst case)         * * `O(1)` (amortized when walking the entire tree)         *         * @method preceding         * @memberOf module:symbol-tree#         * @param {Object} object         * @param {Object} [options]         * @param {Object} [options.root] If set, `root` must be an inclusive ancestor         *        of the return value (or else null is returned). This check _assumes_         *        that `root` is also an inclusive ancestor of the given `object`         * @return {?Object}         */        preceding(object, options) {                const treeRoot = options && options.root;                if (object === treeRoot) {                        return null;                }                const previousSibling = this._node(object).previousSibling;                if (previousSibling) {                        return this.lastInclusiveDescendant(previousSibling);                }                // if there is no previous sibling return the parent (might be null)                return this._node(object).parent;        }        /**         * Find the following object (A) of the given object (B).         * An object A is following an object B if A and B are in the same tree         * and A comes after B in tree order.         *         * * `O(n)` (worst case) where `n` is the amount of objects in the entire tree         * * `O(1)` (amortized when walking the entire tree)         *         * @method following         * @memberOf module:symbol-tree#         * @param {!Object} object         * @param {Object} [options]         * @param {Object} [options.root] If set, `root` must be an inclusive ancestor         *        of the return value (or else null is returned). This check _assumes_         *        that `root` is also an inclusive ancestor of the given `object`         * @param {Boolean} [options.skipChildren=false] If set, ignore the children of `object`         * @return {?Object}         */        following(object, options) {                const treeRoot = options && options.root;                const skipChildren = options && options.skipChildren;                const firstChild = !skipChildren && this._node(object).firstChild;                if (firstChild) {                        return firstChild;                }                let current = object;                do {                        if (current === treeRoot) {                                return null;                        }                        const nextSibling = this._node(current).nextSibling;                        if (nextSibling) {                                return nextSibling;                        }                        current = this._node(current).parent;                } while (current);                return null;        }        /**         * Append all children of the given object to an array.         *         * * `O(n)` where `n` is the amount of children of the given `parent`         *         * @method childrenToArray         * @memberOf module:symbol-tree#         * @param {Object} parent         * @param {Object} [options]         * @param {Object[]} [options.array=[]]         * @param {Function} [options.filter] Function to test each object before it is added to the array.         *                            Invoked with arguments (object). Should return `true` if an object         *                            is to be included.         * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.         * @return {Object[]}         */        childrenToArray(parent, options) {                const array   = (options && options.array) || [];                const filter  = (options && options.filter) || returnTrue;                const thisArg = (options && options.thisArg) || undefined;                const parentNode = this._node(parent);                let object = parentNode.firstChild;                let index = 0;                while (object) {                        const node = this._node(object);                        node.setCachedIndex(parentNode, index);                        if (filter.call(thisArg, object)) {                                array.push(object);                        }                        object = node.nextSibling;                        ++index;                }                return array;        }        /**         * Append all inclusive ancestors of the given object to an array.         *         * * `O(n)` where `n` is the amount of ancestors of the given `object`         *         * @method ancestorsToArray         * @memberOf module:symbol-tree#         * @param {Object} object         * @param {Object} [options]         * @param {Object[]} [options.array=[]]         * @param {Function} [options.filter] Function to test each object before it is added to the array.         *                            Invoked with arguments (object). Should return `true` if an object         *                            is to be included.         * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.         * @return {Object[]}         */        ancestorsToArray(object, options) {                const array   = (options && options.array) || [];                const filter  = (options && options.filter) || returnTrue;                const thisArg = (options && options.thisArg) || undefined;                let ancestor = object;                while (ancestor) {                        if (filter.call(thisArg, ancestor)) {                                array.push(ancestor);                        }                        ancestor = this._node(ancestor).parent;                }                return array;        }        /**         * Append all descendants of the given object to an array (in tree order).         *         * * `O(n)` where `n` is the amount of objects in the sub-tree of the given `object`         *         * @method treeToArray         * @memberOf module:symbol-tree#         * @param {Object} root         * @param {Object} [options]         * @param {Object[]} [options.array=[]]         * @param {Function} [options.filter] Function to test each object before it is added to the array.         *                            Invoked with arguments (object). Should return `true` if an object         *                            is to be included.         * @param {*} [options.thisArg] Value to use as `this` when executing `filter`.         * @return {Object[]}         */        treeToArray(root, options) {                const array   = (options && options.array) || [];                const filter  = (options && options.filter) || returnTrue;                const thisArg = (options && options.thisArg) || undefined;                let object = root;                while (object) {                        if (filter.call(thisArg, object)) {                                array.push(object);                        }                        object = this.following(object, {root: root});                }                return array;        }        /**         * Iterate over all children of the given object         *         * * `O(1)` for a single iteration         *         * @method childrenIterator         * @memberOf module:symbol-tree#         * @param {Object} parent         * @param {Object} [options]         * @param {Boolean} [options.reverse=false]         * @return {Object} An iterable iterator (ES6)         */        childrenIterator(parent, options) {                const reverse = options && options.reverse;                const parentNode = this._node(parent);                return new TreeIterator(                        this,                        parent,                        reverse ? parentNode.lastChild : parentNode.firstChild,                        reverse ? TreeIterator.PREV : TreeIterator.NEXT                );        }        /**         * Iterate over all the previous siblings of the given object. (in reverse tree order)         *         * * `O(1)` for a single iteration         *         * @method previousSiblingsIterator         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object} An iterable iterator (ES6)         */        previousSiblingsIterator(object) {                return new TreeIterator(                        this,                        object,                        this._node(object).previousSibling,                        TreeIterator.PREV                );        }        /**         * Iterate over all the next siblings of the given object. (in tree order)         *         * * `O(1)` for a single iteration         *         * @method nextSiblingsIterator         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object} An iterable iterator (ES6)         */        nextSiblingsIterator(object) {                return new TreeIterator(                        this,                        object,                        this._node(object).nextSibling,                        TreeIterator.NEXT                );        }        /**         * Iterate over all inclusive ancestors of the given object         *         * * `O(1)` for a single iteration         *         * @method ancestorsIterator         * @memberOf module:symbol-tree#         * @param {Object} object         * @return {Object} An iterable iterator (ES6)         */        ancestorsIterator(object) {                return new TreeIterator(                        this,                        object,                        object,                        TreeIterator.PARENT                );        }        /**         * Iterate over all descendants of the given object (in tree order).         *         * Where `n` is the amount of objects in the sub-tree of the given `root`:         *         * * `O(n)` (worst case for a single iteration)         * * `O(n)` (amortized, when completing the iterator)         *         * @method treeIterator         * @memberOf module:symbol-tree#         * @param {Object} root         * @param {Object} options         * @param {Boolean} [options.reverse=false]         * @return {Object} An iterable iterator (ES6)         */        treeIterator(root, options) {                const reverse = options && options.reverse;                return new TreeIterator(                        this,                        root,                        reverse ? this.lastInclusiveDescendant(root) : root,                        reverse ? TreeIterator.PRECEDING : TreeIterator.FOLLOWING                );        }        /**         * Find the index of the given object (the number of preceding siblings).         *         * * `O(n)` where `n` is the amount of preceding siblings         * * `O(1)` (amortized, if the tree is not modified)         *         * @method index         * @memberOf module:symbol-tree#         * @param {Object} child         * @return {Number} The number of preceding siblings, or -1 if the object has no parent         */        index(child) {                const childNode = this._node(child);                const parentNode = this._node(childNode.parent);                if (!parentNode) {                        // In principal, you could also find out the number of preceding siblings                        // for objects that do not have a parent. This method limits itself only to                        // objects that have a parent because that lets us optimize more.                        return -1;                }                let currentIndex = childNode.getCachedIndex(parentNode);                if (currentIndex >= 0) {                        return currentIndex;                }                currentIndex = 0;                let object = parentNode.firstChild;                if (parentNode.childIndexCachedUpTo) {                        const cachedUpToNode = this._node(parentNode.childIndexCachedUpTo);                        object = cachedUpToNode.nextSibling;                        currentIndex = cachedUpToNode.getCachedIndex(parentNode) + 1;                }                while (object) {                        const node = this._node(object);                        node.setCachedIndex(parentNode, currentIndex);                        if (object === child) {                                break;                        }                        ++currentIndex;                        object = node.nextSibling;                }                parentNode.childIndexCachedUpTo = child;                return currentIndex;        }        /**         * Calculate the number of children.         *         * * `O(n)` where `n` is the amount of children         * * `O(1)` (amortized, if the tree is not modified)         *         * @method childrenCount         * @memberOf module:symbol-tree#         * @param {Object} parent         * @return {Number}         */        childrenCount(parent) {                const parentNode = this._node(parent);                if (!parentNode.lastChild) {                        return 0;                }                return this.index(parentNode.lastChild) + 1;        }        /**         * Compare the position of an object relative to another object. A bit set is returned:         *         * <ul>         *     <li>DISCONNECTED : 1</li>         *     <li>PRECEDING : 2</li>         *     <li>FOLLOWING : 4</li>         *     <li>CONTAINS : 8</li>         *     <li>CONTAINED_BY : 16</li>         * </ul>         *         * The semantics are the same as compareDocumentPosition in DOM, with the exception that         * DISCONNECTED never occurs with any other bit.         *         * where `n` and `m` are the amount of ancestors of `left` and `right`;         * where `o` is the amount of children of the lowest common ancestor of `left` and `right`:         *         * * `O(n + m + o)` (worst case)         * * `O(n + m)` (amortized, if the tree is not modified)         *         * @method compareTreePosition         * @memberOf module:symbol-tree#         * @param {Object} left         * @param {Object} right         * @return {Number}         */        compareTreePosition(left, right) {                // In DOM terms:                // left = reference / context object                // right = other                if (left === right) {                        return 0;                }                /* jshint -W016 */                const leftAncestors = []; { // inclusive                        let leftAncestor = left;                        while (leftAncestor) {                                if (leftAncestor === right) {                                        return TreePosition.CONTAINS | TreePosition.PRECEDING;                                        // other is ancestor of reference                                }                                leftAncestors.push(leftAncestor);                                leftAncestor = this.parent(leftAncestor);                        }                }                const rightAncestors = []; {                        let rightAncestor = right;                        while (rightAncestor) {                                if (rightAncestor === left) {                                        return TreePosition.CONTAINED_BY | TreePosition.FOLLOWING;                                }                                rightAncestors.push(rightAncestor);                                rightAncestor = this.parent(rightAncestor);                        }                }                const root = reverseArrayIndex(leftAncestors, 0);                if (!root || root !== reverseArrayIndex(rightAncestors, 0)) {                        // note: unlike DOM, preceding / following is not set here                        return TreePosition.DISCONNECTED;                }                // find the lowest common ancestor                let commonAncestorIndex = 0;                const ancestorsMinLength = Math.min(leftAncestors.length, rightAncestors.length);                for (let i = 0; i < ancestorsMinLength; ++i) {                        const leftAncestor  = reverseArrayIndex(leftAncestors, i);                        const rightAncestor = reverseArrayIndex(rightAncestors, i);                        if (leftAncestor !== rightAncestor) {                                break;                        }                        commonAncestorIndex = i;                }                // indexes within the common ancestor                const leftIndex  = this.index(reverseArrayIndex(leftAncestors, commonAncestorIndex + 1));                const rightIndex = this.index(reverseArrayIndex(rightAncestors, commonAncestorIndex + 1));                return rightIndex < leftIndex                        ? TreePosition.PRECEDING                        : TreePosition.FOLLOWING;        }        /**         * Remove the object from this tree.         * Has no effect if already removed.         *         * * `O(1)`         *         * @method remove         * @memberOf module:symbol-tree#         * @param {Object} removeObject         * @return {Object} removeObject         */        remove(removeObject) {                const removeNode = this._node(removeObject);                const parentNode = this._node(removeNode.parent);                const prevNode = this._node(removeNode.previousSibling);                const nextNode = this._node(removeNode.nextSibling);                if (parentNode) {                        if (parentNode.firstChild === removeObject) {                                parentNode.firstChild = removeNode.nextSibling;                        }                        if (parentNode.lastChild === removeObject) {                                parentNode.lastChild = removeNode.previousSibling;                        }                }                if (prevNode) {                        prevNode.nextSibling = removeNode.nextSibling;                }                if (nextNode) {                        nextNode.previousSibling = removeNode.previousSibling;                }                removeNode.parent = null;                removeNode.previousSibling = null;                removeNode.nextSibling = null;                removeNode.cachedIndex = -1;                removeNode.cachedIndexVersion = NaN;                if (parentNode) {                        parentNode.childrenChanged();                }                return removeObject;        }        /**         * Insert the given object before the reference object.         * `newObject` is now the previous sibling of `referenceObject`.         *         * * `O(1)`         *         * @method insertBefore         * @memberOf module:symbol-tree#         * @param {Object} referenceObject         * @param {Object} newObject         * @throws {Error} If the newObject is already present in this SymbolTree         * @return {Object} newObject         */        insertBefore(referenceObject, newObject) {                const referenceNode = this._node(referenceObject);                const prevNode = this._node(referenceNode.previousSibling);                const newNode = this._node(newObject);                const parentNode = this._node(referenceNode.parent);                if (newNode.isAttached) {                        throw Error('Given object is already present in this SymbolTree, remove it first');                }                newNode.parent = referenceNode.parent;                newNode.previousSibling = referenceNode.previousSibling;                newNode.nextSibling = referenceObject;                referenceNode.previousSibling = newObject;                if (prevNode) {                        prevNode.nextSibling = newObject;                }                if (parentNode && parentNode.firstChild === referenceObject) {                        parentNode.firstChild = newObject;                }                if (parentNode) {                        parentNode.childrenChanged();                }                return newObject;        }        /**         * Insert the given object after the reference object.         * `newObject` is now the next sibling of `referenceObject`.         *         * * `O(1)`         *         * @method insertAfter         * @memberOf module:symbol-tree#         * @param {Object} referenceObject         * @param {Object} newObject         * @throws {Error} If the newObject is already present in this SymbolTree         * @return {Object} newObject         */        insertAfter(referenceObject, newObject) {                const referenceNode = this._node(referenceObject);                const nextNode = this._node(referenceNode.nextSibling);                const newNode = this._node(newObject);                const parentNode = this._node(referenceNode.parent);                if (newNode.isAttached) {                        throw Error('Given object is already present in this SymbolTree, remove it first');                }                newNode.parent = referenceNode.parent;                newNode.previousSibling = referenceObject;                newNode.nextSibling = referenceNode.nextSibling;                referenceNode.nextSibling = newObject;                if (nextNode) {                        nextNode.previousSibling = newObject;                }                if (parentNode && parentNode.lastChild === referenceObject) {                        parentNode.lastChild = newObject;                }                if (parentNode) {                        parentNode.childrenChanged();                }                return newObject;        }        /**         * Insert the given object as the first child of the given reference object.         * `newObject` is now the first child of `referenceObject`.         *         * * `O(1)`         *         * @method prependChild         * @memberOf module:symbol-tree#         * @param {Object} referenceObject         * @param {Object} newObject         * @throws {Error} If the newObject is already present in this SymbolTree         * @return {Object} newObject         */        prependChild(referenceObject, newObject) {                const referenceNode = this._node(referenceObject);                const newNode = this._node(newObject);                if (newNode.isAttached) {                        throw Error('Given object is already present in this SymbolTree, remove it first');                }                if (referenceNode.hasChildren) {                        this.insertBefore(referenceNode.firstChild, newObject);                }                else {                        newNode.parent = referenceObject;                        referenceNode.firstChild = newObject;                        referenceNode.lastChild = newObject;                        referenceNode.childrenChanged();                }                return newObject;        }        /**         * Insert the given object as the last child of the given reference object.         * `newObject` is now the last child of `referenceObject`.         *         * * `O(1)`         *         * @method appendChild         * @memberOf module:symbol-tree#         * @param {Object} referenceObject         * @param {Object} newObject         * @throws {Error} If the newObject is already present in this SymbolTree         * @return {Object} newObject         */        appendChild(referenceObject, newObject) {                const referenceNode = this._node(referenceObject);                const newNode = this._node(newObject);                if (newNode.isAttached) {                        throw Error('Given object is already present in this SymbolTree, remove it first');                }                if (referenceNode.hasChildren) {                        this.insertAfter(referenceNode.lastChild, newObject);                }                else {                        newNode.parent = referenceObject;                        referenceNode.firstChild = newObject;                        referenceNode.lastChild = newObject;                        referenceNode.childrenChanged();                }                return newObject;        }}module.exports = SymbolTree;SymbolTree.TreePosition = TreePosition;
 |