Подтвердить что ты не робот

Искать HTML для 2 фраз (игнорируя все теги) и отбрасывать все остальное

У меня есть html-код, хранящийся в строке, например:

$html = '
        <html>
        <body>
        <p>Hello <em>進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.</p>
        </body>
        </html>
        ';

Тогда у меня есть два предложения, хранящиеся в переменных:

$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

Я хочу найти $html для этих двух предложений и разбить все до и после них. Таким образом, $html станет:

$html = 'Hello <em>進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.';

Как я могу это достичь? Обратите внимание, что переменные $begin и $end не имеют тегов html, но предложения в $html, скорее всего, имеют теги, как показано выше.

Может быть подход регулярного выражения?

То, что я пробовал до сих пор

  • A strpos(). Проблема в том, что $html содержит теги в предложениях, в результате чего предложения $begin и $end не совпадают. Я могу strip_tags($html) перед запуском strpos(), но тогда я, очевидно, получаю $html без тегов.

  • Найдите часть переменной, например Hello, но она никогда не будет безопасной и даст много совпадений.

4b9b3361

Ответ 1

Вот короткое, но верное, рабочее решение, основанное на регулярном регулярном выражении с ленивыми точками (которое можно улучшить, создав более длинное развернутое регулярное выражение, но должно быть достаточно, если у вас действительно большие куски текста).

$html = "<html>\n<body>\n<p><p>H<div>ello</div><script></script> <em>進&nbsp;&nbsp;&nbsp;撃の巨人</em>!</p>\nrandom code\nrandom code\n<p>Lorem <span>ipsum<span>.</p>\n</body>\n </html>";
$begin = 'Hello     進撃の巨人!';
$end = 'Lorem ipsum.';
$begin = preg_replace_callback('~\s++(?!\z)|(\s++\z)~u', function ($m) { return !empty($m[1]) ? '' : ' '; }, $begin);
$end = preg_replace_callback('~\s++(?!\z)|(\s++\z)~u', function ($m) { return !empty($m[1]) ? '' : ' '; }, $end);
$begin_arr = preg_split('~(?=\X)~u', $begin, -1, PREG_SPLIT_NO_EMPTY);
$end_arr = preg_split('~(?=\X)~u', $end, -1, PREG_SPLIT_NO_EMPTY);
$reg = "(?s)(?:<[^<>]+>)?(?:&#?\\w+;)*\\s*" .  implode("", array_map(function($x, $k) use ($begin_arr) { return ($k < count($begin_arr) - 1 ? preg_quote($x, "~") . "(?:\s*(?:<[^<>]+>|&#?\\w+;))*" : preg_quote($x, "~"));}, $begin_arr, array_keys($begin_arr)))
        . "(.*?)" . 
        implode("", array_map(function($x, $k) use ($end_arr) { return ($k < count($end_arr) - 1 ? preg_quote($x, "~") . "(?:\s*(?:<[^<>]+>|&#?\\w+;))*" : preg_quote($x, "~"));}, $end_arr, array_keys($end_arr))); 
echo $reg .PHP_EOL;
preg_match('~' . $reg . '~u', $html, $m);
print_r($m[0]);

Смотрите демонстрацию IDEONE

Алгоритм:

  • Создайте динамический шаблон регулярного выражения, разделив строки разделителя на одиночные графемы (так как это могут быть символы Юникода, я предлагаю использовать preg_split('~(?<!^)(?=\X)~u', $end)) и вставлять обратно обратно, добавляя шаблон соответствия меток (?:<[^<>]+>)?.
  • Затем (?s) включает режим DOTALL, когда . соответствует любому символу, включая новую строку, и .*? будет соответствовать 0 + символам от ведущего к трейлинг-разделителю.

