All files / roosterjs-content-model-core/lib/command/paste retrieveHtmlInfo.ts

100% Statements 41/41
85.71% Branches 24/28
100% Functions 8/8
100% Lines 39/39

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 1071x 1x       1x 1x                               1x       43x         43x 42x             42x     43x       42x   42x 285x 147x   147x 53x   138x 107x       42x       42x 42x   42x 39x     42x 46x     42x                 42x 42x 42x 42x 42x   42x 17x 17x 17x   25x     42x       42x   47x    
import { isBlockElement, isNodeOfType, toArray } from 'roosterjs-content-model-dom';
import { retrieveCssRules } from '../createModelFromHtml/convertInlineCss';
import type { ClipboardData } from 'roosterjs-content-model-types';
import type { CssRule } from '../createModelFromHtml/convertInlineCss';
 
const START_FRAGMENT = '<!--StartFragment-->';
const END_FRAGMENT = '<!--EndFragment-->';
 
/**
 * @internal
 */
export interface HtmlFromClipboard {
    metadata: Record<string, string>;
    globalCssRules: CssRule[];
    htmlBefore?: string;
    htmlAfter?: string;
    containsBlockElements?: boolean;
}
 
/**
 * @internal
 */
export function retrieveHtmlInfo(
    doc: Document | null,
    clipboardData: Partial<ClipboardData>
): HtmlFromClipboard {
    let result: HtmlFromClipboard = {
        metadata: {},
        globalCssRules: [],
    };
 
    if (doc) {
        result = {
            ...retrieveHtmlStrings(clipboardData),
            globalCssRules: retrieveCssRules(doc),
            metadata: retrieveMetadata(doc),
            containsBlockElements: checkBlockElements(doc),
        };
 
        clipboardData.htmlFirstLevelChildTags = retrieveTopLevelTags(doc);
    }
 
    return result;
}
 
function retrieveTopLevelTags(doc: Document): string[] {
    const topLevelTags: string[] = [];
 
    for (let child = doc.body.firstChild; child; child = child.nextSibling) {
        if (isNodeOfType(child, 'TEXT_NODE')) {
            const trimmedString = child.nodeValue?.replace(/(\r\n|\r|\n)/gm, '').trim();
 
            if (trimmedString) {
                topLevelTags.push(''); // Push an empty string as tag for text node
            }
        } else if (isNodeOfType(child, 'ELEMENT_NODE')) {
            topLevelTags.push(child.tagName);
        }
    }
 
    return topLevelTags;
}
 
function retrieveMetadata(doc: Document): Record<string, string> {
    const result: Record<string, string> = {};
    const attributes = doc.querySelector('html')?.attributes;
 
    (attributes ? toArray(attributes) : []).forEach(attr => {
        result[attr.name] = attr.value;
    });
 
    toArray(doc.querySelectorAll('meta')).forEach(meta => {
        result[meta.name] = meta.content;
    });
 
    return result;
}
 
function retrieveHtmlStrings(
    clipboardData: Partial<ClipboardData>
): {
    htmlBefore: string;
    htmlAfter: string;
} {
    const rawHtml = clipboardData.rawHtml ?? '';
    const startIndex = rawHtml.indexOf(START_FRAGMENT);
    const endIndex = rawHtml.lastIndexOf(END_FRAGMENT);
    let htmlBefore = '';
    let htmlAfter = '';
 
    if (startIndex >= 0 && endIndex >= startIndex + START_FRAGMENT.length) {
        htmlBefore = rawHtml.substring(0, startIndex);
        htmlAfter = rawHtml.substring(endIndex + END_FRAGMENT.length);
        clipboardData.html = rawHtml.substring(startIndex + START_FRAGMENT.length, endIndex);
    } else {
        clipboardData.html = rawHtml;
    }
 
    return { htmlBefore, htmlAfter };
}
 
function checkBlockElements(doc: Document): boolean {
    const elements = toArray(doc.body.querySelectorAll('*'));
 
    return elements.some(el => isNodeOfType(el, 'ELEMENT_NODE') && isBlockElement(el));
}