Я пытаюсь добавить теги 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»)
Что означают разные значения в массивах?
Похоже, что «начинается с символа X» и «заканчивается после символа Y». Таким образом, первый начинается с символа 1 и заканчивается после символа 3.
@spudley Использование DomDocument может быть вариантом, но как добавить теги в указанные позиции? Я пытаюсь показать HTML-документ, аннотированный на лету элементами HTML.
@AleksG разные значения в массиве означают смещения начала и конца аннотации
Что такое единицы? Это символы, слова, теги и т.д.?
@THM спасибо за высокую оценку! Я исправляю эту проблему.
@AleksG Спасибо за вопрос. Единицы - символы, как примеры
Уважаемый @micmackusa Большое спасибо за внимание. Я исправляю прокомментированные проблемы и пытался улучшить вопрос в этом отношении.
@micmackusa Спасибо за наблюдение. Рассмотрим первую аннотацию. Если вы хотите сделать аннотацию, начинающуюся с 0 и заканчивающуюся после символа 2, аннотация должна быть (0,3). Это отмечено символом 0. Начальная позиция должна быть включающей, а конечная позиция должна быть исключающей. Если этого не сделать, аннотация первого символа будет (0,0), что смещения нет. Методика аналогична выбору фрагмента текста, если вы хотите отметить 1 символ в строке, курсор должен начинаться с позиции 0 и заканчиваться перед 2 символом. То есть (0,1)
@mickmackusa Второй диапазон должен быть в пределах 1 диапазона, чтобы иметь вывод с правильно сформированным HTML и соответствовать заданному массиву аннотаций. Наоборот, произойдет следующее: <div> <span class = 'annotation n-1'> Th <span class = 'annotation n-2'> i </ span> s </ span>. Как видите, этот HTML-код не соответствует смещениям данных аннотаций. При использовании этой методологии аннотация 2 включает только символ «i».
Можете ли вы перепроверить приведенный вами пример ("результат должен быть следующим")? Похоже, что 1
-й диапазон (n-2
) начинается перед 2-м символом, но в примере $annotationCharactersPositions
есть 1=>array(3,6)
. Также рассмотрите более четкое объяснение мотивации всего этого процесса; кажется вероятным, что кто-то предложит совершенно другой подход, который может работать лучше в долгосрочной перспективе.
@ShapeOfMatter большое спасибо за уведомление! Я исправляю эту проблему. Я добавил немного больше информации, чтобы попытаться улучшить вопрос. Спасибо!
@mickmackusa большое спасибо за ваши вопросы! Я изменил тему, чтобы дать ответы на ваши вопросы. Уникальный идентификатор для каждого класса был только примером, показывающим, что классы аннотаций могут быть переменными. Большое спасибо за высокую оценку
@micmackusa Спасибо за уведомление! Я исправляю проблему.
После загрузки 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. Это лучше той хакерской чепухи, которую я собирал вместе. Определенно больше не буду выкладывать свой мусор. Определенно достойный награды. Хорошо сделано.
Браво +2. замечательный ответ!!! В принципе все работает правильно! Если я найду ошибку в будущем, я укажу на нее. Только одна оценка для будущих разработчиков. Чтобы использовать это решение, необходимо установить пакет php-intl. Большое спасибо @Thw
@ThW Что означает <<<'HTML' в вашем ответе? Как это работает?
Это строковый синтаксис, который называется NOWDOC (php.net/manual/ru/…). Мне нравится использовать его для демонстрационных данных, потому что он требует меньше экранирования.
@ThW Я обнаружил ошибку в коде, когда входная строка содержит элементы в виде <notHtmlString>. Например, для строки «различные структуры P<3>» или «адениндинуклеотид (NAD<+>)» php throw «htmlParseStartTag: недопустимое имя элемента в Entity». Как это можно решить, если функция PHP htmlspecialchars не может использоваться в этом контексте? (поскольку использование нарушит структуру смещений аннотаций)
Не ошибка. <3>
недопустимый HTML. Таким образом, синтаксический анализатор выдает предупреждение и исправляет HTML. Вы можете использовать внутреннюю обработку ошибок libxmls для захвата ошибок. 3v4l.org/1nbFm . Более сложный и иногда единственный способ — восстановить HTML с помощью строковых функций (и PCRE) перед загрузкой в виде HTML.
@ThW Да, я уже знал, что это недопустимый html, однако предоставленное решение не может обрабатывать какие-либо специальные символы в HTML, такие как «<», «&», «>», поскольку, если эти символы закодированы перед вызовом к вашему коду (как элементы HTML), массив смещений входных аннотаций не будет соответствовать, так как некоторые символы были добавлены во входную строку при преобразовании "&" в "&";
DOMNode::$textContent содержит содержимое с декодированными сущностями: 3v4l.org/jGo8c и DOMDocument::saveHTML() будут кодировать по мере необходимости.
Да, вам нужно будет использовать DomDocument; создайте его как узлы Dom и забудьте об использовании конкатенации строк, если вы хотите, чтобы в конце процесса оставался какой-либо разум. Но, честно говоря, я изо всех сил пытаюсь понять, чего вы на самом деле пытаетесь достичь здесь?