all files / lib/ cheerio-extend.js

98.72% Statements 77/78
94.12% Branches 32/34
100% Functions 10/10
98.72% Lines 77/78
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164                                  45×     45×       35×       10×               12× 12× 12×     12× 12×   12× 12×             10× 10×                 21× 12× 12×   21×   21× 21× 21×     21× 21×   21× 21×               19× 19× 80× 80× 80× 80×   73× 73× 24×   49×   42×         19× 17×   17×       19× 53×         19× 19× 53× 53× 53× 60× 60×     19× 18×     19× 19×   19×     19×        
/*eslint no-invalid-this:0*/
'use strict';
 
var cheerio   = require('cheerio');
var util      = require('util');
var assert    = require('assert');
var urlParser = require('url');
var ent       = require('ent');
 
/**
 * cheerioオブジェクト拡張モジュール(プロトタイプにメソッド追加)
 */
module.exports = function (encoding, client) {
  /**
   * cheerioデフォルトのtext(), html()メソッドはエンティティ表記をそのまま返すので
   * 上記メソッドを拡張してエンティティもデコードした状態で返すようにする
   *
   * @param str 指定した場合はその文字列を要素のテキスト(HTML)として設定
   *            指定しない場合は要素に設定されているテキスト(HTML)を返す
   */
  // cheerioデフォルトのメソッドを'_'付きで退避
  cheerio.prototype._text = cheerio.prototype.text;
  cheerio.prototype._html = cheerio.prototype.html;
 
  var decodeEntities = function (str) {
    // 文字列でない場合(cheerioオブジェクトなど)はそのまま返す
    Iif (typeof str !== 'string') {
      return str;
    }
    return ent.decode(str);
  };
 
  cheerio.prototype.text = function (str) {
    // cheerioデフォルトのtext()結果をデコード(エンティティ可読文字化)したものを返す
    return decodeEntities(this._text(str));
  };
 
  cheerio.prototype.html = function (str) {
    // cheerioデフォルトのhtml()結果をデコード(エンティティ可読文字化)したものを返す
    return decodeEntities(this._html(str));
  };
 
  /**
   * a要素のリンクのクリックをエミュレート(リンク先のページを取得)
   *
   * @param callback リクエスト完了時のコールバック関数(err, response, body(buffer))
   */
  cheerio.prototype.click = function (callback) {
    var doc = this._root[0]._documentInfo;
    var $ = cheerio;
    var $link = null;
 
    // a要素でなければエラー
    try {
      assert.ok(this.length > 0);
      // 複数ある場合は先頭のリンクのみ
      $link = $(this[0]);
      assert.ok($link.is('a'));
    } catch (e) {
      return client.error('element is not link', {
        param: { uri: doc.url },
        callback: callback
      });
    }
 
    var url = urlParser.resolve(doc.url, $link.attr('href'));
    return client.run('GET', url, callback);
  };
 
  /**
   * form要素からの送信をエミュレート
   *
   * @param param    疑似設定するフォーム送信パラメータ
   * @param callback リクエスト完了時のコールバック関数(err, response, body(buffer))
   */
  cheerio.prototype.submit = function (param, callback) {
    if (param instanceof Function) {
      callback = param;
      param = {};
    }
    param = param || {};
 
    var doc = this._root[0]._documentInfo;
    var $ = cheerio;
    var $form = null;
 
    // form要素でなければエラー
    try {
      assert.ok(this.length > 0);
      // 複数ある場合は先頭のフォームのみ
      $form = $(this[0]);
      assert.ok($form.is('form'));
    } catch (e) {
      return client.error('element is not form', {
        param: { uri: doc.url },
        callback: callback
      });
    }
 
    // フォーム送信パラメータ作成
    var formParam = {};
    $form.find('input,textarea,select').each(function (idx) {
      var name = $(this).attr('name');
      var type = $(this).attr('type');
      var value = $(this).val();
      if (! name) {
        return;
      }
      formParam[name] = formParam[name] || [];
      if (/(checkbox|radio)/i.test(type) && ! $(this).attr('checked')) {
        return;
      }
      if (util.isArray(value)) {
        formParam[name] = formParam[name].concat(value);
      } else {
        formParam[name].push(value);
      }
    });
 
    // フォーム内のデフォルトパラメータを引数で指定したパラメータで上書き
    Object.keys(param).forEach(function (p) {
      if (! param[p]) {
        param[p] = '';
      }
      formParam[p] = (util.isArray(param[p])) ? param[p] : [ param[p] ];
    });
 
    // 空パラメータでもname=のみで送信するための仕込み
    Object.keys(formParam).forEach(function (p) {
      if (formParam[p].length === 0) {
        formParam[p].push('');
      }
    });
 
    // 各種エンコーディングに対応したURLエンコードをする必要があるのでパラメータ文字列を自力で作成
    var formParamStr = '';
    Object.keys(formParam).forEach(function (p) {
      var fp = formParam[p];
      var pstr = '';
      for (var i = 0; i < fp.length; i++) {
        var escval = encoding.escape(doc.encoding, fp[i]);
        formParamStr += '&' + p + '=' + escval;
      }
    });
    if (formParamStr.length > 0) {
      formParamStr = formParamStr.substr(1);
    }
 
    var url = urlParser.resolve(doc.url, $form.attr('action') || '');
    var method = ($form.attr('method') || 'GET').toUpperCase();
    // GETの場合はURLに繋げてパラメータを空にする(そうしないと上手く動かないケースがたまにあった)
    if (method === 'GET') {
      var join = (url.indexOf('?') === -1) ? '?' : '&';
      if (formParamStr.length > 0) {
        url += join + formParamStr;
      }
      formParamStr = {};
    }
    return client.run(method, url, formParamStr, callback);
  };
 
  return cheerio;
};