5679 lines
162 KiB
JavaScript
5679 lines
162 KiB
JavaScript
import { StackElementMetadata, INITIAL, Registry as Registry$1, Theme } from './textmate.mjs';
|
||
import { FontStyle } from './types.mjs';
|
||
|
||
function toArray(x) {
|
||
return Array.isArray(x) ? x : [x];
|
||
}
|
||
/**
|
||
* Slipt a string into lines, each line preserves the line ending.
|
||
*/
|
||
function splitLines(code, preserveEnding = false) {
|
||
const parts = code.split(/(\r?\n)/g);
|
||
let index = 0;
|
||
const lines = [];
|
||
for (let i = 0; i < parts.length; i += 2) {
|
||
const line = preserveEnding
|
||
? parts[i] + (parts[i + 1] || '')
|
||
: parts[i];
|
||
lines.push([line, index]);
|
||
index += parts[i].length;
|
||
index += parts[i + 1]?.length || 0;
|
||
}
|
||
return lines;
|
||
}
|
||
/**
|
||
* Check if the language is plaintext that is ignored by Shiki.
|
||
*
|
||
* Hard-coded plain text languages: `plaintext`, `txt`, `text`, `plain`.
|
||
*/
|
||
function isPlainLang(lang) {
|
||
return !lang || ['plaintext', 'txt', 'text', 'plain'].includes(lang);
|
||
}
|
||
/**
|
||
* Check if the language is specially handled or bypassed by Shiki.
|
||
*
|
||
* Hard-coded languages: `ansi` and plaintexts like `plaintext`, `txt`, `text`, `plain`.
|
||
*/
|
||
function isSpecialLang(lang) {
|
||
return lang === 'ansi' || isPlainLang(lang);
|
||
}
|
||
/**
|
||
* Check if the theme is specially handled or bypassed by Shiki.
|
||
*
|
||
* Hard-coded themes: `none`.
|
||
*/
|
||
function isNoneTheme(theme) {
|
||
return theme === 'none';
|
||
}
|
||
/**
|
||
* Check if the theme is specially handled or bypassed by Shiki.
|
||
*
|
||
* Hard-coded themes: `none`.
|
||
*/
|
||
function isSpecialTheme(theme) {
|
||
return isNoneTheme(theme);
|
||
}
|
||
/**
|
||
* Utility to append class to a hast node
|
||
*
|
||
* If the `property.class` is a string, it will be splitted by space and converted to an array.
|
||
*/
|
||
function addClassToHast(node, className) {
|
||
if (!className)
|
||
return node;
|
||
node.properties ||= {};
|
||
node.properties.class ||= [];
|
||
if (typeof node.properties.class === 'string')
|
||
node.properties.class = node.properties.class.split(/\s+/g);
|
||
if (!Array.isArray(node.properties.class))
|
||
node.properties.class = [];
|
||
const targets = Array.isArray(className) ? className : className.split(/\s+/g);
|
||
for (const c of targets) {
|
||
if (c && !node.properties.class.includes(c))
|
||
node.properties.class.push(c);
|
||
}
|
||
return node;
|
||
}
|
||
/**
|
||
* Split a token into multiple tokens by given offsets.
|
||
*
|
||
* The offsets are relative to the token, and should be sorted.
|
||
*/
|
||
function splitToken(token, offsets) {
|
||
let lastOffset = 0;
|
||
const tokens = [];
|
||
for (const offset of offsets) {
|
||
if (offset > lastOffset) {
|
||
tokens.push({
|
||
...token,
|
||
content: token.content.slice(lastOffset, offset),
|
||
offset: token.offset + lastOffset,
|
||
});
|
||
}
|
||
lastOffset = offset;
|
||
}
|
||
if (lastOffset < token.content.length) {
|
||
tokens.push({
|
||
...token,
|
||
content: token.content.slice(lastOffset),
|
||
offset: token.offset + lastOffset,
|
||
});
|
||
}
|
||
return tokens;
|
||
}
|
||
/**
|
||
* Split 2D tokens array by given breakpoints.
|
||
*/
|
||
function splitTokens(tokens, breakpoints) {
|
||
const sorted = Array.from(breakpoints instanceof Set ? breakpoints : new Set(breakpoints))
|
||
.sort((a, b) => a - b);
|
||
if (!sorted.length)
|
||
return tokens;
|
||
return tokens.map((line) => {
|
||
return line.flatMap((token) => {
|
||
const breakpointsInToken = sorted
|
||
.filter(i => token.offset < i && i < token.offset + token.content.length)
|
||
.map(i => i - token.offset)
|
||
.sort((a, b) => a - b);
|
||
if (!breakpointsInToken.length)
|
||
return token;
|
||
return splitToken(token, breakpointsInToken);
|
||
});
|
||
});
|
||
}
|
||
function resolveColorReplacements(theme, options) {
|
||
const replacements = typeof theme === 'string' ? {} : { ...theme.colorReplacements };
|
||
const themeName = typeof theme === 'string' ? theme : theme.name;
|
||
for (const [key, value] of Object.entries(options?.colorReplacements || {})) {
|
||
if (typeof value === 'string')
|
||
replacements[key] = value;
|
||
else if (key === themeName)
|
||
Object.assign(replacements, value);
|
||
}
|
||
return replacements;
|
||
}
|
||
function applyColorReplacements(color, replacements) {
|
||
if (!color)
|
||
return color;
|
||
return replacements?.[color?.toLowerCase()] || color;
|
||
}
|
||
function getTokenStyleObject(token) {
|
||
const styles = {};
|
||
if (token.color)
|
||
styles.color = token.color;
|
||
if (token.bgColor)
|
||
styles['background-color'] = token.bgColor;
|
||
if (token.fontStyle) {
|
||
if (token.fontStyle & FontStyle.Italic)
|
||
styles['font-style'] = 'italic';
|
||
if (token.fontStyle & FontStyle.Bold)
|
||
styles['font-weight'] = 'bold';
|
||
if (token.fontStyle & FontStyle.Underline)
|
||
styles['text-decoration'] = 'underline';
|
||
}
|
||
return styles;
|
||
}
|
||
function stringifyTokenStyle(token) {
|
||
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(';');
|
||
}
|
||
/**
|
||
* Creates a converter between index and position in a code block.
|
||
*/
|
||
function createPositionConverter(code) {
|
||
const lines = splitLines(code, true).map(([line]) => line);
|
||
function indexToPos(index) {
|
||
let character = index;
|
||
let line = 0;
|
||
for (const lineText of lines) {
|
||
if (character < lineText.length)
|
||
break;
|
||
character -= lineText.length;
|
||
line++;
|
||
}
|
||
return { line, character };
|
||
}
|
||
function posToIndex(line, character) {
|
||
let index = 0;
|
||
for (let i = 0; i < line; i++)
|
||
index += lines[i].length;
|
||
index += character;
|
||
return index;
|
||
}
|
||
return {
|
||
lines,
|
||
indexToPos,
|
||
posToIndex,
|
||
};
|
||
}
|
||
|
||
// src/colors.ts
|
||
var namedColors = [
|
||
"black",
|
||
"red",
|
||
"green",
|
||
"yellow",
|
||
"blue",
|
||
"magenta",
|
||
"cyan",
|
||
"white",
|
||
"brightBlack",
|
||
"brightRed",
|
||
"brightGreen",
|
||
"brightYellow",
|
||
"brightBlue",
|
||
"brightMagenta",
|
||
"brightCyan",
|
||
"brightWhite"
|
||
];
|
||
|
||
// src/decorations.ts
|
||
var decorations = {
|
||
1: "bold",
|
||
2: "dim",
|
||
3: "italic",
|
||
4: "underline",
|
||
7: "reverse",
|
||
9: "strikethrough"
|
||
};
|
||
|
||
// src/parser.ts
|
||
function findSequence(value, position) {
|
||
const nextEscape = value.indexOf("\x1B[", position);
|
||
if (nextEscape !== -1) {
|
||
const nextClose = value.indexOf("m", nextEscape);
|
||
return {
|
||
sequence: value.substring(nextEscape + 2, nextClose).split(";"),
|
||
startPosition: nextEscape,
|
||
position: nextClose + 1
|
||
};
|
||
}
|
||
return {
|
||
position: value.length
|
||
};
|
||
}
|
||
function parseColor(sequence, index) {
|
||
let offset = 1;
|
||
const colorMode = sequence[index + offset++];
|
||
let color;
|
||
if (colorMode === "2") {
|
||
const rgb = [
|
||
sequence[index + offset++],
|
||
sequence[index + offset++],
|
||
sequence[index + offset]
|
||
].map((x) => Number.parseInt(x));
|
||
if (rgb.length === 3 && !rgb.some((x) => Number.isNaN(x))) {
|
||
color = {
|
||
type: "rgb",
|
||
rgb
|
||
};
|
||
}
|
||
} else if (colorMode === "5") {
|
||
const colorIndex = Number.parseInt(sequence[index + offset]);
|
||
if (!Number.isNaN(colorIndex)) {
|
||
color = { type: "table", index: Number(colorIndex) };
|
||
}
|
||
}
|
||
return [offset, color];
|
||
}
|
||
function parseSequence(sequence) {
|
||
const commands = [];
|
||
for (let i = 0; i < sequence.length; i++) {
|
||
const code = sequence[i];
|
||
const codeInt = Number.parseInt(code);
|
||
if (Number.isNaN(codeInt))
|
||
continue;
|
||
if (codeInt === 0) {
|
||
commands.push({ type: "resetAll" });
|
||
} else if (codeInt <= 9) {
|
||
const decoration = decorations[codeInt];
|
||
if (decoration) {
|
||
commands.push({
|
||
type: "setDecoration",
|
||
value: decorations[codeInt]
|
||
});
|
||
}
|
||
} else if (codeInt <= 29) {
|
||
const decoration = decorations[codeInt - 20];
|
||
if (decoration) {
|
||
commands.push({
|
||
type: "resetDecoration",
|
||
value: decoration
|
||
});
|
||
}
|
||
} else if (codeInt <= 37) {
|
||
commands.push({
|
||
type: "setForegroundColor",
|
||
value: { type: "named", name: namedColors[codeInt - 30] }
|
||
});
|
||
} else if (codeInt === 38) {
|
||
const [offset, color] = parseColor(sequence, i);
|
||
if (color) {
|
||
commands.push({
|
||
type: "setForegroundColor",
|
||
value: color
|
||
});
|
||
}
|
||
i += offset;
|
||
} else if (codeInt === 39) {
|
||
commands.push({
|
||
type: "resetForegroundColor"
|
||
});
|
||
} else if (codeInt <= 47) {
|
||
commands.push({
|
||
type: "setBackgroundColor",
|
||
value: { type: "named", name: namedColors[codeInt - 40] }
|
||
});
|
||
} else if (codeInt === 48) {
|
||
const [offset, color] = parseColor(sequence, i);
|
||
if (color) {
|
||
commands.push({
|
||
type: "setBackgroundColor",
|
||
value: color
|
||
});
|
||
}
|
||
i += offset;
|
||
} else if (codeInt === 49) {
|
||
commands.push({
|
||
type: "resetBackgroundColor"
|
||
});
|
||
} else if (codeInt >= 90 && codeInt <= 97) {
|
||
commands.push({
|
||
type: "setForegroundColor",
|
||
value: { type: "named", name: namedColors[codeInt - 90 + 8] }
|
||
});
|
||
} else if (codeInt >= 100 && codeInt <= 107) {
|
||
commands.push({
|
||
type: "setBackgroundColor",
|
||
value: { type: "named", name: namedColors[codeInt - 100 + 8] }
|
||
});
|
||
}
|
||
}
|
||
return commands;
|
||
}
|
||
function createAnsiSequenceParser() {
|
||
let foreground = null;
|
||
let background = null;
|
||
let decorations2 = /* @__PURE__ */ new Set();
|
||
return {
|
||
parse(value) {
|
||
const tokens = [];
|
||
let position = 0;
|
||
do {
|
||
const findResult = findSequence(value, position);
|
||
const text = findResult.sequence ? value.substring(position, findResult.startPosition) : value.substring(position);
|
||
if (text.length > 0) {
|
||
tokens.push({
|
||
value: text,
|
||
foreground,
|
||
background,
|
||
decorations: new Set(decorations2)
|
||
});
|
||
}
|
||
if (findResult.sequence) {
|
||
const commands = parseSequence(findResult.sequence);
|
||
for (const styleToken of commands) {
|
||
if (styleToken.type === "resetAll") {
|
||
foreground = null;
|
||
background = null;
|
||
decorations2.clear();
|
||
} else if (styleToken.type === "resetForegroundColor") {
|
||
foreground = null;
|
||
} else if (styleToken.type === "resetBackgroundColor") {
|
||
background = null;
|
||
} else if (styleToken.type === "resetDecoration") {
|
||
decorations2.delete(styleToken.value);
|
||
}
|
||
}
|
||
for (const styleToken of commands) {
|
||
if (styleToken.type === "setForegroundColor") {
|
||
foreground = styleToken.value;
|
||
} else if (styleToken.type === "setBackgroundColor") {
|
||
background = styleToken.value;
|
||
} else if (styleToken.type === "setDecoration") {
|
||
decorations2.add(styleToken.value);
|
||
}
|
||
}
|
||
}
|
||
position = findResult.position;
|
||
} while (position < value.length);
|
||
return tokens;
|
||
}
|
||
};
|
||
}
|
||
|
||
// src/palette.ts
|
||
var defaultNamedColorsMap = {
|
||
black: "#000000",
|
||
red: "#bb0000",
|
||
green: "#00bb00",
|
||
yellow: "#bbbb00",
|
||
blue: "#0000bb",
|
||
magenta: "#ff00ff",
|
||
cyan: "#00bbbb",
|
||
white: "#eeeeee",
|
||
brightBlack: "#555555",
|
||
brightRed: "#ff5555",
|
||
brightGreen: "#00ff00",
|
||
brightYellow: "#ffff55",
|
||
brightBlue: "#5555ff",
|
||
brightMagenta: "#ff55ff",
|
||
brightCyan: "#55ffff",
|
||
brightWhite: "#ffffff"
|
||
};
|
||
function createColorPalette(namedColorsMap = defaultNamedColorsMap) {
|
||
function namedColor(name) {
|
||
return namedColorsMap[name];
|
||
}
|
||
function rgbColor(rgb) {
|
||
return `#${rgb.map((x) => Math.max(0, Math.min(x, 255)).toString(16).padStart(2, "0")).join("")}`;
|
||
}
|
||
let colorTable;
|
||
function getColorTable() {
|
||
if (colorTable) {
|
||
return colorTable;
|
||
}
|
||
colorTable = [];
|
||
for (let i = 0; i < namedColors.length; i++) {
|
||
colorTable.push(namedColor(namedColors[i]));
|
||
}
|
||
let levels = [0, 95, 135, 175, 215, 255];
|
||
for (let r = 0; r < 6; r++) {
|
||
for (let g = 0; g < 6; g++) {
|
||
for (let b = 0; b < 6; b++) {
|
||
colorTable.push(rgbColor([levels[r], levels[g], levels[b]]));
|
||
}
|
||
}
|
||
}
|
||
let level = 8;
|
||
for (let i = 0; i < 24; i++, level += 10) {
|
||
colorTable.push(rgbColor([level, level, level]));
|
||
}
|
||
return colorTable;
|
||
}
|
||
function tableColor(index) {
|
||
return getColorTable()[index];
|
||
}
|
||
function value(color) {
|
||
switch (color.type) {
|
||
case "named":
|
||
return namedColor(color.name);
|
||
case "rgb":
|
||
return rgbColor(color.rgb);
|
||
case "table":
|
||
return tableColor(color.index);
|
||
}
|
||
}
|
||
return {
|
||
value
|
||
};
|
||
}
|
||
|
||
function tokenizeAnsiWithTheme(theme, fileContents, options) {
|
||
const colorReplacements = resolveColorReplacements(theme, options);
|
||
const lines = splitLines(fileContents);
|
||
const colorPalette = createColorPalette(Object.fromEntries(namedColors.map(name => [
|
||
name,
|
||
theme.colors?.[`terminal.ansi${name[0].toUpperCase()}${name.substring(1)}`],
|
||
])));
|
||
const parser = createAnsiSequenceParser();
|
||
return lines.map(line => parser.parse(line[0]).map((token) => {
|
||
let color;
|
||
let bgColor;
|
||
if (token.decorations.has('reverse')) {
|
||
color = token.background ? colorPalette.value(token.background) : theme.bg;
|
||
bgColor = token.foreground ? colorPalette.value(token.foreground) : theme.fg;
|
||
}
|
||
else {
|
||
color = token.foreground ? colorPalette.value(token.foreground) : theme.fg;
|
||
bgColor = token.background ? colorPalette.value(token.background) : undefined;
|
||
}
|
||
color = applyColorReplacements(color, colorReplacements);
|
||
bgColor = applyColorReplacements(bgColor, colorReplacements);
|
||
if (token.decorations.has('dim'))
|
||
color = dimColor(color);
|
||
let fontStyle = FontStyle.None;
|
||
if (token.decorations.has('bold'))
|
||
fontStyle |= FontStyle.Bold;
|
||
if (token.decorations.has('italic'))
|
||
fontStyle |= FontStyle.Italic;
|
||
if (token.decorations.has('underline'))
|
||
fontStyle |= FontStyle.Underline;
|
||
return {
|
||
content: token.value,
|
||
offset: line[1], // TODO: more accurate offset? might need to fork ansi-sequence-parser
|
||
color,
|
||
bgColor,
|
||
fontStyle,
|
||
};
|
||
}));
|
||
}
|
||
/**
|
||
* Adds 50% alpha to a hex color string or the "-dim" postfix to a CSS variable
|
||
*/
|
||
function dimColor(color) {
|
||
const hexMatch = color.match(/#([0-9a-f]{3})([0-9a-f]{3})?([0-9a-f]{2})?/);
|
||
if (hexMatch) {
|
||
if (hexMatch[3]) {
|
||
// convert from #rrggbbaa to #rrggbb(aa/2)
|
||
const alpha = Math.round(Number.parseInt(hexMatch[3], 16) / 2)
|
||
.toString(16)
|
||
.padStart(2, '0');
|
||
return `#${hexMatch[1]}${hexMatch[2]}${alpha}`;
|
||
}
|
||
else if (hexMatch[2]) {
|
||
// convert from #rrggbb to #rrggbb80
|
||
return `#${hexMatch[1]}${hexMatch[2]}80`;
|
||
}
|
||
else {
|
||
// convert from #rgb to #rrggbb80
|
||
return `#${Array.from(hexMatch[1])
|
||
.map(x => `${x}${x}`)
|
||
.join('')}80`;
|
||
}
|
||
}
|
||
const cssVarMatch = color.match(/var\((--[\w-]+-ansi-[\w-]+)\)/);
|
||
if (cssVarMatch)
|
||
return `var(${cssVarMatch[1]}-dim)`;
|
||
return color;
|
||
}
|
||
|
||
/**
|
||
* Code to tokens, with a simple theme.
|
||
*/
|
||
function codeToTokensBase(internal, code, options = {}) {
|
||
const { lang = 'text', theme: themeName = internal.getLoadedThemes()[0], } = options;
|
||
if (isPlainLang(lang) || isNoneTheme(themeName))
|
||
return splitLines(code).map(line => [{ content: line[0], offset: line[1] }]);
|
||
const { theme, colorMap } = internal.setTheme(themeName);
|
||
if (lang === 'ansi')
|
||
return tokenizeAnsiWithTheme(theme, code, options);
|
||
const _grammar = internal.getLanguage(lang);
|
||
return tokenizeWithTheme(code, _grammar, theme, colorMap, options);
|
||
}
|
||
function tokenizeWithTheme(code, grammar, theme, colorMap, options) {
|
||
const colorReplacements = resolveColorReplacements(theme, options);
|
||
const { tokenizeMaxLineLength = 0, tokenizeTimeLimit = 500, } = options;
|
||
const lines = splitLines(code);
|
||
let ruleStack = INITIAL;
|
||
let actual = [];
|
||
const final = [];
|
||
const themeSettingsSelectors = [];
|
||
if (options.includeExplanation) {
|
||
for (const setting of theme.settings) {
|
||
let selectors;
|
||
switch (typeof setting.scope) {
|
||
case 'string':
|
||
selectors = setting.scope.split(/,/).map(scope => scope.trim());
|
||
break;
|
||
case 'object':
|
||
selectors = setting.scope;
|
||
break;
|
||
default:
|
||
continue;
|
||
}
|
||
themeSettingsSelectors.push({
|
||
settings: setting,
|
||
selectors: selectors.map(selector => selector.split(/ /)),
|
||
});
|
||
}
|
||
}
|
||
for (let i = 0, len = lines.length; i < len; i++) {
|
||
const [line, lineOffset] = lines[i];
|
||
if (line === '') {
|
||
actual = [];
|
||
final.push([]);
|
||
continue;
|
||
}
|
||
// Do not attempt to tokenize if the line length is longer than the `tokenizationMaxLineLength`
|
||
if (tokenizeMaxLineLength > 0 && line.length >= tokenizeMaxLineLength) {
|
||
actual = [];
|
||
final.push([{
|
||
content: line,
|
||
offset: lineOffset,
|
||
color: '',
|
||
fontStyle: 0,
|
||
}]);
|
||
continue;
|
||
}
|
||
let resultWithScopes;
|
||
let tokensWithScopes;
|
||
let tokensWithScopesIndex;
|
||
if (options.includeExplanation) {
|
||
resultWithScopes = grammar.tokenizeLine(line, ruleStack);
|
||
tokensWithScopes = resultWithScopes.tokens;
|
||
tokensWithScopesIndex = 0;
|
||
}
|
||
const result = grammar.tokenizeLine2(line, ruleStack, tokenizeTimeLimit);
|
||
const tokensLength = result.tokens.length / 2;
|
||
for (let j = 0; j < tokensLength; j++) {
|
||
const startIndex = result.tokens[2 * j];
|
||
const nextStartIndex = j + 1 < tokensLength ? result.tokens[2 * j + 2] : line.length;
|
||
if (startIndex === nextStartIndex)
|
||
continue;
|
||
const metadata = result.tokens[2 * j + 1];
|
||
const color = applyColorReplacements(colorMap[StackElementMetadata.getForeground(metadata)], colorReplacements);
|
||
const fontStyle = StackElementMetadata.getFontStyle(metadata);
|
||
const token = {
|
||
content: line.substring(startIndex, nextStartIndex),
|
||
offset: lineOffset + startIndex,
|
||
color,
|
||
fontStyle,
|
||
};
|
||
if (options.includeExplanation) {
|
||
token.explanation = [];
|
||
let offset = 0;
|
||
while (startIndex + offset < nextStartIndex) {
|
||
const tokenWithScopes = tokensWithScopes[tokensWithScopesIndex];
|
||
const tokenWithScopesText = line.substring(tokenWithScopes.startIndex, tokenWithScopes.endIndex);
|
||
offset += tokenWithScopesText.length;
|
||
token.explanation.push({
|
||
content: tokenWithScopesText,
|
||
scopes: explainThemeScopes(themeSettingsSelectors, tokenWithScopes.scopes),
|
||
});
|
||
tokensWithScopesIndex += 1;
|
||
}
|
||
}
|
||
actual.push(token);
|
||
}
|
||
final.push(actual);
|
||
actual = [];
|
||
ruleStack = result.ruleStack;
|
||
}
|
||
return final;
|
||
}
|
||
function explainThemeScopes(themeSelectors, scopes) {
|
||
const result = [];
|
||
for (let i = 0, len = scopes.length; i < len; i++) {
|
||
const parentScopes = scopes.slice(0, i);
|
||
const scope = scopes[i];
|
||
result[i] = {
|
||
scopeName: scope,
|
||
themeMatches: explainThemeScope(themeSelectors, scope, parentScopes),
|
||
};
|
||
}
|
||
return result;
|
||
}
|
||
function matchesOne(selector, scope) {
|
||
return selector === scope
|
||
|| (scope.substring(0, selector.length) === selector && scope[selector.length] === '.');
|
||
}
|
||
function matches(selectors, scope, parentScopes) {
|
||
if (!matchesOne(selectors[selectors.length - 1], scope))
|
||
return false;
|
||
let selectorParentIndex = selectors.length - 2;
|
||
let parentIndex = parentScopes.length - 1;
|
||
while (selectorParentIndex >= 0 && parentIndex >= 0) {
|
||
if (matchesOne(selectors[selectorParentIndex], parentScopes[parentIndex]))
|
||
selectorParentIndex -= 1;
|
||
parentIndex -= 1;
|
||
}
|
||
if (selectorParentIndex === -1)
|
||
return true;
|
||
return false;
|
||
}
|
||
function explainThemeScope(themeSettingsSelectors, scope, parentScopes) {
|
||
const result = [];
|
||
for (const { selectors, settings } of themeSettingsSelectors) {
|
||
for (const selectorPieces of selectors) {
|
||
if (matches(selectorPieces, scope, parentScopes)) {
|
||
result.push(settings);
|
||
break; // continue to the next theme settings
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Get tokens with multiple themes
|
||
*/
|
||
function codeToTokensWithThemes(internal, code, options) {
|
||
const themes = Object.entries(options.themes)
|
||
.filter(i => i[1])
|
||
.map(i => ({ color: i[0], theme: i[1] }));
|
||
const tokens = syncThemesTokenization(...themes.map(t => codeToTokensBase(internal, code, {
|
||
...options,
|
||
theme: t.theme,
|
||
})));
|
||
const mergedTokens = tokens[0]
|
||
.map((line, lineIdx) => line
|
||
.map((_token, tokenIdx) => {
|
||
const mergedToken = {
|
||
content: _token.content,
|
||
variants: {},
|
||
offset: _token.offset,
|
||
};
|
||
tokens.forEach((t, themeIdx) => {
|
||
const { content: _, explanation: __, offset: ___, ...styles } = t[lineIdx][tokenIdx];
|
||
mergedToken.variants[themes[themeIdx].color] = styles;
|
||
});
|
||
return mergedToken;
|
||
}));
|
||
return mergedTokens;
|
||
}
|
||
/**
|
||
* Break tokens from multiple themes into same tokenization.
|
||
*
|
||
* For example, given two themes that tokenize `console.log("hello")` as:
|
||
*
|
||
* - `console . log (" hello ")` (6 tokens)
|
||
* - `console .log ( "hello" )` (5 tokens)
|
||
*
|
||
* This function will return:
|
||
*
|
||
* - `console . log ( " hello " )` (8 tokens)
|
||
* - `console . log ( " hello " )` (8 tokens)
|
||
*/
|
||
function syncThemesTokenization(...themes) {
|
||
const outThemes = themes.map(() => []);
|
||
const count = themes.length;
|
||
for (let i = 0; i < themes[0].length; i++) {
|
||
const lines = themes.map(t => t[i]);
|
||
const outLines = outThemes.map(() => []);
|
||
outThemes.forEach((t, i) => t.push(outLines[i]));
|
||
const indexes = lines.map(() => 0);
|
||
const current = lines.map(l => l[0]);
|
||
while (current.every(t => t)) {
|
||
const minLength = Math.min(...current.map(t => t.content.length));
|
||
for (let n = 0; n < count; n++) {
|
||
const token = current[n];
|
||
if (token.content.length === minLength) {
|
||
outLines[n].push(token);
|
||
indexes[n] += 1;
|
||
current[n] = lines[n][indexes[n]];
|
||
}
|
||
else {
|
||
outLines[n].push({
|
||
...token,
|
||
content: token.content.slice(0, minLength),
|
||
});
|
||
current[n] = {
|
||
...token,
|
||
content: token.content.slice(minLength),
|
||
offset: token.offset + minLength,
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return outThemes;
|
||
}
|
||
|
||
class ShikiError extends Error {
|
||
constructor(message) {
|
||
super(message);
|
||
this.name = 'ShikiError';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* High-level code-to-tokens API.
|
||
*
|
||
* It will use `codeToTokensWithThemes` or `codeToTokensBase` based on the options.
|
||
*/
|
||
function codeToTokens(internal, code, options) {
|
||
let bg;
|
||
let fg;
|
||
let tokens;
|
||
let themeName;
|
||
let rootStyle;
|
||
if ('themes' in options) {
|
||
const { defaultColor = 'light', cssVariablePrefix = '--shiki-', } = options;
|
||
const themes = Object.entries(options.themes)
|
||
.filter(i => i[1])
|
||
.map(i => ({ color: i[0], theme: i[1] }))
|
||
.sort((a, b) => a.color === defaultColor ? -1 : b.color === defaultColor ? 1 : 0);
|
||
if (themes.length === 0)
|
||
throw new ShikiError('`themes` option must not be empty');
|
||
const themeTokens = codeToTokensWithThemes(internal, code, options);
|
||
if (defaultColor && !themes.find(t => t.color === defaultColor))
|
||
throw new ShikiError(`\`themes\` option must contain the defaultColor key \`${defaultColor}\``);
|
||
const themeRegs = themes.map(t => internal.getTheme(t.theme));
|
||
const themesOrder = themes.map(t => t.color);
|
||
tokens = themeTokens
|
||
.map(line => line.map(token => mergeToken(token, themesOrder, cssVariablePrefix, defaultColor)));
|
||
const themeColorReplacements = themes.map(t => resolveColorReplacements(t.theme, options));
|
||
fg = themes.map((t, idx) => (idx === 0 && defaultColor
|
||
? ''
|
||
: `${cssVariablePrefix + t.color}:`) + (applyColorReplacements(themeRegs[idx].fg, themeColorReplacements[idx]) || 'inherit')).join(';');
|
||
bg = themes.map((t, idx) => (idx === 0 && defaultColor
|
||
? ''
|
||
: `${cssVariablePrefix + t.color}-bg:`) + (applyColorReplacements(themeRegs[idx].bg, themeColorReplacements[idx]) || 'inherit')).join(';');
|
||
themeName = `shiki-themes ${themeRegs.map(t => t.name).join(' ')}`;
|
||
rootStyle = defaultColor ? undefined : [fg, bg].join(';');
|
||
}
|
||
else if ('theme' in options) {
|
||
const colorReplacements = resolveColorReplacements(options.theme, options.colorReplacements);
|
||
tokens = codeToTokensBase(internal, code, options);
|
||
const _theme = internal.getTheme(options.theme);
|
||
bg = applyColorReplacements(_theme.bg, colorReplacements);
|
||
fg = applyColorReplacements(_theme.fg, colorReplacements);
|
||
themeName = _theme.name;
|
||
}
|
||
else {
|
||
throw new ShikiError('Invalid options, either `theme` or `themes` must be provided');
|
||
}
|
||
return {
|
||
tokens,
|
||
fg,
|
||
bg,
|
||
themeName,
|
||
rootStyle,
|
||
};
|
||
}
|
||
function mergeToken(merged, variantsOrder, cssVariablePrefix, defaultColor) {
|
||
const token = {
|
||
content: merged.content,
|
||
explanation: merged.explanation,
|
||
offset: merged.offset,
|
||
};
|
||
const styles = variantsOrder.map(t => getTokenStyleObject(merged.variants[t]));
|
||
// Get all style keys, for themes that missing some style, we put `inherit` to override as needed
|
||
const styleKeys = new Set(styles.flatMap(t => Object.keys(t)));
|
||
const mergedStyles = styles.reduce((acc, cur, idx) => {
|
||
for (const key of styleKeys) {
|
||
const value = cur[key] || 'inherit';
|
||
if (idx === 0 && defaultColor) {
|
||
acc[key] = value;
|
||
}
|
||
else {
|
||
const keyName = key === 'color' ? '' : key === 'background-color' ? '-bg' : `-${key}`;
|
||
const varKey = cssVariablePrefix + variantsOrder[idx] + (key === 'color' ? '' : keyName);
|
||
if (acc[key])
|
||
acc[key] += `;${varKey}:${value}`;
|
||
else
|
||
acc[key] = `${varKey}:${value}`;
|
||
}
|
||
}
|
||
return acc;
|
||
}, {});
|
||
token.htmlStyle = defaultColor
|
||
? stringifyTokenStyle(mergedStyles)
|
||
: Object.values(mergedStyles).join(';');
|
||
return token;
|
||
}
|
||
|
||
/**
|
||
* A built-in transformer to add decorations to the highlighted code.
|
||
*/
|
||
function transformerDecorations() {
|
||
const map = new WeakMap();
|
||
function getContext(shiki) {
|
||
if (!map.has(shiki.meta)) {
|
||
const converter = createPositionConverter(shiki.source);
|
||
function normalizePosition(p) {
|
||
if (typeof p === 'number') {
|
||
return {
|
||
...converter.indexToPos(p),
|
||
offset: p,
|
||
};
|
||
}
|
||
else {
|
||
return {
|
||
...p,
|
||
offset: converter.posToIndex(p.line, p.character),
|
||
};
|
||
}
|
||
}
|
||
const decorations = (shiki.options.decorations || [])
|
||
.map((d) => ({
|
||
...d,
|
||
start: normalizePosition(d.start),
|
||
end: normalizePosition(d.end),
|
||
}));
|
||
verifyIntersections(decorations);
|
||
map.set(shiki.meta, {
|
||
decorations,
|
||
converter,
|
||
source: shiki.source,
|
||
});
|
||
}
|
||
return map.get(shiki.meta);
|
||
}
|
||
function verifyIntersections(items) {
|
||
for (let i = 0; i < items.length; i++) {
|
||
const foo = items[i];
|
||
if (foo.start.offset > foo.end.offset)
|
||
throw new ShikiError(`Invalid decoration range: ${JSON.stringify(foo.start)} - ${JSON.stringify(foo.end)}`);
|
||
for (let j = i + 1; j < items.length; j++) {
|
||
const bar = items[j];
|
||
const isFooHasBarStart = foo.start.offset < bar.start.offset && bar.start.offset < foo.end.offset;
|
||
const isFooHasBarEnd = foo.start.offset < bar.end.offset && bar.end.offset < foo.end.offset;
|
||
const isBarHasFooStart = bar.start.offset < foo.start.offset && foo.start.offset < bar.end.offset;
|
||
const isBarHasFooEnd = bar.start.offset < foo.end.offset && foo.end.offset < bar.end.offset;
|
||
if (isFooHasBarStart || isFooHasBarEnd || isBarHasFooStart || isBarHasFooEnd) {
|
||
if (isFooHasBarEnd && isFooHasBarEnd)
|
||
continue; // nested
|
||
if (isBarHasFooStart && isBarHasFooEnd)
|
||
continue; // nested
|
||
throw new ShikiError(`Decorations ${JSON.stringify(foo.start)} and ${JSON.stringify(bar.start)} intersect.`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return {
|
||
name: 'shiki:decorations',
|
||
tokens(tokens) {
|
||
if (!this.options.decorations?.length)
|
||
return;
|
||
const ctx = getContext(this);
|
||
const breakpoints = ctx.decorations.flatMap(d => [d.start.offset, d.end.offset]);
|
||
const splitted = splitTokens(tokens, breakpoints);
|
||
return splitted;
|
||
},
|
||
code(codeEl) {
|
||
if (!this.options.decorations?.length)
|
||
return;
|
||
const ctx = getContext(this);
|
||
const lines = Array.from(codeEl.children).filter(i => i.type === 'element' && i.tagName === 'span');
|
||
if (lines.length !== ctx.converter.lines.length)
|
||
throw new ShikiError(`Number of lines in code element (${lines.length}) does not match the number of lines in the source (${ctx.converter.lines.length}). Failed to apply decorations.`);
|
||
function applyLineSection(line, start, end, decoration) {
|
||
const lineEl = lines[line];
|
||
let text = '';
|
||
let startIndex = -1;
|
||
let endIndex = -1;
|
||
function stringify(el) {
|
||
if (el.type === 'text')
|
||
return el.value;
|
||
if (el.type === 'element')
|
||
return el.children.map(stringify).join('');
|
||
return '';
|
||
}
|
||
if (start === 0)
|
||
startIndex = 0;
|
||
if (end === 0)
|
||
endIndex = 0;
|
||
if (end === Number.POSITIVE_INFINITY)
|
||
endIndex = lineEl.children.length;
|
||
if (startIndex === -1 || endIndex === -1) {
|
||
for (let i = 0; i < lineEl.children.length; i++) {
|
||
text += stringify(lineEl.children[i]);
|
||
if (startIndex === -1 && text.length === start)
|
||
startIndex = i + 1;
|
||
if (endIndex === -1 && text.length === end)
|
||
endIndex = i + 1;
|
||
}
|
||
}
|
||
if (startIndex === -1)
|
||
throw new ShikiError(`Failed to find start index for decoration ${JSON.stringify(decoration.start)}`);
|
||
if (endIndex === -1)
|
||
throw new ShikiError(`Failed to find end index for decoration ${JSON.stringify(decoration.end)}`);
|
||
const children = lineEl.children.slice(startIndex, endIndex);
|
||
// Full line decoration
|
||
if (!decoration.alwaysWrap && children.length === lineEl.children.length) {
|
||
applyDecoration(lineEl, decoration, 'line');
|
||
}
|
||
// Single token decoration
|
||
else if (!decoration.alwaysWrap && children.length === 1 && children[0].type === 'element') {
|
||
applyDecoration(children[0], decoration, 'token');
|
||
}
|
||
// Create a wrapper for the decoration
|
||
else {
|
||
const wrapper = {
|
||
type: 'element',
|
||
tagName: 'span',
|
||
properties: {},
|
||
children,
|
||
};
|
||
applyDecoration(wrapper, decoration, 'wrapper');
|
||
lineEl.children.splice(startIndex, children.length, wrapper);
|
||
}
|
||
}
|
||
function applyLine(line, decoration) {
|
||
lines[line] = applyDecoration(lines[line], decoration, 'line');
|
||
}
|
||
function applyDecoration(el, decoration, type) {
|
||
const properties = decoration.properties || {};
|
||
const transform = decoration.transform || (i => i);
|
||
el.tagName = decoration.tagName || 'span';
|
||
el.properties = {
|
||
...el.properties,
|
||
...properties,
|
||
class: el.properties.class,
|
||
};
|
||
if (decoration.properties?.class)
|
||
addClassToHast(el, decoration.properties.class);
|
||
el = transform(el, type) || el;
|
||
return el;
|
||
}
|
||
const lineApplies = [];
|
||
// Apply decorations in reverse order so the nested ones get applied first.
|
||
const sorted = ctx.decorations.sort((a, b) => b.start.offset - a.start.offset);
|
||
for (const decoration of sorted) {
|
||
const { start, end } = decoration;
|
||
if (start.line === end.line) {
|
||
applyLineSection(start.line, start.character, end.character, decoration);
|
||
}
|
||
else if (start.line < end.line) {
|
||
applyLineSection(start.line, start.character, Number.POSITIVE_INFINITY, decoration);
|
||
for (let i = start.line + 1; i < end.line; i++)
|
||
lineApplies.unshift(() => applyLine(i, decoration));
|
||
applyLineSection(end.line, 0, end.character, decoration);
|
||
}
|
||
}
|
||
lineApplies.forEach(i => i());
|
||
},
|
||
};
|
||
}
|
||
|
||
const builtInTransformers = [
|
||
/* @__PURE__ */ transformerDecorations(),
|
||
];
|
||
function getTransformers(options) {
|
||
return [
|
||
...options.transformers || [],
|
||
...builtInTransformers,
|
||
];
|
||
}
|
||
|
||
function codeToHast(internal, code, options, transformerContext = {
|
||
meta: {},
|
||
options,
|
||
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
|
||
codeToTokens: (_code, _options) => codeToTokens(internal, _code, _options),
|
||
}) {
|
||
let input = code;
|
||
for (const transformer of getTransformers(options))
|
||
input = transformer.preprocess?.call(transformerContext, input, options) || input;
|
||
let { tokens, fg, bg, themeName, rootStyle, } = codeToTokens(internal, input, options);
|
||
const { mergeWhitespaces = true, } = options;
|
||
if (mergeWhitespaces === true)
|
||
tokens = mergeWhitespaceTokens(tokens);
|
||
else if (mergeWhitespaces === 'never')
|
||
tokens = splitWhitespaceTokens(tokens);
|
||
const contextSource = {
|
||
...transformerContext,
|
||
get source() {
|
||
return input;
|
||
},
|
||
};
|
||
for (const transformer of getTransformers(options))
|
||
tokens = transformer.tokens?.call(contextSource, tokens) || tokens;
|
||
return tokensToHast(tokens, {
|
||
...options,
|
||
fg,
|
||
bg,
|
||
themeName,
|
||
rootStyle,
|
||
}, contextSource);
|
||
}
|
||
function tokensToHast(tokens, options, transformerContext) {
|
||
const transformers = getTransformers(options);
|
||
const lines = [];
|
||
const root = {
|
||
type: 'root',
|
||
children: [],
|
||
};
|
||
const { structure = 'classic', } = options;
|
||
let preNode = {
|
||
type: 'element',
|
||
tagName: 'pre',
|
||
properties: {
|
||
class: `shiki ${options.themeName || ''}`,
|
||
style: options.rootStyle || `background-color:${options.bg};color:${options.fg}`,
|
||
tabindex: '0',
|
||
...Object.fromEntries(Array.from(Object.entries(options.meta || {}))
|
||
.filter(([key]) => !key.startsWith('_'))),
|
||
},
|
||
children: [],
|
||
};
|
||
let codeNode = {
|
||
type: 'element',
|
||
tagName: 'code',
|
||
properties: {},
|
||
children: lines,
|
||
};
|
||
const lineNodes = [];
|
||
const context = {
|
||
...transformerContext,
|
||
structure,
|
||
addClassToHast,
|
||
get source() {
|
||
return transformerContext.source;
|
||
},
|
||
get tokens() {
|
||
return tokens;
|
||
},
|
||
get options() {
|
||
return options;
|
||
},
|
||
get root() {
|
||
return root;
|
||
},
|
||
get pre() {
|
||
return preNode;
|
||
},
|
||
get code() {
|
||
return codeNode;
|
||
},
|
||
get lines() {
|
||
return lineNodes;
|
||
},
|
||
};
|
||
tokens.forEach((line, idx) => {
|
||
if (idx) {
|
||
if (structure === 'inline')
|
||
root.children.push({ type: 'element', tagName: 'br', properties: {}, children: [] });
|
||
else if (structure === 'classic')
|
||
lines.push({ type: 'text', value: '\n' });
|
||
}
|
||
let lineNode = {
|
||
type: 'element',
|
||
tagName: 'span',
|
||
properties: { class: 'line' },
|
||
children: [],
|
||
};
|
||
let col = 0;
|
||
for (const token of line) {
|
||
let tokenNode = {
|
||
type: 'element',
|
||
tagName: 'span',
|
||
properties: {},
|
||
children: [{ type: 'text', value: token.content }],
|
||
};
|
||
const style = token.htmlStyle || stringifyTokenStyle(getTokenStyleObject(token));
|
||
if (style)
|
||
tokenNode.properties.style = style;
|
||
for (const transformer of transformers)
|
||
tokenNode = transformer?.span?.call(context, tokenNode, idx + 1, col, lineNode) || tokenNode;
|
||
if (structure === 'inline')
|
||
root.children.push(tokenNode);
|
||
else if (structure === 'classic')
|
||
lineNode.children.push(tokenNode);
|
||
col += token.content.length;
|
||
}
|
||
if (structure === 'classic') {
|
||
for (const transformer of transformers)
|
||
lineNode = transformer?.line?.call(context, lineNode, idx + 1) || lineNode;
|
||
lineNodes.push(lineNode);
|
||
lines.push(lineNode);
|
||
}
|
||
});
|
||
if (structure === 'classic') {
|
||
for (const transformer of transformers)
|
||
codeNode = transformer?.code?.call(context, codeNode) || codeNode;
|
||
preNode.children.push(codeNode);
|
||
for (const transformer of transformers)
|
||
preNode = transformer?.pre?.call(context, preNode) || preNode;
|
||
root.children.push(preNode);
|
||
}
|
||
let result = root;
|
||
for (const transformer of transformers)
|
||
result = transformer?.root?.call(context, result) || result;
|
||
return result;
|
||
}
|
||
function mergeWhitespaceTokens(tokens) {
|
||
return tokens.map((line) => {
|
||
const newLine = [];
|
||
let carryOnContent = '';
|
||
let firstOffset = 0;
|
||
line.forEach((token, idx) => {
|
||
const isUnderline = token.fontStyle && token.fontStyle & FontStyle.Underline;
|
||
const couldMerge = !isUnderline;
|
||
if (couldMerge && token.content.match(/^\s+$/) && line[idx + 1]) {
|
||
if (!firstOffset)
|
||
firstOffset = token.offset;
|
||
carryOnContent += token.content;
|
||
}
|
||
else {
|
||
if (carryOnContent) {
|
||
if (couldMerge) {
|
||
newLine.push({
|
||
...token,
|
||
offset: firstOffset,
|
||
content: carryOnContent + token.content,
|
||
});
|
||
}
|
||
else {
|
||
newLine.push({
|
||
content: carryOnContent,
|
||
offset: firstOffset,
|
||
}, token);
|
||
}
|
||
firstOffset = 0;
|
||
carryOnContent = '';
|
||
}
|
||
else {
|
||
newLine.push(token);
|
||
}
|
||
}
|
||
});
|
||
return newLine;
|
||
});
|
||
}
|
||
function splitWhitespaceTokens(tokens) {
|
||
return tokens.map((line) => {
|
||
return line.flatMap((token) => {
|
||
if (token.content.match(/^\s+$/))
|
||
return token;
|
||
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
||
const match = token.content.match(/^(\s*)(.*?)(\s*)$/);
|
||
if (!match)
|
||
return token;
|
||
const [, leading, content, trailing] = match;
|
||
if (!leading && !trailing)
|
||
return token;
|
||
const expanded = [{
|
||
...token,
|
||
offset: token.offset + leading.length,
|
||
content,
|
||
}];
|
||
if (leading) {
|
||
expanded.unshift({
|
||
content: leading,
|
||
offset: token.offset,
|
||
});
|
||
}
|
||
if (trailing) {
|
||
expanded.push({
|
||
content: trailing,
|
||
offset: token.offset + leading.length + content.length,
|
||
});
|
||
}
|
||
return expanded;
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* List of HTML void tag names.
|
||
*
|
||
* @type {Array<string>}
|
||
*/
|
||
const htmlVoidElements = [
|
||
'area',
|
||
'base',
|
||
'basefont',
|
||
'bgsound',
|
||
'br',
|
||
'col',
|
||
'command',
|
||
'embed',
|
||
'frame',
|
||
'hr',
|
||
'image',
|
||
'img',
|
||
'input',
|
||
'keygen',
|
||
'link',
|
||
'meta',
|
||
'param',
|
||
'source',
|
||
'track',
|
||
'wbr'
|
||
];
|
||
|
||
/**
|
||
* @typedef {import('./info.js').Info} Info
|
||
* @typedef {Record<string, Info>} Properties
|
||
* @typedef {Record<string, string>} Normal
|
||
*/
|
||
|
||
class Schema {
|
||
/**
|
||
* @constructor
|
||
* @param {Properties} property
|
||
* @param {Normal} normal
|
||
* @param {string} [space]
|
||
*/
|
||
constructor(property, normal, space) {
|
||
this.property = property;
|
||
this.normal = normal;
|
||
if (space) {
|
||
this.space = space;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @type {Properties} */
|
||
Schema.prototype.property = {};
|
||
/** @type {Normal} */
|
||
Schema.prototype.normal = {};
|
||
/** @type {string|null} */
|
||
Schema.prototype.space = null;
|
||
|
||
/**
|
||
* @typedef {import('./schema.js').Properties} Properties
|
||
* @typedef {import('./schema.js').Normal} Normal
|
||
*/
|
||
|
||
|
||
/**
|
||
* @param {Schema[]} definitions
|
||
* @param {string} [space]
|
||
* @returns {Schema}
|
||
*/
|
||
function merge(definitions, space) {
|
||
/** @type {Properties} */
|
||
const property = {};
|
||
/** @type {Normal} */
|
||
const normal = {};
|
||
let index = -1;
|
||
|
||
while (++index < definitions.length) {
|
||
Object.assign(property, definitions[index].property);
|
||
Object.assign(normal, definitions[index].normal);
|
||
}
|
||
|
||
return new Schema(property, normal, space)
|
||
}
|
||
|
||
/**
|
||
* @param {string} value
|
||
* @returns {string}
|
||
*/
|
||
function normalize(value) {
|
||
return value.toLowerCase()
|
||
}
|
||
|
||
class Info {
|
||
/**
|
||
* @constructor
|
||
* @param {string} property
|
||
* @param {string} attribute
|
||
*/
|
||
constructor(property, attribute) {
|
||
/** @type {string} */
|
||
this.property = property;
|
||
/** @type {string} */
|
||
this.attribute = attribute;
|
||
}
|
||
}
|
||
|
||
/** @type {string|null} */
|
||
Info.prototype.space = null;
|
||
Info.prototype.boolean = false;
|
||
Info.prototype.booleanish = false;
|
||
Info.prototype.overloadedBoolean = false;
|
||
Info.prototype.number = false;
|
||
Info.prototype.commaSeparated = false;
|
||
Info.prototype.spaceSeparated = false;
|
||
Info.prototype.commaOrSpaceSeparated = false;
|
||
Info.prototype.mustUseProperty = false;
|
||
Info.prototype.defined = false;
|
||
|
||
let powers = 0;
|
||
|
||
const boolean = increment();
|
||
const booleanish = increment();
|
||
const overloadedBoolean = increment();
|
||
const number = increment();
|
||
const spaceSeparated = increment();
|
||
const commaSeparated = increment();
|
||
const commaOrSpaceSeparated = increment();
|
||
|
||
function increment() {
|
||
return 2 ** ++powers
|
||
}
|
||
|
||
var types = /*#__PURE__*/Object.freeze({
|
||
__proto__: null,
|
||
boolean: boolean,
|
||
booleanish: booleanish,
|
||
commaOrSpaceSeparated: commaOrSpaceSeparated,
|
||
commaSeparated: commaSeparated,
|
||
number: number,
|
||
overloadedBoolean: overloadedBoolean,
|
||
spaceSeparated: spaceSeparated
|
||
});
|
||
|
||
/** @type {Array<keyof types>} */
|
||
// @ts-expect-error: hush.
|
||
const checks = Object.keys(types);
|
||
|
||
class DefinedInfo extends Info {
|
||
/**
|
||
* @constructor
|
||
* @param {string} property
|
||
* @param {string} attribute
|
||
* @param {number|null} [mask]
|
||
* @param {string} [space]
|
||
*/
|
||
constructor(property, attribute, mask, space) {
|
||
let index = -1;
|
||
|
||
super(property, attribute);
|
||
|
||
mark(this, 'space', space);
|
||
|
||
if (typeof mask === 'number') {
|
||
while (++index < checks.length) {
|
||
const check = checks[index];
|
||
mark(this, checks[index], (mask & types[check]) === types[check]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
DefinedInfo.prototype.defined = true;
|
||
|
||
/**
|
||
* @param {DefinedInfo} values
|
||
* @param {string} key
|
||
* @param {unknown} value
|
||
*/
|
||
function mark(values, key, value) {
|
||
if (value) {
|
||
// @ts-expect-error: assume `value` matches the expected value of `key`.
|
||
values[key] = value;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('./schema.js').Properties} Properties
|
||
* @typedef {import('./schema.js').Normal} Normal
|
||
*
|
||
* @typedef {Record<string, string>} Attributes
|
||
*
|
||
* @typedef {Object} Definition
|
||
* @property {Record<string, number|null>} properties
|
||
* @property {(attributes: Attributes, property: string) => string} transform
|
||
* @property {string} [space]
|
||
* @property {Attributes} [attributes]
|
||
* @property {Array<string>} [mustUseProperty]
|
||
*/
|
||
|
||
|
||
const own$3 = {}.hasOwnProperty;
|
||
|
||
/**
|
||
* @param {Definition} definition
|
||
* @returns {Schema}
|
||
*/
|
||
function create(definition) {
|
||
/** @type {Properties} */
|
||
const property = {};
|
||
/** @type {Normal} */
|
||
const normal = {};
|
||
/** @type {string} */
|
||
let prop;
|
||
|
||
for (prop in definition.properties) {
|
||
if (own$3.call(definition.properties, prop)) {
|
||
const value = definition.properties[prop];
|
||
const info = new DefinedInfo(
|
||
prop,
|
||
definition.transform(definition.attributes || {}, prop),
|
||
value,
|
||
definition.space
|
||
);
|
||
|
||
if (
|
||
definition.mustUseProperty &&
|
||
definition.mustUseProperty.includes(prop)
|
||
) {
|
||
info.mustUseProperty = true;
|
||
}
|
||
|
||
property[prop] = info;
|
||
|
||
normal[normalize(prop)] = prop;
|
||
normal[normalize(info.attribute)] = prop;
|
||
}
|
||
}
|
||
|
||
return new Schema(property, normal, definition.space)
|
||
}
|
||
|
||
const xlink = create({
|
||
space: 'xlink',
|
||
transform(_, prop) {
|
||
return 'xlink:' + prop.slice(5).toLowerCase()
|
||
},
|
||
properties: {
|
||
xLinkActuate: null,
|
||
xLinkArcRole: null,
|
||
xLinkHref: null,
|
||
xLinkRole: null,
|
||
xLinkShow: null,
|
||
xLinkTitle: null,
|
||
xLinkType: null
|
||
}
|
||
});
|
||
|
||
const xml = create({
|
||
space: 'xml',
|
||
transform(_, prop) {
|
||
return 'xml:' + prop.slice(3).toLowerCase()
|
||
},
|
||
properties: {xmlLang: null, xmlBase: null, xmlSpace: null}
|
||
});
|
||
|
||
/**
|
||
* @param {Record<string, string>} attributes
|
||
* @param {string} attribute
|
||
* @returns {string}
|
||
*/
|
||
function caseSensitiveTransform(attributes, attribute) {
|
||
return attribute in attributes ? attributes[attribute] : attribute
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, string>} attributes
|
||
* @param {string} property
|
||
* @returns {string}
|
||
*/
|
||
function caseInsensitiveTransform(attributes, property) {
|
||
return caseSensitiveTransform(attributes, property.toLowerCase())
|
||
}
|
||
|
||
const xmlns = create({
|
||
space: 'xmlns',
|
||
attributes: {xmlnsxlink: 'xmlns:xlink'},
|
||
transform: caseInsensitiveTransform,
|
||
properties: {xmlns: null, xmlnsXLink: null}
|
||
});
|
||
|
||
const aria = create({
|
||
transform(_, prop) {
|
||
return prop === 'role' ? prop : 'aria-' + prop.slice(4).toLowerCase()
|
||
},
|
||
properties: {
|
||
ariaActiveDescendant: null,
|
||
ariaAtomic: booleanish,
|
||
ariaAutoComplete: null,
|
||
ariaBusy: booleanish,
|
||
ariaChecked: booleanish,
|
||
ariaColCount: number,
|
||
ariaColIndex: number,
|
||
ariaColSpan: number,
|
||
ariaControls: spaceSeparated,
|
||
ariaCurrent: null,
|
||
ariaDescribedBy: spaceSeparated,
|
||
ariaDetails: null,
|
||
ariaDisabled: booleanish,
|
||
ariaDropEffect: spaceSeparated,
|
||
ariaErrorMessage: null,
|
||
ariaExpanded: booleanish,
|
||
ariaFlowTo: spaceSeparated,
|
||
ariaGrabbed: booleanish,
|
||
ariaHasPopup: null,
|
||
ariaHidden: booleanish,
|
||
ariaInvalid: null,
|
||
ariaKeyShortcuts: null,
|
||
ariaLabel: null,
|
||
ariaLabelledBy: spaceSeparated,
|
||
ariaLevel: number,
|
||
ariaLive: null,
|
||
ariaModal: booleanish,
|
||
ariaMultiLine: booleanish,
|
||
ariaMultiSelectable: booleanish,
|
||
ariaOrientation: null,
|
||
ariaOwns: spaceSeparated,
|
||
ariaPlaceholder: null,
|
||
ariaPosInSet: number,
|
||
ariaPressed: booleanish,
|
||
ariaReadOnly: booleanish,
|
||
ariaRelevant: null,
|
||
ariaRequired: booleanish,
|
||
ariaRoleDescription: spaceSeparated,
|
||
ariaRowCount: number,
|
||
ariaRowIndex: number,
|
||
ariaRowSpan: number,
|
||
ariaSelected: booleanish,
|
||
ariaSetSize: number,
|
||
ariaSort: null,
|
||
ariaValueMax: number,
|
||
ariaValueMin: number,
|
||
ariaValueNow: number,
|
||
ariaValueText: null,
|
||
role: null
|
||
}
|
||
});
|
||
|
||
const html$3 = create({
|
||
space: 'html',
|
||
attributes: {
|
||
acceptcharset: 'accept-charset',
|
||
classname: 'class',
|
||
htmlfor: 'for',
|
||
httpequiv: 'http-equiv'
|
||
},
|
||
transform: caseInsensitiveTransform,
|
||
mustUseProperty: ['checked', 'multiple', 'muted', 'selected'],
|
||
properties: {
|
||
// Standard Properties.
|
||
abbr: null,
|
||
accept: commaSeparated,
|
||
acceptCharset: spaceSeparated,
|
||
accessKey: spaceSeparated,
|
||
action: null,
|
||
allow: null,
|
||
allowFullScreen: boolean,
|
||
allowPaymentRequest: boolean,
|
||
allowUserMedia: boolean,
|
||
alt: null,
|
||
as: null,
|
||
async: boolean,
|
||
autoCapitalize: null,
|
||
autoComplete: spaceSeparated,
|
||
autoFocus: boolean,
|
||
autoPlay: boolean,
|
||
blocking: spaceSeparated,
|
||
capture: null,
|
||
charSet: null,
|
||
checked: boolean,
|
||
cite: null,
|
||
className: spaceSeparated,
|
||
cols: number,
|
||
colSpan: null,
|
||
content: null,
|
||
contentEditable: booleanish,
|
||
controls: boolean,
|
||
controlsList: spaceSeparated,
|
||
coords: number | commaSeparated,
|
||
crossOrigin: null,
|
||
data: null,
|
||
dateTime: null,
|
||
decoding: null,
|
||
default: boolean,
|
||
defer: boolean,
|
||
dir: null,
|
||
dirName: null,
|
||
disabled: boolean,
|
||
download: overloadedBoolean,
|
||
draggable: booleanish,
|
||
encType: null,
|
||
enterKeyHint: null,
|
||
fetchPriority: null,
|
||
form: null,
|
||
formAction: null,
|
||
formEncType: null,
|
||
formMethod: null,
|
||
formNoValidate: boolean,
|
||
formTarget: null,
|
||
headers: spaceSeparated,
|
||
height: number,
|
||
hidden: boolean,
|
||
high: number,
|
||
href: null,
|
||
hrefLang: null,
|
||
htmlFor: spaceSeparated,
|
||
httpEquiv: spaceSeparated,
|
||
id: null,
|
||
imageSizes: null,
|
||
imageSrcSet: null,
|
||
inert: boolean,
|
||
inputMode: null,
|
||
integrity: null,
|
||
is: null,
|
||
isMap: boolean,
|
||
itemId: null,
|
||
itemProp: spaceSeparated,
|
||
itemRef: spaceSeparated,
|
||
itemScope: boolean,
|
||
itemType: spaceSeparated,
|
||
kind: null,
|
||
label: null,
|
||
lang: null,
|
||
language: null,
|
||
list: null,
|
||
loading: null,
|
||
loop: boolean,
|
||
low: number,
|
||
manifest: null,
|
||
max: null,
|
||
maxLength: number,
|
||
media: null,
|
||
method: null,
|
||
min: null,
|
||
minLength: number,
|
||
multiple: boolean,
|
||
muted: boolean,
|
||
name: null,
|
||
nonce: null,
|
||
noModule: boolean,
|
||
noValidate: boolean,
|
||
onAbort: null,
|
||
onAfterPrint: null,
|
||
onAuxClick: null,
|
||
onBeforeMatch: null,
|
||
onBeforePrint: null,
|
||
onBeforeToggle: null,
|
||
onBeforeUnload: null,
|
||
onBlur: null,
|
||
onCancel: null,
|
||
onCanPlay: null,
|
||
onCanPlayThrough: null,
|
||
onChange: null,
|
||
onClick: null,
|
||
onClose: null,
|
||
onContextLost: null,
|
||
onContextMenu: null,
|
||
onContextRestored: null,
|
||
onCopy: null,
|
||
onCueChange: null,
|
||
onCut: null,
|
||
onDblClick: null,
|
||
onDrag: null,
|
||
onDragEnd: null,
|
||
onDragEnter: null,
|
||
onDragExit: null,
|
||
onDragLeave: null,
|
||
onDragOver: null,
|
||
onDragStart: null,
|
||
onDrop: null,
|
||
onDurationChange: null,
|
||
onEmptied: null,
|
||
onEnded: null,
|
||
onError: null,
|
||
onFocus: null,
|
||
onFormData: null,
|
||
onHashChange: null,
|
||
onInput: null,
|
||
onInvalid: null,
|
||
onKeyDown: null,
|
||
onKeyPress: null,
|
||
onKeyUp: null,
|
||
onLanguageChange: null,
|
||
onLoad: null,
|
||
onLoadedData: null,
|
||
onLoadedMetadata: null,
|
||
onLoadEnd: null,
|
||
onLoadStart: null,
|
||
onMessage: null,
|
||
onMessageError: null,
|
||
onMouseDown: null,
|
||
onMouseEnter: null,
|
||
onMouseLeave: null,
|
||
onMouseMove: null,
|
||
onMouseOut: null,
|
||
onMouseOver: null,
|
||
onMouseUp: null,
|
||
onOffline: null,
|
||
onOnline: null,
|
||
onPageHide: null,
|
||
onPageShow: null,
|
||
onPaste: null,
|
||
onPause: null,
|
||
onPlay: null,
|
||
onPlaying: null,
|
||
onPopState: null,
|
||
onProgress: null,
|
||
onRateChange: null,
|
||
onRejectionHandled: null,
|
||
onReset: null,
|
||
onResize: null,
|
||
onScroll: null,
|
||
onScrollEnd: null,
|
||
onSecurityPolicyViolation: null,
|
||
onSeeked: null,
|
||
onSeeking: null,
|
||
onSelect: null,
|
||
onSlotChange: null,
|
||
onStalled: null,
|
||
onStorage: null,
|
||
onSubmit: null,
|
||
onSuspend: null,
|
||
onTimeUpdate: null,
|
||
onToggle: null,
|
||
onUnhandledRejection: null,
|
||
onUnload: null,
|
||
onVolumeChange: null,
|
||
onWaiting: null,
|
||
onWheel: null,
|
||
open: boolean,
|
||
optimum: number,
|
||
pattern: null,
|
||
ping: spaceSeparated,
|
||
placeholder: null,
|
||
playsInline: boolean,
|
||
popover: null,
|
||
popoverTarget: null,
|
||
popoverTargetAction: null,
|
||
poster: null,
|
||
preload: null,
|
||
readOnly: boolean,
|
||
referrerPolicy: null,
|
||
rel: spaceSeparated,
|
||
required: boolean,
|
||
reversed: boolean,
|
||
rows: number,
|
||
rowSpan: number,
|
||
sandbox: spaceSeparated,
|
||
scope: null,
|
||
scoped: boolean,
|
||
seamless: boolean,
|
||
selected: boolean,
|
||
shadowRootDelegatesFocus: boolean,
|
||
shadowRootMode: null,
|
||
shape: null,
|
||
size: number,
|
||
sizes: null,
|
||
slot: null,
|
||
span: number,
|
||
spellCheck: booleanish,
|
||
src: null,
|
||
srcDoc: null,
|
||
srcLang: null,
|
||
srcSet: null,
|
||
start: number,
|
||
step: null,
|
||
style: null,
|
||
tabIndex: number,
|
||
target: null,
|
||
title: null,
|
||
translate: null,
|
||
type: null,
|
||
typeMustMatch: boolean,
|
||
useMap: null,
|
||
value: booleanish,
|
||
width: number,
|
||
wrap: null,
|
||
|
||
// Legacy.
|
||
// See: https://html.spec.whatwg.org/#other-elements,-attributes-and-apis
|
||
align: null, // Several. Use CSS `text-align` instead,
|
||
aLink: null, // `<body>`. Use CSS `a:active {color}` instead
|
||
archive: spaceSeparated, // `<object>`. List of URIs to archives
|
||
axis: null, // `<td>` and `<th>`. Use `scope` on `<th>`
|
||
background: null, // `<body>`. Use CSS `background-image` instead
|
||
bgColor: null, // `<body>` and table elements. Use CSS `background-color` instead
|
||
border: number, // `<table>`. Use CSS `border-width` instead,
|
||
borderColor: null, // `<table>`. Use CSS `border-color` instead,
|
||
bottomMargin: number, // `<body>`
|
||
cellPadding: null, // `<table>`
|
||
cellSpacing: null, // `<table>`
|
||
char: null, // Several table elements. When `align=char`, sets the character to align on
|
||
charOff: null, // Several table elements. When `char`, offsets the alignment
|
||
classId: null, // `<object>`
|
||
clear: null, // `<br>`. Use CSS `clear` instead
|
||
code: null, // `<object>`
|
||
codeBase: null, // `<object>`
|
||
codeType: null, // `<object>`
|
||
color: null, // `<font>` and `<hr>`. Use CSS instead
|
||
compact: boolean, // Lists. Use CSS to reduce space between items instead
|
||
declare: boolean, // `<object>`
|
||
event: null, // `<script>`
|
||
face: null, // `<font>`. Use CSS instead
|
||
frame: null, // `<table>`
|
||
frameBorder: null, // `<iframe>`. Use CSS `border` instead
|
||
hSpace: number, // `<img>` and `<object>`
|
||
leftMargin: number, // `<body>`
|
||
link: null, // `<body>`. Use CSS `a:link {color: *}` instead
|
||
longDesc: null, // `<frame>`, `<iframe>`, and `<img>`. Use an `<a>`
|
||
lowSrc: null, // `<img>`. Use a `<picture>`
|
||
marginHeight: number, // `<body>`
|
||
marginWidth: number, // `<body>`
|
||
noResize: boolean, // `<frame>`
|
||
noHref: boolean, // `<area>`. Use no href instead of an explicit `nohref`
|
||
noShade: boolean, // `<hr>`. Use background-color and height instead of borders
|
||
noWrap: boolean, // `<td>` and `<th>`
|
||
object: null, // `<applet>`
|
||
profile: null, // `<head>`
|
||
prompt: null, // `<isindex>`
|
||
rev: null, // `<link>`
|
||
rightMargin: number, // `<body>`
|
||
rules: null, // `<table>`
|
||
scheme: null, // `<meta>`
|
||
scrolling: booleanish, // `<frame>`. Use overflow in the child context
|
||
standby: null, // `<object>`
|
||
summary: null, // `<table>`
|
||
text: null, // `<body>`. Use CSS `color` instead
|
||
topMargin: number, // `<body>`
|
||
valueType: null, // `<param>`
|
||
version: null, // `<html>`. Use a doctype.
|
||
vAlign: null, // Several. Use CSS `vertical-align` instead
|
||
vLink: null, // `<body>`. Use CSS `a:visited {color}` instead
|
||
vSpace: number, // `<img>` and `<object>`
|
||
|
||
// Non-standard Properties.
|
||
allowTransparency: null,
|
||
autoCorrect: null,
|
||
autoSave: null,
|
||
disablePictureInPicture: boolean,
|
||
disableRemotePlayback: boolean,
|
||
prefix: null,
|
||
property: null,
|
||
results: number,
|
||
security: null,
|
||
unselectable: null
|
||
}
|
||
});
|
||
|
||
const svg$1 = create({
|
||
space: 'svg',
|
||
attributes: {
|
||
accentHeight: 'accent-height',
|
||
alignmentBaseline: 'alignment-baseline',
|
||
arabicForm: 'arabic-form',
|
||
baselineShift: 'baseline-shift',
|
||
capHeight: 'cap-height',
|
||
className: 'class',
|
||
clipPath: 'clip-path',
|
||
clipRule: 'clip-rule',
|
||
colorInterpolation: 'color-interpolation',
|
||
colorInterpolationFilters: 'color-interpolation-filters',
|
||
colorProfile: 'color-profile',
|
||
colorRendering: 'color-rendering',
|
||
crossOrigin: 'crossorigin',
|
||
dataType: 'datatype',
|
||
dominantBaseline: 'dominant-baseline',
|
||
enableBackground: 'enable-background',
|
||
fillOpacity: 'fill-opacity',
|
||
fillRule: 'fill-rule',
|
||
floodColor: 'flood-color',
|
||
floodOpacity: 'flood-opacity',
|
||
fontFamily: 'font-family',
|
||
fontSize: 'font-size',
|
||
fontSizeAdjust: 'font-size-adjust',
|
||
fontStretch: 'font-stretch',
|
||
fontStyle: 'font-style',
|
||
fontVariant: 'font-variant',
|
||
fontWeight: 'font-weight',
|
||
glyphName: 'glyph-name',
|
||
glyphOrientationHorizontal: 'glyph-orientation-horizontal',
|
||
glyphOrientationVertical: 'glyph-orientation-vertical',
|
||
hrefLang: 'hreflang',
|
||
horizAdvX: 'horiz-adv-x',
|
||
horizOriginX: 'horiz-origin-x',
|
||
horizOriginY: 'horiz-origin-y',
|
||
imageRendering: 'image-rendering',
|
||
letterSpacing: 'letter-spacing',
|
||
lightingColor: 'lighting-color',
|
||
markerEnd: 'marker-end',
|
||
markerMid: 'marker-mid',
|
||
markerStart: 'marker-start',
|
||
navDown: 'nav-down',
|
||
navDownLeft: 'nav-down-left',
|
||
navDownRight: 'nav-down-right',
|
||
navLeft: 'nav-left',
|
||
navNext: 'nav-next',
|
||
navPrev: 'nav-prev',
|
||
navRight: 'nav-right',
|
||
navUp: 'nav-up',
|
||
navUpLeft: 'nav-up-left',
|
||
navUpRight: 'nav-up-right',
|
||
onAbort: 'onabort',
|
||
onActivate: 'onactivate',
|
||
onAfterPrint: 'onafterprint',
|
||
onBeforePrint: 'onbeforeprint',
|
||
onBegin: 'onbegin',
|
||
onCancel: 'oncancel',
|
||
onCanPlay: 'oncanplay',
|
||
onCanPlayThrough: 'oncanplaythrough',
|
||
onChange: 'onchange',
|
||
onClick: 'onclick',
|
||
onClose: 'onclose',
|
||
onCopy: 'oncopy',
|
||
onCueChange: 'oncuechange',
|
||
onCut: 'oncut',
|
||
onDblClick: 'ondblclick',
|
||
onDrag: 'ondrag',
|
||
onDragEnd: 'ondragend',
|
||
onDragEnter: 'ondragenter',
|
||
onDragExit: 'ondragexit',
|
||
onDragLeave: 'ondragleave',
|
||
onDragOver: 'ondragover',
|
||
onDragStart: 'ondragstart',
|
||
onDrop: 'ondrop',
|
||
onDurationChange: 'ondurationchange',
|
||
onEmptied: 'onemptied',
|
||
onEnd: 'onend',
|
||
onEnded: 'onended',
|
||
onError: 'onerror',
|
||
onFocus: 'onfocus',
|
||
onFocusIn: 'onfocusin',
|
||
onFocusOut: 'onfocusout',
|
||
onHashChange: 'onhashchange',
|
||
onInput: 'oninput',
|
||
onInvalid: 'oninvalid',
|
||
onKeyDown: 'onkeydown',
|
||
onKeyPress: 'onkeypress',
|
||
onKeyUp: 'onkeyup',
|
||
onLoad: 'onload',
|
||
onLoadedData: 'onloadeddata',
|
||
onLoadedMetadata: 'onloadedmetadata',
|
||
onLoadStart: 'onloadstart',
|
||
onMessage: 'onmessage',
|
||
onMouseDown: 'onmousedown',
|
||
onMouseEnter: 'onmouseenter',
|
||
onMouseLeave: 'onmouseleave',
|
||
onMouseMove: 'onmousemove',
|
||
onMouseOut: 'onmouseout',
|
||
onMouseOver: 'onmouseover',
|
||
onMouseUp: 'onmouseup',
|
||
onMouseWheel: 'onmousewheel',
|
||
onOffline: 'onoffline',
|
||
onOnline: 'ononline',
|
||
onPageHide: 'onpagehide',
|
||
onPageShow: 'onpageshow',
|
||
onPaste: 'onpaste',
|
||
onPause: 'onpause',
|
||
onPlay: 'onplay',
|
||
onPlaying: 'onplaying',
|
||
onPopState: 'onpopstate',
|
||
onProgress: 'onprogress',
|
||
onRateChange: 'onratechange',
|
||
onRepeat: 'onrepeat',
|
||
onReset: 'onreset',
|
||
onResize: 'onresize',
|
||
onScroll: 'onscroll',
|
||
onSeeked: 'onseeked',
|
||
onSeeking: 'onseeking',
|
||
onSelect: 'onselect',
|
||
onShow: 'onshow',
|
||
onStalled: 'onstalled',
|
||
onStorage: 'onstorage',
|
||
onSubmit: 'onsubmit',
|
||
onSuspend: 'onsuspend',
|
||
onTimeUpdate: 'ontimeupdate',
|
||
onToggle: 'ontoggle',
|
||
onUnload: 'onunload',
|
||
onVolumeChange: 'onvolumechange',
|
||
onWaiting: 'onwaiting',
|
||
onZoom: 'onzoom',
|
||
overlinePosition: 'overline-position',
|
||
overlineThickness: 'overline-thickness',
|
||
paintOrder: 'paint-order',
|
||
panose1: 'panose-1',
|
||
pointerEvents: 'pointer-events',
|
||
referrerPolicy: 'referrerpolicy',
|
||
renderingIntent: 'rendering-intent',
|
||
shapeRendering: 'shape-rendering',
|
||
stopColor: 'stop-color',
|
||
stopOpacity: 'stop-opacity',
|
||
strikethroughPosition: 'strikethrough-position',
|
||
strikethroughThickness: 'strikethrough-thickness',
|
||
strokeDashArray: 'stroke-dasharray',
|
||
strokeDashOffset: 'stroke-dashoffset',
|
||
strokeLineCap: 'stroke-linecap',
|
||
strokeLineJoin: 'stroke-linejoin',
|
||
strokeMiterLimit: 'stroke-miterlimit',
|
||
strokeOpacity: 'stroke-opacity',
|
||
strokeWidth: 'stroke-width',
|
||
tabIndex: 'tabindex',
|
||
textAnchor: 'text-anchor',
|
||
textDecoration: 'text-decoration',
|
||
textRendering: 'text-rendering',
|
||
transformOrigin: 'transform-origin',
|
||
typeOf: 'typeof',
|
||
underlinePosition: 'underline-position',
|
||
underlineThickness: 'underline-thickness',
|
||
unicodeBidi: 'unicode-bidi',
|
||
unicodeRange: 'unicode-range',
|
||
unitsPerEm: 'units-per-em',
|
||
vAlphabetic: 'v-alphabetic',
|
||
vHanging: 'v-hanging',
|
||
vIdeographic: 'v-ideographic',
|
||
vMathematical: 'v-mathematical',
|
||
vectorEffect: 'vector-effect',
|
||
vertAdvY: 'vert-adv-y',
|
||
vertOriginX: 'vert-origin-x',
|
||
vertOriginY: 'vert-origin-y',
|
||
wordSpacing: 'word-spacing',
|
||
writingMode: 'writing-mode',
|
||
xHeight: 'x-height',
|
||
// These were camelcased in Tiny. Now lowercased in SVG 2
|
||
playbackOrder: 'playbackorder',
|
||
timelineBegin: 'timelinebegin'
|
||
},
|
||
transform: caseSensitiveTransform,
|
||
properties: {
|
||
about: commaOrSpaceSeparated,
|
||
accentHeight: number,
|
||
accumulate: null,
|
||
additive: null,
|
||
alignmentBaseline: null,
|
||
alphabetic: number,
|
||
amplitude: number,
|
||
arabicForm: null,
|
||
ascent: number,
|
||
attributeName: null,
|
||
attributeType: null,
|
||
azimuth: number,
|
||
bandwidth: null,
|
||
baselineShift: null,
|
||
baseFrequency: null,
|
||
baseProfile: null,
|
||
bbox: null,
|
||
begin: null,
|
||
bias: number,
|
||
by: null,
|
||
calcMode: null,
|
||
capHeight: number,
|
||
className: spaceSeparated,
|
||
clip: null,
|
||
clipPath: null,
|
||
clipPathUnits: null,
|
||
clipRule: null,
|
||
color: null,
|
||
colorInterpolation: null,
|
||
colorInterpolationFilters: null,
|
||
colorProfile: null,
|
||
colorRendering: null,
|
||
content: null,
|
||
contentScriptType: null,
|
||
contentStyleType: null,
|
||
crossOrigin: null,
|
||
cursor: null,
|
||
cx: null,
|
||
cy: null,
|
||
d: null,
|
||
dataType: null,
|
||
defaultAction: null,
|
||
descent: number,
|
||
diffuseConstant: number,
|
||
direction: null,
|
||
display: null,
|
||
dur: null,
|
||
divisor: number,
|
||
dominantBaseline: null,
|
||
download: boolean,
|
||
dx: null,
|
||
dy: null,
|
||
edgeMode: null,
|
||
editable: null,
|
||
elevation: number,
|
||
enableBackground: null,
|
||
end: null,
|
||
event: null,
|
||
exponent: number,
|
||
externalResourcesRequired: null,
|
||
fill: null,
|
||
fillOpacity: number,
|
||
fillRule: null,
|
||
filter: null,
|
||
filterRes: null,
|
||
filterUnits: null,
|
||
floodColor: null,
|
||
floodOpacity: null,
|
||
focusable: null,
|
||
focusHighlight: null,
|
||
fontFamily: null,
|
||
fontSize: null,
|
||
fontSizeAdjust: null,
|
||
fontStretch: null,
|
||
fontStyle: null,
|
||
fontVariant: null,
|
||
fontWeight: null,
|
||
format: null,
|
||
fr: null,
|
||
from: null,
|
||
fx: null,
|
||
fy: null,
|
||
g1: commaSeparated,
|
||
g2: commaSeparated,
|
||
glyphName: commaSeparated,
|
||
glyphOrientationHorizontal: null,
|
||
glyphOrientationVertical: null,
|
||
glyphRef: null,
|
||
gradientTransform: null,
|
||
gradientUnits: null,
|
||
handler: null,
|
||
hanging: number,
|
||
hatchContentUnits: null,
|
||
hatchUnits: null,
|
||
height: null,
|
||
href: null,
|
||
hrefLang: null,
|
||
horizAdvX: number,
|
||
horizOriginX: number,
|
||
horizOriginY: number,
|
||
id: null,
|
||
ideographic: number,
|
||
imageRendering: null,
|
||
initialVisibility: null,
|
||
in: null,
|
||
in2: null,
|
||
intercept: number,
|
||
k: number,
|
||
k1: number,
|
||
k2: number,
|
||
k3: number,
|
||
k4: number,
|
||
kernelMatrix: commaOrSpaceSeparated,
|
||
kernelUnitLength: null,
|
||
keyPoints: null, // SEMI_COLON_SEPARATED
|
||
keySplines: null, // SEMI_COLON_SEPARATED
|
||
keyTimes: null, // SEMI_COLON_SEPARATED
|
||
kerning: null,
|
||
lang: null,
|
||
lengthAdjust: null,
|
||
letterSpacing: null,
|
||
lightingColor: null,
|
||
limitingConeAngle: number,
|
||
local: null,
|
||
markerEnd: null,
|
||
markerMid: null,
|
||
markerStart: null,
|
||
markerHeight: null,
|
||
markerUnits: null,
|
||
markerWidth: null,
|
||
mask: null,
|
||
maskContentUnits: null,
|
||
maskUnits: null,
|
||
mathematical: null,
|
||
max: null,
|
||
media: null,
|
||
mediaCharacterEncoding: null,
|
||
mediaContentEncodings: null,
|
||
mediaSize: number,
|
||
mediaTime: null,
|
||
method: null,
|
||
min: null,
|
||
mode: null,
|
||
name: null,
|
||
navDown: null,
|
||
navDownLeft: null,
|
||
navDownRight: null,
|
||
navLeft: null,
|
||
navNext: null,
|
||
navPrev: null,
|
||
navRight: null,
|
||
navUp: null,
|
||
navUpLeft: null,
|
||
navUpRight: null,
|
||
numOctaves: null,
|
||
observer: null,
|
||
offset: null,
|
||
onAbort: null,
|
||
onActivate: null,
|
||
onAfterPrint: null,
|
||
onBeforePrint: null,
|
||
onBegin: null,
|
||
onCancel: null,
|
||
onCanPlay: null,
|
||
onCanPlayThrough: null,
|
||
onChange: null,
|
||
onClick: null,
|
||
onClose: null,
|
||
onCopy: null,
|
||
onCueChange: null,
|
||
onCut: null,
|
||
onDblClick: null,
|
||
onDrag: null,
|
||
onDragEnd: null,
|
||
onDragEnter: null,
|
||
onDragExit: null,
|
||
onDragLeave: null,
|
||
onDragOver: null,
|
||
onDragStart: null,
|
||
onDrop: null,
|
||
onDurationChange: null,
|
||
onEmptied: null,
|
||
onEnd: null,
|
||
onEnded: null,
|
||
onError: null,
|
||
onFocus: null,
|
||
onFocusIn: null,
|
||
onFocusOut: null,
|
||
onHashChange: null,
|
||
onInput: null,
|
||
onInvalid: null,
|
||
onKeyDown: null,
|
||
onKeyPress: null,
|
||
onKeyUp: null,
|
||
onLoad: null,
|
||
onLoadedData: null,
|
||
onLoadedMetadata: null,
|
||
onLoadStart: null,
|
||
onMessage: null,
|
||
onMouseDown: null,
|
||
onMouseEnter: null,
|
||
onMouseLeave: null,
|
||
onMouseMove: null,
|
||
onMouseOut: null,
|
||
onMouseOver: null,
|
||
onMouseUp: null,
|
||
onMouseWheel: null,
|
||
onOffline: null,
|
||
onOnline: null,
|
||
onPageHide: null,
|
||
onPageShow: null,
|
||
onPaste: null,
|
||
onPause: null,
|
||
onPlay: null,
|
||
onPlaying: null,
|
||
onPopState: null,
|
||
onProgress: null,
|
||
onRateChange: null,
|
||
onRepeat: null,
|
||
onReset: null,
|
||
onResize: null,
|
||
onScroll: null,
|
||
onSeeked: null,
|
||
onSeeking: null,
|
||
onSelect: null,
|
||
onShow: null,
|
||
onStalled: null,
|
||
onStorage: null,
|
||
onSubmit: null,
|
||
onSuspend: null,
|
||
onTimeUpdate: null,
|
||
onToggle: null,
|
||
onUnload: null,
|
||
onVolumeChange: null,
|
||
onWaiting: null,
|
||
onZoom: null,
|
||
opacity: null,
|
||
operator: null,
|
||
order: null,
|
||
orient: null,
|
||
orientation: null,
|
||
origin: null,
|
||
overflow: null,
|
||
overlay: null,
|
||
overlinePosition: number,
|
||
overlineThickness: number,
|
||
paintOrder: null,
|
||
panose1: null,
|
||
path: null,
|
||
pathLength: number,
|
||
patternContentUnits: null,
|
||
patternTransform: null,
|
||
patternUnits: null,
|
||
phase: null,
|
||
ping: spaceSeparated,
|
||
pitch: null,
|
||
playbackOrder: null,
|
||
pointerEvents: null,
|
||
points: null,
|
||
pointsAtX: number,
|
||
pointsAtY: number,
|
||
pointsAtZ: number,
|
||
preserveAlpha: null,
|
||
preserveAspectRatio: null,
|
||
primitiveUnits: null,
|
||
propagate: null,
|
||
property: commaOrSpaceSeparated,
|
||
r: null,
|
||
radius: null,
|
||
referrerPolicy: null,
|
||
refX: null,
|
||
refY: null,
|
||
rel: commaOrSpaceSeparated,
|
||
rev: commaOrSpaceSeparated,
|
||
renderingIntent: null,
|
||
repeatCount: null,
|
||
repeatDur: null,
|
||
requiredExtensions: commaOrSpaceSeparated,
|
||
requiredFeatures: commaOrSpaceSeparated,
|
||
requiredFonts: commaOrSpaceSeparated,
|
||
requiredFormats: commaOrSpaceSeparated,
|
||
resource: null,
|
||
restart: null,
|
||
result: null,
|
||
rotate: null,
|
||
rx: null,
|
||
ry: null,
|
||
scale: null,
|
||
seed: null,
|
||
shapeRendering: null,
|
||
side: null,
|
||
slope: null,
|
||
snapshotTime: null,
|
||
specularConstant: number,
|
||
specularExponent: number,
|
||
spreadMethod: null,
|
||
spacing: null,
|
||
startOffset: null,
|
||
stdDeviation: null,
|
||
stemh: null,
|
||
stemv: null,
|
||
stitchTiles: null,
|
||
stopColor: null,
|
||
stopOpacity: null,
|
||
strikethroughPosition: number,
|
||
strikethroughThickness: number,
|
||
string: null,
|
||
stroke: null,
|
||
strokeDashArray: commaOrSpaceSeparated,
|
||
strokeDashOffset: null,
|
||
strokeLineCap: null,
|
||
strokeLineJoin: null,
|
||
strokeMiterLimit: number,
|
||
strokeOpacity: number,
|
||
strokeWidth: null,
|
||
style: null,
|
||
surfaceScale: number,
|
||
syncBehavior: null,
|
||
syncBehaviorDefault: null,
|
||
syncMaster: null,
|
||
syncTolerance: null,
|
||
syncToleranceDefault: null,
|
||
systemLanguage: commaOrSpaceSeparated,
|
||
tabIndex: number,
|
||
tableValues: null,
|
||
target: null,
|
||
targetX: number,
|
||
targetY: number,
|
||
textAnchor: null,
|
||
textDecoration: null,
|
||
textRendering: null,
|
||
textLength: null,
|
||
timelineBegin: null,
|
||
title: null,
|
||
transformBehavior: null,
|
||
type: null,
|
||
typeOf: commaOrSpaceSeparated,
|
||
to: null,
|
||
transform: null,
|
||
transformOrigin: null,
|
||
u1: null,
|
||
u2: null,
|
||
underlinePosition: number,
|
||
underlineThickness: number,
|
||
unicode: null,
|
||
unicodeBidi: null,
|
||
unicodeRange: null,
|
||
unitsPerEm: number,
|
||
values: null,
|
||
vAlphabetic: number,
|
||
vMathematical: number,
|
||
vectorEffect: null,
|
||
vHanging: number,
|
||
vIdeographic: number,
|
||
version: null,
|
||
vertAdvY: number,
|
||
vertOriginX: number,
|
||
vertOriginY: number,
|
||
viewBox: null,
|
||
viewTarget: null,
|
||
visibility: null,
|
||
width: null,
|
||
widths: null,
|
||
wordSpacing: null,
|
||
writingMode: null,
|
||
x: null,
|
||
x1: null,
|
||
x2: null,
|
||
xChannelSelector: null,
|
||
xHeight: number,
|
||
y: null,
|
||
y1: null,
|
||
y2: null,
|
||
yChannelSelector: null,
|
||
z: null,
|
||
zoomAndPan: null
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @typedef {import('./util/schema.js').Schema} Schema
|
||
*/
|
||
|
||
|
||
const valid = /^data[-\w.:]+$/i;
|
||
const dash = /-[a-z]/g;
|
||
const cap = /[A-Z]/g;
|
||
|
||
/**
|
||
* @param {Schema} schema
|
||
* @param {string} value
|
||
* @returns {Info}
|
||
*/
|
||
function find(schema, value) {
|
||
const normal = normalize(value);
|
||
let prop = value;
|
||
let Type = Info;
|
||
|
||
if (normal in schema.normal) {
|
||
return schema.property[schema.normal[normal]]
|
||
}
|
||
|
||
if (normal.length > 4 && normal.slice(0, 4) === 'data' && valid.test(value)) {
|
||
// Attribute or property.
|
||
if (value.charAt(4) === '-') {
|
||
// Turn it into a property.
|
||
const rest = value.slice(5).replace(dash, camelcase);
|
||
prop = 'data' + rest.charAt(0).toUpperCase() + rest.slice(1);
|
||
} else {
|
||
// Turn it into an attribute.
|
||
const rest = value.slice(4);
|
||
|
||
if (!dash.test(rest)) {
|
||
let dashes = rest.replace(cap, kebab);
|
||
|
||
if (dashes.charAt(0) !== '-') {
|
||
dashes = '-' + dashes;
|
||
}
|
||
|
||
value = 'data' + dashes;
|
||
}
|
||
}
|
||
|
||
Type = DefinedInfo;
|
||
}
|
||
|
||
return new Type(prop, value)
|
||
}
|
||
|
||
/**
|
||
* @param {string} $0
|
||
* @returns {string}
|
||
*/
|
||
function kebab($0) {
|
||
return '-' + $0.toLowerCase()
|
||
}
|
||
|
||
/**
|
||
* @param {string} $0
|
||
* @returns {string}
|
||
*/
|
||
function camelcase($0) {
|
||
return $0.charAt(1).toUpperCase()
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('./lib/util/info.js').Info} Info
|
||
* @typedef {import('./lib/util/schema.js').Schema} Schema
|
||
*/
|
||
|
||
const html$2 = merge([xml, xlink, xmlns, aria, html$3], 'html');
|
||
const svg = merge([xml, xlink, xmlns, aria, svg$1], 'svg');
|
||
|
||
/**
|
||
* @callback Handler
|
||
* Handle a value, with a certain ID field set to a certain value.
|
||
* The ID field is passed to `zwitch`, and it’s value is this function’s
|
||
* place on the `handlers` record.
|
||
* @param {...any} parameters
|
||
* Arbitrary parameters passed to the zwitch.
|
||
* The first will be an object with a certain ID field set to a certain value.
|
||
* @returns {any}
|
||
* Anything!
|
||
*/
|
||
|
||
/**
|
||
* @callback UnknownHandler
|
||
* Handle values that do have a certain ID field, but it’s set to a value
|
||
* that is not listed in the `handlers` record.
|
||
* @param {unknown} value
|
||
* An object with a certain ID field set to an unknown value.
|
||
* @param {...any} rest
|
||
* Arbitrary parameters passed to the zwitch.
|
||
* @returns {any}
|
||
* Anything!
|
||
*/
|
||
|
||
/**
|
||
* @callback InvalidHandler
|
||
* Handle values that do not have a certain ID field.
|
||
* @param {unknown} value
|
||
* Any unknown value.
|
||
* @param {...any} rest
|
||
* Arbitrary parameters passed to the zwitch.
|
||
* @returns {void|null|undefined|never}
|
||
* This should crash or return nothing.
|
||
*/
|
||
|
||
/**
|
||
* @template {InvalidHandler} [Invalid=InvalidHandler]
|
||
* @template {UnknownHandler} [Unknown=UnknownHandler]
|
||
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
|
||
* @typedef Options
|
||
* Configuration (required).
|
||
* @property {Invalid} [invalid]
|
||
* Handler to use for invalid values.
|
||
* @property {Unknown} [unknown]
|
||
* Handler to use for unknown values.
|
||
* @property {Handlers} [handlers]
|
||
* Handlers to use.
|
||
*/
|
||
|
||
const own$2 = {}.hasOwnProperty;
|
||
|
||
/**
|
||
* Handle values based on a field.
|
||
*
|
||
* @template {InvalidHandler} [Invalid=InvalidHandler]
|
||
* @template {UnknownHandler} [Unknown=UnknownHandler]
|
||
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
|
||
* @param {string} key
|
||
* Field to switch on.
|
||
* @param {Options<Invalid, Unknown, Handlers>} [options]
|
||
* Configuration (required).
|
||
* @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
|
||
*/
|
||
function zwitch(key, options) {
|
||
const settings = options || {};
|
||
|
||
/**
|
||
* Handle one value.
|
||
*
|
||
* Based on the bound `key`, a respective handler will be called.
|
||
* If `value` is not an object, or doesn’t have a `key` property, the special
|
||
* “invalid” handler will be called.
|
||
* If `value` has an unknown `key`, the special “unknown” handler will be
|
||
* called.
|
||
*
|
||
* All arguments, and the context object, are passed through to the handler,
|
||
* and it’s result is returned.
|
||
*
|
||
* @this {unknown}
|
||
* Any context object.
|
||
* @param {unknown} [value]
|
||
* Any value.
|
||
* @param {...unknown} parameters
|
||
* Arbitrary parameters passed to the zwitch.
|
||
* @property {Handler} invalid
|
||
* Handle for values that do not have a certain ID field.
|
||
* @property {Handler} unknown
|
||
* Handle values that do have a certain ID field, but it’s set to a value
|
||
* that is not listed in the `handlers` record.
|
||
* @property {Handlers} handlers
|
||
* Record of handlers.
|
||
* @returns {unknown}
|
||
* Anything.
|
||
*/
|
||
function one(value, ...parameters) {
|
||
/** @type {Handler|undefined} */
|
||
let fn = one.invalid;
|
||
const handlers = one.handlers;
|
||
|
||
if (value && own$2.call(value, key)) {
|
||
// @ts-expect-error Indexable.
|
||
const id = String(value[key]);
|
||
// @ts-expect-error Indexable.
|
||
fn = own$2.call(handlers, id) ? handlers[id] : one.unknown;
|
||
}
|
||
|
||
if (fn) {
|
||
return fn.call(this, value, ...parameters)
|
||
}
|
||
}
|
||
|
||
one.handlers = settings.handlers || {};
|
||
one.invalid = settings.invalid;
|
||
one.unknown = settings.unknown;
|
||
|
||
// @ts-expect-error: matches!
|
||
return one
|
||
}
|
||
|
||
/**
|
||
* @typedef CoreOptions
|
||
* @property {Array<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
|
||
*/
|
||
|
||
/**
|
||
* Encode certain characters in `value`.
|
||
*
|
||
* @param {string} value
|
||
* @param {CoreWithFormatOptions} options
|
||
* @returns {string}
|
||
*/
|
||
function core(value, options) {
|
||
value = value.replace(
|
||
options.subset ? charactersToExpression(options.subset) : /["&'<>`]/g,
|
||
basic
|
||
);
|
||
|
||
if (options.subset || options.escapeOnly) {
|
||
return value
|
||
}
|
||
|
||
return (
|
||
value
|
||
// Surrogate pairs.
|
||
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, surrogate)
|
||
// BMP control characters (C0 except for LF, CR, SP; DEL; and some more
|
||
// non-ASCII ones).
|
||
.replace(
|
||
// 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,
|
||
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
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {Array<string>} subset
|
||
* @returns {RegExp}
|
||
*/
|
||
function charactersToExpression(subset) {
|
||
/** @type {Array<string>} */
|
||
const groups = [];
|
||
let index = -1;
|
||
|
||
while (++index < subset.length) {
|
||
groups.push(subset[index].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'));
|
||
}
|
||
|
||
return new RegExp('(?:' + groups.join('|') + ')', 'g')
|
||
}
|
||
|
||
/**
|
||
* 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 && !/[\dA-Fa-f]/.test(String.fromCharCode(next))
|
||
? value
|
||
: value + ';'
|
||
}
|
||
|
||
/**
|
||
* 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 && !/\d/.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$1 = {}.hasOwnProperty;
|
||
|
||
/**
|
||
* `characterEntitiesHtml4` but inverted.
|
||
*
|
||
* @type {Record<string, string>}
|
||
*/
|
||
const characters = {};
|
||
|
||
/** @type {string} */
|
||
let key;
|
||
|
||
for (key in characterEntitiesHtml4) {
|
||
if (own$1.call(characterEntitiesHtml4, key)) {
|
||
characters[characterEntitiesHtml4[key]] = key;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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$1.call(characters, character)) {
|
||
const name = characters[character];
|
||
const value = '&' + name;
|
||
|
||
if (
|
||
omit &&
|
||
characterEntitiesLegacy.includes(name) &&
|
||
!dangerous.includes(name) &&
|
||
(!attribute ||
|
||
(next &&
|
||
next !== 61 /* `=` */ &&
|
||
/[^\da-z]/i.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))
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Comment} Comment
|
||
* @typedef {import('hast').Parents} Parents
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
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
|
||
})
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Doctype} Doctype
|
||
* @typedef {import('hast').Parents} Parents
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
/**
|
||
* 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 Options
|
||
* Configuration for `stringify`.
|
||
* @property {boolean} [padLeft=true]
|
||
* Whether to pad a space before a token.
|
||
* @property {boolean} [padRight=false]
|
||
* Whether to pad a space after a token.
|
||
*/
|
||
|
||
|
||
/**
|
||
* Serialize an array of strings or numbers to comma-separated tokens.
|
||
*
|
||
* @param {Array<string|number>} values
|
||
* List of tokens.
|
||
* @param {Options} [options]
|
||
* Configuration for `stringify` (optional).
|
||
* @returns {string}
|
||
* Comma-separated tokens.
|
||
*/
|
||
function stringify$1(values, options) {
|
||
const settings = options || {};
|
||
|
||
// Ensure the last empty entry is seen.
|
||
const input = values[values.length - 1] === '' ? [...values, ''] : values;
|
||
|
||
return input
|
||
.join(
|
||
(settings.padRight ? ' ' : '') +
|
||
',' +
|
||
(settings.padLeft === false ? '' : ' ')
|
||
)
|
||
.trim()
|
||
}
|
||
|
||
/**
|
||
* Parse space-separated tokens to an array of strings.
|
||
*
|
||
* @param {string} value
|
||
* Space-separated tokens.
|
||
* @returns {Array<string>}
|
||
* List of tokens.
|
||
*/
|
||
|
||
/**
|
||
* Serialize an array of strings as space separated-tokens.
|
||
*
|
||
* @param {Array<string|number>} values
|
||
* List of tokens.
|
||
* @returns {string}
|
||
* Space-separated tokens.
|
||
*/
|
||
function stringify(values) {
|
||
return values.join(' ').trim()
|
||
}
|
||
|
||
/**
|
||
* @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, '') === ''
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Parents} Parents
|
||
* @typedef {import('hast').RootContent} RootContent
|
||
*/
|
||
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Element} Element
|
||
* @typedef {import('hast').Parents} Parents
|
||
*/
|
||
|
||
/**
|
||
* @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 = {}.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.call(handlers, node.tagName) &&
|
||
handlers[node.tagName](node, index, parent)
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Element} Element
|
||
* @typedef {import('hast').Parents} Parents
|
||
*/
|
||
|
||
|
||
const closing = omission({
|
||
body: body$1,
|
||
caption: headOrColgroupOrCaption,
|
||
colgroup: headOrColgroupOrCaption,
|
||
dd,
|
||
dt,
|
||
head: headOrColgroupOrCaption,
|
||
html: html$1,
|
||
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$1(_, 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'))
|
||
)
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Element} Element
|
||
* @typedef {import('hast').Parents} Parents
|
||
*/
|
||
|
||
|
||
const opening = omission({
|
||
body,
|
||
colgroup,
|
||
head,
|
||
html,
|
||
tbody
|
||
});
|
||
|
||
/**
|
||
* Whether to omit `<html>`.
|
||
*
|
||
* @param {Element} node
|
||
* Element.
|
||
* @returns {boolean}
|
||
* Whether the opening tag can be omitted.
|
||
*/
|
||
function html(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) {
|
||
const children = node.children;
|
||
/** @type {Array<string>} */
|
||
const seen = [];
|
||
let index = -1;
|
||
|
||
while (++index < children.length) {
|
||
const child = children[index];
|
||
if (
|
||
child.type === 'element' &&
|
||
(child.tagName === 'title' || child.tagName === 'base')
|
||
) {
|
||
if (seen.includes(child.tagName)) return false
|
||
seen.push(child.tagName);
|
||
}
|
||
}
|
||
|
||
return children.length > 0
|
||
}
|
||
|
||
/**
|
||
* 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')
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Element} Element
|
||
* @typedef {import('hast').Parents} Parents
|
||
* @typedef {import('hast').Properties} Properties
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
/**
|
||
* 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 = 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$1 : stringify)(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)
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Parents} Parents
|
||
* @typedef {import('hast').Text} Text
|
||
*
|
||
* @typedef {import('mdast-util-to-hast').Raw} Raw
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
// 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(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
|
||
})
|
||
)
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Parents} Parents
|
||
*
|
||
* @typedef {import('mdast-util-to-hast').Raw} Raw
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
/**
|
||
* 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(node, index, parent, state) {
|
||
return state.settings.allowDangerousHtml
|
||
? node.value
|
||
: text(node, index, parent, state)
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Parents} Parents
|
||
* @typedef {import('hast').Root} Root
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
/**
|
||
* 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(node, _1, _2, state) {
|
||
return state.all(node)
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Nodes} Nodes
|
||
* @typedef {import('hast').Parents} Parents
|
||
*
|
||
* @typedef {import('../index.js').State} State
|
||
*/
|
||
|
||
|
||
/**
|
||
* @type {(node: Nodes, index: number | undefined, parent: Parents | undefined, state: State) => string}
|
||
*/
|
||
const handle = zwitch('type', {
|
||
invalid,
|
||
unknown,
|
||
handlers: {comment, doctype, element, raw, root, text}
|
||
});
|
||
|
||
/**
|
||
* 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 + '`')
|
||
}
|
||
|
||
/**
|
||
* @typedef {import('hast').Nodes} Nodes
|
||
* @typedef {import('hast').Parents} Parents
|
||
* @typedef {import('hast').RootContent} RootContent
|
||
*
|
||
* @typedef {import('property-information').Schema} Schema
|
||
*
|
||
* @typedef {import('stringify-entities').Options} StringifyEntitiesOptions
|
||
*/
|
||
|
||
|
||
/** @type {Options} */
|
||
const emptyOptions = {};
|
||
|
||
/** @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;
|
||
const quote = options_.quote || '"';
|
||
const alternative = quote === '"' ? "'" : '"';
|
||
|
||
if (quote !== '"' && quote !== "'") {
|
||
throw new Error('Invalid quote `' + quote + '`, expected `\'` or `"`')
|
||
}
|
||
|
||
/** @type {State} */
|
||
const state = {
|
||
one,
|
||
all,
|
||
settings: {
|
||
omitOptionalTags: options_.omitOptionalTags || false,
|
||
allowParseErrors: options_.allowParseErrors || false,
|
||
allowDangerousCharacters: options_.allowDangerousCharacters || false,
|
||
quoteSmart: options_.quoteSmart || false,
|
||
preferUnquoted: options_.preferUnquoted || false,
|
||
tightAttributes: options_.tightAttributes || false,
|
||
upperDoctype: options_.upperDoctype || false,
|
||
tightDoctype: options_.tightDoctype || false,
|
||
bogusComments: options_.bogusComments || false,
|
||
tightCommaSeparatedLists: options_.tightCommaSeparatedLists || false,
|
||
tightSelfClosing: options_.tightSelfClosing || false,
|
||
collapseEmptyAttributes: options_.collapseEmptyAttributes || false,
|
||
allowDangerousHtml: options_.allowDangerousHtml || false,
|
||
voids: options_.voids || htmlVoidElements,
|
||
characterReferences:
|
||
options_.characterReferences || emptyCharacterReferences,
|
||
closeSelfClosing: options_.closeSelfClosing || false,
|
||
closeEmptyElements: options_.closeEmptyElements || false
|
||
},
|
||
schema: options_.space === 'svg' ? svg : html$2,
|
||
quote,
|
||
alternative
|
||
};
|
||
|
||
return state.one(
|
||
Array.isArray(tree) ? {type: 'root', children: tree} : tree,
|
||
undefined,
|
||
undefined
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Serialize a node.
|
||
*
|
||
* @this {State}
|
||
* Info passed around about the current state.
|
||
* @param {Nodes} node
|
||
* Node to handle.
|
||
* @param {number | undefined} index
|
||
* Index of `node` in `parent.
|
||
* @param {Parents | undefined} parent
|
||
* Parent of `node`.
|
||
* @returns {string}
|
||
* Serialized node.
|
||
*/
|
||
function one(node, index, parent) {
|
||
return handle(node, index, parent, this)
|
||
}
|
||
|
||
/**
|
||
* Serialize all children of `parent`.
|
||
*
|
||
* @this {State}
|
||
* Info passed around about the current state.
|
||
* @param {Parents | undefined} parent
|
||
* Parent whose children to serialize.
|
||
* @returns {string}
|
||
*/
|
||
function all(parent) {
|
||
/** @type {Array<string>} */
|
||
const results = [];
|
||
const children = (parent && parent.children) || emptyChildren;
|
||
let index = -1;
|
||
|
||
while (++index < children.length) {
|
||
results[index] = this.one(children[index], index, parent);
|
||
}
|
||
|
||
return results.join('')
|
||
}
|
||
|
||
/**
|
||
* Get highlighted code in HTML.
|
||
*/
|
||
function codeToHtml(internal, code, options) {
|
||
const context = {
|
||
meta: {},
|
||
options,
|
||
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
|
||
codeToTokens: (_code, _options) => codeToTokens(internal, _code, _options),
|
||
};
|
||
let result = toHtml(codeToHast(internal, code, options, context));
|
||
for (const transformer of getTransformers(options))
|
||
result = transformer.postprocess?.call(context, result, options) || result;
|
||
return result;
|
||
}
|
||
|
||
async function main(init) {
|
||
let wasmMemory;
|
||
let buffer;
|
||
const binding = {};
|
||
function updateGlobalBufferAndViews(buf) {
|
||
buffer = buf;
|
||
binding.HEAPU8 = new Uint8Array(buf);
|
||
binding.HEAPU32 = new Uint32Array(buf);
|
||
}
|
||
function _emscripten_get_now() {
|
||
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
||
}
|
||
function _emscripten_memcpy_big(dest, src, num) {
|
||
binding.HEAPU8.copyWithin(dest, src, src + num);
|
||
}
|
||
function getHeapMax() {
|
||
return 2147483648;
|
||
}
|
||
function emscripten_realloc_buffer(size) {
|
||
try {
|
||
wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
|
||
updateGlobalBufferAndViews(wasmMemory.buffer);
|
||
return 1;
|
||
}
|
||
catch (e) { }
|
||
}
|
||
function _emscripten_resize_heap(requestedSize) {
|
||
const oldSize = binding.HEAPU8.length;
|
||
requestedSize = requestedSize >>> 0;
|
||
const maxHeapSize = getHeapMax();
|
||
if (requestedSize > maxHeapSize)
|
||
return false;
|
||
const alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);
|
||
for (let cutDown = 1; cutDown <= 4; cutDown *= 2) {
|
||
let overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
|
||
overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
|
||
const newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536));
|
||
const replacement = emscripten_realloc_buffer(newSize);
|
||
if (replacement)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
const UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined;
|
||
function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead = 1024) {
|
||
const endIdx = idx + maxBytesToRead;
|
||
let endPtr = idx;
|
||
while (heapOrArray[endPtr] && !(endPtr >= endIdx))
|
||
++endPtr;
|
||
if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
|
||
return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
|
||
}
|
||
let str = '';
|
||
while (idx < endPtr) {
|
||
let u0 = heapOrArray[idx++];
|
||
if (!(u0 & 128)) {
|
||
str += String.fromCharCode(u0);
|
||
continue;
|
||
}
|
||
const u1 = heapOrArray[idx++] & 63;
|
||
if ((u0 & 224) === 192) {
|
||
str += String.fromCharCode(((u0 & 31) << 6) | u1);
|
||
continue;
|
||
}
|
||
const u2 = heapOrArray[idx++] & 63;
|
||
if ((u0 & 240) === 224) {
|
||
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
|
||
}
|
||
else {
|
||
u0 = ((u0 & 7) << 18)
|
||
| (u1 << 12)
|
||
| (u2 << 6)
|
||
| (heapOrArray[idx++] & 63);
|
||
}
|
||
if (u0 < 65536) {
|
||
str += String.fromCharCode(u0);
|
||
}
|
||
else {
|
||
const ch = u0 - 65536;
|
||
str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
|
||
}
|
||
}
|
||
return str;
|
||
}
|
||
function UTF8ToString(ptr, maxBytesToRead) {
|
||
return ptr ? UTF8ArrayToString(binding.HEAPU8, ptr, maxBytesToRead) : '';
|
||
}
|
||
const asmLibraryArg = {
|
||
emscripten_get_now: _emscripten_get_now,
|
||
emscripten_memcpy_big: _emscripten_memcpy_big,
|
||
emscripten_resize_heap: _emscripten_resize_heap,
|
||
fd_write: () => 0,
|
||
};
|
||
async function createWasm() {
|
||
const info = {
|
||
env: asmLibraryArg,
|
||
wasi_snapshot_preview1: asmLibraryArg,
|
||
};
|
||
const exports = await init(info);
|
||
wasmMemory = exports.memory;
|
||
updateGlobalBufferAndViews(wasmMemory.buffer);
|
||
Object.assign(binding, exports);
|
||
binding.UTF8ToString = UTF8ToString;
|
||
}
|
||
await createWasm();
|
||
return binding;
|
||
}
|
||
|
||
/* ---------------------------------------------------------
|
||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||
*-------------------------------------------------------- */
|
||
let onigBinding = null;
|
||
let defaultDebugCall = false;
|
||
function throwLastOnigError(onigBinding) {
|
||
throw new ShikiError(onigBinding.UTF8ToString(onigBinding.getLastOnigError()));
|
||
}
|
||
class UtfString {
|
||
static _utf8ByteLength(str) {
|
||
let result = 0;
|
||
for (let i = 0, len = str.length; i < len; i++) {
|
||
const charCode = str.charCodeAt(i);
|
||
let codepoint = charCode;
|
||
let wasSurrogatePair = false;
|
||
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
|
||
// Hit a high surrogate, try to look for a matching low surrogate
|
||
if (i + 1 < len) {
|
||
const nextCharCode = str.charCodeAt(i + 1);
|
||
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
|
||
// Found the matching low surrogate
|
||
codepoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
|
||
wasSurrogatePair = true;
|
||
}
|
||
}
|
||
}
|
||
if (codepoint <= 0x7F)
|
||
result += 1;
|
||
else if (codepoint <= 0x7FF)
|
||
result += 2;
|
||
else if (codepoint <= 0xFFFF)
|
||
result += 3;
|
||
else
|
||
result += 4;
|
||
if (wasSurrogatePair)
|
||
i++;
|
||
}
|
||
return result;
|
||
}
|
||
utf16Length;
|
||
utf8Length;
|
||
utf16Value;
|
||
utf8Value;
|
||
utf16OffsetToUtf8;
|
||
utf8OffsetToUtf16;
|
||
constructor(str) {
|
||
const utf16Length = str.length;
|
||
const utf8Length = UtfString._utf8ByteLength(str);
|
||
const computeIndicesMapping = (utf8Length !== utf16Length);
|
||
const utf16OffsetToUtf8 = computeIndicesMapping ? new Uint32Array(utf16Length + 1) : null;
|
||
if (computeIndicesMapping)
|
||
utf16OffsetToUtf8[utf16Length] = utf8Length;
|
||
const utf8OffsetToUtf16 = computeIndicesMapping ? new Uint32Array(utf8Length + 1) : null;
|
||
if (computeIndicesMapping)
|
||
utf8OffsetToUtf16[utf8Length] = utf16Length;
|
||
const utf8Value = new Uint8Array(utf8Length);
|
||
let i8 = 0;
|
||
for (let i16 = 0; i16 < utf16Length; i16++) {
|
||
const charCode = str.charCodeAt(i16);
|
||
let codePoint = charCode;
|
||
let wasSurrogatePair = false;
|
||
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
|
||
// Hit a high surrogate, try to look for a matching low surrogate
|
||
if (i16 + 1 < utf16Length) {
|
||
const nextCharCode = str.charCodeAt(i16 + 1);
|
||
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
|
||
// Found the matching low surrogate
|
||
codePoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
|
||
wasSurrogatePair = true;
|
||
}
|
||
}
|
||
}
|
||
if (computeIndicesMapping) {
|
||
utf16OffsetToUtf8[i16] = i8;
|
||
if (wasSurrogatePair)
|
||
utf16OffsetToUtf8[i16 + 1] = i8;
|
||
if (codePoint <= 0x7F) {
|
||
utf8OffsetToUtf16[i8 + 0] = i16;
|
||
}
|
||
else if (codePoint <= 0x7FF) {
|
||
utf8OffsetToUtf16[i8 + 0] = i16;
|
||
utf8OffsetToUtf16[i8 + 1] = i16;
|
||
}
|
||
else if (codePoint <= 0xFFFF) {
|
||
utf8OffsetToUtf16[i8 + 0] = i16;
|
||
utf8OffsetToUtf16[i8 + 1] = i16;
|
||
utf8OffsetToUtf16[i8 + 2] = i16;
|
||
}
|
||
else {
|
||
utf8OffsetToUtf16[i8 + 0] = i16;
|
||
utf8OffsetToUtf16[i8 + 1] = i16;
|
||
utf8OffsetToUtf16[i8 + 2] = i16;
|
||
utf8OffsetToUtf16[i8 + 3] = i16;
|
||
}
|
||
}
|
||
if (codePoint <= 0x7F) {
|
||
utf8Value[i8++] = codePoint;
|
||
}
|
||
else if (codePoint <= 0x7FF) {
|
||
utf8Value[i8++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
|
||
}
|
||
else if (codePoint <= 0xFFFF) {
|
||
utf8Value[i8++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
|
||
}
|
||
else {
|
||
utf8Value[i8++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
|
||
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
|
||
}
|
||
if (wasSurrogatePair)
|
||
i16++;
|
||
}
|
||
this.utf16Length = utf16Length;
|
||
this.utf8Length = utf8Length;
|
||
this.utf16Value = str;
|
||
this.utf8Value = utf8Value;
|
||
this.utf16OffsetToUtf8 = utf16OffsetToUtf8;
|
||
this.utf8OffsetToUtf16 = utf8OffsetToUtf16;
|
||
}
|
||
createString(onigBinding) {
|
||
const result = onigBinding.omalloc(this.utf8Length);
|
||
onigBinding.HEAPU8.set(this.utf8Value, result);
|
||
return result;
|
||
}
|
||
}
|
||
class OnigString {
|
||
static LAST_ID = 0;
|
||
static _sharedPtr = 0; // a pointer to a string of 10000 bytes
|
||
static _sharedPtrInUse = false;
|
||
id = (++OnigString.LAST_ID);
|
||
_onigBinding;
|
||
content;
|
||
utf16Length;
|
||
utf8Length;
|
||
utf16OffsetToUtf8;
|
||
utf8OffsetToUtf16;
|
||
ptr;
|
||
constructor(str) {
|
||
if (!onigBinding)
|
||
throw new ShikiError('Must invoke loadWasm first.');
|
||
this._onigBinding = onigBinding;
|
||
this.content = str;
|
||
const utfString = new UtfString(str);
|
||
this.utf16Length = utfString.utf16Length;
|
||
this.utf8Length = utfString.utf8Length;
|
||
this.utf16OffsetToUtf8 = utfString.utf16OffsetToUtf8;
|
||
this.utf8OffsetToUtf16 = utfString.utf8OffsetToUtf16;
|
||
if (this.utf8Length < 10000 && !OnigString._sharedPtrInUse) {
|
||
if (!OnigString._sharedPtr)
|
||
OnigString._sharedPtr = onigBinding.omalloc(10000);
|
||
OnigString._sharedPtrInUse = true;
|
||
onigBinding.HEAPU8.set(utfString.utf8Value, OnigString._sharedPtr);
|
||
this.ptr = OnigString._sharedPtr;
|
||
}
|
||
else {
|
||
this.ptr = utfString.createString(onigBinding);
|
||
}
|
||
}
|
||
convertUtf8OffsetToUtf16(utf8Offset) {
|
||
if (this.utf8OffsetToUtf16) {
|
||
if (utf8Offset < 0)
|
||
return 0;
|
||
if (utf8Offset > this.utf8Length)
|
||
return this.utf16Length;
|
||
return this.utf8OffsetToUtf16[utf8Offset];
|
||
}
|
||
return utf8Offset;
|
||
}
|
||
convertUtf16OffsetToUtf8(utf16Offset) {
|
||
if (this.utf16OffsetToUtf8) {
|
||
if (utf16Offset < 0)
|
||
return 0;
|
||
if (utf16Offset > this.utf16Length)
|
||
return this.utf8Length;
|
||
return this.utf16OffsetToUtf8[utf16Offset];
|
||
}
|
||
return utf16Offset;
|
||
}
|
||
dispose() {
|
||
if (this.ptr === OnigString._sharedPtr)
|
||
OnigString._sharedPtrInUse = false;
|
||
else
|
||
this._onigBinding.ofree(this.ptr);
|
||
}
|
||
}
|
||
class OnigScanner {
|
||
_onigBinding;
|
||
_ptr;
|
||
constructor(patterns) {
|
||
if (!onigBinding)
|
||
throw new ShikiError('Must invoke loadWasm first.');
|
||
const strPtrsArr = [];
|
||
const strLenArr = [];
|
||
for (let i = 0, len = patterns.length; i < len; i++) {
|
||
const utfString = new UtfString(patterns[i]);
|
||
strPtrsArr[i] = utfString.createString(onigBinding);
|
||
strLenArr[i] = utfString.utf8Length;
|
||
}
|
||
const strPtrsPtr = onigBinding.omalloc(4 * patterns.length);
|
||
onigBinding.HEAPU32.set(strPtrsArr, strPtrsPtr / 4);
|
||
const strLenPtr = onigBinding.omalloc(4 * patterns.length);
|
||
onigBinding.HEAPU32.set(strLenArr, strLenPtr / 4);
|
||
const scannerPtr = onigBinding.createOnigScanner(strPtrsPtr, strLenPtr, patterns.length);
|
||
for (let i = 0, len = patterns.length; i < len; i++)
|
||
onigBinding.ofree(strPtrsArr[i]);
|
||
onigBinding.ofree(strLenPtr);
|
||
onigBinding.ofree(strPtrsPtr);
|
||
if (scannerPtr === 0)
|
||
throwLastOnigError(onigBinding);
|
||
this._onigBinding = onigBinding;
|
||
this._ptr = scannerPtr;
|
||
}
|
||
dispose() {
|
||
this._onigBinding.freeOnigScanner(this._ptr);
|
||
}
|
||
findNextMatchSync(string, startPosition, arg) {
|
||
let debugCall = defaultDebugCall;
|
||
let options = 0 /* FindOption.None */;
|
||
if (typeof arg === 'number') {
|
||
if (arg & 8 /* FindOption.DebugCall */)
|
||
debugCall = true;
|
||
options = arg;
|
||
}
|
||
else if (typeof arg === 'boolean') {
|
||
debugCall = arg;
|
||
}
|
||
if (typeof string === 'string') {
|
||
string = new OnigString(string);
|
||
const result = this._findNextMatchSync(string, startPosition, debugCall, options);
|
||
string.dispose();
|
||
return result;
|
||
}
|
||
return this._findNextMatchSync(string, startPosition, debugCall, options);
|
||
}
|
||
_findNextMatchSync(string, startPosition, debugCall, options) {
|
||
const onigBinding = this._onigBinding;
|
||
let resultPtr;
|
||
if (debugCall)
|
||
resultPtr = onigBinding.findNextOnigScannerMatchDbg(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
|
||
else
|
||
resultPtr = onigBinding.findNextOnigScannerMatch(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
|
||
if (resultPtr === 0) {
|
||
// no match
|
||
return null;
|
||
}
|
||
const HEAPU32 = onigBinding.HEAPU32;
|
||
let offset = resultPtr / 4; // byte offset -> uint32 offset
|
||
const index = HEAPU32[offset++];
|
||
const count = HEAPU32[offset++];
|
||
const captureIndices = [];
|
||
for (let i = 0; i < count; i++) {
|
||
const beg = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
|
||
const end = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
|
||
captureIndices[i] = {
|
||
start: beg,
|
||
end,
|
||
length: end - beg,
|
||
};
|
||
}
|
||
return {
|
||
index,
|
||
captureIndices,
|
||
};
|
||
}
|
||
}
|
||
function isInstantiatorOptionsObject(dataOrOptions) {
|
||
return (typeof dataOrOptions.instantiator === 'function');
|
||
}
|
||
function isInstantiatorModule(dataOrOptions) {
|
||
return (typeof dataOrOptions.default === 'function');
|
||
}
|
||
function isDataOptionsObject(dataOrOptions) {
|
||
return (typeof dataOrOptions.data !== 'undefined');
|
||
}
|
||
function isResponse(dataOrOptions) {
|
||
return (typeof Response !== 'undefined' && dataOrOptions instanceof Response);
|
||
}
|
||
function isArrayBuffer(data) {
|
||
return (typeof ArrayBuffer !== 'undefined' && (data instanceof ArrayBuffer || ArrayBuffer.isView(data)))
|
||
// eslint-disable-next-line node/prefer-global/buffer
|
||
|| (typeof Buffer !== 'undefined' && Buffer.isBuffer?.(data))
|
||
|| (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
|
||
|| (typeof Uint32Array !== 'undefined' && data instanceof Uint32Array);
|
||
}
|
||
let initPromise;
|
||
function loadWasm(options) {
|
||
if (initPromise)
|
||
return initPromise;
|
||
async function _load() {
|
||
onigBinding = await main(async (info) => {
|
||
let instance = options;
|
||
instance = await instance;
|
||
if (typeof instance === 'function')
|
||
instance = await instance(info);
|
||
if (typeof instance === 'function')
|
||
instance = await instance(info);
|
||
if (isInstantiatorOptionsObject(instance)) {
|
||
instance = await instance.instantiator(info);
|
||
}
|
||
else if (isInstantiatorModule(instance)) {
|
||
instance = await instance.default(info);
|
||
}
|
||
else {
|
||
if (isDataOptionsObject(instance))
|
||
instance = instance.data;
|
||
if (isResponse(instance)) {
|
||
if (typeof WebAssembly.instantiateStreaming === 'function')
|
||
instance = await _makeResponseStreamingLoader(instance)(info);
|
||
else
|
||
instance = await _makeResponseNonStreamingLoader(instance)(info);
|
||
}
|
||
else if (isArrayBuffer(instance)) {
|
||
instance = await _makeArrayBufferLoader(instance)(info);
|
||
}
|
||
// import("shiki/onig.wasm") returns `{ default: WebAssembly.Module }` on cloudflare workers
|
||
// https://developers.cloudflare.com/workers/wrangler/bundling/
|
||
else if (instance instanceof WebAssembly.Module) {
|
||
instance = await _makeArrayBufferLoader(instance)(info);
|
||
}
|
||
else if ('default' in instance && instance.default instanceof WebAssembly.Module) {
|
||
instance = await _makeArrayBufferLoader(instance.default)(info);
|
||
}
|
||
}
|
||
if ('instance' in instance)
|
||
instance = instance.instance;
|
||
if ('exports' in instance)
|
||
instance = instance.exports;
|
||
return instance;
|
||
});
|
||
}
|
||
initPromise = _load();
|
||
return initPromise;
|
||
}
|
||
function _makeArrayBufferLoader(data) {
|
||
return importObject => WebAssembly.instantiate(data, importObject);
|
||
}
|
||
function _makeResponseStreamingLoader(data) {
|
||
return importObject => WebAssembly.instantiateStreaming(data, importObject);
|
||
}
|
||
function _makeResponseNonStreamingLoader(data) {
|
||
return async (importObject) => {
|
||
const arrayBuffer = await data.arrayBuffer();
|
||
return WebAssembly.instantiate(arrayBuffer, importObject);
|
||
};
|
||
}
|
||
function createOnigString(str) {
|
||
return new OnigString(str);
|
||
}
|
||
function createOnigScanner(patterns) {
|
||
return new OnigScanner(patterns);
|
||
}
|
||
|
||
/**
|
||
* https://github.com/microsoft/vscode/blob/f7f05dee53fb33fe023db2e06e30a89d3094488f/src/vs/platform/theme/common/colorRegistry.ts#L258-L268
|
||
*/
|
||
const VSCODE_FALLBACK_EDITOR_FG = { light: '#333333', dark: '#bbbbbb' };
|
||
const VSCODE_FALLBACK_EDITOR_BG = { light: '#fffffe', dark: '#1e1e1e' };
|
||
const RESOLVED_KEY = '__shiki_resolved';
|
||
/**
|
||
* Normalize a textmate theme to shiki theme
|
||
*/
|
||
function normalizeTheme(rawTheme) {
|
||
// @ts-expect-error private field
|
||
if (rawTheme?.[RESOLVED_KEY])
|
||
return rawTheme;
|
||
const theme = {
|
||
...rawTheme,
|
||
};
|
||
// Fallback settings
|
||
if (theme.tokenColors && !theme.settings) {
|
||
theme.settings = theme.tokenColors;
|
||
delete theme.tokenColors;
|
||
}
|
||
theme.type ||= 'dark';
|
||
theme.colorReplacements = { ...theme.colorReplacements };
|
||
theme.settings ||= [];
|
||
// Guess fg/bg colors
|
||
let { bg, fg } = theme;
|
||
if (!bg || !fg) {
|
||
/**
|
||
* First try:
|
||
* Theme might contain a global `tokenColor` without `name` or `scope`
|
||
* Used as default value for foreground/background
|
||
*/
|
||
const globalSetting = theme.settings
|
||
? theme.settings.find((s) => !s.name && !s.scope)
|
||
: undefined;
|
||
if (globalSetting?.settings?.foreground)
|
||
fg = globalSetting.settings.foreground;
|
||
if (globalSetting?.settings?.background)
|
||
bg = globalSetting.settings.background;
|
||
/**
|
||
* Second try:
|
||
* If there's no global `tokenColor` without `name` or `scope`
|
||
* Use `editor.foreground` and `editor.background`
|
||
*/
|
||
if (!fg && theme?.colors?.['editor.foreground'])
|
||
fg = theme.colors['editor.foreground'];
|
||
if (!bg && theme?.colors?.['editor.background'])
|
||
bg = theme.colors['editor.background'];
|
||
/**
|
||
* Last try:
|
||
* If there's no fg/bg color specified in theme, use default
|
||
*/
|
||
if (!fg)
|
||
fg = theme.type === 'light' ? VSCODE_FALLBACK_EDITOR_FG.light : VSCODE_FALLBACK_EDITOR_FG.dark;
|
||
if (!bg)
|
||
bg = theme.type === 'light' ? VSCODE_FALLBACK_EDITOR_BG.light : VSCODE_FALLBACK_EDITOR_BG.dark;
|
||
theme.fg = fg;
|
||
theme.bg = bg;
|
||
}
|
||
// Push a no-scope setting with fallback colors
|
||
if (!(theme.settings[0] && theme.settings[0].settings && !theme.settings[0].scope)) {
|
||
theme.settings.unshift({
|
||
settings: {
|
||
foreground: theme.fg,
|
||
background: theme.bg,
|
||
},
|
||
});
|
||
}
|
||
// Push non-hex colors to color replacements, as `vscode-textmate` doesn't support them
|
||
let replacementCount = 0;
|
||
const replacementMap = new Map();
|
||
function getReplacementColor(value) {
|
||
if (replacementMap.has(value))
|
||
return replacementMap.get(value);
|
||
replacementCount += 1;
|
||
const hex = `#${replacementCount.toString(16).padStart(8, '0').toLowerCase()}`;
|
||
if (theme.colorReplacements?.[`#${hex}`]) // already exists
|
||
return getReplacementColor(value);
|
||
replacementMap.set(value, hex);
|
||
return hex;
|
||
}
|
||
theme.settings = theme.settings.map((setting) => {
|
||
const replaceFg = setting.settings?.foreground && !setting.settings.foreground.startsWith('#');
|
||
const replaceBg = setting.settings?.background && !setting.settings.background.startsWith('#');
|
||
if (!replaceFg && !replaceBg)
|
||
return setting;
|
||
const clone = {
|
||
...setting,
|
||
settings: {
|
||
...setting.settings,
|
||
},
|
||
};
|
||
if (replaceFg) {
|
||
const replacement = getReplacementColor(setting.settings.foreground);
|
||
theme.colorReplacements[replacement] = setting.settings.foreground;
|
||
clone.settings.foreground = replacement;
|
||
}
|
||
if (replaceBg) {
|
||
const replacement = getReplacementColor(setting.settings.background);
|
||
theme.colorReplacements[replacement] = setting.settings.background;
|
||
clone.settings.background = replacement;
|
||
}
|
||
return clone;
|
||
});
|
||
for (const key of Object.keys(theme.colors || {})) {
|
||
// Only patch for known keys
|
||
if (key === 'editor.foreground' || key === 'editor.background' || key.startsWith('terminal.ansi')) {
|
||
if (!theme.colors[key]?.startsWith('#')) {
|
||
const replacement = getReplacementColor(theme.colors[key]);
|
||
theme.colorReplacements[replacement] = theme.colors[key];
|
||
theme.colors[key] = replacement;
|
||
}
|
||
}
|
||
}
|
||
Object.defineProperty(theme, RESOLVED_KEY, {
|
||
enumerable: false,
|
||
writable: false,
|
||
value: true,
|
||
});
|
||
return theme;
|
||
}
|
||
|
||
class Registry extends Registry$1 {
|
||
_resolver;
|
||
_themes;
|
||
_langs;
|
||
_alias;
|
||
_resolvedThemes = new Map();
|
||
_resolvedGrammars = new Map();
|
||
_langMap = new Map();
|
||
_langGraph = new Map();
|
||
_textmateThemeCache = new WeakMap();
|
||
_loadedThemesCache = null;
|
||
_loadedLanguagesCache = null;
|
||
constructor(_resolver, _themes, _langs, _alias = {}) {
|
||
super(_resolver);
|
||
this._resolver = _resolver;
|
||
this._themes = _themes;
|
||
this._langs = _langs;
|
||
this._alias = _alias;
|
||
_themes.forEach(t => this.loadTheme(t));
|
||
_langs.forEach(l => this.loadLanguage(l));
|
||
}
|
||
getTheme(theme) {
|
||
if (typeof theme === 'string')
|
||
return this._resolvedThemes.get(theme);
|
||
else
|
||
return this.loadTheme(theme);
|
||
}
|
||
loadTheme(theme) {
|
||
const _theme = normalizeTheme(theme);
|
||
if (_theme.name) {
|
||
this._resolvedThemes.set(_theme.name, _theme);
|
||
// Reset cache
|
||
this._loadedThemesCache = null;
|
||
}
|
||
return _theme;
|
||
}
|
||
getLoadedThemes() {
|
||
if (!this._loadedThemesCache)
|
||
this._loadedThemesCache = [...this._resolvedThemes.keys()];
|
||
return this._loadedThemesCache;
|
||
}
|
||
// Override and re-implement this method to cache the textmate themes as `TextMateTheme.createFromRawTheme`
|
||
// is expensive. Themes can switch often especially for dual-theme support.
|
||
//
|
||
// The parent class also accepts `colorMap` as the second parameter, but since we don't use that,
|
||
// we omit here so it's easier to cache the themes.
|
||
setTheme(theme) {
|
||
let textmateTheme = this._textmateThemeCache.get(theme);
|
||
if (!textmateTheme) {
|
||
textmateTheme = Theme.createFromRawTheme(theme);
|
||
this._textmateThemeCache.set(theme, textmateTheme);
|
||
}
|
||
// @ts-expect-error Access private `_syncRegistry`, but should work in runtime
|
||
this._syncRegistry.setTheme(textmateTheme);
|
||
}
|
||
getGrammar(name) {
|
||
if (this._alias[name]) {
|
||
const resolved = new Set([name]);
|
||
while (this._alias[name]) {
|
||
name = this._alias[name];
|
||
if (resolved.has(name))
|
||
throw new ShikiError(`Circular alias \`${Array.from(resolved).join(' -> ')} -> ${name}\``);
|
||
resolved.add(name);
|
||
}
|
||
}
|
||
return this._resolvedGrammars.get(name);
|
||
}
|
||
async loadLanguage(lang) {
|
||
if (this.getGrammar(lang.name))
|
||
return;
|
||
const embeddedLazilyBy = new Set([...this._langMap.values()]
|
||
.filter(i => i.embeddedLangsLazy?.includes(lang.name)));
|
||
this._resolver.addLanguage(lang);
|
||
const grammarConfig = {
|
||
balancedBracketSelectors: lang.balancedBracketSelectors || ['*'],
|
||
unbalancedBracketSelectors: lang.unbalancedBracketSelectors || [],
|
||
};
|
||
// @ts-expect-error Private members, set this to override the previous grammar (that can be a stub)
|
||
this._syncRegistry._rawGrammars.set(lang.scopeName, lang);
|
||
const g = await this.loadGrammarWithConfiguration(lang.scopeName, 1, grammarConfig);
|
||
this._resolvedGrammars.set(lang.name, g);
|
||
if (lang.aliases) {
|
||
lang.aliases.forEach((alias) => {
|
||
this._alias[alias] = lang.name;
|
||
});
|
||
}
|
||
// Reset cache
|
||
this._loadedLanguagesCache = null;
|
||
// If there is a language that embeds this language lazily, we need to reload it
|
||
if (embeddedLazilyBy.size) {
|
||
for (const e of embeddedLazilyBy) {
|
||
this._resolvedGrammars.delete(e.name);
|
||
// Reset cache
|
||
this._loadedLanguagesCache = null;
|
||
// @ts-expect-error clear cache
|
||
this._syncRegistry?._injectionGrammars?.delete(e.scopeName);
|
||
// @ts-expect-error clear cache
|
||
this._syncRegistry?._grammars?.delete(e.scopeName);
|
||
await this.loadLanguage(this._langMap.get(e.name));
|
||
}
|
||
}
|
||
}
|
||
async init() {
|
||
this._themes.map(t => this.loadTheme(t));
|
||
await this.loadLanguages(this._langs);
|
||
}
|
||
dispose() {
|
||
super.dispose();
|
||
this._resolvedThemes.clear();
|
||
this._resolvedGrammars.clear();
|
||
this._langMap.clear();
|
||
this._langGraph.clear();
|
||
this._loadedThemesCache = null;
|
||
}
|
||
async loadLanguages(langs) {
|
||
for (const lang of langs)
|
||
this.resolveEmbeddedLanguages(lang);
|
||
const langsGraphArray = Array.from(this._langGraph.entries());
|
||
const missingLangs = langsGraphArray.filter(([_, lang]) => !lang);
|
||
if (missingLangs.length) {
|
||
const dependents = langsGraphArray
|
||
.filter(([_, lang]) => lang && lang.embeddedLangs?.some(l => missingLangs.map(([name]) => name).includes(l)))
|
||
.filter(lang => !missingLangs.includes(lang));
|
||
throw new ShikiError(`Missing languages ${missingLangs.map(([name]) => `\`${name}\``).join(', ')}, required by ${dependents.map(([name]) => `\`${name}\``).join(', ')}`);
|
||
}
|
||
for (const [_, lang] of langsGraphArray)
|
||
this._resolver.addLanguage(lang);
|
||
for (const [_, lang] of langsGraphArray)
|
||
await this.loadLanguage(lang);
|
||
}
|
||
getLoadedLanguages() {
|
||
if (!this._loadedLanguagesCache) {
|
||
this._loadedLanguagesCache = [
|
||
...new Set([...this._resolvedGrammars.keys(), ...Object.keys(this._alias)]),
|
||
];
|
||
}
|
||
return this._loadedLanguagesCache;
|
||
}
|
||
resolveEmbeddedLanguages(lang) {
|
||
this._langMap.set(lang.name, lang);
|
||
this._langGraph.set(lang.name, lang);
|
||
if (lang.embeddedLangs) {
|
||
for (const embeddedLang of lang.embeddedLangs)
|
||
this._langGraph.set(embeddedLang, this._langMap.get(embeddedLang));
|
||
}
|
||
}
|
||
}
|
||
|
||
class Resolver {
|
||
_langs = new Map();
|
||
_scopeToLang = new Map();
|
||
_injections = new Map();
|
||
_onigLibPromise;
|
||
constructor(onigLibPromise, langs) {
|
||
this._onigLibPromise = onigLibPromise;
|
||
langs.forEach(i => this.addLanguage(i));
|
||
}
|
||
get onigLib() {
|
||
return this._onigLibPromise;
|
||
}
|
||
getLangRegistration(langIdOrAlias) {
|
||
return this._langs.get(langIdOrAlias);
|
||
}
|
||
async loadGrammar(scopeName) {
|
||
return this._scopeToLang.get(scopeName);
|
||
}
|
||
addLanguage(l) {
|
||
this._langs.set(l.name, l);
|
||
if (l.aliases) {
|
||
l.aliases.forEach((a) => {
|
||
this._langs.set(a, l);
|
||
});
|
||
}
|
||
this._scopeToLang.set(l.scopeName, l);
|
||
if (l.injectTo) {
|
||
l.injectTo.forEach((i) => {
|
||
if (!this._injections.get(i))
|
||
this._injections.set(i, []);
|
||
this._injections.get(i).push(l.scopeName);
|
||
});
|
||
}
|
||
}
|
||
getInjections(scopeName) {
|
||
const scopeParts = scopeName.split('.');
|
||
let injections = [];
|
||
for (let i = 1; i <= scopeParts.length; i++) {
|
||
const subScopeName = scopeParts.slice(0, i).join('.');
|
||
injections = [...injections, ...(this._injections.get(subScopeName) || [])];
|
||
}
|
||
return injections;
|
||
}
|
||
}
|
||
|
||
let _defaultWasmLoader;
|
||
/**
|
||
* Set the default wasm loader for `loadWasm`.
|
||
* @internal
|
||
*/
|
||
function setDefaultWasmLoader(_loader) {
|
||
_defaultWasmLoader = _loader;
|
||
}
|
||
let instancesCount = 0;
|
||
/**
|
||
* Get the minimal shiki context for rendering.
|
||
*/
|
||
async function createShikiInternal(options = {}) {
|
||
instancesCount += 1;
|
||
if (options.warnings !== false && instancesCount >= 10 && instancesCount % 10 === 0)
|
||
console.warn(`[Shiki] ${instancesCount} instances have been created. Shiki is supposed to be used as a singleton, consider refactoring your code to cache your highlighter instance; Or call \`highlighter.dispose()\` to release unused instances.`);
|
||
let isDisposed = false;
|
||
async function normalizeGetter(p) {
|
||
return Promise.resolve(typeof p === 'function' ? p() : p).then(r => r.default || r);
|
||
}
|
||
async function resolveLangs(langs) {
|
||
return Array.from(new Set((await Promise.all(langs
|
||
.filter(l => !isSpecialLang(l))
|
||
.map(async (lang) => await normalizeGetter(lang).then(r => Array.isArray(r) ? r : [r])))).flat()));
|
||
}
|
||
const wasmLoader = options.loadWasm || _defaultWasmLoader;
|
||
const [themes, langs,] = await Promise.all([
|
||
Promise.all((options.themes || []).map(normalizeGetter)).then(r => r.map(normalizeTheme)),
|
||
resolveLangs(options.langs || []),
|
||
wasmLoader ? loadWasm(wasmLoader) : undefined,
|
||
]);
|
||
const resolver = new Resolver(Promise.resolve({
|
||
createOnigScanner(patterns) {
|
||
return createOnigScanner(patterns);
|
||
},
|
||
createOnigString(s) {
|
||
return createOnigString(s);
|
||
},
|
||
}), langs);
|
||
const _registry = new Registry(resolver, themes, langs, options.langAlias);
|
||
await _registry.init();
|
||
let _lastTheme;
|
||
function getLanguage(name) {
|
||
ensureNotDisposed();
|
||
const _lang = _registry.getGrammar(typeof name === 'string' ? name : name.name);
|
||
if (!_lang)
|
||
throw new ShikiError(`Language \`${name}\` not found, you may need to load it first`);
|
||
return _lang;
|
||
}
|
||
function getTheme(name) {
|
||
if (name === 'none')
|
||
return { bg: '', fg: '', name: 'none', settings: [], type: 'dark' };
|
||
ensureNotDisposed();
|
||
const _theme = _registry.getTheme(name);
|
||
if (!_theme)
|
||
throw new ShikiError(`Theme \`${name}\` not found, you may need to load it first`);
|
||
return _theme;
|
||
}
|
||
function setTheme(name) {
|
||
ensureNotDisposed();
|
||
const theme = getTheme(name);
|
||
if (_lastTheme !== name) {
|
||
_registry.setTheme(theme);
|
||
_lastTheme = name;
|
||
}
|
||
const colorMap = _registry.getColorMap();
|
||
return {
|
||
theme,
|
||
colorMap,
|
||
};
|
||
}
|
||
function getLoadedThemes() {
|
||
ensureNotDisposed();
|
||
return _registry.getLoadedThemes();
|
||
}
|
||
function getLoadedLanguages() {
|
||
ensureNotDisposed();
|
||
return _registry.getLoadedLanguages();
|
||
}
|
||
async function loadLanguage(...langs) {
|
||
ensureNotDisposed();
|
||
await _registry.loadLanguages(await resolveLangs(langs));
|
||
}
|
||
async function loadTheme(...themes) {
|
||
ensureNotDisposed();
|
||
await Promise.all(themes.map(async (theme) => isSpecialTheme(theme)
|
||
? null
|
||
: _registry.loadTheme(await normalizeGetter(theme))));
|
||
}
|
||
function ensureNotDisposed() {
|
||
if (isDisposed)
|
||
throw new ShikiError('Shiki instance has been disposed');
|
||
}
|
||
function dispose() {
|
||
if (isDisposed)
|
||
return;
|
||
isDisposed = true;
|
||
_registry.dispose();
|
||
instancesCount -= 1;
|
||
}
|
||
return {
|
||
setTheme,
|
||
getTheme,
|
||
getLanguage,
|
||
getLoadedThemes,
|
||
getLoadedLanguages,
|
||
loadLanguage,
|
||
loadTheme,
|
||
dispose,
|
||
[Symbol.dispose]: dispose,
|
||
};
|
||
}
|
||
/**
|
||
* @deprecated Use `createShikiInternal` instead.
|
||
*/
|
||
function getShikiInternal(options = {}) {
|
||
// TODO: next: console.warn('`getShikiInternal` is deprecated. Use `createShikiInternal` instead.')
|
||
return createShikiInternal(options);
|
||
}
|
||
|
||
/**
|
||
* Create a Shiki core highlighter instance, with no languages or themes bundled.
|
||
* Wasm and each language and theme must be loaded manually.
|
||
*
|
||
* @see http://shiki.style/guide/install#fine-grained-bundle
|
||
*/
|
||
async function createHighlighterCore(options = {}) {
|
||
const internal = await createShikiInternal(options);
|
||
return {
|
||
codeToTokensBase: (code, options) => codeToTokensBase(internal, code, options),
|
||
codeToTokensWithThemes: (code, options) => codeToTokensWithThemes(internal, code, options),
|
||
codeToTokens: (code, options) => codeToTokens(internal, code, options),
|
||
codeToHast: (code, options) => codeToHast(internal, code, options),
|
||
codeToHtml: (code, options) => codeToHtml(internal, code, options),
|
||
...internal,
|
||
getInternalContext: () => internal,
|
||
};
|
||
}
|
||
function makeSingletonHighlighterCore(createHighlighter) {
|
||
let _shiki;
|
||
async function getSingletonHighlighterCore(options = {}) {
|
||
if (!_shiki) {
|
||
_shiki = createHighlighter({
|
||
...options,
|
||
themes: options.themes || [],
|
||
langs: options.langs || [],
|
||
});
|
||
return _shiki;
|
||
}
|
||
else {
|
||
const s = await _shiki;
|
||
await Promise.all([
|
||
s.loadTheme(...(options.themes || [])),
|
||
s.loadLanguage(...(options.langs || [])),
|
||
]);
|
||
return s;
|
||
}
|
||
}
|
||
return getSingletonHighlighterCore;
|
||
}
|
||
const getSingletonHighlighterCore = /* @__PURE__ */ makeSingletonHighlighterCore(createHighlighterCore);
|
||
/**
|
||
* @deprecated Use `createHighlighterCore` or `getSingletonHighlighterCore` instead.
|
||
*/
|
||
/* v8 ignore next 5 */
|
||
function getHighlighterCore(options = {}) {
|
||
// TODO: next: console.warn('`getHighlighterCore` is deprecated. Use `createHighlighterCore` or `getSingletonHighlighterCore` instead.')
|
||
return createHighlighterCore(options);
|
||
}
|
||
|
||
/**
|
||
* Create a `createHighlighter` function with bundled themes and languages.
|
||
*
|
||
* @param bundledLanguages
|
||
* @param bundledThemes
|
||
* @param loadWasm
|
||
*/
|
||
function createdBundledHighlighter(bundledLanguages, bundledThemes, loadWasm) {
|
||
async function createHighlighter(options) {
|
||
function resolveLang(lang) {
|
||
if (typeof lang === 'string') {
|
||
if (isSpecialLang(lang))
|
||
return [];
|
||
const bundle = bundledLanguages[lang];
|
||
if (!bundle)
|
||
throw new ShikiError(`Language \`${lang}\` is not included in this bundle. You may want to load it from external source.`);
|
||
return bundle;
|
||
}
|
||
return lang;
|
||
}
|
||
function resolveTheme(theme) {
|
||
if (isSpecialTheme(theme))
|
||
return 'none';
|
||
if (typeof theme === 'string') {
|
||
const bundle = bundledThemes[theme];
|
||
if (!bundle)
|
||
throw new ShikiError(`Theme \`${theme}\` is not included in this bundle. You may want to load it from external source.`);
|
||
return bundle;
|
||
}
|
||
return theme;
|
||
}
|
||
const _themes = (options.themes ?? []).map(i => resolveTheme(i));
|
||
const langs = (options.langs ?? [])
|
||
.map(i => resolveLang(i));
|
||
const core = await createHighlighterCore({
|
||
...options,
|
||
themes: _themes,
|
||
langs,
|
||
loadWasm,
|
||
});
|
||
return {
|
||
...core,
|
||
loadLanguage(...langs) {
|
||
return core.loadLanguage(...langs.map(resolveLang));
|
||
},
|
||
loadTheme(...themes) {
|
||
return core.loadTheme(...themes.map(resolveTheme));
|
||
},
|
||
};
|
||
}
|
||
return createHighlighter;
|
||
}
|
||
function makeSingletonHighlighter(createHighlighter) {
|
||
let _shiki;
|
||
async function getSingletonHighlighter(options = {}) {
|
||
if (!_shiki) {
|
||
_shiki = createHighlighter({
|
||
...options,
|
||
themes: options.themes || [],
|
||
langs: options.langs || [],
|
||
});
|
||
return _shiki;
|
||
}
|
||
else {
|
||
const s = await _shiki;
|
||
await Promise.all([
|
||
s.loadTheme(...(options.themes || [])),
|
||
s.loadLanguage(...(options.langs || [])),
|
||
]);
|
||
return s;
|
||
}
|
||
}
|
||
return getSingletonHighlighter;
|
||
}
|
||
function createSingletonShorthands(createHighlighter) {
|
||
const getSingletonHighlighter = makeSingletonHighlighter(createHighlighter);
|
||
return {
|
||
getSingletonHighlighter(options) {
|
||
return getSingletonHighlighter(options);
|
||
},
|
||
async codeToHtml(code, options) {
|
||
const shiki = await getSingletonHighlighter({
|
||
langs: [options.lang],
|
||
themes: ('theme' in options ? [options.theme] : Object.values(options.themes)),
|
||
});
|
||
return shiki.codeToHtml(code, options);
|
||
},
|
||
async codeToHast(code, options) {
|
||
const shiki = await getSingletonHighlighter({
|
||
langs: [options.lang],
|
||
themes: ('theme' in options ? [options.theme] : Object.values(options.themes)),
|
||
});
|
||
return shiki.codeToHast(code, options);
|
||
},
|
||
async codeToTokens(code, options) {
|
||
const shiki = await getSingletonHighlighter({
|
||
langs: [options.lang],
|
||
themes: ('theme' in options ? [options.theme] : Object.values(options.themes)),
|
||
});
|
||
return shiki.codeToTokens(code, options);
|
||
},
|
||
async codeToTokensBase(code, options) {
|
||
const shiki = await getSingletonHighlighter({
|
||
langs: [options.lang],
|
||
themes: [options.theme],
|
||
});
|
||
return shiki.codeToTokensBase(code, options);
|
||
},
|
||
async codeToTokensWithThemes(code, options) {
|
||
const shiki = await getSingletonHighlighter({
|
||
langs: [options.lang],
|
||
themes: Object.values(options.themes).filter(Boolean),
|
||
});
|
||
return shiki.codeToTokensWithThemes(code, options);
|
||
},
|
||
};
|
||
}
|
||
|
||
export { FontStyle, ShikiError, addClassToHast, applyColorReplacements, codeToHast, codeToHtml, codeToTokens, codeToTokensBase, codeToTokensWithThemes, createHighlighterCore, createPositionConverter, createShikiInternal, createSingletonShorthands, createdBundledHighlighter, getHighlighterCore, getShikiInternal, getSingletonHighlighterCore, getTokenStyleObject, toHtml as hastToHtml, isNoneTheme, isPlainLang, isSpecialLang, isSpecialTheme, loadWasm, makeSingletonHighlighter, makeSingletonHighlighterCore, normalizeTheme, resolveColorReplacements, setDefaultWasmLoader, splitLines, splitToken, splitTokens, stringifyTokenStyle, toArray, tokenizeAnsiWithTheme, tokenizeWithTheme, tokensToHast, transformerDecorations };
|