¿Cuál es el algoritmo correcto para invertir cursiva en un texto mixto?

8

La motivación de la pregunta se describió en la sección a continuación. Hay muchas formas de hacer que el texto esté en cursiva , por lo tanto, tal vez, haya más de un buen " algoritmo de intercambio de cursiva ". El problema revela algunas dificultades adicionales en un código XHTML, y el uso de la <i>etiqueta, que debe ser equilibrado . Ejemplo:

 <!-- original text: -->
 <p id="p1"><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
 <!-- same text, swapping italics: -->
 <p id="p2">Several more <i>Homo sapiens</i> fossils were discovered.</p>

Entonces, se ve así,

  1. Se descubrieron varios fósiles más del Homo sapiens .

  2. Se descubrieron varios fósiles más del Homo sapiens .


Introducción y discusión de algoritmos

Para la " solución de diseño ", el algoritmo más simple es verificar la font-stylepropiedad CSS de todos los bloques de texto e invertirlos con jQuery:

$('#myFragment *').each(function(){
   if ($(this).css('font-style')=='italic')
       $(this).css('font-style','normal')
    else
       $(this).css('font-style','italic')        
}); 

Pero este algoritmo no sobrevive a una prueba un poco más compleja,

 <p id="p3"><b><i>F</i>RAGMENT <big><i>with italics</i> and </big> withOUT.</b></p>

El segundo algoritmo más simple es para una solución concreta , y se utilizó en la sección "Ejemplos". Tiene dos pasos:

  1. encierra el fragmento XHTML en cursiva;
  2. invertir abrir / cerrar etiquetas en cursiva (ej. </i>a <i>).

Es decir, escribiendo con Javascript,

var s = '... a fragment of XHTML content ...';
s = '<i>'+
   s.replace(/<(\/?)i>/mg, 
          function (m,p1){
              return p1? '<i>': '</i>';
          }
   ) +
   '</i>';  

Pero tampoco sobrevive a la segunda prueba, perdiendo el equilibrio de las etiquetas ... El algoritmo "corregido" se ejecuta (!), Pero no es portátil, ni rápido ni elegante. Se muestra aquí , y en la sección de ejemplos a continuación.

¡El punto!

Entonces la pregunta es,

Hay un algoritmo simple, bueno y genérico (utilizable en cualquier navegador y portátil a otros idiomas)? ¿Conoces otro "algoritmo de intercambio de cursiva"?

PD: "genérico" en el sentido de que incluso traduzco tu algoritmo a XSLT. El algoritmo debe producir código XHTML direccionalmente equilibrado (sin una caja negra intermedia como Tidy).


Motivaciones

Necesito portar el "algoritmo de intercambio de cursiva" a editores de texto, analizadores de servidores, etc. En todos los casos puedo "normalizar la entrada" (y salida) mediante XHTML estándar y <i>etiqueta.

Estoy analizando el texto XHTML de libros en prosa y artículos científicos, exportados desde diferentes orígenes y estilos ... La mayoría de los textos se exportan como "texto normal", pero muchos títulos (por ejemplo, título de artículo, título de capítulo) y, a veces , un capítulo completo o un cuadro de texto completo (por ejemplo, resumen del artículo) están estilizados en cursiva. Todos estos "estilizados con cursiva" deben invertirse. Casos tipicos:

  • Transforme el original "cursiva de todos los capítulos" en "texto normal de todos los capítulos": vea este caso , donde en un libro de aproximadamente 300 páginas, 8 de los 25 capítulos deben invertirse.

  • Comillas en cursiva, resúmenes, etc. Vea este ejemplo . Necesita volver a la normalidad, pero sin perder las palabras de énfasis.

  • La escritura de nombres binomiales de especies , en textos científicos, generalmente está escrita en cursiva (o invertida, en una fuente diferente de la utilizada para "texto normal"). Cientos de títulos en cursiva (de artículos y secciones de artículos) de artículos exportados por XHTML deben invertirse en mi lugar de trabajo. PD: vea el ejemplo del comienzo de la pregunta ("Varios Homo sapiens más ...").

