IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

XPath に文字列を埋め込むときの注意

よく、以下のように XPath に文字列を埋め込む事があります

document.evaluate('//*[@class="' + text + '"]', document, null, 7, null);

まあ、僕もよくこんなコード書くんですけど。

でも、これって

text が外部から来るものだったら、意図通りの動作をしないんですよね
たとえば、以下のような例です。

var text = '"] | /hoge/fuga/piyo | .["';
document.evaluate('//*[@class="' + text + '"]', document, null, 7, null);

というわけで

任意の文字列を XPath の式に変換する JavaScript を書いてみた

以下で試せます

http://amachang.sakura.ne.jp/misc/xpath_escape/ ←クリック!

コードはこちら
function escapeXPathExpr(text) {
    var matches = text.match(/[^"]+|"/g);

    function esc(t) {
        return t == '"' ? ('\'' + t + '\'') : ('"' + t + '"');
    }

    if (matches) {
        if (matches.length == 1) {
            return esc(matches[0]);
        }
        else {
            var results = [];
            for (var i = 0; i < matches.length; i ++) {
                results.push(esc(matches[i]));
            }
            return 'concat(' + results.join(', ') + ')';
        }
    }
    else {
        return '""';
    }
}
以下のように使います
document.evaluate('//*[@class=' + escapeXPathExpr(text) + ']', document, null, 7, null);

まとめ

急いでコードを書いているときは忘れがちですが。
「文脈」と「任意のデータ」があれば、やっぱりそこにはエスケープの必要性があるということを忘れてはいけませんね

フィードをオートディスカバリーする XPath

こんな感じ

document.evaluate('/html/head/link[contains(concat(" ", @rel, " "), " alternate ") and (@type = "application/x.atom+xml" or @type = "application/atom+xml" or @type = "application/xml" or @type = "text/xml" or @type = "application/rss+xml" or @type = "application/rdf+xml")]', document, null, 7, null)

ブックマークレットにしてみる

javascript:var ___r=document.evaluate('/html/head/link[contains(concat(" ", @rel, " "), " alternate ") and (@type = "application/x.atom+xml" or @type = "application/atom+xml" or @type = "application/xml" or @type = "text/xml" or @type = "application/rss+xml" or @type = "application/rdf+xml")]',document,null,7,null);for(var ___t=[],___i=0;___i<___r.snapshotLength;___i++)___t.push(___r.snapshotItem(___i).href);alert(___t.join('\n'));

でも、この辺あまり詳しくないんです><
あまり、自身ないっす

XPath を指定すると、リンクに Pathtraq のスコアを付加する関数

はじめに

Pathtraq API を使ってもっといろいろできないかなあと思って、 XPath で指定したリンクに Pathtraq のスコアを付加する JavaScript の関数を作ってみました。
GreasemonkeyBookmarklet から使うことができます。

たとえば

関数を読み込んだ状態のはてなブックマークのページで以下のようなコードを叩くだけで

appendPtScore('//a[@class="bookmark"]');

以下のように XPath で選択した a 要素にパストラックのスコアを付加してくれます。

コード

function appendPtScore(xpath) {
  var self = arguments.callee;
  var obj = new self.PtObject(xpath);
  obj.timeout();
}
appendPtScore.PtObject = function(xpath) {
  var self = this;
  this.id = appendPtScore.PtObject.count++;
  this.xpath = xpath;
  var result = document.evaluate(xpath, document, null, 7, null);
  this.elms = [];
  for (var i = 0; i < result.snapshotLength; i ++) {
    this.elms.push(result.snapshotItem(i));
  }
  this.elmsLength = result.snapshotLength;
  this.elmsIndex  = 0;
  this.script     = null;
  appendPtScore['callback_' + this.id] = function(data) {
    self.callback(data)
  };
};
appendPtScore.PtObject.count = 0;
appendPtScore.PtObject.prototype = {
  callback: function(data) {
    var self = this;
    document.body.removeChild(this.script);
    this.script = null;
    var a = this.elms[this.elmsIndex++];
    var a2 = document.createElement('a');
    a2.href = 'http://pathtraq.com/page/' + a.href;
    var img = document.createElement('img');
    img.src = 'http://pathtraq.com/favicon.ico';
    a2.appendChild(img);
    var span = document.createElement('span');
    span.appendChild(document.createTextNode('(' + data.count + ')'));
    span.style.fontSize = '0.8em';
    span.style.color = '#88CCFF';
    span.style.fontWeight = 'bold';
    a2.style.textDecoration = 'none';
    a2.appendChild(span);
    a.parentNode.insertBefore(a2, a.nextSibling);
    setTimeout(function() { self.timeout() }, 1000);
  },
  timeout: function() {
    if (this.elmsLength <= this.elmIndex) return;
    if (this.elms[this.elmsIndex].tagName.toLowerCase() == 'a') {
      this.script = document.createElement('script')
      this.script.src = 'http://api.pathtraq.com/page_counter?m=popular&callback=appendPtScore.callback_' + this.id + '&api=json&url=' + encodeURIComponent(this.elms[this.elmsIndex].href);
      document.body.appendChild(this.script);
    }
    else {
      this.elmsIndex++;
      this.timeout();
    }
  }
};

ライセンス

Public Domain です。改変、再配布、商用なんでもどうぞ。
GreasemonkeyBookmarklet などにご自由にお使いください

まとめ

もっともっと Pathtraq API を使ったおもしろいアイデアもありそうですね!

駄文 - Selenium の中の人からメール来た><

超要約

JavaScript-XPath 続けてく気あるんかゴルァ!
バグの報告はどこにすればいいんじゃゴルァ!

ひー!

という訳で

とりあえず、コミット権が欲しかったら username と password 送ってって言ってみた。
もし、ホントに送ってきたら id:yappo さんに転送します。

XPathGraph のテクニック

XPathGraph とは

http://xpath.kayac.com/
URL と XPath を指定すると URL の先をスクレイピングしてグラフを作ってくれるサービスです。

このエントリでは、どのような手順で XPath を組み立てていけばいいかをよくあるパターンで解説します。

0. 値の元となる要素を探す

<div class="hoge">1,234,567</div>
//div[@class="hoge"]

1. カンマを外すなど、純粋な数値に変換する

1,234,567
translate(//div[@class="hoge"], ",", "")

translate 関数は、第一引数の div を文字列に変換し、カンマを空の文字に置換します(つまり、カンマを削ります)

2. 演算する

1234567
translate(//div[@class="hoge"], ",", "") div 1000

div は割り算の演算子です。 div によって、translate の結果の文字列が数値に変換されます。

3. 小数点を消す

1234.567
floor(translate(//div[@class="hoge"], ",", "") div 1000)

floor 関数によって小数点は切り捨てられます。XPathGraph では 1.2 は 12 としてグラフにされてしまうので、必ず割り算をしたときは小数点を切り捨てるようにしておきましょう。

4. 完成!

1234

あとは、毎日グラフをチェックしてニヤニヤしましょう。

XPathGraph のプラス演算子が無視されるバグと回避方法

以下のエントリで

加減乗除算ができると書きましたが
XPathGraph がすごい件と、XPath で出来ることのヒント - IT戦記
現状 XPathGraph では、プラス演算子が無視されるバグがあるようです。

バグの回避方法

XPath では、単項マイナス演算子が使えるので

1 + 1
1 -- 1

-- とすることで + を表現することができます。