sabberworm/php-css-parser

PHP编写的CSS文件解析器


README

Build Status Coverage Status

PHP编写的CSS文件解析器。可以将CSS文件提取到数据结构中,操作该结构,并以(优化后)CSS的形式输出。

使用方法

使用Composer安装

composer require sabberworm/php-css-parser

提取

要使用CSS解析器,创建一个新的实例。构造函数具有以下形式

new \Sabberworm\CSS\Parser($css);

例如,要读取一个文件,你可以这样做

$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
$cssDocument = $parser->parse();

在输出之前,可以操作生成的CSS文档结构。

选项

字符集

如果CSS文件不包含@charset声明,将使用字符集选项。默认为UTF-8,因此如果你不打算更改,根本不需要创建设置对象。

$settings = \Sabberworm\CSS\Settings::create()
    ->withDefaultCharset('windows-1252');
$parser = new \Sabberworm\CSS\Parser($css, $settings);

严格解析

要让解析器在遇到无效/未知构造时抛出异常(而不是尝试忽略它们并继续解析),提供如下配置的\Sabberworm\CSS\Settings对象

$parser = new \Sabberworm\CSS\Parser(
    file_get_contents('somefile.css'),
    \Sabberworm\CSS\Settings::create()->beStrict()
);

请注意,这也会禁用解析旧版IE特定filter规则的未引号变体的工作区。

禁用多字节函数

为了实现更快的解析,你可以选择让PHP-CSS-Parser使用常规字符串函数而不是mb_*函数。在大多数情况下,这应该可以正常工作,即使是UTF-8文件,因为所有多字节字符都在字符串字面量中。仍然不建议使用此功能处理你无法控制的输入,因为它没有经过充分的测试。

$settings = \Sabberworm\CSS\Settings::create()->withMultibyteSupport(false);
$parser = new \Sabberworm\CSS\Parser($css, $settings);

操作

生成的数据结构主要由五种基本类型组成:CSSListRuleSetRuleSelectorValue。还有两种额外的类型:ImportCharset,你很少会用到。

CSSList

CSSList代表一个通用的CSS容器,很可能包含声明块(具有选择器的规则集),但它也可能包含at规则、字符集声明等。

要访问存储在CSSList中的项目(例如,当你调用$parser->parse()时返回的文档),使用getContents(),然后遍历该集合,并使用instanceof检查你是否正在处理另一个CSSListRuleSetImportCharset

要将新项目(选择器、媒体查询等)附加到现有的CSSList中,使用该类的构造函数构建它,并使用append($oItem)方法。

RuleSet

RuleSet是单个规则的容器。规则集最常见的形式是由选择器约束的。以下是一些具体的子类型

  • AtRuleSet - 用于通用的at规则,这些规则未由特定类覆盖,即不是@import@charset@media。一个常见的例子是@font-face
  • DeclarationBlock - 由Selector约束的RuleSet;包含一个选择器对象数组(在CSS中以逗号分隔)以及应用于匹配元素的规则。

注意:一个CSSList可以包含其他CSSList(以及ImportCharset),而一个RuleSet只能包含Rule

如果你要操作RuleSet,请使用方法addRule(Rule $rule)getRules()removeRule($rule)(它接受一个Rule或规则名;可选地以短横线结尾以删除所有相关规则)。

Rule

规则只有一个字符串键(规则)和一个

是一个抽象类,它只定义了render方法。原子值类型的具体子类包括:

  • Size – 包含一个数值size和一个单位。
  • Color – 颜色可以以#rrggbb、#rgb或schema(val1, val2, …)的形式输入,但始终以('s' => val1, 'c' => val2, 'h' => val3, …)的数组形式存储,并以第二种形式输出。
  • CSSString – 这只是一个用于区分关键字的引号字符串的包装器;始终以双引号输出。
  • URL – CSS中的URL;始终以URL("")表示法输出。

还有一个Value的抽象子类,ValueList:一个ValueList表示一系列Value,由某些分隔符(通常是,、空白或/)分隔。

