当ホームページは多くの目次ページを有するが、それらは個々に作成・編集しているわけではなく、 ホームページ全体を下に示すようなただ一つのファイル toc.xml で管理している。
一つの複数の divセクションから成り、一つの divセクションは一つ以上の tableからなる。 このファイルtoc.xmlはhtmlブラウザで表示する訳ではないが、使い慣れた htmlタグを使用している。
現在はC言語プログラムでこのファイルを解釈し、無数の目次ファイルに展開しているが、 これを C# に置き換えてみよう。
現在は厳密に XMLの規格に従う必要はないが、C#では XML関連機能を使う関係から、 必要に応じて toc.xml の記述規則を修正する。
<div name="デジタル回路" id="dc"> <table> <caption>デジタル回路[Java Applet版]</caption> <tr><td>dc</td><td>dc</td><td></td></tr> <tr><td>シミュレータの操作方法</td><td>dc/manual.html</td><td></td></tr> [中略] </table> <table> <caption>デジタル回路[JavaScript版]</caption> <tr><td>dc2</td><td>dc2</td><td></td></tr> <tr><td>シミュレータの操作方法</td><td>dc2sim/manual01.html</td><td></td></tr> [中略] </table> [中略] </div>
XML 文書を扱う API として、DOM (Document Object Model) を使用する。 DOM は、W3C によって勧告されている API であり、XML 文書を予めメモリに読み込んで 仮想的な階層構造ツリー (DOM ツリー) を作成する。 そして、DOM は、階層化された XML 文書の要素や属性に対し直接アクセスするものである。
XML文書を読み込み、読み込んだ結果を表示するだけのプログラムを次に示す。
using System; using System.Xml; class XmlParser { static void Main() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("c:/mh/www/toc.xml"); Console.Write(xmlDoc.InnerXml); } }
XMLの規格に合わせるために、下のように、先頭に2行、末尾に1行を追加した。
<?xml version="1.0" encoding="shift_jis"?> <root> [ここは従来と同じ] </root>
従って、全体の階層構造は [root]-[div]-[table] である。 目次画面左の大項目目次は次のようにして生成している。
XmlNodeList nodeList = xmlDoc.SelectNodes("/root/div"); によって divノードが抽出できる。 このリストの要素 nodeDiv が一つの divノード(divタグ)にあたる。 divタグは <div name="デジタル回路" id="dc"> のように二つの属性 name と id を持っている。 この属性の値 "デジタル回路" は nodeDiv.Attributes.GetNamedItem("name").Value によって取り出すことができる。
XmlNodeList nodeList = xmlDoc.SelectNodes("/root/div"); for (int i = 0; i < nodeList.Count; i++) { XmlNode nodeDiv = nodeList[i]; string name = nodeDiv.Attributes.GetNamedItem("name").Value; string id = nodeDiv.Attributes.GetNamedItem("id").Value; sbHeader.AppendLine("<li><a href=\"" + id + ".html\">" + name + "</a></li>"); }
このdivノードは、1つ以上の子ノードを持つ。子ノードは tableノードである。 右側のページメニューを作成する場合にも、一部 divノードの情報が必要であり、 目次生成プログラムでは、スキャンを二度行っている。
nodeList.Countは divノードの個数、nodeDiv.ChildNodes.Count は divノードに含まれる tableノードの個数を表す。 同様に nodeTable.ChildNodes.Count は tableノードの子ノードの個数を表す。
tableノードの子ノードとしては captionノード、trノード、hrノードの三つのタイプがある。
これらは子ノードとしては対等である。このタグ名は Nameプロパティにより得られる。 captionノードは属性を持たず、目次のタイトルは 開始タグと終了タグに挟まれた文字列である。 この文字列はInnerTextプロパティによって取り出すことができる。 captionノードはtableノードnodeTableの最初の子ノードであるから、文字列は nodeTable.ChildNodes[0].InnerText となる。 trノードは三つの td ノードを子コードとして持つ。最後の子ノードが現時点では使用していない。 tdノードも属性は待たず、開始タグと終了タグで挟まれた文字列を目次生成に使用する。 この取り出しは先に述べたように、InnerTextプロパティを使う。
ページメニュー生成プログラムを下に示す。
for (int i = 0; i < nodeList.Count; i++) { XmlNode nodeDiv = nodeList[i]; string div = nodeDiv.Attributes.GetNamedItem("name").Value; string id = nodeDiv.Attributes.GetNamedItem("id").Value; string path = wwwdir + "/_toc/" + id + ".html"; StringBuilder sbToc = new StringBuilder(); sbToc.Append(header.Replace("$title$", div)); // table: captionがある. また, 先頭レコードは特別扱い for (int j = 0; j < nodeDiv.ChildNodes.Count; j++) { XmlNode nodeTable = nodeDiv.ChildNodes[j]; // caption string caption = nodeTable.ChildNodes[0].InnerText; // #0 XmlNode nodeDir = nodeTable.ChildNodes[1]; // #1 // 先頭レコード string code = nodeDir.ChildNodes[0].InnerText; // 現在は使用していない string dir = nodeDir.ChildNodes[1].InnerText; sbToc.AppendLine("<a name=\"" + dir + "\"></a>"); sbToc.AppendLine("<h1>" + caption + "</h1>"); sbToc.AppendLine("<ul style=\"list-style-type: disc;\">"); for (int k = 2; k < nodeTable.ChildNodes.Count; k++) { XmlNode nodeRecord = nodeTable.ChildNodes[k]; if (nodeRecord.Name == "hr") { sbToc.AppendLine("<hr/>"); continue; } // <tr> string title = nodeRecord.ChildNodes[0].InnerText; string url = nodeRecord.ChildNodes[1].InnerText; sbToc.AppendLine("<li><a href=\"../"+ url +"\">"+ title +"</a></li>"); } sbToc.AppendLine("</ul>"); genRefresh(caption, id, dir); } }
目次生成プログラム全体のソースコードを下に示す。
using System; using System.IO; using System.Text; using System.Xml; class XmlParser { const string idxheadfile = "c:/mh/www/idxhead.txt"; const string idxbodyfile = "c:/mh/www/idxbody.txt"; const string topfile = "c:/mh/www/top.txt"; const string xmlfile = "c:/mh/www/toc.xml"; const string wwwdir = "c:/mh/www"; static Encoding SJIS = Encoding.GetEncoding("Shift_JIS"); static void Main() { // xmlファイルを読み込む XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(xmlfile); // ① 全目次ファイル共通部. $title$は後で置き換え StringBuilder sbHeader = new StringBuilder(); sbHeader.Append(File.ReadAllText(idxheadfile,SJIS)); sbHeader.AppendLine("<!--先頭からここまで idxhead.txt をコピー-->"); sbHeader.AppendLine("<!--タイトルはプログラムにより生成-->"); sbHeader.AppendLine("<title>$title$</title>\r\n</head>"); sbHeader.AppendLine("<!--これよりidxbody.txtをコピー-->"); sbHeader.Append(File.ReadAllText(idxbodyfile,SJIS)); sbHeader.AppendLine("<!--これよりプログラムにより生成-->"); // 左の大項目メニュー部 XmlNodeList nodeList = xmlDoc.SelectNodes("/root/div"); // div for (int i = 0; i < nodeList.Count; i++) { XmlNode nodeDiv = nodeList[i]; string name = nodeDiv.Attributes.GetNamedItem("name").Value; string id = nodeDiv.Attributes.GetNamedItem("id").Value; sbHeader.AppendLine("<li><a href=\"" + id + ".html\">" + name + "</a></li>"); } sbHeader.AppendLine("</ul>"); sbHeader.AppendLine("</td>"); sbHeader.AppendLine("<td valign=\"top\">"); string header = sbHeader.ToString(); // メニューファイル共通部 // ② 個別部. 右のページメニュー部 string tail = "<!--これよりプログラムにより生成-->\r\n"; tail += "</td></tr></table>\r\n"; tail += "</div>\r\n"; tail += "</body>\r\n</html>\r\n"; // 最上位のページ string tocTop = header.Replace("$title$", "Hatada's Home Page"); tocTop += "<!--これよりtop.txtをコピー-->\r\n"; tocTop += File.ReadAllText(topfile,SJIS); tocTop += tail; File.WriteAllText(wwwdir + "/_toc/index.html", tocTop, SJIS); // 個々の目次ページ for (int i = 0; i < nodeList.Count; i++) { XmlNode nodeDiv = nodeList[i]; string div = nodeDiv.Attributes.GetNamedItem("name").Value; string id = nodeDiv.Attributes.GetNamedItem("id").Value; string path = wwwdir + "/_toc/" + id + ".html"; StringBuilder sbToc = new StringBuilder(); sbToc.Append(header.Replace("$title$", div)); // table: captionがある. また, 先頭レコードは特別扱い for (int j = 0; j < nodeDiv.ChildNodes.Count; j++) { XmlNode nodeTable = nodeDiv.ChildNodes[j]; // caption string caption = nodeTable.ChildNodes[0].InnerText; // #0 XmlNode nodeDir = nodeTable.ChildNodes[1]; // #1 // 先頭レコード string code = nodeDir.ChildNodes[0].InnerText; // 現在は使用していない string dir = nodeDir.ChildNodes[1].InnerText; sbToc.AppendLine("<a name=\"" + dir + "\"></a>"); sbToc.AppendLine("<h1>" + caption + "</h1>"); sbToc.AppendLine("<ul style=\"list-style-type: disc;\">"); for (int k = 2; k < nodeTable.ChildNodes.Count; k++) { XmlNode nodeRecord = nodeTable.ChildNodes[k]; if (nodeRecord.Name == "hr") { sbToc.AppendLine("<hr/>"); continue; } // <tr> string title = nodeRecord.ChildNodes[0].InnerText; string url = nodeRecord.ChildNodes[1].InnerText; sbToc.AppendLine("<li><a href=\"../"+ url +"\">"+ title +"</a></li>"); } sbToc.AppendLine("</ul>"); genRefresh(caption, id, dir); } sbToc.Append(tail); string newToc = sbToc.ToString(); string oldToc = File.ReadAllText(path, SJIS); if (newToc != oldToc) File.WriteAllText(path, newToc, SJIS); else Console.WriteLine("no change: " + path); } } static void genRefresh(string title, string id, string dir) { string url = (dir.IndexOf('/') > 0 ? "../" : "") + "../_toc/" + id + ".html#" + dir; string path = wwwdir + "/" + dir + "/index.html"; if (File.Exists(path)) return; StringBuilder sbHtml = new StringBuilder(); sbHtml.AppendLine("<html>"); sbHtml.AppendLine("<head>"); sbHtml.AppendLine("<title>" + title + "</title>"); sbHtml.AppendLine("<meta http-equiv=\"REFRESH\" content=\"0;url=" + url + "\">"); sbHtml.AppendLine("</head>"); sbHtml.AppendLine("<body>"); sbHtml.AppendLine("自動転送します。<br>"); sbHtml.AppendLine("転送されない場合はこちらをクリックして下さい。<br>"); sbHtml.AppendLine("<a href=\"" + url + "\">クリック</a>"); sbHtml.AppendLine("</body>"); sbHtml.AppendLine("</html>"); File.WriteAllText(path, sbHtml.ToString(), SJIS); } }