Analysieren Sie eine HTML-Zeichenfolge mit JS

258

Ich habe nach einer Lösung gesucht, aber nichts war relevant. Hier ist mein Problem:

Ich möchte eine Zeichenfolge analysieren, die HTML-Text enthält. Ich möchte es in JavaScript tun.

Ich habe diese Bibliothek ausprobiert, aber es scheint, dass sie den HTML-Code meiner aktuellen Seite analysiert, nicht aus einer Zeichenfolge. Denn wenn ich den folgenden Code versuche, ändert sich der Titel meiner Seite:

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

Mein Ziel ist es, Links von einer externen HTML-Seite zu extrahieren, die ich wie eine Zeichenfolge lese.

Kennen Sie eine API dafür?

Bühne
quelle
1
Die Methode für das verknüpfte Duplikat erstellt aus einer bestimmten Zeichenfolge ein HTML-Dokument. Dann können Sie doc.getElementsByTagName('a')die Links lesen (oder sogar doc.links).
Rob W
Es ist erwähnenswert, dass es bei Verwendung eines Frameworks wie React.js möglicherweise spezifische Möglichkeiten für das Framework gibt, z. B
Mike Lyons
Beantwortet das deine Frage? HTML aus Text
entfernen

Antworten:

373

Erstellen Sie ein Dummy-DOM-Element und fügen Sie die Zeichenfolge hinzu. Dann können Sie es wie jedes DOM-Element bearbeiten.

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

Bearbeiten: Hinzufügen einer jQuery-Antwort, um die Fans zufrieden zu stellen!

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
Florian Margaine
quelle
9
Nur eine Anmerkung: Wenn ich mit dieser Lösung eine "Warnung (el.innerHTML)" mache, verliere ich die Tags <html>, <body> und <head> ....
Stufe
2
Problem: Ich muss Links vom <frame> -Tag erhalten. Aber mit dieser Lösung werden die Frame-Tags gelöscht ...
Stufe
3
@stage Ich bin ein bisschen zu spät zur Party, aber du solltest in der Lage sein, document.createElement('html');die <head>und <body>Tags zu bewahren .
Omninonsense
3
Es sieht so aus, als würden Sie ein HTML-Element in ein HTML-Element einfügen
Symbiont
6
Ich bin besorgt, wird als Top-Antwort positiv bewertet. Die folgende parse()Lösung ist wiederverwendbarer und eleganter.
Justin
233

Es ist ganz einfach:

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

Laut MDN müssen Sie dazu wie in Chrome Folgendes analysieren:

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

Es wird derzeit vom Webkit nicht unterstützt und Sie müssten Florians Antwort folgen. In den meisten Fällen funktioniert es nicht mit mobilen Browsern.

Bearbeiten: Jetzt weit verbreitet unterstützt

Cilan
quelle
35
Erwähnenswert ist, dass DOMParser 2016 mittlerweile weitgehend unterstützt wird. caniuse.com/#feat=xml-serializer
aendrew
5
Beachten Sie, dass alle relativen Links im erstellten Dokument fehlerhaft sind, da das Dokument durch Erben des documentURLvon erstellt wird window, was höchstwahrscheinlich von der URL der Zeichenfolge abweicht.
Ceving
2
Beachten Sie, dass Sie nurnew DOMParser einmal aufrufen und dann dasselbe Objekt im Rest Ihres Skripts wiederverwenden sollten .
Jack Giffin
1
Die folgende parse()Lösung ist wiederverwendbarer und spezifischer für HTML. Dies ist jedoch hilfreich, wenn Sie ein XML-Dokument benötigen.
Justin
Wie kann ich diese analysierte Webseite in einem Dialogfeld oder so etwas anzeigen? Ich konnte keine Lösung dafür finden
Shariq Musharaf
18

BEARBEITEN: Die folgende Lösung gilt nur für HTML- "Fragmente", da HTML, Kopf und Text entfernt werden. Ich denke, die Lösung für diese Frage ist die parseFromString () -Methode von DOMParser.


Für HTML-Fragmente funktionieren die hier aufgeführten Lösungen für die meisten HTML-Dateien, in bestimmten Fällen jedoch nicht.

Versuchen Sie beispielsweise das Parsen <td>Test</td>. Dieser funktioniert weder mit der div.innerHTML-Lösung noch mit DOMParser.prototype.parseFromString oder range.createContextualFragment. Das td-Tag geht verloren und nur der Text bleibt übrig.

Nur jQuery behandelt diesen Fall gut.

Die zukünftige Lösung (MS Edge 13+) besteht also darin, ein Vorlagen-Tag zu verwenden:

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

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

Für ältere Browser habe ich die parseHTML () -Methode von jQuery in eine unabhängige Liste extrahiert - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99

