第十章 文本处理

10.1 状态机

#include <ctype.h>
#include <stdio.h>

#define IS_WORD_CHAR(c)	(isalpha(c) || isdigit(c))

int count_word(const char* text)
{
	enum _State
	{
		STAT_INIT,
		STAT_IN_WORD,
		STAT_OUT_WORD,
	}state = STAT_INIT;

	int count = 0;
	const char* p = text;

	for(p = text; *p != '\0'; p++){
		switch(state){
			case STAT_INIT:{
				if(IS_WORD_CHAR(*p)){
					state = STAT_IN_WORD;
				}else{
					state = STAT_OUT_WORD;
				}
				break;
			}
			case STAT_IN_WORD:{
				if(!IS_WORD_CHAR(*p)){
					count++;
					state = STAT_OUT_WORD;
				}
				break;
			}
			case STAT_OUT_WORD:{
				if(IS_WORD_CHAR(*p)){
					state = STAT_IN_WORD;
				}
				break;
			}
			default:break;
		}
	}

	if(state == STAT_IN_WORD){
		count++;
	}

	return count;
}

#ifdef COUNT_WORD_TEST
#include <assert.h>

int main(int argc, char* argv[])
{
	assert(count_word("") == 0);
	assert(count_word(" ") == 0);
	assert(count_word("it") == 1);
	assert(count_word("it ") == 1);
	assert(count_word("it is") == 2);
	assert(count_word("it is used to count words.") == 6);
	assert(count_word("it is used to count words. is it easy?") == 9);

	return 0;
}
#endif/*COUNT_WORD_TEST*/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define IS_WORD_CHAR(c)	(isalpha(c) || isdigit(c))

typedef void (*OnWordFunc)(void* ctx, const char* word);

int word_segmentation(const char* text, OnWordFunc on_word, void* ctx)
{
	enum _State{
		STAT_INIT,
		STAT_IN_WORD,
		STAT_OUT_WORD,
	}state = STAT_INIT;

	int count = 0;
	char* copy_text = strdup(text);
	char* p = copy_text;
	char* word = copy_text;

	for(p = copy_text; *p != '\0'; p++){
		switch(state){
			case STAT_INIT:{
				if(IS_WORD_CHAR(*p)){
					word = p;
					state = STAT_IN_WORD;
				}
				break;
			}
			case STAT_IN_WORD:{
				if(!IS_WORD_CHAR(*p)){
					count++;
					*p = '\0';
					on_word(ctx, word);
					state = STAT_OUT_WORD;
				}
				break;
			}
			case STAT_OUT_WORD:{
				if(IS_WORD_CHAR(*p)){
					word = p;
					state = STAT_IN_WORD;
				}
				break;
			}
			default:break;
		}
	}

	if(state == STAT_IN_WORD){
		count++;
		on_word(ctx, word);
	}

	free(copy_text);

	return count;
}

#ifdef WORD_SEGMENTATION_TEST
#include <assert.h>

void on_word(void* ctx, const char* word)
{
	printf("%s\n", word);

	return;
}

int main(int argc, char* argv[])
{
	assert(word_segmentation("it is used to word segmentation. is it easy?", on_word, NULL) == 9);

	return 0;
}
#endif/*WORD_SEGMENTATION_TEST*/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef void (*OnTokenFunc)(void* ctx, int index, const char* token);

#define IS_DELIM(c) (strchr(delims, c) != NULL)
int parse_token(const char* text, const char* delims, OnTokenFunc on_token, void* ctx)
{
	enum _State{
		STAT_INIT,
		STAT_IN,
		STAT_OUT,
	}state = STAT_INIT;

	int   count     = 0;
	char* copy_text = strdup(text);
	char* p         = copy_text;
	char* token     = copy_text;

	for(p = copy_text; *p != '\0'; p++){
		switch(state){
			case STAT_INIT:
			case STAT_OUT:{
				if(!IS_DELIM(*p)){
					token = p;
					state = STAT_IN;
				}
				break;
			}
			case STAT_IN:{
				if(IS_DELIM(*p)){
					*p = '\0';
					on_token(ctx, count++, token);
					state = STAT_OUT;
				}
				break;
			}
			default:break;
		}
	}

	if(state == STAT_IN){
		on_token(ctx, count++, token);
	}

	on_token(ctx, -1, NULL);
	free(copy_text);

	return count;
}

