• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3//Const
4var NOAH_ARK_CAPACITY = 3;
5
6//List of formatting elements
7var FormattingElementList = module.exports = function (treeAdapter) {
8    this.length = 0;
9    this.entries = [];
10    this.treeAdapter = treeAdapter;
11    this.bookmark = null;
12};
13
14//Entry types
15FormattingElementList.MARKER_ENTRY = 'MARKER_ENTRY';
16FormattingElementList.ELEMENT_ENTRY = 'ELEMENT_ENTRY';
17
18//Noah Ark's condition
19//OPTIMIZATION: at first we try to find possible candidates for exclusion using
20//lightweight heuristics without thorough attributes check.
21FormattingElementList.prototype._getNoahArkConditionCandidates = function (newElement) {
22    var candidates = [];
23
24    if (this.length >= NOAH_ARK_CAPACITY) {
25        var neAttrsLength = this.treeAdapter.getAttrList(newElement).length,
26            neTagName = this.treeAdapter.getTagName(newElement),
27            neNamespaceURI = this.treeAdapter.getNamespaceURI(newElement);
28
29        for (var i = this.length - 1; i >= 0; i--) {
30            var entry = this.entries[i];
31
32            if (entry.type === FormattingElementList.MARKER_ENTRY)
33                break;
34
35            var element = entry.element,
36                elementAttrs = this.treeAdapter.getAttrList(element);
37
38            if (this.treeAdapter.getTagName(element) === neTagName &&
39                this.treeAdapter.getNamespaceURI(element) === neNamespaceURI &&
40                elementAttrs.length === neAttrsLength) {
41                candidates.push({idx: i, attrs: elementAttrs});
42            }
43        }
44    }
45
46    return candidates.length < NOAH_ARK_CAPACITY ? [] : candidates;
47};
48
49FormattingElementList.prototype._ensureNoahArkCondition = function (newElement) {
50    var candidates = this._getNoahArkConditionCandidates(newElement),
51        cLength = candidates.length;
52
53    if (cLength) {
54        var neAttrs = this.treeAdapter.getAttrList(newElement),
55            neAttrsLength = neAttrs.length,
56            neAttrsMap = {};
57
58        //NOTE: build attrs map for the new element so we can perform fast lookups
59        for (var i = 0; i < neAttrsLength; i++) {
60            var neAttr = neAttrs[i];
61
62            neAttrsMap[neAttr.name] = neAttr.value;
63        }
64
65        for (var i = 0; i < neAttrsLength; i++) {
66            for (var j = 0; j < cLength; j++) {
67                var cAttr = candidates[j].attrs[i];
68
69                if (neAttrsMap[cAttr.name] !== cAttr.value) {
70                    candidates.splice(j, 1);
71                    cLength--;
72                }
73
74                if (candidates.length < NOAH_ARK_CAPACITY)
75                    return;
76            }
77        }
78
79        //NOTE: remove bottommost candidates until Noah's Ark condition will not be met
80        for (var i = cLength - 1; i >= NOAH_ARK_CAPACITY - 1; i--) {
81            this.entries.splice(candidates[i].idx, 1);
82            this.length--;
83        }
84    }
85};
86
87//Mutations
88FormattingElementList.prototype.insertMarker = function () {
89    this.entries.push({type: FormattingElementList.MARKER_ENTRY});
90    this.length++;
91};
92
93FormattingElementList.prototype.pushElement = function (element, token) {
94    this._ensureNoahArkCondition(element);
95
96    this.entries.push({
97        type: FormattingElementList.ELEMENT_ENTRY,
98        element: element,
99        token: token
100    });
101
102    this.length++;
103};
104
105FormattingElementList.prototype.insertElementAfterBookmark = function (element, token) {
106    var bookmarkIdx = this.length - 1;
107
108    for (; bookmarkIdx >= 0; bookmarkIdx--) {
109        if (this.entries[bookmarkIdx] === this.bookmark)
110            break;
111    }
112
113    this.entries.splice(bookmarkIdx + 1, 0, {
114        type: FormattingElementList.ELEMENT_ENTRY,
115        element: element,
116        token: token
117    });
118
119    this.length++;
120};
121
122FormattingElementList.prototype.removeEntry = function (entry) {
123    for (var i = this.length - 1; i >= 0; i--) {
124        if (this.entries[i] === entry) {
125            this.entries.splice(i, 1);
126            this.length--;
127            break;
128        }
129    }
130};
131
132FormattingElementList.prototype.clearToLastMarker = function () {
133    while (this.length) {
134        var entry = this.entries.pop();
135
136        this.length--;
137
138        if (entry.type === FormattingElementList.MARKER_ENTRY)
139            break;
140    }
141};
142
143//Search
144FormattingElementList.prototype.getElementEntryInScopeWithTagName = function (tagName) {
145    for (var i = this.length - 1; i >= 0; i--) {
146        var entry = this.entries[i];
147
148        if (entry.type === FormattingElementList.MARKER_ENTRY)
149            return null;
150
151        if (this.treeAdapter.getTagName(entry.element) === tagName)
152            return entry;
153    }
154
155    return null;
156};
157
158FormattingElementList.prototype.getElementEntry = function (element) {
159    for (var i = this.length - 1; i >= 0; i--) {
160        var entry = this.entries[i];
161
162        if (entry.type === FormattingElementList.ELEMENT_ENTRY && entry.element == element)
163            return entry;
164    }
165
166    return null;
167};
168