$re = '/\A # Begin at the beginning only! (No doing it again)
(?: # Non-capturing group consuming the string
(?| # Branch reset on two groups: p2 and p3 (p for prefix)
. # Consume a character
(?=.*+\n(?<p2>\k<p2>?+.) # Lookahead: Go to next line. Then add a character to a group
.*+\n(?<p3>\k<p3>?+.)) # Same. Will fail if the line is too short.
| # When the first brach fails:
.*+\n(?<p2>)(?<p3>) # Consume the rest of the string. Reset the groups.
)+? # As little as possible (Only one character at a time)
(?<=X)(?=.*\n\k<p2>(?<=X).*\n\k<p3>(?<=X)) # Check to see if there\'s a column of X\'s
(?=[\s\S]*(?<count>[\s\S](?(<count>)\k<count>))) # Add a character to a count group at the end.
)+ # Keep doing it until all is consumed/mx';
$str = 'XX
XX
XX
X
X
X';
preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);
// Print the entire match result
var_dump($matches);
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for PHP, please visit: http://php.net/manual/en/ref.pcre.php