Phân tích chuỗi HTML bằng JS


258

Tôi đã tìm kiếm một giải pháp nhưng không có gì liên quan, vì vậy đây là vấn đề của tôi:

Tôi muốn phân tích một chuỗi chứa văn bản HTML. Tôi muốn làm điều đó trong JavaScript.

Tôi đã thử thư viện này nhưng có vẻ như nó phân tích cú pháp HTML của trang hiện tại của tôi chứ không phải từ một chuỗi. Bởi vì khi tôi thử mã dưới đây, nó sẽ thay đổi tiêu đề của trang của tôi:

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

Mục tiêu của tôi là trích xuất các liên kết từ một trang bên ngoài HTML mà tôi đọc giống như một chuỗi.

Bạn có biết một API để làm điều đó?



1
Phương thức trên bản sao được liên kết tạo ra một tài liệu HTML từ một chuỗi đã cho. Sau đó, bạn có thể sử dụng doc.getElementsByTagName('a')để đọc các liên kết (hoặc thậm chí doc.links).
Cướp W

Điều đáng nói là nếu bạn đang sử dụng một khung công tác như React.js thì có thể có các cách thực hiện cụ thể cho khung đó như: stackoverflow.com/questions/23616226/
Mike Lyons

Điều này có trả lời câu hỏi của bạn không? Tách HTML khỏi Text JavaScript
Leif Arne Storset

Câu trả lời:


373

Tạo một phần tử DOM giả và thêm chuỗi vào nó. Sau đó, bạn có thể thao tác nó như bất kỳ phần tử DOM nào.

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

Chỉnh sửa: thêm câu trả lời jQuery để làm hài lòng người hâm mộ!

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
Chỉ cần một lưu ý: Với giải pháp này, nếu tôi thực hiện "cảnh báo (el.innerHTML)", tôi sẽ mất thẻ <html>, <body> và <head> ....
giai đoạn

2
Vấn đề: Tôi cần nhận liên kết từ thẻ <frame>. Nhưng với giải pháp này, thẻ khung hình sẽ bị xóa ...
giai đoạn

3
@stage Tôi đến bữa tiệc muộn một chút, nhưng bạn sẽ có thể sử dụng document.createElement('html');để giữ các thẻ <head><body>.
omninonsense

3
có vẻ như bạn đang đặt một phần tử html trong một phần tử html
symbiont

6
Tôi quan tâm được đánh giá là câu trả lời hàng đầu. Các parse()giải pháp dưới đây là tái sử dụng và thanh lịch hơn.
Justin

233

Nó khá đơn giản:

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

Theo MDN , để thực hiện điều này trong chrome, bạn cần phân tích cú pháp như XML:

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

Nó hiện không được hỗ trợ bởi webkit và bạn phải tuân theo câu trả lời của Florian và hầu như không hoạt động trong hầu hết các trường hợp trên trình duyệt di động.

Chỉnh sửa: Hiện được hỗ trợ rộng rãi


35
Đáng chú ý là năm 2016 DOMParser hiện được hỗ trợ rộng rãi. caniuse.com/#feat=xml-serializer

5
Đáng chú ý là tất cả các liên kết tương đối trong tài liệu được tạo ra bị phá vỡ, bởi vì tài liệu được tạo ra bởi sự kế thừa documentURLcủa window, mà hầu hết khác với khả năng từ URL của chuỗi.
ceving

2
Đáng lưu ý rằng bạn chỉ nên gọi new DOMParsermột lần và sau đó sử dụng lại cùng một đối tượng trong suốt phần còn lại của tập lệnh của bạn.
Jack Giffin

1
Các parse()giải pháp dưới đây là tái sử dụng nhiều hơn và cụ thể cho HTML. Điều này là tốt nếu bạn cần một tài liệu XML, tuy nhiên.
Justin

Làm cách nào tôi có thể hiển thị trang web được phân tích cú pháp này trên hộp thoại hoặc một cái gì đó? Tôi đã không thể tìm ra giải pháp cho điều đó
Shariq Musharaf

18

EDIT: Giải pháp bên dưới chỉ dành cho "các đoạn" HTML do html, phần đầu và phần thân bị xóa. Tôi đoán giải pháp cho câu hỏi này là phương thức parseFromString () của DOMPars.


Đối với các đoạn HTML, các giải pháp được liệt kê ở đây hoạt động với hầu hết HTML, tuy nhiên đối với một số trường hợp nhất định, nó sẽ không hoạt động.

Ví dụ: thử phân tích cú pháp <td>Test</td> . Cái này sẽ không hoạt động trên giải pháp div.innerHTML cũng như giải pháp DOMParser.prototype.parseFromString cũng như Range.createContextualFragment. Thẻ td bị thiếu và chỉ còn lại văn bản.

Chỉ jQuery xử lý trường hợp đó tốt.

Vì vậy, giải pháp trong tương lai (MS Edge 13+) là sử dụng thẻ mẫu:

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

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

Đối với các trình duyệt cũ hơn, tôi đã trích xuất phương thức parseHTML () của jQuery thành một ý chính độc lập - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99


