Выделите текст на основе индекса в textContent

премия

Баунти поступит в самое быстрое решение, как продемонстрировано jsPerf, в последних версиях Firefox, Chrome и Internet Explorer во время тестирования или ответ, наиболее полезный при создании такого решения по моему усмотрению. Mwahahaha!

Я буду в основном удовлетворен решением, которое берет все смещения s и необработанный <span> и добавляет выделение к этому, так что parent.textContent = parent.textContent за которым следует выполнение решения в обновленном списке смещений, будет повторное выделение, но это имеет неблагоприятную временную сложность, поэтому не является предпочтительным.


Связанные вопросы


У меня есть элемент, содержащий только текст, который я хотел бы выделить. У меня также есть массив [startline, startcol, endline, endcol] который, зная длины каждой строки из .textContent, я могу нормализовать [startoffset, endoffset]. Как я могу выделить между каждой парой смещений s?

Эта проблема сложнее, чем кажется:

  • в содержании не гарантируется отсутствие повторов (так что не найти/заменить), и
  • подсветка должна в конечном счете выполняться на уже выделенном тексте, иногда пересекая текст, который уже выделен, и
  • подсветка должна выполняться на основе индекса родительского элемента .textContent.

Определения

  • выделить: поместить подмножество текста из элемента textContent в один или несколько <span class="highlighted"> без изменения значения textContent родительского элемента, так что текст, который подсвечивается n раз, находится в пределах n вложенных <span class="highlighted"> элементы.
  • offset: неотрицательное целое число, представляющее число символов s до некоторой точки (которая находится между двумя символами s).
  • character: экземпляр любого JavaScript дает вам значение в данном индексе строки .textContent (включая пробелы).

MCVE

function highlight(parent, startoff, endoff) {
  // Erm...
  parent.textContent;
}

// Test cases

var starts = [
  5,  44, 0, 50, 6,  100, 99,  50, 51, 52
];
var ends = [
  20, 62, 4, 70, 10, 100, 101, 54, 53, 53
];
for (var i = 0; i < 10; i += 1) {
  highlight(document.getElementById("target"),
            starts[i], ends[i]);
}
#target {
  white-space: pre-wrap;
}
<span id="target">
'Twas brillig, and the slithy toves
  Did gyre and gimble in the wabe:
All mimsy were the borogoves,
  And the mome raths outgrabe.

"Beware the Jabberwock, my son!
  The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
  The frumious Bandersnatch!"

He took his vorpal sword in hand:
  Long time the manxome foe he sought --
So rested he by the Tumtum tree,
  And stood awhile in thought.

And, as in uffish thought he stood,
  The Jabberwock, with eyes of flame,
Came whiffling through the tulgey wood,
  And burbled as it came!

One, two! One, two! And through and through
  The vorpal blade went snicker-snack!
He left it dead, and with its head
  He went galumphing back.

"And, has thou slain the Jabberwock?
  Come to my arms, my beamish boy!
O frabjous day! Callooh! Callay!'
  He chortled in his joy.

'Twas brillig, and the slithy toves
  Did gyre and gimble in the wabe;
All mimsy were the borogoves,
  And the mome raths outgrabe.
</span>

Ответ 1

Сделайте нормализацию для начала/окончания позиций, чтобы избежать перекрытия.

  1. Объединить начальные и конечные позиции в один список с противоположными значениями (например, -1 и 1)
  2. Сортировка списка по положению, а затем - по значению маркера (и на основе сортировки второго уровня вы можете либо различать последовательные диапазоны, либо объединять их)
  3. пройдите список позиций и добавьте маркер текущей позиции в текущую сумму; как только он "0" - это означает, что вы только что нашли окончание для некоторых наборов вложенных/пересекающихся разделов;

Таким образом вы получите позиции для выделения без вложенных/перекрывающихся диапазонов.

Чтобы заменить текстовый узел на сочетание текстовых узлов и элементов HTML (например, <span>), documentFragment и .replaceChild() помогут:

let starts = [
    5,  44, 0, 50, 6,  100, 99,  50, 51, 52
];
let ends = [
    20, 62, 4, 70, 10, 100, 101, 54, 53, 53
];

let positions = [];
let normalizedPositions = [];
starts.forEach(function(position) {
    positions.push({position, value: 1});
});
ends.forEach(function(position) {
    positions.push({position, value: -1});
});
positions = positions.sort(function(a, b) {
    return a.position - b.position || 
        b.value - a.value
});

var currentSection = {from: 0, counter: 0};

for(position of positions) {
    if (!currentSection.counter) {
        if (position.value === -1) {
            throw 'inconsistent boundaries: closing before opening ${position.position}';
        }
        currentSection.from = position.position;  
    }
    currentSection.counter += position.value;

    if (!currentSection.counter) { 
        normalizedPositions.push({
            from: currentSection.from, 
            to: position.position
        });
    }
}
if (currentSection.counter) {
    throw "last section has not been closed properly";   
}


let parentNode = document.querySelector('p');
let textNodeToReplace = parentNode.childNodes[0];
let sourceText = textNodeToReplace.nodeValue;

let documentFragment = document.createDocumentFragment();
let withoutHighlightingStart = 0;

normalizedPositions.forEach(function (highlightRange) {
    if (highlightRange.from> withoutHighlightingStart) {
      let notHighlighted = createTextNode(sourceText.slice(withoutHighlightingStart, highlightRange.from));
      documentFragment.appendChild(notHighlighted);
    }
    let highlighted = createHighlighted(sourceText.slice(highlightRange.from, highlightRange.to));
    documentFragment.appendChild(highlighted);
    withoutHighlightingStart = highlightRange.to;
});
let lastNotHighlighted = createTextNode(sourceText.slice(withoutHighlightingStart));
documentFragment.appendChild(lastNotHighlighted);

parentNode.replaceChild(documentFragment, textNodeToReplace);

function createTextNode(str) {
   return document.createTextNode(str);
}

function createHighlighted(str) {
   let span = document.createElement('span');
   span.classList.add('highlight');
   span.appendChild(createTextNode(str));
   return span;
}
.highlight {
    background-color: yellow;
    color: dark-blue;
}
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>