存在两种类型的ValueList

  • RuleValueList – 默认类型,用于表示所有多值规则,如font: bold 12px/3 Helvetica, Verdana, sans-serif;(其中值将是一个空格分隔的基本值bold的列表,一个斜杠分隔的列表和一个逗号分隔的列表)。
  • CSSFunction – 一种包含函数名和值是函数参数的特殊类型值。还处理像filter: alpha(opacity=90);这样的等号分隔的参数列表。

便利方法

Document上有一些便利方法,可以简化查找、操作和删除规则

  • getAllDeclarationBlocks() – 如其名所示;无论选择器嵌套有多深。别名getAllSelectors()
  • getAllRuleSets() – 如其名所示;无论规则集嵌套有多深。
  • getAllValues() – 在Rule中查找所有Value对象。

待办事项

  • 更多便利方法(如selectorsWithElement($sId/Class/TagName)attributesOfType($type)removeAttributesOfType($type)
  • 真正的多字节支持。目前,只有首255个代码点只占用一个字节且与ASCII相同的单字节多字节字符集才受支持(是的,UTF-8符合这个描述)。
  • 命名颜色支持(使用Color而不是匿名字符串字面量)

用例

使用Parser将ID预置于所有选择器中

$myId = "#my_id";
$parser = new \Sabberworm\CSS\Parser($css);
$cssDocument = $parser->parse();
foreach ($cssDocument->getAllDeclarationBlocks() as $block) {
    foreach ($block->getSelectors() as $selector) {
        // Loop over all selector parts (the comma-separated strings in a
        // selector) and prepend the ID.
        $selector->setSelector($myId.' '.$selector->getSelector());
    }
}

将所有绝对大小缩小一半

$parser = new \Sabberworm\CSS\Parser($css);
$cssDocument = $parser->parse();
foreach ($cssDocument->getAllValues() as $value) {
    if ($value instanceof CSSSize && !$value->isRelative()) {
        $value->setSize($value->getSize() / 2);
    }
}

删除不需要的规则

$parser = new \Sabberworm\CSS\Parser($css);
$cssDocument = $parser->parse();
foreach($cssDocument->getAllRuleSets() as $oRuleSet) {
    // Note that the added dash will make this remove all rules starting with
    // `font-` (like `font-size`, `font-weight`, etc.) as well as a potential
    // `font` rule.
    $oRuleSet->removeRule('font-');
    $oRuleSet->removeRule('cursor');
}

输出

要将整个CSS文档输出到变量中,只需使用->render()

$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
$cssDocument = $parser->parse();
print $cssDocument->render();

如果您想格式化输出,请传递一个类型为\Sabberworm\CSS\OutputFormat的实例

$format = \Sabberworm\CSS\OutputFormat::create()
    ->indentWithSpaces(4)->setSpaceBetweenRules("\n");
print $cssDocument->render($format);

或使用预定义的格式之一

print $cssDocument->render(Sabberworm\CSS\OutputFormat::createPretty());
print $cssDocument->render(Sabberworm\CSS\OutputFormat::createCompact());

要了解输出格式化可以做什么,请查看tests/OutputFormatTest.php中的测试。

示例

示例1(At规则)

输入

@charset "utf-8";

@font-face {
  font-family: "CrassRoots";
  src: url("../media/cr.ttf");
}

html, body {
    font-size: 1.6em;
}

@keyframes mymove {
    from { top: 0px; }
    to { top: 200px; }
}
结构(《var_dump()》)
class Sabberworm\CSS\CSSList\Document#4 (2) {
  protected $aContents =>
  array(4) {
    [0] =>
    class Sabberworm\CSS\Property\Charset#6 (2) {
      private $sCharset =>
      class Sabberworm\CSS\Value\CSSString#5 (2) {
        private $sString =>
        string(5) "utf-8"
        protected $iLineNo =>
        int(1)
      }
      protected $iLineNo =>
      int(1)
    }
    [1] =>
    class Sabberworm\CSS\RuleSet\AtRuleSet#7 (4) {
      private $sType =>
      string(9) "font-face"
      private $sArgs =>
      string(0) ""
      private $aRules =>
      array(2) {
        'font-family' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#8 (4) {
            private $sRule =>
            string(11) "font-family"
            private $mValue =>
            class Sabberworm\CSS\Value\CSSString#9 (2) {
              private $sString =>
              string(10) "CrassRoots"
              protected $iLineNo =>
              int(4)
            }
            private $bIsImportant =>
            bool(false)
            protected $iLineNo =>
            int(4)
          }
        }
        'src' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#10 (4) {
            private $sRule =>
            string(3) "src"
            private $mValue =>
            class Sabberworm\CSS\Value\URL#11 (2) {
              private $oURL =>
              class Sabberworm\CSS\Value\CSSString#12 (2) {
                private $sString =>
                string(15) "../media/cr.ttf"
                protected $iLineNo =>
                int(5)
              }
              protected $iLineNo =>
              int(5)
            }
            private $bIsImportant =>
            bool(false)
            protected $iLineNo =>
            int(5)
          }
        }
      }
      protected $iLineNo =>
      int(3)
    }
    [2] =>
    class Sabberworm\CSS\RuleSet\DeclarationBlock#13 (3) {
      private $aSelectors =>
      array(2) {
        [0] =>
        class Sabberworm\CSS\Property\Selector#14 (2) {
          private $sSelector =>
          string(4) "html"
          private $iSpecificity =>
          NULL
        }
        [1] =>
        class Sabberworm\CSS\Property\Selector#15 (2) {
          private $sSelector =>
          string(4) "body"
          private $iSpecificity =>
          NULL
        }
      }
      private $aRules =>
      array(1) {
        'font-size' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#16 (4) {
            private $sRule =>
            string(9) "font-size"
            private $mValue =>
            class Sabberworm\CSS\Value\Size#17 (4) {
              private $fSize =>
              double(1.6)
              private $sUnit =>
              string(2) "em"
              private $bIsColorComponent =>
              bool(false)
              protected $iLineNo =>
              int(9)
            }
            private $bIsImportant =>
            bool(false)
            protected $iLineNo =>
            int(9)
          }
        }
      }
      protected $iLineNo =>
      int(8)
    }
    [3] =>
    class Sabberworm\CSS\CSSList\KeyFrame#18 (4) {
      private $vendorKeyFrame =>
      string(9) "keyframes"
      private $animationName =>
      string(6) "mymove"
      protected $aContents =>
      array(2) {
        [0] =>
        class Sabberworm\CSS\RuleSet\DeclarationBlock#19 (3) {
          private $aSelectors =>
          array(1) {
            [0] =>
            class Sabberworm\CSS\Property\Selector#20 (2) {
              private $sSelector =>
              string(4) "from"
              private $iSpecificity =>
              NULL
            }
          }
          private $aRules =>
          array(1) {
            'top' =>
            array(1) {
              [0] =>
              class Sabberworm\CSS\Rule\Rule#21 (4) {
                private $sRule =>
                string(3) "top"
                private $mValue =>
                class Sabberworm\CSS\Value\Size#22 (4) {
                  private $fSize =>
                  double(0)
                  private $sUnit =>
                  string(2) "px"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(13)
                }
                private $bIsImportant =>
                bool(false)
                protected $iLineNo =>
                int(13)
              }
            }
          }
          protected $iLineNo =>
          int(13)
        }
        [1] =>
        class Sabberworm\CSS\RuleSet\DeclarationBlock#23 (3) {
          private $aSelectors =>
          array(1) {
            [0] =>
            class Sabberworm\CSS\Property\Selector#24 (2) {
              private $sSelector =>
              string(2) "to"
              private $iSpecificity =>
              NULL
            }
          }
          private $aRules =>
          array(1) {
            'top' =>
            array(1) {
              [0] =>
              class Sabberworm\CSS\Rule\Rule#25 (4) {
                private $sRule =>
                string(3) "top"
                private $mValue =>
                class Sabberworm\CSS\Value\Size#26 (4) {
                  private $fSize =>
                  double(200)
                  private $sUnit =>
                  string(2) "px"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(14)
                }
                private $bIsImportant =>
                bool(false)
                protected $iLineNo =>
                int(14)
              }
            }
          }
          protected $iLineNo =>
          int(14)
        }
      }
      protected $iLineNo =>
      int(12)
    }
  }
  protected $iLineNo =>
  int(1)
}

