Desafio: Ao substituir texto usando métodos simples como document.execCommand, a seleção pode ser perdida ao interagir com outras partes da página. Além disso, ao lidar com texto rico que contém múltiplos estilos, uma frase pode ser dividida em vários nós de texto, dificultando a correspondência e substituição direta.
Abordagem 1: Uso de Seleção e ExecCommand
Esta abordagem utiliza a API de seleção do navegador para inserir novo texto, mas é suscetível à perda de foco.
// Função para substituir texto selecionado no editor
function substituirTextoSelecionado(conteudoNovo) {
const selecao = window.getSelection();
const intervalo = selecao.rangeCount > 0 ? selecao.getRangeAt(0) : null;
if (intervalo) {
document.execCommand('insertText', false, conteudoNovo);
}
}
// Função para capturar texto selecionado (puro)
function aoLargarMouse() {
const textoSelecionado = window.getSelection().toString();
this.$emit('aoLargarMouse', textoSelecionado);
}
Abordagme 2: Travessia de Nós DOM
Ao percorrer os nós do DOM, é possível tentar substituir o texto diretamente, mas isso falha quando o texto está estilizado e fragmentado em múltiplos elementos.
// Exemplo de travessia para substituir texto em conteúdo rico
const analisador = new DOMParser();
const documento = analisador.parseFromString(this.conteudoHtml, 'text/html');
const percorredor = document.createTreeWalker(documento.body, NodeFilter.SHOW_TEXT, null, false);
while (percorredor.nextNode()) {
const noAtual = percorredor.currentNode;
if (noAtual.nodeValue.includes(this.textoAlvo)) {
noAtual.nodeValue = noAtual.nodeValue.replace(this.textoAlvo, this.textoNovo);
}
}
const htmlModificado = documento.body.innerHTML;
this.$refs.editorFroala.setHtml(htmlModificado);
Abordagem 3: Uso de Árvores Diff e DOM
Esta é a abordagem mais eficaz, envolvendo a geração de um diff entre textos e a aplicação cuidadosa no conteúdo rico, preservando a estrutura DOM.
Funções Utilitárias
Primeiro, definimos funções para calcular o diff, converter texto rico em puro, e realizar correspondência difusa entre strings.
// Função para calcular a diferença entre duas strings usando o algoritmo LCS
function calcularDiferenca(stringOriginal, stringNova) {
const comprimentoOriginal = stringOriginal.length;
const comprimentoNovo = stringNova.length;
const tabela = Array(comprimentoOriginal + 1).fill().map(() => Array(comprimentoNovo + 1).fill(0));
for (let i = 1; i <= comprimentoOriginal; i++) {
for (let j = 1; j <= comprimentoNovo; j++) {
if (stringOriginal[i - 1] === stringNova[j - 1]) {
tabela[i][j] = tabela[i - 1][j - 1] + 1;
} else {
tabela[i][j] = Math.max(tabela[i - 1][j], tabela[i][j - 1]);
}
}
}
let i = comprimentoOriginal;
let j = comprimentoNovo;
const diferenca = [];
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && stringOriginal[i - 1] === stringNova[j - 1]) {
diferenca.unshift({ tipo: 'igual', valor: stringOriginal[i - 1] });
i--;
j--;
} else if (j > 0 && (i === 0 || tabela[i][j - 1] >= tabela[i - 1][j])) {
diferenca.unshift({ tipo: 'adicionado', valor: stringNova[j - 1] });
j--;
} else if (i > 0 && (j === 0 || tabela[i][j - 1] < tabela[i - 1][j])) {
diferenca.unshift({ tipo: 'removido', valor: stringOriginal[i - 1] });
i--;
}
}
return diferenca;
}
// Converter texto rico para texto puro
function converterRichParaPlain(entradaHtml) {
const parser = new DOMParser();
const documento = parser.parseFromString(entradaHtml, 'text/html');
return documento.body.textContent || '';
}
// Correspondência difusa entre strings, ignorando espaços
function correspondenciaDifusa(textoBase, textoBusca) {
const textoBuscaLimpo = textoBusca.replace(/\s/g, '');
if (textoBase.length < textoBuscaLimpo.length) {
console.error('O texto base não pode ser menor que o texto de busca');
return null;
}
let cursorBase = 0;
let cursorBusca = 0;
let textoCorrespondido = '';
while (cursorBase < textoBase.length && cursorBusca < textoBuscaLimpo.length) {
if (/\s/.test(textoBase[cursorBase])) {
textoCorrespondido += textoBase[cursorBase];
cursorBase++;
} else if (textoBase[cursorBase] === textoBuscaLimpo[cursorBusca]) {
textoCorrespondido += textoBase[cursorBase];
cursorBase++;
cursorBusca++;
} else {
cursorBase++;
textoCorrespondido = '';
cursorBusca = 0;
}
}
if (cursorBusca >= textoBuscaLimpo.length) {
return textoCorrespondido;
} else {
console.error('Falha na correspondência');
return null;
}
}
Função Principal para Substituição no Rich Text
A função substituirEmConteudoRico aplica o diff ao conteúdo rico, percorrendo os nós DOM para substituir o texto com precisão.
// Substituir texto em conteúdo rico usando diff e posição
function substituirEmConteudoRico(htmlOriginal, diffArray, posicaoInicio, posicaoFim) {
const containerTemp = document.createElement('div');
containerTemp.innerHTML = htmlOriginal;
let posicaoCorrente = 0;
let indiceDiff = 0;
function processarNo(no) {
if (posicaoCorrente > posicaoFim) return;
if (no.nodeType === Node.TEXT_NODE) {
const conteudoNo = no.textContent;
if (posicaoCorrente + conteudoNo.length <= posicaoInicio) {
posicaoCorrente += conteudoNo.length;
return;
}
let novoConteudo = '';
let indiceNo = 0;
while (posicaoCorrente < posicaoInicio && indiceNo < conteudoNo.length) {
novoConteudo += conteudoNo[indiceNo];
posicaoCorrente++;
indiceNo++;
}
while (posicaoCorrente <= posicaoFim && indiceNo < conteudoNo.length && indiceDiff < diffArray.length) {
const itemDiff = diffArray[indiceDiff];
if (itemDiff.tipo === 'igual') {
novoConteudo += itemDiff.valor;
indiceNo++;
posicaoCorrente++;
indiceDiff++;
} else if (itemDiff.tipo === 'removido') {
indiceNo++;
posicaoCorrente++;
indiceDiff++;
} else if (itemDiff.tipo === 'adicionado') {
novoConteudo += itemDiff.valor;
indiceDiff++;
}
}
while (indiceNo < conteudoNo.length) {
novoConteudo += conteudoNo[indiceNo];
indiceNo++;
posicaoCorrente++;
}
no.textContent = novoConteudo;
} else if (no.nodeType === Node.ELEMENT_NODE) {
Array.from(no.childNodes).forEach(processarNo);
}
}
processarNo(containerTemp);
return containerTemp.innerHTML;
}
Exemplo de Uso Integrado
A seguir, um exemplo demonstrando como as funções são combinadas para substituir texto em um conteúdo rico.
const htmlRico = '<p>Este é um exemplo de <strong>texto rico</strong> com <em>estilos variados</em>.</p>';
const trechoAlvo = 'texto rico com estilos';
const trechoNovo = 'conteúdo formatado com modificações';
const textoPuro = converterRichParaPlain(htmlRico);
const textoCorrespondido = correspondenciaDifusa(textoPuro, trechoAlvo);
const posicaoEncontrada = textoPuro.indexOf(textoCorrespondido);
const diferenca = calcularDiferenca(textoCorrespondido, trechoNovo);
const htmlFinal = substituirEmConteudoRico(
htmlRico,
diferenca,
posicaoEncontrada,
posicaoEncontrada + textoCorrespondido.length
);
console.log(htmlFinal);