Skip to main content
Bun v1.4 中新增
在 Bun 中,XML 与 JSON、TOML、YAML 和 JSON5 一样是一等公民。你可以:
  • 使用 Bun.XML.parse 和 Bun.XML.stringify 解析和序列化 XML
  • 在运行时将 XML 文件 import 和 require 为模块(包括热重载和 watch 模式支持)
  • 在使用 Bun 打包器的前端应用中 import 和 require XML 文件

运行时 API

Bun.XML.parse()

将 XML 文档解析为普通 JavaScript 对象。
默认情况下,结果是一个以元素名称为键的紧凑对象——这是大多数 XML 转对象库采用的 @attr/#text 约定。其工作方式如下:
  • 结果只有一个键,即根元素的名称。
  • 没有属性和子元素的元素会变成其字符数据:一个字符串;如果为空,则为 ""。在 XML 中,<paid/> 和 <paid></paid> 是同一回事。
  • 其他元素都会变成对象。每个属性对应一个 "@name" 键,然后按照各个子元素名称首次出现的顺序,为每个不同的子元素名称添加一个键,并为元素自身的文本添加一个 "#text" 键。
  • 如果某个子元素名称在元素中出现多次,其键对应的值就是按文档顺序排列的数组;否则对应单个值。请参阅一个或多个。
  • compact 用于选择结构,不会改变值。Bun 会按原样返回文本:包括前导、尾随和内部空白字符,展开 CDATA 段和实体引用,并将行尾规范化为 \n——这与该元素对应的树结构所给出的文本相同。由于一个元素只有一个 "#text",Bun 会串联该元素的文本片段,并忽略位于子元素之间的纯空白片段(即文档的排版空白)。如果文档经过手动排版(<name>\n value\n</name>),请在读取时进行修剪。
  • 所有值都是字符串。不会将任何值转换为数字、布尔值或 null。
  • 名称保持原样,包括命名空间前缀("soap:Body");xmlns 声明是普通属性。
  • 注释、处理指令、<?xml …?> 声明和 <!DOCTYPE …> 都不会被表示。
@ 和 # 不能作为 XML 名称的开头,因此属性键和文本键不会与子元素键冲突。 紧凑结构用于 数据。它不会保留名称不同的兄弟元素之间的相对顺序,也不会保留文本相对于子元素的位置:
如果这些信息很重要——例如处理文档而非数据时——请传入 { compact: false },以获取根元素的树结构,并保留元素内容在文档中的顺序:
每个元素都是 { name, attributes, children };即使为空,这两个键也都会存在。children 按顺序保存元素内容:文本以字符串形式保存(保持原样,包括纯空白片段,相邻文本会合并)、子元素、以 { comment } 表示的注释,以及以 { target, data } 表示的处理指令。可以根据对象所包含的键来区分对象子项。与紧凑结构一样,树结构不会表示声明、DOCTYPE,或根元素之前和之后的任何内容。

一个或多个

在紧凑结构中,一个元素的列表和两个元素的列表具有不同的类型(entry: {…} 与 entry: [{…}, {…}])。通常是字符串的元素,如果带有属性,也会变成对象(<title> 与 <title type="html">)。对于需要遍历或可能带有属性的值,请采取稳妥的处理方式:
树结构没有这类歧义:children 是数组,每个元素都是一个对象。

输入类型和编码

XML.parse 接受字符串,或以 Buffer、TypedArray、DataView、ArrayBuffer 或 Blob 表示的字节数据。 字符串已经是解码后的文本,因此会检查其中 encoding 声明的语法,但除此之外会忽略它。字节数据会按照 XML 规则解码:字节顺序标记或 <?xml version="1.0" encoding="..."?> 中的 encoding 会指定使用 UTF-8(默认值)、UTF-16(任一字节序)或 ISO-8859-1。其他编码会抛出错误。

错误处理

当文档格式不正确时,Bun.XML.parse() 会抛出 SyntaxError(没有宽松模式);对于深度异常的嵌套,会抛出 RangeError:

Bun.XML.stringify()

