Użycie .text () do pobrania tylko tekstu nie zagnieżdżonego w znacznikach potomnych


386

Jeśli mam taki HTML:

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

Próbuję użyć, .text()aby pobrać tylko ciąg „To jest jakiś tekst”, ale gdybym powiedział $('#list-item').text(), otrzymuję „To jest jakiś tekst Pierwszy tekst o rozpiętości drugiego tekstu”.

Czy istnieje sposób, aby uzyskać (i ewentualnie usunąć, za pomocą czegoś podobnego .text("")) tylko dowolny tekst w znaczniku, a nie tekst w jego znacznikach potomnych?

HTML nie został napisany przeze mnie, więc z tym muszę pracować. Wiem, że pisanie html byłoby proste po prostu zawinąć tekst w tagi, ale znowu html jest wstępnie napisany.


Ponieważ nie mam jeszcze wystarczającej reputacji, aby móc komentować, i nie chcę, aby wiedza została utracona (mam nadzieję, że pomaga to komuś innemu), kombinacja odpowiedzi macio.Jun , RegExp i iStranger ', aby zastąpić textNode HTML w JavaScript? pozwolił mi przeszukiwać tekstowe węzły w poszukiwaniu ciągu i zastępować wszystkie wystąpienia linkami.
JDQ,

Odpowiedzi:


509

Podobała mi się ta implementacja wielokrotnego użytku oparta na clone()metodzie znalezionej tutaj, aby uzyskać tylko tekst wewnątrz elementu nadrzędnego.

Kod podany dla łatwego odniesienia:

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

5
Dzięki temu rozwiązaniu otrzymujesz tylko tekst bez dziecka, ale nie możesz zastąpić samego tekstu.
BenRoe

1
Nie dostaję 1 rzeczy: jeśli .end () wraca do wybranego elementu, to text () powinien skopiować oryginalny tekst z elementami potomnymi. Ale w praktyce widzę, że tekst z naszego zmanipulowanego klonu jest kopiowany. Więc end () wraca do clone ()?

68
Jest to naprawdę nieefektywny sposób na zrobienie tego
billyonecan

5
@billyonecan, czy możesz zasugerować bardziej wydajną metodę? Jest to atrakcyjne, ponieważ jest „czyste” i „krótkie”. Co sugerujesz?
derekmx271,

1
@ derekmx271 spójrz na odpowiedź Stuarta
billyonecan

364

Prosta odpowiedź:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 

38
Nie rozumiem, dlaczego wydajne odpowiedzi (które nie generują obcych struktur danych) nie są głosowane tak bardzo, jak odpowiedzi wyglądające mniej przerażająco. +5, gdybym mógł.
Steven Lu

16
prosta i skuteczna odpowiedź
Paul Carroll

9
Jest to nie tylko bardziej wydajne, ale także poprawne! To rozwiązanie służy do sytuacji, w których tekst jest rozproszony między elementami potomnymi. +5
Kyryll Tenin Baum

15
Aby być jeszcze jaśniejszym, jeśli używasz IE8 +, możesz użyć this.nodeType == Node.TEXT_NODEzamiast this.nodeType == 3. Łatwiej czytać i rozumieć IMO.
NorTicUs,

8
To się zepsuje, jeśli użyjesz go do czegoś bez tekstu. Jeśli używasz tego jako funkcji i masz scenariusz, w którym możesz mieć tekst lub nie, po prostu przechwyć .contents().filter(...)wywołanie do zmiennej lokalnej i sprawdź jej długość, np. var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema

157

Wydaje mi się to nadużywaniem jquery. Poniższe pobierze tekst ignorując inne węzły:

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

Musisz to przyciąć, ale uzyskasz to, czego chcesz w jednej, łatwej linii.

EDYTOWAĆ

Powyższe otrzyma węzeł tekstowy . Aby uzyskać rzeczywisty tekst, użyj tego:

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

31
Najlepsza odpowiedź, nie powinieneś potrzebować wtyczki do tego ani łańcucha 10 wywołań jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
raine

5
co jeśli zawartość tekstu zostanie podzielona na kilka węzłów (np. sekwencja crlf, tekst, crlf)? czy są jakieś (rael-life) gwarancje, że dom zbudowany przez UA będzie używał najprostszej struktury?
Collapsar

5
Zupełnie najlepsza odpowiedź ... dlaczego inni ludzie czasami zbyt często korzystają z jQuery?
ncubica

11
Działa to tylko w przypadku <div id = "listItem"> tekstu, który chcesz <span> inny </span> </div>. Nie będzie działać dla <div id = "listItem"> <span> inny </span> tekst, który chcesz </div>
Spencer

