Я хотел бы получить все текстовые узлы-потомки элемента в виде коллекции jQuery. Как лучше всего это сделать?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Jauco опубликовал хорошее решение в комментарии, поэтому я копирую его здесь:
$(elem)
.contents()
.filter(function() {
return this.nodeType === 3; //Node.TEXT_NODE
});
IE7 не определяет глобальный узел, поэтому, к сожалению, вам придется использовать this.nodeType == 3: stackoverflow.com/questions/1423599/node-textnode-and-ie7
Разве это не только возвращает текстовые узлы, которые являются прямыми дочерними элементами элемента, а не потомками элемента, как запрошено OP?
Я только что заметил, что ваш первый ответ из 2008 года был почти в точности тем, что я независимо придумал намного позже. Зачем вы его редактировали?
добавьте .text() в конце, если хотите, чтобы это была строка. В противном случае это все еще объект. Попытка отобразить его в документе приведет к отображению [Объект объекта].
@ChristianOudard Это было бы действительно просто полифилить, не так ли? Сделал бы ваш код более разборчивым.
это не сработает, если текстовый узел глубоко вложен в другие элементы, потому что метод contents () возвращает только непосредственные дочерние узлы, api.jquery.com/contents
@Jauco, нет, недостаточно! поскольку .contents () возвращает только непосредственные дочерние узлы
В jQuery нет удобной функции для этого. Вам нужно объединить contents(), который даст только дочерние узлы, но включает текстовые узлы, с find(), который дает все дочерние элементы, но не текстовые узлы. Вот что я придумал:
var getTextNodesIn = function(el) {
return $(el).find(":not(iframe)").addBack().contents().filter(function() {
return this.nodeType == 3;
});
};
getTextNodesIn(el);
Примечание. Если вы используете jQuery 1.7 или более раннюю версию, приведенный выше код не будет работать. Чтобы исправить это, замените addBack() на andSelf(). andSelf() устарел и заменен addBack() начиная с версии 1.8.
Это несколько неэффективно по сравнению с чистыми методами DOM и должно включать уродливый обходной путь для перегрузки jQuery его функции contents() (спасибо @rabidsnail в комментариях за указание на это), поэтому здесь не-jQuery решение, использующее простую рекурсивную функцию. Параметр includeWhitespaceNodes определяет, включаются ли в вывод текстовые узлы с пробелами (в jQuery они автоматически отфильтровываются).
Обновление: исправлена ошибка, когда includeWhitespaceNodes является ложным.
function getTextNodesIn(node, includeWhitespaceNodes) {
var textNodes = [], nonWhitespaceMatcher = /\S/;
function getTextNodes(node) {
if (node.nodeType == 3) {
if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
textNodes.push(node);
}
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
getTextNodes(node.childNodes[i]);
}
}
}
getTextNodes(node);
return textNodes;
}
getTextNodesIn(el);
Может ли переданный элемент быть именем div?
@crosenblum: Вы можете сначала вызвать document.getElementById(), если вы это имеете в виду: var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);
Из-за ошибки в jQuery, если у вас есть какие-либо фреймы в el, вам нужно использовать .find (': not (iframe)') вместо .find ('*').
@rabidsnail: Я думаю, использование .contents() в любом случае подразумевает, что он также будет искать через iframe. Я не понимаю, как это могло быть ошибкой.
@rabidsnail: Хорошо, я думаю, что это как минимум раздражение (если не ошибка) в jQuery и аргумент в пользу простой версии DOM. Отредактирую свой ответ. Спасибо.
Вы могли бы рассмотреть nonWhitespace = /\S/ и if (includeWhitespaceNodes || nonWhitespace.test(node.nodeValue)) {, которые, по крайней мере, могут похвастаться большей простотой (хотя они по-разному реагировали бы на пустые текстовые узлы, если это возможно). Я также думаю, что можно было бы улучшить имя переменной регулярного выражения ... что-то вроде whitespaceMatcher или что-нибудь, чтобы указать, что это за переменная.
@ErikE: Мне нравятся описательные имена переменных. У меня такое чувство, что я выбрал whitespace, чтобы избежать появления горизонтальных полос прокрутки кода в моем браузере.
@ErikE: Я согласен с вами по обоим пунктам и отредактировал свой ответ. Пустые текстовые узлы действительно возможны, но будут обрабатываться одинаково как для !/^\s*$/.test(), так и для /\S/.test(), поэтому здесь нет никаких проблем.
о, да, это был *, а не +, поэтому пустые узлы сопоставлялись раньше. Рад, что вам понравились мои предложения!
Отличное предложение. Я бы рекомендовал использовать Node.TextNode вместо 3 для лучшей читаемости.
@BenS: Я бы хотел, но Node.TEXT_NODE не поддерживается в IE <= 8.
В этом коде есть ошибка. Прямо сейчас, когда вы передаете false для включения пробелов, он ТОЛЬКО изменяет узлы пробелов, а не исключает их. Вместо этого в строке if (includeWhitespaceNodes || !nonWhitespaceMatcher.test(node.nodeValue)) следует читать: if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)).
@BrianGeihsler: Вы правы, спасибо. В ноябре прошлого года я упростил регулярное выражение, но не смог отменить условие. Хотел бы я проверить это сейчас.
@TimDown Я пробовал ваш метод, но он выдает узлы из строя. Что нужно сделать, чтобы теги были в порядке? Я здесь отдельный вопрос задал stackoverflow.com/questions/63270123/…
@Amanda: Я думаю, что версия, отличная от jQuery, предоставит вам узлы в порядке документа.
@TimDown Вы имеете в виду версию jquery? Я использую cheerio, но не понимаю.
@TimDown Я нашел ответ AKX на stackoverflow.com/questions/63270123/…. Кажется, это работает. Чем он отличается от того, что вы предложили в ответе? Не могли бы вы объяснить / помочь?
Если вы можете сделать предположение, что все дочерние элементы являются либо узлами элементов, либо текстовыми узлами, то это одно из решений.
Чтобы получить все дочерние текстовые узлы в виде коллекции jquery:
$('selector').clone().children().remove().end().contents();
Чтобы получить копию исходного элемента с удаленными нетекстовыми дочерними элементами:
$('selector').clone().children().remove().end();
Только что заметил комментарий Тима Дауна по поводу другого ответа. Это решение получает только прямых потомков, а не всех потомков.
если вы хотите удалить все теги, попробуйте это
функция:
String.prototype.stripTags=function(){
var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');
}
использование:
var newText=$('selector').html().stripTags();
У меня была такая же проблема, и я решил ее:
Код:
$.fn.nextNode = function(){
var contents = $(this).parent().contents();
return contents.get(contents.index(this)+1);
}
Использование:
$('#my_id').nextNode();
Похож на next(), но также возвращает текстовые узлы.
.nextSibling взят из спецификации Dom: developer.mozilla.org/en/Document_Object_Model_(DOM)/…
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
Также можно сделать так:
var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
return this.nodeType == 3;
});
Приведенный выше код фильтрует textNodes от прямых дочерних узлов данного элемента.
... но не все дочерние узлы потомок (например, текстовый узел, который является дочерним элементом элемента, который является дочерним по отношению к исходному элементу).
Для меня простой старый .contents(), казалось, работал, чтобы возвращать текстовые узлы, просто нужно быть осторожным с вашими селекторами, чтобы вы знали, что они будут текстовыми узлами.
Например, это обернуло все текстовое содержимое TD в моей таблице тегами pre и не дало никаких проблем.
jQuery("#resultTable td").content().wrap("<pre/>")
jQuery.contents() можно использовать с jQuery.filter для поиска всех дочерних текстовых узлов. С небольшим поворотом, вы также можете найти текстовые узлы внуков. Рекурсия не требуется:
$(function() {
var $textNodes = $("#test, #test *").contents().filter(function() {
return this.nodeType === Node.TEXT_NODE;
});
/*
* for testing
*/
$textNodes.each(function() {
console.info(this);
});
});div { margin-left: 1em; }<script src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id = "test">
child text 1<br>
child text 2
<div>
grandchild text 1
<div>grand-grandchild text 1</div>
grandchild text 2
</div>
child text 3<br>
child text 4
</div>Я пробовал это. Он печатает имена тегов в неправильном порядке. Есть ли способ печатать имена тегов в том порядке, в котором они встречаются? Я здесь отдельный вопрос задал stackoverflow.com/questions/63276378/…
По какой-то причине contents() не работал у меня, поэтому, если это не сработало для вас, вот решение, которое я сделал, я создал jQuery.fn.descendants с возможностью включать текстовые узлы или нет
использование
Получить всех потомков, включая текстовые узлы и узлы элементов
jQuery('body').descendants('all');
Получить всех потомков, возвращающих только текстовые узлы
jQuery('body').descendants(true);
Получить всех потомков, возвращающих только узлы элементов
jQuery('body').descendants();
Coffeescript Original:
jQuery.fn.descendants = ( textNodes ) ->
# if textNodes is 'all' then textNodes and elementNodes are allowed
# if textNodes if true then only textNodes will be returned
# if textNodes is not provided as an argument then only element nodes
# will be returned
allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]
# nodes we find
nodes = []
dig = (node) ->
# loop through children
for child in node.childNodes
# push child to collection if has allowed type
nodes.push(child) if child.nodeType in allowedTypes
# dig through child if has children
dig child if child.childNodes.length
# loop and dig through nodes in the current
# jQuery object
dig node for node in this
# wrap with jQuery
return jQuery(nodes)
Перетащите версию Javascript
var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if (t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e== = "all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if (a=r.nodeType,__indexOf.call(t,a)>=0){i.push(r)}if (r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}
Unminified Javascript version: http://pastebin.com/cX3jMfuD
Это кроссбраузер, в код включен небольшой полифилл Array.indexOf.
Я получал много пустых текстовых узлов с принятой функцией фильтра. Если вас интересует только выбор текстовых узлов, которые не содержат пробелов, попробуйте добавить условие nodeValue к вашей функции filter, например, простой $.trim(this.nodevalue) !== '':
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && $.trim(this.nodeValue) !== '';
});
Или, чтобы избежать странных ситуаций, когда содержимое выглядит как пробел, но не так (например, мягкий дефис ­, символы новой строки \n, табуляции и т. д.), Вы можете попробовать использовать регулярное выражение. Например, \S будет соответствовать любым непробельным символам:
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && /\S/.test(this.nodeValue);
});
Я пробовал это. Он печатает имена тегов в неправильном порядке. Есть ли способ печатать имена тегов в том порядке, в котором они встречаются? Я здесь отдельный вопрос задал stackoverflow.com/questions/63276378/…
на самом деле $ (elem) .contents () .filter (function () {return this.nodeType == Node.TEXT_NODE;}); достаточно