特集:C# 3.0入門 特別編

C#で実感する「LINQ to XML」の素晴らしさ

株式会社ピーデー 川俣 晶
2009/01/16
Page1 Page2

 本稿は「連載:C# 3.0入門」からスピンオフした(平たくいえばページの都合で掲載されなかった)、「LINQ to XML」に関する紹介である(LINQ自体の基本的な内容については、同連載の第6〜8回で解説している)。

 C# 3.0入門 第9回では、なぜSQL Serverが使えないかについて述べたが、本稿では、なぜXMLが「使えない」のかという話から始めよう。

XML最大の災厄

 XMLという技術を襲った最大の災厄とは、「僕の賢さ」を誇示しようとする「精神の子どもたち」の大挙流入にあるといえる。ここでいう「精神の子どもたち」とは、自分自身を自らの能力以上に優れた存在だと思い込んでいる者たちを示す。別のいい方をすれば、自己評価と社会からの評価にギャップがあるという問題を抱え込んでいる者たちだともいえる。つまり、「僕は本当はもっと優秀なのに、社会はそれを認めてくれない」という屈折を抱えた者たちである。

 通常、このような「思い込み」は社会と触れ合うことで「錯覚」であることが思い知らされ、自己評価を下げて、社会評価との整合を取る。これが精神的な意味で「子どもから大人になる」ということである。

 ところが、XMLが生まれた前後の時代、1990年代後期から2000年代にかけては、インターネットが世界を変えるというある種の幻想が社会にまん延し、インターネットを社会に普及させる担い手を自認すれば、それだけで優秀な人材であるかのように社会も承認してしまう風潮が生まれた。だが、その優秀さを認めない者たちもいる。昔ながらの泥臭い現場を知っている者たちには、そのような風潮が「時代のムード」でしかなく、根拠がないことが分かってしまうのである。

 従って、「精神の子どもたち」は、彼らに対して自らの優秀さを証明しなければならない。そこで彼らの目の前に運良く(あるいは運悪く)飛び込んできたのがXMLである。

 XMLは、SGMLの後継言語として生まれたが、誰でも使えるようにSGMLの過剰に複雑な機能を除去して成立したものであった。そして、一貫して普及戦略の「簡単である」「誰でも使える」というイメージをアピールした。その結果として、さほど高い技術力を持たない「精神の子どもたち」であっても、XMLを扱うことができた。

 一方で、単純明快にするために多くの機能をそぎ落として成立したXMLには、含まれていない機能も多くあった。そこで、「精神の子どもたち」はXMLを使って「僕らの優秀さ」を証明できることに気付いたわけである。つまり、大人気のXMLに、僕の知っているこの機能を付加すれば、XMLはもっと素晴らしい存在になり、それを成した僕の優秀さが証明できるというわけである。

 もちろん、このような発想を持った時点で、彼は自分で思うほど優秀ではないことを露呈している。なぜなら、誰もが知っている機能を追加することよりも、実用性を失わずに機能を削る方が、はるかに優れた知性の証明になるからだ。しかし、世の中の風潮に後押しされ、彼らはそのことに気付くことができない。

 その結果、本来のXMLの中核であるExtensible Markup Languageの周辺には、膨大な数のさまざまな言語や技術が生み出された。そして、それらの多くは使い物にならなかった。肥大化しすぎて容易に実装できなかったり、大企業しか実装できなかったり、そもそも仕様としての整合性が成立していなかったり、似て非なる言語が競合したり、単に作成者の技術力や見識不足で消えてなくなったり……。中には「本物のプロ」が見れば最初から破たんが予見できた技術も決して少なくなかった。

 さて、このような風潮は、実際に泥臭い現場で使うための技術ではなく、「僕の賢さを証明する手段」の上に成立しているため、派手ではない技術にはあまり注目が集まらない傾向がある。その典型例が、DOM(Document Object Model)である。

 DOMは、プログラムからXML文書を操作するための最も基本的なAPIとして標準化されたものである。XMLに対応と称する言語、ライブラリ、開発環境の大多数がサポートしているといっても過言ではないほど、普及したものだ。

 しかし、その内容はといえば、あまりに悲惨でありすぎる。

 以下、W3C勧告のDOMではなく、.NET FrameworkのDOM実装をベースにして具体例を見てみよう。