También necesito traducir el algoritmo genérico (¡de su respuesta!) A una biblioteca XSLT , donde no existe una "corrección de equilibrio de etiqueta".

Ejemplos

Implementando en Javascript y PHP un "algoritmo de intercambio de cursiva" no genérico . Una genérica necesita un "algoritmo de intercalación XML" general ... Aquí uso las correcciones del navegador (DOM) y Tidy, como alternativa a la "intercalación".

Javascript

Se ejecuta con entradas complejas (!). Ilustrando, mediante una implementación de jQuery :

 var s = $('#sample1').html(); // get original html text fragment

 // INVERSION ALGORITHM: add and remove italics.
 s = "<i>"+
     s.replace(/<(\/?)i>/mg, 
               function (m,p1){
                   return p1? '<i>': '</i>';
               }
     ) +
     "</i>";  // a not-well-formed-XHTML, but it is ok...
 $('#inverted').html(s); // ...the DOM do all rigth!
 // minor corrections, for clean empties:
 s = $('#inverted').html();
 s = s.replace(/<([a-z]+)>(\s*)<\/\1>/mg,'$2'); // clean
 s = s.replace(/<([a-z]+)>(\s*)<\/\1>/mg,'$2'); // clean remain
 $('#inverted').html(s);  
 // END ALGORITHM

 alert(s);

PHP, con Tidy

Lo mismo de Javascript, "traducido" a PHP: la traducción natural es usar DOMDocument()class y loadHTML/ saveXMLmethodos, pero lo que tiene el mismo comportamiento que los corresponsales del navegador es la tidyclase . Muestra los mismos resultados (!)

 $sample1='<b><i>O</i>RIGINAL <big><i>with italics</i> and </big> withOUT</b>';
 $inverted = '... inverted will be here ...';
 echo $sample1;
 // Tidy correction
 $s = $sample1; // get original html text fragment
  // INVERSION ALGORITHM: add and remove italics.
  $s = "<i>".
      preg_replace_callback('/<(\/?)i>/s', function ($m){
       return $m[1]? '<i>': '</i>';}, $s) .
      "</i>";  // a not-well-formed-XHTML, but it is ok...
  $config = array('show-body-only'=>true,'output-xhtml'=>true);
  $tidy = new tidy;
  $tidy->parseString($s, $config, 'utf8');
  $s = $tidy;  // ... because Tidy corrects!     
  // minor corrections, for clean empties:
  $s = preg_replace('/<([a-z]+)>(\s*)<\/\1>/s', '$2', $s); // clean
  $s = preg_replace('/<([a-z]+)>(\s*)<\/\1>/s', '$2', $s); // clean remain
  // END ALGORITHM
  echo "\n\n$s";
Peter Krauss
fuente
No puedo hacer cara o cruz de esta pregunta. ¿Puedes aclararlo? Tal vez acortarlo al punto esencial?
Bobson
Los puntos esenciales son con bulets y negrita ... ¿Puedes editar para ponerlo en la pregunta: comenzar o con más énfasis?
Peter Krauss
Reescribí todo el texto de la pregunta, centrándome más en un problema concreto.
Peter Krauss
Si esto es solo para mostrar (?), ¿Ha considerado modificar el CSS de la página para que el valor predeterminado esté en cursiva y la parte dentro de las etiquetas no?
Hum ... No "solo para mostrar", el resultado final es una base de datos final, como el PMC . Edité la sección de "motivaciones".
Peter Krauss

Respuestas:

2

Actualización (18 de junio de 2013): uso de esta respuesta para explicar algoritmos y resumir conclusiones.


Acerca de las soluciones alternativas de "solución de diseño" de jQuery.

Después del comentario de @Wilbert, adapté el "algoritmo más simple", para evitar el comportamiento dinámico del check .prop(), que cambia con la .each()iteración, eliminando el else. Después de toda la iteración, se resuelve un "cursiva principal". Ver aquí o el código a continuación.

