Web Clipper 过滤器与模板逻辑
上一篇讲了变量怎么取值,取回来的原始数据往往还要再加工,比如把日期换个格式、把数组拼成列表、没取到作者时给个兜底文案。这些加工靠过滤器和模板逻辑完成。
这套模板语法叫 Knap,是 Obsidian 开源的模板语言。内容比较多,先收藏,写模板时当手册翻。
过滤器
语法
竖线接过滤器名,冒号传参数,可以链式串多个:
{{title|lower|replace:" ":"-"}}
{{published|date:"YYYY-MM-DD"}}
字符串参数用引号包住,多个参数用逗号分隔。
日期
| 过滤器 | 作用 | 示例 |
|---|---|---|
date |
格式化日期,默认输出 YYYY-MM-DD |
{{published|date:"YYYY年MM月DD日"}} |
date_modify |
日期加减 | {{date|date_modify:"+1 day"}}、{{date|date_modify:"-2 weeks"}} |
duration |
把 ISO 8601 时长或秒数转成 HH:mm:ss |
{{duration|duration:"mm:ss"}} |
几点说明:
date支持第二个参数指定输入格式,{{published|date:"YYYY-MM-DD","MM/DD/YYYY"}}。date_modify的单位支持 year、month、week、day、hour、minute、second,单复数都行。duration适合处理视频时长这类数据,不指定格式时超过一小时输出HH:mm:ss,否则输出mm:ss。
文本转换与大小写
| 过滤器 | 作用 | 示例 |
|---|---|---|
lower / upper |
全小写 / 全大写 | {{title|lower}} |
capitalize |
首字母大写 | {{author|capitalize}} |
title |
每个单词首字母大写 | {{title|title}} |
camel / pascal |
驼峰 / 帕斯卡命名 | {{title|camel}} |
kebab / snake |
连字符 / 下划线命名 | {{title|kebab}} |
uncamel |
把驼峰拆回空格分隔 | {{var|uncamel}} |
trim |
去掉首尾空白 | {{var|trim}} |
replace |
替换文本,支持正则 | 见下 |
safe_name |
转成合法文件名 | {{title|safe_name}} |
decode_uri |
URI 解码 | {{var|decode_uri}} |
unescape |
反转义 HTML 实体 | {{var|unescape}} |
replace 是最常用的,支持多组替换连续执行,也支持斜杠包正则:
{{title|replace:"草稿 - ":""}}
{{url|replace:"https://":"","/":""}}
{{title|replace:"/\\s+/g":"-"}}
safe_name 会去掉 Windows、macOS、Linux 文件名里的非法字符和 Obsidian 的特殊字符(# [ ] | ^),结果为空时返回 Untitled。可以传参数 "windows"、"mac"、"linux" 按平台规则处理,不传则用最严格的合集。写笔记名称时建议都过一遍它。
文本格式化
| 过滤器 | 作用 | 示例 |
|---|---|---|
blockquote |
每行加 > 变引用块 |
{{content|blockquote}} |
callout |
包成 Obsidian Callout | {{content|callout:"tip","小提示"}} |
footnote |
数组转成脚注定义列表 | {{footnotes|footnote}} |
image |
URL 转 Markdown 图片 | {{image|image:"封面图"}} |
link |
URL 转 Markdown 链接 | {{url|link:"原文"}} |
wikilink |
转 Obsidian 双链 | {{author|wikilink}} |
list |
数组转列表 | {{tags|list:"numbered"}} |
table |
数组或对象转 Markdown 表格 | {{rows|table}} |
fragment_link |
给高亮附加原文定位链接 | 见下 |
几点说明:
callout第三个参数传"true"生成默认折叠的 Callout,"false"生成默认展开的。list的参数有numbered(有序)、task(任务列表)、numbered-task,不传是无序列表,嵌套数组会自动缩进。wikilink传参数作为别名,{{title|wikilink:"别名"}}输出[[标题|别名]]。fragment_link会在文本后追加一个带#:~:text=定位参数的链接,点开能直接跳到原文对应位置,配合{{highlights}}使用效果最好。
数字
| 过滤器 | 作用 | 示例 |
|---|---|---|
calc |
四则运算 | {{words|calc:"+100"}}、{{price|calc:"*0.8"}} |
length |
字符串长度、数组或对象的条目数 | {{tags|length}} |
number_format |
千分位格式化 | {{price|number_format:2,".",","}} |
round |
四舍五入,参数为小数位数 | {{rating|round:1}} |
calc 的运算符支持 + - * / ^ **(后两个都是乘方)。number_format 的三个参数依次是小数位数、小数点符号、千分位符号。
HTML 处理
这组过滤器处理 {{contentHtml}}、{{selectorHtml:...}} 这类 HTML 格式的变量。
| 过滤器 | 作用 | 示例 |
|---|---|---|
markdown |
HTML 转 Markdown | {{selectorHtml:#main|markdown}} |
remove_html |
删除匹配选择器的元素(含内容) | {{contentHtml|remove_html:".ads,footer"}} |
remove_tags |
删除指定标签但保留内容 | {{contentHtml|remove_tags:"span,font"}} |
remove_attr |
删除指定属性 | {{contentHtml|remove_attr:"class,style"}} |
replace_tags |
把一种标签换成另一种 | {{contentHtml|replace_tags:"b:strong","i:em"}} |
strip_tags |
去掉所有 HTML 标签,传参则只保留指定标签 | {{contentHtml|strip_tags:"p,a"}} |
strip_attr |
去掉所有属性,传参则只保留指定属性 | {{contentHtml|strip_attr:"href"}} |
strip_md |
去掉 Markdown 语法只留纯文本 | {{content|strip_md}} |
html_to_json |
HTML 转 JSON 结构,方便再加工 | {{contentHtml|html_to_json}} |
处理页面时推荐一条链,先用选择器取范围,再清理,最后转 Markdown:
{{selectorHtml:#main|remove_html:".share,.related"|strip_attr|markdown}}
数组与对象
{{highlights}}、{{schema:author[*].name}}、多匹配的 selector 变量,拿到的都是数组,这组过滤器专门处理。
| 过滤器 | 作用 | 示例 |
|---|---|---|
first / last |
第一项 / 最后一项 | {{authors|first}} |
join |
拼成字符串,默认逗号分隔 | {{tags|join:", "}} |
map |
逐项提取属性或用箭头函数变换 | 见下 |
merge |
往数组追加元素 | {{tags|merge:"读书笔记"}} |
nth |
按位置筛选,CSS nth 风格 | {{items|nth:2}}、{{items|nth:"2n"}} |
object |
对象转键数组、值数组或键值对数组 | {{obj|object:"keys"}} |
reverse |
倒序 | {{items|reverse}} |
slice |
截取片段,和 JS 的 slice 一样 | {{items|slice:0,5}} |
split |
字符串拆成数组 | {{keywords|split:","}} |
template |
按模板串逐项渲染 | 见下 |
unique |
去重 | {{tags|unique}} |
map 有两种写法。属性路径写法适合对象数组,箭头函数写法更灵活:
{{schema:author[*]|map:"name"|join:", "}}
{{highlights|map: item => item.text|join:"\n\n"}}
template 把数组里每个对象套进一段模板,用 ${属性} 占位,条目之间空一行:
{{highlights|template:"${text}\n- [[${created}]]"}}
模板逻辑
模板逻辑用大括号加百分号的标签语法,写在模板正文中。注意下面所有示例都要整段放进模板里,标签不能拆开用。
条件判断
变量存在才输出对应内容:
{% if author %}
作者: {{author}}
{% endif %}
支持 elseif 和 else 多分支:
{% if domain contains "zhihu" %}
来源: 知乎
{% elseif domain contains "weixin" %}
来源: 公众号
{% else %}
来源: {{domain}}
{% endif %}
比较运算符有 ==、!=、>、<、>=、<=、contains(字符串包含或数组成员)。逻辑运算符有 and、or、not,也可以用 &&、||、!,支持括号分组。
单独写一个变量时是真假判断。空字符串、空数组、0、false、null 都算假,其余都算真。
变量赋值
set 标签可以在模板里造新变量,后续直接用:
{% set slug = title|lower|replace:" ":"-" %}
文件名: {{slug}}
可以赋其他变量、字面量、过滤器处理结果,也可以是选择器抓取的结果:
{% set comments = selector:.comment %}
回退值
两个问号给空值准备兜底文案:
作者: {{author ?? "未知"}}
可以连着写,依次尝试:
{{author ?? schema:author.name ?? "未知"}}
注意优先级,过滤器比 ?? 先执行。{{title|upper ?? "UNTITLED"}} 是先做大写转换再判断空不空。想给兜底值也加过滤器,要用括号把意图分开写。
循环
for 标签遍历数组:
{% for item in schema:author %}
- {{item.name}}
{% endfor %}
循环可以直接遍历 schema 数组、选择器结果,或先 set 再遍历。循环内部有个 loop 对象:
| 属性 | 含义 |
|---|---|
loop.index |
当前序号,从 1 开始 |
loop.index0 |
当前序号,从 0 开始 |
loop.first / loop.last |
是否第一项 / 最后一项 |
loop.length |
总条数 |
数组按下标取元素用方括号,比如两个平行数组配合输出:
{% set transcripts = selector:.transcript-text %}
{% set timestamps = selector:.timestamp %}
{% for line in transcripts %}
{{timestamps[loop.index0]}} - {{line}}
{% endfor %}
循环可以嵌套,也能和条件判断混用。
求值顺序
模板分两步处理,顺序很重要:
- 先执行全部模板逻辑,条件、循环、赋值和普通变量在这一步完成。
- 再把 prompt 变量发给 Interpreter 的大模型,拿回结果填进去。
所以可以用模板逻辑拼接出 prompt 变量的内容,但 prompt 变量的结果不能再参与条件和循环。
速查卡片
| 主题 | 要点 |
|---|---|
| 过滤器 | 竖线串联,冒号传参,字符串参数加引号 |
| 高频 | date、replace、safe_name、join、map、markdown |
| 条件 | if 标签,支持 contains 和 and/or/not |
| 兜底 | ??,可链式,过滤器先执行 |
| 循环 | for 标签,loop 对象拿下标和首尾状态 |
| 顺序 | 模板逻辑先于 prompt 变量执行 |
下一篇讲 Web Clipper 最好玩也最贵的功能,用自然语言驱动大模型的 Interpreter,以及常见问题的排查方法。