Nokogiri::XML::Builder.new 是构建 XML 的最可控起点,需注意命名空间声明、属性哈希必须首参、UTF-8 编码显式指定,避免动态追加节点和错误解析方式。

用 Nokogiri::XML::Builder 创建空文档再填充
直接调用 Nokogiri::XML::Builder.new 是最可控的起点,它默认生成带 XML 声明的空文档,且节点自动闭合逻辑清晰。别用 Nokogiri::XML 的字符串解析方式来“构建”,那是为解析设计的,强行拼接容易漏转义或结构错乱。
常见错误是传入空字符串或 nil 初始化 builder,导致后续 doc 属性不可用:
Nokogiri::XML::Builder.new { |xml| xml.root { xml.child "text" } }.doc
# ✅ 正确:闭包内定义结构,返回完整文档对象
注意 builder 默认不加换行缩进,如需可手动插入 /n 或用 to_xml(indent: 2) 输出时格式化。
嵌套标签与属性必须用哈希传参
Nokogiri::XML::Builder 的 DSL 要求属性必须作为第一个参数以哈希形式传入,内容(文本或子块)在后。顺序错了会把属性当文本、或抛 ArgumentError:
-
xml.tag({ id: "1" }, "value")✅ -
xml.tag("value", { id: "1" })❌ —— 这会把 “value” 当作属性哈希的 key -
xml.tag(id: "1") { "value" }✅ —— 块内写文本也合法
特殊字符如 &、 会被自动转义,无需手动调 CGI.escape;但 CDATA 需显式调用 xml.cdata 方法。
处理命名空间和前缀要提前声明
如果目标 XML 含命名空间(如 xmlns:ns="http://example.com"),不能靠后期追加属性。必须在根节点初始化时用 xmlns 键声明,并在子节点中用符号前缀引用:
Nokogiri::XML::Builder.new do |xml|
xml.root("xmlns:ns" => "http://example.com") do
xml["ns"].item("NS content")
end
end.doc
没声明就直接写 xml.ns:item 会报 NoMethodError;用字符串前缀如 xml["ns"].item 才是正确语法。多个命名空间可并列传入哈希,但冲突前缀会导致覆盖。
中文或 UTF-8 内容要确保 Builder 实例编码一致
默认 builder 生成文档是 UTF-8 编码,但若 Ruby 源文件本身不是 UTF-8(比如系统 locale 是 GBK),中文字符串可能被错误解释为 ASCII-8BIT,导致输出乱码或 Encoding::CompatibilityError。
安全做法是显式指定编码:
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
xml.root { xml.text "你好" }
end.doc.to_xml
另外,避免用 doc.root.add_child 动态追加含中文的 Nokogiri::XML::Text 节点——它不会自动继承父节点编码,容易出错。所有内容尽量在 builder 闭包内一次性定义。
命名空间声明、属性传参顺序、编码一致性这三点,实际项目里最容易反复踩坑。
