Scala中处理XML文件的功能是比较强大的,因此不管怎样至少也得接触一下。XML也是Scala中的“第一类公民”,直接将一个XML格式的字符串赋值给一个val值变量就可以开始处理了。最基本的有scala.xml.Elem类中的 \ 方法和 \\ 方法,它们可用于直接对XML文件进行解析和提取。 

假如有如下的XML文件:

  1. <symbols> 
  2.     <symbol ticker="AAPL"> 
  3.         <units>200</units> 
  4.     </symbol> 
  5.      
  6.     <units>300</units> 
  7.      
  8.     <symbol ticker="IBM"> 
  9.         <units>400</units> 
  10.     </symbol> 
  11. </symbols> 

 

我们可以写一个Scala脚本来提取出其中的<units>元素,顺便比较一下 \ \\ 方面的区别。脚本代码如下:

  1. val xmlFile = 
  2.     <symbols> 
  3.         <symbol ticker="AAPL"
  4.             <units>200</units> 
  5.         </symbol> 
  6.          
  7.         <units>300</units> 
  8.          
  9.         <symbol ticker="IBM"
  10.             <units>400</units> 
  11.         </symbol> 
  12.     </symbols> 
  13.  
  14. println(xmlFile)    // 直接打印xmlFragment的内容 
  15. println(xmlFile.getClass()) // 打印xmlFragment的类型 
  16.  
  17. val unitsNodes = xmlFile \ "units"  // 提取出<units>元素 
  18. println(unitsNodes mkString "\n")   // 打印提取出的结果
  19. println(unitsNodes getClass)    // 打印symbolNodes的类型 

运行结果如下:
 
  1. <symbols> 
  2.         <symbol ticker="AAPL">  
  3.                 <units>200</units> 
  4.         </symbol>  
  5.   
  6.         <units>300</units>  
  7.   
  8.         <symbol ticker="IBM">  
  9.                 <units>400</units> 
  10.         </symbol>  
  11. </symbols> 
  12. class scala.xml.Elem 
  13. <units>300</units> 
  14. class scala.xml.NodeSeq$$anon$1 
 

我们调用单反斜杠 \ 方法很简单地就提取出了xmlFile中的<units>元素,并且打印出来的结果也跟源文件格式一致。但是,我们注意到提取出来的<units>元素仅仅是当前节点<symbols>下的<units>元素,如果我们想要同时提取出xmlFile中嵌套在<symbol>中的<units>元素,此时应该使用双反斜杠 \\ 方法来代替上面脚本中的单反斜杠,修改如下:

  1. val unitsNodes = xmlFragment \\ "units" 

 执行结果如下:

 

  1. <units>200</units> 
  2. <units>300</units> 
  3. <units>400</units> 
  4. class scala.xml.NodeSeq$$anon$1 
 

如果想要获取<units>300</units>中的数值300,则应该使用到Scala中的模式匹配,利用case语句来实现这一功能。假如我们针对上面使用了双反斜杠提取出来的unitsNodes,因为结果是一个数组,所以应该用unitsNodes(1)来获得对于<units>300</units>的引用,具体代码如下:


  1. unitsNodes(1) match { 
  2.       case <units>{numOfUnits}</units> =>  
  3.              println("num of units : " + numOfUnits)  
 
注意,上面的<units>{numOfUnits}</units>中的花括号与><之间都不能存在空格。花括号里面是对<units>…</units>之中值的引用。执行结果如下:
 
  1. num of units : 300  
 
当然,对于这样子简单的<units>…</units>似乎没必要用一个模式匹配来实现,那么我们可以如下操作,调用NodeSeq类中的text()方法,如下:
 
  1. println(unitNodes(1).text)  
 
 执行结果与上面的一致,num of units : 300,只不过是对简单应用的简化方法而已。然而,我们把事情看得比较简单了,要是<units>…</units>内还嵌套有其他<units>…</units>元素对那怎么办?虽然使用上面的 text 方法同样能够提取出被嵌套的<units>…</units>中的300(你可以试一下哦),但是你会发现打印出来是其格式有点儿不对劲,我想原因是在于层层的嵌套使得Scala对其解析之后的结果也以空格来表示了。
 

既然不能简单处理,那只能用回Scala中强大的模式匹配了。另外,如果我们想要同时提取出类似于<symbol ticker="AAPL">中的ticker属性的值,也同样使用到了模式匹配。具体脚本如下:

  1. xmlFile match { 
  2.     case <symbols>{allSymbol @ _*}</symbols> => 
  3.         for(symbolNode @ <symbol>{_*}</symbol> <- allSymbol){ 
  4.             println("%-7s %s".format( 
  5.                 symbolNode \ "@ticker", (symbolNode \ "units").text))    
  6.         } 

 

在这里,<symbols>{allSymbol @ _*}</symbols> 中的@符号用于声明定义一个临时变量用来引用<symbols>…</symbols>中的所有内容(即下划线加星号 _*,在这个例子中就是所有<symbol>…</symbol>元素对了),包括其他节点元素。

 

Scala的模式匹配case语句中,使用特殊符号@可以定义临时变量,例如for表达式,如下。真是强大啊!那么接下来的这一句:

  1. for(symbolNode @ <symbol>{_*}</symbol> <- allSymbol) 

 中的symbolNode 也容易理解了,而 <- allSymbol 不就是 for 表达式中的迭代赋值吗?最后,println()中使用了正则表达式以及格式化输出format方法。

 

那么,for表达式中的symbolNode allSymbols 是同样类型的(allSymbols迭代地赋值给 symbolNode ),所以每一个 symbolNode 其实也就是每一个<symbol>…</symbol>元素对了,看看文章最前面给出的XML文件,<symbol>内是包含了 ticker 属性的,如下:

  1. <symbol ticker="AAPL">  
  2.         <units>200</units>  
  3. </symbol>  

  

那么我们使用symbolNode \ "@ticker" 这种格式将ticker属性的值提取出来。而接下来的 (symbolNode \ "units").text 我们都知道是对当前的<symbol>元素对往下找出<units>元素对,再用text方法取出其值。 

注:小心看清楚脚本代码中的括号对等等哦!^_^ 

执行结果如下:

AAPL    200
IBM     400