Para aplicar um mapa de substituição sem alterar o texto recém-inserido, pesquise apenas na string original e crie um resultado separado. Este guia usa JavaScript no Safari em um Mac para atualizar um pequeno trecho com substituições literais e previsíveis.

Suponha que uma tarefa de edição no computador exija trocar blue por green e green por gold. Um blue original deve se tornar green uma única vez. Um green original deve se tornar gold. A abordagem a seguir trata essa distinção, frases sobrepostas, texto entre aspas e valores de substituição que contêm pontuação.

Por que um loop de substituição altera o texto duas vezes?

Um loop que aplica cada regra à saída da regra anterior cria um efeito cascata. Com essas duas regras de cores, a primeira operação insere green; a segunda operação então encontra essa palavra recém-inserida e a altera para gold. Reordenar as regras pode resolver um exemplo, mas não define um comportamento confiável para todos os mapas.

O replace() do JavaScript retorna uma nova string e não altera sua entrada. O efeito cascata acontece quando as chamadas seguintes recebem a string retornada. Portanto, a tarefa precisa de uma regra clara: todas as correspondências devem vir da entrada original. Consulte a documentação do String.prototype.replace() da MDN.

Para a entrada escrita The blue cup is beside the green cup., o resultado desejado é The green cup is beside the gold cup. As duas palavras de cor originais são alteradas, enquanto o green inserido permanece intacto. Essa é a transformação realizada pela implementação abaixo.

O que deve acontecer quando as chaves de substituição se sobrepõem?

Impedir efeitos cascata deixa outra decisão: qual chave prevalece quando várias correspondem ao mesmo texto de origem? Para esta tarefa de edição, use a seguinte política explícita. Essas são escolhas de projeto para o exemplo, e não regras fornecidas automaticamente pelo JavaScript.

  • Percorra a entrada original da esquerda para a direita.
  • Em cada posição, escolha a chave correspondente mais longa.
  • Consuma toda essa correspondência na origem antes de continuar.
  • Insira o valor associado literalmente, sem pesquisá-lo.
  • Copie exatamente o texto sem correspondência.
  • Faça a correspondência exata de maiúsculas e minúsculas e dos espaços.

Por exemplo, se blue e blue vase forem chaves, a frase blue vase prevalece na mesma posição inicial. Uma correspondência que começa antes tem prioridade sobre uma correspondência mais longa que comece depois. Depois que um trecho da origem é consumido, uma correspondência sobreposta não pode reutilizá-lo.

Essa política é adequada para um conjunto pequeno de edições literais de frases: frases específicas têm prioridade sobre seus prefixos mais curtos. Ela também torna a saída independente da ordem em que as chaves de tamanhos diferentes foram inseridas. Se sua tarefa exigir outra política para sobreposições, decida isso antes de aplicar o mapa.

Como executar a transformação em um Mac?

O Safari oferece um console JavaScript, então este exemplo não exige a instalação de nenhum aplicativo adicional. Ative o Web Inspector em Safari → Settings… → Advanced → Show features for web developers. Em seguida, abra Develop → Show Web Inspector ou pressione Option-Command-I. Estas etapas seguem a documentação de configuração do Web Inspector do WebKit.

Abra uma página em branco digitando about:blank no campo de endereço do Safari, depois abra o inspetor e selecione a aba Console. Cole o programa completo abaixo no prompt do console e execute-o. O WebKit documenta que o prompt executa JavaScript e exibe os resultados no console. Consulte Interface do Web Inspector.

O exemplo é um programa completo para navegador, com texto sintético e sem importações. A função externa mantém as declarações no escopo local, permitindo colar e executar o bloco novamente após editar o exemplo.

(() => {
  function applyReplacementMap(input, replacements) {
    if (typeof input !== "string" || !(replacements instanceof Map)) {
      throw new TypeError("Use a string and a Map.");
    }

    const entries = [...replacements];

    for (const [key, value] of entries) {
      if (
        typeof key !== "string" ||
        key.length === 0 ||
        typeof value !== "string"
      ) {
        throw new TypeError(
          "Keys must be nonempty strings; values must be strings."
        );
      }
    }

    entries.sort((a, b) => b[0].length - a[0].length);

    const output = [];
    let position = 0;

    while (position < input.length) {
      const entry = entries.find(([key]) =>
        input.startsWith(key, position)
      );

      if (entry) {
        output.push(entry[1]);
        position += entry[0].length;
      } else {
        output.push(input.slice(position, position + 1));
        position += 1;
      }
    }

    return output.join("");
  }

  const replacements = new Map([
    ["blue", "green"],
    ["green", "gold"],
    ["blue vase", "green bowl"],
    ["[tag]", "$&"],
    ["(draft)", ""]
  ]);

  const input =
    'The blue vase is beside the green cup. Label: "[tag]". Note: (draft)';

  const result = applyReplacementMap(input, replacements);
  console.log(result);
})();

O texto registrado é mostrado abaixo. Permanece um espaço final após Note: porque a regra remove apenas (draft), preservando o espaço anterior.

The green bowl is beside the gold cup. Label: \"$&\". Note: 

