CLOVER🍀

That was when it all began.

JavaでXMLを曞き出す

昚日、こちらののブログで芋かけた、こんな゚ントリ。

JDOM2でXMLファむルを出力しおみる
http://kikutaro777.hatenablog.com/entry/2013/09/04/215134

JavaでXMLを出力する方法を探されおいたようなのですが、䜿われおいたラむブラリがJDOMだったので、思わずTwitterでいろいろ぀ぶやいおしたいたした。

JDOMは、JavaにおけるDOMの代替ですが、自分はJDOMがただβ版の頃から䜿っおいた、なおか぀Javaのラむブラリで初めお扱ったものだったので非垞に感慚深く ハむ。

で、せっかくなので「JavaでXMLを出力する」ずいう方法に぀いお、普段自分が䜿う方法を3぀ほど挙げたいず思いたす。

今回は、元のブログで目暙ずされおいる、

<?xml version="1.0" encoding="UTF-8"?>
<Product>
  <Option Id="Opt">
    <Name>
      <JPN>オプション品</JPN>
      <ENG>Option Item</ENG>
    </Name>
  </Option>
  <!-- Optionタグの繰り返し -->
</Product>

ずいう感じのXMLを出力するものずしたす。OptionのIdやJPN、ENGの倀には実際には連番を含めるようです。

DOMツリヌを䜿うラむブラリを䜿甚する

JDOMは、このパタヌンにあたりたす。

JDOM
http://www.jdom.org/

JDOM2 A Primer
https://github.com/hunterhacker/jdom/wiki/JDOM2-A-Primer

最新版は、JDOM2。リリヌスを知らなかったです 。

類䌌のものずしおは、Java暙準だずDOM、倖郚ラむブラリずしおはdom4jがありたす。

dom4j
http://dom4j.sourceforge.net/

では、JDOMの䟋を。

Maven䟝存関係。

    <dependency>
      <groupId>org.jdom</groupId>
      <artifactId>jdom2</artifactId>
      <version>2.0.5</version>
    </dependency>

コヌド䟋。

import java.io.IOException;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.output.DOMOutputter;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

public class JdomExample {
    public static void main(String[] args) {
        Element product = new Element("Product");
        Document document = new Document(product);

        for (int i = 0; i < 5; i++) {
            Element option =
                new Element("Option")
                .setAttribute("Id", "Opt" + i);
            product.addContent(option);

            Element name = new Element("Name");
            option.addContent(name);

            name.addContent(new Element("JPN").setText("オプション品" + i));
            name.addContent(new Element("ENG").setText("Option Item" + i));
        }

        try {
            XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
            outputter.output(document, System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            DOMOutputter domOutputter = new DOMOutputter();
            org.w3c.dom.Document dom = domOutputter.output(document);
        } catch (JDOMException e) {
            e.printStackTrace();
        }
    }
}

元のブログのコヌドず、倧差ありたせん。

最埌に、DOMに倉換するコヌドを加えおいたす。

            DOMOutputter domOutputter = new DOMOutputter();
            org.w3c.dom.Document dom = domOutputter.output(document);

たあ、DOMず盞互運甚できたすよっおこずで。

DOMに比べおの優䜍性は、

  • メモリ䜿甚量が少ない
  • JavaのCollectionが䜿えるNodeListなどは、Collectionではない
  • DOMのように、Nodeむンタヌフェヌスからのダりンキャストをあたり求められない

ずいうずころでしょうか。

䞍利なずころはやはり暙準APIではないので、DOMに倉換可胜かどうかずいう点はちょっず気になるずころです。

よく比范されるdom4jの方が高機胜なのですが、個人的にはJDOMをよく䜿いたす。JDOMは2になっおゞェネリクスをサポヌトするようになっおいるので、この点も嬉しいずころです。

今回挙げる方法では、もっずもプリミティブな手段になるので、最近は埌述するJAXBなどを䜿うこずが倚いです。

DOM、JDOM、dom4jなどを䜿甚する時は、個人的には

  • さたざたな構造のXMLを扱うため、汎甚的に凊理を曞きたい
  • XPathなどを䜿いたい
  • JAXBなどのマッピング系を䜿うには、オヌバヌスペックに感じる

ずいった時に䜿うようにしおいたす。

ちなみに、Java暙準APIだずStAXを䜿っおもXMLを出力できたすが、これよりさらに面倒だず思いたす 。

XMLずオブゞェクトをマッピングする

デヌタベヌスずのORマッピング的なものになりたす。これは、Java暙準で入っおいるJAXBをよく䜿いたす。

ずころにより、XStreamあたりも。

XStream
http://xstream.codehaus.org/

今回は、JAXBで。

JAXBに぀いおは、以前゚ントリを曞いたこずがありたす。

JAXBをXML Schemaなしで䜿っおみる
http://d.hatena.ne.jp/Kazuhira/20120716/1342435297

暙準APIなので、Maven䟝存関係は䞍芁です。

では、コヌドを。

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.XmlRootElement;

public class JaxbExample {
    public static void main(String[] args) {
        Product product = new Product();

        for (int i = 0; i < 5; i++) {
            Option option = new Option("Opt" + i);
            option
                .name
                .addLang(new Jpn("オプション品" + i))
                .addLang(new Eng("Option Item" + i));
            product.options.add(option);
        }

        JAXB.marshal(product, System.out);
    }
}


@XmlRootElement(name = "Product")
class Product {
    @XmlElement(name = "Option")
    List<Option> options = new ArrayList<>();
}

@XmlAccessorType(XmlAccessType.FIELD)
class Option {
    @XmlAttribute(name = "Id")
    String id;

