DHCPはセキュリティが低い!?

このところ、「うちは固定IPです」というのをよく聞きます。ネットワークの設定を業者に依頼したときに、「DHCPはセキュリティ的に問題があるから固定IPにしましょう」と言われるそうです。もちろん、買ってきたマシンのセットアップが面倒ということもありますが、ところによっては、パソコンが増えるたびにIPアドレスを業者に指定してもらって設定しているとか。どうも、それでメンテナンス料を取っているのじゃないかと思われる節があります。

いくつかのサイトを見たところ、DHCPが脆弱であるという理由は、次の通りです。

  • どんな機材にでもIPアドレス等インターネット接続に必要な情報を渡すため、誰でも接続できる。
  • 不正アクセス時に誰がアクセスしたか分からない。

前者は、「悪意を持った人が簡単に接続できる」みたいな書き方をしていれば、まあ、みなさん、恐れるのは無理も無いかもしれませんが、よく考えると、この理屈には問題があります。

一般に、ネットワークと言えば、ワイヤレスないしはEthernetでしょう。ワイヤレスは接続自体にパスワードをかけるのが現状では一般化しているので、「社内の人しか使えない」みたいに言えなくはありません。まあ、そこも突っ込みどころありますが、ここではEthernetにフォーカスしましょう。

仮に、ある事務所で、EthernetでDHCPを使っていたとします。そうすると、もちろん、事務所外の人がそこにPCを持ち込み接続すると、インターネット接続できます。DHCPの問題点はそこだというわけですね。これは、Microsoftの技術文書にも書いてあります。

もちろん、MACアドレス(Ethernet ID)をDHCPサーバに登録するという方法で、外部の人の接続は阻止できますし、Active Directoryは確かにDHCPやDNSとの連動で阻害はできるので、いわゆる普通のDHCPより安全であることは言えるでしょう。では、一般的なDHCPが安全ではないと決めつける事ができるのでしょうか?

一般の事務所で、部外者が勝手に入ってきて、Ethernetにつなぐという行為があったとき、事務所の人は無視をしますか? 普通、どなたですか? ご用事はなんでしょうか? くらい聞きますよね。人の出入りが激しく部外者がいつでも出入りしている…というところがあったとしても、それは、Ethernetを設置するのが間違いで、普通はワイヤレスだけにします。部外者がEthernetを勝手に使えるような事務所があったとしたら、きっと、普通の悪者は「インターネットにつなごう」と思う前に、「財布を盗もう」とか「家具をぱくってやれ」くらい考えるでしょう。知らない人が入ってきて、勝手にお茶をわかそうとしたら、普通は排除しますよね。それと同じで一般には事務所のEthernetは人の目で監視されているはずです。そうではないとしたら、ネットワークのセキュリティ以前の問題です。

しかし、それでも、例えば、深夜に忍び込んでアクセスできるということもあるかもしれません。ネットワークを勝手に使ってデメリットはあるでしょうか? 勝手にファイルサーバの中身を盗まれたり、消されたりするという心配もあるでしょう。しかしながら、これは「すべてのサーバはユーザ名とパスワードの入力が必要」としておくことで、防ぐことができます。面倒臭いからと言って、誰でも読み書きできるファイルサーバを立てていれば、もちろん危険ですが、そういうサーバ管理はDHCP以前の問題で、そういう運用自体が危険です。重要な情報はともかく認証が必要としておけば、何も心配することはありませんし、不正アクセス自体をほぼ100%排除できるわけです。排除できれば、誰がやったのかを調べる必要はありません。これはDHCP以前に、ネットワークのセキュリティを確保するために必要なことです。

そうしたら、深夜に忍び込んだ悪者は、単にインターネットでGoogle検索するくらいしかやることはありません。月額固定の通信料だとしたら、ほとんど被害はないでしょう。だいたい、それ以前に、椅子とかロッカーとか盗まれますね。

さらにさらに、固定IPにすれば、インターネット接続できないと思っている人も居るかもしれませんが、固定IPのネットワークにつないで、ある程度ルータやDNSのアドレスに目星をつける事もできます。Mac OS Xだと、とりあえず可能なようです。やり方は、「ping 10.255.255.255」「ping 192.168.255.255」といった末尾が255のグローバルPINGでいろんなネットワークアドレスを試します。その後に「arp -a」とすれば、だいたい、応答があったホストのIPは分かります。後は、経験と勘でどれがルータか分かりますし、DNSサーバのホストもポートスキャンで分かります。不正アクセスしようという輩は、この手の手法はよくご存知でしょうから、「固定IPにしたら、インターネット接続できない」ということ自体間違いです。