DOMの憂うつ

 ここで、とあるXML文書から、id属性が「M」という値を持つname属性(とある名前空間URIを持つ)の値を出力する例を見てみよう。

 まず、DOMの基本機能のみを使用し、XPathのような別機能の力を借りないで書いてみよう(なお、GetElementsByTagNameメソッドの代わりにGetElementsByIdメソッドを使うという選択肢もあるが、ここでは使用していない)。

using System;
using System.Xml;

class Program
{
  private const string xmldoc =
@"<?xml version='1.0'?>
<names xmlns='http://www.piedey.co.jp/example/linqtoxml200811'>
  <name id='X'>Xenogears</name>
  <name id='M'>Mystic Quest</name>
  <name id='L'>LEGEND OF MANA</name>
</names>
";

  static void Main(string[] args)
  {
    var dom = new XmlDocument();
    dom.LoadXml(xmldoc);

    foreach (XmlElement node in dom.GetElementsByTagName("name",
      "http://www.piedey.co.jp/example/linqtoxml200811"))
    {
      if (node.Attributes["id"].Value == "M")
      {
        Console.WriteLine(node.InnerText);
      }
    }
    // 出力:Mystic Quest
  }
}
リスト1 XPathの力を借りないDOM

 このサンプル・コードは、要求された処理内容に比して、記述量が多すぎる傾向が見られる。量が増える理由は以下のとおりである。

  • GetElementsByTagNameメソッドは、ごく基本的でよく使われるにもかかわらず、あまりに名前が長すぎる(20文字)
  • 名前が長い割に、指定した名前と一致した要素を列挙する機能しか持たない
  • 要素の名前以外で絞り込むには、さらに別途if文が必要となる
  • その結果、求める要素は1つだけなのに、繰り返し処理が要求される

 従来、筆者がC#でのDOMプログラミングを「耐え難い水準とまではいえない」と判断して継続してきた理由は、XPath式を併用することでこれらの問題を緩和できるからである。

 GetElementsByTagNameメソッドの代わりに、XPathのSelectSingleNodeメソッドを使えば、上記の問題はほぼ解消される。SelectSingleNodeという名前も長いが、GetElementsByTagNameよりは短いし、読みやすく高機能なので許容範囲内である。

 これを使ってリスト1を書き直した例を以下に示す。

static void Main(string[] args)
{
  var dom = new XmlDocument();
  dom.LoadXml(xmldoc);

  var nsmgr = new XmlNamespaceManager(dom.NameTable);
  nsmgr.AddNamespace(
      "ex", "http://www.piedey.co.jp/example/linqtoxml200811");

  var node = dom.SelectSingleNode("//ex:name[@id='M']", nsmgr);
  if (node != null)
  {
    Console.WriteLine(node.InnerText);
  }
}
リスト2 XPathの力を借りたDOM (Mainメソッド以外はリスト1と同じ)

 しかし、このサンプルソースには別の憂うつが入り込んできている。

 XPath式で名前空間URIを持つ値の検索を行うには、XmlNamespaceManagerオブジェクトを作成しなければならないのである。しかも、このオブジェクトには厄介な点がいくつもある。

  • XmlNamespaceManagerは名前が長すぎる。GetElementsByTagNameメソッドの20文字に匹敵する19文字。従来は「var nsmgr」を「XmlNamespaceManager nsmgr」と記述する必要があったが、varキーワードが導入されて少しマシになっている。しかし、それでも長い
  • XmlNamespaceManagerオブジェクトのコンストラクタに指定したDOMドキュメントのNameTableと、実際に使用するDOMドキュメントの対応関係はプログラマーが注意して維持しなければならない。食い違ってもコンパイラは教えてくれない
  • AddNamespaceメソッドで追加した名前空間接頭辞(ここでは“ex”)と、XPath式の中に書き込んだ名前空間接頭辞が正しく同じであるよう、プログラマー自身が注意深く維持しなければならない。食い違ってもコンパイラは教えてくれない

 つまり、確かにSelectSingleNodeメソッドやSelectNodesメソッドのおかげで何とかDOMは使える水準にまで達してくれたわけではあるが、それでも名前空間が絡むと憂うつなものになってしまったのである。