#ifdef PARSE_TOKEN_TEST
#include <assert.h>

void on_token(void* ctx, int index, const char* token)
{
	printf("[%d] %s\n", index, token);

	return;
}

int main(int argc, char* argv[])
{
	assert(parse_token("it is used to token segmentation. is it easy?", " .?", on_token, NULL) == 9);

	assert(parse_token("/backup/tools/jdk1.5.0_18/bin/"":/usr/lib/qt-3.3/bin:/usr/kerberos/bin:"
			   "/backup/tools/jdk1.5.0_18/bin/:/usr/lib/ccache:/usr/local/bin:/usr/bin:"
			   "/bin:/home/lixianjing/bin", ":", on_token, NULL) == 9
	      );
	assert(parse_token("/backup/tools/jdk1.5.0_18/bin/", "/", on_token, NULL) == 4);

	return 0;
}
#endif/*COUNT_TEST*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

const char* strtrim(char* str)
{
	char* p = NULL;

	p = str + strlen(str) - 1;

	while(p != str && isspace(*p)){
		*p = '\0';
		p--;
	}

	p = str;
	while(*p != '\0' && isspace(*p)) p++;

	if(p != str){
		char* s = p;
		char* d = str;
		while(*s != '\0'){
			*d = *s;
			d++;
			s++;
		}
		*d = '\0';
	}

	return str;
}

static void ini_parse_internal(char* buffer, char comment_char, char delim_char)
{
	char* p = buffer;
	char* group_start = NULL;
	char* key_start   = NULL;
	char* value_start = NULL;
	
	enum _State{
		STAT_NONE = 0, /*空白状态*/
		STAT_GROUP,
		STAT_KEY,
		STAT_VALUE, 
		STAT_COMMENT /*注释状态*/
	}state = STAT_NONE;

	for(p = buffer; *p != '\0'; p++){
		switch(state){
			case STAT_NONE:{
				if(*p == '['){
					state = STAT_GROUP;
					group_start = p + 1;
				}else if(*p == comment_char){
					state = STAT_COMMENT;
				}else if(!isspace(*p)){
					state = STAT_KEY;
					key_start = p;
				}
				break;
			}
			case STAT_GROUP:{
				if(*p == ']'){
					*p = '\0';
					state = STAT_NONE;
					strtrim(group_start);
					printf("[%s]\n", group_start);
				}
				break;
			}
			case STAT_COMMENT:{
				if(*p == '\n'){
					state = STAT_NONE;
					break;
				}
				break;
			}
			case STAT_KEY:{
				if(*p == delim_char || (delim_char == ' ' && *p == '\t')){
					*p = '\0';
					state = STAT_VALUE;
					value_start = p + 1;
				}
				break;
			}
			case STAT_VALUE:{
				if(*p == '\n' || *p == '\r'){
					*p = '\0';
					state = STAT_NONE;
					strtrim(key_start);
					strtrim(value_start);
					printf("%s%c%s\n", key_start, delim_char, value_start);
				}
				break;
			}
			default:break;
		}
	}

	if(state == STAT_VALUE)
	{
		strtrim(key_start);
		strtrim(value_start);
		printf("%s%c%s\n", key_start, delim_char, value_start);
	}

	return;
}

#ifdef INI_PARSER_TEST

int main(int argc, char* argv[])
{
	char* buffer = strdup("[lixianjing]\nname=lixianjing\ngender=male\n[zhangshan]\nname=zhangshan\ngender=male\nage=100");
	ini_parse_internal(buffer, '#', '=');
	free(buffer);

	return 0;
}
#endif/*INI_PARSER_TEST*/

#ifndef XML_PARSER_H
#define XML_PARSER_H
struct _XmlParser;
typedef struct _XmlParser XmlParser;

XmlParser* xml_parser_create(void);
void xml_parser_parse(XmlParser* thiz, const char* xml);
void xml_parser_destroy(XmlParser* thiz);

#endif/*XML_PARSER_H*/

