ここではデザインの定義ファイルからデータを置換したり変換したりしながら、HTMLを作成します!
以前の記事 で紹介したデザインのコードを拡張しました。
今回は、ソースをダウンロードするようにしました。
解凍は、Cドライブの直下など、日本語のフォルダ名が入らないパスにおいてください。
すぐ遊べるように、DevC++とVC++のプロジェクトファイルも入れておきました。
それぞれ以下のファイルです。
DevC++ ⇒ stl_stylesheet/Project1.devVC++ ⇒ stl_stylesheet/stylesheet_vcprj/stylesheet.sln
その他のコンパイラの場合は、各自で頑張ってコンパイルしてみてください。
標準C++とコンパイル不要なBoostライブラリしか使用していないので多分ほとんどのコンパイラで
コンパイルできると思います。
【準備】
Boostをインストールしてください。
コンパイル不要のライブラリしか使用していないので、コピーするだけです。
方法はこちら 。
【使用例サンプル (main.cppの再掲)】
try{
DesignStyleFactory factory(wordKind("sjis"));
//デザインの内容を解釈してスタイルクラスを作成する
DesignStylePtr styles = factory.createFromFile(inputFile);
//置換データの作成
DesignDataMapPtr dd_map(new DesignDataMap);
(*dd_map)["cat"] << string("にゃー");
(*dd_map)["cat-cnt"] << 5;
(*dd_map)["dog"] << string("わんわんkyu-");
(*dd_map)["remarks"] << string("メール多すぎ!\n内容無さすぎ!!");
(*dd_map)["time"] << time(NULL);
//中略
//データ出力
ofstream ofs("out.html");
if(!ofs.is_open()){
cout << "write file not opened." << endl;
return;
}
styles->output(ofs, dd_map);
}catch(runtime_error& e){
cout << "エラー発生:" << e.what() << endl;
}catch(exception& e){
cout << "エラー発生:" << e.what() << endl;
}
【入力スタイルファイル(test.txt)】
<html>
<nana:set name="$screen_name" select="'トップ画面'" />
<nana:include file="include/header.txt"/>
<body>
<table border="1">
<tr>
<td>No.</td><td>タイトル</td><td>メールアドレス</td><td>備考</td>
</tr>
<nana:for-each select="mails" cnt-var="$i">
<nana:set name="$mod" select="$i" format="mod(2)"/>
<nana:if select="$mod" cmp="eq" value="0">
<nana:set name="$color" select="'#5500AA'" />
</nana:if>
<nana:if select="$mod" cmp="ne" value="0">
<nana:set name="$color" select="'#FF00FF'" />
</nana:if>
<tr bgcolor="<nana:value-of select="$color" />" >
<td>
<nana:value-of select="$i" /></td>
<td><nana:value-of select="subject"/></td>
<td><nana:value-of select="address"/></td>
<td><nana:value-of select="/remarks" escape="html"/></td>
</tr>
</nana:for-each>
</table>
<br>
<br>
時間=<nana:value-of select="time" format="to_date(%Y/%m/%d %H:%M:%S 日本時間)" escape="html"/><br/>
<table border="1">
・・・中略
</body>
</html>
【出力ファイル(out.html)】
No. | タイトル | メールアドレス | 備考 |
0 | that shared pointer | jalkfa@tt.co.jp | メール多すぎ! 内容無さすぎ!! |
1 | that shared pointer | jalkfa@tt.co.jp | メール多すぎ! 内容無さすぎ!! |
2 | 実体メールです | jittai@tt.co.jp | メール多すぎ! 内容無さすぎ!! |
3 | 実体メールです | jittai@tt.co.jp | メール多すぎ! 内容無さすぎ!! |
時間=2008/07/03 21:11:46 日本時間
23 | yah |
02 | heyhe-i |
【説明】
DesignDataやDesignStyleのコードの説明は、以前の記事 と変わりません。
以前の記事では使用しづらかったと思います。
これらを使用しやすくしたのがこのソースです。
是非、いろいろいじって遊んでみてください。
クラス | 内容 |
DesignData | デザインデータの基底クラス。 スタイルに渡すデータを保存しておくクラスです。 |
DesignDataPath | デザインデータの指定の位置を表します。 保持している位置からデータも取り出せます。 |
DesignStyle | デザインデータからデザインを作成するスタイルです。 |
DesignStyleFactory | 文字列からスタイルを作成するファクトリーです。 |
詳しくは、doxygenでdocを作成してみてください。
doxygenの設定ファイルも入っていますので(^^)
doxygenについてはこちら 。
<注意>
属性値の指定は="の間に空白を入れられません。
例えば、<nana:value-of select="cat"/>で青字は連続している必要があります。
空白を入れられるようにするのが面倒でしたので。。すみません。
各自でソースをいじって空白可能にしてみても面白いと思います。
【使用できるタグ】
<nana:value-of select="データ名" format="関数" escape="エスケープ"/> |
デザインデータ内からデータ名を検索して、 値を出力します。 データ名に"cat"を指定すると、 ここでは"にゃー"という文字列なので、 このタグ全体が"にゃー"に置換されます。 formatには関数to_dateやmodなどを指定します。 エスケープは、文字列をHTMLエスケープしたりできます |
---|---|
<nana:for-each select="データ名" cnt-var="変数名"> </nana:for-each> |
データ名に対応した配列(DesignDataVector)を 繰り返し出力します。 指定したデータが配列でないといけません。 cnt-varに変数名を指定するとその変数に今何番目の ループかを代入します。数値は0~です。 |
<nana:set select="変数名" value="データ"/> |
指定の変数にデータを設定します。 データは文字列を指定するか、デザインデータ名、変数名を指定します。 文字列を指定する場合はシングルクオートで括ります。 例:"'トップ画面'" 変数名は、"$i"のように先頭に$を付けます。 |
<nana:if select="データ名" cmp="比較演算子" value="文字列" type="タイプ"> </nana:if> |
データ名と文字列を比較してtrueのとき、nana:ifタグで 括られた箇所を出力します。 比較演算子は、eq、ne、lt、le、gt、geなどが使用できます。 eq ⇒ = (イコール) 、 ne ⇒ != lt ⇒ < (小なり) 、 le ⇒ <= gt ⇒ > (大なり) 、 ge ⇒ >= ※比較は文字列比較になります。 もし数値比較したい場合は、タイプにnumberを指定 してください。typeを省略すると文字列比較です。 |
<nana:include file="ファイル名"/> |
他のファイルをインクルードします。 自分自身のファイルからの相対パスを指定します。 |
※データ名はパスで指定します。例:「cat」、「mails[1]/subject」
データにダブルクオート(")などを使用したい場合、"のようにHTMLエスケープします。
【拡張した内容(DesignData)】
デザインデータの入れ方:
以前の記事では、 (*dd_map)["cat"] = createDesignData(string("nya-")); のように関数を介して代入しました。
ここでは、演算子でコピー代入できるようにしました。
(*dd_map)["cat"] << string("にゃー");
内部では、(*dd_map)["cat"] = createDesignData(string("nya-"));をしているのと同じです。
また、自作のクラスも代入できるようにDesignDataSharedクラスを用意しました。
このクラスは、mapDesignDataRef()テンプレートを作成しておけば、<<が使用できるようになるクラスです。
ただし、shared_ptrに代入したクラスのみ<<が使用できます。(shared_ptrのコピー代入)
shared_ptr<Mail> mailPtr;
(*dd_map)["mail"] << mailPtr;
内部では、(*dd_map)["cat"] = DesignDataSharedPtr(new DesignDataShared(mailPtr));と同じです。
shared_ptrをデザインデータに保持するので、いつデザインHTMLの作成style->output(cout, dd_map);
してもデータがなくなることはありません。
実は、mapDesignDataRef()テンプレートを作成しておくと、もうひとつクラスが使用できるようにしています。
DesignDataRefクラスです。
そして&=の演算子を使用できるようになります。
Mail mail;
(*dd_map)["mail"] &= mail;
内部では、(*dd_map)["cat"] = DesignDataRefPtr(new DesignDataRef(mail));と同じです。
実体mailのポインタ(&mail)をデザインデータに代入します。
ですので、実体のコピーやshared_ptrクラスを生成するような余計な処理が入りません。
ただし、ポインタですので、style->output(cout, dd_map); を呼び出したときに
mailの実体が破壊(デストラクト)されていた場合は予期せぬエラーになります。
※デザインデータ(DesignData)クラスは=で代入します。
<<や&=を使用してはいけません。
▲これらの演算子やクラスをどのように実装しているかを見てみたい場合、以下の箇所を見てみてください。
DesignDataExt.hファイル
class DesignDataRef
class DesignDataShared
template <class Type>
void operator &=(DesignDataPtr& ddPtr, const Type& data)
【拡張した内容(DesignStyle)】
nana:XXX で使用できる予約語を増やしました。
以前のソースでは、value-of、for-each、くらいでした。
set、if、include、を追加しました。
それぞの機能は、doxygenでdocを作成して説明を読んでみてください。
set・・・変数への値の設定。 VariableDesignStyle 参照。
if・・・条件。trueのときのみタグで括られた箇所を出力する。 IfDesignStyle 参照。
include・・・他のファイルを取り込む。ファイルパスの指定は、元のファイルの相対パスになることに注意。
それぞれソースを見ると、機能追加の仕方が分かると思います。
いろいろ追加して遊んでみてください
DesignStyle.h、DesignStyle.cpp ファイルを見てみてください。
【拡張した内容(DesignStyleのvalue-of)】
nana:value-of で使用できる属性format、escapeを増やしました。
以前はformat、escape属性はありませんでした。
format ・・・出力データを変更する関数を指定します
escape ・・・エスケープの方法(HTML、URL)を指定します
例えば、<nana:value-of select="cat-cnt" format="mod(2)" />とすると
cat-cntの数が5なら、5%2=1で、1を出力します。
▲これらをどのように実装しているかを見てみたい場合、以下の箇所を見てみてください。
DesignFormat.h、 .cppファイル
DesignFormatMod
DesignFormatToDate
createDesignFormat
上記のクラスは、DesignFactory.cppファイルのDesignStyleFactory::createValueOf()
でcreateDesignFormat()を介して生成されます。
【拡張した内容(DesignData)のdbugOutput()】
プログラムだけ打っているとデータがどのような階層になっているか分からなくなります。
データのパスを正しく書いているはずなのに出力されない!みたいな自体に陥ります。
こんなときは、debugOutput()を呼び出すと階層が分かります。
【スタイルファイルのデバッグ用アプリ】
このデザイン(View)用のプログラムで、処理ロジックとデザインを分けられます。
デザインを分ける目的の1つは、デザイナーとプログラマーが別々に平行して作業できるようにすることです。
ところが、このままだとDesignDataにデータを代入し、DesignStyleのoutput()を呼び出すプログラムが
できないと、スタイルファイルが正しく動作するか分かりません。
selectで指定したデザインデータ名を入力ミスするかもしれませんものね。
これを解決するため、debugtoolを作ってみました
このプログラムは、ファイルからDesignDataを作成し、そのデータでスタイルの実行をします。
結果はファイルで出力されます。
これで、ファイルにデータを記述して、スタイルファイルを作成し、debugtoolを実行すれば
作成したスタイルファイルが正しいか分かりますよね(^^)
【もっと基本的な説明】
このサンプルでは色々なテクニックを使用しています。
まず、virtual です。
virtualの記事は以下を見てみてください。
・ユーザに機能追加を自由にさせるには? ⇒ DesignFormatで使用されてます。
・ユーザに処理の順番を自由に変更させるには? ⇒ DesignStyleで使用されています。
boost::tokenizerの記事は以下。
・HTMLのタグを分解するには?
(セパレータの拡張) ⇒HtmlTagSeparatorで使用されています。
・HTMLタグの閉じ忘れをチェックするには?
⇒DesignStyleFactoryで使用されています。
エスケープは以下。
⇒DesignFormatHtmlEscape、DesignFormatUrlEscapeで使用されています。
いろいろ参考にして、遊んでみてください。
【遊びのヒント】
さしあたり、スタイルで使用する属性名を自分の分かりやすいものに変更してみてはどうでしょう。
<nana:value-of select="cat-cnt" foramt="mod(2)"/>
で、例えばselectをdataに変えてみる、とかいかがでしょう。
DesignStyleFactory.cppファイルの
DesignStyleFactory::createValueOf() をいじってみてください。
次に、DesignFormatの派生クラスを作成してみてはどうでしょう?
出力文字の置換をするフォーマットを作成してみましょう。
DesignFormat.hのDesignFormatModクラスなどを参考にしてみてください。
作成したクラスをformat属性で指定できるようにするには、
DesignFormat.cppのcreateDesignFormat()関数をいじってみてください。
あとは、属性名と=と"の間に空白を入れられるようにするのも面白いかもしれません。
(現状はselect="cat"のように空白を入れられません。。)
C++の勉強用のプログラムですので、いろいろ中身を見て、ここはこうした方が良い!と叫んでみたり、
ちょっといじって機能追加したりして遊んでみてください。
標準のC++だけでもいろいろできる!
楽しめる!
って思ってもらえたらうれしいです。(^^)
参考:
・Viewとは?