Проаналізуйте рядок HTML за допомогою JS


258

Я шукав рішення, але нічого не було актуальним, тому ось моя проблема:

Я хочу розібрати рядок, який містить текст HTML. Я хочу це зробити в JavaScript.

Я спробував цю бібліотеку, але, схоже, вона аналізує HTML моєї поточної сторінки, а не з рядка. Тому що, коли я спробую код нижче, він змінює заголовок моєї сторінки:

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

Моя мета - витягнути посилання із зовнішньої сторінки HTML, яку я читаю так само, як рядок.

Чи знаєте ви API, щоб це зробити?



1
Метод на зв'язаному дублікаті створює HTML-документ із заданого рядка. Потім ви можете використовувати doc.getElementsByTagName('a')для читання посилань (або навіть doc.links).
Роб Ш

Варто згадати, що якщо ви використовуєте таку структуру, як React.js, то, можливо, існують способи її виконання, які є специфічними для такої рамки, як: stackoverflow.com/questions/23616226/…
Майк Ліонс

Чи відповідає це на ваше запитання?
Стріпте

Відповіді:


373

Створіть фіктивний елемент DOM і додайте до нього рядок. Потім ви можете маніпулювати нею, як будь-яким елементом 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

Редагувати: додавши відповідь jQuery, щоб порадувати шанувальників!

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
Лише зауваження: за допомогою цього рішення, якщо я роблю "попередження (el.innerHTML)", я втрачаю теги <html>, <body> та <head> ....
етап

2
Проблема: мені потрібно отримати посилання з тегу <frame>. Але за допомогою цього рішення тег фрейму видаляється ...
етап

3
@stage Я трохи запізнююся на вечірку, але ви повинні мати можливість document.createElement('html');зберегти теги <head>та <body>теги.
omninonsense

3
схоже, ви додаєте елемент html в елемент html
symbiont

6
Я стурбований, що це головна відповідь. parse()Розчин нижче більш багаторазовий і елегантний.
Джастін

232

Це досить просто:

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

Згідно з MDN , для цього в хромі потрібно розбирати XML, як:

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

Наразі веб-код не підтримується, і вам доведеться дотримуватися відповіді Флоріана, і в більшості випадків невідомо, як працювати в мобільних браузерах.

Редагувати: Зараз широко підтримується


35
Варто зазначити, що в 2016 році DOMParser зараз широко підтримується. caniuse.com/#feat=xml-serializer
aendrew

5
Варто відзначити , що всі відносні посилання в створеному документі зламані, так як документ отримує створене успадковують documentURLвід window, який , швидше за все , відрізняється від URL рядка.
припинення

2
Варто зауважити, що вам слід зателефонувати лишеnew DOMParser один раз, а потім повторно використати цей самий об’єкт протягом решти сценарію.
Джек Гіффін

1
parse()Розчину нижче більш повторне використання і специфічний для HTML. Це добре, якщо вам потрібен документ XML.
Джастін

Як я можу відобразити цю проаналізовану веб-сторінку в діалоговому вікні чи щось таке? Я не зміг знайти рішення для цього
Шарік Мушараф

18

РЕДАКТУВАННЯ. Нижче наведено рішення лише для "фрагментів" HTML, оскільки видалено html, голову та тіло Я думаю, що рішенням цього питання є метод parseFromString () DOMParser.


Для фрагментів HTML, перераховані тут рішення працюють для більшості HTML, однак для певних випадків вони не працюватимуть.

Наприклад, спробуйте розібратися <td>Test</td>. Цей не працюватиме на рішенні div.innerHTML, ні на DOMParser.prototype.parseFromString, ні на діапазоні.createContextualFragment. Тег td відсутній, і залишається лише текст.

Тільки jQuery добре обробляє цей випадок.

Тож майбутнім рішенням (MS Edge 13+) є використання тегів шаблонів:

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

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

Для більш старих браузерів я вилучив метод jQuery parseHTML () в незалежний список - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99


