YAML -- 想要爱你很容易

YAML 作为一种比 XML 更为简单易读的序列化语言,正越来越多地被用于应用及配置文件的开发中。本文将简单介绍 YAML 的现状,YAML 与 XML 相比的优劣势,并通过实际的例子给出 YAML 的典型应用场景及其使用方法(涵盖 c/c++ 和 ruby)。读者将通过本文建立对 YAML 的理解并学会如何在实际开发中使用该格式的语言。

林容容, 高级软件工程师, EMC2

林容容,EMC2 上海研发中心高级软件工程师,曾在 IBM 中国研发中心系统与技术部(CSTL)任高级工程师。



2011 年 3 月 10 日

尽管在实际的项目中有许多现有项目的数据表达都是用 XML 组织的,然而 YAML 作为一种比 XML,或者 JSON 都更为简单易读的序列化语言,正越来越多地被人们所接受和喜欢,并应用于软件项目的开发中,比如:现实生活中的数据上程序中的序列化表示,以及系统中的配置文件的书写。众所周知,XML 的设计使得数据的表达几乎无所不能,那么是什么让 YAML 这个后来者抢占了其一席之地呢?

本文将首先简单介绍 YAML 的定义,语法及其与 XML 的不同之处,然后通过两方面的例子展示 YAML 在实际应用中的场景和方法,让读者在具体的例子中体会 YAML 的优势所在,并能通过对本文的学习建立应用 YAML 的基础。

YAML 简介及其与 XML 的不同之处

自从有了递归定义,大家都爱上了这种起名方式,YAML 也是。YAML 的定义是:“YAML Ain ’ t a Markup Language”,即:YAML 不是一种标记语言的递归缩写。要问 YAML 到底是不是一种标记语言呢?答案:是的。有意思的是:在 YAML 开发的早期,YAML 其实参考了许多其他语言,如 XML, SDL 及电子邮件格式等等,并最终把自己定义为:“Yet Another Markup Language”。既然明明是标记语言,为什么后来又改名换姓,非说自己不是标记语言了呢?其实名字的更换正是为了强调 YAML 的与众不同:YAML 是以数据为设计语言的重点的,而不是像 XML 以标记为重点。实事上,正是因为这样一种设计理念使得 YAML 在后来的不少应用中取代 XML,成为一种可读性高,易于表达数据序列的编程语言。

YAML 的数据组织主要依靠的是空白,缩进,分行等结构。这使得 YAML 语言很容易上手。我们就以 YAML 官方网站上给出的一个例子来看看 YAML 文件的书写(如清单 1 所示)。相信熟悉 XML 的人都会感受到 YAML 是多么的简洁明了!

YAML 的语法十分简单:用“-”来表示一些序列的项(Sequence),如清单 1 里的产品(product)有两样东西(Basketball 和 Super Hoop)组织为一个序列;用“:”来表示一对项目(Map)里的栏目(Key)和其相应的值(Value),比如清单 1 发票里的时间(date)的值是 2001-01-23,这就是一个 Map。这些就是 YAML 里最重要的语法了。如果想知道其他语法的细节可以参看 YAML 官方网页里的参考卡片(reference card):http://www.yaml.org/refcard.html

清单 1. 用 YAML 表达的一个购物发票
 --- !clarkevans.com/^invoice 
 invoice: 34843 
 date : 2001-01-23 
 bill-to: &id001 
  given : Chris 
  family : Dumars 
  address: 
  lines: | 
  458 Walkman Dr. 
  Suite #292 
  city : Royal Oak 
  state : MI 
  postal : 48046 
 ship-to: *id001 
 product: 
  - sku : BL394D 
  quantity : 4 
  description : Basketball 
  price : 450.00 
  - sku : BL4438H 
  quantity : 1 
  description : Super Hoop 
  price : 2392.00 
 tax : 251.42 
 total: 4443.52 
 comments: > 
  Late afternoon is best. 
  Backup contact is Nancy 
  Billsmer @ 338-4338.

正因为 YAML 的语法和组织结构简单巧妙,YAML 很容易就可以插入另一个 YAML 文件,甚至其他类型的文件,包括 XML, SDL, JSON 等。相反,如果要在 XML 里插入 YAML,相信了解 XML 的朋友都知道,那是要加很多符号(Potential Sigils)才能完成。

通过上面的举例,我们可以看出,YAML 较 XML 而言可读性更好,也更易于实现。但 YAML 的优点远不止于此。当 YAML 诞生不久的时候,其应用还只在动态编程语言如 Perl,Python, Ruby,及当时还应用得不算广泛的 java 编程中。而现如今 YAML 的支持库已包括 C/C++,C#/.NET,PHP 等。可以说,几乎在如今流行的编程语言中,YAML 已经无处不在。