思うに、こういう「DHCPはだめ」というのは、後々のメンテナンス費用で稼ごうとしている業者の作戦なのではないかと考えます。DHCPでちゃんと動けば、後は何もしなくてもOKとなると、メンテナンス費用をかせげません。IPをつどつど業者からアサインしてもらう上では、確かに業務としては発生しますが、なんだか、倫理的にぎりぎり、あるいは一線を超えてないでしょうか。

そういうわけで、DHCPは便利ですから、DHCPを使いましょう。言い換えれば、DHCPを取り入れたネットワーク管理ができるようになりましょう。家庭で当たり前のように使われているDHCPですが、事務所で使えないことにもっと疑問を持つべきです。

それでも、不安と思う方は、次のように考えてみてください。固定IPにしたときの設定の手間やIPアドレスの管理といった仕事が増えるコストは、DHCPにすることによるその損失(仮にあるとすれば)より、高くなっていませんか? 典型的な、セキュリティにかける費用が損失を上回る失敗プランなのです。いわば、保険金の払い過ぎってイメージでしょうか。

[IM]Enclosure/Repeaterの展開

INTER-Mediatrorの再構築はいいところまで行きそうなところで、実はつまずきました。Enclosure/Repeater/Linked Elementという階層を考えていたのですが、EnclosureでありLinked Elementなもの、RepeaterでありLinked Elementであるものもあります。そこを考慮していなかったので、少し戻らないといけなくなってしまいました。まずは図を書いて考えます。

45781

 

また、実際のタグと展開パターンをまとめておいて、再検討に入ります。こう考えると、3種類のオブジェクトに分離しているのは、表だけですね(苦笑)。

 

形態 Enclosure Repeater Linked Element
TBODY TR any elements
汎用 DIV._im_enclosure DIV._im_repeater any elements
番号リスト OL LI LI itself or inside of LI elements
箇条書き UL LI LI itself or inside of LI elements
ポップアップ、リスト SELECT OPTION OPTION itself
ラジオボタン、チェックボタンリスト DIV._im_enclosure INPUT [@type=readio or check] INPUT itself

[IM]パラメータの受け渡し

しばらく超忙しかったので、INTER-Mediatorに手を付けられなかったのですが、そろそろ戻れそうです。

Ver.5までのINTER-Mediatorは、テンプレートになるHTMLファイルを.phpにして、ページの冒頭にパラメータを定義してPHP関数の呼び出しを行う事で、生成したJavaScriptのプログラムをマージしていました。リストラクチャで、そのあたりの仕組みを変更します。今作っている部分は、あるページがあって、それがテンプレートとして機能させる場合、次のようなファイル構成にします。

テンプレートのHTMLファイル:たとえば「test.html」

サポートのPHPファイル:たとえば「Sample1.php」

これら2つのファイルを用意します。以後、「たとえば」のファイル名で具体的に説明します。Sample1.phpは、JavaScriptのソースを戻すスクリプトです。そこで、test.htmlのヘッダ部分に以下のような記述を加えます。

<scripttype="text/javascript"src="Sample1.php"></script>

データベースに必要なパラメータは、Sample1.php側に記述します。具体例は、GitHubにあるソースを見てください。

test.htmlのbodyタグのonloadで、たとえば、次のようなJavaScript関数を呼び出すと、テンプレートの展開を行うようになります。

function im_start()	{
var im = new INTERMediator( );
im.showMessages();
}

ページのロード後に最初のテンプレート展開が行われ、そのときに、必要なテーブルへのアクセスを行います。その部分はAjaxで動きますし、保存や更新等の処理でも、同様にAjaxで行います。これらは、HTMLページからはSample1.phpをそれぞれ必要なパラメータで呼び出します。従って、Webサーバで公開していないといけないPHPのスクリプトはSample1.phpだけで、INTER-Mediatorのスクリプト群についてはWebサーバで公開していないところにも移動できるようになります。

この方針で進めることにしました。

「久しぶり」の認定

認定試験を受けました。というと、Apple関連と思われるかもしれませんが、取得したのはMCASのAccess 2007です。正確には、次のような名称です。

Microsoft Certified Application Specialist / Microsoft Office Access 2007

合格しました。とりあえずひと安心。まあ、なんでこんな資格をと思う方もいらっしゃるかもしれませんが、それは別機会として、デスクトップのデータベースアプリケーションとしては、FileMakerかAccessかという話もあり、その対決を認定試験で見てみましょう。ちなみに、FileMakerは最新版はVer.11ですが、私はVer.8とVer.9の認定資格を取得しています。Accessは2002の認定試験は持っていましたが、このほど最新版を取得した次第です。

