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 146 | 1x 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 };
|