Here's how I would tackle this:
$str='
<a href="link.htm">1</a>
<a href="http://www.site.com/link.htm">2</a>
<a href="#link">3</a>
<a href="mailto:mail@example.com">4</a>
';
$arr = preg_split('#(<a\b[^>]+>)#', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach($arr as &$val){
if(preg_match('#<a\b#', $val) && !preg_match('~(?:http|#|mailto)~', $val)){
$val = preg_replace('#^([^"]+")([^"]+)#', '$1'.'http://www.example.com/folder/'.'$2', $val);
}
}
$arr = implode('', $arr);
echo $arr;
Ouput (when viewed as source code):
<a href="http://www.example.com/folder/link.htm">1</a>
<a href="http://www.site.com/link.htm">2</a>
<a href="#link">3</a>
<a href="mailto:mail@example.com">4</a>
I think a big problem with what you have is this: [http#] in your pattern...
This is a negated character class, which basically says, any character that is not an h, nor a t, nor a p, nor a carot, nor a hash... but this doesn't work out as planned..as only the first carot () at the beginning acts a negative.. everything else is simply a list of characters that are not perimissable.
Another problem is perhaps trying to pack it all into regex.. while it can be done, I am more of a fan in mixing regex with addition (and often faster) functionality.. you can still get the same results as one done in pure regex, but often with quicker execution.
EDIT - Come to think of it, we can get rid of the first conditional preg_match of the if statement within the foreach loop and replace with strpos instead...
if(strpos($val, '<a ') !== false && !preg_match('~(?:http|#|mailto)~', $val)){