/*
 * File:    xml_parser.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "xml_parser.h"

#define MAX_ATTR_NR 64

struct _XmlParser
{
	const char* read_ptr;

	int   attrs_nr;
	char* attrs[2*MAX_ATTR_NR+1];

	char* buffer;
	int buffer_used;
	int buffer_total;

};

static const char* strtrim(char* str);
static void xml_parser_parse_entity(XmlParser* thiz);
static void xml_parser_parse_start_tag(XmlParser* thiz);
static void xml_parser_parse_end_tag(XmlParser* thiz);
static void xml_parser_parse_comment(XmlParser* thiz);
static void xml_parser_parse_pi(XmlParser* thiz);
static void xml_parser_parse_text(XmlParser* thiz);
static void xml_parser_reset_buffer(XmlParser* thiz);

XmlParser* xml_parser_create(void)
{
	return (XmlParser*)calloc(1, sizeof(XmlParser));
}

void xml_parser_parse(XmlParser* thiz, const char* xml)
{
	enum _State{
		STAT_NONE,
		STAT_AFTER_LT,
		STAT_START_TAG,
		STAT_END_TAG,
		STAT_TEXT,
		STAT_PRE_COMMENT1,
		STAT_PRE_COMMENT2,
		STAT_COMMENT,
		STAT_PROCESS_INSTRUCTION,
	}state = STAT_NONE;

	thiz->read_ptr = xml;

	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		char c = thiz->read_ptr[0];

		switch(state){
			case STAT_NONE:{

				if(c == '<'){
					xml_parser_reset_buffer(thiz);
					state = STAT_AFTER_LT;
				}
				else if(!isspace(c)){
					state = STAT_TEXT;
				}
				break;
			}
			case STAT_AFTER_LT: {
				if(c == '?'){
					state = STAT_PROCESS_INSTRUCTION;
				}
				else if(c == '/'){
					state = STAT_END_TAG;
				}
				else if(c == '!'){
					state = STAT_PRE_COMMENT1;
				}
				else if(isalpha(c) || c == '_'){
					state = STAT_START_TAG;
				}
				else{}
				break;
			}
			case STAT_START_TAG:{
				xml_parser_parse_start_tag(thiz);
				state = STAT_NONE;
				break;
			}
			case STAT_END_TAG:{
				xml_parser_parse_end_tag(thiz);
				state = STAT_NONE;
				break;
			}
			case STAT_PROCESS_INSTRUCTION:{
				xml_parser_parse_pi(thiz);
				state = STAT_NONE;
				break;
			}
			case STAT_TEXT:{
				xml_parser_parse_text(thiz);
				state = STAT_NONE;
				break;
			}
			case STAT_PRE_COMMENT1:{
				if(c == '-'){
					state = STAT_PRE_COMMENT2;
				}
				else{
				}
				break;
			}
			case STAT_PRE_COMMENT2:{
				if(c == '-'){
					state = STAT_COMMENT;
				}
				else
				{
				}
			}
			case STAT_COMMENT:{
				xml_parser_parse_comment(thiz);	
				state = STAT_NONE;
				break;
			}
			default:break;
		}

		if(*thiz->read_ptr == '\0'){
			break;
		}
	}

	return;
}

static void xml_parser_reset_buffer(XmlParser* thiz)
{
	thiz->buffer_used = 0;
	thiz->attrs_nr = 0;
	thiz->attrs[0] = NULL;

	return;
}

static int xml_parser_strdup(XmlParser* thiz, const char* start, size_t length)
{
	int offset = -1;

	if((thiz->buffer_used + length) >= thiz->buffer_total){
		size_t length = thiz->buffer_total+(thiz->buffer_total>>1) + 128;
		char* buffer = realloc(thiz->buffer, length);
		if(buffer != NULL){
			thiz->buffer = buffer;
			thiz->buffer_total = length;
		}
	}

	if((thiz->buffer_used + length) >= thiz->buffer_total){
		return offset;
	}

	offset = thiz->buffer_used;
	strncpy(thiz->buffer + offset, start, length);
	thiz->buffer[offset + length] = '\0';
	strtrim(thiz->buffer+offset);
	thiz->buffer_used += length + 1;

	return offset;
}

static void xml_parser_parse_attrs(XmlParser* thiz, char end_char)
{
	int i = 0;
	enum _State{
		STAT_PRE_KEY,
		STAT_KEY,
		STAT_PRE_VALUE,
		STAT_VALUE,
		STAT_END,
	}state = STAT_PRE_KEY;

	char value_end = '\"';
	const char* start = thiz->read_ptr;

	thiz->attrs_nr = 0;
	for(; *thiz->read_ptr != '\0' && thiz->attrs_nr < MAX_ATTR_NR; thiz->read_ptr++){
		char c = *thiz->read_ptr;
	
		switch(state){
			case STAT_PRE_KEY:{
				if(c == end_char || c == '>'){
					state = STAT_END;
				}
				else if(!isspace(c)){
					state = STAT_KEY;
					start = thiz->read_ptr;
				}
			}
			case STAT_KEY:{
				if(c == '='){
					thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
					state = STAT_PRE_VALUE;
				}

				break;
			}
			case STAT_PRE_VALUE:{
				if(c == '\"' || c == '\''){
					state = STAT_VALUE;
					value_end = c;
					start = thiz->read_ptr + 1;
				}
				break;
			}
			case STAT_VALUE:{
				if(c == value_end){
					thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
					state = STAT_PRE_KEY;
				}
			}
			default:break;
		}

		if(state == STAT_END){
			break;
		}
	}
	
	for(i = 0; i < thiz->attrs_nr; i++){
		thiz->attrs[i] = thiz->buffer + (size_t)(thiz->attrs[i]);
	}
	thiz->attrs[thiz->attrs_nr] = NULL;

	return;
}

static void xml_parser_parse_start_tag(XmlParser* thiz)
{
	enum _State{
		STAT_NAME,
		STAT_ATTR,
		STAT_END,
	}state = STAT_NAME;

	char* tag_name = NULL;
	const char* start = thiz->read_ptr - 1;

	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		char c = *thiz->read_ptr;
	
		switch(state){
			case STAT_NAME:{
				if(isspace(c) || c == '>' || c == '/'){
					tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
					state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
				}
				break;
			}
			case STAT_ATTR:{
				xml_parser_parse_attrs(thiz, '/');
				state = STAT_END;

				break;
			}
			default:break;
		}

		if(state == STAT_END){
			break;
		}
	}
	
	tag_name = thiz->buffer + (size_t)tag_name;
	
	if(thiz->read_ptr[0] == '/'){
	}

	for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

	return;
}

static void xml_parser_parse_end_tag(XmlParser* thiz)
{
	char* tag_name = NULL;
	const char* start = thiz->read_ptr;
	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		if(*thiz->read_ptr == '>'){
			tag_name = thiz->buffer + xml_parser_strdup(thiz, start, thiz->read_ptr-start);
			break;
		}
	}
	
	return;
}

static void xml_parser_parse_comment(XmlParser* thiz)
{
	enum _State{
		STAT_COMMENT,
		STAT_MINUS1,
		STAT_MINUS2,
	}state = STAT_COMMENT;

	const char* start = ++thiz->read_ptr;
	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		char c = *thiz->read_ptr;

		switch(state){
			case STAT_COMMENT:{
				if(c == '-'){
					state = STAT_MINUS1;
				}
				break;
			}
			case STAT_MINUS1:{
				if(c == '-'){
					state = STAT_MINUS2;
				}else{
					state = STAT_COMMENT;
				}
				break;
			}
			case STAT_MINUS2:{
				if(c == '>'){
					return;
				}else{
					state = STAT_COMMENT;
				}
			}
			default:break;
		}
	}

	return;
}

static void xml_parser_parse_pi(XmlParser* thiz)
{
	enum _State{
		STAT_NAME,
		STAT_ATTR,
		STAT_END
	}state = STAT_NAME;

	char* tag_name = NULL;
	const char* start = thiz->read_ptr;

	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		char c = *thiz->read_ptr;
	
		switch(state){
			case STAT_NAME:{
				if(isspace(c) || c == '>'){
					tag_name = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
					state = c != '>' ? STAT_ATTR : STAT_END;
				}

				break;
			}
			case STAT_ATTR:{
				xml_parser_parse_attrs(thiz, '?');
				state = STAT_END;
				break;
			}
			default:break;
		}

		if(state == STAT_END)
		{
			break;
		}
	}
	
	tag_name = thiz->buffer + (size_t)tag_name;

	for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '\0'; thiz->read_ptr++);

	return;
}

static void xml_parser_parse_text(XmlParser* thiz)
{
	const char* start = thiz->read_ptr - 1;
	for(; *thiz->read_ptr != '\0'; thiz->read_ptr++){
		char c = *thiz->read_ptr;

		if(c == '<'){
			if(thiz->read_ptr > start){
			}
			thiz->read_ptr--;
			return;
		}
		else if(c == '&'){
			xml_parser_parse_entity(thiz);
		}
	}

	return;
}

static void xml_parser_parse_entity(XmlParser* thiz)
{
	/*TODO*/

	return;
}

