Разобрать строку 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, и вам придется следовать ответу Флориана, и в большинстве случаев он неизвестен для мобильных браузеров.

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

Cilan
источник
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

Munawwar
источник
Если вы хотите написать совместимый с прямым кодом код, который также работает в старых браузерах, вы можете заполнить <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.

jmar777
источник
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>');
AnthumChris
источник
Примечание по безопасности: это выполнит любой сценарий на входе и, следовательно, не подходит для ненадежного ввода.
Лейф Арне Сторсет
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"]'));
NaabNuts
источник