Munawwar
quelle
Wenn Sie vorwärtskompatiblen Code schreiben möchten, der auch in alten Browsern funktioniert, können Sie das <template>Tag mehrfach ausfüllen . Dies hängt von benutzerdefinierten Elementen ab, die Sie möglicherweise auch zum Polyfill benötigen . Tatsächlich möchten Sie möglicherweise nur webcomponents.js verwenden, um benutzerdefinierte Elemente, Vorlagen, Schattendom, Versprechen und einige andere Dinge auf einmal zu füllen.
Jeff Laughlin
12
var doc = new DOMParser().parseFromString(html, "text/html");
var links = doc.querySelectorAll("a");
Mathieu
quelle
4
Warum stellst du ein Präfix vor $? Wie im verknüpften Duplikat erwähnt , text/htmlwird es auch nicht sehr gut unterstützt und muss mithilfe einer Polyfüllung implementiert werden.
Rob W
1
Ich habe diese Zeile aus einem Projekt kopiert. Ich bin es gewohnt, Variablen in der Javascript-Anwendung (nicht in der Bibliothek) $ voranzustellen. Es geht nur darum, einen Konflikt mit einer Bibliothek zu vermeiden. Das ist nicht sehr nützlich, da fast jede Variable einen Gültigkeitsbereich hat, aber früher nützlich war. es hilft auch (vielleicht), Variablen leicht zu identifizieren.
Mathieu
1
Leider DOMParserfunktioniert keine dieser Arbeiten text/htmlin Chrome. Diese MDN-Seite bietet eine Problemumgehung.
Jokester
Sicherheitshinweis: Dies wird ohne Browserkontext ausgeführt, sodass keine Skripts ausgeführt werden. Es sollte für nicht vertrauenswürdige Eingaben geeignet sein.
Leif Arne Storset
6

Der schnellste Weg, um HTML in Chrome und Firefox zu analysieren, ist Range # createContextualFragment:

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

Ich würde empfehlen, eine Hilfsfunktion zu erstellen, die createContextualFragment verwendet, falls verfügbar, und ansonsten auf innerHTML zurückgreift.

Benchmark: http://jsperf.com/domparser-vs-createelement-innerhtml/3

Joel Richard
quelle
Beachten Sie, dass dies wie (das einfache) innerHTMLein <img>'s ausführt onerror.
Ry-
Ein Problem dabei ist, dass HTML wie '<td> test </ td>' das td im Kontext document.body ignoriert (und nur den Textknoten 'test' erstellt) .OTOH, wenn es intern in einer Template-Engine verwendet wird dann wäre der richtige Kontext verfügbar.
Munawwar
Übrigens unterstützt IE 11 createContextualFragment.
Munawwar
Die Frage war, wie man mit JS analysiert - nicht Chrome oder Firefox
sea26.2
Sicherheitshinweis: Dadurch wird jedes Skript in der Eingabe ausgeführt und ist daher nicht für nicht vertrauenswürdige Eingaben geeignet.
Leif Arne Storset
6

Die folgende Funktion parseHTMLgibt entweder zurück:

  • a DocumentWenn Ihre Datei mit einem Doctype beginnt.

  • a DocumentFragmentWenn Ihre Datei nicht mit einem Doctype beginnt.


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

Wie benutzt man :

var links = parseHTML('<!doctype html><html><head></head><body><a>Link 1</a><a>Link 2</a></body></html>').getElementsByTagName('a');
John Slegers
quelle
Ich konnte dies nicht auf IE8 zum Laufen bringen. Ich erhalte die Fehlermeldung "Objekt unterstützt diese Eigenschaft oder Methode nicht" für die erste Zeile in der Funktion. Ich glaube nicht, dass die Funktion createHTMLDocument existiert
Sebastian Carroll
Was genau ist Ihr Anwendungsfall? Wenn Sie nur HTML analysieren möchten und Ihr HTML für den Hauptteil Ihres Dokuments bestimmt ist, können Sie Folgendes tun: (1) var div = document.createElement ("DIV"); (2) div.innerHTML = Markup; (3) Ergebnis = div.childNodes; --- Dies gibt Ihnen eine Sammlung von Kinderknoten und sollte nicht nur in IE8, sondern auch in IE6-7 funktionieren.
John Slegers
Vielen Dank für die alternative Option. Ich werde es versuchen, wenn ich dies erneut tun muss. Im Moment habe ich jedoch die oben genannte JQuery-Lösung verwendet.
Sebastian Carroll
@SebastianCarroll Beachten Sie, dass IE8 die trimMethode für Zeichenfolgen nicht unterstützt . Siehe stackoverflow.com/q/2308134/3210837 .
Zahnbürste
2
@Toothbrush: Ist die IE8-Unterstützung zu Beginn des Jahres 2017 noch relevant?
John Slegers
4

Wenn Sie für die Verwendung von jQuery offen sind, bietet es einige nützliche Funktionen zum Erstellen von getrennten DOM-Elementen aus HTML-Zeichenfolgen. Diese können dann mit den üblichen Mitteln abgefragt werden, zB:

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

Bearbeiten - habe gerade @ Florians Antwort gesehen, die richtig ist. Dies ist im Grunde genau das, was er gesagt hat, aber mit jQuery.

jmar777
quelle
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>`) );


Es werden nur gültige Kinder Nodeinnerhalb des Elternteils Node(Anfang des Range) analysiert. Andernfalls können unerwartete Ergebnisse auftreten:

// <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>');
AnthumChris
quelle
Sicherheitshinweis: Dadurch wird jedes Skript in der Eingabe ausgeführt und ist daher nicht für nicht vertrauenswürdige Eingaben geeignet.
Leif Arne Storset
0

Mit diesem einfachen Code können Sie das tun:

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"]'));
NaabNuts
quelle