I've been looking at similar problems with a similar chunk of code in the migration program. I just now found the same thing, that nested items is what requires looping but some of the expressions interfere with each other.

I think the fix might be to reverse the loop so that each expression is evaluated 10 times rather than the whole batch 10 times. That will resolve all the nestings for one regex before moving to the next.

Example:

Code
foreach( $regexes as $z ) {
   for( $loop = 0; $loop < 10; $loop++ ) {
      if( !preg_match( $z[0], $body ) )
         break;
      $body = preg_replace( $z[0], $z[1], $body );
   }
}

I like the way you're checking to see if you're done with the expression rather than put up with just doing it 10 times (since we can't count on the count parameter being there in preg_replace).

Anyway, the above works so far in the migration code and still keeps the code simple, but time will tell.