Використання .text () для отримання лише тексту, який не вкладений у дочірні теги


386

Якщо у мене є такий html:

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

Я намагаюся використовувати .text()для отримання лише рядок "Це якийсь текст", але якщо мені сказати $('#list-item').text(), я отримую "Це текст" Перший текст тексту "Другий проміжок тексту".

Чи є спосіб отримати (а можливо, вилучити через щось подібне .text("")) просто вільний текст у тезі, а не текст у його дочірніх тегах?

HTML не був написаний мною, тому з цим я маю працювати. Я знаю, що було б просто загортати текст тегами під час написання html, але знову ж таки, html заздалегідь написаний.


Тому що мені ще не вистачає репутації для коментарів, і я не хочу втрачати знання (сподіваємось, це допомагає комусь іншому), поєднання відповіді macio.Jun, відповіді RegExp та iStranger на Замінити textNode HTML у Javascript? дозволив мені шукати текстові вузли для рядка та замінювати всі події посиланнями.
JDQ

Відповіді:


509

Мені сподобалась ця багаторазова реалізація, заснована на clone()методі, знайденому тут, щоб отримати лише текст всередині батьківського елемента.

Код передбачений для легкої довідки:

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

5
За допомогою цього рішення ви отримуєте текст лише без дитини, але ви не можете замінити лише текст.
BenRoe

1
Я не отримую жодної речі: якщо .end () повертається до вибраного елемента, тоді text () повинен копіювати оригінальний текст із дочірніми елементами Але на практиці я бачу, що текст нашого маніпульованого клону копіюється. Отже, end () повертається до clone ()?

68
Це дійсно неефективний спосіб зробити це
billyonecan

5
@billyonecan, чи можете ви запропонувати більш ефективний метод? Це привабливо, оскільки воно "чисте" та "коротке". Що ти пропонуєш?
derekmx271

1
@ derekmx271 подивіться на відповідь Стюарта
billyonecan

364

Проста відповідь:

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

38
Я не розумію, чому за ефективні відповіді (які не генерують сторонні структури даних) проголосують не стільки, скільки відповіді, які виглядають менш страшними. +5, якби міг.
Стівен Лу

16
проста і ефективна відповідь
Пол Керролл

9
Це не тільки більш ефективно, але й правильно! Це рішення відповідає ситуаціям, коли текст розкидається між дочірніми елементами. +5
Kyryll Tenin Baum

15
Щоб було ще зрозуміліше, якщо ви використовуєте IE8 +, ви можете використовувати this.nodeType == Node.TEXT_NODEзамість this.nodeType == 3. Легше читати та розуміти ІМО.
NorTicUs

8
Це порушиться, якщо використовувати його на щось, без тексту. Якщо ви використовуєте це як функцію та маєте сценарій, коли ви можете або не маєте тексту, просто .contents().filter(...)var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
введіть

157

Це здається мені випадком надмірного використання jquery. Далі буде захоплено текст, ігноруючи інші вузли:

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

Вам потрібно буде обрізати це, але ви отримаєте те, що хочете, за один простий рядок.

EDIT

Наведене вище отримає текстовий вузол . Щоб отримати власне текст, скористайтеся цим:

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

31
Найкраща відповідь, вам не потрібно мати плагін для цього або ланцюжок з 10 дзвінків jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
дощ

5
що робити, якщо текстовий вміст розділений на кілька вузлів (наприклад, послідовність crlf, текст, crlf)? чи існують гарантії, що побудований ua dom використовує найпростішу конструкцію?
колапс

5
Цілком найкраща відповідь ... чому інші люди іноді більше користуються jQuery?
ncubica

11
Це працює лише у випадку тексту <div id = "listItem"> тексту, який ви хочете <span> інше </span> </div>. Це не буде працювати для <div id = "listItem"> <span> іншого </span> тексту, який ви хочете </div>
Спенсер

1
Іноді у вас немає document. Сюди завітали cheerio.
спалах

67

Простіше та швидше:

$("#listItem").contents().get(0).nodeValue

