CodeGym /コース /JAVA 25 SELF /DOM と SAX による XML 解析

DOM と SAX による XML 解析

JAVA 25 SELF
レベル 47 , レッスン 1
使用可能

1. DOM (Document Object Model)

XML ファイルが大きすぎてメモリに丸ごと読み込めないことがあります。文書の構造が事前に分からない、あるいは複雑すぎる場合もあります。別のケースでは、ファイル全体ではなく一部のデータだけを処理したい、またはプログラムの実行中に文書を変更したい(ノードの削除、新しい要素の追加、構造の一部の組み替えなど)こともあります。

このような状況では、定番のツールである DOMSAX が適しています。これらは、あらかじめ定義された Java クラスに縛られずに XML の内容を直接扱うことを可能にします。

DOM の仕組み

DOM は、XML 文書をメモリ上のオブジェクトツリーとして表現する方法です。各タグはツリーのノード(Node)となり、属性、テキスト値、コメントも別個のオブジェクトになります。文書を読み込んだ後はその構造に完全にアクセスでき、要素や属性の読み取り・変更・削除・追加が可能です。

Java における DOM の主なクラス

  • DocumentBuilderFactory — パーサを作成するファクトリ。
  • DocumentBuilder — XML をツリーに変換するパーサ。
  • Document — ツリーのルートオブジェクト。
  • Element — XML の要素(タグ)。
  • NodeList — ノードのリスト(例: <items> 内のすべての <item>)。

例: DOM を使って XML ファイルを読む

次のような XML ファイルを読み込むとします:

<contacts>
    <person id="1">
        <name>Ivan</name>
        <email>ivan@example.com</email>
    </person>
    <person id="2">
        <name>Mariya</name>
        <email>mariya@example.com</email>
    </person>
</contacts>

コード: 要素の読み取りと走査

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;

public class DomExample {
    public static void main(String[] args) throws Exception {
        // 1. ファクトリとパーサを作成
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // 2. XML ファイルをメモリに読み込む
        Document doc = builder.parse(new File("contacts.xml"));

        // 3. ルート要素を取得
        Element root = doc.getDocumentElement();
        System.out.println("ルート要素: " + root.getTagName());

        // 4. すべての <person> のリストを取得
        NodeList persons = root.getElementsByTagName("person");

        for (int i = 0; i < persons.getLength(); i++) {
            Element person = (Element) persons.item(i);

            String id = person.getAttribute("id");
            String name = person.getElementsByTagName("name").item(0).getTextContent();
            String email = person.getElementsByTagName("email").item(0).getTextContent();

            System.out.println("id: " + id + ", name: " + name + ", email: " + email);
        }
    }
}

ここでの処理の流れ:

  • 最初にパーサを作成し、XML ファイルを読み込みます。
  • ルート要素(contacts)を取得します.
  • <person> 要素をすべて見つけ、走査します。
  • <person> について、属性 id<name> および <email> 要素を読み取ります。

例の DOM ツリー図

contacts
├── person (id="1")
│   ├── name ("Ivan")
│   └── email ("ivan@example.com")
└── person (id="2")
    ├── name ("Mariya")
    └── email ("mariya@example.com")

DOM で XML を変更する

DOM は読み取りだけでなく、XML の変更も可能です。例えば、新しい人物を追加してみます:

// 新しい要素 <person> を作成
Element newPerson = doc.createElement("person");
newPerson.setAttribute("id", "3");

// <name>
Element name = doc.createElement("name");
name.setTextContent("Sergey");
newPerson.appendChild(name);

// <email>
Element email = doc.createElement("email");
email.setTextContent("sergey@example.com");
newPerson.appendChild(email);

// ルートに追加
root.appendChild(newPerson);

// 変更をファイルに保存
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(new File("contacts-updated.xml")));

DOM の主な長所と短所

  • 長所: 小規模なファイルに向き、任意の変更が容易。ツリーを自由に行き来できる。
  • 短所: XML 全体をメモリに保持するため、大きなファイル(数百 MB 以上)には不向き。すぐにメモリ不足になる。

2. SAX (Simple API for XML)

SAX はイベント駆動のパーサです。ツリーを構築せず、XML を「左から右へ」読み進めながら「要素開始」「テキスト」「要素終了」などのイベントを呼び出します。必要なイベントに反応するハンドラを自分で実装します。

たとえ話:
DOM がオーガナイザーの全ページを机に広げることだとすれば、SAX はオーガナイザーをページごとに読み、必要なページに出会ったときだけメモを取るようなものです。

Java における SAX の主なクラス

  • SAXParserFactorySAXParser — ファクトリとパーサ。
  • DefaultHandler — イベント処理用の基底クラス。
  • ハンドラメソッド: startElementcharactersendElementstartDocumentendDocument

例: SAX を使って XML ファイルを読む

同じく contacts.xml とします。全員の名前と e-mail を出力したいとします。

コード: SAX パーサ

import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;

public class SaxExample {
    public static void main(String[] args) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        parser.parse(new File("contacts.xml"), new ContactHandler());
    }
}

