All files index.js

91.66% Statements 77/84
85.71% Branches 30/35
92.85% Functions 26/28
92.5% Lines 74/80

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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141  1x 1x   6x     5x 5x 1x 4x 9x   10x 5x   5x 5x     4x   1x         6x 6x     6x     8x     8x     6x   1x 10x   1x 6x 5x     1x 8x 4x     1x   4x 4x 4x       3x   6x           6x 3x       6x     5x     6x     13x 13x 13x 13x 13x 13x     4x     4x 1x   3x 3x   3x 9x 9x 9x 2x   7x 3x     3x 3x 1x       4x     2x     2x     5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x      
import beautify from 'beautify';
const selfClosingTags = ['input', 'img', 'br', 'hr', 'meta', 'link', 'col', 'area', 'base'];
const tagsRequiringClosing = new Set(['div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'form', 'button', 'textarea', 'select', 'option', 'a']);
export function wrapIntoDiv(html) {
    return `<div>${html}</div>`;
}
function cssToObject(cssString) {
    const cleanCss = cssString.replace(/['"]/g, '').trim();
    if (!cleanCss)
        return '{}';
    const styles = cleanCss.split(';')
        .filter((style) => style.trim())
        .map((style) => {
        const [property, value] = style.split(':').map((s) => s.trim());
        Iif (!property || !value)
            return '';
        const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
        return `${camelProperty}: "${value}"`;
    })
        .filter(Boolean);
    return `{${styles.join(', ')}}`;
}
const eventAttributesCallback = (_match, eventName, handler) => {
    const newEventName = eventName.slice(2).split('')[0].toUpperCase();
    return `on${newEventName}${eventName.slice(3)}={${handler}}`;
};
export function closeSelfClosingTags(html) {
    const result = html.replaceAll(new RegExp(`<(${selfClosingTags.join("|")})(?=[\\s>/])([^>]*)\\s*/?>`, "gi"), (_match, tagName, attributes) => `<${tagName}${attributes ? attributes : ""}/>`);
    return result.replace(/\/\/>/g, "/>");
}
export function convertEventAttributesToCamelCase(html) {
    return html.replaceAll(/(\bon\w+)=["']([^"']+)["']/g, eventAttributesCallback);
}
export function convertClassToClassName(html) {
    return html.replaceAll(/class=/g, 'className=');
}
export function removeComments(html) {
    return html.replaceAll(/<!--[\s\S]*?-->/g, '');
}
export function indentAllLines(html) {
    return beautify(html, { format: 'html' });
}
const isTagClosed = (tag) => {
    return !selfClosingTags.includes(tag) && tagsRequiringClosing.has(tag);
};
const validateInput = (html) => {
    if (typeof html !== 'string' || html.trim() === '' || !html) {
        throw new TypeError('Input must be valid a string.');
    }
};
const validateTag = (tag) => {
    if (!isTagClosed(tag)) {
        throw new Error(`Tag <${tag}> is not closed.`);
    }
};
const validateTags = (html) => {
    let match;
    const regex = /<([^\s>\/]+)/g;
    while ((match = regex.exec(html)) !== null) {
        validateTag(match[1].toLowerCase());
    }
};
export function toCamelCase(string) {
    return string
        .split(/[-_\s]/)
        .map((word, index) => index === 0
        ? word.toLowerCase()
        : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
        .join('');
}
export function convertStyleToObject(html) {
    return html.replaceAll(/style\s*=\s*(".*?")/gi, (match, styleValue) => {
        return `style={${cssToObject(styleValue)}}`;
    });
}
export function imageFix(html) {
    return html.replaceAll('</img>', '');
}
export function removeInvalidTags(html) {
    return html.replace(/<!DOCTYPE html>|<!DOCTYPE>/gi, '');
}
export function removeUnsuportedAttrs(html) {
    return html.replaceAll('xmlns:xlink="http://www.w3.org/1999/xlink"', '');
}
export function replaceAttributes(html) {
    html = html.replace(/for=/gi, 'htmlFor=');
    html = html.replace(/\b(autocomplete)\b/gi, 'autoComplete');
    html = html.replace(/\b(tabindex)\b/ig, 'tabIndex');
    html = html.replace(/\b(stroke-width)\b/ig, 'strokeWidth');
    html = html.replace(/\b(stroke-linejoin)\b/ig, 'strokeLinejoin');
    return html.replace(/\b(stroke-linecap)\b/ig, 'strokeLinecap');
}
export function validateHtml(html) {
    Iif (typeof html !== 'string') {
        throw new TypeError('Input must be a string.');
    }
    if (html.trim() === '') {
        return 'HTML is valid.';
    }
    const tagStack = [];
    const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g;
    let match;
    while ((match = tagRegex.exec(html)) !== null) {
        const fullTag = match[0];
        const tagName = match[1].toLowerCase();
        if (fullTag.endsWith('/>') || selfClosingTags.includes(tagName)) {
            continue;
        }
        if (fullTag.startsWith('</')) {
            Iif (tagStack.length === 0) {
                throw new Error(`Unexpected closing tag: ${fullTag}`);
            }
            const lastOpenTag = tagStack.pop();
            if (lastOpenTag !== tagName) {
                throw new Error(`Mismatched tags: expected </${lastOpenTag}> but found </${tagName}>`);
            }
        }
        else {
            tagStack.push(tagName);
        }
    }
    Iif (tagStack.length > 0) {
        throw new Error(`Unclosed tags: ${tagStack.map(tag => `<${tag}>`).join(', ')}`);
    }
    return 'HTML is valid.';
}
export default function convert(html) {
    html = removeInvalidTags(html);
    html = wrapIntoDiv(html);
    html = closeSelfClosingTags(html);
    html = convertEventAttributesToCamelCase(html);
    html = convertClassToClassName(html);
    html = removeComments(html);
    html = imageFix(html);
    html = convertStyleToObject(html);
    html = removeUnsuportedAttrs(html);
    html = replaceAttributes(html);
    return indentAllLines(html);
}
export { isTagClosed, validateTag, validateTags, cssToObject, validateInput };