Java Tips | 基本ユーティリティ:XML生成

Java Java
スポンサーリンク

XML生成は「機械にも人にも読める構造を作る」技

JSON が主流になった今でも、業務の世界では XML が普通に生きています。
古い外部システム連携、バッチの入出力、設定ファイル、帳票レイアウト定義など、「まだまだ XML 前提」の世界は多いです。

XML生成で大事なのは、「見た目だけそれっぽい文字列」を作るのではなく、
タグの対応・エスケープ・インデントなどをきちんと守った「構造として正しい XML」を、毎回同じルールで出せるようにすることです。


まずは「XMLの最低限のルール」を押さえる

タグの対応とエスケープが最重要

XML はざっくり言うと「タグで囲まれた階層構造のテキスト」です。

<user>
    <id>u-001</id>
    <name>山田太郎</name>
</user>

ここで最低限守らないといけないルールは次のようなものです。

タグは必ず開始タグと終了タグがペアになる(<user></user>)。
< > & " ' などの記号は、内容として使うときにエスケープが必要(&lt; &gt; &amp; など)。
ルート要素は一つだけ(XML 全体を包む一番外側のタグ)。

これを手書きの文字列連結でやろうとすると、すぐに壊れます。
だからこそ、「XML を組み立てる仕組み」に任せるか、「エスケープと構造」をユーティリティに閉じ込めるのが大事になります。


業務で現実的な選択肢:ライブラリを使うか、最小限だけ自前でやるか

JAXB や Jackson XML などの「オブジェクト→XML」ライブラリ

本格的に XML を扱うなら、JAXB(Jakarta XML Binding)や Jackson の XML モジュールのような、
「Java オブジェクトを XML に変換してくれるライブラリ」を使うのが王道です。

ただ、ここでは「まずは仕組みを理解したい」「小さなユーティリティから始めたい」という前提で、
標準 API と軽いユーティリティで XML を生成する流れを見ていきます。


DOM を使った「ちゃんとした XML生成」の最小例

DocumentBuilder でツリーを作ってから文字列にする

標準の org.w3c.domjavax.xml.transform を使うと、
「XML のツリー(DOM)を組み立ててから、文字列に変換する」という流れで、安全に XML を生成できます。

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;

public final class Xmls {

    private Xmls() {}

    public static String buildUserXml(String id, String name) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.newDocument();

            Element root = doc.createElement("user");
            doc.appendChild(root);

            Element idElem = doc.createElement("id");
            idElem.setTextContent(id);
            root.appendChild(idElem);

            Element nameElem = doc.createElement("name");
            nameElem.setTextContent(name);
            root.appendChild(nameElem);

            return toString(doc);
        } catch (Exception e) {
            throw new IllegalStateException("XML 生成に失敗しました", e);
        }
    }

    private static String toString(Document doc) throws Exception {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(writer));
        return writer.toString();
    }
}
Java

使い方はこうです。

String xml = Xmls.buildUserXml("u-001", "山田 & 太郎 <営業>");
System.out.println(xml);
Java

出力イメージは次のようになります。

<?xml version="1.0" encoding="UTF-8"?>
<user>
    <id>u-001</id>
    <name>山田 &amp; 太郎 &lt;営業&gt;</name>
</user>

ここで深掘りしたい重要ポイントは、「テキスト内容を setTextContent で設定すると、危ない文字を自動でエスケープしてくれる」ことです。
&< を自分で &amp;&lt; に変換する必要はありません。
DOM の API に任せることで、「エスケープ漏れで XML が壊れる」という事故を防げます。

また、TransformerINDENT を設定することで、インデント付きの読みやすい XML を出力しています。
人間が見るログやファイルではインデントあり、本番通信ではインデントなし、という切り替えもここでできます。


例題:業務エンティティから XML を生成する

POJO から「レポート用 XML」を作る

例えば、注文情報を表すクラスがあるとします。

public class Order {

    private String id;
    private String userId;
    private int amount;

    public Order(String id, String userId, int amount) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
    }

    public String getId()      { return id; }
    public String getUserId()  { return userId; }
    public int getAmount()     { return amount; }
}
Java

この Order を XML に変換するユーティリティを作ります。

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public final class OrderXmlBuilder {

    private OrderXmlBuilder() {}

    public static String toXml(Order order) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.newDocument();

            Element root = doc.createElement("order");
            doc.appendChild(root);

            appendTextElement(doc, root, "id", order.getId());
            appendTextElement(doc, root, "userId", order.getUserId());
            appendTextElement(doc, root, "amount", String.valueOf(order.getAmount()));

            return Xmls.toString(doc); // 先ほどの toString を再利用
        } catch (Exception e) {
            throw new IllegalStateException("Order XML 生成に失敗しました", e);
        }
    }

    private static void appendTextElement(Document doc, Element parent, String name, String value) {
        Element elem = doc.createElement(name);
        elem.setTextContent(value);
        parent.appendChild(elem);
    }
}
Java