Para adaptar o exemplo, edite input e os pares dentro de new Map(...). Cada par contém a string de origem exata seguida de sua substituição. Use uma entrada por chave: os mapas JavaScript mantêm chaves únicas, então repetir uma chave atualiza seu valor em vez de criar outra regra concorrente. Consulte a referência de Map da MDN.

Por que o texto inserido permanece intacto

O cursor chamado position avança apenas por input. A correspondência usa input.startsWith(key, position), que verifica se há uma string em uma posição específica e diferencia maiúsculas de minúsculas, conforme documentado na referência de startsWith() da MDN. O array de saída nunca é usado como alvo da pesquisa.

Quando nenhuma regra corresponde, slice(position, position + 1) copia a próxima unidade da origem. O segundo índice de fatia é exclusivo, e o fatiamento não modifica a string original. Essa semântica documentada permite que a função preserve o conteúdo sem correspondência enquanto cria o resultado separadamente. Consulte a referência de slice() da MDN.

O exemplo insere green bowl no lugar de blue vase. Embora essa frase inserida contenha outra chave, green, a função já avançou além da frase original. Ela nunca examina green bowl. Um green original que apareça mais adiante na entrada ainda é alterado para gold.

Como funcionam os valores literais e a exclusão

Esta implementação acrescenta os valores de substituição diretamente. Assim, $& permanece $&, e colchetes ou parênteses nas chaves são caracteres comuns. Uma string de substituição vazia exclui o trecho correspondente da origem. Nesta função não há expressões regulares nem interpretação de modelos de substituição.

Chaves vazias são rejeitadas porque não consomem texto da origem; permitir uma delas impediria o avanço do ramo de correspondência. Valores que não sejam strings também são rejeitados antes do início da transformação, tornando visível um valor incorreto em vez de convertê-lo ou omiti-lo silenciosamente. Um mapa vazio retorna a entrada sem alterações, e uma entrada vazia retorna uma string vazia.

Como verificar o resultado antes de copiá-lo?

Use casos pequenos que diferenciem o comportamento desejado. Os resultados esperados abaixo são derivados da implementação acima. Salvo indicação em contrário, eles usam o mapa de exemplo.

Verificação Entrada Resultado esperado
O texto inserido permanece inalterado blue green green gold
A frase mais longa prevalece blue vase green bowl
A pontuação da substituição permanece literal [tag] $&
A correspondência diferencia maiúsculas e minúsculas Blue Blue
A correspondência inclui substrings blueprint greenprint
As aspas são preservadas \"blue\" \"green\"
Mapa vazio blue blue

A função de substituição e esses comportamentos foram verificados no Node.js 20.19.2, junto com a exclusão, a entrada vazia, as correspondências sobrepostas e a rejeição de mapas inválidos. Isso verifica a transformação JavaScript nesse ambiente de execução; as etapas da interface do Safari vêm da documentação do WebKit.

Preste atenção especial à verificação de substring. Esta função não infere limites de palavras nem corrige a gramática. Uma chave pode corresponder a uma parte de uma palavra mais longa, e remover texto pode deixar espaços extras. Em uma tarefa de edição de frases, inspecione os caracteres ao redor e torne o mapa tão específico quanto a alteração desejada exigir.

Também diferencie uma aplicação de aplicações repetidas. Executar a função uma vez no texto original evita efeitos cascata durante essa execução. Passar o resultado novamente para a função inicia uma nova transformação, então palavras inseridas anteriormente podem corresponder. Mantenha o input original como origem ao executar a edição novamente.

Uma única chamada de replace do JavaScript consegue fazer o mesmo?

Sim. Para um conjunto pequeno e fixo de chaves simples, uma única expressão regular global e uma função de retorno podem oferecer o mesmo comportamento sem efeito cascata. A especificação ECMAScript reúne as correspondências da string de origem e, em seguida, constrói o resultado da substituição. Consulte o algoritmo especificado para substituição com expressões regulares.

Este exemplo separado e completo trata das duas chaves fixas de cores:

(() => {
  const replacements = new Map([
    ["blue", "green"],
    ["green", "gold"]
  ]);

  const input = "The blue cup is beside the green cup.";
  const result = input.replace(
    /blue|green/g,
    (word) => replacements.get(word)
  );

  console.log(result);
  // The green cup is beside the gold cup.
})();

A flag g faz a expressão substituir todas as correspondências. Retornar valores de uma função de retorno também evita padrões de string de substituição como $&, que, de outro modo, representa a substring correspondente. A MDN documenta explicitamente ambos os comportamentos em sua referência de replace().

Para um mapa variável que contenha pontuação, construir uma expressão regular introduz requisitos adicionais. As chaves precisam ser escapadas como padrões literais, e as alternativas sobrepostas precisam de uma prioridade explícita. O JavaScript oferece RegExp.escape() para construir padrões literais, mas a MDN observa que navegadores mais antigos podem não oferecer suporte a isso. Consulte a documentação de RegExp.escape() da MDN.

Para a tarefa de edição de frases no computador apresentada aqui, o analisador literal deixa essas decisões explícitas: lê o texto original, escolhe a chave mais longa em cada posição e acrescenta cada substituição sem alterações. Execute-o no trecho original, confira o texto e os espaços resultantes e, em seguida, copie o texto final para o documento.

Fontes