Hatena::Groupcprogramming

Going My C Way このページをアンテナに追加 RSSフィード

2010年11月01日(月)

Lex で書いた小さな XMLパーザ

| 23:23 |  Lex で書いた小さな XMLパーザ - Going My C Way を含むブックマーク はてなブックマーク -  Lex で書いた小さな XMLパーザ - Going My C Way  Lex で書いた小さな XMLパーザ - Going My C Way のブックマークコメント

minixml.l

%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
        handlers
*/
static void default_stag_handler(char *name)    {}
static void default_etag_handler(char *name)    {}
static void default_space_handler(char *text)   {}
static void default_chars_handler(char *text)   {}
static void default_charref_handler(int c)              {}
static void default_fatal_handler(char *msg)
{
        fprintf(stderr, "ERROR:#%d: %s at '%s'\n", yylineno, msg, yytext);
        exit(EXIT_FAILURE);
}

static struct {
        void (*stag)(char *name);
        void (*etag)(char *name);
        void (*space)(char *text);
        void (*chars)(char *text);
        void (*charref)(int c);
        void (*fatal)(char *s);
} callback = {
        stag:           default_stag_handler,
        etag:           default_etag_handler,
        space:          default_space_handler,
        chars:          default_chars_handler,
        charref:        default_charref_handler,
        fatal:          default_fatal_handler,
};

/*
        parser API
*/
void set_stag_handler(void (*handler)(char *)) { callback.stag = handler; }
void set_etag_handler(void (*handler)(char *)) { callback.etag = handler; }
void set_space_handler(void (*handler)(char *)){ callback.space = handler; }
void set_chars_handler(void (*handler)(char *)){ callback.chars = handler; }
void set_charref_handler(void (*handler)(int)) { callback.charref = handler; }
void set_fatal_handler(void (*handler)(char *)){ callback.fatal = handler; }
void parse(FILE *file) { yyin = file; yylex(); }

/*
        stack
*/
#if !defined(STACK_MAX)
        #define STACK_MAX       (32)
#endif

static struct {
        int ptr;
        char *token[STACK_MAX];
} stack;

static void push(char *stag)
{
        if (stack.ptr >= STACK_MAX) {
                callback.fatal("stack overflow");
        }
        stack.token[stack.ptr++] = strdup(stag);
}

static void pop(char *etag)     /* pop and check */
{
        char *stag;

        if (stack.ptr == 0) {
                callback.fatal("stack empty");
        }
        stag = stack.token[--stack.ptr];       

        if (strcmp(stag, etag) != 0) {
                callback.fatal("stag-etag unmatch");
        }
        free(stag);
}

static void is_text_allow_here(void)
{
        if (stack.ptr == 0) {
                callback.fatal("text is out of root element.");
        }
}

%}

%option noyywrap

Name            [^<>&/]+
CharData        [^<>&]
CharRef         &#[0-9]+;
WS                      [ \t\r\n]

STag            <{Name}>
ETag            <\/{Name}>

%%
{STag} {yytext[yyleng-1]='\0'; callback.stag(&yytext[1]); push(&yytext[1]);}
{ETag} {yytext[yyleng-1]='\0'; pop(&yytext[2]); callback.etag(&yytext[2]);}
{WS}+           { callback.space(yytext); }
{CharData}+     { is_text_allow_here(); callback.chars(yytext); }
{CharRef}       { is_text_allow_here(); callback.charref(atoi(&yytext[2])); }
.|\n            { callback.fatal("syntax error"); }

%%
int main(void) { parse(NULL); return EXIT_SUCCESS; }

minixml.h

#ifndef INCLUDED_MINIXML_H
#define INCLUDED_MINIXML_H

#include <stdio.h>

/* minixml.c */
void set_stag_handler(void (*handler)(char *));
void set_etag_handler(void (*handler)(char *));
void set_space_handler(void (*handler)(char *));
void set_chars_handler(void (*handler)(char *));
void set_charref_handler(void (*handler)(int));
void set_fatal_handler(void (*handler)(char *));
void parse(FILE *file);

#endif /* !INCLUDED_MINIXML_H */

Makefile (コマンド行の先頭はタブです。コピペ時は要注意)

CC     = gcc
CFLAGS = -fPIC 

OBJ = minixml.o
SRC = ${OBJ:.o=.c}

.PHONY: clean

libminixml.so : ${OBJ}
        ${CC} ${CFLAGS} -shared -o $@ $^

clean :
        ${RM} ${OBJ} ${SRC}

make すると libminixml.so ができる。

parser.c

#include "minixml.h"

void print_stag(char *name) { printf("tag = %s\n", name); }
void print_etag(char *name) { printf("tag(end) = %s\n", name); }
void print_chars(char *text) { printf("text = %s\n", text); }
void print_space(char *text) { printf("space = :%s:\n", text); }
void print_charref(int c) { printf("char = %c\n", c); }

int main(void)
{
        set_stag_handler(print_stag);
        set_etag_handler(print_etag);
        set_chars_handler(print_chars);
        set_charref_handler(print_charref);
        set_space_handler(print_space);

        parse(NULL);

        return 0;
}

以下のコマンドでビルド

$ make CFLAGS=-L. LDLIBS=-lminixml parser

以下のコマンドで実行。

$ export LD_LIBRARY_PATH=.        # libminixml.so がカレントにあるとする
$ ./parser text.xml               # text.xml は適当な XMLファイル