Analisar uma string HTML com JS


258

Eu procurei uma solução, mas nada era relevante, então aqui está o meu problema:

Eu quero analisar uma string que contém texto HTML. Eu quero fazer isso em JavaScript.

Eu tentei esta biblioteca, mas parece que ela analisa o HTML da minha página atual, não de uma string. Porque quando tento o código abaixo, ele altera o título da minha página:

var parser = new HTMLtoDOM("<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>", document);

Meu objetivo é extrair links de uma página externa em HTML que eu li como uma string.

Você conhece uma API para fazer isso?



1
O método na duplicata vinculada cria um documento HTML a partir de uma determinada sequência. Então, você pode usar doc.getElementsByTagName('a')para ler os links (ou mesmo doc.links).
— 22712 Rob Rob W

Vale a pena mencionar que, se você estiver usando uma estrutura como React.js então pode haver maneiras de fazer isso que são específicos para a estrutura, tais como: stackoverflow.com/questions/23616226/...
— Mike Lyons

Respostas:


373

Crie um elemento DOM fictício e adicione a string a ele. Em seguida, você pode manipulá-lo como qualquer elemento DOM.

var el = document.createElement( 'html' );
el.innerHTML = "<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>";

el.getElementsByTagName( 'a' ); // Live NodeList of your anchor elements

Edit: adicionando uma resposta jQuery para agradar os fãs!

var el = $( '<div></div>' );
el.html("<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>");

$('a', el) // All the anchor elements

9
Apenas uma observação: com esta solução, se eu fizer um "alerta (el.innerHTML)", perco as tags <html>, <body> e <head> ....
— etapa

2
Problema: preciso obter links da tag <frame>. Mas com esta solução, a tag frame é excluída ...
— estágio

3
@stage Estou um pouco atrasado para a festa, mas você deve poder usar document.createElement('html');para preservar as tags <head>e <body>.
— Omninonsense

3
parece que você está colocando um elemento HTML dentro de um elemento html
— simbionte

6
Estou preocupado que seja votado como a melhor resposta. A parse()solução abaixo é mais reutilizável e elegante.
— Justin

233

É bem simples:

var parser = new DOMParser();
var htmlDoc = parser.parseFromString(txt, 'text/html');
// do whatever you want with htmlDoc.getElementsByTagName('a');

De acordo com o MDN , para fazer isso no chrome, você precisa analisar como XML da seguinte forma:

var parser = new DOMParser();
var htmlDoc = parser.parseFromString(txt, 'text/xml');
// do whatever you want with htmlDoc.getElementsByTagName('a');

No momento, ele não é suportado pelo webkit e você teria que seguir a resposta de Florian, e é desconhecido que na maioria dos casos funcione em navegadores móveis.

Edit: Agora amplamente suportado


35
Vale ressaltar que em 2016 o DOMParser agora é amplamente suportado. caniuse.com/#feat=xml-serializer
— aendrew

5
Vale ressaltar que todos os links relativos no documento criado estão quebrados, porque o documento é criado herdando o documentURLde window, o que provavelmente difere da URL da string.
— ceving 03/11/19

2
Vale a pena notar que você deve ligar apenasnew DOMParser uma vez e, em seguida, reutilizar o mesmo objeto no restante do seu script.
— Jack Giffin

1
A parse()solução abaixo é mais reutilizável e específica para HTML. Isso é bom se você precisar de um documento XML, no entanto.
— Justin

Como posso exibir esta página da Web analisada em uma caixa de diálogo ou algo assim? Não consegui encontrar uma solução para isso #
— Shariq Musharaf

18

EDIT: A solução abaixo é apenas para "fragmentos" de HTML, pois o html, a cabeça e o corpo são removidos. Eu acho que a solução para esta pergunta é o método parseFromString () do DOMParser.


Para fragmentos HTML, as soluções listadas aqui funcionam para a maioria dos HTML; no entanto, em certos casos, não funciona.

Por exemplo, tente analisar <td>Test</td> . Este não funcionará na solução div.innerHTML nem no DOMParser.prototype.parseFromString nem no range.createContextualFragment. A tag td desaparece e apenas o texto permanece.

Somente o jQuery lida bem com esse caso.

Portanto, a solução futura (MS Edge 13+) é usar a tag template:

function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content.cloneNode(true);
}

var documentFragment = parseHTML('<td>Test</td>');

Para navegadores mais antigos, extraí o método parseHTML () do jQuery em uma lista independente - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99


Se você deseja escrever um código compatível com a frente que também funcione em navegadores antigos, é possível preencher novamente a <template>tag . Depende dos elementos personalizados que também podem ser necessários para o polyfill . Na verdade, você pode apenas querer usar o webcomponents.js para preencher polyfill elementos personalizados, modelos, sombra dom, promessas e algumas outras coisas de uma só vez.
— Jeff Laughlin

12
var doc = new DOMParser().parseFromString(html, "text/html");
var links = doc.querySelectorAll("a");

4
Por que você está prefixando $? Além disso, como mencionado na duplicata vinculada , text/htmlnão é muito bem suportado e deve ser implementado usando um polyfill.
— Rob W

1
Copiei esta linha de um projeto, estou acostumado a prefixar variáveis ​​com $ no aplicativo javascript (não na biblioteca). é apenas para avoir ter um conflito com uma biblioteca. isso não é muito útil, pois quase todas as variáveis ​​têm escopo definido, mas costumavam ser úteis. também (talvez) ajuda a identificar variáveis ​​facilmente.
— 1619 Mathieu

1
Infelizmente, DOMParsernem funciona text/htmlno chrome, esta página MDN fornece uma solução alternativa.
— Jokester

