| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 | 'use strict';const HTML = require('../common/html');//Aliasesconst $ = HTML.TAG_NAMES;const NS = HTML.NAMESPACES;//Element utils//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.//It's faster than using dictionary.function isImpliedEndTagRequired(tn) {    switch (tn.length) {        case 1:            return tn === $.P;        case 2:            return tn === $.RB || tn === $.RP || tn === $.RT || tn === $.DD || tn === $.DT || tn === $.LI;        case 3:            return tn === $.RTC;        case 6:            return tn === $.OPTION;        case 8:            return tn === $.OPTGROUP;    }    return false;}function isImpliedEndTagRequiredThoroughly(tn) {    switch (tn.length) {        case 1:            return tn === $.P;        case 2:            return (                tn === $.RB ||                tn === $.RP ||                tn === $.RT ||                tn === $.DD ||                tn === $.DT ||                tn === $.LI ||                tn === $.TD ||                tn === $.TH ||                tn === $.TR            );        case 3:            return tn === $.RTC;        case 5:            return tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD;        case 6:            return tn === $.OPTION;        case 7:            return tn === $.CAPTION;        case 8:            return tn === $.OPTGROUP || tn === $.COLGROUP;    }    return false;}function isScopingElement(tn, ns) {    switch (tn.length) {        case 2:            if (tn === $.TD || tn === $.TH) {                return ns === NS.HTML;            } else if (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS) {                return ns === NS.MATHML;            }            break;        case 4:            if (tn === $.HTML) {                return ns === NS.HTML;            } else if (tn === $.DESC) {                return ns === NS.SVG;            }            break;        case 5:            if (tn === $.TABLE) {                return ns === NS.HTML;            } else if (tn === $.MTEXT) {                return ns === NS.MATHML;            } else if (tn === $.TITLE) {                return ns === NS.SVG;            }            break;        case 6:            return (tn === $.APPLET || tn === $.OBJECT) && ns === NS.HTML;        case 7:            return (tn === $.CAPTION || tn === $.MARQUEE) && ns === NS.HTML;        case 8:            return tn === $.TEMPLATE && ns === NS.HTML;        case 13:            return tn === $.FOREIGN_OBJECT && ns === NS.SVG;        case 14:            return tn === $.ANNOTATION_XML && ns === NS.MATHML;    }    return false;}//Stack of open elementsclass OpenElementStack {    constructor(document, treeAdapter) {        this.stackTop = -1;        this.items = [];        this.current = document;        this.currentTagName = null;        this.currentTmplContent = null;        this.tmplCount = 0;        this.treeAdapter = treeAdapter;    }    //Index of element    _indexOf(element) {        let idx = -1;        for (let i = this.stackTop; i >= 0; i--) {            if (this.items[i] === element) {                idx = i;                break;            }        }        return idx;    }    //Update current element    _isInTemplate() {        return this.currentTagName === $.TEMPLATE && this.treeAdapter.getNamespaceURI(this.current) === NS.HTML;    }    _updateCurrentElement() {        this.current = this.items[this.stackTop];        this.currentTagName = this.current && this.treeAdapter.getTagName(this.current);        this.currentTmplContent = this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : null;    }    //Mutations    push(element) {        this.items[++this.stackTop] = element;        this._updateCurrentElement();        if (this._isInTemplate()) {            this.tmplCount++;        }    }    pop() {        this.stackTop--;        if (this.tmplCount > 0 && this._isInTemplate()) {            this.tmplCount--;        }        this._updateCurrentElement();    }    replace(oldElement, newElement) {        const idx = this._indexOf(oldElement);        this.items[idx] = newElement;        if (idx === this.stackTop) {            this._updateCurrentElement();        }    }    insertAfter(referenceElement, newElement) {        const insertionIdx = this._indexOf(referenceElement) + 1;        this.items.splice(insertionIdx, 0, newElement);        if (insertionIdx === ++this.stackTop) {            this._updateCurrentElement();        }    }    popUntilTagNamePopped(tagName) {        while (this.stackTop > -1) {            const tn = this.currentTagName;            const ns = this.treeAdapter.getNamespaceURI(this.current);            this.pop();            if (tn === tagName && ns === NS.HTML) {                break;            }        }    }    popUntilElementPopped(element) {        while (this.stackTop > -1) {            const poppedElement = this.current;            this.pop();            if (poppedElement === element) {                break;            }        }    }    popUntilNumberedHeaderPopped() {        while (this.stackTop > -1) {            const tn = this.currentTagName;            const ns = this.treeAdapter.getNamespaceURI(this.current);            this.pop();            if (                tn === $.H1 ||                tn === $.H2 ||                tn === $.H3 ||                tn === $.H4 ||                tn === $.H5 ||                (tn === $.H6 && ns === NS.HTML)            ) {                break;            }        }    }    popUntilTableCellPopped() {        while (this.stackTop > -1) {            const tn = this.currentTagName;            const ns = this.treeAdapter.getNamespaceURI(this.current);            this.pop();            if (tn === $.TD || (tn === $.TH && ns === NS.HTML)) {                break;            }        }    }    popAllUpToHtmlElement() {        //NOTE: here we assume that root <html> element is always first in the open element stack, so        //we perform this fast stack clean up.        this.stackTop = 0;        this._updateCurrentElement();    }    clearBackToTableContext() {        while (            (this.currentTagName !== $.TABLE && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) ||            this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML        ) {            this.pop();        }    }    clearBackToTableBodyContext() {        while (            (this.currentTagName !== $.TBODY &&                this.currentTagName !== $.TFOOT &&                this.currentTagName !== $.THEAD &&                this.currentTagName !== $.TEMPLATE &&                this.currentTagName !== $.HTML) ||            this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML        ) {            this.pop();        }    }    clearBackToTableRowContext() {        while (            (this.currentTagName !== $.TR && this.currentTagName !== $.TEMPLATE && this.currentTagName !== $.HTML) ||            this.treeAdapter.getNamespaceURI(this.current) !== NS.HTML        ) {            this.pop();        }    }    remove(element) {        for (let i = this.stackTop; i >= 0; i--) {            if (this.items[i] === element) {                this.items.splice(i, 1);                this.stackTop--;                this._updateCurrentElement();                break;            }        }    }    //Search    tryPeekProperlyNestedBodyElement() {        //Properly nested <body> element (should be second element in stack).        const element = this.items[1];        return element && this.treeAdapter.getTagName(element) === $.BODY ? element : null;    }    contains(element) {        return this._indexOf(element) > -1;    }    getCommonAncestor(element) {        let elementIdx = this._indexOf(element);        return --elementIdx >= 0 ? this.items[elementIdx] : null;    }    isRootHtmlElementCurrent() {        return this.stackTop === 0 && this.currentTagName === $.HTML;    }    //Element in scope    hasInScope(tagName) {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (tn === tagName && ns === NS.HTML) {                return true;            }            if (isScopingElement(tn, ns)) {                return false;            }        }        return true;    }    hasNumberedHeaderInScope() {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (                (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) &&                ns === NS.HTML            ) {                return true;            }            if (isScopingElement(tn, ns)) {                return false;            }        }        return true;    }    hasInListItemScope(tagName) {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (tn === tagName && ns === NS.HTML) {                return true;            }            if (((tn === $.UL || tn === $.OL) && ns === NS.HTML) || isScopingElement(tn, ns)) {                return false;            }        }        return true;    }    hasInButtonScope(tagName) {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (tn === tagName && ns === NS.HTML) {                return true;            }            if ((tn === $.BUTTON && ns === NS.HTML) || isScopingElement(tn, ns)) {                return false;            }        }        return true;    }    hasInTableScope(tagName) {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (ns !== NS.HTML) {                continue;            }            if (tn === tagName) {                return true;            }            if (tn === $.TABLE || tn === $.TEMPLATE || tn === $.HTML) {                return false;            }        }        return true;    }    hasTableBodyContextInTableScope() {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (ns !== NS.HTML) {                continue;            }            if (tn === $.TBODY || tn === $.THEAD || tn === $.TFOOT) {                return true;            }            if (tn === $.TABLE || tn === $.HTML) {                return false;            }        }        return true;    }    hasInSelectScope(tagName) {        for (let i = this.stackTop; i >= 0; i--) {            const tn = this.treeAdapter.getTagName(this.items[i]);            const ns = this.treeAdapter.getNamespaceURI(this.items[i]);            if (ns !== NS.HTML) {                continue;            }            if (tn === tagName) {                return true;            }            if (tn !== $.OPTION && tn !== $.OPTGROUP) {                return false;            }        }        return true;    }    //Implied end tags    generateImpliedEndTags() {        while (isImpliedEndTagRequired(this.currentTagName)) {            this.pop();        }    }    generateImpliedEndTagsThoroughly() {        while (isImpliedEndTagRequiredThoroughly(this.currentTagName)) {            this.pop();        }    }    generateImpliedEndTagsWithExclusion(exclusionTagName) {        while (isImpliedEndTagRequired(this.currentTagName) && this.currentTagName !== exclusionTagName) {            this.pop();        }    }}module.exports = OpenElementStack;
 |