Spaces:
Paused
Paused
| //Const | |
| const NOAH_ARK_CAPACITY = 3; | |
| export var EntryType; | |
| (function (EntryType) { | |
| EntryType[EntryType["Marker"] = 0] = "Marker"; | |
| EntryType[EntryType["Element"] = 1] = "Element"; | |
| })(EntryType || (EntryType = {})); | |
| const MARKER = { type: EntryType.Marker }; | |
| //List of formatting elements | |
| export class FormattingElementList { | |
| constructor(treeAdapter) { | |
| this.treeAdapter = treeAdapter; | |
| this.entries = []; | |
| 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, neAttrs) { | |
| const candidates = []; | |
| const neAttrsLength = neAttrs.length; | |
| const neTagName = this.treeAdapter.getTagName(newElement); | |
| const neNamespaceURI = this.treeAdapter.getNamespaceURI(newElement); | |
| for (let i = 0; i < this.entries.length; i++) { | |
| const entry = this.entries[i]; | |
| if (entry.type === EntryType.Marker) { | |
| break; | |
| } | |
| const { element } = entry; | |
| if (this.treeAdapter.getTagName(element) === neTagName && | |
| this.treeAdapter.getNamespaceURI(element) === neNamespaceURI) { | |
| const elementAttrs = this.treeAdapter.getAttrList(element); | |
| if (elementAttrs.length === neAttrsLength) { | |
| candidates.push({ idx: i, attrs: elementAttrs }); | |
| } | |
| } | |
| } | |
| return candidates; | |
| } | |
| _ensureNoahArkCondition(newElement) { | |
| if (this.entries.length < NOAH_ARK_CAPACITY) | |
| return; | |
| const neAttrs = this.treeAdapter.getAttrList(newElement); | |
| const candidates = this._getNoahArkConditionCandidates(newElement, neAttrs); | |
| if (candidates.length < NOAH_ARK_CAPACITY) | |
| return; | |
| //NOTE: build attrs map for the new element, so we can perform fast lookups | |
| const neAttrsMap = new Map(neAttrs.map((neAttr) => [neAttr.name, neAttr.value])); | |
| let validCandidates = 0; | |
| //NOTE: remove bottommost candidates, until Noah's Ark condition will not be met | |
| for (let i = 0; i < candidates.length; i++) { | |
| const candidate = candidates[i]; | |
| // We know that `candidate.attrs.length === neAttrs.length` | |
| if (candidate.attrs.every((cAttr) => neAttrsMap.get(cAttr.name) === cAttr.value)) { | |
| validCandidates += 1; | |
| if (validCandidates >= NOAH_ARK_CAPACITY) { | |
| this.entries.splice(candidate.idx, 1); | |
| } | |
| } | |
| } | |
| } | |
| //Mutations | |
| insertMarker() { | |
| this.entries.unshift(MARKER); | |
| } | |
| pushElement(element, token) { | |
| this._ensureNoahArkCondition(element); | |
| this.entries.unshift({ | |
| type: EntryType.Element, | |
| element, | |
| token, | |
| }); | |
| } | |
| insertElementAfterBookmark(element, token) { | |
| const bookmarkIdx = this.entries.indexOf(this.bookmark); | |
| this.entries.splice(bookmarkIdx, 0, { | |
| type: EntryType.Element, | |
| element, | |
| token, | |
| }); | |
| } | |
| removeEntry(entry) { | |
| const entryIndex = this.entries.indexOf(entry); | |
| if (entryIndex !== -1) { | |
| this.entries.splice(entryIndex, 1); | |
| } | |
| } | |
| /** | |
| * Clears the list of formatting elements up to the last marker. | |
| * | |
| * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-list-of-active-formatting-elements-up-to-the-last-marker | |
| */ | |
| clearToLastMarker() { | |
| const markerIdx = this.entries.indexOf(MARKER); | |
| if (markerIdx === -1) { | |
| this.entries.length = 0; | |
| } | |
| else { | |
| this.entries.splice(0, markerIdx + 1); | |
| } | |
| } | |
| //Search | |
| getElementEntryInScopeWithTagName(tagName) { | |
| const entry = this.entries.find((entry) => entry.type === EntryType.Marker || this.treeAdapter.getTagName(entry.element) === tagName); | |
| return entry && entry.type === EntryType.Element ? entry : null; | |
| } | |
| getElementEntry(element) { | |
| return this.entries.find((entry) => entry.type === EntryType.Element && entry.element === element); | |
| } | |
| } | |