Детали регек:

  • '~(?<!^)(?=\X)~u соответствует любому местоположению, отличному от начала строки перед каждой графемой
  • (примерное окончательное регулярное выражение) (?s)(?:<[^<>]+>)?(?:&#?\w+;)*\s*H(?:\s*(?:<[^<>]+>|&#?\w+;))*e(?:\s*(?:<[^<>]+>|&#?\w+;))*l(?:\s*(?:<[^<>]+>|&#?\w+;))*l(?:\s*(?:<[^<>]+>|&#?\w+;))*o(?:\s*(?:<[^<>]+>|&#?\w+;))* (?:\s*(?:<[^<>]+>|&#?\w+;))*進(?:\s*(?:<[^<>]+>|&#?\w+;))*撃(?:\s*(?:<[^<>]+>|&#?\w+;))*の(?:\s*(?:<[^<>]+>|&#?\w+;))*巨(?:\s*(?:<[^<>]+>|&#?\w+;))*人(?:\s*(?:<[^<>]+>|&#?\w+;))*\!(?:\s*(?:<[^<>]+>|&#?\w+;))* + (.*?) + L(?:\s*(?:<[^<>]+>|&#?\w+;))*o(?:\s*(?:<[^<>]+>|&#?\w+;))*r(?:\s*(?:<[^<>]+>|&#?\w+;))*e(?:\s*(?:<[^<>]+>|&#?\w+;))*m(?:\s*(?:<[^<>]+>|&#?\w+;))* (?:\s*(?:<[^<>]+>|&#?\w+;))*i(?:\s*(?:<[^<>]+>|&#?\w+;))*p(?:\s*(?:<[^<>]+>|&#?\w+;))*s(?:\s*(?:<[^<>]+>|&#?\w+;))*u(?:\s*(?:<[^<>]+>|&#?\w+;))*m(?:\s*(?:<[^<>]+>|&#?\w+;))*\. - ведущие и конечные разделители с дополнительными подшаблонами для сопоставления тегов, а внутри (.*?) (захват может не понадобиться).
  • ~u необходим модификатор, поскольку строки Unicode должны обрабатываться.
  • ОБНОВЛЕНИЕ. Для учета 1 + пробелов любые пробелы в шаблонах begin и end могут быть заменены подшаблоном \s+, чтобы соответствовать любым символам пробела 1+ в строка ввода.
  • ОБНОВЛЕНИЕ 2. Вспомогательные $begin = preg_replace('~\s+~u', ' ', $begin); и $end = preg_replace('~\s+~u', ' ', $end); необходимы для учета пробелов 1+ во входной строке.
  • Для учета объектов HTML добавьте еще один подшаблон к необязательным частям: &#?\\w+;, он также будет соответствовать &nbsp; и &#123; подобным объектам. Он также добавляется с \s* для соответствия произвольному пробелу и определяется с помощью * (может быть ноль или более).

Ответ 2

Я действительно хотел написать регулярное выражение. Но я претерпел некоторые приятные и сложные решения. Итак, это нерепрессивное решение.

Краткое объяснение: Основная проблема заключается в сохранении тегов HTML. Мы могли бы легко искать текст, если теги HTML были удалены. Итак: разделите их! Мы можем легко искать в разделенном контенте и создавать подстроку, которую хотим вырезать. Затем попытайтесь вырезать эту подстроку из HTML, сохранив теги.

<сильные > Преимущества:

  • Поиск прост и независим от HTML, вы можете выполнять поиск с регулярным выражением, если вам нужно
  • Требования масштабируемы: вы можете легко добавить полную многобайтную поддержку, поддержку сущностей и сглаживание пробелов и т.д.
  • Относительно быстро (возможно, что прямое регулярное выражение может быть быстрее)
  • Не касается оригинального HTML и не адаптируется к другим языкам разметки

Статический класс утилиты для этого сценария:

class HtmlExtractUtil
{

    const FAKE_MARKUP = '<>';
    const MARKUP_PATTERN = '#<[^>]+>#u';

    static public function extractBetween($html, $startTextToFind, $endTextToFind)
    {
        $strippedHtml = preg_replace(self::MARKUP_PATTERN, '', $html);
        $startPos = strpos($strippedHtml, $startTextToFind);
        $lastPos = strrpos($strippedHtml, $endTextToFind);

        if ($startPos === false || $lastPos === false) {
            return "";
        }

        $endPos = $lastPos + strlen($endTextToFind);
        if ($endPos <= $startPos) {
            return "";
        }

        return self::extractSubstring($html, $startPos, $endPos);
    }

    static public function extractSubstring($html, $startPos, $endPos)
    {
        preg_match_all(self::MARKUP_PATTERN, $html, $matches, PREG_OFFSET_CAPTURE);
        $start = -1;
        $end = -1;
        $previousEnd = 0;
        $stripPos = 0;
        $matchArray = $matches[0];
        $matchArray[] = [self::FAKE_MARKUP, strlen($html)];
        foreach ($matchArray as $match) {
            $diff = $previousEnd - $stripPos;
            $textLength = $match[1] - $previousEnd;
            if ($start == (-1)) {
                if ($startPos >= $stripPos && $startPos < $stripPos + $textLength) {
                    $start = $startPos + $diff;
                }
            }
            if ($end == (-1)) {
                if ($endPos > $stripPos && $endPos <= $stripPos + $textLength) {
                    $end = $endPos + $diff;
                    break;
                }
            }
            $tagLength = strlen($match[0]);
            $previousEnd = $match[1] + $tagLength;
            $stripPos += $textLength;
        }

        if ($start == (-1)) {
            return "";
        } elseif ($end == (-1)) {
            return substr($html, $start);
        } else {
            return substr($html, $start, $end - $start);
        }
    }

}

Применение:

$html = '
<html>
<body>
<p>Any string before</p>
<p>Hello <em>進撃の巨人</em>!</p>
random code
random code
<p>Lorem <span>ipsum<span>.</p>
<p>Any string after</p>
</body>
</html>
';
$startTextToFind = 'Hello 進撃の巨人!';
$endTextToFind = 'Lorem ipsum.';

$extractedText = HtmlExtractUtil::extractBetween($html, $startTextToFind, $endTextToFind);

header("Content-type: text/plain; charset=utf-8");
echo $extractedText . "\n";

Ответ 3

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

Потенциальные проблемы при использовании регулярных выражений

Например, представьте, что этот тег появляется в HTML перед частью, которая должна быть извлечена:

<p attr="Hello 進撃の巨人!">This comes before the match</p>

Многие решения regexp будут спотыкаться над этим и возвращать строку, которая начинается в середине этого открывающего тега p.

Или рассмотрите комментарий внутри раздела HTML, который должен быть сопоставлен:

<!-- Next paragraph will display "Lorem ipsum." -->

Или появляются некоторые символы меньшего размера и больше, чем знаки (скажем, в комментарии или значение атрибута):

<!-- Next paragraph will display >-> << Lorem ipsum. >> -->
<p data-attr="->->->" class="myclass">

Что будут с этими регулярными выражениями?

Это всего лишь примеры... Есть множество других ситуаций, которые создают проблемы для решений на основе регулярных выражений.

Есть более надежные способы анализа HTML.

Загрузите HTML в DOM

Я предлагаю здесь решение, основанное на DOMDocument, используя этот алгоритм:

  • Получите текстовое содержимое документа HTML и определите два смещения, где расположены обе подстроки (начало/конец).

  • Затем переходите через текстовые узлы DOM, отслеживая смещения, в которые входят эти узлы. В узлах, где пересекается любой из двух ограничивающих смещений, вставлен предопределенный разделитель (|). Этот разделитель не должен присутствовать в строке HTML. Поэтому он удваивается (||, ||||,...) до тех пор, пока это условие не будет выполнено;

  • Наконец, разбить представление HTML на этот разделитель и извлечь из него среднюю часть.

Вот код:

function extractBetween($html, $begin, $end) {
    $dom = new DOMDocument();
    // Load HTML in DOM, making sure it supports UTF-8; double HTML tags are no problem
    $dom->loadHTML('<html><head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
        </head></html>' . $html);
    // Get complete text content
    $text = $dom->textContent;
    // Get positions of the beginning/ending text; exit if not found.
    if (($from = strpos($text, $begin)) === false) return false;
    if (($to = strpos($text, $end, $from + strlen($begin))) === false) return false;
    $to += strlen($end);
    // Define a non-occurring delimiter by repeating `|` enough times:
    for ($delim = '|'; strpos($html, $delim) !== false; $delim .= $delim);
    // Use XPath to traverse the DOM
    $xpath = new DOMXPath($dom);
    // Go through the text nodes keeping track of total text length.
    // When exceeding one of the two offsets, inject a delimiter at that position.
    $pos = 0;
    foreach($xpath->evaluate("//text()") as $node) {
        // Add length of node text content to total length
        $newpos = $pos + strlen($node->nodeValue);
        while ($newpos > $from || ($from === $to && $newpos === $from)) {
            // The beginning/ending text starts/ends somewhere in this text node.
            // Inject the delimiter at that position:
            $node->nodeValue = substr_replace($node->nodeValue, $delim, $from - $pos, 0);
            // If a delimiter was inserted at both beginning and ending texts,
            // then get the HTML and return the part between the delimiters
            if ($from === $to) return explode($delim, $dom->saveHTML())[1];
            // Delimiter was inserted at beginning text. Now search for ending text
            $from = $to;
        }
        $pos = $newpos;
    }
}

Вы бы назвали это следующим образом:

// Sample input data
$html = '
        <html>
        <body>
        <p>This comes before the match</p>
        <p>Hey! Hello <em>進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>. la la la</p>
        <p>This comes after the match</p>
        </body>
        </html>
        ';

$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

// Call
$html = extractBetween($html, $begin, $end);

// Output result
echo $html;

Вывод:

Hello <em>進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.

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

Посмотрите, как он работает на eval.in.

Ответ 4

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

<?php
$subject = ' <html> 
<body> 
<p>He<i>l</i>lo <em>Lydia</em>!</p> 
random code 
random code 
<p>Lorem <span>ipsum</span>.</p> 
</body> 
</html>';

$begin = 'Hello Lydia!';
$end = 'Lorem ipsum.';

$begin_chars = str_split($begin);
$end_chars = str_split($end);

$begin_re = '';
$end_re = '';

foreach ($begin_chars as $c) {
    if ($c == ' ') {
        $begin_re .= '(\s|(<[a-z/]+>))+';
    }
    else {
        $begin_re .= $c . '(<[a-z/]+>)?';
    }
}
foreach ($end_chars as $c) {
    if ($c == ' ') {
        $end_re .= '(\s|(<[a-z/]+>))+';
    }
    else {
        $end_re .= $c . '(<[a-z/]+>)?';
    }
}

$re = '~(.*)((' . $begin_re . ')(.*)(' . $end_re . '))(.*)~ms';

$result = preg_match( $re, $subject , $matches );
$start_tag = preg_match( '~(<[a-z/]+>)$~', $matches[1] , $stmatches );

echo $stmatches[1] . $matches[2];

Выводится:

<p>He<i>l</i>lo <em>Lydia</em>!</p> 
random code 
random code 
<p>Lorem <span>ipsum</span>.</p>

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

В общем, что делает этот фрагмент:

  • Разделение строк на массив, каждое значение массива, представляющее один символ. Это нужно сделать, потому что Hello должен соответствовать Hel<i>l</i>o.
  • Для этого для части регулярного выражения добавляется дополнительный (<[a-z/]+>)? после каждого символа со специальным случаем для символа пробела.

Ответ 5

Вы можете попробовать этот RegEx:

(.*?)  # Data before sentences (to be removed)
(      # Capture Both sentences and text in between
  H.*?e.*?l.*?l.*?o.*?\s    # Hello[space]
  (<.*?>)*                  # Optional Opening Tag(s)
  進.*?撃.*?の.*?巨.*?人.*?   # 進撃の巨人
  (<\/.*?>)*                # Optional Closing Tag(s)
  (.*?)                     # Optional Data in between sentences
  (<.*?>)*                  # Optional Opening Tag(s)
  L.*?o.*?r.*?e.*?m.*?\s    # Lorem[space]
  (<.*?>)*                  # Optional Opening Tag(s)
  i.*?p.*?s.*?u.*?m.*?      # ipsum
)
(.*)   # Data after sentences (to be removed)

Подставляя группу 2nd Capture Group

Live Demo on Regex101

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

(.*?)(H.*?e.*?l.*?l.*?o.*?\s(<.*?>)*進.*?撃.*?の.*?巨.*?人.*?(<\/.*?>)*(.*?)(<.*?>)*L.*?o.*?r.*?e.*?m.*?\s(<.*?>)*i.*?p.*?s.*?u.*?m.*?)(.*)

Ответ 6

Просто для удовольствия

<?php
$begin = 'Hello Moto!';
$end = 'Lorem ipsum.';
//https://regex101.com/r/mC8aO6/1
$re = "/[\\w\\W]/"; 
$str = $begin.$end; 
$subst = "$0.*?"; 

$result = preg_replace($re, $subst, $str);
//Hello Moto! 
//to
//H.*?e.*?l.*?l.*?o.*? .*?M.*?o.*?t.*?o.*?!.*?

//https://regex101.com/r/fS6zG2/1
$re = "/(\\!|\\.\\.)/"; 
$str = $result; 
$subst = "\\\\$1";

$result = preg_replace($re, $subst, $str);

$re = "/.*(<p.*?$result.*?p>).*/s"; 
$str = "        <html>\n        <body>\n        <p>He<i>l</i>lo <em>Moto</em>!\n        random code\n        random code\n        <p>Lorem <span>ipsum<span>.<p>\n        </body>\n        </html>\n        "; 
$subst = "$1"; 

$result = preg_replace($re, $subst, $str);
echo $result."\n";
?>

Ввод

$begin = 'Hello Moto!';
$end = 'Lorem ipsum.';

    <html>
    <body>
    <p>He<i>l</i>lo <em>Moto</em>!
    random code
    random code
    <p>Lorem <span>ipsum<span>.<p>
    </body>
    </html>

Выход

<p>He<i>l</i>lo <em>Moto</em>!
        random code
        random code
        <p>Lorem <span>ipsum<span>.<p>

Ответ 7

Как насчет этого?

$escape=array('\\'=>1,'^'=>1,'?'=>1,'+'=>1,'*'=>1,'{'=>1,'}'=>1,'('=>1,')'=>1,'['=>1,']'=>1,'|'=>1,'.'=>1,'$'=>1,'+'=>1,'/'=>1);
$pattern='/';
for($i=0;isset($begin[$i]);$i++){
if(ord($c=$begin[$i])<0x80||ord($c)>0xbf){
    if(isset($escape[$c]))
        $pattern.="([ \t\r\n\v\f]*<\\/?[a-zA-Z]+>[ \t\r\n\v\f]*)*\\$c";
    else
        $pattern.="([ \t\r\n\v\f]*<\\/?[a-zA-Z]+>[ \t\r\n\v\f]*)*$c";
    }
    else
        $pattern.=$c;
}
$pattern.="(.|\n|\r)*";
for($i=0;isset($end[$i]);$i++){
if(ord($c=$end[$i])<0x80||ord($c)>0xbf){
    if(isset($escape[$c]))
        $pattern.="([ \t\r\n\v\f]*<\\/?[a-zA-Z]+>[ \t\r\n\v\f]*)*\\$c";
    else
        $pattern.="([ \t\r\n\v\f]*<\\/?[a-zA-Z]+>[ \t\r\n\v\f]*)*$c";
    }
    else
        $pattern.=$c;
}
$pattern[17]='?';
$pattern.='(<\\/?[a-zA-Z]+>)?/';
preg_match($pattern,$html,$a);
$match=$a[0];

Ответ 8

Существует несколько различных подходов к поиску контента в HTML-источнике. Все они имеют преимущества и недостатки. Если структура неизвестного кода является проблемой, самым безопасным способом будет использование анализатора XML, однако они сложны и поэтому довольно медленны.

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

Анализ структур HTML выполняется рекурсивными регулярными выражениями. Поскольку замедлить обработку и трудно отлаживать, я предпочитаю кодировать базовую логику в PHP и использовать функции preg_ для выполнения небольших задач.

Вот решение в OOP, крошечном классе, предназначенном для обработки многих запросов в одном и том же HTML-источнике. Это уже подход к решению расширенных подобных проблем, таких как добавление предыдущего и последующего содержимого до следующей границы тега. Он не претендует на идеальное решение, но он легко расширяется.

Логика такова: Оплатите некоторую рабочую среду для инициализации, чтобы сохранить позиции тегов относительно обычного текста, разбить теги и сохранить строки между <...> и суммой длины. Затем в каждом поиске контента совпадают иголки с простым содержимым. Найдите исходную/конечную позицию в источнике HTML с помощью двоичного поиска.

Двоичный поиск работает следующим образом: требуется отсортированный список. Вы сохраняете индекс первого и последнего элемента + 1. Вычислите среднее значение с помощью добавления и целочисленного деления на 2. Разделение и пол выполняется с помощью правильного битрейта. Если найденное значение является низким, установите меньшее значение индекса var на текущий индекс, иначе он больше. Остановка по разнице индексов 1. Если вы ищете точное значение, ломайте ранний элемент. 0, (14 + 1) = > 7; 7,15 = > 11; 7,11 = > 9; 7,9 = > 8; 8-7 = diff.1 Вместо 15 итераций выполняется только 4. Чем больше начальное значение, тем больше времени экспоненциально сохраняется.

Класс PHP:

<?php
class HtmlTextSearch
{
  protected 
    $html            = '',
    $heystack        = '',
    $tags            = [],
    $current_tag_idx = null
  ;

  const
    RESULT_NO_MODIFICATION      = 0,
    RESULT_PREPEND_TAG          = 1,
    RESULT_PREPEND_TAG_CONTENT  = 2,
    RESULT_APPEND_TAG           = 4,
    RESULT_APPEND_TAG_CONTENT   = 8,
    MATCH_CASE_INSENSITIVE      =16,
    MATCH_BLANK_AS_WHITESPACE   =32,
    MATCH_BLANK_MULTIPLE        =64
  ;

  public function __construct($html)
  {
    $this->set_html($html);
  }

  public function set_html($html)
  {
    $this->html = $html;
    $regexp = '~<.*?>~su';
    preg_match_all($regexp, $html, $this->tags, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);
    $this->tags = $this->tags[0];
    # we use exact the same algorithm to strip html
    $this->heystack = preg_replace($regexp, '', $html);

    # convert positions to plain content
    $sum_length = 0;
    foreach($this->tags as &$tag)
    { $tag['pos_in_content'] = $tag[1] - $sum_length;
      $tag['sum_length'    ] = $sum_length += strlen($tag[0]);
    }

    # zero length dummy tags to mark start/end position of strings not beginning/ending with a tag
    array_unshift($this->tags , [0 => '', 1 => 0, 'pos_in_content' => 0, 'sum_length' => 0 ]); 
    array_push   ($this->tags , [0 => '', 1 => strlen($html)-1]); 
  }

  public function translate_pos_plain2html($content_position)
  {
    # binary search
    $idx = [true => 0, false => count($this->tags)-1];
    while(1 < $idx[false] - $idx[true])
    { $i = ($idx[true] + $idx[false]) >>1;                               // integer half of both array indexes
      $idx[$this->tags[$i]['pos_in_content'] <= $content_position] = $i; // hold one index less and the other greater
    }

    $this->current_tag_idx = $idx[true];
    return $this->tags[$this->current_tag_idx]['sum_length'] + $content_position;
  }

  public function &find_content($needle_start, $needle_end = '', $result_modifiers = self::RESULT_NO_MODIFICATION)
  {
    $needle_start = preg_quote($needle_start, '~');
    $needle_end   = '' == $needle_end ? '' : preg_quote($needle_end  , '~');
    if((self::MATCH_BLANK_MULTIPLE | self::MATCH_BLANK_AS_WHITESPACE) & $result_modifiers)
    { 
      $replacement  = self::MATCH_BLANK_AS_WHITESPACE & $result_modifiers ? '\s' : ' ';
      if(self::MATCH_BLANK_MULTIPLE & $result_modifiers)
      { $replacement .= '+';
        $multiplier = '+';
      }
      else
        $multiplier = '';
      $repl_pattern = "~ $multiplier~";
      $needle_start = preg_replace($repl_pattern, $replacement, $needle_start);
      $needle_end   = preg_replace($repl_pattern, $replacement, $needle_end);
    }

    $icase = self::MATCH_CASE_INSENSITIVE & $result_modifiers ? 'i' : '';
    $search_pattern = "~{$needle_start}.*?{$needle_end}~su$icase";
    preg_match_all($search_pattern, $this->heystack, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);

    foreach($matches[0] as &$match)
    { $pre = $post = '';

      $pos_start = $this->translate_pos_plain2html($match[1]);
      if(self::RESULT_PREPEND_TAG_CONTENT & $result_modifiers)
        $pos_start = $this->tags[$this->current_tag_idx][1]
          +( self::RESULT_PREPEND_TAG & $result_modifiers ? 0 : strlen ($this->tags[$this->current_tag_idx][0]) );
      elseif(self::RESULT_PREPEND_TAG     & $result_modifiers)
        $pre = $this->tags[$this->current_tag_idx][0];

      $pos_end   = $this->translate_pos_plain2html($match[1] + strlen($match[0]));
      if(self::RESULT_APPEND_TAG_CONTENT & $result_modifiers)
      { $next_tag = $this->tags[$this->current_tag_idx+1];
        $pos_end = $next_tag[1]
          +( self::RESULT_APPEND_TAG  & $result_modifiers ? strlen ($next_tag[0]) : 0);
      }
      elseif(self::RESULT_APPEND_TAG     & $result_modifiers)
        $post = $this->tags[$this->current_tag_idx+1][0];

      $match = $pre . substr($this->html, $pos_start, $pos_end - $pos_start) . $post;
    };
    return $matches[0];
  }
}

Некоторые тестовые примеры:

$html_source = get($_POST['html'], <<< ___
<html>
  <body>
    <p>He said: "Hello <em>進撃の巨人</em>!"</p>
    random code
    random code
    <p>Lorem <span>ipsum</span>. foo bar</p>
  </body>
</html>
___
);


  function get(&$ref, $default=null) { return isset($ref) ? $ref : $default; }

  function attr_checked($name, $method = "post")
  { $req = ['post' => '_POST', 'get' => '_GET'];
    return isset($GLOBALS[$req[$method]][$name]) ? ' checked="checked"' : '';
  }

  $begin = get($_POST['begin'], '"Hello 進撃の巨人!"');
  $end   = get($_POST['end'  ], 'Lorem ipsum.'   );
?>

<form action="" method="post">
  <textarea name="html" cols="80" rows="10"><?php
echo $html_source;
?></textarea>

  <br><input type="text"  name="begin" value="<?php echo $begin;?>">
  <br><input type="text"  name="end"   value="<?php echo $end  ;?>">

  <br><input type="checkbox" name="tag-pre" id="tag-pre"<?php echo attr_checked('tag-pre');?>>
      <label for="tag-pre">prefix tag</label>
      <br><input type="checkbox" name="txt-pre" id="txt-pre"<?php echo attr_checked('txt-pre');?>>
      <label for="txt-pre">prefix content</label>
  <br><input type="checkbox" name="txt-suf" id="txt-suf"<?php echo attr_checked('txt-suf');?>>
      <label for="txt-suf">suffix content</label>
  <br><input type="checkbox" name="tag-suf" id="tag-suf"<?php echo attr_checked('tag-suf');?>>
      <label for="tag-suf">suffix tag</label>
  <br>
  <br><input type="checkbox" name="wspace" id="wspace"<?php echo attr_checked('wspace');?>>
      <label for="wspace">blanc (#32) matches any whitespace character</label>
  <br><input type="checkbox" name="multiple" id="wspace"<?php echo attr_checked('multiple');?>>
      <label for="multiple">one or more blancs match any number of blancs/whitespaces</label>
  <br><input type="checkbox" name="icase"    id="icase"<?php echo attr_checked('icase');?>>
      <label for="icase">case insensitive</label>

  <br><button type="submit">submit</button>
</form>

<?php
  $html = new HtmlTextSearch($html_source);

  $opts=
  [ 'tag-pre' => HtmlTextSearch::RESULT_PREPEND_TAG,
    'txt-pre' => HtmlTextSearch::RESULT_PREPEND_TAG_CONTENT,
    'txt-suf' => HtmlTextSearch::RESULT_APPEND_TAG_CONTENT,
    'tag-suf' => HtmlTextSearch::RESULT_APPEND_TAG,
    'wspace'  => HtmlTextSearch::MATCH_BLANK_AS_WHITESPACE,
    'multiple'=> HtmlTextSearch::MATCH_BLANK_MULTIPLE,
    'icase'   => HtmlTextSearch::MATCH_CASE_INSENSITIVE
  ];
  $options = 0;
  foreach($opts as $k => $v)
    if(isset($_POST[$k]))
      $options |= $v;
  $results = $html->find_content($begin, $end, $options);
  var_dump($results);
?>

Ответ 9

Решение PHP:

PHPFiddle Demo

$html = '
        <html>
        <body>
        <p>Hello <em>進撃の巨人</em>!</p>
        random code
        random code
        <p>Lorem <span>ipsum<span>.</p>
        </body>
        </html>
        ';
$begin = 'Hello 進撃の巨人!';
$end = 'Lorem ipsum.';

$matchHtmlTag = '(?:<.*?>)?';
$matchAllNonGreedy = '(?:.|\r?\n)*?';
$matchUnescapedCharNotAtEnd = '([^\\\\](?!$)|\\.(?!$))';
$matchBeginWithTags = preg_replace(
    $matchUnescapedCharNotAtEnd, '$0' . $matchHtmlTag, preg_quote($begin));
$matchEndWithTags = preg_replace(
    $matchUnescapedCharNotAtEnd, '$0' . $matchHtmlTag, preg_quote($end));
$pattern = '/' . $matchBeginWithTags . $matchAllNonGreedy . $matchEndWithTags . '/';

preg_match($pattern, $html, $matches);
$html = $matches[0];

Сгенерированное регулярное выражение ($ pattern):

Regex101 Demo

H(?:<.*?>)?e(?:<.*?>)?l(?:<.*?>)?l(?:<.*?>)?o(?:<.*?>)? (?:<.*?>)?進(?:<.*?>)?撃(?:<.*?>)?の(?:<.*?>)?巨(?:<.*?>)?人(?:<.*?>)?!(?:.|\r?\n)*?L(?:<.*?>)?o(?:<.*?>)?r(?:<.*?>)?e(?:<.*?>)?m(?:<.*?>)? (?:<.*?>)?i(?:<.*?>)?p(?:<.*?>)?s(?:<.*?>)?u(?:<.*?>)?m(?:<.*?>)?\.

Ответ 10

Предполагая, что random code в вашем примере находится внутри <p></p>, я предлагаю использовать domdocument и xpath, а не регулярное выражение в том, что вы пытаетесь сделать.

$html = '
        <html>
        <body>
        <div>nada blahhh <p>test paragraph</p> <em>blahh</em></div>
        <p>test</p>
        <span>this is test</span>
        <p>Hello <em>進撃の巨人</em>!</p>
        <p>random code</p>
        <p>random code</p>
        <p>Lorem <span>ipsum<span>.</p>
        <div>nada blahhh <p>test paragraph</p> <em>blahh</em></div>
        <p>test</p>
        <span>this is test</span>
        </body>
        </html>
        ';
$begin = 'Hello 進撃の巨人!';
$begin = iconv ( 'iso-8859-1','utf-8' , $begin ); // had to use iconv it won't be needed in your case
$end = 'Lorem ipsum.';       
$doc = new DOMDocument();
$doc->loadHTML($html);

$xpath = new DOMXpath($doc);
// example 3: same as above with wildcard
$elements = $xpath->query("*/p");

if (!is_null($elements)) {
    $flag = 'no_output';
  foreach ($elements as $element) {
      if($flag=='prepare_for_output'){$flag='output';}
      if($element->nodeValue==$begin){
      $flag='prepare_for_output';
      }
      if($element->nodeValue==$end){
      $flag='no_output';
      }
      if($flag=='output') {
      echo $element->nodeValue."\n";
      }
  }
}

http://sandbox.onlinephpfunctions.com/code/fa1095d98c6ef5c600f7b06366b4e0c4798a112f

Ответ 11

вы можете использовать эту концепцию, код приведен ниже.

        <html lang="en-US">
        <head>

        <title>HTML Unicode UTF-8</title>

        <meta charset="utf-8">
        </head>

        <body>
        <?php
        $html = '
            <html>
            <body>
            <p>Hello <em>進撃の巨人</em>!</p>
            random code
            random code
            <p>Lorem <span>ipsum<span>.</p>

            </body>
            </html>
            ';

        $begin = 'Hello 進撃の巨人!';
        $end = 'Lorem ipsum.';

        $stripped =strip_tags($html);

        if (strpos($stripped, $end) !== false) {

            $final =str_replace($begin,"",$stripped);

           echo str_replace($end,"",$final);
        }
        ?>
        </body>  
        </html>

Ответ 12

Не пытайтесь использовать регулярное выражение.

Используйте библиотеку DOM для PHP: http://php.net/manual/en/book.dom.php

<?php

    header('Content-Type: text/html; charset=UTF-8');

    $html = '
            <html>
            <body>
            <p>Hello <em>進撃の巨人</em>!</p>
            random code
            random code
            <p>Lorem <span>ipsum<span>.</p>
            </body>
            </html>
            ';

    $doc = new DOMDocument();
    $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));

    $body_elements = $doc->getElementsByTagName("body"); 

    $code = '';

    foreach ($body_elements as $element) { 

        $children  = $element->childNodes;

        foreach ($children as $child) 
        { 
            $code.= $element->ownerDocument->saveHTML($child);
        }

    }

    echo $code;
?>

Если вы запустите этот код в файле примера php, вы должны проверить источник веб-страницы, используя "Просмотр источника" в своем браузере, чтобы увидеть теги html. <p> или <em> должен быть там; -)