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 的官方网址为http://www.yaml.org/

posted on 2013-10-19 11:36  荣锋亮  阅读(2716)  评论(0编辑  收藏  举报

导航