¿Cómo obtener el nodo de texto de un elemento?

98
<div class="title">
   I am text node
   <a class="edit">Edit</a>
</div>

Deseo obtener el "Yo soy nodo de texto", no deseo eliminar la etiqueta "editar" y necesito una solución de navegador cruzado.

Val
fuente
esta pregunta es prácticamente idéntica a stackoverflow.com/questions/3172166/… - vea esas respuestas para obtener una versión JS simple de la respuesta de James
Mala

Respuestas:

79
var text = $(".title").contents().filter(function() {
  return this.nodeType == Node.TEXT_NODE;
}).text();

Esto obtiene el valor contentsdel elemento seleccionado y le aplica una función de filtro. La función de filtro devuelve solo nodos de texto (es decir, los nodos con nodeType == Node.TEXT_NODE).

James Allardice
fuente
@Val: lo siento, me perdí eso del código original. Actualizaré la respuesta para mostrarla. Lo necesita text()porque la filterfunción devuelve los nodos en sí, no el contenido de los nodos.
James Allardice
1
No estoy seguro de por qué, pero no tengo éxito al probar la teoría anterior. Ejecuté lo siguiente jQuery("*").each(function() { console.log(this.nodeType); })y obtuve 1 para todos los tipos de nodos.
Batandwa
¿Es posible obtener texto en el nodo seleccionado y texto en todos sus elementos secundarios?
Jenna Kwon
Esto es interesante y resuelve este problema, pero ¿qué sucede cuando la situación se vuelve más compleja? Existe una forma más flexible de hacer el trabajo.
Anthony Rutledge
Sin jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran
53

Puede obtener el nodeValue del primer childNode usando

$('.title')[0].childNodes[0].nodeValue

http://jsfiddle.net/TU4FB/

Dogbert
fuente
4
Si bien eso funcionará, depende de la posición de los nodos secundarios. Si (cuando) eso cambia, se romperá.
Armstrongest
Si el nodo de texto no es el primer hijo, puede obtener nullun valor de retorno.
Anthony Rutledge
14

Si te refieres a obtener el valor del primer nodo de texto en el elemento, este código funcionará:

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    if (curNode.nodeName === "#text") {
        firstText = curNode.nodeValue;
        break;
    }
}

Puedes ver esto en acción aquí: http://jsfiddle.net/ZkjZJ/

Shadow Wizard es el oído para ti
fuente
Creo que podrías usar en curNode.nodeType == 3lugar de nodeNametambién.
Nilloc
1
@Nilloc probablemente, pero ¿cuál es la ganancia?
Shadow Wizard es Ear For You
5
@ShadowWizard @Nilloc La forma recomendada para eso es usar constantes ... curNode.nodeType == Node.TEXT_NODE(la comparación numérica es más rápida pero curNode.nodeType == 3 no es legible, ¿qué nodo tiene el número 3?)
Mikep
1
Uso de @ShadowWizard curNode.NodeType === Node.TEXT_NODE. Esta comparación ocurre dentro de un ciclo de posibles iteraciones desconocidas. Comparar dos números pequeños es mejor que comparar cadenas de varias longitudes (consideraciones de tiempo y espacio). La pregunta correcta para hacer en esta situación es "¿qué tipo / tipo de nodo tengo?", Y no "¿qué nombre tengo?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge
2
@ShadowWizard Además, si va a utilizar un bucle para filtrar childNodes, sepa que un nodo de elemento puede tener más de un nodo de texto. En una solución genérica, es posible que deba especificar qué instancia de un nodo de texto dentro de un nodo de elemento desea apuntar (el primero, el segundo, el tercero, etc.).
Anthony Rutledge
13

Otra solución JS nativa que puede ser útil para elementos "complejos" o profundamente anidados es usar NodeIterator . Póngalo NodeFilter.SHOW_TEXTcomo segundo argumento ("whatToShow") e itere solo sobre los nodos secundarios de texto del elemento.

var root = document.querySelector('p'),
    iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT),
    textnode;

// print all text nodes
while (textnode = iter.nextNode()) {
  console.log(textnode.textContent)
}
<p>
<br>some text<br>123
</p>

También puede utilizar TreeWalker. La diferencia entre los dos es que NodeIteratores un iterador lineal simple, mientras TreeWalkerque le permite navegar a través de hermanos y ancestros también.

Yuval A.
fuente
9

JavaScript puro: minimalista

En primer lugar, siempre tenga esto en cuenta cuando busque texto en el DOM.

MDN - Espacio en blanco en DOM

Este problema le hará prestar atención a la estructura de su XML / HTML.

En este ejemplo puro de JavaScript, considero la posibilidad de múltiples nodos de texto que podrían intercalar con otros tipos de nodos . Sin embargo, inicialmente, no juzgo los espacios en blanco, dejando esa tarea de filtrado a otro código.

En esta versión, paso una entrada NodeListdesde el código de llamada / cliente.

/**
* Gets strings from text nodes. Minimalist. Non-robust. Pre-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length; // Because you may have many child nodes.

    for (var i = 0; i < length; i++) {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
        }
    }

    return null;
}

Por supuesto, al probar node.hasChildNodes()primero, no sería necesario utilizar un forciclo de prueba previa .

/**
* Gets strings from text nodes. Minimalist. Non-robust. Post-test loop version.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @param nodeList The child nodes of a Node, as in node.childNodes.
* @param target A positive whole number >= 1
* @return String The text you targeted.
*/
function getText(nodeList, target)
{
    var trueTarget = target - 1,
        length = nodeList.length,
        i = 0;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
            return nodeList[i].nodeValue;  // Done! No need to keep going.
         }

        i++;
    } while (i < length);

    return null;
}

JavaScript puro: robusto

Aquí la función getTextById()usa dos funciones auxiliares: getStringsFromChildren()y filterWhitespaceLines().


getStringsFromChildren ()

/**
* Collects strings from child text nodes.
* Generic, cross platform solution. No string filtering or conditioning.
*
* @author Anthony Rutledge
* @version 7.0
* @param parentNode An instance of the Node interface, such as an Element. object.
* @return Array of strings, or null.
* @throws TypeError if the parentNode is not a Node object.
*/
function getStringsFromChildren(parentNode)
{
    var strings = [],
        nodeList,
        length,
        i = 0;

    if (!parentNode instanceof Node) {
        throw new TypeError("The parentNode parameter expects an instance of a Node.");
    }

    if (!parentNode.hasChildNodes()) {
        return null; // We are done. Node may resemble <element></element>
    }

    nodeList = parentNode.childNodes;
    length = nodeList.length;

    do {
        if ((nodeList[i].nodeType === Node.TEXT_NODE)) {
            strings.push(nodeList[i].nodeValue);
         }

        i++;
    } while (i < length);

    if (strings.length > 0) {
        return strings;
    }

    return null;
}

filterWhitespaceLines ()

/**
* Filters an array of strings to remove whitespace lines.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param textArray a String associated with the id attribute of an Element.
* @return Array of strings that are not lines of whitespace, or null.
* @throws TypeError if the textArray param is not of type Array.
*/
function filterWhitespaceLines(textArray) 
{
    var filteredArray = [],
        whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

    if (!textArray instanceof Array) {
        throw new TypeError("The textArray parameter expects an instance of a Array.");
    }

    for (var i = 0; i < textArray.length; i++) {
        if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
            filteredArray.push(textArray[i].trim());  // Trimming here is fine. 
        }
    }

    if (filteredArray.length > 0) {
        return filteredArray ; // Leave selecting and joining strings for a specific implementation. 
    }

    return null; // No text to return.
}

getTextById ()

/**
* Gets strings from text nodes. Robust.
* Generic, cross platform solution.
*
* @author Anthony Rutledge
* @version 6.0
* @param id A String associated with the id property of an Element.
* @return Array of strings, or null.
* @throws TypeError if the id param is not of type String.
* @throws TypeError if the id param cannot be used to find a node by id.
*/
function getTextById(id) 
{
    var textArray = null;             // The hopeful output.
    var idDatatype = typeof id;       // Only used in an TypeError message.
    var node;                         // The parent node being examined.

    try {
        if (idDatatype !== "string") {
            throw new TypeError("The id argument must be of type String! Got " + idDatatype);
        }

        node = document.getElementById(id);

        if (node === null) {
            throw new TypeError("No element found with the id: " + id);
        }

        textArray = getStringsFromChildren(node);

        if (textArray === null) {
            return null; // No text nodes found. Example: <element></element>
        }

        textArray = filterWhitespaceLines(textArray);

        if (textArray.length > 0) {
            return textArray; // Leave selecting and joining strings for a specific implementation. 
        }
    } catch (e) {
        console.log(e.message);
    }

    return null; // No text to return.
}

A continuación, el valor de retorno (Array o nulo) se envía al código del cliente donde debe manejarse. Con suerte, la matriz debe tener elementos de cadena de texto real, no líneas de espacios en blanco.

