Utilisation de .text () pour récupérer uniquement le texte non imbriqué dans les balises enfants

386

Si j'ai du HTML comme ça:

<li id="listItem">
    This is some text
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>

J'essaie d'utiliser .text()pour récupérer uniquement la chaîne "Ceci est du texte", mais si je devais dire $('#list-item').text(), j'obtiens "Ceci est du texte.

Existe-t-il un moyen d'obtenir (et éventuellement de supprimer, via quelque chose comme .text("")) juste le texte libre dans une balise, et non le texte dans ses balises enfants?

Le HTML n'a pas été écrit par moi, c'est donc ce avec quoi je dois travailler. Je sais qu'il serait simple de simplement envelopper le texte dans des balises lors de l'écriture du HTML, mais encore une fois, le HTML est pré-écrit.

MegaMatt
la source
Parce que je n'ai pas encore assez de réputation pour commenter et je ne souhaite pas que les connaissances soient perdues (j'espère que cela aide quelqu'un d'autre), une combinaison de réponse de macio.Jun , un RegExp et une réponse d' iStranger pour remplacer un textNode par HTML en Javascript? m'a permis de rechercher des nœuds de texte uniquement pour une chaîne et de remplacer toutes les occurrences par des liens.
JDQ

Réponses:

509

J'ai aimé cette implémentation réutilisable basée sur la clone()méthode trouvée ici pour obtenir uniquement le texte à l'intérieur de l'élément parent.

Code fourni pour référence facile:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text();
DotNetWala
la source
5
Avec cette solution, vous obtenez uniquement le texte sans l'enfant, mais vous ne pouvez pas remplacer uniquement le texte.
BenRoe
1
Je n'obtiens pas une chose: si .end () revient à l'élément sélectionné, alors text () devrait copier le texte original avec les éléments enfants. Mais dans la pratique, je vois que le texte de notre clone manipulé est copié. Donc end () retourne à clone ()?
68
C'est une façon vraiment inefficace de le faire
Billyonecan
5
@billyonecan, pouvez-vous suggérer une méthode plus efficace? C'est attrayant car c'est "propre" et "court". Que suggérez-vous?
derekmx271
1
@ derekmx271 jetez un oeil à la réponse de Stuart
billyonecan
364

Réponse simple:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 
macio.Jun
la source
38
Je ne comprends pas pourquoi les réponses efficaces (qui ne génèrent pas de structures de données superflues) ne sont pas autant votées que les réponses qui semblent moins effrayantes. +5 si je pouvais.
Steven Lu
16
la réponse simple et efficace
Paul Carroll
9
Ce n'est pas seulement plus efficace mais aussi correct! Cette solution répond aux situations où le texte est dispersé entre des éléments enfants. +5
Kyryll Tenin Baum
15
Pour être encore plus clair, si vous utilisez IE8 +, vous pouvez utiliser à la this.nodeType == Node.TEXT_NODEplace de this.nodeType == 3. Plus facile à lire et à comprendre l'OMI.
NorTicUs
8
Cela se cassera si vous l'utilisez sur quelque chose sans texte. Si vous l'utilisez comme fonction et avez un scénario où vous pouvez ou non avoir du texte, capturez simplement l' .contents().filter(...)appel dans une variable locale et vérifiez sa longueur, par exemple, var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema
158

Cela me semble être un cas d'utilisation excessive de jquery. Les éléments suivants saisiront le texte en ignorant les autres nœuds:

document.getElementById("listItem").childNodes[0];

Vous aurez besoin de couper cela, mais cela vous donne ce que vous voulez en une seule ligne facile.

ÉDITER

Ce qui précède obtiendra le nœud de texte . Pour obtenir le texte réel, utilisez ceci:

document.getElementById("listItem").childNodes[0].nodeValue;
rg88
la source
31
Meilleure réponse, vous n'êtes pas censé avoir besoin d'un plugin pour cela ou d'une chaîne de 10 appels jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
raine
5
que faire si le contenu du texte est divisé en plusieurs nœuds (comme une séquence de crlf, text, crlf)? y a-t-il des garanties (rael-life) que le dom construit par l'u utilisera la structure la plus simple?
collapsar
5
Totalement la meilleure réponse ... pourquoi d'autres personnes utilisent parfois jQuery?
ncubica
11
Cela ne fonctionne que dans le cas du <div id = "listItem"> texte que vous souhaitez <span> autre </span> </div>. Cela ne fonctionnera pas pour <div id = "listItem"> <span> autre </span> texte que vous souhaitez </div>
Spencer
1
Parfois, vous n'en avez pas document. Entré ici en utilisant cheerio.
flash
67

Plus simple et plus rapide:

$("#listItem").contents().get(0).nodeValue
RéveilMatin
la source
Ce navigateur croisé est-il compatible?
Rajat Gupta
Bien sûr, il récupère l'un des éléments mis en correspondance par l'objet jQuery donné par l'index: Jquery Docs .get () .
WakeupMorning
1
@Nate Si vous avez besoin de l'utiliser sur une balise <br/>, vous pouvez utiliser la réponse de macio.Jun .
WakeupMorning du
Ce devrait être la réponse acceptée.
Danny
2
Pourquoi get(0)au lieu de juste [0]?
Clonkex
28

Similaire à la réponse acceptée, mais sans clonage:

$("#foo").contents().not($("#foo").children()).text();

Et voici un plugin jQuery à cet effet:

$.fn.immediateText = function() {
    return this.contents().not(this.children()).text();
};

Voici comment utiliser ce plugin:

$("#foo").immediateText(); // get the text without children
DUzun
la source
Qu'est-ce que t dans t.children ()?
FrEaKmAn
C'est une solution en double de celle que pbjk a écrite en janvier 15 ... néanmoins - elle a l'air bien.
Oskar Holmkratz
1
Pas vraiment, @Oskar. La .contents()partie est critique ici!
DUzun
Mauvaise solution si vos nœuds n'utilisent pas d'identifiants.
AndroidDev
3
@AndroidDev Vous pouvez toujours remplacer le sélecteur par ce qui fonctionne pour vous. C'est juste pour illustrer la technique! J'ai également ajouté une version plugin pour montrer qu'il fonctionne même sans ID
DUzun
8

n'est pas le code:

var text  =  $('#listItem').clone().children().remove().end().text();

devenir juste jQuery pour l'amour de jQuery? Lorsque des opérations simples impliquent autant de commandes chaînées et autant de traitement (inutile), il est peut-être temps d'écrire une extension jQuery:

(function ($) {
    function elementText(el, separator) {
        var textContents = [];
        for(var chld = el.firstChild; chld; chld = chld.nextSibling) {
            if (chld.nodeType == 3) { 
                textContents.push(chld.nodeValue);
            }
        }
        return textContents.join(separator);
    }
    $.fn.textNotChild = function(elementSeparator, nodeSeparator) {
    if (arguments.length<2){nodeSeparator="";}
    if (arguments.length<1){elementSeparator="";}
        return $.map(this, function(el){
            return elementText(el,nodeSeparator);
        }).join(elementSeparator);
    }
} (jQuery));

appeler:

var text = $('#listItem').textNotChild();

les arguments sont dans le cas où un scénario différent est rencontré, tel que

<li>some text<a>more text</a>again more</li>
<li>second text<a>more text</a>again more</li>

var text = $("li").textNotChild(".....","<break>");

le texte aura une valeur:

some text<break>again more.....second text<break>again more
Brent
la source
1
Agréable. Que diriez-vous d'en faire une pull request pour la prochaine version de jQuery?
Jared Tomaszewski
8

Essaye ça:

$('#listItem').not($('#listItem').children()).text()
pbjk
la source
6

Il faudra que ce soit quelque chose adapté aux besoins, qui dépendent de la structure qui vous est présentée. Pour l'exemple que vous avez fourni, cela fonctionne:

