jquery: Tabulator 出来てみた5.5(「AJAX Data Loading」ない AJAX Data Loading)

ちょっと作りたいものがあって、その過程からのネタ。

jquery: Tabulator 出来てみた5(AJAX Data Loading) でこんなことを書いた:

ただ…、そうなんだけど「結局データローダは自分で書くことが多いだろう」は相変わらずだと思うよ。たとえば https://www.metaweather.com/api/location/1118370/ だと consolidated_weather の外に結構色んな情報が入ってるでしょう? 何が主役かにもよるけれど、このデータ全体を活用したいという際に、無理やり Tabulator の ajaxResponse を入り口にする必要はないと思う。(つまりテーブルに突っ込む表データとそうでない情報を別々の UI に一度に流し込みたい、なんて良くあるでしょうよ。)

まさにこれをしたくなったのですわ。

何をしたいのか? 何を考えてるのか? 最終的に何をしたいかは言わない。目先の話だけね。

  1. jquery: Tabulator 出来てみた5(AJAX Data Loading) で触れた JSON 祭りてくれるありがたい API のポータルから Jikan の存在を知る。
  2. ところがこれ、「まだフルセットじゃない」だけならまだしも、Person は壊れている(なぜか この人 だけうまくいく)。
  3. OSS なので直して使えるかなぁと思うも、PHP なんぞで作りおってからに、しんでくれ。
  4. 一応少しだけ PHP のまま弄ぼうとしかけたけれど果てたので、もう全部自分でやっちまえ、と。
  5. けどサーバサイドでの処理を避けたいなぁ。

まぁこういう流れで、最後の「サーバサイドでの処理を避けたい」は、これは心理的には「うー、Python でやっちまいたいけど…」との闘い。javascript/jQuery にそんなに長けてるわけじゃない、というよりは正直「不慣れ極まりない」ので、「サーバの管理が面倒になるのはヤダ」という思いは強くても、なかなか腰を上げるのは大変。やだ…、けどやる、ふんっ。

ひとまず「ローカルにダウンロードしたページをパースする」処理を、node.js で何も苦労しないように jQuery などの依存を避けて「愚直に」書いた。ターゲットとするページは people/nnn であり、これは予め wget でダウンロードして(tk.html 名で保存して)いる:

aaa.js
 1 // ------------------------------------------
 2 function split_by(s, tag, fun) {
 3     var reg_st = new RegExp("<" + tag, "g");
 4     var reg_end = new RegExp("</" + tag + ">");
 5     var trashattrs = [
 6 	new RegExp(' [vh]?align="[^"]+"', "g"),
 7 	new RegExp(' class="[^"]+"', "g"),
 8 	new RegExp(' width="[^"]+"', "g"),
 9 	new RegExp(' height="[^"]+"', "g"),
10 	new RegExp(' border="[^"]+"', "g"),
11 	new RegExp(' alt="[^"]+"', "g"),
12 	new RegExp(' title="[^"]+"', "g"),
13 	new RegExp(' nowrap', "g"),
14 	new RegExp('<a href="[a-z:/.]+login.php[^"]+">add</a>', "g"),
15 	new RegExp('</?div>', "g"),
16 	new RegExp('</?td>', "g"),
17 	new RegExp(' ', "g"),
18 	new RegExp('[\n ]*$'),
19 	]
20     var result = [];
21     var m;
22     while ((m = reg_st.exec(s))) {
23         var ls = s.slice(m.index);
24         var part = ls.slice(0, reg_end.exec(ls).index + "</td>".length);
25         if (fun) {
26             result.push(fun(part));
27         } else {
28             trashattrs.forEach(function(rgx, i) {
29                     part = part.replace(rgx, "");
30                 });
31             result.push(part);
32         }
33     }
34     return result;
35 }
36 function parse_person(html) {
37     var reg_va_st = /<\/div>Voice Acting Roles<\/div>/;
38     var reg_sp_st = /<\/span>Anime Staff Positions<\/div>/;
39     var rv = reg_va_st.exec(html);
40     var rs = reg_sp_st.exec(html.slice(rv.index));
41     var va_part = html.slice(rv.index).slice(0, rs.index);
42     var tmp = split_by(va_part, "tr", function(s) {
43             return split_by(s, "td");
44         });
45     var result = [];
46     tmp.forEach(function(row, i) {
47             var matches = [
48                 (new RegExp(
49                     '<a href="https://myanimelist.net/anime/([0-9]+)/.*"><img data-src="(.*)"></a>')).exec(row[0]),
50                 (new RegExp('<a href=".*">(.*)</a>')).exec(row[1]),
51                 (new RegExp('<a href=".*">(.*)</a>(.*)')).exec(row[2]),
52                 (new RegExp(
53                     '<a href="https://myanimelist.net/character/([0-9]+)/.*"><img data-src="(.*)"></a>')).exec(row[3]),
54             ];
55             result.push({
56                     "anime_id": matches[0][1],
57                     //"anime_thumbnail": matches[0][2],
58                     "anime_name": matches[1][1],
59                     "character": matches[2][1],
60                     "main_support": matches[2][2],
61                     "character_id": matches[3][1],
62                     //"character_thumbnail": matches[3][2],
63                 });
64         });
65     console.log(result);
66     return result;
67 }
68 // ------------------------------------------
69 var fn = process.argv[2];
70 var fs = require('fs'),
71     path = require('path'),    
72     filePath = path.join(__dirname, fn);
73 
74 fs.readFile(filePath, {encoding: 'utf-8'}, function(err, data) {
75     if (!err) {
76         parse_person(data)
77     } else {
78         console.log(err);
79     }
80 });
81 // ------------------------------------------