Nota de segurança: isso será executado sem nenhum contexto do navegador, portanto, nenhum script será executado. Deve ser adequado para entrada não confiável.
— Leif Arne Storset 11/03

6

A maneira mais rápida de analisar HTML no Chrome e Firefox é Range # createContextualFragment:

var range = document.createRange();
range.selectNode(document.body); // required in Safari
var fragment = range.createContextualFragment('<h1>html...</h1>');
var firstNode = fragment.firstChild;

Eu recomendaria criar uma função auxiliar que use createContextualFragment, se disponível, e retorne ao innerHTML.

Referência: http://jsperf.com/domparser-vs-createelement-innerhtml/3


Observe que, como (o simples) innerHTML, isso executará um <img>'s onerror.
— Ry-

Um problema é que html como '<td> test </td>' ignoraria o td no contexto document.body (e apenas criaria o nó de texto 'test') .OTOH, se usado internamente em um mecanismo de modelagem então o contexto correto estaria disponível.
— Munawwar

Além disso, o IE 11 também suporta createContextualFragment.
— Munawwar

A questão era como analisar com JS - não Chrome ou Firefox
— sea26.2

Nota de segurança: isso executará qualquer script na entrada e, portanto, não é adequado para entradas não confiáveis.
— Leif Arne Storset 11/03

6

A seguinte função parseHTMLretornará:

  • a Documentquando seu arquivo iniciar com um doctype.

  • a DocumentFragmentquando seu arquivo não iniciar com um doctype.


O código :

function parseHTML(markup) {
    if (markup.toLowerCase().trim().indexOf('<!doctype') === 0) {
        var doc = document.implementation.createHTMLDocument("");
        doc.documentElement.innerHTML = markup;
        return doc;
    } else if ('content' in document.createElement('template')) {
       // Template tag exists!
       var el = document.createElement('template');
       el.innerHTML = markup;
       return el.content;
    } else {
       // Template tag doesn't exist!
       var docfrag = document.createDocumentFragment();
       var el = document.createElement('body');
       el.innerHTML = markup;
       for (i = 0; 0 < el.childNodes.length;) {
           docfrag.appendChild(el.childNodes[i]);
       }
       return docfrag;
    }
}

Como usar :

var links = parseHTML('<!doctype html><html><head></head><body><a>Link 1</a><a>Link 2</a></body></html>').getElementsByTagName('a');

Não consegui fazer isso funcionar no IE8. Recebo o erro "O objeto não suporta esta propriedade ou método" para a primeira linha da função. Eu não acho que a função createHTMLDocument existe
— Sebastian Carroll

Qual é exatamente o seu caso de uso? Se você apenas deseja analisar o HTML e seu HTML é destinado ao corpo do seu documento, você pode fazer o seguinte: (1) var div = document.createElement ("DIV"); (2) div.innerHTML = marcação; (3) resultado = div.childNodes; --- Isso fornece uma coleção de nós filhos e deve funcionar não apenas no IE8, mas também no IE6-7.
— John Slegers

Obrigado pela opção alternativa, tentarei se precisar fazer isso novamente. Por enquanto, embora eu usei a solução JQuery acima.
— Sebastian Carroll

@SebastianCarroll Observe que o IE8 não suporta o trimmétodo em strings. Consulte stackoverflow.com/q/2308134/3210837 .
— Escova de dentes

2
@ Toothbrush: O suporte ao IE8 ainda é relevante no início de 2017?
— John Slegers

4

Se você está aberto a usar o jQuery, ele possui algumas boas instalações para criar elementos DOM desanexados a partir de strings de HTML. Estes podem ser consultados através dos meios habituais, por exemplo:

var html = "<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>";
var anchors = $('<div/>').append(html).find('a').get();

Editar - apenas vi a resposta de @ Florian, que está correta. Isso é basicamente exatamente o que ele disse, mas com o jQuery.


4
const parse = Range.prototype.createContextualFragment.bind(document.createRange());

document.body.appendChild( parse('<p><strong>Today is:</strong></p>') ),
document.body.appendChild( parse(`<p style="background: #eee">${new Date()}</p>`) );


Somente filhos válidos Nodedentro do pai Node(início do Range) serão analisados. Caso contrário, poderão ocorrer resultados inesperados:

// <body> is "parent" Node, start of Range
const parseRange = document.createRange();
const parse = Range.prototype.createContextualFragment.bind(parseRange);

// Returns Text "1 2" because td, tr, tbody are not valid children of <body>
parse('<td>1</td> <td>2</td>');
parse('<tr><td>1</td> <td>2</td></tr>');
parse('<tbody><tr><td>1</td> <td>2</td></tr></tbody>');

// Returns <table>, which is a valid child of <body>
parse('<table> <td>1</td> <td>2</td> </table>');
parse('<table> <tr> <td>1</td> <td>2</td> </tr> </table>');
parse('<table> <tbody> <td>1</td> <td>2</td> </tbody> </table>');

// <tr> is parent Node, start of Range
parseRange.setStart(document.createElement('tr'), 0);

// Returns [<td>, <td>] element array
parse('<td>1</td> <td>2</td>');
parse('<tr> <td>1</td> <td>2</td> </tr>');
parse('<tbody> <td>1</td> <td>2</td> </tbody>');
parse('<table> <td>1</td> <td>2</td> </table>');

Nota de segurança: isso executará qualquer script na entrada e, portanto, não é adequado para entradas não confiáveis.
— Leif Arne Storset 11/03

0

Com este código simples, você pode fazer isso:

let el = $('<div></div>');
$(document.body).append(el);
el.html(`<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>`);
console.log(el.find('a[href="test0"]'));
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.