void xml_parser_destroy(XmlParser* thiz)
{
	if(thiz != NULL){
		free(thiz->buffer);
		free(thiz);
	}

	return;
}

static const char* strtrim(char* str)
{
	char* p = NULL;

	p = str + strlen(str) - 1;

	while(p != str && isspace(*p)){
		*p = '\0';
		p--;
	}

	p = str;
	while(*p != '\0' && isspace(*p)) p++; 
	if(p != str){
		char* s = p;
		char* d = str;
		while(*s != '\0'){
			*d = *s;
			d++;
			s++;
		}
		*d = '\0';
	}

	return str;
}

#ifdef XML_PARSER_TEST
#define XML "<?xml version=\"1.0\" encoding=\"utf-8\"?> \
<!--comment--> <br/> <p>ppp</p> <br />\
<programmer name=\"lixianjing\" blog=\"http://www.limodev.cn/blog\">text</programmer>"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


char* read_file(const char* file_name){
	char* buffer = NULL;
	FILE* fp = fopen(file_name, "r");

	if(fp != NULL){
		struct stat st = {0};
		if(stat(file_name, &st) == 0){
			buffer = malloc(st.st_size + 1);
			fread(buffer, st.st_size, 1, fp);
			buffer[st.st_size] = '\0';
		}
	}

	return buffer;
}