$(document).ready(function(){
     var $tmp = $('#listItem').children().remove();
     $('#listItem').text('').append($tmp);
});

Démo: http://jquery.nodnod.net/cases/2385/run

Mais cela dépend assez du balisage qui est similaire à ce que vous avez publié.


la source
2
Futur lecteur, méfiez-vous: le code de cette réponse tue les enfants dans l'élément réel. On devrait utiliser la cloneméthode ici si ce n'est pas l'effet recherché.
Mahn
La réponse de @ DotNetWala, ci-dessous, et doit être utilisée à la place de celle-ci. Ou au moins, utilisez la .detach()méthode au lieu de .remove().
Don McCurdy
4
$($('#listItem').contents()[0]).text()

Variante courte de la réponse de Stuart.

ou avec get()

$($('#listItem').contents().get(0)).text()
galeksandrp
la source
4
jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};
Brave Dolphin
la source
1
Merci pour cet extrait de code, qui peut fournir une aide immédiate. Une explication appropriée améliorerait considérablement sa valeur éducative en montrant pourquoi il s'agit d'une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs ayant des questions similaires, mais pas identiques. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables.
Toby Speight
3

C'est une vieille question, mais la première réponse est très inefficace. Voici une meilleure solution:

$.fn.myText = function() {
    var str = '';

    this.contents().each(function() {
        if (this.nodeType == 3) {
            str += this.textContent || this.innerText || '';
        }
    });

    return str;
};

Et faites ceci:

$("#foo").myText();
rotaercz
la source
3

Je suppose que ce serait également une bonne solution - si vous souhaitez obtenir le contenu de tous les nœuds de texte qui sont des enfants directs de l'élément sélectionné.

$(selector).contents().filter(function(){ return this.nodeType == 3; }).text();

Remarque: la documentation jQuery utilise un code similaire pour expliquer la fonction de contenu: https://api.jquery.com/contents/

PS Il y a aussi un moyen un peu plus laid de le faire, mais cela montre plus en détail comment les choses fonctionnent et permet un séparateur personnalisé entre les nœuds de texte (peut-être que vous voulez un saut de ligne là-bas)

$(selector).contents().filter(function(){ return this.nodeType == 3; }).map(function() { return this.nodeValue; }).toArray().join("");
mvmn
la source
1

Je propose d'utiliser le createTreeWalker pour trouver tous les éléments de texte non attachés aux éléments html (cette fonction peut être utilisée pour étendre jQuery):

function textNodesOnlyUnder(el) {
  var resultSet = [];
  var n = null;
  var treeWalker  = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, function (node) {
    if (node.parentNode.id == el.id && node.textContent.trim().length != 0) {
      return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_SKIP;
  }, false);
  while (n = treeWalker.nextNode()) {
    resultSet.push(n);
  }
  return resultSet;
}



window.onload = function() {
  var ele = document.getElementById('listItem');
  var textNodesOnly = textNodesOnlyUnder(ele);
  var resultingText = textNodesOnly.map(function(val, index, arr) {
    return 'Text element N. ' + index + ' --> ' + val.textContent.trim();
  }).join('\n');
  document.getElementById('txtArea').value = resultingText;
}
<li id="listItem">
    This is some text
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>
<textarea id="txtArea" style="width: 400px;height: 200px;"></textarea>

gaetanoM
la source
1

Si la position indexdu nœud de texte est fixe parmi ses frères et sœurs, vous pouvez utiliser

$('parentselector').contents().eq(index).text()
inarilo
la source
1

Je ne sais pas dans quelle mesure ou combien de cas vous devez couvrir, mais pour votre exemple, si le texte vient toujours avant les premières balises HTML - pourquoi ne pas simplement diviser le code HTML interne à la première balise et prendre la première:

$('#listItem').html().split('<span')[0]; 

et si vous en avez besoin plus large peut-être juste

$('#listItem').html().split('<')[0]; 