事实上,纵观程序语言的发展,从 C 到 C++,Java,再到动态脚本语言 Perl,Python,PHP, 直到如今相当之流行的 Ruby,人们越来越摈弃那些规则复杂,语法深奥的语言,取而代之的则是灵活简单,易读易写的,越来越趋近人类阅读书写习惯的程序语言。在数据的序列化上,这种趋势其实是一样的。YAML 作为可以和 XML 一样扩展性强,表达力强且基于流操作的语言,凭借着自身在可读性,易实现,与脚本语言易交互,于宿主语言的数据结构类型易使用等各方面优势,在数据序列化格式领域正成为越来越为人们喜爱的一种语言。

本文的后续两个章节将从开发应用和配置文件两个方面以及 C++ 和 Ruby 这两种语言的角度,以实例介绍 YAML 的使用方法和优越之处。


YAML 在开发应用中的使用及实例(C++)

我们还以清单 1 里的发票数据为例。假如你有一个 C++ 开发的应用,其中的发票数据需要用一种语言来做序列化。选择 YAML,我们现在就把发票信息数据用 YAML 写在一个文件里的(具体请参考附件里的 invoice.yaml),然后下载并安装 yaml-cpp 库文件包,详细的方法和步骤可以参考 yaml-cpp 的官方网址上的说明:http://code.google.com/p/yaml-cpp/。接着我们就可以开始设计相应的 C++ 代码里的数据结构了。具体定义可以参看下面的清单 2.

清单 2. 发票数据在 C++ 中的数据结构表示
 struct Product { 
  std::string sku; 
  int quantity; 
  std::string description; 
  float price; 
 }; 

 struct Address { 
  std::string lines; 
  std::string city; 
  std::string state; 
  int postal; 
 }; 

 struct Bill { 
  std::string given; 
  std::string family; 
  Address address; 
 }; 

 struct Invoice { 
  int invoice; 
  std::string date; 
  Bill bill; 
  std::vector <Product> products; 
  float tax; 
  float total; 
  std::string comments; 
 };

虽然 C++ 在标准模板库(STL)里有 Map,List 等与 YAML 相对应的数据类型,但是本例中为了更清晰看出 YAML 的用法,只简单构建了一些结构体与之对应。(从另一角度说,如果 YAML 用在像 Ruby 一样的脚本语言中,那么数据类型的对应将更加简单。在下一节的内容中将给出相关的实例。)另外,在 C++ 里,为了能够使用操作符“>>”(stream extraction operator)帮组我们完成 YAML 数据和 C++ 数据之间的赋值,我们需要对该操作符进行重载(overload)。具体实现可参考清单 3。

清单 3. 重载运算符“>>”
 void operator >> (const YAML::Node& node, Product& p) { 
  node["sku"] >> p.sku; 
  node["quantity"] >> p.quantity; 
  node["description"] >> p.description; 
  node["price"] >> p.price; 
 } 

 void operator >> (const YAML::Node& node, Address& a) { 
  node["lines"] >> a.lines; 
  node["city"] >> a.city; 
  node["state"] >> a.state; 
  node["postal"] >> a.postal; 
 } 

 void operator >> (const YAML::Node& node, Bill& b) { 
  node["given"] >> b.given; 
  node["family"] >> b.family; 
  node["address"] >> b.address; 
 } 

 void operator >> (const YAML::Node& node, Invoice& invoice) { 
  node["invoice"] >> invoice.invoice; 
  node["date"] >> invoice.date; 
  node["bill-to"] >> invoice.bill; 

  const YAML::Node& products = node["product"]; 
  for(unsigned i=0;i<products.size();i++) { 
  Product p; 
  products[i] >> p; 
  invoice.products.push_back(p); 
  } 
  
				 node["tax"] >> invoice.tax; 
  node["total"] >> invoice.total; 
  node["comments"] >> invoice.comments; 
 }

在对 Product 的实现中,这里使用了 C++ 标准模板库里的 vector 容器以完成对多个 product 的赋值。

有了数据结构,有了“>>”,那么当我们需要使用 YAML 数据的时候,只需读入 YAML 文件,交给 YAML 库的解析器(Parser)便高枕无忧了。相应的代码示例请参看清单 4。

清单 4. 解析 YAML 数据
 std::ifstream fin("invoice.yaml"); 
 YAML::Parser parser(fin); 
 YAML::Node doc; 
 parser.GetNextDocument(doc); 
 Invoice invoice; 
 doc >> invoice;