int main(int argc, char* argv[])
{
	XmlParser* thiz = xml_parser_create();
	if(argc > 1){
		char* buffer = read_file(argv[1]);
		xml_parser_parse(thiz, buffer);
		free(buffer);
	}else{
		xml_parser_parse(thiz, XML);
	}
	xml_parser_destroy(thiz);

	return 0;
}
#endif/*XML_PARSER_TEST*/

all:
	gcc -g -m32 -Wall count_word.c -DCOUNT_WORD_TEST -o count_word_test
	gcc -g -m32 -Wall word_segmentation.c -DWORD_SEGMENTATION_TEST -o word_segmentation_test
	gcc -g -m32 -Wall parse_token.c -DPARSE_TOKEN_TEST -o parse_token_test
	gcc -g -m32 -Wall ini_parser.c -DINI_PARSER_TEST -o ini_parser_test
	gcc -g -m32 -Wall xml_parser.c -DXML_PARSER_TEST -o xml_parser_test

clean:
	rm -f *test

10.2 Builder模式

/*
 * File:    typedef.h
 */

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

#ifndef TYPEDEF_H
#define TYPEDEF_H

typedef enum _Ret
{
	RET_OK,
	RET_OOM,
	RET_STOP,
	RET_INVALID_PARAMS,
	RET_FAIL
}Ret;

typedef void  (*DataDestroyFunc)(void* ctx, void* data);
typedef int   (*DataCompareFunc)(void* ctx, void* data);
typedef Ret   (*DataVisitFunc)(void* ctx, void* data);
typedef int   (*DataHashFunc)(void* data);

#ifdef __cplusplus
#define DECLS_BEGIN extern "C" {
#define DECLS_END   }
#else
#define DECLS_BEGIN
#define DECLS_END
#endif/*__cplusplus*/

#define return_if_fail(p) if(!(p)) \
	{printf("%s:%d Warning: "#p" failed.\n", \
		__func__, __LINE__); return;}
#define return_val_if_fail(p, ret) if(!(p)) \
	{printf("%s:%d Warning: "#p" failed.\n",\
	__func__, __LINE__); return (ret);}

