Comment obtenir le nœud de texte d'un élément?

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

Je souhaite obtenir le nœud "Je suis texte", je ne souhaite pas supprimer la balise "edit" et j'ai besoin d'une solution multi-navigateurs.

Val
la source
cette question est à peu près identique à stackoverflow.com/questions/3172166/… - voir ces réponses pour une version JS simple de la réponse de James
Mala

Réponses:

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

Cela récupère l' contentsélément sélectionné et lui applique une fonction de filtre. La fonction de filtre ne renvoie que les nœuds de texte (c'est-à-dire les nœuds avec nodeType == Node.TEXT_NODE).

James Allardice
la source
@Val - désolé, j'ai manqué cela du code d'origine. Je mettrai à jour la réponse pour la montrer. Vous en avez besoin text()car la filterfonction renvoie les nœuds eux-mêmes, pas le contenu des nœuds.
James Allardice
1
Je ne sais pas pourquoi mais je n'ai pas réussi lors du test de la théorie ci-dessus. J'ai couru ce qui suit jQuery("*").each(function() { console.log(this.nodeType); })et j'en ai obtenu 1 pour tous les types de nœuds.
Batandwa
Est-il possible d'obtenir du texte au niveau du nœud cliqué et du texte dans tous ses enfants?
Jenna Kwon
Cela est intéressant et résout ce problème, mais que se passe-t-il lorsque la situation se complexifie? Il existe une manière plus flexible de faire le travail.
Anthony Rutledge
Sans jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran
57

Vous pouvez obtenir le nodeValue du premier childNode en utilisant

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

http://jsfiddle.net/TU4FB/

Dogbert
la source
4
Bien que cela fonctionne, cela dépend de la position des nœuds enfants. Si (quand) cela change, il se cassera.
Armstrongest le
Si le nœud de texte n'est pas le premier enfant, vous pouvez obtenir nullune valeur de retour.
Anthony Rutledge
15

Si vous voulez dire obtenir la valeur du premier nœud de texte de l'élément, ce code fonctionnera:

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

Vous pouvez le voir en action ici: http://jsfiddle.net/ZkjZJ/

Shadow Wizard est une oreille pour vous
la source
Je pense que vous pouvez également utiliser à la curNode.nodeType == 3place nodeName.
Nilloc
1
@Nilloc probablement, mais quel est le gain?
Shadow Wizard est une oreille pour vous
5
@ShadowWizard @Nilloc est la méthode recommandée pour cela est d'utiliser des constantes ... curNode.nodeType == Node.TEXT_NODE(la comparaison numérique est plus rapide mais curNode.nodeType == 3 n'est pas lisible - quel nœud a le numéro 3?)
mikep
1
Utilisation de @ShadowWizard curNode.NodeType === Node.TEXT_NODE. Cette comparaison se produit dans une boucle d'itérations possibles inconnues. Il est préférable de comparer deux petits nombres que de comparer des chaînes de différentes longueurs (considérations de temps et d'espace). La bonne question à se poser dans cette situation est "quel type / type de nœud ai-je?", Et non "quel nom ai-je?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge
2
@ShadowWizard De plus, si vous allez utiliser une boucle pour passer au crible childNodes, sachez qu'un nœud d'élément peut avoir plusieurs nœuds de texte. Dans une solution générique, il peut être nécessaire de spécifier quelle instance d'un nœud de texte dans un nœud d'élément que vous souhaitez cibler (le premier, le deuxième, le troisième, etc.).
Anthony Rutledge
14

Une autre solution JS native qui peut être utile pour les éléments «complexes» ou profondément imbriqués consiste à utiliser NodeIterator . Mettez NodeFilter.SHOW_TEXTcomme deuxième argument ("whatToShow"), et itérez uniquement sur les enfants du nœud de texte de l'élément.

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>

Vous pouvez également utiliser TreeWalker. La différence entre les deux est qu'il NodeIterators'agit d'un simple itérateur linéaire, tout en TreeWalkervous permettant de naviguer également via les frères et sœurs et les ancêtres.

Yuval A.
la source
9

JavaScript pur: minimaliste

Tout d'abord, gardez toujours cela à l'esprit lorsque vous recherchez du texte dans le DOM.

