[groonga-dev,02584] Re: Groongaのハイライト関数の実装について

Back to archive index

Kouhei Sutou kou****@clear*****
2014年 8月 8日 (金) 23:37:11 JST


須藤です。

In <CANM+Hhe9CON4=kU2b-****@mail*****>
  "[groonga-dev,02583] Groongaのハイライト関数の実装について" on Fri, 8 Aug 2014 07:24:06 +0900,
  Naoya Murakami <visio****@gmail*****> wrote:

> 以前、以下のメールで、本文全体をハイライトさせるためにスニペットのタグ上限
> について、相談させていただいたところ、RroongaのPatriciaTrie#tag_keys相当
> の機能を提供するとよいということを教えていただきました。
> 
> http://sourceforge.jp/projects/groonga/lists/archive/dev/2014-May/002345.html
> 
> パッチ箇所の軽減のため、Groongaのハイライト関数を実装してみました。
> 
> https://github.com/naoa/groonga-function-highlight/blob/master/functions/highlight.c

おぉ!

> とりあえず動きはするのですが、仕様がいまいちイケていない気がしていて、
> Groonga本体にgrn_pat_tag_keysのAPI化とhighlight_html関数のプル
> リクエストをしようか悩んでいます。

たしかにこのままだと取り込めないんですが、整理すればいけそう
な雰囲気を感じました!

> highlight_htmlでは、Rroongaやsnippet_htmlを習うとタグのヒット上限値1024個
> で、タグは1個、ノーマライザーは、NormalizerAuto決めうちになるのかなと
> 思います。

そうですね!
引数は1つでハイライト対象の文字列だけになるでしょうね!

  highlight_html("XXX")
  highlight_html(column)

みたいに。

キーワードは検索条件から自動で引っ張ってくるとよさそうですね。
あ、grn_exprからキーワードを引っ張ってくるAPIないですね。。。

> <質問>
> ・snippetでは、grn_snip_execをしてmax_tagged_lengthを取得してから、
> バッファを確保し、grn_snip_get_resultしていると思います。
> https://github.com/groonga/groonga/blob/master/lib/proc.c#L3961
> 
> 当方が実装してみた上記のgrn_pat_tag_keysではgrn_pat_scanとresult
> の書き込みを同時にやっているので、正確なバッファ長を事前に把握する
> ことができません。
> 
> https://github.com/naoa/groonga-function-highlight/blob/master/functions/highlight.c#L135
> 
> ハイライトでもsnippetと同様に、APIをexecとget_resultを分けてやって、
> 正確なバッファ長でgrn_bulk_spaceする必要がありますか?
> (そもそもGRN_TEXT_INITすれば、明示的なバッファ確保は不要?
> grn_bulk_spaceをやっているのはmallocを効率良くやるため?)

バッファ確保は不要です。
推察の通りgrn_bulk_spaceは効率のためです。

なので、execとget_resultはわけなくてもよいです。

> ・上記のAPIとhighlight_htmlをGroonga本体に組み込んでもいいでしょうか?
> (私が必要なのは、highlight_fullですが。。)

max_n_hitsは指定しなくてもいいですよ。
固定のhits用バッファーを何回も使いまわすので。whileの中で
grn_pat_scan()しているのはそういうことです。

ということで、こんな感じがいいんじゃないかなぁと思います。
highlight_htmlを作ってみたらまた変わるかもしれませんが。。。

(宣言時に配列のサイズを動的に決めるとVisual Studioでビルド
できなくなるんですよ。)

--
static grn_rc
grn_pat_tag_keys(grn_ctx *ctx, grn_obj *keywords,
                 const char *string, unsigned int string_length,
                 const char **open_tags, unsigned int *open_tag_lengths,
                 const char **close_tags, unsigned int *close_tag_lengths,
                 unsigned int n_tags,
                 grn_obj *highlighted)
{
  while (string_length > 0) {
#define MAX_N_HITS 1024
    grn_pat_scan_hit hits[MAX_N_HITS];
    const char *rest;
    unsigned int i, n_hits;
    unsigned int previous = 0;


    n_hits = grn_pat_scan(ctx, (grn_pat *)keywords,
                          string, string_length,
                          hits, MAX_N_HITS, &rest);

    for (i = 0; i < n_hits; i++) {
      unsigned int nth_tag;
      if (hits[i].offset - previous > 0) {
        GRN_TEXT_PUT(ctx, highlighted,
                     string + previous, hits[i].offset - previous);
      }
      nth_tag = ((hits[i].id - 1) % n_tags);
      GRN_TEXT_PUT(ctx, highlighted,
                   open_tags[nth_tag], open_tag_lengths[nth_tag]);
      GRN_TEXT_PUT(ctx, highlighted,
                   string + hits[i].offset, hits[i].length);
      GRN_TEXT_PUT(ctx, highlighted,
                   close_tags[nth_tag], close_tag_lengths[nth_tag]);
      previous = hits[i].offset + hits[i].length;
    }
    if (string_length - previous > 0) {
      GRN_TEXT_PUT(ctx, highlighted,
                   string + previous, string_length - previous);
    }
    string_length -= rest - string;
    string = rest;
#undef MAX_N_HITS
  }

  return GRN_SUCCESS;
}

static grn_obj *
func_highlight_full(grn_ctx *ctx, int nargs, grn_obj **args,
                    grn_user_data *user_data)
{
  grn_obj *highlighted = NULL;

#define N_REQUIRED_ARGS 2
#define KEYWORD_SET_SIZE 3
  if (nargs > (N_REQUIRED_ARGS + KEYWORD_SET_SIZE) &&
      (nargs - N_REQUIRED_ARGS) % KEYWORD_SET_SIZE == 0) {
    grn_obj *string = args[0];
    grn_obj *normalizer_name = args[1];
    grn_obj **keyword_set_args = args + N_REQUIRED_ARGS;
    unsigned int n_keyword_sets = (nargs - N_REQUIRED_ARGS) / KEYWORD_SET_SIZE;
    unsigned int i;
    grn_obj open_tags;
    grn_obj open_tag_lengths;
    grn_obj close_tags;
    grn_obj close_tag_lengths;
    grn_obj *keywords;

    keywords = grn_table_create(ctx, NULL, 0, NULL,
                                GRN_OBJ_TABLE_PAT_KEY,
                                grn_ctx_at(ctx, GRN_DB_SHORT_TEXT),
                                NULL);

    if (GRN_TEXT_LEN(normalizer_name)) {
      grn_obj *normalizer;
      normalizer = grn_ctx_get(ctx,
                               GRN_TEXT_VALUE(normalizer_name),
                               GRN_TEXT_LEN(normalizer_name));
      //TODO: checks normalizer object
      grn_obj_set_info(ctx, keywords, GRN_INFO_NORMALIZER, normalizer);
      grn_obj_unlink(ctx, normalizer);
    }

    GRN_OBJ_INIT(&open_tags, GRN_BULK, 0, GRN_DB_VOID);
    GRN_OBJ_INIT(&open_tag_lengths, GRN_BULK, 0, GRN_DB_VOID);
    GRN_OBJ_INIT(&close_tags, GRN_BULK, 0, GRN_DB_VOID);
    GRN_OBJ_INIT(&close_tag_lengths, GRN_BULK, 0, GRN_DB_VOID);
    for (i = 0; i < n_keyword_sets; i++) {
      grn_obj *keyword   = keyword_set_args[i * KEYWORD_SET_SIZE + 0];
      grn_obj *open_tag  = keyword_set_args[i * KEYWORD_SET_SIZE + 1];
      grn_obj *close_tag = keyword_set_args[i * KEYWORD_SET_SIZE + 2];

      grn_table_add(ctx, keywords,
                    GRN_TEXT_VALUE(keyword),
                    GRN_TEXT_LEN(keyword),
                    NULL);

      {
        const char *open_tag_content = GRN_TEXT_VALUE(open_tag);
        grn_bulk_write(ctx, &open_tags,
                       (const char *)(&open_tag_content),
                       sizeof(char *));
      }
      {
        unsigned int open_tag_length = GRN_TEXT_LEN(open_tag);
        grn_bulk_write(ctx, &open_tag_lengths,
                       (const char *)(&open_tag_length),
                       sizeof(unsigned int));
      }
      {
        const char *close_tag_content = GRN_TEXT_VALUE(close_tag);
        grn_bulk_write(ctx, &close_tags,
                       (const char *)(&close_tag_content),
                       sizeof(char *));
      }
      {
        unsigned int close_tag_length = GRN_TEXT_LEN(close_tag);
        grn_bulk_write(ctx, &close_tag_lengths,
                       (const char *)(&close_tag_length),
                       sizeof(unsigned int));
      }
    }

    highlighted = grn_plugin_proc_alloc(ctx, user_data, GRN_DB_TEXT, 0);
    grn_pat_tag_keys(ctx, keywords,
                     GRN_TEXT_VALUE(string), GRN_TEXT_LEN(string),
                     (const char **)GRN_BULK_HEAD(&open_tags),
                     (unsigned int *)GRN_BULK_HEAD(&open_tag_lengths),
                     (const char **)GRN_BULK_HEAD(&close_tags),
                     (unsigned int *)GRN_BULK_HEAD(&close_tag_lengths),
                     n_keyword_sets,
                     highlighted);

    grn_obj_unlink(ctx, keywords);
    grn_obj_unlink(ctx, &open_tags);
    grn_obj_unlink(ctx, &open_tag_lengths);
    grn_obj_unlink(ctx, &close_tags);
    grn_obj_unlink(ctx, &close_tag_lengths);
  }
#undef N_REQUIRED_ARGS
#undef KEYWORD_SET_SIZE

  if (!highlighted) {
    highlighted = grn_plugin_proc_alloc(ctx, user_data, GRN_DB_VOID, 0);
  }

  return highlighted;
}
--

> ・grn_pat_tag_keysをAPIとしてGroonga本体に実装するならどこに実装
> しますか?pat.cですか?

そうですね。
と、いいたいところなんですが、この実装だとkeywordsテーブルの
レコードIDとどのタグを使うかの情報が一致していることを前提と
しているのでpat.cではないかなぁという感じです。とりあえず、
proc.cにstaticになりそうです。

あぁ、あと、highlight_htmlを作るときはgrn_pat_tag_keysで
GRN_TEXT_PUTするんじゃなくてgrn_text_escape_xmlしなくちゃダ
メなので、やっぱりこのままpat.cはムリですねぇ。

highlight_htmlとhighlight_fullを入れるのはアリだと思います。

あれ、highlight_html_fullじゃなくてhighlight_fullで大丈夫で
す?

-- 
須藤 功平 <kou****@clear*****>
株式会社クリアコード <http://www.clear-code.com/>

Groongaベースの全文検索システムを総合サポート:
  http://groonga.org/ja/support/
パッチ採用 - プログラミングが楽しい人向けの採用プロセス:
  http://www.clear-code.com/recruitment/
コードリーダー育成支援 - 自然とリーダブルコードを書くチームへ:
  http://www.clear-code.com/services/code-reader/




groonga-dev メーリングリストの案内
Back to archive index