Flutter XML解析插件xml2的使用

Flutter XML解析插件xml2的使用

Dart XML

Dart XML 是一个轻量级库,用于解析、遍历、查询、转换和构建XML文档。

这个库是开源的、稳定的并且经过了良好的测试。开发在 GitHub 上进行。你可以在此报告问题或创建拉取请求。通用问题最好在 StackOverflow 上提问。

该包托管在 dart packages 上。每次发布都会生成最新的类文档。

安装

遵循 dart packages 上的安装说明。

在你的 Dart 代码中导入库:

import 'package:xml/xml.dart';

⚠️ 这个库广泛使用了静态扩展方法。如果你使用库前缀或者仅选择性地显示类,可能会错过一些功能。由于历史原因,公共类具有 Xml 前缀,因此与其他代码的冲突应该很少。

读取和写入

要读取 XML 输入,可以使用工厂方法 XmlDocument.parse(String input):

final bookshelfXml = '''<?xml version="1.0"?>
<bookshelf>
  <book>
    <title lang="en">Growing a Language</title>
    <price>29.99</price>
  </book>
  <book>
    <title lang="en">Learning XML</title>
    <price>39.95</price>
  </book>
  <price>132.00</price>
</bookshelf>''';
final document = XmlDocument.parse(bookshelfXml);

结果对象是一个 XmlDocument 的实例。如果文档无法解析,则会抛出 XmlParserException 异常。

要将解析后的 XML 文档写回,只需调用 toString() 或者如果需要更多控制则调用 toXmlString(...):

print(document.toString());
print(document.toXmlString(pretty: true, indent: '\t'));

要从文件中读取 XML,请使用 dart:io 库:

final file = new File('bookshelf.xml');
final document = XmlDocument.parse(file.readAsStringSync());

如果文件不是 UTF-8 编码,请在 readAsStringSync 中传递正确的编码。对于读取和写入大型文件,你可能需要使用事件驱动的 API。

遍历和查询

访问器允许访问 XML 树中的节点:

  • attributes 返回节点的属性。
  • children 返回节点的直接子节点。

这两个列表都是可变的,并支持所有常见的 List 方法,例如 add(XmlNode)addAll(Iterable<XmlNode>)insert(int, XmlNode)insertAll(int, Iterable<XmlNode>)。尝试添加 null 值或不支持的节点类型会抛出 XmlNodeTypeError 错误。已经作为树一部分的节点不会自动移动,你需要先创建一个副本,否则会抛出 XmlParentError 异常。XmlDocumentFragment 节点会自动展开并添加其子节点的副本。

有方法沿着不同的轴遍历 XML 树:

  • siblings 返回在同一级别上位于当前节点之前和之后的节点的可迭代对象。
  • preceding 返回位于当前节点开头标签之前的节点的可迭代对象。
  • descendants 返回位于当前节点之后的节点的可迭代对象。
  • following 返回位于当前节点结尾标签之后的节点的可迭代对象。
  • ancestors 返回当前节点的祖先节点的可迭代对象,即父节点、祖父节点等。注意,这是唯一一个以逆文档顺序遍历节点的可迭代对象。

例如,可以使用 descendants 迭代器来提取 XML 树中的所有文本内容:

final textual = document.descendants
  .where((node) => node is XmlText && node.text.trim().isNotEmpty)
  .join('\n');
print(textual);

还有一些方便的助手只过滤元素节点:childElementssiblingElementsprecedingElementsdescendantElementsfollowingElementsancestorElements

此外,还有一些帮助程序来查找具有特定标签的元素:

  • getElement(String name) 查找具有提供的 name 标签的第一个直接子节点,或者返回 null
  • findElements(String name) 查找具有提供的 name 标签的当前节点的直接子节点。
  • findAllElements(String name) 查找具有提供的 name 标签的当前节点的直接和间接子节点。

例如,要查找所有 <title> 标签的节点,可以这样写:

final titles = document.findAllElements('title');

上述代码返回一个惰性迭代器,递归遍历 XML 文档并返回具有请求标签名的所有元素节点。要提取文本内容,可以调用 text

titles
    .map((node) => node.text)
    .forEach(print);

这将打印 Growing a LanguageLearning XML

同样,为了计算所有书籍的总价,可以编写以下表达式:

final total = document.findAllElements('book')
    .map((node) => double.parse(node.findElements('price').single.text))
    .reduce((a, b) => a + b);
print(total);

注意,这首先找到所有的书籍,然后提取价格,以避免计算包含在书架中的价格标签。

构建

虽然可以手动实例化和组合 XmlDocumentXmlElementXmlText 节点,但 XmlBuilder 提供了一个简单的流畅 API 来构建完整的 XML 树。为了创建上面的书架示例,可以这样写:

