メモ代わり。てきとーに。 いや、ですからてきとーですって。 2年前ぐらいにPythonあたりでメールくれた方、ごめんなさい。メール紛失してしまい無視した形になってしまいました。。。

2008年5月16日金曜日

[libcroco] CSSパーサ、マニピュレータのlibcrocoを使ってみた

libcrocoをSACのみ使ってみたメモ。

libcrocoとは
GNOMEで使われているC言語で書かれたCSSパーサ、CSS操作ツールキット。
本家(freespiders.org)に接続できない・・・。もしかして、開発停止???
GNOMEのvcsでは、6週間前にコミットされているので停止されているということは無い・・・といいな。

使いそうな構造体
まず、使用しそうな構造体を列挙する。

CRParser
パーサをあらわす。内部構造には基本的に触れられない。
この構造体を使用してパースする。

CRDocHandler
イベントが発生するとコールされるハンドラを登録できる。
登録できるハンドラは以下のとおり。


void (*start_document) (CRDocHandler *a_this);ドキュメントの開始時にコールされる
void (*end_document) (CRDocHandler *a_this) ;ドキュメントが終了したときにコールされる
void (*charset) (CRDocHandler *a_this, CRString *a_charset, CRParsingLocation *a_charset_sym_location) ;キャラクタルールが見つかったときにコールされる。
void (*import_style) (CRDocHandler *a_this, GList *a_media_list, CRString *a_uri, CRString *a_uri_default_ns, CRParsingLocation *a_location) ;import文があったときにコールされる。
void (*import_style_result) (CRDocHandler *a_this, GList *a_media_list, CRString *a_uri, CRString *a_uri_default_ns, CRStyleSheet *a_sheet) ;??
void (*namespace_declaration) (CRDocHandler *a_this, CRString *a_prefix, CRString *a_uri, CRParsingLocation *a_location) ;namespace宣言があった場合にコールされる。
void (*comment) (CRDocHandler *a_this, CRString *a_comment) ;コメントがあった場合にコールされる。
void (*start_selector) (CRDocHandler * a_this, CRSelector *a_selector_list) ;ルール開始時にコールされる。 a_selector_listにはルールに前にあるセレクタのリストが渡される。
void (*end_selector) (CRDocHandler *a_this, CRSelector *a_selector_list) ;ルールの終了を通知する。
void (*property) (CRDocHandler *a_this, CRString *a_name, CRTerm *a_expression, gboolean a_is_important) ;プロパティ宣言がある度にコールされる。
a_nameにはプロパティ名
a_expressionには式
void (*start_font_face) (CRDocHandler *a_this, CRParsingLocation *a_location) ;@font-face文が開始したときにコールされる。
void (*end_font_face) (CRDocHandler *a_this) ; @font-face文が終了したときにコールされる。
void (*start_media) (CRDocHandler *a_this, GList *a_media_list, CRParsingLocation *a_location);media文が開始したときにコールされる。
void (*end_media) (CRDocHandler *a_this, GList *a_media_list) ;media文が終了したときにコールされる。
void (*start_page) (CRDocHandler *a_this, CRString *a_name, CRString *a_pseudo_page, CRParsingLocation *a_location) ;@page文が開始したときにコールされる。
void (*end_page) (CRDocHandler *a_this, CRString *a_name, CRString *pseudo_page) ;@page文が終了したときにコールされる。
void (*ignorable_at_rule) (CRDocHandler *a_this, CRString *a_name) ;知らない@ルールがあった場合にコールされる。
void (*error) (CRDocHandler *a_this) ;パースエラーが起きたときにコールされる。
void (*unrecoverable_error) (CRDocHandler *a_this) ;復帰できないエラーがおきたときにコールされる。



CRSelector
セレクタを表現する構造体。

CRTerm
項を表現する構造体。

CRString
文字列を表現する構造体。



処理の大まかな流れ
大まかな流れは以下のとおり。
  1. CRParserを生成する。
  2. CRDocHandlerを生成する。
  3. CRDocHandlerにハンドラを登録する。
  4. CRDocHandlerをCRParserに登録する。
  5. パースする。
  6. CRParserを解放する。(CRDocHandlerはCRParser解放時に解放される)

使いそうな関数
libcrocoの中で使用しそうな関数は以下のとおり。

* CRParser *cr_parser_new_from_file(const guchar * a_file_uri, enum CREncoding a_enc)
a_file_uriに指定されたファイルをパースするパーサを生成する。
- a_file_uri - オープンするファイルのURIを指定する。
- a_enc - エンコードを指定する。 CR_UCS_4、CR_UCS_1、CR_ISO_8859_1、 CR_ASCII、CR_UTF_8、CR_UTF_16、CR_AUTOを指定できる。

* CRParser *cr_parser_new_from_input(CRInput * a_input)
a_inputに指定された入力ストリームをパースするパーサを生成する。
- a_input - 入力ストリームを指定する。

* CRDocHandler *cr_doc_handler_new (void)
CRDocHandlerを生成する。

* enum CRStatus cr_parser_set_sac_handler (CRParser * a_this, CRDocHandler * a_handler)
CRDocHandlerをCRParserに登録する。
- a_this - 登録先CRParserを指定する。
- a_handler - 登録するCRDocHandlerを指定する。

* enum CRStatus cr_parser_parse (CRParser * a_this)
パースする。
- a_this - cr_parse_new_from_file()やcr_parser_new_from_input()で生成されたCRParserを指定する。

* void cr_parser_destroy (CRParser * a_this)
cr_parser_new_from_file()やcr_parser_new_from_input()で生成されたCRParserを破棄する。
- a_this - cr_parse_new_from_file()やcr_parser_new_from_input()で生成されたCRParserを指定する。

* gboolean cr_doc_handler_unref (CRDocHandler * a_this)
cr_doc_handler_new()で生成されたCRDocHandlerを破棄する。通常cr_parse_destroy()の中で破棄しているため、必要ない。
- a_this - 破棄すべきCRDocHandlerを指定する。

* guchar * cr_term_to_string (CRTerm *a_this) ;
CRTermを文字列に変換する。

* guchar * cr_term_one_to_string (CRTerm * a_this) ;
CRTermを文字列に変換する。

* int cr_term_nr_values (CRTerm *a_this)
CRTermを数値に変換する。


* const gchar * cr_string_peek_raw_str (CRString *a_this);
CRStringをgchar*に変換する。
 



早速なんか作ってみる
a.cssを読み込み、セレクタがあればセレクタを表示。プロパティがあればプロパティを表示するだけの
しょぼいプログラムを作る。

まず、CRParserを生成する。

CRParser *parser = NULL;

parser = cr_parser_new_from_file((unsigned char*)"a.css", CR_ASCII);
if (!parser) {
/* パーサを作成できなかった! */
fprintf(stderr, "parser生成失敗\n");
return -1;
}
 

な感じで生成できる。
cr_parser_new_from_fileの第二引数には
CR_UCS_4、CR_UCS_1、CR_ISO_8859_1、 CR_ASCII、CR_UTF_8、CR_UTF_16、CR_AUTO
しか指定できない。なので、Shift_JISやEUCはダメ。

次にCRDocHandlerを生成する。

CRDocHandler *sac_handler = NULL;

sac_handler = cr_doc_handler_new();
if (!sac_handler) {
fprintf(stderr, "sac_handler生成失敗\n");
return -2;
}
 

な感じで生成。

次は、ハンドラを定義する。
ハンドラは、start_selector、end_selector、propertyで良いはず。
ということで、start_selector、end_selector、propertyを実装する。

static void
s_start_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
{
if (a_selector_list) {
CRSelector *cur = NULL;
for (cur = a_selector_list; cur; cur = cur->next) {
if (cur->simple_sel) {
guchar *tmp_str = cr_simple_sel_to_string(cur->simple_sel);
if (tmp_str) {
printf("start selector:[%s]\n", tmp_str);
g_free (tmp_str);
tmp_str = NULL;
}
}
}
}
}

static void
s_end_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
{
if (a_selector_list) {
CRSelector *cur = NULL;
for (cur = a_selector_list; cur; cur = cur->next) {
if (cur->simple_sel) {
guchar *tmp_str = cr_simple_sel_to_string(cur->simple_sel);
if (tmp_str) {
printf("end selector:[%s]\n", tmp_str);
g_free (tmp_str);
tmp_str = NULL;
}
}
}
}
}

static void
s_property(CRDocHandler *a_this, CRString *a_name, CRTerm *a_expression, gboolean a_is_important)
{
if (a_name) {
printf("property: name:[%s]\n", cr_string_peek_raw_str(a_name));
}
if (a_expression) {
CRTerm *cur = NULL;
for (cur = a_expression; cur; cur = cur->next) {
guchar *tmp = cr_term_one_to_string(cur);
printf("property: value:[%s]\n", tmp);
g_free(tmp);
tmp = NULL;
}
}
}
 