et si vous avez besoin du texte entre deux marqueurs, comme après une chose mais avant une autre, vous pouvez faire quelque chose comme (non testé) et utiliser les instructions if pour le rendre suffisamment flexible pour avoir un marqueur de début ou de fin ou les deux, tout en évitant les erreurs de référence nulles :

var startMarker = '';// put any starting marker here
var endMarker = '<';// put the end marker here
var myText = String( $('#listItem').html() );
// if the start marker is found, take the string after it
myText = myText.split(startMarker)[1];        
// if the end marker is found, take the string before it
myText = myText.split(endMarker)[0];
console.log(myText); // output text between the first occurrence of the markers, assuming both markers exist.  If they don't this will throw an error, so some if statements to check params is probably in order...

Je crée généralement des fonctions utilitaires pour des choses utiles comme celle-ci, les rend exemptes d'erreurs, puis je les utilise souvent une fois solides, plutôt que de toujours réécrire ce type de manipulation de chaînes et risquer les références nulles, etc. De cette façon, vous pouvez réutiliser la fonction dans de nombreux projets et ne plus jamais y perdre de temps à déboguer pourquoi une référence de chaîne a une erreur de référence non définie. Ce n'est peut-être pas le code 1 ligne le plus court jamais créé, mais une fois que vous avez la fonction utilitaire, il ne s'agit plus que d'une ligne. Notez que la plupart du code gère simplement les paramètres présents ou non pour éviter les erreurs :)

Par exemple:

/**
* Get the text between two string markers.
**/
function textBetween(__string,__startMark,__endMark){
    var hasText = typeof __string !== 'undefined' && __string.length > 0;
    if(!hasText) return __string;
    var myText = String( __string );
    var hasStartMarker = typeof __startMark !== 'undefined' && __startMark.length > 0 && __string.indexOf(__startMark)>=0;
    var hasEndMarker =  typeof __endMark !== 'undefined' && __endMark.length > 0 && __string.indexOf(__endMark) > 0;
    if( hasStartMarker )  myText = myText.split(__startMark)[1];
    if( hasEndMarker )    myText = myText.split(__endMark)[0];
    return myText;
}

// now with 1 line from now on, and no jquery needed really, but to use your example:
var textWithNoHTML = textBetween( $('#listItem').html(), '', '<'); // should return text before first child HTML tag if the text is on page (use document ready etc)
OG Sean
la source
si vous devez remplacer du texte, utilisez simplement $('#listItem').html( newHTML ); où newHTML est une variable qui a déjà le texte supprimé.
OG Sean
0

C'est un bon moyen pour moi

   var text  =  $('#listItem').clone().children().remove().end().text();
Mif.ComicVN
la source
1
C'est exactement la même chose que la réponse de DotNetWala .
Tous les travailleurs sont essentiels
0

J'ai trouvé une solution spécifique qui devrait être beaucoup plus efficace que le clonage et la modification du clone. Cette solution ne fonctionne qu'avec les deux réserves suivantes, mais devrait être plus efficace que la solution actuellement acceptée:

  1. Vous obtenez uniquement le texte
  2. Le texte que vous souhaitez extraire se trouve avant les éléments enfants

Cela dit, voici le code:

// 'element' is a jQuery element
function getText(element) {
  var text = element.text();
  var childLength = element.children().text().length;
  return text.slice(0, text.length - childLength);
}
Yu Jiang Tham
la source
0

Tout comme la question, je tentais de texte extrait afin de faire une substitution regex du texte , mais devenais problèmes où mes éléments internes (ex: <i>, <div>,<span> , etc.) ont été enlevés également obtenir.

Le code suivant semble bien fonctionner et a résolu tous mes problèmes.

Il utilise certaines des réponses fournies ici mais en particulier, ne remplacera le texte que lorsque l'élément est de nodeType === 3.

