LCUI
搜索文档…
CSS 解析器
CSS 解析器的流程和解析器的添加方法介绍。

简单的例子

这个例子展示了如何从字符串中加载 CSS 样式:
1
#include <LCUI.h>
2
#include <LCUI/gui/css_library.h>
3
#include <LCUI/gui/css_parser.h>
4
5
int main(void)
6
{
7
LCUI_Selector selector = Selector(".button");
8
LCUI_StyleSheet stylesheet = StyleSheet();
9
10
LCUI_InitCSSLibrary();
11
LCUI_InitCSSParser();
12
LCUI_LoadCSSString(".toolbar .button { color: #000 }");
13
LCUI_GetStyleSheet(selector, stylesheet);
14
LCUI_PrintStyleSheet(stylesheet);
15
StyleSheet_Delete(stylesheet);
16
Selector_Delete(selector);
17
LCUI_FreeCSSParser();
18
LCUI_FreeCSSLibrary();
19
return 0;
20
}
Copied!
LCUI_LoadCSSString() 函数用于从字符串中加载 CSS 样式到数据库中,它内部会调用一系列的解析器对字符串进行解析,最终转换成便于操作的数据结构。

工作流程

CSS 解析器在解析时会将解析目标分为:无、注释、规则名称、规则数据、选择器、CSS 属性名 和 CSS 属性值,例如以下 CSS 代码:
1
@import "common.css";
2
3
/* button style */
4
.btn {
5
font-size: 16px;
6
}
Copied!
CSS 解析器会将它识别为以下目标进行解析:
1
@[rule-name] [rule-data];
2
3
[comment]
4
[selector] {
5
[css-property-name]: [css-property-value];
6
}
Copied!
每个解析目标都有对应的解析器,它们的解析行为大致是这样的:
  • 无:寻找解析目标,遍历字符串,根据当前读取到的字符来决定切换到哪种解析目标,例如读取到的字符是 @ ,则切换解析目标为规则名称。
  • 规则名称:存储每个字符直到读取的字符是空白符为止,解析完后切换解析目标为规则数据。
  • 规则数据:如果有与规则名称对应的规则解析器,则调用它解析,否则忽略。
  • 选择器:存储每个字符直到解析到左括号 { 为止,转换字符串为选择器,然后切换解析目标为 CSS 属性名。
  • CSS 属性名:存储每个字符直到冒号 :为止,将存储的字符串作为属性名,并记录对应的 CSS 属性解析器,然后切换解析目标为 CSS 属性值。
  • CSS 属性值:存储每个字符直到右括号 } 或分号 ; 为止,如果存在对应的 CSS 属性解析器,则调用它更新当前解析上下文中的样式表。
  • 注释:以上解析器在解析到斜杆 / 时都会切换解析目标为注释,当解析完注释后再恢复解析目标为上个解析目标。

添加 at 规则解析器