输出(《render()》)

@charset "utf-8";
@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}
html, body {font-size: 1.6em;}
@keyframes mymove {from {top: 0px;} to {top: 200px;}}

示例2(值)

输入

#header {
    margin: 10px 2em 1cm 2%;
    font-family: Verdana, Helvetica, "Gill Sans", sans-serif;
    color: red !important;
}
结构(《var_dump()》)
class Sabberworm\CSS\CSSList\Document#4 (2) {
  protected $aContents =>
  array(1) {
    [0] =>
    class Sabberworm\CSS\RuleSet\DeclarationBlock#5 (3) {
      private $aSelectors =>
      array(1) {
        [0] =>
        class Sabberworm\CSS\Property\Selector#6 (2) {
          private $sSelector =>
          string(7) "#header"
          private $iSpecificity =>
          NULL
        }
      }
      private $aRules =>
      array(3) {
        'margin' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#7 (4) {
            private $sRule =>
            string(6) "margin"
            private $mValue =>
            class Sabberworm\CSS\Value\RuleValueList#12 (3) {
              protected $aComponents =>
              array(4) {
                [0] =>
                class Sabberworm\CSS\Value\Size#8 (4) {
                  private $fSize =>
                  double(10)
                  private $sUnit =>
                  string(2) "px"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(2)
                }
                [1] =>
                class Sabberworm\CSS\Value\Size#9 (4) {
                  private $fSize =>
                  double(2)
                  private $sUnit =>
                  string(2) "em"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(2)
                }
                [2] =>
                class Sabberworm\CSS\Value\Size#10 (4) {
                  private $fSize =>
                  double(1)
                  private $sUnit =>
                  string(2) "cm"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(2)
                }
                [3] =>
                class Sabberworm\CSS\Value\Size#11 (4) {
                  private $fSize =>
                  double(2)
                  private $sUnit =>
                  string(1) "%"
                  private $bIsColorComponent =>
                  bool(false)
                  protected $iLineNo =>
                  int(2)
                }
              }
              protected $sSeparator =>
              string(1) " "
              protected $iLineNo =>
              int(2)
            }
            private $bIsImportant =>
            bool(false)
            protected $iLineNo =>
            int(2)
          }
        }
        'font-family' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#13 (4) {
            private $sRule =>
            string(11) "font-family"
            private $mValue =>
            class Sabberworm\CSS\Value\RuleValueList#15 (3) {
              protected $aComponents =>
              array(4) {
                [0] =>
                string(7) "Verdana"
                [1] =>
                string(9) "Helvetica"
                [2] =>
                class Sabberworm\CSS\Value\CSSString#14 (2) {
                  private $sString =>
                  string(9) "Gill Sans"
                  protected $iLineNo =>
                  int(3)
                }
                [3] =>
                string(10) "sans-serif"
              }
              protected $sSeparator =>
              string(1) ","
              protected $iLineNo =>
              int(3)
            }
            private $bIsImportant =>
            bool(false)
            protected $iLineNo =>
            int(3)
          }
        }
        'color' =>
        array(1) {
          [0] =>
          class Sabberworm\CSS\Rule\Rule#16 (4) {
            private $sRule =>
            string(5) "color"
            private $mValue =>
            string(3) "red"
            private $bIsImportant =>
            bool(true)
            protected $iLineNo =>
            int(4)
          }
        }
      }
      protected $iLineNo =>
      int(1)
    }
  }
  protected $iLineNo =>
  int(1)
}

输出(《render()》)

#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}

类图

API和弃用策略

请参阅我们的API和弃用策略

贡献

以错误报告、功能请求或拉取请求形式进行的贡献非常欢迎。🙏请参阅我们的贡献指南,了解如何为PHP-CSS-Parser做出贡献。

贡献者/感谢

杂项

遗留支持

此项目的最新预PSR-0版本可以通过 0.9.0 标签检查。

运行测试

要为此项目运行所有持续集成(CI)检查(包括单元测试),

  • 运行 composer install 以安装由Composer管理的开发依赖项;
  • 运行 phive install 以安装由PHIVE管理的开发依赖项;
  • 运行 composer ci 以运行所有静态和动态CI检查。

有关其他Composer脚本的详细信息(例如,运行特定的CI检查)可通过 composer list 提供。