Greasemonkeyの0.8が出ましたね

いくつか新しい機能が増えてるのだけれど、@resourceが激しく便利

@resource

Value: String
Compatibility: Greasemonkey 0.8.0+
Usage: // @resource resourceName http://www.example.com/resource.png

* While the resourceName is non-semantic, it is suggested that it should be compatible with JavaScript variable naming conventions and XML/CSS naming conventions to help keep things consistent.
* Each resourceName must have a unique name.
* This metadata block key property may be used to include local and remote resources into the current script at first install but not successive installs. A user is required to uninstall the current user script before installing a newer version if new resource keys or require keys are present.
* Resources may include the scheme of file, http, https or ftp. If no scheme is provided, it is assumed to be the domain from which the script is installed including the full path.

外部のJS(例えばjQuery)を取り込んでユーザスクリプト内で使えると言う事
例えば

〜略〜
// @resource      jQuery http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js
// ==/UserScript==

(function(){
    eval(GM_getResourceText("jQuery")); //もちろんevalしないと駄目
〜略〜
    $("#main").append($("< /p>").text("hoge"));

みたいな感じ、しかもGzipが掛かっていてもOKだという素晴らしさ

同様にして他サイトのURLを指定するとまるまる持って来れたりするけどGM_xmlhttpRequestのresponseTextと同じだったりするので残念な感じ
もしかするとjQueryの機能で簡単にパース出来るかもしれないけど、今の所うまいやり方がわからず

あと、全体を()で括って無名関数にしている時の@requireの使いかたが?うちの図書館用のグリモンみたいにロジックは一緒だけど与えるURL等が異なる時にうまく使えればなぁ

Web::ScraperでXPath式を使ってパースする

今までHTML:TagParser*1で一生懸命切ったりはったりして所蔵検索の結果を組み立てていましたがWeb::Scraperを使うと同じ事がかなり楽にできるようなので試してみた

HTML::TagParserだと

〜略〜
Readonly my $QUERY_URL_PREFIX =>
    'https://library.city.iwaki.fukushima.jp/wehome/we/opac/kensakucheck.jsp?'
    . 'kensaku.x=35&kensaku.y=22&sryskb0=1&allsryskb0=1&sryskb1=2&allsryskb1=2&sryskb2=3'
    . '&allsryskb2=3&sryskb_length=3&taisyokan1=0&kanmei_length=6&max_kensu=10&KSKNO1=019'
    . '&KEYWORD1=&ITTI1=1&f_kanzen1=0&ANDOR2=0&KSKNO2=020&KEYWORD2=&ITTI2=1&f_kanzen2=0'
    . '&ANDOR3=0&KSKNO3=005&KEYWORD3=&ITTI3=1&f_kanzen3=0&ANDOR4=0&KSKNO4=070&KEYWORD4='
    . '&ITTI4=1&f_kanzen4=0&tandoku=120&tandoku_keyword='
    ;
Readonly my $QUERY_URL_SUFFIX =>
    '&siborikomi=040&hanni1=&hanni2='
    ;
Readonly my $DETAIL_URL =>
    'https://library.city.iwaki.fukushima.jp/wehome/we/opac/'
    ;

Readonly my $LIBNAME => {
    '総合' => '01',
    '小名' => '02',
    '勿来' => '03',
    '常磐' => '04',
    '内郷' => '05',
    '四倉' => '06',
};

my $obj = {
    key   => '07204',
    stock => 'false',
    uri   => [],
    lib   => {
        '01' => 'false',
        '02' => 'false',
        '03' => 'false',
        '04' => 'false',
        '05' => 'false',
        '06' => 'false',
    },
    name  => {
        '00' => 'いわき市立総合図書館',
        '01' => 'いわき市立総合図書館',
        '02' => 'いわき市立小名浜図書',
        '03' => 'いわき市立勿来図書館',
        '04' => 'いわき市立常磐図書館',
        '05' => 'いわき市立内郷図書館',
        '06' => 'いわき市立四倉図書館',
    },
};

my $html = HTML::TagParser->new($QUERY_URL_PREFIX . $isbn . $QUERY_URL_SUFFIX);
if (my @list = $html->getElementsByTagName( 'a' )) {
    for my $elem ( @list ) {
        my $attr = $elem->attributes;
        for my $vals ( values %$attr ) {
            if ( $vals =~ m{ \A itiranview }xms ) {
                push(@{ $obj->{uri} }, $DETAIL_URL . $vals );
                $obj->{stock} = 'true';
            }
        }
    }
};
for my $vals ( @{$obj->{uri}} ) {
    $html = HTML::TagParser->new( $vals );
    if (my @list = $html->getElementsByTagName( 'tr' )) {
        for my $elem ( @list ) {
            my $text = $elem->innerText();
            if ( $text =~ m{ \d{6,} }xms ) {
                ($text) = split(/\n/, $text);
                ${ $obj->{lib} }{ $LIBNAME->{ $text } || '01' } = 'true';
            }
        }
    }
}

指定したタグ配下がフラットなテキストになってしまうので切り出しが大変><

Web:Scraperだと

〜略〜
Readonly my $XPATH_1 => '//p[3]/table/tr/td[3]/a';
Readonly my $XPATH_2 => '//form/p[4]/table/tr/td[1]/b';
〜略〜
my $urls = scraper {
        process $XPATH_1, \&get_urls;
    }->scrape(URI->new($QUERY_URL_PREFIX . $isbn . $QUERY_URL_SUFFIX));


sub get_urls {
    my $node = shift;
    my $url  = URI->new_abs($node->attr('href'), $DETAIL_URL)->as_string;
    push(@{ $obj->{uri} }, $url);
    my $res = scraper {
            process $XPATH_2, \&get_details;
        }->scrape(URI->new($url));
    $obj->{stock} = 'true';
}

sub get_details {
    my $node = shift;
    my $text = encode_utf8($node->as_text);
    ${ $obj->{lib} }{ $LIBNAME->{ $text } || '01' } = 'true';
}

一寸試しただけでここまでシンプルに記述できるとは思いませんでした

追記

scraper {
        process $XPATH_1, \&get_urls;
    }->scrape(URI->new($QUERY_URL_PREFIX . $isbn . $QUERY_URL_SUFFIX));

resultを返さないタイプなので別に代入は不要

*1:このモジュールがダメなわけではなく、この使い方には向いてない

Amazonに図書館の検索結果を差し込むGreasemonkey

だいぶほったらかしになっていたAmazonの商品詳細ページから図書館の蔵書の有無を調べるGreasemonkeyの更新なのですが、
大まかに動作が確認出来たので再度途中経過など

大きな変更点としては、所増の有無の判定にXPathを使う事と検索中のメッセージを表示する事の2点
GM_xmlhttpRequestのレスポンスからノードに変換するのはhttp://userscripts.org/scripts/show/8551を参考にさせていただきました

サンプル

〜略〜
var XPATH = "//table[5]/tbody/tr/td[3]/a";
〜略〜
    GM_xmlhttpRequest({
        method : "GET",
        url    : QUERY_URL_PREFIX + isbn + QUERY_URL_SUFFIX,
        headers: {
                    'User-Agent'  : 'Mozilla/4.0 (compatible) Greasemonkey',
                    'Content-type': 'application/x-www-form-urlencoded'
                 },
        onerror : function(response) {
                var tx = STTS_NAME_5 + LIB_NAME_2;
                lk.appendChild(document.createTextNode(tx))
                loading.removeChild(loading.lastChild);
                loading.appendChild(df);
        },
        onload : function(response) {
            FlgRes = true;

            var htmlDoc = createHTMLDocumentByString(response.responseText);
            var nodes = document.evaluate(
                        XPATH, htmlDoc, null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
            switch(nodes.snapshotLength) {
                case 0:     // 所蔵なし
                    var tx   = STTS_NAME_1;
                    var href = QUERY_URL_PREFIX + isbn + QUERY_URL_SUFFIX;
                    break;
                case 1:     // 1件所蔵
                    var tx   = STTS_NAME_2;
                    var href = DETAIL_URL
                             + nodes.snapshotItem(0).getAttribute("href");
                    break;
                default:    // 複数所蔵
                    var tx   = STTS_NAME_3;
                    var href = DETAIL_URL
                             + nodes.snapshotItem(0).getAttribute("href");
            }
            lk.setAttribute('title', LIB_NAME_2);
            lk.setAttribute('href',  href);
            lk.appendChild(document.createTextNode(tx + LIB_NAME_2))
            df.appendChild(lk);
            loading.removeChild(loading.lastChild);
            loading.appendChild(df);
      }
    });

// utility functions.
function createHTMLDocumentByString(str) {
    var html = str.replace(/<!DOCTYPE.*?>/, '').replace(/<html.*?>/, '').replace(/<\/html>.*/, '')
    var htmlDoc  = document.implementation.createDocument(null, 'html', null)
    var fragment = createDocumentFragmentByString(html)
    try {
        fragment = htmlDoc.adoptNode(fragment)
    } catch(e) {
        fragment = htmlDoc.importNode(fragment, true)
    }
    htmlDoc.documentElement.appendChild(fragment)
   return htmlDoc
}

function createDocumentFragmentByString(str) {
    var range = document.createRange()
    range.setStartAfter(document.body)
    return range.createContextualFragment(str)
}

}
})();

