通过正则表达式解析 CSS
Posted
技术标签:
【中文标题】通过正则表达式解析 CSS【英文标题】:Parsing CSS by regex 【发布时间】:2010-09-19 05:10:01 【问题描述】:我正在创建一个 CSS 编辑器,并尝试创建一个可以从 CSS 文档中获取数据的正则表达式。如果我有一个属性,但我无法让它适用于所有属性,则此正则表达式有效。我在 php 中使用 preg/perl 语法。
正则表达式
(?<selector>[A-Za-z]+[\s]*)[\s]*[\s]*((?<properties>[A-Za-z0-9-_]+)[\s]*:[\s]*(?<values>[A-Za-z0-9#, ]+);[\s]*)*[\s]*
测试用例
body background: #f00; font: 12px Arial;
预期结果
Array(
[0] => Array(
[0] => body background: #f00; font: 12px Arial;
[selector] => Array(
[0] => body
)
[1] => Array(
[0] => body
)
[2] => font: 12px Arial;
[properties] => Array(
[0] => font
)
[3] => Array(
[0] => font
)
[values] => Array(
[0] => 12px Arial
[1] => background: #f00
)
[4] => Array(
[0] => 12px Arial
[1] => background: #f00
)
)
)
实际结果
Array(
[0] => Array
(
[0] => body background: #f00; font: 12px Arial;
[selector] => body
[1] => body
[2] => font: 12px Arial;
[properties] => font
[3] => font
[values] => 12px Arial
[4] => 12px Arial
)
)
提前感谢您的帮助 - 这让我整个下午都感到困惑!
【问题讨论】:
能否请您发布此示例的工作代码部分? @Khaled:我放弃了这个项目,因为我并没有真正“获得”PHP 解析器。当我有时间时,我会考虑用 PHP 或 C# 来实现它。 没问题,我现在正在使用 CSSTidy,它做得很好。 【参考方案1】:在 Tanktalus 当前答案的基础上,还有一些改进和边缘情况需要注意。
CSS 解析正则表达式
\s*([^]+)\s*\\s*([^]*?)\s*
此正则表达式将执行一些空间修剪并命中本示例中列出的一些其他边缘情况:https://regex101.com/r/qQRIHx/5
键:值对;更复杂的正则表达式的陷阱
我也开始尝试定义键:值对,但很快发现在每个选择器有多种样式的情况下,事情开始变得比我想要的要复杂。您可以在此处查看我尝试分隔键:值的正则表达式版本 1 以及它如何因多个声明而失败:https://regex101.com/r/qQRIHx/1
实施
正如其他人提到的,您应该将其分解为多个步骤来解析和标记您的 css。此正则表达式将帮助您获取声明,但您需要将其解析出来。
声明解析器
在获得第一组匹配项后,您可以使用类似的方法来解析声明。
([^:\s]+)*\s*:\s*([^;]+);
例如:https://regex101.com/r/py9OKO/1/
边缘案例上面的示例适用于多个声明,但它可能只是 1 个没有分号结尾的声明,它会在 [大多数] 浏览器中呈现,但会破坏这个正则表达式。
注意案例
如果有媒体查询,您可能还需要考虑嵌套规则。在这种情况下,我会尝试针对提取的声明运行 css 匹配正则表达式。如果你得到匹配,你可以在它上面运行递归(虽然我不确定在某些情况下你会为 vanilla CSS 嵌套超过 1 级)。
边缘案例 这不处理字符串中的右花括号明天的研究
我决定改用像 css
或 cssom
这样的 npm 包。我知道这是在 PHP 中,但它会为我做很多繁重的工作并处理我一直遇到的边缘情况。
编辑:
我最终使用了 Jotform 的公共 css.js 库。它的占用空间非常小,这是我在选择库来解析 CSS 时的主要要求之一。
https://github.com/jotform/css.js/tree/master 他们还发表了这篇文章来解释他们的过程: https://stories.jotform.com/writing-a-css-parser-in-javascript-3ecaa1719a43【讨论】:
非常感谢您提供示例和详细答案!【参考方案2】:对于单个正则表达式来说,这似乎太复杂了。好吧,我确信通过正确的扩展,高级用户可以创建正确的正则表达式。但是你需要一个更高级的用户来调试它。
相反,我建议使用正则表达式提取片段,然后分别标记每个片段。例如,
/([^])\s*\\s*([^]*?)\s*/
然后您最终将选择器和属性放在不同的字段中,然后将它们分开。 (即使选择器解析起来也会很有趣。)请注意,如果 可以出现在引号或其他内容中,即使这样也会很痛苦。您可以再次将其复杂化以避免这种情况,但最好在这里完全避免正则表达式,并通过一次解析一个字段来处理它,也许通过使用递归下降解析器或 yacc/bison 或随便。
【讨论】:
同意第一部分——分解问题。但我不明白为什么这不能成为正则表达式的工作。 “任何足够先进的正则表达式都无法与魔法区分开来”——Arthur C. Clarke 的错误引用(我认为)这可能是正则表达式的工作——问题是正则表达式是否是正确的工具。 @harpo 这是解析器的工作,解析器可能会使用正则表达式来帮助它识别令牌,但您需要的不仅仅是正则表达式来实现解析器。 其实正则表达式应该是([^]+)\s*\\s*([^]+)\s*
,因为非括号字符不止一个。【参考方案3】:
我写了一段很容易解析 CSS 的代码。你所要做的就是做一些真正的爆炸...... $css 变量是 CSS 的字符串。你所要做的就是做一个print_r($css)
来得到一个很好的CSS数组,完全解析。
$css_array = array(); // master array to hold all values
$element = explode('', $css);
foreach ($element as $element)
// get the name of the CSS element
$a_name = explode('', $element);
$name = $a_name[0];
// get all the key:value pair styles
$a_styles = explode(';', $element);
// remove element name from first property element
$a_styles[0] = str_replace($name . '', '', $a_styles[0]);
// loop through each style and split apart the key from the value
$count = count($a_styles);
for ($a=0;$a<$count;$a++)
if ($a_styles[$a] != '')
$a_key_value = explode(':', $a_styles[$a]);
// build the master css array
$css_array[$name][$a_key_value[0]] = $a_key_value[1];
给你这个:
Array
(
[body] => Array
(
[background] => #f00
[font] => 12px arial
)
)
【讨论】:
【参考方案4】:我正在使用下面的正则表达式,它几乎可以工作......当然这个问题现在很老了,我看到你已经放弃了你的努力......但万一其他人遇到它:
(?<selector>(?:(?:[^,]+),?)*?)\(?:(?<name>[^:]+):?(?<value>[^;]+);?)*?\
(为了安全起见,首先从 CSS 中删除所有 /* cmets */)
【讨论】:
这个非常适合我的目的。感谢您发布此内容。 媒体查询怎么样?【参考方案5】:试试这个
function trimStringArray($stringArray)
$result = array();
for($i=0; $i < count($stringArray); $i++)
$trimmed = trim($stringArray[$i]);
if($trimmed != '') $result[] = $trimmed;
return $result;
$regExp = '/\|\/';
$rawCssData = preg_split($regExp, $style);
$cssArray = array();
for($i=0; $i < count($rawCssData); $i++)
if($i % 2 == 0)
$cssStyle['selectors'] = array();
$selectors = split(',', $rawCssData[$i]);
$cssStyle['selectors'] = trimStringArray($selectors);
if($i % 2 == 1)
$attributes = split(';', $rawCssData[$i]);
$cssStyle['attributes'] = trimStringArray($attributes);
$cssArray[] = $cssStyle;
//return false;
echo '<pre>'."\n";
print_r($cssArray);
echo '</pre>'."\n";
【讨论】:
这不适用于嵌套的@media ?能否请您修改并提出建议。【参考方案6】:不要使用您自己的正则表达式来解析 CSS。 为什么要在有代码等着你、准备好使用并且(希望)没有错误的时候重新发明***?
有两个普遍可用的类可以为您解析 CSS:
pear.php.net 上的 html_CSS PEAR 包
和
PHPCLasses 上的 CSS Parser 类:
http://www.phpclasses.org/browse/package/1289.html
【讨论】:
【参考方案7】:我建议不要使用正则表达式来解析 CSS - 尤其是在单个正则表达式中!
如果您坚持在正则表达式中进行解析,请将其拆分为合理的部分 - 使用一个正则表达式来拆分所有 body..
块,然后使用另一个正则表达式来解析 color:rgb(1,2,3);
属性。
如果您实际上是在尝试编写“有用”的东西(而不是尝试学习正则表达式),请寻找预先编写的 CSS 解析器。
我发现this cssparser.php 似乎工作得很好:
$cssp = new cssparser;
$cssp -> ParseStr("body background: #f00;font: 12px Arial; ");
print_r($cssp->css);
..输出以下内容:
Array
(
[body] => Array
(
[background] => #f00
[font] => 12px arial
)
)
解析器非常简单,所以应该很容易弄清楚它在做什么。哦,我不得不删除读取if($this->html) $this->Add("VAR", "");
的行(它似乎是一个调试的东西,留在里面)
我已经镜像了脚本here,上面的改动在
【讨论】:
【参考方案8】:您试图从数据中提取结构,而不仅仅是单个值。正则表达式可能会很费力地完成这项工作,但你真的进入了解析器领域,应该拿出大枪,即解析器。
我从未使用过 PHP 解析器生成工具,但是在对文档进行简单扫描后,它们看起来还不错。查看LexerGenerator 和ParserGenerator。 LexerGenerator 将采用一堆正则表达式来描述一种语言(在本例中为 CSS)中不同类型的标记,并生成一些识别各个标记的代码。 ParserGenerator 将采用语法,描述一种语言中的哪些事物由哪些其他事物组成,并吐出一个解析器,该代码采用一堆标记并返回语法树(您所追求的数据结构。
【讨论】:
以上是关于通过正则表达式解析 CSS的主要内容,如果未能解决你的问题,请参考以下文章