XMLファイルをコピーする(6)


最後の例は本命(?)のScalaです.XMLのロードとセーブが簡単になる分ぐっとコードが短くなります.

import scala.xml.XML
import scala.xml.Node
import scala.xml.NodeSeq
import scala.xml.Elem
import scala.xml.Text

object Main {
  
  def main(args: Array[String]): Unit = {
      val root:Elem = XML.loadFile("MusicLibrary.xml")
      val rootCopy:Node=processNodeSeq(root).asInstanceOf[Node]
      XML.save("MusicLibrary_Copy.xml",rootCopy,"UTF-8",true,null)
  }
  
  def processNodeSeq(nodeSeq:NodeSeq): NodeSeq ={
      val resultNode:NodeSeq = if (nodeSeq.size > 1) 
                                   processNode(nodeSeq.apply(0)) ++: processNodeSeq(nodeSeq.drop(1))
                               else
                                   processNode(nodeSeq.apply(0));
      resultNode;
  }
  
  def processNode(node:Node): NodeSeq={
      node match{
     case e: Elem  => new Elem(e.prefix, e.label, e.attributes, e.scope, true, (processNodeSeq(e.child)):_*);
     case t: Text  => new Text(t.text);
     case n        => null;
      }
  }
}

入力XMLファイルは、XML.LoadFileで一発で読み込めます.保存はXML.Saveです.

F#のLINQ For XMLの場合と違うのは、processNode関数のElem(要素)の処理部分です.LINQ For XMLでは、要素のXElementをコンストラクトしてから、それに属性を加えたり、子のノードを加えたりできました.しかしScalaではそういうことはできません.何故かというとScalaのElemは immutable objectだからです.つまりいったんコンストラクトしたら変えられません.なので、

new Elem(e.prefix, e.label, e.attributes, e.scope, true, (processNodeSeq(e.child)):_*);

とコンストラクタを呼んで一発で下位も含めて作ってやらねばならないのです.ScalaのElemのコンストラクタは、ちょっと複雑で、


new Elem(prefix: String, label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, child: Node*)

という形をしています.最初コーディングするときにchild: NodeSeqかと思ったんですが違いました.child: Node*はvararg(可変個数のパラメータ)を表します.processNodeSeqはNodeSeqを返しますが、これに対して":_*"を記述することにより、コンパイラーにNodeSeqをvarargに展開して解釈するように指定してくれるようです.

Scalaでこんなに短く書けるとは思いませんでした.初心者でもやってみるとできますね.

※ このプログラムでXML宣言と改行コードが異なるだけのXMLファイルが出来上がります.元のXML宣言は

<?xml version="1.0" encoding="utf-8"?>

でしたが、Scalaが作成してくれたのは

<?xml version='1.0' encoding='UTF-8'?>

でした.改行コードは元のXMLファイルがCR/LFでした.Scalaが作ったXMLはLFのみのものになります.