Verwenden von .text (), um nur Text abzurufen, der nicht in untergeordneten Tags verschachtelt ist

386

Wenn ich HTML wie folgt habe:

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

Ich versuche, .text()nur die Zeichenfolge "Dies ist ein Text" abzurufen, aber wenn ich sagen würde $('#list-item').text(), erhalte ich "Dies ist ein TextFirst span textSecond span text".

Gibt es eine Möglichkeit, .text("")nur den freien Text in einem Tag und nicht den Text in den untergeordneten Tags abzurufen (und möglicherweise über so etwas zu entfernen )?

Der HTML-Code wurde nicht von mir geschrieben, daher muss ich damit arbeiten. Ich weiß, dass es einfach wäre, den Text beim Schreiben des HTML-Codes einfach in Tags zu verpacken, aber auch hier ist der HTML-Code vorab geschrieben.

MegaMatt
quelle
Da ich noch nicht genug Ruf habe, um Kommentare abzugeben, und ich nicht möchte, dass das Wissen verloren geht (hoffentlich hilft es jemand anderem), eine Kombination aus macio.Jun-Antwort , RegExp und iStranger-Antwort, um einen Textknoten durch HTML zu ersetzen in Javascript? Ich konnte nur Textknoten nach einer Zeichenfolge durchsuchen und alle Vorkommen durch Links ersetzen.
JDQ

Antworten:

509

Ich mochte diese wiederverwendbare Implementierung, die auf der hierclone() gefundenen Methode basiert , um nur den Text innerhalb des übergeordneten Elements zu erhalten.

Code zur einfachen Bezugnahme:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text();
DotNetWala
quelle
5
Mit dieser Lösung erhalten Sie nur den Text ohne das untergeordnete Element, können jedoch nicht nur den Text ersetzen.
BenRoe
1
Ich bekomme nichts: Wenn .end () zum ausgewählten Element zurückkehrt, sollte text () den Originaltext mit untergeordneten Elementen kopieren. In der Praxis sehe ich jedoch, dass Text aus unserem manipulierten Klon kopiert wird. Also geht end () zurück zu clone ()?
68
Dies ist eine wirklich ineffiziente Methode, um dies zu tun
Billyonecan
5
@billyonecan, können Sie eine effizientere Methode vorschlagen? Dies ist ansprechend, weil es "sauber" und "kurz" ist. Was schlagen Sie vor?
derekmx271
1
@ derekmx271 werfen Sie einen Blick auf Stuarts Antwort
Billyonecan
364

Einfache Antwort:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 
macio.Jun
quelle
38
Ich verstehe nicht, warum effiziente Antworten (die keine fremden Datenstrukturen erzeugen) nicht so oft bewertet werden wie Antworten, die weniger beängstigend aussehen. +5 wenn ich könnte.
Steven Lu
16
die einfache und effiziente Antwort
Paul Carroll
9
Das ist nicht nur effizienter, sondern auch richtig! Diese Lösung ist für Situationen geeignet, in denen der Text zwischen untergeordneten Elementen verteilt ist. +5
Kyryll Tenin Baum
15
Um noch klarer zu sein, wenn Sie IE8 + verwenden, können Sie this.nodeType == Node.TEXT_NODEstattdessen verwenden this.nodeType == 3. IMO leichter zu lesen und zu verstehen.
NorTicUs
8
Dies wird unterbrochen, wenn Sie es für etwas ohne Text verwenden. Wenn Sie dies als Funktion verwenden und ein Szenario haben, in dem Sie möglicherweise Text haben oder nicht, erfassen Sie den .contents().filter(...)Aufruf einfach in einer lokalen Variablen und überprüfen Sie deren Länge, z. B. var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema
157

Dies scheint mir ein Fall von Überbeanspruchung von Fragen zu sein. Im Folgenden wird der Text erfasst, wobei die anderen Knoten ignoriert werden:

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

Sie müssen das kürzen, aber es bringt Ihnen in einer einfachen Linie das, was Sie wollen.

BEARBEITEN

Das Obige erhält den Textknoten . Verwenden Sie Folgendes, um den eigentlichen Text zu erhalten:

