All files / lib/pipeline/module pageBuilder.ts

88.37% Statements 38/43
50% Branches 3/6
85.71% Functions 6/7
87.8% Lines 36/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 11526x                 26x   10x 10x 9x       10x     10x     10x     10x     10x 10x 10x 10x       10x   10x 10x                               10x 10x     10x       26x               10x     10x   9x     9x       46x 10x     10x 1x 1x     10x         26x           10x     10x     46x 18x     10x  
import { DocumentNode, NodeType, TagNode, NodeWithChildren, Node } from '../..';
 
/**
 * Transform an arbitrary fragment DOM into a valid HTML page.
 * Will create <html>, <head>, <body>, and <title> tags if not already present.
 * Duplicates of those tags will be merged, and the final tags will all be placed in the correct order
 * 
 * @param dom DOM to transform
 */
export function buildPage(dom: DocumentNode): void {
    // extract PIs
    const procIns = dom.findChildNodesByNodeType(NodeType.ProcessingInstruction);
    for (const procIn of procIns) {
        procIn.removeSelf();
    }
 
    // init head
    const headTag = createHead(dom);
 
    // init body
    const bodyTag = createBody(dom);
 
    // get or create html
    const htmlTag = createHtml(dom);
 
    // remove any left over nodes
    dom.clear();
 
    // rebuild page
    dom.appendChildren(procIns);
    dom.appendChild(htmlTag);
    htmlTag.appendChild(headTag);
    htmlTag.appendChild(bodyTag);
}
 
function createHtml(dom: DocumentNode): TagNode {
    const htmlTag = new TagNode('html');
 
    const sourceHtmlTags = dom.findChildTagsByTagName('html');
    for (const sourceHtml of sourceHtmlTags) {
        // copy children
        htmlTag.appendChildren(sourceHtml.childNodes);
 
        // copy attributes
        sourceHtml.getAttributes().forEach((value, key) => {
            if (!htmlTag.hasAttribute(key)) {
                htmlTag.setRawAttribute(key, value);
            }
        });
 
        // remove HTML tag
        sourceHtml.removeSelf();
    }
 
    // add required "lang" attribute if missing
    Eif (!htmlTag.hasAttribute('lang')) {
        htmlTag.setAttribute('lang', 'en');
    }
 
    return htmlTag;
}
 
// Tags that are only allowed (or should only be used) in the <head> section
const headTags = [
    'style',
    'link',
    'meta',
    'base'
];
 
function createHead(root: NodeWithChildren): TagNode {
    const headTag = new TagNode('head');
 
    // extract children of head tags into new head
    for (const existingHead of root.findChildTagsByTagName('head')) {
        // move children
        headTag.appendChildren(existingHead.childNodes);
 
        // remove the old head when done
        existingHead.removeSelf();
    }
 
    // move elements that can only exist in the head
    const headContents: Node[] = root.findChildTags((tag: TagNode) => headTags.includes(tag.tagName));
    headTag.appendChildren(headContents);
 
    // add title if missing
    if (headTag.findChildTagByTagName('title', false) === null) {
        const titleTag = new TagNode('title');
        headTag.appendChild(titleTag);
    }
 
    return headTag;
}
 
// Tags that should be "promoted" in the <body> section.
// Promoting a tag means removing it but preserving its children.
const bodyPromoteTags = [
    'html',
    'body'
];
 
function createBody(root: NodeWithChildren): TagNode {
    const bodyTag = new TagNode('body');
 
    // copy all remaining elements from dom
    bodyTag.appendChildren(root.childNodes);
 
    // "promote" children of nested <head> tags
    for (const childBody of bodyTag.findChildTags((tag: TagNode) => bodyPromoteTags.includes(tag.tagName))) {
        childBody.removeSelf(true);
    }
 
    return bodyTag;
}