65.9K
CodeProject 正在变化。 阅读更多。
Home

解析 CSS 以进行分析

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2011 年 10 月 31 日

CPOL

4分钟阅读

viewsIcon

10154

解析 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 由一个选择器和一组声明组成。 方法 GetSelectorGetDeclarations 负责解析 CSS 文档的那些部分。 一旦我们有了这些数据,我们就可以用它来创建一个规则。 我们使用 Ninject Kernel 来创建 IRuleISelector 实现的实例。

请注意,现在,我们将选择器视为一个单一实体。 未来的改进可能是将选择器拆分为多个部分,并单独为其分配类型。

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 解析的良好见解。

© . All rights reserved.