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

Я пытаюсь добавить теги 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 и при каждой вставке аннотации (т. Е. $ tag ) в тексте $ sourceнеобходимо учитывать для инкапсуляции / аннотации следующих аннотаций.

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

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

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

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

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

1 Ответ

8 голосов
/ 10 июня 2019

После загрузки 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>
...