@blankeos/velite
Version:
Turns Markdown / MDX, YAML, JSON, or other files into app's data layer with type-safe schema.
2,052 lines (1,886 loc) • 275 kB
JavaScript
import { B as BRAND, D as DIRTY, E as EMPTY_PATH, I as INVALID, N as NEVER, O as OK, P as ParseStatus, Z as ZodType, a as ZodAny, b as ZodArray, c as ZodBigInt, d as ZodBoolean, e as ZodBranded, f as ZodCatch, g as ZodDate, h as ZodDefault, i as ZodDiscriminatedUnion, j as ZodEffects, k as ZodEnum, l as ZodError, m as ZodFirstPartyTypeKind, n as ZodFunction, o as ZodIntersection, p as ZodIssueCode, q as ZodLazy, r as ZodLiteral, s as ZodMap, t as ZodNaN, u as ZodNativeEnum, v as ZodNever, w as ZodNull, x as ZodNullable, y as ZodNumber, z as ZodObject, A as ZodOptional, C as ZodParsedType, F as ZodPipeline, G as ZodPromise, H as ZodReadonly, J as ZodRecord, K as ZodSet, L as ZodString, M as ZodSymbol, Q as ZodTuple, R as ZodUndefined, S as ZodUnion, T as ZodUnknown, U as ZodVoid, V as addIssueToContext, W as anyType, X as arrayType, Y as bigIntType, _ as booleanType, $ as coerce, a0 as custom, a1 as dateType, a2 as datetimeRegex, a3 as errorMap, a4 as discriminatedUnionType, a5 as effectsType, a6 as enumType, a7 as functionType, a8 as getErrorMap, a9 as getParsedType, aa as instanceOfType, ab as intersectionType, ac as isAborted, ad as isAsync, ae as isDirty, af as isValid, ag as late, ah as lazyType, ai as literalType, aj as makeIssue, ak as mapType, al as nanType, am as nativeEnumType, an as neverType, ao as nullType, ap as nullableType, aq as numberType, ar as objectType, as as objectUtil, at as oboolean, au as onumber, av as optionalType, aw as ostring, ax as pipelineType, ay as preprocessType, az as promiseType, aA as quotelessJson, aB as recordType, aC as setType, aD as setErrorMap, aE as strictObjectType, aF as stringType, aG as symbolType, aH as tupleType, aI as undefinedType, aJ as unionType, aK as unknownType, aL as util, aM as voidType, aN as isRelativePath, aO as processAsset, aP as getImageMetadata, aQ as raw$2, aR as svg, aS as find, aT as stringify, aU as stringify$1, aV as zwitch, aW as html$3, aX as htmlVoidElements, aY as convert, aZ as visitParents, a_ as ok, a$ as unicodeWhitespace, b0 as unicodePunctuation, b1 as normalizeIdentifier, b2 as classifyCharacter, b3 as visit, b4 as toString, b5 as EXIT, b6 as asciiAlphanumeric, b7 as asciiAlpha, b8 as markdownLineEndingOrSpace, b9 as asciiControl, ba as blankLine, bb as factorySpace, bc as splice, bd as resolveAll, be as markdownLineEnding, bf as markdownSpace, bg as combineExtensions, bh as fromMarkdown, bi as toHast, bj as getDefaultExportFromCjs, bk as VFile, bl as rehypeCopyLinkedFiles, bm as remarkCopyLinkedFiles, bn as structuredClone } from './velite-oUlu1YbQ.js';
export { bv as VeliteFile, bp as assets, bq as build, br as defineCollection, bu as defineConfig, bs as defineLoader, bt as defineSchema, bo as logger } from './velite-oUlu1YbQ.js';
import { readFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
import 'os';
import 'path';
import 'util';
import 'stream';
import 'events';
import 'fs';
import 'node:crypto';
import 'node:url';
import 'esbuild';
var z = /*#__PURE__*/Object.freeze({
__proto__: null,
BRAND: BRAND,
DIRTY: DIRTY,
EMPTY_PATH: EMPTY_PATH,
INVALID: INVALID,
NEVER: NEVER,
OK: OK,
ParseStatus: ParseStatus,
Schema: ZodType,
ZodAny: ZodAny,
ZodArray: ZodArray,
ZodBigInt: ZodBigInt,
ZodBoolean: ZodBoolean,
ZodBranded: ZodBranded,
ZodCatch: ZodCatch,
ZodDate: ZodDate,
ZodDefault: ZodDefault,
ZodDiscriminatedUnion: ZodDiscriminatedUnion,
ZodEffects: ZodEffects,
ZodEnum: ZodEnum,
ZodError: ZodError,
ZodFirstPartyTypeKind: ZodFirstPartyTypeKind,
ZodFunction: ZodFunction,
ZodIntersection: ZodIntersection,
ZodIssueCode: ZodIssueCode,
ZodLazy: ZodLazy,
ZodLiteral: ZodLiteral,
ZodMap: ZodMap,
ZodNaN: ZodNaN,
ZodNativeEnum: ZodNativeEnum,
ZodNever: ZodNever,
ZodNull: ZodNull,
ZodNullable: ZodNullable,
ZodNumber: ZodNumber,
ZodObject: ZodObject,
ZodOptional: ZodOptional,
ZodParsedType: ZodParsedType,
ZodPipeline: ZodPipeline,
ZodPromise: ZodPromise,
ZodReadonly: ZodReadonly,
ZodRecord: ZodRecord,
ZodSchema: ZodType,
ZodSet: ZodSet,
ZodString: ZodString,
ZodSymbol: ZodSymbol,
ZodTransformer: ZodEffects,
ZodTuple: ZodTuple,
ZodType: ZodType,
ZodUndefined: ZodUndefined,
ZodUnion: ZodUnion,
ZodUnknown: ZodUnknown,
ZodVoid: ZodVoid,
addIssueToContext: addIssueToContext,
any: anyType,
array: arrayType,
bigint: bigIntType,
boolean: booleanType,
coerce: coerce,
custom: custom,
date: dateType,
datetimeRegex: datetimeRegex,
defaultErrorMap: errorMap,
discriminatedUnion: discriminatedUnionType,
effect: effectsType,
enum: enumType,
function: functionType,
getErrorMap: getErrorMap,
getParsedType: getParsedType,
instanceof: instanceOfType,
intersection: intersectionType,
isAborted: isAborted,
isAsync: isAsync,
isDirty: isDirty,
isValid: isValid,
late: late,
lazy: lazyType,
literal: literalType,
makeIssue: makeIssue,
map: mapType,
nan: nanType,
nativeEnum: nativeEnumType,
never: neverType,
null: nullType,
nullable: nullableType,
number: numberType,
object: objectType,
get objectUtil () { return objectUtil; },
oboolean: oboolean,
onumber: onumber,
optional: optionalType,
ostring: ostring,
pipeline: pipelineType,
preprocess: preprocessType,
promise: promiseType,
quotelessJson: quotelessJson,
record: recordType,
set: setType,
setErrorMap: setErrorMap,
strictObject: strictObjectType,
string: stringType,
symbol: symbolType,
transformer: effectsType,
tuple: tupleType,
undefined: undefinedType,
union: unionType,
unknown: unknownType,
get util () { return util; },
void: voidType
});
const excerpt = ({ length = 260 } = {}) => custom((i) => i === void 0 || typeof i === "string").transform(async (value, { meta, addIssue }) => {
value = value ?? meta.plain;
if (value == null || value.length === 0) {
addIssue({ code: "custom", message: "The content is empty" });
return "";
}
return value.slice(0, length);
});
const file = ({ allowNonRelativePath = true } = {}) => stringType().transform(async (value, { meta, addIssue }) => {
try {
if (allowNonRelativePath && !isRelativePath(value)) return value;
const { output } = meta.config;
return await processAsset(value, meta.path, output.name, output.base);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
addIssue({ fatal: true, code: "custom", message });
return null;
}
});
const image$1 = ({ absoluteRoot } = {}) => stringType().transform(async (value, { meta, addIssue }) => {
try {
if (absoluteRoot && /^\//.test(value)) {
const buffer = await readFile(join(absoluteRoot, value));
const metadata = await getImageMetadata(buffer);
if (metadata == null) throw new Error(`Failed to get image metadata: ${value}`);
return { src: value, ...metadata };
}
const { output } = meta.config;
return await processAsset(value, meta.path, output.name, output.base, true);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
addIssue({ fatal: true, code: "custom", message });
return null;
}
});
const isodate = () => stringType().refine((value) => !isNaN(Date.parse(value)), "Invalid date string").transform((value) => new Date(value).toISOString());
/**
* @typedef {import('hast').Root} Root
* @typedef {import('hast-util-raw').Options} RawOptions
* @typedef {import('vfile').VFile} VFile
*/
/**
* Parse the tree (and raw nodes) again, keeping positional info okay.
*
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
function rehypeRaw(options) {
/**
* @param {Root} tree
* Tree.
* @param {VFile} file
* File.
* @returns {Root}
* New tree.
*/
return function (tree, file) {
// Assume root in -> root out.
const result = /** @type {Root} */ (raw$2(tree, {...options, file}));
return result
}
}
/**
* @typedef CoreOptions
* @property {ReadonlyArray<string>} [subset=[]]
* Whether to only escape the given subset of characters.
* @property {boolean} [escapeOnly=false]
* Whether to only escape possibly dangerous characters.
* Those characters are `"`, `&`, `'`, `<`, `>`, and `` ` ``.
*
* @typedef FormatOptions
* @property {(code: number, next: number, options: CoreWithFormatOptions) => string} format
* Format strategy.
*
* @typedef {CoreOptions & FormatOptions & import('./util/format-smart.js').FormatSmartOptions} CoreWithFormatOptions
*/
const defaultSubsetRegex = /["&'<>`]/g;
const surrogatePairsRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
const controlCharactersRegex =
// eslint-disable-next-line no-control-regex, unicorn/no-hex-escape
/[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g;
const regexEscapeRegex = /[|\\{}()[\]^$+*?.]/g;
/** @type {WeakMap<ReadonlyArray<string>, RegExp>} */
const subsetToRegexCache = new WeakMap();
/**
* Encode certain characters in `value`.
*
* @param {string} value
* @param {CoreWithFormatOptions} options
* @returns {string}
*/
function core(value, options) {
value = value.replace(
options.subset
? charactersToExpressionCached(options.subset)
: defaultSubsetRegex,
basic
);
if (options.subset || options.escapeOnly) {
return value
}
return (
value
// Surrogate pairs.
.replace(surrogatePairsRegex, surrogate)
// BMP control characters (C0 except for LF, CR, SP; DEL; and some more
// non-ASCII ones).
.replace(controlCharactersRegex, basic)
)
/**
* @param {string} pair
* @param {number} index
* @param {string} all
*/
function surrogate(pair, index, all) {
return options.format(
(pair.charCodeAt(0) - 0xd800) * 0x400 +
pair.charCodeAt(1) -
0xdc00 +
0x10000,
all.charCodeAt(index + 2),
options
)
}
/**
* @param {string} character
* @param {number} index
* @param {string} all
*/
function basic(character, index, all) {
return options.format(
character.charCodeAt(0),
all.charCodeAt(index + 1),
options
)
}
}
/**
* A wrapper function that caches the result of `charactersToExpression` with a WeakMap.
* This can improve performance when tooling calls `charactersToExpression` repeatedly
* with the same subset.
*
* @param {ReadonlyArray<string>} subset
* @returns {RegExp}
*/
function charactersToExpressionCached(subset) {
let cached = subsetToRegexCache.get(subset);
if (!cached) {
cached = charactersToExpression(subset);
subsetToRegexCache.set(subset, cached);
}
return cached
}
/**
* @param {ReadonlyArray<string>} subset
* @returns {RegExp}
*/
function charactersToExpression(subset) {
/** @type {Array<string>} */
const groups = [];
let index = -1;
while (++index < subset.length) {
groups.push(subset[index].replace(regexEscapeRegex, '\\$&'));
}
return new RegExp('(?:' + groups.join('|') + ')', 'g')
}
const hexadecimalRegex = /[\dA-Fa-f]/;
/**
* Configurable ways to encode characters as hexadecimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toHexadecimal(code, next, omit) {
const value = '&#x' + code.toString(16).toUpperCase();
return omit && next && !hexadecimalRegex.test(String.fromCharCode(next))
? value
: value + ';'
}
const decimalRegex = /\d/;
/**
* Configurable ways to encode characters as decimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toDecimal(code, next, omit) {
const value = '&#' + String(code);
return omit && next && !decimalRegex.test(String.fromCharCode(next))
? value
: value + ';'
}
/**
* List of legacy HTML named character references that don’t need a trailing semicolon.
*
* @type {Array<string>}
*/
const characterEntitiesLegacy = [
'AElig',
'AMP',
'Aacute',
'Acirc',
'Agrave',
'Aring',
'Atilde',
'Auml',
'COPY',
'Ccedil',
'ETH',
'Eacute',
'Ecirc',
'Egrave',
'Euml',
'GT',
'Iacute',
'Icirc',
'Igrave',
'Iuml',
'LT',
'Ntilde',
'Oacute',
'Ocirc',
'Ograve',
'Oslash',
'Otilde',
'Ouml',
'QUOT',
'REG',
'THORN',
'Uacute',
'Ucirc',
'Ugrave',
'Uuml',
'Yacute',
'aacute',
'acirc',
'acute',
'aelig',
'agrave',
'amp',
'aring',
'atilde',
'auml',
'brvbar',
'ccedil',
'cedil',
'cent',
'copy',
'curren',
'deg',
'divide',
'eacute',
'ecirc',
'egrave',
'eth',
'euml',
'frac12',
'frac14',
'frac34',
'gt',
'iacute',
'icirc',
'iexcl',
'igrave',
'iquest',
'iuml',
'laquo',
'lt',
'macr',
'micro',
'middot',
'nbsp',
'not',
'ntilde',
'oacute',
'ocirc',
'ograve',
'ordf',
'ordm',
'oslash',
'otilde',
'ouml',
'para',
'plusmn',
'pound',
'quot',
'raquo',
'reg',
'sect',
'shy',
'sup1',
'sup2',
'sup3',
'szlig',
'thorn',
'times',
'uacute',
'ucirc',
'ugrave',
'uml',
'uuml',
'yacute',
'yen',
'yuml'
];
/**
* Map of named character references from HTML 4.
*
* @type {Record<string, string>}
*/
const characterEntitiesHtml4 = {
nbsp: ' ',
iexcl: '¡',
cent: '¢',
pound: '£',
curren: '¤',
yen: '¥',
brvbar: '¦',
sect: '§',
uml: '¨',
copy: '©',
ordf: 'ª',
laquo: '«',
not: '¬',
shy: '',
reg: '®',
macr: '¯',
deg: '°',
plusmn: '±',
sup2: '²',
sup3: '³',
acute: '´',
micro: 'µ',
para: '¶',
middot: '·',
cedil: '¸',
sup1: '¹',
ordm: 'º',
raquo: '»',
frac14: '¼',
frac12: '½',
frac34: '¾',
iquest: '¿',
Agrave: 'À',
Aacute: 'Á',
Acirc: 'Â',
Atilde: 'Ã',
Auml: 'Ä',
Aring: 'Å',
AElig: 'Æ',
Ccedil: 'Ç',
Egrave: 'È',
Eacute: 'É',
Ecirc: 'Ê',
Euml: 'Ë',
Igrave: 'Ì',
Iacute: 'Í',
Icirc: 'Î',
Iuml: 'Ï',
ETH: 'Ð',
Ntilde: 'Ñ',
Ograve: 'Ò',
Oacute: 'Ó',
Ocirc: 'Ô',
Otilde: 'Õ',
Ouml: 'Ö',
times: '×',
Oslash: 'Ø',
Ugrave: 'Ù',
Uacute: 'Ú',
Ucirc: 'Û',
Uuml: 'Ü',
Yacute: 'Ý',
THORN: 'Þ',
szlig: 'ß',
agrave: 'à',
aacute: 'á',
acirc: 'â',
atilde: 'ã',
auml: 'ä',
aring: 'å',
aelig: 'æ',
ccedil: 'ç',
egrave: 'è',
eacute: 'é',
ecirc: 'ê',
euml: 'ë',
igrave: 'ì',
iacute: 'í',
icirc: 'î',
iuml: 'ï',
eth: 'ð',
ntilde: 'ñ',
ograve: 'ò',
oacute: 'ó',
ocirc: 'ô',
otilde: 'õ',
ouml: 'ö',
divide: '÷',
oslash: 'ø',
ugrave: 'ù',
uacute: 'ú',
ucirc: 'û',
uuml: 'ü',
yacute: 'ý',
thorn: 'þ',
yuml: 'ÿ',
fnof: 'ƒ',
Alpha: 'Α',
Beta: 'Β',
Gamma: 'Γ',
Delta: 'Δ',
Epsilon: 'Ε',
Zeta: 'Ζ',
Eta: 'Η',
Theta: 'Θ',
Iota: 'Ι',
Kappa: 'Κ',
Lambda: 'Λ',
Mu: 'Μ',
Nu: 'Ν',
Xi: 'Ξ',
Omicron: 'Ο',
Pi: 'Π',
Rho: 'Ρ',
Sigma: 'Σ',
Tau: 'Τ',
Upsilon: 'Υ',
Phi: 'Φ',
Chi: 'Χ',
Psi: 'Ψ',
Omega: 'Ω',
alpha: 'α',
beta: 'β',
gamma: 'γ',
delta: 'δ',
epsilon: 'ε',
zeta: 'ζ',
eta: 'η',
theta: 'θ',
iota: 'ι',
kappa: 'κ',
lambda: 'λ',
mu: 'μ',
nu: 'ν',
xi: 'ξ',
omicron: 'ο',
pi: 'π',
rho: 'ρ',
sigmaf: 'ς',
sigma: 'σ',
tau: 'τ',
upsilon: 'υ',
phi: 'φ',
chi: 'χ',
psi: 'ψ',
omega: 'ω',
thetasym: 'ϑ',
upsih: 'ϒ',
piv: 'ϖ',
bull: '•',
hellip: '…',
prime: '′',
Prime: '″',
oline: '‾',
frasl: '⁄',
weierp: '℘',
image: 'ℑ',
real: 'ℜ',
trade: '™',
alefsym: 'ℵ',
larr: '←',
uarr: '↑',
rarr: '→',
darr: '↓',
harr: '↔',
crarr: '↵',
lArr: '⇐',
uArr: '⇑',
rArr: '⇒',
dArr: '⇓',
hArr: '⇔',
forall: '∀',
part: '∂',
exist: '∃',
empty: '∅',
nabla: '∇',
isin: '∈',
notin: '∉',
ni: '∋',
prod: '∏',
sum: '∑',
minus: '−',
lowast: '∗',
radic: '√',
prop: '∝',
infin: '∞',
ang: '∠',
and: '∧',
or: '∨',
cap: '∩',
cup: '∪',
int: '∫',
there4: '∴',
sim: '∼',
cong: '≅',
asymp: '≈',
ne: '≠',
equiv: '≡',
le: '≤',
ge: '≥',
sub: '⊂',
sup: '⊃',
nsub: '⊄',
sube: '⊆',
supe: '⊇',
oplus: '⊕',
otimes: '⊗',
perp: '⊥',
sdot: '⋅',
lceil: '⌈',
rceil: '⌉',
lfloor: '⌊',
rfloor: '⌋',
lang: '〈',
rang: '〉',
loz: '◊',
spades: '♠',
clubs: '♣',
hearts: '♥',
diams: '♦',
quot: '"',
amp: '&',
lt: '<',
gt: '>',
OElig: 'Œ',
oelig: 'œ',
Scaron: 'Š',
scaron: 'š',
Yuml: 'Ÿ',
circ: 'ˆ',
tilde: '˜',
ensp: ' ',
emsp: ' ',
thinsp: ' ',
zwnj: '',
zwj: '',
lrm: '',
rlm: '',
ndash: '–',
mdash: '—',
lsquo: '‘',
rsquo: '’',
sbquo: '‚',
ldquo: '“',
rdquo: '”',
bdquo: '„',
dagger: '†',
Dagger: '‡',
permil: '‰',
lsaquo: '‹',
rsaquo: '›',
euro: '€'
};
/**
* List of legacy (that don’t need a trailing `;`) named references which could,
* depending on what follows them, turn into a different meaning
*
* @type {Array<string>}
*/
const dangerous = [
'cent',
'copy',
'divide',
'gt',
'lt',
'not',
'para',
'times'
];
const own$3 = {}.hasOwnProperty;
/**
* `characterEntitiesHtml4` but inverted.
*
* @type {Record<string, string>}
*/
const characters = {};
/** @type {string} */
let key;
for (key in characterEntitiesHtml4) {
if (own$3.call(characterEntitiesHtml4, key)) {
characters[characterEntitiesHtml4[key]] = key;
}
}
const notAlphanumericRegex = /[^\dA-Za-z]/;
/**
* Configurable ways to encode characters as named references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @param {boolean|undefined} attribute
* @returns {string}
*/
function toNamed(code, next, omit, attribute) {
const character = String.fromCharCode(code);
if (own$3.call(characters, character)) {
const name = characters[character];
const value = '&' + name;
if (
omit &&
characterEntitiesLegacy.includes(name) &&
!dangerous.includes(name) &&
(!attribute ||
(next &&
next !== 61 /* `=` */ &&
notAlphanumericRegex.test(String.fromCharCode(next))))
) {
return value
}
return value + ';'
}
return ''
}
/**
* @typedef FormatSmartOptions
* @property {boolean} [useNamedReferences=false]
* Prefer named character references (`&`) where possible.
* @property {boolean} [useShortestReferences=false]
* Prefer the shortest possible reference, if that results in less bytes.
* **Note**: `useNamedReferences` can be omitted when using `useShortestReferences`.
* @property {boolean} [omitOptionalSemicolons=false]
* Whether to omit semicolons when possible.
* **Note**: This creates what HTML calls “parse errors” but is otherwise still valid HTML — don’t use this except when building a minifier.
* Omitting semicolons is possible for certain named and numeric references in some cases.
* @property {boolean} [attribute=false]
* Create character references which don’t fail in attributes.
* **Note**: `attribute` only applies when operating dangerously with
* `omitOptionalSemicolons: true`.
*/
/**
* Configurable ways to encode a character yielding pretty or small results.
*
* @param {number} code
* @param {number} next
* @param {FormatSmartOptions} options
* @returns {string}
*/
function formatSmart(code, next, options) {
let numeric = toHexadecimal(code, next, options.omitOptionalSemicolons);
/** @type {string|undefined} */
let named;
if (options.useNamedReferences || options.useShortestReferences) {
named = toNamed(
code,
next,
options.omitOptionalSemicolons,
options.attribute
);
}
// Use the shortest numeric reference when requested.
// A simple algorithm would use decimal for all code points under 100, as
// those are shorter than hexadecimal:
//
// * `c` vs `c` (decimal shorter)
// * `d` vs `d` (equal)
//
// However, because we take `next` into consideration when `omit` is used,
// And it would be possible that decimals are shorter on bigger values as
// well if `next` is hexadecimal but not decimal, we instead compare both.
if (
(options.useShortestReferences || !named) &&
options.useShortestReferences
) {
const decimal = toDecimal(code, next, options.omitOptionalSemicolons);
if (decimal.length < numeric.length) {
numeric = decimal;
}
}
return named &&
(!options.useShortestReferences || named.length < numeric.length)
? named
: numeric
}
/**
* @typedef {import('./core.js').CoreOptions & import('./util/format-smart.js').FormatSmartOptions} Options
* @typedef {import('./core.js').CoreOptions} LightOptions
*/
/**
* Encode special characters in `value`.
*
* @param {string} value
* Value to encode.
* @param {Options} [options]
* Configuration.
* @returns {string}
* Encoded value.
*/
function stringifyEntities(value, options) {
return core(value, Object.assign({format: formatSmart}, options))
}
/**
* @import {Comment, Parents} from 'hast'
* @import {State} from '../index.js'
*/
const htmlCommentRegex = /^>|^->|<!--|-->|--!>|<!-$/g;
// Declare arrays as variables so it can be cached by `stringifyEntities`
const bogusCommentEntitySubset = ['>'];
const commentEntitySubset = ['<', '>'];
/**
* Serialize a comment.
*
* @param {Comment} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function comment(node, _1, _2, state) {
// See: <https://html.spec.whatwg.org/multipage/syntax.html#comments>
return state.settings.bogusComments
? '<?' +
stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {
subset: bogusCommentEntitySubset
})
) +
'>'
: '<!--' + node.value.replace(htmlCommentRegex, encode) + '-->'
/**
* @param {string} $0
*/
function encode($0) {
return stringifyEntities(
$0,
Object.assign({}, state.settings.characterReferences, {
subset: commentEntitySubset
})
)
}
}
/**
* @import {Doctype, Parents} from 'hast'
* @import {State} from '../index.js'
*/
/**
* Serialize a doctype.
*
* @param {Doctype} _1
* Node to handle.
* @param {number | undefined} _2
* Index of `node` in `parent.
* @param {Parents | undefined} _3
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function doctype(_1, _2, _3, state) {
return (
'<!' +
(state.settings.upperDoctype ? 'DOCTYPE' : 'doctype') +
(state.settings.tightDoctype ? '' : ' ') +
'html>'
)
}
/**
* Count how often a character (or substring) is used in a string.
*
* @param {string} value
* Value to search in.
* @param {string} character
* Character (or substring) to look for.
* @return {number}
* Number of times `character` occurred in `value`.
*/
function ccount(value, character) {
const source = String(value);
if (typeof character !== 'string') {
throw new TypeError('Expected character')
}
let count = 0;
let index = source.indexOf(character);
while (index !== -1) {
count++;
index = source.indexOf(character, index + character.length);
}
return count
}
/**
* @typedef {import('hast').Nodes} Nodes
*/
// HTML whitespace expression.
// See <https://infra.spec.whatwg.org/#ascii-whitespace>.
const re = /[ \t\n\f\r]/g;
/**
* Check if the given value is *inter-element whitespace*.
*
* @param {Nodes | string} thing
* Thing to check (`Node` or `string`).
* @returns {boolean}
* Whether the `value` is inter-element whitespace (`boolean`): consisting of
* zero or more of space, tab (`\t`), line feed (`\n`), carriage return
* (`\r`), or form feed (`\f`); if a node is passed it must be a `Text` node,
* whose `value` field is checked.
*/
function whitespace(thing) {
return typeof thing === 'object'
? thing.type === 'text'
? empty(thing.value)
: false
: empty(thing)
}
/**
* @param {string} value
* @returns {boolean}
*/
function empty(value) {
return value.replace(re, '') === ''
}
/**
* @import {Parents, RootContent} from 'hast'
*/
const siblingAfter = siblings(1);
const siblingBefore = siblings(-1);
/** @type {Array<RootContent>} */
const emptyChildren$1 = [];
/**
* Factory to check siblings in a direction.
*
* @param {number} increment
*/
function siblings(increment) {
return sibling
/**
* Find applicable siblings in a direction.
*
* @template {Parents} Parent
* Parent type.
* @param {Parent | undefined} parent
* Parent.
* @param {number | undefined} index
* Index of child in `parent`.
* @param {boolean | undefined} [includeWhitespace=false]
* Whether to include whitespace (default: `false`).
* @returns {Parent extends {children: Array<infer Child>} ? Child | undefined : never}
* Child of parent.
*/
function sibling(parent, index, includeWhitespace) {
const siblings = parent ? parent.children : emptyChildren$1;
let offset = (index || 0) + increment;
let next = siblings[offset];
if (!includeWhitespace) {
while (next && whitespace(next)) {
offset += increment;
next = siblings[offset];
}
}
// @ts-expect-error: it’s a correct child.
return next
}
}
/**
* @import {Element, Parents} from 'hast'
*/
/**
* @callback OmitHandle
* Check if a tag can be omitted.
* @param {Element} element
* Element to check.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether to omit a tag.
*
*/
const own$2 = {}.hasOwnProperty;
/**
* Factory to check if a given node can have a tag omitted.
*
* @param {Record<string, OmitHandle>} handlers
* Omission handlers, where each key is a tag name, and each value is the
* corresponding handler.
* @returns {OmitHandle}
* Whether to omit a tag of an element.
*/
function omission(handlers) {
return omit
/**
* Check if a given node can have a tag omitted.
*
* @type {OmitHandle}
*/
function omit(node, index, parent) {
return (
own$2.call(handlers, node.tagName) &&
handlers[node.tagName](node, index, parent)
)
}
}
/**
* @import {Element, Parents} from 'hast'
*/
const closing = omission({
body: body$1,
caption: headOrColgroupOrCaption,
colgroup: headOrColgroupOrCaption,
dd,
dt,
head: headOrColgroupOrCaption,
html: html$2,
li,
optgroup,
option,
p,
rp: rubyElement,
rt: rubyElement,
tbody: tbody$1,
td: cells,
tfoot,
th: cells,
thead,
tr
});
/**
* Macro for `</head>`, `</colgroup>`, and `</caption>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function headOrColgroupOrCaption(_, index, parent) {
const next = siblingAfter(parent, index, true);
return (
!next ||
(next.type !== 'comment' &&
!(next.type === 'text' && whitespace(next.value.charAt(0))))
)
}
/**
* Whether to omit `</html>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function html$2(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</body>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function body$1(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</p>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function p(_, index, parent) {
const next = siblingAfter(parent, index);
return next
? next.type === 'element' &&
(next.tagName === 'address' ||
next.tagName === 'article' ||
next.tagName === 'aside' ||
next.tagName === 'blockquote' ||
next.tagName === 'details' ||
next.tagName === 'div' ||
next.tagName === 'dl' ||
next.tagName === 'fieldset' ||
next.tagName === 'figcaption' ||
next.tagName === 'figure' ||
next.tagName === 'footer' ||
next.tagName === 'form' ||
next.tagName === 'h1' ||
next.tagName === 'h2' ||
next.tagName === 'h3' ||
next.tagName === 'h4' ||
next.tagName === 'h5' ||
next.tagName === 'h6' ||
next.tagName === 'header' ||
next.tagName === 'hgroup' ||
next.tagName === 'hr' ||
next.tagName === 'main' ||
next.tagName === 'menu' ||
next.tagName === 'nav' ||
next.tagName === 'ol' ||
next.tagName === 'p' ||
next.tagName === 'pre' ||
next.tagName === 'section' ||
next.tagName === 'table' ||
next.tagName === 'ul')
: !parent ||
// Confusing parent.
!(
parent.type === 'element' &&
(parent.tagName === 'a' ||
parent.tagName === 'audio' ||
parent.tagName === 'del' ||
parent.tagName === 'ins' ||
parent.tagName === 'map' ||
parent.tagName === 'noscript' ||
parent.tagName === 'video')
)
}
/**
* Whether to omit `</li>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function li(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'li')
}
/**
* Whether to omit `</dt>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dt(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd')
)
}
/**
* Whether to omit `</dd>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dd(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd'))
)
}
/**
* Whether to omit `</rt>` or `</rp>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function rubyElement(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'rp' || next.tagName === 'rt'))
)
}
/**
* Whether to omit `</optgroup>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function optgroup(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'optgroup')
}
/**
* Whether to omit `</option>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function option(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'option' || next.tagName === 'optgroup'))
)
}
/**
* Whether to omit `</thead>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function thead(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot')
)
}
/**
* Whether to omit `</tbody>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tbody$1(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot'))
)
}
/**
* Whether to omit `</tfoot>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tfoot(_, index, parent) {
return !siblingAfter(parent, index)
}
/**
* Whether to omit `</tr>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tr(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'tr')
}
/**
* Whether to omit `</td>` or `</th>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function cells(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'td' || next.tagName === 'th'))
)
}
/**
* @import {Element, Parents} from 'hast'
*/
const opening = omission({
body,
colgroup,
head,
html: html$1,
tbody
});
/**
* Whether to omit `<html>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function html$1(node) {
const head = siblingAfter(node, -1);
return !head || head.type !== 'comment'
}
/**
* Whether to omit `<head>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function head(node) {
/** @type {Set<string>} */
const seen = new Set();
// Whether `srcdoc` or not,
// make sure the content model at least doesn’t have too many `base`s/`title`s.
for (const child of node.children) {
if (
child.type === 'element' &&
(child.tagName === 'base' || child.tagName === 'title')
) {
if (seen.has(child.tagName)) return false
seen.add(child.tagName);
}
}
// “May be omitted if the element is empty,
// or if the first thing inside the head element is an element.”
const child = node.children[0];
return !child || child.type === 'element'
}
/**
* Whether to omit `<body>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function body(node) {
const head = siblingAfter(node, -1, true);
return (
!head ||
(head.type !== 'comment' &&
!(head.type === 'text' && whitespace(head.value.charAt(0))) &&
!(
head.type === 'element' &&
(head.tagName === 'meta' ||
head.tagName === 'link' ||
head.tagName === 'script' ||
head.tagName === 'style' ||
head.tagName === 'template')
))
)
}
/**
* Whether to omit `<colgroup>`.
* The spec describes some logic for the opening tag, but it’s easier to
* implement in the closing tag, to the same effect, so we handle it there
* instead.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function colgroup(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1, true);
// Previous colgroup was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
previous.tagName === 'colgroup' &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'col')
}
/**
* Whether to omit `<tbody>`.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function tbody(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1);
// Previous table section was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
(previous.tagName === 'thead' || previous.tagName === 'tbody') &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'tr')
}
/**
* @import {Element, Parents, Properties} from 'hast'
* @import {State} from '../index.js'
*/
/**
* Maps of subsets.
*
* Each value is a matrix of tuples.
* The value at `0` causes parse errors, the value at `1` is valid.
* Of both, the value at `0` is unsafe, and the value at `1` is safe.
*
* @type {Record<'double' | 'name' | 'single' | 'unquoted', Array<[Array<string>, Array<string>]>>}
*/
const constants = {
// See: <https://html.spec.whatwg.org/#attribute-name-state>.
name: [
['\t\n\f\r &/=>'.split(''), '\t\n\f\r "&\'/=>`'.split('')],
['\0\t\n\f\r "&\'/<=>'.split(''), '\0\t\n\f\r "&\'/<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(unquoted)-state>.
unquoted: [
['\t\n\f\r &>'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')],
['\0\t\n\f\r "&\'<=>`'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state>.
single: [
["&'".split(''), '"&\'`'.split('')],
["\0&'".split(''), '\0"&\'`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state>.
double: [
['"&'.split(''), '"&\'`'.split('')],
['\0"&'.split(''), '\0"&\'`'.split('')]
]
};
/**
* Serialize an element node.
*
* @param {Element} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function element(node, index, parent, state) {
const schema = state.schema;
const omit = schema.space === 'svg' ? false : state.settings.omitOptionalTags;
let selfClosing =
schema.space === 'svg'
? state.settings.closeEmptyElements
: state.settings.voids.includes(node.tagName.toLowerCase());
/** @type {Array<string>} */
const parts = [];
/** @type {string} */
let last;
if (schema.space === 'html' && node.tagName === 'svg') {
state.schema = svg;
}
const attributes = serializeAttributes(state, node.properties);
const content = state.all(
schema.space === 'html' && node.tagName === 'template' ? node.content : node
);
state.schema = schema;
// If the node is categorised as void, but it has children, remove the
// categorisation.
// This enables for example `menuitem`s, which are void in W3C HTML but not
// void in WHATWG HTML, to be stringified properly.
// Note: `menuitem` has since been removed from the HTML spec, and so is no
// longer void.
if (content) selfClosing = false;
if (attributes || !omit || !opening(node, index, parent)) {
parts.push('<', node.tagName, attributes ? ' ' + attributes : '');
if (
selfClosing &&
(schema.space === 'svg' || state.settings.closeSelfClosing)
) {
last = attributes.charAt(attributes.length - 1);
if (
!state.settings.tightSelfClosing ||
last === '/' ||
(last && last !== '"' && last !== "'")
) {
parts.push(' ');
}
parts.push('/');
}
parts.push('>');
}
parts.push(content);
if (!selfClosing && (!omit || !closing(node, index, parent))) {
parts.push('</' + node.tagName + '>');
}
return parts.join('')
}
/**
* @param {State} state
* @param {Properties | null | undefined} properties
* @returns {string}
*/
function serializeAttributes(state, properties) {
/** @type {Array<string>} */
const values = [];
let index = -1;
/** @type {string} */
let key;
if (properties) {
for (key in properties) {
if (properties[key] !== null && properties[key] !== undefined) {
const value = serializeAttribute(state, key, properties[key]);
if (value) values.push(value);
}
}
}
while (++index < values.length) {
const last = state.settings.tightAttributes
? values[index].charAt(values[index].length - 1)
: undefined;
// In tight mode, don’t add a space after quoted attributes.
if (index !== values.length - 1 && last !== '"' && last !== "'") {
values[index] += ' ';
}
}
return values.join('')
}
/**
* @param {State} state
* @param {string} key
* @param {Properties[keyof Properties]} value
* @returns {string}
*/
function serializeAttribute(state, key, value) {
const info = find(state.schema, key);
const x =
state.settings.allowParseErrors && state.schema.space === 'html' ? 0 : 1;
const y = state.settings.allowDangerousCharacters ? 0 : 1;
let quote = state.quote;
/** @type {string | undefined} */
let result;
if (info.overloadedBoolean && (value === info.attribute || value === '')) {
value = true;
} else if (
(info.boolean || info.overloadedBoolean) &&
(typeof value !== 'string' || value === info.attribute || value === '')
) {
value = Boolean(value);
}
if (
value === null ||
value === undefined ||
value === false ||
(typeof value === 'number' && Number.isNaN(value))
) {
return ''
}
const name = stringifyEntities(
info.attribute,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: constants.name[x][y]
})
);
// No value.
// There is currently only one boolean property in SVG: `[download]` on
// `<a>`.
// This property does not seem to work in browsers (Firefox, Safari, Chrome),
// so I can’t test if dropping the value works.
// But I assume that it should:
//
// ```html
// <!doctype html>
// <svg viewBox="0 0 100 100">
// <a href=https://example.com download>
// <circle cx=50 cy=40 r=35 />
// </a>
// </svg>
// ```
//
// See: <https://github.com/wooorm/property-information/blob/main/lib/svg.js>
if (value === true) return name
// `spaces` doesn’t accept a second argument, but it’s given here just to
// keep the code cleaner.
value = Array.isArray(value)
? (info.commaSeparated ? stringify : stringify$1)(value, {
padLeft: !state.settings.tightCommaSeparatedLists
})
: String(value);
if (state.settings.collapseEmptyAttributes && !value) return name
// Check unquoted value.
if (state.settings.preferUnquoted) {
result = stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
attribute: true,
subset: constants.unquoted[x][y]
})
);
}
// If we don’t want unquoted, or if `value` contains character references when
// unquoted…
if (result !== value) {
// If the alternative is less common than `quote`, switch.
if (
state.settings.quoteSmart &&
ccount(value, quote) > ccount(value, state.alternative)
) {
quote = state.alternative;
}
result =
quote +
stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: (quote === "'" ? constants.single : constants.double)[x][y],
attribute: true
})
) +
quote;
}
// Don’t add a `=` for unquoted empties.
return name + (result ? '=' + result : result)
}
/**
* @import {Parents, Text} from 'hast'
* @import {Raw} from 'mdast-util-to-hast'
* @import {State} from '../index.js'
*/
// Declare array as variable so it can be cached by `stringifyEntities`
const textEntitySubset = ['<', '&'];
/**
* Serialize a text node.
*
* @param {Raw | Text} node
* Node to handle.
* @param {number | undefined} _
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function text$2(node, _, parent, state) {
// Check if content of `node` should be escaped.
return parent &&
parent.type === 'element' &&
(parent.tagName === 'script' || parent.tagName === 'style')
? node.value
: stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {
subset: textEntitySubset
})
)
}
/**
* @import {Parents} from 'hast'
* @import {Raw} from 'mdast-util-to-hast'
* @import {State} from '../index.js'
*/
/**
* Serialize a raw node.
*
* @param {Raw} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function raw$1(node, index, parent, state) {
return state.settings.allowDangerousHtml
? node.value
: text$2(node, index, parent, state)
}
/**
* @import {Parents, Root} from 'hast'
* @import {State} from '../index.js'
*/
/**
* Serialize a root.
*
* @param {Root} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function root$1(node, _1, _2, state) {
return state.all(node)
}
/**
* @import {Nodes, Parents} from 'hast'
* @import {State} from '../index.js'
*/
/**
* @type {(node: Nodes, index: number | undefined, parent: Parents | undefined, state: State) => string}
*/
const handle$1 = zwitch('type', {
invalid,
unknown,
handlers: {comment, doctype, element, raw: raw$1, root: root$1, text: text$2}
});
/**
* Fail when a non-node is found in the tree.
*
* @param {unknown} node
* Unknown value.
* @returns {never}
* Never.
*/
function invalid(node) {
throw new Error('Expected node, not `' + node + '`')
}
/**
* Fail when a node with an unknown type is found in the tree.
*
* @param {unknown} node_
* Unknown node.
* @returns {never}
* Never.
*/
function unknown(node_) {
// `type` is guaranteed by runtime JS.
const node = /** @type {Nodes} */ (node_);
throw new Error('Cannot compile unknown node `' + node.type + '`')
}
/**
* @import {Nodes, Parents, RootContent} from 'hast'
* @import {Schema} from 'property-information'
* @import {Options as StringifyEntitiesOptions} from 'stringify-entities'
*/
/** @type {Options} */
const emptyOptions$1 = {};
/** @type {CharacterReferences} */
const emptyCharacterReferences = {};
/** @type {Array<never>} */
const emptyChildren = [];
/**
* Serialize hast as HTML.
*
* @param {Array<RootContent> | Nodes} tree
* Tree to serialize.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized HTML.
*/
function toHtml(tree, options) {
const options_ = options || emptyOptions$1;
const quote = options_.quote || '"';
const alternative = quote === '"' ? "'" : '"';
if (quote !== '"' && quote !== "'") {
throw new Error('Invalid quote `' + quote + '`, expected `\'` or `"`')
}
/** @type {State} */
const state = {
one: one$1,
all: all$1,
settings: {
omitOptionalTags: options_.omitOptionalTags || false,
allowParseErrors: options_.allowParseErrors || false,
allowDangerousCharacters: options_.allowDangerousCharacters || false,
quoteSmart: options_.quoteSmart || false,
preferUnquote