zh CN 编写标签处理器 - chiba233/yumeDSL GitHub Wiki
编写标签处理器
大部分标签用 处理器辅助函数 批量注册就行了。 只有辅助函数搞不定的场景——条件输出、验证、副作用——才需要手写 TagHandler。
关于
ctx: 回调里的ctx是解析器传给你的上下文对象,你不需要知道它是什么,写上就行。想深入了解的看 DslContext。签名注意: 本页讨论的是底层
TagHandler接口,回调的第一个参数是原始的tokens(inline)或arg(raw/block)。如果你用createPipeHandlers,它帮你做了管道预解析,回调签名不同——第一个参数变成了PipeArgs。两套签名见下方对比。
TagHandler 长什么样
TagHandler = {
inline? → 用户写 $$tag(content)$$ 时调用
raw? → 用户写 $$tag(arg)%...%end$$ 时调用
block? → 用户写 $$tag(arg)*...*end$$ 时调用
}
interface TagHandler {
inline?: (tokens: TextToken[], ctx?: DslContext) => TokenDraft;
raw?: (arg: string | undefined, content: string, ctx?: DslContext) => TokenDraft;
block?: (arg: string | undefined, content: TextToken[], ctx?: DslContext) => TokenDraft;
}
只实现你的标签支持的形式。用户写了你没实现的形式 → 整段标记降级为字面文本,不报错。
两套签名对比
| 形式 | 底层 TagHandler(本页) |
createPipeHandlers 包装后 |
|---|---|---|
| inline | (tokens: TextToken[], ctx?) => TokenDraft |
(args: PipeArgs, ctx?) => TokenDraft |
| raw | (arg: string|undefined, content: string, ctx?) => TokenDraft |
(args: PipeArgs, content: string, ctx?, rawArg?) => TokenDraft |
| block | (arg: string|undefined, content: TextToken[], ctx?) => TokenDraft |
(args: PipeArgs, content: TextToken[], ctx?, rawArg?) => TokenDraft |
底层版本的第一个参数是原始数据(tokens 或 arg 字符串)。包装版本帮你做了管道预解析,第一个参数变成了 PipeArgs。
三种 handler 的参数一览
inline
inline: (tokens, ctx) => TokenDraft
| 参数 | 是什么 |
|---|---|
tokens |
括号内递归解析的子 token。注意:文本叶子里的转义还没被解掉。 |
ctx |
解析上下文,转发给工具函数用 |
关于转义: 用户写了 $$bold(hello \| world)$$,你拿到的文本 token 值是 "hello \\| world"(还有反斜杠)。想要干净的文本?用
materializeTextTokens(tokens, ctx) 或 parsePipeArgs。
raw
raw: (arg, content, ctx) => TokenDraft
| 参数 | 是什么 |
|---|---|
arg |
( 和 )% 之间的文本。空参数时为 undefined。是原始字符串,不是管道解析后的 |
content |
raw 正文——原封不动的字符串,不解析嵌套标签 |
ctx |
解析上下文 |
适合:代码块、数学公式、嵌入 JSON——任何不该被递归解析的内容。
block
block: (arg, content, ctx) => TokenDraft
| 参数 | 是什么 |
|---|---|
arg |
和 raw 一样的原始参数字符串 |
content |
TextToken[]——已经递归解析好的 block 正文 |
ctx |
解析上下文 |
完整示例:辅助函数 + 手写混搭
import {
createSimpleInlineHandlers,
createPipeHandlers,
parseRichText,
parsePipeTextArgs,
type TagHandler,
type TokenDraft,
type DslContext,
} from "yume-dsl-rich-text";
// 简单标签用辅助函数
const simple = createSimpleInlineHandlers(["bold", "italic", "underline"]);
// 需要管道参数的用 createPipeHandlers
const piped = createPipeHandlers({
link: {
inline: (args, ctx) => ({
type: "link",
url: args.text(0),
value: args.materializedTailTokens(1),
}),
},
});
// 需要自定义逻辑的手写
const manual: Record<string, TagHandler> = {
code: {
raw: (arg, content, ctx): TokenDraft => {
const pipeArgs = parsePipeTextArgs(arg ?? "", ctx);
return {
type: "code",
lang: pipeArgs.text(0, "text"),
label: pipeArgs.text(1, ""),
value: content.trim(),
};
},
},
};
// 合并
const handlers = {...simple, ...piped, ...manual};
const tokens = parseRichText("$$bold(Hello)$$ $$link(https://example.com | click)$$", {handlers});
PipeArgs
parsePipeArgs / parsePipeTextArgs 返回的结构化视图。详见 处理器工具函数。
interface PipeArgs {
parts: TextToken[][];
has: (index: number) => boolean;
text: (index: number, fallback?: string) => string;
materializedTokens: (index: number, fallback?: TextToken[]) => TextToken[];
materializedTailTokens: (startIndex: number, fallback?: TextToken[]) => TextToken[];
}
| 方法 | 干嘛的 |
|---|---|
parts |
原始 token 段(还没反转义) |
has(i) |
第 i 段存在吗 |
text(i) |
第 i 段的纯文本(反转义 + 修剪) |
materializedTokens(i) |
第 i 段的 token(文本反转义,结构保留) |
materializedTailTokens(start) |
从 start 开始所有段合并——适合"后面全是自由文本可能含管道"的场景 |
典型用法
// URL 是纯文本,显示内容可能含管道和富文本
const args = parsePipeArgs(tokens, ctx);
return {
type: "link",
url: args.text(0), // "https://example.com"
value: args.materializedTailTokens(1, []), // "Click | here | for details" 全部合并
};
parsePipeTextList
最简单的管道分割——字符串进字符串数组出:
parsePipeTextList("ts | Demo | Label"); // → ["ts", "Demo", "Label"]
parsePipeTextList("a \\| b | c"); // → ["a | b", "c"]
raw/block handler 里拆 arg 参数的首选:
code: {
raw: (arg, content, ctx) => {
const parts = parsePipeTextList(arg ?? "");
return {type: "code", lang: parts[0] || "text", value: content};
},
}