Как использовать PHP для аннотирования строки с помощью HTML (т.е. как вставить HTML-теги в строку с помощью смещений, поддерживающих действительный HTML)?

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

//array(Start offset, End offset) in characters
//Note that annotation starts in the Start offset number and ends before the End offset number
$annotationCharactersPositions= array(
   0=>array(0,3),
   1=>array(2,6),
   2=>array(8,10)
);

Таким образом, чтобы аннотировать следующий текст HTML ($ source) с помощью следующего тега HTML ($ tag). Это обертка символов, разделенных массивом $annotationPositions (без учета HTML-тегов источника).

$source = "<div>This is</div> only a test for stackoverflow";
$tag = "<span class='annotation n-$cont'>";

результат должен быть следующим (https://jsfiddle.net/cotg2pn1/):

charPos   =--------------------------------- 01---------------------------- 2-------------------------------------------3------------------------------------------45-------67-----------------------------89-------10,11,12,13......
$output = "<div><span class='annotation n-1'>Th<span class='annotation n-2'>i</span></span><span class='annotation n-2'>s</span><span class='annotation n-2'> i</span>s</div> <span class='annotation n-3'>on</span>ly a test for stackoverflow"

Как я могу запрограммировать следующую функцию:

    $cont=0;
    $myAnnotationClass = "placesOfTheWorld";
    for ($annotationCharactersPositions as $position) {
         $tag = "<span class='annotation $myAnnotationClass'>";             
         $source=addHTMLtoString($source,$tag,$position);
         $cont++;
    }

принимая во внимание, что теги HTML входной строки не должны учитываться при подсчете символов, описанных в массиве $annotationCharactersPositions и каждая вставка аннотации (т.е. $тег) в исходный текст $ должны учитываться для инкапсуляции/аннотации следующих аннотаций.

Идея всего этого процесса состоит в том, что для данного текста Вход (этот может содержать или не содержать теги HTML) группа символов будет аннотирована (принадлежащая одному или нескольким словам), так что результат будет иметь выбранные символы (через массив, который определяет, где начинается и заканчивается каждая аннотация) обернут тегом HTML, который может варьироваться (a , span, mark) с переменным количеством атрибутов html (name, class, id, data-*). Кроме того, документ результат должен быть правильно сформированным действительным HTML, так что, если какая-либо аннотация находится между несколькими аннотациями, html должен быть записан в выводе соответственно.

Знаете ли вы какую-либо библиотеку или решение для этого? Возможно, функции PHP DOMDocument могут быть полезны? Но как применить смещения к функциям php DomDocument? Любая идея или помощь хорошо приняты.

Примечание 1: вводимый текст представляет собой необработанный текст UTF-8 с любым типом внедренных объектов HTML (0-n).

Заметка 2: Тег ввода может быть любым HTML-тегом с переменным количеством атрибутов (0-n).

Заметка 3: Начальная позиция должна быть включающей, а конечная позиция должна быть исключающей. т. е. аннотация 1º начинается перед 2-м символом (включая 2-й символ «i») и заканчивается перед 6-м символом (исключая 6-й символ «s»)

Да, вам нужно будет использовать DomDocument; создайте его как узлы Dom и забудьте об использовании конкатенации строк, если вы хотите, чтобы в конце процесса оставался какой-либо разум. Но, честно говоря, я изо всех сил пытаюсь понять, чего вы на самом деле пытаетесь достичь здесь?

Spudley 05.06.2019 16:58

Что означают разные значения в массивах?

Aleks G 05.06.2019 17:02

Похоже, что «начинается с символа X» и «заканчивается после символа Y». Таким образом, первый начинается с символа 1 и заканчивается после символа 3.

TMH 05.06.2019 17:05

@spudley Использование DomDocument может быть вариантом, но как добавить теги в указанные позиции? Я пытаюсь показать HTML-документ, аннотированный на лету элементами HTML.

Martin 05.06.2019 17:06

@AleksG разные значения в массиве означают смещения начала и конца аннотации

Martin 05.06.2019 17:07

Что такое единицы? Это символы, слова, теги и т.д.?

Aleks G 05.06.2019 17:30

@THM спасибо за высокую оценку! Я исправляю эту проблему.

Martin 05.06.2019 17:39

@AleksG Спасибо за вопрос. Единицы - символы, как примеры

Martin 05.06.2019 17:40

Уважаемый @micmackusa Большое спасибо за внимание. Я исправляю прокомментированные проблемы и пытался улучшить вопрос в этом отношении.

Martin 06.06.2019 11:12

@micmackusa Спасибо за наблюдение. Рассмотрим первую аннотацию. Если вы хотите сделать аннотацию, начинающуюся с 0 и заканчивающуюся после символа 2, аннотация должна быть (0,3). Это отмечено символом 0. Начальная позиция должна быть включающей, а конечная позиция должна быть исключающей. Если этого не сделать, аннотация первого символа будет (0,0), что смещения нет. Методика аналогична выбору фрагмента текста, если вы хотите отметить 1 символ в строке, курсор должен начинаться с позиции 0 и заканчиваться перед 2 символом. То есть (0,1)

Martin 06.06.2019 11:44

@mickmackusa Второй диапазон должен быть в пределах 1 диапазона, чтобы иметь вывод с правильно сформированным HTML и соответствовать заданному массиву аннотаций. Наоборот, произойдет следующее: <div> <span class = 'annotation n-1'> Th <span class = 'annotation n-2'> i </ span> s </ span>. Как видите, этот HTML-код не соответствует смещениям данных аннотаций. При использовании этой методологии аннотация 2 включает только символ «i».

Martin 06.06.2019 11:52

Можете ли вы перепроверить приведенный вами пример ("результат должен быть следующим")? Похоже, что 1-й диапазон (n-2) начинается перед 2-м символом, но в примере $annotationCharactersPositions есть 1=>array(3,6). Также рассмотрите более четкое объяснение мотивации всего этого процесса; кажется вероятным, что кто-то предложит совершенно другой подход, который может работать лучше в долгосрочной перспективе.

ShapeOfMatter 07.06.2019 18:22

@ShapeOfMatter большое спасибо за уведомление! Я исправляю эту проблему. Я добавил немного больше информации, чтобы попытаться улучшить вопрос. Спасибо!

Martin 10.06.2019 11:03

@mickmackusa большое спасибо за ваши вопросы! Я изменил тему, чтобы дать ответы на ваши вопросы. Уникальный идентификатор для каждого класса был только примером, показывающим, что классы аннотаций могут быть переменными. Большое спасибо за высокую оценку

Martin 10.06.2019 11:19

@micmackusa Спасибо за уведомление! Я исправляю проблему.

Martin 11.06.2019 10:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
6
15
1 347
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После загрузки HTML в документ DOM вы можете получить любой текстовый узел, являющийся потомком узла элемента, с помощью выражения Xpath (.//text()) в итерируемом списке. Это позволяет отслеживать символы перед текущим текстовым узлом. На текстовом узле вы проверяете, должно ли текстовое содержимое (или его часть) быть заключено в тег аннотации. Если это так, разделите его и создайте фрагмент, содержащий до 3 узлов. (текст до, аннотация, текст после). Замените текстовый узел фрагментом.

function annotate(
  \DOMElement $container, int $start, int $end, string $name
) {
  $document = $container->ownerDocument;
  $xpath = new DOMXpath($document);
  $currentOffset = 0;
  // fetch and iterate all text node descendants 
  $textNodes = $xpath->evaluate('.//text()', $container);
  foreach ($textNodes as $textNode) {
    $text = $textNode->textContent;
    $nodeLength = grapheme_strlen($text);
    $nextOffset = $currentOffset + $nodeLength;
    if ($currentOffset > $end) {
      // after annotation: break
      break;
    }
    if ($start >= $nextOffset) {
      // before annotation: continue
      $currentOffset = $nextOffset;
      continue;
    }
    // make string offsets relative to node start
    $relativeStart = $start - $currentOffset;
    $relativeLength = $end - $start;
    if ($relativeStart < 0) {
      $relativeLength -= $relativeStart;
      $relativeStart = 0;
    }
    $relativeEnd = $relativeStart + $relativeLength;
    // create a fragment for the annotation nodes
    $fragment = $document->createDocumentFragment();
    if ($relativeStart > 0) {
      // append string before annotation as text node
      $fragment->appendChild(
        $document->createTextNode(grapheme_substr($text, 0, $relativeStart))
      );
    }
    // create annotation node, configure and append
    $span = $document->createElement('span');
    $span->setAttribute('class', 'annotation '.$name);
    $span->textContent = grapheme_substr($text, $relativeStart, $relativeLength);
    $fragment->appendChild($span);
    if ($relativeEnd < $nodeLength) {
      // append string after annotation as text node
      $fragment->appendChild(
        $document->createTextNode(grapheme_substr($text, $relativeEnd))
      );
    }
    // replace current text node with new fragment
    $textNode->parentNode->replaceChild($fragment, $textNode);
    $currentOffset = $nextOffset;
  }
}

$html = <<<'HTML'
<div><div>This is</div> only a test for stackoverflow</div>
HTML;

$annotations = [
  0 => [0, 3],
  1 => [2, 6],
  2 => [8, 10]
];

$document = new DOMDocument();
$document->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

foreach ($annotations as $index => $offsets) {
  annotate($document->documentElement, $offsets[0], $offsets[1], 'n-'.$index);
}

echo $document->saveHTML();

Выход:

<div><div><span class = "annotation n-0">Th<span class = "annotation n-1">i</span></span><span class = "annotation n-1">s is</span></div> <span class = "annotation n-2">on</span>ly a test for stackoverflow</div>

Браво +1. Это лучше той хакерской чепухи, которую я собирал вместе. Определенно больше не буду выкладывать свой мусор. Определенно достойный награды. Хорошо сделано.

mickmackusa 10.06.2019 14:39

Браво +2. замечательный ответ!!! В принципе все работает правильно! Если я найду ошибку в будущем, я укажу на нее. Только одна оценка для будущих разработчиков. Чтобы использовать это решение, необходимо установить пакет php-intl. Большое спасибо @Thw

Martin 10.06.2019 17:27

@ThW Что означает <<<'HTML' в вашем ответе? Как это работает?

Martin 11.06.2019 10:43

Это строковый синтаксис, который называется NOWDOC (php.net/manual/ru/…). Мне нравится использовать его для демонстрационных данных, потому что он требует меньше экранирования.

ThW 11.06.2019 12:43

@ThW Я обнаружил ошибку в коде, когда входная строка содержит элементы в виде <notHtmlString>. Например, для строки «различные структуры P<3>» или «адениндинуклеотид (NAD<+>)» php throw «htmlParseStartTag: недопустимое имя элемента в Entity». Как это можно решить, если функция PHP htmlspecialchars не может использоваться в этом контексте? (поскольку использование нарушит структуру смещений аннотаций)

Martin 11.06.2019 17:32

Не ошибка. <3> недопустимый HTML. Таким образом, синтаксический анализатор выдает предупреждение и исправляет HTML. Вы можете использовать внутреннюю обработку ошибок libxmls для захвата ошибок. 3v4l.org/1nbFm . Более сложный и иногда единственный способ — восстановить HTML с помощью строковых функций (и PCRE) перед загрузкой в ​​виде HTML.

ThW 11.06.2019 17:48

@ThW Да, я уже знал, что это недопустимый html, однако предоставленное решение не может обрабатывать какие-либо специальные символы в HTML, такие как «<», «&», «>», поскольку, если эти символы закодированы перед вызовом к вашему коду (как элементы HTML), массив смещений входных аннотаций не будет соответствовать, так как некоторые символы были добавлены во входную строку при преобразовании "&" в "&amp";

Martin 11.06.2019 17:59

DOMNode::$textContent содержит содержимое с декодированными сущностями: 3v4l.org/jGo8c и DOMDocument::saveHTML() будут кодировать по мере необходимости.

ThW 12.06.2019 01:07

Другие вопросы по теме