Чи сумісний цей крос-браузер?
Раджат Гупта

Звичайно, він отримує один з елементів, що відповідає об'єкту jQuery, заданому індексом: Jquery Docs .get () .
WakeupMorning

1
@Nate Якщо вам потрібно використовувати його на тезі <br/>, ви можете використовувати відповідь macio.Jun .
WakeupMorning

Це має бути прийнятою відповіддю.
Денні

2
Чому get(0)замість просто [0]?
Клонкекс

28

Схожий на прийняту відповідь, але без клонування:

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

І ось для цього використовується плагін jQuery:

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

Ось як використовувати цей плагін:

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

Що таке t у t.children ()?
FrEaKmAn

Це повторне рішення того, про яке pbjk написав у січні 15… тим не менше - це виглядає приємно.
Оскар Холмкрац

1
Не дуже, @Oskar. Тут .contents()важлива частина!
DUzun

Неправильне рішення, якщо ваші вузли не використовують ідентифікатори.
AndroidDev

3
@AndroidDev Ви завжди можете замінити селектор тим, що працює для вас. Це просто для ілюстрації техніки! Я також додав версію плагіна, щоб показати, що вона працює навіть без ідентифікаторів
DUzun

8

не код:

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

просто став jQuery заради jQuery? Коли прості операції передбачають, що багато ланцюгових команд і стільки (непотрібної) обробки, можливо, саме час написати розширення 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));

дзвонити:

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

аргументи є на випадок, якщо трапляється інший сценарій, наприклад

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

текст буде мати значення:

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

1
Приємно. Як щодо цього зробити запит на наступну версію jQuery?
Джаред Томашевський

8

Спробуйте це:

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

6

Це повинно бути чимось з урахуванням потреб, які залежать від структури, з якою ви представлені. Для прикладу, який ви надали, це працює:

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

Демо: http://jquery.nodnod.net/cases/2385/run

Але це досить залежить від того, як розмітка схожа на ту, яку ви розмістили.


2
Майбутній читач остерігайся: код у цій відповіді вбиває дітей у фактичному елементі. Тут слід використовувати cloneметод, якщо це не призначений ефект.
Ман

@ Відповідь DotNetWala, наведена нижче, і повинна використовуватися замість цієї. Або принаймні використовувати .detach()метод замість .remove().
Дон МакКерді


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

1
Дякуємо за цей фрагмент коду, який може надати негайну допомогу. Правильне пояснення значно покращило б його навчальну цінність, показавши, чому це хороше рішення проблеми, та зробило б її кориснішою для майбутніх читачів із подібними, але не однаковими питаннями. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Toby Speight

3

Це давнє запитання, але головна відповідь дуже неефективна. Ось краще рішення:

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

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

    return str;
};

І просто так:

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

3

Я припускаю, що це буде прекрасним рішенням - якщо ви хочете отримати вміст усіх текстових вузлів, які є прямими дітьми вибраного елемента.

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

Примітка: документація jQuery використовує аналогічний код для пояснення функції вмісту: https://api.jquery.com/contents/

PS Існує також трохи потворніший спосіб зробити це, але це більш глибоко показує, як все працює, і дозволяє встановити спеціальний роздільник між текстовими вузлами (можливо, ви хочете там перервати рядки)

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

1

