blogger-hbs 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/compiler/validate.d.ts +4 -0
- package/dist/compiler/validate.js +8 -1
- package/dist/compiler/validate.js.map +1 -1
- package/dist/config.d.ts +6 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/xml/expressions.d.ts +28 -0
- package/dist/xml/expressions.js +129 -0
- package/dist/xml/expressions.js.map +1 -0
- package/dist/xml/format.d.ts +6 -2
- package/dist/xml/format.js +30 -8
- package/dist/xml/format.js.map +1 -1
- package/dist/xml/index.d.ts +2 -1
- package/dist/xml/index.js +2 -1
- package/dist/xml/scanner.d.ts +19 -1
- package/dist/xml/scanner.js +35 -3
- package/dist/xml/scanner.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CompileError } from "../compiler/errors.js";
|
|
|
2
2
|
import { didYouMean } from "../utils/suggest.js";
|
|
3
3
|
import { COMPILE_OPTION_KEYS, assertCompileOptions, assertDirectories, assertEntry, resolvePaths } from "../compiler/options.js";
|
|
4
4
|
import { formatXml, validateXml } from "../xml/format.js";
|
|
5
|
+
import "../xml/index.js";
|
|
5
6
|
import { compileTemplate } from "../compiler/compile.js";
|
|
6
7
|
import { watchTemplate } from "../compiler/watch.js";
|
|
7
8
|
import "../compiler/index.js";
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["import { type FSWatcher, watch } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { styleText } from 'node:util';\nimport { cac } from 'cac';\nimport {\n\ttype CompiledTemplate,\n\tCompileError,\n\ttype CompileTemplateOptions,\n\tcompileTemplate,\n\ttype TemplateWatcher,\n\twatchTemplate,\n} from '../compiler';\nimport {\n\tassertCompileOptions,\n\tassertDirectories,\n\tassertEntry,\n\tCOMPILE_OPTION_KEYS,\n\tresolvePaths,\n} from '../compiler/options';\nimport { type LoadedConfig, loadConfig } from '../config';\nimport { renderTemplate } from '../runtime';\nimport { didYouMean } from '../utils/suggest';\nimport { formatXml, validateXml } from '../xml';\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as {\n\tversion: string;\n};\n\ninterface BuildFlags {\n\tconfig?: string;\n\tentry?: string;\n\toutDir?: string;\n\tcontext?: string;\n\twatch?: boolean;\n\tclean?: boolean;\n\tvalidate?: boolean;\n\tformat?: boolean;\n}\n\n/** Everything a build needs, from the config file and the flags. */\ninterface BuildSetup {\n\toptions: CompileTemplateOptions;\n\tloaded: LoadedConfig;\n\toutDir: string;\n\tmoduleFile: string | false;\n\ttemplateFile: string | false;\n\tclean: boolean;\n\tcontext: unknown;\n\t/** Whether the rendered template is validated. */\n\tvalidate: boolean;\n\t/** Whether the rendered template is formatted, only when it is validated. */\n\tformat: boolean;\n\t/** Absolute path of the public directory, undefined when nothing is copied. */\n\tpublicDir?: string;\n}\n\nconst log = {\n\tsuccess: (message: string) =>\n\t\tconsole.log(`${styleText('green', '✓')} ${message}`),\n\tinfo: (message: string) =>\n\t\tconsole.log(`${styleText('cyan', '●')} ${message}`),\n\terror: (error: unknown) =>\n\t\tconsole.error(`${styleText('red', '✗')} ${formatError(error)}`),\n};\n\nconst cli = cac('blogger-hbs');\n\ncli\n\t.command('[root]', 'Compile the template and render it')\n\t.alias('build')\n\t.option('-c, --config <file>', 'Path of the config file')\n\t.option('--entry <file>', 'Entry template, relative to the root')\n\t.option('-o, --out-dir <dir>', 'Output directory, relative to the root')\n\t.option('--context <file>', 'JSON file with the context to render with')\n\t.option('-w, --watch', 'Build again when a file changes')\n\t.option('--no-clean', 'Keep the files in the output directory')\n\t.option(\n\t\t'--no-validate',\n\t\t'Do not validate the rendered template as XML, nor format it (templates are always validated when compiling)',\n\t)\n\t.option('--no-format', 'Do not format the rendered template')\n\t.example('blogger-hbs')\n\t.example('blogger-hbs --watch')\n\t.example('blogger-hbs ./theme --entry src/index.hbs --context context.json')\n\t.action(async (rootArg: unknown, rawFlags: BuildFlags) => {\n\t\t// cac turns values that look like numbers into numbers, `-o 2024`\n\t\tconst text = (value: unknown) =>\n\t\t\tvalue === undefined ? undefined : String(value);\n\t\tconst root = text(rootArg);\n\t\tconst flags: BuildFlags = {\n\t\t\t...rawFlags,\n\t\t\tconfig: text(rawFlags.config),\n\t\t\tentry: text(rawFlags.entry),\n\t\t\toutDir: text(rawFlags.outDir),\n\t\t\tcontext: text(rawFlags.context),\n\t\t};\n\n\t\tif (flags.watch) {\n\t\t\tawait watchBuild(root, flags);\n\t\t} else {\n\t\t\tawait build(root, flags);\n\t\t}\n\t});\n\ncli.help();\ncli.version(version);\n\ntry {\n\tcli.parse(process.argv, { run: false });\n\tawait cli.runMatchedCommand();\n} catch (error) {\n\tlog.error(describeCliError(error));\n\tprocess.exitCode = 1;\n}\n\n/** Adds a suggestion and a pointer to `--help` to the errors of the arguments. */\nfunction describeCliError(error: unknown): unknown {\n\tif (!(error instanceof Error) || error.name !== 'CACError') {\n\t\treturn error;\n\t}\n\n\tconst flags = [...cli.commands, cli.globalCommand].flatMap((command) =>\n\t\tcommand.options.flatMap(\n\t\t\t(option) => option.rawName.match(/--[\\w-]+/g) ?? [],\n\t\t),\n\t);\n\tconst unknown = /^Unknown option `(.+)`$/.exec(error.message)?.[1];\n\tconst hint = unknown ? didYouMean(unknown, flags, '`') : '';\n\n\treturn new Error(\n\t\t`${error.message}${hint}\\n Run 'blogger-hbs --help' to see the available options`,\n\t);\n}\n\nasync function setup(\n\troot: string | undefined,\n\tflags: BuildFlags,\n): Promise<BuildSetup> {\n\tconst cwd = process.cwd();\n\tconst rootFlag = root === undefined ? undefined : path.resolve(cwd, root);\n\n\t// the config is looked for in the root, which has to exist for that\n\tif (rootFlag !== undefined && !(await isDirectory(rootFlag))) {\n\t\tthrow new CompileError(\n\t\t\t(await exists(rootFlag))\n\t\t\t\t? `Root '${root}' is not a directory`\n\t\t\t\t: `Root directory '${root}' does not exist`,\n\t\t);\n\t}\n\n\tconst loaded = await loadConfig({\n\t\tcwd: rootFlag ?? cwd,\n\t\tconfigFile: flags.config && path.resolve(cwd, flags.config),\n\t\twatch: flags.watch,\n\t});\n\tconst { config } = loaded;\n\n\tconst resolvedRoot = rootFlag ?? config.root ?? cwd;\n\tconst output = config.output ?? {};\n\n\t// the config has more than the compile options\n\tconst options: CompileTemplateOptions = {};\n\tfor (const key of COMPILE_OPTION_KEYS) {\n\t\tif (key in config) {\n\t\t\tObject.assign(options, { [key]: config[key as keyof typeof config] });\n\t\t}\n\t}\n\toptions.root = resolvedRoot;\n\toptions.entry = flags.entry ?? config.entry;\n\n\tassertCompileOptions(options);\n\tawait assertDirectories(options);\n\n\tconst outDir = path.resolve(\n\t\tresolvedRoot,\n\t\tflags.outDir ?? output.dir ?? 'dist',\n\t);\n\tconst clean = flags.clean === false ? false : (output.clean ?? true);\n\tif (clean) {\n\t\tassertCleanable(outDir, options);\n\t}\n\n\treturn {\n\t\toptions,\n\t\tloaded,\n\t\toutDir,\n\t\tmoduleFile: output.module ?? 'compiled.js',\n\t\ttemplateFile: output.template ?? 'theme.xml',\n\t\tvalidate: flags.validate === false ? false : (output.validate ?? true),\n\t\tformat: flags.format === false ? false : (output.format ?? true),\n\t\tclean,\n\t\tcontext: flags.context\n\t\t\t? await readContext(path.resolve(cwd, flags.context), flags.context)\n\t\t\t: (config.context ?? {}),\n\t\tpublicDir: await resolvePublicDir(resolvedRoot, config.publicDir, outDir),\n\t};\n}\n\n/** The public directory, `public` when it exists unless set otherwise. */\nasync function resolvePublicDir(\n\troot: string,\n\tvalue: string | false | undefined,\n\toutDir: string,\n): Promise<string | undefined> {\n\tif (value === false) {\n\t\treturn;\n\t}\n\n\tconst directory = path.resolve(root, value ?? 'public');\n\tconst shown = path.relative(process.cwd(), directory) || '.';\n\n\tif (!(await isDirectory(directory))) {\n\t\tif (value === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new CompileError(\n\t\t\t(await exists(directory))\n\t\t\t\t? `Public directory '${shown}' is not a directory`\n\t\t\t\t: `Public directory '${shown}' does not exist, set 'publicDir: false' to not copy anything`,\n\t\t);\n\t}\n\n\tif (isInside(outDir, directory) || isInside(directory, outDir)) {\n\t\tthrow new CompileError(\n\t\t\t`Public directory '${shown}' and output directory '${path.relative(process.cwd(), outDir) || '.'}' overlap, they must be separate`,\n\t\t);\n\t}\n\n\treturn directory;\n}\n\n/** Copies the public directory into the output directory. */\nasync function copyPublic({\n\tpublicDir,\n\toutDir,\n\tmoduleFile,\n\ttemplateFile,\n}: BuildSetup): Promise<void> {\n\tif (publicDir === undefined) {\n\t\treturn;\n\t}\n\n\tfor (const name of await fs.readdir(publicDir)) {\n\t\tif (name === moduleFile || name === templateFile) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`'${name}' in the public directory has the same name as the ${name === moduleFile ? 'compiled module' : 'rendered template'} and would be overwritten, rename it or set 'output.${name === moduleFile ? 'module' : 'template'}'`,\n\t\t\t);\n\t\t}\n\t}\n\n\tawait fs.cp(publicDir, outDir, { recursive: true, force: true });\n}\n\n/**\n * Cleaning removes everything in the output directory, which must not hold the\n * project: the root, the working directory, the templates or an alias target.\n */\nfunction assertCleanable(outDir: string, options: CompileTemplateOptions) {\n\tconst { root, entry } = resolvePaths(options);\n\tconst shown = path.relative(process.cwd(), outDir) || '.';\n\tconst kept: [string, string][] = [\n\t\t[root, 'the root'],\n\t\t[process.cwd(), 'the working directory'],\n\t\t[path.dirname(entry), 'the entry template'],\n\t\t...Object.entries(options.alias ?? {}).map(\n\t\t\t([name, target]): [string, string] => [\n\t\t\t\tpath.resolve(root, target),\n\t\t\t\t`the target of the alias '${name}'`,\n\t\t\t],\n\t\t),\n\t];\n\n\tfor (const [directory, what] of kept) {\n\t\tif (isInside(directory, outDir)) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`Refusing to clean the output directory '${shown}', it contains ${what}. Use another output directory, or --no-clean / 'output.clean: false' to keep its files`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function readContext(file: string, shown: string): Promise<unknown> {\n\tlet text: string;\n\ttry {\n\t\ttext = await fs.readFile(file, 'utf-8');\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\tthrow new CompileError(`Context file '${shown}' not found`);\n\t\t}\n\t\tthrow new CompileError(`Failed to read the context file '${shown}'`, {\n\t\t\tcause,\n\t\t});\n\t}\n\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch (cause) {\n\t\tthrow new CompileError(\n\t\t\t`Context file '${shown}' is not valid JSON: ${(cause as Error).message}`,\n\t\t);\n\t}\n}\n\n/** Whether `child` is `parent` or inside of it. */\nfunction isInside(child: string, parent: string): boolean {\n\tconst relative = path.relative(parent, child);\n\treturn (\n\t\trelative === '' ||\n\t\t(!relative.startsWith('..') && !path.isAbsolute(relative))\n\t);\n}\n\nasync function exists(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t() => true,\n\t\t() => false,\n\t);\n}\n\nasync function isDirectory(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t(stats) => stats.isDirectory(),\n\t\t() => false,\n\t);\n}\n\nasync function build(root: string | undefined, flags: BuildFlags) {\n\tconst current = await setup(root, flags);\n\t// nothing is written when there is nothing to build, the watcher waits\n\t// for the entry instead\n\tawait assertEntry(current.options);\n\tawait prepareOutDir(current);\n\tawait copyPublic(current);\n\n\tconst start = performance.now();\n\tconst compiled = await compileTemplate(current.options);\n\tlog.success(`Compiled in ${elapsed(start)}`);\n\n\tawait write(current, compiled);\n}\n\n/** Watches the template and restarts when the config file changes. */\nasync function watchBuild(root: string | undefined, flags: BuildFlags) {\n\tlet current = await setup(root, flags);\n\tawait prepareOutDir(current);\n\n\tlet watcher: TemplateWatcher | undefined;\n\tlet watchers: FSWatcher[] = [];\n\tlet restarting = false;\n\n\tconst start = async () => {\n\t\tlet begin = performance.now();\n\t\tlet first = true;\n\n\t\ttry {\n\t\t\tawait copyPublic(current);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\n\t\twatcher = await watchTemplate({\n\t\t\t...current.options,\n\t\t\tonStart() {\n\t\t\t\tbegin = performance.now();\n\t\t\t},\n\t\t\tasync onCompile(compiled) {\n\t\t\t\tlog.success(`${first ? 'Compiled' : 'Rebuilt'} in ${elapsed(begin)}`);\n\t\t\t\tfirst = false;\n\t\t\t\tawait write(current, compiled);\n\t\t\t},\n\t\t\tonError: log.error,\n\t\t});\n\n\t\tfor (const file of current.loaded.dependencies) {\n\t\t\ttry {\n\t\t\t\tconst configWatcher = watch(file, () => {\n\t\t\t\t\t// saving fires more than once, a restart is enough\n\t\t\t\t\tif (!restarting) {\n\t\t\t\t\t\trestarting = true;\n\t\t\t\t\t\trestart().finally(() => {\n\t\t\t\t\t\t\trestarting = false;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconfigWatcher.on('error', () => configWatcher.close());\n\t\t\t\twatchers.push(configWatcher);\n\t\t\t} catch {\n\t\t\t\t// a dependency that can not be watched does not restart\n\t\t\t}\n\t\t}\n\n\t\tif (current.publicDir !== undefined) {\n\t\t\twatchers.push(watchPublic(current));\n\t\t}\n\t};\n\n\tconst stopWatchers = () => {\n\t\tfor (const fileWatcher of watchers) {\n\t\t\tfileWatcher.close();\n\t\t}\n\t\twatchers = [];\n\t};\n\n\tconst restart = async () => {\n\t\tlog.info(\n\t\t\t`Config changed, restarting ${styleText('dim', path.relative(process.cwd(), current.loaded.file ?? ''))}`,\n\t\t);\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\ttry {\n\t\t\tcurrent = await setup(root, flags);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t\tawait start();\n\t};\n\n\tawait start();\n\tlog.info(\n\t\t`Watching for changes ${styleText('dim', '(press Ctrl+C to stop)')}`,\n\t);\n\n\tconst stop = async () => {\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\tprocess.exit(0);\n\t};\n\tprocess.once('SIGINT', stop);\n\tprocess.once('SIGTERM', stop);\n}\n\n/** Copies the public directory again when something in it changes. */\nfunction watchPublic(current: BuildSetup): FSWatcher {\n\tconst publicDir = current.publicDir as string;\n\tconst changed = new Set<string>();\n\tlet timer: NodeJS.Timeout | undefined;\n\n\tconst sync = async () => {\n\t\tconst names = [...changed];\n\t\tchanged.clear();\n\t\ttry {\n\t\t\t// what was deleted from the public directory goes from the output too\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!(await exists(path.join(publicDir, name)))) {\n\t\t\t\t\tawait fs.rm(path.join(current.outDir, name), {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait copyPublic(current);\n\t\t\tlog.success(\n\t\t\t\t`Copied ${styleText('dim', path.relative(process.cwd(), publicDir))}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t};\n\n\tconst publicWatcher = watch(\n\t\tpublicDir,\n\t\t{ recursive: true },\n\t\t(_event, name) => {\n\t\t\tif (name) {\n\t\t\t\tchanged.add(name);\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(sync, 50);\n\t\t},\n\t);\n\tpublicWatcher.on('error', () => publicWatcher.close());\n\tpublicWatcher.on('close', () => clearTimeout(timer));\n\treturn publicWatcher;\n}\n\nasync function prepareOutDir({ outDir, clean }: BuildSetup) {\n\tif (clean) {\n\t\tawait fs.rm(outDir, { recursive: true, force: true });\n\t}\n\tawait fs.mkdir(outDir, { recursive: true });\n}\n\n/**\n * Writes the compiled module and the rendered template, validated and\n * formatted after rendering when enabled.\n */\nasync function write(current: BuildSetup, compiled: CompiledTemplate) {\n\tconst { outDir, moduleFile, templateFile, context, loaded } = current;\n\n\tif (moduleFile) {\n\t\tawait fs.writeFile(path.join(outDir, moduleFile), compiled.source);\n\t}\n\tif (!templateFile) {\n\t\treturn;\n\t}\n\n\tconst file = path.join(outDir, templateFile);\n\tconst shown = path.relative(process.cwd(), file);\n\n\tlet start = performance.now();\n\tconst rendered = renderTemplate({\n\t\t...compiled,\n\t\tcontext,\n\t\tplugins: loaded.config.runtimePlugins,\n\t});\n\tlog.success(`Rendered in ${elapsed(start)}`);\n\n\tstart = performance.now();\n\tlet output: string;\n\ttry {\n\t\toutput = finalizeXml(current, rendered);\n\t} catch (error) {\n\t\t// written anyway, to look at where it is invalid\n\t\tawait fs.writeFile(file, rendered);\n\t\tthrow new CompileError(\n\t\t\t`The rendered template ${shown} is not valid XML: ${(error as Error).message}`,\n\t\t);\n\t}\n\tif (current.validate) {\n\t\tlog.success(\n\t\t\t`${current.format ? 'Formatted' : 'Validated'} in ${elapsed(start)}`,\n\t\t);\n\t}\n\n\tawait fs.writeFile(file, output);\n\tlog.success(`Written ${styleText('dim', shown)}`);\n}\n\n/** Validates and formats the rendered template, as enabled. */\nfunction finalizeXml({ validate, format }: BuildSetup, xml: string): string {\n\tif (!validate) {\n\t\treturn xml;\n\t}\n\tif (!format) {\n\t\tvalidateXml(xml);\n\t\treturn xml;\n\t}\n\treturn formatXml(xml);\n}\n\nfunction elapsed(start: number): string {\n\treturn styleText('yellow', `${Math.round(performance.now() - start)}ms`);\n}\n\n/** The message of the error and of everything that caused it. */\nfunction formatError(error: unknown): string {\n\tconst lines: string[] = [];\n\tlet current: unknown = error;\n\n\twhile (current !== undefined) {\n\t\tlines.push(\n\t\t\tcurrent instanceof Error\n\t\t\t\t? `${lines.length === 0 ? '' : 'Caused by: '}${current.message}`\n\t\t\t\t: String(current),\n\t\t);\n\t\tcurrent = current instanceof Error ? current.cause : undefined;\n\t}\n\n\treturn lines.join('\\n ');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["import { type FSWatcher, watch } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { styleText } from 'node:util';\nimport { cac } from 'cac';\nimport {\n\ttype CompiledTemplate,\n\tCompileError,\n\ttype CompileTemplateOptions,\n\tcompileTemplate,\n\ttype TemplateWatcher,\n\twatchTemplate,\n} from '../compiler';\nimport {\n\tassertCompileOptions,\n\tassertDirectories,\n\tassertEntry,\n\tCOMPILE_OPTION_KEYS,\n\tresolvePaths,\n} from '../compiler/options';\nimport { type LoadedConfig, loadConfig } from '../config';\nimport { renderTemplate } from '../runtime';\nimport { didYouMean } from '../utils/suggest';\nimport { formatXml, validateXml } from '../xml';\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as {\n\tversion: string;\n};\n\ninterface BuildFlags {\n\tconfig?: string;\n\tentry?: string;\n\toutDir?: string;\n\tcontext?: string;\n\twatch?: boolean;\n\tclean?: boolean;\n\tvalidate?: boolean;\n\tformat?: boolean;\n}\n\n/** Everything a build needs, from the config file and the flags. */\ninterface BuildSetup {\n\toptions: CompileTemplateOptions;\n\tloaded: LoadedConfig;\n\toutDir: string;\n\tmoduleFile: string | false;\n\ttemplateFile: string | false;\n\tclean: boolean;\n\tcontext: unknown;\n\t/** Whether the rendered template is validated. */\n\tvalidate: boolean;\n\t/** Whether the rendered template is formatted, only when it is validated. */\n\tformat: boolean;\n\t/** Absolute path of the public directory, undefined when nothing is copied. */\n\tpublicDir?: string;\n}\n\nconst log = {\n\tsuccess: (message: string) =>\n\t\tconsole.log(`${styleText('green', '✓')} ${message}`),\n\tinfo: (message: string) =>\n\t\tconsole.log(`${styleText('cyan', '●')} ${message}`),\n\terror: (error: unknown) =>\n\t\tconsole.error(`${styleText('red', '✗')} ${formatError(error)}`),\n};\n\nconst cli = cac('blogger-hbs');\n\ncli\n\t.command('[root]', 'Compile the template and render it')\n\t.alias('build')\n\t.option('-c, --config <file>', 'Path of the config file')\n\t.option('--entry <file>', 'Entry template, relative to the root')\n\t.option('-o, --out-dir <dir>', 'Output directory, relative to the root')\n\t.option('--context <file>', 'JSON file with the context to render with')\n\t.option('-w, --watch', 'Build again when a file changes')\n\t.option('--no-clean', 'Keep the files in the output directory')\n\t.option(\n\t\t'--no-validate',\n\t\t'Do not validate the rendered template as XML, nor format it (templates are always validated when compiling)',\n\t)\n\t.option('--no-format', 'Do not format the rendered template')\n\t.example('blogger-hbs')\n\t.example('blogger-hbs --watch')\n\t.example('blogger-hbs ./theme --entry src/index.hbs --context context.json')\n\t.action(async (rootArg: unknown, rawFlags: BuildFlags) => {\n\t\t// cac turns values that look like numbers into numbers, `-o 2024`\n\t\tconst text = (value: unknown) =>\n\t\t\tvalue === undefined ? undefined : String(value);\n\t\tconst root = text(rootArg);\n\t\tconst flags: BuildFlags = {\n\t\t\t...rawFlags,\n\t\t\tconfig: text(rawFlags.config),\n\t\t\tentry: text(rawFlags.entry),\n\t\t\toutDir: text(rawFlags.outDir),\n\t\t\tcontext: text(rawFlags.context),\n\t\t};\n\n\t\tif (flags.watch) {\n\t\t\tawait watchBuild(root, flags);\n\t\t} else {\n\t\t\tawait build(root, flags);\n\t\t}\n\t});\n\ncli.help();\ncli.version(version);\n\ntry {\n\tcli.parse(process.argv, { run: false });\n\tawait cli.runMatchedCommand();\n} catch (error) {\n\tlog.error(describeCliError(error));\n\tprocess.exitCode = 1;\n}\n\n/** Adds a suggestion and a pointer to `--help` to the errors of the arguments. */\nfunction describeCliError(error: unknown): unknown {\n\tif (!(error instanceof Error) || error.name !== 'CACError') {\n\t\treturn error;\n\t}\n\n\tconst flags = [...cli.commands, cli.globalCommand].flatMap((command) =>\n\t\tcommand.options.flatMap(\n\t\t\t(option) => option.rawName.match(/--[\\w-]+/g) ?? [],\n\t\t),\n\t);\n\tconst unknown = /^Unknown option `(.+)`$/.exec(error.message)?.[1];\n\tconst hint = unknown ? didYouMean(unknown, flags, '`') : '';\n\n\treturn new Error(\n\t\t`${error.message}${hint}\\n Run 'blogger-hbs --help' to see the available options`,\n\t);\n}\n\nasync function setup(\n\troot: string | undefined,\n\tflags: BuildFlags,\n): Promise<BuildSetup> {\n\tconst cwd = process.cwd();\n\tconst rootFlag = root === undefined ? undefined : path.resolve(cwd, root);\n\n\t// the config is looked for in the root, which has to exist for that\n\tif (rootFlag !== undefined && !(await isDirectory(rootFlag))) {\n\t\tthrow new CompileError(\n\t\t\t(await exists(rootFlag))\n\t\t\t\t? `Root '${root}' is not a directory`\n\t\t\t\t: `Root directory '${root}' does not exist`,\n\t\t);\n\t}\n\n\tconst loaded = await loadConfig({\n\t\tcwd: rootFlag ?? cwd,\n\t\tconfigFile: flags.config && path.resolve(cwd, flags.config),\n\t\twatch: flags.watch,\n\t});\n\tconst { config } = loaded;\n\n\tconst resolvedRoot = rootFlag ?? config.root ?? cwd;\n\tconst output = config.output ?? {};\n\n\t// the config has more than the compile options\n\tconst options: CompileTemplateOptions = {};\n\tfor (const key of COMPILE_OPTION_KEYS) {\n\t\tif (key in config) {\n\t\t\tObject.assign(options, { [key]: config[key as keyof typeof config] });\n\t\t}\n\t}\n\toptions.root = resolvedRoot;\n\toptions.entry = flags.entry ?? config.entry;\n\n\tassertCompileOptions(options);\n\tawait assertDirectories(options);\n\n\tconst outDir = path.resolve(\n\t\tresolvedRoot,\n\t\tflags.outDir ?? output.dir ?? 'dist',\n\t);\n\tconst clean = flags.clean === false ? false : (output.clean ?? true);\n\tif (clean) {\n\t\tassertCleanable(outDir, options);\n\t}\n\n\treturn {\n\t\toptions,\n\t\tloaded,\n\t\toutDir,\n\t\tmoduleFile: output.module ?? 'compiled.js',\n\t\ttemplateFile: output.template ?? 'theme.xml',\n\t\tvalidate: flags.validate === false ? false : (output.validate ?? true),\n\t\tformat: flags.format === false ? false : (output.format ?? true),\n\t\tclean,\n\t\tcontext: flags.context\n\t\t\t? await readContext(path.resolve(cwd, flags.context), flags.context)\n\t\t\t: (config.context ?? {}),\n\t\tpublicDir: await resolvePublicDir(resolvedRoot, config.publicDir, outDir),\n\t};\n}\n\n/** The public directory, `public` when it exists unless set otherwise. */\nasync function resolvePublicDir(\n\troot: string,\n\tvalue: string | false | undefined,\n\toutDir: string,\n): Promise<string | undefined> {\n\tif (value === false) {\n\t\treturn;\n\t}\n\n\tconst directory = path.resolve(root, value ?? 'public');\n\tconst shown = path.relative(process.cwd(), directory) || '.';\n\n\tif (!(await isDirectory(directory))) {\n\t\tif (value === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new CompileError(\n\t\t\t(await exists(directory))\n\t\t\t\t? `Public directory '${shown}' is not a directory`\n\t\t\t\t: `Public directory '${shown}' does not exist, set 'publicDir: false' to not copy anything`,\n\t\t);\n\t}\n\n\tif (isInside(outDir, directory) || isInside(directory, outDir)) {\n\t\tthrow new CompileError(\n\t\t\t`Public directory '${shown}' and output directory '${path.relative(process.cwd(), outDir) || '.'}' overlap, they must be separate`,\n\t\t);\n\t}\n\n\treturn directory;\n}\n\n/** Copies the public directory into the output directory. */\nasync function copyPublic({\n\tpublicDir,\n\toutDir,\n\tmoduleFile,\n\ttemplateFile,\n}: BuildSetup): Promise<void> {\n\tif (publicDir === undefined) {\n\t\treturn;\n\t}\n\n\tfor (const name of await fs.readdir(publicDir)) {\n\t\tif (name === moduleFile || name === templateFile) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`'${name}' in the public directory has the same name as the ${name === moduleFile ? 'compiled module' : 'rendered template'} and would be overwritten, rename it or set 'output.${name === moduleFile ? 'module' : 'template'}'`,\n\t\t\t);\n\t\t}\n\t}\n\n\tawait fs.cp(publicDir, outDir, { recursive: true, force: true });\n}\n\n/**\n * Cleaning removes everything in the output directory, which must not hold the\n * project: the root, the working directory, the templates or an alias target.\n */\nfunction assertCleanable(outDir: string, options: CompileTemplateOptions) {\n\tconst { root, entry } = resolvePaths(options);\n\tconst shown = path.relative(process.cwd(), outDir) || '.';\n\tconst kept: [string, string][] = [\n\t\t[root, 'the root'],\n\t\t[process.cwd(), 'the working directory'],\n\t\t[path.dirname(entry), 'the entry template'],\n\t\t...Object.entries(options.alias ?? {}).map(\n\t\t\t([name, target]): [string, string] => [\n\t\t\t\tpath.resolve(root, target),\n\t\t\t\t`the target of the alias '${name}'`,\n\t\t\t],\n\t\t),\n\t];\n\n\tfor (const [directory, what] of kept) {\n\t\tif (isInside(directory, outDir)) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`Refusing to clean the output directory '${shown}', it contains ${what}. Use another output directory, or --no-clean / 'output.clean: false' to keep its files`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function readContext(file: string, shown: string): Promise<unknown> {\n\tlet text: string;\n\ttry {\n\t\ttext = await fs.readFile(file, 'utf-8');\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\tthrow new CompileError(`Context file '${shown}' not found`);\n\t\t}\n\t\tthrow new CompileError(`Failed to read the context file '${shown}'`, {\n\t\t\tcause,\n\t\t});\n\t}\n\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch (cause) {\n\t\tthrow new CompileError(\n\t\t\t`Context file '${shown}' is not valid JSON: ${(cause as Error).message}`,\n\t\t);\n\t}\n}\n\n/** Whether `child` is `parent` or inside of it. */\nfunction isInside(child: string, parent: string): boolean {\n\tconst relative = path.relative(parent, child);\n\treturn (\n\t\trelative === '' ||\n\t\t(!relative.startsWith('..') && !path.isAbsolute(relative))\n\t);\n}\n\nasync function exists(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t() => true,\n\t\t() => false,\n\t);\n}\n\nasync function isDirectory(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t(stats) => stats.isDirectory(),\n\t\t() => false,\n\t);\n}\n\nasync function build(root: string | undefined, flags: BuildFlags) {\n\tconst current = await setup(root, flags);\n\t// nothing is written when there is nothing to build, the watcher waits\n\t// for the entry instead\n\tawait assertEntry(current.options);\n\tawait prepareOutDir(current);\n\tawait copyPublic(current);\n\n\tconst start = performance.now();\n\tconst compiled = await compileTemplate(current.options);\n\tlog.success(`Compiled in ${elapsed(start)}`);\n\n\tawait write(current, compiled);\n}\n\n/** Watches the template and restarts when the config file changes. */\nasync function watchBuild(root: string | undefined, flags: BuildFlags) {\n\tlet current = await setup(root, flags);\n\tawait prepareOutDir(current);\n\n\tlet watcher: TemplateWatcher | undefined;\n\tlet watchers: FSWatcher[] = [];\n\tlet restarting = false;\n\n\tconst start = async () => {\n\t\tlet begin = performance.now();\n\t\tlet first = true;\n\n\t\ttry {\n\t\t\tawait copyPublic(current);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\n\t\twatcher = await watchTemplate({\n\t\t\t...current.options,\n\t\t\tonStart() {\n\t\t\t\tbegin = performance.now();\n\t\t\t},\n\t\t\tasync onCompile(compiled) {\n\t\t\t\tlog.success(`${first ? 'Compiled' : 'Rebuilt'} in ${elapsed(begin)}`);\n\t\t\t\tfirst = false;\n\t\t\t\tawait write(current, compiled);\n\t\t\t},\n\t\t\tonError: log.error,\n\t\t});\n\n\t\tfor (const file of current.loaded.dependencies) {\n\t\t\ttry {\n\t\t\t\tconst configWatcher = watch(file, () => {\n\t\t\t\t\t// saving fires more than once, a restart is enough\n\t\t\t\t\tif (!restarting) {\n\t\t\t\t\t\trestarting = true;\n\t\t\t\t\t\trestart().finally(() => {\n\t\t\t\t\t\t\trestarting = false;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconfigWatcher.on('error', () => configWatcher.close());\n\t\t\t\twatchers.push(configWatcher);\n\t\t\t} catch {\n\t\t\t\t// a dependency that can not be watched does not restart\n\t\t\t}\n\t\t}\n\n\t\tif (current.publicDir !== undefined) {\n\t\t\twatchers.push(watchPublic(current));\n\t\t}\n\t};\n\n\tconst stopWatchers = () => {\n\t\tfor (const fileWatcher of watchers) {\n\t\t\tfileWatcher.close();\n\t\t}\n\t\twatchers = [];\n\t};\n\n\tconst restart = async () => {\n\t\tlog.info(\n\t\t\t`Config changed, restarting ${styleText('dim', path.relative(process.cwd(), current.loaded.file ?? ''))}`,\n\t\t);\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\ttry {\n\t\t\tcurrent = await setup(root, flags);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t\tawait start();\n\t};\n\n\tawait start();\n\tlog.info(\n\t\t`Watching for changes ${styleText('dim', '(press Ctrl+C to stop)')}`,\n\t);\n\n\tconst stop = async () => {\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\tprocess.exit(0);\n\t};\n\tprocess.once('SIGINT', stop);\n\tprocess.once('SIGTERM', stop);\n}\n\n/** Copies the public directory again when something in it changes. */\nfunction watchPublic(current: BuildSetup): FSWatcher {\n\tconst publicDir = current.publicDir as string;\n\tconst changed = new Set<string>();\n\tlet timer: NodeJS.Timeout | undefined;\n\n\tconst sync = async () => {\n\t\tconst names = [...changed];\n\t\tchanged.clear();\n\t\ttry {\n\t\t\t// what was deleted from the public directory goes from the output too\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!(await exists(path.join(publicDir, name)))) {\n\t\t\t\t\tawait fs.rm(path.join(current.outDir, name), {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait copyPublic(current);\n\t\t\tlog.success(\n\t\t\t\t`Copied ${styleText('dim', path.relative(process.cwd(), publicDir))}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t};\n\n\tconst publicWatcher = watch(\n\t\tpublicDir,\n\t\t{ recursive: true },\n\t\t(_event, name) => {\n\t\t\tif (name) {\n\t\t\t\tchanged.add(name);\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(sync, 50);\n\t\t},\n\t);\n\tpublicWatcher.on('error', () => publicWatcher.close());\n\tpublicWatcher.on('close', () => clearTimeout(timer));\n\treturn publicWatcher;\n}\n\nasync function prepareOutDir({ outDir, clean }: BuildSetup) {\n\tif (clean) {\n\t\tawait fs.rm(outDir, { recursive: true, force: true });\n\t}\n\tawait fs.mkdir(outDir, { recursive: true });\n}\n\n/**\n * Writes the compiled module and the rendered template, validated and\n * formatted after rendering when enabled.\n */\nasync function write(current: BuildSetup, compiled: CompiledTemplate) {\n\tconst { outDir, moduleFile, templateFile, context, loaded } = current;\n\n\tif (moduleFile) {\n\t\tawait fs.writeFile(path.join(outDir, moduleFile), compiled.source);\n\t}\n\tif (!templateFile) {\n\t\treturn;\n\t}\n\n\tconst file = path.join(outDir, templateFile);\n\tconst shown = path.relative(process.cwd(), file);\n\n\tlet start = performance.now();\n\tconst rendered = renderTemplate({\n\t\t...compiled,\n\t\tcontext,\n\t\tplugins: loaded.config.runtimePlugins,\n\t});\n\tlog.success(`Rendered in ${elapsed(start)}`);\n\n\tstart = performance.now();\n\tlet output: string;\n\ttry {\n\t\toutput = finalizeXml(current, rendered);\n\t} catch (error) {\n\t\t// written anyway, to look at where it is invalid\n\t\tawait fs.writeFile(file, rendered);\n\t\tthrow new CompileError(\n\t\t\t`The rendered template ${shown} is not valid XML: ${(error as Error).message}`,\n\t\t);\n\t}\n\tif (current.validate) {\n\t\tlog.success(\n\t\t\t`${current.format ? 'Formatted' : 'Validated'} in ${elapsed(start)}`,\n\t\t);\n\t}\n\n\tawait fs.writeFile(file, output);\n\tlog.success(`Written ${styleText('dim', shown)}`);\n}\n\n/** Validates and formats the rendered template, as enabled. */\nfunction finalizeXml({ validate, format }: BuildSetup, xml: string): string {\n\tif (!validate) {\n\t\treturn xml;\n\t}\n\tif (!format) {\n\t\tvalidateXml(xml);\n\t\treturn xml;\n\t}\n\treturn formatXml(xml);\n}\n\nfunction elapsed(start: number): string {\n\treturn styleText('yellow', `${Math.round(performance.now() - start)}ms`);\n}\n\n/** The message of the error and of everything that caused it. */\nfunction formatError(error: unknown): string {\n\tconst lines: string[] = [];\n\tlet current: unknown = error;\n\n\twhile (current !== undefined) {\n\t\tlines.push(\n\t\t\tcurrent instanceof Error\n\t\t\t\t? `${lines.length === 0 ? '' : 'Caused by: '}${current.message}`\n\t\t\t\t: String(current),\n\t\t);\n\t\tcurrent = current instanceof Error ? current.cause : undefined;\n\t}\n\n\treturn lines.join('\\n ');\n}\n"],"mappings":"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"}
|
|
@@ -13,6 +13,10 @@ import { Program } from "./ast/types.js";
|
|
|
13
13
|
* `{{#import}}`, `{{#> partial}}` and `{{#*inline}}` blocks is rendered
|
|
14
14
|
* elsewhere and checked on its own.
|
|
15
15
|
*
|
|
16
|
+
* Blogger expressions (`b:if cond`, `expr:*`, ...) are parsed with
|
|
17
|
+
* `blogger-expr`, unless their value has Handlebars in it, which is only known
|
|
18
|
+
* once rendered.
|
|
19
|
+
*
|
|
16
20
|
* `label` names the template in errors, a path or the name of a partial.
|
|
17
21
|
*/
|
|
18
22
|
export declare function validateTemplateXml(program: Program, label: string): void;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CompileError } from "./errors.js";
|
|
2
2
|
import { XmlScanner, XmlSyntaxError, createLocate } from "../xml/scanner.js";
|
|
3
|
+
import { parseExpressions } from "../xml/expressions.js";
|
|
4
|
+
import "../xml/index.js";
|
|
3
5
|
//#region src/compiler/validate.ts
|
|
4
6
|
/** Helpers rendering one of their branches once, which can each open a tag. */
|
|
5
7
|
const CONDITIONALS = /* @__PURE__ */ new Set(["if", "unless"]);
|
|
@@ -16,6 +18,10 @@ const CONDITIONALS = /* @__PURE__ */ new Set(["if", "unless"]);
|
|
|
16
18
|
* `{{#import}}`, `{{#> partial}}` and `{{#*inline}}` blocks is rendered
|
|
17
19
|
* elsewhere and checked on its own.
|
|
18
20
|
*
|
|
21
|
+
* Blogger expressions (`b:if cond`, `expr:*`, ...) are parsed with
|
|
22
|
+
* `blogger-expr`, unless their value has Handlebars in it, which is only known
|
|
23
|
+
* once rendered.
|
|
24
|
+
*
|
|
19
25
|
* `label` names the template in errors, a path or the name of a partial.
|
|
20
26
|
*/
|
|
21
27
|
function validateTemplateXml(program, label) {
|
|
@@ -30,7 +36,7 @@ function validateTemplateXml(program, label) {
|
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
function validateFragment(program) {
|
|
33
|
-
const scanner = new XmlScanner();
|
|
39
|
+
const scanner = new XmlScanner({ onStartTag: parseExpressions });
|
|
34
40
|
visitProgram(program, scanner);
|
|
35
41
|
scanner.finish(endOf(program));
|
|
36
42
|
}
|
|
@@ -91,6 +97,7 @@ function visitBlock(block, scanner) {
|
|
|
91
97
|
}
|
|
92
98
|
} else for (const [end, part] of [[program, "content"], [inverse, "'{{else}}' content"]]) if (end.signature() !== start.signature()) throw new XmlSyntaxError(`The ${part} of the ${label} has to keep the XML as it is, but it ${end.describeChange(start)}`, startOf(block));
|
|
93
99
|
scanner.assign(program);
|
|
100
|
+
scanner.dynamicValue();
|
|
94
101
|
}
|
|
95
102
|
function locateContent(content) {
|
|
96
103
|
const offset = Math.max(0, content.original.indexOf(content.value));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","names":[],"sources":["../../src/compiler/validate.ts"],"sourcesContent":["import {\n\tcreateLocate,\n\ttype Locate,\n\ttype Position,\n\tXmlScanner,\n\tXmlSyntaxError,\n} from '../xml';\nimport type {\n\tBlockStatement,\n\tContentStatement,\n\tNode,\n\tProgram,\n\tStatement,\n} from './ast';\nimport { CompileError } from './errors';\n\n/** Helpers rendering one of their branches once, which can each open a tag. */\nconst CONDITIONALS = new Set(['if', 'unless']);\n\n/**\n * Checks that the XML of a template is well-formed, taking its Handlebars\n * syntax into account. The template is a fragment: anything can be at its\n * top, but every tag it opens has to be closed.\n *\n * Expressions are accepted where their output can be valid XML: in text, in\n * quoted attribute values and between attributes. The content of a block has\n * to leave the XML as it found it, except for the branches of `{{#if}}` and\n * `{{#unless}}`, which can each open the same tag, so\n * `{{#if a}}<div class='a'>{{else}}<div>{{/if}}` is fine. The content of\n * `{{#import}}`, `{{#> partial}}` and `{{#*inline}}` blocks is rendered\n * elsewhere and checked on its own.\n *\n * `label` names the template in errors, a path or the name of a partial.\n */\nexport function validateTemplateXml(program: Program, label: string): void {\n\ttry {\n\t\tvalidateFragment(program);\n\t} catch (error) {\n\t\tif (error instanceof XmlSyntaxError) {\n\t\t\tconst { line, column } = error.position;\n\t\t\tthrow new CompileError(\n\t\t\t\t`Invalid XML in ${label}:${line}:${column + 1}, ${lowerFirst(error.message)}`,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction validateFragment(program: Program): void {\n\tconst scanner = new XmlScanner();\n\tvisitProgram(program, scanner);\n\tscanner.finish(endOf(program));\n}\n\nfunction visitProgram(program: Program, scanner: XmlScanner): void {\n\tfor (const statement of program.body) {\n\t\tvisitStatement(statement, scanner);\n\t}\n}\n\nfunction visitStatement(statement: Statement, scanner: XmlScanner): void {\n\t// decorators are not in the typings\n\tconst type = statement.type as\n\t\t| Statement['type']\n\t\t| 'Decorator'\n\t\t| 'DecoratorBlock';\n\n\tswitch (type) {\n\t\tcase 'ContentStatement': {\n\t\t\tconst content = statement as ContentStatement;\n\t\t\tscanner.feed(content.value, locateContent(content));\n\t\t\treturn;\n\t\t}\n\n\t\tcase 'CommentStatement':\n\t\tcase 'Decorator':\n\t\t\treturn;\n\n\t\tcase 'DecoratorBlock':\n\t\t\t// `{{#*inline \"name\"}}` defines a partial, it renders nothing here\n\t\t\tvalidateFragment((statement as BlockStatement).program);\n\t\t\treturn;\n\n\t\tcase 'MustacheStatement':\n\t\tcase 'PartialStatement':\n\t\t\tscanner.unknown(startOf(statement), describe(statement));\n\t\t\treturn;\n\n\t\tcase 'PartialBlockStatement':\n\t\t\tif (statement.type === 'PartialBlockStatement') {\n\t\t\t\tvalidateFragment(statement.program);\n\t\t\t}\n\t\t\tscanner.unknown(startOf(statement), describe(statement));\n\t\t\treturn;\n\n\t\tcase 'BlockStatement':\n\t\t\tvisitBlock(statement as BlockStatement, scanner);\n\t\t\treturn;\n\t}\n}\n\nfunction visitBlock(block: BlockStatement, scanner: XmlScanner): void {\n\tconst name = block.path.original;\n\n\t// rendered by the imported template, in place of `{{> @partial-block}}`\n\tif (name === 'import') {\n\t\tvalidateFragment(block.program);\n\t\tscanner.unknown(startOf(block), describe(block));\n\t\treturn;\n\t}\n\n\tconst start = scanner.clone();\n\tconst program = start.clone();\n\tvisitProgram(block.program, program);\n\n\tconst inverse = start.clone();\n\tif (block.inverse) {\n\t\tvisitProgram(block.inverse, inverse);\n\t}\n\n\tconst label = `'{{#${name}}}' block`;\n\n\tif (CONDITIONALS.has(name)) {\n\t\t// one of the branches renders, so they only have to end the same way\n\t\tif (program.signature() !== inverse.signature()) {\n\t\t\tconst [changed, part, other] =\n\t\t\t\tprogram.signature() !== start.signature()\n\t\t\t\t\t? [program, 'content', \"its '{{else}}' content\"]\n\t\t\t\t\t: [inverse, \"'{{else}}' content\", 'its content'];\n\t\t\tconst fix = block.inverse\n\t\t\t\t? `${other} has to do the same`\n\t\t\t\t: `close it inside the block, or add an '{{else}}' doing the same`;\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`The ${part} of the ${label} ${changed.describeChange(start)}, ${fix}`,\n\t\t\t\tstartOf(block),\n\t\t\t);\n\t\t}\n\t} else {\n\t\t// a block can render its content any number of times\n\t\tfor (const [end, part] of [\n\t\t\t[program, 'content'],\n\t\t\t[inverse, \"'{{else}}' content\"],\n\t\t] as const) {\n\t\t\tif (end.signature() !== start.signature()) {\n\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t`The ${part} of the ${label} has to keep the XML as it is, but it ${end.describeChange(start)}`,\n\t\t\t\t\tstartOf(block),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tscanner.assign(program);\n}\n\nfunction locateContent(content: ContentStatement): Locate {\n\t// stripped whitespace is left out of the value, the location is of the original\n\tconst offset = Math.max(0, content.original.indexOf(content.value));\n\tconst locate = createLocate(content.original, startOf(content));\n\treturn (index) => locate(offset + index);\n}\n\nfunction startOf(node: Node): Position {\n\tconst start = node.loc?.start;\n\treturn start\n\t\t? { line: start.line, column: start.column }\n\t\t: { line: 1, column: 0 };\n}\n\nfunction endOf(node: Node): Position {\n\tconst end = node.loc?.end;\n\treturn end ? { line: end.line, column: end.column } : { line: 1, column: 0 };\n}\n\nfunction describe(statement: Statement): string {\n\tswitch (statement.type) {\n\t\tcase 'MustacheStatement':\n\t\t\treturn `'{{${expression(statement.path)}}}'`;\n\t\tcase 'PartialStatement':\n\t\t\treturn `'{{> ${expression(statement.name)}}}'`;\n\t\tcase 'PartialBlockStatement':\n\t\t\treturn `'{{#> ${expression(statement.name)}}}'`;\n\t\tcase 'BlockStatement':\n\t\t\treturn `'{{#${statement.path.original}}}'`;\n\t\tdefault:\n\t\t\treturn 'The expression';\n\t}\n}\n\nfunction expression(node: Node): string {\n\tif (node.type === 'PathExpression') {\n\t\treturn node.original;\n\t}\n\tif (node.type === 'SubExpression') {\n\t\treturn `(${node.path.original} ...)`;\n\t}\n\treturn 'value' in node ? String(node.value) : node.type;\n}\n\nfunction lowerFirst(text: string): string {\n\treturn /^[A-Z][a-z]/.test(text)\n\t\t? text[0].toLowerCase() + text.slice(1)\n\t\t: text;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.js","names":[],"sources":["../../src/compiler/validate.ts"],"sourcesContent":["import {\n\tcreateLocate,\n\ttype Locate,\n\ttype Position,\n\tparseExpressions,\n\tXmlScanner,\n\tXmlSyntaxError,\n} from '../xml';\nimport type {\n\tBlockStatement,\n\tContentStatement,\n\tNode,\n\tProgram,\n\tStatement,\n} from './ast';\nimport { CompileError } from './errors';\n\n/** Helpers rendering one of their branches once, which can each open a tag. */\nconst CONDITIONALS = new Set(['if', 'unless']);\n\n/**\n * Checks that the XML of a template is well-formed, taking its Handlebars\n * syntax into account. The template is a fragment: anything can be at its\n * top, but every tag it opens has to be closed.\n *\n * Expressions are accepted where their output can be valid XML: in text, in\n * quoted attribute values and between attributes. The content of a block has\n * to leave the XML as it found it, except for the branches of `{{#if}}` and\n * `{{#unless}}`, which can each open the same tag, so\n * `{{#if a}}<div class='a'>{{else}}<div>{{/if}}` is fine. The content of\n * `{{#import}}`, `{{#> partial}}` and `{{#*inline}}` blocks is rendered\n * elsewhere and checked on its own.\n *\n * Blogger expressions (`b:if cond`, `expr:*`, ...) are parsed with\n * `blogger-expr`, unless their value has Handlebars in it, which is only known\n * once rendered.\n *\n * `label` names the template in errors, a path or the name of a partial.\n */\nexport function validateTemplateXml(program: Program, label: string): void {\n\ttry {\n\t\tvalidateFragment(program);\n\t} catch (error) {\n\t\tif (error instanceof XmlSyntaxError) {\n\t\t\tconst { line, column } = error.position;\n\t\t\tthrow new CompileError(\n\t\t\t\t`Invalid XML in ${label}:${line}:${column + 1}, ${lowerFirst(error.message)}`,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction validateFragment(program: Program): void {\n\tconst scanner = new XmlScanner({\n\t\tonStartTag: parseExpressions,\n\t});\n\tvisitProgram(program, scanner);\n\tscanner.finish(endOf(program));\n}\n\nfunction visitProgram(program: Program, scanner: XmlScanner): void {\n\tfor (const statement of program.body) {\n\t\tvisitStatement(statement, scanner);\n\t}\n}\n\nfunction visitStatement(statement: Statement, scanner: XmlScanner): void {\n\t// decorators are not in the typings\n\tconst type = statement.type as\n\t\t| Statement['type']\n\t\t| 'Decorator'\n\t\t| 'DecoratorBlock';\n\n\tswitch (type) {\n\t\tcase 'ContentStatement': {\n\t\t\tconst content = statement as ContentStatement;\n\t\t\tscanner.feed(content.value, locateContent(content));\n\t\t\treturn;\n\t\t}\n\n\t\tcase 'CommentStatement':\n\t\tcase 'Decorator':\n\t\t\treturn;\n\n\t\tcase 'DecoratorBlock':\n\t\t\t// `{{#*inline \"name\"}}` defines a partial, it renders nothing here\n\t\t\tvalidateFragment((statement as BlockStatement).program);\n\t\t\treturn;\n\n\t\tcase 'MustacheStatement':\n\t\tcase 'PartialStatement':\n\t\t\tscanner.unknown(startOf(statement), describe(statement));\n\t\t\treturn;\n\n\t\tcase 'PartialBlockStatement':\n\t\t\tif (statement.type === 'PartialBlockStatement') {\n\t\t\t\tvalidateFragment(statement.program);\n\t\t\t}\n\t\t\tscanner.unknown(startOf(statement), describe(statement));\n\t\t\treturn;\n\n\t\tcase 'BlockStatement':\n\t\t\tvisitBlock(statement as BlockStatement, scanner);\n\t\t\treturn;\n\t}\n}\n\nfunction visitBlock(block: BlockStatement, scanner: XmlScanner): void {\n\tconst name = block.path.original;\n\n\t// rendered by the imported template, in place of `{{> @partial-block}}`\n\tif (name === 'import') {\n\t\tvalidateFragment(block.program);\n\t\tscanner.unknown(startOf(block), describe(block));\n\t\treturn;\n\t}\n\n\tconst start = scanner.clone();\n\tconst program = start.clone();\n\tvisitProgram(block.program, program);\n\n\tconst inverse = start.clone();\n\tif (block.inverse) {\n\t\tvisitProgram(block.inverse, inverse);\n\t}\n\n\tconst label = `'{{#${name}}}' block`;\n\n\tif (CONDITIONALS.has(name)) {\n\t\t// one of the branches renders, so they only have to end the same way\n\t\tif (program.signature() !== inverse.signature()) {\n\t\t\tconst [changed, part, other] =\n\t\t\t\tprogram.signature() !== start.signature()\n\t\t\t\t\t? [program, 'content', \"its '{{else}}' content\"]\n\t\t\t\t\t: [inverse, \"'{{else}}' content\", 'its content'];\n\t\t\tconst fix = block.inverse\n\t\t\t\t? `${other} has to do the same`\n\t\t\t\t: `close it inside the block, or add an '{{else}}' doing the same`;\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`The ${part} of the ${label} ${changed.describeChange(start)}, ${fix}`,\n\t\t\t\tstartOf(block),\n\t\t\t);\n\t\t}\n\t} else {\n\t\t// a block can render its content any number of times\n\t\tfor (const [end, part] of [\n\t\t\t[program, 'content'],\n\t\t\t[inverse, \"'{{else}}' content\"],\n\t\t] as const) {\n\t\t\tif (end.signature() !== start.signature()) {\n\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t`The ${part} of the ${label} has to keep the XML as it is, but it ${end.describeChange(start)}`,\n\t\t\t\t\tstartOf(block),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tscanner.assign(program);\n\t// a block in an attribute value renders what is in it, or not\n\tscanner.dynamicValue();\n}\n\nfunction locateContent(content: ContentStatement): Locate {\n\t// stripped whitespace is left out of the value, the location is of the original\n\tconst offset = Math.max(0, content.original.indexOf(content.value));\n\tconst locate = createLocate(content.original, startOf(content));\n\treturn (index) => locate(offset + index);\n}\n\nfunction startOf(node: Node): Position {\n\tconst start = node.loc?.start;\n\treturn start\n\t\t? { line: start.line, column: start.column }\n\t\t: { line: 1, column: 0 };\n}\n\nfunction endOf(node: Node): Position {\n\tconst end = node.loc?.end;\n\treturn end ? { line: end.line, column: end.column } : { line: 1, column: 0 };\n}\n\nfunction describe(statement: Statement): string {\n\tswitch (statement.type) {\n\t\tcase 'MustacheStatement':\n\t\t\treturn `'{{${expression(statement.path)}}}'`;\n\t\tcase 'PartialStatement':\n\t\t\treturn `'{{> ${expression(statement.name)}}}'`;\n\t\tcase 'PartialBlockStatement':\n\t\t\treturn `'{{#> ${expression(statement.name)}}}'`;\n\t\tcase 'BlockStatement':\n\t\t\treturn `'{{#${statement.path.original}}}'`;\n\t\tdefault:\n\t\t\treturn 'The expression';\n\t}\n}\n\nfunction expression(node: Node): string {\n\tif (node.type === 'PathExpression') {\n\t\treturn node.original;\n\t}\n\tif (node.type === 'SubExpression') {\n\t\treturn `(${node.path.original} ...)`;\n\t}\n\treturn 'value' in node ? String(node.value) : node.type;\n}\n\nfunction lowerFirst(text: string): string {\n\treturn /^[A-Z][a-z]/.test(text)\n\t\t? text[0].toLowerCase() + text.slice(1)\n\t\t: text;\n}\n"],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface OutputConfig {
|
|
|
17
17
|
clean?: boolean;
|
|
18
18
|
/**
|
|
19
19
|
* Whether the rendered template is validated as a Blogger theme: well-formed
|
|
20
|
-
* XML with the XML declaration and the doctype on the first two lines
|
|
20
|
+
* XML with the XML declaration and the doctype on the first two lines, and
|
|
21
|
+
* expressions (`b:if cond`, `expr:*`, ...) that parse with `blogger-expr`.
|
|
21
22
|
* `true` by default, formatting needs it. The templates and the partials of
|
|
22
23
|
* plugins are validated when compiling either way.
|
|
23
24
|
*/
|
|
@@ -25,8 +26,10 @@ export interface OutputConfig {
|
|
|
25
26
|
/**
|
|
26
27
|
* Whether the rendered template is formatted as Blogger saves it: the
|
|
27
28
|
* declarations added or moved to the top, start tags on a single line, line
|
|
28
|
-
* breaks in attribute values collapsed
|
|
29
|
-
*
|
|
29
|
+
* breaks in attribute values collapsed, the attributes sorted and quoted
|
|
30
|
+
* with single quotes, and the
|
|
31
|
+
* expressions printed by `blogger-expr` on a single line. `true` by default,
|
|
32
|
+
* nothing is formatted without validation.
|
|
30
33
|
*/
|
|
31
34
|
format?: boolean;
|
|
32
35
|
}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":["loadRsbuildConfig"],"sources":["../src/config.ts"],"sourcesContent":["import * as path from 'node:path';\nimport { loadConfig as loadRsbuildConfig } from '@rsbuild/core';\nimport { CompileError, type CompileTemplateOptions } from './compiler';\nimport { assertCompileOptions, invalidOption } from './compiler/options';\nimport type { RuntimePlugin } from './plugins';\nimport { didYouMean, isPlainObject } from './utils/suggest';\n\nconst CONFIG_KEYS = ['context', 'runtimePlugins', 'output', 'publicDir'];\nconst OUTPUT_KEYS = [\n\t'dir',\n\t'module',\n\t'template',\n\t'clean',\n\t'validate',\n\t'format',\n];\n\nexport const CONFIG_FILE_NAMES = [\n\t'blogger-hbs.config.ts',\n\t'blogger-hbs.config.mts',\n\t'blogger-hbs.config.js',\n\t'blogger-hbs.config.mjs',\n];\n\nexport interface OutputConfig {\n\t/** Directory of the output, relative to the root, `dist` by default. */\n\tdir?: string;\n\t/**\n\t * File name of the compiled module (the ES module consumed by\n\t * `renderTemplate`), `compiled.js` by default, `false` to not write it.\n\t */\n\tmodule?: string | false;\n\t/** File name of the rendered template, `theme.xml` by default, `false` to not render it. */\n\ttemplate?: string | false;\n\t/** Whether the directory is emptied before the first build, `true` by default. */\n\tclean?: boolean;\n\t/**\n\t * Whether the rendered template is validated as a Blogger theme: well-formed\n\t * XML with the XML declaration and the doctype on the first two lines
|
|
1
|
+
{"version":3,"file":"config.js","names":["loadRsbuildConfig"],"sources":["../src/config.ts"],"sourcesContent":["import * as path from 'node:path';\nimport { loadConfig as loadRsbuildConfig } from '@rsbuild/core';\nimport { CompileError, type CompileTemplateOptions } from './compiler';\nimport { assertCompileOptions, invalidOption } from './compiler/options';\nimport type { RuntimePlugin } from './plugins';\nimport { didYouMean, isPlainObject } from './utils/suggest';\n\nconst CONFIG_KEYS = ['context', 'runtimePlugins', 'output', 'publicDir'];\nconst OUTPUT_KEYS = [\n\t'dir',\n\t'module',\n\t'template',\n\t'clean',\n\t'validate',\n\t'format',\n];\n\nexport const CONFIG_FILE_NAMES = [\n\t'blogger-hbs.config.ts',\n\t'blogger-hbs.config.mts',\n\t'blogger-hbs.config.js',\n\t'blogger-hbs.config.mjs',\n];\n\nexport interface OutputConfig {\n\t/** Directory of the output, relative to the root, `dist` by default. */\n\tdir?: string;\n\t/**\n\t * File name of the compiled module (the ES module consumed by\n\t * `renderTemplate`), `compiled.js` by default, `false` to not write it.\n\t */\n\tmodule?: string | false;\n\t/** File name of the rendered template, `theme.xml` by default, `false` to not render it. */\n\ttemplate?: string | false;\n\t/** Whether the directory is emptied before the first build, `true` by default. */\n\tclean?: boolean;\n\t/**\n\t * Whether the rendered template is validated as a Blogger theme: well-formed\n\t * XML with the XML declaration and the doctype on the first two lines, and\n\t * expressions (`b:if cond`, `expr:*`, ...) that parse with `blogger-expr`.\n\t * `true` by default, formatting needs it. The templates and the partials of\n\t * plugins are validated when compiling either way.\n\t */\n\tvalidate?: boolean;\n\t/**\n\t * Whether the rendered template is formatted as Blogger saves it: the\n\t * declarations added or moved to the top, start tags on a single line, line\n\t * breaks in attribute values collapsed, the attributes sorted and quoted\n\t * with single quotes, and the\n\t * expressions printed by `blogger-expr` on a single line. `true` by default,\n\t * nothing is formatted without validation.\n\t */\n\tformat?: boolean;\n}\n\nexport interface UserConfig extends CompileTemplateOptions {\n\t/** The context the template is rendered with. */\n\tcontext?: unknown;\n\t/** Plugins used when rendering. */\n\truntimePlugins?: RuntimePlugin[];\n\toutput?: OutputConfig;\n\t/**\n\t * Directory whose files are copied as they are into the output directory,\n\t * relative to the root. `public` by default, when it exists, `false` to not\n\t * copy anything.\n\t */\n\tpublicDir?: string | false;\n}\n\nexport interface ConfigEnv {\n\t/** `NODE_ENV` */\n\tenv: string;\n\t/** The command of the CLI, `build`. */\n\tcommand: string;\n\tenvMode: string;\n\tmeta?: {\n\t\t/** Whether the CLI watches for changes. */\n\t\twatch?: boolean;\n\t};\n}\n\nexport type UserConfigExport =\n\t| UserConfig\n\t| ((env: ConfigEnv) => UserConfig | Promise<UserConfig>);\n\n/** Gives type checking and completions to a config file. */\nexport function defineConfig<T extends UserConfigExport>(config: T): T {\n\treturn config;\n}\n\nexport interface LoadedConfig {\n\tconfig: UserConfig;\n\t/** Path of the config file, undefined when there is none. */\n\tfile?: string;\n\t/** The config file and the files it imports. */\n\tdependencies: string[];\n}\n\n/** Throws unless `config` is a valid config. */\nexport function assertUserConfig(\n\tconfig: unknown,\n): asserts config is UserConfig {\n\tassertCompileOptions(config, CONFIG_KEYS);\n\tconst { runtimePlugins, output, publicDir } = config as UserConfig;\n\n\tif (\n\t\tpublicDir !== undefined &&\n\t\tpublicDir !== false &&\n\t\t(typeof publicDir !== 'string' || publicDir === '')\n\t) {\n\t\tthrow invalidOption('publicDir', 'a path or false', publicDir);\n\t}\n\n\tif (runtimePlugins !== undefined) {\n\t\tif (!Array.isArray(runtimePlugins)) {\n\t\t\tthrow invalidOption(\n\t\t\t\t'runtimePlugins',\n\t\t\t\t'an array of runtime plugins',\n\t\t\t\truntimePlugins,\n\t\t\t);\n\t\t}\n\t\truntimePlugins.forEach((plugin: unknown, i) => {\n\t\t\tif (typeof plugin !== 'object' || plugin === null) {\n\t\t\t\tthrow invalidOption(`runtimePlugins[${i}]`, 'a runtime plugin', plugin);\n\t\t\t}\n\t\t\tconst { helpers } = plugin as RuntimePlugin;\n\t\t\tif (helpers !== undefined && !isPlainObject(helpers)) {\n\t\t\t\tthrow invalidOption(\n\t\t\t\t\t`runtimePlugins[${i}].helpers`,\n\t\t\t\t\t'an object of helpers by name',\n\t\t\t\t\thelpers,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\tif (output === undefined) {\n\t\treturn;\n\t}\n\tif (!isPlainObject(output)) {\n\t\tthrow invalidOption('output', 'an object', output);\n\t}\n\tfor (const key of Object.keys(output)) {\n\t\tif (!OUTPUT_KEYS.includes(key)) {\n\t\t\tconst known = OUTPUT_KEYS.map((name) => `output.${name}`);\n\t\t\tconst hint = didYouMean(`output.${key}`, known);\n\t\t\tthrow new CompileError(\n\t\t\t\t`Unknown option 'output.${key}'${hint}${hint ? ' Valid' : ', valid'} options: ${known.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst { dir, module, template, clean, validate, format } = output;\n\tif (dir !== undefined && (typeof dir !== 'string' || dir === '')) {\n\t\tthrow invalidOption('output.dir', 'a path', dir);\n\t}\n\tfor (const [name, value] of [\n\t\t['module', module],\n\t\t['template', template],\n\t] as const) {\n\t\tif (\n\t\t\tvalue !== undefined &&\n\t\t\tvalue !== false &&\n\t\t\t(typeof value !== 'string' || value === '')\n\t\t) {\n\t\t\tthrow invalidOption(`output.${name}`, 'a file name or false', value);\n\t\t}\n\t}\n\tif (\n\t\t(module ?? 'compiled.js') !== false &&\n\t\t(module ?? 'compiled.js') === (template ?? 'theme.xml')\n\t) {\n\t\tthrow new CompileError(\n\t\t\t`'output.module' and 'output.template' are both '${module ?? template}', they would overwrite each other`,\n\t\t);\n\t}\n\tfor (const [name, value] of [\n\t\t['clean', clean],\n\t\t['validate', validate],\n\t\t['format', format],\n\t] as const) {\n\t\tif (value !== undefined && typeof value !== 'boolean') {\n\t\t\tthrow invalidOption(`output.${name}`, 'a boolean', value);\n\t\t}\n\t}\n}\n\n/**\n * Loads `blogger-hbs.config.*` from `cwd`, or the file at `configFile`. A\n * relative `root` in it is resolved from the directory of the file.\n */\nexport async function loadConfig({\n\tcwd = process.cwd(),\n\tconfigFile,\n\tcommand = 'build',\n\twatch = false,\n}: {\n\tcwd?: string;\n\tconfigFile?: string;\n\tcommand?: string;\n\twatch?: boolean;\n} = {}): Promise<LoadedConfig> {\n\tconst { content, filePath, dependencies } =\n\t\tawait loadRsbuildConfig<UserConfig>({\n\t\t\tcwd,\n\t\t\tpath: configFile,\n\t\t\tconfigFileNames: CONFIG_FILE_NAMES,\n\t\t\tcommand,\n\t\t\tmeta: { watch },\n\t\t});\n\n\tif (!filePath) {\n\t\treturn { config: {}, dependencies: [] };\n\t}\n\n\t// rsbuild records where its own config came from, which is not needed here\n\tconst { _privateMeta, ...config } = content as UserConfig & {\n\t\t_privateMeta?: unknown;\n\t};\n\n\ttry {\n\t\tassertUserConfig(config);\n\t} catch (error) {\n\t\tthrow new CompileError(\n\t\t\t`Invalid config '${path.relative(cwd, filePath) || filePath}': ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tconfig: {\n\t\t\t...config,\n\t\t\troot: path.resolve(path.dirname(filePath), config.root ?? '.'),\n\t\t},\n\t\tfile: filePath,\n\t\t// the loader does not always list the config file itself\n\t\tdependencies: [...new Set([filePath, ...(dependencies ?? [])])],\n\t};\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ import { HelperError } from "./runtime/helpers/error.js";
|
|
|
10
10
|
import { RenderTemplateOptions, renderTemplate } from "./runtime/render.js";
|
|
11
11
|
import "./runtime/index.js";
|
|
12
12
|
import { XmlError, formatXml, validateXml } from "./xml/format.js";
|
|
13
|
+
import "./xml/index.js";
|
|
13
14
|
export { CompileError, type CompilePlugin, type CompileTemplateOptions, type CompiledTemplate, type ConfigEnv, type DataContext, type DataResult, type FinalizeContext, HelperError, type OutputConfig, type PluginData, PluginError, type PluginPartials, type ProvideContext, type ProvideResult, type RenderTemplateOptions, type RsbuildOptions, type RuntimePlugin, type TemplateContext, type TemplateWatcher, type UserConfig, type UserConfigExport, type WatchTemplateOptions, XmlError, compileTemplate, defineConfig, formatXml, renderTemplate, validateXml, watchTemplate };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CompileError } from "./compiler/errors.js";
|
|
2
2
|
import { PluginError } from "./plugins/index.js";
|
|
3
3
|
import { XmlError, formatXml, validateXml } from "./xml/format.js";
|
|
4
|
+
import "./xml/index.js";
|
|
4
5
|
import { compileTemplate } from "./compiler/compile.js";
|
|
5
6
|
import { watchTemplate } from "./compiler/watch.js";
|
|
6
7
|
import "./compiler/index.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ScannedAttribute, ScannedStartTag } from "./scanner.js";
|
|
2
|
+
import { Node } from "blogger-expr";
|
|
3
|
+
//#region src/xml/expressions.d.ts
|
|
4
|
+
/** Whether the attribute `name` of `<tag>` holds an expression. */
|
|
5
|
+
export declare function isExpressionAttribute(tag: string, name: string): boolean;
|
|
6
|
+
/** A range of `b:loop values`, which `blogger-expr` does not parse. */
|
|
7
|
+
export interface LoopRange {
|
|
8
|
+
type: 'LoopRange';
|
|
9
|
+
from: number;
|
|
10
|
+
to: number;
|
|
11
|
+
}
|
|
12
|
+
/** What an expression attribute is parsed into. */
|
|
13
|
+
export type ParsedExpression = Node | LoopRange;
|
|
14
|
+
/**
|
|
15
|
+
* Parses the expressions in the attributes of `tag` with `blogger-expr`, by
|
|
16
|
+
* attribute. Dynamic attributes, with Handlebars in them, are left out, as
|
|
17
|
+
* are `b:include data` shorthands. The first one that does not parse throws
|
|
18
|
+
* an `XmlSyntaxError` where it is.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseExpressions(tag: ScannedStartTag): Map<ScannedAttribute, ParsedExpression>;
|
|
21
|
+
/**
|
|
22
|
+
* A parsed expression printed by `blogger-expr` on a single line, escaped for
|
|
23
|
+
* single quotes, as formatted attributes have: `data:a&&data:b` becomes
|
|
24
|
+
* `data:a and data:b`. Undefined when it can not be printed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatExpression(expression: ParsedExpression): string | undefined;
|
|
27
|
+
//#endregion
|
|
28
|
+
//# sourceMappingURL=expressions.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { XmlSyntaxError } from "./scanner.js";
|
|
2
|
+
import { LexError, ParseError, PrintError, parse, print } from "blogger-expr";
|
|
3
|
+
//#region src/xml/expressions.ts
|
|
4
|
+
/**
|
|
5
|
+
* Attributes holding an expression, by element, besides `expr:*` which do on
|
|
6
|
+
* any element.
|
|
7
|
+
*/
|
|
8
|
+
const EXPRESSION_ATTRIBUTES = {
|
|
9
|
+
"b:if": ["cond"],
|
|
10
|
+
"b:elseif": ["cond"],
|
|
11
|
+
"b:loop": ["values"],
|
|
12
|
+
"b:with": ["value"],
|
|
13
|
+
"b:eval": ["expr"],
|
|
14
|
+
"b:switch": ["var"],
|
|
15
|
+
"b:include": ["cond", "data"],
|
|
16
|
+
"b:widget": ["cond"],
|
|
17
|
+
"b:section": ["cond"]
|
|
18
|
+
};
|
|
19
|
+
/** Whether the attribute `name` of `<tag>` holds an expression. */
|
|
20
|
+
function isExpressionAttribute(tag, name) {
|
|
21
|
+
return name.startsWith("expr:") || (EXPRESSION_ATTRIBUTES[tag]?.includes(name) ?? false);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `b:include data` also takes a bare name or path, `data='post'` for
|
|
25
|
+
* `data:post`, which is not an expression and is left as it is.
|
|
26
|
+
*/
|
|
27
|
+
const INCLUDE_DATA_SHORTHAND = /^\s*[A-Za-z_][\w-]*(?:\.[\w-]+)*\s*$/;
|
|
28
|
+
/**
|
|
29
|
+
* `b:loop values` also takes a range, `values='1..20'`. Blogger only supports
|
|
30
|
+
* integer literals for its ends, not expressions, and no spaces.
|
|
31
|
+
*/
|
|
32
|
+
const LOOP_RANGE = /^(-?\d+)\.\.(-?\d+)$/;
|
|
33
|
+
/** A range with spaces, rejected with its own message. */
|
|
34
|
+
const SPACED_LOOP_RANGE = /^\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*$/;
|
|
35
|
+
/**
|
|
36
|
+
* Parses the expressions in the attributes of `tag` with `blogger-expr`, by
|
|
37
|
+
* attribute. Dynamic attributes, with Handlebars in them, are left out, as
|
|
38
|
+
* are `b:include data` shorthands. The first one that does not parse throws
|
|
39
|
+
* an `XmlSyntaxError` where it is.
|
|
40
|
+
*/
|
|
41
|
+
function parseExpressions(tag) {
|
|
42
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const attribute of tag.attributes) {
|
|
44
|
+
if (attribute.dynamic || !isExpressionAttribute(tag.name, attribute.name)) continue;
|
|
45
|
+
const { text, offsets } = decodeValue(attribute);
|
|
46
|
+
if (tag.name === "b:include" && attribute.name === "data" && INCLUDE_DATA_SHORTHAND.test(text)) continue;
|
|
47
|
+
if (tag.name === "b:loop" && attribute.name === "values") {
|
|
48
|
+
const [, from, to] = LOOP_RANGE.exec(text) ?? [];
|
|
49
|
+
if (from !== void 0) {
|
|
50
|
+
parsed.set(attribute, {
|
|
51
|
+
type: "LoopRange",
|
|
52
|
+
from: Number(from),
|
|
53
|
+
to: Number(to)
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const [, spacedFrom, spacedTo] = SPACED_LOOP_RANGE.exec(text) ?? [];
|
|
58
|
+
if (spacedFrom !== void 0) throw new XmlSyntaxError(`Invalid range in the attribute 'values' of <b:loop>, it can not have spaces: '${spacedFrom}..${spacedTo}'`, attribute.positionOf(0));
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
parsed.set(attribute, parse(text));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!(error instanceof LexError || error instanceof ParseError)) throw error;
|
|
64
|
+
const message = error.message.replace(/ \(at position \d+\)$/, "");
|
|
65
|
+
throw new XmlSyntaxError(`Invalid expression in the attribute '${attribute.name}' of <${tag.name}>: ${message}`, attribute.positionOf(offsets[error.position] ?? attribute.value.length));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A parsed expression printed by `blogger-expr` on a single line, escaped for
|
|
72
|
+
* single quotes, as formatted attributes have: `data:a&&data:b` becomes
|
|
73
|
+
* `data:a and data:b`. Undefined when it can not be printed.
|
|
74
|
+
*/
|
|
75
|
+
function formatExpression(expression) {
|
|
76
|
+
if (expression.type === "LoopRange") return `${expression.from}..${expression.to}`;
|
|
77
|
+
let printed;
|
|
78
|
+
try {
|
|
79
|
+
printed = print(expression);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof PrintError) return;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
return printed.replace(/&/g, "&").replace(/</g, "<").replace(/'/g, "'");
|
|
85
|
+
}
|
|
86
|
+
const ENTITIES = {
|
|
87
|
+
amp: "&",
|
|
88
|
+
lt: "<",
|
|
89
|
+
gt: ">",
|
|
90
|
+
quot: "\"",
|
|
91
|
+
apos: "'"
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The value with its entities decoded, as Blogger parses it, and the offset
|
|
95
|
+
* in the written value of each character of it.
|
|
96
|
+
*/
|
|
97
|
+
function decodeValue({ value }) {
|
|
98
|
+
let text = "";
|
|
99
|
+
const offsets = [];
|
|
100
|
+
for (let i = 0; i < value.length; i++) {
|
|
101
|
+
const entity = value[i] === "&" ? /^&(#x[0-9a-f]+|#[0-9]+|\w+);/i.exec(value.slice(i)) : null;
|
|
102
|
+
if (entity === null) {
|
|
103
|
+
text += value[i];
|
|
104
|
+
offsets.push(i);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const [whole, name] = entity;
|
|
108
|
+
const decoded = decodeEntity(name) ?? whole;
|
|
109
|
+
text += decoded;
|
|
110
|
+
for (let unit = 0; unit < decoded.length; unit++) offsets.push(i);
|
|
111
|
+
i += whole.length - 1;
|
|
112
|
+
}
|
|
113
|
+
offsets.push(value.length);
|
|
114
|
+
return {
|
|
115
|
+
text,
|
|
116
|
+
offsets
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** The character of `&name;`, undefined for an unknown name or code point. */
|
|
120
|
+
function decodeEntity(name) {
|
|
121
|
+
if (name[0] !== "#") return ENTITIES[name];
|
|
122
|
+
const hex = name[1].toLowerCase() === "x";
|
|
123
|
+
const code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);
|
|
124
|
+
return code <= 1114111 ? String.fromCodePoint(code) : void 0;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { formatExpression, isExpressionAttribute, parseExpressions };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=expressions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressions.js","names":[],"sources":["../../src/xml/expressions.ts"],"sourcesContent":["import {\n\tLexError,\n\ttype Node,\n\tParseError,\n\tPrintError,\n\tparse,\n\tprint,\n} from 'blogger-expr';\nimport {\n\ttype ScannedAttribute,\n\ttype ScannedStartTag,\n\tXmlSyntaxError,\n} from './scanner';\n\n/**\n * Attributes holding an expression, by element, besides `expr:*` which do on\n * any element.\n */\nconst EXPRESSION_ATTRIBUTES: Record<string, string[]> = {\n\t'b:if': ['cond'],\n\t'b:elseif': ['cond'],\n\t'b:loop': ['values'],\n\t'b:with': ['value'],\n\t'b:eval': ['expr'],\n\t'b:switch': ['var'],\n\t'b:include': ['cond', 'data'],\n\t'b:widget': ['cond'],\n\t'b:section': ['cond'],\n};\n\n/** Whether the attribute `name` of `<tag>` holds an expression. */\nexport function isExpressionAttribute(tag: string, name: string): boolean {\n\treturn (\n\t\tname.startsWith('expr:') ||\n\t\t(EXPRESSION_ATTRIBUTES[tag]?.includes(name) ?? false)\n\t);\n}\n\n/**\n * `b:include data` also takes a bare name or path, `data='post'` for\n * `data:post`, which is not an expression and is left as it is.\n */\nconst INCLUDE_DATA_SHORTHAND = /^\\s*[A-Za-z_][\\w-]*(?:\\.[\\w-]+)*\\s*$/;\n\n/**\n * `b:loop values` also takes a range, `values='1..20'`. Blogger only supports\n * integer literals for its ends, not expressions, and no spaces.\n */\nconst LOOP_RANGE = /^(-?\\d+)\\.\\.(-?\\d+)$/;\n/** A range with spaces, rejected with its own message. */\nconst SPACED_LOOP_RANGE = /^\\s*(-?\\d+)\\s*\\.\\.\\s*(-?\\d+)\\s*$/;\n\n/** A range of `b:loop values`, which `blogger-expr` does not parse. */\nexport interface LoopRange {\n\ttype: 'LoopRange';\n\tfrom: number;\n\tto: number;\n}\n\n/** What an expression attribute is parsed into. */\nexport type ParsedExpression = Node | LoopRange;\n\n/**\n * Parses the expressions in the attributes of `tag` with `blogger-expr`, by\n * attribute. Dynamic attributes, with Handlebars in them, are left out, as\n * are `b:include data` shorthands. The first one that does not parse throws\n * an `XmlSyntaxError` where it is.\n */\nexport function parseExpressions(\n\ttag: ScannedStartTag,\n): Map<ScannedAttribute, ParsedExpression> {\n\tconst parsed = new Map<ScannedAttribute, ParsedExpression>();\n\n\tfor (const attribute of tag.attributes) {\n\t\tif (attribute.dynamic || !isExpressionAttribute(tag.name, attribute.name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { text, offsets } = decodeValue(attribute);\n\t\tif (\n\t\t\ttag.name === 'b:include' &&\n\t\t\tattribute.name === 'data' &&\n\t\t\tINCLUDE_DATA_SHORTHAND.test(text)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (tag.name === 'b:loop' && attribute.name === 'values') {\n\t\t\tconst [, from, to] = LOOP_RANGE.exec(text) ?? [];\n\t\t\tif (from !== undefined) {\n\t\t\t\tparsed.set(attribute, {\n\t\t\t\t\ttype: 'LoopRange',\n\t\t\t\t\tfrom: Number(from),\n\t\t\t\t\tto: Number(to),\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst [, spacedFrom, spacedTo] = SPACED_LOOP_RANGE.exec(text) ?? [];\n\t\t\tif (spacedFrom !== undefined) {\n\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t`Invalid range in the attribute 'values' of <b:loop>, it can not have spaces: '${spacedFrom}..${spacedTo}'`,\n\t\t\t\t\tattribute.positionOf(0),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tparsed.set(attribute, parse(text));\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof LexError || error instanceof ParseError)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\t// the message of blogger-expr ends with the position in the decoded text\n\t\t\tconst message = error.message.replace(/ \\(at position \\d+\\)$/, '');\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`Invalid expression in the attribute '${attribute.name}' of <${tag.name}>: ${message}`,\n\t\t\t\tattribute.positionOf(offsets[error.position] ?? attribute.value.length),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn parsed;\n}\n\n/**\n * A parsed expression printed by `blogger-expr` on a single line, escaped for\n * single quotes, as formatted attributes have: `data:a&&data:b` becomes\n * `data:a and data:b`. Undefined when it can not be printed.\n */\nexport function formatExpression(\n\texpression: ParsedExpression,\n): string | undefined {\n\tif (expression.type === 'LoopRange') {\n\t\treturn `${expression.from}..${expression.to}`;\n\t}\n\n\tlet printed: string;\n\ttry {\n\t\tprinted = print(expression);\n\t} catch (error) {\n\t\tif (error instanceof PrintError) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn printed\n\t\t.replace(/&/g, '&')\n\t\t.replace(/</g, '<')\n\t\t.replace(/'/g, ''');\n}\n\nconst ENTITIES: Record<string, string> = {\n\tamp: '&',\n\tlt: '<',\n\tgt: '>',\n\tquot: '\"',\n\tapos: \"'\",\n};\n\n/**\n * The value with its entities decoded, as Blogger parses it, and the offset\n * in the written value of each character of it.\n */\nfunction decodeValue({ value }: ScannedAttribute): {\n\ttext: string;\n\toffsets: number[];\n} {\n\tlet text = '';\n\tconst offsets: number[] = [];\n\n\tfor (let i = 0; i < value.length; i++) {\n\t\tconst entity =\n\t\t\tvalue[i] === '&'\n\t\t\t\t? /^&(#x[0-9a-f]+|#[0-9]+|\\w+);/i.exec(value.slice(i))\n\t\t\t\t: null;\n\t\tif (entity === null) {\n\t\t\ttext += value[i];\n\t\t\toffsets.push(i);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst [whole, name] = entity;\n\t\tconst decoded = decodeEntity(name) ?? whole;\n\t\ttext += decoded;\n\t\t// one per UTF-16 unit, as positions in `text` are\n\t\tfor (let unit = 0; unit < decoded.length; unit++) {\n\t\t\toffsets.push(i);\n\t\t}\n\t\ti += whole.length - 1;\n\t}\n\toffsets.push(value.length);\n\n\treturn { text, offsets };\n}\n\n/** The character of `&name;`, undefined for an unknown name or code point. */\nfunction decodeEntity(name: string): string | undefined {\n\tif (name[0] !== '#') {\n\t\treturn ENTITIES[name];\n\t}\n\tconst hex = name[1].toLowerCase() === 'x';\n\tconst code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);\n\treturn code <= 0x10ffff ? String.fromCodePoint(code) : undefined;\n}\n"],"mappings":"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"}
|
package/dist/xml/format.d.ts
CHANGED
|
@@ -12,7 +12,9 @@ export declare const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"
|
|
|
12
12
|
export declare const DOCTYPE = "<!DOCTYPE html>";
|
|
13
13
|
/**
|
|
14
14
|
* Throws an `XmlError` unless `xml` is a well-formed document starting with
|
|
15
|
-
* the XML declaration on the first line and the doctype on the second
|
|
15
|
+
* the XML declaration on the first line and the doctype on the second, both
|
|
16
|
+
* as `formatXml` requires them (XML 1.0 in UTF-8, an `html` doctype), whose
|
|
17
|
+
* expressions (`b:if cond`, `expr:*`, ...) parse with `blogger-expr`.
|
|
16
18
|
*/
|
|
17
19
|
export declare function validateXml(xml: string): void;
|
|
18
20
|
/**
|
|
@@ -21,7 +23,9 @@ export declare function validateXml(xml: string): void;
|
|
|
21
23
|
* - the XML declaration on the first line and the doctype on the second, added
|
|
22
24
|
* when missing and moved there when elsewhere
|
|
23
25
|
* - start tags on a single line, line breaks in attribute values collapsed to
|
|
24
|
-
* a space
|
|
26
|
+
* a space, the attributes sorted by name and quoted with single quotes
|
|
27
|
+
* - expressions (`b:if cond`, `expr:*`, ...) printed by `blogger-expr` on a
|
|
28
|
+
* single line, `data:a&&data:b` as `data:a and data:b`
|
|
25
29
|
*
|
|
26
30
|
* Everything else is left as it is.
|
|
27
31
|
*/
|
package/dist/xml/format.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { XmlScanner, XmlSyntaxError, createLocate } from "./scanner.js";
|
|
2
|
+
import { formatExpression, parseExpressions } from "./expressions.js";
|
|
2
3
|
//#region src/xml/format.ts
|
|
3
4
|
var XmlError = class extends Error {
|
|
4
5
|
line;
|
|
@@ -17,7 +18,9 @@ const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
|
|
|
17
18
|
const DOCTYPE = "<!DOCTYPE html>";
|
|
18
19
|
/**
|
|
19
20
|
* Throws an `XmlError` unless `xml` is a well-formed document starting with
|
|
20
|
-
* the XML declaration on the first line and the doctype on the second
|
|
21
|
+
* the XML declaration on the first line and the doctype on the second, both
|
|
22
|
+
* as `formatXml` requires them (XML 1.0 in UTF-8, an `html` doctype), whose
|
|
23
|
+
* expressions (`b:if cond`, `expr:*`, ...) parse with `blogger-expr`.
|
|
21
24
|
*/
|
|
22
25
|
function validateXml(xml) {
|
|
23
26
|
const { declarations } = scanDocument(xml);
|
|
@@ -36,6 +39,8 @@ function validateXml(xml) {
|
|
|
36
39
|
line: 2,
|
|
37
40
|
column: 0
|
|
38
41
|
});
|
|
42
|
+
formatXmlDeclaration(xmlDeclaration);
|
|
43
|
+
formatDoctype(doctype);
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
46
|
* Validates the document and formats it as Blogger saves it:
|
|
@@ -43,17 +48,19 @@ function validateXml(xml) {
|
|
|
43
48
|
* - the XML declaration on the first line and the doctype on the second, added
|
|
44
49
|
* when missing and moved there when elsewhere
|
|
45
50
|
* - start tags on a single line, line breaks in attribute values collapsed to
|
|
46
|
-
* a space
|
|
51
|
+
* a space, the attributes sorted by name and quoted with single quotes
|
|
52
|
+
* - expressions (`b:if cond`, `expr:*`, ...) printed by `blogger-expr` on a
|
|
53
|
+
* single line, `data:a&&data:b` as `data:a and data:b`
|
|
47
54
|
*
|
|
48
55
|
* Everything else is left as it is.
|
|
49
56
|
*/
|
|
50
57
|
function formatXml(xml) {
|
|
51
|
-
const { tags, declarations } = scanDocument(xml);
|
|
58
|
+
const { tags, declarations, expressions } = scanDocument(xml);
|
|
52
59
|
const edits = [
|
|
53
60
|
...tags.map((tag) => ({
|
|
54
61
|
start: tag.start,
|
|
55
62
|
end: tag.end,
|
|
56
|
-
text: formatStartTag(tag)
|
|
63
|
+
text: formatStartTag(tag, expressions)
|
|
57
64
|
})),
|
|
58
65
|
...indentChildren(xml, tags),
|
|
59
66
|
...declarations.map((item) => ({
|
|
@@ -110,11 +117,15 @@ function scanDocument(xml) {
|
|
|
110
117
|
const locate = createLocate(xml);
|
|
111
118
|
const scanned = {
|
|
112
119
|
tags: [],
|
|
113
|
-
declarations: []
|
|
120
|
+
declarations: [],
|
|
121
|
+
expressions: /* @__PURE__ */ new Map()
|
|
114
122
|
};
|
|
115
123
|
const scanner = new XmlScanner({
|
|
116
124
|
document: true,
|
|
117
|
-
onStartTag: (tag) =>
|
|
125
|
+
onStartTag: (tag) => {
|
|
126
|
+
scanned.tags.push(tag);
|
|
127
|
+
for (const [attribute, expression] of parseExpressions(tag)) scanned.expressions.set(attribute, expression);
|
|
128
|
+
},
|
|
118
129
|
onDeclaration: (declaration) => scanned.declarations.push(declaration)
|
|
119
130
|
});
|
|
120
131
|
try {
|
|
@@ -175,8 +186,19 @@ function formatDoctype({ content, position }) {
|
|
|
175
186
|
const identifiers = rest.trim().replace(/\s+/g, " ");
|
|
176
187
|
return identifiers ? `<!DOCTYPE html ${identifiers}>` : DOCTYPE;
|
|
177
188
|
}
|
|
178
|
-
function formatStartTag({ name, attributes, selfClosing }) {
|
|
179
|
-
return `<${
|
|
189
|
+
function formatStartTag({ name: tagName, attributes, selfClosing }, expressions) {
|
|
190
|
+
return `<${tagName}${[...attributes].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((attribute) => {
|
|
191
|
+
const parsed = expressions.get(attribute);
|
|
192
|
+
const value = (parsed && formatExpression(parsed)) ?? singleQuoted(attribute, collapseLineBreaks(attribute.value));
|
|
193
|
+
return ` ${attribute.name}='${value}'`;
|
|
194
|
+
}).join("")}${selfClosing ? "/>" : ">"}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A value written in double quotes, rewritten for single quotes: `'` escaped
|
|
198
|
+
* as `'` and `"` unescaped. Other entities are left as they are.
|
|
199
|
+
*/
|
|
200
|
+
function singleQuoted({ quote }, value) {
|
|
201
|
+
return quote === "'" ? value : value.replace(/'/g, "'").replace(/"/g, "\"");
|
|
180
202
|
}
|
|
181
203
|
/** Every line break becomes a space, the spaces around it stay as they are. */
|
|
182
204
|
function collapseLineBreaks(value) {
|
package/dist/xml/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../../src/xml/format.ts"],"sourcesContent":["import {\n\tcreateLocate,\n\ttype Position,\n\ttype ScannedDeclaration,\n\ttype ScannedStartTag,\n\tXmlScanner,\n\tXmlSyntaxError,\n} from './scanner';\n\nexport class XmlError extends Error {\n\treadonly line: number;\n\t/** 1-based. */\n\treadonly column: number;\n\n\tconstructor(message: string, { line, column }: Position) {\n\t\tsuper(`${message} (${line}:${column + 1})`);\n\t\tthis.name = 'XmlError';\n\t\tthis.line = line;\n\t\tthis.column = column + 1;\n\t}\n}\n\n/** The XML declaration Blogger writes, the first line of a theme. */\nexport const XML_DECLARATION = '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>';\n/** The doctype Blogger writes, the second line of a theme. */\nexport const DOCTYPE = '<!DOCTYPE html>';\n\n/**\n * Throws an `XmlError` unless `xml` is a well-formed document starting with\n * the XML declaration on the first line and the doctype on the second.\n */\nexport function validateXml(xml: string): void {\n\tconst { declarations } = scanDocument(xml);\n\tconst [first, second] = [...xml.split('\\n', 2)].map((line) =>\n\t\tline.replace(/\\r$/, ''),\n\t);\n\tconst xmlDeclaration = declarations.find((item) => item.kind === 'xml');\n\tconst doctype = declarations.find((item) => item.kind === 'doctype');\n\n\tif (xmlDeclaration?.start !== 0) {\n\t\tthrow new XmlError(\n\t\t\t`The first line has to be the XML declaration, ${XML_DECLARATION}`,\n\t\t\txmlDeclaration?.position ?? { line: 1, column: 0 },\n\t\t);\n\t}\n\tif (first.trimEnd() !== xml.slice(0, xmlDeclaration.end)) {\n\t\tthrow new XmlError(\n\t\t\t'The XML declaration has to be alone on the first line',\n\t\t\t{ line: 1, column: xmlDeclaration.end },\n\t\t);\n\t}\n\tif (\n\t\tdoctype?.position.line !== 2 ||\n\t\tsecond?.trim() !== xml.slice(doctype.start, doctype.end)\n\t) {\n\t\tthrow new XmlError(\n\t\t\t`The second line has to be the doctype, ${DOCTYPE}`,\n\t\t\tdoctype?.position ?? { line: 2, column: 0 },\n\t\t);\n\t}\n}\n\n/**\n * Validates the document and formats it as Blogger saves it:\n *\n * - the XML declaration on the first line and the doctype on the second, added\n * when missing and moved there when elsewhere\n * - start tags on a single line, line breaks in attribute values collapsed to\n * a space and the attributes sorted by name\n *\n * Everything else is left as it is.\n */\nexport function formatXml(xml: string): string {\n\tconst { tags, declarations } = scanDocument(xml);\n\n\t// the declarations are taken out, they are written at the top\n\tconst edits: { start: number; end: number; text: string }[] = [\n\t\t...tags.map((tag) => ({\n\t\t\tstart: tag.start,\n\t\t\tend: tag.end,\n\t\t\ttext: formatStartTag(tag),\n\t\t})),\n\t\t...indentChildren(xml, tags),\n\t\t...declarations.map((item) => ({\n\t\t\t...lineOf(xml, item.start, item.end),\n\t\t\ttext: '',\n\t\t})),\n\t].sort((a, b) => a.start - b.start || a.end - b.end);\n\n\tlet body = '';\n\tlet last = 0;\n\tfor (const { start, end, text } of edits) {\n\t\tbody += xml.slice(last, start) + text;\n\t\tlast = end;\n\t}\n\tbody += xml.slice(last);\n\n\tconst xmlDeclaration = declarations.find((item) => item.kind === 'xml');\n\tconst doctype = declarations.find((item) => item.kind === 'doctype');\n\n\treturn [\n\t\txmlDeclaration ? formatXmlDeclaration(xmlDeclaration) : XML_DECLARATION,\n\t\tdoctype ? formatDoctype(doctype) : DOCTYPE,\n\t\tbody.replace(/^\\s+/, ''),\n\t].join('\\n');\n}\n\n/** Elements whose children Blogger writes each on a line, indented. */\nconst INDENTED_PARENTS = new Set(['b:widget', 'b:widget-settings']);\n\n/**\n * Puts the start tag of each child of an indented parent on its own line,\n * indented two spaces more than the line of the parent, by replacing the\n * whitespace before it. What is in the children is left as it is.\n */\nfunction indentChildren(\n\txml: string,\n\ttags: ScannedStartTag[],\n): { start: number; end: number; text: string }[] {\n\t// indentation by start tag offset, of the parents moved to a line of their own\n\tconst moved = new Map<number, string>();\n\tconst edits: { start: number; end: number; text: string }[] = [];\n\n\tfor (const tag of tags) {\n\t\tconst { parent } = tag;\n\t\tif (parent === undefined || !INDENTED_PARENTS.has(parent.name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst indent = `${moved.get(parent.start) ?? indentationOf(xml, parent.start)} `;\n\t\tlet start = tag.start;\n\t\twhile (start > 0 && /\\s/.test(xml[start - 1])) {\n\t\t\tstart--;\n\t\t}\n\n\t\tmoved.set(tag.start, indent);\n\t\tedits.push({ start, end: tag.start, text: `\\n${indent}` });\n\t}\n\n\treturn edits;\n}\n\n/** The whitespace at the start of the line `offset` is on. */\nfunction indentationOf(xml: string, offset: number): string {\n\tconst lineStart = xml.lastIndexOf('\\n', offset - 1) + 1;\n\treturn /^[ \\t]*/.exec(xml.slice(lineStart, offset))?.[0] ?? '';\n}\n\ninterface Scanned {\n\ttags: ScannedStartTag[];\n\tdeclarations: ScannedDeclaration[];\n}\n\nfunction scanDocument(xml: string): Scanned {\n\tconst locate = createLocate(xml);\n\tconst scanned: Scanned = { tags: [], declarations: [] };\n\tconst scanner = new XmlScanner({\n\t\tdocument: true,\n\t\tonStartTag: (tag) => scanned.tags.push(tag),\n\t\tonDeclaration: (declaration) => scanned.declarations.push(declaration),\n\t});\n\n\ttry {\n\t\tscanner.feed(xml, locate);\n\t\tscanner.finish(locate(xml.length));\n\n\t\tfor (const kind of ['xml', 'doctype'] as const) {\n\t\t\tconst found = scanned.declarations.filter((item) => item.kind === kind);\n\t\t\tif (found.length > 1) {\n\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t`There can only be one ${kind === 'xml' ? 'XML declaration' : 'doctype'}, another one is at ${found[0].position.line}:${found[0].position.column + 1}`,\n\t\t\t\t\tfound[1].position,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof XmlSyntaxError) {\n\t\t\tthrow new XmlError(error.message, error.position);\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn scanned;\n}\n\n/**\n * The range to remove for a declaration: its whole line when nothing else is\n * on it, so no empty line is left, otherwise only the declaration.\n */\nfunction lineOf(\n\txml: string,\n\tstart: number,\n\tend: number,\n): { start: number; end: number } {\n\tlet lineStart = start;\n\twhile (lineStart > 0 && /[ \\t]/.test(xml[lineStart - 1])) {\n\t\tlineStart--;\n\t}\n\tlet lineEnd = end;\n\twhile (lineEnd < xml.length && /[ \\t\\r]/.test(xml[lineEnd])) {\n\t\tlineEnd++;\n\t}\n\n\tconst alone =\n\t\t(lineStart === 0 || xml[lineStart - 1] === '\\n') &&\n\t\t(lineEnd === xml.length || xml[lineEnd] === '\\n');\n\n\treturn alone\n\t\t? { start: lineStart, end: Math.min(lineEnd + 1, xml.length) }\n\t\t: { start, end };\n}\n\n/** `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>`, it has to be XML 1.0 in UTF-8. */\nfunction formatXmlDeclaration({\n\tcontent,\n\tposition,\n}: ScannedDeclaration): string {\n\tconst attributes = new Map<string, string>();\n\tconst rest = content\n\t\t.slice('xml'.length)\n\t\t.replace(\n\t\t\t/\\s*([\\w-]+)\\s*=\\s*([\"'])(.*?)\\2/g,\n\t\t\t(_, name: string, _quote, value: string) => {\n\t\t\t\tattributes.set(name, value);\n\t\t\t\treturn '';\n\t\t\t},\n\t\t)\n\t\t.trim();\n\n\tconst fail = (message: string): never => {\n\t\tthrow new XmlError(message, position);\n\t};\n\n\tif (rest !== '') {\n\t\tfail(`Invalid XML declaration '<?${content}?>'`);\n\t}\n\tfor (const name of attributes.keys()) {\n\t\tif (!['version', 'encoding', 'standalone'].includes(name)) {\n\t\t\tfail(`Unknown '${name}' in the XML declaration`);\n\t\t}\n\t}\n\tif (attributes.get('version') !== '1.0') {\n\t\tfail(\n\t\t\t`The XML declaration has to have version=\"1.0\", got: ${attributes.has('version') ? `'${attributes.get('version')}'` : 'none'}`,\n\t\t);\n\t}\n\tconst encoding = attributes.get('encoding');\n\tif (encoding !== undefined && encoding.toUpperCase() !== 'UTF-8') {\n\t\tfail(\n\t\t\t`Blogger themes are UTF-8, the XML declaration has encoding '${encoding}'`,\n\t\t);\n\t}\n\n\treturn XML_DECLARATION;\n}\n\n/** `<!DOCTYPE html>`, an identifier after `html` is kept. */\nfunction formatDoctype({ content, position }: ScannedDeclaration): string {\n\tconst [, root, rest] = /^DOCTYPE\\s+([^\\s[>]+)([\\s\\S]*)$/i.exec(content) ?? [];\n\tif (root?.toLowerCase() !== 'html') {\n\t\tthrow new XmlError(\n\t\t\t`The doctype has to be ${DOCTYPE}, got: '<!${content}>'`,\n\t\t\tposition,\n\t\t);\n\t}\n\tconst identifiers = rest.trim().replace(/\\s+/g, ' ');\n\treturn identifiers ? `<!DOCTYPE html ${identifiers}>` : DOCTYPE;\n}\n\nfunction formatStartTag({\n\tname,\n\tattributes,\n\tselfClosing,\n}: ScannedStartTag): string {\n\tconst formatted = [...attributes]\n\t\t.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n\t\t.map(\n\t\t\t({ name, value, quote }) =>\n\t\t\t\t` ${name}=${quote}${collapseLineBreaks(value)}${quote}`,\n\t\t)\n\t\t.join('');\n\n\treturn `<${name}${formatted}${selfClosing ? '/>' : '>'}`;\n}\n\n/** Every line break becomes a space, the spaces around it stay as they are. */\nfunction collapseLineBreaks(value: string): string {\n\treturn value.replace(/\\r?\\n/g, ' ');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../src/xml/format.ts"],"sourcesContent":["import {\n\tformatExpression,\n\ttype ParsedExpression,\n\tparseExpressions,\n} from './expressions';\nimport {\n\tcreateLocate,\n\ttype Position,\n\ttype ScannedAttribute,\n\ttype ScannedDeclaration,\n\ttype ScannedStartTag,\n\tXmlScanner,\n\tXmlSyntaxError,\n} from './scanner';\n\nexport class XmlError extends Error {\n\treadonly line: number;\n\t/** 1-based. */\n\treadonly column: number;\n\n\tconstructor(message: string, { line, column }: Position) {\n\t\tsuper(`${message} (${line}:${column + 1})`);\n\t\tthis.name = 'XmlError';\n\t\tthis.line = line;\n\t\tthis.column = column + 1;\n\t}\n}\n\n/** The XML declaration Blogger writes, the first line of a theme. */\nexport const XML_DECLARATION = '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>';\n/** The doctype Blogger writes, the second line of a theme. */\nexport const DOCTYPE = '<!DOCTYPE html>';\n\n/**\n * Throws an `XmlError` unless `xml` is a well-formed document starting with\n * the XML declaration on the first line and the doctype on the second, both\n * as `formatXml` requires them (XML 1.0 in UTF-8, an `html` doctype), whose\n * expressions (`b:if cond`, `expr:*`, ...) parse with `blogger-expr`.\n */\nexport function validateXml(xml: string): void {\n\tconst { declarations } = scanDocument(xml);\n\tconst [first, second] = [...xml.split('\\n', 2)].map((line) =>\n\t\tline.replace(/\\r$/, ''),\n\t);\n\tconst xmlDeclaration = declarations.find((item) => item.kind === 'xml');\n\tconst doctype = declarations.find((item) => item.kind === 'doctype');\n\n\tif (xmlDeclaration?.start !== 0) {\n\t\tthrow new XmlError(\n\t\t\t`The first line has to be the XML declaration, ${XML_DECLARATION}`,\n\t\t\txmlDeclaration?.position ?? { line: 1, column: 0 },\n\t\t);\n\t}\n\tif (first.trimEnd() !== xml.slice(0, xmlDeclaration.end)) {\n\t\tthrow new XmlError(\n\t\t\t'The XML declaration has to be alone on the first line',\n\t\t\t{ line: 1, column: xmlDeclaration.end },\n\t\t);\n\t}\n\tif (\n\t\tdoctype?.position.line !== 2 ||\n\t\tsecond?.trim() !== xml.slice(doctype.start, doctype.end)\n\t) {\n\t\tthrow new XmlError(\n\t\t\t`The second line has to be the doctype, ${DOCTYPE}`,\n\t\t\tdoctype?.position ?? { line: 2, column: 0 },\n\t\t);\n\t}\n\n\t// their contents are checked as when formatting\n\tformatXmlDeclaration(xmlDeclaration);\n\tformatDoctype(doctype);\n}\n\n/**\n * Validates the document and formats it as Blogger saves it:\n *\n * - the XML declaration on the first line and the doctype on the second, added\n * when missing and moved there when elsewhere\n * - start tags on a single line, line breaks in attribute values collapsed to\n * a space, the attributes sorted by name and quoted with single quotes\n * - expressions (`b:if cond`, `expr:*`, ...) printed by `blogger-expr` on a\n * single line, `data:a&&data:b` as `data:a and data:b`\n *\n * Everything else is left as it is.\n */\nexport function formatXml(xml: string): string {\n\tconst { tags, declarations, expressions } = scanDocument(xml);\n\n\t// the declarations are taken out, they are written at the top\n\tconst edits: { start: number; end: number; text: string }[] = [\n\t\t...tags.map((tag) => ({\n\t\t\tstart: tag.start,\n\t\t\tend: tag.end,\n\t\t\ttext: formatStartTag(tag, expressions),\n\t\t})),\n\t\t...indentChildren(xml, tags),\n\t\t...declarations.map((item) => ({\n\t\t\t...lineOf(xml, item.start, item.end),\n\t\t\ttext: '',\n\t\t})),\n\t].sort((a, b) => a.start - b.start || a.end - b.end);\n\n\tlet body = '';\n\tlet last = 0;\n\tfor (const { start, end, text } of edits) {\n\t\tbody += xml.slice(last, start) + text;\n\t\tlast = end;\n\t}\n\tbody += xml.slice(last);\n\n\tconst xmlDeclaration = declarations.find((item) => item.kind === 'xml');\n\tconst doctype = declarations.find((item) => item.kind === 'doctype');\n\n\treturn [\n\t\txmlDeclaration ? formatXmlDeclaration(xmlDeclaration) : XML_DECLARATION,\n\t\tdoctype ? formatDoctype(doctype) : DOCTYPE,\n\t\tbody.replace(/^\\s+/, ''),\n\t].join('\\n');\n}\n\n/** Elements whose children Blogger writes each on a line, indented. */\nconst INDENTED_PARENTS = new Set(['b:widget', 'b:widget-settings']);\n\n/**\n * Puts the start tag of each child of an indented parent on its own line,\n * indented two spaces more than the line of the parent, by replacing the\n * whitespace before it. What is in the children is left as it is.\n */\nfunction indentChildren(\n\txml: string,\n\ttags: ScannedStartTag[],\n): { start: number; end: number; text: string }[] {\n\t// indentation by start tag offset, of the parents moved to a line of their own\n\tconst moved = new Map<number, string>();\n\tconst edits: { start: number; end: number; text: string }[] = [];\n\n\tfor (const tag of tags) {\n\t\tconst { parent } = tag;\n\t\tif (parent === undefined || !INDENTED_PARENTS.has(parent.name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst indent = `${moved.get(parent.start) ?? indentationOf(xml, parent.start)} `;\n\t\tlet start = tag.start;\n\t\twhile (start > 0 && /\\s/.test(xml[start - 1])) {\n\t\t\tstart--;\n\t\t}\n\n\t\tmoved.set(tag.start, indent);\n\t\tedits.push({ start, end: tag.start, text: `\\n${indent}` });\n\t}\n\n\treturn edits;\n}\n\n/** The whitespace at the start of the line `offset` is on. */\nfunction indentationOf(xml: string, offset: number): string {\n\tconst lineStart = xml.lastIndexOf('\\n', offset - 1) + 1;\n\treturn /^[ \\t]*/.exec(xml.slice(lineStart, offset))?.[0] ?? '';\n}\n\ninterface Scanned {\n\ttags: ScannedStartTag[];\n\tdeclarations: ScannedDeclaration[];\n\t/** The expressions of the attributes, parsed while scanning. */\n\texpressions: Map<ScannedAttribute, ParsedExpression>;\n}\n\nfunction scanDocument(xml: string): Scanned {\n\tconst locate = createLocate(xml);\n\tconst scanned: Scanned = {\n\t\ttags: [],\n\t\tdeclarations: [],\n\t\texpressions: new Map(),\n\t};\n\tconst scanner = new XmlScanner({\n\t\tdocument: true,\n\t\tonStartTag: (tag) => {\n\t\t\tscanned.tags.push(tag);\n\t\t\tfor (const [attribute, expression] of parseExpressions(tag)) {\n\t\t\t\tscanned.expressions.set(attribute, expression);\n\t\t\t}\n\t\t},\n\t\tonDeclaration: (declaration) => scanned.declarations.push(declaration),\n\t});\n\n\ttry {\n\t\tscanner.feed(xml, locate);\n\t\tscanner.finish(locate(xml.length));\n\n\t\tfor (const kind of ['xml', 'doctype'] as const) {\n\t\t\tconst found = scanned.declarations.filter((item) => item.kind === kind);\n\t\t\tif (found.length > 1) {\n\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t`There can only be one ${kind === 'xml' ? 'XML declaration' : 'doctype'}, another one is at ${found[0].position.line}:${found[0].position.column + 1}`,\n\t\t\t\t\tfound[1].position,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof XmlSyntaxError) {\n\t\t\tthrow new XmlError(error.message, error.position);\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn scanned;\n}\n\n/**\n * The range to remove for a declaration: its whole line when nothing else is\n * on it, so no empty line is left, otherwise only the declaration.\n */\nfunction lineOf(\n\txml: string,\n\tstart: number,\n\tend: number,\n): { start: number; end: number } {\n\tlet lineStart = start;\n\twhile (lineStart > 0 && /[ \\t]/.test(xml[lineStart - 1])) {\n\t\tlineStart--;\n\t}\n\tlet lineEnd = end;\n\twhile (lineEnd < xml.length && /[ \\t\\r]/.test(xml[lineEnd])) {\n\t\tlineEnd++;\n\t}\n\n\tconst alone =\n\t\t(lineStart === 0 || xml[lineStart - 1] === '\\n') &&\n\t\t(lineEnd === xml.length || xml[lineEnd] === '\\n');\n\n\treturn alone\n\t\t? { start: lineStart, end: Math.min(lineEnd + 1, xml.length) }\n\t\t: { start, end };\n}\n\n/** `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>`, it has to be XML 1.0 in UTF-8. */\nfunction formatXmlDeclaration({\n\tcontent,\n\tposition,\n}: ScannedDeclaration): string {\n\tconst attributes = new Map<string, string>();\n\tconst rest = content\n\t\t.slice('xml'.length)\n\t\t.replace(\n\t\t\t/\\s*([\\w-]+)\\s*=\\s*([\"'])(.*?)\\2/g,\n\t\t\t(_, name: string, _quote, value: string) => {\n\t\t\t\tattributes.set(name, value);\n\t\t\t\treturn '';\n\t\t\t},\n\t\t)\n\t\t.trim();\n\n\tconst fail = (message: string): never => {\n\t\tthrow new XmlError(message, position);\n\t};\n\n\tif (rest !== '') {\n\t\tfail(`Invalid XML declaration '<?${content}?>'`);\n\t}\n\tfor (const name of attributes.keys()) {\n\t\tif (!['version', 'encoding', 'standalone'].includes(name)) {\n\t\t\tfail(`Unknown '${name}' in the XML declaration`);\n\t\t}\n\t}\n\tif (attributes.get('version') !== '1.0') {\n\t\tfail(\n\t\t\t`The XML declaration has to have version=\"1.0\", got: ${attributes.has('version') ? `'${attributes.get('version')}'` : 'none'}`,\n\t\t);\n\t}\n\tconst encoding = attributes.get('encoding');\n\tif (encoding !== undefined && encoding.toUpperCase() !== 'UTF-8') {\n\t\tfail(\n\t\t\t`Blogger themes are UTF-8, the XML declaration has encoding '${encoding}'`,\n\t\t);\n\t}\n\n\treturn XML_DECLARATION;\n}\n\n/** `<!DOCTYPE html>`, an identifier after `html` is kept. */\nfunction formatDoctype({ content, position }: ScannedDeclaration): string {\n\tconst [, root, rest] = /^DOCTYPE\\s+([^\\s[>]+)([\\s\\S]*)$/i.exec(content) ?? [];\n\tif (root?.toLowerCase() !== 'html') {\n\t\tthrow new XmlError(\n\t\t\t`The doctype has to be ${DOCTYPE}, got: '<!${content}>'`,\n\t\t\tposition,\n\t\t);\n\t}\n\tconst identifiers = rest.trim().replace(/\\s+/g, ' ');\n\treturn identifiers ? `<!DOCTYPE html ${identifiers}>` : DOCTYPE;\n}\n\nfunction formatStartTag(\n\t{ name: tagName, attributes, selfClosing }: ScannedStartTag,\n\texpressions: Map<ScannedAttribute, ParsedExpression>,\n): string {\n\tconst formatted = [...attributes]\n\t\t.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n\t\t.map((attribute) => {\n\t\t\tconst parsed = expressions.get(attribute);\n\t\t\tconst value =\n\t\t\t\t(parsed && formatExpression(parsed)) ??\n\t\t\t\tsingleQuoted(attribute, collapseLineBreaks(attribute.value));\n\t\t\treturn ` ${attribute.name}='${value}'`;\n\t\t})\n\t\t.join('');\n\n\treturn `<${tagName}${formatted}${selfClosing ? '/>' : '>'}`;\n}\n\n/**\n * A value written in double quotes, rewritten for single quotes: `'` escaped\n * as `'` and `"` unescaped. Other entities are left as they are.\n */\nfunction singleQuoted({ quote }: ScannedAttribute, value: string): string {\n\treturn quote === \"'\"\n\t\t? value\n\t\t: value.replace(/'/g, ''').replace(/"/g, '\"');\n}\n\n/** Every line break becomes a space, the spaces around it stay as they are. */\nfunction collapseLineBreaks(value: string): string {\n\treturn value.replace(/\\r?\\n/g, ' ');\n}\n"],"mappings":"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"}
|
package/dist/xml/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Locate, Position, XmlScanner, XmlSyntaxError, createLocate } from "./scanner.js";
|
|
2
|
+
import { parseExpressions } from "./expressions.js";
|
|
2
3
|
import { DOCTYPE, XML_DECLARATION, XmlError, formatXml, validateXml } from "./format.js";
|
|
3
|
-
export { DOCTYPE, type Locate, type Position, XML_DECLARATION, XmlError, XmlScanner, XmlSyntaxError, createLocate, formatXml, validateXml };
|
|
4
|
+
export { DOCTYPE, type Locate, type Position, XML_DECLARATION, XmlError, XmlScanner, XmlSyntaxError, createLocate, formatXml, parseExpressions, validateXml };
|
package/dist/xml/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { XmlScanner, XmlSyntaxError, createLocate } from "./scanner.js";
|
|
2
|
+
import { parseExpressions } from "./expressions.js";
|
|
2
3
|
import { DOCTYPE, XML_DECLARATION, XmlError, formatXml, validateXml } from "./format.js";
|
|
3
|
-
export { DOCTYPE, XML_DECLARATION, XmlError, XmlScanner, XmlSyntaxError, createLocate, formatXml, validateXml };
|
|
4
|
+
export { DOCTYPE, XML_DECLARATION, XmlError, XmlScanner, XmlSyntaxError, createLocate, formatXml, parseExpressions, validateXml };
|
package/dist/xml/scanner.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface ScannedAttribute {
|
|
|
25
25
|
/** The value as it is written, entities are not decoded. */
|
|
26
26
|
value: string;
|
|
27
27
|
quote: '"' | "'";
|
|
28
|
+
/**
|
|
29
|
+
* Whether the name or the value has output unknown until rendering, a
|
|
30
|
+
* Handlebars expression or block, `value` is then only the text around it.
|
|
31
|
+
*/
|
|
32
|
+
dynamic: boolean;
|
|
33
|
+
/** Position of the character of `value` at `index`, of the closing quote at its length. */
|
|
34
|
+
positionOf(index: number): Position;
|
|
28
35
|
}
|
|
29
36
|
export interface ScannedStartTag {
|
|
30
37
|
name: string;
|
|
@@ -43,7 +50,10 @@ export interface ScannerOptions {
|
|
|
43
50
|
* is a fragment, which can have anything at its top.
|
|
44
51
|
*/
|
|
45
52
|
document?: boolean;
|
|
46
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Called with every start tag. Its offsets are only right when the text is
|
|
55
|
+
* fed in one piece, `ScannedAttribute.positionOf` always is.
|
|
56
|
+
*/
|
|
47
57
|
onStartTag?(tag: ScannedStartTag): void;
|
|
48
58
|
/** Called with the XML declaration and the doctype, like `onStartTag`. */
|
|
49
59
|
onDeclaration?(declaration: ScannedDeclaration): void;
|
|
@@ -78,6 +88,8 @@ export declare class XmlScanner {
|
|
|
78
88
|
private attrName;
|
|
79
89
|
private attrDynamic;
|
|
80
90
|
private attrValue;
|
|
91
|
+
private attrValueDynamic;
|
|
92
|
+
private attrValueSegments;
|
|
81
93
|
private quote;
|
|
82
94
|
/** Whether whitespace (or an expression) separates the next attribute. */
|
|
83
95
|
private separated;
|
|
@@ -101,10 +113,16 @@ export declare class XmlScanner {
|
|
|
101
113
|
* accepted where any output can be valid, otherwise it throws.
|
|
102
114
|
*/
|
|
103
115
|
unknown(position: Position, label: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Marks the attribute value being scanned as dynamic, when a block is in
|
|
118
|
+
* it. Its content is scanned on a copy, so `unknown` is not called.
|
|
119
|
+
*/
|
|
120
|
+
dynamicValue(): void;
|
|
104
121
|
/** Throws unless everything fed is complete. */
|
|
105
122
|
finish(position: Position): void;
|
|
106
123
|
private step;
|
|
107
124
|
private declare;
|
|
125
|
+
private addValueSegment;
|
|
108
126
|
private startEntity;
|
|
109
127
|
private missingValue;
|
|
110
128
|
private endAttribute;
|
package/dist/xml/scanner.js
CHANGED
|
@@ -53,6 +53,8 @@ var XmlScanner = class XmlScanner {
|
|
|
53
53
|
attrName = "";
|
|
54
54
|
attrDynamic = false;
|
|
55
55
|
attrValue = "";
|
|
56
|
+
attrValueDynamic = false;
|
|
57
|
+
attrValueSegments = [];
|
|
56
58
|
quote = "\"";
|
|
57
59
|
/** Whether whitespace (or an expression) separates the next attribute. */
|
|
58
60
|
separated = false;
|
|
@@ -72,7 +74,8 @@ var XmlScanner = class XmlScanner {
|
|
|
72
74
|
Object.assign(copy, this, {
|
|
73
75
|
stack: [...this.stack],
|
|
74
76
|
attributes: [...this.attributes],
|
|
75
|
-
attributeNames: new Set(this.attributeNames)
|
|
77
|
+
attributeNames: new Set(this.attributeNames),
|
|
78
|
+
attrValueSegments: [...this.attrValueSegments]
|
|
76
79
|
});
|
|
77
80
|
return copy;
|
|
78
81
|
}
|
|
@@ -95,6 +98,8 @@ var XmlScanner = class XmlScanner {
|
|
|
95
98
|
}
|
|
96
99
|
/** Scans `text`, `base` is added to the offsets of `onStartTag`. */
|
|
97
100
|
feed(text, locate, base = 0) {
|
|
101
|
+
if (this.mode === "attrValue") this.addValueSegment(this.attrValue.length, locate, 0);
|
|
102
|
+
else if (this.mode === "entity" && this.entityReturn === "attrValue") this.addValueSegment(this.attrValue.length + this.buffer.length, locate, 0);
|
|
98
103
|
for (let i = 0; i < text.length; i++) this.step(text[i], i, locate, base);
|
|
99
104
|
}
|
|
100
105
|
/**
|
|
@@ -104,8 +109,10 @@ var XmlScanner = class XmlScanner {
|
|
|
104
109
|
unknown(position, label) {
|
|
105
110
|
const error = (message) => new XmlSyntaxError(`${label} ${message}`, position);
|
|
106
111
|
switch (this.mode) {
|
|
107
|
-
case "text":
|
|
108
112
|
case "attrValue":
|
|
113
|
+
this.attrValueDynamic = true;
|
|
114
|
+
return;
|
|
115
|
+
case "text":
|
|
109
116
|
case "comment":
|
|
110
117
|
case "cdata":
|
|
111
118
|
case "pi":
|
|
@@ -126,6 +133,13 @@ var XmlScanner = class XmlScanner {
|
|
|
126
133
|
default: throw error(`can not be inside ${DESCRIPTIONS[this.mode]}`);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Marks the attribute value being scanned as dynamic, when a block is in
|
|
138
|
+
* it. Its content is scanned on a copy, so `unknown` is not called.
|
|
139
|
+
*/
|
|
140
|
+
dynamicValue() {
|
|
141
|
+
if (this.mode === "attrValue") this.attrValueDynamic = true;
|
|
142
|
+
}
|
|
129
143
|
/** Throws unless everything fed is complete. */
|
|
130
144
|
finish(position) {
|
|
131
145
|
if (this.mode !== "text") throw new XmlSyntaxError(`Unexpected end inside ${DESCRIPTIONS[this.mode]}${this.tagName ? ` of <${this.tagName}>` : ""}`, this.mode === "comment" || this.mode === "cdata" || this.mode === "entity" ? this.entityPosition : this.tagPosition);
|
|
@@ -199,6 +213,9 @@ var XmlScanner = class XmlScanner {
|
|
|
199
213
|
this.mode = "attrValue";
|
|
200
214
|
this.quote = char;
|
|
201
215
|
this.attrValue = "";
|
|
216
|
+
this.attrValueDynamic = false;
|
|
217
|
+
this.attrValueSegments = [];
|
|
218
|
+
this.addValueSegment(0, locate, i + 1);
|
|
202
219
|
} else if (!WHITESPACE.test(char)) fail(`The value of the attribute '${this.attrName}' of <${this.tagName}> has to be quoted`);
|
|
203
220
|
return;
|
|
204
221
|
case "attrValue":
|
|
@@ -295,6 +312,13 @@ var XmlScanner = class XmlScanner {
|
|
|
295
312
|
position: this.tagPosition
|
|
296
313
|
});
|
|
297
314
|
}
|
|
315
|
+
addValueSegment(index, locate, textIndex) {
|
|
316
|
+
this.attrValueSegments.push({
|
|
317
|
+
index,
|
|
318
|
+
locate,
|
|
319
|
+
textIndex
|
|
320
|
+
});
|
|
321
|
+
}
|
|
298
322
|
startEntity(from, position) {
|
|
299
323
|
this.mode = "entity";
|
|
300
324
|
this.entityReturn = from;
|
|
@@ -312,7 +336,9 @@ var XmlScanner = class XmlScanner {
|
|
|
312
336
|
this.attributes.push({
|
|
313
337
|
name: this.attrName,
|
|
314
338
|
value: this.attrValue,
|
|
315
|
-
quote: this.quote
|
|
339
|
+
quote: this.quote,
|
|
340
|
+
dynamic: this.attrDynamic || this.attrValueDynamic,
|
|
341
|
+
positionOf: positionInSegments(this.attrValueSegments)
|
|
316
342
|
});
|
|
317
343
|
this.mode = "inTag";
|
|
318
344
|
this.separated = false;
|
|
@@ -353,6 +379,12 @@ var XmlScanner = class XmlScanner {
|
|
|
353
379
|
function describeElement({ name, position }) {
|
|
354
380
|
return `<${name}> (opened at ${position.line}:${position.column + 1})`;
|
|
355
381
|
}
|
|
382
|
+
function positionInSegments(segments) {
|
|
383
|
+
return (index) => {
|
|
384
|
+
const segment = segments.findLast((item) => item.index <= index) ?? segments[0];
|
|
385
|
+
return segment.locate(segment.textIndex + index - segment.index);
|
|
386
|
+
};
|
|
387
|
+
}
|
|
356
388
|
/** Positions in a single text, computed from the offsets of its line starts. */
|
|
357
389
|
function createLocate(text, start = {
|
|
358
390
|
line: 1,
|
package/dist/xml/scanner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.js","names":[],"sources":["../../src/xml/scanner.ts"],"sourcesContent":["/**\n * An incremental XML scanner. Text is fed in pieces and parts whose content is\n * unknown (Handlebars expressions) can be marked in between, so a template can\n * be checked without being rendered. The state can be copied to follow the\n * branches of a block separately.\n *\n * It checks well-formedness only: tags, attributes, entities, comments, CDATA\n * sections, processing instructions and the doctype. The first error throws.\n */\n\nexport interface Position {\n\t/** 1-based line. */\n\tline: number;\n\t/** 0-based column, as Handlebars has it, messages show it 1-based. */\n\tcolumn: number;\n}\n\n/** Position of a character of the text fed, by its index in that text. */\nexport type Locate = (index: number) => Position;\n\nexport class XmlSyntaxError extends Error {\n\treadonly position: Position;\n\n\tconstructor(message: string, position: Position) {\n\t\tsuper(message);\n\t\tthis.name = 'XmlSyntaxError';\n\t\tthis.position = position;\n\t}\n}\n\nexport interface ScannedAttribute {\n\tname: string;\n\t/** The value as it is written, entities are not decoded. */\n\tvalue: string;\n\tquote: '\"' | \"'\";\n}\n\nexport interface ScannedStartTag {\n\tname: string;\n\t/** Offset of `<` in the text fed, see `ScannerOptions.onStartTag`. */\n\tstart: number;\n\t/** Offset right after `>`. */\n\tend: number;\n\tattributes: ScannedAttribute[];\n\tselfClosing: boolean;\n\t/** The element the tag is in, undefined at the top. */\n\tparent?: OpenElement;\n}\n\nexport interface ScannerOptions {\n\t/**\n\t * A whole document: a single root element and no text out of it. A template\n\t * is a fragment, which can have anything at its top.\n\t */\n\tdocument?: boolean;\n\t/** Called with every start tag, only when the text is fed in one piece. */\n\tonStartTag?(tag: ScannedStartTag): void;\n\t/** Called with the XML declaration and the doctype, like `onStartTag`. */\n\tonDeclaration?(declaration: ScannedDeclaration): void;\n}\n\nexport interface ScannedDeclaration {\n\tkind: 'xml' | 'doctype';\n\t/** What is between `<?` and `?>`, or `<!` and `>`. */\n\tcontent: string;\n\t/** Offset of `<`. */\n\tstart: number;\n\t/** Offset right after `>`. */\n\tend: number;\n\tposition: Position;\n}\n\ntype Mode =\n\t| 'text'\n\t| 'tagOpen'\n\t| 'startTagName'\n\t| 'inTag'\n\t| 'attrName'\n\t| 'afterAttrName'\n\t| 'beforeAttrValue'\n\t| 'attrValue'\n\t| 'selfClose'\n\t| 'endTagName'\n\t| 'afterEndTagName'\n\t| 'markup'\n\t| 'comment'\n\t| 'cdata'\n\t| 'doctype'\n\t| 'pi'\n\t| 'entity';\n\nexport interface OpenElement {\n\tname: string;\n\tposition: Position;\n\t/** Offset of the `<` of its start tag. */\n\tstart: number;\n}\n\nconst NAME_START = /[A-Za-z_:À-]/;\nconst NAME_CHAR = /[A-Za-z0-9_:.\\-·À-]/;\nconst WHITESPACE = /[ \\t\\r\\n]/;\nconst ENTITY = /^(?:amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+)$/;\nconst ENTITY_CHAR = /[A-Za-z0-9#]/;\nconst MARKUP = ['--', '[CDATA[', 'DOCTYPE'];\n\nconst DESCRIPTIONS: Record<Mode, string> = {\n\ttext: 'text',\n\ttagOpen: \"a tag right after '<'\",\n\tstartTagName: 'the name of a tag',\n\tinTag: 'a tag',\n\tattrName: 'the name of an attribute',\n\tafterAttrName: 'an attribute',\n\tbeforeAttrValue: 'an attribute',\n\tattrValue: 'an attribute value',\n\tselfClose: \"a tag right after '/'\",\n\tendTagName: 'a closing tag',\n\tafterEndTagName: 'a closing tag',\n\tmarkup: \"markup right after '<!'\",\n\tcomment: 'a comment',\n\tcdata: 'a CDATA section',\n\tdoctype: 'the doctype',\n\tpi: 'a processing instruction',\n\tentity: 'an entity reference',\n};\n\nexport class XmlScanner {\n\tprivate readonly options: ScannerOptions;\n\n\tprivate mode: Mode = 'text';\n\t/** The elements open, innermost last. */\n\tprivate stack: OpenElement[] = [];\n\tprivate rootClosed = false;\n\n\t// the tag being scanned\n\tprivate tagName = '';\n\tprivate tagPosition: Position = { line: 1, column: 0 };\n\tprivate tagStart = 0;\n\tprivate attributes: ScannedAttribute[] = [];\n\tprivate attributeNames = new Set<string>();\n\tprivate attrName = '';\n\tprivate attrDynamic = false;\n\tprivate attrValue = '';\n\tprivate quote: '\"' | \"'\" = '\"';\n\t/** Whether whitespace (or an expression) separates the next attribute. */\n\tprivate separated = false;\n\tprivate buffer = '';\n\tprivate count = 0;\n\tprivate entityReturn: 'text' | 'attrValue' = 'text';\n\tprivate entityPosition: Position = { line: 1, column: 0 };\n\n\tconstructor(options: ScannerOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/** A copy to scan a branch with. */\n\tclone(): XmlScanner {\n\t\tconst copy = new XmlScanner(this.options);\n\t\tObject.assign(copy, this, {\n\t\t\tstack: [...this.stack],\n\t\t\tattributes: [...this.attributes],\n\t\t\tattributeNames: new Set(this.attributeNames),\n\t\t});\n\t\treturn copy;\n\t}\n\n\t/** Continues from the state of `other`, a scanned branch. */\n\tassign(other: XmlScanner): void {\n\t\tObject.assign(this, other.clone());\n\t}\n\n\t/** Compares the parts of the state that make the following text valid or not. */\n\tsignature(): string {\n\t\tconst inside = this.mode === 'attrValue' ? this.quote : '';\n\t\treturn `${this.mode}${inside}|${this.stack.map((element) => element.name).join('/')}|${this.tagName}`;\n\t}\n\n\t/** Describes how this state differs from `start`, after a block. */\n\tdescribeChange(start: XmlScanner): string {\n\t\tconst opened = this.stack.slice(start.stack.length);\n\t\tconst common = this.stack.length >= start.stack.length;\n\n\t\tif (common && opened.length > 0 && this.mode === 'text') {\n\t\t\treturn `opens ${opened.map(describeElement).join(', ')} without closing ${opened.length > 1 ? 'them' : 'it'}`;\n\t\t}\n\t\tif (this.stack.length < start.stack.length) {\n\t\t\tconst closed = start.stack.slice(this.stack.length);\n\t\t\treturn `closes ${closed.map(describeElement).join(', ')}, opened before it`;\n\t\t}\n\t\tif (this.mode !== start.mode) {\n\t\t\treturn `ends inside ${DESCRIPTIONS[this.mode]}${this.tagName ? ` of <${this.tagName}>` : ''}, but starts inside ${DESCRIPTIONS[start.mode]}`;\n\t\t}\n\t\treturn 'changes the XML structure';\n\t}\n\n\t/** Scans `text`, `base` is added to the offsets of `onStartTag`. */\n\tfeed(text: string, locate: Locate, base = 0): void {\n\t\tfor (let i = 0; i < text.length; i++) {\n\t\t\tthis.step(text[i], i, locate, base);\n\t\t}\n\t}\n\n\t/**\n\t * Marks output unknown until rendering, a Handlebars expression. It is\n\t * accepted where any output can be valid, otherwise it throws.\n\t */\n\tunknown(position: Position, label: string): void {\n\t\tconst error = (message: string) =>\n\t\t\tnew XmlSyntaxError(`${label} ${message}`, position);\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'text':\n\t\t\tcase 'attrValue':\n\t\t\tcase 'comment':\n\t\t\tcase 'cdata':\n\t\t\tcase 'pi':\n\t\t\tcase 'doctype':\n\t\t\t\treturn;\n\t\t\tcase 'inTag':\n\t\t\t\t// it can render attributes, which have to be separated as well\n\t\t\t\tthis.separated = true;\n\t\t\t\treturn;\n\t\t\tcase 'attrName':\n\t\t\t\tthis.attrDynamic = true;\n\t\t\t\treturn;\n\t\t\tcase 'afterAttrName':\n\t\t\tcase 'beforeAttrValue':\n\t\t\t\tthrow error(\n\t\t\t\t\t`is the value of the attribute '${this.attrName}', which has to be quoted: ${this.attrName}='...'`,\n\t\t\t\t);\n\t\t\tcase 'tagOpen':\n\t\t\tcase 'startTagName':\n\t\t\tcase 'endTagName':\n\t\t\tcase 'afterEndTagName':\n\t\t\t\tthrow error('can not be (part of) the name of a tag');\n\t\t\tcase 'entity':\n\t\t\t\tthrow error(\n\t\t\t\t\t\"can not be part of an entity reference, escape '&' as '&'\",\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tthrow error(`can not be inside ${DESCRIPTIONS[this.mode]}`);\n\t\t}\n\t}\n\n\t/** Throws unless everything fed is complete. */\n\tfinish(position: Position): void {\n\t\tif (this.mode !== 'text') {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`Unexpected end inside ${DESCRIPTIONS[this.mode]}${this.tagName ? ` of <${this.tagName}>` : ''}`,\n\t\t\t\tthis.mode === 'comment' ||\n\t\t\t\t\tthis.mode === 'cdata' ||\n\t\t\t\t\tthis.mode === 'entity'\n\t\t\t\t\t? this.entityPosition\n\t\t\t\t\t: this.tagPosition,\n\t\t\t);\n\t\t}\n\t\tconst open = this.stack.at(-1);\n\t\tif (open) {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`${describeElement(open)} is not closed`,\n\t\t\t\topen.position,\n\t\t\t);\n\t\t}\n\t\tif (this.options.document && !this.rootClosed) {\n\t\t\tthrow new XmlSyntaxError('The document has no root element', position);\n\t\t}\n\t}\n\n\tprivate step(char: string, i: number, locate: Locate, base: number): void {\n\t\tconst fail = (message: string): never => {\n\t\t\tthrow new XmlSyntaxError(message, locate(i));\n\t\t};\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'text':\n\t\t\t\tif (char === '<') {\n\t\t\t\t\tthis.mode = 'tagOpen';\n\t\t\t\t\tthis.tagStart = base + i;\n\t\t\t\t\tthis.tagPosition = locate(i);\n\t\t\t\t} else if (char === '&') {\n\t\t\t\t\tthis.startEntity('text', locate(i));\n\t\t\t\t} else if (\n\t\t\t\t\tthis.options.document &&\n\t\t\t\t\tthis.stack.length === 0 &&\n\t\t\t\t\t!WHITESPACE.test(char)\n\t\t\t\t) {\n\t\t\t\t\tfail(`Text out of the root element: '${char}'`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'tagOpen':\n\t\t\t\tif (char === '/') {\n\t\t\t\t\tthis.mode = 'endTagName';\n\t\t\t\t\tthis.tagName = '';\n\t\t\t\t} else if (char === '!') {\n\t\t\t\t\tthis.mode = 'markup';\n\t\t\t\t\tthis.buffer = '';\n\t\t\t\t} else if (char === '?') {\n\t\t\t\t\tthis.mode = 'pi';\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t\tthis.buffer = '';\n\t\t\t\t} else if (NAME_START.test(char)) {\n\t\t\t\t\tthis.mode = 'startTagName';\n\t\t\t\t\tthis.tagName = char;\n\t\t\t\t\tthis.attributes = [];\n\t\t\t\t\tthis.attributeNames.clear();\n\t\t\t\t} else {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`Invalid character '${char}' after '<', escape a literal '<' as '<'`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'startTagName':\n\t\t\t\tif (NAME_CHAR.test(char)) {\n\t\t\t\t\tthis.tagName += char;\n\t\t\t\t} else if (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'inTag';\n\t\t\t\t\tthis.separated = true;\n\t\t\t\t} else if (char === '>') {\n\t\t\t\t\tthis.endStartTag(false, base + i + 1);\n\t\t\t\t} else if (char === '/') {\n\t\t\t\t\tthis.mode = 'selfClose';\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in the name of <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'inTag':\n\t\t\t\tif (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.separated = true;\n\t\t\t\t} else if (char === '>') {\n\t\t\t\t\tthis.endStartTag(false, base + i + 1);\n\t\t\t\t} else if (char === '/') {\n\t\t\t\t\tthis.mode = 'selfClose';\n\t\t\t\t} else if (NAME_START.test(char)) {\n\t\t\t\t\tif (!this.separated) {\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\t`Missing whitespace between the attributes of <${this.tagName}>`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.mode = 'attrName';\n\t\t\t\t\tthis.attrName = char;\n\t\t\t\t\tthis.attrDynamic = false;\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'attrName':\n\t\t\t\tif (NAME_CHAR.test(char)) {\n\t\t\t\t\tthis.attrName += char;\n\t\t\t\t} else if (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'afterAttrName';\n\t\t\t\t} else if (char === '=') {\n\t\t\t\t\tthis.mode = 'beforeAttrValue';\n\t\t\t\t} else {\n\t\t\t\t\tthis.missingValue(fail);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'afterAttrName':\n\t\t\t\tif (char === '=') {\n\t\t\t\t\tthis.mode = 'beforeAttrValue';\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tthis.missingValue(fail);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'beforeAttrValue':\n\t\t\t\tif (char === '\"' || char === \"'\") {\n\t\t\t\t\tthis.mode = 'attrValue';\n\t\t\t\t\tthis.quote = char;\n\t\t\t\t\tthis.attrValue = '';\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`The value of the attribute '${this.attrName}' of <${this.tagName}> has to be quoted`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'attrValue':\n\t\t\t\tif (char === this.quote) {\n\t\t\t\t\tthis.endAttribute(fail);\n\t\t\t\t} else if (char === '<') {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`'<' is not allowed in the value of the attribute '${this.attrName}', escape it as '<'`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tif (char === '&') {\n\t\t\t\t\t\tthis.startEntity('attrValue', locate(i));\n\t\t\t\t\t}\n\t\t\t\t\tthis.attrValue += char;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'selfClose':\n\t\t\t\tif (char === '>') {\n\t\t\t\t\tthis.endStartTag(true, base + i + 1);\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Expected '>' after '/' in <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'endTagName':\n\t\t\t\tif (\n\t\t\t\t\tthis.tagName === '' ? NAME_START.test(char) : NAME_CHAR.test(char)\n\t\t\t\t) {\n\t\t\t\t\tthis.tagName += char;\n\t\t\t\t} else if (this.tagName !== '' && WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'afterEndTagName';\n\t\t\t\t} else if (this.tagName !== '' && char === '>') {\n\t\t\t\t\tthis.endEndTag();\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in a closing tag`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'afterEndTagName':\n\t\t\t\tif (char === '>') {\n\t\t\t\t\tthis.endEndTag();\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tfail(`Expected '>' in the closing tag </${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'markup': {\n\t\t\t\tthis.buffer += char;\n\t\t\t\tconst match = MARKUP.find((markup) =>\n\t\t\t\t\tmarkup.startsWith(this.buffer.toUpperCase()),\n\t\t\t\t);\n\t\t\t\tif (match === undefined) {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`Invalid markup '<!${this.buffer}', expected a comment, a CDATA section or the doctype`,\n\t\t\t\t\t);\n\t\t\t\t} else if (match === this.buffer.toUpperCase()) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmatch === 'DOCTYPE' &&\n\t\t\t\t\t\tthis.options.document &&\n\t\t\t\t\t\t(this.stack.length > 0 || this.rootClosed)\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\t'The doctype has to come before the root element',\n\t\t\t\t\t\t\tthis.tagPosition,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.entityPosition = this.tagPosition;\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t\tthis.mode =\n\t\t\t\t\t\tmatch === '--'\n\t\t\t\t\t\t\t? 'comment'\n\t\t\t\t\t\t\t: match === 'DOCTYPE'\n\t\t\t\t\t\t\t\t? 'doctype'\n\t\t\t\t\t\t\t\t: 'cdata';\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcase 'comment':\n\t\t\t\tif (char === '-') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else {\n\t\t\t\t\tif (this.count >= 2) {\n\t\t\t\t\t\tif (char !== '>') {\n\t\t\t\t\t\t\tfail(\"'--' is not allowed inside a comment\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\t}\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'cdata':\n\t\t\t\tif (char === ']') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else {\n\t\t\t\t\tif (char === '>' && this.count >= 2) {\n\t\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\t}\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'doctype':\n\t\t\t\tif (char === '[') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else if (char === ']') {\n\t\t\t\t\tthis.count--;\n\t\t\t\t} else if (char === '>' && this.count <= 0) {\n\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\tthis.declare('doctype', this.buffer, base + i + 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.buffer += char;\n\t\t\t\treturn;\n\n\t\t\tcase 'pi':\n\t\t\t\tif (char === '>' && this.count > 0) {\n\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\tconst content = this.buffer.slice(0, -1);\n\t\t\t\t\tconst target = /^[^\\s?]*/.exec(content)?.[0] ?? '';\n\t\t\t\t\tif (target.toLowerCase() === 'xml') {\n\t\t\t\t\t\tif (target !== 'xml') {\n\t\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\t\t`'${target}' is reserved, the XML declaration is '<?xml ...?>'`,\n\t\t\t\t\t\t\t\tthis.tagPosition,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.declare('xml', content, base + i + 1);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.buffer += char;\n\t\t\t\tthis.count = char === '?' ? 1 : 0;\n\t\t\t\treturn;\n\n\t\t\tcase 'entity':\n\t\t\t\tif (char === ';') {\n\t\t\t\t\tif (!ENTITY.test(this.buffer)) {\n\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\tthis.buffer === ''\n\t\t\t\t\t\t\t\t? \"'&' has to be escaped as '&'\"\n\t\t\t\t\t\t\t\t: `Undefined entity '&${this.buffer};', XML only has & < > " ', use a numeric reference like ' ' for others`,\n\t\t\t\t\t\t\tthis.entityPosition,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.mode = this.entityReturn;\n\t\t\t\t\tif (this.mode === 'attrValue') {\n\t\t\t\t\t\tthis.attrValue += `${this.buffer};`;\n\t\t\t\t\t}\n\t\t\t\t} else if (ENTITY_CHAR.test(char) && this.buffer.length < 32) {\n\t\t\t\t\tthis.buffer += char;\n\t\t\t\t} else {\n\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\"'&' has to be escaped as '&'\",\n\t\t\t\t\t\tthis.entityPosition,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate declare(\n\t\tkind: ScannedDeclaration['kind'],\n\t\tcontent: string,\n\t\tend: number,\n\t): void {\n\t\tthis.options.onDeclaration?.({\n\t\t\tkind,\n\t\t\tcontent,\n\t\t\tstart: this.tagStart,\n\t\t\tend,\n\t\t\tposition: this.tagPosition,\n\t\t});\n\t}\n\n\tprivate startEntity(from: 'text' | 'attrValue', position: Position): void {\n\t\tthis.mode = 'entity';\n\t\tthis.entityReturn = from;\n\t\tthis.entityPosition = position;\n\t\tthis.buffer = '';\n\t}\n\n\tprivate missingValue(fail: (message: string) => never): never {\n\t\treturn fail(\n\t\t\t`The attribute '${this.attrName}' of <${this.tagName}> has no value, XML requires one: ${this.attrName}='...'`,\n\t\t);\n\t}\n\n\tprivate endAttribute(fail: (message: string) => never): void {\n\t\tif (!this.attrDynamic) {\n\t\t\tif (this.attributeNames.has(this.attrName)) {\n\t\t\t\tfail(`Duplicate attribute '${this.attrName}' in <${this.tagName}>`);\n\t\t\t}\n\t\t\tthis.attributeNames.add(this.attrName);\n\t\t}\n\t\tthis.attributes.push({\n\t\t\tname: this.attrName,\n\t\t\tvalue: this.attrValue,\n\t\t\tquote: this.quote,\n\t\t});\n\t\tthis.mode = 'inTag';\n\t\tthis.separated = false;\n\t}\n\n\tprivate endStartTag(selfClosing: boolean, end: number): void {\n\t\tif (this.options.document && this.stack.length === 0 && this.rootClosed) {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`Only one root element is allowed, found <${this.tagName}> after it`,\n\t\t\t\tthis.tagPosition,\n\t\t\t);\n\t\t}\n\n\t\tthis.options.onStartTag?.({\n\t\t\tname: this.tagName,\n\t\t\tstart: this.tagStart,\n\t\t\tend,\n\t\t\tattributes: this.attributes,\n\t\t\tselfClosing,\n\t\t\tparent: this.stack.at(-1),\n\t\t});\n\n\t\tif (selfClosing) {\n\t\t\tif (this.stack.length === 0) {\n\t\t\t\tthis.rootClosed = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.stack.push({\n\t\t\t\tname: this.tagName,\n\t\t\t\tposition: this.tagPosition,\n\t\t\t\tstart: this.tagStart,\n\t\t\t});\n\t\t}\n\t\tthis.tagName = '';\n\t\tthis.mode = 'text';\n\t}\n\n\tprivate endEndTag(): void {\n\t\t// reported where the closing tag starts\n\t\tconst fail = (message: string): never => {\n\t\t\tthrow new XmlSyntaxError(message, this.tagPosition);\n\t\t};\n\n\t\tconst open = this.stack.at(-1);\n\t\tif (open === undefined) {\n\t\t\tfail(`Closing tag </${this.tagName}> has no opening tag`);\n\t\t} else if (open.name !== this.tagName) {\n\t\t\tfail(\n\t\t\t\t`Expected </${open.name}> to close ${describeElement(open)}, found </${this.tagName}>`,\n\t\t\t);\n\t\t}\n\t\tthis.stack.pop();\n\t\tif (this.stack.length === 0) {\n\t\t\tthis.rootClosed = true;\n\t\t}\n\t\tthis.tagName = '';\n\t\tthis.mode = 'text';\n\t}\n}\n\nfunction describeElement({ name, position }: OpenElement): string {\n\treturn `<${name}> (opened at ${position.line}:${position.column + 1})`;\n}\n\n/** Positions in a single text, computed from the offsets of its line starts. */\nexport function createLocate(\n\ttext: string,\n\tstart: Position = { line: 1, column: 0 },\n): Locate {\n\tconst lineStarts = [0];\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text[i] === '\\n') {\n\t\t\tlineStarts.push(i + 1);\n\t\t}\n\t}\n\n\treturn (index) => {\n\t\tlet low = 0;\n\t\tlet high = lineStarts.length - 1;\n\t\twhile (low < high) {\n\t\t\tconst middle = (low + high + 1) >> 1;\n\t\t\tif (lineStarts[middle] <= index) {\n\t\t\t\tlow = middle;\n\t\t\t} else {\n\t\t\t\thigh = middle - 1;\n\t\t\t}\n\t\t}\n\t\treturn low === 0\n\t\t\t? { line: start.line, column: start.column + index }\n\t\t\t: { line: start.line + low, column: index - lineStarts[low] };\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scanner.js","names":[],"sources":["../../src/xml/scanner.ts"],"sourcesContent":["/**\n * An incremental XML scanner. Text is fed in pieces and parts whose content is\n * unknown (Handlebars expressions) can be marked in between, so a template can\n * be checked without being rendered. The state can be copied to follow the\n * branches of a block separately.\n *\n * It checks well-formedness only: tags, attributes, entities, comments, CDATA\n * sections, processing instructions and the doctype. The first error throws.\n */\n\nexport interface Position {\n\t/** 1-based line. */\n\tline: number;\n\t/** 0-based column, as Handlebars has it, messages show it 1-based. */\n\tcolumn: number;\n}\n\n/** Position of a character of the text fed, by its index in that text. */\nexport type Locate = (index: number) => Position;\n\nexport class XmlSyntaxError extends Error {\n\treadonly position: Position;\n\n\tconstructor(message: string, position: Position) {\n\t\tsuper(message);\n\t\tthis.name = 'XmlSyntaxError';\n\t\tthis.position = position;\n\t}\n}\n\nexport interface ScannedAttribute {\n\tname: string;\n\t/** The value as it is written, entities are not decoded. */\n\tvalue: string;\n\tquote: '\"' | \"'\";\n\t/**\n\t * Whether the name or the value has output unknown until rendering, a\n\t * Handlebars expression or block, `value` is then only the text around it.\n\t */\n\tdynamic: boolean;\n\t/** Position of the character of `value` at `index`, of the closing quote at its length. */\n\tpositionOf(index: number): Position;\n}\n\n/** Where a piece of an attribute value is in the text it was fed with. */\ninterface ValueSegment {\n\t/** Index in the value of its first character. */\n\tindex: number;\n\tlocate: Locate;\n\t/** Index in the text fed of its first character. */\n\ttextIndex: number;\n}\n\nexport interface ScannedStartTag {\n\tname: string;\n\t/** Offset of `<` in the text fed, see `ScannerOptions.onStartTag`. */\n\tstart: number;\n\t/** Offset right after `>`. */\n\tend: number;\n\tattributes: ScannedAttribute[];\n\tselfClosing: boolean;\n\t/** The element the tag is in, undefined at the top. */\n\tparent?: OpenElement;\n}\n\nexport interface ScannerOptions {\n\t/**\n\t * A whole document: a single root element and no text out of it. A template\n\t * is a fragment, which can have anything at its top.\n\t */\n\tdocument?: boolean;\n\t/**\n\t * Called with every start tag. Its offsets are only right when the text is\n\t * fed in one piece, `ScannedAttribute.positionOf` always is.\n\t */\n\tonStartTag?(tag: ScannedStartTag): void;\n\t/** Called with the XML declaration and the doctype, like `onStartTag`. */\n\tonDeclaration?(declaration: ScannedDeclaration): void;\n}\n\nexport interface ScannedDeclaration {\n\tkind: 'xml' | 'doctype';\n\t/** What is between `<?` and `?>`, or `<!` and `>`. */\n\tcontent: string;\n\t/** Offset of `<`. */\n\tstart: number;\n\t/** Offset right after `>`. */\n\tend: number;\n\tposition: Position;\n}\n\ntype Mode =\n\t| 'text'\n\t| 'tagOpen'\n\t| 'startTagName'\n\t| 'inTag'\n\t| 'attrName'\n\t| 'afterAttrName'\n\t| 'beforeAttrValue'\n\t| 'attrValue'\n\t| 'selfClose'\n\t| 'endTagName'\n\t| 'afterEndTagName'\n\t| 'markup'\n\t| 'comment'\n\t| 'cdata'\n\t| 'doctype'\n\t| 'pi'\n\t| 'entity';\n\nexport interface OpenElement {\n\tname: string;\n\tposition: Position;\n\t/** Offset of the `<` of its start tag. */\n\tstart: number;\n}\n\nconst NAME_START = /[A-Za-z_:À-]/;\nconst NAME_CHAR = /[A-Za-z0-9_:.\\-·À-]/;\nconst WHITESPACE = /[ \\t\\r\\n]/;\nconst ENTITY = /^(?:amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+)$/;\nconst ENTITY_CHAR = /[A-Za-z0-9#]/;\nconst MARKUP = ['--', '[CDATA[', 'DOCTYPE'];\n\nconst DESCRIPTIONS: Record<Mode, string> = {\n\ttext: 'text',\n\ttagOpen: \"a tag right after '<'\",\n\tstartTagName: 'the name of a tag',\n\tinTag: 'a tag',\n\tattrName: 'the name of an attribute',\n\tafterAttrName: 'an attribute',\n\tbeforeAttrValue: 'an attribute',\n\tattrValue: 'an attribute value',\n\tselfClose: \"a tag right after '/'\",\n\tendTagName: 'a closing tag',\n\tafterEndTagName: 'a closing tag',\n\tmarkup: \"markup right after '<!'\",\n\tcomment: 'a comment',\n\tcdata: 'a CDATA section',\n\tdoctype: 'the doctype',\n\tpi: 'a processing instruction',\n\tentity: 'an entity reference',\n};\n\nexport class XmlScanner {\n\tprivate readonly options: ScannerOptions;\n\n\tprivate mode: Mode = 'text';\n\t/** The elements open, innermost last. */\n\tprivate stack: OpenElement[] = [];\n\tprivate rootClosed = false;\n\n\t// the tag being scanned\n\tprivate tagName = '';\n\tprivate tagPosition: Position = { line: 1, column: 0 };\n\tprivate tagStart = 0;\n\tprivate attributes: ScannedAttribute[] = [];\n\tprivate attributeNames = new Set<string>();\n\tprivate attrName = '';\n\tprivate attrDynamic = false;\n\tprivate attrValue = '';\n\tprivate attrValueDynamic = false;\n\tprivate attrValueSegments: ValueSegment[] = [];\n\tprivate quote: '\"' | \"'\" = '\"';\n\t/** Whether whitespace (or an expression) separates the next attribute. */\n\tprivate separated = false;\n\tprivate buffer = '';\n\tprivate count = 0;\n\tprivate entityReturn: 'text' | 'attrValue' = 'text';\n\tprivate entityPosition: Position = { line: 1, column: 0 };\n\n\tconstructor(options: ScannerOptions = {}) {\n\t\tthis.options = options;\n\t}\n\n\t/** A copy to scan a branch with. */\n\tclone(): XmlScanner {\n\t\tconst copy = new XmlScanner(this.options);\n\t\tObject.assign(copy, this, {\n\t\t\tstack: [...this.stack],\n\t\t\tattributes: [...this.attributes],\n\t\t\tattributeNames: new Set(this.attributeNames),\n\t\t\tattrValueSegments: [...this.attrValueSegments],\n\t\t});\n\t\treturn copy;\n\t}\n\n\t/** Continues from the state of `other`, a scanned branch. */\n\tassign(other: XmlScanner): void {\n\t\tObject.assign(this, other.clone());\n\t}\n\n\t/** Compares the parts of the state that make the following text valid or not. */\n\tsignature(): string {\n\t\tconst inside = this.mode === 'attrValue' ? this.quote : '';\n\t\treturn `${this.mode}${inside}|${this.stack.map((element) => element.name).join('/')}|${this.tagName}`;\n\t}\n\n\t/** Describes how this state differs from `start`, after a block. */\n\tdescribeChange(start: XmlScanner): string {\n\t\tconst opened = this.stack.slice(start.stack.length);\n\t\tconst common = this.stack.length >= start.stack.length;\n\n\t\tif (common && opened.length > 0 && this.mode === 'text') {\n\t\t\treturn `opens ${opened.map(describeElement).join(', ')} without closing ${opened.length > 1 ? 'them' : 'it'}`;\n\t\t}\n\t\tif (this.stack.length < start.stack.length) {\n\t\t\tconst closed = start.stack.slice(this.stack.length);\n\t\t\treturn `closes ${closed.map(describeElement).join(', ')}, opened before it`;\n\t\t}\n\t\tif (this.mode !== start.mode) {\n\t\t\treturn `ends inside ${DESCRIPTIONS[this.mode]}${this.tagName ? ` of <${this.tagName}>` : ''}, but starts inside ${DESCRIPTIONS[start.mode]}`;\n\t\t}\n\t\treturn 'changes the XML structure';\n\t}\n\n\t/** Scans `text`, `base` is added to the offsets of `onStartTag`. */\n\tfeed(text: string, locate: Locate, base = 0): void {\n\t\tif (this.mode === 'attrValue') {\n\t\t\tthis.addValueSegment(this.attrValue.length, locate, 0);\n\t\t} else if (this.mode === 'entity' && this.entityReturn === 'attrValue') {\n\t\t\tthis.addValueSegment(\n\t\t\t\tthis.attrValue.length + this.buffer.length,\n\t\t\t\tlocate,\n\t\t\t\t0,\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < text.length; i++) {\n\t\t\tthis.step(text[i], i, locate, base);\n\t\t}\n\t}\n\n\t/**\n\t * Marks output unknown until rendering, a Handlebars expression. It is\n\t * accepted where any output can be valid, otherwise it throws.\n\t */\n\tunknown(position: Position, label: string): void {\n\t\tconst error = (message: string) =>\n\t\t\tnew XmlSyntaxError(`${label} ${message}`, position);\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'attrValue':\n\t\t\t\tthis.attrValueDynamic = true;\n\t\t\t\treturn;\n\t\t\tcase 'text':\n\t\t\tcase 'comment':\n\t\t\tcase 'cdata':\n\t\t\tcase 'pi':\n\t\t\tcase 'doctype':\n\t\t\t\treturn;\n\t\t\tcase 'inTag':\n\t\t\t\t// it can render attributes, which have to be separated as well\n\t\t\t\tthis.separated = true;\n\t\t\t\treturn;\n\t\t\tcase 'attrName':\n\t\t\t\tthis.attrDynamic = true;\n\t\t\t\treturn;\n\t\t\tcase 'afterAttrName':\n\t\t\tcase 'beforeAttrValue':\n\t\t\t\tthrow error(\n\t\t\t\t\t`is the value of the attribute '${this.attrName}', which has to be quoted: ${this.attrName}='...'`,\n\t\t\t\t);\n\t\t\tcase 'tagOpen':\n\t\t\tcase 'startTagName':\n\t\t\tcase 'endTagName':\n\t\t\tcase 'afterEndTagName':\n\t\t\t\tthrow error('can not be (part of) the name of a tag');\n\t\t\tcase 'entity':\n\t\t\t\tthrow error(\n\t\t\t\t\t\"can not be part of an entity reference, escape '&' as '&'\",\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tthrow error(`can not be inside ${DESCRIPTIONS[this.mode]}`);\n\t\t}\n\t}\n\n\t/**\n\t * Marks the attribute value being scanned as dynamic, when a block is in\n\t * it. Its content is scanned on a copy, so `unknown` is not called.\n\t */\n\tdynamicValue(): void {\n\t\tif (this.mode === 'attrValue') {\n\t\t\tthis.attrValueDynamic = true;\n\t\t}\n\t}\n\n\t/** Throws unless everything fed is complete. */\n\tfinish(position: Position): void {\n\t\tif (this.mode !== 'text') {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`Unexpected end inside ${DESCRIPTIONS[this.mode]}${this.tagName ? ` of <${this.tagName}>` : ''}`,\n\t\t\t\tthis.mode === 'comment' ||\n\t\t\t\t\tthis.mode === 'cdata' ||\n\t\t\t\t\tthis.mode === 'entity'\n\t\t\t\t\t? this.entityPosition\n\t\t\t\t\t: this.tagPosition,\n\t\t\t);\n\t\t}\n\t\tconst open = this.stack.at(-1);\n\t\tif (open) {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`${describeElement(open)} is not closed`,\n\t\t\t\topen.position,\n\t\t\t);\n\t\t}\n\t\tif (this.options.document && !this.rootClosed) {\n\t\t\tthrow new XmlSyntaxError('The document has no root element', position);\n\t\t}\n\t}\n\n\tprivate step(char: string, i: number, locate: Locate, base: number): void {\n\t\tconst fail = (message: string): never => {\n\t\t\tthrow new XmlSyntaxError(message, locate(i));\n\t\t};\n\n\t\tswitch (this.mode) {\n\t\t\tcase 'text':\n\t\t\t\tif (char === '<') {\n\t\t\t\t\tthis.mode = 'tagOpen';\n\t\t\t\t\tthis.tagStart = base + i;\n\t\t\t\t\tthis.tagPosition = locate(i);\n\t\t\t\t} else if (char === '&') {\n\t\t\t\t\tthis.startEntity('text', locate(i));\n\t\t\t\t} else if (\n\t\t\t\t\tthis.options.document &&\n\t\t\t\t\tthis.stack.length === 0 &&\n\t\t\t\t\t!WHITESPACE.test(char)\n\t\t\t\t) {\n\t\t\t\t\tfail(`Text out of the root element: '${char}'`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'tagOpen':\n\t\t\t\tif (char === '/') {\n\t\t\t\t\tthis.mode = 'endTagName';\n\t\t\t\t\tthis.tagName = '';\n\t\t\t\t} else if (char === '!') {\n\t\t\t\t\tthis.mode = 'markup';\n\t\t\t\t\tthis.buffer = '';\n\t\t\t\t} else if (char === '?') {\n\t\t\t\t\tthis.mode = 'pi';\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t\tthis.buffer = '';\n\t\t\t\t} else if (NAME_START.test(char)) {\n\t\t\t\t\tthis.mode = 'startTagName';\n\t\t\t\t\tthis.tagName = char;\n\t\t\t\t\tthis.attributes = [];\n\t\t\t\t\tthis.attributeNames.clear();\n\t\t\t\t} else {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`Invalid character '${char}' after '<', escape a literal '<' as '<'`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'startTagName':\n\t\t\t\tif (NAME_CHAR.test(char)) {\n\t\t\t\t\tthis.tagName += char;\n\t\t\t\t} else if (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'inTag';\n\t\t\t\t\tthis.separated = true;\n\t\t\t\t} else if (char === '>') {\n\t\t\t\t\tthis.endStartTag(false, base + i + 1);\n\t\t\t\t} else if (char === '/') {\n\t\t\t\t\tthis.mode = 'selfClose';\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in the name of <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'inTag':\n\t\t\t\tif (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.separated = true;\n\t\t\t\t} else if (char === '>') {\n\t\t\t\t\tthis.endStartTag(false, base + i + 1);\n\t\t\t\t} else if (char === '/') {\n\t\t\t\t\tthis.mode = 'selfClose';\n\t\t\t\t} else if (NAME_START.test(char)) {\n\t\t\t\t\tif (!this.separated) {\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\t`Missing whitespace between the attributes of <${this.tagName}>`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.mode = 'attrName';\n\t\t\t\t\tthis.attrName = char;\n\t\t\t\t\tthis.attrDynamic = false;\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'attrName':\n\t\t\t\tif (NAME_CHAR.test(char)) {\n\t\t\t\t\tthis.attrName += char;\n\t\t\t\t} else if (WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'afterAttrName';\n\t\t\t\t} else if (char === '=') {\n\t\t\t\t\tthis.mode = 'beforeAttrValue';\n\t\t\t\t} else {\n\t\t\t\t\tthis.missingValue(fail);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'afterAttrName':\n\t\t\t\tif (char === '=') {\n\t\t\t\t\tthis.mode = 'beforeAttrValue';\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tthis.missingValue(fail);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'beforeAttrValue':\n\t\t\t\tif (char === '\"' || char === \"'\") {\n\t\t\t\t\tthis.mode = 'attrValue';\n\t\t\t\t\tthis.quote = char;\n\t\t\t\t\tthis.attrValue = '';\n\t\t\t\t\tthis.attrValueDynamic = false;\n\t\t\t\t\tthis.attrValueSegments = [];\n\t\t\t\t\tthis.addValueSegment(0, locate, i + 1);\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`The value of the attribute '${this.attrName}' of <${this.tagName}> has to be quoted`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'attrValue':\n\t\t\t\tif (char === this.quote) {\n\t\t\t\t\tthis.endAttribute(fail);\n\t\t\t\t} else if (char === '<') {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`'<' is not allowed in the value of the attribute '${this.attrName}', escape it as '<'`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tif (char === '&') {\n\t\t\t\t\t\tthis.startEntity('attrValue', locate(i));\n\t\t\t\t\t}\n\t\t\t\t\tthis.attrValue += char;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'selfClose':\n\t\t\t\tif (char === '>') {\n\t\t\t\t\tthis.endStartTag(true, base + i + 1);\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Expected '>' after '/' in <${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'endTagName':\n\t\t\t\tif (\n\t\t\t\t\tthis.tagName === '' ? NAME_START.test(char) : NAME_CHAR.test(char)\n\t\t\t\t) {\n\t\t\t\t\tthis.tagName += char;\n\t\t\t\t} else if (this.tagName !== '' && WHITESPACE.test(char)) {\n\t\t\t\t\tthis.mode = 'afterEndTagName';\n\t\t\t\t} else if (this.tagName !== '' && char === '>') {\n\t\t\t\t\tthis.endEndTag();\n\t\t\t\t} else {\n\t\t\t\t\tfail(`Invalid character '${char}' in a closing tag`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'afterEndTagName':\n\t\t\t\tif (char === '>') {\n\t\t\t\t\tthis.endEndTag();\n\t\t\t\t} else if (!WHITESPACE.test(char)) {\n\t\t\t\t\tfail(`Expected '>' in the closing tag </${this.tagName}>`);\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'markup': {\n\t\t\t\tthis.buffer += char;\n\t\t\t\tconst match = MARKUP.find((markup) =>\n\t\t\t\t\tmarkup.startsWith(this.buffer.toUpperCase()),\n\t\t\t\t);\n\t\t\t\tif (match === undefined) {\n\t\t\t\t\tfail(\n\t\t\t\t\t\t`Invalid markup '<!${this.buffer}', expected a comment, a CDATA section or the doctype`,\n\t\t\t\t\t);\n\t\t\t\t} else if (match === this.buffer.toUpperCase()) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tmatch === 'DOCTYPE' &&\n\t\t\t\t\t\tthis.options.document &&\n\t\t\t\t\t\t(this.stack.length > 0 || this.rootClosed)\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\t'The doctype has to come before the root element',\n\t\t\t\t\t\t\tthis.tagPosition,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.entityPosition = this.tagPosition;\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t\tthis.mode =\n\t\t\t\t\t\tmatch === '--'\n\t\t\t\t\t\t\t? 'comment'\n\t\t\t\t\t\t\t: match === 'DOCTYPE'\n\t\t\t\t\t\t\t\t? 'doctype'\n\t\t\t\t\t\t\t\t: 'cdata';\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcase 'comment':\n\t\t\t\tif (char === '-') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else {\n\t\t\t\t\tif (this.count >= 2) {\n\t\t\t\t\t\tif (char !== '>') {\n\t\t\t\t\t\t\tfail(\"'--' is not allowed inside a comment\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\t}\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'cdata':\n\t\t\t\tif (char === ']') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else {\n\t\t\t\t\tif (char === '>' && this.count >= 2) {\n\t\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\t}\n\t\t\t\t\tthis.count = 0;\n\t\t\t\t}\n\t\t\t\treturn;\n\n\t\t\tcase 'doctype':\n\t\t\t\tif (char === '[') {\n\t\t\t\t\tthis.count++;\n\t\t\t\t} else if (char === ']') {\n\t\t\t\t\tthis.count--;\n\t\t\t\t} else if (char === '>' && this.count <= 0) {\n\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\tthis.declare('doctype', this.buffer, base + i + 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.buffer += char;\n\t\t\t\treturn;\n\n\t\t\tcase 'pi':\n\t\t\t\tif (char === '>' && this.count > 0) {\n\t\t\t\t\tthis.mode = 'text';\n\t\t\t\t\tconst content = this.buffer.slice(0, -1);\n\t\t\t\t\tconst target = /^[^\\s?]*/.exec(content)?.[0] ?? '';\n\t\t\t\t\tif (target.toLowerCase() === 'xml') {\n\t\t\t\t\t\tif (target !== 'xml') {\n\t\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\t\t`'${target}' is reserved, the XML declaration is '<?xml ...?>'`,\n\t\t\t\t\t\t\t\tthis.tagPosition,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.declare('xml', content, base + i + 1);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.buffer += char;\n\t\t\t\tthis.count = char === '?' ? 1 : 0;\n\t\t\t\treturn;\n\n\t\t\tcase 'entity':\n\t\t\t\tif (char === ';') {\n\t\t\t\t\tif (!ENTITY.test(this.buffer)) {\n\t\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\tthis.buffer === ''\n\t\t\t\t\t\t\t\t? \"'&' has to be escaped as '&'\"\n\t\t\t\t\t\t\t\t: `Undefined entity '&${this.buffer};', XML only has & < > " ', use a numeric reference like ' ' for others`,\n\t\t\t\t\t\t\tthis.entityPosition,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthis.mode = this.entityReturn;\n\t\t\t\t\tif (this.mode === 'attrValue') {\n\t\t\t\t\t\tthis.attrValue += `${this.buffer};`;\n\t\t\t\t\t}\n\t\t\t\t} else if (ENTITY_CHAR.test(char) && this.buffer.length < 32) {\n\t\t\t\t\tthis.buffer += char;\n\t\t\t\t} else {\n\t\t\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t\t\t\"'&' has to be escaped as '&'\",\n\t\t\t\t\t\tthis.entityPosition,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate declare(\n\t\tkind: ScannedDeclaration['kind'],\n\t\tcontent: string,\n\t\tend: number,\n\t): void {\n\t\tthis.options.onDeclaration?.({\n\t\t\tkind,\n\t\t\tcontent,\n\t\t\tstart: this.tagStart,\n\t\t\tend,\n\t\t\tposition: this.tagPosition,\n\t\t});\n\t}\n\n\tprivate addValueSegment(index: number, locate: Locate, textIndex: number) {\n\t\tthis.attrValueSegments.push({ index, locate, textIndex });\n\t}\n\n\tprivate startEntity(from: 'text' | 'attrValue', position: Position): void {\n\t\tthis.mode = 'entity';\n\t\tthis.entityReturn = from;\n\t\tthis.entityPosition = position;\n\t\tthis.buffer = '';\n\t}\n\n\tprivate missingValue(fail: (message: string) => never): never {\n\t\treturn fail(\n\t\t\t`The attribute '${this.attrName}' of <${this.tagName}> has no value, XML requires one: ${this.attrName}='...'`,\n\t\t);\n\t}\n\n\tprivate endAttribute(fail: (message: string) => never): void {\n\t\tif (!this.attrDynamic) {\n\t\t\tif (this.attributeNames.has(this.attrName)) {\n\t\t\t\tfail(`Duplicate attribute '${this.attrName}' in <${this.tagName}>`);\n\t\t\t}\n\t\t\tthis.attributeNames.add(this.attrName);\n\t\t}\n\t\tthis.attributes.push({\n\t\t\tname: this.attrName,\n\t\t\tvalue: this.attrValue,\n\t\t\tquote: this.quote,\n\t\t\tdynamic: this.attrDynamic || this.attrValueDynamic,\n\t\t\tpositionOf: positionInSegments(this.attrValueSegments),\n\t\t});\n\t\tthis.mode = 'inTag';\n\t\tthis.separated = false;\n\t}\n\n\tprivate endStartTag(selfClosing: boolean, end: number): void {\n\t\tif (this.options.document && this.stack.length === 0 && this.rootClosed) {\n\t\t\tthrow new XmlSyntaxError(\n\t\t\t\t`Only one root element is allowed, found <${this.tagName}> after it`,\n\t\t\t\tthis.tagPosition,\n\t\t\t);\n\t\t}\n\n\t\tthis.options.onStartTag?.({\n\t\t\tname: this.tagName,\n\t\t\tstart: this.tagStart,\n\t\t\tend,\n\t\t\tattributes: this.attributes,\n\t\t\tselfClosing,\n\t\t\tparent: this.stack.at(-1),\n\t\t});\n\n\t\tif (selfClosing) {\n\t\t\tif (this.stack.length === 0) {\n\t\t\t\tthis.rootClosed = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.stack.push({\n\t\t\t\tname: this.tagName,\n\t\t\t\tposition: this.tagPosition,\n\t\t\t\tstart: this.tagStart,\n\t\t\t});\n\t\t}\n\t\tthis.tagName = '';\n\t\tthis.mode = 'text';\n\t}\n\n\tprivate endEndTag(): void {\n\t\t// reported where the closing tag starts\n\t\tconst fail = (message: string): never => {\n\t\t\tthrow new XmlSyntaxError(message, this.tagPosition);\n\t\t};\n\n\t\tconst open = this.stack.at(-1);\n\t\tif (open === undefined) {\n\t\t\tfail(`Closing tag </${this.tagName}> has no opening tag`);\n\t\t} else if (open.name !== this.tagName) {\n\t\t\tfail(\n\t\t\t\t`Expected </${open.name}> to close ${describeElement(open)}, found </${this.tagName}>`,\n\t\t\t);\n\t\t}\n\t\tthis.stack.pop();\n\t\tif (this.stack.length === 0) {\n\t\t\tthis.rootClosed = true;\n\t\t}\n\t\tthis.tagName = '';\n\t\tthis.mode = 'text';\n\t}\n}\n\nfunction describeElement({ name, position }: OpenElement): string {\n\treturn `<${name}> (opened at ${position.line}:${position.column + 1})`;\n}\n\nfunction positionInSegments(segments: ValueSegment[]) {\n\treturn (index: number): Position => {\n\t\tconst segment =\n\t\t\tsegments.findLast((item) => item.index <= index) ?? segments[0];\n\t\treturn segment.locate(segment.textIndex + index - segment.index);\n\t};\n}\n\n/** Positions in a single text, computed from the offsets of its line starts. */\nexport function createLocate(\n\ttext: string,\n\tstart: Position = { line: 1, column: 0 },\n): Locate {\n\tconst lineStarts = [0];\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text[i] === '\\n') {\n\t\t\tlineStarts.push(i + 1);\n\t\t}\n\t}\n\n\treturn (index) => {\n\t\tlet low = 0;\n\t\tlet high = lineStarts.length - 1;\n\t\twhile (low < high) {\n\t\t\tconst middle = (low + high + 1) >> 1;\n\t\t\tif (lineStarts[middle] <= index) {\n\t\t\t\tlow = middle;\n\t\t\t} else {\n\t\t\t\thigh = middle - 1;\n\t\t\t}\n\t\t}\n\t\treturn low === 0\n\t\t\t? { line: start.line, column: start.column + index }\n\t\t\t: { line: start.line + low, column: index - lineStarts[low] };\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blogger-hbs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Build Blogger themes from Handlebars templates, with bundled scripts and styles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"blogger",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@rsbuild/core": "^2.2.11",
|
|
49
|
+
"blogger-expr": "0.0.1",
|
|
49
50
|
"cac": "^7.1.0",
|
|
50
51
|
"handlebars": "^4.7.9",
|
|
51
52
|
"normalize-path": "^3.0.0"
|