#define SAFE_FREE(p) if(p != NULL) {free(p); p = NULL;}

typedef Ret (*SortFunc)(void** array, size_t nr, DataCompareFunc cmp);

#define MAX_ATTR_NR 64

#endif/*TYPEDEF_H*/
/*
 * File:    xml_builder.h
 */

#include "typedef.h"

#ifndef XML_BUILDER_H
#define XML_BUILDER_H

DECLS_BEGIN

struct _XmlBuilder;
typedef struct _XmlBuilder XmlBuilder;

typedef void (*XmlBuilderOnStartElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
typedef void (*XmlBuilderOnEndElementFunc)(XmlBuilder* thiz, const char* tag);
typedef void (*XmlBuilderOnTextFunc)(XmlBuilder* thiz, const char* text, size_t length);
typedef void (*XmlBuilderOnCommentFunc)(XmlBuilder* thiz, const char* text, size_t length);
typedef void (*XmlBuilderOnPiElementFunc)(XmlBuilder* thiz, const char* tag, const char** attrs);
typedef void (*XmlBuilderOnErrorFunc)(XmlBuilder* thiz, int line, int row, const char* message);
typedef void (*XmlBuilderDestroyFunc)(XmlBuilder* thiz);

struct _XmlBuilder
{
	XmlBuilderOnStartElementFunc on_start_element;
	XmlBuilderOnEndElementFunc   on_end_element;
	XmlBuilderOnTextFunc         on_text;
	XmlBuilderOnCommentFunc      on_comment;
	XmlBuilderOnPiElementFunc    on_pi_element;
	XmlBuilderOnErrorFunc        on_error;
	XmlBuilderDestroyFunc        destroy;

	char priv[1];
};

static inline void xml_builder_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
	return_if_fail(thiz != NULL && thiz->on_start_element != NULL);

	thiz->on_start_element(thiz, tag, attrs);

	return;
}

static inline void xml_builder_on_end_element(XmlBuilder* thiz, const char* tag)
{
	return_if_fail(thiz != NULL && thiz->on_end_element != NULL);

	thiz->on_end_element(thiz, tag);

	return;
}

static inline void xml_builder_on_text(XmlBuilder* thiz, const char* text, size_t length)
{
	return_if_fail(thiz != NULL && thiz->on_text != NULL);

	thiz->on_text(thiz, text, length);

	return;
}

static inline void xml_builder_on_comment(XmlBuilder* thiz, const char* text, size_t length)
{
	return_if_fail(thiz != NULL);

	if(thiz->on_comment != NULL){
		thiz->on_comment(thiz, text, length);
	}

	return;
}

static inline void xml_builder_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
	return_if_fail(thiz != NULL);
	
	if(thiz->on_pi_element != NULL){
		thiz->on_pi_element(thiz, tag, attrs);
	}

	return;
}

static inline void xml_builder_on_error(XmlBuilder* thiz, int line, int row, const char* message)
{
	return_if_fail(thiz != NULL);

	if(thiz->on_error != NULL){
		thiz->on_error(thiz, line, row, message);
	}

	return;
}

static inline void xml_builder_destroy(XmlBuilder* thiz)
{
	if(thiz != NULL && thiz->destroy != NULL){
		thiz->destroy(thiz);
	}

	return;
}

DECLS_END

#endif/*XML_BUILDER_H*/

/*
 * File:    xml_builder_dump.h
 */

#ifndef XML_BUILDER_DUMP_H
#define XML_BUILDER_DUMP_H
#include "xml_builder.h"

DECLS_BEGIN

XmlBuilder* xml_builder_dump_create(FILE* fp);

DECLS_END

#endif/*XML_BUILDER_DUMP_H*/

/*
 * File:    xml_builder_dump.c
 */

#include "xml_builder_dump.h"

typedef struct _PrivInfo
{
	FILE* fp;
}PrivInfo;

static void xml_builder_dump_on_start_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
	int i = 0;
	PrivInfo* priv = (PrivInfo*)thiz->priv;
	fprintf(priv->fp, "<%s", tag);

	for(i = 0; attrs != NULL && attrs[i] != NULL && attrs[i + 1] != NULL; i += 2){
		fprintf(priv->fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);
	}
	fprintf(priv->fp, ">");

	return;
}