class ContactHandler extends DefaultHandler {
    private String currentElement = "";
    private String name = "";
    private String email = "";

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        currentElement = qName;
        if ("person".equals(qName)) {
            String id = attributes.getValue("id");
            System.out.println("新しい人物、id: " + id);
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        String text = new String(ch, start, length).trim();
        if (text.isEmpty()) return;
        if ("name".equals(currentElement)) {
            name = text;
        } else if ("email".equals(currentElement)) {
            email = text;
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {
        if ("person".equals(qName)) {
            System.out.println("名前: " + name + ", email: " + email);
            name = "";
            email = "";
        }
        currentElement = "";
    }
}

では、ここで何が起きているかを平易に説明します。SAX パーサが開始タグ(例えば <person>)に出会うと、startElement メソッドが呼ばれます。そこで属性 id を読み取り、すぐに出力します。内部でテキスト(名前や e-mail)が見つかると、characters に制御が渡り、そのテキストを一時変数に保存します。パーサが閉じタグ </person> に到達すると endElement が呼ばれ、その時点で人物の名前と e-mail が分かっているので出力できます。その後、次の連絡先に備えて変数をクリアします。

要点は、SAX は XML 全体をメモリに保持せず、ストリームの「読み手」として上から下へと進み、開始タグ・テキスト・終了タグといったイベントに反応するということです。特に大きなファイルに対して、高速かつメモリ効率が良い方法です。

DOM と SAX の簡単な比較

DOM SAX
スタイル ツリー(構造全体をメモリに保持) イベント(ストリーミング処理)
メモリ XML 全体を読み込む メモリ使用は最小限
変更 読み取り/変更/追加が可能 読み取り専用(通常)
データ検索 どこでも簡単に検索できる 現在位置を自分で追跡する必要がある
ファイルサイズ 小〜中規模ファイル向け 非常に大きなファイル向け

3. DOM と SAX を使い分ける場面

DOM が適しているのは次のような場合:

  • ファイルが小規模〜中規模である。
  • XML のさまざまな箇所に繰り返しアクセスする必要がある。
  • 文書構造を変更する必要がある。

SAX が向いているのは次のような場合:

  • ファイルが巨大で、メモリに読み込めない。
  • 必要な情報の一部だけをすばやく取り出したい(例えば、特定の属性を持つ <item> をすべて見つける)。
  • 高いパフォーマンスと最小限のメモリ使用が求められる。
  • XML を変更せず、読み取りのみ行う。

ヒント:
実プロジェクトでは両方を併用することがよくあります。DOM は「人間向け」の設定や小さな設定ファイルに、SAX はログ、エクスポート、巨大インポートの処理に使う、といった具合です。

4. 実践課題: アプリ用の小さなパーサ

学習用アプリ(例: 「連絡先帳」)で、gmail.com の e-mail を持つユーザー数をすばやく数えるという課題があるとします。

DOM 解法:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("contacts.xml"));

NodeList emails = doc.getElementsByTagName("email");
int count = 0;
for (int i = 0; i < emails.getLength(); i++) {
    String email = emails.item(i).getTextContent();
    if (email.endsWith("@gmail.com")) {
        count++;
    }
}
System.out.println("gmail.com のユーザー数: " + count);

SAX 解法:

class GmailCounterHandler extends DefaultHandler {
    private String currentElement = "";
    int count = 0;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        currentElement = qName;
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        if ("email".equals(currentElement)) {
            String email = new String(ch, start, length).trim();
            if (email.endsWith("@gmail.com")) {
                count++;
            }
        }
    }
    @Override
    public void endDocument() {
        System.out.println("gmail.com のユーザー数: " + count);
    }
}

5. DOM と SAX の注意点・コツ

  • DOM は、XML ファイルが非常に大きいとメモリを食い尽くすことがあります。OutOfMemoryError が出たら、SAX への切り替え時期かもしれません。
  • SAX は注意深さが必要です。現在どの要素にいるかを追跡し、必要なデータを丁寧に組み立てる必要があります。深いネストで混乱しないよう、スタックや補助変数を使うこともあります。
  • SAX の characters は、同じテキストブロックに対して複数回呼ばれることがあります(特にテキストが長い場合や特殊文字を含む場合)。テキストは StringBuilder に蓄積するとよいでしょう。
  • DOM は検索・ナビゲーション・構造変更に適していますが、ストリーミング処理には不向きです。
  • どちらを選ぶか迷うなら、まずは簡単な DOM から始め、メモリが「きつく」なったら SAX に書き換えましょう。

6. DOM と SAX でよくあるミス

ミス 1: SAX における空白と改行の扱いが不適切。
characters メソッドは、要素間の空白や改行を含むテキスト片を返すことがあります。.trim().isEmpty() のフィルタをしないと、「空」の呼び出しが多発したり、テキストの組み立てに失敗します。

ミス 2: SAX で XML を変更しようとする。
SAX は読み取り専用です!構造を変更する必要があるなら DOM を使いましょう。

ミス 3: SAX のイベント順序の扱いを誤る。
endElement で変数をリセットしないと、要素間でデータが「漏れる」ことがあります。

ミス 4: 巨大なファイルに DOM を使う。
結果は OutOfMemoryError か、極端に遅い処理です。

ミス 5: DOM での不適切なキャスト。
DOM ではすべてが Node ですが、属性や子要素を扱うには Element へのキャストが必要です。キャストの誤りは ClassCastException を引き起こします。

1
タスク
JAVA 25 SELF, レベル 47, レッスン 1
ロック未解除
ライブラリのルート要素の識別 🏛️
ライブラリのルート要素の識別 🏛️
1
タスク
JAVA 25 SELF, レベル 47, レッスン 1
ロック未解除
システム内のキャラクターまたはユーザーの一覧 👤
システム内のキャラクターまたはユーザーの一覧 👤
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION