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/