<?php
$balanced_string_regex = "~(?x)                  # Free-Spacing
(?(DEFINE)            # Define a few subroutines
   (?<double>&ldquo;(?:(?!&[lr]squo;).)*&rdquo;)  # full set of doubles (no quotes inside)
   (?<single>&lsquo;(?:(?!&[lr]dquo;).)*&rsquo;)  # full set of singles (no quotes inside)
   (?<notquotes>(?:(?!&[lr][sd]quo;).)*)          # chars that are not quotes
)                     # end DEFINE

^                       # Start of string
(?:                     # Start non-capture group
   (?&notquotes)        # Any non-quote chars
   &l(?<type>[sd])quo;  # Opening quote, capture single or double type
   # any full singles, doubles, not quotes or recursion
   (?:(?&single)|(?&double)|(?&notquotes)|(?R))*
   &r\k<type>quo;       # Closing quote of the correct type
   (?&notquotes)      # 
)++                   # Repeat non-capture group
$                     # End of string
~";

$string = "&ldquo;He said  &rdquo; &lsquo;He said  &rsquo;";
check_string($string);
$string = "<p>&ldquo;Wait a moment,&rdquo; Jacey said. The street light lit up his aged, rat face. Who&rsquo;s on the move?&rdquo;</p>";
check_string($string);
$string = "<p>&ldquo;Wait a moment,&rdquo; Jacey said. The street light lit up his aged, rat face. &lsquo;Whos on the &ldquo;move?&rdquo; &rsquo;</p>";
check_string($string);
$string = "<p>&ldquo;He said he&rsquo; coming afer you,&rdquo; Harry said, and he&rsquo; bringing the boys too!&rdquo;</p>";
check_string($string);
$string = "<p>&ldquo;He &lsquo;said he&rsquo; coming afer you,&rdquo; Harry said, and he&ldquo; bringing the boys too!&rdquo;</p>";
check_string($string);


function check_string($string) {
	global $balanced_string_regex;
	echo (preg_match($balanced_string_regex, $string)) ?
        "Balanced!\n" :
		" Nah... Not Balanced.\n" ;
}
