сохранить HTML-формат после замены некоторого текста (используя PHP и JS) - PullRequest
15 голосов
/ 02 апреля 2010

Я хотел бы изменить HTML как

I am <b>Sadi, novice</b> programmer.

до

I am <b>Sadi, learner</b> programmer.

Для этого я буду искать, используя строку " начинающий программист ". Как я могу сделать это, пожалуйста? Есть идеи?

Поиск с использованием более одного слова "начинающий программист". Это может быть целое предложение. Лишний пробел (например, новая строка, табуляция) должен игнорироваться, а любой тег должен игнорироваться во время поиска. Но при замене тег должен быть сохранен.

Это своего рода конвертер. Будет лучше, если она будет без учета регистра .

Спасибо

Сади


Дополнительные уточнения:

Я получаю хороший ответ с возможным решением. Но, пожалуйста, продолжайте писать, если у вас есть идеи.

Я бы хотел больше прояснить проблему на случай, если кто-то пропустит ее. Основной пост показывает проблему в качестве примера сценария.

1) Теперь проблема в найти и заменить строку без учета тегов . Теги могут отображаться в одном слове. Строка может содержать несколько слов. Тег появляется только в строке содержимого или в документе . Фраза поиска никогда не содержит тегов .

Мы можем легко удалить все теги и выполнить некоторые текстовые операции. Но здесь появляется другая проблема.

2) Теги должны быть сохранены даже после замены текста. Вот что показывает пример.

Еще раз спасибо за помощь

Ответы [ 6 ]

4 голосов
/ 18 апреля 2010

хорошо, я думаю, это то, что вы хотите. он берет ваш входной поиск и замену, разбивает их на массивы строк, разделенных пробелом, генерирует регулярное выражение, которое находит входное предложение с любым количеством пробельных / html-тегов, и заменяет его замещающим предложением с теми же тегами, заменяемыми между словами .

если количество слов в поисковом предложении выше, чем в слове замены, оно просто использует пробелы между любыми дополнительными словами, а если количество слов замены выше, чем поиск, то в конце будут добавлены все «потерянные» теги. он также обрабатывает регулярные выражения в поиске и замене.

<?php
function htmlFriendlySearchAndReplace($find, $replace, $subject) {
    $findWords = explode(" ", $find);
    $replaceWords = explode(" ", $replace);

    $findRegexp = "/";
    for ($i = 0; $i < count($findWords); $i++) {
        $findRegexp .= preg_replace("/([\\$\\^\\|\\.\\+\\*\\?\\(\\)\\[\\]\\{\\}\\\\\\-])/", "\\\\$1", $findWords[$i]);
        if ($i < count($findWords) - 1) {
            $findRegexp .= "(\s?(?:<[^>]*>)?\s(?:<[^>]*>)?)";
        }
    }
    $findRegexp .= "/i";

    $replaceRegexp = "";
    for ($i = 0; $i < count($findWords) || $i < count($replaceWords); $i++) {
        if ($i < count($replaceWords)) {
            $replaceRegexp .= str_replace("$", "\\$", $replaceWords[$i]);
        }
        if ($i < count($findWords) - 1) {
            $replaceRegexp .= "$" . ($i + 1);
        } else {
            if ($i < count($replaceWords) - 1) {
                $replaceRegexp .= " ";
            }
        }
    }

    return preg_replace($findRegexp, $replaceRegexp, $subject);
}
?>

вот результаты нескольких тестов:

Original : <b>Novice Programmer</b>
Search : Novice Programmer
Replace : Advanced Programmer
Result : <b>Advanced Programmer</b>

Original : Hi, <b>Novice Programmer</b>
Search : Novice Programmer
Replace : Advanced Programmer
Result : Hi, <b>Advanced Programmer</b>

Original : I am not a <b>Novice</b> Programmer
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b>Advanced</b> Programmer

Original : Novice <b>Programmer</b> in the house
Search : Novice Programmer
Replace : Advanced Programmer
Result : Advanced <b>Programmer</b> in the house

Original : <i>I am not a <b>Novice</b> Programmer</i>
Search : Novice Programmer
Replace : Advanced Programmer
Result : <i>I am not a <b>Advanced</b> Programmer</i>

Original : I am not a <b><i>Novice</i> Programmer</b> any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b><i>Advanced</i> Programmer</b> any more

Original : I am not a <b><i>Novice</i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a <b><i>Advanced</i></b> Programmer any more

Original : I am not a Novice<b> <i> </i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a Advanced<b> <i> </i></b> Programmer any more

Original : I am not a Novice <b><i> </i></b> Programmer any more
Search : Novice Programmer
Replace : Advanced Programmer
Result : I am not a Advanced <b><i> </i></b> Programmer any more

Original : <i>I am a <b>Novice</b> Programmer</i> too, now
Search : Novice Programmer too
Replace : Advanced Programmer
Result : <i>I am a <b>Advanced</b> Programmer</i> , now

Original : <i>I am a <b>Novice</b> Programmer</i>, now
Search : Novice Programmer
Replace : Advanced Programmer Too
Result : <i>I am a <b>Advanced</b> Programmer Too</i>, now

Original : <i>I make <b>No money</b>, now</i>
Search : No money
Replace : Mucho$1 Dollar$
Result : <i>I make <b>Mucho$1 Dollar$</b>, now</i>

Original : <i>I like regexp, you can do [A-Z]</i>
Search : [A-Z]
Replace : [Z-A]
Result : <i>I like regexp, you can do [Z-A]</i>
3 голосов
/ 02 апреля 2010

Я бы сделал это:

if (preg_match('/(.*)novice((?:<.*>)?\s(?:<.*>)?programmer.*)/',$inString,$attributes) {
  $inString = $attributes[1].'learner'.$attributes[2];
}

Должно соответствовать любому из следующего:

novice programmer
novice</b> programmer
novice </b>programmer
novice<span> programmer

Тестовая версия состояний регулярных выражений будет выглядеть примерно так: сопоставляйте любой набор символов, пока не достигнете «новичка», и не поместите его в группу захвата, а затем, возможно, сопоставьте что-то, начинающееся с «<» и имеющее любое число символов после него и затем оканчивается на «>» (но не перехватывать его), но тогда сопоставляется только что-то с пробелом, а затем, возможно, снова сопоставляется что-то, начинающееся с «<» и имеющее любое количество символов после он заканчивается символом «>» (но не фиксирует его), за которым должен следовать программист, затем любое количество символов и помещать его в группу захвата.

Я бы сделал какое-то конкретное тестирование, поскольку я, возможно, пропустил некоторые вещи. Regex - лучший друг программистов!

1 голос
/ 02 апреля 2010

Ну, может быть, есть лучший способ, но не в моей голове (при условии, что теги не появятся в середине слов, HTML правильно сформирован и т. Д.) ...

По сути, вам понадобятся три вещи (извините, если это звучит покровительственно, не так): 1. Метод сопоставления подстрок, игнорирующий теги. 2. Способ сделать замену с сохранением тегов. 3. Способ собрать все вместе.

1 - Это, наверное, самый сложный бит. Один из методов заключается в том, чтобы выполнить итерацию по всем символам в исходной строке (строки в основном являются массивами символов, поэтому вы можете обращаться к символам, как если бы они были элементами массива), пытаясь сопоставить как можно больше символов из строки поиска, останавливая когда вы либо сопоставили все символы или исчерпали соответствующие символы. Любые символы между '<' и '> и включая их должны игнорироваться. Некоторый псевдокод (проверьте это, уже поздно и могут быть ошибки):

findMatch(startingPos : integer, subject : string, searchString : string){
    //Variables for keeping track of characters matched, positions, etc.
    inTag = false;
    matchFound = false;
    matchedCharacters = 0;
    matchStart = 0;
    matchEnd = 0;

    for(i from startingPos to length(searchString)){
        //Work out when entering or exiting tags, ignore tag contents
        if(subject[i] == '<' || subject[i] == '>'){
            inTag = !inTag;
        }
        else if(!inTag){
            //Check if the character matches expected in search string
            if(subject[i] == searchString[matchedCharacters]){
                if(!matchFound){
                    matchFound = true;
                    matchStart = i;
                }
                matchedCharacters++;

                //If all of the characters have been matched, return the start and end positions of the substring
                if(matchedCharacters + 1 == length(searchString)){
                    matchEnd = i - matchStart;
                    return matchStart, matchEnd;
                }
            }
            else{
                //Reset counts if not found
                matchFound = false;
                matchCharacters = 0;
            }
        }
    }
    //If no full matches were found, return error
    return -1;
}

2 - разбить исходный код HTML на три строки - бит, над которым вы хотите работать (между двумя позициями, возвращаемыми функцией сопоставления) и часть до и после. Разделите бит, который вы хотите изменить, например:

$parts = preg_split("/(<[^>]*>)/",$string, -1, PREG_SPLIT_DELIM_CAPTURE);

Вести учет того, где находятся теги, объединять сегменты без тегов и выполнять замену подстроки на этом, как обычно, затем снова разбивать измененную строку и снова собирать с установленными тегами.

3 - Это самая простая часть, просто объединить измененную часть и два других бита вместе.

Я мог бы ужасно усложнить этот ум, если это так, просто игнорируй меня.

0 голосов
/ 16 апреля 2010

Поскольку вы не указали точные сведения о том, для чего вы будете использовать это, я буду использовать ваш пример «Я сади, новичок программист».

$before = 'I am <b>sadi, novice</b> programmer';
$after = preg_replace ('/I am (<.*>)?(.*), novice(<.*>)? programmer/','/I am $1$2,     learner$3 programmer/',$string);

В качестве альтернативы для любого текста:

$string = '<b>Hello</b>, world!';
$orig = 'Hello';
$replace = 'Goodbye';
$pattern = "/(<.*>)?$orig(<.*>)?/";
$final = "/$1$replace$2/";
$result = preg_replace($pattern,$final,$string);
//$result should now be 'Goodbye, world!'

Надеюсь, это помогло. : Д

Редактировать: пример вашего примера со вторым фрагментом кода: $ string = 'Я Сади, начинающий Программист.';
$ orig = 'новичок';
$ replace = 'ученик';
$ pattern = "/(<.<em>>)?$orig(<.</em>>)?/";
$ final = "$ 1 $ replace $ 2";
$ result = htmlspecialchars (preg_replace ($ pattern, $ final, $ string));
echo $ result;

Единственная проблема в том, что вы искали что-то, что было длиннее слова.

Редактировать 2: Наконец, придумал способ сделать это через несколько слов. Вот код:

function htmlreplace($string,$orig,$replace) 
 {
  $orig = explode(' ',$orig);
  $replace = explode(' ',$replace);
  $result = $string;
  while (count($orig)>0)
   {
    $shift = array_shift($orig);
    $rshift = array_shift($replace);

    $pattern = "/$shift\s?(<.*>)?/";
    $replacement = "$rshift$1";
    $result = preg_replace($pattern,$replacement,$result);
   }
  $result .= implode(' ',$replace);
  return $result;
 }

Веселись! : Д

0 голосов
/ 02 апреля 2010

Интересная проблема.

Я бы использовал DOM и XPath для поиска ближайших узлов, содержащих этот текст, а затем использовал бы сопоставление подстрок, чтобы выяснить, какой бит строки находится в каком узле. Это будет включать в себя сопоставление символов за символом и возможное возвращение назад, однако.

Вот первая часть, нахождение узлов контейнера:

<?php
error_reporting(E_ALL);
header('Content-Type: text/plain; charset=UTF-8');

$doc = new DOMDocument();
$doc->loadHTML(<<<EOD
<p>
    <span>
        <i>
            I am <b>Sadi, novice</b> programmer.
        </i>
    </span>
</p>
<ul>
    <li>
        <div>
            I am <em>Cornholio, novice</em> programmer of television shows.
        </div>
    </li>
</ul>
EOD
);
$xpath = new DOMXPath($doc);
// First, get a list of all nodes containing the text anywhere in their tree.
$nodeList = $xpath->evaluate('//*[contains(string(.), "programmer")]');
$deepestNodes = array();
// Now only keep the deepest nodes, because the XPath query will also return HTML, BODY, ...
foreach ($nodeList as $node) {
    $deepestNodes[] = $node;
    $ancestor = $node;
    while (($ancestor = $ancestor->parentNode) && ($ancestor instanceof DOMElement)) {
        $deepestNodes = array_filter($deepestNodes, function ($existingNode) use ($ancestor) {
            return ($ancestor !== $existingNode);
        });
    }
}
foreach ($deepestNodes as $node) {
    var_dump($node->tagName);
}

Надеюсь, это поможет вам.

0 голосов
/ 02 апреля 2010

Если cOm уже не написал это, регулярное выражение будет лучшим путем:

$cleaned_string = preg_replace('/\<.\>/', $raw_text, "");

Или что-то в этом роде. Мне нужно изучить / проверить регулярное выражение.

Тогда вы можете просто использовать $foobar = str_replace($find, $replace_with, $cleaned_string);, чтобы найти текст, который вы хотите заменить.

Не осознавал, что хочет вернуть HTML-код. Это все регулярное выражение для этого, и больше, чем я знаю в данный момент.

Зная, что я знаю, по технике я бы, вероятно, использовал выражение, которое не игнорировало бы пробел между словами, но делало между скобками < и >, затем использовал переменные способности вывода регулярных выражений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...