[IM]新規レコード作成時の挙動

INTER-Mediatorで、テーブルに新たにレコードを作るときの挙動にバグがあり、全体的に見直しをしました。以下の内容は、Ver.3.4以降で有効です。

まず、「新規レコード作成」のクライアント側でのAPI、つまり、JavaScriptベースでの呼び出しは4カ所あります。最初の3つは、主としてINTER-Mediator自身が使うものです。

  • INTERMediator.insertButton(…):レコードの繰り返しの下などに表示されるボタン
  • INTERMediator.insertRecordFromNavi(…):ナビゲーションのボタン
  • INTERMediator.clickPostOnlyButton(node):新規レコード作成専用モード
  • INTERMediator_DBAdapter.db_createRecordWithAuth(args, completion):汎用

最後のメソッドは、INTERMediator_DBAdapter.db_createRecordでもかまいませんが、上記のメソッドだと認証のある場合、ない場合、どちらでも利用できます。

さて、データベース処理に関係する定義ファイル内のコンテキストのキーはまとめるとこんな感じです。ここでは、レコード作成に絞って考えます。

  • name:コンテキスト名、tableがないとこの名前のテーブルにINSERTする
  • table:実際のテーブル名を記述
  • view:(無視)
  • records:(ナビゲーションのボタンでレコードを作ったときの動作に影響)
  • key:(同上)
  • relation:(関連レコードの作成時に利用)
  • query:(この設定は無視する)
  • sort:(無意味)
  • default-values:(フィールドの初期値)
  • script:(新規レコード作成時に実行するスクリプトを指定、主にFileMaker)
  • アクセス権設定:(新規レコードの認可の定義)

一時期のINTER-Mediatorでは、queryキーで指定した内容を、初期値として設定するようなプログラムが組み込まれていました。そうしないと、新規レコードを作った後に「検索されない」という状況が発生するからです。しかし、それはdefault-valuesキーで指定をすることで、検索される状態は設定できるので、あえて、queryキーを無視するようにしました。

一方、relationキーについては、関連レコードのテンプレート処理側では、これに対応した外部キーへの値の設定がないと、新しく作ったレコードは関連レコードになりません。なので、INTERMediator.insertbuttonメソッド内で、コンテキストのこの情報を見て、外部キーのフィールドの初期値を自動的に与えています。なお、演算子は無視しているので、演算子に>があるような場合などは要注意です。INTERMediator.insertRecordFromNaviメソッドは、ナビゲーションのボタンで呼び出されます。従って、通常は関連レコードのコンテキストには適用されず、relationキーは無視します。INTERMediator_DBAdapter.db_createRecordWithAuthについても、relationキーは無視します。

一方、JavaScriptのプログラムで、検索条件、ソート条件、さらに新規レコードの初期値を指定するプロパティが以下のように定義されています。新規レコードの初期値はVer.3.4が初出です。

  • INTERMediator.additionalCondition:(無視する)
  • INTERMediator.additionalSortKey:(無意味)
  • INTERMediator.additionalFieldValueOnNewRecord:(初期値として適用される)

新規レコードではソート条件は関係ありません。検索条件のINTERMediator.additionalConditionについては、新規レコード作成時は一切無視されます。一方、すべての新規レコード作成時に、INTERMediator.additionalFieldValueOnNewRecordに指定した値が初期値として設定されます。

まとめると、新規レコード作成時には検索条件は一切利用しません。その代わり、スタティックな初期値としてのdefault-valuesキー、ダイナミックな初期値としてのINTERMediator.additionalFieldValueOnNewRecordが適用されます。また、関連レコード内でのInsertボタンを押したときにはrelationキーの値が利用されますが、その他の場合にはrelationキーは適用されません。

[IM]RSAを使って、JavaScriptで暗号化し、PHPで復号する

表題の通りの仕組みを、INTER-Mediatorに組み込もうとして、2日ほどもがきました。RSAつまり秘密鍵と公開鍵を使った暗号化をフレームワークに組み込み、クライアントで入力したパスワードを、暗号化してサーバに送るという仕組みを組み込んでいます。普通パスワードはハッシュだろうと思われるかもしれませんが、フレームワークが認証するパスワードではなく、パスワードをデータベースエンジンへのアクセスに使うべく、クライアントで入力したものをバックエンドまで安全に到達させるための暗号化なのです。

