Analyser une chaîne HTML avec JS


259

J'ai cherché une solution mais rien n'était pertinent, voici donc mon problème:

Je souhaite analyser une chaîne contenant du texte HTML. Je veux le faire en JavaScript.

J'ai essayé cette bibliothèque mais il semble qu'elle analyse le code HTML de ma page actuelle, pas à partir d'une chaîne. Parce que lorsque j'essaye le code ci-dessous, cela change le titre de ma page:

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

Mon objectif est d'extraire des liens d'une page externe HTML que je lis comme une chaîne.

Connaissez-vous une API pour le faire?



1
La méthode sur le doublon lié crée un document HTML à partir d'une chaîne donnée. Ensuite, vous pouvez utiliser doc.getElementsByTagName('a')pour lire les liens (ou même doc.links).
Rob W

Il convient de mentionner que si vous utilisez un framework comme React.js, il peut y avoir des moyens de le faire qui sont spécifiques au framework tels que: stackoverflow.com/questions/23616226/…
Mike Lyons

Est-ce que cela répond à votre question? Supprimer HTML du texte JavaScript
Leif Arne Storset

Réponses:


373

Créez un élément DOM factice et ajoutez-y la chaîne. Ensuite, vous pouvez le manipuler comme n'importe quel élément 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: ajouter une réponse jQuery pour faire plaisir aux fans!

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
Juste une note: Avec cette solution, si je fais une "alerte (el.innerHTML)", je perds les balises <html>, <body> et <head> ....
stage

2
Problème: j'ai besoin d'obtenir des liens à partir de la balise <frame>. Mais avec cette solution, la balise frame est supprimée ...
étape

3
@stage Je suis un peu en retard à la fête, mais vous devriez pouvoir utiliser document.createElement('html');pour conserver les balises <head>et <body>.
omninonsense

3
il semble que vous mettiez un élément html dans un élément html
symbiont

6
Je suis inquiet est voté comme la meilleure réponse. La parse()solution ci-dessous est plus réutilisable et élégante.
Justin

233

C'est assez simple:

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

Selon MDN , pour ce faire dans Chrome, vous devez analyser en XML comme ceci:

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

Il n'est actuellement pas pris en charge par le webkit et vous devrez suivre la réponse de Florian, et il est inconnu de fonctionner dans la plupart des cas sur les navigateurs mobiles.

Edit: désormais largement pris en charge


35
Il convient de noter qu'en 2016, DOMParser est désormais largement pris en charge. caniuse.com/#feat=xml-serializer
aendrew

5
Il convient de noter que tous les liens relatifs dans le document créé sont rompus, car le document est créé en héritant du documentURLof window, qui diffère très probablement de l'URL de la chaîne.
Ceving

2
Il convient de noter que vous ne devez appeler qu'une seulenew DOMParser fois, puis réutiliser ce même objet dans le reste de votre script.
Jack Giffin

1
La parse()solution ci-dessous est plus réutilisable et spécifique au HTML. Cependant, c'est bien si vous avez besoin d'un document XML.
Justin

Comment puis-je afficher cette page Web analysée dans une boîte de dialogue ou quelque chose? Je n'ai pas pu trouver de solution à cela
Shariq Musharaf

18

EDIT: La solution ci-dessous est uniquement pour les "fragments" HTML car le HTML, la tête et le corps sont supprimés. Je suppose que la solution à cette question est la méthode parseFromString () de DOMParser.


Pour les fragments HTML, les solutions répertoriées ici fonctionnent pour la plupart du HTML, mais dans certains cas cela ne fonctionnera pas.

Par exemple, essayez d'analyser <td>Test</td>. Celui-ci ne fonctionnera pas sur la solution div.innerHTML ni DOMParser.prototype.parseFromString ni range.createContextualFragment. La balise td disparaît et seul le texte reste.

Seul jQuery gère bien ce cas.

Donc, la future solution (MS Edge 13+) consiste à utiliser la balise de modèle:

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

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

Pour les navigateurs plus anciens, j'ai extrait la méthode parseHTML () de jQuery dans un résumé indépendant - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99


Si vous souhaitez écrire du code compatible avec le futur qui fonctionne également sur les anciens navigateurs, vous pouvez remplir la <template>balise de manière polyvalente . Cela dépend des éléments personnalisés que vous devrez peut-être également remplir . En fait, vous voudrez peut-être simplement utiliser webcomponents.js pour remplir des éléments personnalisés, des modèles, des shadow dom, des promesses et quelques autres choses en une seule fois.
Jeff Laughlin

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

