如何匹配不在字符串/注释中的模式?

时间:2012-01-31 21:39:37

标签: php regex

环境:PHP 5.3

我正在尝试编写自己的查询参数替换方法。基本上我想接受这个:

select * from xxx where a=? and b>?

并将其转换为

select * from xxx where a=1 and b>2

当然,假设已知所有?参数的值。好的,所以这有点简化,但足以解决问题。

所以,我需要做的是找到给定字符串中的所有?标记。容易,对吗?但有一个问题:我不想找到字符串或注释中的标记。所以,在这个字符串中:

select *  -- I know * is bad, but just once can't hurt, right?
from xxx /* ? */ where a=? and b='Question?'

只应替换其中一个?标记。

我的直觉告诉我,PHP的preg_replace() 应该完成任务...但是我的正则表达式知识使我无法构建适当的模式。 :(我也可以“手动”解析它,但我担心表演会受到不适当的打击。

那么 - 这可以通过正则表达式快速完成(如果是的话,模式是什么),还是我应该逐个字符地解析它?

4 个答案:

答案 0 :(得分:1)

您可以先尝试删除注释中的所有问号并记住它们并在查询中放置占位符,然后通过preg_replace()解析查询,然后在注释中插入带有占位符的问号。 我的意思是......好像

$matches = array();
preg_match_all('/\/\*.*?.*\*\//U', $query, $matches);
preg_replace('/\/\*.*?.*\*\//U', $arrayWithIndicesOfParameters, $query);
preg_replace(/*your replacement of parameters*/);
preg_replace($arrayWithIndicesOfParameters, $matches, $query); //str_replace should be sufficient here

答案 1 :(得分:0)

这对正则表达式来说是个难题。解析器更合适,但只要满足某些约束,正则表达式就可以正常工作。约束是:

  • 没有嵌套评论
  • 字符串内没有转义引号
  • 评论中没有单引号
  • 字符串内没有评论分隔符
  • 所有报价和评论均衡正确
  • 您可以指定最大行长度

如果是这种情况,那么您只需查找?

  • 不在--之前(在同一行)。
  • 除非*/首先出现,否则
  • 后面没有/*
  • 后跟偶数引号,

假设最大行长度为100,则为您提供

$result = preg_replace(
    '%(?<!--.{0,100})    # Assert no -- preceding on this line
    \?                   # Match a ?
    (?!                  # Assert that it\'s impossible to match...
     (?s:                #  (allowing the dot to match newlines here):
      (?!/\*)            #  (and making sure there is no intervening /*)
      .                  #  any character
     )*                  # zero or more times, if that string is followed by
     \*/                 # */
    )                    # End of lookahead
    (?=                  # Assert that it *is* possible to match
     (?:                 # the following regex:
      [^\']*\'[^\']*\'   #  a string containing exactly two quotes
     )*                  #  repeated zero or more times
     [^\']*              # followed by only non-quote characters
     $                   # until the end of the string.
    )                    # End of lookahead.
    %x', 
    'REPLACE', $subject);

答案 2 :(得分:0)

其他人可能有一个更优雅的解决方案,但我的第一直觉是使用正则表达式完全剥离注释,然后模式匹配参数。

$expressions = array(
  "#/\*(.*)\*/#",
  "#[-]{2}(.*)\\n#",
);
$query = preg_replace($expressions, "", $query);

答案 3 :(得分:0)

我可能会这样做:

  • 为每个字符串语法和注释语法查找正则表达式。
  • 将它们组合以匹配字符串语法,注释语法或“其他任何内容”。
  • 对每个“其他任何”部分执行替换。

以下是一个示例实现:

$escapeSequence = "(?:\\\\[0'\"bnrtZ\\\\%_])";
$singleQuoted   = "'(?:[^'\\\\]|{$escapeSequence}|'')*'";
$doubleQuoted   = "\"(?:[^\"\\\\]|{$escapeSequence}|\"\")*\"";
$string         = "(?:{$singleQuoted}|{$doubleQuoted})";

$lineEndComment   = "(?:#[^\\r\\n]*|--\\s[^\\r\\n]*)";
$multiLineComment = "(?:\/\\*(?:.|[\\r\\n])*?\\*\/)";
$comment          = "(?:{$lineEndComment}|{$multiLineComment})";

$pattern = "/({$string}|{$comment})/";

$parts = preg_split($pattern, $query, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i=0, $n=count($parts)-1; $i<$n; $i+=2) {
    $part = $parts[$i]; // non-string, non-comment part
    // perform replacement of ?
}
$query = implode('', $parts);

模式可能不完整,但它应该足以让我们明白。