ekhtml是一个高效SAX方式的HTML解析库。

文件说明

官网下载ekhtml-0.3.2.tar.gz文件解压后,内部包括源码、测试文件、文档、编译脚本等。

如需编译成静态库或动态库后进行集成,可以参考INSTALL文件,使用./configure && make命令编译,编译后的库文件生成与src/.libs/中。直接使用库文件,还需要引用include/ekhtml.h文件。

如需进行源码集成,可以复制srcinclude两个文件夹中的文件。

其中ekhtml.h是整个库对外的API,本文档也是基于ekhtml.h进行介绍。

基本类型

typedef struct ekhtml_string_t {
const char *str; /**< Actual string data */
size_t len; /**< Length of the data in `str` */
} ekhtml_string_t;

核心的字符串类型,与nginx的字符串类似采用非NULL结尾的类型定义。

typedef struct ekhtml_attr_t {
ekhtml_string_t name; /**< Name of the attribute */
ekhtml_string_t val; /**< Value of the attribute */
unsigned int isBoolean; /**< True of the attribute is boolean */
struct ekhtml_attr_t *next; /**< Pointer to next attribute in the list */
} ekhtml_attr_t;

属性列表,用于存放标签属性和内容的

typedef struct ekhtml_parser_t ekhtml_parser_t;

解析器对象,可以认为是ekhtml运转的关键。

基本使用方法

  1. 新建parser对象
  2. 给parser配置回调函数(例如script标签回调函数、img标签回调函数)
  3. 将HTML文本输入给parser,parser会根据解析情况调用步骤2注册的回调函数。
  4. 释放parser对象

parser新建与释放

extern ekhtml_parser_t *ekhtml_parser_new(void *cbdata);
extern void ekhtml_parser_destroy(ekhtml_parser_t *parser);

其中void *cbdata将来会传递给回调函数,用于事先具体业务,一般可以使用一个自定义结构体,保存上下文信息,用于将几个回调函数关联起来。关于void *cbdata还有一个动态修改的接口:

extern void ekhtml_parser_cbdata_set(ekhtml_parser_t *parser, void *cbdata);

回调函数类型与注册

支持的回调函数类型有下三种:

typedef void (*ekhtml_data_cb_t)(void *cbdata, ekhtml_string_t *data);
typedef void (*ekhtml_starttag_cb_t)(void *cbdata, ekhtml_string_t *tag, ekhtml_attr_t *attrs);
typedef void (*ekhtml_endtag_cb_t)(void *cbdata, ekhtml_string_t *tag);

以数据<FOO bar="baz">data_to_process</FOO>解析举例:

  • ekhtml_data_cb_t 用于处理文本内容,解析到data_to_process时会调用ekhtml_data_cb_t类型的函数,data参数就是data_to_process
  • ekhtml_starttag_cb_t用于处理起始标签,解析到<FOO bar="baz">时会调用ekhtml_starttag_cb_t类型的函数,tag参数为FOO ,attrs为属性信息bar="baz",数据结构参考上文的ekhtml_attr_t
  • ekhtml_endtag_cb_t用于处理结尾标签,解析到</FOO>时会调用ekhtml_endtag_cb_t类型的函数,tag参数为FOO
  • 三种回调函数类型的cbdata参数,在上文已经说明。

【注意】单闭合标签例如<link rel="stylesheet" />,只会调用starttag不会调用endtag。

函数注册接口有4个:

extern void ekhtml_parser_datacb_set(ekhtml_parser_t *parser, ekhtml_data_cb_t cb);
extern void ekhtml_parser_commentcb_set(ekhtml_parser_t *parser, ekhtml_data_cb_t cb);
extern void ekhtml_parser_startcb_add(ekhtml_parser_t *parser, const char *tag, ekhtml_starttag_cb_t cb);
extern void ekhtml_parser_endcb_add(ekhtml_parser_t *parser, const char *tag, ekhtml_endtag_cb_t cb);

ekhtml_parser_startcb_addekhtml_parser_endcb_add没有特别需要解释的。ekhtml_parser_datacb_setekhtml_parser_commentcb_set都是注册ekhtml_data_cb_t类型的回调函数,但ekhtml_parser_commentcb_set只用于处理注释信息。

【注意】ekhtml_parser_datacb_set是一个扩展性极强的函数,包括非标准标签,标签间的回车换行空格都会调用ekhtml_parser_datacb_set配置的回调函数,具体可以运行下文的demo感受一下。

HTML文本输入

涉及到2个接口

extern void ekhtml_parser_feed(ekhtml_parser_t *parser, ekhtml_string_t *data);
extern int ekhtml_parser_flush(ekhtml_parser_t *parser, int flushall);
  • ekhtml_parser_feed接口允许将数据分多次输入,即使多少次输入的数据在关键位置出现分段。第一次调用输入<FO第二次调用输入0></FOO>,也是可以实现正确解析的。
  • ekhtml_parser_flush此接口与内部实现机制相关,用于立刻刷新内部缓存。ekhtml存在一块内部BUF,feed接口的入参会复制到内部BUF,多次调用feed接口后,内部BUF满之后,ekhtml调用ekhtml_parser_flush进行数据解析。可以手动调用flush立刻进行解析,为了减少解析次数,提升整体性能,建议不需要手动调用flush。
  • flushall参数一般配置为0即可,配置为1会强制刷新未解析完成的内容,上文提到的标签文本被分离的场景将失效。