お尻のほうの process.argv 部分から下が node.js 前提の処理で、コマンドラインからこんな具合に動かしながら作ってた:

1 [me@host: ~]$ node.exe aaa.js tk.html | less

綺麗なコードなのかどうかはよくわからん。とにかく出来るやり方でやった。そもそも MAL のページは行ってソースの表示してみてもらえればわかるが、全然構造化されてないので非常にパースしずらく、「DOM 的に」やろうとしても無駄。Jikan もそうだが、ワタシのコードも「ただのテキストファイルであるのだこれは」と思い込んで処理してる、ので、少なくともコードが美しく見えないのはそれのせいもある。

というわけで、あとはこのパース処理を、Tabulator の AJAX Data Loading 機能ではない jQuery そのものの ajax でページをお取り寄せるコードから呼び出せばいいだけだわと:


いつものように「フレームのソースを表示」してもらえればわかる。Tabulator の AJAX 連携機能を使ってない、てことを鑑賞しとくれ。


一応言っとくとワタシが最終的にやりたいことは Tabulator そのものとはあんまし関係ないんだけれど、ただ書いたパーサが正しく動いてるか「わかりやすく」確認するにはやっぱりこういう美しいテーブルが良くて、なので「ついでなので」ということで今回のネタになった。


2018-01-14 14:40追記:
「Tabulator 自身が持ってる AJAX 連携機能を使わない」動機は今回の場合は「json を返してくれるわけではないサーバの相手をしたい」が主だったが、「つまりテーブルに突っ込む表データとそうでない情報を別々の UI に一度に流し込みたい、なんて良くあるでしょうよ。」の例にはなってなかったよね。こういうこと:


ページをパースする処理は大々的に書き換えてるけどそっちは今回のネタでは本題じゃない。Tabulator に流し込むデータと Tabulator 以外に流し込むデータをどっちも一緒に処理してるとこに着目してもらえればいい。


2018-01-14 21:20追記:
さっきの追記で終わりにしとこうと思っていたんだけど、今時点の方がより「「Tabulator 自身が持ってる AJAX 連携機能を使わない」動機」が伝わるかなと思ったんで:


モノ自体は随分進化しているけれど、無論本題はそれではなくて、「「Tabulator 自身が持ってる AJAX 連携機能を使わない」動機」に着目してな。



Related Posts