MDN - Espace blanc dans le DOM

Ce problème vous fera faire attention à la structure de votre XML / HTML.

Dans cet exemple purement JavaScript, je tiens compte de la possibilité de plusieurs nœuds de texte qui pourraient être entrelacés avec d'autres types de nœuds . Cependant, au départ, je ne porte pas de jugement sur les espaces, laissant cette tâche de filtrage à un autre code.

Dans cette version, je passe un NodeListà partir du code appelant / 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;
}

Bien sûr, en testant d' node.hasChildNodes()abord, il ne serait pas nécessaire d'utiliser une forboucle de pré-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 pur: robuste

Ici, la fonction getTextById()utilise deux fonctions d'assistance: getStringsFromChildren()et 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.
}

Ensuite, la valeur de retour (Array ou null) est envoyée au code client où elle doit être gérée. Espérons que le tableau devrait avoir des éléments de chaîne de texte réel, pas des lignes d'espaces.

Les chaînes vides ( "") ne sont pas renvoyées car vous avez besoin d'un nœud de texte pour indiquer correctement la présence de texte valide. Renvoyer ( "") peut donner la fausse impression qu'un nœud de texte existe, amenant quelqu'un à supposer qu'il peut modifier le texte en changeant la valeur de .nodeValue. Ceci est faux, car un nœud de texte n'existe pas dans le cas d'une chaîne vide.

Exemple 1 :

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

Exemple 2 :

<p id="bio">

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

Le problème survient lorsque vous souhaitez rendre votre HTML facile à lire en l'espaçant. Maintenant, même s'il n'y a pas de texte valide lisible par l'homme, il y a toujours des nœuds de texte avec des caractères newline ( "\n") dans leurs .nodeValuepropriétés.

Les humains voient les exemples un et deux comme fonctionnellement équivalents - des éléments vides en attente d'être remplis. Le DOM est différent du raisonnement humain. C'est pourquoi la getStringsFromChildren()fonction doit déterminer si des nœuds de texte existent et rassembler les .nodeValuevaleurs dans un tableau.

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

Dans l'exemple deux, deux nœuds de texte existent et getStringFromChildren() renverront les .nodeValuedeux ( "\n"). Cependant, filterWhitespaceLines()utilise une expression régulière pour filtrer les lignes de caractères d'espaces blancs purs.

Revient null au lieu de caractères newline ( "\n") est-il une forme de mensonge au client / code d'appel? En termes humains, non. En termes DOM, oui. Cependant, le problème ici est d' obtenir du texte, pas de le modifier. Il n'y a pas de texte humain pour revenir au code d'appel.

On ne peut jamais savoir combien de caractères de nouvelle ligne peuvent apparaître dans le code HTML de quelqu'un. La création d'un compteur qui recherche le "deuxième" caractère de nouvelle ligne n'est pas fiable. Cela pourrait ne pas exister.

Bien sûr, plus tard, la question de l' édition de texte dans un <p></p>élément vide avec des espaces supplémentaires (exemple 2) peut signifier détruire (peut-être, sauter) tous les nœuds de texte sauf un entre les balises d'un paragraphe pour s'assurer que l'élément contient précisément ce qu'il est censé afficher.

Quoi qu'il en soit, sauf dans les cas où vous faites quelque chose d'extraordinaire, vous aurez besoin d'un moyen de déterminer quelle .nodeValuepropriété du nœud de texte a le vrai texte lisible par l'homme que vous souhaitez modifier. filterWhitespaceLinesnous amène à mi-chemin.

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

À ce stade, vous pouvez avoir une sortie qui ressemble à ceci:

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

Il n'y a aucune garantie que ces deux chaînes soient adjacentes l'une à l'autre dans le DOM, donc les joindre avec .join() pourrait créer un composite non naturel. Au lieu de cela, dans le code qui appelle getTextById(), vous devez choisir la chaîne avec laquelle vous souhaitez travailler.

Testez la sortie.

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

On pourrait ajouter à l' .trim()intérieur de getStringsFromChildren()pour se débarrasser des espaces de début et de fin (ou pour transformer un tas d'espaces en une chaîne de longueur nulle ( ""), mais comment savoir a priori ce que chaque application peut avoir besoin pour le texte (chaîne) une fois qu'il est trouvé? Vous ne le faites pas, alors laissez cela à une implémentation spécifique, et laissezgetStringsFromChildren() générique.

