从字符串解析HTML而不丢失功能

时间:2018-07-01 07:29:44

标签: javascript html parsing dom

我正在尝试从字符串(在javascript中)解析html,并对html代码进行一些操作(因此我需要将html解析为节点,以便我可以轻松地操作每个节点),然后我想插入真实dom的节点。 我尝试使用new DOMParser(); API,但是将javascript标记和noscript标记注入到真实dom中时将无法执行。 我尝试了createContextualFragment,但是我的html代码具有html / head / body标签...而createContextualFragment则忽略了它们。 当注入包括js,noscript的真实dom时,我需要能够保留所有html代码功能。 有什么想法吗?

代码示例: DomParser API:

var parser = new DOMParser();
htmlDoc = parser.parseFromString(htmlString, "text/html");
htmlDoc.querySelectorAll("*").forEach(function(node) {
//some manipulations....
//but if i inject the nodes to the real dom all js will not execute
}

createContextualFragment:

var DocumentFragmentDom = document.createRange().createContextualFragment(Html);
var DocumentFragmentLength = DocumentFragmentDom.children.length;
for(var i = 0 ; i < DocumentFragmentLength; i++ ){
//some manipulations
//but all head/body/html tags will be disregarded...
}

2 个答案:

答案 0 :(得分:0)

我不知道DomFragmentDomParser,但最简单的(IMO)类似于:

var htmlDoc = document.createElement('div');
htmlDoc.innerHtml = htmlString;
document.body.appendChild(htmlDoc);

答案 1 :(得分:0)

简短的回答是,您实际上无法做您似乎想做的事情。如果您要这样做,那么拥有多个body / head / html标签是没有意义的。

在我再说什么之前,我要说的是,试图解析代码并将其注入您的网站,尤其是包含JS的代码,可能非常危险。如果来自不可信来源,则可能包含XSS attack。即使它来自受信任的来源,也可能容易受到reflected XSS攻击。

您可能真正想要的是<iframe>s<iframe>可让您展示嵌入在页面内的一个或多个页面。 <iframe>中加载的所有代码都是沙盒化的,不会影响您的父文档,从而降低了XSS的风险。

let url = 'http://www.example.org/';

let iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.width = '90%';
iframe.style.height = '300px';
iframe.style.display = 'block';
iframe.style.margin = '1em auto';
document.body.appendChild(iframe);

很遗憾,由于Same-origin policy,如果您加载的页面来自其他服务器,您将无法对其进行操作。您可以使用服务器端脚本来抓取页面并进行更改,然后将其加载到<iframe>中。 (这样做是从另一个子域进行的,就像Stack Overflow上的代码段系统一样,因此保持同源策略有效,因此代理页面无法访问您的主域。)

如果您所做的只是供自己使用,则还可以编写userscript来在浏览页面时直接修改页面,而不必尝试解析它们并将它们自己加载到另一个页面中。


您可以使用这种我完全不建议使用的非常骇人听闻的方法来伪造您似乎想做的事情。它对文档进行两次解析,一次使用DOMParser提取head标签和body标签的属性,然后再次使用createContextualFragment创建要插入的实际节点。正如我在上面的警告中所述,这很危险(更不用说速度慢了,因为您两次解析了文档)

// I'm just getting the HTML from the data attribute of an element in
  // the page instead of using XHR...
  // Can't just store it in a string here because when the browser sees a
  // script tag inside of a string it assumes it is the end of the script
  // and the script contains an unterminated string literal instead of a string
  // containing a script tag.
  let html = document.getElementById('data').dataset.html;

  // parse the document with DOMParser to get the attributes of body
  let parsedDoc = (new DOMParser()).parseFromString(html, "text/html");
  let bodyAttr = [...parsedDoc.body.attributes];

  // parse the html into a fragment
  var frag = document.createRange().createContextualFragment(html);
  frag.querySelector('h1').style.color = '#f00';
  
  // avoid inserting the style tag from the head into the middle of the document
  frag.querySelectorAll('style').forEach(tag => {
    frag.removeChild(tag);
  });

  // insert the fragment
  document.body.appendChild(frag);

  // replace the document head with the parsed one
  document.documentElement.replaceChild(parsedDoc.head, document.head);

  // augment the body of the document with the attributes
  // from the parsed document
  bodyAttr.forEach(attr => {
    document.body.setAttribute(attr.nodeName, attr.nodeValue);
  });
  
  // AGAIN, PLEASE don't do this unless you absolute control
  // over the data that will be parsed with it, i.e. it is
  // also coming from your server and you authored it, it is
  // not user submitted. Again, this mostly works, but is
  // slow and dangerous, it would be much better to use iframes
<div id="data" data-html="
<html>
  <head>
    <style>
      body {color: #00f}
    </style>
  </head>
  <body style='background: #000' lang='en-us' data-test='test data'>
    <h1>Hello World</h1>
    <p>Some text</p>
    <script>
      console.log('Hello JS');document.querySelector('h1').style.background = '#FF0';
    </script>
  </body>
</html>
"></div>