Menggunakan .text () untuk mengambil hanya teks yang tidak bersarang di tag anak

386

Jika saya punya html seperti ini:

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

Saya mencoba menggunakan .text()untuk mengambil hanya string "Ini adalah beberapa teks", tetapi jika saya mengatakan $('#list-item').text(), saya mendapatkan "Ini adalah beberapa textFirst span textSecond span text".

Apakah ada cara untuk mendapatkan (dan mungkin menghapus, melalui sesuatu seperti .text("")) hanya teks gratis di dalam tag, dan bukan teks di dalam tag turunannya?

HTML tidak ditulis oleh saya, jadi ini yang harus saya kerjakan. Saya tahu itu akan mudah untuk hanya membungkus teks dalam tag saat menulis html, tetapi sekali lagi, html adalah pra-ditulis.

MegaMatt
sumber
Karena saya belum memiliki reputasi yang cukup untuk berkomentar dan saya tidak ingin pengetahuannya hilang (mudah-mudahan ini membantu orang lain), kombinasi jawaban macio.Jun , RegExp, dan iStranger untuk Mengganti textNode dengan HTML dalam Javascript? memungkinkan saya untuk mencari simpul teks saja dan mengganti semua kemunculan dengan tautan.
JDQ

Jawaban:

509

Saya menyukai implementasi yang dapat digunakan kembali ini berdasarkan clone()metode yang ditemukan di sini untuk mendapatkan hanya teks di dalam elemen induk.

Kode disediakan untuk referensi mudah:

$("#foo")
    .clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text();
DotNetWala
sumber
5
Dengan solusi ini Anda hanya mendapatkan teks tanpa anak, tetapi Anda tidak bisa hanya mengganti teks.
BenRoe
1
Saya tidak mendapatkan 1 hal: Jika .end () kembali ke elemen yang dipilih, daripada teks () harus menyalin teks asli dengan elemen anak-anak. Tetapi dalam praktiknya saya melihat bahwa teks dari klon yang kami manipulasi sedang disalin. Jadi end () kembali ke mengkloning ()?
68
Ini adalah cara yang sangat tidak efisien untuk melakukan ini
billyonecan
5
@ billyonecan, dapatkah Anda menyarankan metode yang lebih efisien? Ini menarik karena "bersih" dan "pendek". Apa yang Anda sarankan?
derekmx271
1
@ derekmx271 lihat jawaban Stuart
billyonecan
364

Jawaban sederhana:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 
macio.Jun
sumber
38
Saya tidak mengerti mengapa jawaban yang efisien (yang tidak menghasilkan struktur data asing) tidak dipilih sebanyak jawaban yang terlihat kurang menakutkan. +5 jika aku bisa.
Steven Lu
16
sederhana dan efisien jawaban
Paul Carroll
9
Ini tidak hanya lebih efisien tetapi juga benar! Solusi ini melayani situasi ketika teks tersebar di antara elemen anak. +5
Kyryll Tenin Baum
15
Untuk menjadi lebih jelas, jika Anda menggunakan IE8 +, Anda dapat menggunakan this.nodeType == Node.TEXT_NODEbukan this.nodeType == 3. Lebih mudah membaca dan memahami IMO.
NorTicUs
8
Ini akan rusak jika Anda menggunakannya pada sesuatu tanpa teks. Jika Anda menggunakan ini sebagai fungsi dan memiliki skenario di mana Anda mungkin atau mungkin tidak memiliki teks, cukup ambil .contents().filter(...)panggilan ke variabel lokal dan periksa panjangnya, misalnya, var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema
158

Ini sepertinya kasus jquery yang terlalu sering saya gunakan. Berikut ini akan mengambil teks mengabaikan node lain:

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

Anda harus memotongnya tetapi itu memberi Anda apa yang Anda inginkan dalam satu garis yang mudah.

EDIT

Di atas akan mendapatkan simpul teks . Untuk mendapatkan teks yang sebenarnya, gunakan ini:

document.getElementById("listItem").childNodes[0].nodeValue;
rg88
sumber
31
Jawaban terbaik, Anda tidak seharusnya memerlukan plugin untuk ini atau 10 panggilan jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
hujan
5
bagaimana jika konten teks dibagi menjadi beberapa node (seperti urutan crlf, text, crlf)? adakah (rael-life) yang menjamin bahwa dom yang dibangun oleh ua akan menggunakan struktur yang paling sederhana?
collapsar
5
Benar-benar jawaban terbaik ... mengapa orang lain terkadang menggunakan jQuery?
ncubica
11
Ini hanya berfungsi pada kasus <div id = "listItem"> teks yang Anda inginkan <span> lainnya </span> </div>. Ini tidak akan berfungsi untuk <div id = "listItem"> <span> lainnya </span> teks yang Anda inginkan </div>
Spencer
1
Terkadang kamu tidak punya document. Datang ke sini menggunakan cheerio.
Flash
67

Lebih mudah dan lebih cepat:

$("#listItem").contents().get(0).nodeValue
Bangun tidur
sumber
Apakah browser lintas ini kompatibel?
Rajat Gupta
Tentu saja ia mengambil salah satu elemen yang cocok dengan objek jQuery yang diberikan oleh indeks: Jquery Docs .get () .
WakeupMorning
1
@Nate Jika Anda perlu menggunakannya pada tag <br/> Anda bisa menggunakan jawaban macio.Jun .
WakeupMorning
Ini harus menjadi jawaban yang diterima.
Danny
2
Kenapa get(0)bukannya adil [0]?
Clonkex
28

Mirip dengan jawaban yang diterima, tetapi tanpa kloning:

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

Dan ini adalah plugin jQuery untuk tujuan ini:

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

Berikut ini cara menggunakan plugin ini:

$("#foo").immediateText(); // get the text without children
DUzun
sumber
Apa yang dimaksud dengan t.children ()?
FrEaKmAn
Ini adalah solusi duplikat dari yang ditulis pbjk di Jan'15 ... tetap saja - ini terlihat bagus.
Oskar Holmkratz
1
Tidak juga, @Oskar. Bagian .contents()ini sangat penting di sini!
DUzun
Solusi buruk jika node Anda tidak menggunakan id.
AndroidDev
3
@AndroidDev Anda selalu dapat mengganti pemilih dengan apa pun yang sesuai untuk Anda. Ini hanya untuk menggambarkan tekniknya! Saya juga menambahkan versi Plugin untuk menunjukkan bahwa itu berfungsi bahkan tanpa ID
DUzun
8

bukan kode:

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

hanya menjadi jQuery demi jQuery? Ketika operasi sederhana melibatkan banyak perintah dirantai & pemrosesan sebanyak itu (yang tidak perlu), mungkin sekarang saatnya untuk menulis ekstensi 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));

memanggil:

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

argumen dalam kasus skenario yang berbeda ditemui, seperti

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

teks akan memiliki nilai:

some text<break>again more.....second text<break>again more
Brent
sumber
1
Bagus. Bagaimana kalau membuat ini permintaan tarik untuk versi jQuery berikutnya?
Jared Tomaszewski
8

Coba ini:

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

Ini harus berupa sesuatu yang disesuaikan dengan kebutuhan, yang tergantung pada struktur yang Anda sajikan. Untuk contoh yang Anda berikan, ini berfungsi:

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

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

Tapi itu cukup tergantung pada markup yang mirip dengan apa yang Anda posting.


sumber
2
Waspada pembaca masa depan: kode dalam jawaban ini membunuh anak-anak di elemen yang sebenarnya. Orang harus menggunakan clonemetode di sini jika itu bukan efek yang diinginkan.
Mahn
@ DotNetWala menjawab, di bawah, dan harus digunakan sebagai pengganti yang ini. Atau setidaknya, gunakan .detach()metode sebagai ganti .remove().
Don McCurdy
4
$($('#listItem').contents()[0]).text()