RSAやハッシュ関連のことは、ネイティブな開発ならOpenSSLを使えばおおむねOKなので、同じようにライブラリを集めれば楽勝だろうと思っていました。また、PHPにはopensslライブラリがあるので、まあ、なんとかなるだろうと思いました。要件としては、キーの指定を簡単にするということ。つまり、公開鍵と秘密鍵が1つもPEM形式で得られれば、それを設定ファイルに書き込むことでキーの指定ができるということです。もちろん、生成は「openssl rsagen 1024」みたいなコマンドで簡単に済ませたいわけです。

JavaScriptの世界では、Dave Shapiroさんが作っているライブラリがあり、後々のいくつかのライブラリの多くはこれを改良しています。DaveさんのはPEM形式でキーを与えられないので、Tom WoさんのCrypticoが作られたようで、さらに別の人が署名までできるようにしています。Crypticoでいろいろやっていたのですが、うまくいかない…というか、単純にこのライブラリの問題ではなく、PHPのライブラリとの組み合わせの問題があったのです。

それで、PHPの方はというと、openssl関連関数があるので大丈夫かと思っていたら、これがなかなかうまくいかいないのです。まず、PEMでキーを与えるのが原則とすれば、Crypticoを使ってクライアント側で暗号化するのが手軽です。それを、PHPのopensslの関数で復号するのがどうしてもうまくいかないのです。そのあたりのドキュメントが今ひとつ詳しく書いておらず、コメントには「マニュアルは正しくない…」などと書いてある始末で、あれこれやってもだめでした。

ただし、PHPのopensslの関数を使えば、PEMの鍵データから、RSAの本来の鍵というか、計算式に出てくる鍵の数値をそのまま得られる事がわかりました。さらに、RSA: Encrypting in JavaScript and Decrypting in PHPというドンピシャなものがあったのですが、それはDave Shapiroさんのライブラリを使っていて、RSAの本来の鍵を使う方法が記載されています。ところが、このサイトに書いてあるPHPのライブラリはpearにあるCrypt_RSAで、説明に使っているものはすでのメンテナンスがなされていません。それを使う手もありますが、メンテナンスされているライブラリの方がいいかと思い新しいサイトへのリンクを見ると、なんとこれが、以前このblogでも紹介したPHPだけで作られたSSHのライブラリを含むphpseclib:に移行していたのです。SSHのライブラリはきわめて順調に動いたので期待をして、こちらのRSAを使って復号しようとしました。しかしながら、どんなにこねくり回してもだめなのです。このライブラリのサポートのBBSにあるこの書き込みと同じ場所でエラーが出ているようです。Macだとだめで、debianだと動くようなことが書かれているのが気になるところですが、この方法でうまく流すには簡単には行きそうにありません。

Dave Shapiroさんのライブラリで暗号復号するのはできます。また、phpseclib:で暗号復号はできます。つまり、異なるライブラリをまたいでの暗号復号がうまくいかないのです。Dave Shapiroさんのライブラリで暗号化したものを、opensslコマンドで復号してみると、パディングに関するエラーが出て復号できません。データ形式の微妙な違いがあるのでしょうか? ドキュメンテーションの薄いライブラリや、あまりカスタマイズできないライブラリということで、これ以上手を下すことができず、ここで行き詰まってしまいました。

それで、改めてググって必死に探した結果、見つけたのがbi2phpというライブラリで、しかもMIT Licenseです。JavaScriptのライブラリは、Daveさんのものに独自に改良したものだそうです。それと互換でPHPのクラスを作ったというところでしょうか。いずれも、RSAの本来の鍵を指定するのですが、「データは全部HEX」というように、ある意味非常にわかりやすく、さくっとうまく行きました。

