Node用MeCabバインディング あと非同期版つくろうとして失敗した話

似非原さんの自分で記事を読むのもだるくなってきたので、コンピューターに記事を読ませることにした。 - 蟲!虫!蟲! - #!/usr/bin/bugrammer を読んでて、スクレイピングならnodejsが楽だしnodeでmecab使う手段あったっけーと思いながら調べてたら、あった。

で、KOBA719さんのnode用MeCab(https://github.com/KOBA789/node-mecab)に

# これをforkしてpackage.jsonくっつけてnpmにでも投げてくれる人が現れないかなー(ボソッ

とあったので、とりあえず package.json書いてギッハブに置いといた。https://github.com/mizchi/node-mecab
で、他に登録している人もいないし、npm publishしようと思ったんだけど、自分のnpmのバージョンが古くて登録出来なくて、でよく考えると自分でメンテする気も無いので*1、cloneしてnpmのローカルインストールで使ってください。

インストール

たぶん、mecabをUTF8でインストールしておく必要がある、と思う。node-iconv噛ませたらいけるだろうが、多言語化めんどい。

$ git clone git://github.com/mizchi/node-mecab.git
$ cd node-mecab
$ npm install .  & npm link mecab

使い方:

元のリポジトリに書いてあった使い方だと、品詞とるのに自分でパースする必要があって面倒だったので、適当にインターフェースだけでっち上げた。

mecab = require("mecab");
console.log( mecab.parse("すもももももももものうち"));

[ [ 'すももももも',
'名詞',
'一般',
'*',
'*',
'*',
'*',
'すももももも',
'*',
'*',
'wikipedia' ],
[ 'も',
'助詞',
'係助詞',
'*',
'*',
'*',
'*',
'も',
'モ',
'モ' ],
[ 'もも',
'名詞',
'一般',
'*',
'*',
'*',
'*',
'もも',
'モモ',
'モモ' ],
[ 'の',
'助詞',
'連体化',
'*',
'*',
'*',
'*',
'の',
'ノ',
'ノ' ],
[ 'うち',
'名詞',
'非自立',
'副詞可能',
'*',
'*',
'*',
'うち',
'ウチ',
'ウチ' ] ]

どうでもいいけど、wikipediaから抽出したうちのオリジナル辞書だと「すもももももも」(漫画)が一単語として認識されてて辛い

追記

Twitterで非同期じゃないじゃん、と言われてC++の非同期版書こうとして、挫折した。
失敗した理由は、Taggerの初期化に失敗したか、Tagger#parseへの文字列の受け渡しが失敗してるか、どっちか。

僕はC++見よう見まねで書いてて、全く書けない状態からコピペで作ったので、限界がある。とくにchar* の扱いが苦手。

参考 C++ で node.js ライブラリを作る・その2 - Node.jsで遊ぶよ - Node.jsグループ


mecab_async.cc

#include <node.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <mecab.h>

using namespace v8;
using namespace node;

class AsyncObject : ObjectWrap {
public:
  static 
  void Initialize (const Handle<Object> target) {
    HandleScope scope;
    Local<FunctionTemplate> tmpl = FunctionTemplate::New(New);
    tmpl->InstanceTemplate()->SetInternalFieldCount(1);
    NODE_SET_PROTOTYPE_METHOD(tmpl, "exec", Exec);
    target->Set(String::New("AsyncObject"), tmpl->GetFunction());
  }

  ~AsyncObject () {}
  MeCab::Tagger *tagger;

private:
  AsyncObject (const char *refername) {}
  
  struct BufferedData {
    AsyncObject *refer;
    char *ret_param;
    Persistent<Value> callback;
    MeCab::Tagger *tagger;
  };

  // called at new AsyncObject();
  static Handle<Value> New(const Arguments& args) {
    HandleScope scope;

    BufferedData *buff = (BufferedData *)malloc(sizeof(BufferedData));
    // TODO : 初期化はこれでいい?
    buff->tagger = MeCab::createTagger("");
    // buff->tagger = MeCab::createTagger((args.Length() >= 1 && args[0]->IsString()) ? *String::Utf8Value(args[0]->ToString()) : "");

    if (!args.IsConstructCall()) return args.Callee()->NewInstance();
    try {
      (new AsyncObject(*String::Utf8Value(args[0])))->Wrap(args.This());
    } catch (const char *msg) {
      return ThrowException(Exception::Error(String::New(msg)));
    }

    return args.This();
  }

  static Handle<Value> Exec (const Arguments& args) {
    HandleScope scope;

    BufferedData *buff = (BufferedData *)malloc(sizeof(BufferedData));
    buff->refer = Unwrap<AsyncObject>(args.This());
    buff->ret_param = strdup(*String::Utf8Value(args[0]));
    buff->callback = Persistent<Value>::New(args[1]);

    eio_custom(EIO_Exec, EIO_PRI_DEFAULT, Callback, buff);

    buff->refer->Ref();
    ev_ref(EV_DEFAULT_UC);
    return Undefined();
  }

  int Exec (const char *arg) {
    return 1;
  }
  
  static int EIO_Exec(eio_req *req) {
    HandleScope scope;
    BufferedData *buff = (BufferedData*)(req->data);
    req->result = buff->refer->Exec(buff->ret_param);

    // TODO : パースに失敗しているのか、代入に失敗しているのか
    buff->ret_param =  const_cast<char*>(buff->tagger->parse(buff->ret_param));

    // 文字列を渡すだけ。これはちゃんと動く
    // sprintf( buff->ret_param , "%s", buff->tagger->parse(buff->ret_param));
    return 0;
  }

  // called as callback  
  static int Callback(eio_req *req) {
    HandleScope scope;
    ev_unref(EV_DEFAULT_UC);

    BufferedData *buff = (BufferedData*)(req->data);

    if (buff->callback->IsFunction()) {
      Local<Value> argv[2];
      argv[0] = req->result < 0 ?
        Local<Value>::New(String::New("Error")) :
        Local<Value>::New(Undefined());
      argv[1] = Local<Value>::New( String::New(buff->ret_param)  ) ;

      Persistent<Function>::Cast(buff->callback)->Call(
                       buff->refer->handle_, 2, argv);
    }

    buff->refer->Unref();
    buff->callback.Dispose();
    free(buff->ret_param);
    free(buff);
    return 0;
  }

  
};

extern "C" void init (Handle<Object> target) {
  AsyncObject::Initialize(target);
}

wscript

srcdir = "."
blddir = "build"
VERSION = "0.0.1"

def set_options(opt):
    opt.tool_options("compiler_cxx")

def configure(conf):
    conf.check_tool("compiler_cxx")
    conf.check_tool("node_addon")
    conf.check(lib='mecab', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='MECAB')

def build(bld):
    obj = bld.new_task_gen("cxx",  "shlib",  "node_addon")
    obj.source = "mecab_async.cc"
    obj.target = "mecab_async"
    obj.uselib = "MECAB"

*1:C++あんま読めないし