【问题标题】:Parsing Html from String without losing functionality从字符串解析 Html 而不会丢失功能
【发布时间】:2018-07-15 19:13:05
【问题描述】:

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

代码示例: 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
}

创建上下文片段:

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

【问题讨论】:

标签: javascript html parsing dom


【解决方案1】:

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

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

【讨论】:

【解决方案2】:

简短的回答是,你不能真正做你想做的事。如果您正在尝试这样做,那么拥有多个 body/head/html 标签是没有意义的。

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

您可能真正在寻找的是&lt;iframe&gt;s&lt;iframe&gt;s 将允许您展示嵌入在您的页面中的一个或多个页面。 &lt;iframe&gt; 中加载的任何代码都是沙盒代码,不会影响您的父文档,从而降低 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,不幸的是,如果您加载的页面来自另一台服务器,您将无法对其进行操作。您可以做的是使用服务器端脚本来抓取页面并进行更改,然后将其加载到&lt;iframe&gt;。 (从另一个子域这样做,就像 Stack Overflow 的 sn-p 系统一样,因此保持同源策略有效,因此代理页面无法访问您的主域。

如果你所做的一切只是为了你自己的使用,你也可以写一个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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-21
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多