Разбор CSS с помощью регулярных выражений - PullRequest
12 голосов
/ 26 октября 2008

Я создаю редактор CSS и пытаюсь создать регулярное выражение, которое может получать данные из документа CSS. Это регулярное выражение работает, если у меня есть одно свойство, но я не могу заставить его работать для всех свойств. Я использую синтаксис preg / perl в PHP.

Regex

(?<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
        )
    )

Заранее спасибо за любую помощь - это сбивает меня с толку весь день!

Ответы [ 8 ]

19 голосов
/ 26 октября 2008

Это кажется слишком запутанным для одного регулярного выражения. Ну, я уверен, что с правильными расширениями продвинутый пользователь может создать правильное регулярное выражение. Но тогда вам потребуется еще более опытный пользователь для его отладки.

Вместо этого я бы предложил использовать регулярное выражение, чтобы вытащить кусочки, а затем токенизировать каждый кусочек отдельно. например.,

/([^{])\s*\{\s*([^}]*?)\s*}/

Затем вы получите селектор и атрибуты в отдельных полях, а затем разделите их. (Даже селектор будет интересно анализировать.) Обратите внимание, что даже это будет иметь боль, если} может появляться внутри кавычек или чего-то еще. Вы могли бы, опять же, сделать из этого чертово множество, чтобы избежать этого, но, возможно, даже лучше избегать использования здесь регулярных выражений и обрабатывать их, анализируя по одному полю за раз, возможно, используя анализатор с рекурсивным спуском или yacc / bison или что угодно.

10 голосов
/ 18 июня 2009

Не используйте свои собственные регулярные выражения для анализа CSS. Зачем изобретать велосипед, пока вас ждет код, готовый к использованию и (надеюсь) без ошибок?

Существует два общедоступных класса, которые могут проанализировать CSS для вас:

HTML_CSS PEAR пакет на pear.php.net

и

Класс CSS Parser в PHPCLasses:

http://www.phpclasses.org/browse/package/1289.html

10 голосов
/ 26 октября 2008

Вы пытаетесь извлечь из данных структуру, а не только отдельные значения. Регулярные выражения могут быть болезненно растянуты, чтобы выполнить работу, но вы действительно входите на территорию парсера и должны вытаскивать большие пушки, а именно парсеры.

Я никогда не использовал инструменты генерации парсера PHP, но они выглядят хорошо после легкого сканирования документов. Проверьте LexerGenerator и ParserGenerator . LexerGenerator возьмет несколько регулярных выражений, описывающих различные типы токенов в языке (в данном случае, CSS), и выдаст некоторый код, который распознает отдельные токены. ParserGenerator возьмет грамматику, описание того, что в языке состоит из каких-то других вещей, и выдаст синтаксический анализатор, код, который берет кучу токенов и возвращает синтаксическое дерево (структуру данных, которую вы ищете.

8 голосов
/ 26 октября 2008

Я бы рекомендовал не использовать регулярные выражения для разбора CSS - особенно в одном регулярном выражении!

Если вы настаиваете на разборе регулярных выражений, разделите его на разумные разделы - используйте одно регулярное выражение для разбиения всех блоков body{..}, а другой - для анализа атрибутов color:rgb(1,2,3);.

Если вы на самом деле пытаетесь написать что-то «полезное» (не пытаетесь выучить регулярные выражения), поищите заранее написанный синтаксический анализатор CSS.

Я нашел этот 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", "");} (кажется, это было отладочной вещью, которая осталась в)

Я отразил скрипт здесь , с вышеуказанными изменениями в

6 голосов
/ 29 марта 2011

Я написал фрагмент кода, который легко анализирует 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
        )
)
6 голосов
/ 23 апреля 2010

Я использую приведенное ниже регулярное выражение, и оно в значительной степени работает ... конечно, этот вопрос уже устарел, и я вижу, что вы отказались от своих усилий ... но в случае, если кто-то еще столкнется с ним:

(?<selector>(?:(?:[^,{]+),?)*?)\{(?:(?<name>[^}:]+):?(?<value>[^};]+);?)*?\}

(необходимо сначала удалить все / * комментарии * / из вашего CSS)

2 голосов
/ 22 февраля 2018

Основываясь на текущем ответе Танкталуса, стоит отметить несколько улучшений и крайних случаев.

CSS-синтаксический анализ Regex

\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 для извлеченных объявлений. Если вы получаете совпадения, вы можете запустить рекурсию (хотя я не уверен, что бывают случаи, когда для ванильного CSS у вас будет более 1 уровня вложенности).

Краевые Чехлы
  • Это не относится к правой фигурной скобке в строке

Завтрашнее исследование

Я решил вместо этого использовать пакет npm, например css или cssom. Я знаю, что это в PHP, но это будет очень тяжело для меня и поможет в крайних случаях, с которыми я продолжаю сталкиваться.

Редактировать

В итоге я использовал публичную библиотеку Jotform css.js. Он имеет очень маленькую площадь, что было одним из основных требований, которые я предъявлял при выборе библиотек для анализа CSS

0 голосов
/ 09 мая 2010

Попробуйте это

<code>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 '
"\ п".
...