All files index.js

100% Statements 88/88
93.87% Branches 46/49
100% Functions 28/28
100% Lines 84/84

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 142 143 144 145 1461x 1x   56x     21x 21x 1x 20x 52x   68x 34x 2x 32x 32x     20x   1x 4x 4x 3x 3x   4x     56x 56x     56x     58x     57x   1x 10x   1x 6x 5x     1x 8x 4x     1x   4x 4x 4x       3x   6x           55x 16x       55x     54x     55x     63x 63x 63x 63x 63x 63x     8x 1x   7x 1x   6x 6x   6x 14x 14x 14x 2x   12x 5x 1x   4x 4x 2x       7x     3x 2x   2x     54x   54x     54x 54x 54x 54x 54x 54x 54x 54x 54x 54x 54x      
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());
        if (!property || !value)
            return '';
        const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
        return `${camelProperty}: "${value}"`;
    })
        .filter(Boolean);
    return `{${styles.join(', ')}}`;
}
export const eventAttributesCallback = (_match, eventName, handler) => {
    let newEventName = '';
    if (typeof eventName === 'string' && eventName.length > 2) {
        const first = eventName.slice(2).split('')[0];
        newEventName = first ? first.toUpperCase() : '';
    }
    return `on${newEventName}${eventName && eventName.length > 3 ? 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, '');
}
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) {
    if (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('</')) {
            if (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);
        }
    }
    if (tagStack.length > 0) {
        throw new Error(`Unclosed tags: ${tagStack.map(tag => `<${tag}>`).join(', ')}`);
    }
    return 'HTML is valid.';
}
export function indentAllLines(html, options) {
    Iif (options && options.indentCode) {
    }
    return html;
}
export default function convert(html, options) {
    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, options);
}
export { isTagClosed, validateTag, validateTags, cssToObject, validateInput };