使用cmark解析Markdown文档

如果在C或者C++项目中解析Markdown,可以使用cmark库。

开发环境

Fedora系统可以直接通过

dnf install cmark-devel

来安装cmark的开发库。

安装之后,就可以使用头文件/usr/include/cmark.h 中的函数进行开发,最后在程序中链接-lcmark即可。

为了简化这一操作,也可以使用pkg-config文件。

需要注意的是,在Fedora系统中,cmark的pkgconfig文件名字是libcmark.pc。即,pkg-config命令需要查询libcmark。

如:

~/$ pkg-config --cflags --libs libcmark
-lcmark

使用方法

cmark使用三个主要的数据结构,分别是cmark_parser、cmark_iter与cmark_node。

其中,cmark_parser用来解析,有一系列与解析相关的函数;cmark_iter用来遍历,可以实现在cmark_node节点间切换;而cmark_node是所有数据节点。

在头文件中,使用注释的方式说明了cmark_parser的使用方法。

443  * ## Parsing
444  *
445  * Simple interface:
446  *
447  *     cmark_node *document = cmark_parse_document("Hello *world*", 13,
448  *                                                 CMARK_OPT_DEFAULT);
449  *
450  * Streaming interface:
451  *
452  *     cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
453  *     FILE *fp = fopen("myfile.md", "rb");
454  *     while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
455  *         cmark_parser_feed(parser, buffer, bytes);
456  *         if (bytes < sizeof(buffer)) {
457  *             break;
458  *         }
459  *     }
460  *     document = cmark_parser_finish(parser);
461  *     cmark_parser_free(parser);
462  

即可以使用cmark_parse_document解析文本内容,输入参数分别是字符串指针与大小。

也可以使用cmark_parser来解析流式数据,方法为先创建一个cmark_parser,之后使用cmark_parser_feed来喂数据,最后调用cmark_parser_finish来指示解析结束,返回解析出来的cmark_node。

cmark_node

cmark的几个主要数据结构定义,并没有在头文件里,而是使用了typedef的别名。如果只是使用cmark,可以不用关心它们的具体定义,只使用头文件中的操作它们的函数即可。(这种信息隐藏,也是一种故意为之的设计,起到了类似C++中的private作用。)

cmark_node的主要属性,就是它的类型,即cmark_node_type,可以通过:

261 /** Returns the type of 'node', or `CMARK_NODE_NONE` on error.
262  */
263 CMARK_EXPORT cmark_node_type cmark_node_get_type(cmark_node *node);
264 
265 /** Like 'cmark_node_get_type', but returns a string representation
266     of the type, or `"<unknown>"`.
267  */
268 CMARK_EXPORT
269 const char *cmark_node_get_type_string(cmark_node *node);

这两个方法,分别获得cmark_node的类型值,与类型的字符串表示。

cmark_node_type是一个枚举值,定义为:

/** ## Node Structure
 31  */
 32 
 33 typedef enum {
 34   /* Error status */
 35   CMARK_NODE_NONE,
 36 
 37   /* Block */
 38   CMARK_NODE_DOCUMENT,
 39   CMARK_NODE_BLOCK_QUOTE,
 40   CMARK_NODE_LIST,
 41   CMARK_NODE_ITEM,
 42   CMARK_NODE_CODE_BLOCK,
 43   CMARK_NODE_HTML_BLOCK,
 44   CMARK_NODE_CUSTOM_BLOCK,
 45   CMARK_NODE_PARAGRAPH,
 46   CMARK_NODE_HEADING,
 47   CMARK_NODE_THEMATIC_BREAK,
 48 
 49   CMARK_NODE_FIRST_BLOCK = CMARK_NODE_DOCUMENT,
 50   CMARK_NODE_LAST_BLOCK = CMARK_NODE_THEMATIC_BREAK,
 51 
 52   /* Inline */
 53   CMARK_NODE_TEXT,
 54   CMARK_NODE_SOFTBREAK,
 55   CMARK_NODE_LINEBREAK,
 56   CMARK_NODE_CODE,
 57   CMARK_NODE_HTML_INLINE,
 58   CMARK_NODE_CUSTOM_INLINE,
 59   CMARK_NODE_EMPH,
 60   CMARK_NODE_STRONG,
 61   CMARK_NODE_LINK,
 62   CMARK_NODE_IMAGE,
 63 
 64   CMARK_NODE_FIRST_INLINE = CMARK_NODE_TEXT,
 65   CMARK_NODE_LAST_INLINE = CMARK_NODE_IMAGE
 66 } cmark_node_type;