static void xml_builder_dump_on_end_element(XmlBuilder* thiz, const char* tag)
{
	PrivInfo* priv = (PrivInfo*)thiz->priv;
	fprintf(priv->fp, "</%s>\n", tag);

	return;
}

static void xml_builder_dump_on_text(XmlBuilder* thiz, const char* text, size_t length)
{
	PrivInfo* priv = (PrivInfo*)thiz->priv;
	fwrite(text, length, 1, priv->fp);

	return;
}

static void xml_builder_dump_on_comment(XmlBuilder* thiz, const char* text, size_t length)
{
	PrivInfo* priv = (PrivInfo*)thiz->priv;
	fprintf(priv->fp, "<!--");
	fwrite(text, length, 1, priv->fp);
	fprintf(priv->fp, "-->\n");

	return;
}

static void xml_builder_dump_on_pi_element(XmlBuilder* thiz, const char* tag, const char** attrs)
{
	int i = 0;
	PrivInfo* priv = (PrivInfo*)thiz->priv;
	fprintf(priv->fp, "<?%s", tag);

	for(i = 0; attrs != NULL && attrs[i] != NULL && attrs[i + 1] != NULL; i += 2)
	{
		fprintf(priv->fp, " %s=\"%s\"", attrs[i], attrs[i + 1]);
	}
	fprintf(priv->fp, "?>\n");

	return;
}

static void xml_builder_dump_on_error(XmlBuilder* thiz, int line, int row, const char* message)
{
	fprintf(stderr, "(%d,%d) %s\n", line, row, message);

	return;
}

static void xml_builder_dump_destroy(XmlBuilder* thiz)
{
	if(thiz != NULL){
		free(thiz);
	}

	return;
}

XmlBuilder* xml_builder_dump_create(FILE* fp)
{
	XmlBuilder* thiz = (XmlBuilder*)calloc(1, sizeof(XmlBuilder));

	if(thiz != NULL){
		PrivInfo* priv = (PrivInfo*)thiz->priv;

		thiz->on_start_element = xml_builder_dump_on_start_element;
		thiz->on_end_element   = xml_builder_dump_on_end_element;
		thiz->on_text          = xml_builder_dump_on_text;
		thiz->on_comment       = xml_builder_dump_on_comment;
		thiz->on_pi_element    = xml_builder_dump_on_pi_element;
		thiz->on_error         = xml_builder_dump_on_error;
		thiz->destroy          = xml_builder_dump_destroy;

		priv->fp = fp != NULL ? fp : stdout;
	}

	return thiz;
}

#ifdef XML_BUILDER_DUMP_TEST
int main(int argc, char* argv[])
{
	const char* pi_attrs[] = {"version", "1.0", "encoding", "utf-8", NULL};
	const char* root_attrs[] = {"name", "lixianjing", "blog", "http://www.limodev.cn/blog",NULL};

	XmlBuilder* thiz = xml_builder_dump_create(stdout);

	xml_builder_on_pi_element(thiz, "xml", pi_attrs);
	xml_builder_on_comment(thiz,"comment", 6);
	xml_builder_on_start_element(thiz, "programmer", root_attrs);
	xml_builder_on_text(thiz,"text", 4);
	xml_builder_on_end_element(thiz, "programmer");

	xml_builder_destroy(thiz);

	return 0;
}
#endif/*XML_BUILDER_DUMP_TEST*/

all:
	gcc -g -m32 -Wall -DXML_BUILDER_DUMP_TEST xml_builder_dump.c -o xml_builder_dump_test
	gcc -g -m32 -Wall -DXML_PARSER_TEST xml_parser.c xml_builder_dump.c xml_builder_tree.c xml_tree.c xml_parser_test.c -o xml_parser_test
	gcc -g -m32 -Wall -DXML_TREE_TEST xml_tree.c xml_builder_dump.c -o xml_tree_test
	gcc -g -m32 -Wall -DXML_BUILDER_TREE_TEST xml_builder_tree.c xml_tree.c xml_builder_dump.c -o xml_builder_tree_test
clean:
	rm -f *test

10.3 管道过滤器模式