Varian pendek dari jawaban Stuart.

atau dengan get()

$($('#listItem').contents().get(0)).text()
galeksandrp
sumber
4
jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};
Dolphin Berani
sumber
1
Terima kasih atas cuplikan kode ini, yang dapat memberikan bantuan segera. Penjelasan yang tepat akan sangat meningkatkan nilai pendidikannya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini, dan akan membuatnya lebih bermanfaat bagi pembaca masa depan dengan pertanyaan yang serupa, tetapi tidak sama. Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku.
Toby Speight
3

Ini adalah pertanyaan lama tetapi jawaban teratas sangat tidak efisien. Inilah solusi yang lebih baik:

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

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

    return str;
};

Dan lakukan ini:

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

Saya kira ini akan menjadi solusi yang bagus juga - jika Anda ingin mendapatkan konten dari semua node teks yang merupakan anak-anak langsung dari elemen yang dipilih.

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

Catatan: Dokumentasi jQuery menggunakan kode serupa untuk menjelaskan fungsi konten: https://api.jquery.com/contents/

PS Ada juga cara yang sedikit lebih buruk untuk melakukan itu, tetapi ini menunjukkan lebih mendalam bagaimana hal-hal bekerja, dan memungkinkan untuk pemisah kustom antara node teks (mungkin Anda ingin jeda baris di sana)

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

Saya mengusulkan untuk menggunakan createTreeWalker untuk menemukan semua elemen teks yang tidak dilampirkan ke elemen html (fungsi ini dapat digunakan untuk memperluas 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
sumber
1

Jika posisi indexnode teks diperbaiki di antara saudara kandungnya, Anda dapat menggunakan

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

Tidak yakin seberapa fleksibel atau berapa banyak kasus yang Anda butuhkan untuk dicakup, tetapi sebagai contoh, jika teks selalu muncul sebelum tag HTML pertama - mengapa tidak hanya membagi html bagian dalam pada tag pertama dan mengambil yang pertama:

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

dan jika Anda membutuhkannya lebih luas mungkin saja

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

dan jika Anda memerlukan teks di antara dua penanda, seperti setelah satu hal tetapi sebelum yang lain, Anda dapat melakukan sesuatu seperti (belum diuji) dan menggunakan pernyataan jika membuatnya cukup fleksibel untuk memiliki penanda awal atau akhir atau keduanya, sambil menghindari kesalahan ref nol :

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

Saya biasanya membuat fungsi utilitas untuk hal-hal berguna seperti ini, menjadikannya bebas kesalahan, dan kemudian mengandalkannya sesekali solid, daripada selalu menulis ulang jenis manipulasi string dan mempertaruhkan referensi nol dll. Dengan begitu, Anda dapat menggunakan kembali fungsi tersebut dalam banyak proyek dan tidak perlu membuang waktu lagi untuk debugging mengapa referensi string memiliki kesalahan referensi yang tidak ditentukan. Mungkin bukan kode baris 1 terpendek, tetapi setelah Anda memiliki fungsi utilitas, itu adalah satu baris sejak saat itu. Perhatikan sebagian besar kode hanya menangani parameter yang ada atau tidak untuk menghindari kesalahan :)

Sebagai contoh:

