1.6 入力、バリデーション、関数

(1/1)
パソコンのキーボードを打っているイラスト
前々回作った 1から10までの整数の和を求めるプログラムだが、任意の整数区間の和を求めることができように画面からパラメータを入力できるようにする。整数以外が入力されたらエラーメッセージを表示できるよう、入力バリデーションを加える。

目次

サンプル・プログラム

パラメータの入力

前々回作った 1から10までの整数の和を求めるプログラムだが、開始値a、終了値bの値を変えることで、任意の整数区間の和を求めることができる。そこで、開始値と終了値の2つを画面から入力できるようにしてみよう。
パラメータの入力
"input1.html" は、開始値と終了値の2つのパラメータをHTMLフォームから入力し、計算結果をHTMLとしてブラウザに表示するプログラムである。1と10を入力して計算ボタンをクリックすると、上図のようになる。

  44: <!-- 入力と表示 -->
  45: <input type="text" id="from" size="4" value="" />から
  46: <input type="text" id="to"   size="4" value="" />の和は
  47: <span id="sum"></span>です.
  48: <input type="button" id="exec" value="計算" onClick="execute()" />
  49: 

まずHTMLの部分――<input>タグを使って、開始値(idがfrom)、終了値(idがto)を入力する。計算結果は前回同様、<span>タグ(idがsum)である。
計算をさせるためのボタン <input type="button" id="exec"> を用意した。このボタンをクリックすると、onClick属性で指定しているJavaScriptの execute()関数(後述)を呼び出す。

  10: /**
  11:  * 計算と画面表示
  12: */
  13: function execute() {
  14:     //変数宣言
  15:     let a = document.getElementById('from').value;  //開始値
  16:     a = parseInt(a, 10);                            //10進整数化
  17:     let b = document.getElementById('to').value;    //終了値
  18:     b = parseInt(b, 10);                            //10進整数化
  19:     let n = 0;                                      //合計値
  20: 
  21:     //aからbまでの整数の合計を求める
  22:     for (let i = ai <bi++) {
  23:         n +i;
  24:     }
  25: 
  26:     //結果を表示する
  27:     document.getElementById('sum').innerHTML = n;
  28: }

次にJavaScriptの部分――基本的な部分は前回作ったプログラムと同じだが、"document.getElementById('from').value" によって、idがfromのHTMLフォームの値を取り出すというところが新しい。

HTMLフォームから取り出した値は文字列なので、計算できるように parseInt関数を使って整数に変換する。

また、このブロック全体が execute()関数 として定義されている。関数については、追い追い解説する。ここでは、ボタンがクリックされたら呼び出されるプログラムと考えておいてほしい。

入力値のバリデーション

"input1.html" は、小数や文字列を入力できてしまう。
文字列が混じっていても parseInt 関数が 0 を返すので forループ が回らないが、小数の場合は計算してしまう。
そこで、小数や文字列が入力されたらエラーメッセージを表示して計算しないようにしてみよう。
入力値のバリデーション
入力値のバリデーション
"input2.html" は、小数や文字列が入力されたらエラーメッセージを表示して計算しないようにしたプログラムである。プログラムの流れは上図の通り。

まず、画面から入力されたデータ(文字列)を変数a, bに代入する。これは "input1.html" と同じである。
次に、入力されたa, bの先頭や末尾の空白を除去する。trim メソッドを使う。これはWebアプリに限らず、入力の前処理作法の1つだ。
それから、入力されたa, bをエスケープする。ここでいうエスケープとは、HTMLの特殊文字 &, ", ', <, > をそのまま表示するとHTMLのタグなどに解釈されて表示が崩れるため、エンティティ参照に変換する処理のことである。Webアプリでは、入力ミスもそうだが、悪意のある第三者が入力する可能性もあるので、エスケープ処理を行うなどして、プログラムの誤動作を防ぐようにしなければならない。

