All files / roosterjs-content-model-plugins/lib/paste/WordDesktop getStyleMetadata.ts

100% Statements 31/31
78.57% Branches 11/14
100% Functions 4/4
100% Lines 28/28

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 821x       1x                                           1x 21x 21x 21x   21x 5x   5x 5x 225x 225x 225x 225x     225x               225x       225x     225x 225x   1129x 1129x 904x 904x   904x       225x         325x 225x       21x    
import { getObjectKeys } from 'roosterjs-content-model-dom';
import type { WordMetadata } from './WordMetadata';
import type { BeforePasteEvent, DOMCreator } from 'roosterjs-content-model-types';
 
const FORMATING_REGEX = /[\n\t'{}"]+/g;
 
/**
 * @internal
 * Word Desktop content has a style tag that contains data for the lists.
 * So this function query that style tag and extract the data from the innerHTML, since it is not available from the HTMLStyleElement.sheet.
 *
 * The format is like:
 * example of style element content
 * @list l0:level1 {
 * styleTag: styleValue;
 * ...
 * }
 *
 * To extract the data:
 * 1. Substring the value of the style selector, using @ index and { index
 * 2. Substring the value of the style rules by Substring the content between { and }
 * 3. Split the value of the rules using ; as separator { styleTag: styleValue; styleTag1: StyleValue1 } = ['styleTag: styleValue',  'styleTag1: StyleValue1']
 * 4. Split the value of the rule  using : as separator: styleTag: styleValue = [styleTag, styleValue]
 * 5. Save data in record and only use the required information.
 *
 */
export function getStyleMetadata(ev: BeforePasteEvent, domCreator: DOMCreator) {
    const metadataMap: Map<string, WordMetadata> = new Map();
    const doc = domCreator.htmlToDOM(ev.htmlBefore);
    const styles = doc.querySelectorAll('style');
 
    styles.forEach(style => {
        const text = style?.innerHTML.trim() || '';
 
        let index = 0;
        while (index >= 0) {
            const indexAt = text.indexOf('@', index + 1);
            const indexCurlyEnd = text.indexOf('}', indexAt);
            const indexCurlyStart = text.indexOf('{', indexAt);
            index = indexAt;
 
            // 1.
            const metadataName = text
                .substring(indexAt + 1, indexCurlyStart)
                .replace(FORMATING_REGEX, '')
                .replace('list', '')
                .trimRight()
                .trimLeft();
 
            // 2.
            const dataName = text
                .substring(indexCurlyStart, indexCurlyEnd + 1)
                .trimLeft()
                .trimRight();
            const record: Record<string, string> = {};
 
            // 3.
            const entries = dataName.split(';');
            entries.forEach(entry => {
                // 4.
                const [key, value] = entry.split(':');
                if (key && value) {
                    const formatedKey = key.replace(FORMATING_REGEX, '').trimRight().trimLeft();
                    const formatedValue = value.replace(FORMATING_REGEX, '').trimRight().trimLeft();
                    // 5.
                    record[formatedKey] = formatedValue;
                }
            });
 
            const data: WordMetadata = {
                'mso-level-number-format': record['mso-level-number-format'],
                'mso-level-start-at': record['mso-level-start-at'] || '1',
                'mso-level-text': record['mso-level-text'],
            };
            Eif (getObjectKeys(data).some(key => !!data[key])) {
                metadataMap.set(metadataName, data);
            }
        }
    });
    return metadataMap;
}