<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Desejo obter o "I am text node", não desejo remover a tag "edit" e preciso de uma solução para vários navegadores.
<div class="title">
I am text node
<a class="edit">Edit</a>
</div>
Desejo obter o "I am text node", não desejo remover a tag "edit" e preciso de uma solução para vários navegadores.
Respostas:
var text = $(".title").contents().filter(function() {
return this.nodeType == Node.TEXT_NODE;
}).text();
Isso obtém o contents
do elemento selecionado e aplica uma função de filtro a ele. A função de filtro retorna apenas nós de texto (ou seja, aqueles nós com nodeType == Node.TEXT_NODE
).
text()
porque a filter
função retorna os próprios nós, não o conteúdo dos nós.
jQuery("*").each(function() { console.log(this.nodeType); })
e obtive 1 para todos os tipos de nó.
Você pode obter o nodeValue do primeiro childNode usando
$('.title')[0].childNodes[0].nodeValue
null
um valor de retorno.
Se você quiser obter o valor do primeiro nó de texto no elemento, este código funcionará:
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;
}
}
Você pode ver isso em ação aqui: http://jsfiddle.net/ZkjZJ/
curNode.nodeType == 3
vez de nodeName
.
curNode.nodeType == Node.TEXT_NODE
(a comparação numérica é mais rápida, mas curNode.nodeType == 3 não é legível - qual nó tem o número 3?)
curNode.NodeType === Node.TEXT_NODE
. Essa comparação está ocorrendo dentro de um loop de possíveis iterações desconhecidas. Comparar dois números pequenos é melhor do que comparar strings de vários comprimentos (considerações de tempo e espaço). A pergunta correta a fazer nessa situação é "que tipo / tipo de nó eu tenho?", E não "que nome eu tenho?" developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
childNodes
, saiba que um nó de elemento pode ter mais de um nó de texto. Em uma solução genérica, pode ser necessário especificar qual instância de um nó de texto dentro de um nó de elemento que você deseja atingir (o primeiro, segundo, terceiro, etc ...).
Outra solução JS nativa que pode ser útil para elementos "complexos" ou profundamente aninhados é usar NodeIterator . Coloque NodeFilter.SHOW_TEXT
como o segundo argumento ("whatToShow") e itere apenas sobre os filhos do nó de texto do elemento.
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>
Você também pode usar TreeWalker
. A diferença entre os dois é que NodeIterator
é um iterador linear simples, enquanto TreeWalker
permite que você navegue por meio de irmãos e ancestrais também.
Em primeiro lugar, sempre tenha isso em mente ao procurar por texto no DOM.
Este problema fará com que você preste atenção na estrutura do seu XML / HTML.
Neste exemplo de JavaScript puro, considero a possibilidade de vários nós de texto que podem ser intercalados com outros tipos de nós . No entanto, inicialmente, não julgo os espaços em branco, deixando essa tarefa de filtragem para outro código.
Nesta versão, passo um NodeList
do código de chamada / cliente.
/**
* 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;
}
Obviamente, testando node.hasChildNodes()
primeiro, não haveria necessidade de usar um for
loop de pré-teste .
/**
* 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;
}
Aqui, a função getTextById()
usa duas funções auxiliares: getStringsFromChildren()
e 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.
}
Em seguida, o valor de retorno (Array ou null) é enviado ao código do cliente onde deve ser tratado. Felizmente, a matriz deve ter elementos de string de texto real, não linhas de espaço em branco.
Strings vazias ( ""
) não são retornadas porque você precisa de um nó de texto para indicar corretamente a presença de texto válido. Returning ( ""
) pode dar a falsa impressão de que existe um nó de texto, levando alguém a supor que pode alterar o texto alterando o valor de .nodeValue
. Isso é falso, porque um nó de texto não existe no caso de uma string vazia.
Exemplo 1 :
<p id="bio"></p> <!-- There is no text node here. Return null. -->
Exemplo 2 :
<p id="bio">
</p> <!-- There are at least two text nodes ("\n"), here. -->
O problema surge quando você deseja tornar seu HTML fácil de ler, espaçando-o. Agora, embora não haja texto válido legível por humanos, ainda existem nós de texto com "\n"
caracteres newline ( ) em suas .nodeValue
propriedades.
Os humanos veem os exemplos um e dois como funcionalmente equivalentes - elementos vazios esperando para serem preenchidos. O DOM é diferente do raciocínio humano. É por isso que a getStringsFromChildren()
função deve determinar se existem nós de texto e reunir os .nodeValue
valores em uma matriz.
for (var i = 0; i < length; i++) {
if (nodeList[i].nodeType === Node.TEXT_NODE) {
textNodes.push(nodeList[i].nodeValue);
}
}
No exemplo dois, dois nós de texto existem e getStringFromChildren()
retornarão o .nodeValue
de ambos ( "\n"
). No entanto, filterWhitespaceLines()
usa uma expressão regular para filtrar linhas de caracteres de espaço em branco puros.
Retornar em null
vez de "\n"
caracteres newline ( ) é uma forma de mentir para o cliente / código de chamada? Em termos humanos, não. Em termos de DOM, sim. No entanto, o problema aqui é obter texto, não editá-lo. Não há texto humano para retornar ao código de chamada.
Nunca se sabe quantos caracteres de nova linha podem aparecer no HTML de alguém. A criação de um contador que procura o caractere de nova linha "segundo" não é confiável. Pode não existir.
Claro, mais abaixo na linha, a questão de editar texto em um <p></p>
elemento vazio com espaço em branco extra (exemplo 2) pode significar destruir (talvez, pular) todos, exceto um nó de texto entre as tags de um parágrafo para garantir que o elemento contenha precisamente o que é deveria exibir.
Independentemente disso, exceto nos casos em que você está fazendo algo extraordinário, você precisará encontrar uma maneira de determinar qual .nodeValue
propriedade do nó de texto possui o texto legível verdadeiro que deseja editar. filterWhitespaceLines
nos leva a meio caminho.
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.
}
}
Neste ponto, você pode ter uma saída semelhante a esta:
["Dealing with text nodes is fun.", "Some people just use jQuery."]
Não há garantia de que essas duas strings sejam adjacentes uma à outra no DOM, portanto, juntá-las com .join()
pode formar uma composição não natural. Em vez disso, no código que chamagetTextById()
, você precisa escolher com qual string deseja trabalhar.
Teste a saída.
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);
}
Pode-se adicionar .trim()
dentro de getStringsFromChildren()
para se livrar dos espaços em branco à esquerda e à direita (ou para transformar um monte de espaços em uma string de comprimento zero ( ""
), mas como você pode saber a priori o que cada aplicativo pode precisar que aconteça com o texto (string) uma vez que for encontrado? Você não, então deixe isso para uma implementação específica e deixe getStringsFromChildren()
ser genérico.
Pode haver momentos em que esse nível de especificidade (o target
e tal) não seja necessário. Isso é ótimo. Use uma solução simples nesses casos. No entanto, um algoritmo generalizado permite acomodar situações simples e complexas.
Versão ES6 que retorna o primeiro #text node content
const extract = (node) => {
const text = [...node.childNodes].find(child => child.nodeType === Node.TEXT_NODE);
return text && text.textContent.trim();
}
.from()
para fazer uma instância de array copiado superficialmente. (2) O uso de .find()
para fazer comparações de strings usando .nodeName
. Usar node.NodeType === Node.TEXT_NODE
seria melhor. (3) Retornar uma string vazia quando nenhum valor,, null
é mais verdadeiro se nenhum nó de texto for encontrado. Se nenhum nó de texto for encontrado, pode ser necessário criar um! Se você retornar uma string vazia,, ""
poderá dar a falsa impressão de que existe um nó de texto e pode ser manipulado normalmente. Em essência, devolver uma string vazia é uma mentira inocente e é melhor evitar.
[...node.childNodes]
para converter HTMLCollection em Arrays
$('.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
a
elemento também: jsfiddle.net/ekHJH
.
no início do seu seletor, o que significa que você realmente obtém o texto do title
elemento, não os elementos comclass="title"
.innerText
é uma antiga convenção do IE adotada recentemente. Em termos de script DOM padrão, node.nodeValue
é como se pega o texto de um nó de texto.
Isso irá ignorar o espaço em branco também, portanto, você nunca obteve o código textNodes em branco usando o Javascript principal.
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;
}
}
Verifique em jsfiddle: - http://jsfiddle.net/webx/ZhLep/
curNode.nodeType === Node.TEXT_NODE
seria melhor. Usar comparação de string e uma expressão regular em um loop é uma solução de baixo desempenho, especialmente à medida que a magnitude do oDiv.childNodes.length
aumento. Este algoritmo resolve a questão específica do OP, mas, potencialmente, a um custo de desempenho terrível. Se o arranjo, ou número, de nós de texto mudar, então esta solução não pode ser garantida para retornar uma saída precisa. Em outras palavras, você não pode direcionar o nó de texto exato que deseja. Você está à mercê da estrutura HTML e da organização do texto.
Você também pode usar o text()
teste de nó do XPath para obter apenas os nós de texto. Por exemplo
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;
}
Esta é minha solução no ES6 para criar uma string contendo o texto concatenado de todos os childnodes (recursivo) . Observe que também é possível visitar o shdowroot de 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');
}
Esta solução foi inspirada na solução de https://stackoverflow.com/a/41051238./1300775 .