"DXML": 目次を XML から DHTML へ変換する

George Young
Microsoft Corporation

April 26, 1999

Download sample code この記事のソース コードをダウンロードする (zip形式, 5.88K)

私は統合的なサンプル、つまり数種類のテクノロジを1つの小さなアプリケーションにまとめたサンプルがとても好きです。

今月のCode Cornerでは、まさにこれを行おうと思います。つまり、Extensible Markup Language (XML)、Extensible Stylesheet Language (XSL)、JScript®、およびカスケーディングスタイルシート(CSS)を組み合わせて、階層的なDynamic HTML (DHTML)の目次(Table of Contents;TOC)を作るのです。このサンプルは意図的に単純な作りになっています。これらのテクノロジをすでに知っている読者は、このサンプルから、各種のテクノロジの組み合わせ方を学ぶことができるでしょう。また、これらのテクノロジを知らない読者は、このサンプルを見て関心を抱いてくれることを期待します。

XML は Web 開発者に対して、データ要素を「意味のある」タグに入れることによって、情報の移植性と柔軟性を高めることができます。Internet Explorer 5 の XSL サポートの強化により、XMLデータをブラウザ内に表示するのがはるかに簡単になりました。

われわれは、Web Workshopで、目次情報を格納するために XML をかなり前から使用しており、また XSL スタイルシートを使ってその情報をHTMLに変換してきました。また、スタイルシートは CSS および JScript ファイルにリンクを「書き込む」ので、XML から DHTML への変換を1ステップで行うことができます。データを XML に格納しておけば、1つの XSL スタイルシートを変更することで、すべての目次の出力フォーマットを簡単に変更することができます。

では、順番に XML、XSL、JScript、および CSS の4つのファイルを見ていきましょう。

目次を XML へ格納

このサンプルでは、Web 開発に関係する記事のリスト、すなわち「トピックス」を作成しています。個々の TOPIC 要素は、それを記述する TITLE と、URL から成ります。トピックスは TOPICS 要素の中で TYPE によってグループ化されています。第3の TOPICS 要素が TOPICS 要素自身を含んでいることに注意してください。webdev.xml ファイルの先頭にある <?xml:stylesheet type="text/xsl" href="list.xsl"?> という処理命令は、Internet Explorer 5 に対して、XMLファイルをブラウザで直接開いたときに、このスタイルシートを使って XML のレンダリングを行うように指示しています(これをサーバー上の ASP で行う方法については、このコラムの後の方で説明します)。

次にXMLデータを示します。

Listing 1: webdev.xml

                  
<?xml version="1.0"?>
<?xml:stylesheet type="text/xsl" href="list.xsl"?>

<TOPICLIST TYPE="Web Dev References">

<TOPICS TYPE="DHTML">
  <TOPIC>
    <TITLE>Objects</TITLE>
    <URL>/workshop/author/dhtml/reference/objects.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Properties</TITLE>
    <URL>/workshop/author/dhtml/reference/properties.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Methods</TITLE>
    <URL>/workshop/author/dhtml/reference/methods.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Events</TITLE>
    <URL>/workshop/author/dhtml/reference/events.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Collections</TITLE>
    <URL>/workshop/author/dhtml/reference/collections.asp</URL>
  </TOPIC>
</TOPICS>

<TOPICS TYPE="CSS">
  <TOPIC>
    <TITLE>Attributes</TITLE>
    <URL>/workshop/author/css/reference/attributes.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Length units</TITLE>
    <URL>/workshop/author/css/reference/lengthunits.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Color table</TITLE>
    <URL>/workshop/author/dhtml/reference/colors/colors.asp</URL>
  </TOPIC>
</TOPICS>

<TOPICS TYPE="XML">

  <TOPICS TYPE="XML DOM">
    <TOPIC>
      <TITLE>Developer's guide</TITLE>
      <URL>/xml/XMLGuide/default.asp</URL>
    </TOPIC>
    <TOPIC>
      <TITLE>Objects</TITLE>
      <URL>/xml/reference/scriptref/XMLDOM_Objects.asp</URL>
    </TOPIC>
  </TOPICS>

  <TOPICS TYPE="XSL">
    <TOPIC>
      <TITLE>Developer's guide</TITLE>
      <URL>/xml/XSLGuide/default.asp</URL>
    </TOPIC>
    <TOPIC>
      <TITLE>Elements</TITLE>
      <URL>/xml/reference/xsl/XSLElements.asp</URL>
    </TOPIC>
    <TOPIC>
      <TITLE>Methods</TITLE>
      <URL>/xml/reference/xsl/xslmethods.asp</URL>
    </TOPIC>
    <TOPIC>
      <TITLE>Pattern syntax</TITLE>
      <URL>/xml/reference/xsl/XSLPatternSyntax.asp</URL>
    </TOPIC>
  </TOPICS>

