HTMLのひな形

概論

ソースを変更するだけで実行結果が簡単に代えられる Perl と違って、C/C++などのコンパイル言語では、HTML をソースコードに埋めると保守が大変です。部分的な変更を加えるだけで、いちいちコンパイルし直す必要が出てきます。

そこで、出力結果の雛形をあらかじめ外部の HTML ファイルとして作っておいて、そのファイルの特定の文字列を、動的に変更していくという考えが自然に浮かぶはずです。一般的なサーバーにおける SSI や、C++ Builder でいう TPageProducer がまさにこれです。

これからは、XMLで書くべきかもしれませんが(^^;

  <P>今日は &%date; です。</P>
                    ↓
  <P>今日は 2017/Dec/14 です。</P>

C++ Builder では、これを TPageProducer というコンポーネントで簡単に書けるのですが、ここではその簡易版を実際に作ればよいわけです。

仕様

まずは仕様を決めましょう。

…と思っていましたが、ここでは岡山白陵音楽部OB会で実際に使用しているテンプレートを解説します。

以下が実際に使用しているテンプレート(一部)です。

<template id="header">
    <?xml version="1.0" encoding="ISO-2022-JP"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html lang="ja" dir="ltr" xml:lang="ja-JP">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-2022-JP" />
        <meta http-equiv="Content-Style-Type" content="text/css" />
        <link rel="stylesheet" href="/~okahaku/css/std.css" type="text/css" title="標準CSS" />
        <link rel="contents" href="/~okahaku/index.html" title="目次" />
        <link rel="start" href="/~okahaku/guesthouse/bbs/bbs.cgi" />
        <link rev="made" href="mailto:okahaku@dx.sakura.ne.jp" />
        <title>Music Club Alumni / BBS / &%title;</title>
    </head>

    <body>

    <h1>
        <img src="/~okahaku/image/title.png" width="556" height="59" border="0"
            alt="岡山白陵音楽部OB会" title="岡山白陵音楽部OB会" vspace="8" />
    </h1>

    <h1 class="nodisplay">岡山白陵音楽部OB会</h1>

    <hr class="nodisplay" />

    <div class="location"> 場所:
        <a href="/~okahaku/">トップ</A> >
        <a href="/~okahaku/guesthouse/">ゲストハウス</A> >
        <a href="&%cgi;">BBS掲示板</a> >
        &%jtitle;
    </div>


    <h2>BBS掲示板</h2>

</template>

ここで、<template id="〜"> から </template> までが一連のテンプレートで、一つのファイルに複数のテンプレートを一緒に保存することができます。

使用時には、たとえば header の部分を表示するときは、<template id="header"> をファイルの中から探し出し、それ以降 </template> がでてくるまで一行ずつ調べ、&%...; があればそれを置換して表示します。

そこで、このシステムは以下のようにコーディングできます。

void scan(ifstream& file, string id, map<string, string>& replace_map)
{
    file.seekg(0, ios::beg);
    string line;
    string template_begin = "<template id=\" + id + "\">";
    
    // <template id="..."> を探す
    file.getline(line);
    while (line != template_begin)
        file.getline(line);

    // </template> がでるまで読み出す
    while (getline(file, line).good() && line != "</template>") {
        unsigned int xpos = 0;
        while ((xpos = line.find("&%", xpos)) != std::string::npos) {
            unsigned int tag_end_pos = line.find(';', xpos+2);
            if (tag_end_pos != std::string::npos) {
                string item = line.substr(xpos+2, tag_end_pos - (xpos+2));
                line.erase(xpos, tag_end_pos - xpos + 1);
                line.insert(xpos, replace_map[item]);
                xpos += replace_map[item].length();
            } else {
                // 対応する ';' が見つからない場合は、一文字進める
                xpos++;
            }
        }
        cout << line << endl;
    }
}

なお、map<string, string> replace_map の使い方ですが、

replace_map["cgi"] = "/path/self.cgi";

とすれば、テンプレート中の "&%cgi;" という文字列が "/path/self.cgi" という文字列に置換されます。

ちなみに、replace_map の中に置換するべきキーがない場合は、その部分が削除されます。というのも、map はキーが存在しない場合は勝手に値を作ってくれるため、"" で初期化された文字列が挿入されるためです。

また、先述の Borland C++ Builder などの TWebProducer と互換性を持たせたい場合は、"&%" の部分を "<!" にして、';' を '>' に変えます。ただ、Builder の TPageProducer の場合、テンプレートは1ファイルに1テンプレートしか保存できず、いろいろな形態を持つ CGI などには向いていません…と思う。