6.1 HTMLによる入出力

(1/1)
プログラミングをする人のイラスト(女性)
1.5 画面表示」や「1.6 入力、バリデーション、関数」などで紹介したとおり、JavaScriptはHTMLファイルそのものをオブジェクトと認識している。
つまり、画面出力やキーボード入力は、そのスクリプトが動作しているHTMLファイル(documentオブジェクト)のプロパティにデータを出力したり入力することで実現する。

目次

サンプル・プログラム

圧縮ファイルの内容
output3.htmlHTMLへの出力
input3.htmlHTMLからの入力
input4.html入力バリデーション
output3.html 更新履歴
バージョン 更新日 内容
1.0.1 2023/08/14 コメント追加
1.0 2021/08/17 初版
input3.html 更新履歴
バージョン 更新日 内容
1.1.0 2023/08/14 specialCharacters2HTMLentities()見直し
1.0 2021/08/21 初版
input4.html 更新履歴
バージョン 更新日 内容
1.1.0 2023/08/14 specialCharacters2HTMLentities()見直し
1.0 2021/08/21 初版

HTMLへの出力

JavaScriptによる画面出力は、これまでのプログラムで見てきたように、そのスクリプトが動作しているHTMLオブジェクトのプロパティにデータを代入する形をとる。ここで代表的な方法を整理しておこう。
HTML出力

  55:     //データをセット
  56:     document.getElementById('output1').innerHTML = OUTPUT1;
  57:     document.getElementById('output2').value     = OUTPUT2;
  58:     document.getElementById('output3').value     = OUTPUT3;
  59: }

HTMLへ出力するには、document オブジェクトを利用する。あらかじめ出力先のオブジェクトにid名を付けておこう。getElementById メソッドを使って、出力先のオブジェクトを選択する。

pタグやdivタグのように、直接HTMLとして出力するタイプのオブジェクトのときは、innerHTML プロパティに代入する。ここではプレーン・テキストを代入しているが、HTML文を代入することもできる。
さらに、JavaScriptのプログラムそのものを代入することができるのだが、後述する脆弱性に繋がるので注意しておきたい。

inputタグやtextareaタグのように、属性valueに表示データを記述するタイプのオブジェクトのときは、value プロパティに代入する。

  49:     //スタイルシートをセット
  50:     const IDs = [ 'input1', 'input2', 'output1', 'output2', 'help' ];
  51:     IDs.forEach(function (id) {
  52:         document.getElementById(id).style.width = (WIDTH - 30).toString() + 'px';
  53:     });
  54: 

スタイルシートの値を設定したいときには、このように style プロパティの属性値に代入する。
ここでは、複数の要素に同じスタイルシート(width)を指定したいので、指定するIDを配列に持たせ、forEachメソッドを使って一気に処理している。

HTMLからの入力

JavaScriptによるキーボード出力は、これまでのプログラムで見てきたように、そのスクリプトが動作しているHTMLオブジェクトのプロパティに入っているデータを取得する形をとる。ここで代表的な方法を整理しておこう。
HTML入力

  75: /**
  76:  * 実行ボタン押下処理
  77:  * @param   なし
  78:  * @return  なし
  79: */
  80: function execute() {
  81:     let str1 = document.getElementById('input1').value;
  82:     let str2 = document.getElementById('input2').value;
  83: 
  84:     //入力文字のエスケープ
  85:     str1 = specialCharacters2HTMLentities(str1);
  86:     str2 = specialCharacters2HTMLentities(str2);
  87: 
  88:     //結果を表示
  89:     document.getElementById('output1').innerHTML = 'input1:' + str1;
  90:     document.getElementById('output2').innerHTML = 'input2:' + str2;
  91: }

HTMLから入力するには、document オブジェクトを利用する。あらかじめ入力元のオブジェクトにid名を付けておこう。getElementById メソッドを使って、入力元のオブジェクトを選択する。

入力元として利用可能なオブジェクトは、inputタグやtextareaタグである。value プロパティの値を取得する。