1
Czasami nie masz document. Przyszedł tutaj za pomocą cheerio.
flash


28

Podobna do zaakceptowanej odpowiedzi, ale bez klonowania:

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

A oto wtyczka jQuery do tego celu:

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

Oto jak korzystać z tej wtyczki:

$("#foo").immediateText(); // get the text without children

Co to jest t w t.children ()?
FrEaKmAn

To jest duplikat tego, które pbjk napisał w styczniu 2015 roku ... niemniej jednak - ładnie wygląda.
Oskar Holmkratz

1
Nie bardzo @Oskar. Ta .contents()część jest tutaj kluczowa!
DUzun,

Złe rozwiązanie, jeśli twoje węzły nie używają identyfikatorów.
AndroidDev,

3
@AndroidDev Zawsze możesz zastąpić selektor tym, co Ci odpowiada. To tylko w celu zilustrowania techniki! Dodałem również wersję wtyczki, aby pokazać, że działa nawet bez identyfikatorów
DUzun

8

nie jest kodem:

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

właśnie stajesz się jQuery dla dobra jQuery? Gdy proste operacje wymagają użycia wielu powiązanych łańcuchów i tyle (niepotrzebnego) przetwarzania, być może nadszedł czas, aby napisać rozszerzenie 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));

zadzwonić:

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

argumenty występują w przypadku napotkania innego scenariusza, np

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

tekst będzie miał wartość:

some text<break>again more.....second text<break>again more

1
Miły. A może zrobisz to jako żądanie ściągnięcia dla następnej wersji jQuery?
Jared Tomaszewski

8

Spróbuj tego:

$('#listItem').not($('#listItem').children()).text()

6

Będzie to musiało być coś dostosowanego do potrzeb, które zależą od prezentowanej struktury. W podanym przykładzie działa to:

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

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

Ale to dość zależy od tego, że znaczniki są podobne do tego, co opublikowałeś.


2
Przyszły czytelnik, strzeż się: kod w tej odpowiedzi zabija dzieci w rzeczywistym elemencie. Należy zastosować tę clonemetodę tutaj, jeśli nie jest to zamierzony efekt.
Mahn

Odpowiedź @ DotNetWala poniżej i powinna być używana zamiast tej. A przynajmniej użyj .detach()metody zamiast .remove().
Don McCurdy,


4
jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};

1
Dziękujemy za ten fragment kodu, który może zapewnić natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego wartość edukacyjną, pokazując, dlaczego jest to dobre rozwiązanie problemu i uczyniłoby to bardziej użytecznym dla przyszłych czytelników z podobnymi, ale nie identycznymi pytaniami. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Toby Speight

3

To stare pytanie, ale najlepsza odpowiedź jest bardzo nieefektywna. Oto lepsze rozwiązanie:

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

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

    return str;
};

I po prostu zrób to:

$("#foo").myText();

3

Zakładam, że byłoby to również dobre rozwiązanie - jeśli chcesz uzyskać zawartość wszystkich węzłów tekstowych, które są bezpośrednimi dziećmi wybranego elementu.

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

Uwaga: Dokumentacja jQuery używa podobnego kodu do wyjaśnienia funkcji zawartości: https://api.jquery.com/contents/

PS Istnieje również nieco bardziej brzydki sposób, ale pokazuje to bardziej szczegółowo, jak to działa, i pozwala na niestandardowy separator między węzłami tekstowymi (być może potrzebujesz podziału linii)

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

1

Proponuję użyć createTreeWalker do znalezienia wszystkich elementów tekstowych nieprzyłączonych do elementów HTML (ta funkcja może być użyta do rozszerzenia 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>


1

Jeśli pozycja indexwęzła tekstowego jest ustalona między rodzeństwem, możesz użyć

$('parentselector').contents().eq(index).text()

1

Nie wiem, jak elastycznie lub ile spraw potrzebujesz, aby to uwzględnić, ale na przykład, jeśli tekst zawsze pojawia się przed pierwszymi znacznikami HTML - dlaczego nie po prostu podzielić wewnętrzny HTML na pierwszy znacznik i wziąć pierwszy:

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

a jeśli potrzebujesz go szerszego, może po prostu

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

a jeśli potrzebujesz tekstu między dwoma znacznikami, na przykład po jednej rzeczy, ale przed drugą, możesz zrobić coś takiego (nieprzetestowane) i użyć instrukcji if, aby uczynić go wystarczająco elastycznym, aby mieć znacznik początkowy lub końcowy lub oba, jednocześnie unikając błędów zerowania odwołania :

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

Generalnie tworzę funkcje użyteczne dla takich przydatnych rzeczy, robię je bezbłędne, a następnie polegam na nich często, gdy są solidne, zamiast zawsze przepisywać tego typu manipulacje ciągami i ryzykować zerowe odwołania itp. W ten sposób możesz ponownie użyć funkcji w wielu projektach i nigdy więcej nie trzeba tracić czasu na debugowanie, dlaczego odwołanie do ciągu zawiera nieokreślony błąd odwołania. Być może nie będzie to najkrótszy 1-liniowy kod w historii, ale po uruchomieniu funkcji narzędziowej jest to jedna linia od tego momentu. Zauważ, że większość kodu to po prostu obsługa parametrów, aby uniknąć błędów :)