ということで、わかった事をまとめておきます。RSAの秘密鍵、公開鍵という仕組みはもういいとして、そこで使う鍵は、たとえば「openssl genrsa 1024」というコマンド入力で作られます。そのデータはPEMという1つのテキスト列であり、いわば、そこに秘密鍵と公開鍵などなど、RSA暗号処理に必要なデータがPKCSのルールに従ってパックされたものです。

$ openssl genrsa 1024
Generating RSA private key, 1024 bit long modulus ...................++++++ ............++++++ e is 65537 (0x10001) -----BEGIN RSA PRIVATE KEY----- MIICXwIBAAKBgQC/BlONnPUfSc95YmrcOUV0IbmeBZvibbAssetKBXAG0DGeKzc7        : 2MgfcIZ3C7lf0+yx3/RhXwJBAIYHkht7UPSpPeTvPzc4v89yBlkkGeN9xLbdONT3 uzINAQkGvVmDhNLYqxkgDysBUy/Q2f41DenUZJfEFLQBs5w= -----END RSA PRIVATE KEY-----

PHP側では、このPEM形式の文字列を使って、以下のプログラムで、RSAの計算に使うキーを求めることができます。$generatedPrivateKeyにはPEM形式のキーの文字列、圧縮アルゴリズムがAESの場合はパスフレーズを入れるので、その場合は$passPhraseに指定しますが、パスフレーズが設定されていない鍵は2つ目の引数は適当に無視します。

$res = openssl_pkey_get_private( $generatedPrivateKey, $passPhrase );
$keyArray = openssl_pkey_get_details($res);

この、$keyArrayの配列にいろいろなものが入っていて、公開鍵だけを取り出すには、$keyArray[‘key’]とします。

Daveさんのライブラリを使うには、鍵のオブジェクトを作成しますが、当然、自分で生成するのではなく、このPEMを鍵としてオブジェクトを初期化します。初期化関数部分は、次のように定義されています。biRSAKeyPairが鍵のクラス名と考えていいでしょう。

function biRSAKeyPair(encryptionExponent, decryptionExponent, modulus)

ここで、encryptionExponentは$keyArray[‘rsa’][‘e’]、decryptionExponentは$keyArray[‘rsa’][‘d’]、modulusは$keyArray[‘rsa’][‘n’]から得られます。生成した鍵からopensslのコマンドを使って出力(例えば、openssl rsa -text -in gen.key)した結果を見ながら、対応付けを確認しました。配列から得られた結果はバイナリなので、PHPの場合、bin2hex関数を使って16進文字列にして、JavaScript側の引数に指定をします。2次元目のeとdは、encrypt、decryptなんでしょうね。

なお、INTER-Mediatorでは、JavaScript側では暗号化しかしないので、2番目の引数は指定しません。というか、指定してクライアント側で見えたらいけません。なぜなら、$keyArray[‘rsa’][‘d’]は秘密鍵であり、暗号化には使わないからです。いずれにしても、JavaScriptのプログラムをサーバ側のPHPで生成してクライアントに送り込むという部分がこうした処理を組み合わせて作る事ができます。

bi2phpのPHP側のクラスは、JavaScriptとほぼ同様なプログラムでいいようになっており、サンプルのHTMLを見ればプログラムの作り方はすぐにわかると思います。非常に紆余曲折がありましたが、JavaScriptとPHPの双方で暗号復号をするには、bi2phpというライブラリでOKということです。ただし、PHPのopenssl関数を使って、PEMから鍵の値を得たりということは処理として必要になるということです。このライブラリをまとめたAndrey Ovcharenkoさんに感謝するともに、ライブラリをここまで育て上げたみなさんに感謝します。

[IM]INTER-Mediatorの認証フレームワーク(2)

少し前に書いた記事「INTER-Mediatorの認証フレームワーク」でのプロトコルでは、中間者攻撃に弱いのが分かったので、最初に1回やりとりを増やしました。

cd573

こうすれば、中間者が得られるデータは、un、ch、h(ch, h(pw))となります。しかしながら、正しいリクエストでは、h(ch, h(pw))の計算におけるh(pw)つまりパスワードにハッシュをかけたデータが必ず必要になります。中間者はそれを持っていないので、正しいリクエストを生成できません。(当然ですが、ハッシュなので、h(ch, h(pw))からその計算の元になっているh(pw)は求められないという前提があります)