$('#myFragment *').each(function(){
   if ($(this).css('font-style')=='italic')
       $(this).css('font-style','normal');
});
$('#myFragment').parent().css('font-style','italic');

Otra forma de lidiar con el comportamiento dinámico es verificar una propiedad estática prop('tagName'), que no cambia. Ver aquí o el código a continuación.

$('#myFragment').parent().css('font-style','italic');
$('#myFragment *').each(function(){
   if ($(this).prop('tagName')=='I')  // not changes with parent
       $(this).css('font-style','normal');
});

Necesita más pruebas y necesita un análisis final para cambiar las propiedades de estilo a <i>etiquetas concretas . Para aplicar el algoritmo dos veces, necesitamos un poco de cuidado.


Solución de diseño

¡Esta no es una solución para la presente pregunta, pero produce algunas buenas pistas, y es la mejor solución (¡al menos la más pequeña!) Para el "problema de diseño".

El toggleClass()método se puede utilizar para cambiar de una "clase en cursiva" a una "clase de texto normal". Ver aquí o el código a continuación.

 $('#myFragment *').each(function(){
     $(this).toggleClass( "original change");
 });

Y podemos aplicar este pequeño algoritmo dos veces, y tantas veces como queramos ... ¡Es una buena solución! Pero no es un "algoritmo XML de reescritura", el CSS es una clave aquí :

 .original { font-style:normal; } /* use class="original" in your XHTML fragment */
i.original { font-style:italic; }

 .change { font-style:italic; }
i.change{ font-style:normal; }

... Entonces, para un algoritmo que transforma <i>etiquetas, el problema aún está abierto ...

Solución concreta

Una "solución 100%, en puro XSLT1" (¡probada en muchos casos!) Basada en una adaptación de @ DanielHaley's . Es una efectiva <i>transformación de etiquetas.

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:preserve-space elements="p"/>

<xsl:template match="@*|node()"> <!-- copy all -->
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="i"> <!-- remove tag i -->
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="text()[not(ancestor::i)]"> <!-- inlcude tag i -->
    <i><xsl:copy-of select="."/></i>
</xsl:template>
 </xsl:stylesheet>

Esquema como un "algoritmo de unidad por evento" en un proceso de copia:

  • eliminar ietiquetas: copie cualquier cosa de " <i> cosa </i> " como " cosa ".

  • incluir ietiquetas: copie cualquier texto como " <i> texto </i> ", cuando el texto no esté en un contexto de padres en cursiva. PD: el texto es un nodo terminal del árbol DOM.

Conclusiones

Las "soluciones de diseño" son buenas para los editores de texto , pero utilizan algunos trucos y soluciones no rigurosas (sin importar las superposiciones, el rendimiento, etc.). Para el proceso XML tenemos que lidiar con la <i>transformación de etiquetas ... Entonces, los lenguajes naturales para expresar el algoritmo son XSLT o xQuery.

El algoritmo implementado con XSLT muestra las necesidades del marco:

  1. el selector ancestro (padre, abuelo, etc.), para verificar si es o no un "contexto en cursiva";

  2. el text()acceso de nodo de texto (DOM );

  3. eliminar e incluir la ietiqueta.

Entonces, podemos ver los problemas con cada marco.

  • DOM (el marco estándar W3C): el DOMDocument::renameNode(), para el ítem 3, aún no está implementado (ver PHP, Javascript, etc.).

  • jQuery: no tiene una función convenientes para el artículo 2, ver esta respuesta .

  • XSLT: lo mejor para expresar el algoritmo, pero no está disponible en ningún contexto como Javascript.

Yo (¡o por favor!) Intentaré expresar el algoritmo XSLT con métodos de "DOM2 puro". Esa versión DOM será el "algoritmo genérico" ... Bueno: si la traducción es válida solo para DOM3 (usando renameNode y otros trucos), la conclusión por ahora es que "NO hay algoritmo genérico / traducible".

Peter Krauss
fuente
1

Intento de XSLT desde https://stackoverflow.com/a/17156452/317052 ...

