Analizuj ciąg HTML za pomocą JS

258

Szukałem rozwiązania, ale nic nie było istotne, więc oto mój problem:

Chcę przeanalizować ciąg zawierający tekst HTML. Chcę to zrobić w JavaScript.

Wypróbowałem tę bibliotekę, ale wygląda na to, że analizuje ona HTML mojej bieżącej strony, a nie ciąg znaków. Ponieważ kiedy wypróbuję poniższy kod, zmienia tytuł mojej strony:

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

Moim celem jest wyodrębnienie linków ze strony zewnętrznej HTML, którą czytam jak ciąg znaków.

Czy znasz interfejs API, aby to zrobić?

etap
źródło
1
Metoda na połączonym duplikacie tworzy dokument HTML z podanego ciągu. Następnie możesz użyć doc.getElementsByTagName('a')do odczytania linków (lub nawet doc.links).
Rob W
Warto wspomnieć, że jeśli używasz frameworka takiego jak React.js, mogą istnieć sposoby, które są specyficzne dla frameworka, takie jak: stackoverflow.com/questions/23616226/...
Mike Lyons
Czy to odpowiada na twoje pytanie? Usuń HTML z tekstowego JavaScript
Leif Arne Storset

Odpowiedzi:

373

Utwórz fikcyjny element DOM i dodaj do niego ciąg. Następnie możesz nim manipulować jak dowolnym elementem 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

Edycja: dodając odpowiedź jQuery, aby zadowolić fanów!

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
źródło
9
Uwaga: dzięki temu rozwiązaniu, jeśli zrobię „alert (el.innerHTML)”, tracę tag <html>, <body> i <head> ....
etap
2
Problem: Potrzebuję uzyskać linki z tagu <frame>. Ale dzięki temu rozwiązaniu znacznik ramki jest usuwany ...
etap
3
@stage Jestem trochę spóźniony na imprezę, ale powinieneś być w stanie document.createElement('html');zachować tagi <head>i <body>.
omninonsense
3
wygląda na to, że wstawiasz element html do elementu html
symbiont
6
Obawiam się, że jest oceniany jako najlepsza odpowiedź. Poniższe parse()rozwiązanie jest bardziej wielokrotnego użytku i eleganckie.
Justin
232

To dość proste:

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

Według MDN , aby to zrobić w chrome, musisz parsować jako XML tak:

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

Obecnie nie jest obsługiwany przez webkit i musisz podążać za odpowiedzią Floriana, a w większości przypadków nie działa w przeglądarkach mobilnych.

Edycja: teraz szeroko obsługiwany

Cilan
źródło
35
Warto zauważyć, że w 2016 roku DOMParser jest obecnie szeroko obsługiwany. caniuse.com/#feat=xml-serializer
aendrew
5
Warto zauważyć, że wszystkie względne łącza w utworzonym dokumencie są uszkodzone, ponieważ dokument jest tworzony przez dziedziczenie documentURLz window, który najprawdopodobniej różni się od adresu URL ciągu.
ceving
2
Warto zauważyć, że powinieneś zadzwonić tylkonew DOMParser raz, a następnie ponownie użyć tego samego obiektu w pozostałej części skryptu.
Jack Giffin,
1
Poniższe parse()rozwiązanie jest bardziej przydatne i specyficzne dla HTML. Jest to jednak przydatne, jeśli potrzebujesz dokumentu XML.
Justin
Jak wyświetlić tę przeanalizowaną stronę internetową w oknie dialogowym lub w innym oknie? Nie byłem w stanie znaleźć rozwiązania tego
problemu
18

EDYCJA: Poniższe rozwiązanie dotyczy tylko „fragmentów” HTML, ponieważ html, head i body są usuwane. Wydaje mi się, że rozwiązaniem tego pytania jest metoda parseFromString () DOMParsera.


W przypadku fragmentów HTML wymienione tutaj rozwiązania działają dla większości HTML, jednak w niektórych przypadkach nie będą działać.

Na przykład spróbuj parsować <td>Test</td>. Ten nie będzie działał z rozwiązaniem div.innerHTML ani DOMParser.prototype.parseFromString ani z range.createContextualFragment. Tag td znika i pozostaje tylko tekst.

Tylko jQuery dobrze sobie z tym radzi.

Zatem przyszłym rozwiązaniem (MS Edge 13+) jest użycie tagu szablonu:

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

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

W przypadku starszych przeglądarek wyodrębniłem metodę parseHTML () jQuery do niezależnej listy - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99