入力バリデーション

  40: /**
  41:  * 特殊文字をHTMLエンティティに変換する
  42:  * @param   String str 文字列
  43:  * @return  String 変換後文字列
  44: */
  45: function specialCharacters2HTMLentities(str) {
  46:     //文字列置換テーブル
  47:     //regx: 置換パターン
  48:     //regs: 置換文字列
  49:     const CONVERT_TABLE = [
  50:     {
  51:         regx:       /&/g,
  52:         reps:       '&',
  53:     }, {
  54:         regx:       /&#x22/g,
  55:         reps:       '"',
  56:     }, {
  57:         regx:       /&#x27/g,
  58:         reps:       ''',
  59:     }, {
  60:         regx:       /</g,
  61:         reps:       '&lt;',
  62:     }, {
  63:         regx:       />/g,
  64:         reps:       '&gt;'
  65:     }];
  66: 
  67:     let ss = str + '';
  68:     CONVERT_TABLE.forEach(function (item) {
  69:         ss = ss.replace(item['regx'], item['reps']);
  70:     });
  71: 
  72:     return ss;
  73: }

1.6 入力、バリデーション、関数」で紹介したが、入力文字列に &, ", ', <, > が含まれていると、HTML出力するときに予期しない表示となったり、JavaScriptそのものを実行される可能性がある。悪意をもってこういう動作をさせることを XSS攻撃(クロスサイトスクリプティング攻撃)と呼ぶ。
XSS攻撃を回避するため、正規表現を用い、これらの文字をエンティティ参照に変換する specialCharacters2HTMLentities 関数を用意した。
置換パターン(regx)と置換文字列(regs)のセットをオブジェクト配列 CONVERT_TABLE として用意した。この配列を編集すれば、容易に他のパターンを置換することができるようになる。
次に、さまざまな入力に対するバリデーションを行うプログラムを作ってみることにする。ここでは、整数、小数、文字列(英数字)、文字列(日本語を含む)、パスワードの5種類の入力を想定する。
入力バリデーション

  76: /**
  77:  * 文字列のバリデーション
  78:  * @param   String data      チェックする文字列
  79:  * @param   Number min, max  文字列長の最短/最長(NaNなら無視)
  80:  * @param   Array  pattern   適合パターン(正規表現)配列(OR条件)
  81:  * @return  String '':成功/その他はエラーメッセージ
  82: */
  83: function validString(data, min, max, patterns) {
  84:     //引数チェック
  85:     if (! Array.isArray(patterns)) {
  86:         return '引数patternsが間違っています';
  87:     }
  88:     if (! isNaN(min&& ! Number.isInteger(min)) {
  89:         return '引数minが間違っています';
  90:     }
  91:     if (! isNaN(max&& ! Number.isInteger(max)) {
  92:         return '引数maxが間違っています';
  93:     }
  94:     if (! isNaN(min&& ! isNaN(max&& (min > max)) {
  95:         return '引数minまたはmaxが間違っています';
  96:     }
  97:     if (data == '') {
  98:         return '引数dataが空です';
  99:     }
 100: 
 101:     //長さチェック
 102:     if (! isNaN(min&& (data.length < min)) {
 103:         return min.toString() + '文字より短い';
 104:     }
 105:     if (! isNaN(max&& (data.length > max)) {
 106:         return max.toString() + '文字より長い';
 107:     }
 108: 
 109:     //パターンマッチング
 110:     let flag = false;
 111:     patterns.forEach(function (regx) {
 112:         //1つでもマッチすればフラグを立てて脱出する.
 113:         if (data.match(regx)) {
 114:             flag = true;
 115:             return;
 116:         }
 117:     });
 118:     
 119:     return flag ? '' : '規定外の文字が含まれている';
 120: }

基本になるのは、引数として与えられた文字列をバリデーションするユーザー関数 validString である。文字列の最短、最大、および適合するパターンを正規表現で指定する。
前半は、この関数に渡される引数そのもののバリデーションを行う。
続いて、文字列の長さをチェックする。
最後に、パターンに一致するかどうかをチェックする。配列の要素の1つでも一致していればOKとする。

 251:     //英数字
 252:     let pat3 = [/^[a-z0-9]+$/i];

英数字かどうかをバリデーションしたければ、上記のような正規表現を渡す。

 259:     //日本語
 260:     let pat4 = [/^[\x00-\x7F\u3041-\u3096\u30A1-\u30FA々〇〻\u3400-\u9FFF\uF900-\uFAFF\uD840-\uD87F\uDC00-\uDFFF]+$/];

日本語を含めると、正規表現がかなり長くなる。ブラウザによってUnicodeプロパティを指定することができないため、Unicodeの16進表記で範囲指定している。

 122: /**
 123:  * 整数のバリデーション
 124:  * @param   String  data      チェックする数字
 125:  * @param   Number  min, max  最小値,最大値(NaNなら無視)
 126:  * @param   Boolean comma     true:区切りカンマを認める/false:認めない
 127:  * @return  String '':成功/その他はエラーメッセージ
 128: */
 129: function validInteger(data, min, max, comma) {
 130:     //パターン
 131:     const pat1 = [/^[0-9\+\-]+$/];
 132:     const pat2 = [/^[0-9\+\-(\,)]+$/];
 133: 
 134:     //引数チェック
 135:     if (! isNaN(min&& ! Number.isInteger(min)) {
 136:         return '引数minが間違っています';
 137:     }
 138:     if (! isNaN(max&& ! Number.isInteger(max)) {
 139:         return '引数maxが間違っています';
 140:     }
 141:     if (! isNaN(min&& ! isNaN(max&& (min > max)) {
 142:         return '引数minまたはmaxが間違っています';
 143:     }
 144:     if (typeof(comma!'boolean') {
 145:         return '引数commaが間違っています';
 146:     }
 147: 
 148:     //パターンマッチング
 149:     let res, str, n;
 150:     if (comma) {
 151:         res = validString(data, NaN, NaN, pat2);
 152:         if (res !'')      return res;
 153:         //コンマ除去
 154:         str = data.replace(pat2, function(match, p1) {
 155:             return '';
 156:         });
 157:         n = parseInt(str, 10);
 158:     } else {
 159:         res = validString(data, NaN, NaN, pat1);
 160:         if (res !'')      return res;
 161:         n = parseInt(data, 10);
 162:     }
 163: 
 164:     //値の範囲チェック
 165:     if (!isNaN(min&& (n < min))    return min.toString() + 'より小さい';
 166:     if (!isNaN(max&& (n > max))   return max.toString() + 'より大きい';
 167: 
 168:     return '';
 169: }

引数として与えられた整数をバリデーションするユーザー関数 validInteger は、まず、validString を呼び出して数字列かどうかをチェックする。
桁区切りとしてカンマを認めているときは、カンマを除去してから整数化して値域チェックを行う。

 171: /**
 172:  * 小数のバリデーション
 173:  * @param String  data      チェックする数字
 174:  * @param Number  min, max  最小値,最大値(NaNなら無視)
 175:  * @param Boolean exp       true:指数表記を認める/false:認めない
 176:  * @return String '':成功/その他はエラーメッセージ
 177: */
 178: function validFloat(data, min, max, exp) {
 179:     //パターン
 180:     const pat1 = [/^[0-9\+\-\.]+$/];
 181:     const pat2 = [/^[0-9\+\-\.eE]+$/];
 182: 
 183:     //引数チェック
 184:     if (! isNaN(min&& (typeof(min!'number')) {
 185:         return '引数minが間違っています';
 186:     }
 187:     if (! isNaN(max&& (typeof(max!'number')) {
 188:         return '引数maxが間違っています';
 189:     }
 190:     if (! isNaN(min&& isNaN(max&& (min > max)) {
 191:         return '引数minまたはmaxが間違っています';
 192:     }
 193:     if (typeof(exp!'boolean') {
 194:         return '引数commaが間違っています';
 195:     }
 196: 
 197:     //パターンマッチング
 198:     let res, str, n;
 199:     res = validString(data, NaN, NaN, exp ? pat2 : pat1);
 200:     if (res !'')  return res;
 201:     x = parseFloat(data);
 202: 
 203:     //値の範囲チェック
 204:     if (!isNaN(min&& (x < min))    return min.toString() + 'より小さい';
 205:     if (!isNaN(max&& (x > max))   return max.toString() + 'より大きい';
 206: 
 207:     return '';
 208: }

引数として与えられた小数をバリデーションするユーザー関数 validFloat も、流れは validInteger と同じである。

コラム:セキュリティ対策

ハッカー・ネットワーク犯罪のイラスト(セキュリティー)
XSS攻撃について触れたように、JavaScriptのようにインターネット上で不特定多数の利用を想定したプログラムでは、悪意のある第三者からのサイバー攻撃を防ぐサイバーセキュリティ対策が必要になってくる。バリデーションより上位の対策になる。
たとえばパスワード入力だが、サンプル・プログラムでは何度も続けて入力することができるようにしているが、本来、3度パスワードを間違えたらロックをかける(しばらく入力できないようにする)方がいいだろう。reCAPTCHA を使って、自動的な入力を回避する方法も有効である。

ぱふぅ家ホームページでも、ログ解析してみたところ、入力を許しているページへの無意味なアクセスが非常に多い。サイトの脆弱性を突いて、そこを踏み台にして他者への攻撃を行ったり、サイト改ざんしてマルウェアをばら撒くなどの目的でアクセスしているのだろう。

このシリーズは入門者向けということで、サイバーセキュリティ対策については詳しく解説しないが、自作プログラムをインターネット上で公開するときは、悪意のある第三者に狙われていることを常に意識していただきたい。
(この項おわり)
header