Я пропоную використовувати createTreeWalker для пошуку всіх елементів тексту, не приєднаних до елементів html (цю функцію можна використовувати для розширення 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

Якщо положення indexтекстового вузла закріплено серед його побратимів, можна скористатися

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

1

Не впевнений, наскільки гнучким або скільки справ вам потрібно це охопити, але для вашого прикладу, якщо текст завжди надходить перед першими тегами HTML - чому б просто не розділити внутрішній html на перший тег і взяти перший:

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

і якщо вам це потрібно ширше, можливо просто

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

і якщо вам потрібен текст між двома маркерами, як-от після одного, але перед іншим, ви можете зробити щось на кшталт (неперевірене) і використовувати, якщо заяви, щоб зробити його досить гнучким, щоб мати маркер початку або кінця або обидва, уникаючи помилок нульового відхилення :

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

Я, як правило, роблю корисні функції для таких корисних речей, роблю їх без помилок, а потім часто покладаюсь на них, колись суцільно, а не завжди переписуючи цей тип маніпуляцій рядків та ризикуючи нульовими посиланнями тощо. Таким чином, ви можете повторно використовувати функцію у багатьох проектах і ніколи не доведеться витрачати час на це знову налагодження, чому посилання рядка має невизначену помилку посилання. Можливо, це не найкоротший 1-й рядковий код будь-коли, але після того, як у вас з'явиться функція утиліти, це буде один рядок відтепер. Зверніть увагу, що більша частина коду - це лише обробка параметрів, які існують або не для уникнення помилок :)

Наприклад:

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

якщо вам потрібно замінити текст, просто використовуйте $('#listItem').html( newHTML ); де newHTML - це змінна, яка вже має зведений текст.
ОГ Шон


0

Я придумав конкретне рішення, яке повинно бути набагато ефективнішим, ніж клонування та модифікація клону. Це рішення працює лише з наступними двома застереженнями, але має бути більш ефективним, ніж прийняте на даний момент рішення:

  1. Ви отримуєте лише текст
  2. Текст, який ви хочете витягти, знаходиться перед дочірніми елементами

З урахуванням сказаного, ось код:

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

Так само , як питання, я намагався витягти текст для того , щоб зробити деякі регулярний вираз заміни тексту , але отримую проблеми там , де мої внутрішні елементи (тобто <i>, <div>,<span> і т.д.) ставали також вилучені.

Наступний код, здається, працює добре і вирішив усі мої проблеми.

Він використовує деякі відповіді, надані тут, але зокрема, замінить текст лише тоді, коли елемент є 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;
  }
});

Згадане вище - це проходження циклу через усі елементи даного el(що було просто отримано $("div.my-class[name='some-name']");. Для кожного внутрішнього елемента, він в основному їх ігнорує. Для кожної частини тексту (як визначено if (this.nodeType === 3)) він застосовуватиме підстановку регулярного виразу лише до цих елементів .

this.textContent = textЧастина просто замінює заміщення текст, який в моєму випадку, я шукав лексем , як [[min.val]], [[max.val]]і т.д.

Цей короткий витяг з коду допоможе всім, хто намагається зробити те, про що ставив запитання ... та ще трохи.


-1

просто покладіть його в <p>або <font>і захопіть цей $ ('# listItem font'). text ()

Перше, що прийшло в голову

<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
Я не маю контролю над розміщенням вільного тексту в тегах, оскільки код, над яким я працюю, не був створений мною. Якби я міг схопити лише цей текст, я міг би його видалити і замінити тегами навколо нього або зробити все, що завгодно. Але знову ж таки, html вже попередньо записаний.
MegaMatt

а, добре. Тоді я думаю, що вам доведеться фільтрувати результати: Вибачте.
Дорджан


-2

Використовуйте додаткову умову, щоб перевірити, чи innerHTML та innerText однакові. Тільки в цих випадках замініть текст.

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

Щоб мати можливість обрізати результат, використовуйте 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();

Я дізнався, що використання коротшої версії, як, наприклад document.getElementById("listItem").childNodes[0], не буде працювати з обробкою jQuery ().


3
Це тому, що document.getElementById("listItem").childNodes[0]це звичайний javascript, вам доведеться зафіксувати його у функції jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz

Гаразд, це має сенс. Ха-ха. Дякую!
Маріон Іди

1
Це майже ідентично DotNetWala в відповідь . Все, що ви зробили, було додано .trim()до кінця. Чи потрібна ця відповідь?
Усі працівники є найважливішими

-3

Я не експерт із питань jquery, але як насчет

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

1
Якщо ви помітили експерта з питань jquery, то чому б не стати більше експертом, прочитавши спочатку інші відповіді? ... Один з них стався практично таким же, як ви написали, із коментарями нижче, що пояснюють, чому це не так гарна ідея.
Оскар Холмкрац

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.