Замена вхождений определенного слова, если оно не продолжается другим конкретным словом?

У меня есть текст типа:

*open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah *close*

Мне было интересно, как я могу удалить/заменить любые вхождения *close*, которые не обрабатываются *open*.

Таким образом, приведенный выше текст будет выглядеть так:

*open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah

Я думал об использовании регулярных выражений с preg_replace, но мои навыки регулярных выражений не так сильны?

<?php
$string = "*open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah *close*";

$string = preg_replace('#(?<!\*open\*)\*close\*#', '', $string); //this only works for immediate proceedings

echo($string);
?>

Приветствуются примеры кода.


person user962026    schedule 24.05.2013    source источник
comment
См. Отрицательный просмотр назад. После прочтения этого, если вы не можете заставить его работать, опубликуйте свой код.   -  person Barmar    schedule 24.05.2013
comment
Ни одному из ваших вхождений close не предшествует open, им всем предшествует blah. Почему их всех не убрать?   -  person Barmar    schedule 24.05.2013
comment
@Barmar Я добавил свой код, но он работает только для немедленных действий, что не всегда так (вероятно, между ними будет какой-то текст - это то, что я пытаюсь интегрировать в регулярное выражение).   -  person user962026    schedule 24.05.2013
comment
Как я уже сказал, ваш вопрос не очень ясен. Если вы не имеете в виду непосредственно предшествующий, то ВСЕ вхождения close предшествуют open. Я думаю, вы на самом деле имеете в виду, что когда между двумя вхождениями close нет открытия, вы хотите удалить второе, не так ли?   -  person Barmar    schedule 24.05.2013
comment
@Barmar Да, это правильно.   -  person user962026    schedule 24.05.2013
comment
@Barmar Я бы сказал, что они ищут непревзойденные теги ... если вы рассматриваете * open * и * close * как теги.   -  person AbsoluteƵERØ    schedule 24.05.2013
comment
К сожалению, регулярные выражения очень плохо подходят для поиска совпадающих/несоответствующих элементов.   -  person Barmar    schedule 24.05.2013
comment
Вы должны токенизировать ввод вместо использования регулярных выражений. См. php.net/manual/en/function.strtok.php для отправная точка.   -  person leftclickben    schedule 24.05.2013
comment
Потенциально (я предполагаю) вы можете столкнуться с проблемой с вложенными элементами \*open\* blah \*open\* blah blah \*close\* blah \*close\*.   -  person AbsoluteƵERØ    schedule 24.05.2013


Ответы (2)


Это можно сделать без регулярного выражения с помощью следующего кода:

$openTag = '*open*';
$closeTag = '*close*';
$openTagLength = mb_strlen($openTag);
$closeTagLength = mb_strlen($closeTag);

$subj = '*open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah *close*';
$len = mb_strlen($subj);
$isOpened = false;
$res = '';
for ($i = 0; $i < $len; )
{
    if (mb_substr($subj, $i, $openTagLength) === $openTag) {
        // found open tag
        $res .= $openTag;
        $isOpened = true;
        $i += $openTagLength;
    } elseif (mb_substr($subj, $i, $closeTagLength) === $closeTag) {
        // found close tag
        if ($isOpened) {
            $res .= $closeTag;
        } // else skip
        $isOpened = false;
        $i += $closeTagLength;
    } else {
        // non-tag
        $res .= mb_substr($subj, $i, 1);
        $i++;
    }
}
echo $res;
person Alex Boyko    schedule 24.05.2013

Попробуйте это:

    $pattern = "/(\\*open\\*.*?\\*close\\*)/";
    $target = "*close* *close* *open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah *close* *close* *open* *open* *close* ";

    $prevMatchEndIndex = 0;
    $matches = array();
    $lastMatchEndIndex = 0;
    $resultParts = array();
    while(preg_match($pattern, $target, $matches, PREG_OFFSET_CAPTURE, $prevMatchEndIndex)) {
        $matchedString = $matches[0][0];
        $matchStartIndex = $matches[0][1];
        $matchEndIndex = $matchStartIndex + strlen($matchedString) + 1;

        $unmatchedString = substr($target, $prevMatchEndIndex, $matchStartIndex - $prevMatchEndIndex);

        $unmatchedString = preg_replace("/\\s*\\*close\\*\\s*/", " ", $unmatchedString);

        $resultParts[] = trim($unmatchedString);
        $resultParts[] = trim($matchedString);

        $prevMatchEndIndex = $matchEndIndex;
        $lastMatchEndIndex = $matchEndIndex;
    }

    $lastUnmatchedPart = substr($target, $lastMatchEndIndex);
    $lastUnmatchedPart = preg_replace("/\\s*\\*close\\*\\s*/", " ", $lastUnmatchedPart);
    $resultParts[] = $lastUnmatchedPart;    

    echo $target . "<br />";
    echo join($resultParts, " ");
person Bhashit Parikh    schedule 24.05.2013
comment
В этом примере не удаляется последнее несопоставленное закрытие. Вывод из предоставленной строки OP: *open* blah blah blah blah blah *close* blah blah *open* blah blah *close* blah blah *close* - person AbsoluteƵERØ; 24.05.2013
comment
&$matches выдает Call-time pass-by-reference has been removed фатальную ошибку в последней версии PHP. Забыл упомянуть раньше. Если вы удалите &, он отлично работает. - person AbsoluteƵERØ; 24.05.2013
comment
@AbsoluteƵERØ: Опять верно. Спасибо. Я прекратил разработку на PHP уже более двух лет. Хотя это не оправдание. Не мог игнорировать вопрос, поскольку в основном речь шла о регулярном выражении, решенном на Java, портированном на PHP с использованием онлайн-руководства по PHP. - person Bhashit Parikh; 24.05.2013