目前预设的 at 规则有:@font-face@import@media,其中 @media 规则解析暂未实现,如果你想添加新的 at 规则解析器,则必须修改 CSS 解析器源码才能做到。
首先编辑 include/LCUI/gui/css_parser.h 头文件,在 LCUI_CSSRule 枚举的定义里追加你的新规则的枚举值(例如:CSS_RULE_MY_RULE)。
然后定义你的规则解析上下文和解析器函数,例如:
1
#define GetParserContext(CTX) (CTX)->rule.parsers[CSS_RULE_MY_RULE].data
2
3
typedef struct MyRuleParserContextRec_ {
4
// Define your data
5
// ...
6
} MyRuleParserContextRec, *MyRuleParserContext;
7
8
static int MyRuleParser_Begin(LCUI_CSSParserContext ctx)
9
{
10
// Initialize your parser context
11
// ...
12
// ctx->
13
ctx->rule.state = /* your parser initial state */;
14
return 0;
15
}
16
17
static void MyRuleParser_End(LCUI_CSSParserContext ctx)
18
{
19
MyRuleParserContext data = GetParserContext(ctx);
20
21
// Destroy your data
22
// ...
23
// free(data->xxx);
24
}
25
26
int CSSParser_InitMyRuleParser(LCUI_CSSParserContext ctx)
27
{
28
LCUI_CSSRuleParser parser;
29
FontFaceParserContext data;
30
31
parser = &ctx->rule.parsers[CSS_RULE_MY_RULE];
32
data = NEW(MyRuleParserContextRec, 1);
33
if (!data) {
34
return -ENOMEM;
35
}
36
// Initialize your data
37
// ...
38
// data->xxxx = xxxx;
39
parser->data = data;
40
parser->parse = MyRuleParser_Parse;
41
parser->begin = MyRuleParser_Begin;
42
strcpy(parser->name, "my-rule");
43
return 0;
44
}
45
46
47
void CSSParser_FreeMyRuleParser(LCUI_CSSParserContext ctx)
48
{
49
FontFaceParserContext data = GetParserContext(ctx);
50
51
MyRuleParser_End(ctx);
52
free(data);
53
ctx->rule.parsers[CSS_RULE_MY_RULE].data = NULL;
54
}
55
Copied!
定义好后,在 src/gui/css_parser.c 的中追加调用你的解析器的初始化和销毁函数:
1
LCUI_CSSParserContext CSSParser_Begin(size_t buffer_size, const char *space)
2
{
3
...
4
5
CSSParser_InitMyRuleParser(ctx);
6
return ctx;
7
}
8
9
void CSSParser_End(LCUI_CSSParserContext ctx)
10
{
11
...
12
CSSParser_FreeMyRuleParser(ctx);
13
...
14
free(ctx->buffer);
15
free(ctx);
16
}
Copied!
如需了解更详细的规则解析器实现方式,可参考 @font-face 规则解析器的源码:src/gui/css_rule_font_face.c

添加 CSS 属性解析器

在添加新的 CSS 属性解析器前,我们需要先用 LCUI_AddCssPropertyName() 注册自定义属性名称,拿到该属性的标识号,也就是该属性在样式表中的下标,然后调用LCUI_AddCSSPropertyParser() 函数注册我们的解析器的自定义标识号、解析函数和名称。
接下来我们从下面这个简单的示例来了解如何添加 CSS 属性解析器:
1
#define GetMYPropertyKey(CTX) keys[(CTX)->parser->key]
2
#define SetMyProperty(CTX, S) \
3
CSSStyleParser_SetCSSProperty(CTX, GetMYPropertyKey(CTX), S);
4
5
enum CSSMyPropertyKey {
6
key_my_css_property,
7
// define other css property keys
8
// key_xxx
9
// ...
10
MY_PROPERTY_KEY_TOTAL
11
};
12
13
static int keys[MY_PROPERTY_KEY_TOTAL];
14
15
static int OnParseMyCSSProperty(LCUI_CSSParserStyleContext ctx, const char *str)
16
{
17
LCUI_StyleRec style;
18
19
// parse data from str
20
// ...
21
22
CSSStyleParser_SetCSSProperty(ctx, keys[ctx->parser-key], style);
23
return 0;
24
}
25
26
void InitMyCSSProperties(void)
27
{
28
LCUI_CSSPropertyParserRec my_parser = {
29
key_my_css_property,
30
"my-css-property",
31
OnParseMyCSSProperty
32
};
33
34
keys[my_parser->key] = LCUI_AddCSSPropertyName(my_parser->name);
35
LCUI_AddCSSPropertyParser(my_parser);
36
}
Copied!
如需了解更详细的 CSS 属性解析器实现方式,可参考字体样式解析器的源码:src/gui/css_fontstyle.c

待办事项

完善 CSS 解析器的错误处理
CSS 解析器是以 CSS 代码完全正确为前提而工作的,一旦加载的 CSS 代码存在语法错误或不支持的语法时,可能会出现怪异的解析行为和解析结果,为解决这一问题,我们应该补充错误判断并输出相关错误信息,以便开发者快速定位问题。