I understand since Regex is essentially stateless, it's rather difficult to achieve complicated matches without resorting to supplementing application logic, however I'm curious to know if the following is possible.
Match all whitespace, easy enough: \s+
But skip whitespace between certain delimiters, in my case the word <pre>
and </pre>
nostrip
.
Are there any tricks to achieve this? I was thinking along the lines of two separate matches, one for all whitespace, and one for nostrip sections, and somehow negating the latter from the former.<pre>
blocks
"This is some text NOSTRIP this is more text NOSTRIP some more text."
// becomes
"ThisissometextNOSTRIP this is more text NOSTRIPsomemoretext."
The nesting of given tags nostrip sections is irrelevant, and I'm not trying to parse the tree HTML or anything, just tidying a text file, but saving the whitespace in nostrip sections for obvious reasons.<pre>
blocks
(better?)
This is ultimately what I went with. I'm sure it can be optimized in a few places, but it works nicely for now.
public function stripWhitespace($html, Array $skipTags = array('pre')){
foreach($skipTags as &$tag){
$tag = "<{$tag}.*?/{$tag}>";
}
$skipped = array();
$buffer = preg_replace_callback('#(?<tag>' . implode('|', $skipTags) . ')#si',
function($match) use(&$s开发者_StackOverflowkipped){
$skipped[] = $match['tag'];
return "\x1D" . (count($skipped) - 1) . "\x1D";
}, $html
);
$buffer = preg_replace('#\s+#si', ' ', $buffer);
$buffer = preg_replace('#(?:(?<=>)\s|\s(?=<))#si', '', $buffer);
for($i = count($skipped) - 1; $i >= 0; $i--){
$buffer = str_replace("\x1D{$i}\x1D", $skipped[$i], $buffer);
}
return $buffer;
}
I you are using a scripting language, I would use a multi-step approach.
- pull out the NOSTRIP sections, and save to an array, and replace with markers (### or something)
- replace all the spaces
- re-inject all your saved NOSTRIP snippets
I once created a set of functions to reduce white space in html outputs:
function minify($html) {
if(empty($html)) {
return $html;
}
$html = preg_replace('/^(.*)((<pre.*<\/pre>)(.*?))?$/Ues', "parse('$1').'$3'.minify('$4')", $html);
return $html;
}
function parse($html) {
var_dump('1'.$html);
// Replace multiple spaces with a single space
$html = preg_replace('/(\s+)/m', ' ', $html);
// Remove spaces that are followed by either > or <
$html = preg_replace('/ ([<>])/', '$1', $html);
$html = str_replace('> ', '>', $html);
return $html;
}
$html = minify($html);
You'll probably have to modify this slightly to fit your needs.
精彩评论