Pertanyaan preg_replace ketika tidak ada di dalam tanda kutip ganda


Pada dasarnya saya ingin mengganti kata-kata tertentu (misalnya kata "pohon" dengan kata "pizza") dalam kalimat. Pembatasan: Bila kata yang harus diganti adalah antara tanda kutip ganda, penggantian tidak harus dilakukan.

Contoh:

The tree is green. -> REPLACE tree WITH pizza
"The" tree is "green". -> REPLACE tree WITH pizza
"The tree" is green. -> DONT REPLACE
"The tree is" green. -> DONT REPLACE
The ""tree is green. -> REPLACE tree WITH pizza

Mungkinkah melakukan ini dengan ekspresi reguler? Saya akan menghitung jumlah tanda kutip ganda sebelum kata dan memeriksa apakah itu ganjil atau genap. Tetapi apakah ini mungkin menggunakan preg_replace di php?

Terima kasih!

// EDIT:

Saat ini kode saya terlihat seperti ini:

preg_replace("/tree/", "pizza", $sentence)

Tapi masalahnya di sini adalah menerapkan logika dengan tanda kutip ganda. Saya mencoba hal-hal seperti:

preg_replace("/[^"]tree/", "pizza", $sentence)

Tetapi ini tidak berhasil, karena hanya memeriksa jika kutipan ganda ada di depan kata. Tetapi ada contoh di atas di mana pemeriksaan ini gagal. Impor adalah bahwa saya ingin menyelesaikan masalah itu dengan regex saja.


5
2017-12-24 21:48


asal


Jawaban:


Ekspresi reguler bukanlah alat yang akan melakukan apa yang Anda butuhkan untuk setiap pekerjaan. Anda dapat menggunakan ekspresi reguler untuk ini sampai batas tertentu, tetapi untuk semua kasus di antaranya kutipan bertingkat, itu terus menjadi lebih rumit.

Anda bisa menggunakan Lookahead Negatif sini.

$text = preg_replace('/\btree\b(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/i', 'pizza', $text);

Lihat Working demo

Ekspresi reguler:

\b               the boundary between a word char (\w) and not a word char
 tree            'tree'
