Para aplicar un mapa de reemplazos sin cambiar el texto recién insertado, busca solo en la cadena original y crea un resultado aparte. Esta guía usa JavaScript en Safari en un Mac para actualizar un pasaje breve con reemplazos literales y predecibles.

Supongamos que una tarea de edición en una computadora requiere cambiar blue por green y green por gold. Un blue original debería convertirse en green una sola vez. Un green original debería convertirse en gold. El siguiente método contempla esa distinción, las frases superpuestas, el texto entre comillas y los valores de reemplazo que contienen signos de puntuación.

¿Por qué un bucle de reemplazo cambia el texto dos veces?

Un bucle que aplica cada regla al resultado de la regla anterior crea una cascada. Con esas dos reglas de color, la primera operación inserta green; la segunda operación encuentra entonces esa palabra recién insertada y la cambia por gold. Reordenar las reglas puede resolver un ejemplo, pero no define un comportamiento fiable para todos los mapas.

El método replace() de JavaScript devuelve una cadena nueva y deja intacta la cadena de entrada. La cascada se produce cuando las llamadas posteriores reciben la cadena devuelta. Por tanto, la tarea necesita una regla clara: todas las coincidencias deben proceder de la entrada original. Consulta la documentación de String.prototype.replace() de MDN.

Para la entrada escrita The blue cup is beside the green cup., el resultado deseado es The green cup is beside the gold cup. Las dos palabras de color originales cambian, mientras que el green insertado permanece intacto. Esta es la transformación que realiza la implementación siguiente.

¿Qué debería ocurrir cuando se superponen las claves de reemplazo?

Evitar las cascadas deja pendiente otra decisión: ¿qué clave gana cuando varias coinciden con el mismo texto de origen? Para esta tarea de edición, usa la siguiente política explícita. Son decisiones de diseño para este ejemplo, no reglas automáticas proporcionadas por JavaScript.

  • Recorre la entrada original de izquierda a derecha.
  • En cada posición, elige la clave coincidente más larga.
  • Consume toda esa coincidencia del texto de origen antes de continuar.
  • Inserta literalmente su valor asignado, sin buscar en ese valor.
  • Copia exactamente el texto que no coincide.
  • Respeta exactamente las mayúsculas, las minúsculas y los espacios.

Por ejemplo, si tanto blue como blue vase son claves, la frase blue vase gana en la misma posición inicial. Una coincidencia que empieza antes tiene prioridad sobre una coincidencia más larga que empieza después. Una vez consumido un tramo de origen, ninguna coincidencia superpuesta puede volver a usarlo.

Esta política es adecuada para un conjunto pequeño de ediciones literales de frases: las frases específicas tienen prioridad sobre sus prefijos más cortos. También hace que el resultado no dependa del orden en que se hayan introducido las claves de distintas longitudes. Si tu tarea requiere otra política para las superposiciones, decídela antes de aplicar el mapa.

¿Cómo puedes ejecutar la transformación en un Mac?

Safari incluye una consola de JavaScript, así que este ejemplo no requiere instalar ninguna aplicación adicional. Activa Web Inspector en Safari → Settings… → Advanced → Show features for web developers. Después abre Develop → Show Web Inspector, o pulsa Option-Command-I. Estos pasos siguen la documentación de configuración de Web Inspector de WebKit.

Abre una página en blanco escribiendo about:blank en el campo de dirección de Safari; después abre el inspector y selecciona la pestaña Console. Pega el programa completo de abajo en el indicador de la consola y ejecútalo. WebKit documenta que el indicador evalúa JavaScript y muestra los resultados en la consola. Consulta Interfaz de Web Inspector.

El ejemplo es un programa completo para el navegador, con texto sintético y sin importaciones. Su función externa mantiene las declaraciones en el ámbito local, lo que te permite volver a pegar y ejecutar el bloque después de editar el ejemplo.

(() => {
  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);
})();

El texto registrado se muestra abajo. Queda un espacio final después de Note: porque la regla elimina solo (draft), conservando el espacio anterior.

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

Para adaptar el ejemplo, edita input y los pares dentro de new Map(...). Cada par contiene la cadena de origen exacta seguida de su reemplazo. Usa una entrada por clave: los mapas de JavaScript contienen claves únicas, así que repetir una clave actualiza su valor en lugar de crear otra regla que compita con ella. Consulta la referencia de Map de MDN.

Por qué el texto insertado permanece intacto

El cursor llamado position avanza solo por input. La búsqueda usa input.startsWith(key, position), que comprueba si hay una cadena en una posición especificada y distingue entre mayúsculas y minúsculas, según se documenta en la referencia de startsWith() de MDN. El arreglo de salida nunca se usa como objetivo de búsqueda.