No estoy seguro de si esto cubriría todos los casos, pero podría hacer esto:

Entrada XML

<html>
    <!-- original text: -->
    <p id="p1"><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
    <!-- same text, swapping italics: -->
    <p id="p2">Several more <i>Homo sapiens</i> fossils were discovered.</p>
    <p>Leave me alone!</p>
    <p><b><i>O</i>RIGINAL <big><i>with italics</i> and </big> withOUT</b></p>
</html>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[i]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="node()" mode="swapItal"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="text()" mode="swapItal" priority="1">
        <i><xsl:value-of select="."/></i>
    </xsl:template>

    <xsl:template match="i" mode="swapItal">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="@*|node()" mode="swapItal">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="swapItal"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Salida XML

<html>
   <!-- original text: -->
   <p id="p1">Several more<i> Homo sapiens </i>fossils were discovered<i>.</i></p>
   <!-- same text, swapping italics: -->
   <p id="p2"><i>Several more </i>Homo sapiens<i> fossils were discovered.</i></p>
   <p>Leave me alone!</p>
   <p><b>O<i>RIGINAL </i><big>with italics<i> and </i></big><i> withOUT</i></b></p>
</html>

Entrada procesada

Se descubrieron varios fósiles más del Homo sapiens .

Se descubrieron varios fósiles más del Homo sapiens .

¡Déjame solo!

O RIGINAL con cursiva y sin


Salida procesada

Se descubrieron varios fósiles más del Homo sapiens .

Se descubrieron varios fósiles más del Homo sapiens .

¡Déjame solo!

O RIGINAL con cursiva y sin

Daniel Haley
fuente
Hola muchas gracias Ya hemos discutido, en Stackoverflow, la necesidad de algunas pequeñas correcciones a un "XSLT perfecto", así que agrego mi adaptación XSLT a mi respuesta. Aquí también el XSLT no es la "respuesta final" sin una "representación del algoritmo" o pistas para la traducción general (vea la sección "El punto") ... Entonces, no recibirá toda la recompensa. Le agrego 1 voto, entendiendo que, según las reglas de recompensa de este sitio, recibirá el 50% de la recompensa ... Compruebe si estoy equivocado acerca de esta regla.
Peter Krauss
-1

Yo simplemente:

  1. Convierte todo <i>a </i>s
  2. Convierte todo </i>a <i>s
  3. agregar un <i>al principio
  4. agregar un </i>al final

Entonces

 <p id="p1"><i>Several more</i> Homo sapiens <i>fossils were discovered</i>.</p>
 <!-- converts to: -->
 <i><p id="p2">Several more </i>Homo sapiens<i> fossils were discovered.</p></i>
Imbéciles
fuente
1
Sí, es exactamente lo que están haciendo los ejemplos (ver sección "Ejemplos") con Javascript y PHP, con funciones de "reemplazo de expresión regular". También hay un enlace, jsfiddle.net/rdfJ5 ... Pero, desafortunadamente, no es una solución general: pruebe su paso a paso con este caso, <b><i>O</i>RIGINAL <big><i>with italics</i> and </big> withOUT</b>... produce código umbalanced (¡prohibido!) Y bloques vacíos .
Peter Krauss
@ PeterKrauss, ¿no es suficiente para ti? ¿Cuál es el problema?
Morons
1
(¡Edité el comentario, vea el ejemplo donde su paso a paso falla!). Consulte la sección "El punto": no es general y no se puede usar con XSLT ni reutilizar por bibliotecas.
Peter Krauss
<i><b></i>O<i>RIGINAL <big></i>with italics<i> and </big> withOUT</b></i>puede no ser bonito pero funciona en todos los navegadores
Morons
1
Por favor, verifique si comprende lo que está "equilibrado" o "bien formado". Algunos enlaces: wikipedia , W3C ... Sintetizando la regla : "Todas las etiquetas deben estar equilibradas". <i><b></i>no lo es, y TODA LA DISCUSIÓN AQUÍ se trata de "un algoritmo que produce resultados equilibrados".
Peter Krauss