Il peut y avoir des moments où ce niveau de spécificité (le targetet tel) n'est pas requis. C'est super. Utilisez une solution simple dans ces cas. Cependant, un algorithme généralisé permet de s'adapter à des situations simples et complexes.

Anthony Rutledge
la source
8

Version ES6 qui renvoie le premier contenu du nœud #text

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}
jujule
la source
Je m'interroge sur l'efficacité et la flexibilité. (1) Utilisation de .from()pour créer une instance de tableau copiée superficiellement. (2) L'utilisation de .find()pour faire une comparaison de chaînes en utilisant .nodeName. Utiliser node.NodeType === Node.TEXT_NODEserait mieux. (3) Renvoyer une chaîne vide lorsqu'aucune valeur,, nullest plus vraie si aucun nœud de texte n'est trouvé. Si aucun nœud de texte n'est trouvé, vous devrez peut-être en créer un! Si vous renvoyez une chaîne vide "", vous pouvez donner la fausse impression qu'un nœud de texte existe et peut être manipulé normalement. En substance, renvoyer une chaîne vide est un mensonge blanc et il vaut mieux éviter.
Anthony Rutledge
(4) S'il y a plus d'un nœud de texte dans une liste de nœuds, il n'y a aucun moyen ici de spécifier le nœud de texte souhaité. Vous voudrez peut-être le premier nœud de texte, mais vous voudrez peut-être très bien le dernier nœud de texte.
Anthony Rutledge
Que proposez-vous pour remplacer le Array.from?
jujule
@Snowman veuillez ajouter votre propre réponse pour ces changements de fond, ou faire des recommandations à OP pour leur donner la possibilité de les incorporer dans leur réponse.
TylerH
@jujule - Mieux vaut utiliser [...node.childNodes]pour convertir HTMLCollection en tableaux
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
la source
1
Je pense que la méthode pour le javascript standard doit être 'innerText'
Reporter le
2
Cela ne fonctionne pas comme le souhaite l'OP - il obtiendra également le texte dans l' aélément: jsfiddle.net/ekHJH
James Allardice
1
@James Allardice - J'ai fini avec la solution jquery maintenant cela fonctionnera .................
Pranay Rana
Cela fonctionnera presque, mais vous manquez le .au début de votre sélecteur, ce qui signifie que vous obtenez en fait le texte de l' titleélément, pas les éléments avecclass="title"
James Allardice
@reporter .innerTextest une ancienne convention IE récemment adoptée. En termes de script DOM standard, node.nodeValuec'est ainsi que l'on saisit le texte d'un nœud de texte.
Anthony Rutledge
2

Cela ignorera également les espaces blancs, vous n'avez donc jamais obtenu le code Blank textNodes..en utilisant le noyau 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;
    }
}

Vérifiez-le sur jsfiddle: - http://jsfiddle.net/webx/ZhLep/

webx
la source
curNode.nodeType === Node.TEXT_NODEserait mieux. L'utilisation de la comparaison de chaînes et d'une expression régulière dans une boucle est une solution peu performante, d'autant plus que l'ampleur des oDiv.childNodes.lengthaugmentations. Cet algorithme résout la question spécifique de l'OP, mais, potentiellement, à un coût de performance terrible. Si la disposition ou le nombre de nœuds de texte change, il n'est pas garanti que cette solution renvoie une sortie précise. En d'autres termes, vous ne pouvez pas cibler le nœud de texte exact souhaité. Vous êtes à la merci de la structure HTML et de la disposition du texte.
Anthony Rutledge
1

Vous pouvez également utiliser le text()test de nœud de XPath pour obtenir uniquement les nœuds de texte. Par exemple

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;
}
doubleDown
la source
0

C'est ma solution dans ES6 pour créer une chaîne contraining le texte concaténé de tous les nœuds enfants (récursif) . Notez que c'est également visitez le shdowroot des nœuds enfants.

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

Cette solution a été inspirée de la solution de https://stackoverflow.com/a/41051238./1300775 .

Damien
la source