Usando .text () para recuperar apenas texto não aninhado em tags filho


386

Se eu tiver html assim:

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

Estou tentando usar .text()para recuperar apenas a string "Este é um texto", mas, se eu disser $('#list-item').text(), recebo "Este é um texto textFirst span textSecond span text".

Existe uma maneira de obter (e possivelmente remover, por meio de algo como .text("")) apenas o texto livre em uma tag e não o texto em suas tags filho?

O HTML não foi escrito por mim, então é com isso que tenho que trabalhar. Eu sei que seria simples colocar apenas o texto em tags ao escrever o html, mas, novamente, o html é pré-escrito.


Como ainda não tenho reputação suficiente para comentar e não desejo que o conhecimento seja perdido (espero que ajude outra pessoa), uma combinação de resposta macia.Jun , uma resposta RegExp e iStranger para substituir um textNode por HTML em Javascript? permitiu-me procurar uma string em nós somente de texto e substituir todas as ocorrências por links.
JDQ #

Respostas:


509

Gostei dessa implementação reutilizável com base no clone()método encontrado aqui para obter apenas o texto dentro do elemento pai.

Código fornecido para fácil referência:

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

5
Com esta solução, você só obtém o texto sem o filho, mas não pode substituir apenas o texto.
BenRoe

11
Eu não entendo uma coisa: se .end () voltar ao elemento selecionado, então text () deverá copiar o texto original com elementos filhos. Mas, na prática, vejo que o texto do nosso clone manipulado está sendo copiado. Então end () volta ao clone ()?

68
Esta é realmente uma maneira ineficiente de fazer isso
billyonecan

5
@billyonecan, você pode sugerir um método mais eficiente? Isso é atraente porque é "limpo" e "curto". O que você sugere?
precisa saber é o seguinte

11
@ derekmx271 dê uma olhada na resposta de Stuart
billyonecan

364

Resposta simples:

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

38
Não entendo por que respostas eficientes (que não geram estruturas de dados estranhas) não são votadas tanto quanto as respostas que parecem menos assustadoras. +5 se eu pudesse.
Steven Lu

16
a resposta simples e eficiente
Paul Carroll

9
Isso não é apenas mais eficiente, mas também correto! Essa solução atende a situações em que o texto está espalhado entre elementos filho. +5
Kyryll Tenin Baum

15
Para ficar ainda mais claro, se você usa o IE8 +, pode usar em this.nodeType == Node.TEXT_NODEvez de this.nodeType == 3. Mais fácil de ler e entender o IMO.
NorTicUs

8
Isso será interrompido se você o usar em algo sem texto. Se você estiver usando isso como uma função e tiver um cenário em que possa ou não ter texto, basta capturar a .contents().filter(...)chamada em uma variável local e verificar seu tamanho, por exemplo, var text = $(this).contents().filter(...); if (text.length) { return text[0].nodeValue; } return "";
Carl Bussema

157

Este parece ser um caso de uso excessivo de jquery para mim. A seguir, o texto será ignorado pelos outros nós:

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

Você precisará apará-lo, mas ele obterá o que você deseja em uma linha fácil.

EDITAR

O acima irá obter o nó de texto . Para obter o texto real, use o seguinte:

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

31
Melhor resposta, você não precisa de um plug-in para esta ou uma cadeia de 10 chamadas jQuery. $('.foo')[0].childNodes[0].nodeValue.trim()
Raine

5
e se o conteúdo do texto for dividido em vários nós (como uma sequência de crlf, text, crlf)? Existe alguma garantia (vida útil) de que o dom construído pelos EUA use a estrutura mais simples?
Collapsar

5
Totalmente a melhor resposta ... por que outras pessoas às vezes usam jQuery?
Ncubica

11
Isso funciona apenas no caso de <div id = "listItem"> texto que você deseja <span> outro </span> </div>. Não funcionará para o <div id = "listItem"> <span> outro </span> texto que você deseja </div>
Spencer

11
Às vezes você não tem document. Vim aqui usando cheerio.
flash

