青空文庫へのリンクを生成するグリモンのテスト

new AozoraBunko().getXhtmlUrl('梶井 基次郎', '檸檬', function(url) { console.log(url) });

 ↓

http://www.aozora.gr.jp/cards/000074/./files/424_19826.html
// ==UserScript==
// @name           TestAozora
// @namespace      http://d.hatena.ne.jp/hetappi
// @include        http://www.google.com/intl/ja/
// ==/UserScript==

var ab = new AozoraBunko;
// ab.getAuthorId('梶井 基次郎', function(id) { console.log(id) });
// ab.getCards(74, function(cards) { cards.forEach(function(card) { console.log(card.id) }) });
// ab.getCardId(74, '檸檬', function(id) { console.log(id) });
// ab.getCardUrl('梶井 基次郎', '檸檬', function(url) { console.log(url) });
ab.getXhtmlUrl('梶井 基次郎', '檸檬', function(url) { console.log(url) });

// AozoraBunko
function AozoraBunko() {
  return {
    // todo: cache
    _getAuthors: function(callback) {
      new TinyScraper({
        '//ol/li': {
          'authors[]': {
            './a': {
              author: 'text',
              id : 'substring-before(substring-after(@href, "person"), ".html")'
            }
          }
        }
      }).scrape(
        'http://www.aozora.gr.jp/index_pages/person_all.html', callback, { encoding: 'euc-jp' });
    },
    // todo: cache
    _getCards: function(authorId, callback) {
      new TinyScraper({
        '//ol/li': {
          'cards[]': {
            './a': {
              title: 'text',
              id : 'substring-before(substring-after(@href, "' + authorId + '/card"), ".html")'
            }
          }
        }
      }).scrape(
        'http://www.aozora.gr.jp/index_pages/person' + authorId + '.html', callback, { encoding: 'euc-jp' });
    },
    
    getAuthors: function(callback) {
      this._getAuthors(function(resp) { callback(resp.authors) });
    },
    getAuthorId: function(author, callback) {
      this._getAuthors(function(resp) {
        for (var i = 0, m = resp.authors.length; i < m; ++i) {
          if (resp.authors[i].author.replace(' ', '') == author.replace(' ', '')) {
            callback(resp.authors[i].id);
            return;
          }
        }
        callback(null);
      });
    },
    getCards: function(authorId, callback) {
      this._getCards(authorId, function(resp) { callback(resp.cards) });
    },
    getCardId: function(authorId, title, callback) {
      this._getCards(authorId, function(resp) {
        for (var i = 0, m = resp.cards.length; i < m; ++i) {
          if (resp.cards[i].title == title) {
            callback(resp.cards[i].id);
            return;
          }
        }
        callback(null);
      });
    },
    getCardUrl: function(author, title, callback) {
      var self = this;
      self.getAuthorId(author, function(authorId) {
        if (!authorId) {
          callback(null);
          return;
        }
        self.getCardId(authorId, title, function(cardId) {
          if (!cardId) {
            callback(null);
            return;
          }
          callback([
            'http://www.aozora.gr.jp/cards/', ('000000' + authorId).substr(authorId.length), '/card', cardId, '.html'
          ].join(''));
        });
      });
    },
    getXhtmlUrl: function(author, title, callback) {
      this.getCardUrl(author, title, function(url) {
        if (!url) {
          callback(null);
          return;
        }
        new TinyScraper({
          '//table[@class="download"]/tbody/tr[4]/td[3]/a': {
            url: 'concat("' + url.replace(/card\d+\.html/, '') + '", @href)'
          }
        }).scrape(
          url, function(resp) { callback(resp.url) }, { encoding: 'euc-jp' });
      });
    }
  };
}

TinyScraper はどこかにあるので略した。なんか青空文庫 API を作りたくなってきた。