Skip to content

Commit 7764d97

Browse files
Compare relative tree position between two objects
1 parent c0511d6 commit 7764d97

File tree

3 files changed

+154
-1
lines changed

3 files changed

+154
-1
lines changed

lib/SymbolTree.js

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
*/
77

88
const SymbolTreeNode = require('./SymbolTreeNode');
9+
const TreePosition = require('./TreePosition');
910

1011
function returnTrue() {
1112
return true;
1213
}
1314

15+
function reverseArrayIndex(array, reverseIndex) {
16+
return array[array.length - 1 - reverseIndex]; // no need to check `index >= 0`
17+
}
18+
1419
class SymbolTree {
1520

1621
/**
@@ -528,7 +533,7 @@ class SymbolTree {
528533
*
529534
* @method childrenCount
530535
* @memberOf module:symbol-tree#
531-
* @param parent
536+
* @param {Object} parent
532537
* @return {Number}
533538
*/
534539
childrenCount(parent) {
@@ -541,6 +546,98 @@ class SymbolTree {
541546
return this.index(parentNode.last) + 1;
542547
}
543548

549+
/**
550+
* Compare the position of an object relative to another object. A bit set is returned:
551+
*
552+
* <ul>
553+
* <li>DISCONNECTED : 1</li>
554+
* <li>PRECEDING : 2</li>
555+
* <li>FOLLOWING : 4</li>
556+
* <li>CONTAINS : 8</li>
557+
* <li>CONTAINED_BY : 16</li>
558+
* </ul>
559+
*
560+
* The semantics are the same as compareDocumentPosition in DOM, with the exception that
561+
* DISCONNECTED never occurs with any other bit.
562+
*
563+
* `O(n)` (worst case)
564+
*
565+
* @method childrenCount
566+
* @memberOf module:symbol-tree#
567+
* @param {Object} left
568+
* @param {Object} right
569+
* @return {Number}
570+
*/
571+
compareTreePosition(left, right) {
572+
// In DOM terms:
573+
// left = reference / context object
574+
// right = other
575+
576+
if (left === right) {
577+
return 0;
578+
}
579+
580+
/* jshint -W016 */
581+
582+
const leftAncestors = []; { // inclusive
583+
let leftAncestor = left;
584+
585+
while (leftAncestor) {
586+
if (leftAncestor === right) {
587+
return TreePosition.CONTAINS | TreePosition.PRECEDING;
588+
// other is ancestor of reference
589+
}
590+
591+
leftAncestors.push(leftAncestor);
592+
leftAncestor = this.parent(leftAncestor);
593+
}
594+
}
595+
596+
597+
const rightAncestors = []; {
598+
let rightAncestor = right;
599+
600+
while (rightAncestor) {
601+
if (rightAncestor === left) {
602+
return TreePosition.CONTAINED_BY | TreePosition.FOLLOWING;
603+
}
604+
605+
rightAncestors.push(rightAncestor);
606+
rightAncestor = this.parent(rightAncestor);
607+
}
608+
}
609+
610+
611+
const root = reverseArrayIndex(leftAncestors, 0);
612+
613+
if (!root || root !== reverseArrayIndex(rightAncestors, 0)) {
614+
// note: unlike DOM, preceding / following is not set here
615+
return TreePosition.DISCONNECTED;
616+
}
617+
618+
let commonAncestorIndex = 0;
619+
const ancestorsMinLength = Math.min(leftAncestors.length, rightAncestors.length);
620+
621+
for (let i = 0; i < ancestorsMinLength; ++i) {
622+
const leftAncestor = reverseArrayIndex(leftAncestors , i);
623+
const rightAncestor = reverseArrayIndex(rightAncestors, i);
624+
625+
if (leftAncestor !== rightAncestor) {
626+
break;
627+
}
628+
629+
commonAncestorIndex = i;
630+
}
631+
632+
// indexes within the common ancestor
633+
const leftIndex = this.index(reverseArrayIndex(leftAncestors , commonAncestorIndex + 1));
634+
const rightIndex = this.index(reverseArrayIndex(rightAncestors, commonAncestorIndex + 1));
635+
636+
return rightIndex < leftIndex
637+
? TreePosition.PRECEDING
638+
: TreePosition.FOLLOWING;
639+
}
640+
544641
/**
545642
* Remove the object from this tree.
546643
* Has no effect if already removed.

lib/TreePosition.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
module.exports = Object.freeze({
4+
// same as DOM DOCUMENT_POSITION_
5+
DISCONNECTED : 1,
6+
PRECEDING : 2,
7+
FOLLOWING : 4,
8+
CONTAINS : 8,
9+
CONTAINED_BY : 16
10+
});

test/SymbolTree.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,3 +1007,49 @@ test('children count', function(t) {
10071007

10081008
t.end();
10091009
});
1010+
1011+
1012+
test('compare tree position', function(t) {
1013+
const tree = new SymbolTree();
1014+
const a = {};
1015+
const aa = {};
1016+
const aaa = {};
1017+
const ab = {};
1018+
const aba = {};
1019+
const abaa = {};
1020+
const ac = {};
1021+
1022+
const b = {};
1023+
const ba = {};
1024+
1025+
tree.insertLast(aa, a);
1026+
tree.insertLast(aaa, aa);
1027+
tree.insertLast(ab, a);
1028+
tree.insertLast(aba, ab);
1029+
tree.insertLast(abaa, aba);
1030+
tree.insertLast(ac, a);
1031+
1032+
tree.insertAfter(b, a);
1033+
tree.insertLast(ba, b);
1034+
1035+
t.equal(0, tree.compareTreePosition(a, a), 'object equal');
1036+
1037+
t.equal(1, tree.compareTreePosition(a, {}), 'object disconnected');
1038+
t.equal(1, tree.compareTreePosition(a, b), 'object disconnected');
1039+
1040+
t.equal(20, tree.compareTreePosition(a, aa), 'contained by & following');
1041+
t.equal(10, tree.compareTreePosition(aa, a), 'contains & preceding');
1042+
t.equal(20, tree.compareTreePosition(a, abaa), 'contained by & following');
1043+
t.equal(10, tree.compareTreePosition(abaa, a), 'contains & preceding');
1044+
1045+
t.equal(4, tree.compareTreePosition(aa, ab), 'following');
1046+
t.equal(2, tree.compareTreePosition(ab, aa), 'preceding');
1047+
t.equal(4, tree.compareTreePosition(aa, aba), 'following');
1048+
t.equal(2, tree.compareTreePosition(aba, aa), 'preceding');
1049+
t.equal(4, tree.compareTreePosition(aa, abaa), 'following');
1050+
t.equal(2, tree.compareTreePosition(abaa, aa), 'preceding');
1051+
t.equal(4, tree.compareTreePosition(aaa, abaa), 'following');
1052+
t.equal(2, tree.compareTreePosition(abaa, aaa), 'preceding');
1053+
1054+
t.end();
1055+
});

0 commit comments

Comments
 (0)