67

Mais fácil e rápido:

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

Este navegador cruzado é compatível?
Rajat Gupta

Obviamente, ele recupera um dos elementos correspondentes ao objeto jQuery fornecido pelo índice: Jquery Docs .get () .
WakeupMorning

11
@Nate Caso você precise usá-lo em uma tag <br/>, poderá usar a resposta do macio.Jun .
WakeupMorning

Essa deve ser a resposta aceita.
Danny

2
Por que ao get(0)invés de apenas [0]?
Clonkex 17/02

28

Semelhante à resposta aceita, mas sem clonagem:

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

E aqui está um plugin jQuery para este fim:

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

Aqui está como usar este plugin:

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

O que é t em t.children ()?
FrEaKmAn

Esta é uma solução duplicada da que o pbjk escreveu em Jan'15 ... no entanto - parece bom.
Oskar Holmkratz

11
Na verdade não, @Oskar. A .contents()parte é crítica aqui!
DUzun

Solução ruim se seus nós não usam IDs.
AndroidDev 14/08/17

3
@AndroidDev Você sempre pode substituir o seletor pelo que funcione para você. Isso é apenas para ilustrar a técnica! Eu também adicionei uma versão Plugin para mostrar que ele funciona mesmo sem IDs
DUzun

8

não é o código:

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

apenas se tornando jQuery pelo bem do jQuery? Quando operações simples envolvem tantos comandos encadeados e muito processamento (desnecessário), talvez seja hora de escrever uma extensão 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));

chamar:

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

os argumentos estão no caso de um cenário diferente, como

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

o texto terá valor:

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

11
Agradável. Que tal fazer disso uma solicitação pull para a próxima versão do jQuery?
Jared Tomaszewski

8

Tente o seguinte:

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

6

Terá de ser algo adaptado às necessidades, que depende da estrutura que lhe é apresentada. Para o exemplo que você forneceu, isso funciona:

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

Demo: http://jquery.nodnod.net/cases/2385/run

Mas é bastante dependente da marcação ser semelhante à que você postou.


2
Futuro leitor, cuidado: o código nesta resposta mata os filhos no elemento real. Deve-se usar o clonemétodo aqui se esse não for o efeito pretendido.
Mahn

@ A resposta da DotNetWala, abaixo, e deve ser usada em vez desta. Ou, pelo menos, use o .detach()método em vez de .remove().
quer


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

11
Obrigado por este trecho de código, que pode fornecer ajuda imediata. Uma explicação adequada melhoraria muito seu valor educacional, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com perguntas semelhantes, mas não idênticas. Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam.
Toby Speight

3

Esta é uma pergunta antiga, mas a resposta principal é muito ineficiente. Aqui está uma solução melhor:

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

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

    return str;
};

E faça isso:

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

3

Presumo que essa seria uma boa solução também - se você deseja obter o conteúdo de todos os nós de texto que são filhos diretos do elemento selecionado.

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

Nota: a documentação do jQuery usa código semelhante para explicar a função do conteúdo: https://api.jquery.com/contents/

PS: Há também uma maneira um pouco mais feia de fazer isso, mas isso mostra mais detalhadamente como as coisas funcionam e permite um separador personalizado entre nós de texto (talvez você queira uma quebra de linha lá)

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

1

Proponho usar o createTreeWalker para encontrar todos os elementos de texto não anexados aos elementos html (essa função pode ser usada para estender o 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

Se a posição indexdo nó de texto estiver fixa entre seus irmãos, você poderá usar

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

1

Não tenho certeza de quão flexível ou quantos casos você precisa cobrir, mas, por exemplo, se o texto sempre vem antes das primeiras tags HTML - por que não dividir o html interno na primeira tag e pegar o primeiro:

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

e se você precisar mais amplo talvez apenas

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

e se você precisar do texto entre dois marcadores, como após uma coisa, mas antes de outra, poderá fazer algo como (não testado) e usar as instruções if para torná-la flexível o suficiente para ter um marcador de início ou fim ou ambos, evitando erros nulos de ref :

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

Geralmente, faço funções utilitárias para coisas úteis como essa, as deixo livres de erros e depois confio nelas frequentemente uma vez sólidas, em vez de sempre reescrever esse tipo de manipulação de string e arriscar referências nulas, etc. Dessa forma, você pode reutilizar a função em muitos projetos e nunca mais perca tempo depurando por que uma referência de string tem um erro de referência indefinido. Pode não ser o código de 1 linha mais curto de todos os tempos, mas depois que você tiver a função de utilitário, será uma linha a partir de então. Observe que a maior parte do código é apenas manipular parâmetros que estão lá ou não para evitar erros :)