</TOPICS>

</TOPICLIST>

XSL スタイルシートを使った XML から HTML への変換

XML は、直接コード内で操作することもできますが、XSL 使ったコード外部の宣言文を利用する方法によって、きわめて少ないコード(および苦痛)で、XML を表示出力(このケースでは HTML)に変換することができます。XSL のおかげで、木構造を渡り歩くコードを大量に書く必要がなくなります。これは、XML ファイルの中に複雑な入れ子階層がある場合には特に便利です。このケースでは、TOPICS レベルの数は未知ですが、XSL はこれをきれいに処理してくれます。

次に XSL コードを示します。

Listing 2: list.xsl

                  
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">
  <HTML>
  <HEAD>
  <TITLE>List <xsl:value-of select="TOPICLIST/@TYPE" /></TITLE>
  <LINK REL="stylesheet" TYPE="text/css" HREF="list.css" />
  <SCRIPT TYPE="text/javascript" LANGUAGE="javascript" SRC="list.js"></SCRIPT>
  </HEAD>
  <BODY>
  <BUTTON ONCLICK="ShowAll('UL')">Show All</BUTTON>
  <BUTTON ONCLICK="HideAll('UL')">Hide All</BUTTON>
  <H1>List <xsl:value-of select="TOPICLIST/@TYPE" /></H1>
  <UL><xsl:apply-templates select="TOPICLIST/TOPICS" /></UL>
  <P><BUTTON ONCLICK="window.alert(document.body.innerHTML);">View HTML</BUTTON></P>
  </BODY>
  </HTML>
</xsl:template>

<xsl:template match="TOPICS">
  <LI CLASS="clsHasKids"><xsl:value-of select="@TYPE" />
  <UL>
  <xsl:for-each select="TOPIC">
    <LI>
    <A TARGET="_new">
      <xsl:attribute name="HREF">
        https://msdn.microsoft.com<xsl:value-of select="URL" />
      </xsl:attribute>
      <xsl:value-of select="TITLE" />
    </A>
    </LI>
  </xsl:for-each>
  <xsl:if test="TOPICS"><xsl:apply-templates /></xsl:if>
  </UL>
  </LI>
</xsl:template>

</xsl:stylesheet>

これからわかるように、多数の HTML タグの間に、少数の "xsl:" 要素が散らばっています。XSL ファイルの先頭では、これが XSL ファイルであることをプロセッサに対して明示的に指定し、<xsl:template match="/"><xsl:template match="TOPICS"> という2つの xsl:template 要素ブロックを宣言しています。

ルートの変換

最初のブロック、**<xsl:template match="/">**は、プロセッサに対して、1つのスラッシュで示される XML ファイルのルートから変換を開始するように指示しています。このブロックでは2つの処理を行っています。まず、HTML 出力の構造と汎用的な要素を「書き込み」、次にその他のテンプレートブロックを条件的に割り当てています。TOPICLIST 要素の TYPE 属性値(Web Dev References)を、<TITLE> と <H1> の HTML 要素 に流し込みます。また、DHTML の外観と振る舞いを与えるスクリプト(list.js)およびスタイルシート(list.css)ファイルも割り当てます。上の方には、ユーザーがすべての目次ノードの表示と非表示を切り換えるための、スクリプトファイルの中の2つの関数を使用する一対のボタンを表示し、下の方にも HTML ソースをアラートボックスの中に表示するためのボタンを追加します。

この最初のブロックの最も興味深い要素は、<xsl:apply-templates select="TOPICLIST/TOPICS" /> です。これはプロセッサに対して、ルートの TOPICLIST ノードの子要素である、XML 文書のすべての TOPICS ノードを変換するように指示しています。上記のXMLファイルを見ると、これが TYPE "DHTML"、"CSS"、および"XML"の TOPICS ノードから構成されていることがわかります。この時点で、XSL プロセッサはこの最初のブロックから分岐し、スタイルシートの中の、select="TOPICLIST/TOPICS" 属性にマッチする別の xsl:template ブロックを探します。見当がついた方もいらっしゃるかと思いますが、これが第2のブロックです。

TOPICLISTの変換