さて、MCASをググると、「侮れない試験」のような書き方が目立つので、一瞬ひるむ訳ですが、若干のテクニックをマスターすれば、普段ソフトを使っている人ならさほど難しくないでしょう。ソフトを偏った使い方をしているときついかもしれませんが、Accessで開発をしているような人ならAccessの試験は朝飯前だと思います。ただ、解答はAccessを操作する事で行います。採点方法の微妙なところは知らされて居ませんが、やはり試行錯誤して設定を行ったりすると正解になるかどうかは怪しいので、間違いなく操作するというところがポイントのような気がします。操作をミスった場合、リセットして最初からできるので、落ち着いてやれば大丈夫です。試験問題は29問でしたが、公表値は問題数に幅があります。時間は50分で、私は余裕がありましたが、試行錯誤をしていれば時間はあっという間に終わるでしょう。要は、「それなりに勉強して覚えておく」必要がある試験で、たちどころに操作を正確に終えればいいわけです。従って事前に予習していれば、全然難しくはありません。ただ、設定をして「保存をするのかしないのか」が明確でない問題があり、そういうのってどっちにするのかは非常に迷いますが、対策勉強していれば問題なくできるでしょう。

FileMakerの試験とAccessの試験、どっちが難しいと言えば、圧倒的にFileMakerです。Accessの試験は、Accessそのものが難しいとは言え、VBAやサーバ動作、アカウントとアクセス権といった難しいところは試験範囲外となっています。言ってしまえば、ツールバーにあるアイコンとOfficeメニューの項目の動作を知っていれば合格できるような試験です。FileMakerの試験は、関数、スクリプト、サーバといったところが必須の項目であり、これらを知っていないと試験合格はかなりつらいところです。一方、FIleMakerの試験はFileMakerを操作するのではなく、試験システムの画面でほとんどの問題が選択肢から選択する形式です。しかしながら、この手の試験は手の込んだひっかけ問題を作る事ができるので、その点でもなかなか大変な試験です。</div>

善し悪しはともかく、「どっちがいいか?」と取りざたされるFileMakerとAccessですが、認定試験は大きく違います。FileMakerの試験は敷居が高いので、プロの開発者でも持っていない事が多い感じがします。逆に言えば難しい試験なので、FileMakerの認定資格を持っている開発者は上級者の証でもあります。一方、Accessのような試験だったら、合格しているからと言ってソリューション開発能力を証明できるようなものではありません。ちなみに、試験の英語名は「Using Microsoft Access 2007」ですから、まさにその通りです。「Develop」ではないわけです。Accessのプロのみなさんは、たぶん、持っていても仕方ないということで取ってないのかなとも思います。あるいはあえて言わないのかな? Officeの試験のWord/Exce/PowerPoit/Outlookに合格すると「オフィスマスター」という資格がもらえるのですが、これにはAccessは要件に入っていないのもモチベーション下げるでしょうね。両試験とも、結果的に開発現場でメジャーにならないというところでしょうか。もっとも、Microsoft系の本格的なデータベース開発となると、SQL Serverがあり、そちらのMCP資格等がありますので、まともな開発者そちらを目指すということになるのかもしれません。そう考えると、FileMaker、Accessの資格が必須となるのは認定トレーナーだけってことになりますね。

[IM]テーブル名とフィールド名のルール

これまでのプログラミングで、元になるHTMLから、リンクした要素を拾い出し、リカーシブに集めることができるようになっています。集めたノードの配列から、あるリピーターに対するテーブル名とフィールド名のリストがデータベースアクセスには必要です。

INTER-Mediator.jsのexpandEnclosure関数でこれらの処理を続けています。リンクした要素は配列linkDefsに入ります。INPUT/TEXTAREA/SELECT要素があると、それを書き戻す処理を追加する必要があるので、これらがあるかどうかのチェックを行います。それを行いながら、リンクした要素の定義を連想配列に入れ直しておきます。入れ直した結果は配列linkDefsHashです。

そして、それらの中で、いちばん多く指定されているテーブル名を取り出し、リピーターに対応するテーブルとします。テーブル名 ” もありとして、これは定義の最初のテーブルを取得する事にします。このルールで運用すれば、一連のリンクした要素ではすべての定義にテーブル名をきちんと入れる必要はなく、最低でも1つ入っていればいいわけです。