Якщо ви хочете написати сумісний з прямим кодом код, який також працює у старих браузерах, ви можете поліфагувати <template>тег . Це залежить від користувацьких елементів, які вам також можуть знадобитися для заповнення . Насправді ви, можливо, просто захочете використовувати webcomponents.js для заповнення спеціальних елементів, шаблонів, тіньового дому, обіцянок та кількох інших речей за один раз.
Джефф Лафлін

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

4
Чому ви маєте префіксацію $? Крім того, як згадується у зв'язаному дублікаті , text/htmlвін не підтримується дуже добре, і його потрібно реалізувати за допомогою поліфазу.
Роб Ш

1
Я скопіював цей рядок із проекту, я використовую для префіксації змінних $ в додатку javascript (не в бібліотеці). це просто уникати конфлікту з бібліотекою. це не дуже корисно, так як майже кожна змінна має масштаб, але раніше вона була корисною. це також (можливо) допоможе легко визначити змінні.
Матьє

1
На жаль, і це DOMParserне працює text/htmlв хромі, ця MDN-сторінка дає змогу вирішити проблему.
Джокер

Примітка безпеки: це буде виконуватися без будь-якого контексту браузера, тому ніякі сценарії не запускаються. Він повинен бути придатним для ненадійного введення.
Лейф Арн Шторсет

6

Найшвидший спосіб розбору HTML в Chrome і Firefox - це діапазон # createContextualFragment:

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

Я рекомендую створити допоміжну функцію, яка використовує createContextualFragment, якщо вона доступна, і в іншому випадку повертається до innerHTML.

Орієнтир: http://jsperf.com/domparser-vs-createelement-innerhtml/3


Зауважте, що, як (простий) innerHTML, це буде виконувати <img>'s onerror.
Ри-

Проблема з цим полягає в тому, що html на зразок '<td> test </td>' ігнорує td у контексті document.body (і створює лише текстовий вузол 'test'). тоді правильний контекст був би доступний.
Munawwar

Також BTW, IE 11 підтримує createContextualFragment.
Munawwar

Питання полягало в тому, як розібратися з JS - не Chrome чи Firefox
sea26.2

Примітка безпеки: це виконає будь-який скрипт на вході, і, таким чином, не підходить для ненадійного введення.
Лейф Арн Шторсет

6

Наступна функція parseHTMLповерне або:

  • a, Documentколи ваш файл починається з doctype.

  • a, DocumentFragmentколи ваш файл не починається з doctype.


Код :

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

Як використовувати :

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

Я не міг змусити це працювати над IE8. Я отримую помилку "Об'єкт не підтримує це властивість чи метод" для першого рядка функції. Я не думаю, що функція createHTMLDocument існує
Себастьян Керролл

Який саме ваш випадок використання? Якщо ви просто хочете розібрати HTML, а ваш HTML призначений для тіла документа, ви можете зробити наступне: (1) var div = document.createElement ("DIV"); (2) div.innerHTML = розмітка; (3) результат = div.childNodes; --- Це дає вам колекцію дочірніх вузлів і повинні працювати не лише в IE8, але навіть в IE6-7.
Джон Slegers

Дякую за альтернативний варіант, я спробую, якщо мені доведеться це зробити ще раз. Поки що я використовував рішення JQuery вище.
Себастьян Керролл

@SebastianCarroll Зауважте, що IE8 не підтримує trimметод на рядках. Дивіться stackoverflow.com/q/2308134/3210837 .
Зубна щітка

2
@Toothbrush: Чи підтримка IE8 все ще актуальна на зорі 2017 року?
John Slegers

4

Якщо ви відкриті для використання jQuery, він має деякі приємні можливості для створення відокремлених елементів DOM із рядків HTML. Потім їх можна запитати звичайними засобами, наприклад:

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

Редагувати - щойно побачив правильну відповідь @ Флоріана. Це в основному саме те, що він сказав, але з 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>`) );


Буде розібраний лише дійсний дочірній Nodes у батьків Node(початок Range). В іншому випадку можуть виникнути несподівані результати:

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

Примітка безпеки: це виконає будь-який скрипт на вході, і, таким чином, не підходить для ненадійного введення.
Лейф Арн Шторсет

0

за допомогою цього простого коду ви можете це зробити:

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"]'));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.