公司动态
wechat-miniprogram-examples之富文本解析原理:手写轻量级HTML解析器的完整思路
wechat-miniprogram-examples之富文本解析原理手写轻量级HTML解析器的完整思路【免费下载链接】wechat-miniprogram-examplesWeChat mini program examples. 微信小程序示例项目地址: https://gitcode.com/gh_mirrors/we/wechat-miniprogram-examples微信小程序示例项目 wechat-miniprogram-examples 中的知乎日报案例没有使用任何第三方库而是用一套手写的轻量级HTML解析器完成了对知乎日报接口 HTML 正文的富文本解析与渲染。本文从零拆解这套解析器的完整设计思路如何用正则做词法分析、如何用标签栈维护嵌套结构、如何把 HTML 转成小程序可渲染的结构化数据。无论是想理解富文本解析原理还是准备在小程序中实现自己的富文本渲染方案这份思路都值得收藏。为什么小程序必须手写HTML解析器在 Web 页面里渲染一段 HTML一句innerHTML就搞定了。但微信小程序没有 DOM 和 BOMinnerHTML完全不可用这是微信小程序富文本解析的第一个拦路虎。常见的替代方案有两个但都不够理想web-view 网页容器性能开销大加载慢页面体验割裂还会出现跨域、登录态等一堆问题rich-text 富文本组件虽然能渲染 HTML但支持的标签和事件有限对于知乎日报这种来源复杂、标签多样的正文常常水土不服。于是知乎日报案例选择了一条更彻底的路线自己写一个轻量级HTML解析器把 HTML 解析成纯数据再用 WXML 逐段渲染。解析器的完整源码就放在项目的 htmlParseUtil.js 中全文只有几百行却包含了一个解析器最核心的三大设计词法分析、结构维护、回调分发。词法分析三个正则吃遍HTML HTML 虽然标签繁多但语法模式非常固定。解析器的第一步就是用三个正则把整段 HTML 拆成可识别的词法单元开始标签startTag匹配div classx这类标签同时捕获标签名、属性列表和自闭合标记结束标签endTag匹配/div这类闭合标签属性attr在开始标签内部继续匹配classx、srcy、idz等不同写法的属性。这三个正则是整个解析器的基础分别定义在 htmlParseUtil.js 的第 28~30 行注释也写得非常清晰适合对照学习。有了它们再乱的 HTML 也能被切成一串开始标签 文本 结束标签的 token 流。元素分类表6 张户籍表管住所有标签 拿到标签后怎么知道它该不该入栈、能不能自闭合解析器准备了 6 张哈希表makeMap生成相当于给 HTML 元素做了户籍登记分类表作用例子empty空元素不需要闭合标签br、img、hrblock块级元素遇到时自动关闭上层行内标签div、p、ulinline行内元素与块级配合做隐式闭合span、a、strongcloseSelf可自动闭合同类型标签可隐式结束li、td、pfillAttrs布尔属性缺省值自动填充disabled、checkedspecial特殊元素内部内容原样输出script、style正是这些分类让解析器能处理 HTML 中大量不标准的写法——比如p不写/p、li嵌套li这在真实接口返回的 HTML 里太常见了。结构维护一个栈搞定嵌套关系 ️解析器的核心数据结构只有一个标签栈stack。主循环的逻辑非常朴素每次从剩余 HTML 的头部做一次模式识别判断当前遇到的是注释、DOCTYPE、script、style、结束标签、开始标签还是纯文本然后分别处理。以开始标签为例如果是块级元素先弹出栈顶所有行内元素隐式闭合如果是closeSelf且栈顶同名先闭合栈顶空元素直接不压栈否则把标签名压入栈中调用start回调把标签名和属性交给调用方。遇到结束标签时parseEndTag会从栈顶向下找到同名标签把中间所有未闭合的标签依次补上结束回调。循环的每一轮都保证html在缩短如果某轮没缩短就抛出Parse Error防止死循环——这个细节非常值得学习。回调机制SAX 式的边读边播报 这个解析器采用的是类似 SAXSimple API for XML的设计解析器本身不构建 DOM 树而是边扫描边通过回调函数播报事件调用方拿到事件自己决定怎么处理。核心回调有四个start(tag, attrs, unary)遇到开始标签end(tag)遇到结束标签chars(text)遇到文本内容comment(text)遇到注释。这种设计的好处是内存占用极小、速度极快非常契合小程序端轻量化的诉求。进阶封装像 jQuery 一样链式取内容 只有原始回调还不够方便。解析器在 SAX 内核之上又封装了一层html_extractor查询器支持类似 jQuery 的链式调用tag(div)匹配任意层级的指定标签first_tag()必须是文档起始标签next_tag()必须是上一个标签的直接子元素attr(class, question)追加属性条件match()执行匹配返回结果数组。例如$(html).tag(div).attr(class, question).match()就能一次性取出所有classquestion的 div 内容。这一层的封装代码同样在 htmlParseUtil.js 中接口设计非常适合作为模板复用到自己的项目里。业务层从 HTML 到结构化数据的翻译官 解析器只是通用工具真正理解知乎日报正文结构的是业务层的parseStory函数定义在 util.js 中。它的工作分两步第一步按块切分。知乎日报的正文由多个问答块div.question组成每个块包含标题、作者、头像、正文和查看知乎讨论链接。parseStory先把这些块整体提取出来再逐块解析内部结构。第二步按类型归类。对每个块的正文部分把每个p段落进一步细分解析出的类型含义WXML 中的渲染方式img段落里的图片image组件 点击预览pstrong加粗段落加粗样式文本pem强调段落斜体样式文本blockquote引用块引用样式文本p普通段落普通文本与此同时decodeHtml会完成实体符号的转义nbsp;、ldquo;等decodeUnicode则处理主题日报里常见的 Unicode 编码内容保证最终展示的文字干净可读。渲染层WXML 的按类型分发 解析完成后数据交给 detail.wxml 渲染。渲染方式并不神秘就是wx:for遍历内容数组再用wx:if/wx:elif按item.type分发到不同的展示节点文本段落用text输出图片用image输出并绑定bindtap事件调用wx.previewImage实现点击放大预览加粗、强调、引用分别套用不同的样式类。整个渲染层没有使用任何富文本组件完全由原生组件拼装而成这就是解析成数据带来的最大自由度——每一段内容都能被精确控制样式和交互。数据加载的入口逻辑在 detail.js 的loadData中data.body Utils.parseStory(data.body, false)一行即可完成从 HTML 到结构化数据的转换。写在最后这套思路能给你什么回顾整个微信小程序富文本解析方案其实只做了三件事正则切词、栈维护结构、回调分发事件再往上叠加一层业务解析和按类型渲染。它没有炫技却完整展示了手写HTML解析器的所有关键设计代码量小、无依赖、可读性强非常适合作为学习富文本解析原理的入门范本。如果你想亲自运行这个案例克隆仓库后打开ZhiHuDaily目录即可仓库地址为https://gitcode.com/gh_mirrors/we/wechat-miniprogram-examples三个示例分别在DouBanBookApp、MatterAssistant、ZhiHuDaily目录下。理解了这套轻量级HTML解析器的思路以后无论在哪个平台遇到HTML 不能直接渲染的问题你都可以胸有成竹地说自己写一个就好。【免费下载链接】wechat-miniprogram-examplesWeChat mini program examples. 微信小程序示例项目地址: https://gitcode.com/gh_mirrors/we/wechat-miniprogram-examples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考