    @XmlElement(name = "Name")
    Name name = new Name();

    Option(String id) {
        this.id = id;
    }
    
}

@XmlAccessorType(XmlAccessType.FIELD)
class Name {
    @XmlElements({@XmlElement(name = "JPN", type = Jpn.class),
                  @XmlElement(name = "ENG", type = Eng.class)})
    List<Lang> langs = new ArrayList<>();

    public Name addLang(Lang lang) {
        langs.add(lang);
        return this;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
abstract class Lang {
    @XmlValue
    String value;

    Lang(String value) {
        this.value = value;
    }
}

class Jpn extends Lang {
    Jpn(String value) {
        super(value);
    }
}

class Eng extends Lang {
    Eng(String value) {
        super(value);
    }
}

 実は、こっちはけっこう厄介でした。JPNやENGタグずいう存圚がけっこう埮劙で、きっずこれはもうちょっず蚀語ごずに増えたりするんだろうなぁずか予想しおみるず、だいぶ冗長感たっぷりな感じに笑。Listにしなかった堎合は、JPNずかENGの単䜍でフィヌルド定矩ずかをする矜目になるんじゃないかず。

オブゞェクトを組むずころ自䜓はDOM系に比べるずすっきりしたすが、あずはどれだけ簡単にマッピング先のクラスを曞けるかずいうずころでしょうか。

この手のラむブラリは、結局クラスの定矩でXMLの構造が決たっおしたうので、XMLの定矩が完党に決定しおいお、なおか぀ある皋床マッピング項目がある堎合に䜿っおいたす。

読み蟌み時の性胜が気になる時ずかは、たた別ですが。

今回の䟋だず、完党にDOM系の方が簡単でした 。

テンプレヌト゚ンゞンを䜿う

別にXMLに関するAPIを䜿わなくおも、XMLは出力できたすよずいうこずで、時々䜿うのがテンプレヌト゚ンゞン。

倧抵は、FreeMarkerかVelocityを䜿いたす。

FreeMarker
http://freemarker.org/

Apache Velocity
http://velocity.apache.org/

今回は、FreeMarkerを䜿甚したす。

Maven䟝存関係。

    <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
      <version>2.3.20</version>
    </dependency>

Javaコヌド。

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import freemarker.cache.StringTemplateLoader;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class FreeMarkerExample {
    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        configuration.setNumberFormat("###");
        configuration.setTemplateLoader(new ClassTemplateLoader(FreeMarkerExample.class, ""));

        Map<String, Object> options = new LinkedHashMap<>();
        for (int i = 0; i < 5; i++) {
            List<Map<String, String>> langs = new ArrayList<>();

            Map<String, String> jpn = new LinkedHashMap<>();
            jpn.put("JPN", "オプション品" + i);
            langs.add(jpn);

            Map<String, String> eng = new LinkedHashMap<>();
            eng.put("ENG", "Option Item" + i);
            langs.add(eng);

            options.put("Opt" + i, langs);
        }

        Map<String, Object> context = new LinkedHashMap<>();
        context.put("options", options);

        try {
            Template template = configuration.getTemplate("template.ftl");
            StringWriter writer = new StringWriter();
            template.process(context, writer);

            System.out.println(writer);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }
}

今回のデヌタ構造は、すべおMapずListで衚珟したした。

テンプレヌト゚ンゞンなので、テンプレヌトも必芁です。今回はクラスパス䞊にテンプレヌトを眮きたした。

<?xml version="1.0" encoding="UTF-8"?>
<Product>
    <#list options?keys as optionId>
    <Option id="${optionId?xml}">
        <Name>
            <#list options[optionId] as lang>
                <#list lang?keys as name>
            <${name}>${lang[name]?xml}</${name}>
                </#list>
            </#list>
        </Name>
    </Option>
    </#list>
</Product>

蚘法さえそれなりに芚えれば、たあ楜です。利甚するシヌンはけっこうDOM系ずかぶるのですが、

  • XML自䜓にマッピングするデヌタ構造は、割ず汎甚的に䜜りたい
  • でも、耇雑なデヌタ構造じゃない
  • 結果のXML定矩はテンプレヌト定矩で、柔軟に倉曎したい
  • あんたりやりたせんがテンプレヌト䞊で、少し凊理を挟みたい

みたいな時に䜿っおいたす。

特に、XML定矩を自分じゃなくお別の人に䜜っおもらうような時に䜿いがちですかね 。「SELECT文の結果をテンプレヌトに入れずくので、あずはテンプレヌトで奜きに敎圢しおね」みたいな感じでしょうか。

今回玹介しおいる䟋で、唯䞀XMLの読み蟌み機胜がない方法です笑。

泚意点ずしおは、劥圓なXMLであるこずをテンプレヌト゚ンゞンは保障しおくれないので、ちゃんず確認する必芁があるこずず、XML゚スケヌプを忘れないこず、でしょうか。

だいたい、よく䜿うのはこんな感じですね。