2013-08-26 3 views
1

나는 거대한 카탈로그 제품 설명의 일부 SEO preg_replace_callback을 사용하고 정규 표현식과 함께 몇 가지 어려움이 있습니다.부정적인 lookbehind와 PHP의 정규식

"men 's"뒤에 +0-2 단어 사이의 단어를 제외하고이 단어 (모자, 셔츠)를 모두 바꾸고 싶습니다. "남자의 예쁜 검은 모자", "남자의 긴 셔츠"는 교체하면 안됩니다.

$str = "men's black hat, and orange shirt!"; 
preg_match_all('/((\s|\.\s|,\s|\!\s|\?\s)(hat|shirt)(\s|\.|\.\s|,\s|\!|\!\s|\?|\?\s))/i', $str, &$_matches); 
print_r($_matches); 

감사

+0

질문을 명확히 해 주시겠습니까? 당신은 대체에 대해서 말했지만'preg_match_all'을 사용하고 있습니다. 또한 언급 한 디버그 코드에서 기대하는 결과는 무엇입니까? – Jerry

답변

0

Lookbehind는 고정 길이 여야하므로 문제를 공격하는 방법은 작동하지 않습니다.

IMHO 너는 preg_relace_callback을 너무 많이 만들려고 노력하고있다. 특정 레벨 이상으로 복잡한 조작을 수행하려는 경우 단일 함수 호출의 편의를 상실하는 것이 좋습니다.

  1. 사용 preg_split 각 단어가 원래의 텍스트로 표시 위치를 알 수 있도록 플래그 PREG_SPLIT_OFFSET_CAPTURE과 함께 단어에 텍스트를 분할 : 여기에 문제를 공격 할 수있는 또 다른 방법입니다.
  2. 단어 배열을 반복합니다. 배열에서 "부정적인 lookbehind"를 수행하고 모자 또는 셔츠 앞에 관심있는 다른 용어가 표시되는지 확인하는 것이 매우 쉽습니다.
  3. 모자 또는 셔츠와 일치하는 것이 발견되면 preg_split의 오프셋과 (일치하는) 일치하는 길이를 사용하여 원본 텍스트 입력에 substr_replace에 전원을 공급하십시오. 예를 들어

:

$str = "men's black hat, and orange shirt!"; 
$targets = array('hat', 'shirt'); 
$shield = 'men\'s'; 
$bias = 0; 

for ($i = 0; $i < count($words); ++$i) { 
    list ($word, $offset) = $words[$i]; 

    if (!in_array($word, $targets)) { 
     continue; 
    } 

    for ($j = max($i - 2, 0); $j < $i; ++$j) { 
     if ($words[$j][0] === $shield) { 
      continue 2; 
     } 
    } 

    $replacement = 'FOO'; 
    $str = substr_replace($str, $replacement, $offset + $bias, strlen($word)); 
    $bias += strlen($replacement) - strlen($word); 
} 

echo $str; 

See it in action.

+0

preg_split() 및 추가 처리에 대해 매우 감사드립니다. 그냥 작동합니다. 고마워, 친구! – c0rewell

+1

유일한 것은 "FOO"가 원래'$ word'와 길이가 같지 않다면 첫 번째 iteration => substr_replace가 원래 텍스트를 뒤집은 후에'$ offset'이 올바르지 않다는 것입니다. :) $ offsetCorrection + = (strlen ($ replacement) - strlen ($ word))' – c0rewell

+0

@ c0rewell : 그래, 그건 분명히 버그 야. 피드백을 주셔서 감사합니다, 그에 따라 답변을 수정. – Jon

0

을 내가 가변 길이 부정적인 lookbehinds이 가능하다고 생각하지 않습니다 여기에

실제 응용 프로그램에서 나는 각 단어에 대한 적절한 교체를 선택하는 콜백을 사용하여 디버그 코드입니다.

트릭은 문자열을 바꾸어 부정적인 미리보기를 사용하는 것입니다. 당신이 할 위치 그래서, "이상적"하고 싶은 :

preg_match_all('/(?<!\bmen\'s\s+(\w+\s+){0,2})(hat|shirt)\b/i', $str, &$_matches); 

당신이

preg_match_all('/\b(tah|trihs)(?!(\s+\w+){0,2}\s+s\'nem\b)/i', strrev($str), $rev_matches); 

을하고 다시 모든 결과를 반대로 array_map를 사용할 수 있습니다.

그런데 \b워드 경계으로 알려져 있습니다. 아마 (\s|\.|\.\s|,\s|\!|\!\s|\?|\?\s) 대신에 무엇을 사용 하시겠습니까?

+0

가변 길이 lookbehind를 에뮬레이트하는 또 다른 방법은'\ K'를 사용하는 것입니다. '\ K'를 만나기 전에 일치하는 것은 버려진/"잊혀진"것입니다. 이것은 당신이 무엇이든 일치시킬 수있게 해주고'\ K' 다음에 캡쳐를 시작합니다. [PCRE 맨 페이지] (http://pcre.org/pcre.txt)의 중간에서 "경기 시작 재설정"을 검색하십시오. – Wiseguy

+0

좋은 아이디어지만, 너무 까다로운 것, 감사합니다 – c0rewell

+0

괜찮습니다.나는 \ @ 존스의 솔루션이 더 좋고 나 자신도 좋고 @ Wiseguy의'\ K' 구조에 대해서도 몰랐다. –

관련 문제