RegEx in PHP: pattern matching outside of non-exclusive quotes

I am writing a method to remove specific data from a SQL query string, and I need the regex to match any word inside the curly braces ONLY when it appears outside the single quotes. I also need it to account for the possibility of escaping (preceded by a backslash) as well as escaped backslashes.

In the following examples, I need the regex to match {FOO}, not {BAR}:

blah blah {FOO} blah 'I\'m typing {BAR} here with an escaped backslash \\'
blah blah {FOO} 'Three backslashes {BAR} and an escaped quote \\\\\\\' here {BAR}'

      

I am using preg_match in PHP to get a word in curly braces (FOO in this case). Here's the regex string I have so far:

$regex = '/' .
    // Match the word in braces
    '\{(\w+)\}' .
    // Only if it is followed by an even number of single-quotes
    '(?=(?:[^\']*\'[^\']*\')*[^\']*$)' .
    // The end
    '/';

      

My logic is that since the only thing I am handling is a legitimate SQL string (besides the added curly brace), if the set of curly braces is followed by an even number of unexperienced quotes, then it must be outside the quotes.

The regex I provided is 100% successful EXCEPT for accounting for hidden quotes. I just need to make sure there is no odd number of backslashes before the quoted match, but for the life of me, I can't pipe that to RegEx. Any members?

0


a source to share


3 answers


The way to deal with escaped quotes and backslashes is to consume them in matched pairs.

(?=(?:(?:(?:[^\'\\]++|\\.)*+\'){2})*+(?:[^\'\\]++|\\.)*+$)

      

In other words, when you look at the next quote, you are missing any pair of characters that start with a backslash. This takes care of all hidden quotes and avoids the backslash. This lookahead will allow escaped characters outside of quoted sections to be escaped, which is probably not necessary, but it probably won't hurt either.



ps, Note the liberal use of possessive quantifiers ( *+

and ++

); without them, you may have performance issues, especially if the target rows are large. Also, if the lines may contain line breaks, you may need to do DOTALL matching (otherwise, "singleline" or "/ s").

However, I agree with mmyers: if you try to parse SQL you will run into problems that regular expressions cannot handle at all. Of everything that has a regular expression, SQL is one of the worst.

+1


a source


Simple, and perhaps naively, str_replace all of your double backslashes. Then str_replace outside the escaped single quotes. At this point, it is relatively easy to find matches that are not related between single quotes (for example, using an existing regex).



0


a source


If you really want to use regular expressions for this, I would do it in two steps:

  • Separate lines from lines without preg_split

    :

    $re = "('(?:[^\\\\']+|\\\\(\\\\\\\\)*.)*')";
    $parts = preg_split('/'.$re.'/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    
          

  • Replace whatever is on the lines:

    foreach ($parts as $key => $val) {
        if (preg_match('/^'.$re.'$/', $val)) {
            $parts[$key] = preg_replace('/\{([^}]*)}/', '$1', $val);
        }
    }
    
          

But a real parser will probably do better as this approach is not that efficient.

0


a source







All Articles