extraia todo o conteúdo de texto em htmlcollection para array com javascript

Sep 08 2020

Aqui estão os dados brutos

anycasestr = "<div style="color: rgb(51, 51, 51); background-color: rgb(253, 246, 227); font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; font-size: 12px; line-height: 18px;"><div>refinement</div><div>decent</div><div>elegant</div></div>";

para extrair todo o conteúdo do texto para uma matriz como ["refinamento", "decente", "elegante"]

Eu escrevi este código (jquery foi incluído)

htmlObject = $(anycasestr);
arr = Array.prototype.slice.call(htmlObject[0].getElementsByTagName("div"));
arr_page = [];
for (i = 0; i < 3; i++) {
    arr_page.push(arr[i].textContent);
}

Produz o que eu quero.

Só estou preocupado se for implementado de uma forma elegante. Alguém poderia dar uma olhada nisso?

Por exemplo, eu deveria ter usado xpath ou algo assim, em termos de questões computacionais?

Respostas

3 CertainPerformance Sep 08 2020 at 08:12

Declare suas variáveis - sempre que você atribuir ou referenciar uma variável sem defini-la primeiro, você (1) criará implicitamente uma propriedade no objeto global (o que pode resultar em bugs estranhos) ou (2) lançará um erro, se você está executando no modo estrito. No momento, você não está definindo nenhuma de suas variáveis. Corrija-o colocando const(ou, quando necessário, let) na frente deles ao atribuir a eles pela primeira vez, por exemplo const htmlObject = $(anycasestr);.

Métodos jQuery ou DOM? Você está usando jQuery para transformar a string em uma coleção de elementos jQuery, mas está usando getElementsByTagNamepara selecionar filhos. Se você estiver usando jQuery, você pode ser conciso e consistente para usá-lo para selecionar os <div>filhos. Para encontrar os filhos de um elemento que correspondem a um nome de tag específico, chame .finda coleção jQuery - então, você pode usar .mappara transformar o Elementos jQuery em uma coleção de apenas o texto dos elementos:

const $parent = $(anycasestr); const arr = $parent.find('div')
  .map((_, child) => child.textContent)
  .get(); // turn the jQuery collection of strings into an array of strings

const anycasestr = `<div style="color: rgb(51, 51, 51); background-color: rgb(253, 246, 227); font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; font-size: 12px; line-height: 18px;"><div>refinement</div><div>decent</div><div>elegant</div></div>`;
const $parent = $(anycasestr);
const arr = $parent.find('div')
  .map((_, child) => child.textContent)
  .get(); // turn the jQuery collection of strings into an array of strings
console.log(arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Ou você pode usar o DOMParser. Usar DOMParser em vez de jQuery para transformar o texto em uma coleção de elementos pode evitar a execução acidental de scripts maliciosos. Exemplo de exploração usando jQuery:

const anycasestr = `<img src="" onerror="alert('evil')">`;
const $parent = $(anycasestr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Com DOMParser:

const anycasestr = `<div style="color: rgb(51, 51, 51); background-color: rgb(253, 246, 227); font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; font-size: 12px; line-height: 18px;"><div>refinement</div><div>decent</div><div>elegant</div></div>`;
const doc = new DOMParser().parseFromString(anycasestr, 'text/html');
const arr = [...doc.querySelectorAll('div > div')]
  .map(div => div.textContent);
console.log(arr);

A string de consulta div > divseleciona <div>elementos que são filhos diretos de outro <div>. Funciona exatamente da mesma maneira que os seletores CSS. querySelectorAllé uma ótima ferramenta para uma seleção concisa de elementos - pode ser mais fácil de escrever e entender à primeira vista do que outros métodos (como o original htmlObject[0].getElementsByTagName("div")).

Array.prototype.slice.callé um pouco prolixo - em ambientes não antigos, você pode usar a sintaxe de propagação, como fiz acima. Criar um array de uma vez por meio do mapeamento também é um pouco mais elegante do que declarar um array e .pushinserir nele.

Se você tivesse mais <div>filhos e quisesse obter apenas o texto dos 3 primeiros, seria mais funcional para .slicea matriz de elementos em vez de colocar uma contagem de iteração em um forloop:

const anycasestr = `<div style="color: rgb(51, 51, 51); background-color: rgb(253, 246, 227); font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; font-size: 12px; line-height: 18px;">
  <div>refinement</div>
  <div>decent</div>
  <div>elegant</div>
  <div>don't include me</div>
  <div>don't include me</div>
  <div>don't include me</div>
</div>`;
const doc = new DOMParser().parseFromString(anycasestr, 'text/html');
const arr = [...doc.querySelectorAll('div > div')]
  .slice(0, 3)
  .map(div => div.textContent);
console.log(arr);

em termos de questões computacionais?

A menos que o material que precisa ser analisado seja excessivamente grande , o desempenho para esse tipo de coisa não é uma preocupação; melhor escrever código limpo, legível e sustentável. Se, posteriormente, você descobrir que algo está demorando mais para ser executado do que o ideal, poderá identificar o gargalo e descobrir como corrigi-lo. (Mas isso quase certamente não será o gargalo.)