E4XのXMLサポート

 さて、基本機能だけ使ってもうまくいかず、XPath式の力を借りて何とか使えていたDOMだが、そこにも不満の種が残った。

 もちろん、そのような不満を述べるプログラマーの数は多かったが、それがメジャーな主流としての世論を形成することはなかったように思う。なぜなら、XML界の世論とは、語る前に手を動かしてコードを書く誠実な技術者ではなく、手は動かさずに語ることだけで「僕の賢さ」をアピールする者たちによって形成される傾向があったからだ。もちろん、それは当然の成り行きといえる。語らずにコードを書く者はいくら人数が多くとも、語らないが故に世論を形成しないのである。

 しかし、一般論として他言語も含めれば、問題解決のための試みがなかったわけではない。

 例えば、ECMAScript for XML(E4X)は、ECMAScriptにXMLサポートを組み込むための言語仕様であり、DOMよりもはるかに簡潔にXML文書を扱える。E4Xは一部のWebブラウザやFlashですでにサポートされているので、体験することは容易である。

 ここでは、筆者が以前に作成したFlashによるクイズ・ゲームである「超クイズ」のソース・コード(ActionScript 3.0)を引用してみよう。

overClaimButton1.addEventListener(MouseEvent.CLICK, function(e)
  {
    descriptionText.text =
      quizeSet.set[quizeCount].d + "\n\n問題提供者連絡先:\n"
      + quizeSet.creator + ", " + quizeSet.contact;
  }
);
リスト3 超クイズのソース・コードから抜粋(ActionScript 3.0)

mainQuizeSet = <qset>

<title>テスト用内蔵クイズ</title>
<creator>オータム</creator>
<contact>autumn@example.com</contact>

<set>
<q>1980年前後の初期マイコンブーム時代に存在していない雑誌名はどれか</q>
<c>ASCII</c>
<c>I/O</c>
<c>bit</c>
<c>RAM</c>
<c>マイコン</c>
<a>0</a>
<d>ASCII, I/O, bit, RAM, マイコン、以上はすべて実在した雑誌名。ただしbitはマイコン雑誌ではなく、コンピュータ一般を扱う雑誌であり、出題意図を「マイコン雑誌」と誤読すると選ばれるわなとして用意されている。</d>
</set>
</qset>;
リスト4 クイズ定義データ(抜粋)

 DOMに慣れ親しんでいると、リスト3のコードがXML文書を扱うことがピンとこないかもしれない。変数quizeSetには、qset要素に当たるオブジェクトが代入されている。そこで、quizeSet.set[quizeCount].dは、qset要素の子要素「set」の中のquizeCount番目の要素を取り出し、さらにその子要素のd要素の内容を取り出している。つまり、リスト4の「ASCII, I/O……として用意されている。」という文字列が式を評価した値となる。また同様に、quizeSet.creatorを評価した値は「オータム」、quizeSet.contactを評価した値は「autumn@example.com」となる。

 このようなコードが記述可能であることは、想像力のある者なら容易に思い付くし、E4Xの初版は2004年6月に勧告されている以上、単なる想像ではなく現実に可能であることはこの時点で証明済みであったともいえる。

 では、ECMAScript(JavaScript)はそれでいいとして、C#の対応はどうなっているのだろうか。

 

 INDEX
  C# 3.0入門 特別編
  C#で実感する「LINQ to XML」の素晴らしさ
  1.XML最大の災厄/DOMの憂うつ/E4XのXMLサポート
    2.LINQ to XMLというブレークスルー/単純化されたXML文書生成


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間