当ホームページは多くの目次ページを有するが、それらは個々に作成・編集しているわけではなく、 ホームページ全体を下に示すようなただ一つのファイル 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);
}
}