Bagaimana cara mendapatkan simpul teks dari suatu elemen?

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

Saya ingin mendapatkan "Saya node teks", tidak ingin menghapus tag "edit", dan memerlukan solusi lintas browser.

Val
sumber
pertanyaan ini sangat mirip dengan stackoverflow.com/questions/3172166/… - lihat jawaban tersebut untuk versi JS biasa dari jawaban James
Mala

Jawaban:

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

Ini mendapatkan contentselemen yang dipilih, dan menerapkan fungsi filter padanya. Fungsi filter hanya mengembalikan node teks (yaitu node dengan nodeType == Node.TEXT_NODE).

James Allardice
sumber
@Val - maaf, saya melewatkan kode aslinya. Saya akan memperbarui jawaban untuk menunjukkannya. Anda perlu text()karena filterfungsi mengembalikan node itu sendiri, bukan konten node.
James Allardice
1
Tidak yakin mengapa tapi saya tidak berhasil saat menguji teori di atas. Saya menjalankan yang berikut ini jQuery("*").each(function() { console.log(this.nodeType); })dan saya mendapatkan 1 untuk semua jenis node.
Batandwa
Apakah mungkin untuk mendapatkan teks pada node yang diklik dan teks pada semua anaknya?
Jenna Kwon
Ini menarik dan memecahkan masalah ini, tetapi apa yang terjadi ketika situasinya semakin kompleks? Ada cara yang lebih fleksibel untuk menyelesaikan pekerjaan.
Anthony Rutledge
Tanpa jQuery, document.querySelector (". Title"). ChildNodes [0] .nodeValue
Balaji Gunasekaran
53

Anda bisa mendapatkan nodeValue dari childNode pertama menggunakan

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

http://jsfiddle.net/TU4FB/

Dogbert
sumber
4
Sementara itu akan berhasil, itu tergantung pada posisi node anak. Jika (ketika) itu berubah, itu akan rusak.
Armstrongest
Jika node teks bukan anak pertama, Anda mungkin mendapatkan nullnilai kembali.
Anthony Rutledge
14

Jika Anda bermaksud mendapatkan nilai node teks pertama dalam elemen, kode ini akan berfungsi:

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

Anda dapat melihat ini beraksi di sini: http://jsfiddle.net/ZkjZJ/

Shadow Wizard adalah Ear For You
sumber
Saya pikir Anda bisa menggunakan curNode.nodeType == 3bukan nodeNamejuga.
Nilloc
1
@Nilloc mungkin, tapi apa untungnya?
Shadow Wizard adalah Ear For You
5
@ShadowWizard @Nilloc merekomendasikan cara untuk itu adalah dengan menggunakan konstanta ... curNode.nodeType == Node.TEXT_NODE(perbandingan numerik lebih cepat tetapi curNode.nodeType == 3 tidak dapat dibaca - node apa yang memiliki angka 3?)
mikep
1
@ShadowWizard Gunakan curNode.NodeType === Node.TEXT_NODE. Perbandingan ini terjadi dalam loop iterasi yang mungkin tidak diketahui. Membandingkan dua angka kecil lebih baik daripada membandingkan string dengan panjang yang berbeda (pertimbangan waktu dan ruang). Pertanyaan yang benar untuk ditanyakan dalam situasi ini adalah "jenis / tipe node apa yang saya miliki?", Dan bukan "nama apa yang saya miliki?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
Anthony Rutledge
2
@ShadowWizard Selain itu, jika Anda akan menggunakan loop untuk menyaring childNodes, ketahuilah bahwa node elemen dapat memiliki lebih dari satu node teks. Dalam solusi umum, seseorang mungkin perlu menentukan instance node teks mana di dalam node elemen yang ingin Anda targetkan (pertama, kedua, ketiga, dll ...).
Anthony Rutledge
13

Solusi JS asli lainnya yang dapat berguna untuk elemen "kompleks" atau sangat bertingkat adalah dengan menggunakan NodeIterator . Letakkan NodeFilter.SHOW_TEXTsebagai argumen kedua ("whatToShow"), dan lakukan iterasi hanya pada anak node teks elemen tersebut.

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>

Anda juga bisa menggunakan TreeWalker. Perbedaan antara keduanya adalah NodeIteratoriterator linier sederhana, sementara TreeWalkermemungkinkan Anda untuk menavigasi melalui saudara dan leluhur juga.

Yuval A.
sumber
9

JavaScript Murni: Minimalis

Pertama, selalu ingat ini saat mencari teks di DOM.

