如何使用 PHP 来注释带有 HTML 的字符串(即如何通过保持有效 HTML 的偏移量将 HTML 标记插入字符串)?

How to use PHP to annotate an string with HTML (i.e How. insert HTML tags to an string by offsets mantaining a valid 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 标签 ($tag) 注释下面的 HTML 文本 ($source)。这是包装由 $annotationPositions 数组分隔的字符(不考虑源的 HTML 标签)。

$source="<div>This is</div> only a test for Whosebug";
$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 Whosebug"

如何编写下一个功能:

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

考虑到 在计算 $annotationCharactersPositions 数组 中描述的字符以及每次插入对于以下注释的 encapsulation/annotation,必须考虑 $source 文本中的注释(即 $tag)。

整个过程的想法是给定一个 输入 文本( 可能包含也可能不包含 HTML 标签 ) 一组字符将被注释(属于一个或几个单词)以便 结果将具有选定的字符 (通过定义每个注释开始和结束位置的数组) 由 HTML 标签包装,标签可以变化 (a、跨度、标记),具有可变数量的 html 属性(名称、class、id、数据- *).此外 结果必须是格式正确的有效 HTML 文档,这样如果任何注释位于多个注释之间, html 应该相应地写入输出.

你知道有什么库或解决方案可以做到这一点吗?也许 PHP DOMDocument 功能很有用?但是如何将偏移量应用于 php DomDocument 功能?任何想法或帮助都很受欢迎。

注意 1:输入文本是 UTF-8 原始文本,嵌入了任何类型的 HTML 实体 (0-n)。

注释 2:输入标签可以是任意 HTML 标签,具有可变数量的属性 (0-n)。

注3:起始位置必须包含,结束位置必须不包含。即 1º 注释从第 2 个字符(包括第 2 个字符 'i')开始到第 6 个字符(不包括第 6 个字符 's')

之前结束

将 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 Whosebug</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 Whosebug</div>