document.getElementById("listItem").childNodes[0].nodeValue;
rg88
quelle
31
Beste Antwort, Sie sollten kein Plugin für diese oder eine Kette von 10 jQuery-Aufrufen benötigen. $('.foo')[0].childNodes[0].nodeValue.trim()
Raine
5
Was ist, wenn der Textinhalt in mehrere Knoten aufgeteilt ist (wie eine Folge von crlf, text, crlf)? Gibt es irgendwelche (Rael-Life) Garantien, dass der von den USA konstruierte Dom die einfachste Struktur verwendet?
Kollapsar
5
Absolut die beste Antwort ... warum andere Leute jQuery manchmal überbeanspruchen?
Ncubica
11
Dies funktioniert nur bei <div id = "listItem"> Text, den Sie <span> other </ span> </ div> wünschen. Es wird nicht funktionieren für <div id = "listItem"> <span> anderen </ span> Text, den Sie möchten </ div>
Spencer
1
Manchmal hast du nicht document. Kam hier mit cheerio.
Flash
67

Einfacher und schneller:

$("#listItem").contents().get(0).nodeValue
WakeupMorning
quelle
Ist dieser Cross-Browser kompatibel?
Rajat Gupta
Natürlich ruft es eines der Elemente ab, die mit dem vom Index angegebenen jQuery-Objekt übereinstimmen: Jquery Docs .get () .
WakeupMorning
1
@Nate Falls Sie es für ein <br/> -Tag verwenden müssen, können Sie die Antwort von macio.Jun verwenden .
WakeupMorning
Dies sollte die akzeptierte Antwort sein.
Danny
2
Warum get(0)statt nur [0]?
Clonkex
28

Ähnlich der akzeptierten Antwort, jedoch ohne Klonen:

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

Und hier ist ein jQuery-Plugin für diesen Zweck:

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

So verwenden Sie dieses Plugin:

$("#foo").immediateText(); // get the text without children
DUzun
quelle
Was ist t in t.children ()?
FrEaKmAn
Dies ist eine doppelte Lösung von der, die pbjk im Januar 15 geschrieben hat ... trotzdem - es sieht gut aus.
Oskar Holmkratz
1
Nicht wirklich, @Oskar. Der .contents()Teil ist hier kritisch!
DUzun
Schlechte Lösung, wenn Ihre Knoten keine IDs verwenden.
AndroidDev
3
@AndroidDev Sie können den Selektor jederzeit durch das ersetzen, was für Sie funktioniert. Dies dient nur zur Veranschaulichung der Technik! Ich habe auch eine Plugin-Version hinzugefügt, um zu zeigen, dass es auch ohne IDs
funktioniert
8

ist nicht der Code:

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

Nur um jQuery willen jQuery werden? Wenn einfache Operationen so viele verkettete Befehle und so viel (unnötige) Verarbeitung beinhalten, ist es vielleicht an der Zeit, eine jQuery-Erweiterung zu schreiben:

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

anrufen:

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

Die Argumente sind für den Fall, dass ein anderes Szenario auftritt, z

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

Text hat Wert:

some text<break>again more.....second text<break>again more
Brent
quelle
1
Nett. Wie wäre es damit, eine Pull-Anfrage für die nächste Version von jQuery zu machen?
Jared Tomaszewski
8

Versuche dies:

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

Es muss etwas sein, das auf die Bedürfnisse zugeschnitten ist, die von der Struktur abhängen, mit der Sie präsentiert werden. Für das von Ihnen bereitgestellte Beispiel funktioniert dies:

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

Demo: http://jquery.nodnod.net/cases/2385/run

Aber es hängt ziemlich davon ab, dass das Markup dem ähnelt, was Sie gepostet haben.


quelle
2
Zukünftige Leser aufgepasst: Der Code in dieser Antwort tötet die Kinder im eigentlichen Element. Man sollte die cloneMethode hier anwenden, wenn dies nicht der beabsichtigte Effekt ist.
Mahn
@ DotNetWalas Antwort unten und sollte anstelle dieser verwendet werden. Oder verwenden Sie zumindest die .detach()Methode anstelle von .remove().
Don McCurdy
4
$($('#listItem').contents()[0]).text()