MDN - Spasi di DOM

Masalah ini akan membuat Anda memperhatikan struktur XML / HTML Anda.

Dalam contoh JavaScript murni ini, saya memperhitungkan kemungkinan beberapa node teks yang dapat disisipkan dengan node jenis lain . Namun, awalnya, saya tidak memberikan penilaian pada spasi, membiarkan tugas pemfilteran itu ke kode lain.

Dalam versi ini, saya meneruskan NodeListdari kode panggilan / klien.

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

Tentu saja, dengan menguji node.hasChildNodes()terlebih dahulu, tidak perlu menggunakan forloop pra-tes .

/**
* 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 Murni: Kuat

Di sini fungsi tersebut getTextById()menggunakan dua fungsi pembantu: getStringsFromChildren()dan 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.
}

Selanjutnya, nilai yang dikembalikan (Array, atau null) dikirim ke kode klien tempat nilai itu harus ditangani. Mudah-mudahan, array harus memiliki elemen string teks asli, bukan baris spasi.

String kosong ( "") tidak dikembalikan karena Anda memerlukan node teks untuk menunjukkan dengan benar keberadaan teks yang valid. Returning ( "") dapat memberikan kesan yang salah bahwa node teks ada, membuat seseorang berasumsi bahwa mereka dapat mengubah teks dengan mengubah nilai .nodeValue. Ini salah, karena node teks tidak ada dalam kasus string kosong.

Contoh 1 :

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

Contoh 2 :

<p id="bio">

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

Masalahnya muncul saat Anda ingin membuat HTML Anda mudah dibaca dengan memberi jarak. Sekarang, meskipun tidak ada teks valid yang dapat dibaca manusia, masih ada node teks dengan "\n"karakter newline ( ) di .nodeValuepropertinya.

Manusia melihat contoh satu dan dua sebagai fungsi yang setara - elemen kosong menunggu untuk diisi. DOM berbeda dengan penalaran manusia. Inilah sebabnya mengapa getStringsFromChildren()fungsi harus menentukan apakah ada node teks dan mengumpulkan .nodeValuenilai - nilai ke dalam array.

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

Dalam contoh dua, dua node teks memang ada dan getStringFromChildren()akan mengembalikan .nodeValuekeduanya ( "\n"). Namun, filterWhitespaceLines()menggunakan ekspresi reguler untuk memfilter baris karakter spasi putih murni.

Apakah mengembalikan karakter nullbaris baru ( "\n") sebagai bentuk kebohongan ke klien / kode panggilan? Dalam istilah manusia, tidak. Dalam istilah DOM, ya. Namun, masalahnya di sini adalah mendapatkan teks, bukan mengeditnya. Tidak ada teks manusia untuk kembali ke kode panggilan.

Seseorang tidak akan pernah tahu berapa banyak karakter baris baru yang mungkin muncul di HTML seseorang. Membuat penghitung yang mencari karakter baris baru "kedua" tidak dapat diandalkan. Mungkin tidak ada.

Tentu saja, lebih jauh lagi, masalah pengeditan teks dalam <p></p>elemen kosong dengan spasi ekstra (contoh 2) mungkin berarti menghancurkan (mungkin, melewatkan) semua kecuali satu simpul teks di antara tag paragraf untuk memastikan elemen tersebut berisi dengan tepat apa itu. seharusnya ditampilkan.

Terlepas dari itu, kecuali untuk kasus di mana Anda melakukan sesuatu yang luar biasa, Anda akan memerlukan cara untuk menentukan .nodeValueproperti node teks mana yang memiliki teks yang benar dan dapat dibaca manusia yang ingin Anda edit. filterWhitespaceLinesmembuat kita setengah jalan ke sana.

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

Pada titik ini Anda mungkin memiliki keluaran yang terlihat seperti ini:

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

Tidak ada jaminan bahwa kedua string ini berdekatan satu sama lain di DOM, jadi menggabungkan keduanya dengan .join()dapat membuat komposit yang tidak wajar. Sebagai gantinya, dalam kode yang memanggil getTextById(), Anda harus memilih string mana yang ingin Anda gunakan.

Uji hasilnya.

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

Seseorang dapat menambahkan .trim()di dalam getStringsFromChildren()untuk menghilangkan spasi kosong di depan dan di belakang (atau untuk mengubah sekumpulan spasi menjadi string panjang nol ( ""), tetapi bagaimana Anda bisa mengetahui apriori apa yang mungkin dibutuhkan setiap aplikasi untuk terjadi pada teks (string) setelah ditemukan? Anda tidak, jadi serahkan itu pada implementasi tertentu, dan biarkan getStringsFromChildren()generik.

Mungkin ada kalanya tingkat kekhususan ini ( targetdan semacamnya) tidak diperlukan. Itu hebat. Gunakan solusi sederhana dalam kasus tersebut. Namun, algoritme umum memungkinkan Anda mengakomodasi situasi yang sederhana dan kompleks.

Anthony Rutledge
sumber
8

Versi ES6 yang mengembalikan konten node #text pertama

const extract = (node) => {
  const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
  return text && text.textContent.trim();
}
jujule
sumber
Saya bertanya-tanya tentang efisiensi dan fleksibilitas. (1) Penggunaan .from()untuk membuat instance array yang disalin dangkal. (2) Penggunaan .find()untuk melakukan perbandingan string menggunakan .nodeName. Menggunakan node.NodeType === Node.TEXT_NODEakan lebih baik. (3) Mengembalikan string kosong bila tidak ada nilai,, nulllebih benar jika tidak ada simpul teks yang ditemukan. Jika tidak ada simpul teks yang ditemukan, seseorang mungkin perlu membuatnya! Jika Anda mengembalikan string kosong`` ""Anda dapat memberikan kesan palsu bahwa node teks ada dan dapat dimanipulasi secara normal. Intinya, mengembalikan string kosong adalah kebohongan putih dan sebaiknya dihindari.
Anthony Rutledge
(4) Jika ada lebih dari satu node teks dalam nodeList, tidak ada cara untuk menentukan node teks mana yang Anda inginkan. Anda mungkin menginginkan simpul teks pertama , tetapi Anda mungkin menginginkan simpul teks terakhir .
Anthony Rutledge
Apa yang Anda sarankan untuk mengganti Array.from?
jujule
@Snowman tolong tambahkan jawaban Anda sendiri untuk perubahan substantif tersebut, atau buat rekomendasi untuk OP untuk memberi mereka kesempatan untuk memasukkannya ke dalam jawaban mereka.
TylerH
@jujule - Lebih baik digunakan [...node.childNodes]untuk mengonversi HTMLCollection menjadi Array
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
sumber
1
Saya pikir metode untuk javascript standar harus 'innerText'
Reporter
2
Ini tidak bekerja seperti yang diinginkan OP - ini akan mendapatkan teks di dalam aelemen juga: jsfiddle.net/ekHJH
James Allardice
1
@James Allardice - Saya sudah selesai dengan solusi jquery sekarang ini akan berhasil .................
Pranay Rana
Itu hampir akan berhasil, tetapi Anda kehilangan .di awal pemilih Anda, yang berarti Anda benar-benar mendapatkan teks titleelemen, bukan elemen denganclass="title"
James Allardice
@reporter .innerTextadalah konvensi IE lama yang baru saja diadopsi. Dalam hal skrip DOM standar, node.nodeValueadalah bagaimana seseorang mengambil teks dari sebuah simpul teks.
Anthony Rutledge
2

Ini akan mengabaikan whitespace juga jadi, Anda tidak akan pernah mendapatkan Blank textNodes..code menggunakan core 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;
    }
}

Periksa di jsfiddle: - http://jsfiddle.net/webx/ZhLep/

webx
sumber
curNode.nodeType === Node.TEXT_NODEakan lebih baik. Menggunakan perbandingan string dan ekspresi reguler dalam satu loop adalah solusi berkinerja rendah, terutama karena besarnya oDiv.childNodes.lengthkenaikan. Algoritme ini memecahkan pertanyaan spesifik OP, tetapi berpotensi dengan biaya kinerja yang buruk. Jika pengaturan, atau jumlah, node teks berubah, maka solusi ini tidak dapat dijamin untuk menghasilkan keluaran yang akurat. Dengan kata lain, Anda tidak dapat menargetkan node teks yang Anda inginkan. Anda berada di bawah kekuasaan struktur HTML dan pengaturan teks di sana.
Anthony Rutledge
1

Anda juga dapat menggunakan text()pengujian node XPath untuk mendapatkan node teks saja. Sebagai contoh

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

Ini adalah solusi saya di ES6 untuk membuat string yang mengandung teks gabungan dari semua childnodes (rekursif) . Perhatikan bahwa kunjungi juga shdowroot of childnodes.

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

Solusi ini terinspirasi oleh solusi https://stackoverflow.com/a/41051238./1300775 .

Damien
sumber