UNPKG

@progress/kendo-e2e

Version:

Kendo UI end-to-end test utilities.

179 lines 7.58 kB
"use strict"; /** * Lightweight HTML formatter that indents well-formed HTML markup. * Replaces js-beautify for the snapshot-markup use case where * input is always valid DOM serialization (outerHTML). */ Object.defineProperty(exports, "__esModule", { value: true }); exports.formatHtml = formatHtml; const VOID_ELEMENTS = new Set([ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]); const _INLINE_ELEMENTS = new Set([ 'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map', 'object', 'output', 'q', 'samp', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'textarea', 'time', 'tt', 'u', 'var' ]); const PRESERVE_CONTENT_ELEMENTS = new Set([ 'pre', 'code', 'textarea', 'script', 'style' ]); /** * Format an HTML string with proper indentation. * * @param html - The HTML string to format (must be well-formed) * @param options - Formatting options * @returns The formatted HTML string */ function formatHtml(html, options) { var _a; const indentSize = (_a = options === null || options === void 0 ? void 0 : options.indent_size) !== null && _a !== void 0 ? _a : 4; const indent = ' '.repeat(indentSize); // Parse with a regex-based approach to preserve doctype and other non-element content const lines = []; let level = 0; // Tokenize the HTML into tags and text const tokens = tokenize(html); for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.type === 'doctype') { lines.push(indent.repeat(level) + token.value.trim()); } else if (token.type === 'comment') { lines.push(indent.repeat(level) + token.value.trim()); } else if (token.type === 'close') { level = Math.max(0, level - 1); lines.push(indent.repeat(level) + token.value.trim()); } else if (token.type === 'open') { // When an element is empty (open + close) or contains only text (open + text + close), keep it on one line const next = tokens[i + 1]; const afterNext = tokens[i + 2]; if ((next === null || next === void 0 ? void 0 : next.type) === 'close') { // Empty element: <span></span> lines.push(indent.repeat(level) + token.value.trim() + next.value.trim()); i += 1; // skip the close token } else if ((next === null || next === void 0 ? void 0 : next.type) === 'text' && (afterNext === null || afterNext === void 0 ? void 0 : afterNext.type) === 'close' && next.value.trim()) { lines.push(indent.repeat(level) + token.value.trim() + next.value.trim() + afterNext.value.trim()); i += 2; // skip the text and close tokens } else { lines.push(indent.repeat(level) + token.value.trim()); const tagName = getTagName(token.value); if (!VOID_ELEMENTS.has(tagName) && !token.selfClosing) { level++; } } } else if (token.type === 'void' || token.type === 'self-closing') { lines.push(indent.repeat(level) + token.value.trim()); } else if (token.type === 'text') { const trimmed = token.value.trim(); if (trimmed) { lines.push(indent.repeat(level) + trimmed); } } else if (token.type === 'preserve') { // Content of pre/script/style — output as-is, indented at current level const preserveLines = token.value.split('\n'); for (const pLine of preserveLines) { const trimmed = pLine.trimEnd(); if (trimmed) { lines.push(indent.repeat(level) + trimmed.trimStart()); } } } } return lines.filter(l => l.trim().length > 0).join('\n'); } function getTagName(tag) { const match = tag.match(/<\/?([a-zA-Z][a-zA-Z0-9-]*)/); return match ? match[1].toLowerCase() : ''; } function tokenize(html) { const tokens = []; let pos = 0; while (pos < html.length) { if (html[pos] === '<') { // Doctype if (html.substring(pos, pos + 9).toLowerCase() === '<!doctype') { const end = html.indexOf('>', pos); if (end !== -1) { tokens.push({ type: 'doctype', value: html.substring(pos, end + 1) }); pos = end + 1; continue; } } // Comment if (html.substring(pos, pos + 4) === '<!--') { const end = html.indexOf('-->', pos); if (end !== -1) { tokens.push({ type: 'comment', value: html.substring(pos, end + 3) }); pos = end + 3; continue; } } // Closing tag if (html[pos + 1] === '/') { const end = html.indexOf('>', pos); if (end !== -1) { tokens.push({ type: 'close', value: html.substring(pos, end + 1) }); pos = end + 1; continue; } } // Opening tag const end = html.indexOf('>', pos); if (end !== -1) { const tag = html.substring(pos, end + 1); const tagName = getTagName(tag); const selfClosing = tag.endsWith('/>'); if (VOID_ELEMENTS.has(tagName) || selfClosing) { tokens.push({ type: 'void', value: tag }); pos = end + 1; } else if (PRESERVE_CONTENT_ELEMENTS.has(tagName)) { // Find matching close tag and preserve content const closeTag = `</${tagName}>`; const closeIdx = html.toLowerCase().indexOf(closeTag, end + 1); if (closeIdx !== -1) { tokens.push({ type: 'open', value: tag }); const content = html.substring(end + 1, closeIdx); if (content.trim()) { tokens.push({ type: 'preserve', value: content }); } tokens.push({ type: 'close', value: html.substring(closeIdx, closeIdx + closeTag.length) }); pos = closeIdx + closeTag.length; } else { tokens.push({ type: 'open', value: tag }); pos = end + 1; } } else { tokens.push({ type: 'open', value: tag, selfClosing }); pos = end + 1; } continue; } // Malformed — treat as text tokens.push({ type: 'text', value: html[pos] }); pos++; } else { // Text content const nextTag = html.indexOf('<', pos); const text = nextTag === -1 ? html.substring(pos) : html.substring(pos, nextTag); if (text.trim()) { tokens.push({ type: 'text', value: text }); } pos = nextTag === -1 ? html.length : nextTag; } } return tokens; } //# sourceMappingURL=format-html.js.map