$(el).contents().each(function() { 
  console.log(" > Content: %s [%s]", this, (this.nodeType === 3));

  if (this.nodeType === 3) {
    var text = this.textContent;
    console.log(" > Old   : '%s'", text);

    regex = new RegExp("\\[\\[" + rule + "\\.val\\]\\]", "g");
    text = text.replace(regex, value);

    regex = new RegExp("\\[\\[" + rule + "\\.act\\]\\]", "g");
    text = text.replace(regex, actual);

    console.log(" > New   : '%s'", text);
    this.textContent = text;
  }
});

Ce qui précède consiste à parcourir tous les éléments du donné el(qui a été simplement obtenu avec $("div.my-class[name='some-name']");. Pour chaque élément interne, il les ignore fondamentalement. Pour chaque partie de texte (comme déterminé parif (this.nodeType === 3) ), il appliquera la substitution d'expression régulière uniquement à ces éléments .

La this.textContent = textpartie remplace simplement le texte substitutif, qui dans mon cas, je cherchais des jetons comme [[min.val]], [[max.val]], etc.

Cet extrait de code court aidera quiconque essaie de faire ce que la question demandait ... et un peu plus.

Jeach
la source
-1

il suffit de le mettre dans un <p>ou<font> et de saisir cette $ ('# listItem font'). text ()

La première chose qui m'est venue à l'esprit

<li id="listItem">
    <font>This is some text</font>
    <span id="firstSpan">First span text</span>
    <span id="secondSpan">Second span text</span>
</li>
Dorjan
la source
6
Je n'ai aucun contrôle sur la mise en texte libre dans les balises, car le code sur lequel je travaille n'a pas été créé par moi. Si je pouvais saisir juste ce texte, je pourrais le supprimer et le remplacer par des balises autour de lui, ou faire tout ce que je veux. Mais encore une fois, le HTML est déjà pré-écrit.
MegaMatt
Ah ok. Ensuite, je pense que vous allez devoir filtrer les résultats: S désolé.
Dorjan
-1

Vous pouvez essayer ceci

alert(document.getElementById('listItem').firstChild.data)
achakravarty
la source
-2

Utilisez une condition supplémentaire pour vérifier si innerHTML et innerText sont identiques. Seulement dans ces cas, remplacez le texte.

$(function() {
$('body *').each(function () {
    console.log($(this).html());
    console.log($(this).text());
    if($(this).text() === "Search" && $(this).html()===$(this).text())  {
        $(this).html("Find");
    }
})
})

http://jsfiddle.net/7RSGh/

Paul Verschoor
la source
-2

Pour pouvoir rogner le résultat, utilisez le DotNetWala comme ceci:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text()
    .trim();

J'ai découvert que l'utilisation de la version plus courte comme document.getElementById("listItem").childNodes[0]ne fonctionnerait pas avec jQuery trim ().

Marion Go
la source
3
C'est parce que document.getElementById("listItem").childNodes[0]c'est du javascript simple, vous devez l'envelopper dans la fonction jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz
D'accord, cela a du sens. Haha. Merci!
Marion Go
1
Ceci est presque identique à la réponse de DotNetWala . Tout ce que vous avez fait a été ajouté .trim()à la fin. Cette réponse est-elle nécessaire?
Tous les travailleurs sont essentiels
-3

Je ne suis pas un expert jquery, mais que diriez-vous,

$('#listItem').children().first().text()
Sudheera
la source
1
Si vous êtes un expert jquery, alors pourquoi ne pas devenir plus expert en lisant d'abord les autres réponses? ... L'une d'elles était pratiquement la même que celle que vous avez écrite, avec des commentaires ci-dessous qui expliquent pourquoi ce n'est pas une bonne idée.
Oskar Holmkratz
-4

Cela n'a pas été testé, mais je pense que vous pourrez peut-être essayer quelque chose comme ceci:

 $('#listItem').not('span').text();

http://api.jquery.com/not/

El Guapo
la source
3
Parce que c'est la même chose que $('#listItem').text(). #listItemn'est pas un <span>ajout not('span')donc ne fait rien.
Thomas Higginbotham