われわれの第2のテンプレートブロック <xsl:template match="TOPICS"> から、お楽しみが始まります。コンテンツが第1ブロックからこのブロックに渡された3つの TOPICS ノードがそれぞれ、ここで処理されます。まず、HTML のリスト項目要素(LI)を開き、これに "clsHasKids" という CLASS 属性を与えます。この属性は後に DHTML の中で使用します。その後、<xsl:value-of select="@TYPE" /> を使って、処理中の TOPICS ノードの TYPE を出力します。ノードの値を XSL で取得するために、xsl:value-of 要素が使用されています。属性の値を取得する場合には、属性名の先頭に"@"を付けます。

次に、現在の TOPICS 要素のすべての子要素を含むことになるHTMLの順序なしリスト(<UL>)をオープンします。また、個々のTOPICの子要素について、その要素(この場合では TOPIC)にコンテキストを切り換える xsl:for-each ループ構造を使用して、<LI>要素を作成し、その TITLE を A リンクで囲んで出力します。この要素の HREF 属性は、URL 要素の値に設定されます。XSL で HTML 出力要素に動的に属性を追加するためには、xsl:attribute を使用し、その name 属性を、作成したい HTML 属性に設定します。つまり、<xsl:attribute name="HREF"> ということになります。HREF 値にはサーバー名 https://msdn.microsoft.com と、TOPIC URL に含まれている仮想ルート URL、<xsl:value-of select="URL" /> を格納します。

これでトップグループのすべてのリンクを書き出すことができましたが、TOPICS の子要素の TOPICS 、つまりトピックが入れ子になったリストについての処理は行っていません。これは XSL が特に効果を発揮する分野です。XSL はすべての再帰処理を1つの文で行ってくれるのです! 第2ブロックに入って TOPICS 要素の処理を開始したときに開いたしたコンテナ <UL> とその親要素 <LI> を閉じるする前に、<xsl:if test="TOPICS"><xsl:apply-templates /></xsl:if> を宣言します。テストしようとしている要素へコンテキストを変更する xsl:if 条件を使うことで、木構造に対し、子要素の TOPICS ノードが存在するかどうかを尋ねます。存在している場合には、現在のコンテキストと照合を行うようにプロセッサに指示する <xsl:apply-templates /> を呼び出します。ここでは TOPICS について尋ねたので、子要素が存在する場合には、再びこの第2ブロックで照合が行われることになります。したがって、第3の TOPICS 要素である "XML" のケースでは、2つの子要素 TOPICS 要素のマッチングを行い、この XSL ブロックに再び入り、これらを HTML に出力することになります。適正は入れ子構造は自動的に維持されます。クールでしょう?

これが、変換を行ったときの生の HTML 出力 です。

これで XSL コードの説明は終わりです。XML文書を再帰的に処理することで、入れ子状の HTML リストの集まりを作成してきました。これで出力が得られたので、次は動的な目次を実現する .js と .css のコードを簡単に説明します。

スクリプトによるリストノードの表示制御

4つの関数が、HTML 版の目次のすべての「アクション」を処理します。ShowAll()HideAll() は、tagName パラメータを取得し、ページ上にそのタグの(最初のものを除く)すべての要素のコレクションを構築し、個々の style.display プロパティを "block" (表示)または "none" (非表示)に設定します。最初の2つの関数、document.onclick()GetChildElem() は、協力して、親要素の <LI> がクリックされた <UL> を表示または非表示にします。実際には XSL の中の個々の <LI> について ONCLICK 属性を書くことも可能ですが、コラム仲間である DHTML Dude と同様に、われわれも Internet Explorer のイベントバブリングの抽象性、パワー、および柔軟性を愛しているのです。

document.onlclick() は、文書の onclick() イベントをこの関数にバインドするので、文書上でのすべてのクリックがここで処理されます。ここでは、<UL> という子要素を持つ <LI> がクリックされたときにだけ処理を行いたいので、まずユーザーが "clsHasKids" という className を持った要素をクリックしたのかどうかをチェックします。次に、ある程度の予防的な例外処理を行うために、GetChildElem() を使用して、正当な子要素 <UL> への参照を返すか、これが存在しない場合には false を返します。最後に、JScript の3項から成る条件式を使用して、style.display を現在の設定を反転します。

Listing 3: list.js

                  
function GetChildElem(eSrc,sTagName)
{
  var cKids = eSrc.children;
  for (var i=0;i<cKids.length;i++)
  {
    if (sTagName == cKids[i].tagName) return cKids[i];
  }
  return false;
}

