JavaでXMLデータ扱うの何だか難しそうだと思ってたけど実際やってみたら超簡単だった

XMLとは

こんなやつです。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<parent>
    <child>
        <element>data1</element>
        <element>data2</element>
    </child>
</parent>

よく見るとファイル構造と何となく似ています。
parentはフォルダ、childもフォルダ、elementはファイルでdata1,data2はファイルの中身です。
ファイル構造と違う点といえばelementが同じフォルダに重複していることですね。

Javaでの扱い方(DOM)

DOM(Document Object Model)とかSAXとかいくつか扱い方があるようですが、今回はDOMを使ったのでDOMで説明します。

デモコード

ひとまず全部載せます。

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XML {

	private File file = new File("xml.txt");

	public static void main(String[] args) {
		new XML();
	}

	public XML() {
		Document document = create();
		writeFile(document);
		Document document2 = readFile();
		print(document2);
	}

	private Document create() {
		DocumentBuilder builder = null;
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return null;
		}
		Document document = builder.newDocument();

		Element parent = document.createElement("parent");
		document.appendChild(parent);

		Element child1 = document.createElement("child");
		parent.appendChild(child1);

		Element element1 = document.createElement("element");
		child1.appendChild(element1);
		element1.appendChild(document.createTextNode("data1"));

		Element element2 = document.createElement("element");
		child1.appendChild(element2);
		element2.appendChild(document.createTextNode("data2"));

		return document;
	}

	private void writeFile(Document document) {
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = null;
		try {
			transformer = factory.newTransformer();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			return;
		}

		transformer.setOutputProperty("indent","yes");
		transformer.setOutputProperty("encoding","UTF-8");

		try {
			transformer.transform(new DOMSource(document),new StreamResult(file));
		} catch (TransformerException e) {
			e.printStackTrace();
			return;
		}
	}

	private Document readFile() {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			if(!file.exists()) return null;
			return builder.parse(file);
		} catch (ParserConfigurationException | SAXException | IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	private void print(Document document) {
		Element parent = document.getDocumentElement();

		NodeList child_list = parent.getElementsByTagName("child");
		Element child1 = (Element)child_list.item(0);

		NodeList element_list = child1.getElementsByTagName("element");
		for(int i=0;i<element_list.getLength();i++) {
			System.out.println(
					String.format(
							"%s : %s",
							element_list.item(i).getNodeName(),
							element_list.item(i).getTextContent()));
		}
	}
}

出力結果は冒頭で例に出したものがそうです。(若干見やすいように修正しています)
以降このコードを部分部分で見ていきます。
コードと出力結果を交互に見比べながら読み進めると理解しやすいかと思います。

import

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

Documentはjavax.swing.text.Document等いくつかありますが、org.w3c.dom.Documentを使用します。

クラス

public class XML {

	private File file = new File("xml.txt");

	public static void main(String[] args) {
		new XML();
	}

	public XML() {
		Document document = create();
		writeFile(document);
		Document document2 = readFile();
		print(document2);
	}
}

fileフィールドは作成したxmlデータをテキストファイルで出力する出力先ファイル名です。

mainメソッドではXMLクラスのコンストラクタを呼び出すだけです。

XMLのコンストラクタでは4つの段階に分けて処理が並んでいます。

  1. create()メソッドはXMLデータを作成します。作成したデータはDocument型で返され、document変数に格納されます。
  2. writeFile(Document)メソッドは渡されたXMLデータをファイルに保存します。保存先ファイル名はfileフィールドを使用します。
  3. readFile()メソッドはテキストファイルとして保存してあるXMLデータを読み取り、Document型で返します。
  4. print(Document)メソッドは渡されたDocumentを読み取り、内容を出力します。

create() //XMLデータの作成

	private Document create() {
		DocumentBuilder builder = null;
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return null;
		}
		Document document = builder.newDocument();

		Element parent = document.createElement("parent");
		document.appendChild(parent);

		Element child1 = document.createElement("child");
		parent.appendChild(child1);

		Element element1 = document.createElement("element");
		child1.appendChild(element1);
		element1.appendChild(document.createTextNode("data1"));

		Element element2 = document.createElement("element");
		child1.appendChild(element2);
		element2.appendChild(document.createTextNode("data2"));

		return document;
	}

DocumentBuilderクラスはDocumentのオブジェクトを作成するのに必要です。
DocumentBuilderのインスタンス化に失敗した場合はその時点で例外によって処理が終了してしまいます。

builder.newDocument()で空のドキュメントを作成します。
このdocumentをファイル出力してみると、何もないXMLが出力されます。

ここから実際に中身(ファイルやフォルダのようなもの)を作っていきます。

Element parent = document.createElement("parent");

"parent"という名前のエレメントを作ります。この時点では中身のないエレメント、</parent>が作成されます。

document.appendChild(parent);

appendChildでエレメントをdocumentに組み込むことでやっとXMLにエレメントが一つ追加されます。

Element child1 = document.createElement("child");

次は子エレメントを作ります。
名前が違うだけでparentの時と同じことをしています。

parent.appendChild(child1);

どのオブジェクトからappendChildを呼び出しているかに注意してください。
parentを追加した先はdocumentですが、childを追加しているのはparentです。
documentに追加することで、そのエレメントはrootとなります。
parentのようなエレメントに追加することで、そのエレメントは子エレメントになります。

