UNPKG

fortissimo-html

Version:

Fortissimo HTML - Flexible, Forgiving, Formatting HTML Parser

248 lines (245 loc) 9.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.stylizeHtml = void 0; const copy_script_1 = require("./copy-script"); const dom_1 = require("./dom"); const characters_1 = require("./characters"); const elements_1 = require("./elements"); const DEFAULT_OPTIONS = { dark: true, font: '12px Menlo, "Courier New", monospace', includeCopyScript: true, outerTag: 'html', showWhitespace: false, stylePrefix: 'fh-', tabSize: 8, title: 'Stylized HTML' }; const DEFAULT_DARK_THEME = { attrib: '#9CDCFE', background: '#1E1E1E', bg_whitespace: '#555555', comment: '#699856', entity: '#66BBBB', error: '#CC4444', foreground: '#D4D4D4', invalid: '#FF00FF', markup: '#808080', tag: '#569CD6', value: '#CE9178', warning: '#F49810', whitespace: '#605070' }; const DEFAULT_LIGHT_THEME = { attrib: '#5544FF', background: '#FFFFFF', bg_whitespace: '#CCCCCC', comment: '#80B0B0', entity: '#0088DD', error: '#D40000', foreground: '#222222', invalid: '#FF00FF', markup: '#808080', tag: '#000080', value: '#008088', warning: '#F49810', whitespace: '#C0D0F0' }; const COLORS = Object.keys(DEFAULT_LIGHT_THEME); function stylizeHtml(elem, options) { options = processOptions(options); const fullDocument = (options.outerTag === 'html'); const tag = fullDocument ? 'body' : options.outerTag; return (fullDocument ? `<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>${options.title}</title> <style> ${generateCss(options)} </style> </head> ` : '') + `<${tag} class="${options.stylePrefix}html">${stylize(elem, options)}${options.includeCopyScript ? '<script>' + (0, copy_script_1.getCopyScript)(options.stylePrefix) + '</script>' : ''}</${tag}>` + (fullDocument ? '</html>' : ''); } exports.stylizeHtml = stylizeHtml; function stylize(elem, options) { const pf = options.stylePrefix; const ws = options.showWhitespace; if (elem instanceof dom_1.CommentElement) return markup(elem.toString(), pf, 'comment', ws, false); else if (elem instanceof dom_1.CData) { return markup('<![CDATA[', pf, 'markup', false, false) + markup(elem.content, pf, null, ws, false) + markup(']]>', pf, 'markup', false, false); } else if (elem instanceof dom_1.DocType) { return elem.toString().replace(/("[^"]*?"[ \n\r\t\f]*|[^ ">]+[ \n\r\t\f]*|.+)/g, match => { if (match.startsWith('"')) return markup(match, pf, 'value', ws, false); else if (/^\w/.test(match)) return markup(match, pf, 'attrib', ws, false); else return markup(match, pf, 'markup', ws, false); }); } else if (elem instanceof dom_1.DeclarationElement || elem instanceof dom_1.ProcessingElement) return markup(elem.toString(), pf, 'markup', ws, false); else if (elem instanceof dom_1.TextElement) return markup(elem.toString(), pf, null, ws, !elem.parent || !elements_1.NO_ENTITIES_ELEMENTS.has(elem.parent.tagLc)); else if (elem instanceof dom_1.UnmatchedClosingTag) return markup(elem.toString(), pf, 'error', ws, false); else if (elem instanceof dom_1.DomNode) { const result = []; const badTerminator = elem.badTerminator; let tagClass = 'tag'; if (!elem.synthetic) { if (!(0, characters_1.isAllPCENChar)(elem.tag)) tagClass = 'warning'; result.push(markup('<', pf, badTerminator !== null ? 'error' : 'markup', false, false)); result.push(markup(elem.tag, pf, badTerminator ? 'error' : tagClass, false, false)); elem.attributes.forEach((attrib, index) => { result.push(markup(elem.spacing[index], pf, null, ws, false)); result.push(markup(attrib, pf, attrib === '/' ? 'error' : 'attrib', false, false)); result.push(markup(elem.equals[index] || '', pf, null, ws, false)); const quote = elem.quotes[index]; const value = (0, dom_1.OQ)(quote) + elem.values[index] + (0, dom_1.CQ)(quote); if (!quote && /["'=<>`]/.test(value)) result.push(markup(value, pf, 'warning', false, false)); else result.push(markup(value, pf, 'value', ws, true)); }); result.push(markup(elem.innerWhitespace, pf, null, ws, false)); if (badTerminator !== null) result.push(markup(badTerminator, pf, 'error', false, false)); else if (elem.closureState === dom_1.ClosureState.SELF_CLOSED) result.push(markup('/>', pf, 'markup', false, false)); else result.push(markup('>', pf, 'markup', false, false)); } if (elem.children) elem.children.forEach(child => result.push(stylize(child, options))); if (!elem.synthetic && elem.closureState === dom_1.ClosureState.EXPLICITLY_CLOSED) { const terminated = elem.endTagText.endsWith('>'); result.push(markup('</', pf, terminated ? 'markup' : 'error', false, false)); if (terminated) { result.push(markup(elem.endTagText.substring(2, elem.endTagText.length - 1), pf, tagClass, ws, false)); result.push(markup('>', pf, 'markup', false, false)); } else result.push(markup(elem.endTagText.substr(2), pf, 'error', false, false)); } return result.join(''); } return null; } function processOptions(options) { options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options || {}); options.colors = Object.assign(Object.assign({}, options.dark ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME), options.colors); return options; } function generateCss(options) { const prefix = options.stylePrefix; let css = ` .${prefix}html { background-color: ${options.colors.background}; color: ${options.colors.foreground}; font: ${options.font}; -moz-tab-size: ${options.tabSize}; tab-size: ${options.tabSize}; white-space: pre; } .${prefix}tab { color: ${options.colors.whitespace}; } .${prefix}tab::before { content: "→"; display: inline-block; overflow-x: visible; width: 0; } `; COLORS.forEach(color => { const property = color.startsWith('bg_') ? 'background-color' : 'color'; const value = options.colors[color]; css += ` .${prefix}${color} { ${property}: ${value}; }\n`; }); return css; } const whitespaces = { ' ': '·', '\t': '\t', '\n': '↵\n', '\f': '↧\f', '\r': '␍\r', '\r\n': '␍↵\r\n', '\xA0': '•' }; function markup(s, prefix, qlass, markWhitespace, markEntities, checkInvalid = true) { if (!s) return ''; else if (!qlass && !markWhitespace && !markEntities && !checkInvalid) return (0, characters_1.minimalEscape)(s); else if (markWhitespace) { return s.split(/([ \n\r\f\xA0]+|\t)/).map((match, index) => { if (index % 2 === 1) { match = match.replace(/\r\n|\n|\r|./g, ch => whitespaces[ch]); return markup(match, prefix, match === '\t' ? 'tab' : 'whitespace', false, false, false); } else if (match) { return match.split(/([\u2000-\u200A]|\u202F|\u205F|\u3000)/).map((match2, index2) => { if (index2 % 2 === 1) return markup(match2, prefix, 'bg_whitespace', false, false, false); else return markup(match2, prefix, qlass, false, markEntities, checkInvalid); }).join(''); } else return ''; }).join(''); } else if (checkInvalid) { s = (0, characters_1.replaceIsolatedSurrogates)(s); return s.split(/([\x00-\x08\x0B\x0E-\x1F\x7F-\x9F\uFFFD]+)/).map((match, index) => { if (index % 2 === 1) return markup('�'.repeat(match.length), prefix, 'invalid', false, false, false); else { return markup(match, prefix, qlass, false, markEntities, false); } }).join(''); } else if (markEntities) { const sb = []; (0, characters_1.separateEntities)(s).forEach((part, index) => { if (index % 2 === 0) sb.push(markup(part, prefix, qlass, false, false, false)); else { const eClass = getEntityClass(part, qlass && qlass.endsWith('value')); sb.push(markup(part, prefix, eClass, false, false, false)); } }); return sb.join(''); } return `<span class="${prefix}${qlass}">${(0, characters_1.minimalEscape)(s)}</span>`; } function getEntityClass(entity, forAttribValue) { let bestCase = 'entity'; entity = entity.substr(1); if (!entity.endsWith(';')) { if (forAttribValue) return 'value'; else bestCase = 'warning'; } else entity = entity.substr(0, entity.length - 1); let cp; if (entity.toLowerCase().startsWith('#x')) return isNaN(cp = parseInt(entity.substr(2), 16)) || !(0, characters_1.isValidEntityCodepoint)(cp) ? 'error' : (cp === 0xFFFD ? 'invalid' : bestCase); if (entity.toLowerCase().startsWith('#')) return isNaN(cp = parseInt(entity.substr(1), 10)) || !(0, characters_1.isValidEntityCodepoint)(cp) ? 'error' : (cp === 0xFFFD ? 'invalid' : bestCase); return (0, characters_1.isKnownNamedEntity)(entity) ? 'entity' : 'warning'; } //# sourceMappingURL=stylizer.js.map