Come ottenere il nodo di testo di un elemento?


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

Desidero ottenere il "I am text node", non desidero rimuovere il tag "edit" e ho bisogno di una soluzione cross browser.


questa domanda è praticamente identica a stackoverflow.com/questions/3172166/… - vedi quelle risposte per una semplice versione JS della risposta di James
Mala

Risposte:


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

Questo ottiene l' contentselemento selezionato e gli applica una funzione di filtro. La funzione di filtro restituisce solo nodi di testo (cioè quei nodi con nodeType == Node.TEXT_NODE).


@Val - scusa, mi è sfuggito il codice originale. Aggiornerò la risposta per mostrarlo. È necessario text()perché la filterfunzione restituisce i nodi stessi, non il contenuto dei nodi.
James Allardice

1
Non sono sicuro del perché, ma non riesco a testare la teoria sopra. Ho eseguito quanto segue jQuery("*").each(function() { console.log(this.nodeType); })e ho ottenuto 1 per tutti i tipi di nodo.
Batandwa

È possibile ottenere testo nel nodo cliccato e testo in tutti i suoi figli?
Jenna Kwon

Questo è interessante e risolve questo problema, ma cosa succede quando la situazione diventa più complessa? C'è un modo più flessibile per portare a termine il lavoro.
Anthony Rutledge

Senza jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran

53

Puoi ottenere il nodeValue del primo childNode usando

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

http://jsfiddle.net/TU4FB/


4
Anche se funzionerà, dipende dalla posizione dei nodi figli. Se (quando) cambia, si romperà.
Armstrongest

Se il nodo di testo non è il primo figlio, potresti ottenere nullun valore di ritorno.
Anthony Rutledge

14

Se intendi ottenere il valore del primo nodo di testo nell'elemento, questo codice funzionerà:

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;
    }
}

Puoi vederlo in azione qui: http://jsfiddle.net/ZkjZJ/


Penso che potresti usare anche al curNode.nodeType == 3posto di nodeName.
Nilloc

1
@ Nilloc probabilmente, ma qual è il guadagno?
Shadow Wizard is Ear For You

5
Il modo consigliato da @ShadowWizard @Nilloc è quello di usare le costanti ... curNode.nodeType == Node.TEXT_NODE(il confronto numerico è più veloce ma curNode.nodeType == 3 non è leggibile - quale nodo ha il numero 3?)
mikep

1
@ShadowWizard Use curNode.NodeType === Node.TEXT_NODE. Questo confronto avviene all'interno di un ciclo di iterazioni possibili sconosciute. Confrontare due piccoli numeri è meglio che confrontare stringhe di varie lunghezze (considerazioni di tempo e spazio). La domanda corretta da porre in questa situazione è "che tipo / tipo di nodo ho?" E non "che nome ho?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge

2
@ShadowWizard Inoltre, se intendi utilizzare un ciclo per setacciare childNodes, sappi che un nodo elemento può avere più di un nodo di testo. In una soluzione generica, potrebbe essere necessario specificare quale istanza di un nodo di testo all'interno di un nodo elemento si desidera targetizzare (la prima, la seconda, la terza, ecc ...).
Anthony Rutledge

13

Un'altra soluzione JS nativa che può essere utile per elementi "complessi" o profondamente nidificati è utilizzare NodeIterator . Metti NodeFilter.SHOW_TEXTcome secondo argomento ("whatToShow") e ripeti solo sui nodi secondari del nodo di testo dell'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>

Puoi anche usare TreeWalker. La differenza tra i due è che NodeIteratorè un semplice iteratore lineare, mentre TreeWalkerti consente di navigare anche tramite fratelli e antenati.


9

JavaScript puro: minimalista

Prima di tutto, tienilo sempre a mente quando cerchi del testo nel DOM.

MDN: spazi bianchi nel DOM

Questo problema ti farà prestare attenzione alla struttura del tuo XML / HTML.