/**
* 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
sumber
jika Anda perlu mengganti teks, cukup gunakan $('#listItem').html( newHTML ); di mana newHTML adalah variabel yang sudah memiliki teks yang dilucuti.
OG Sean
0

Ini cara yang baik untuk saya

   var text  =  $('#listItem').clone().children().remove().end().text();
Mif.ComicVN
sumber
1
Ini persis sama dengan jawaban DotNetWala .
Semua Pekerja Sangat Penting
0

Saya datang dengan solusi spesifik yang harus jauh lebih efisien daripada kloning dan memodifikasi klon. Solusi ini hanya berfungsi dengan dua reservasi berikut, tetapi harus lebih efisien daripada solusi yang saat ini diterima:

  1. Anda hanya mendapatkan teks
  2. Teks yang ingin Anda ekstrak adalah sebelum elemen anak

Dengan itu, inilah kodenya:

// '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
sumber
0

Sama seperti pertanyaan, saya mencoba untuk ekstrak teks untuk melakukan beberapa regex substitusi teks tetapi mendapatkan masalah di mana unsur-unsur batin saya (yaitu: <i>, <div>, <span>, dll) yang mendapatkan juga dihapus.

Kode berikut tampaknya berfungsi dengan baik dan menyelesaikan semua masalah saya.

Ini menggunakan beberapa jawaban yang disediakan di sini tetapi khususnya, hanya akan menggantikan teks ketika elemen dari 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;
  }
});

Apa yang dilakukan di atas adalah loop melalui semua elemen yang diberikan el(yang hanya diperoleh dengan $("div.my-class[name='some-name']");. Untuk setiap elemen dalam, itu pada dasarnya mengabaikan mereka. Untuk setiap bagian teks (sebagaimana ditentukan oleh if (this.nodeType === 3)) itu akan menerapkan penggantian regex hanya untuk elemen-elemen tersebut. .

The this.textContent = textporsi hanya menggantikan teks tersubstitusi, yang dalam kasus saya, saya sedang mencari token seperti [[min.val]], [[max.val]], dll

Kutipan kode pendek ini akan membantu siapa saja yang mencoba melakukan apa yang ditanyakan oleh pertanyaan ... dan sedikit lagi.

Jeach
sumber
-1

cukup letakkan di <p>atau <font>dan ambil $ ('# font fontItem'). teks ()

Hal pertama yang terlintas di benak saya

<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
sumber
6
Saya tidak memiliki kendali untuk memasukkan teks gratis ke dalam tag, karena kode yang saya gunakan tidak dibuat oleh saya. Jika saya bisa mengambil teks itu saja, saya bisa menghapusnya dan menggantinya dengan tag di sekitarnya, atau melakukan apa pun yang saya inginkan. Tetapi sekali lagi, html sudah ditulis sebelumnya.
MegaMatt
ah baiklah Maka saya pikir Anda harus memfilter hasilnya: S sorry.
Dorjan
-1

Anda bisa mencoba ini

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

Gunakan kondisi ekstra untuk memeriksa apakah innerHTML dan innerText sama. Hanya dalam kasus tersebut, ganti teks.

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

Agar dapat memangkas hasilnya, gunakan DotNetWala seperti:

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

Saya menemukan bahwa menggunakan versi yang lebih pendek seperti document.getElementById("listItem").childNodes[0]tidak akan berfungsi dengan trim jQuery ().

Marion Go
sumber
3
Itu karena document.getElementById("listItem").childNodes[0]javascript biasa, Anda harus membungkusnya dalam fungsi jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz
Oke itu masuk akal. Ha ha. Terima kasih!
Marion Go
1
Ini hampir identik dengan jawaban DotNetWala . Semua yang Anda lakukan ditambahkan .trim()ke bagian akhir. Apakah jawaban ini perlu?
Semua Pekerja Sangat Penting
-3

Saya bukan ahli jquery, tapi bagaimana,

$('#listItem').children().first().text()
Sudheera
sumber
1
Jika Anda perhatikan seorang ahli jquery, lalu mengapa tidak menjadi lebih ahli dengan membaca jawaban yang lain terlebih dahulu? ... Salah satunya kebetulan hampir sama dengan apa yang Anda tulis, dengan komentar di bawah ini yang menjelaskan mengapa itu bukan sebuah ide bagus.
Oskar Holmkratz
-4

Ini belum teruji, tetapi saya pikir Anda mungkin dapat mencoba sesuatu seperti ini:

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

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

El Guapo
sumber
3
Karena sama dengan $('#listItem').text(). #listItembukan <span>jadi menambahkan not('span')tidak melakukan apa-apa.
Thomas Higginbotham