ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nokogiri API 演进路线图:序列化、SAX、选择器与解析选项的改造蓝图

Nokogiri API 演进路线图:序列化、SAX、选择器与解析选项的改造蓝图 后端【免费下载链接】nokogiriNokogiri (鋸) makes it easy and painless to work with XML and HTML from Ruby.项目地址https://gitcode.com/gh_mirrors/no/nokogiri点击查看免费下载本文基于 Nokogiri 官方仓库中的 ROADMAP.md 展开逐条剖析项目对 XML/HTML 解析、序列化、搜索与解析选项等核心 API 的既有缺陷与改造设想并结合仓库当前源码如 lib/nokogiri/xml/parse_options.rb、lib/nokogiri/xml/node.rb给出落地证据。读完本文你将清楚 Nokogiri 各 API 的当前实现方式、被点名的问题所在以及未来可能出现的接口形态便于在升级版本时提前迁移。1. 路线图概览一份给 API 动手术的清单ROADMAP.md是一份面向 Nokogiri API 的演进规划而非版本发布排期。它列出的是项目维护者认为现在这样不够好、需要改造的九个方向方向核心痛点相关源码/测试序列化与 pretty printing API格式化行为不可关闭、默认行为不直观lib/nokogiri/xml/node/save_options.rbSAX 解析性能慢得可怕ext/nokogiri/xml_sax_parser.cNode 的 Enumerable 混入与属性 API混入 Enumerable 带来意外副作用lib/nokogiri/xml/node.rbCSS 查询解析伪类支持不足、选择器语义错误lib/nokogiri/css/parser.yDocumentFragment上下文节点参与搜索时结果不一致lib/nokogiri/xml/document_fragment.rbXPath 函数/查询语法自定义函数与参数提取的语法不统一lib/nokogiri/xml/searchable.rb编码处理libxml2 编码探测能力弱lib/nokogiri/html4/encoding_reader.rbReader不安全对象引用可导致应用崩溃lib/nokogiri/xml/reader.rbParseOptions镜像 libxml2 选项、命名危险、难以组合lib/nokogiri/xml/parse_options.rb以下各节按路线图顺序展开并在每个方向后补充当前仓库中可验证的实现证据与演进脉络。2. 序列化与 pretty printing API 的重构路线图把彻底改造序列化/pretty printing API列在第一位引出的两个具体问题都指向格式化的不可控性[#530]XHTML 格式化无法关闭[#415]XML 默认应该无格式化。当前实现中序列化由Nokogiri::XML::Node#serialize及其派生的to_xml/to_html/to_xhtml驱动其格式行为由 lib/nokogiri/xml/node/save_options.rb 中的SaveOptions位掩码决定常量值含义FORMAT1序列化时格式化输出缩进NO_DECLARATION2不输出 XML 声明NO_EMPTY_TAGS4不输出空标签如br/NO_XHTML8不按 XHTML 序列化AS_XHTML16按 XHTML 序列化AS_XML32按 XML 序列化AS_HTML64按 HTML 序列化有趣的是路线图指出的默认行为问题已经部分落地。在非 JRuby 平台上save_options.rb 中DEFAULT_XML FORMAT | AS_XML——即 XML 默认仍带格式化而文件里直接引用了 issue #415 作为设计注记说明该议题正是DEFAULT_XML取值讨论的一部分。反观 JRuby 分支DEFAULT_XML AS_XML无FORMAT恰恰实现了XML 默认无格式化的设想两个平台默认值至今仍未统一。SerializeOptions的位运算代码options | constant的 setter 与options constant的查询方法由constants.each的class_eval批量生成与ParseOptions的模式一致。如果你想在升级前自主控制输出可以用save_with显式组合node.serialize(encoding: UTF-8, save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) node.serialize(encoding: UTF-8) do |config| config.format # 启用格式化 end底层serialize在 lib/nokogiri/xml/node.rb 中把选项合并进SaveOptions后交给原生层输出。可以推断路线图设想的API 改造将聚焦于把save_with/FORMAT这套位掩码语义收敛为更直观、可显式关闭格式化的接口。3. SAX 解析的性能与重构路线图用一句SAX parsing is wicked slowfairy wing throwdown 之论点名了 SAX 解析的性能问题。SAXSimple API for XML本应是流式、低内存的解析方式适合处理超大文档性能瓶颈通常来自事件回调的 Ruby 边界穿越与原生层对象分配。当前 SAX 体系的 Ruby 侧入口分散在多处XMLNokogiri::XML::SAX::Parser/ParserContext/PushParserlib/nokogiri/xml/sax/HTML4Nokogiri::HTML4::SAX::Parser/ParserContext/PushParserlib/nokogiri/html4/sax/原生实现CRuby 走 ext/nokogiri/xml_sax_parser.c 与 ext/nokogiri/xml_sax_push_parser.cJRuby 走 ext/java/nokogiri/XmlSaxParser.java 等。从代码结构看overhaul and optimize至少包含两层含义一是减少 Ruby↔C/Java 边界的调用次数与对象装箱二是统一 XML 与 HTML4 两套 SAX 入口的接口。路线图未给出具体时间表但后续版本中 SAX 的接口逐步收敛到Parser#parse(io)与PushParser#(chunk)两种范式正是该议题持续推进的体现。4. Node 不应是 Enumerable并应拥有更好的属性 API路线图明确指出Nokogiri::XML::Node混入Enumerable带来了非预期的副作用引述 issue #679因此应该移除Enumerable混入同时改进属性访问 API。从当前源码看该设想尚未落地在 lib/nokogiri/xml/node.rb 第 92 行仍是include Enumerable也就是说node.map、node.each这类操作目前依然可用遍历子节点。路线图认为这种能力免费混入到每个节点上并不合适——例如Enumerable#first、#any?等方法的语义对单个 DOM 节点而言并不自然且与NodeSet真正的节点集合职责重叠。改造方向是把节点集合行为收敛到NodeSet让Node专注树结构操作。至于更好的属性 API路线图链接了 #666已关闭与 #765 两个议题。当前属性访问已具备类 Hash 风格node Nokogiri::XML::DocumentFragment.parse(a href#foo idlinklink/a).at_css(a) node[href] # #foo node.keys # [href, id] node[class] green # 可写 node.attribute_nodes # 返回 Attr 节点集合相关的 C 侧实现位于 ext/nokogiri/xml_node.cRuby 侧属性方法群在 lib/nokogiri/xml/node.rb 的Working with Node Attributes部分。可以推断改进方向是提供显式的、不依赖 Hash 惯例的属性遍历/命名空间感知接口。5. CSS 查询解析的改进清单路线图在 CSS 查询解析下列出了大量待办几乎全部是选择器语义缺失或错误:not()不支持非平凡参数如:not(div p.c)#528链式:not伪类#451更完整的 jQuery/CSS 伪类支持#621、#342、#628、#652、#688nth-of-type等选择器计算结果错误#394查询被执行错误#309:has行为不正确#350。这些能力的实现位于 CSS 解析器 lib/nokogiri/css/parser.yBison 语法文件由parser.y生成parser.rb、词法分析器 lib/nokogiri/css/tokenizer.rex以及将 CSS AST 翻译为 XPath 的 lib/nokogiri/css/xpath_visitor.rb。关键机制是Nokogiri 的 CSS 查询并不会直接作用于 DOM而是先被翻译为 XPath再交由底层 XPath 引擎求值。例如div.employee会被XPathVisitor转成等价的 XPath 表达式。因此:not()带复杂参数、:has()、nth-of-type等问题本质上是CSS→XPath 翻译器对选择器语义覆盖不全。对应测试见 test/css/test_xpath_visitor.rb 与 test/css/test_css.rb。若你当前在使用这些伪类需要在升级后回归验证结果。另外lib/nokogiri/xml/searchable.rb 中的search方法用正则LOOKS_LIKE_XPATH %r{^(\./|/|\.\.|\.$)}启发式区分 CSS 与 XPath文档已明确警告该启发式将来可能变化并建议明确调用css或xpath——这与路线图改进查询解析的意图一致。6. DocumentFragment 的上下文搜索问题路线图整理了四个 ticket#213、#370、#454、#572共同症状是在 DocumentFragment 上执行搜索时是否把上下文节点纳入搜索范围会导致结果不一致。路线图提出的修复设想是让DocumentFragment成为NodeSet的子类。当前实现中DocumentFragment Nokogiri::XML::Nodelib/nokogiri/xml/document_fragment.rb它并没有继承NodeSet。其搜索行为直接透传给子节点def css(*args) if children.any? children.css(*args) # children is a smell here else NodeSet.new(document) end end源码注释里赫然写着# children is a smell here并且xpath干脆没有独立实现注释# NOTE that we dont delegate #xpath to children ... another smell.。search同样在内部按xpath/children.css两条路径分发。这些smell正是路线图所述上下文节点参与搜索行为不一致的代码级根源。此外DocumentFragment的构造支持context:参数——当传入上下文节点时会调用context.parse让解析器仿佛该节点是片段子树的父节点并相对其解析命名空间JRuby 下还会用namespace_declarations补齐根元素的命名空间声明见注释中 issue #770 的修复。若未来改为继承NodeSet这类上下文语义需要重新设计。7. XPath 与 NodeSet 的查询语法统一7.1 自定义 XPath 函数处理器路线图希望为自定义 XPath 函数提供更好的语法PR #464。当前机制要求用户自建处理器类把实现挂在nokogiri命名空间下lib/nokogiri/xml/searchable.rbhandler Class.new { def regex(node_set, regex) node_set.find_all { |node| node[some_attribute] ~ /#{regex}/ } end }.new node.xpath(.//title[nokogiri:regex(., \w)], handler) node.css(title:regex(\w), handler)处理器类可以出现在参数列表的任意位置由extract_params识别params.find { |param| ![Hash, String, Symbol].include?(param.class) }。这套鸭子类型虽灵活但不够显式——这正是路线图要改进的语法。7.2 Node#xpath 与 NodeSet#xpath 的参数提取路线图指出Node#xpath/NodeSet#xpath以及Node#{css,search}都用到Node#extract_params来解析位置参数设想统一成一份 Hash 选项。当前Searchable#extract_params的行为是从参数尾部循环弹出 Hash/nil 作为命名空间绑定与变量绑定且绑定顺序为ns, binds hashes.reverse。例如node.xpath(.//address[domestic$value], nil, { value: Yes })命名空间与变量绑定都靠位置堆叠调用约定较为隐晦。路线图的设想是改用关键字参数如namespaces:、variables:、handler:并顺带回答NodeSet#xpath 到底该返回什么#656当前返回NodeSet。JRuby 侧的变量绑定实现在 ext/java/nokogiri/NokogiriXPathVariableResolver.javaCRuby 侧在 ext/nokogiri/xml_xpath_context.c 的XPathContext中Ruby 注册入口是 lib/nokogiri/xml/xpath_context.rb 的register_namespaces/register_variables。8. 编码处理EncodingReader 的现状与前景路线图在Encoding一节直言我们对编码有一堆未解决的问题……需要一个懂编码的人来牵头并设想把EncodingReader抽成一个可注入的真实对象。其背景在 lib/nokogiri/html4/encoding_reader.rb 中写得很清楚libxml2 的编码探测能力弱——既不识别 HTML5 风格的meta charset声明即使探测到编码提示也不会回头重新解码前面已乱码的部分。EncodingReader的目标就是在 libxml2 之外做更高级的编码探测并在发现编码提示后模拟回绕流让 libxml2 从头重新解析。其探测逻辑EncodingReader.detect_encoding覆盖两种提示XML 声明?xml ... ?中的 encoding通过Nokogiri.XML小文档取.encodingHTMLmeta标签SAX 扫描meta charset...或meta http-equivContent-Type content...; charset...SAXHandler类实现了对该两形态的提取JRuby 下还直接对首块做正则匹配或经JumpSAXHandler提前抛出:encoding_found信号。read方法配合原生层htmlReadIO工作它预期首次read调用会拿到约 1KB 的首块数据用于探测一旦发现编码就抛出EncodingFound异常并把首块缓存进firstchunk上层据此设置编码后重新从流开头解析。这个先探测、后回放的协作模式正是路线图想把它变成可注入对象的动机——目前它被内嵌在Document#read_io的解析管线中难以被用户替换或测试。相关测试见 test/html4/test_document_encoding.rb编码处理器的注册机制见 lib/nokogiri/encoding_handler.rb。9. Reader 的固有问题路线图对Nokogiri::XML::Reader的评价是从根本上就坏了——具体指无法阻止用户以不安全的方式使用对象引用导致应用崩溃。Reader是面向流的只读遍历接口见 lib/nokogiri/xml/reader.rb它按节点推进并暴露当前节点的属性由于底层直接持有原生解析状态若在遍历过程中保存并跨迭代使用节点引用原生对象可能已被释放从而触发崩溃而非 Ruby 层的干净异常。路线图并未给出修复方案细节但从表述看改造重点在于让不安全的用法失败得安全——例如在原生层增加生命周期保护、或禁止保存跨迭代引用。当前Reader的行为没有变化使用时应遵循仅在当前迭代内消费节点信息的纪律。10. 类方法需要 Document 的约定问题路线图指出Nokogiri::XML::Comment.new等类方法要求传入一个Document对象这个约定不直观、违背惯例应改为在Document上提供实例方法如new_comment作为推荐写法。当前仓库已部分落实这一设想。在 lib/nokogiri/xml/document.rb 中可以看到一组Document#create_*实例方法方法职责create_element(name, *contents_or_attrs, block)创建并返回一个元素节点create_text_node(string, block)创建文本节点create_cdata(string, block)创建 CDATA 节点create_comment(string, block)创建注释节点这些方法由文档持有天然满足新节点必须归属于某文档的不变式比裸调Nokogiri::XML::Comment.new(document, ...)更安全、更符合直觉。路线图中的命名是new_comment而当前仓库提供的是create_comment——可以推断在后续演进中这类工厂方法会继续扩充如create_processing_instruction等并逐渐成为创建节点的推荐入口。11.collect_namespaces的缺陷路线图直指collect_namespaces就是坏的它返回一个 Hash导致无法表达同前缀的多个命名空间背景见 #885。当前实现位于 lib/nokogiri/xml/document.rb 的collect_namespaces返回类型为HashString(Namespace#prefix) ⇒ String(Namespace#href)。测试 test/xml/test_document.rb 中有test_collect_namespaces用例覆盖其基本行为。由于 Hash 键是前缀字符串一旦文档中同名前缀绑定到不同 URI后出现的条目就会覆盖先出现的——信息丢失不可避免。路线图作者甚至调侃这方法似乎没用但反正我讨厌 XML谁知道呢。结论是若你的代码依赖collect_namespaces处理复杂命名空间文档应当改用Namespace节点遍历如node.namespaces、namespace_scopes、namespace_definitions来获得无信息丢失的完整视图。12. ParseOptions 的彻底改造从位掩码到语义化选项这是路线图中讨论最深入的一项值得展开。12.1 现状镜像 libxml2 的位掩码当前ParseOptionslib/nokogiri/xml/parse_options.rb完整镜像了 libxml2 的解析选项位再在 JRuby 上追溯映射到 Xerces-J/NekoHTML。核心位常量常量位值含义与安全提示STRICT0严格解析等价于关闭 RECOVERRECOVER10从输入错误中恢复NOENT11替换实体。⚠️ 名字与行为相反是启用实体替换解析不可信文档时设置它不安全DTDLOAD12加载外部子集不可信文档不安全DTDATTR13默认 DTD 属性DTDVALID14用 DTD 校验NOERROR15抑制错误报告NOWARNING16抑制警告报告PEDANTIC17启用迂腐式错误报告NOBLANKS18移除空白节点XINCLUDE110执行 XInclude 替换NONET111禁止网络访问。解析不可信文档时关闭它不安全NSCLEAN113移除冗余命名空间声明NOCDATA114将 CDATA 合并为文本节点NOXINCNODE115不生成 XInclude START/END 节点COMPACT116压缩小文本节点⚠️ 解析后禁止再修改 DOMOLD10117按 XML 1.0 update 5 之前的版本解析NOBASEFIX118不修复 XInclude 的 xml:base URIHUGE119放宽解析器硬性限制。⚠️不可信文档不安全BIG_LINES122行号支持long int默认short int另有四个速记组合常量DEFAULT_XMLRECOVER|NONET|BIG_LINES、DEFAULT_XSLT额外含 NOENT/DTDLOAD/DTDATTR/NOCDATA官方警告不要解析不可信 XSLT、DEFAULT_HTML含 NOERROR/NOWARNING、DEFAULT_SCHEMA仅 NONET|BIG_LINES。12.2 使用方式的三个痛点路线图明确列举了现有 API 的难用之处并全部能在源码中印证用块设置/取消选项很笨重。虽然支持块式写法XML::parse(xml) { |o| o.strict }但每次都要包一层 block。拼位掩码常量很笨重。例如MY_PARSE_OPTIONS ParseOptions::STRICT | ParseOptions::RECOVER | ...长且易错。命名危险。最典型的是NOENT——按名字理解是不替换实体实际行为是替换实体libxml2的XML_PARSE_NOENT语义如此官方在 parse_options.rb 的注释里特别标注contrary to what the name implies并加了盾牌警告。这直接引用了 issue #1582 的讨论。12.3 设想的改造方向路线图提出的方向可归纳为三条识别哪些选项在 libxml2 与 Xerces-J 两个解析器上都可用避免设了但没生效的跨平台差异官方文档也提示并非所有解析选项在 JRuby 上受支持让可信/不可信文档成为一组联动语义解析不可信文档时应把NONET、NOENT、DTDLOAD一起翻转——即不可信文档应同时关闭网络、实体替换与外子集加载允许发明新的解析选项例如 #1582 提出的允许本地实体、但禁止外部实体的安全中间态。从当前ParseOptions的class_eval批量生成 setter/unsetter/查询方法options.recover、options.norecover、options.recover?可以看出它的对象模型已经为语义化改造铺好了路——未来版本很可能会新增诸如信任级别这类高阶选项对象把NOENT/DTDLOAD/NONET打包成可信/不可信两个预设而保留noent、nonoent这类细粒度开关。12.4 一个可复制的安全实践在改造完成前解析不可信文档建议显式收紧require nokogiri untrusted File.read(untrusted.xml) doc Nokogiri::XML::Document.parse(untrusted) do |options| options.nonet # 确保网络访问被禁止默认已开启显式重申 options.nonoent # 关闭实体替换NOENT 默认关闭显式重申 options.nodtdload # 关闭外部 DTD 加载 options.norecover # 可选严格模式让错误直接抛出 end注意上述 setter 由ParseOptions的动态方法生成每个都返回self便于链式调用。13. 从路线图看 Nokogiri 的演进方法论纵观整个 ROADMAP可以提炼出 Nokogiri 在 API 演进上的几条方法论先立反例再动手每项改造都绑定具体的 issue/PR#530、#415、#679、#765、#528、#451、#656、#885、#1582……保证改动有真实痛点支撑以文档安全为硬约束NOENT、DTDLOAD、HUGE、NONET的安全警示贯穿 ParseOptions 设计可信/不可信文档将成为未来选项分组的主轴跨平台对齐优先反复强调 libxml2 与 Xerces-JJRuby的行为差异选项、编码、命名空间处理都要在两个解析器间收敛惯例优于魔法Document#create_*取代需要隐式传 Document 的类方法、Hash 选项取代位置参数提取、显式css/xpath取代启发式search都指向让接口更不容易用错。这些方向中Document#create_*工厂方法已经落地lib/nokogiri/xml/document.rbParseOptions的对象模型已具备语义化改造的骨架其余各项则仍处于规划状态。若你在代码中大量依赖save_with位掩码、search启发式、collect_namespaces或Comment.new这类接口建议密切关注上述 issue 与后续版本 changelogCHANGELOG.md以便平滑迁移。赞分享后端【免费下载链接】nokogiriNokogiri (鋸) makes it easy and painless to work with XML and HTML from Ruby.项目地址https://gitcode.com/gh_mirrors/no/nokogiri点击查看免费下载相关推荐image_picker_for_web 演进全解析Flutter 官方 Web 端图片选择插件的架构、限制与版本路线图image_picker_for_web 演进全解析Flutter 官方 Web 端图片选择插件的架构、限制与版本路线图 本篇技术指南以 Flutter 官方跨平台移动开发UI组件开发工具为什么选择haloregnetz_b.ra3_in1k与其他图像分类模型的全面对比分析为什么选择haloregnetz_b.ra3_in1k与其他图像分类模型的全面对比分析 在当今深度学习领域图像分类模型的 选择 变得至关重要。 haloreHackMyResume 开发路线图解读从 1.7 到 2.0 的技术演进蓝图HackMyResume 开发路线图解读从 1.7 到 2.0 的技术演进蓝图 这篇技术指南以 HackMyResume 仓库的 ROADMAP.md httCLI开发工具上一篇Microsoft Activation Scripts (MAS) 完整教程免费激活 Windows 和 Office 的 3 步实操下一篇从零跑通 yet-another-anime-game-launcher:Mac 动漫游戏安装、更新与调参完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表