Munawwar
źródło
Jeśli chcesz napisać kod zgodny z poprzednimi wersjami, który działa również w starszych przeglądarkach, możesz wypełnić <template>tag tagiem . To zależy od niestandardowych elementów, które mogą być również potrzebne do wypełniania . W rzeczywistości możesz po prostu użyć webcomponents.js, aby za jednym razem polifillować niestandardowe elementy, szablony, shadow dom, obietnice i kilka innych rzeczy.
Jeff Laughlin
12
var doc = new DOMParser().parseFromString(html, "text/html");
var links = doc.querySelectorAll("a");
Mathieu
źródło
4
Dlaczego prefiksujesz $? Ponadto, jak wspomniano w połączonym duplikacie , text/htmlnie jest on obsługiwany bardzo dobrze i musi zostać zaimplementowany przy użyciu wypełniacza wielofunkcyjnego.
Rob W
1
Skopiowałem ten wiersz z projektu, jestem przyzwyczajony do prefiksu zmiennych $ w aplikacji javascript (nie w bibliotece). to po prostu uniknąć konfliktu z biblioteką. to nie jest bardzo przydatne, ponieważ prawie każda zmienna ma zakres, ale kiedyś była użyteczna. Pomaga także (być może) w łatwej identyfikacji zmiennych.
Mathieu
1
Niestety, DOMParserżadna z nich nie działa text/htmlw chrome, ta strona MDN daje obejście.
Jokester
Uwaga dotycząca bezpieczeństwa: będzie działać bez kontekstu przeglądarki, więc nie będą działać żadne skrypty. Powinien być odpowiedni dla niezaufanych danych wejściowych.
Leif Arne Storset
6

Najszybszym sposobem na parsowanie HTML w Chrome i Firefox jest Range # createContextualFragment:

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

Poleciłbym utworzyć funkcję pomocnika, która używa createContextualFragment, jeśli jest dostępna, w przeciwnym razie wraca do innerHTML.

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

Joel Richard
źródło
Należy zauważyć, że, podobnie jak (prostego) innerHTML, to będzie wykonać <img>„s onerror.
Ry-
Problem polega na tym, że html taki jak „<td> test </td>” zignorowałby td w kontekście document.body (i tylko utworzył „tekstowy węzeł testowy”) .OTOH, gdyby był używany wewnętrznie w silniku szablonów wtedy odpowiedni kontekst byłby dostępny.
Munawwar
BTW, IE 11 obsługuje także funkcję createContextualFragment.
Munawwar
Pytanie brzmiało: jak
parsować
Uwaga bezpieczeństwa: spowoduje to wykonanie dowolnego skryptu na wejściu, a zatem nie nadaje się do niezaufanego wejścia.
Leif Arne Storset
6

Zwróci następującą funkcję parseHTML:


Kod :

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

Jak używać :

var links = parseHTML('<!doctype html><html><head></head><body><a>Link 1</a><a>Link 2</a></body></html>').getElementsByTagName('a');
John Slegers
źródło
Nie mogłem tego uruchomić w IE8. W pierwszym wierszu funkcji pojawia się błąd „Obiekt nie obsługuje tej właściwości lub metody”. Nie sądzę, aby istniała funkcja createHTMLDocument
Sebastian Carroll
Jaki dokładnie jest twój przypadek użycia? Jeśli chcesz tylko parsować HTML, a Twój HTML jest przeznaczony do treści dokumentu, możesz wykonać następujące czynności: (1) var div = document.createElement („DIV”); (2) div.innerHTML = znaczniki; (3) wynik = div.childNodes; --- To daje kolekcję childnodes i powinno działać nie tylko w IE8, ale nawet w IE6-7.
John Slegers
Dzięki za alternatywną opcję, spróbuję, jeśli będę musiał to zrobić ponownie. Na razie jednak użyłem powyższego rozwiązania JQuery.
Sebastian Carroll
@SebastianCarroll Zauważ, że IE8 nie obsługuje trimmetody na łańcuchach. Zobacz stackoverflow.com/q/2308134/3210837 .
Szczoteczka do zębów
2
@Toothbrush: Czy obsługa IE8 jest nadal aktualna na początku 2017 roku?
John Slegers
4

Jeśli jesteś otwarty na używanie jQuery, ma kilka ciekawych funkcji do tworzenia odłączonych elementów DOM z ciągów HTML. Można je następnie przesłać w zwykły sposób, np .:

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

Edytuj - właśnie widziałem odpowiedź @ Florian, która jest poprawna. Jest to w zasadzie dokładnie to, co powiedział, ale z jQuery.

jmar777
źródło
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>`) );


Tylko poprawne dziecko Nodew obrębie rodzica Node(początek Range) zostanie przeanalizowane. W przeciwnym razie mogą wystąpić nieoczekiwane wyniki:

// <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
źródło
Uwaga bezpieczeństwa: spowoduje to wykonanie dowolnego skryptu na wejściu, a zatem nie nadaje się do niezaufanego wejścia.
Leif Arne Storset
0

za pomocą tego prostego kodu możesz to zrobić:

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
źródło