解析 CSS 以进行分析
解析 CSS,以便我们可以使用结果进行分析。
这一次,我们将研究 CSS 解析,以便我们可以使用结果进行分析。
故事
由于我们正在使用 ASP.NET MVC3 启动一个新项目,我想到的其中一件事是如何更好地管理我们的 CSS 和 JavaScript。 我认为首先要做的是,如果你能对你的所有 CSS 文件进行一些静态分析,看看是否出现某些情况,那就太好了。
多人处理基于 HTML 的 UI 的问题在于,很难找到要使用的 CSS 类。 因此,您最终可能会得到执行相同操作的类,具有不同定义的类被定义多次,等等。
所有这些都会导致一个不太理想的局面。消除问题的根源,即不知道使用哪个 CSS 类,被证明是困难的。 帮助解决问题的症状证明容易得多,所以这是我现在选择的路径。
两部分解决方案
为了对只能以纯文本形式提供给我的内容进行静态分析,我首先需要将纯文本解析成更容易分析的内容。 然后,我可以使用结果来执行实际分析并传达结果。
分析如何解析 CSS
要为任何语言编写解析器,需要深入了解其语法。 幸运的是,CSS 不是一种非常复杂的语言。 W3Schools.com 在提供 CSS 语法的解释方面非常有用。 归结起来就是这样
- CSS 文档由 CSS 规则组成
- CSS 规则由一个选择器和一组声明组成
- 每个声明都由一个属性和一个值组成
- 注释可以存在于 CSS 文档的顶层(在 CSS 规则之外)或声明之间
为了对此进行静态分析,我想出了一些 interface
定义,允许我查询 CSS 文档的结构
1: public enum SelectorType
2: {
3: Tag,
4: Id,
5: Class
6: }
7:
8: public interface ICSSDocument
9: {
10: string FilePath { get; set; }
11: IEnumerable<IRule> Rules { get; }
12: void AddRule(IRule rule);
13: }
14:
15: public interface IRule
16: {
17: ISelector Selector { get; set; }
18: IEnumerable<IDeclaration> Declarations { get; }
19: void AddDeclaration(IDeclaration declaration);
20: }
21:
22: public interface ISelector
23: {
24: string Name { get; set; }
25: SelectorType SelectorType { get; set; }
26: }
27:
28: public interface IDeclaration
29: {
30: string Name { get; set; }
31: string Value { get; set; }
32: }
请注意,这可能不是最终版本,因为我可能会提出实现静态分析的要求。
对于像这样的文本解析,总是存在几种方法。 在这种情况下,我认为使用简单明了的文本解析是最灵活的,因为我将来可能想向解析器添加功能。 这是我提出的主要解析循环
1: public void Parse()
2: {
3: string data = File.ReadAllText(FilePath);
4:
5: _position = 0;
6: _isInComment = false;
7: while (_position < data.Length)
8: {
9: if (IsEndOfFile(data))
10: {
11: break;
12: }
13: HandleBeginOfComment(data);
14: HandleEndOfComment(data);
15: if (!_isInComment)
16: {
17: HandleRule(data);
18: }
19: else
20: {
21: _position++;
22: }
23: }
24: }
正如你所看到的,我有一个(private
)字段来跟踪 CSS 文档中的位置,另一个字段来跟踪注释。
您可能会发现 IsEndOfFile
方法很奇怪,因为我在 while
循环中有一个条件应该做同样的事情。 但是,我需要提前检查一个位置,以防我仍在检查注释(或者实际上是在注释中)。 该方法的定义很简单
1: private bool IsEndOfFile(string data)
2: {
3: return _position == data.Length - 1;
4: }
HandleBeginOfComment
方法检查注释的开头
1: private void HandleBeginOfComment(string data)
2: {
3: if (data[_position] == '/' && data[_position + 1] == '*')
4: {
5: _position += 2;
6: _isInComment = true;
7: }
8: }
基本上,它检查 string /*
,如果在当前位置找到该 string
,它会将光标移动两个字符并设置 _isInComment
标志。 HandleEndOfComment
对 */
执行相同的操作,并将 _isInComment
标志再次设置为 false
。 目前忽略任何注释,但很容易扩展主解析循环以允许解析注释。
HandleRule
方法处理所有的解析魔法,这是有道理的,因为 Rule 是 CSS 文档的主要组成部分。
1: private void HandleRule(string data)
2: {
3: while (_position < data.Length && !StartOfRule(data[_position]))
4: {
5: HandleBeginOfComment(data);
6: if (_isInComment)
7: {
8: return;
9: }
10: _position++;
11: }
12: string selectorData = GetSelector(data);
13: string declarationsData = GetDeclarations(data);
14:
15: IRule rule = _kernel.Get<IRule>();
16:
17: ISelector selector = _kernel.Get<ISelector>();
18: selector.Name = selectorData;
19: selector.SelectorType = GetSelectorTypeFromName(selectorData);
20: rule.Selector = selector;
21:
22: HandleDeclarations(rule, declarationsData);
23:
24: AddRule(rule);
25: }
第一个循环处理文档后面遇到的注释。 如果我们确实遇到注释,我们只需返回到主循环,然后主循环将处理查找注释的结尾。 在同一个循环中,它检查 Rule 的开头。
如果在此循环之后我们仍然在该方法中,则我们已到达 Rule 的开头。 正如我们之前提到的,Rule 由一个选择器和一组声明组成。 方法 GetSelector
和 GetDeclarations
负责解析 CSS 文档的那些部分。 一旦我们有了这些数据,我们就可以用它来创建一个规则。 我们使用 Ninject Kernel 来创建 IRule
和 ISelector
实现的实例。
请注意,现在,我们将选择器视为一个单一实体。 未来的改进可能是将选择器拆分为多个部分,并单独为其分配类型。
HandleDeclarations
方法接受声明文本,将其解析为 IDeclaration
实现并将其添加到给定的 IRule
1: private void HandleDeclarations(IRule rule, string declarationsData)
2: {
3: string[] declarations = declarationsData.Split(new char[] { ';' },
StringSplitOptions.RemoveEmptyEntries);
4: foreach (string declaration in declarations)
5: {
6: if (string.IsNullOrWhiteSpace(declaration))
7: {
8: continue;
9: }
10: int splitterIndex = declaration.IndexOf(":");
11: string declarationName = declaration.Substring(0, splitterIndex).Trim();
12: string declarationValue = declaration.Substring(splitterIndex + 1).Trim();
13:
14: IDeclaration declarationInstance = _kernel.Get<IDeclaration>();
15: declarationInstance.Name = declarationName;
16: declarationInstance.Value = declarationValue;
17: rule.AddDeclaration(declarationInstance);
18: }
19: }
请注意,我使用 String.Trim
来确保我们的声明数据中没有空格,这可能会妨碍我们的分析(并且在 CSS 中没有任何价值)。
到目前为止一切顺利。 我们现在可以将 CSS 解析为对象模型,这使我们能够以结构化的方式分析 CSS。 我计划写下一篇文章,展示基于此模型的分析。
未包含完整的源代码:不幸的是,由于此项目归我的雇主所有,因此我无法包含完整的源代码,但是,我包含的部分应该为您提供对 CSS 解析的良好见解。