14.5 读写XML

只有将XML读入内存,才能够进行解析。下一步,我们来了解如何将XML文档加载到程序里,如何把内存里的文档存入文件。作为例子,我们会加载一个包含股票代码和股份的文件,对股份加1,然后将更新的内容存回到另一个XML文件里。我们先来处理加载文件的这一步。

这是要加载的样例文件stocks.xml

UsingScala/stocks.xml

  1. <symbols>
  2. <symbol ticker="AAPL"><units>200</units></symbol>
  3. <symbol ticker="ADBE"><units>125</units></symbol>
  4. <symbol ticker="ALU"><units>150</units></symbol>
  5. <symbol ticker="AMD"><units>150</units></symbol>
  6. <symbol ticker="CSCO"><units>250</units></symbol>
  7. <symbol ticker="HPQ"><units>225</units></symbol>
  8. <symbol ticker="IBM"><units>215</units></symbol>
  9. <symbol ticker="INTC"><units>160</units></symbol>
  10. <symbol ticker="MSFT"><units>190</units></symbol>
  11. <symbol ticker="NSM"><units>200</units></symbol>
  12. <symbol ticker="ORCL"><units>200</units></symbol>
  13. <symbol ticker="SYMC"><units>230</units></symbol>
  14. <symbol ticker="TXN"><units>190</units></symbol>
  15. <symbol ticker="VRSN"><units>200</units></symbol>
  16. <symbol ticker="XRX"><units>240</units></symbol>
  17. </symbols>

scala.xml包的XML单例对象的load()方法可以辅助加载文件,如下所示:

UsingScala/ReadWriteXML.scala

  1. import scala.xml._
  2. val stocksAndUnits = XML.load("stocks.xml")
  3. println(stocksAndUnits.getClass())
  4. println("Loaded file has " + (stocksAndUnits \\ "symbol").size +
  5. " symbol elements")

从下面的输出可以看出,load()返回的是一个scala.xml.Elem实例。此外还可以看到,加载的文件(stocks.xml)包含15个symbol元素。

  1. class scala.xml.Elem
  2. Loaded file has 15 symbol elements

我们已经知道如何解析这个文档的内容,以及如何将股票代码及其对应的股份存到Map里。下面这段代码做的就是这件事:

UsingScala/ReadWriteXML.scala

  1. val stocksAndUnitsMap =
  2. (Map[String, Int]() /: (stocksAndUnits \ "symbol")) { (map, symbolNode) =>
  3. val ticker = (symbolNode \ "@ticker").toString
  4. val units = (symbolNode \ "units").text.toInt
  5. map(ticker) = units //Creates and returns a new Map
  6. }
  7. println("Number of symbol elements found is " + stocksAndUnitsMap.size)

上面的代码里,处理每个symbol元素时,还把股票代码及其对应的股份增加到一个新的Map里。从下面的输出可以看出,从文档里加载股票代码的数量:

  1. 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

  1. val updatedStocksAndUnitsXML =
  2. <symbols>
  3. { stocksAndUnitsMap.map { updateUnitsAndCreateXML } }
  4. </symbols>
  5. def updateUnitsAndCreateXML(element : (String, Int)) = {
  6. val (ticker, units) = element
  7. <symbol ticker={ticker}>
  8. <units>{units + 1}</units>
  9. </symbol>
  10. }
  11. XML save ("stocks2.xml", updatedStocksAndUnitsXML)
  12. println("The saved file contains " +
  13. (XML.load("stocks2.xml") \\ "symbol").size + " symbol elements")

上面代码的输出如下:

  1. The saved file contains 15 symbol elements

看一下产生上面输出的代码。先创建了一个以symbols为根元素的XML文档。对于要嵌入根元素的子元素,其数据在之前创建的Map里,也就是stocksAndUnitsMap。接下来,对这个map的每个元素进行迭代,使用尚未实现的updateUnitsAndCreateXML()方法创建XML表示。这个操作的结果是元素的容器(因为用的是map()方法)。记住,在map()方法所附的闭包里,Scala隐式将闭包接收的参数(Map的元素)传给updateUnitsAnd- CreateXML()方法。

现在,看一下updateUnitsAndCreateXML()方法,它接收Map的元素为参数,创建XML片段,其格式为value。处理每支股票的代码时,我们都会满足到对股份加1的需要。

最后一步是保存生成文档,用save()完成这个任务。然后,从文件stocks2.xml中读回保存的文档,查看生成的内容。

save()方法只是简单地保存了XML文档,没有任何花哨的东西。如果想要添加XML版本,添加doctype,指定编码,可以使用XML单例对象中save()方法的变体。