ubichupas.net

【Java】XML文書をDOMで処理してみるサンプル【C#】

DOM(Document Object Model)のAPIはIDL(Interface Definition Language)で定義されていて、 様々なプログラミング言語に実装されているらしい。 つまり使用する言語が変わってもXMLの知識は持ち越せるってことやね。 その辺りを確認すべく、同じDOMプログラムをJavaとC#で書いてみました。

入力するXMLファイル

まずお題として次のソースXML文書(GundamList.xml)を用意しました。

GundamList.xml
<?xml version="1.0" encoding="Shift_JIS" ?>

<!DOCTYPE gundamList SYSTEM "GundamList.dtd">

<gundamList xmlns="urn:gundam:anime">
 <gundam media="TV">
     <name>機動戦士ガンダム</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="TV">
     <name>機動戦士Zガンダム</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0087</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="TV">
     <name>機動戦士ガンダムZZ</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0088</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="MOVIE">
     <name>機動戦士ガンダム 逆襲のシャア</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0093</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="OVA">
     <name>機動戦士ガンダム0080 ポケットの中の戦争</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0080</calendar>
     <director>高山文彦</director>
 </gundam>
 <gundam media="MOVIE">
     <name>機動戦士ガンダムF91</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0123</calendar>
     <director>富野由悠季</director>
 </gundam>
 <gundam media="OVA">
     <name>機動戦士ガンダム0083 STARDUST MEMORY</name>
     <calendar jpn="宇宙世紀" eng="U.C.">0083</calendar>
     <director>加瀬充子</director>
<director>今西隆志</director> </gundam> <gundam media="TV"> <name>機動戦士Vガンダム</name> <calendar jpn="宇宙世紀" eng="U.C.">0153</calendar> <director>富野由悠季</director> </gundam> <gundam media="TV"> <name>機動武闘伝Gガンダム</name> <calendar jpn="未来世紀" eng="F.C.">60</calendar> <director>今川泰宏</director> </gundam> <gundam media="TV"> <name>新機動戦記ガンダムW</name> <calendar jpn="アフターコロニー" eng="A.C.">195</calendar> <director>池田成</director> <director>高松信司</director> </gundam> <gundam media="TV"> <name>新機動戦記ガンダムX</name> <calendar jpn="アフターウォー" eng="A.W.">15</calendar> <director>高松信司</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダム 第08MS小隊</name> <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar> <director>神田武幸</director> <director>飯田馬之介</director> </gundam> <gundam media="OVA"> <name>新機動戦記ガンダムW Endless Waltz</name> <calendar jpn="アフターコロニー" eng="A.C.">196</calendar> <director>青木康直</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダム 第08MS小隊 特別編 ラスト・リゾート</name> <calendar jpn="宇宙世紀" eng="U.C.">0079</calendar> <director>森邦宏</director> <director>仕舞屋鉄</director> </gundam> <gundam media="TV"> <name>∀ガンダム</name> <calendar jpn="正暦" eng="C.C.">2345</calendar> <director>富野由悠季</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダムSEED</name> <calendar jpn="コズミック・イラ" eng="C.E.">71</calendar> <director>福田己津央</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダムSEED DESTINY</name> <calendar jpn="コズミック・イラ" eng="C.E.">73</calendar> <director>福田己津央</director> </gundam> <gundam media="OVA"> <name>機動戦士ガンダムSEED C.E.73 STARGAZER</name> <calendar jpn="コズミック・イラ" eng="C.E.">73</calendar> <director>西澤晋</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダム00 ファーストシーズン</name> <calendar jpn="西暦" eng="A.D.">2307</calendar> <director>水島精二</director> </gundam> <gundam media="TV"> <name>機動戦士ガンダム00 セカンドシーズン</name> <calendar jpn="西暦" eng="A.D.">2312</calendar> <director>水島精二</director> </gundam> </gundamList>