また、チャレンジは毎回異なる(確率的に同一である可能性は0とみなせる)とすれば、毎回チャレンジの値が変わり、「以前と同じチャレンジが生成されるまで待つ」という手段もとれなくなります。また、毎回チャレンジが変わることで、同じ通信内容でも生成したデータが異なることになり、過去のデータをため込むことでの一致を見つける手法もほとんど役に立たないと思われます。

これで、クライアントとサーバの間の通信経路のセキュリティはとりあえずクリアできたとします。

アプリケーションの実行する上では、クライアントのJavaScriptはある意味、プログラムを勝手に実行されて変数の値などを取り出されたり設定できる可能性があります。その状態で懸念されるのは、他人への成り済ましです。自分のアカウントでログインをした後、javascript:…でユーザ名の変数を書き換えて、アクセスが成立すると問題です。特にその方法で他人に成り済ますのは重大なセキュリティの問題になります。

上記の方法では、仮にユーザ名を変えることができても、チャレンジとユーザは対応関係があるため、ユーザ名の変更をしてしまった後では必ず認証エラーが出るはずです。

開発者が意図したテーブル以外にアクセスできるか…ですが、データベースのアカウントはサーバから移動しないので(ただし、デバッグモードで見える事もある)クライアントで参照はできない。つまり、データベースの直接続は無理です。リクエストのテーブル名を変更する事もできません。定義ファイルに記述したテーブルとそれへのアクセスしかできないので、たとえば、一般には見せていないユーザ管理テーブルを参照することも不可能です。

さて、また、少し塩漬けします(笑)。

[IM]INTER-Mediatorの認証フレームワーク

INTER-Mediatorはだいぶんと実用的になってきました。そろそろ、先のアーキテクチャを考えはじめています。認証のフレームワークをどうするか、いろいろ考えています。要は、AJAXでの認証となると、単にHTTPでの処理と同じという情報しか得られませんでした。それでもいいのですが、Webアプリケーションでの苦労をもう一度行うことになります。認証は確かにできるのですけど、その後、アクセス権の管理をアプリケーションに組み込まないといけません。たとえば、ユーザごとに参照できるレコードが決まっている場合、認証結果からクエリのパラメータを調整するなどの処理が必要になります。下手をすると、ユーザ名をうまく切り替えたら他人になってしまえるようなものになってしまいかねません。

つまり、認証とアクセス制御をフレームワークで処理をすることによって、アプリケーション作成の上で、安全確実に認証をすることを考えたいと思いました。

とは言え、独自にプロトコルを組むというのも大変な話です。ここで、AWSのやり方をヒントにして、ハッシュをリクエストに入れて認証をするという方式を利用してみることにします。

前提は、データベースがあって、ユーザ、ハッシュしたパスワード、チャレンジ、チャレンジの発行タイムスタンプを持つテーブルを利用するとします。これらはフィールド名は決め打ちにしてしまうつもりですが、他のフィールドは自由に作成できます。その上で、以下のようなシーケンスを考えてみました。h( ) はハッシュを求める関数のつもりです。48623

現在、テーブルは自由に利用できますが、たとえば “security” という属性を作り、そこにフィールド名を記述すると、そのフィールド名に、ユーザ名と同じデータしか取り出さないようにするという仕組みにしたいと思います。たとえば、personテーブルがあれば、

“name”=>”person”,
“security”=>”accessibleusername”,…

のように定義ファイルを作ります。WHERE句で、accessibleusername = ユーザ名、という条件を常に付ければいいかと思います。なお、”security” 属性をたとえば、”__im_authonly” にすると、認証が通ればすべてのデータを参照できるといったような仕組みも入れておこうかと思っています。

あと、ユーザデータベースの保護についても考えないといけませんね。まあ、でも、定義ファイルにあるテーブルしか処理できないという仕組みを徹底させればいいのかなと思ったりします。何か穴があれば、ぜひともご指摘ください。

[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サーバで公開していないところにも移動できるようになります。

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

[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 ) {

}