\b               the boundary between a word char (\w) and not a word char
(?!              look ahead to see if there is not:
 [^"]*           any character except: '"' (0 or more times)
  "              '"'
 (?:             group, but do not capture (0 or more times)
  (?:            group, but do not capture (2 times):
   [^"]*         any character except: '"' (0 or more times)
    "            '"'
  ){2}           end of grouping
 )*              end of grouping
 [^"]*           any character except: '"' (0 or more times)
 $               before an optional \n, and the end of the string
)                end of look-ahead

Pilihan lain adalah menggunakan backtracking terkendali karena Anda dapat melakukan hal ini

$text = preg_replace('/"[^"]*"(*SKIP)(*FAIL)|\btree\b/i', 'pizza', $text);

Lihat Working demo

Idenya adalah untuk melewatkan konten dalam kutipan. Saya pertama kali cocok dengan kutipan yang diikuti oleh karakter apa pun kecuali " diikuti dengan kutipan dan kemudian membuat subpattern gagal dan memaksa mesin ekspresi reguler untuk tidak mencoba kembali substring dengan alternatif lain dengan (*SKIP) dan (*FAIL) kata kerja kontrol mundur.


6
2017-12-24 21:56



Ada trik praktis menggunakan beberapa kekuatan regex tersembunyi:

~".*?"(*SKIP)(*FAIL)|\btree\b~s

Penjelasan:

~                   # start delimiter (we could have used /, #, @ etc...)
"                   # match a double quote
.*?                 # match anything ungreedy until ...
"                   # match a double quote
(*SKIP)(*FAIL)      # make it fail
|                   # or
\btree\b            # match a tree with wordboundaries
~                   # end delimiter
s                   # setting the s modifier to match newlines with dots .

Dalam kode PHP yang sebenarnya, Anda ingin menggunakannya preg_quote() untuk melepaskan karakter regex. Ini sedikit cuplikan:

$search = 'tree';
$replace = 'plant';
$input = 'The tree is green.
"The" tree is "green".
"The tree" is green.
"The tree is" green.
The ""tree is green.';

$regex = '~".*?"(*SKIP)(*FAIL)|\b' . preg_quote($search, '~') . '\b~s';
$output = preg_replace($regex, $replace, $input);
echo $output;

Demo regex onlineDemo PHP online


3
2017-12-24 23:10



Yang ini cocok tree menggunakan lookahead:

$pattern = '~\btree\b(?=([^"]|("[^"]*"))*$)~im';

$str = '
The tree is green. -> REPLACE tree WITH pizza
"The" tree is "green". -> REPLACE tree WITH pizza
"The tree" is green. -> DONT REPLACE
"The tree is" green. -> DONT REPLACE
The ""tree is green. -> REPLACE tree WITH pizza';

echo "<pre>".preg_replace($pattern,"pizza",$str)."</pre>";

Itu yang dicari tree, jika ditemukan, hanya cocok, jika diikuti oleh karakter, yang bukan tanda kutip ganda [^"] atau kelompok yang dikutip "[^"]*" sampai akhir baris menggunakan pengubah i (PCRE_CASELESS) dan m (PCRE_MULTILINE).

Saya tidak ingin pizza hijau! Selamat Hari Natal :-)


1
2017-12-24 23:24



gunakan pola ini tree(?=(?:(?:[^"]*"){2})*[^"]*$) dengan gm pilihan Demo 

ini adalah bagaimana itu dibangun dari bawah ke atas:
tree(?=[^"]*")  "pohon" yang melihat sejumlah karakter non-kutipan diikuti dengan kutipan
tree(?=([^"]*"){2})  ~ dua kali
tree(?=(([^"]*"){2})*)  ~ sebanyak mungkin
tree(?=(([^"]*"){2})*[^"]*)  ~ maka karakter non-kutipan opsional
tree(?=(([^"]*"){2})*[^"]*$)  ~ sampai akhir
tree(?=(?:(?:[^"]*"){2})*[^"]*$)   tambahkan grup yang tidak menangkap

demo php


0
2017-12-24 21:57



Saya sedang membangun JS minimizer dan halaman ini banyak membantu saya mendapatkan ekspresi reguler yang tepat untuk itu. Tapi apa halaman ini belum menjawab sofar adalah apa yang harus dilakukan ketika string yang dikutip berisi kutipan yang lolos. Saya menandai halaman ini ketika saya menemukan resepnya.

/*
Regular expression group 'NotBetween'.
*/
function rgxgNotBetween($chars, $sep="|")
{
    $chars = explode($sep, $chars);

    $NB = [];

    foreach($chars as $CHR){
        //(*PRUNE) steps over $CHR when it is escaped; that is, preceded by a backslash.
        $NB[] = "(?:$CHR(?:\\\\$CHR(*PRUNE)|.)*?$CHR)";
    }

    $NB = join("|", $NB);

    return "(?:(?:$NB)(*SKIP)(*FAIL))";
}

function jsIdReplace($search, $replace, $source)
{
    $search = ""

    //SKIP further matching when between...
    //double or single qoutes or js regular expression slashes
    .rgxgNotBetween("\x22|\x27|\/")

    //match when NO preceding '.' and no ending ':' (object properties)
    ."|(?:(?<!\.)\b$search\b(?!:))"

    //but do match when preceding '?' or ':' AND ending ':' (ternary statements)
    ."|(?:(?<=\?|:)\b$search\b(?=:))";

    return preg_replace($search, $replace, $source);
}

function jsNoComments($source)
{
    //js comment markers NOT between quotes
    $NBQ = rgxgNotBetween("\x22|\x27");

    //block comments
    $source = preg_replace("#$NBQ|/\*.*?\*/#s", "", $source);

    //line comments; not preceded by backslash
    $source = preg_replace("#$NBQ|\h*(?<!\\\\)//.*\n?#", "", $source);

    return $source;
}

0
2017-12-10 13:01