root/lang/javascript/userscripts/jautopagerize.user.js @ 1966

Revision 1966, 14.3 kB (checked in by cho45, 2 years ago)

lang/javascript/userscripts/jautopagerize.user.js:

Fx でも HTMLDocument つくってやるように

  • Property svn:keywords set to Rev Date
Line 
1// ==UserScript==
2// @name        jAutoPagerize
3// @description Just Another AutoPagerize
4// @namespace   http://lowreal.net/
5// @include     http://*
6// ==/UserScript==
7// This script uses JavaScript 1.7, 1.8 functions:
8//    Array.prototype.{filter,forEach,map,reduce} etc...
9//
10// $Date$
11//
12// Latest::  http://svn.coderepos.org/share/lang/javascript/userscripts/jautopagerize.user.js
13
14// define export object;
15AutoPagerize = {};
16AutoPagerize.VERSION = "jautopagerize $Rev$";
17AutoPagerize.Config  =
18{ site_info_urls : [ 'http://swdyh.infogami.com/autopagerize'
19                   , 'http://userjs.oh.land.to/pagerization/convert.php?file=siteinfo.v5'
20                   ]
21, site_info      : [ {}
22                   /* template
23                   , { url          : ''
24                     , nextLink     : ''
25                     , insertBefore : ''
26                     , pageElement  : ''
27                   }
28                   */
29                   ]
30, color          : { on         : '#22a06d'
31                   , off        : '#cccccc'
32                   , loading    : '#00587f'
33                   , terminated : '#000000'
34                   , error      : '#990000'
35                   }
36, cache_expire   : 24 * 60 * 60 * 1000
37, remain_height  : 400
38, autostart      : true
39};
40
41/*
42 * filter APIs
43 */
44AutoPagerize.filters = [];
45AutoPagerize.addFilter = function (fun) {
46        AutoPagerize.filters.push(fun);
47};
48
49window.AutoPagerize = AutoPagerize;
50
51(function () {
52        if (window != window.parent) return;
53        BeCompatible();
54
55        /*
56         * define utilities
57         */
58
59        // Async is similar to Mochikit Deferred.
60        function Async () { this.init.apply(this, arguments) }
61        Async.prototype = {
62                init : function () {
63                        this.chain = { ok: [], ng: [] };
64                },
65
66                ready : function (value) {
67                        return this.call("ok", value);
68                },
69
70                error : function (value) {
71                        return this.call("ng", value);
72                },
73
74                call : function (okng, value) {
75                        if (typeof value == "function") {
76                                this.chain[okng].push(value);
77                        } else { try {
78                                var c = this.chain[okng];
79                                for (var i = 0; i < c.length; i++) {
80                                        c[i](value);
81                                }
82                        } catch (e) { this.call("ng", e) } }
83                        return this;
84                }
85        };
86
87        function AsyncList (asyncs) {
88                var ret = new Async();
89                var values = [];
90                asyncs.forEach(function (a, i) {
91                        a.ready(function (v) {
92                                asyncs.pop();
93                                values[i] = v;
94                                if (asyncs.length == 0) {
95                                        ret.ready(values);
96                                }
97                        });
98                        a.error(function (v) {
99                                ret.error(v);
100                        });
101                        values.push(null);
102                });
103                return ret;
104        }
105
106        function Resource (uri, convertfun) {
107                var a = new Async();
108                if (!convertfun) convertfun = function (i) { return i };
109                GM_xmlhttpRequest({
110                        method  : "GET",
111                        url     : uri,
112                        onload  : function (req) { try {
113                                var res = convertfun(req.responseText);
114                                a.ready(res);
115                        } catch (e) { a.error(e) } },
116                        onerror : function (e) {
117                                a.error(e);
118                        }
119                });
120                return a;
121        }
122
123        function HTMLResource (uri) {
124                function createDocumentFromString (s) {
125                        s = String(s);
126                        s = s.replace(/<script[^>]+>([^\s]|\s)*?<\/script>/g, "");
127                        s = s.replace(/<\/?(i?frame|html|script|object)[^<>]+>/g, "");
128                        if (document.implementation.createHTMLDocument) {
129                                var d = document.implementation.createHTMLDocument("");
130                                var r = d.createRange();
131                                while (d.documentElement.firstChild) d.documentElement.removeChild(d.documentElement.firstChild);
132                                r.selectNodeContents(d.documentElement);
133                                d.documentElement.appendChild(r.createContextualFragment(s));
134
135//                              d.documentElement.innerHTML = s;
136//                              alert(d.documentElement.innerHTML);
137                                return d;
138                        } else {
139                                log("fallback: use div to parse HTML");
140                                $X.forceRelative = true;
141                                var d = document.createElement("div");
142                                d.innerHTML = s;
143                                return d;
144                        }
145                }
146                return Resource(uri, createDocumentFromString);
147        }
148
149        function CachedResource (uri, convertfun, expire) {
150                var a   = new Async();
151                var key = uri;
152                var v   = eval(GM_getValue(key)) || ({});
153                a.clear = function () {
154                        GM_setValue(key, "");
155                        return this;
156                };
157                if (v.time && v.time > (new Date).getTime() - expire) {
158                        log("Cache Hitted: " + key);
159                        setTimeout(function () { a.ready(v.body); }, 10);
160                } else {
161                        log("Cache expired; getting... " + key);
162                        GM_xmlhttpRequest({
163                                method  : "GET",
164                                url     : uri,
165                                onload  : function (req) { try {
166                                        var res = convertfun(req.responseText);
167                                        GM_setValue(key, {time:(new Date).getTime(), body:res}.toSource());
168                                        log("Cached: " + key);
169                                        a.ready(res);
170                                } catch (e) { a.error(e) } },
171                                onerror : function (e) {
172                                        a.error("HTTPError:"+e);
173                                }
174                        });
175                }
176                return a;
177        };
178
179        /*
180         * jAutoPagerize
181         */
182
183        AutoPagerize.parseSiteInfo = function (html) {
184                var d = h(html);
185
186                var siteinfo = [];
187                $X(".//*[@class='autopagerize_data']", d).forEach(function (e) {
188                        // using replace as scan and folding key/value to i
189                        var i = {}; e.value.replace(/^\s*([^:\s]+):\s*(.*)$/gm, function (m, key, value) {
190                                i[key] = value;
191                        });
192                        siteinfo.push(i);
193                });
194                return siteinfo;
195        };
196
197        AutoPagerize.onscroll = function (e) {
198                var height = document.documentElement.scrollHeight || document.body.scrollHeight;
199                var remain = height - window.innerHeight - window.scrollY;
200                if (AutoPagerize.enable && (remain < AutoPagerize.Config.remain_height)) {
201                        AutoPagerize.loadNext();
202                }
203        };
204
205        AutoPagerize.toggle = function () {
206                AutoPagerize.enable = !AutoPagerize.enable;
207                AutoPagerize.updateStatus();
208                return AutoPagerize.enable;
209        };
210
211        AutoPagerize.loadNext = function () {
212                if ( AutoPagerize._loading) return;
213                if (!AutoPagerize._nextURI) {
214                        AutoPagerize._terminate = true;
215                        AutoPagerize.updateStatus();
216                        return;
217                }
218
219                AutoPagerize._loading = true;
220                HTMLResource(AutoPagerize._nextURI).ready(function (r) {
221                        AutoPagerize.filters.forEach(function (f) { f(r) });
222                        setTimeout(function () { try {
223                                // in Safari 3 & GreaseKit. ib is often undefined
224                                // and freeze as refering this...
225                                var ib  = AutoPagerize._insertBefore;
226                                var pib = ib.parentNode;
227                                pib.insertBefore(h("<hr /><p>AutoPagerized: <a href='%s'>%s</a></p>".replace(/%s/g, AutoPagerize._nextURI)), ib);
228                                $X(AutoPagerize._pageinfo.pageElement, r).forEach(function (i) {
229                                        log(String(i));
230                                        i = document.importNode(i, true);
231                                        pib.insertBefore(i, ib);
232                                });
233
234                                AutoPagerize._nextURI = ($X(AutoPagerize._pageinfo.nextLink, r)[0] || {}).href;
235                                AutoPagerize._loading = false;
236                                AutoPagerize.updateStatus();
237                        } catch (e) { AutoPagerize.errorHandler(e) } }, 10);
238                }).error(AutoPagerize.errorHandler);
239                AutoPagerize.updateStatus();
240        };
241
242        AutoPagerize.updateStatus = function () {
243                var i = AutoPagerize.icon;
244                var s = i.style;
245                s.fontSize   = "0.7em";
246                s.position   = "fixed";
247                s.top        = "0";
248                s.right      = "0";
249                s.margin     = "0.3em";
250                s.padding    = "0 1em";
251                s.color      = "#fff";
252                s.zIndex     = "9999";
253                s.fontWeight = "bold";
254                s.lineHeight = "1.33";
255
256                if (AutoPagerize._error) {
257                        i.innerHTML  = AutoPagerize._error;
258                        s.background = AutoPagerize.Config.color["error"];
259                } else
260                if (AutoPagerize._terminate) {
261                        i.innerHTML  = ".";
262                        s.background = AutoPagerize.Config.color["terminated"];
263                } else
264                if (AutoPagerize._loading) {
265                        i.innerHTML  = "loading...";
266                        s.background = AutoPagerize.Config.color["loading"];
267                } else {
268                        i.innerHTML  = ".";
269                        s.background = AutoPagerize.Config.color[AutoPagerize.enable ? "on" : "off"];
270                }
271        };
272
273
274        AutoPagerize.init = function () {
275                // init/define all state variables
276                AutoPagerize.enable        = AutoPagerize.Config.autostart;
277                AutoPagerize.icon          = h("<div id='autopagerize_icon'/>").firstChild;
278                AutoPagerize._loading      = false;
279                AutoPagerize._terminate    = false;
280                AutoPagerize._error        = null;
281                AutoPagerize._pageinfo     = [];
282                AutoPagerize._nextURI      = null;
283                AutoPagerize._insertBefore = null;
284
285                // get siteinfo
286                AsyncList(AutoPagerize.Config.site_info_urls.map(function (url) {
287                        var cr = CachedResource(url, AutoPagerize.parseSiteInfo, AutoPagerize.Config.cache_expire);
288                        if (location.href == url) cr.clear();
289                        return cr;
290                })).ready(function (r) {
291                        r = AutoPagerize.Config.site_info.concat(r.reduce(function (r, i) { return r.concat(i) }));
292                        var info = r.filter(function (i) {
293                                if (!i.url)          return false;
294                                if (!i.nextLink)     return false;
295                                if (!i.pageElement)  return false;
296                                if (!i.insertBefore) return false;
297                                return location.href.match(i.url) && !!$X(i.insertBefore)[0];
298                        });
299                        if (!info.length) return;
300
301                        AutoPagerize._pageinfo     = info[0];
302                        AutoPagerize._nextURI      = ($X(AutoPagerize._pageinfo.nextLink)[0] || {}).href;
303                        AutoPagerize._insertBefore = $X(AutoPagerize._pageinfo.insertBefore)[0];
304                        document.body.appendChild(AutoPagerize.icon);
305
306                        window.addEventListener("scroll",   AutoPagerize.onscroll, false);
307                        window.addEventListener("dblclick", AutoPagerize.toggle,   false);
308
309                        AutoPagerize.updateStatus();
310                }).error(AutoPagerize.errorHandler);
311        };
312
313        AutoPagerize.errorHandler = function (e) {
314                AutoPagerize._error = String(e);
315                AutoPagerize.updateStatus();
316                alert(e.toSource());
317        };
318
319        // Set error handler to all functions
320        for (var i in AutoPagerize) {
321                if (AutoPagerize.hasOwnProperty(i) &&
322                    typeof AutoPagerize[i] == "function") {
323                        AutoPagerize[i] = (function (f) {
324                                return function () { try {
325                                        return f.apply(AutoPagerize, arguments);
326                                } catch (e) { AutoPagerize.errorHandler(e) } };
327                        })(AutoPagerize[i]);
328                }
329        }
330
331        // Run!
332        window.addEventListener("load", function () {
333                AutoPagerize.init();
334        }, false);
335
336
337        function BeCompatible () {
338                // Memo::
339                // Safari 3 has JS 1.6 functions but 1.8 and toSource.
340
341                if (!Array.prototype.reduce) {
342                        Array.prototype.reduce = function(fun /*, initial*/) {
343                                var len = this.length;
344                                if (typeof fun != "function") throw new TypeError();
345
346                                // no value to return if no initial value and an empty array
347                                if (len == 0 && arguments.length == 1) throw new TypeError();
348
349                                var i = 0;
350                                if (arguments.length >= 2) {
351                                        var rv = arguments[1];
352                                } else {
353                                        do {
354                                                if (i in this) {
355                                                        rv = this[i++];
356                                                        break;
357                                                }
358
359                                                // if array contains no values, no initial value to return
360                                                if (++i >= len) throw new TypeError();
361                                        } while (true);
362                                }
363
364                                for (; i < len; i++) {
365                                        if (i in this) rv = fun.call(null, rv, this[i], i, this);
366                                }
367
368                                return rv;
369                        };
370                }
371
372                if (!Object.prototype.toSource) {
373                        Object.prototype.toSource = function () {
374                                var props = [];
375                                for (var key in this) {
376                                        if (this.hasOwnProperty(key)) {
377                                                var v;
378                                                switch (typeof this[key]) {
379                                                        case "undefined": v = "undefined"; break;
380                                                        case "null"     : v = "null"; break;
381                                                        default         : v = this[key].toSource();
382                                                }
383                                                props.push(key.toSource() + ":" + v);
384                                        }
385                                }
386                                return "({" + props.join(",") + "})";;
387                        };
388
389                        String.prototype.toSource = function () {
390                                return '"' + this.replace(/"/g, '\\"') + '"';
391                        };
392
393                        Array.prototype.toSource = function () {
394                                return "[" + this.map(function (i) { return i.toSource() }).join(",") + "]";
395                        };
396
397                        Date.prototype.toSource = function () {
398                                return "(new Date(%d))".replace(/%d/, this.getTime());
399                        };
400
401                        Number.prototype.toSource = function () {
402                                return String(this);
403                        };
404
405                        RegExp.prototype.toSource = function () {
406                                return String(this);
407                        };
408
409                        Boolean.prototype.toSource = function () {
410                                return String(this);
411                        };
412                }
413
414                // Firefox doesn't have createHTMLDocument
415                if (!document.implementation.createHTMLDocument &&
416                    navigator.userAgent.match(/Firefox/)) {
417                        var x = new XSLTProcessor();
418                        x.importStylesheet((new DOMParser).parseFromString([
419                                "<stylesheet version='1.0' xmlns='http://www.w3.org/1999/XSL/Transform'>",
420                                "<output method='html'/>",
421                                "</stylesheet>"].join("\n"), "application/xml"));
422                        return x.transformToDocument((new DOMParser).parseFromString("<nice-boat/>", "application/xml"));
423                }
424
425//              [
426//                      {},
427//                      {date:new Date,body:[{a:1,b:2}]}
428//              ].forEach(function (i) {
429//                      alert(i.toSource());
430//              });
431//
432//              throw "";
433        }
434
435        /*
436         * template functions
437         */
438
439        function log (m) {
440//              var c = unsafeWindow.console;
441//              if (c) c.log.apply(c, arguments);
442                var o = Array.prototype.concat.apply([], arguments);
443                if (window.console) {
444                        window.console.log(o.join(", "));
445                } else {
446                        location.href = "javascript:(function () { if (window.console) console.log.apply(console.log, "+o.toSource()+") })();";
447                }
448        }
449
450        function h (s) {
451                var d = document.createElement("div");
452                d.innerHTML = s;
453                return d;
454        }
455
456        // extend version of $X
457        // $X(exp);
458        // $X(exp, context);
459        // $X(exp, type);
460        // $X(exp, context, type);
461        function $X (exp, context, type /* want type */) {
462                if (arguments.callee.forceRelative) {
463                        exp = exp.replace(/id\((?:"([^"]+)"|'([^']+)')\)/g, function (_, v1, v2) {
464                                return '//*[@id="' + (v1 || v2) + '"]';
465                        });
466                        if (exp.indexOf("(//") == 0)
467                                exp = "(.//" + exp.substring(3);
468                        else
469                                exp = ((exp[0] == "/") ? "." : "./") + exp;
470                }
471                log("xpath:" + exp);
472
473                if (typeof context == "function") {
474                        type    = context;
475                        context = null;
476                }
477                if (!context) context = document;
478                var exp = (context.ownerDocument || context).createExpression(exp, function (prefix) {
479                        var o = document.createNSResolver(context)(prefix);
480                        if (o) return o;
481                        return (document.contentType == "application/xhtml+xml") ? "http://www.w3.org/1999/xhtml" : "";
482                });
483
484                switch (type) {
485                        case String:
486                                return exp.evaluate(
487                                        context,
488                                        XPathResult.STRING_TYPE,
489                                        null
490                                ).stringValue;
491                        case Number:
492                                return exp.evaluate(
493                                        context,
494                                        XPathResult.NUMBER_TYPE,
495                                        null
496                                ).numberValue;
497                        case Boolean:
498                                return exp.evaluate(
499                                        context,
500                                        XPathResult.BOOLEAN_TYPE,
501                                        null
502                                ).booleanValue;
503                        case Array:
504                                var result = exp.evaluate(
505                                        context,
506                                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
507                                        null
508                                );
509                                var ret = [];
510                                for (var i = 0, len = result.snapshotLength; i < len; i++) {
511                                        ret.push(result.snapshotItem(i));
512                                }
513                                return ret;
514                        case undefined:
515                                var result = exp.evaluate(context, XPathResult.ANY_TYPE, null);
516                                switch (result.resultType) {
517                                        case XPathResult.STRING_TYPE : return result.stringValue;
518                                        case XPathResult.NUMBER_TYPE : return result.numberValue;
519                                        case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
520                                        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: {
521                                                // not ensure the order.
522                                                var ret = [];
523                                                var i = null;
524                                                while (i = result.iterateNext()) {
525                                                        ret.push(i);
526                                                }
527                                                return ret;
528                                        }
529                                }
530                                return null;
531                        default:
532                                throw(TypeError("$X: specified type is not valid type."));
533                }
534        }
535
536//      unsafeWindow.$X = $X;
537
538})();
Note: See TracBrowser for help on using the browser.