function document.onclick()
{
  var eSrc = window.event.srcElement;
  if ("clsHasKids" == eSrc.className && (eChild = GetChildElem(eSrc,"UL")))
  {
    eChild.style.display = ("block" == eChild.style.display ? "none" : "block");
  }
}

function ShowAll(sTagName)
{
  var cElems = document.all.tags(sTagName);
  var iNumElems = cElems.length;
  for (var i=1;i<iNumElems;i++) cElems[i].style.display = "block";
}

function HideAll(sTagName)
{
  var cElems = document.all.tags(sTagName);
  var iNumElems = cElems.length;
  for (var i=1;i<iNumElems;i++) cElems[i].style.display = "none";
}

CSS を使って見栄えを良くする

最後に、CSS を使って 目次の外観を微調整します。このサンプルの CSS はきわめて単純です。Web Workshop の 目次のように、これよりも複雑な(そして見栄えの良い)ものを簡単に作成することも可能です。ここで問題となるスタイル規則は、<UL> と <LI> だけです。まず、<LI> の子要素であるすべての <UL> 要素の display の初期設定を "none" に設定し、クリックすると何かが起こる(このケースでは子の <UL> の表示と非表示)ことが分かるように、className "clsHasKids" の <LI> 要素に「手」のカーソルを設定し、<UL> と <LI> の list-style-type とマージンを微調整します。

Listing 4: list.css

                  
BODY { font-family:verdana; font-size:70%; }
H1 { font-size:120%; font-style:italic; }

UL { margin-left:0px; margin-bottom:5px; }
LI UL { display:none; margin-left:16px; }
LI { font-weight:bold; list-style-type:square; cursor:default; }
LI.clsHasKids { list-style-type:none; cursor:hand; }

A:link, A:visited, A:active { font-weight:normal; color:navy; }
A:hover { text-decoration:none; }

BUTTON { font-family:tahoma; font-size:100%; }

様々な 目次ビューの表示

これまで述べてきたように、データを XML に格納することの最も大きな利点の1つは、異なるスタイルシートを使って変換することで、同じデータの異なるビューを簡単に生成できるということです。ダウンロード可能なサンプルコードには、4つの XSL スタイルシート(およびスクリプトとスタイルファイル)が含まれています。TOPIC を視覚的な階層を持つ DIV としてレンダリングする divs.xsl、すべての TOPIC を同じ階層にレンダリングするflat.xsl、実際の URL を TABLE に出力する links.xsl、および list.xsl に変更を加えて、よりクリーンな HTML ソースを生成する list_pp.xsl です。

すべてのブラウザへの対応: サーバー上での変換

このサンプルは、Internet Explorer 5 がインストールされているクライアント用に、XML mime-type を使って書かれています。しかし、クロスブラウザの環境下では、サーバー上で XML を変換して送信することもきわめて簡単です。次のコード(やはりダウンロード可能な .zip ファイルに含まれています)はこれを行います。サンプル ファイルを Internet Information Server(IIS) サーバーに保存し、webdev.asp を読み込んで、オプションのクエリー文字列パラメータとして XSL ファイル名を渡します。例: http://<yourmachinename>/codecorner/xml/webdev.asp?xsl=divs.xsl。また、各種の XML/XSL の組み合わせへのリンクのリストを含んでいる default.asp ページをロードしてもかまいません。

Listing 5: webdev.asp

                  
<% @LANGUAGE="JScript" %>
<%
  var sXml = "webdev.xml"
  var sXsl = new String(Request.QueryString("xsl"));
  if ("undefined" == sXsl) sXsl = "list.xsl"
  
  var oXmlDoc = Server.CreateObject("MICROSOFT.XMLDOM");
  var oXslDoc = Server.CreateObject("MICROSOFT.XMLDOM");
  oXmlDoc.async = false;
  oXslDoc.async = false;
  oXmlDoc.load(Server.MapPath(sXml));
  oXslDoc.load(Server.MapPath(sXsl));
  Response.Write(oXmlDoc.transformNode(oXslDoc));  
%>

関連情報

XML から DHTML を作成する方法や、XML のその他の興味深い使い方の詳細については、MSDN Online Web Workshop の XML エリア、特にこのサンプルで強調表示しているリンクを参照してください。また、同僚の Charlie Heinemann が定期的に執筆している MSDN Online Voices のコラム Extreme XML にも目を通してください。

George Young は、Internet Explorer チームの開発者であり、以前は Windows 2000、MSDN Online、および Site Builder Network サイトの開発を手がけました。余暇には、Windows Media Player でメキシコのラジオ局に耳を傾け、ニューオーリンズからワシントン州レドモンドへキャデラックで通勤しています。