@progress/kendo-e2e
Version:
Kendo UI end-to-end test utilities.
179 lines • 7.58 kB
JavaScript
"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