Анализ CSS по регулярному выражению

Я создаю редактор 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
        )
    )

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

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

и

Класс Parser CSS в PHPCLasses:

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

Ответ 4

Я бы рекомендовал не использовать регулярное выражение для разбора 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", "");} (кажется, это отладочная вещь, которая была оставлена)

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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";

Ответ 8

Исходя из текущего ответа Tanktalus, там есть несколько улучшений и красных случаев.

Редактор Parsing CSS

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

Это Regex выполнит некоторую прорисовку пространства и попадет на некоторые дополнительные граничные случаи, перечисленные в этом примере: https://regex101.com/r/qQRIHx/5

ключ: пары значений; Ловушки дальнейшего сложного регулярного выражения

Я тоже начал работать над разграничением пар ключ: значение, но быстро увидел в случае, когда в селекторе было несколько стилей, что вещи начали становиться более хитрыми, чем я хотел. Вы можете просмотреть версию 1 регулярного выражения, где я попытался разграничить значения ключа: и как это произошло с несколькими объявлениями здесь: https://regex101.com/r/qQRIHx/1

Реализация

Как уже упоминалось, вы должны разбить это на несколько шагов для анализа и tokenize вашего css. Это регулярное выражение поможет вам получить объявления, но вам нужно будет их разобрать.

Объявление Parser

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

([^:\s]+)*\s*:\s*([^;]+);

Пример: https://regex101.com/r/py9OKO/1/

Кронштейн

В приведенном выше примере отлично работает с несколькими объявлениями, но возможно, что это всего лишь 1 объявление без конца двоеточия, которое будет отображаться в [большинстве] браузеров, но сломает это регулярное выражение.

Отмеченные случаи

Вам также может потребоваться учитывать вложенные правила в случае, если есть запрос на мультимедиа. В этом случае я попытался бы запустить регулярное выражение css, соответствующее извлеченным объявлениям. Если вы получаете совпадения, вы можете запустить рекурсию (хотя я не уверен, что там, где у вас было бы более 1 уровня, вложенных для ванильного CSS).

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