Различие между текстом и интервалами

Я хочу обновить 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]
}

Добро пожаловать в мир DOM (объектная модель документа), каждый браузер обрабатывает ее по-своему. текст Target хранится как узел, который не имеет типа (теги, такие как div и т. д.)

Dean Van Greunen 25.07.2023 14:26

@DeanVanGreunen Текстовые узлы имеют тип, а именно: Node.TEXT_NODE.

Mark Baijens 25.07.2023 14:29

Можете ли вы опубликовать свой текущий код? Таким образом, мы можем указать, где вы ошиблись.

Mark Baijens 25.07.2023 14:30

@MarkBaijens, спасибо, только что сделал

Schalton 25.07.2023 14:45

Пролеты будут добавляться постепенно — все работает с начальной загрузкой, но дополнительные пролеты терпят неудачу, потому что они не идентифицируются с бездетным элементом. Хакерский обходной путь состоял бы в том, чтобы удалить все диапазоны, а затем снова добавить их (с новыми)

Schalton 25.07.2023 14:48

У меня есть бездетное ограничение, чтобы избежать вложенных интервалов, но я мог бы ослабить это ограничение

Schalton 25.07.2023 14:49

Я думаю, что стоило бы полностью избавиться от этого кода и заменить его. Современные браузеры имеют возможности делать это с гораздо меньшим и более понятным кодом, как вы можете видеть в ответе Unmitigated.

Mark Baijens 25.07.2023 14:56

Сейчас я смотрю на ответ Unmitigated (вижу, как его применить). FMI, как бы вы реструктурировали вещи? updateLabels применяет общие функции маскирования к обходу дома (это полезно, если вы много ходите по дому), visitChildren выполняет обход дома — сейчас я пытаюсь повторно реализовать это с помощью узлов. replaceItemWithTag пытается обрезать дерево вместо обхода всего поддерева при замене отрезков. По своей сути я думаю каждая функция нужна, нет? Существуют ли встроенные методы документа, которые обрабатывают эти действия?

Schalton 25.07.2023 15:05

Спасибо @MarkBaijens, я хотел сказать это.

Dean Van Greunen 25.07.2023 15:40

@Schalton Я добавил ответ с рекурсивной функцией. Я думаю, это то, что вы имеете в виду, говоря о ходьбе по дому.

Mark Baijens 25.07.2023 15:43
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
10
73
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Вы можете перебрать 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) для принуждения.

Schalton 26.07.2023 12:49

С этим я расширил ответ @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>

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