Kurze Variante der Antwort von Stuart.

oder mit get()

$($('#listItem').contents().get(0)).text()
galeksandrp
quelle
4
jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};
Tapferer Delphin
quelle
1
Vielen Dank für dieses Code-Snippet, das möglicherweise sofortige Hilfe bietet. Eine richtige Erklärung würde den Bildungswert erheblich verbessern , indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und sie für zukünftige Leser mit ähnlichen, aber nicht identischen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, und geben Sie an, welche Einschränkungen und Annahmen gelten.
Toby Speight
3

Dies ist eine alte Frage, aber die beste Antwort ist sehr ineffizient. Hier ist eine bessere Lösung:

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

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

    return str;
};

Und mach einfach das:

$("#foo").myText();
rotaercz
quelle
3

Ich gehe davon aus, dass dies auch eine gute Lösung wäre - wenn Sie den Inhalt aller Textknoten abrufen möchten, die direkte untergeordnete Elemente des ausgewählten Elements sind.

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

Hinweis: In der jQuery-Dokumentation wird ein ähnlicher Code verwendet, um die Inhaltsfunktion zu erläutern: https://api.jquery.com/contents/

PS Es gibt auch einen etwas hässlicheren Weg, dies zu tun, aber dies zeigt detaillierter, wie die Dinge funktionieren, und ermöglicht ein benutzerdefiniertes Trennzeichen zwischen Textknoten (vielleicht möchten Sie dort einen Zeilenumbruch)

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

Ich schlage vor, den createTreeWalker zu verwenden, um alle Textelemente zu finden, die nicht an HTML-Elemente angehängt sind (diese Funktion kann zum Erweitern von jQuery verwendet werden):

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
quelle
1

Wenn die Position indexdes Textknotens unter seinen Geschwistern festgelegt ist, können Sie verwenden

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

Sie sind sich nicht sicher, wie flexibel oder wie viele Fälle Sie benötigen, um es abzudecken, aber zum Beispiel, wenn der Text immer vor den ersten HTML-Tags steht - warum nicht einfach das innere HTML beim ersten Tag aufteilen und das erstere nehmen:

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

und wenn Sie es breiter brauchen, vielleicht nur

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

und wenn Sie den Text zwischen zwei Markierungen benötigen, wie nach einer Sache, aber vor einer anderen, können Sie so etwas wie (ungetestet) tun und if-Anweisungen verwenden, um ihn flexibel genug zu machen, um eine Start- oder Endmarkierung oder beides zu haben, während Null-Ref-Fehler vermieden werden ::

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

Im Allgemeinen mache ich Dienstprogrammfunktionen für nützliche Dinge wie diese, mache sie fehlerfrei und verlasse mich dann häufig auf sie, sobald sie fest sind, anstatt diese Art der Zeichenfolgenmanipulation immer neu zu schreiben und Nullreferenzen usw. zu riskieren. Auf diese Weise können Sie die Funktion wiederverwenden in vielen Projekten und müssen nie wieder Zeit damit verschwenden, zu debuggen, warum eine Zeichenfolgenreferenz einen undefinierten Referenzfehler aufweist. Könnte nicht der kürzeste 1-Zeilen-Code aller Zeiten sein, aber nachdem Sie die Utility-Funktion haben, ist es von da an eine Zeile. Beachten Sie, dass der größte Teil des Codes nur Parameter behandelt, die vorhanden sind oder nicht, um Fehler zu vermeiden :)

Zum Beispiel:

