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ć?



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

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


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


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

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


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

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.


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

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"]'));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.