用JS解析HTML字符串


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)。
罗伯W

值得一提的是,如果你使用像React.js框架则有可能是特定于框架如这样做的方式:stackoverflow.com/questions/23616226/...
迈克·莱昂斯

这回答了你的问题了吗?从文本JavaScript中
删除

Answers:


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>and <body>标签。
omn​​inonsense,2015年

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,要在chrome中执行此操作,您需要将其解析为XML,如下所示:

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

目前,webkit不支持它,您必须遵循Florian的回答,并且在大多数情况下无法在移动浏览器上正常工作。

编辑:现在得到广泛支持


35
值得一提的是,2016年,DOMParser得到了广泛的支持。caniuse.com/#feat=xml-serializer
aendrew '16

5
值得一提的是,已创建文档中的所有相关链接均已断开,因为该文档是通过继承的documentURLof来创建的,该文档window很可能与字符串的URL不同。
ceving

2
值得一提的是,您应调用new DOMParser一次,然后在其余脚本中重用同一对象。
杰克·吉芬

1
parse()下面的解决方案更可重用,并且特定于HTML。但是,如果需要XML文档,这很好。
贾斯汀

如何在对话框等上显示此已解析的网页?我找不到解决方案
Shariq Musharaf

18

编辑:下面的解决方案仅适用于HTML“片段”,因为html,head和body被删除了。我猜这个问题的解决方案是DOMParser的parseFromString()方法。


对于HTML片段,此处列出的解决方案适用于大多数HTML,但是在某些情况下不起作用。

例如,尝试解析<td>Test</td>。此代码不适用于div.innerHTML解决方案,DOMParser.prototype.parseFromString和range.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>标记进行填充。这取决于您可能还需要polyfill的自定义元素。实际上,您可能只想一次使用webcomponents.js填充自定义元素,模板,影子dom,promise和其他一些东西。
杰夫·劳克林

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

4
为什么要加上前缀$?另外,如链接的重复项中所述,text/html不是很好地支持,必须使用polyfill来实现。
罗伯W

1
我从项目中复制了这一行,我习惯在javascript应用程序(而不是库中)中使用$作为变量的前缀。只是为了与图书馆发生冲突。这不是很有用,因为几乎每个变量都具有作用域,但是它曾经是有用的。它还(也许)有助于轻松识别变量。
Mathieu

1
遗憾的是,DOMParser两者都无法text/html在Chrome中使用,此MDN页面提供了解决方法。
Jokester

安全说明:这将在没有任何浏览器上下文的情况下执行,因此不会运行任何脚本。它应该适合于不受信任的输入。
Leif Arne Storset

6

在Chrome和Firefox中解析HTML的最快方法是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-

与此相关的一个问题是,像“ <td> test </ td>”这样的html会忽略document.body上下文中的td(并且仅创建“ test”文本节点)。那么正确的上下文将可用。
Munawwar

顺便说一句,IE 11也支持createContextualFragment。
Munawwar

问题是如何使用JS解析-而不是Chrome或Firefox
sea26.2

安全说明:这将在输入中执行任何脚本,因此不适合不受信任的输入。
Leif Arne Storset

6

以下函数parseHTML将返回:


代码 :

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函数不存在
Sebastian Carroll

您的用例到底是什么?如果您只想解析HTML并且HTML是用于文档正文的,则可以执行以下操作:(1)var div = document.createElement(“ DIV”); (2)div.innerHTML =标记;(3)结果= div.childNodes; ---这为您提供了一个子节点集合,不仅应在IE8中工作,甚至应在IE6-7中工作。
John Slegers 2014年

感谢您提供替代选项,如果需要再次执行此操作,请尝试一下。现在,尽管我使用了上面的JQuery解决方案。
塞巴斯蒂安·卡洛尔

@SebastianCarroll注意IE8不支持trim字符串方法。请参阅stackoverflow.com/q/2308134/3210837
牙刷

2
@Toothbrush:IE8支持在2017年初仍然有意义吗?
John Slegers '16

4

如果您愿意使用jQuery,它具有一些不错的功能,可用于从HTML字符串创建分离的DOM元素。然后可以通过通常的方式来查询这些内容,例如:

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

编辑-刚刚看到@Florian的答案是正确的。这基本上就是他所说的,但使用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>');

安全说明:这将在输入中执行任何脚本,因此不适合不受信任的输入。
Leif Arne Storset

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"]'));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.