将任一结构中的一个元素序列化为 XML。
当一个值具有字符串类型的 name,并且具有 children 或 attributes 属性时,Bun 会将其作为树节点写入。在 children 中,包含 name 的对象是元素,包含 comment 的对象是注释,包含 target 的对象是处理指令。其他所有值都会被视为紧凑对象,其中一个键用于命名根元素。Bun 会按顺序写入键,并将 @ 键作为属性。字符串、数字、布尔值和 bigint 会通过 String() 转换为文本;Date 会转换为其 ISO 字符串。null 会变成空元素。与 JSON.stringify 一样,Bun 会跳过 undefined、函数和符号。数组会为每个项生成一个元素。 输出是格式正确的 XML,否则 stringify 会抛出错误。Bun 会转义 &、< 和 >。属性值中的 "、制表符和换行符,以及任意位置的回车符,都会以字符引用的形式写入,以便解析后保持不变。XML 无法容纳的值会导致错误,而不是生成损坏的文档:不是 XML 名称的元素和属性名称("first name"、"0")、XML 字符集之外的字符(U+0000 和其他控制字符、未配对的代理项——XML 1.0 没有可用于这些字符的转义形式)、注释中的 --、处理指令中的 ?>、根节点处的数组或其他数组中的数组,以及循环结构。 结果只有元素本身,不包含 <?xml …?> 声明和 DOCTYPE,因此你可以将结果串联到外层元素中。要写入文件,请自行添加序言:

美化打印

传入 space 参数(空格数或缩进字符串,与 JSON.stringify 一样),即可缩进仅包含元素的内容。对于包含文本的元素,Bun 会将其写在同一行,因此缩进不会改变字符数据:
第二个参数已保留,请传入 null 或 undefined。 对于 XML.parse 生成的值,无论采用哪种结构,XML.parse(XML.stringify(value)) 都会返回相等的值。

模块导入

ES Modules

你可以直接导入 XML 文件。Bun 会像处理传递给 XML.parse 的字节数据一样解码文件(根据字节顺序标记或声明使用 UTF-8、UTF-16 或 ISO-8859-1)。模块的值是上文所述的紧凑对象:
config.xml

默认导入

app.ts

命名导入

根元素也可以作为命名导入使用:
app.ts

CommonJS

app.ts

导入属性

使用 with { type: "xml" } 将其他扩展名的文件作为 XML 解析:

XML 热重载

使用 bun --hot 运行应用程序时,Bun 会在 XML 文件发生变化时重新加载它们:
server.ts
terminal

打包器集成

使用 Bun 打包时,打包器会在构建时解析导入的 XML 文件,并将它们内联为 JavaScript 对象:
terminal
在构建时解析意味着:
  • 生产环境中零运行时 XML 解析开销
  • 更小的打包体积
  • 对未使用的属性进行 Tree Shaking

动态导入

你可以动态导入 XML 文件:

一致性

Bun 的 XML 解析器使用 Rust 编写,并作为非验证处理器实现了 XML 1.0(第五版),不会读取外部实体:
  • 整个文档(包括内部 DTD 子集)都必须格式正确,否则会抛出 SyntaxError。
  • 解析器会展开文档中声明的内部实体,并设置展开限制,因此“十亿笑声”攻击载荷会失败,而不是耗尽内存。它会规范化属性值,并应用内部子集中声明的属性默认值。
  • 解析器不会获取或读取外部 DTD 或外部实体,因此不存在 XXE 攻击面。对于没有 DTD 的文档,引用未声明的实体会导致错误。如果 DOCTYPE 指向外部子集(或使用参数实体),而该子集可能声明了此实体,解析器就会保留原样的引用(&nbsp; 仍为 &nbsp;),除非文档指定了 standalone="yes"。
  • 解析器不会根据 DTD 进行验证。它不会解析命名空间,并会原样保留带前缀的名称。
解析器使用 W3C XML 一致性测试套件进行测试。对于此类处理器有明确预期结果的 1,679 个测试用例,解析器均已通过:拒绝格式不正确的文档,并接受格式正确的文档。对于测试套件提供了规范输出的用例,格式正确文档的元素树(包括处理指令)与规范输出逐字节匹配。已转换的测试套件列出了所有用例,包括那些结果合理地取决于是否读取外部实体的用例。

性能

与 Bun 的 JSON 解析器一样,解析器分两个阶段工作。SIMD 扫描阶段(运行时分派 AVX2/AVX-512/NEON/SVE 内核)会查找可能影响解析的字节,因此解析器无需逐字节扫描字符数据、属性值、注释和 CDATA 段。元素名称和属性名称会像 JSON.parse 一样复用 JavaScriptCore 的原子字符串缓存。 bench/xml/xml.mjs 会在相同文档上比较 Bun.XML.parse 与常用的 npm 解析器(越低越好;Linux x64,单核):