Las cadenas vacías ( "") no se devuelven porque necesita un nodo de texto para indicar correctamente la presencia de texto válido. Devolver ( "") puede dar la falsa impresión de que existe un nodo de texto, lo que lleva a alguien a asumir que puede alterar el texto cambiando el valor de .nodeValue. Esto es falso, porque un nodo de texto no existe en el caso de una cadena vacía.

Ejemplo 1 :

<p id="bio"></p> <!-- There is no text node here. Return null. -->

Ejemplo 2 :

<p id="bio">

</p> <!-- There are at least two text nodes ("\n"), here. -->

El problema surge cuando desea que su HTML sea fácil de leer al espaciarlo. Ahora, aunque no hay texto válido legible por humanos, todavía hay nodos de texto con caracteres de nueva línea ( "\n") en sus .nodeValuepropiedades.

Los seres humanos ven los ejemplos uno y dos como funcionalmente equivalentes: elementos vacíos que esperan ser llenados. El DOM es diferente al razonamiento humano. Es por eso que la getStringsFromChildren()función debe determinar si existen nodos de texto y recopilar los .nodeValuevalores en una matriz.

for (var i = 0; i < length; i++) {
    if (nodeList[i].nodeType === Node.TEXT_NODE) {
            textNodes.push(nodeList[i].nodeValue);
    }
}

En el ejemplo dos, existen dos nodos de texto y getStringFromChildren()devolverán el .nodeValuede ambos ( "\n"). Sin embargo, filterWhitespaceLines()usa una expresión regular para filtrar líneas de caracteres de espacio en blanco puro.

¿Devolver en nulllugar de los "\n"caracteres de nueva línea ( ) es una forma de mentir al cliente / código de llamada? En términos humanos, no. En términos de DOM, sí. Sin embargo, el problema aquí es obtener texto, no editarlo. No hay texto humano para volver al código de llamada.

Nunca se puede saber cuántos caracteres de nueva línea pueden aparecer en el HTML de alguien. La creación de un contador que busque el "segundo" carácter de nueva línea no es confiable. Puede que no exista.

Por supuesto, más adelante en la línea, el problema de editar texto en un <p></p>elemento vacío con espacio en blanco adicional (ejemplo 2) podría significar destruir (tal vez, omitir) todos los nodos de texto excepto uno entre las etiquetas de un párrafo para garantizar que el elemento contenga exactamente lo que es. se supone que debe mostrar.

Independientemente, excepto en los casos en los que esté haciendo algo extraordinario, necesitará una forma de determinar qué .nodeValuepropiedad del nodo de texto tiene el texto verdadero y legible por humanos que desea editar. filterWhitespaceLinesnos lleva a la mitad del camino.

var whitespaceLine = /(?:^\s+$)/; // Non-capturing Regular Expression.

for (var i = 0; i < filteredTextArray.length; i++) {
    if (!whitespaceLine.test(textArray[i])) {  // If it is not a line of whitespace.
        filteredTextArray.push(textArray[i].trim());  // Trimming here is fine. 
    }
}

En este punto, es posible que tenga una salida similar a esta:

["Dealing with text nodes is fun.", "Some people just use jQuery."]

No hay garantía de que estas dos cadenas sean adyacentes entre sí en el DOM, por lo que unirlas con .join()podría generar una composición antinatural. En cambio, en el código que llama getTextById(), debe elegir con qué cadena desea trabajar.

Pruebe la salida.

try {
    var strings = getTextById("bio");

    if (strings === null) {
        // Do something.
    } else if (strings.length === 1) {
        // Do something with strings[0]
    } else { // Could be another else if
        // Do something. It all depends on the context.
    }
} catch (e) {
    console.log(e.message);
}

Se podría agregar .trim()dentro de getStringsFromChildren()para deshacerse de los espacios en blanco iniciales y finales (o para convertir un montón de espacios en una cadena de longitud cero ( ""), pero ¿cómo puede saber a priori lo que cada aplicación necesita que le suceda al texto (cadena)? una vez que se encuentra? Usted no, así que déjelo a una implementación específica, y getStringsFromChildren()sea ​​genérico.

Puede haber ocasiones en las que este nivel de especificidad (el targety tal) no sea necesario. Eso es grandioso. Utilice una solución sencilla en esos casos. Sin embargo, un algoritmo generalizado le permite adaptarse a situaciones simples y complejas.

Anthony Rutledge
fuente
8

Versión ES6 que devuelve el primer contenido del nodo #text

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}
jujule
fuente
Me pregunto acerca de la eficiencia y la flexibilidad. (1) El uso de .from()para crear una instancia de matriz con copia superficial. (2) El uso de .find()para hacer comparaciones de cadenas usando .nodeName. Usar node.NodeType === Node.TEXT_NODEsería mejor. (3) Devolver una cadena vacía cuando no hay valor null, es más cierto si no se encuentra ningún nodo de texto . Si no se encuentra ningún nodo de texto, ¡es posible que deba crear uno! Si devuelve una cadena vacía, ""puede dar la falsa impresión de que existe un nodo de texto y se puede manipular normalmente. En esencia, devolver una cuerda vacía es una mentira piadosa y es mejor evitarla.
Anthony Rutledge
(4) Si hay más de un nodo de texto en una lista de nodos, aquí no hay forma de especificar qué nodo de texto desea. Es posible que desee el primer nodo de texto, pero es muy posible que desee el último nodo de texto.
Anthony Rutledge
¿Qué sugieres para reemplazar el Array. De?
jujule
@Snowman, por favor agregue su propia respuesta para tales cambios sustanciales, o haga recomendaciones para OP para darles la oportunidad de incorporarlos en su respuesta.
TylerH
@jujule - Es mejor usar [...node.childNodes]para convertir HTMLCollection en matrices
vsync
5

.text() - for jquery

$('.title').clone()    //clone the element
.children() //select all the children
.remove()   //remove all the children
.end()  //again go back to selected element
.text();    //get the text of element
Pranay Rana
fuente
1
Creo que el método para javascript estándar debe ser 'innerText'
Reporter
2
Esto no funciona de la manera que el OP quiere; también obtendrá el texto dentro del aelemento: jsfiddle.net/ekHJH
James Allardice
1
@James Allardice - Terminé con la solución jquery ahora esto funcionará .................
Pranay Rana
Eso casi funcionará, pero le falta el .al principio de su selector, lo que significa que en realidad obtiene el texto del titleelemento, no los elementos conclass="title"
James Allardice
@reporter .innerTextes una antigua convención de IE adoptada recientemente. En términos de secuencias de comandos DOM estándar, node.nodeValuees cómo se toma el texto de un nodo de texto.
Anthony Rutledge
2

Esto también ignorará los espacios en blanco, por lo que nunca obtuvo el código de TextNodes en blanco ... usando Javascript central.

var oDiv = document.getElementById("MyDiv");
var firstText = "";
for (var i = 0; i < oDiv.childNodes.length; i++) {
    var curNode = oDiv.childNodes[i];
    whitespace = /^\s*$/;
    if (curNode.nodeName === "#text" && !(whitespace.test(curNode.nodeValue))) {
        firstText = curNode.nodeValue;
        break;
    }
}

Compruébelo en jsfiddle: - http://jsfiddle.net/webx/ZhLep/

webx
fuente
curNode.nodeType === Node.TEXT_NODEseria mejor. El uso de la comparación de cadenas y una expresión regular dentro de un bucle es una solución de bajo rendimiento, especialmente a medida que oDiv.childNodes.lengthaumenta la magnitud . Este algoritmo resuelve la pregunta específica del OP, pero, potencialmente, a un costo de rendimiento terrible. Si cambia la disposición, o el número, de los nodos de texto, no se puede garantizar que esta solución devuelva resultados precisos. En otras palabras, no puede apuntar al nodo de texto exacto que desea. Estás a merced de la estructura HTML y la disposición del texto allí.
Anthony Rutledge
1

También puede usar la text()prueba de nodo de XPath para obtener solo los nodos de texto. Por ejemplo

var target = document.querySelector('div.title');
var iter = document.evaluate('text()', target, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var node;
var want = '';

while (node = iter.iterateNext()) {
    want += node.data;
}
doblar
fuente
0

Esta es mi solución en ES6 para crear una cadena que contraiga el texto concatenado de todos los childnodes (recursivo) . Tenga en cuenta que también visite el shdowroot de childnodes.

function text_from(node) {
    const extract = (node) => [...node.childNodes].reduce(
        (acc, childnode) => [
            ...acc,
            childnode.nodeType === Node.TEXT_NODE ? childnode.textContent.trim() : '',
            ...extract(childnode),
            ...(childnode.shadowRoot ? extract(childnode.shadowRoot) : [])],
        []);

    return extract(node).filter(text => text.length).join('\n');
}

Esta solución se inspiró en la solución de https://stackoverflow.com/a/41051238./1300775 .

Damien
fuente