Cuando ninguna regla coincide, slice(position, position + 1) copia la siguiente unidad de origen. El índice final de slice es exclusivo, y el método no modifica la cadena original. Estas características documentadas permiten que la función conserve el contenido que no coincide y construya el resultado por separado. Consulta la referencia de slice() de MDN.

El ejemplo inserta green bowl en lugar de blue vase. Aunque la frase insertada contiene otra clave, green, la función ya ha avanzado más allá de la frase original. Nunca examina green bowl. Si más adelante aparece un green original en la entrada, este sí se transforma en gold.

Cómo funcionan los valores literales y la eliminación

Esta implementación añade los valores de reemplazo directamente. Por tanto, $& sigue siendo $&, y los corchetes o paréntesis en las claves son caracteres normales. Una cadena de reemplazo vacía elimina el tramo de origen coincidente. Esta función no usa expresiones regulares ni interpreta plantillas de reemplazo.

Se rechazan las claves vacías porque no consumen texto de origen; permitir una impediría que la rama de coincidencia avanzara. También se rechazan los valores que no sean cadenas antes de iniciar la transformación, para que un valor equivocado se haga visible en lugar de convertirse u omitirse silenciosamente. Un mapa vacío devuelve la entrada sin cambios, y una entrada vacía devuelve una cadena vacía.

¿Cómo deberías comprobar el resultado antes de copiarlo?

Usa casos pequeños que permitan distinguir el comportamiento que buscas. Los resultados esperados siguientes se derivan de la implementación de arriba. A menos que se indique lo contrario, usan el mapa del ejemplo.

Comprobación Entrada Resultado esperado
El texto insertado permanece intacto blue green green gold
Gana la frase más larga blue vase green bowl
Los signos de puntuación del reemplazo siguen siendo literales [tag] $&
Se respetan las mayúsculas y minúsculas Blue Blue
Se buscan coincidencias dentro de otras cadenas blueprint greenprint
Se conservan las comillas \"blue\" \"green\"
Mapa vacío blue blue

La función de reemplazo y estos comportamientos se comprobaron en Node.js 20.19.2, junto con la eliminación, la entrada vacía, las coincidencias superpuestas y el rechazo de mapas no válidos. Esto verifica la transformación de JavaScript en ese entorno de ejecución; los pasos de la interfaz de Safari proceden de la documentación de WebKit.

Presta especial atención a la comprobación de subcadenas. Esta función no deduce límites entre palabras ni corrige la gramática. Una clave puede coincidir dentro de una palabra más larga, y eliminar texto puede dejar espacios adicionales. Para una tarea de edición de frases, revisa los caracteres circundantes y concreta el mapa tanto como lo requiera el cambio deseado.

Distingue también entre aplicar la transformación una vez y aplicarla varias veces. Ejecutar la función una vez sobre el texto original evita las cascadas durante esa ejecución. Volver a pasar su resultado a la función inicia una nueva transformación, por lo que las palabras insertadas anteriormente pueden coincidir entonces. Al volver a ejecutar una edición, usa el input original como entrada.

¿Puede una sola llamada a replace de JavaScript hacer lo mismo?

Sí. Para un conjunto pequeño y fijo de claves sencillas, una sola expresión regular global y una función de reemplazo pueden proporcionar el mismo comportamiento sin cascadas. La especificación de ECMAScript recopila las coincidencias de la cadena de origen y luego construye el resultado del reemplazo. Consulta el algoritmo especificado para el reemplazo mediante expresiones regulares.

Este ejemplo independiente y completo maneja las dos claves de color fijas:

(() => {
  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.
})();

El indicador g hace que la expresión reemplace todas las coincidencias. Devolver valores desde una función de reemplazo también evita patrones de cadenas de reemplazo como $&, que de otro modo representa la subcadena coincidente. MDN documenta explícitamente ambos comportamientos en su referencia de replace().

Para un mapa variable que contenga signos de puntuación, construir una expresión regular añade requisitos. Las claves deben escaparse para que se interpreten como patrones literales, y las alternativas superpuestas necesitan una prioridad explícita. JavaScript proporciona RegExp.escape() para construir patrones literales, pero MDN señala que puede no estar disponible en navegadores antiguos. Consulta la documentación de RegExp.escape() de MDN.

Para la tarea de edición de frases en una computadora que se describe aquí, el analizador literal mantiene claras esas decisiones: lee el texto original, elige la clave más larga en cada posición y añade cada reemplazo sin modificarlo. Ejecútalo sobre el pasaje original, revisa la redacción y los espacios resultantes y luego copia el texto final en tu documento.

Fuentes