このあとがバリデーション(validation)である。ここでいうバリデーションとは、入力されたデータが期待通りの値であることを検証する処理を指す。
ここでは、変数a, bが整数(0,1,2‥‥9の文字)であること、a<b であることの2つを検証する。2つの検証をクリアしたらforループに入る、それ以外ならエラーメッセージを表示して計算しないようにする。

  26: /**
  27:  * 計算と画面表示
  28: */
  29: function execute() {
  30:     //変数宣言
  31:     let a = document.getElementById('from').value;  //開始値
  32:     let b = document.getElementById('to').value;    //終了値
  33:     let n = '';                                     //合計値
  34:     let errmsg = '';                                //エラーメッセージ
  35: 
  36:     //空白除去
  37:     a = a.trim();
  38:     b = b.trim();
  39: 
  40:     //入力文字のエスケープ
  41:     a = specialCharacters2HTMLentities(a);
  42:     b = specialCharacters2HTMLentities(b);
  43: 
  44:     //バリデーション
  45:     if (a.length === 0) {
  46:          errmsg = '開始値に整数を入力してください';
  47:     } else if (b.length === 0) {
  48:          errmsg = '終了値に整数を入力してください';
  49:     } else if (! a.match(/^[0-9]+$/gi)) {
  50:          errmsg = '"' + a + '" は整数ではありません';
  51:     } else if (! b.match(/^[0-9]+$/gi)) {
  52:          errmsg = '"' + b + '" は整数ではありません';
  53:     } else {
  54:         a = parseInt(a, 10);                        //10進整数化
  55:         b = parseInt(b, 10);                        //10進整数化
  56:         if (a >b) {
  57:              errmsg = b + 'は' + a + 'より大きな整数にしてください';
  58:         } else {
  59:             //aからbまでの整数の合計を求める
  60:             n = 0;
  61:             for (let i = ai <bi++) {
  62:                 n +i;
  63:             }
  64:         }
  65:     }
  66:     //エラー・メッセージがあれば整形
  67:     if (errmsg !== '') {
  68:         errmsg = 'エラー:' + errmsg + '.';
  69:     }
  70:     
  71:     //結果を表示する
  72:     document.getElementById('sum').innerHTML = n;
  73:     document.getElementById('error').innerHTML = errmsg;
  74: }

この流れを実装したのが新しい関数 execute である。
また、エスケープを行うユーザー関数 htmlspecialchars を用意した。

"if (a.length === 0) {...}" は、変数aに入っている文字列の長さが0、つまり何も文字が入っていないとき(空文字)はブロック内を実行するという if文)である。
そうでなく、"else if (b.length === 0) {...}" は、変数bに入っている文字列の長さが0、つまり何も文字が入っていないとき(空文字)はブロック内を実行するという else if文)である。
そうでなく、"else if (! a.match(/^\[0-9\]+$/gi)) {...}" は、変数aに0,1,2‥‥9以外の文字が含まれていないとき、ブロック内を実行するという if文)である。
そうでなく、"else if (! b.match(/^\[0-9\]+$/gi)) {...}" は、変数bに0,1,2‥‥9以外の文字が含まれていないとき、ブロック内を実行するという else if文)である。
上記いずれでもない場合は "else {...}" のブロックを実行する。
さらに elseブロックの中で、a<b を検証するif文が入れ子になっており、そのelse文のブロック(すべてのverificationをクリアした場合)に、計算のためのforループがある。

このようにif~else文やforループは、ブロックの中に入れ子にすることができる。詳しいことは、プログラムの制御で説明する。
matchメソッドは正規表現のマッチング結果を受け取るもので、これも追々解説する。

  10: /**
  11:  * 特殊文字をHTMLエンティティに変換する
  12:  * @param   string str 文字列
  13:  * @return  string 変換後文字列
  14: */
  15: function specialCharacters2HTMLentities(str) {
  16:     let ss = str + '';
  17:     ss = ss.replace(/&/g, '&amp;');
  18:     ss = ss.replace(/&#x22/g, '&quot;');
  19:     ss = ss.replace(/&#x27/g, '&#x27;');
  20:     ss = ss.replace(/</g, '&lt;');
  21:     ss = ss.replace(/>/g, '&gt;');
  22: 
  23:     return ss;
  24: }

前述の通り、specialCharacters2HTMLentities は、入力文字列strをエスケープして返す関数である。これも正規表現を使っているが、詳しいことは関数のところで説明する。

コラム:エスケープ処理

ハッカー・ネットワーク犯罪のイラスト(セキュリティー)
入力した文字列をエラーメッセージで表示するような場合で、もしエスケープ処理をしなかったら――
<script>window.open("/");</script>
――という文字列を入力すると、エラーを表示した途端、ぱふぅ家のホームページへジャンプさせることができてしまう場合がある。
このような予期しない動作をさせるサイバー攻撃のことを総称してクロスサイトスクリプティング(XSS)攻撃と呼ぶ。
今回はHTMLの特殊文字をエンティティに変換するだけだが、データベースにアクセスするようなときにはSQLインジェクションにも配慮する必要がある。

人間が入力したデータや他システムから送られてくるデータは、かならずしも予期したとおりのものでないことがある。入力に対しては、かならず、バリデーション(エスケープ処理を含む)を組み込む習慣を身につけよう。
(この項おわり)
header