preg_replace replace line breaks across multiple lines between two symbols

Viewed 97

How can I match what's between a . and a { and merge them onto a single line without touching the spacing between them.

This regex (\.|@)[^}]*\{ inculdes the ending { no matter what I try, and I need the (\.|@) part so I can add more selectors in there. Online Regex

Yes, I know I shouldn't be doing this with regex, but I have no other option as of now. So any help will be greatly appreciated.

Edit I'll be using this in preg_replace in php

Raw data

.a {
    // rules
}

.a-b,
.a-b .b, .a-b.s
.x .y, .x {
    // rules
}

@a {
    // rules
}

@k {
    // rules
}

Output

.a {
    // rules
}

.a-b, .a-b .b, .a-b.s, .x .y, .x {
    // rules
}

@a {
    // rules
}

@k {
    // rules
}
2 Answers

You can use

preg_replace('~(?:\G(?!\A)|^[@.])[^{]*?\K\s+~m', ' ', $text)

See the regex demo. Details:

  • (?:\G(?!\A)|^[@.]) - end of the previous successful match (\G(?!\A)) or (|) start of string (^) and then @ or .
  • [^{]*? - any zero or more (but as few as possible) chars other than {
  • \K - match reset operator that discards all text matched so far in the overall match memory buffer
  • \s+ - any one or more whitespace chars.

Note the m flag that is necessary to make ^ match start of a line, not just start of a whole string.

See the PHP demo:

$text = ".a {\r\n    // rules\r\n}\r\n\r\n.a-b,\r\n.a-b .b, .a-b.s\r\n.x .y, .x {\r\n    // rules\r\n}\r\n\r\n@a {\r\n    // rules\r\n}\r\n\r\n@k {\r\n    // rules\r\n}";
echo preg_replace('~(?:\G(?!\A)|^[@.])[^{]*?\K\s+~m', ' ', $text);

Update: Stupildy, I missed that this was a PHP question, not a JS one. My answer is JS-based; however it is easily adapted to PHP. PHP has preg_replace_callback for the replacement callback, and the pattern will be the same.


If I understand you right, you want to collapse multi-selector CSS rules and media queries into single lines.

The first thing to point out is that your current pattern isn't safe for all CSS selectors. It won't cover those that start with # or *, for example.

Try this:

let edited = str.replace(/^[\*\.#@:\-\[][\s\S]+?(?=\{)/mg, match => {
    return match.replace(/\n/g, ' ');
});

Demo.

Pattern explanation:

  • Anchor to the start of a line
  • The line must start with ., #, *, @, -, : or [, covering most CSS selectors.
  • Following the selector(s), match whatever comes after, until...
  • }
  • We run each match through a callback, replacing line breaks with spaces.

The m (multi-line) flag tells JS to interpret our anchor (^) as pertaining to any line, not just the first.

The g (global) flag handles all instances, not just the first found.

Related