¿Cómo **** formateo este texto?

Nedaim

Llevo ya un buen rato pegandome con un problema. Tengo un trozo de codigo de html, por ejemplo:

<div _ngcontent-c19="" class="case-title" style="font-size: 13px;">
    <h1 _ngcontent-c19="" style="margin-right: 0px; margin-bottom: 0.5em; margin-left: 0px; font-weight: 700; font-size: 1.5rem; font-family: Roboto, sans-serif !important;">Titulo blablalbal</h1>
</div>
<div _ngcontent-c19="" class="case-specialties" style="font-size: 0.9rem;">
    <p _ngcontent-c19="">
        <span _ngcontent-c19="" class="ng-star-inserted">Subtitutlo blablabla</span>
    </p>
</div>
<div _ngcontent-c19="" class="case-description" style="font-size: 0.9rem;">
    <div _ngcontent-c19="">
        <p></p>
        <h1></h1>
        <p>Texto blablablablabla</p>
    </div>
    <p _ngcontent-c19="" class="ng-star-inserted">
        <label _ngcontent-c19="" style="margin-bottom: 0px; font-weight: 700;">Cosa:</label>
        &nbsp;
        Cosas
    </p>
</div>

Tengo que formatear este texto de forma que no quede ningun atributo y todo el texto esté contenido en <p></p> sin que los saltos de linea cambien. Por ejemplo el texto anterior quedaria así:

<p>Titulo blablalbal</p>
<p>Subtitutlo blablabla</p>
<p>Texto blablablablabla</p>
<p>Cosa: Cosas</p>

Para formatearlo tengo que usar js y jQuery. Ya veis que se queda en nada y parece una chorrada, pero o no lo es, o yo no veo la solución fácil. Una aiudita por favor.

Se que el primer trozo de código es infumable pero es parte del código real que estoy formateando y así no me dejo nada.

Retil

De js ni putisima idea, pero con grep y sed lo puedes sacar super facil, supongo que js tendrá metodos parecidos.
PD: Acabo de mirarlo rapido, con expresiones regulares puedes hacerlo facilmente https://developer.mozilla.org/es/docs/Web/JavaScript/Guide/Regular_Expressions

1 respuesta
Beavis

No te recomiendo usar regexp o cosas similares, lo que parece sencillo al principio se acaba complicando si el input es mínimamente variable. Te valdría cualquier tokenizer de html/xml pero ya que tienes acceso al navegador puedes usar el parser de html del propio navegador y recorrer los nodos uno a uno para decidir qué haces con ellos
Creas un div, le asignas el contenido al innerHTML y ya puedes recorrerlo y trabajar sobre eso. Esta función te puede resultar útil para limpiar un poco el div que has creado antes de recorrerlo: https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize

1 respuesta
Nedaim

#2 Realmente lo que estoy haciendo es reescribir una función que usaba expresiones regulares, llevaba 120 lineas y todavía no cubría todos los casos. ¿Podrías poner un ejemplo de grep y sed a ver si puedo hacer algo parecido en js?

#3 Ahora mismo he probado a coger todos los textNodes y asignarles un <p> pero los <label> dentro de los <p> hacen que donde deberia haber 1 solo <p> haya dos, con lo que tengo un salto de linea que no quiero. Ademas, por la forma en la que se recorren los elementos los textnodes de ese tipo de estructuras, se dan la vuelta (primero se inserta el del label aunque sea el segundo elemento). No me he parado a pensar si es facil arreglar lo del orden porque el tema del salto de linea ya esta complicado. Voy a probar este mismo método haciendo unwrap de los <p> compuestos antes de coger los textnodes a ver en qué queda.

Cualquier otra sugerencia es bienvenida.

Troyer

#1 prueba esta mierda que te he hecho en 5 minutos:

var meta = `
<div _ngcontent-c19="" class="case-title" style="font-size: 13px;">
    <h1 _ngcontent-c19="" style="margin-right: 0px; margin-bottom: 0.5em; margin-left: 0px; font-weight: 700; font-size: 1.5rem; font-family: Roboto, sans-serif !important;">Titulo blablalbal</h1>
</div>
<div _ngcontent-c19="" class="case-specialties" style="font-size: 0.9rem;">
    <p _ngcontent-c19="">
        <span _ngcontent-c19="" class="ng-star-inserted">Subtitutlo blablabla</span>
    </p>
</div>
<div _ngcontent-c19="" class="case-description" style="font-size: 0.9rem;">
    <div _ngcontent-c19="">
        <p></p>
        <h1></h1>
        <p>Texto blablablablabla</p>
    </div>
    <p _ngcontent-c19="" class="ng-star-inserted">
        <label _ngcontent-c19="" style="margin-bottom: 0px; font-weight: 700;">Cosa:</label>
        &nbsp;
        Cosas
    </p>
</div>
`;
meta = meta.replace(/&nbsp+/g, "");
meta = meta.replace(/<[^>]*>+/g, "");

meta = meta.replace(/\n+/g, "");
meta = meta.replace(/\s\s/g, ';')
var res = meta.split(";")
var filtered = res.filter(function (el) {
  return el != "";
});
console.log(filtered);

Lo acabaría pero no tengo tiempo, básicamente lo que yo haría sería buscar el texto dentro de las etiquetas eliminandolas e irlo guardando, luego si me apetece te lo hago.

1 respuesta
Nedaim

#5 Esto me devuelve más o menos lo mismo que la búsqueda de textnodes pero me lo da en orden. A ver si haciendo un unwrap primero y luego pasando esto funciona la cosa. Thx

Kashir

Yo sin pensarlo demasiado haría algo así, por si es lo que necesitas :)

const nodes = document.querySelector('body').getElementsByTagName('*');
const values = [];

Array.from(nodes).forEach((node) => {
  if (node.childElementCount === 0 && node.innerText !== '') {
    values.push(`<p>${node.innerText}</p>`);
  }
});

console.log(values.join(''));
1 1 respuesta
Nedaim

#7 Eso hace que elementos inline que esten dentro de elementos block generen dos elementos "p" con lo que algo que deberia estar en una línea queda en dos. Lo leí mal, aun así no me valdría porque se dejaría los textnodes que tienen hermanos si no me equivoco. Por ej:

<p>Hola <span> caracola </span></p>

De ahi sacaría solo "caracola" porque el nodo <p> tiene textnode y otro hijo hermano del textnode (el span).

Al final solucioné el caso del op con esto:

function parseHTML(text){
    let output = '';
    let tree = $('<div>'+text+'</div>');
    let pChildren = $(tree).find('p *');
    for(let child of pChildren){
        $(child).contents().unwrap();
    }
    tree[0].normalize();
    let list = getTextNodesIn(tree);
    for(let element of list){
        if(element.data.trim()){
            output+='<p>'+element.data+'</p>';
        }
    }
    return output;
}
var getTextNodesIn = function(el) {
    return $(el).find(":not(iframe)").addBack().contents().filter(function() {
        return this.nodeType == 3;
    });
};

Pero no me cubre otros casos con lo que de momento no lo puedo usar. La clave de la cuestión es identificar elementos block e inline pero al ser comportamientos por defecto no consigo sacarlos de ningún lado (getComputedStyle me da nulo). Lo que podría hacer seria tener un mapa de tipoDeNodo->tipoDeDisplay, pero no sé si me merece la pena o hay alguna otra solución mejor.

JuAn4k4

Si no lo entiendo mal, las reglas son:

Cualquier texto en el DOM, es un p nuevo en el resultado.
Si dentro de un nodo con texto, hay hijos, su texto va dentro del p actual.

¿Es asi?

El problema que veo que te pasa es que siempre abres y cierras el p que añades, pero en realidad tienes casos (que podrías hacer recursivamente) en el que el p tiene que incluir el texto de los hijos.

1 respuesta
Nedaim

#9 No exactamente.

<div>
    Hola caracola
    <h1>Hola caracol</h1>
</div>

Esto daría

<p>Hola caracola</p>
<p>Hola caracol</p>

Porque así es como se renderiza. En el caso que describes al tener el div un texto y un nodo hijo con texto iría en su propio p de la siguiente forma:

<p>Hola caracolaHola caracol</p>

Con lo que cambiaría el "aspecto" que tiene el texto para el usuario. Por eso comentaba lo de inline y block.

1 respuesta
JuAn4k4

#10 Vale, si el nodo es de los que añaden nueva linea en el css por defecto, se abre nuevo, pero si no crea nueva linea, si se incluye.

Y si el css pone un h1 + h2 como inline, deberia ponerlos juntos? o el css lo ignoramos?

1 respuesta
Nedaim

#11 El css se ignora, solo vale el comportamiento por defecto de los elementos,

1 respuesta
JuAn4k4

#12 Entonces con un bucle que itere el arbol de izq a derecha, Abriendo p cuando tienes un elemento que añada linea (aunque esté vacio de texto), y cerrandolo al acabar con todos sus hijos, añadiendo el texto al p actual, ya estaría.

pseudocodigo desde movil:

fun parse(result = new ParserResult(), params nodes) {
  if (!nodes || !nodes.length) return;

  for (let node in nodes) {
   if (isNewLiner(node)) {
     result.openNewPharagraph()
     result.addText(node.text)
     parse(result, node.children)
     result.closeParagraph()
   }
  }
  return result.complete()
}

open > abre nuevo p, cierra el actual.
close > cierra el p actual, si hay alguno
add text, añade texto al p actual.

Con esto ya estaría yo creo

Beavis

https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker esto es bastante útil para recorrer un árbol de nodos

1
PiradoIV

Si tienes jQuery, no te vuelvas loco:

$('p').each(function(item) { console.log('<p>' + $(this).text() + '</p>') });

Si no tienes jQuery, XPath es lo mejor:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_using_XPath_in_JavaScript

Con XPath tienes también la función text(), por ejemplo:

//p/text()

Las expresiones regulares no valen, como dice Beavis:
https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/

nerkaid

Quita con Sublime por ejemplo usando regexp todo lo que hay entre <> y luego sustituye cada salto de línea añadiendo </p>br<p>

Usuarios habituales

  • nerkaid
  • PiradoIV
  • Beavis
  • JuAn4k4
  • Nedaim
  • Kashir
  • Retil