Nếu bạn muốn viết mã tương thích về phía trước cũng hoạt động trên các trình duyệt cũ, bạn có thể điền vào <template>thẻ . Nó phụ thuộc vào các yếu tố tùy chỉnh mà bạn cũng có thể cần polyfill . Trong thực tế, bạn có thể chỉ muốn sử dụng webcomponents.js để tạo các phần tử tùy chỉnh, mẫu, bóng tối, lời hứa và một vài thứ khác cùng một lúc.
Jeff Smilelin

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

4
Tại sao bạn lại có tiền tố $? Ngoài ra, như đã đề cập trong bản sao được liên kết , text/htmlkhông được hỗ trợ tốt và phải được thực hiện bằng cách sử dụng một polyfill.
Cướp

1
Tôi đã sao chép dòng này từ một dự án, tôi đã sử dụng các biến tiền tố với $ trong ứng dụng javascript (không phải trong thư viện). nó chỉ để avoir có một cuộc xung đột với một thư viện. điều đó không hữu ích lắm vì hầu hết mọi biến số đều nằm trong phạm vi nhưng nó đã từng rất hữu ích. nó cũng (có thể) giúp xác định các biến dễ dàng.
Mathieu

1
Đáng buồn DOMParserlà không hoạt động trên text/htmlchrome, trang MDN này cung cấp cách giải quyết.
Jokester

Lưu ý bảo mật: điều này sẽ thực thi mà không có bất kỳ bối cảnh trình duyệt nào, vì vậy không có tập lệnh nào sẽ chạy. Nó phải phù hợp với đầu vào không đáng tin cậy.
Leif Arne Storset

6

Cách nhanh nhất để phân tích HTML trong Chrome và Firefox là Range # createdContextualFragment:

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

Tôi khuyên bạn nên tạo một hàm trợ giúp sử dụng createdContextualFragment nếu có và quay lại bên trongHTML.

Điểm chuẩn: http://jsperf.com/domparser-vs-createelement-innerhtml/3


Lưu ý rằng, giống như (đơn giản) innerHTML, điều này sẽ thực hiện một <img>'s onerror.
Ry-

Một vấn đề với điều này là, html như '<td> test </ td>' sẽ bỏ qua td trong bối cảnh document.body (và chỉ tạo nút văn bản 'test') .OTOH, nếu nó được sử dụng nội bộ trong một công cụ tạo khuôn mẫu sau đó bối cảnh phù hợp sẽ có sẵn.
Munawwar

Ngoài ra BTW, IE 11 hỗ trợ createdContextualFragment.
Munawwar

Câu hỏi là làm thế nào để phân tích cú pháp với JS - không phải Chrome hay Firefox
sea26.2

Lưu ý bảo mật: điều này sẽ thực thi bất kỳ tập lệnh nào trong đầu vào và do đó không phù hợp với đầu vào không tin cậy.
Leif Arne Storset

6

Hàm sau parseHTMLsẽ trả về:

  • a Documentkhi tập tin của bạn bắt đầu với một loại tài liệu.

  • a DocumentFragmentkhi tập tin của bạn không bắt đầu với một loại tài liệu.


Mật mã :

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

Cách sử dụng:

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

Tôi không thể làm điều này để làm việc trên IE8. Tôi gặp lỗi "Đối tượng không hỗ trợ thuộc tính hoặc phương thức này" cho dòng đầu tiên trong hàm. Tôi không nghĩ chức năng createdHTMLDocument tồn tại
Sebastian Carroll

Chính xác thì trường hợp sử dụng của bạn là gì? Nếu bạn chỉ muốn phân tích HTML và HTML của bạn dành cho phần thân của tài liệu, bạn có thể làm như sau: (1) var div = document.createEuity ("DIV"); (2) div.innerHTML = đánh dấu; (3) kết quả = div.childNodes; --- Điều này cung cấp cho bạn một bộ sưu tập mã con và sẽ hoạt động không chỉ trong IE8 mà ngay cả trong IE6-7.
John Slegers

Cảm ơn vì lựa chọn thay thế, tôi sẽ thử nếu tôi cần làm lại. Bây giờ mặc dù tôi đã sử dụng giải pháp JQuery ở trên.
Sebastian Carroll

@SebastianCarroll Lưu ý rằng IE8 không hỗ trợ trimphương thức trên chuỗi. Xem stackoverflow.com/q/2308134/3210837 .
Bàn chải đánh răng

2
@Tooth Brush: Hỗ trợ IE8 có còn phù hợp vào buổi bình minh năm 2017 không?
John Slegers

4

Nếu bạn đang mở để sử dụng jQuery, nó có một số phương tiện tốt để tạo các phần tử DOM tách rời khỏi chuỗi HTML. Những thứ này sau đó có thể được truy vấn thông qua các phương tiện thông thường, vd:

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

Chỉnh sửa - chỉ cần xem câu trả lời của @ đó là chính xác. Điều này về cơ bản chính xác như những gì ông nói, nhưng với 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>`) );


Chỉ những đứa trẻ hợp lệ Nodetrong cha mẹ Node(bắt đầu Range) sẽ được phân tích cú pháp. Nếu không, kết quả không mong muốn có thể xảy ra:

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

Lưu ý bảo mật: điều này sẽ thực thi bất kỳ tập lệnh nào trong đầu vào và do đó không phù hợp với đầu vào không tin cậy.
Leif Arne Storset

0

với mã đơn giản này, bạn có thể làm điều đó:

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"]'));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.