DEMO代码

#include <stdio.h>
#include <string.h>
#include "ekhtml.h" #define PRINT_BUFF_SIZE (1024)
static char g_print_buff[PRINT_BUFF_SIZE]; static void print_ekhtml_string(ekhtml_string_t *data) {
if (data == NULL) {
printf("data == NULL\n");
return;
}
if (data->len >= PRINT_BUFF_SIZE) {
printf("data->len >= PRINT_BUFF_SIZE\n");
return;
} strncpy(g_print_buff, data->str, data->len);
g_print_buff[data->len] = '\0';
printf("%s\n", g_print_buff); return;
} static void simple_data_callback(void *cbdata, ekhtml_string_t *data) {
printf("enter simple_data_callback\n");
printf("data = ");
print_ekhtml_string(data); return;
} static void start_tag_callback(void *cbdata, ekhtml_string_t *tag, ekhtml_attr_t *attrs) {
ekhtml_attr_t *attr; printf("enter start_tag_callback\n");
printf("tag = ");
print_ekhtml_string(tag); attr = attrs;
while(attr != NULL) {
printf("attr->name = ");
print_ekhtml_string(&attr->name);
printf("attr->val = ");
print_ekhtml_string(&attr->val);
printf("attr->isBoolean = %u\n", attr->isBoolean); attr = attr->next;
} return;
} static void end_tag_callback(void *cbdata, ekhtml_string_t *tag) {
printf("enter end_tag_callback\n");
printf("tag = ");
print_ekhtml_string(tag); return;
} int main(int argc, char *argv[]) {
FILE *file;
char file_buf[1024];
size_t nread; ekhtml_parser_t *parser;
ekhtml_string_t html_str; parser = ekhtml_parser_new(NULL);
if (parser == NULL) {
printf("ekhtml_parser_new fail\n");
return -1;
} ekhtml_parser_datacb_set(parser, simple_data_callback);
ekhtml_parser_commentcb_set(parser, simple_data_callback);
ekhtml_parser_startcb_add(parser, "script", start_tag_callback);
ekhtml_parser_endcb_add(parser, "script", end_tag_callback); if ((file = fopen("data.html", "r")) == NULL) {
printf("open data.html fail\n");
return -1;
}
while ((nread = fread(file_buf, 1, sizeof(file_buf), file)) > 0) {
html_str.str = file_buf;
html_str.len = nread;
ekhtml_parser_feed(parser, &html_str);
ekhtml_parser_flush(parser, 0);
}
fclose(file); ekhtml_parser_destroy(parser);
return 0;
}

编译脚本:

gcc -g -o demo demo.c libekhtml.a

ekhtml使用总结的更多相关文章

随机推荐

  1. hdu1251 字典树trie 模板题

    //字典树模板题.题意:给一个库,每次查询,求以之为前缀的单词数量. #include<iostream> #include<string> #include<vecto ...

  2. Java运算基础

    计算机对负数的运算 =  先取绝对值的原码----> 然后取反,----->+1   这是负数的补码表示 例如  -5       5的原码= 0000,0101  取反   1111,1 ...

  3. python多线程(三)

    原文:http://www.cnblogs.com/tqsummer/archive/2011/01/25/1944771.html 一.Python中的线程使用: Python中使用线程有两种方式: ...

  4. 关于button中设置文字不显示的问题

    这个因为使用的image加载方式是setimage而不是setbackgroundimage导致文字始终出不来.

  5. Invalid regular expression: unmatched parentheses

    Unmatched ) in Javascript regular expression您的某些字符串包含错误')'.你需要逃避这个.这是这样做的功能: function escapeRegExp(s ...

  6. keras模型总结

    https://keras.io/zh/ https://keras.io/zh/models/about-keras-models/ 在 Keras 中有两类主要的模型:Sequential 顺序模 ...

  7. 使用zTree进行数据动态显示

    由于公司项目的须要.现学了一下zTree的使用. 以下是我项目的结构图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYmVuamFtaW5fd2h4/f ...

  8. 5. TCP客户/服务器程序示例

    signal 信号是一种软件中断,异步发生,在进程运行的时候随时可能发生.信号可以: 由一个进程发给另一个进程,或发给自身 由内核发给某个进程 信号的action: signal handler,在信 ...

  9. openwrt gstreamer实例学习笔记(五. gstreamer BUS)

    1)概述 BUS(总线) 是一个简单的系统,它采用自己的线程机制将一个管道线程的消息分发到一个应用程序当中.总线的优势是:当使用GStreamer的时候,应用程序不需要线程识别,即便GStreamer ...

  10. javascript常用事件及方法

    1.获取鼠标坐标,考虑滚动条拖动 var e = event || window.event; var scrollX = document.documentElement.scrollLeft || ...