2

序章

最初の私の一般的な問題は、文字列内の疑問符を文字列で置き換えたいということですが、それらが引用されていない場合のみです。そこで、SO (リンク)で同様の回答を見つけ、コードのテストを開始しました。残念ながら、もちろん、コードはエスケープされた引用符を考慮していません。

例えば:$string = 'hello="is it me your are looking for\\"?" AND test=?';

その回答から正規表現とコードを質問に適合させました: How to replace words outside double and single quotes、私の質問を読みやすくするためにここに再現されています:

<?php
function str_replace_outside_quotes($replace,$with,$string){
    $result = "";
    $outside = preg_split('/("[^"]*"|\'[^\']*\')/',$string,-1,PREG_SPLIT_DELIM_CAPTURE);
    while ($outside)
        $result .= str_replace($replace,$with,array_shift($outside)).array_shift($outside);
    return $result;
}
?>

実際の問題

"そのため、引用符ではないものとエスケープされた引用符に一致するようにパターンを調整しようとしました\":

<?php
$pattern = '/("(\\"|[^"])*"' . '|' . "'[^']*')/";

// when parsed/echoed by PHP the pattern evaluates to
// /("(\"|[^"])*"|'[^']*')/
?>

しかし、これは私が望んでいたようには機能しません。

私のテスト文字列は次のとおりです。hello="is it me your are looking for\"?" AND test=?

そして、私は次の一致を得ています:

array
  0 => string 'hello=' (length=6)
  1 => string '"is it me your are looking for\"?"' (length=34)
  2 => string '?' (length=1)
  3 => string ' AND test=?' (length=11)

一致インデックス 2 は存在しないはずです。このクエスチョン マークは、一致インデックス 1 の一部としてのみ考慮し、個別に繰り返さないでください。

この同じ修正が解決されたら、単一引用符/アポストロフィのメインの代替の反対側も修正する必要があります'

これが完全な関数によって解析された後、次のように出力されます。

echo str_replace_outside_quotes('?', '%s', 'hello="is it me your are looking for\\"?" AND test=?');
// hello="is it me your are looking for\"?" AND test=%s

これが理にかなっていることを願っており、質問に答えるのに十分な情報を提供しました. そうでない場合は、必要なものを喜んで提供します。

コードをデバッグする

私の現在の(完全な)コードサンプルは、フォーク用のコードパッドにもあります

function str_replace_outside_quotes($replace, $with, $string){
    $result = '';
    var_dump($string);
    $pattern = '/("(\\"|[^"])*"' . '|' . "'[^']*')/";
    var_dump($pattern);
    $outside = preg_split($pattern, $string, -1, PREG_SPLIT_DELIM_CAPTURE);
    var_dump($outside);
    while ($outside) {
        $result .= str_replace($replace, $with, array_shift($outside)) . array_shift($outside);
    }
    return $result;
}
echo str_replace_outside_quotes('?', '%s', 'hello="is it me your are looking for\\"?" AND test=?');

サンプル入力と期待される出力

In: hello="is it me your are looking for\\"?" AND test=? AND hello='is it me your are looking for\\'?' AND test=? hello="is it me your are looking for\\"?" AND test=?' AND hello='is it me your are looking for\\'?' AND test=?
Out: hello="is it me your are looking for\\"?" AND test=%s AND hello='is it me your are looking for\\'?' AND test=%s hello="is it me your are looking for\\"?" AND test=%s AND hello='is it me your are looking for\\'?' AND test=%s

In: my_var = ? AND var_test = "phoned?" AND story = 'he said \'where is it?!?\''
Out: my_var = %s AND var_test = "phoned?" AND story = 'he said \'where is it?!?\''
4

5 に答える 5

3

次のテスト済みスクリプトは、最初に、一重引用符、二重引用符、および引用符なしのチャンクのみで構成される特定の文字列が有効であることを確認します。$re_valid正規表現は、この検証タスクを実行します。文字列が有効な場合はpreg_replace_callback()$re_parse正規表現を使用して文字列を一度に 1 チャンクずつ解析します。コールバック関数は、引用されていないチャンクを を使用して処理し、preg_replace()すべての引用されたチャンクを変更せずに返します。ロジックの唯一のトリッキーな部分は、メイン関数からコールバック関数に$replaceおよび引数の値を渡すことです。$with(PHP の手続き型コードでは、この変数がメイン関数からコールバック関数に渡されるのが少し厄介なことに注意してください。) スクリプトは次のとおりです。

