Разобрать строку HTML с помощью JS


259

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

Я хочу проанализировать строку, которая содержит текст 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).
— Роб W

Стоит отметить, что если вы используете фреймворк, такой как 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
Просто примечание: с этим решением, если я сделаю «alert (el.innerHTML)», я потеряю теги <html>, <body> и <head> ....
— этап

2
Проблема: мне нужно получить ссылки из тега <frame>. Но при таком решении тег кадра удаляется ...
— этап

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

3
похоже, что вы помещаете элемент html в элемент html
— symbiont

6
Я обеспокоен тем, что проголосовал как главный ответ. parse()Раствор ниже более многоразовый и элегантный.
— Джастин,

233

Все довольно просто:

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

Согласно MDN , чтобы сделать это в chrome, вам нужно проанализировать как XML:

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

В настоящее время он не поддерживается webkit, и вам придется следовать ответу Флориана, и в большинстве случаев он неизвестен для мобильных браузеров.

Изменить: теперь широко поддерживается


35
Стоит отметить, что в 2016 году DOMParser теперь широко поддерживается. caniuse.com/#feat=xml-serializer
— aendrew

5
Стоит отметить, что все относительные ссылки в созданном документе не работают, потому что документ создается путем наследования documentURLof window, который, скорее всего, отличается от URL-адреса строки.
— празднование

2
Стоит отметить, что вы должны вызывать толькоnew DOMParser один раз, а затем повторно использовать этот же объект в оставшейся части вашего сценария.
— Джек Гиффин

1
Приведенное parse()ниже решение более пригодно для повторного использования и относится к HTML. Это хорошо, если вам нужен документ XML, однако.
— Джастин,

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

18

РЕДАКТИРОВАТЬ: Решение ниже только для HTML «фрагментов», так как HTML, голова и тело удалены. Я думаю, что решение этого вопроса - метод parseFromString () в DOMParser.


Для фрагментов HTML решения, перечисленные здесь, работают для большей части HTML, однако в некоторых случаях это не будет работать.

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

Только 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, чтобы заполнить пользовательские элементы, шаблоны, shadow dom, обещания и некоторые другие вещи одновременно.
— Джефф Лафлин

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

4
Почему ты префикс $? Кроме того, как упомянуто в связанном дубликате , text/htmlон не очень хорошо поддерживается и должен быть реализован с использованием полизаполнения.
— Роб W

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

1
К сожалению, DOMParserни одна из них не работает text/htmlв Chrome, эта страница MDN дает обходной путь.
— Jokester

Примечание по безопасности: это будет выполняться без какого-либо контекста браузера, поэтому сценарии не будут выполняться. Это должно быть подходящим для ненадежного ввода.
— Лейф Арне Сторсет

6

Самый быстрый способ разбора HTML в Chrome и Firefox - Range # 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>'ы' onerror.
— Ry-

Проблема с этим заключается в том, что html, такой как <td> test </ td>, будет игнорировать td в контексте document.body (и только создавать текстовый узел 'test') .OTOH, если он будет использоваться внутри механизма шаблонов. тогда будет доступен правильный контекст.
— Мунавар

Кроме того, IE 11 поддерживает createContextualFragment.
— Мунавар

Вопрос был в том, как разобрать с JS - не Chrome или Firefox
— sea26.2

Примечание по безопасности: это выполнит любой сценарий на входе и, следовательно, не подходит для ненадежного ввода.
— Лейф Арне Сторсет

6

Следующая функция parseHTMLвернет либо:

  • Documentесли файл начинается с DOCTYPE.

  • 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.
— Джон Слегерс

Спасибо за альтернативный вариант, я попробую, если мне понадобится сделать это снова. Пока что я использовал решение JQuery выше.
— Себастьян Кэрролл

@SebastianCarroll Обратите внимание, что IE8 не поддерживает trimметод для строк. См. Stackoverflow.com/q/2308134/3210837 .
— Зубная щетка

2
@ Зубная щетка: поддержка IE8 все еще актуальна на заре 2017 года?
— Джон Слегерс

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


Только действительные дочерние Nodeэлементы в родительском 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.