使い方はこうです。

Order order = new Order("o-001", "u-001", 5000);
String xml = OrderXmlBuilder.toXml(order);
System.out.println(xml);
Java

ここでのポイントは、「XML の構造(タグ名・階層)を OrderXmlBuilder に閉じ込めている」ことです。
呼び出し側は「Order を XML にしたい」とだけ考えればよく、
「どのタグ名にするか」「どの順番で並べるか」「エスケープはどうするか」は、ビルダー側に任せられます。

こうしておくと、「タグ名を変えたい」「要素を追加したい」といった変更も、
OrderXmlBuilder だけを直せばよくなり、業務ロジックへの影響を最小限にできます。


文字列連結で「軽量XML」を作るときの最低限の防御

ログ用やごく簡単な構造なら、エスケープユーティリティを挟む

DOM を使うのは少し重い、ログ用に簡単な XML っぽいものを出したい、
という場面では、文字列連結で作ることもあります。

その場合でも、「テキスト内容のエスケープだけは絶対にユーティリティに任せる」ようにします。

public final class XmlEscape {

    private XmlEscape() {}

    public static String text(String value) {
        if (value == null) {
            return "";
        }
        String s = value;
        s = s.replace("&", "&");
        s = s.replace("<", "<");
        s = s.replace(">", ">");
        s = s.replace("\"", """);
        s = s.replace("'", "'");
        return s;
    }
}
Java

これを使って、簡易的な XML を作ります。

public final class SimpleXmlLog {

    private SimpleXmlLog() {}

    public static String userLogXml(String id, String name) {
        return "<userLog>" +
                "<id>" + XmlEscape.text(id) + "</id>" +
                "<name>" + XmlEscape.text(name) + "</name>" +
                "</userLog>";
    }
}
Java

使い方はこうです。

String xml = SimpleXmlLog.userLogXml("u-001", "山田 & 太郎 <営業>");
System.out.println(xml);
Java

出力は次のようになります。

<userLog><id>u-001</id><name>山田 &amp; 太郎 &lt;営業&gt;</name></userLog>

ここで深掘りしたいのは、「“タグの構造は自分で書いてもいいが、テキストのエスケープは必ずユーティリティを通す”という線引き」です。
これを徹底するだけで、「& が原因で XML パーサがコケる」といった事故をかなり防げます。


XML生成で意識したい設計のポイント

ルート要素と名前空間をどう扱うか

業務の XML では、ルート要素や名前空間(xmlns="...")が仕様で決まっていることが多いです。

例えば、こんな感じです。

<order xmlns="http://example.com/schema/order">
    ...
</order>

DOM で名前空間付きの要素を作る場合は、createElementNS を使います。

String ns = "http://example.com/schema/order";
Element root = doc.createElementNS(ns, "order");
doc.appendChild(root);
Java

ここでのポイントは、「名前空間やルート要素の扱いも、ビルダー側に閉じ込める」ことです。
呼び出し側が xmlns を意識しなくてよいようにしておくと、仕様変更にも強くなります。

インデントや XML宣言の有無を統一する

Transformer の設定次第で、XML 宣言(<?xml version="1.0" ...?>)を付けるかどうか、
インデントを付けるかどうかを制御できます。

これを各所でバラバラにすると、「この XML だけ宣言がない」「このファイルだけインデントがない」といった揺れが出ます。
Xmls.toString のような共通ユーティリティで、「プロジェクトとしての標準」を一箇所に決めておくと、
どこで XML を出しても同じスタイルになります。


まとめ:XML生成ユーティリティで身につけたい感覚

XML生成は、「それっぽい文字列を組み立てる」のではなく、
「構造とエスケープを仕組みに任せて、毎回同じルールで安全に出す」ための技です。

押さえておきたい感覚は、まず「DOM などの API を使えば、テキストのエスケープを自分でやらなくてよくなる」こと。
次に、「業務エンティティごとに“XMLビルダー”を用意し、タグ構造や名前空間をそこに閉じ込める」こと。
そして、「どうしても文字列連結でやる場面でも、テキスト部分だけは必ずエスケープユーティリティを通す」という最低限の防御ラインを持つことです。

タイトルとURLをコピーしました