aboutsummaryrefslogtreecommitdiff
path: root/node_modules/parse5/lib/parser/formatting-element-list.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/parse5/lib/parser/formatting-element-list.js')
-rw-r--r--node_modules/parse5/lib/parser/formatting-element-list.js181
1 files changed, 181 insertions, 0 deletions
diff --git a/node_modules/parse5/lib/parser/formatting-element-list.js b/node_modules/parse5/lib/parser/formatting-element-list.js
new file mode 100644
index 0000000..0e241db
--- /dev/null
+++ b/node_modules/parse5/lib/parser/formatting-element-list.js
@@ -0,0 +1,181 @@
+'use strict';
+
+//Const
+const NOAH_ARK_CAPACITY = 3;
+
+//List of formatting elements
+class FormattingElementList {
+ constructor(treeAdapter) {
+ this.length = 0;
+ this.entries = [];
+ this.treeAdapter = treeAdapter;
+ this.bookmark = null;
+ }
+
+ //Noah Ark's condition
+ //OPTIMIZATION: at first we try to find possible candidates for exclusion using
+ //lightweight heuristics without thorough attributes check.
+ _getNoahArkConditionCandidates(newElement) {
+ const candidates = [];
+
+ if (this.length >= NOAH_ARK_CAPACITY) {
+ const neAttrsLength = this.treeAdapter.getAttrList(newElement).length;
+ const neTagName = this.treeAdapter.getTagName(newElement);
+ const neNamespaceURI = this.treeAdapter.getNamespaceURI(newElement);
+
+ for (let i = this.length - 1; i >= 0; i--) {
+ const entry = this.entries[i];
+
+ if (entry.type === FormattingElementList.MARKER_ENTRY) {
+ break;
+ }
+
+ const element = entry.element;
+ const elementAttrs = this.treeAdapter.getAttrList(element);
+
+ const isCandidate =
+ this.treeAdapter.getTagName(element) === neTagName &&
+ this.treeAdapter.getNamespaceURI(element) === neNamespaceURI &&
+ elementAttrs.length === neAttrsLength;
+
+ if (isCandidate) {
+ candidates.push({ idx: i, attrs: elementAttrs });
+ }
+ }
+ }
+
+ return candidates.length < NOAH_ARK_CAPACITY ? [] : candidates;
+ }
+
+ _ensureNoahArkCondition(newElement) {
+ const candidates = this._getNoahArkConditionCandidates(newElement);
+ let cLength = candidates.length;
+
+ if (cLength) {
+ const neAttrs = this.treeAdapter.getAttrList(newElement);
+ const neAttrsLength = neAttrs.length;
+ const neAttrsMap = Object.create(null);
+
+ //NOTE: build attrs map for the new element so we can perform fast lookups
+ for (let i = 0; i < neAttrsLength; i++) {
+ const neAttr = neAttrs[i];
+
+ neAttrsMap[neAttr.name] = neAttr.value;
+ }
+
+ for (let i = 0; i < neAttrsLength; i++) {
+ for (let j = 0; j < cLength; j++) {
+ const cAttr = candidates[j].attrs[i];
+
+ if (neAttrsMap[cAttr.name] !== cAttr.value) {
+ candidates.splice(j, 1);
+ cLength--;
+ }
+
+ if (candidates.length < NOAH_ARK_CAPACITY) {
+ return;
+ }
+ }
+ }
+
+ //NOTE: remove bottommost candidates until Noah's Ark condition will not be met
+ for (let i = cLength - 1; i >= NOAH_ARK_CAPACITY - 1; i--) {
+ this.entries.splice(candidates[i].idx, 1);
+ this.length--;
+ }
+ }
+ }
+
+ //Mutations
+ insertMarker() {
+ this.entries.push({ type: FormattingElementList.MARKER_ENTRY });
+ this.length++;
+ }
+
+ pushElement(element, token) {
+ this._ensureNoahArkCondition(element);
+
+ this.entries.push({
+ type: FormattingElementList.ELEMENT_ENTRY,
+ element: element,
+ token: token
+ });
+
+ this.length++;
+ }
+
+ insertElementAfterBookmark(element, token) {
+ let bookmarkIdx = this.length - 1;
+
+ for (; bookmarkIdx >= 0; bookmarkIdx--) {
+ if (this.entries[bookmarkIdx] === this.bookmark) {
+ break;
+ }
+ }
+
+ this.entries.splice(bookmarkIdx + 1, 0, {
+ type: FormattingElementList.ELEMENT_ENTRY,
+ element: element,
+ token: token
+ });
+
+ this.length++;
+ }
+
+ removeEntry(entry) {
+ for (let i = this.length - 1; i >= 0; i--) {
+ if (this.entries[i] === entry) {
+ this.entries.splice(i, 1);
+ this.length--;
+ break;
+ }
+ }
+ }
+
+ clearToLastMarker() {
+ while (this.length) {
+ const entry = this.entries.pop();
+
+ this.length--;
+
+ if (entry.type === FormattingElementList.MARKER_ENTRY) {
+ break;
+ }
+ }
+ }
+
+ //Search
+ getElementEntryInScopeWithTagName(tagName) {
+ for (let i = this.length - 1; i >= 0; i--) {
+ const entry = this.entries[i];
+
+ if (entry.type === FormattingElementList.MARKER_ENTRY) {
+ return null;
+ }
+
+ if (this.treeAdapter.getTagName(entry.element) === tagName) {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ getElementEntry(element) {
+ for (let i = this.length - 1; i >= 0; i--) {
+ const entry = this.entries[i];
+
+ if (entry.type === FormattingElementList.ELEMENT_ENTRY && entry.element === element) {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+}
+
+//Entry types
+FormattingElementList.MARKER_ENTRY = 'MARKER_ENTRY';
+FormattingElementList.ELEMENT_ENTRY = 'ELEMENT_ENTRY';
+
+module.exports = FormattingElementList;