2024-01-05 12:14:38 +00:00
import { FontStyle } from './types.mjs' ;
2024-01-31 06:33:19 +00:00
import { StackElementMetadata , INITIAL , Registry as Registry$1 } from './textmate.mjs' ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
function toArray ( x ) {
return Array . isArray ( x ) ? x : [ x ] ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Slipt a string into lines , each line preserves the line ending .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function splitLines ( str ) {
return Array . from ( str . matchAll ( /^.*$/mg ) ) . map ( x => [ x [ 0 ] , x . index ] ) ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Check if the language is plaintext that is ignored by Shikiji .
*
* Hard - coded plain text languages : ` plaintext ` , ` txt ` , ` text ` , ` plain ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function isPlainLang ( lang ) {
return ! lang || [ 'plaintext' , 'txt' , 'text' , 'plain' ] . includes ( lang ) ;
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Check if the language is specially handled or bypassed by Shikiji .
*
* Hard - coded languages : ` ansi ` and plaintexts like ` plaintext ` , ` txt ` , ` text ` , ` plain ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function isSpecialLang ( lang ) {
return lang === 'ansi' || isPlainLang ( lang ) ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Check if the theme is specially handled or bypassed by Shikiji .
*
* Hard - coded themes : ` none ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function isNoneTheme ( theme ) {
return theme === 'none' ;
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Check if the theme is specially handled or bypassed by Shikiji .
*
* Hard - coded themes : ` none ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function isSpecialTheme ( theme ) {
return isNoneTheme ( theme ) ;
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function addClassToHast ( node , className ) {
if ( ! className )
return ;
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 ) ;
}
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Split a token into multiple tokens by given offsets .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* The offsets are relative to the token , and should be sorted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 ;
}
function applyColorReplacements ( color , replacements ) {
return replacements ? . [ color . toLowerCase ( ) ] || color ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ deprecated Use ` isPlainLang ` instead .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const isPlaintext = isPlainLang ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// src/colors.ts
var namedColors = [
"black" ,
"red" ,
"green" ,
"yellow" ,
"blue" ,
"magenta" ,
"cyan" ,
"white" ,
"brightBlack" ,
"brightRed" ,
"brightGreen" ,
"brightYellow" ,
"brightBlue" ,
"brightMagenta" ,
"brightCyan" ,
"brightWhite"
] ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// src/decorations.ts
var decorations = {
1 : "bold" ,
2 : "dim" ,
3 : "italic" ,
4 : "underline" ,
7 : "reverse" ,
9 : "strikethrough"
} ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// 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 ) } ;
2024-01-05 12:14:38 +00:00
}
}
2024-01-31 06:33:19 +00:00
return [ offset , color ] ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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 ] }
} ) ;
}
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
return commands ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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 ;
}
} ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
// 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 ] ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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
} ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
function tokenizeAnsiWithTheme ( theme , fileContents , options ) {
const colorReplacements = {
... theme . colorReplacements ,
... options ? . colorReplacements ,
} ;
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 ;
if ( token . decorations . has ( 'reverse' ) )
color = token . background ? colorPalette . value ( token . background ) : theme . bg ;
else
color = token . foreground ? colorPalette . value ( token . foreground ) : theme . fg ;
color = applyColorReplacements ( color , 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 ,
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 ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
function codeToThemedTokens ( 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 . getLangGrammar ( lang ) ;
return tokenizeWithTheme ( code , _grammar , theme , colorMap , options ) ;
}
function tokenizeWithTheme ( code , grammar , theme , colorMap , options ) {
const colorReplacements = {
... theme . colorReplacements ,
... options ? . colorReplacements ,
} ;
const lines = splitLines ( code ) ;
let ruleStack = INITIAL ;
let actual = [ ] ;
const final = [ ] ;
for ( let i = 0 , len = lines . length ; i < len ; i ++ ) {
const [ line , lineOffset ] = lines [ i ] ;
if ( line === '' ) {
actual = [ ] ;
final . push ( [ ] ) ;
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 ) ;
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 foreground = StackElementMetadata . getForeground ( metadata ) ;
const foregroundColor = applyColorReplacements ( colorMap [ foreground ] , colorReplacements ) ;
const fontStyle = StackElementMetadata . getFontStyle ( metadata ) ;
const token = {
content : line . substring ( startIndex , nextStartIndex ) ,
offset : lineOffset + startIndex ,
color : foregroundColor ,
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 ( theme , tokenWithScopes . scopes ) ,
} ) ;
tokensWithScopesIndex += 1 ;
}
}
actual . push ( token ) ;
}
final . push ( actual ) ;
actual = [ ] ;
ruleStack = result . ruleStack ;
}
return final ;
}
function explainThemeScopes ( theme , 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 ( theme , scope , parentScopes ) ,
} ;
}
return result ;
}
function matchesOne ( selector , scope ) {
const selectorPrefix = ` ${ selector } . ` ;
if ( selector === scope || scope . substring ( 0 , selectorPrefix . length ) === selectorPrefix )
return true ;
return false ;
}
function matches ( selector , selectorParentScopes , scope , parentScopes ) {
if ( ! matchesOne ( selector , scope ) )
return false ;
let selectorParentIndex = selectorParentScopes . length - 1 ;
let parentIndex = parentScopes . length - 1 ;
while ( selectorParentIndex >= 0 && parentIndex >= 0 ) {
if ( matchesOne ( selectorParentScopes [ selectorParentIndex ] , parentScopes [ parentIndex ] ) )
selectorParentIndex -= 1 ;
parentIndex -= 1 ;
}
if ( selectorParentIndex === - 1 )
return true ;
return false ;
}
function explainThemeScope ( theme , scope , parentScopes ) {
const result = [ ] ;
let resultLen = 0 ;
for ( let i = 0 , len = theme . settings . length ; i < len ; i ++ ) {
const setting = theme . settings [ i ] ;
let selectors ;
if ( typeof setting . scope === 'string' )
selectors = setting . scope . split ( /,/ ) . map ( scope => scope . trim ( ) ) ;
else if ( Array . isArray ( setting . scope ) )
selectors = setting . scope ;
else
continue ;
for ( let j = 0 , lenJ = selectors . length ; j < lenJ ; j ++ ) {
const rawSelector = selectors [ j ] ;
const rawSelectorPieces = rawSelector . split ( / / ) ;
const selector = rawSelectorPieces [ rawSelectorPieces . length - 1 ] ;
const selectorParentScopes = rawSelectorPieces . slice ( 0 , rawSelectorPieces . length - 1 ) ;
if ( matches ( selector , selectorParentScopes , scope , parentScopes ) ) {
// match!
result [ resultLen ++ ] = setting ;
// break the loop
j = lenJ ;
}
}
}
return result ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Get tokens with multiple themes
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 => codeToThemedTokens ( internal , code , {
... options ,
theme : t . theme ,
includeExplanation : false ,
} ) ) ) ;
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 ;
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* 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 )
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
function codeToHast ( internal , code , options , transformerContext = {
meta : { } ,
options ,
codeToHast : ( _code , _options ) => codeToHast ( internal , _code , _options ) ,
} ) {
let input = code ;
for ( const transformer of options . transformers || [ ] )
input = transformer . preprocess ? . call ( transformerContext , input , options ) || input ;
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 Error ( '[shikiji] `themes` option must not be empty' ) ;
const themeTokens = codeToTokensWithThemes ( internal , input , options ) ;
if ( defaultColor && ! themes . find ( t => t . color === defaultColor ) )
throw new Error ( ` [shikiji] \` 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 ) ) ) ;
fg = themes . map ( ( t , idx ) => ( idx === 0 && defaultColor ? '' : ` ${ cssVariablePrefix + t . color } : ` ) + ( themeRegs [ idx ] . fg || 'inherit' ) ) . join ( ';' ) ;
bg = themes . map ( ( t , idx ) => ( idx === 0 && defaultColor ? '' : ` ${ cssVariablePrefix + t . color } -bg: ` ) + ( themeRegs [ idx ] . bg || 'inherit' ) ) . join ( ';' ) ;
themeName = ` shiki-themes ${ themeRegs . map ( t => t . name ) . join ( ' ' ) } ` ;
rootStyle = defaultColor ? undefined : [ fg , bg ] . join ( ';' ) ;
}
else if ( 'theme' in options ) {
tokens = codeToThemedTokens ( internal , input , {
... options ,
includeExplanation : false ,
} ) ;
const _theme = internal . getTheme ( options . theme ) ;
bg = _theme . bg ;
fg = _theme . fg ;
themeName = _theme . name ;
}
else {
throw new Error ( '[shikiji] Invalid options, either `theme` or `themes` must be provided' ) ;
}
const { mergeWhitespaces = true , } = options ;
if ( mergeWhitespaces === true )
tokens = mergeWhitespaceTokens ( tokens ) ;
else if ( mergeWhitespaces === 'never' )
tokens = splitWhitespaceTokens ( tokens ) ;
for ( const transformer of options . transformers || [ ] )
tokens = transformer . tokens ? . call ( transformerContext , tokens ) || tokens ;
return tokensToHast ( tokens , {
... options ,
fg ,
bg ,
themeName ,
rootStyle ,
} , transformerContext ) ;
}
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 varKey = cssVariablePrefix + variantsOrder [ idx ] + ( key === 'color' ? '' : ` - ${ key } ` ) ;
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 ;
}
function tokensToHast ( tokens , options , transformerContext ) {
const { transformers = [ ] , } = options ;
const lines = [ ] ;
const tree = {
type : 'root' ,
children : [ ] ,
} ;
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 ,
get tokens ( ) {
return tokens ;
} ,
get options ( ) {
return options ;
} ,
get root ( ) {
return tree ;
} ,
get pre ( ) {
return preNode ;
} ,
get code ( ) {
return codeNode ;
} ,
get lines ( ) {
return lineNodes ;
} ,
} ;
tokens . forEach ( ( line , idx ) => {
if ( idx )
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 || transformer ? . token ) ? . call ( context , tokenNode , idx + 1 , col , lineNode ) || tokenNode ;
lineNode . children . push ( tokenNode ) ;
col += token . content . length ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
for ( const transformer of transformers )
lineNode = transformer ? . line ? . call ( context , lineNode , idx + 1 ) || lineNode ;
lineNodes . push ( lineNode ) ;
lines . push ( lineNode ) ;
} ) ;
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 ;
tree . children . push ( preNode ) ;
let result = tree ;
for ( const transformer of transformers )
result = transformer ? . root ? . call ( context , result ) || result ;
return result ;
}
function getTokenStyleObject ( token ) {
const styles = { } ;
if ( token . color )
styles . color = token . color ;
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' ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
return styles ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
function stringifyTokenStyle ( token ) {
return Object . entries ( token ) . map ( ( [ key , value ] ) => ` ${ key } : ${ value } ` ) . join ( ';' ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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 ,
content : carryOnContent + token . content ,
} ) ;
}
else {
newLine . push ( {
content : carryOnContent ,
offset : firstOffset ,
} , token ) ;
}
carryOnContent = '' ;
}
else {
newLine . push ( token ) ;
}
}
} ) ;
return newLine ;
} ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
function splitWhitespaceTokens ( tokens ) {
return tokens . map ( ( line ) => {
return line . flatMap ( ( token ) => {
if ( token . content . match ( /^\s+$/ ) )
return token ;
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 ;
} ) ;
} ) ;
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* List of HTML void tag names .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ type { Array < string > }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const htmlVoidElements = [
'area' ,
'base' ,
'basefont' ,
'bgsound' ,
'br' ,
'col' ,
'command' ,
'embed' ,
'frame' ,
'hr' ,
'image' ,
'img' ,
'input' ,
'keygen' ,
'link' ,
'meta' ,
'param' ,
'source' ,
'track' ,
'wbr'
] ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( './info.js' ) . Info } Info
* @ typedef { Record < string , Info > } Properties
* @ typedef { Record < string , string > } Normal
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
class Schema {
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ constructor
* @ param { Properties } property
* @ param { Normal } normal
* @ param { string } [ space ]
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
constructor ( property , normal , space ) {
this . property = property ;
this . normal = normal ;
if ( space ) {
this . space = space ;
}
2024-01-05 12:14:38 +00:00
}
}
2024-01-31 06:33:19 +00:00
/** @type {Properties} */
Schema . prototype . property = { } ;
/** @type {Normal} */
Schema . prototype . normal = { } ;
/** @type {string|null} */
Schema . prototype . space = null ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( './schema.js' ) . Properties } Properties
* @ typedef { import ( './schema.js' ) . Normal } Normal
2024-01-05 12:14:38 +00:00
* /
/ * *
2024-01-31 06:33:19 +00:00
* @ param { Schema [ ] } definitions
* @ param { string } [ space ]
* @ returns { Schema }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 )
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ 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
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 ]
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 ,
capture : boolean ,
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 ,
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 ,
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 ,
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 ,
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 ,
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>`
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// Non-standard Properties.
allowTransparency : null ,
autoCorrect : null ,
autoSave : null ,
disablePictureInPicture : boolean ,
disableRemotePlayback : boolean ,
prefix : null ,
property : null ,
results : number ,
security : null ,
unselectable : null
}
} ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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' ,
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 ,
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
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
} ) ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( './util/schema.js' ) . Schema } Schema
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const valid = /^data[-\w.:]+$/i ;
const dash = /-[a-z]/g ;
const cap = /[A-Z]/g ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ param { Schema } schema
* @ param { string } value
* @ returns { Info }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function find ( schema , value ) {
const normal = normalize ( value ) ;
let prop = value ;
let Type = Info ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( normal in schema . normal ) {
return schema . property [ schema . normal [ normal ] ]
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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 ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( ! dash . test ( rest ) ) {
let dashes = rest . replace ( cap , kebab ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( dashes . charAt ( 0 ) !== '-' ) {
dashes = '-' + dashes ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
value = 'data' + dashes ;
}
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
Type = DefinedInfo ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
return new Type ( prop , value )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ param { string } $0
* @ returns { string }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function kebab ( $0 ) {
return '-' + $0 . toLowerCase ( )
}
/ * *
* @ param { string } $0
2024-01-05 12:14:38 +00:00
* @ returns { string }
* /
2024-01-31 06:33:19 +00:00
function camelcase ( $0 ) {
return $0 . charAt ( 1 ) . toUpperCase ( )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( './lib/util/info.js' ) . Info } Info
* @ typedef { import ( './lib/util/schema.js' ) . Schema } Schema
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const html$2 = merge ( [ xml , xlink , xmlns , aria , html$3 ] , 'html' ) ;
const svg = merge ( [ xml , xlink , xmlns , aria , svg$1 ] , 'svg' ) ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 !
2024-01-05 12:14:38 +00:00
* /
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 !
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
/ * *
* @ 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 .
* /
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const own$2 = { } . hasOwnProperty ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Handle values based on a field .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 > } }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function zwitch ( key , options ) {
const settings = options || { } ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
/ * *
* 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 ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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 ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( fn ) {
return fn . call ( this , value , ... parameters )
}
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
one . handlers = settings . handlers || { } ;
one . invalid = settings . invalid ;
one . unknown = settings . unknown ;
// @ts-expect-error: matches!
return one
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ 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
2024-01-05 12:14:38 +00:00
* /
/ * *
2024-01-31 06:33:19 +00:00
* Encode certain characters in ` value ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { string } value
* @ param { CoreWithFormatOptions } options
2024-01-05 12:14:38 +00:00
* @ returns { string }
* /
2024-01-31 06:33:19 +00:00
function core ( value , options ) {
value = value . replace (
options . subset ? charactersToExpression ( options . subset ) : /["&'<>`]/g ,
basic
) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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
)
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
/ * *
* @ 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
2024-01-05 12:14:38 +00:00
)
2024-01-31 06:33:19 +00:00
}
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ param { Array < string > } subset
* @ returns { RegExp }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function charactersToExpression ( subset ) {
/** @type {Array<string>} */
const groups = [ ] ;
let index = - 1 ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
while ( ++ index < subset . length ) {
groups . push ( subset [ index ] . replace ( /[|\\{}()[\]^$+*?.]/g , '\\$&' ) ) ;
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return new RegExp ( '(?:' + groups . join ( '|' ) + ')' , 'g' )
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Configurable ways to encode characters as hexadecimal references .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { number } code
* @ param { number } next
* @ param { boolean | undefined } omit
* @ returns { string }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function toHexadecimal ( code , next , omit ) {
const value = '&#x' + code . toString ( 16 ) . toUpperCase ( ) ;
return omit && next && ! /[\dA-Fa-f]/ . test ( String . fromCharCode ( next ) )
? value
: value + ';'
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Configurable ways to encode characters as decimal references .
*
* @ param { number } code
* @ param { number } next
* @ param { boolean | undefined } omit
* @ returns { string }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function toDecimal ( code , next , omit ) {
const value = '&#' + String ( code ) ;
return omit && next && ! /\d/ . test ( String . fromCharCode ( next ) )
? value
: value + ';'
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* List of legacy HTML named character references that don ’ t need a trailing semicolon .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ type { Array < string > }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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'
] ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Map of named character references from HTML 4.
*
* @ type { Record < string , string > }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 : '€'
} ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* List of legacy ( that don ’ t need a trailing ` ; ` ) named references which could ,
* depending on what follows them , turn into a different meaning
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ type { Array < string > }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const dangerous = [
'cent' ,
'copy' ,
'divide' ,
'gt' ,
'lt' ,
'not' ,
'para' ,
'times'
] ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
const own$1 = { } . hasOwnProperty ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* ` characterEntitiesHtml4 ` but inverted .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ type { Record < string , string > }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
const characters = { } ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
/** @type {string} */
let key ;
for ( key in characterEntitiesHtml4 ) {
if ( own$1 . call ( characterEntitiesHtml4 , key ) ) {
characters [ characterEntitiesHtml4 [ key ] ] = key ;
2024-01-05 12:14:38 +00:00
}
}
/ * *
2024-01-31 06:33:19 +00:00
* Configurable ways to encode characters as named references .
*
* @ param { number } code
* @ param { number } next
* @ param { boolean | undefined } omit
* @ param { boolean | undefined } attribute
* @ returns { string }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function toNamed ( code , next , omit , attribute ) {
const character = String . fromCharCode ( code ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( own$1 . call ( characters , character ) ) {
const name = characters [ character ] ;
const value = '&' + name ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if (
omit &&
characterEntitiesLegacy . includes ( name ) &&
! dangerous . includes ( name ) &&
( ! attribute ||
( next &&
next !== 61 /* `=` */ &&
/[^\da-z]/i . test ( String . fromCharCode ( next ) ) ) )
) {
return value
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return value + ';'
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return ''
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Configurable ways to encode a character yielding pretty or small results .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { number } code
* @ param { number } next
* @ param { FormatSmartOptions } options
* @ returns { string }
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( './core.js' ) . CoreOptions & import ( './util/format-smart.js' ) . FormatSmartOptions } Options
* @ typedef { import ( './core.js' ) . CoreOptions } LightOptions
* /
/ * *
* Encode special characters in ` value ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { string } value
* Value to encode .
* @ param { Options } [ options ]
* Configuration .
* @ returns { string }
* Encoded value .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function stringifyEntities ( value , options ) {
return core ( value , Object . assign ( { format : formatSmart } , options ) )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Comment } Comment
* @ typedef { import ( 'hast' ) . Parents } Parents
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ typedef { import ( '../index.js' ) . State } State
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Serialize a comment .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 : [ '>' ] } )
) +
'>'
: '<!--' + node . value . replace ( /^>|^->|<!--|-->|--!>|<!-$/g , encode ) + '-->'
/ * *
* @ param { string } $0
* /
function encode ( $0 ) {
return stringifyEntities (
$0 ,
Object . assign ( { } , state . settings . characterReferences , {
subset : [ '<' , '>' ]
} )
)
}
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Doctype } Doctype
* @ typedef { import ( 'hast' ) . Parents } Parents
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ typedef { import ( '../index.js' ) . State } State
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
/ * *
* 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 ) {
2024-01-05 12:14:38 +00:00
return (
2024-01-31 06:33:19 +00:00
'<!' +
( state . settings . upperDoctype ? 'DOCTYPE' : 'doctype' ) +
( state . settings . tightDoctype ? '' : ' ' ) +
'html>'
2024-01-05 12:14:38 +00:00
)
}
/ * *
2024-01-31 06:33:19 +00:00
* Count how often a character ( or substring ) is used in a string .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 ` .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Array < string | number > } values
* List of tokens .
* @ param { Options } [ options ]
* Configuration for ` stringify ` ( optional ) .
* @ returns { string }
* Comma - separated tokens .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 ( )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Parse space - separated tokens to an array of strings .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { string } value
* Space - separated tokens .
* @ returns { Array < string > }
* List of tokens .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
/ * *
* 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 ( )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Nodes } Nodes
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
// HTML whitespace expression.
// See <https://infra.spec.whatwg.org/#ascii-whitespace>.
const re = /[ \t\n\f\r]/g ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Check if the given value is * inter - element whitespace * .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Nodes | string } thing
* Thing to check ( ` Node ` or ` string ` ) .
2024-01-05 12:14:38 +00:00
* @ returns { boolean }
2024-01-31 06:33:19 +00:00
* 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function whitespace ( thing ) {
return typeof thing === 'object'
? thing . type === 'text'
? empty ( thing . value )
: false
: empty ( thing )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ param { string } value
2024-01-05 12:14:38 +00:00
* @ returns { boolean }
* /
2024-01-31 06:33:19 +00:00
function empty ( value ) {
return value . replace ( re , '' ) === ''
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { number } increment
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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
}
2024-01-05 12:14:38 +00:00
}
/ * *
* @ typedef { import ( 'hast' ) . Element } Element
* @ typedef { import ( 'hast' ) . Parents } Parents
* /
2024-01-31 06:33:19 +00:00
/ * *
* @ 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 .
*
* /
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
const own = { } . hasOwnProperty ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Factory to check if a given node can have a tag omitted .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 )
)
}
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Element } Element
* @ typedef { import ( 'hast' ) . Parents } Parents
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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
} ) ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Macro for ` </head> ` , ` </colgroup> ` , and ` </caption> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
2024-01-05 12:14:38 +00:00
* Element .
2024-01-31 06:33:19 +00:00
* @ param { number | undefined } index
* Index of element in parent .
* @ param { Parents | undefined } parent
* Parent of element .
2024-01-05 12:14:38 +00:00
* @ returns { boolean }
2024-01-31 06:33:19 +00:00
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function headOrColgroupOrCaption ( _ , index , parent ) {
const next = siblingAfter ( parent , index , true ) ;
2024-01-05 12:14:38 +00:00
return (
2024-01-31 06:33:19 +00:00
! next ||
( next . type !== 'comment' &&
! ( next . type === 'text' && whitespace ( next . value . charAt ( 0 ) ) ) )
2024-01-05 12:14:38 +00:00
)
}
/ * *
2024-01-31 06:33:19 +00:00
* 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> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
2024-01-05 12:14:38 +00:00
* Element .
* @ param { number | undefined } index
* Index of element in parent .
* @ param { Parents | undefined } parent
* Parent of element .
* @ returns { boolean }
2024-01-31 06:33:19 +00:00
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function body$1 ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return ! next || next . type !== 'comment'
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </p> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
2024-01-05 12:14:38 +00:00
* Element .
* @ param { number | undefined } index
* Index of element in parent .
* @ param { Parents | undefined } parent
* Parent of element .
* @ returns { boolean }
2024-01-31 06:33:19 +00:00
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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' )
)
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </li> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function li ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return ! next || ( next . type === 'element' && next . tagName === 'li' )
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </dt> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function dt ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return Boolean (
next &&
next . type === 'element' &&
( next . tagName === 'dt' || next . tagName === 'dd' )
)
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </dd> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
* Element .
2024-01-05 12:14:38 +00:00
* @ param { number | undefined } index
2024-01-31 06:33:19 +00:00
* Index of element in parent .
2024-01-05 12:14:38 +00:00
* @ param { Parents | undefined } parent
2024-01-31 06:33:19 +00:00
* Parent of element .
* @ returns { boolean }
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function dd ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return (
! next ||
( next . type === 'element' &&
( next . tagName === 'dt' || next . tagName === 'dd' ) )
)
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
/ * *
* 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' ) )
)
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function optgroup ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return ! next || ( next . type === 'element' && next . tagName === 'optgroup' )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function option ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return (
! next ||
( next . type === 'element' &&
( next . tagName === 'option' || next . tagName === 'optgroup' ) )
)
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </thead> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function thead ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return Boolean (
next &&
next . type === 'element' &&
( next . tagName === 'tbody' || next . tagName === 'tfoot' )
)
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
/ * *
* 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' ) )
)
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </tfoot> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
* Element .
* @ param { number | undefined } index
* Index of element in parent .
2024-01-05 12:14:38 +00:00
* @ param { Parents | undefined } parent
2024-01-31 06:33:19 +00:00
* Parent of element .
* @ returns { boolean }
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function tfoot ( _ , index , parent ) {
return ! siblingAfter ( parent , index )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </tr> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function tr ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return ! next || ( next . type === 'element' && next . tagName === 'tr' )
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` </td> ` or ` </th> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } _
* Element .
2024-01-05 12:14:38 +00:00
* @ param { number | undefined } index
2024-01-31 06:33:19 +00:00
* Index of element in parent .
2024-01-05 12:14:38 +00:00
* @ param { Parents | undefined } parent
2024-01-31 06:33:19 +00:00
* Parent of element .
* @ returns { boolean }
* Whether the closing tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function cells ( _ , index , parent ) {
const next = siblingAfter ( parent , index ) ;
return (
! next ||
( next . type === 'element' &&
( next . tagName === 'td' || next . tagName === 'th' ) )
)
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Element } Element
2024-01-05 12:14:38 +00:00
* @ typedef { import ( 'hast' ) . Parents } Parents
* /
2024-01-31 06:33:19 +00:00
const opening = omission ( {
body ,
colgroup ,
head ,
html ,
tbody
} ) ;
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` <html> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } node
* Element .
* @ returns { boolean }
* Whether the opening tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function html ( node ) {
const head = siblingAfter ( node , - 1 ) ;
return ! head || head . type !== 'comment'
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` <head> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } node
* Element .
* @ returns { boolean }
* Whether the opening tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function head ( node ) {
const children = node . children ;
/** @type {Array<string>} */
const seen = [ ] ;
let index = - 1 ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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 ) ;
}
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return children . length > 0
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` <body> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Element } node
* Element .
* @ returns { boolean }
* Whether the opening tag can be omitted .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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' )
) )
)
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* 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 .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function colgroup ( node , index , parent ) {
const previous = siblingBefore ( parent , index ) ;
const head = siblingAfter ( node , - 1 , true ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// Previous colgroup was already omitted.
if (
parent &&
previous &&
previous . type === 'element' &&
previous . tagName === 'colgroup' &&
closing ( previous , parent . children . indexOf ( previous ) , parent )
) {
return false
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return Boolean ( head && head . type === 'element' && head . tagName === 'col' )
}
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Whether to omit ` <tbody> ` .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function tbody ( node , index , parent ) {
const previous = siblingBefore ( parent , index ) ;
const head = siblingAfter ( node , - 1 ) ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
// 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
}
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
return Boolean ( head && head . type === 'element' && head . tagName === 'tr' )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Element } Element
* @ typedef { import ( 'hast' ) . Parents } Parents
* @ typedef { import ( 'hast' ) . Properties } Properties
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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
2024-01-05 12:14:38 +00:00
* Node to handle .
* @ param { number | undefined } index
* Index of ` node ` in ` parent.
* @ param { Parents | undefined } parent
* Parent of ` node ` .
2024-01-31 06:33:19 +00:00
* @ param { State } state
2024-01-05 12:14:38 +00:00
* Info passed around about the current state .
* @ returns { string }
2024-01-31 06:33:19 +00:00
* Serialized node .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
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 ( ) ) ;
2024-01-05 12:14:38 +00:00
/** @type {Array<string>} */
2024-01-31 06:33:19 +00:00
const parts = [ ] ;
/** @type {string} */
let last ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
if ( schema . space === 'html' && node . tagName === 'svg' ) {
state . schema = svg ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
const attrs = 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 ( attrs || ! omit || ! opening ( node , index , parent ) ) {
parts . push ( '<' , node . tagName , attrs ? ' ' + attrs : '' ) ;
if (
selfClosing &&
( schema . space === 'svg' || state . settings . closeSelfClosing )
) {
last = attrs . charAt ( attrs . length - 1 ) ;
if (
! state . settings . tightSelfClosing ||
last === '/' ||
( last && last !== '"' && last !== "'" )
) {
parts . push ( ' ' ) ;
}
parts . push ( '/' ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
parts . push ( '>' ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
parts . push ( content ) ;
if ( ! selfClosing && ( ! omit || ! closing ( node , index , parent ) ) ) {
parts . push ( '</' + node . tagName + '>' ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
return parts . join ( '' )
}
/ * *
* @ param { State } state
* @ param { Properties | null | undefined } props
* @ returns { string }
* /
function serializeAttributes ( state , props ) {
/** @type {Array<string>} */
const values = [ ] ;
let index = - 1 ;
/** @type {string} */
let key ;
if ( props ) {
for ( key in props ) {
if ( props [ key ] !== null && props [ key ] !== undefined ) {
const value = serializeAttribute ( state , key , props [ key ] ) ;
if ( value ) values . push ( value ) ;
2024-01-05 12:14:38 +00:00
}
}
2024-01-31 06:33:19 +00:00
}
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 ] += ' ' ;
2024-01-05 12:14:38 +00:00
}
}
2024-01-31 06:33:19 +00:00
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 ) ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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 ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
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 ;
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
// Don’ t add a `=` for unquoted empties.
return name + ( result ? '=' + result : result )
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Parents } Parents
* @ typedef { import ( 'hast' ) . Text } Text
*
* @ typedef { import ( 'mdast-util-to-hast' ) . Raw } Raw
*
* @ typedef { import ( '../index.js' ) . State } State
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
/ * *
* 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 : [ '<' , '&' ]
} )
)
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
/ * *
* @ 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 )
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
/ * *
* @ 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 )
2024-01-05 12:14:38 +00:00
}
/ * *
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'hast' ) . Nodes } Nodes
* @ typedef { import ( 'hast' ) . Parents } Parents
*
* @ typedef { import ( '../index.js' ) . State } State
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
/ * *
* @ 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 + '`' )
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Fail when a node with an unknown type is found in the tree .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'property-information' ) . Schema } Schema
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ typedef { import ( 'stringify-entities' ) . Options } StringifyEntitiesOptions
* /
/** @type {Options} */
const emptyOptions = { } ;
/** @type {CharacterReferences} */
const emptyCharacterReferences = { } ;
/** @type {Array<never>} */
const emptyChildren = [ ] ;
/ * *
* Serialize hast as HTML .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ param { Array < RootContent > | Nodes } tree
* Tree to serialize .
* @ param { Options | null | undefined } [ options ]
* Configuration ( optional ) .
* @ returns { string }
* Serialized HTML .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function toHtml ( tree , options ) {
const options _ = options || emptyOptions ;
const quote = options _ . quote || '"' ;
const alternative = quote === '"' ? "'" : '"' ;
2024-01-05 12:14:38 +00:00
2024-01-31 06:33:19 +00:00
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
)
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Serialize a node .
2024-01-05 12:14:38 +00:00
*
2024-01-31 06:33:19 +00:00
* @ 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 .
2024-01-05 12:14:38 +00:00
* /
2024-01-31 06:33:19 +00:00
function one ( node , index , parent ) {
return handle ( node , index , parent , this )
2024-01-05 12:14:38 +00:00
}
2024-01-31 06:33:19 +00:00
/ * *
* 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 ( '' )
2024-01-05 12:14:38 +00:00
}
/ * *
* Get highlighted code in HTML .
* /
function codeToHtml ( internal , code , options ) {
const context = {
meta : { } ,
options ,
codeToHast : ( _code , _options ) => codeToHast ( internal , _code , _options ) ,
} ;
let result = toHtml ( codeToHast ( internal , code , options , context ) ) ;
for ( const transformer of options . transformers || [ ] )
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 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 ) ;
}
await createWasm ( ) ;
return binding ;
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( C ) Microsoft Corporation . All rights reserved .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
let onigBinding = null ;
let defaultDebugCall = false ;
function throwLastOnigError ( onigBinding ) {
throw new Error ( 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 Error ( '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 Error ( '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 ;
2024-01-31 06:33:19 +00:00
instance = await instance ;
2024-01-05 12:14:38 +00:00
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 ) ;
}
}
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 ;
_resolvedThemes = { } ;
_resolvedGrammars = { } ;
_langMap = { } ;
_langGraph = new Map ( ) ;
alias = { } ;
constructor ( _resolver , _themes , _langs ) {
super ( _resolver ) ;
this . _resolver = _resolver ;
this . _themes = _themes ;
this . _langs = _langs ;
_themes . forEach ( t => this . loadTheme ( t ) ) ;
_langs . forEach ( l => this . loadLanguage ( l ) ) ;
}
getTheme ( theme ) {
if ( typeof theme === 'string' )
return this . _resolvedThemes [ theme ] ;
else
return this . loadTheme ( theme ) ;
}
loadTheme ( theme ) {
const _theme = normalizeTheme ( theme ) ;
if ( _theme . name )
this . _resolvedThemes [ _theme . name ] = _theme ;
return _theme ;
}
getLoadedThemes ( ) {
return Object . keys ( this . _resolvedThemes ) ;
}
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 Error ( ` [shikiji] Circular alias \` ${ Array . from ( resolved ) . join ( ' -> ' ) } -> ${ name } \` ` ) ;
resolved . add ( name ) ;
}
}
return this . _resolvedGrammars [ name ] ;
}
async loadLanguage ( lang ) {
if ( this . getGrammar ( lang . name ) )
return ;
const embeddedLazilyBy = new Set ( Object . values ( this . _langMap ) . 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 [ lang . name ] = g ;
if ( lang . aliases ) {
lang . aliases . forEach ( ( alias ) => {
this . alias [ alias ] = lang . name ;
} ) ;
}
// If there is a language that embeds this language lazily, we need to reload it
if ( embeddedLazilyBy . size ) {
for ( const e of embeddedLazilyBy ) {
delete this . _resolvedGrammars [ e . name ] ;
// @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 [ e . name ] ) ;
}
}
}
async init ( ) {
this . _themes . map ( t => this . loadTheme ( t ) ) ;
await this . loadLanguages ( this . _langs ) ;
}
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 Error ( ` [shikiji] 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 ( ) {
return Object . keys ( { ... this . _resolvedGrammars , ... this . alias } ) ;
}
resolveEmbeddedLanguages ( lang ) {
this . _langMap [ lang . name ] = lang ;
this . _langGraph . set ( lang . name , lang ) ;
if ( lang . embeddedLangs ) {
for ( const embeddedLang of lang . embeddedLangs )
this . _langGraph . set ( embeddedLang , this . _langMap [ 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 ;
}
}
2024-01-31 06:33:19 +00:00
let _defaultWasmLoader ;
/ * *
* Set the default wasm loader for ` loadWasm ` .
* @ internal
* /
function setDefaultWasmLoader ( _loader ) {
_defaultWasmLoader = _loader ;
}
2024-01-05 12:14:38 +00:00
/ * *
* Get the minimal shiki context for rendering .
* /
async function getShikiInternal ( options = { } ) {
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 . map ( async ( lang ) => await normalizeGetter ( lang ) . then ( r => Array . isArray ( r ) ? r : [ r ] ) ) ) ) . flat ( ) ) ) ;
}
2024-01-31 06:33:19 +00:00
const wasmLoader = options . loadWasm || _defaultWasmLoader ;
2024-01-05 12:14:38 +00:00
const [ themes , langs , ] = await Promise . all ( [
Promise . all ( ( options . themes || [ ] ) . map ( normalizeGetter ) ) . then ( r => r . map ( normalizeTheme ) ) ,
resolveLangs ( options . langs || [ ] ) ,
2024-01-31 06:33:19 +00:00
wasmLoader ? loadWasm ( wasmLoader ) : undefined ,
2024-01-05 12:14:38 +00:00
] ) ;
const resolver = new Resolver ( Promise . resolve ( {
createOnigScanner ( patterns ) {
return createOnigScanner ( patterns ) ;
} ,
createOnigString ( s ) {
return createOnigString ( s ) ;
} ,
} ) , langs ) ;
const _registry = new Registry ( resolver , themes , langs ) ;
Object . assign ( _registry . alias , options . langAlias ) ;
await _registry . init ( ) ;
let _lastTheme ;
function getLangGrammar ( name ) {
const _lang = _registry . getGrammar ( name ) ;
if ( ! _lang )
throw new Error ( ` [shikiji] Language \` ${ name } \` not found, you may need to load it first ` ) ;
return _lang ;
}
function getTheme ( name ) {
2024-01-31 06:33:19 +00:00
if ( name === 'none' )
return { bg : '' , fg : '' , name : 'none' , settings : [ ] , type : 'dark' } ;
2024-01-05 12:14:38 +00:00
const _theme = _registry . getTheme ( name ) ;
if ( ! _theme )
throw new Error ( ` [shikiji] Theme \` ${ name } \` not found, you may need to load it first ` ) ;
return _theme ;
}
function setTheme ( name ) {
const theme = getTheme ( name ) ;
if ( _lastTheme !== name ) {
_registry . setTheme ( theme ) ;
_lastTheme = name ;
}
const colorMap = _registry . getColorMap ( ) ;
return {
theme ,
colorMap ,
} ;
}
function getLoadedThemes ( ) {
return _registry . getLoadedThemes ( ) ;
}
function getLoadedLanguages ( ) {
return _registry . getLoadedLanguages ( ) ;
}
async function loadLanguage ( ... langs ) {
await _registry . loadLanguages ( await resolveLangs ( langs ) ) ;
}
async function loadTheme ( ... themes ) {
2024-01-31 06:33:19 +00:00
await Promise . all ( themes . map ( async ( theme ) => isSpecialTheme ( theme )
? null
: _registry . loadTheme ( await normalizeGetter ( theme ) ) ) ) ;
2024-01-05 12:14:38 +00:00
}
function updateAlias ( alias ) {
Object . assign ( _registry . alias , alias ) ;
}
function getAlias ( ) {
return _registry . alias ;
}
return {
setTheme ,
getTheme ,
getLangGrammar ,
getLoadedThemes ,
getLoadedLanguages ,
getAlias ,
updateAlias ,
loadLanguage ,
loadTheme ,
} ;
}
2024-01-31 06:33:19 +00:00
2024-01-05 12:14:38 +00:00
/ * *
2024-01-31 06:33:19 +00:00
* Create a Shikiji core highlighter instance , with no languages or themes bundled .
* Wasm and each language and theme must be loaded manually .
*
* @ see http : //shikiji.netlify.app/guide/install#fine-grained-bundle
2024-01-05 12:14:38 +00:00
* /
async function getHighlighterCore ( options = { } ) {
const internal = await getShikiInternal ( options ) ;
return {
codeToThemedTokens : ( code , options ) => codeToThemedTokens ( internal , code , options ) ,
codeToTokensWithThemes : ( code , options ) => codeToTokensWithThemes ( internal , code , options ) ,
codeToHast : ( code , options ) => codeToHast ( internal , code , options ) ,
codeToHtml : ( code , options ) => codeToHtml ( internal , code , options ) ,
loadLanguage : internal . loadLanguage ,
loadTheme : internal . loadTheme ,
getTheme : internal . getTheme ,
getLangGrammar : internal . getLangGrammar ,
setTheme : internal . setTheme ,
getLoadedThemes : internal . getLoadedThemes ,
getLoadedLanguages : internal . getLoadedLanguages ,
getInternalContext : ( ) => internal ,
} ;
}
/ * *
* Create a ` getHighlighter ` function with bundled themes and languages .
*
* @ param bundledLanguages
* @ param bundledThemes
* @ param loadWasm
* /
function createdBundledHighlighter ( bundledLanguages , bundledThemes , loadWasm ) {
async function getHighlighter ( options = { } ) {
function resolveLang ( lang ) {
if ( typeof lang === 'string' ) {
if ( isSpecialLang ( lang ) )
return [ ] ;
const bundle = bundledLanguages [ lang ] ;
if ( ! bundle )
throw new Error ( ` [shikiji] Language \` ${ lang } \` is not built-in. ` ) ;
return bundle ;
}
return lang ;
}
function resolveTheme ( theme ) {
2024-01-31 06:33:19 +00:00
if ( isSpecialTheme ( theme ) )
return 'none' ;
2024-01-05 12:14:38 +00:00
if ( typeof theme === 'string' ) {
const bundle = bundledThemes [ theme ] ;
if ( ! bundle )
throw new Error ( ` [shikiji] Theme \` ${ theme } \` is not built-in. ` ) ;
return bundle ;
}
return theme ;
}
const _themes = ( options . themes ? ? [ ] ) . map ( i => resolveTheme ( i ) ) ;
const langs = ( options . langs ? ? [ ] )
. map ( i => resolveLang ( i ) ) ;
const core = await getHighlighterCore ( {
... options ,
themes : _themes ,
langs ,
loadWasm ,
} ) ;
return {
... core ,
loadLanguage ( ... langs ) {
return core . loadLanguage ( ... langs . map ( resolveLang ) ) ;
} ,
loadTheme ( ... themes ) {
return core . loadTheme ( ... themes . map ( resolveTheme ) ) ;
} ,
} ;
}
return getHighlighter ;
}
function createSingletonShorthands ( getHighlighter ) {
let _shiki ;
async function _getHighlighter ( options = { } ) {
if ( ! _shiki ) {
_shiki = getHighlighter ( {
themes : toArray ( options . theme || [ ] ) ,
langs : toArray ( options . lang || [ ] ) ,
} ) ;
return _shiki ;
}
else {
const s = await _shiki ;
await Promise . all ( [
s . loadTheme ( ... toArray ( options . theme || [ ] ) ) ,
s . loadLanguage ( ... toArray ( options . lang || [ ] ) ) ,
] ) ;
return s ;
}
}
return {
getSingletonHighlighter : ( ) => _getHighlighter ( ) ,
2024-01-31 06:33:19 +00:00
async codeToHtml ( code , options ) {
const shiki = await _getHighlighter ( {
lang : options . lang ,
theme : ( 'theme' in options ? [ options . theme ] : Object . values ( options . themes ) ) ,
} ) ;
return shiki . codeToHtml ( code , options ) ;
} ,
async codeToHast ( code , options ) {
const shiki = await _getHighlighter ( {
lang : options . lang ,
theme : ( 'theme' in options ? [ options . theme ] : Object . values ( options . themes ) ) ,
} ) ;
return shiki . codeToHast ( code , options ) ;
} ,
async codeToThemedTokens ( code , options ) {
const shiki = await _getHighlighter ( options ) ;
return shiki . codeToThemedTokens ( code , options ) ;
} ,
async codeToTokensWithThemes ( code , options ) {
const shiki = await _getHighlighter ( {
lang : options . lang ,
theme : Object . values ( options . themes ) . filter ( Boolean ) ,
} ) ;
return shiki . codeToTokensWithThemes ( code , options ) ;
} ,
2024-01-05 12:14:38 +00:00
} ;
}
2024-01-31 06:33:19 +00:00
export { FontStyle , addClassToHast , applyColorReplacements , codeToHast , codeToHtml , codeToThemedTokens , codeToTokensWithThemes , createSingletonShorthands , createdBundledHighlighter , getHighlighterCore , getShikiInternal , toHtml as hastToHtml , isNoneTheme , isPlainLang , isPlaintext , isSpecialLang , isSpecialTheme , loadWasm , normalizeTheme , setDefaultWasmLoader , splitLines , splitToken , toArray , tokenizeAnsiWithTheme , tokenizeWithTheme } ;