/**
* 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
quelle
Wenn Sie Text ersetzen müssen, verwenden Sie einfach $('#listItem').html( newHTML ); Dabei ist newHTML eine Variable, die bereits den abgespeckten Text enthält.
OG Sean
0

Das ist ein guter Weg für mich

   var text  =  $('#listItem').clone().children().remove().end().text();
Mif.ComicVN
quelle
1
Dies entspricht genau der Antwort von DotNetWala .
Alle Arbeiter sind essentiell
0

Ich habe eine spezifische Lösung gefunden, die viel effizienter sein sollte als das Klonen und Modifizieren des Klons. Diese Lösung funktioniert nur mit den folgenden zwei Vorbehalten, sollte jedoch effizienter sein als die derzeit akzeptierte Lösung:

  1. Sie erhalten nur den Text
  2. Der Text, den Sie extrahieren möchten, steht vor den untergeordneten Elementen

Hier ist der 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
quelle
0

Genau wie die Frage, ich habe versucht zu extrahieren Text , um einige regex Substitution des Textes zu tun , sondern war immer Probleme , wo meine inneren Elemente (zB: <i>, <div>, <span>, etc.) wurden ebenfalls entfernt zu werden .

Der folgende Code scheint gut zu funktionieren und hat alle meine Probleme gelöst.

Es werden einige der hier angegebenen Antworten verwendet, aber insbesondere wird der Text nur ersetzt, wenn das Element von ist 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;
  }
});

Was das Obige tut, ist eine Schleife durch alle Elemente des Gegebenen el(was einfach mit erhalten wurde $("div.my-class[name='some-name']");. Für jedes innere Element werden sie grundsätzlich ignoriert. Für jeden Teil des Textes (wie durch bestimmt if (this.nodeType === 3)) wird die Regex-Substitution nur auf diese Elemente angewendet .

Der this.textContent = textTeil ersetzt einfach den substituierten Text, der in meinem Fall, ich war für Token, die wie [[min.val]], [[max.val]]usw.

Dieser Kurzcode-Auszug hilft jedem, der versucht, das zu tun, was die Frage gestellt hat ... und ein bisschen mehr.

Jeach
quelle
-1

Gib es einfach in ein <p>oder <font>und nimm das $ ('# listItem font'). text ()

Das erste, was mir in den Sinn kam

<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
quelle
6
Ich habe keine Kontrolle darüber, wie der freie Text in Tags eingefügt wird, da der Code, an dem ich arbeite, nicht von mir erstellt wurde. Wenn ich nur diesen Text greifen könnte, könnte ich ihn entfernen und durch Tags ersetzen oder alles tun, was ich will. Aber auch hier ist das HTML bereits vorab geschrieben.
MegaMatt
ah, ok Dann müssen Sie die Ergebnisse filtern: Entschuldigung.
Dorjan
-1

Sie können dies versuchen

alert(document.getElementById('listItem').firstChild.data)
Achakravarty
quelle
-2

Verwenden Sie eine zusätzliche Bedingung, um zu überprüfen, ob innerHTML und innerText identisch sind. Ersetzen Sie den Text nur in diesen Fällen.

$(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
quelle
-2

Verwenden Sie DotNetWala wie folgt, um das Ergebnis zu kürzen:

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

Ich fand heraus, dass die Verwendung der kürzeren Version wie document.getElementById("listItem").childNodes[0]mit jQuerys trim () nicht funktioniert.

Marion Go
quelle
3
Das liegt daran, dass document.getElementById("listItem").childNodes[0]es sich um einfaches Javascript handelt. Sie müssten es in die jQuery-Funktion einbinden$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz
Okay, das macht Sinn. Haha. Vielen Dank!
Marion Go
1
Dies ist nahezu identisch mit der Antwort von DotNetWala . Alles, was Sie getan haben, wurde .trim()am Ende hinzugefügt . Ist diese Antwort notwendig?
Alle Arbeiter sind essentiell
-3

Ich bin kein JQuery-Experte, aber wie wäre es,

$('#listItem').children().first().text()
Sudheera
quelle
1
Wenn Sie ein JQuery-Experte sind, warum werden Sie dann nicht mehr ein Experte, indem Sie zuerst die anderen Antworten durchlesen? ... Eine davon war praktisch die gleiche wie die, die Sie geschrieben haben, mit Kommentaren unten, die erklären, warum dies nicht der Fall ist eine gute Idee.
Oskar Holmkratz
-4

Dies ist noch nicht getestet, aber ich denke, Sie können möglicherweise Folgendes ausprobieren:

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

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

El Guapo
quelle
3
Weil es das gleiche ist wie $('#listItem').text(). #listItemist nicht <span>so hinzufügen not('span')macht nichts.
Thomas Higginbotham