Я хочу обновить innerHTML div, но хочу по-другому обрабатывать текст, если он уже принадлежит диапазону
<div id = "a">
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the TARGET
</div>
Моя цель — заменить все экземпляры TARGET на <span class = "target-class">TARGET</span>, если они еще не находятся в интервале. Мой код в настоящее время перебирает дочерние элементы, и перед выполнением замены проверяет, содержит ли тег = SPAN и содержит ли span target-class.
<div id = "a">
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the <span class = "target-class">TARGET</span>
</div>
Когда я вызываю .innerHTML на #a, он возвращает полный HTML-код div. Когда я перебираю дочерние элементы #a, он перебирает промежутки -- я надеюсь, что есть какой-то способ перебрать то, что я условно назову text fragments, чтобы я мог заменить их innerHTML
Текущий код:
Логика переноса для циклического прохождения элементов документа
export async function updateLabels(includedItems: Set<string>, excludedItems: Set<string>, allItems: Set<string>) {
visitChildren(
document.body,
[
(element: HTMLElement) => replaceItemWithTag(element, allItems, `<span class = "custom-label">$1</span>`),
],
)
// Ignore this - it loops through all elements w/ the "custom-label" class and adds another class "custom-label--include"/"custom-label--exclude" depending on which set it belongs to
// setItemClassifications(includedItems, excludedItems)
}
Код прогулки по дому:
export function visitChildren(
element: HTMLElement,
visitors: ((element: Element) => [boolean, string[]])[],
) {
for (let i = 0; i < element.children.length; i++) {
if (element.children[i].tagName === "SCRIPT") {
// skip scripts
continue;
}
if (element.children[i].tagName === "STYLE") {
// skip style
continue;
}
const childVisitors: ((element: Element) => [boolean, string[]])[] = [];
const childVisitorReasons: [boolean, string[]][] = [];
for (let j = 0; j < visitors.length; j++) {
const visitor = visitors[j];
const [result, reasons] = visitor(element.children[i]);
childVisitorReasons.push([result, reasons]);
if (result) {
childVisitors.push(visitor);
}
}
if (childVisitors.length > 0) {
visitChildren(element.children[i] as HTMLElement, childVisitors);
}
}
}
код замены для этого экземпляра:
export function replaceItemWithTag(element: HTMLElement, targets: Set<string>, replaceWith: string): [boolean, string[]] {
const targetArray = Array.from(targets);
const reg = RegExp(`\\b(${targetArray.join("|")})\\b`, "ig");
const reason: string[] = [
`targets: ${targetArray.join(",")}`,
`regex: ${reg}`,
];
// skip element if it's already been replaced
if (element.classList.contains("custom-label")) {
reason.push("already replaced")
return [false, reason]
}
let innerHTMLIncludesATarget = false;
for (const target of targetArray) {
if (element.innerHTML.toLowerCase().includes(target)) {
innerHTMLIncludesATarget = true;
break;
}
}
if (!innerHTMLIncludesATarget) {
reason.push("no target in innerHTML")
if (debugPrint) { console.info({ element, innerHTML: element.innerHTML, innerText: element.innerText, filter: false, reason }) }
return [false, reason];
}
reason.push("target in innerHTML")
let innerTextIncludesATarget = false;
for (const target of targetArray) {
if (element.innerText.toLowerCase().includes(target)) {
innerTextIncludesATarget = true;
break;
}
}
if (innerTextIncludesATarget) {
reason.push("target is in innerText")
// The issue is here -- I'm not checking the text, only the sub - spans
if (element.children.length === 0) {
reason.push("no children")
element.innerHTML = element.innerHTML.replace(reg, replaceWith);
reason.push("replaced")
return [false, reason]; // don't visit children if we've already replaced the string
}
if (debugPrint) { console.info({ element, innerHTML: element.innerHTML, innerText: element.innerText, filter: true, reason }) }
return [true, reason]
}
return [true, reason]
}
@DeanVanGreunen Текстовые узлы имеют тип, а именно: Node.TEXT_NODE.
Можете ли вы опубликовать свой текущий код? Таким образом, мы можем указать, где вы ошиблись.
@MarkBaijens, спасибо, только что сделал
Пролеты будут добавляться постепенно — все работает с начальной загрузкой, но дополнительные пролеты терпят неудачу, потому что они не идентифицируются с бездетным элементом. Хакерский обходной путь состоял бы в том, чтобы удалить все диапазоны, а затем снова добавить их (с новыми)
У меня есть бездетное ограничение, чтобы избежать вложенных интервалов, но я мог бы ослабить это ограничение
Я думаю, что стоило бы полностью избавиться от этого кода и заменить его. Современные браузеры имеют возможности делать это с гораздо меньшим и более понятным кодом, как вы можете видеть в ответе Unmitigated.
Сейчас я смотрю на ответ Unmitigated (вижу, как его применить). FMI, как бы вы реструктурировали вещи? updateLabels применяет общие функции маскирования к обходу дома (это полезно, если вы много ходите по дому), visitChildren выполняет обход дома — сейчас я пытаюсь повторно реализовать это с помощью узлов. replaceItemWithTag пытается обрезать дерево вместо обхода всего поддерева при замене отрезков. По своей сути я думаю каждая функция нужна, нет? Существуют ли встроенные методы документа, которые обрабатывают эти действия?
Спасибо @MarkBaijens, я хотел сказать это.
@Schalton Я добавил ответ с рекурсивной функцией. Я думаю, это то, что вы имеете в виду, говоря о ходьбе по дому.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете перебрать childNodes контейнера, чтобы найти все текстовые узлы, а затем использовать replaceWith, чтобы заменить узел новым набором узлов.
const el = document.querySelector('#a');
for (const node of el.childNodes)
if (node.nodeType === Node.TEXT_NODE) {
node.replaceWith(document.createRange().createContextualFragment(
node.textContent.replaceAll('TARGET', '<span class = "target-class">$&</span>')
));
}.target-class { background-color: yellow; }<div id = "a">
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the TARGET
</div>Спасибо за вашу помощь, ключевой момент здесь был document.createRange().createContextualFragment - не похоже, что TS может оценить структуру наследования в DOM, поэтому я переключился с узлов на элементы, это сработало чисто - я добавил (node as Element) для принуждения.
С этим я расширил ответ @Unmitigated, как то же самое можно применить с рекурсивной функцией, чтобы также применить логику к дочерним узлам.
Поэтому я завернул логику в функцию и вызываю ее из функции на дочерних узлах (когда элементы).
wrapTextWithTag(document.querySelector('#a').childNodes, 'TARGET', '<span class = "target-class">$&</span>');
function wrapTextWithTag(nodelist, text, tag) {
for (const node of nodelist) {
if (node.nodeType === Node.TEXT_NODE) {
node.replaceWith(document.createRange().createContextualFragment(
node.textContent.replaceAll(text, tag)
));
} else if (node.nodeType === Node.ELEMENT_NODE) {
wrapTextWithTag(node.childNodes, text, tag);
}
}
}.target-class {
background-color: yellow;
}<div id = "a">
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the TARGET
<div>
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the TARGET
<div>
Here is some text
<span>Here is some more span text</span>
More Text...
<span>contains TARGET</span>
Another Line with the TARGET
</div>
</div>
</div>
Добро пожаловать в мир DOM (объектная модель документа), каждый браузер обрабатывает ее по-своему. текст
Targetхранится как узел, который не имеет типа (теги, такие как div и т. д.)