熟悉Markdown的应该很清楚这些类型的意义,不再赘述。

需要注意的是,获取这些node的属性的方法,需要根据不同的类型,才能取得相应的值。

比如如果是一个CMARK_NODE_HEADDING,就可以通过cmark_node_get_heading_level来取得层级。

 /** Returns the heading level of 'node', or 0 if 'node' is not a heading.
283  */
284 CMARK_EXPORT int cmark_node_get_heading_level(cmark_node *node);

通过cmark_node_set_heading_level来设置层级。

290 /** Sets the heading level of 'node', returning 1 on success and 0 on error.
291  */
292 CMARK_EXPORT int cmark_node_set_heading_level(cmark_node *node, int level);

而如果是一个CMARK_NODE_IMAGE或者CMARK_NODE_URL,则可以通过cmark_node_get_title取得图片的标题,或者URL的显示文本。

/** Returns the title of a link or image 'node', or an empty  
353     string if no title is set.  Returns NULL if called on a node  
354     that is not a link or image.  
355  */  
356 CMARK_EXPORT const char *cmark_node_get_title(cmark_node *node);

通过cmark_node_get_url取得实际的链接地址。

341 /** Returns the URL of a link or image 'node', or an empty string
342     if no URL is set.  Returns NULL if called on a node that is
343     not a link or image.
344  */
345 CMARK_EXPORT const char *cmark_node_get_url(cmark_node *node);
346 

渲染

我们除了可以根据cmark_node来做自定义的操作之外,还可以使用cmark库的渲染方法,把Markdown文本渲染成其它文本格式。

比如,渲染成XML:

508 /** Render a 'node' tree as XML.  It is the caller's responsibility  
509  * to free the returned buffer.  
510  */  
511 CMARK_EXPORT  
512 char *cmark_render_xml(cmark_node *root, int options);  

渲染成HTML:

514 /** Render a 'node' tree as an HTML fragment.  It is up to the user  
515  * to add an appropriate header and footer. It is the caller's  
516  * responsibility to free the returned buffer.  
517  */  
518 CMARK_EXPORT  
519 char *cmark_render_html(cmark_node *root, int options);  

渲染成man手册页:

521 /** Render a 'node' tree as a groff man page, without the header.  
522  * It is the caller's responsibility to free the returned buffer.  
523  */  
524 CMARK_EXPORT  
525 char *cmark_render_man(cmark_node *root, int options, int width);  

渲染成commonmark:

527 /** Render a 'node' tree as a commonmark document.  
528  * It is the caller's responsibility to free the returned buffer.  
529  */  
530 CMARK_EXPORT  
531 char *cmark_render_commonmark(cmark_node *root, int options, int width);  

或者渲染成latex:

533 /** Render a 'node' tree as a LaTeX document.  
534  * It is the caller's responsibility to free the returned buffer.  
535  */  
536 CMARK_EXPORT  
537 char *cmark_render_latex(cmark_node *root, int options, int width);

以上这些都是渲染一个节点,即cmark_node,如果只是最简单的把一个Markdown渲染成HTML,还可以不解析,直接使用一个方法:

27 CMARK_EXPORT  
28 char *cmark_markdown_to_html(const char *text, size_t len, int options);

输入参数是一个字符串和长度,输出一个HTML的字符串,编码都是UTF-8。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值