final builder = XmlBuilder();
builder.processing('xml', 'version="1.0"');
builder.element('bookshelf', nest: () {
  builder.element('book', nest: () {
    builder.element('title', nest: () {
      builder.attribute('lang', 'en');
      builder.text('Growing a Language');
    });
    builder.element('price', nest: 29.99);
  });
  builder.element('book', nest: () {
    builder.element('title', nest: () {
      builder.attribute('lang', 'en');
      builder.text('Learning XML');
    });
    builder.element('price', nest: 39.95);
  });
  builder.element('price', nest: '132.00');
});
final document = builder.buildDocument();

element 方法支持可选命名参数:

  • 最常见的是 nest: 参数,用于将内容插入元素中。通常,这将是一个调用 builder 方法来定义属性、声明命名空间和添加子元素的函数。但是,该参数也可以是一个字符串或任意 Dart 对象,将其转换为字符串并作为文本节点添加。
  • 为了简化,还有 attributes: 参数,它接受一个映射来定义简单的键值对。
  • 此外,我们可以使用 namespace: 提供元素的命名空间 URI 并使用 namespaces: 声明新的命名空间前缀。有关详细信息,请参阅该方法的文档。

构建器模式允许你轻松地将重复的部分提取到特定的方法中。在上面的例子中,可以将写一本书的部分放到一个单独的方法中,如下所示:

void buildBook(XmlBuilder builder, String title, String language, num price) {
  builder.element('book', nest: () {
    builder.element('title', nest: () {
      builder.attribute('lang', language);
      builder.text(title);
    });
    builder.element('price', nest: price);
  });
}

上述 buildDocument() 方法返回构建的文档。要将构建的节点附加到现有的 XML 文档中,可以使用 buildFragment()。一旦构建器返回构建的节点,其内部状态将被重置。

final builder = XmlBuilder();
buildBook(builder, 'The War of the Worlds', 'en', 12.50);
buildBook(builder, 'Voyages extraordinaries', 'fr', 18.20);
document.rootElement.children.add(builder.buildFragment());

事件驱动

读取大型 XML 文件并将它们的 DOM 实例化到内存中可能是昂贵的。作为替代方案,此库提供了使用 Dart 迭代器或流将 XML 文档读取和转换为事件序列的可能性。这些方法类似于其他库中已知的事件驱动 SAX 解析。

import 'package:xml/xml_events.dart';

迭代器

在最简单的情况下,你可以通过以下代码获得一个 Iterable<XmlEvent> 过滤输入字符串中的事件。这会懒加载解析输入,只有在请求时才会解析输入:

parseEvents(bookshelfXml)
    .whereType<XmlTextEvent>()
    .map((event) => event.text.trim())
    .where((text) => text.isNotEmpty)
    .forEach(print);

这种方法需要整个输入在开始时可用,并且如果数据本身是异步可用的(例如来自慢速网络连接),则不起作用。更复杂的方法是使用 Dart 流。

要从文件或 HTTP 流异步解析和处理事件,可以使用提供的编解码器在字符串、事件和 DOM 树节点之间转换:

  • 编解码器:XmlEventCodec

    • 将字符串解码为一系列 XmlEvent 对象。Stream<List<XmlEvent>> toXmlEvents()Stream<String>
    • 将一系列 XmlEvent 对象编码为字符串。Stream<String> toXmlString()Stream<List<XmlEvent>>
  • 编解码器:XmlNodeCodec

    • 将一系列 XmlEvent 对象解码为 XmlNode 对象。Stream<List<XmlNode>> toXmlNodes()Stream<List<XmlEvent>>
    • 将一系列 XmlNode 对象编码为 XmlEvent 对象。Stream<List<XmlEvent>> toXmlEvents()Stream<List<XmlNode>>

提供了一些转换来简化处理复杂的流:

  • 通过删除空事件和合并相邻的文本事件来规范化一系列 XmlEvent 对象。Stream<List<XmlEvent>> normalizeEvents()Stream<List<XmlEvent>>
  • 使用其父事件注释 XmlEvent 对象,使其可通过 XmlParented.parentEvent 访问。验证嵌套并抛出异常如果无效。Stream<List<XmlEvent>> withParentEvents()Stream<List<XmlEvent>>
  • 从一系列 XmlEvent 对象中过滤形成子树的事件序列,其中谓词返回 trueStream<List<XmlEvent>> selectSubtreeEvents(Predicate<XmlStartElementEvent>)Stream<List<XmlEvent>>
  • 将分块流的对象展平为对象流。Stream<T> flatten()Stream<Iterable<T>>
  • 在此流上的每个事件上执行提供的回调。Future forEachEvent({onText: ...})Stream<XmlEvent>

