14.5 读写XML
只有将XML读入内存,才能够进行解析。下一步,我们来了解如何将XML文档加载到程序里,如何把内存里的文档存入文件。作为例子,我们会加载一个包含股票代码和股份的文件,对股份加1,然后将更新的内容存回到另一个XML文件里。我们先来处理加载文件的这一步。
这是要加载的样例文件stocks.xml
:
UsingScala/stocks.xml
<symbols>
<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="ADBE"><units>125</units></symbol>
<symbol ticker="ALU"><units>150</units></symbol>
<symbol ticker="AMD"><units>150</units></symbol>
<symbol ticker="CSCO"><units>250</units></symbol>
<symbol ticker="HPQ"><units>225</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
<symbol ticker="INTC"><units>160</units></symbol>
<symbol ticker="MSFT"><units>190</units></symbol>
<symbol ticker="NSM"><units>200</units></symbol>
<symbol ticker="ORCL"><units>200</units></symbol>
<symbol ticker="SYMC"><units>230</units></symbol>
<symbol ticker="TXN"><units>190</units></symbol>
<symbol ticker="VRSN"><units>200</units></symbol>
<symbol ticker="XRX"><units>240</units></symbol>
</symbols>
scala.xml
包的XML单例对象的load()
方法可以辅助加载文件,如下所示:
UsingScala/ReadWriteXML.scala
import scala.xml._
val stocksAndUnits = XML.load("stocks.xml")
println(stocksAndUnits.getClass())
println("Loaded file has " + (stocksAndUnits \\ "symbol").size +
" symbol elements")
从下面的输出可以看出,load()
返回的是一个scala.xml.Elem
实例。此外还可以看到,加载的文件(stocks.xml
)包含15个symbol
元素。
class scala.xml.Elem
Loaded file has 15 symbol elements
我们已经知道如何解析这个文档的内容,以及如何将股票代码及其对应的股份存到Map
里。下面这段代码做的就是这件事:
UsingScala/ReadWriteXML.scala
val stocksAndUnitsMap =
(Map[String, Int]() /: (stocksAndUnits \ "symbol")) { (map, symbolNode) =>
val ticker = (symbolNode \ "@ticker").toString
val units = (symbolNode \ "units").text.toInt
map(ticker) = units //Creates and returns a new Map
}
println("Number of symbol elements found is " + stocksAndUnitsMap.size)
上面的代码里,处理每个symbol
元素时,还把股票代码及其对应的股份增加到一个新的Map
里。从下面的输出可以看出,从文档里加载股票代码的数量:
Number of symbol elements found is 15
最后一步,增加股份值,创建数据的XML表示,存入文件。
我们知道,Scala无需将XML元素塞入字符串。但是,也许你会好奇,如何生成动态内容到字符串里?这就是Scala XML程序库聪明的地方,也让它超越了我们迄今所见的所有XML程序库。Scala表达式可以嵌入XML片段里。这样,如果写
,Scala会用tickerSymbol
变量的值替换{tickerSymbol}
,其结果是一个类似
的元素。在{}
间可以放任何Scala代码②,这个块的结果是一个值、一个元素或是一个元素序列。运用这个特性就可以根据上面创建的Map
创建一个XML表示。完成之后,再用XML对象的save()
方法把内容存到文件里。让我们看看做这件事的代码:
②如果想在内容里放一个
{
,还要用一个额外的{
进行转义。也就是说,在内容里,{{
会产生一个{
。
UsingScala/ReadWriteXML.scala
val updatedStocksAndUnitsXML =
<symbols>
{ stocksAndUnitsMap.map { updateUnitsAndCreateXML } }
</symbols>
def updateUnitsAndCreateXML(element : (String, Int)) = {
val (ticker, units) = element
<symbol ticker={ticker}>
<units>{units + 1}</units>
</symbol>
}
XML save ("stocks2.xml", updatedStocksAndUnitsXML)
println("The saved file contains " +
(XML.load("stocks2.xml") \\ "symbol").size + " symbol elements")
上面代码的输出如下:
The saved file contains 15 symbol elements
看一下产生上面输出的代码。先创建了一个以symbols
为根元素的XML文档。对于要嵌入根元素的子元素,其数据在之前创建的Map
里,也就是stocksAndUnitsMap
。接下来,对这个map
的每个元素进行迭代,使用尚未实现的updateUnitsAndCreateXML()
方法创建XML表示。这个操作的结果是元素的容器(因为用的是map()
方法)。记住,在map()
方法所附的闭包里,Scala隐式将闭包接收的参数(Map
的元素)传给updateUnitsAnd- CreateXML()
方法。
现在,看一下updateUnitsAndCreateXML()
方法,它接收Map
的元素为参数,创建XML片段,其格式为
。处理每支股票的代码时,我们都会满足到对股份加1
的需要。
最后一步是保存生成文档,用save()
完成这个任务。然后,从文件stocks2.xml
中读回保存的文档,查看生成的内容。
save()
方法只是简单地保存了XML文档,没有任何花哨的东西。如果想要添加XML版本,添加doctype,指定编码,可以使用XML单例对象中save()
方法的变体。