しかしながら、テーブル名が2つ以上ある場合はどうなるかと言えば、そのうちの1つのテーブル名が使われ、テーブル名を省略している定義の場合は決定されたそのテーブルのフィールドであると見なします。結果的に使われないリンクした要素が登場することになります。異なるテーブルを指定したリンクした要素があれば、ノードツリー上で前にある方の設定が有効になります。いずれにしても、1つのテーブルを決定し、それ以外のテーブルの指定のリンク要素は無視となります。

ここまでのプログラミング結果は、githubの以下のURLで参照できます。(こういう使い方できるのですね〜)

http://github.com/msyk/INTER-Mediator/tree/4bc62c2235d65e7c28097be419bb4fc43635dc60

続いて、いよいよ、データベースとの連携部分の再構築を行います。ここまでのプログラミングを行いながら、HTMLソースを極力いじらない手法を取ることを実現すべく、Ver.0.5までの「HTMLソースにPHP関数を入れる」というのではなく、データベース定義もサーバ側のファイルに残す方法がよりスマートと考えます。

[IM]テーブルとフィールドの指定

INTER-Mediatorでは要素が「どのテーブルのどのフィールド」なのかを記録していなければなりません。しかしながら、より汎用的に、どの属性なのかあるいはテキスト要素なのかを指定することも必要です。再構築前はTITLEなどを利用し要素のタグによってデータベースのデータが差し込まれる場所が決まっていましたが、それを汎用化します。

まず、「どのテーブルのどのフィールドなのか」という情報を、「要素のリンク指定」と呼ぶ事にします。要素のリンク指定は、TITLEないしはCLASS属性を使うということにしようと思います。両方も、一方だけでもOKとします。

TITLEの場合は、

<input title=”table@field” name=”email” />

などと記述します。CLASSの場合は、他のものとの区別を付けるため、

<input class=”IM[table@field] mailfield” name=”email” />

のようにIM[*]の形式で記述します。classにはいくつかのデータが入り、確実に区別したいので、恣意的なマーキングを要求しようと思います。

それから、同じセパレータで区切って、どの属性にセットするのかを指定できるものとします。たとえば、input要素ならvalueに設定したいわけですから、

<input title=”table@field@value” name=”email” />
<input class=”IM[table@field@value] mailfield” name=”email” />

と書くものとします。@で区切った3つ目のコンポーネントがないものは、その要素のテキスト要素になるものとします。たとえば、

<div class=”subtitle IM[table@field]”></div>

は、フィールドの値を、開きタグと閉じタグの間に展開します。

また、別の区切り文字(デバイダーと呼ぶ事にします)によって、複数の属性およびテキストデータに展開するものとします。つまり、デバイダーを縦棒の | としたら、

<option class=”IM[table@id@value|table@name]” />

とすることで、

<option value=”idフィールドの値”>nameフィールドの値</option><

と展開するようにします。

要素を展開したときには、従来の方法では、@1 @2 …をあとに付けていましたが、これはデータベースに書き込みを行うための区別を行うためです。これについては、もう少し開発が進んでからどうするのかは改めて考えます。というのは、書き込みが必要なのは、INPUT、SELECT、TEXTAREAのフィールドだけになります。要素のリンク指定に繰り返し生成された情報が必要なのか、あるいはid属性で何とかなるのか、そのあたりは今後の検討課題とします。

[IM]ノードの展開

もとになるHTMLによるDOMがあって、そこからデータベースのデータに置き換わったノードが展開される様子を一般的に記述できるように、改めて解析を行います。概ね、左の図がもとのDOMで、エンクロージャーを起点にしてリピーターが展開されれたものが右側です。リピータの繰り返し処理を行う場合、リピーターはいったんエンクロージャーの子ノードではなくし、そのクローンを作ってデータベースからのデータを設定し、エンクロージャーの子ノードとして追加するという作業を行います。つまり、R10はノードのひな形であり、実際にデータベースの内容を展開するのは、そのクローンであるR11、R12…です。R10に含まれるリンクされた要素のL101は、データベースの値を設定された場合にはL111、L121となっています。いずれにしても、この番号付けのルールはうまく重複は存在しません。ただし、単に十進数にすると、桁が増えたときの問題になりますので、ユニークな識別子は各桁をハイフンで結ぶなどする必要があります。最終的には、各ノードのidフィールドを与える必要があるので、「R-1-2-1-1」のような各桁をハイフンで区切った文字列を識別子とします。このような識別子にしておけば、リンクされた要素からエンクロージャーやリピータを見つけるのがやりやすくなります。なお、リピーターが複数の要素からなる場合は、「R-1-2-1-1-1」のように一桁多くしておきます。リピーターは「R」以下、偶数桁なので、奇数桁となっている場合には末尾が複数の要素の何番目かを示すものであることが分かります。また、数値の0はひな形、1以降が展開された要素を示すというルールにします。