<?php // test.php Rev:20121113_1500
function str_replace_outside_quotes($replace, $with, $string){
    $re_valid = '/
        # Validate string having embedded quoted substrings.
        ^                           # Anchor to start of string.
        (?:                         # Zero or more string chunks.
          "[^"\\\\]*(?:\\\\.[^"\\\\]*)*"  # Either a double quoted chunk,
        | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'  # or a single quoted chunk,
        | [^\'"\\\\]+               # or an unquoted chunk (no escapes).
        )*                          # Zero or more string chunks.
        \z                          # Anchor to end of string.
        /sx';
    if (!preg_match($re_valid, $string)) // Exit if string is invalid.
        exit("Error! String not valid.");
    $re_parse = '/
        # Match one chunk of a valid string having embedded quoted substrings.
          (                         # Either $1: Quoted chunk.
            "[^"\\\\]*(?:\\\\.[^"\\\\]*)*"  # Either a double quoted chunk,
          | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'  # or a single quoted chunk.
          )                         # End $1: Quoted chunk.
        | ([^\'"\\\\]+)             # or $2: an unquoted chunk (no escapes).
        /sx';
    _cb(null, $replace, $with); // Pass args to callback func.
    return preg_replace_callback($re_parse, '_cb', $string);
}
function _cb($matches, $replace = null, $with = null) {
    // Only set local static vars on first call.
    static $_replace, $_with;
    if (!isset($matches)) { 
        $_replace = $replace;
        $_with = $with;
        return; // First call is done.
    }
    // Return quoted string chunks (in group $1) unaltered.
    if ($matches[1]) return $matches[1];
    // Process only unquoted chunks (in group $2).
    return preg_replace('/'. preg_quote($_replace, '/') .'/',
        $_with, $matches[2]);
}
$data = file_get_contents('testdata.txt');
$output = str_replace_outside_quotes('?', '%s', $data);
file_put_contents('testdata_out.txt', $output);
?>
于 2012-11-13T23:31:52.273 に答える
2

この正規表現は、引用符で囲まれた有効な文字列と一致します。これは、エスケープされた引用符を認識していることを意味します。

^("[^\"\\]*(?:\\.[^\"\\]*)*(?![^\\]\\)")|('[^\'\\]*(?:\\.[^\'\\]*)*(?![^\\]\\)')$

PHP を使用する準備ができました。

$pattern = '/^((?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*(?![^\\\\]\\\\))")|(?:\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(?![^\\\\]\\\\))\'))$/';

適応str_replace_outside_quotes()

$pattern = '/((?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*(?![^\\\\]\\\\))")|(?:\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(?![^\\\\]\\\\))\'))/';
于 2012-11-13T12:42:41.197 に答える
1

» コメントで寄せられたすべての問題を解決するためにコードが更新され、適切に動作するようになりました «


$s入力、$pフレーズ文字列、および$v置換変数があれば、次のようにpreg_replaceを使用します。

$r = '/\G((?:(?:[^\x5C"\']|\x5C(?!["\'])|\x5C["\'])*?(?:\'(?:[^\x5C\']|\x5C(?!\')' .
     '|\x5C\')*\')*(?:"(?:[^\x5C"]|\x5C(?!")|\x5C")*")*)*?)' . preg_quote($p) . '/';
$s = preg_match($r, $s) ? preg_replace($r, "$1" . $v, $s) : $s;

このデモを確認してください。


注: 正規表現で\x5Cは、\文字を表します。

于 2012-11-13T17:54:09.730 に答える
-1

編集、回答を変更しました。正規表現では機能しません (現在の正規表現のみ - str_replace の代わりに preg_replace を使用する方が良いと思いましたが、変更できます)):

function replace_special($what, $with, $str) {
   $res = '';
   $currPos = 0;
   $doWork = true;

   while (true) {
     $doWork = false; //pesimistic approach

     $pos = get_quote_pos($str, $currPos, $quoteType);
     if ($pos !== false) {
       $posEnd = get_specific_quote_pos($str, $quoteType, $pos + 1);
       if ($posEnd !== false) {
           $doWork = $posEnd !== strlen($str) - 1; //do not break if not end of string reached

           $res .= preg_replace($what, $with, 
                                substr($str, $currPos, $pos - $currPos));
           $res .= substr($str, $pos, $posEnd - $pos + 1);                      

           $currPos = $posEnd + 1;
       }
     }

     if (!$doWork) {
        $res .= preg_replace($what, $with, 
                             substr($str, $currPos, strlen($str) - $currPos + 1));
        break;
     }

   }   

   return $res;
}

function get_quote_pos($str, $currPos, &$type) {
   $pos1 = get_specific_quote_pos($str, '"', $currPos);
   $pos2 = get_specific_quote_pos($str, "'", $currPos);
   if ($pos1 !== false) {
      if ($pos2 !== false && $pos1 > $pos2) {
        $type = "'";
        return $pos2;
      }
      $type = '"';
      return $pos1;
   }
   else if ($pos2 !== false) {
      $type = "'";
      return $pos2;
   }

   return false;
}

function get_specific_quote_pos($str, $type, $currPos) {
   $pos = $currPos - 1; //because $fromPos = $pos + 1 and initial $fromPos must be currPos
   do {
     $fromPos = $pos + 1;
     $pos = strpos($str, $type, $fromPos);
   }
   //iterate again if quote is escaped!
   while ($pos !== false && $pos > $currPos && $str[$pos-1] == '\\');
   return $pos;
}

例:

   $str = 'hello ? ="is it me your are looking for\\"?" AND mist="???" WHERE test=? AND dzo=?';
   echo replace_special('/\?/', '#', $str);

戻り値

hello # ="探しているのは私ですか?" AND mist="???" WHERE test=# AND dzo=#

----

--古い回答(完全な質問ではありませんが、何かを解決するので、ここに住んでいます)

<?php
function str_replace_outside_quotes($replace, $with, $string){
    $result = '';
    var_dump($string);
    $pattern = '/(?<!\\\\)"/';
    $outside = preg_split($pattern, $string, -1, PREG_SPLIT_DELIM_CAPTURE);
   var_dump($outside);
    for ($i = 0; $i < count($outside); ++$i) {
       $replaced = str_replace($replace, $with, $outside[$i]);
       if ($i != 0 && $i != count($outside) - 1) { //first and last are not inside quote
          $replaced = '"'.$replaced.'"';
       }
       $result .= $replaced;
    }
   return $result;
}
echo str_replace_outside_quotes('?', '%s', 'hello="is it me your are looking for\\"?" AND test=?');
于 2012-11-13T13:29:50.250 に答える
-1

@ridgerunner が質問のコメントで言及しているように、別の可能な正規表現ソリューションがあります。

function str_replace_outside_quotes($replace, $with, $string){
    $result = '';
    $pattern = '/("[^"\\\\]*(?:\\\\.[^"\\\\]*)*")' // hunt down unescaped double quotes
             . "|('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')/s"; // or single quotes
    $outside = array_filter(preg_split($pattern, $string, -1, PREG_SPLIT_DELIM_CAPTURE));
    while ($outside) {
        $result .= str_replace($replace, $with, array_shift($outside)) // outside quotes
                .  array_shift($outside); // inside quotes
    }
    return $result;
}

array_filterを使用して正規表現から返されたいくつかの一致を削除し、この関数の交互の性質を壊していることに注意してください。


私がすぐに思いついたノー正規表現アプローチ。それは機能しますが、実行できる最適化がいくつかあると確信しています。

function str_replace_outside_quotes($replace, $with, $string){
    $string = str_split($string);
    $accumulation = '';
    $current_unquoted_string = null;
    $inside_quote = false;
    $quotes = array("'", '"');
    foreach($string as $char) {
        if ($char == $inside_quote && "\\" != substr($accumulation, -1)) {
            $inside_quote = false;
        } else if(false === $inside_quote && in_array($char, $quotes)) {
            $inside_quote = $char;
        }

        if(false === $inside_quote) {
            $current_unquoted_string .= $char;
        } else {
            if(null !== $current_unquoted_string) {
                $accumulation .= str_replace($replace, $with, $current_unquoted_string);
                $current_unquoted_string = null;
            }
            $accumulation .= $char;
        }
    }
    if(null !== $current_unquoted_string) {
        $accumulation .= str_replace($replace, $with, $current_unquoted_string);
        $current_unquoted_string = null;
    }
    return $accumulation;
}

私のベンチマークでは、上記の正規表現アプローチの 2 倍の時間がかかり、文字列の長さが増加しても、正規表現オプションのリソース使用量はそれほど増加しません。一方、上記のアプローチは、それに供給されるテキストの長さに比例して増加します。

于 2012-11-13T17:19:45.913 に答える