In questo esempio JavaScript puro, tengo conto della possibilità di più nodi di testo che potrebbero essere interlacciati con altri tipi di nodi . Tuttavia, inizialmente, non giudico gli spazi, lasciando l'attività di filtraggio ad altro codice.

In questa versione, passo un NodeListdal codice chiamante / client.

/**
* 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;
}

Ovviamente, testando node.hasChildNodes()prima, non sarebbe necessario utilizzare un forciclo di pre-test .

/**
* 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

Qui la funzione getTextById()utilizza due funzioni di supporto: getStringsFromChildren()e 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.
}

Successivamente, il valore restituito (Array o null) viene inviato al codice client dove deve essere gestito. Si spera che l'array abbia elementi stringa di testo reale, non righe di spazi bianchi.

Le stringhe vuote ( "") non vengono restituite perché è necessario un nodo di testo per indicare correttamente la presenza di testo valido. Returning ( "") può dare la falsa impressione che esista un nodo di testo, portando qualcuno a presumere di poter alterare il testo cambiando il valore di .nodeValue. Questo è falso, perché un nodo di testo non esiste nel caso di una stringa vuota.

Esempio 1 :

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

Esempio 2 :

<p id="bio">

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

Il problema si presenta quando vuoi rendere il tuo HTML facile da leggere spaziandolo. Ora, anche se non esiste un testo valido leggibile dall'uomo, ci sono ancora nodi di testo con caratteri newline ( "\n") nelle loro .nodeValueproprietà.

Gli esseri umani vedono gli esempi uno e due come funzionalmente equivalenti: elementi vuoti in attesa di essere riempiti. Il DOM è diverso dal ragionamento umano. Questo è il motivo per cui la getStringsFromChildren()funzione deve determinare se esistono nodi di testo e raccogliere i .nodeValuevalori in un array.

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

Nell'esempio due, esistono due nodi di testo e getStringFromChildren()restituiranno il simbolo .nodeValuedi entrambi ( "\n"). Però,filterWhitespaceLines() utilizza un'espressione regolare per filtrare le righe di caratteri di spazi bianchi puri.

Restituire nullinvece dei caratteri newline ( "\n") è una forma di mentire al codice client / chiamante? In termini umani, no. In termini DOM, sì. Tuttavia, il problema qui è ottenere il testo, non modificarlo.Non vi è alcun testo umano per tornare al codice chiamante.

Non si può mai sapere quanti caratteri di nuova riga potrebbero apparire nell'HTML di qualcuno. La creazione di un contatore che cerchi il "secondo" carattere di nuova riga non è affidabile. Potrebbe non esistere.

Ovviamente, più in basso, il problema di modificare il testo in un <p></p>elemento vuoto con spazi bianchi extra (esempio 2) potrebbe significare distruggere (forse, saltare) tutti i nodi di testo tranne uno tra i tag di un paragrafo per garantire che l'elemento contenga esattamente quello che è dovrebbe visualizzare.

Indipendentemente da ciò, tranne nei casi in cui stai facendo qualcosa di straordinario, avrai bisogno di un modo per determinare quale .nodeValueproprietà del nodo di testo ha il vero testo leggibile dall'uomo che desideri modificare. filterWhitespaceLinesci porta a metà strada.

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. 
    }
}

A questo punto potresti avere un output simile a questo:

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

Non vi è alcuna garanzia che queste due stringhe siano adiacenti l'una all'altra nel DOM, quindi unirle .join()potrebbe creare un composto innaturale. Invece, nel codice che chiamagetTextById() , devi scegliere la stringa con cui vuoi lavorare.

Testare l'output.

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);
}

Si potrebbe aggiungere l' .trim()interno di getStringsFromChildren()per sbarazzarsi di spazi bianchi iniziali e finali (o per trasformare un mucchio di spazi in una stringa di lunghezza zero ( ""), ma come si può sapere a priori ciò di cui ogni applicazione potrebbe aver bisogno per accadere al testo (stringa) una volta trovato, non lo fai, quindi lascia che sia un'implementazione specifica e getStringsFromChildren()sia generico.

Ci possono essere momenti in cui questo livello di specificità ( targete così via) non è richiesto. È grandioso. Utilizzare una soluzione semplice in questi casi. Tuttavia, un algoritmo generalizzato consente di adattarsi a situazioni semplici e complesse.


8

Versione ES6 che restituisce il contenuto del primo nodo #text

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}

Mi chiedo efficienza e flessibilità. (1) L'uso di .from()per creare un'istanza di array con copia superficiale. (2) L'uso di .find()per fare confronti di stringhe usando .nodeName. L'uso node.NodeType === Node.TEXT_NODEsarebbe migliore. (3) Restituire una stringa vuota quando nessun valore,, nullè più vero se non viene trovato alcun nodo di testo . Se non viene trovato alcun nodo di testo, potrebbe essere necessario crearne uno! Se restituisci una stringa vuota "", potresti dare la falsa impressione che un nodo di testo esista e possa essere manipolato normalmente. In sostanza, restituire una stringa vuota è una bugia bianca e meglio evitarla.
Anthony Rutledge

(4) Se c'è più di un nodo di testo in un nodeList, non c'è modo qui per specificare quale nodo di testo si desidera. Potresti volere il primo nodo di testo, ma potresti volere molto bene l' ultimo nodo di testo.
Anthony Rutledge

Cosa suggerisci per sostituire Array.from?
jujule

@ Snowman, per favore aggiungi la tua risposta per tali cambiamenti sostanziali, o dai consigli a OP per dare loro l'opportunità di incorporarli nella loro risposta.
TylerH

@jujule - Meglio da usare [...node.childNodes]per convertire HTMLCollection in array
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

1
Penso che il metodo per javascript standard debba essere "innerText"
Reporter

2
Questo non funziona come vuole l'OP - otterrà anche il testo all'interno adell'elemento: jsfiddle.net/ekHJH
James Allardice

1
@James Allardice - Ho finito con la soluzione jquery ora funzionerà .................
Pranay Rana

Quasi funzionerà, ma ti manca .all'inizio del selettore, il che significa che in realtà ottieni il testo titledell'elemento, non gli elementi conclass="title"
James Allardice,

@reporter .innerTextè una vecchia convenzione di IE adottata di recente. In termini di scripting DOM standard, node.nodeValueè come si afferra il testo di un nodo di testo.
Anthony Rutledge

2

Questo ignorerà anche gli spazi vuoti, quindi non avrai mai ottenuto il codice textNodes..Novità usando il core Javascript.

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;
    }
}

Controllalo su jsfiddle: - http://jsfiddle.net/webx/ZhLep/


curNode.nodeType === Node.TEXT_NODEsarebbe meglio. L'utilizzo del confronto tra stringhe e di un'espressione regolare all'interno di un ciclo è una soluzione a basso rendimento, soprattutto se l'entità degli oDiv.childNodes.lengthaumenti. Questo algoritmo risolve la domanda specifica dell'OP, ma, potenzialmente, a un costo di prestazioni terribile. Se la disposizione, o il numero, dei nodi di testo cambia, non è possibile garantire che questa soluzione restituisca un output accurato. In altre parole, non puoi scegliere come target il nodo di testo esatto che desideri. Sei in balia della struttura HTML e della disposizione del testo.
Anthony Rutledge,

1

È inoltre possibile utilizzare il text()test dei nodi di XPath per ottenere solo i nodi di testo. Per esempio

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;
}

0

Questa è la mia soluzione in ES6 per creare una stringa in contrasto con il testo concatenato di tutti i nodi figlio (ricorsivo) . Nota che è anche visitare la radice dei nodi infantili.

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');
}

Questa soluzione è stata ispirata dalla soluzione di https://stackoverflow.com/a/41051238./1300775 .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.