76896

 

この図から、一連の処理を以下のように組み立てることができることが分かります。これにより、リカーシブにノードをたどることで、すべてのエンクロージャー/リピータをデータベースの内容に置き換えて複製展開できます。つまり、seekNodesとexpandEnclosureを順次呼び出すことで、展開ができるということになります。

  1. seekNodesにより、最上位のノードから見たときに最初に到達されるエンクロージャーが見つかる
  2. そのエンクロージャーに対してexpandEnclosure関数を実行する
  3. expandEnclosureでは、リピータをエンクロージャーから取り除き、そのクローンを作って複製する用意をする
  4. 続いて、リンクされる要素を探し出してリストアップする
  5. リストの情報からデータベースヘ接続し、関連付ける必要のあるデータを得る
  6. レコードの数だけリピータの複製、データの埋め込み、エンクロージャーへの追加を行う
  7. リピーターを起点として、seekNodesによりエンクロージャーを見つける
  8. エンクロージャーに対してexpandEnclosure関数を実行する
  9. …以下繰り返し

 

ここで考える必要があるのは、主キー/外部キーのデータを伝達する必要があるということです。expandEnclosure関数ではデータベースにアクセスしてデータを取り出しますが、そのとき、現在のエンクロージャーの上位のエンクロージャーで展開しているリピーターとの連動が必要です。すなわち、上位から、テーブル名、主キーフィールド名、その値をもらわないといけません。つまり、expandEnclosure関数で、これらを引数として引き渡す必要があります。そして、現在のエンクロージャーのリピーターを展開するためのデータを得る時、リピーターに対応するテーブルにおいて、上位のリピータととのリレーションが設定されているかどうかを確認し、設定されているようなら、上位のエンクロージャーからの情報を検索条件に含める必要があります。

なお、このあたりで、ずっと関数で書いていたプログラムを、オブジェクトにカプセル化することにします。当面、外から呼び出すのに必要なメソッドはないので、単にこれまでに書いて来た関数を、以下のようにコンストラクタの中に閉じ込めてしまうことにします。

function INTERMediator( rootNode ) {
 
seekNodes( rootNode );
      :
}
}

 

[IM]リピーターとエンクロージャーの識別

HTMLドキュメントからリピーターを検出し、データベースのないように置き換えるための処理手順を検討します。ここで、ドキュメント内のノードの一部を取り出したものが以下の図です。「エンクロージャー」「リピーター」に加えて、データベースのデータを受け入れる「リンクされた要素」があるものとします。

a2ba5

.5までは、リンクされた要素はINPUTなどいくつかの種類のタグのみで、ElementsByTagNameメソッドで要素を参照して、NAME属性があるかどうかというところをチェックしていました。ここで、リンクされた要素になり得る要素はすべての要素となると、基本的にはドキュメント内のすべての要素をチェックしなければなりません。従って、BODYタグの要素より以下を調べる事になるが、ここで、エンクロージャーが見つかった場合、エンクロージャーよりも下位を走査する必要はない。もしくはしていはいけない。なぜなら、エンクロージャー以下はリピーター要素の複製などによって内容が変わる可能性があるからである。加えて、エンクロージャーが見つかった場合の処理はリピーターの中でカスケードされるようにすれば、全体は走査する必要はない。従って、BODY以下は前順走査を行い、エンクロージャーが見つかった場合はエンクロージャー内の複製・展開処理を行い、引き続いては自分の兄弟のノードに移動すれば良いということになる。この考察に基づいて、ドキュメント中の展開すべきところを検知するプログラムとしては以下のようになる。

var bodyNode = document.getElementsByTagName( ‘BODY’ )[0];
seekNodes( bodyNode );

function seekNodes( node ) {
var enclosure = null;
var nType = node.nodeType;
if ( nType == 1 ) {
if ( isLinkedElement( node ) ) {
enclosure = getEnclosure( node );
} else {
var childs = node.childNodes;
for ( var i = 0 ; i &lt; childs.length ; i++ ) {
var checkingEncl = seekNodes( childs[i] );
if ( checkingEncl != null ) {
if ( checkingEncl == childs[i] ) {
expandEnclosure( node );
enclosure = null;
} else {
return checkingEncl;
}
}
}
}
} else {

}
return enclosure;
}

function isLinkedElement( node ) {
if ( node.getAttribute( ‘TITLE’ ) != null ) {
return true
} else {
return false;
}
}