例如,以下代码段从互联网下载数据,将 UTF-8 输入转换为 Dart 字符串,将字符流解码为 XmlEvent,最后规范化并打印事件:

final url = Uri.parse('http://ip-api.com/xml/');
final request = await HttpClient().getUrl(url);
final response = await request.close();
await response
  .transform(utf8.decoder)
  .toXmlEvents()
  .normalizeEvents()
  .forEachEvent(onText: (event) => print(event.text));

同样,以下代码片段从 sitemap.xml 文件中提取位置信息,将 XML 事件转换为 XML 节点,最后打印包含的文本:

final file = File('sitemap.xml');
await file.openRead()
  .transform(utf8.decoder)
  .toXmlEvents()
  .selectSubtreeEvents((event) => event.name == 'loc')
  .toXmlNodes()
  .expand((nodes) => nodes)
  .forEach((node) => print(node.innerText));

处理 XML 事件流的一个常见挑战是缺乏层次结构信息,因此很难找出诸如查找命名空间 URI 的父依赖关系。.withParentEvents() 转换验证层次结构并注释事件及其父事件。这使功能(如 parentEventnamespaceUri 访问器)变得简单得多,并使映射和选择事件更加简单。例如:

await Stream.fromIterable([shiporderXsd])
  .toXmlEvents()
  .withParentEvents()
  .selectSubtreeEvents((event) =>
      event.localName == 'element' &&
      event.namespaceUri == 'http://www.w3.org/2001/XMLSchema')
  .toXmlNodes()
  .expand((nodes) => nodes)
  .forEach((node) => print(node.toXmlString(pretty: true)));

更多关于Flutter XML解析插件xml2的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter XML解析插件xml2的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在 Flutter 中,xml 是一个常用的 XML 解析库,它提供了简单易用的 API 来解析和生成 XML 文档。xml 库的完整名称是 xml, 你可以在 pubspec.yaml 文件中添加依赖来使用它。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 xml 依赖:

dependencies:
  flutter:
    sdk: flutter
  xml: ^6.1.0  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 解析 XML 文档

假设你有一个 XML 字符串如下:

<bookstore>
  <book>
    <title lang="en">Learning Flutter</title>
    <author>John Doe</author>
    <year>2023</year>
    <price>29.99</price>
  </book>
  <book>
    <title lang="es">Aprendiendo Flutter</title>
    <author>Jane Smith</author>
    <year>2022</year>
    <price>24.99</price>
  </book>
</bookstore>

你可以使用 xml 库来解析这个 XML 字符串:

import 'package:xml/xml.dart' as xml;

void main() {
  String xmlString = '''
  <bookstore>
    <book>
      <title lang="en">Learning Flutter</title>
      <author>John Doe</author>
      <year>2023</year>
      <price>29.99</price>
    </book>
    <book>
      <title lang="es">Aprendiendo Flutter</title>
      <author>Jane Smith</author>
      <year>2022</year>
      <price>24.99</price>
    </book>
  </bookstore>
  ''';

  final document = xml.XmlDocument.parse(xmlString);

  // 获取所有 <book> 元素
  final books = document.findAllElements('book');

  for (var book in books) {
    final title = book.findElements('title').single.text;
    final lang = book.findElements('title').single.getAttribute('lang');
    final author = book.findElements('author').single.text;
    final year = book.findElements('year').single.text;
    final price = book.findElements('price').single.text;

    print('Title: $title, Lang: $lang');
    print('Author: $author');
    print('Year: $year');
    print('Price: $price');
    print('---');
  }
}

3. 生成 XML 文档

你也可以使用 xml 库来生成 XML 文档:

import 'package:xml/xml.dart' as xml;

void main() {
  final builder = xml.XmlBuilder();
  builder.processing('xml', 'version="1.0"');
  builder.element('bookstore', nest: () {
    builder.element('book', nest: () {
      builder.element('title', attributes: {'lang': 'en'}, nest: 'Learning Flutter');
      builder.element('author', nest: 'John Doe');
      builder.element('year', nest: '2023');
      builder.element('price', nest: '29.99');
    });
    builder.element('book', nest: () {
      builder.element('title', attributes: {'lang': 'es'}, nest: 'Aprendiendo Flutter');
      builder.element('author', nest: 'Jane Smith');
      builder.element('year', nest: '2022');
      builder.element('price', nest: '24.99');
    });
  });

  final xmlDocument = builder.buildDocument();
  print(xmlDocument.toXmlString(pretty: true));
}
回到顶部