本文使用了 Scala 编程语言,其版本为 2.6.1。作为一种新生语言,它仍在快速发展,因此需要了解它的最新进展。本文并不要求读者具备 Scala 知识,而是尝试介绍 Scala 的语法和术语。Scala 需要一个 Java 虚拟机。本文使用 JDK 1.6.0_04,但 Scala 只需要 1.5 或更高版本。尽管本文没有包含 Java 代码,但是也要求读者熟悉 Java 编程。

解析XML
首先探讨如何使用 Scala 解析 XML。像大多数编程语言一样,Scala 提供了多种 XML 解析方法。以下是一些基本的方法:基于表示的 InfoSet/DOM、push (SAX) 或 pull (StAX) 事件、与 JAXB(Java Architecture for XML Binding) 类似的数据绑定。您将探讨基于 DOM 的处理,因为它演示了 Scala 语法的众多好处。在深入研究之前,您需要了解要解析的 XML 内容以及对它执行哪些操作。因此需要借助一个样例应用程序。
样例应用程序:FriendFeed
FriendFeed 是一个在 2008 年非常流行的 Web 服务,它允许用户在其他服务中聚合他们的行为,例如各种博客(blog)服务、即时信息传递服务、YouTube、Flickr 和 Twitter 等。然后从这种聚合中创建单独的数据提要。您可以针对个人执行上述操作,即对指定的人员实现聚合行为。尽管可能不是很有用,但是 FriendFeed 的公共提要非常有趣。它在所有 FriendFeed 用户之间聚合所有的公共行为。FriendFeed 提供一个 API 来访问个人提要和公共提要。您将编写一个应用程序来访问和解析公共提要。
利用 Java 库
您要做的首要事情是访问 FriendFeed 的公共提要。其 URL 为 http://friendfeed.com/api/feed/public。默认的情况下它以 JSON 格式显示数据并且显示最新的 30 个条目。要将其改为 XML 格式,添加查询字符串参数 format=xml。例如,要将条目数目改为 100,添加查询字符串参数 num=100 。现在您只需要访问这个 URL。这在 Java 代码中很容易实现,因此在 Scala 代码也很容易。看一下清单1中访问 FriendFeed 公共提要的代码。
清单 1. 访问 FriendFeed
object FriendFeed {
import java.net.{URLConnection, URL}
import scala.xml._
def friendFeed():Elem = {
val url = new URL("http://friendfeed.com/api/feed/public?format=xml&num=100")
val conn = url.openConnection
XML.load(conn.getInputStream)
}
}
|
注意,这里要做的第一件事就是导入两个核心的 Java 类。 Scala 不必使用自己的 API 执行诸如打开 HTTP 连接之类的操作,因为它可以利用 Java 的 API 来解决这个问题。注意 Scala 为从同一包导入多个类提供了捷径。下一行导入 Scala 的核心XML 类。下划线就像Java 中的星号一样,它导入scala.xml 包中的所有类。
因此使用 Java 的 API 打开一个到 FriendFeed 的 HTTP 连接。接下来使用 Scala 的 XML 对象进行解析。这里有很多有趣的现象。首先,XML 是一个 Scala 对象,即它是一个单例(singleton)对象。Scala 没有静态的方法、字段和初始化程序。相反您可以定义一个对象(而不是类)并且它将成为类的一个单例实例。您可以像调用静态方法一样访问单例对象的方法。这就是 XML.load 语句的作用。注意,尽管这是一个 Scala 对象的方法,它接受一个 Java 对象(java.io.InputStream)作为参数。这正体现了 Scala 和 Java 之间的紧密联系。最后要注意没有返回语句。返回语句在 Scala 中是可选的。如果没有返回语句,将返回对方法的最后一个语句的求值(如果可行并且 Scala 没有返回编译错误的话)。现在可以很简单地访问 清单 1 中的方法,如清单2所示。
清单 2. 访问 friendFeed 方法
val feedXml = friendFeed |
注意在调用 friendFeed 的方法时没有必要使用圆括号。您也可以使用 Scala 的类型接口。您没有必要声明 feedXml 的类型,因为它是由 friendFeed 方法的返回类型推断出来的。再次查看 清单 1 并了解它如何利用语法捷径。最后要注意的是您所解析的 XML 对象被声明为 val。这使其成为不可变的对象(像 Java 代码中的字符串),这在 Scala 中是很常见的。把 XML 作为一个不可变的对象有很多优点,但是如果您习惯在 DOM 中使用 appendChild API,那么则很难适应这一点。现在已经从 FriendFeed 中解析了XML,可以开始使用Scala 对其划分。
#p#
导航和模式匹配
许多编程语言将 XML 表示为 DOM 树。这个方法有许多优点,但是不利于以编程的方式遍历树来从 XML 文档中提取数据。Java 技术提供了可以利用 XPath 语法的库。Scala 采取相似的方法,但它有许多优点。Scala 在这个方法中体现了很多函数语言特征。在 Scala 中没有使用操作符(像 + 或 *)。相反,使用 + 或 * 等符号定义可以执行普通数字加减法的函数。这也意味着您可以定义任何类型的操作符(因为它们实际上就是函数)。这些操作符号比 C++ 这类语言中的重载操作符具有更强大的功能。在 XPath 中,由于可以被转换成一个函数调用,您可以在 Scala 中直接应用 XPath 语法的某一部分。
了解了这些内容,我们来看一下 FriendFeed 中的 XML 是什么样子。清单3提供了一个例子。
清单 3. FriendFeed XML 示例
<feed> <entry> <updated>2008-03-26T05:06:36Z</updated> <service> <profileUrl>http://twitter.com/karlerikson</profileUrl> <id>twitter</id> <name>Twitter</name> </service> <title>Listening to Panic at the Disco on Kimmel</title> <link>http://twitter.com/karlerikson/statuses/777188586</link> <published>2008-03-26T05:06:36Z</published> <id>f18ebf10-06be-98e2-6059-fa78fa44584b</id> <user> <profileUrl>http://friendfeed.com/karlerikson</profileUrl> <nickname>karlerikson</nickname> <id>f294a86c-e6f3-11dc-8203-003048343a40</id> <name>Karl Erikson</name> </user> </entry> <entry> <updated>2008-03-26T05:06:35Z</updated> <service> <profileUrl>http://twitter.com/asfaq</profileUrl> <id>twitter</id> <name>Twitter</name> </service> <title>@ceetee lol</title> <link>http://twitter.com/asfaq/statuses/777188582</link> <published>2008-03-26T05:06:35Z</published> <id>d4099bb0-8186-5aa1-ce1f-672246c0fe9c</id> <user> <profileUrl>http://friendfeed.com/asfaq</profileUrl> <nickname>asfaq</nickname> <id>41e24568-ee6b-11dc-a88d-003048343a40</id> <name>Asfaq</name> </user> </entry> <entry> <updated>2008-03-26T05:06:31Z</updated> <service> <profileUrl>http://twitter.com/chrisjlee</profileUrl> <id>twitter</id> <name>Twitter</name> </service> <title>sleep..</title> <link>http://twitter.com/chrisjlee/statuses/777188561</link> <published>2008-03-26T05:06:31Z</published> <id>8c4ec232-3ad5-28e1-16c0-00a428294c9c</id> <user> <profileUrl>http://friendfeed.com/chrisjlee</profileUrl> <nickname>chrisjlee</nickname> <id>5af39ad4-53b6-45d8-ae25-ef7c50fe9568</id> <name>Chris</name> </user> </entry> <entry> <updated>2008-03-26T05:06:49Z</updated> <service> <profileUrl> http://www.google.com/reader/shared/09566745492004297397 </profileUrl> <id>googlereader</id> <name>Google Reader</name> </service> <title>Poketo First Editions Show!!</title> <link> http://www.poketo.com/blog/2008/03/24/poketo-first-editions-show/ </link> <published>2008-03-26T05:06:49Z</published> <id>4caefceb-d71c-59c9-8199-45c5adbc60f2</id> <user> <profileUrl>http://friendfeed.com/misterjt</profileUrl> <nickname>misterjt</nickname> <id>e745cc8a-f9e4-11dc-a477-003048343a40</id> <name>Jason Toney</name> </user> </entry> </feed>
对于您的应用程序,您将首先得到一个基于某种服务的用户列表。因此,将首先过滤提要,从而只获得感兴趣的服务。查看清单4了解Scala 如何实现上述功能。
清单 4. 过滤基于服务的提要
def filterFeed(feed:Elem, feedId:String):Seq[Node] = { |
您的函数 filterFeed 接受一个 XML 元素(提要)和一个服务 ID 作为参数。首先创建一个称为 results 的 XML 节点队列。队列被参数化,类似 Java 中的 List 和 Map。 Scala 使用方括号来表示泛型类型,而不是像 Java 编程使用的尖括号。feed"entry" 行是一个类 XPath 表达式。反斜杠符号实际上是 scala.xml.Elem 类的一个方法。它返回具有给定名称的所有子节点,即提要中所有 <entry> 元素。这将作为一个 scala.xml.NodeSeq 类的实例返回。这个类扩展了 Seq[Node]。因为它是一个 Seq,它具有一个 foreach方法,并将一个闭包作为参数。
(entry) => ... 标记表示一个将单个参数标记为条目的闭包。在这个闭包中,您将再次使用类 XPath 表达式 entry"service""id" 来从 entry 节点提取服务的 ID。把服务 ID 传递给搜索函数来将其与传递给方法的提要 ID 相比较。我们稍后将查看这个函数体。如果匹配的话,您可将创建条目的用户别名添加到结果队列中。注意这个队列目标中类似操作符的符号,+=。再次声明这仅仅是一个队列对象的函数。您可以使用 Scala 的类 XPath 语法来提取用户别名节点。
现在参看搜索函数,这个函数使用一个功能最强大的 Scala 特性:模式匹配。在这种情况下,将输入节点与一个名为 id 的节点相比较,id 节点的子文本节点由传递给函数的 Name 字符串构成。如果匹配则函数返回 true。语法 case _ 和所有内容匹配。其中__再次用作 Scala 的通配符。诸如 case _ 这样的声明和 Java 或 C++ 代码中 case 语句的默认子句类似。这个简单的例子证明了 Scala 中模式匹配的强大功能。下面您将会明白如何构建 XML 结构。
利用模式匹配构建XML
在应用程序中,您需要为从 FriendFeed 公共提要提取出的所有用户别名构建一个新的 XML 结构。实现上述操作有许多方法,但我们将演示如何再一次使用模式匹配方法。看一下清单5中所示的函数。
清单 5. 利用模式匹配构造函数
def add(p:Node, newEntry:Node ):Node = p match { |
这个模式将会和一个具有任意类型的子节点的 UserList 元素匹配。继而返回一个具有相同子节点的新 UserList 元素,另外在现有子节点之后又增加了一个子节点。这在功能上等效于 DOM 规范中的 appendChild 用法。但它有本质的不同,因为原始节点没有改变(它也不能改变,因为它是不可变的)。相反创建并返回了一个新节点。这样比等效的 DOM 操作使用更多的内存。我们来看一下使用 Scala 构建 XML 结构的其他方法。
#p#
创建XML
当创建新的 XML 文档时,Scala 的原生 XML 语法再合适不过。第一个例子是获取创建的 UserList 结构并把它封装在相关服务的节点中。清单6显示了这些代码。
清单 6. 创建服务结果
def results(name:String, cnt:Int, elements:NodeSeq):Any = { |
由于 Scale 提供了对 XML 的原生支持,您可以利用一个模板样式的语法将动态数据插入到 XML 结构中。在本例中,使用传入的名称字符串设置 id 属性。您将获得一串传入的元素,将它们作为正在创建的 Service 元素的子节点。但是要注意,只有在 cnt 参数大于 0 的情况下才执行上述操作。如果 cnt 值等于 0,这个函数将不返回任何值。在 Scale 中您可以通过声明函数返回 Any 来解决这个问题。Any 类在 Scala 中是一个原始的类,类似于 java.lang.Object。Scale 没有 void 类型,但是有一个等价的 Unit 类型。它的优点是可以扩展 Any 类,并且允许函数在某些情况下返回对象,而在其他时候不返回任何内容。
如您所见,在 Scala 的 XML 语法中结合动态数据可以产生强大的功能。再举一个例子,您可以创建一个统计 XML 文档,其中显示的 XML 描述每个服务在提要中出现的次数。代码如清单7所示。
清单 7. 创建统计 XML
def stats(map:HashMap[String,Int]):Node = { |
您的函数要求 HashMap 的键是服务的名称,其值为服务在 FriendFeed 中出现的次数。这个函数使用熟悉的 foreach-closure 风格遍历 HashMap,然后使用 HashMap 的名称/值对创建一个新节点,将这个节点添加到节点队列中。随后创建 Stats 结构并作为动态数据访问节点队列,节点队列随后被赋值给一个 XML 结构。现在准备好了所有函数,您只需驱动程序以便进行测试。
运行和测试
在运行程序之前,需要加入一些代码来驱动它。将创建一个 main 方法,就像使用 Java 编程一样,如清单8所示。
清单 8. FriendFeed main 方法
def main(args:Array[String]) = { |
这个方法创建了 FriendFeed。它接受命令行参数确定哪些服务查找用户并计算统计数据。注意这些语法与 Java 语法非常相似。main 函数接受一个 String 数组(称为 args)作为参数。这个程序为统计文档创建 HashMap,并且为每个服务创建 UserList 文档。然后输出每个 UserList 和统计文档。要运行这个程序,需要使用 scalac FriendFeed.scala 和 scala FriendFeed 进行编译,如清单9所示。
清单 9. 运行程序
$ scalac FriendFeed.scala |
您当然可以选择不同的服务名称作为命令行参数或其他参数。Scala 具备完美的 printer 类,可以使用正确的空格、制表符和格式打印 XML。还提供了 XML 写入程序(writer)将 XML 写回数据流,比如文件。您可以使用 Scala 完成所有普通的任务,同时还可以使用 Scala 提供的一些独有的功能。
结束语
许多人把 Scala 视为 Java 编程语言发展历程中的重要一步。XML 已经成为一种重要的技术,编程语言只有在其语法中内置了 XML 支持,才能自然地应用 XML 技术。而 Scale 做到了这一点。它使得复杂问题变得简单。查看本文使用 Scale 执行的所有功能,想像一下做同样的事情需要使用多少行 Java 代码。