完整的代码示例请参考附件里的“invoice.cpp”和“invoice.yaml”。对于例子中的数据结构和方法,读者也可以构建更加完美的实现,这里仅是一简单示例。


YAML 在配置文件中的使用及实例(Ruby)

假设你在开发一个分布式系统,系统中有不同的节点(node)。那么配置文件里需要写明各种不同角色的节点各自的配置信息。如果你打算用 Ruby 语言来解析这种配置文件,那么用 YAML 来书写配置文件一定是你的不二选择。其实不仅仅是 Ruby,YAML 对于几乎所有的脚本语言来说,它的解析都比 XML 来得容易得多。

Ruby 有着丰富灵活的数据结构,这使得 YAML 加 Ruby 的组合仿佛天生一对。比如:YAML 里的序列(Sequence)对应 Ruby 里的数组(Array);YAML 的一对项(Map)对应着 Ruby 里的 Hash 等等。详细的对应关系可见: http://www.yaml.org/YAML_for_ruby.html#folded_block_as_a_mapping_value

假设我们的配置文件如清单 5 所示。

清单 5. 配置文件示例
 # 
 # Config file example 
 # 

 node_a: 
  conntimeout: 300 
  external: 
  iface: eth0 
  port: 556 
  internal: 
  iface: eth0 
 port: 778 
  broadcast: 
  client: 1000 
  server: 2000 
 node_b: 
  0: 
  ip: 10.0.0.1 
  name: b1 
  1: 
  ip: 10.0.0.2 
  name: b2

该配置文件定义了两种不同角色的节点(node),节点 node_a 是一个负责内外通信的节点。它的配置信息包括:其连接的 timeout 时间为 300 秒,外部及内部连接使用的接口(interface)及端口号(port),其中,内部连接还配置了广播的客户端和服务器端的端口。而 node_b 类型的节点共有两个节点组成,分别是 b1 和 b2,其配置信息包括 IP 地址和主机名。实际的配置当然会比这个文件复杂一些,这里仅仅是一个示例。当我们用 Ruby 语言解析该数据后(解析方法如清单 6 所示),就可以得到清单 7 里的数据结果。

清单 6. 用 Ruby 解析 YAML 数据
 #!/usr/bin/ruby 
 require 'yaml'
 yml = YAML::load(File.open('t.yml')) 
 p yml

大家可以看到,Ruby 语言解析 YAML 只需了了四行代码。其实我们完全可以打印行省掉,改写为“p YAML::load(File.open('t.yml'))”。也就是说,真正需要的就两行,require 和 YAML::load。再看结果,由于清单 7 里的结果在结构上比较复杂,也为了能和清单 5 中的 YAML 数据“看”起来相对应,这里做了一些缩进上的修改。由此我们可以更清晰得看出 YAML 数据在 Ruby 中的组织。

清单 7. 数据解析结果
 {"node_a"=>{ 
  "internal"=>{ 
  "broadcast"=>{ 
  "client"=>1000, 
  "server"=>2000 
  }, 
  "port"=>778, 
  "iface"=>"eth0"
  }, 
  "conntimeout"=>300, 
  "external"=>{ 
  "port"=>556, 
  "iface"=>"eth0"
  } 
 }, 
"node_b"=>{ 
  0=>{ 
  "name"=>"b1", 
  "ip"=>"10.0.0.1" 
  }, 
  1=>{ 
  "name"=>"b2", 
  "ip"=>"10.0.0.2"
  } 
 } 
 }

在 Ruby 中使用 YAML 就是这么简单,你只有 require 了 YAML 的库,一切数据到 Ruby 数据结构的转换都轻而易举。其实 Ruby 的数据也可以 dump 为 YAML。感兴趣的朋友可以参看 YAML for Ruby 的文档:http://yaml4r.sourceforge.net/doc/


总结

也许有人会说 YAML 和 XML 只是有其不同的应用场景和使用范围,也许有人会质疑 YAML 在性能上是否同样可以超越 XML,而且这些争论一直都存在着,然后大家都不得不公认的是:YAML 凭借自己的易读性,易操控性已经成为 XML 的对手。

也许这对 XML 来说是不公平的,毕竟 XML 是由 SGML 演变来的,而 YAML 是全新设计的语言,并且其设计初衷就是为了使得数据的序列化更接近人类阅读习惯。然而我们可以从中看到的是 YAML 的出现反映了一种趋势,或者说是一种喜好。人们正在试图将规则复杂不易理解的编程方式通过某种努力去改变为更为简单方便,更为灵活好用的状态。真是 YAML 语言的这种特质,使得我们不得不说 YAML, 想要爱你很容易。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=631702
ArticleTitle=YAML -- 想要爱你很容易
publish-date=03102011