こんな感じでよいと思う。

で、定義したハンドラをCRDocHandlerに登録する。

sac_handler->start_selector = s_start_selector;
sac_handler->end_selector = s_end_selector;
sac_handler->property = s_property;
 


そして、sac_handlerをparserに登録。

/* sac_handlerをparserに登録 */
ret = cr_parser_set_sac_handler(parser, sac_handler);
if (ret != CR_OK) {
fprintf(stderr, "なんかエラーが起きた:[%d]\n", ret);
cr_parser_destroy(parser);
return -3;
}
 


実パース処理。

/* パース */
ret = cr_parser_parse(parser);
if (ret != CR_OK) {
fprintf(stderr, "なんかエラーが起きた:[%d]\n", ret);
cr_parser_destroy(parser);
return -4;
}
 


で終了処理。parserを解放する。

/* 解放 */
cr_parser_destroy(parser);
 

examplesでは、sac_handlerもここで解放しているが、特に解放する必要はない。
cr_parser_destroy()の中でparserに登録されているsac_handlerは解放しているため。


全体のソースは以下な感じ。

#include <stdio.h>
#include <libcroco/libcroco.h>

/*
* gcc -g -Wall -o test01 `croco-0.6-config --cflags` `croco-0.6-config --libs` a.c
*/
static void
s_start_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
{
if (a_selector_list) {
CRSelector *cur = NULL;
for (cur = a_selector_list; cur; cur = cur->next) {
if (cur->simple_sel) {
guchar *tmp_str = cr_simple_sel_to_string(cur->simple_sel);
if (tmp_str) {
printf("start selector:[%s]\n", tmp_str);
g_free (tmp_str);
tmp_str = NULL;
}
}
}
}
}

static void
s_end_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
{
if (a_selector_list) {
CRSelector *cur = NULL;
for (cur = a_selector_list; cur; cur = cur->next) {
if (cur->simple_sel) {
guchar *tmp_str = cr_simple_sel_to_string(cur->simple_sel);
if (tmp_str) {
printf("end selector:[%s]\n", tmp_str);
g_free (tmp_str);
tmp_str = NULL;
}
}
}
}
}

static void
s_property(CRDocHandler *a_this, CRString *a_name, CRTerm *a_expression, gboolean a_is_important)
{
if (a_name) {
printf("property: name:[%s]\n", cr_string_peek_raw_str(a_name));
}
if (a_expression) {
CRTerm *cur = NULL;
for (cur = a_expression; cur; cur = cur->next) {
guchar *tmp = cr_term_one_to_string(cur);
printf("property: value:[%s]\n", tmp);
g_free(tmp);
tmp = NULL;
}
}
}

int
main (int argc, char *argv[])
{
CRParser *parser = NULL ;
CRDocHandler *sac_handler = NULL ;
enum CRStatus ret;

/* parserオブジェクト生成 */
parser = cr_parser_new_from_file ((unsigned char *)"./a.css", CR_ASCII) ;
if (!parser) {
fprintf(stderr, "parserオブジェクト失敗\n");
return -1;
}

/* ハンドラ生成 */
sac_handler = cr_doc_handler_new();
if (!sac_handler) {
fprintf(stderr, "sac_handlerオブジェクト失敗\n");
cr_parser_destroy(parser);
return -2;
}

/* ハンドラ登録 */
sac_handler->start_selector = s_start_selector;
sac_handler->end_selector = s_end_selector;
sac_handler->property = s_property;

/* sac_handlerをparserに登録 */
ret = cr_parser_set_sac_handler(parser, sac_handler);
if (ret != CR_OK) {
fprintf(stderr, "なんかエラーが起きた:[%d]\n", ret);
cr_parser_destroy(parser);
return -3;
}


/* パース */
ret = cr_parser_parse(parser);
if (ret != CR_OK) {
fprintf(stderr, "なんかエラーが起きた:[%d]\n", ret);
cr_parser_destroy(parser);
return -4;
}


/* 解放 */
cr_parser_destroy(parser);
return 0;
}

 



コンパイル
debianであれば、

gcc -g -Wall -o test01 `croco-0.6-config --cflags` `croco-0.6-config --libs` a.c
 

でOK。
a.cはソースファイル名のつもり。


実行してみる。
実行してみるcssは、以下のとおり。

html {
display: block
}
 


実行。

$ ./sac-example-1
$ ./sac-example-1
start selector:[html]
property: name:[display]
property: value:[block]
end selector:[html]
 


うむ。とりあえずできた。

.

0 コメント: