かとじゅんの技術日誌

技術の話をするところ

XMLとオブジェクトの変換をする方法

XMLファイルをプログラムからアクセスする方法をおさらいすると

といろいろある。
DOMとSAXとStAXと。 - 都元ダイスケ IT-PRESS
このあたりを見てもらうとわかりやすい。

XMLにアクセスする場合は、だいたいはモデルに変換することが多い。上記にような低レベルAPIでないと細かい制御はできないが、もっと単純にXMLの要素をオブジェクトに変換する場合は以下のような高レベルAPIのような選択肢もある。

digesterはライトウェイトで良さそうであるが読み込み専用ということなので、JAXBを少し触ってみた。JAXBはJava6からは標準APIとして搭載されている。

蛇足だが、configurationは設定ファイルを扱うライブラリもある。XMLも読み込める。ただし、独自に作成したモデルオブジェクトへの変換はできない。XMLファイル以外にもプロパティファイルなどもにアクセスできて、使い方次第では役に立つ。

Apache commonsが便利な件(commons-configuration編) - 都元ダイスケ IT-PRESS

まずは、いきなりXMLのサンプルを作り始める

よく見かけるユーザ情報の一覧のようなXMLデータ。

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="j5ik2o">
        <userName>j5ik2o</userName>
        <firstName>Junichi</firstName>
        <lastName>Kato</lastName>
        <urls>
            <blog>http://d.hatena.ne.jp/j5ik2o/</blog>
        </urls>
    </user>
    <user id="daisuke-m">
        <userName>daisuke-m</userName>
        <firstName>Daisuke</firstName>
        <lastName>Miyamoto</lastName>
        <urls>
            <blog>http://d.hatena.ne.jp/daisuke-m/</blog>
        </urls>
    </user>
</users>

XMLスキーマを生成

XMLスキーマDTDが必要なのですが、いろいろ面倒なんでtrang*1というツールでXMLスキーマを自動生成した。

junichi-MacBook:jaxb-sample junichi$ java -jar tools/trang.jar -I xml -O xsd src/users.xml src/users.xsd

これが生成されたXMLスキーマ

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="users">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="user"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="user">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="userName"/>
        <xs:element ref="firstName"/>
        <xs:element ref="lastName"/>
        <xs:element ref="urls"/>
      </xs:sequence>
      <xs:attribute name="id" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="userName" type="xs:string"/>
  <xs:element name="firstName" type="xs:NCName"/>
  <xs:element name="lastName" type="xs:NCName"/>
  <xs:element name="urls">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="blog"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="blog" type="xs:anyURI"/>
</xs:schema>

最初にスキーマを起こすのは手間なのでtrangを使いましたが、メンテナンスをするならキチンとスキーマを理解しておかないといけないでしょう。とりあえず、以下の型ぐらいは理解しておく。
デベロッパーズコーナー:エンジニアのためのXMLスキーマ講座

スキーマからモデルクラスを作成

maven-jaxb2-pluginを使ってxsdからJavaBeanを作ってみた。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>jaxb-example</groupId>
	<artifactId>jaxb-example</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>jaxb-example</name>
	<url>http://maven.apache.org</url>
	<build>
		<plugins>
			<plugin>
				<inherited>true</inherited>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.jvnet.jaxb2.maven2</groupId>
				<artifactId>maven-jaxb2-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<schemaDirectory>src/main/resources/schema</schemaDirectory>
					<generateDirectory>src/main/java/</generateDirectory>
					<generatePackage>example</generatePackage>
					<removeOldOutput>false</removeOldOutput>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Maven 2 Repository</name>
			<url>http://download.java.net/maven/2</url>
		</repository>
		<repository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Maven 1 Repository (legacy)</name>
			<url>http://download.java.net/maven/1</url>
			<layout>legacy</layout>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>maven2-repository.dev.java.net</id>
			<name>Java.net Maven 2 Repository</name>
			<url>http://download.java.net/maven/2</url>
		</pluginRepository>
		<pluginRepository>
			<id>maven-repository.dev.java.net</id>
			<name>Java.net Maven 1 Repository (legacy)</name>
			<url>http://download.java.net/maven/1</url>
			<layout>legacy</layout>
		</pluginRepository>
	</pluginRepositories>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.4</version>
			<scope>test</scope>
			<optional>false</optional>
		</dependency>
	</dependencies>
</project>

上記の例では、src/main/resources/schemaにxsdを配置するようにしている。mvnでビルドするとsrc/main/javaに対応するJavaBeanが生成される。

これが生成されたモデルのソース。
Listなのに複数形になっていないorz

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "user"
})
@XmlRootElement(name = "users")
public class Users {

    @XmlElement(required = true)
    protected List<User> user;

    public List<User> getUser() {
        if (user == null) {
            user = new ArrayList<User>();
        }
        return this.user;
    }

}

仕方ないので、プロパティ名をusersにしてみた。さらにXmlElementでuserというタグと関連づくように修正。

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "users"
})
@XmlRootElement(name = "users")
public class Users {

    @XmlElement(required = true,name="user")
    protected List<User> users;

さらに参照を外部にさらすのはキモイので、複製を作るか、Iterableを返すか、とりあえずIterableに変更。

    public Iterable<User> getUsers() {
        if (users == null) {
            users = new ArrayList<User>();
        }
        return this.users;
    }

その他のクラスはこちら。

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "userName",
    "firstName",
    "lastName",
    "urls"
})
@XmlRootElement(name = "user")
public class User {

    @XmlElement(required = true)
    protected String userName;
    @XmlElement(required = true)
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlSchemaType(name = "NCName")
    protected String firstName;
    @XmlElement(required = true)
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlSchemaType(name = "NCName")
    protected String lastName;
    @XmlElement(required = true)
    protected Urls urls;
    @XmlAttribute(required = true)
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlSchemaType(name = "NCName")
    protected String id;

// getter,setter省略

}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "blogs"
})
@XmlRootElement(name = "urls")
public class Urls {

    @XmlElement(required = true, name="blog")
    @XmlSchemaType(name = "anyURI")
    protected List<String> blogs;

    public Iterable<String> getBlogs() {
        if (blogs == null) {
            blogs = new ArrayList<String>();
        }
        return this.blogs;
    }

}

XMLの読み書き

		InputStream is = null;
		OutputStream os = null;
		try {
			// XMLからオブジェクトに読み込み〜
			is = this.getClass().getResourceAsStream("userList.xml");
			JAXBContext jc = JAXBContext.newInstance(Users.class);
			Unmarshaller u = jc.createUnmarshaller();

			Users users = (Users) u.unmarshal(is);
			for(User user : users.getUsers()){
				System.out.println(user.id);
			}
			
			// XMLにオブジェクトを書き込み〜
			os = new FileOutputStream("./test.xml");
			Marshaller mu = jc.createMarshaller();
			mu.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT ,new Boolean(true));
			mu.marshal(users, os);
		
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (JAXBException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if ( is != null){
				is.close();
			}
			if ( os != null ){
				os.close();
			}
		}

と、まぁ、ここまでくれれば、XMLとオブジェクトの相互変換は非常に簡単です。

まとめ

  • いわずもがな、読み込み書き込みは、Unmarshaller/Marshallerで非常に簡単に行える。
  • JAXBはスキーマを作成するのが面倒という認識があったが、trangを使えばかなり楽に使い始めることができる。ただし、XMLスキーマの知識は必要。
  • maven-jaxb2-pluginでXMLスキーマからモデルオブジェクトを生成する場合は、多少手直しが必要となる場合がある。これは手動と自動がバッティングする可能性があって注意が必要。

気になるところとしては、StAXに依存しているらしいので、なるべくメモリ使用を抑えてXMLのI/Oしてくれそうですが、実際のところはどうなんだろうか。巨大なXMLでもメモリ使用を抑えてXMLのI/Oを行い、モデルオブジェクトに相互変換できるなら非常に使い勝手よいかも。

*1:使い方は http://www.jagat.or.jp/sgml/xml/xmltools/trang-20030619/trang-manual-ja.html