function getEnclosure( node ) {
var currentNode = node;
var encTagCandidate = null;
var encTagCandidateAlt = null;
var encClassCandidate = null;
while ( currentNode != null ) {
var tagName = currentNode.tagName;
var className = currentNode.getAttribute(‘class’);
if ( tagName == ‘TR’ ) {
encTagCandidate = ‘TBODY’;
} else if ( tagName == ‘LI’ ) {
encTagCandidate = ‘UL’;
encTagCandidateAlt = ‘OL’;
} else if ( tagName == ‘OPTION’ ) {
encTagCandidate = ‘SELECT’;
} else if ( tagName == ‘DIV’ &amp;&amp; (className != null) &amp;&amp; className.match(/_im_repeater/) {
encTagCandidate = ‘DIV’;
encClassCandidate = ‘_im_enclosure’;
}
if ( (( encTagCandidate != null ) &amp;&amp; ( tagName == encTagCandidate ))
|| (( encTagCandidateAlt != null ) &amp;&amp; ( tagName == encTagCandidateAlt ))
|| (( encClassCandidate != null ) &amp;&amp; ( className.indexOf(encClassCandidate) &gt;= 0 ))) {
return currentNode;
}
<div class=”wiki_entry”> currentNode 2010-03-08 02:49:24
=”Apple-tab-span”> </span>currentNode = currentNode.parentNode;</div>
}
return null;
}

function expandEnclosure( node ) {

}

[IM]INTER-Mediatorのアーキテクチャ見直し

INTER-Mediator Ver.0.5まで来たところで、見直しを始めました。現状、それなりに動いているのですが、「選択入力」の実現を考えていたときにちょっと壁があることが分かりました。選択入力は簡単ですが、「状況に依存して選択肢が変化する」までを視野に入れると、今のままではスマートな解決方法はありません。もちろん、いかにも取って付けたような対応は可能ですが、それだと美しくありません。加えて、現状では、リスト形式の画面のとき、リストの1行1行からのリレーションがあるような場合にうまく対応できません。これらを考えて、いろいろな状況に対応できるように、データベース処理をより汎用化することを考えました。

Ver.0.5までのアーキテクチャだと、「リレーションのリレーション」ができません。一方、リレーションをいくつも定義できれば、「状況に依存して選択肢が変化する」ができるのです。つまり、選択肢は、リレーションして絞り込まれた結果であるからです。常に同じ選択肢のものは、そのテーブルとのデカルト積で考えれば良いので、この手法で、選択肢の件は解決するはずです。

Ver.0.5までは、InitializePage関数の引数で、テーブル名/キーフィールド名/外部キーフィールド名、を指定していますが、外部キーフィールドが、どのテーブルのどのキーに対応するものなのかを指定する必要があります。1つ目のテーブルの場合や、相手のテーブルのキーフィールドに対応する場合には省略可能とすると、シンプルなものであれば現状と変わらないと言えるでしょう。

そこで、InitializePage関数の第一引数で、’foreign-key’ というキーに対してテーブル名を記述していたのを、以下のキーと値を持つ配列で定義することにします。

  • ‘field-name’:外部キーフィールド名
  • ‘table’:対応するテーブル名
  • ‘key’:対応するテーブルのキーフィールド名

一方、現在は繰り返しは1つのTRタグの繰り返しです。テーブルのデザインによっては、複数のTRで1つのレコードを表現し、それを繰り返したいこともあります。また、UL/OLもあるし、SELECT/OPTIONもある意味繰り返しです。DIVを繰り返したいと思う事もあります。そこで、汎用的な繰り返しを実現するために、リピーターとエンクロージャーを定義します。HTMLページは要素の繰り返しですし、要素に要素が含まれることになります。ここで、1つのノードを「エンクロージャー」とし、その中にあるあるノード以下の全てのノードを「リピーター」とします。元のドキュメントで、エンクロージャーとリピーターが認識できれば、リピーターを繰り返しデータベースのデータに置き換えることで、繰り返すレコードをドキュメント内に展開できることになります。右側はノードとして見たときの関係を示した図です。

f7d47

リピーターとエンクロージャーとして、以下のようなものをサポートする事にします。テーブル、項目リストはもちろんですが、SELECTも同様な仕組みです。また、汎用的にDIV要素と既定儀のクラス名で、リピーター/エンクロージャーのセットを定義できるものとします。DIV以外は、リピーターとエンクロージャーはノードの階層関係の上では親子関係になっていることになります。リピーターとエンクロージャーの間にノードがあっても良いということになると、今度は繰り返しをつなぐポイントを定義しないといけないことになるので、DIV要素の場合も直接の親子関係にあるものとします。ただし、そうなれば、DIV要素の双方にクラスを付けることは不要であるとも考えられますが、これは後からの検討課題とします。

リピーターとエンクロージャーとして、以下のようなものをサポートする事にします。テーブル、項目リストはもちろんですが、SELECTも同様な仕組みです。また、汎用的にDIV要素と既定儀のクラス名で、リピーター/エンクロージャーのセットを定義できるものとします。DIV以外は、リピーターとエンクロージャーはノードの階層関係の上では親子関係になっていることになります。リピーターとエンクロージャーの間にノードがあっても良いということになると、今度は繰り返しをつなぐポイントを定義しないといけないことになるので、DIV要素の場合も直接の親子関係にあるものとします。ただし、そうなれば、DIV要素の双方にクラスを付けることは不要であるとも考えられますが、これは後からの検討課題とします。

リピーター エンクロージャー
TR要素、TR要素の配列 TBODY要素
LI要素、LI要素の配列 OL要素、UL要素
OPTION要素 SELECT要素
DIV.im_repeater要素 DIV.im_enclosure要素

Ver.0.5までは、リピーターとエンクロージャーは明確に識別はされていませんでした。というのは、TABLE要素にある1つのTR要素を繰り返すというルールだけだったからです。ドキュメント内のINPUTタグの要素を検索し、それを含むTRを探し、さらにTRを含むTABLEを探すということを行っていたのです。従って、INPUTタグに何らかの識別マーク(具体的にはNAME属性)を付けることで、そこからリピーターとエンクロージャーを自動的に探す事ができます。また、リレーションも2段階までだったので、見つけたエンクロージャーに対してデータベースからの読み込み結果を適用すれば済みます。

アーキテクチャを変更した後はどうなるでしょうか? ここにはいろいろな問題が発生します。リレーションも多段階であることも考慮すると、ドキュメントからエンクロージャーを見つけて処理するとう方法は限界があります。たとえば、リピーターとして繰り返した要素のさらに下位にエンクロージャーがあった場合はどうなるでしょうか? このことを考えれば、結果的にはドキュメントの要素を順次調べて、リピーターとエンクロージャーを識別し、その都度データベースの差し込みをする必要があります。以後は、Ver.0.5までと同様に、INPUTタグの要素などデータベースのデータと実際に置き換えが行われる要素に「テーブル名」「フィールド名」などの情報を埋め込み、そこからエンクロージャーやリピータを識別することにします。アルゴリズムを考えるときに、リピーターの中にあるエンクロージャーをどう識別するのかということがポイントになると考えられます。

[IM]エンクロージャーの展開の前に

エンクロージャーの展開関数expandEnclosureは作ったのですが、そこに至るまでにいくつかユーティリティ関数を作ったので、それをまず照会しておきます。また、SafariだけでなくIEでも動作チェックを下結果も反映させます。

まず、すでに紹介したisLinkedElement関数は、このままだと、IEでの動作に不都合がありました。Safari/Firefoxだと、getAttributeによって、存在しない属性を指定するとnullが得られますが、IEはnullではなかったので、併せて文字列の長さが0より大きいという条件を付けました。なお、現在はTITLEタグを使ってリンクした要素とすることにしていますが、CLASS属性を使うのがいいのではとの指摘もあります。とりあえずはTITLEで続けますが、CLASSタグを使う場合にはこの関数を改良すればいいということになります。

/**
* Cheking the argument is the Linked Element or not.
*/
function isLinkedElement( node ) {
<div class=”wiki_entry”> // IE: If the node doesn’t have a title attribute, getAttribute doesn’t return null. So it requrired check if it’s empty string.</div 2010-03-08 02:49:50
it’s empty string.</div>
if ( node.getAttribute( ‘TITLE’ ) != null &amp;&amp; node.getAttribute( ‘TITLE’ ).length &gt; 0 ) {
return true
} else {
return false;
}
}
エンクロージャーなのか、リピーターなのかを判定する関数を作って正確には、エンクロージャーの候補となる場合やリピーターの候補となる場合にtrueを返すと言えばいいでしょうか。タグと一部のclass属性の比較だけを行っているシンプルな関数です。

/**
* Check the argument node is an enclosure or not
*/
function isEnclosure( node ) {
<div class=”wiki_entry”><span 2010-03-08 02:49:50
>
if ( node == null || node.nodeType != 1 )
return false;
var tagName = node.tagName;
var className = getClassAttributeFromNode( node );
if ( ( tagName == ‘TBODY’ ) || ( tagName == ‘UL’ ) || ( tagName == ‘OL’ )
|| ( tagName == ‘SELECT’ ) || ( tagName == ‘DIV’ &amp;&amp; className == ‘_im_repeater’ ))
return true;
else
return false;
}

/**
* Check the argument node is an repeater or not
*/
function isRepeater( node ) {
if ( node.nodeType != 1 )
return false;
var tagName = node.tagName;
var className = getClassAttributeFromNode( node );
if ( ( tagName == ‘TR’ ) || ( tagName == ‘LI’ ) || ( tagName == ‘OPTION’ )
|| ( tagName == ‘DIV’ &amp;&amp ( tagName == ‘DIV’ &amp;&amp; className == ‘_im_enclosure’ ))
return true;
else
return false;
}
エンクロージャーのタグに対応するリピーターのタグを戻す関数を以下の通り定義しておきました。これも単なる文字列の関数です。

/**
* Get the repeater tag from the enclosure tag.
*/
function repeaterTagFromEncTag( tag ) {
if ( tag == ‘TBODY’ ) return ‘TR’;
else if ( tag == ‘SELECT’ ) return ‘OPTION’;
else if ( tag == ‘UL’ ) return ‘LI’;
else if ( tag == ‘OL’ ) return ‘LI’;
else if ( tag == ‘DIV’ ) return ‘DIV’;
return null;
}
あるノードがリンクした要素だった場合、そこから親ノードをたぐり、エンクロージャーのノードを求めて返す関数です。見つからない場合にはnullを返します。たとえば、BODY要素の直下にあるINPUTだとnullを返し、結果的にはデータベースとのリンクはなされないことになります。

/**
* Detect the enclosure of the argument node.
*/
function getEnclosure( node ) {
<div class=”wiki_entry”><span class=”Apple-tab-span”> </span 2010-03-08 02:49:50
class=”Apple-tab-span”> </span>var detectedRepeater = null;</div>
var currentNode = node;
while ( currentNode != null ) {
if ( isRepeater( currentNode ) ) {
detectedRepeater = currentNode;
} else if ( isRepeaterOfEnclosure( detectedRepeater, currentNode ) ) {
return currentNode;
}
currentNode = currentNode.parentNode;
}
}
return null;
}
引数に指定した2つのノードが、リピーターとエンクロージャーの組み合わせかどうかを判定します。単にタグと、一部のタグについてはclass属性の値を調べて分岐させているだけです。

/**
* Check the pair of nodes in argument is valid for repater/enclosure.
*/
function isRepeaterOfEnclosure( repeater, enclosure ) {
if ( repeater == null || enclosure == null )
return false;
var repeaterTag = repeater.tagName;
var enclosureTag = enclosure.tagName;
if ( ( repeaterTag == ‘TR’ &amp;&amp; enclosureTag == ‘TBODY’ )
|| ( repeaterTag == ‘OPTION’ &amp;&amp; enclosureTag == ‘SELECT’ )
|| ( repeaterTag == ‘LI’ &amp;&amp; enclosureTag == ‘OL’ )
|| ( repeaterTag == ‘LI’ &amp;&amp; enclosureTag == ‘UL’ ))
return true;
else if ( repeaterTag == ‘DIV’ &amp;&amp; enclosureTag == ‘DIV’ ){</div>
var repeaterClass = getClassAttributeFromNode( repeater );
var enclosureClass = getClassAttributeFromNode( repeater );
if ( repeaterClass != null &amp;&amp; enclosureClass != null
&amp;&amp; repeaterClass.indexOf(‘_im_repeater’)&gt;=0
&amp;&amp; enclosureClass.indexOf(‘_im_enclosure’)&gt;=0 ) {
return true;
}
}
return false;
}
以下の2つのメソッドは、ノードにclass属性を設定する関数と、ノードからclass属性を取り出す関数です。getAttribute/setAttributeで取り出せるのですが、IEはclassName、他のブラウザはclassという属性名を指定する必要があるので、ブラウザによる分岐が必要になります。そのため、関数を作っておくのが得策でしょう。

function setClassAttributeToNode( node, className ) {
if (node == null) return ;
if ( ! navigator.appName.match(/Explorer/)) {
node.setAttribute( ‘class’, className );
} else {
node.setAttribute( ‘className’, className );
}
}

function getClassAttributeFromNode( node ) {
if (node == null) return ”;
var str = ”;
if ( ! navigator.appName.match(/Explorer/)) {
str = node.getAttribute( ‘class’ );
} else {
str = node.getAttribute( ‘className’ );
}
return str;
}