html4cj访问者模式详解:用NodeVisitor与NodeFilter实现自定义节点遍历和过滤
html4cj访问者模式详解用NodeVisitor与NodeFilter实现自定义节点遍历和过滤【免费下载链接】html4cj一个HTML格式解析库项目地址: https://gitcode.com/Cangjie-TPC/html4cjhtml4cj 是一个功能完整的 HTML 解析库可将网页 HTML 解析为 DOM 树并支持 CSS 选择器查询。本文面向新手完整讲解 html4cj 中访问者模式的两大核心接口 NodeVisitor 与 NodeFilter如何自定义节点遍历、如何过滤与删除节点以及它们在库内部的实际应用帮你快速上手这套树遍历机制。一、为什么需要访问者模式html4cj 解析 HTML 后会得到一棵 DOM 树文档、元素、文本、注释都是节点Node的实例。而很多需求都绕不开把树上的节点逐个处理一遍统计页面中所有img标签清理 HTML 中的script、style标签把整个文档序列化回 HTML 字符串outerHtmlCSS 选择器查询doc.select(div p)如果每次都写一套递归逻辑代码就会大量重复。html4cj 的解决方案是访问者模式Visitor Pattern由库负责怎么走树你只需要提供走到每个节点时做什么的回调。整个流程如图所示——解析器先构建 DOM 树之后的所有遍历都发生在这棵树上这套机制的核心由三个文件组成访问者接口node_visitor.cj过滤器接口node_filter.cj遍历引擎node_traversor.cj二、NodeVisitor两行回调实现自定义节点遍历NodeVisitor是一个极简接口只有两个回调方法方法触发时机说明head(node, depth)首次访问节点时必须实现参数为当前节点和相对根节点的深度tail(node, depth)访问完所有子孙节点后有默认空实现只关心head时可不重写使用方式非常直接任何节点Node、Element、Document和元素列表Elements都内置了traverse()方法例如 node.cj 和 elements.cj。一个典型的统计所有元素数量的访问者大致如下class CountVisitor : NodeVisitor { public override func head(node: Node, depth: Int64): Unit { if (node is Element) { count } } } // 使用doc.traverse(CountVisitor())遍历引擎 NodeTraversor 内部采用深度优先策略先向下钻进第一个子节点遇到叶子后再沿兄弟节点横移没有兄弟时逐级回退并触发tail回调。整个过程你无需关心只需专注回调逻辑。 小细节traverse是安全的动态遍历——即使你在head回调中删除或替换了当前节点引擎也能正确继续它会在访问前预先记录父节点和兄弟位置所以访问者里做增删改是安全的。三、NodeFilter用5种过滤决策控制遍历走向 ✂️如果只看不够还想改变遍历路径或直接修改树就轮到NodeFilter上场了。它与NodeVisitor结构相同同样是head/tail两个回调区别在于回调返回的不是 Unit而是一个FilterResult决策值共 5 种定义见 node_filter.cj决策值效果典型用途CONTINUE继续正常遍历默认不干预SKIP_CHILDREN跳过该节点的子树但仍会调用tail只处理某层标签本身SKIP_ENTIRELY跳过子树且不再调用tail快速略过无关区域REMOVE删除该节点及其所有子节点清理 script/style 标签STOP立即终止整个遍历找到第一个目标后停止使用方式同样是内置的filter()方法见 node.cj。一个删除所有 script 标签的过滤器class ScriptCleaner : NodeFilter { public override func head(node: Node, depth: Int64): FilterResult { if (node is Element (node as Element)().normalName() script) { return FilterResult.REMOVE } return FilterResult.CONTINUE } } // 使用doc.filter(ScriptCleaner())引擎对REMOVE的处理非常讲究会先找到父节点/兄弟节点再执行删除见 node_traversor.cj避免删除后游标悬空导致遍历错乱——这也是你使用REMOVE时不需要任何额外处理的原因。四、对比速查遍历 vs 过滤怎么选⚖️维度NodeVisitortraverseNodeFilterfilter回调返回值无UnitFilterResult决策能否修改树✅ 支持删除/替换引擎自动容错✅ 支持REMOVE剪枝能否改变遍历路径❌ 固定深度优先走完全树✅ 可跳过、可提前STOP典型场景收集信息、生成输出清洗 HTML、查找首个匹配入口方法node.traverse(visitor)node.filter(nodeFilter)一句话总结只读分析用 Visitor需要修剪或提前终止用 Filter。五、实战彩蛋html4cj 内部就是这么用的 这套模式不是摆设库内部的核心功能全部基于它构建值得对照源码学习HTML 序列化outerHtml()使用的OuterHtmlVisitor把每个节点的 HTML 片段按顺序追加到字符串缓冲中见 node.cjCSS 选择器查询doc.select(a)背后的 Collector 就是一个NodeVisitor——每个head回调里用选择器求值器测试节点匹配则收入结果集selectFirst 提前终止FirstFinder是一个NodeFilter命中第一个匹配元素立即返回STOP避免扫完全树见 collector.cj文本提取Element.text()通过TextNodeVisitor遍历收集文本节点见 element.cj六、新手常见坑点提醒 ⚠️不要手动递归替代 traverse手写递归很难处理遍历中节点被删除/替换的边界情况NodeTraversor已替你处理SKIP_CHILDREN与SKIP_ENTIRELY的区别在于是否仍触发tail若你的tail里有清理逻辑要选对REMOVE是连子节点一起删只想删标签外壳保留内容时应改用unwrap()这类 APISTOP只终止当前过滤对Elements批量过滤时会在命中STOP后停止处理后续元素见 node_traversor.cj结语html4cj 通过NodeVisitorNodeFilter这两个小接口把树怎么走和节点做什么彻底解耦几行回调即可实现自定义遍历、节点清洗、提前终止查询等操作这正是访问者模式在解析库中的教科书式落地。想进一步熟悉全部 API可查阅官方接口文档 doc/feature_api.md配合 node_traversor.cj 的源码基本就能玩转 html4cj 的树遍历体系了。【免费下载链接】html4cj一个HTML格式解析库项目地址: https://gitcode.com/Cangjie-TPC/html4cj创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考