Использование .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(), я получаю «Это какой-то текст textFirst span textSecond span».

Есть ли способ получить (и, возможно, удалить, через что-то вроде .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
Я не понимаю 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
Кирилл Тенин Баум

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 "";
запишите

159

Это похоже на случай чрезмерного использования jquery для меня. Следующее будет захватывать текст, игнорируя другие узлы:

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

Вам нужно будет обрезать это, но это дает вам то, что вы хотите в одну, простую линию.

РЕДАКТИРОВАТЬ

Выше будет получен текстовый узел . Чтобы получить реальный текст, используйте это:

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

31
Лучший ответ, вам не нужен плагин для этого или цепочка из 10 вызовов jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
дождь

5
Что, если текстовое содержимое разбито на несколько узлов (например, последовательность crlf, text, crlf)? Существуют ли какие-либо (rael-life) гарантии того, что дом, построенный ua, будет использовать простейшую структуру?
коллапсар

5
Абсолютно лучший ответ ... почему другие люди иногда используют JQuery?
ncubica

11
Это работает только в случае <div id = "listItem"> текста, который вы хотите <span> other </ 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]?
Clonkex

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
Не совсем, @ Оскар. Здесь .contents()очень важно!
Дузун

Плохое решение, если ваши узлы не используют идентификаторы.
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?
Джаред Томашевски


6

Это должно быть что-то с учетом потребностей, которые зависят от структуры, с которой вы представлены. Для приведенного вами примера это работает:

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

Демо-версия: http://jquery.nodnod.net/cases/2385/run

Но это в значительной степени зависит от того, насколько разметка похожа на то, что вы опубликовали.


2
Будущий читатель, будьте осторожны: код в этом ответе убивает детей в фактическом элементе. Следует использовать cloneметод здесь, если это не предполагаемый эффект.
Mahn

@ DotNetWala ответ ниже, и должен использоваться вместо этого. Или, по крайней мере, используйте .detach()метод вместо .remove().
Дон МакКарди


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

1
Спасибо за этот фрагмент кода, который может оказать некоторую немедленную помощь. Правильное объяснение значительно повысило бы его образовательную ценность, показав, почему это хорошее решение проблемы, и сделало бы его более полезным для будущих читателей с похожими, но не идентичными вопросами. Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются.
Тоби Спейт

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

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

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

Я обычно делаю вспомогательные функции для таких полезных вещей, как это, освобождаю их от ошибок, а затем часто полагаюсь на них, а не переписываю этот тип манипуляции со строками, рискуя нулевыми ссылками и т. Д. Таким образом, вы можете повторно использовать функцию во многих проектах и ​​никогда не нужно тратить на это время снова, отладка, почему ссылка на строку имеет неопределенную ошибку ссылки. Возможно, это не самый короткий однострочный код, но после того, как у вас есть функция полезности, она станет одной строкой. Обратите внимание, что большая часть кода просто обрабатывает параметры, присутствующие там или нет, чтобы избежать ошибок :)

Например:

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

Ах хорошо. Тогда я думаю, что вам придется фильтровать результаты: S извините.
Дорджан


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

Я обнаружил, что использование более короткой версии like document.getElementById("listItem").childNodes[0]не будет работать с trim () в 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.