Por exemplo:

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

se você precisar substituir o texto, use $('#listItem').html( newHTML ); onde newHTML é uma variável que já possui o texto despojado.
OG Sean


0

Eu vim com uma solução específica que deveria ser muito mais eficiente do que a clonagem e modificação do clone. Essa solução funciona apenas com as duas reservas a seguir, mas deve ser mais eficiente que a solução atualmente aceita:

  1. Você está recebendo apenas o texto
  2. O texto que você deseja extrair é anterior aos elementos filho

Com isso dito, aqui está o código:

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

Assim como a pergunta, eu estava tentando texto extrato, a fim de fazer alguma substituição regex do texto, mas estava ficando problemas onde meus elementos internos (ou seja: <i>, <div>,<span> , etc.) estavam se também removidos.

O código a seguir parece funcionar bem e resolveu todos os meus problemas.

Ele usa algumas das respostas fornecidas aqui, mas em particular, substituirá o texto apenas quando o elemento for de 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;
  }
});

O que o acima faz é percorrer todos os elementos do dado el(que foram simplesmente obtidos com $("div.my-class[name='some-name']");. Para cada elemento interno, basicamente os ignora. Para cada parte do texto (conforme determinado porif (this.nodeType === 3) ), aplicará a substituição do regex apenas nesses elementos .

A this.textContent = textporção simplesmente substitui o texto Substituição, que no meu caso, eu estava à procura de sinais como [[min.val]], [[max.val]], etc.

Este trecho curto de código ajudará qualquer um que tente fazer o que a pergunta estava perguntando ... e um pouco mais.


-1

basta colocá-lo em um <p>ou<font> e pegar o $ ('# listItem font'). text ()

Primeira coisa que veio à mente

<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
Não tenho controle sobre colocar o texto livre em tags, porque o código do qual estou trabalhando não foi criado por mim. Se eu pudesse pegar apenas esse texto, poderia removê-lo e substituí-lo por tags ao redor ou fazer o que eu quisesse. Mas, novamente, o html já está pré-escrito.
MegaMatt

Ah ok. Então acho que você terá que filtrar os resultados: desculpe.
Dorjan

-1

Você pode tentar isso

alert(document.getElementById('listItem').firstChild.data)

-2

Use uma condição extra para verificar se innerHTML e innerText são iguais. Somente nesses casos, substitua o texto.

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

Para poder aparar o resultado, use o DotNetWala da seguinte maneira:

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

Eu descobri que usar a versão mais curta como document.getElementById("listItem").childNodes[0]não funcionará com o trim () do jQuery.


3
Isso porque document.getElementById("listItem").childNodes[0]é javascript simples, você tem que envolvê-la na função jQuery$(document.getElementById("listItem").childNodes[0]).trim()
Red Taz

Ok, isso faz sentido. Haha Obrigado!
Marion Go

11
Isso é quase idêntico à resposta do DotNetWala . Tudo o que você fez foi adicionado .trim()ao final. Essa resposta é necessária?
Todos os trabalhadores têm Essencial

-3

Eu não sou um especialista em jquery, mas que tal,

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

11
Se você notar um especialista em jquery, por que não se tornar mais especialista lendo as outras respostas primeiro? ... Uma delas era praticamente a mesma que a que você escreveu, com comentários abaixo que explicam por que não é uma boa ideia.
Oskar Holmkratz

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.