雑感

全然やる気は無いのだけれどアイデアとして図書館固有の情報*1をサーバーに持つ形式にすると便利になるのかな?やらないけど

*1:館名とかurlとかXPath式など

はてなフォトライフの画像を外部のblogに簡単に貼れるように画像へのurlを表示されるユーザスクリプト ーその3ー

Greasemonkey専用と割り切って更に直してみた

主な修正点

  1. ノードの取得をXPathで書き換えた
  2. alt,title属性を付けるようにした
  3. 文字列の作成方法を変更した
    ちまちまと組み立てるのではなくcreateElementでエレメントを作成して最後にinnerHTMLで文字列に変換

ソース

// ==UserScript==
// @name           fotolife img src disp
// @namespace      http://d.hatena.ne.jp/natu_n/
// @include        http*://f.hatena.ne.jp/*
// ==/UserScript==

(
function(){
    var BASE   = 'http://f.hatena.ne.jp';
    var node, img;
    if (node = document.evaluate("//div[@class='foto-body']",
               document,
               null,
               XPathResult.FIRST_ORDERED_NODE_TYPE,
               null).singleNodeValue || null) {
        if (img = document.evaluate("./img",
                  node,
                  null,
                  XPathResult.FIRST_ORDERED_NODE_TYPE,
                  null).singleNodeValue || null) {
            var src = img.getAttribute("src");
            var ttl = img.getAttribute("title");
            var name = ttl
                     + " by "
                     + document.title.split("'")[0]
                     + ",on Hatena fotolife";
            var img = document.createElement("img");
            img.setAttribute("src", BASE + src);
            img.setAttribute("alt", name);
            
            var df  = document.createDocumentFragment();
            var div = document.createElement("div");
            div.style.textAlign = "right";

            var textBox = document.createElement("input")
            textBox.setAttribute("type",  "textbox");
            textBox.setAttribute("value", BASE + src);
            div.appendChild(document.createTextNode("Direct URL:"));
            div.appendChild(textBox);

            var div2 = document.createElement("div");
            div2.appendChild(img);
            textBox = document.createElement("input")
            textBox.setAttribute("type",  "textbox");
            textBox.setAttribute("value", div2.innerHTML);
            div.appendChild(document.createElement("br"));
            div.appendChild(document.createTextNode("image:"));
            div.appendChild(textBox);

            var link = document.createElement("a");
            link.setAttribute("src",   document.location.href);
            link.setAttribute("title", name);
            link.appendChild(img);
            div2 = document.createElement("div");
            div2.appendChild(link);
            textBox = document.createElement("input")
            textBox.setAttribute("type",  "textextBoxox");
            textBox.setAttribute("value", div2.innerHTML);
            div.appendChild(document.createElement("br"));
            div.appendChild(document.createTextNode("Embed:"));
            div.appendChild(textBox);

            df.appendChild(div);
            node.appendChild(df);
            
        }
    }

})();