Na przykład:

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

jeśli chcesz zamienić tekst, po prostu użyj $('#listItem').html( newHTML ); gdzie newHTML jest zmienną, która ma już usunięty tekst.
OG Sean


0

Wymyśliłem konkretne rozwiązanie, które powinno być znacznie wydajniejsze niż klonowanie i modyfikowanie klonu. To rozwiązanie działa tylko z następującymi dwoma zastrzeżeniami, ale powinno być bardziej wydajne niż obecnie akceptowane rozwiązanie:

  1. Dostajesz tylko tekst
  2. Tekst, który chcesz wyodrębnić, znajduje się przed elementami potomnymi

Powiedziawszy to, oto kod:

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

0

Podobnie jak na pytanie, starałem się tekst ekstraktu w tym celu pewne podstawienie regex tekstu, ale był już problemów, gdzie moje elementy wewnętrzne (np: <i>, <div>, <span>, itd.) Brali również usunięte.

Poniższy kod wydaje się działać dobrze i rozwiązał wszystkie moje problemy.

Wykorzystuje niektóre z podanych tutaj odpowiedzi, ale w szczególności zastąpi tekst tylko wtedy, gdy element jest 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;
  }
});

To, co powyższe robi, polega na przechodzeniu przez wszystkie elementy danego elementu el(który został po prostu uzyskany za pomocą $("div.my-class[name='some-name']");. Dla każdego elementu wewnętrznego, w zasadzie je ignoruje. Dla każdej części tekstu (określonej przez if (this.nodeType === 3)) zastosuje podstawienie wyrażenia regularnego tylko do tych elementów .

this.textContent = textCzęść po prostu zastępuje Zmieniony tekst, który w moim przypadku, ja szukałem takich jak tokeny [[min.val]], [[max.val]]itp

Ten krótki fragment kodu pomoże każdemu, kto spróbuje zrobić to, o co pytało pytanie ... i jeszcze więcej.


-1

po prostu umieść to w <p>lub<font> i weź tę $ ('# listItem font'). text ()

Pierwsza rzecz, jaka przyszła mi do głowy

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

6
Nie mam kontroli nad umieszczaniem wolnego tekstu w tagach, ponieważ kod, nad którym pracuję, nie został stworzony przeze mnie. Gdybym mógł pobrać tylko ten tekst, mógłbym go usunąć i zastąpić tagami wokół niego lub zrobić wszystko, co chcę. Ale znowu HTML jest już wcześniej napisany.
MegaMatt

ah, ok. Więc myślę, że będziesz musiał przefiltrować wyniki: Przepraszam.
Dorjan

-1

Możesz tego spróbować

alert(document.getElementById('listItem').firstChild.data)

-2

Użyj dodatkowego warunku, aby sprawdzić, czy innerHTML i innerText są takie same. Tylko w tych przypadkach zastąp tekst.

$(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/


-2

Aby móc przyciąć wynik, użyj DotNetWala:

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

Dowiedziałem się, że użycie krótszej wersji jak document.getElementById("listItem").childNodes[0]nie działa z trim () jQuery.


3
To dlatego, że document.getElementById("listItem").childNodes[0]jest to zwykły javascript, musiałbyś zawinąć go w funkcję jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz

Ok, to ma sens. Ha ha. Dzięki!
Marion Go

1
Jest to prawie identyczne z odpowiedzią DotNetWala . Wszystko, co zrobiłeś, zostało dodane .trim()do końca. Czy ta odpowiedź jest konieczna?
Wszyscy pracownicy są niezbędni

-3

Nie jestem ekspertem od jquery, ale co powiesz na

$('#listItem').children().first().text()

1
Jeśli jesteś ekspertem od jquery, to dlaczego nie zostać ekspertem od przeczytania najpierw innych odpowiedzi? ... Jedna z nich była praktycznie taka sama jak to, co napisałeś, a poniższe komentarze wyjaśniają, dlaczego tak nie jest dobry pomysł.
Oskar Holmkratz

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.