4
Pourquoi préfixez-vous $? En outre, comme mentionné dans le doublon lié , text/htmln'est pas très bien pris en charge et doit être implémenté à l'aide d'un polyfill.
Rob W

1
J'ai copié cette ligne à partir d'un projet, j'ai l'habitude de préfixer les variables avec $ dans l'application javascript (pas dans la bibliothèque). c'est juste pour avoir un conflit avec une bibliothèque. ce n'est pas très utile car presque toutes les variables ont une portée, mais elles étaient utiles. cela peut aussi (peut-être) aider à identifier facilement les variables.
Mathieu

1
Malheureusement, DOMParserni travailler sur text/htmlChrome, cette page MDN donne une solution de contournement.
Jokester

Note de sécurité: cela s'exécutera sans aucun contexte de navigateur, donc aucun script ne sera exécuté. Il doit convenir à une entrée non fiable.
Leif Arne Storset

6

La façon la plus rapide d'analyser le HTML dans Chrome et Firefox est Range # createContextualFragment:

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

Je recommanderais de créer une fonction d'aide qui utilise createContextualFragment si disponible et retombe dans innerHTML sinon.

Indice de référence: http://jsperf.com/domparser-vs-createelement-innerhtml/3


Notez que, comme (le simple) innerHTML, cela exécutera un <img>s onerror.
Ry-

Un problème avec cela est que, du HTML comme '<td> test </td>' ignorerait le td dans le contexte document.body (et ne créerait que le nœud de texte 'test') .OTOH, s'il était utilisé en interne dans un moteur de template alors le bon contexte serait disponible.
Munawwar

BTW, IE 11 prend également en charge createContextualFragment.
Munawwar

La question était de savoir comment analyser avec JS - pas Chrome ou Firefox
sea26.2

Note de sécurité: cela exécutera n'importe quel script dans l'entrée et ne convient donc pas pour une entrée non approuvée.
Leif Arne Storset

6

La fonction suivante parseHTMLrenverra soit:

  • a Documentlorsque votre fichier commence par un doctype.

  • a DocumentFragmentlorsque votre fichier ne commence pas par un doctype.


Le code :

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;
    }
}

Comment utiliser :

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

Je n'ai pas pu faire fonctionner cela sur IE8. J'obtiens l'erreur "L'objet ne prend pas en charge cette propriété ou méthode" pour la première ligne de la fonction. Je ne pense pas que la fonction createHTMLDocument existe
Sebastian Carroll

Quel est exactement votre cas d'utilisation? Si vous voulez simplement analyser HTML et que votre HTML est destiné au corps de votre document, vous pouvez faire ce qui suit: (1) var div = document.createElement ("DIV"); (2) div.innerHTML = balisage; (3) résultat = div.childNodes; --- Cela vous donne une collection de nœuds enfants et devrait fonctionner non seulement dans IE8 mais même dans IE6-7.
John Slegers

Merci pour l'option alternative, je vais l'essayer si je dois recommencer. Pour l'instant, j'ai utilisé la solution JQuery ci-dessus.
Sebastian Carroll

@SebastianCarroll Notez qu'IE8 ne prend pas en charge la trimméthode sur les chaînes. Voir stackoverflow.com/q/2308134/3210837 .
Brosse à dents

2
@Toothbrush: la prise en charge d'IE8 est-elle toujours pertinente à l'aube de 2017?
John Slegers

4

Si vous êtes ouvert à l'utilisation de jQuery, il dispose de fonctionnalités intéressantes pour créer des éléments DOM détachés à partir de chaînes HTML. Ceux-ci peuvent ensuite être interrogés par les moyens habituels, par exemple:

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

Edit - vient de voir la réponse de @ Florian qui est correcte. C'est exactement ce qu'il a dit, mais avec 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>`) );


Seuls les enfants valides Nodeau sein du parent Node(début du Range) seront analysés. Sinon, des résultats inattendus peuvent se produire:

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

Note de sécurité: cela exécutera n'importe quel script dans l'entrée et ne convient donc pas pour une entrée non approuvée.
Leif Arne Storset

0

avec ce code simple, vous pouvez le faire:

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"]'));
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.