スポンサーリンク

JScript製の簡易 HTML テンプレートエンジン (Webサイト作成時に,画面の共通部品を外部読み込み)

簡易なHTMLテンプレートエンジンのようなもの。


例えばHTMLファイル内に「 #header# 」と書くと,その部分が header.html の内容で置き換えられたような新HTMLが生成される。


つまり,ページ間の共通部分をべた書きしないで済ませるための,よくある置換(include)スクリプト。

  • CMSが使えない
  • CGI/SSIも使えない
  • フレームも使えない
  • わりと小規模

というような時に役立つのでは。(サイトのモックをぱっぱと作ってしまいたいなど)


サンプルはこちらからダウンロードできます。

http://www.name-of-this-site.org/codi...

(.batをダブルクリックすると,devフォルダ内の部品をもとに,publishフォルダ内にwebページを作成)


以下WSHのソースコードと,利用例。


ページの雛型:(#〜〜#がincludeを表す)


dev/pages/index.html

<html>
<head>
	<title>トップページ</title>
</head>
<body>

#css#

#header#

<div id="contents">

	ようこそ<br>

	<a href="./hoge/a.html">こちらへどうぞ</a><br>

</div>

#footer#

</body>
</html>

この「#〜〜#」の部分に相当する「部品」を,elementsフォルダに置く。


dev/elements/css.html

<!-- 共通CSS -->

<style type="text/css">

a:link    { color : #3333CC }
a:visited { color : #3333CC }
a:hover   { color : #3333CC }
a:active  { color : #3333CC }

body{
	padding-left : 30px;
	padding-right: 30px;
	background-color : dodgerblue;
}

#contents{
	border : solid 1px;
	border-collapse : collapse;
	height : 300px;
	padding : 20px;
	background-color : aliceblue;
}

#header{
	color : white;
	text-align : right;
}

#footer{
	padding : 5px;
	text-align : right;
}

</style>


dev/elements/header.html

<!-- 共通ヘッダ -->

<br>

<div id="header">
	TEL 03-xxxx-yyyy
</div>

<br>


dev/elements/footer.html

<!-- 共通フッタ -->

<br>

<div id="footer">
	<a href="mailto:foo@bar">お問い合わせ</a>
</div>

<br>


そして,上記の部品を組み合わせるスクリプト。


publish_site.js

/*

	ページ部品を組み合わせて,Webサイト全体のHTMLを構築するスクリプト
	
	
	使い方:
		・publish_site.batをダブルクリック。
		
	ファイル構成:
		・dev\elements以下に,共通部品を置く。
		・dev\pages以下に,HTMLを置く。
		・それぞれのHTMLの中では,
			#(部品名)#
		  と書けば,指定した部品HTMLの内容を読み込める。
		  例えば,
			#footer#
		  と書くと,dev\elements\footer.html の内容がそこに埋め込まれる。

*/


// ----------  前処理  ----------

// デバッグ用:ポップアップ出力
function log( s )
{
	WScript.Echo( s );
}

// デバッグ用:標準出力
function out( s )
{
	var o = WScript.StdOut;
	o.WriteLine( s );
}

// 設定
var dir_dev			= "dev"; // 開発用フォルダ
var dir_pages		= dir_dev + "\\pages"; // ページ
var dir_elements	= dir_dev + "\\elements"; // 共通部品
var dir_publish		= "publish"; // 完成品用フォルダ


// 定数
var ForReading   = 1; // 読み込み
var ForWriting   = 2; // 書き込み(上書き)
var ForAppending = 8; // 書き込み(追記)
	// http://language-and-engineering.hatenablog.jp/entry/20081017/1224168811


// このディレクトリのパスを取得
var ws = WScript.CreateObject("WScript.Shell");
var arr_dir_script = WScript.ScriptFullName.split("\\");
	// http://wsh.style-mods.net/ref_wscript/index.htm
arr_dir_script.pop();
var dir_script = arr_dir_script.join("\\");
	//log( dir_script );



// ----------  共通部品を読み込み  ----------



// 全エレメントを読み込み
ws.CurrentDirectory = dir_script + "\\" + dir_elements;
var proc = ws.Exec("cmd.exe /c dir /s /b *.html");
var res = proc.StdOut.ReadAll().split("\r\n");
	// 注:ここで"\n"だけにするとあとで\rが残ってひどい目にあう
var fso_r = WScript.CreateObject( "Scripting.FileSystemObject" );
// 登録用の配列
var arr_elems = new Array();
var obj_elems = {};
for( var i = 0; i < res.length; i ++ )
{
	var elem_name = res[i];
	
	// 有効なファイル名か
	if( elem_name.length < 1 )
	{
		continue;
	}
		//log( elem_name );
	
	// 全行読み出し
	var txt_r = fso_r.OpenTextFile( elem_name, ForReading );
	var str = "";
	while( ! txt_r.AtEndOfStream )
	{
		str += txt_r.ReadLine() + "\r\n";
			// 注:ここで\r\nの付加を忘れるとあとでひどい目にあう
	}
	txt_r.Close();
	
	// ファイル名と内容を登録
	var arr_elem_key = elem_name.split("\\");
	var elem_key = arr_elem_key[ arr_elem_key.length - 1 ].replace(".html", "");
	arr_elems.push( { 
		"name" : elem_key,
		"str"  : str
	});
	obj_elems[ elem_key ] = str;
}


// エレメント内で置換
for( var i = 0; i < arr_elems.length; i ++ )
{
	var target_element = arr_elems[i].name;
	var target_str = arr_elems[i].str;
	for( var j = 0; j < arr_elems.length; j ++ )
	{
		target_str = target_str.replace(
			new RegExp( "#(.+)#", "gi" ),
			// 1 番目にマッチした文字列($1)をキーにして値を返す
			function(){
				return obj_elems[ arguments[ 1 ] ]; 
			}
		);
			// http://language-and-engineering.hatenablog.jp/entry/20080924/1222174957
			// http://itmst.blog71.fc2.com/blog-entry-74.html
	}
	
	// 置換済み文字列を再登録
	obj_elems[ target_element ] = target_str;
}



// ----------  ページを生成  ----------



// 全ページを完成品側にコピー
ws.CurrentDirectory = dir_script;
proc = ws.Exec("cmd.exe /c xcopy /s /h " + dir_pages + "\\* .\\" + dir_publish + "\\");
	// xcopy /s /h dev\pages\* .\publish\
	// http://language-and-engineering.hatenablog.jp/entry/20081001/1222857265
res = proc.StdOut.ReadAll().split("\r\n");


// 全ページを読み込み
ws.CurrentDirectory = dir_script + "\\" + dir_publish;
proc = ws.Exec("cmd.exe /c dir /s /b *.html");
res = proc.StdOut.ReadAll().split("\r\n");
// 登録用の配列
var arr_pages = new Array();
var obj_pages = {};
for( var i = 0; i < res.length; i ++ )
{
	var page_name = res[i];
	
	// 有効なファイル名か
	if( page_name.length < 1 )
	{
		continue;
	}
	
	// 全行読み出し
	var txt_r = fso_r.OpenTextFile( page_name, ForReading );
	var str = "";
	while( ! txt_r.AtEndOfStream )
	{
		str += txt_r.ReadLine() + "\r\n";
	}
	txt_r.Close();
	
	// ファイル名と内容を登録
	arr_pages.push( { 
		"name" : page_name,
		"str"  : str
	});
	obj_pages[ page_name ] = str;
}


// 全ページを置換
var fso_w = WScript.CreateObject( "Scripting.FileSystemObject" );
for( var i = 0; i < arr_pages.length; i ++ )
{
	var target_page = arr_pages[i].name;
	var target_str = arr_pages[i].str;
	for( var j = 0; j < arr_elems.length; j ++ )
	{
		target_str = target_str.replace(
			new RegExp( "#(.+)#", "gi" ),
			function(){
				return obj_elems[ arguments[ 1 ] ]; 
			}
		);
	}
		//log( target_str );
	
	// 置換済み文字列を書き込み
	fso_w.DeleteFile( target_page );
	fso_w.CreateTextFile( target_page );
	var txt_w = fso_w.OpenTextFile( target_page,   ForWriting );
	txt_w.WriteLine( target_str );
	txt_w.Close();
}

上記のスクリプトを実行するためのバッチ

publish_site.bat

@echo off
rem 繰り返し実行可能なコンパイルバッチ
rem http://language-and-engineering.hatenablog.jp/entry/20081208/1228708657

:start
echo パブリッシュします・・・


rem 完成品用のディレクトリをクリーン
if exist "publish" rmdir /s /q publish
mkdir publish


rem パブリッシュ
cscript.exe publish_site.js
echo 完了

rem 
echo.
set userkey=
set /p userkey=終了する (Enter) / 再度パブリッシュ (p + Enter) ?
if not '%userkey%'=='' set userkey=%userkey:~0,1%
if '%userkey%'=='p' goto start
goto quit


rem 終了
:quit


実行すると冒頭の画像のようなページが出力される。

コード概説:

  • replaceの第二引数は関数を置ける
  • フォルダ階層の確認なし強制消去は rmdir /s /q
  • xcopyコマンドを直接走らせている部分があるが,こうやって適宜コマンドの実行結果をWSH中で「流用」すると,コマンドプロンプトの不便さを補ったコードが短く書けると思う。


なお,部品の中で他の部品を読み込むこともできる。ただし1階層だけ。

補足

一般に使われているまともなテンプレートエンジンとしては,JavaならVelocityPHPならsmartyがある。

Webアプリケーション開発における「テンプレート・エンジン」活用のススメ
http://www.itarchitect.jp/enterprise/...

PHP とテンプレートエンジン (Smarty/patTemplate)
http://www.gadgety.net/shin/tips/unix...

テンプレートエンジン:JavaScript版ERB、Embedded JavaScript
http://japan.zdnet.com/news/devsys/st...

いずれも変数名を値に展開するパターン。

つまり,素のPHPはそれだけでテンプレートエンジンのようなものだ。


参考:

「独自タグ+コメント」パターン
http://wiki.mesolabo.com/?Log%2F%E4%B...

サーバアクセス時ではなく,ページのファイル生成時にテンプレートを活用する方式を取ったのが今回のスクリプト。

下記のような場合に使う。

HTMLでソースを共通化する
http://qa.asahi.com/qa1502530.html

HTMLでのWEBページ作成で、「共通モジュール」のような考え方で
ソースを共通化したいと考えています。
どのようにすればよいのでしょうか?

補足

既存のCMS製品の中で,エクスポート機能を持ったものがあればそれで代用できる。

静的HTML出力型のCMS「Geego」
http://www.moongift.jp/2008/07/geego/