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 };
|