備考

テキストボックスでタイトルを変更出来たりborderの有無とかを指定出来たりとかは不要ですよね?
あと、自動でクリップボードに入れるのは面倒なのでやりません

追記

流石にここまではやる必要ないよね

//  変更前
var src = img.getAttribute("src");
//  変更後
var src = document.evaluate("./@src",
          img, null, XPathResult.STRING_TYPE, null).stringValue;

はてなフォトライフの画像を外部のblogに簡単に貼れるように画像へのurlを表示されるユーザスクリプト ーその2ー

[id:sanbo-n:20080508:1210242770]で紹介されていたのでちょっと体裁を整えた
http://img.skitch.com/20080510-m6kjdwifbsq4ig1jeergxwsstt.png
コピーしたいテキストボックスの上でトリプルクリックで全選択してコピペして使ってください
titleやaltに"by ユーザ名,on Hatenafotolife"的な文言を付けようかと思ったけれどどうせ直すと思うのでやめました

ソース

// ==UserScript==
// @name           fotolife img src disp
// @namespace      http://d.hatena.ne.jp/natu_n/
// @include        http*://f.hatena.ne.jp/*
// ==/UserScript==

(
function(){
    var BASE   = 'http://f.hatena.ne.jp';
    var PREFIX = '<img src="http://f.hatena.ne.jp';
    var SUFFIX = '">'
    var nodes, img, src, ttl;
    nodes = getElementsByClassName(document.body, 'foto-body');
    if (nodes[0]) {
        if (img = nodes[0].getElementsByTagName("img")[0] || null) {
            src = img.getAttribute("src");
            ttl = img.getAttribute("title");
            var df = document.createDocumentFragment();
            var div = document.createElement("div");
            div.style.textAlign = "right";

            var tb = document.createElement("input")
            tb.setAttribute("type",  "textbox");
            tb.setAttribute("value", BASE + src);
            div.appendChild(document.createTextNode("Direct URL:"));
            div.appendChild(tb);

            tb = document.createElement("input")
            tb.setAttribute("type",  "textbox");
            tb.setAttribute("value", PREFIX + src + SUFFIX);
            div.appendChild(document.createElement("br"));
            div.appendChild(document.createTextNode("image:"));
            div.appendChild(tb);

            tb = document.createElement("input")
            tb.setAttribute("type",  "textbox");
            tb.setAttribute("value", "<a href=" + location.href
                                   + ">" + PREFIX + src + SUFFIX
                                   + "</a>");
            div.appendChild(document.createElement("br"));
            div.appendChild(document.createTextNode("Embed:"));
            div.appendChild(tb);

            df.appendChild(div);
            nodes[0].appendChild(df);
            
        }
    }

function getElementsByClassName(node, classname) {
    var a = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var els = node.getElementsByTagName("*");
    for(var i=0, j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

})();

備考

IE系のエンジンで動作を確認したら画像のURLが相対パスではなくフルパスで取得されてしまうのでこのままでは使えませんねp、だったらgetElementsByClassNameとかごちゃごちゃやらずにXPathですっきり書けばよかった

はてなフォトライフの画像を外部のblogに簡単に貼れるように画像へのurlを表示されるユーザスクリプト

[id:sanbo-n:20080506:1210084239]で画像の詳細でurlわかるからコピペでいいんじゃないか的なコメントしたのだけれど、なんか微妙なニュアンスなレスが返ってきたのでユーザスクリプトを書いてみた。
こんな感じで画像の下にimgタグが表示されるのでコピペで貼る等すればok
http://img.skitch.com/20080508-r6ypcymxhtaca6juern5ffxpyt.png
元画像へのリンク付きのタグでも良かったけれど(flickrみたいに)そこまでいるかしら?とも思ったので簡単な構造に

ソース

// ==UserScript==
// @name           fotolife img src disp
// @namespace      http://d.hatena.ne.jp/natu_n/
// @include        http*://f.hatena.ne.jp/*
// ==/UserScript==

(
function(){
    var PREFIX = '<img src="http://f.hatena.ne.jp';
    var SUFFIX = '">'
    var nodes, img, src;
    nodes = getElementsByClassName(document.body, 'foto-body');
    if (nodes[0]) {
        if (img = nodes[0].getElementsByTagName("img")[0] || null) {
            src = img.getAttribute("src");
            var df = document.createDocumentFragment();
            var cd = document.createElement('code');
            cd.appendChild(document.createTextNode(PREFIX + src + SUFFIX));
            df.appendChild(cd);
            nodes[0].parentNode.insertBefore(df, nodes[0].nextSibling);
        }
    }

function getElementsByClassName(node, classname) {
    var a = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var els = node.getElementsByTagName("*");
    for(var i=0, j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

})();

備考

多分ユーザスクリプトが使えるブラウザなら動作する筈です(IE7proはファイル名を書き換えて)