Element element1 = document.createElement("element");
child1.appendChild(element1);
element1.appendChild(document.createTextNode("data1"));

これまたchild1と同じことをしていますが、3行目は初めて出てくる文です。
document.createTextNode()はエレメントの中身となるデータを作成します。
出力例でもelementの中にdata1というデータが一つだけ入っていることが見られます。

Element element2 = document.createElement("element");
child1.appendChild(element2);
element2.appendChild(document.createTextNode("data2"));

同じことをしています。

以上で冒頭の例のXMLデータができました。
rootは一つしか追加できませんが、子エレメント、孫エレメントの数や深さはいくつでも増やすことができます。
作りたいXMLデータに応じて名前や数を変更してください。

writeFile //ファイル保存

	private void writeFile(Document document) {
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = null;
		try {
			transformer = factory.newTransformer();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			return;
		}

		transformer.setOutputProperty("indent","yes");
		transformer.setOutputProperty("encoding","UTF-8");

		try {
			transformer.transform(new DOMSource(document),new StreamResult(file));
		} catch (TransformerException e) {
			e.printStackTrace();
			return;
		}
	}

このメソッドでは作成したDocumentをファイル保存します。

		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = null;
		try {
			transformer = factory.newTransformer();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
			return;
		}

まずはtransformerを作ります。
ここでも例外が発生したらその時点で処理が終了します。

		transformer.setOutputProperty("indent","yes");
		transformer.setOutputProperty("encoding","UTF-8");

"indent"を"yes"にすると出力されるテキストファイルはエレメントごとに改行されます。
"no"にすると全て1行で書き込まれます。
"encoding"は出力するテキストの文字コードを指定します。

		try {
			transformer.transform(new DOMSource(document),new StreamResult(file));
		} catch (TransformerException e) {
			e.printStackTrace();
			return;
		}

最後にファイルに出力します。
出力先ファイルは2行目で指定されてますね。

このメソッドを見てみると、Documentの内容に全く依存していないのがわかるかと思います。
つまり、保存先ファイル以外は丸コピーでも使えますね。

readFile //ファイル読み込み

	private Document readFile() {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			if(!file.exists()) return null;
			return builder.parse(file);
		} catch (ParserConfigurationException | SAXException | IOException e) {
			e.printStackTrace();
			return null;
		}
	}

このメソッドではファイルを読み込んで保存されているXMLを復元します。

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();

この部分はcreateメソッドでも同じことをしていました。
Documentを作成する準備部分ですね。

			if(!file.exists()) return null;

ファイルが存在するかを確認しています。
ファイルがないのに読み込もうとすると例外が発生します。

			return builder.parse(file);

ファイルを読み込んでいます。

以上でXMLデータのファイルを読み込むことができました。
出力同様にデータの中身に依存しないので、丸コピーで読み込むことができます。

	private void print(Document document) {
		Element parent = document.getDocumentElement();

		NodeList child_list = parent.getElementsByTagName("child");
		Element child1 = (Element)child_list.item(0);

		NodeList element_list = child1.getElementsByTagName("element");
		for(int i=0;i<element_list.getLength();i++) {
			System.out.println(
					String.format(
							"%s : %s",
							element_list.item(i).getNodeName(),
							element_list.item(i).getTextContent()));
		}
	}

XMLデータの解析をします。

		Element parent = document.getDocumentElement();

documentからrootエレメントを取り出しています。
取り出されるのはcreateメソッドで作ったparentエレメントです。

		NodeList child_list = parent.getElementsByTagName("child");
		Element child1 = (Element)child_list.item(0);

続いてparentからgetElementByTagNameメソッドで"child"という名前のついたエレメントを取り出しています。
parentを取り出した時と型が違いますね。
NodeListはparentに含まれていて、名前が"child"のエレメントをまとめたリストのクラスです。
getDocumentElementで取り出されるのはrootエレメントです。
rootエレメントはXMLデータ内で一つしか存在しません。これはXMLデータのルールです。
一つしか存在しないのでリストにする必要はありませんね。
データ例をみるとelementエレメントが重複しています。
このようにrootでないエレメントは重複する可能性があるので、リストとして出力されているのです。

リストの中からエレメント一つを取り出すには、itemメソッドを使用します。
例ではコードの簡単化のために0番のエレメントが存在する前提として書いています。

		NodeList element_list = child1.getElementsByTagName("element");
		for(int i=0;i<element_list.getLength();i++) {
			System.out.println(
					String.format(
							"%s : %s",
							element_list.item(i).getNodeName(),
							element_list.item(i).getTextContent()));

同じように今度はchild1からエレメントのリストを読み出します。
forの条件でelement_list.getLenghtがありますが、リスト内にいくつエレメントが存在するかを数えます。

エレメント名を取り出すのはgetNodeNameメソッド、
データを取り出すのはgetTextContentメソッドです。

実行

実行してみましょう。
コンソール出力は次のようになります。

element : data1
element : data2

またテキストファイルが出力されているはずなので、そちらも確認してみましょう。
内容は次のようになっています。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<parent>
<child>
<element>data1</element>
<element>data2</element>
</child>
</parent>

インデントは有効にしているはずですが、インデントタブは付かないようです。