JavaのDOMパーサーは行頭/行末の空白文字列をノードとしてDOMツリーに取り込んでしまうため、 下記のDTD(Document Type Definition)を用意して意味のない空白文字列であることを明示します。 C#のDOMパーサーはDTD(GundamList.dtd)が無くても勝手に空白文字列を無視してくれるみたいです。

GundamList.dtd
<!ELEMENT gundamList (gundam*)>
<!ATTLIST gundamList xmlns CDATA #IMPLIED>
<!ELEMENT gundam (name, calendar, director+)>
<!ATTLIST gundam media CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT calendar (#PCDATA)>
<!ATTLIST calendar jpn CDATA #REQUIRED>
<!ATTLIST calendar eng CDATA #REQUIRED>
<!ELEMENT director (#PCDATA)>

DOMプログラムの出力

作成するDOMプログラムは「GundamList.xmlの中から宇宙世紀(U.C.)のガンダムシリーズを抜き出して作中の年表順にソートする」プログラムで、次のような出力結果を目指します。

cmd.exe
U.C.0079 機動戦士ガンダム
U.C.0079 機動戦士ガンダム 第08MS小隊
U.C.0079 機動戦士ガンダム 第08MS小隊 特別編 ラスト・リゾート
U.C.0080 機動戦士ガンダム0080 ポケットの中の戦争
U.C.0083 機動戦士ガンダム0083 STARDUST MEMORY
U.C.0087 機動戦士Zガンダム
U.C.0088 機動戦士ガンダムZZ
U.C.0093 機動戦士ガンダム 逆襲のシャア
U.C.0123 機動戦士ガンダムF91
U.C.0153 機動戦士Vガンダム
以上、10作品

DOMプログラムの設計

それでは設計開始。 (`・ω・´)シャキーン DOMはXML文書の各要素をメモリ上にツリー構造として展開してくれるので、素直なループ処理を書けばOKでしょう。

  1. calendar要素のeng属性が"U.C."なgundam要素を抜き出す。
  2. calendar要素の値を整数で比較しながら作業用リストに挿入。
  3. 作業用リストにはDocumentFragmentオブジェクトを使用する。

ポイントは作業用リストにDocumentFragmentオブジェクト(あるいはElementオブジェクト)を使っているところ。 NodeListインターフェースというのもありますが、 これにはlengthプロパティとitem()メソッドしか定義されていないため、 ノードの追加/削除する用途には使えません。
ソート処理には言語独自のListコレクションを使うこともできます。 しかしそれをやってしまうとDOMプログラミングの枠から外れてしまいますからね。

Javaでの実装

Javaでの実装(GundamDom.java)は次のようになりました。 XMLマスターの試験問題はJavaで書かれているため、これで覚えておくと楽です。

GundamDom.java
import org.w3c.dom.*;
import javax.xml.parsers.*;

public class GundamDom
{
    public static void main(String args[])
    {
        try
        {
            // Documentオブジェクトの生成
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            dbf.setValidating(true);
            dbf.setIgnoringElementContentWhitespace(true);
            DocumentBuilder db = dbf.newDocumentBuilder();

            Document doc = db.parse("GundamList.xml");


            // ソート作業用リストの生成
            DocumentFragment ucGandums = doc.createDocumentFragment();

            // 宇宙世紀のガンダムを抽出
            Element root = doc.getDocumentElement();
            for(Node next, gundam=root.getFirstChild(); gundam != null; gundam=next)
            {
                next = gundam.getNextSibling();

                // calendar要素のeng属性で判定
                Element calendar = (Element)gundam.getFirstChild().getNextSibling();
                String eng = calendar.getAttribute("eng");

                if(eng.equals("U.C.") == false)  continue;


                // 宇宙世紀順で挿入位置検索
                int year = Integer.parseInt(calendar.getFirstChild().getNodeValue());

                int pos = 0;
                NodeList list = ucGandums.getChildNodes();
                for(pos=0; pos < list.getLength(); pos++)
                {
                    Node cale = list.item(pos).getFirstChild().getNextSibling();
                    int year2 = Integer.parseInt(cale.getFirstChild().getNodeValue());

                    if(year < year2) break;
                }

                // ソート作業用リストにgundam要素を挿入
                if(pos < list.getLength())
                    ucGandums.insertBefore(gundam, list.item(pos));
                else
                    ucGandums.appendChild(gundam);
            }


            // 結果を出力
            for(Node gundam=ucGandums.getFirstChild(); gundam != null; gundam=gundam.getNextSibling())
            {
                Element name = (Element)gundam.getFirstChild();
                Element cale = (Element)name.getNextSibling();
                String n = name.getFirstChild().getNodeValue();
                String y = cale.getFirstChild().getNodeValue();
                
                System.out.println("U.C." + y + " " + n);
            }

            System.out.println("以上、" + ucGandums.getChildNodes().getLength() + "作品");
        }
        catch(Exception ex)
        {
            System.out.println(ex.toString());
        }
    }
}

C#での実装

C#での実装(GundamDom.cs)は次のようになりました。 最初にDocumentオブジェクトを生成する部分のコードがJavaよりかなり簡単になっていますが、 これはDOMがDocumentオブジェクトの生成方法を規定していないためで、 C#が仕様破りをしているわけではありません。

GundamDom.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace GundamDom
{
    class GundamDom
    {
        static void Main(string[] args)
        {
            // Documentオブジェクトの生成
            XmlDocument doc = new XmlDocument();
            doc.Load(@"..\..\GundamList.xml");


            // ソート作業用リストの生成
            XmlDocumentFragment ucGundams = doc.CreateDocumentFragment();

            // 宇宙世紀のガンダムを抽出
            XmlElement root = doc.DocumentElement;
            for (XmlNode next, gundam = root.FirstChild; gundam != null; gundam = next)
            {
                next = gundam.NextSibling;

                // calendar要素のeng属性で判定
                XmlElement calendar = (XmlElement)gundam.FirstChild.NextSibling;
                String eng = calendar.GetAttribute("eng");

                if (eng.Equals("U.C.") == false) continue;


                // 宇宙世紀順で挿入位置検索
                int year = int.Parse(calendar.FirstChild.Value);

                int pos = 0;
                XmlNodeList list = ucGundams.ChildNodes;
                for (pos = 0; pos < list.Count; pos++)
                {
                    XmlNode cale = list.Item(pos).FirstChild.NextSibling;
                    int year2 = int.Parse(cale.FirstChild.Value);

                    if (year < year2) break;
                }

                // ソート作業用リストに挿入
                if (pos < list.Count)
                    ucGundams.InsertBefore(gundam, list.Item(pos));
                else
                    ucGundams.AppendChild(gundam);
            }


            // 結果を出力
            for (XmlNode gundam = ucGundams.FirstChild; gundam != null; gundam = gundam.NextSibling)
            {
                XmlElement name = (XmlElement)gundam.FirstChild;
                XmlElement cale = (XmlElement)name.NextSibling;
                String n = name.FirstChild.Value;
                String y = cale.FirstChild.Value;

                Console.WriteLine("U.C.{0} {1}", y, n);
            }

            Console.WriteLine("以上、{0}作品", ucGundams.ChildNodes.Count);
        }
    }
}

C#では言語仕様的にGetterとSetterをメソッドにする必要がないとか、lengthプロパティをCountプロパティと呼び変えているといった違いがあるみたいです。 だけどその違いを除けばJavaとほぼ同じようなコードで実装できましたね。 これがDOMの威力です。(´・∀・`)ヘー

試験対策本ではありますが、JavaでのXMLプログラミングを学ぶ入門書としても役立ちます。 つか他に碌な本が無いよね。 ( ゚Д゚)y─┛~~ 章の始めに各インターフェースのプロパティとメソッド一覧が載ってますけど、 眠たくなるので最初は読み飛ばして後で戻ってチェックするのがお薦め。

0 件のコメント: