将确切的innerHTML恢复为DOM

时间:2015-06-26 16:09:27

标签: javascript jquery html firefox dom

我想保存DOM的html字符串,然后将其恢复为完全相同。代码看起来像这样:

var stringified = document.documentElement.innerHTML
// later, after serializing and deserializing
document.documentElement.innerHTML = stringified

当一切都很完美时,这是有效的,但是当DOM不符合w3c时,就会出现问题。第一行工作正常,stringified完全匹配DOM。但是当我从(非w3c兼容的)stringified恢复时,浏览器会产生一些魔力并且生成的DOM与最初的DOM不同。

例如,如果我的原始DOM看起来像

<p><div></div></p>

然后最终的DOM看起来像

<p></p><div></div><p></p>

因为div元素不允许位于p元素内。有没有什么方法可以让浏览器使用与页面加载相同的html解析并按原样接受损坏的html?

为什么首先打破html? DOM不受我控制。

这是一个显示行为http://jsfiddle.net/b2x7rnfm/5/的方法。打开你的控制台。

<body>
    <div id="asdf"><p id="outer"></p></div>
    <script type="text/javascript">
        var insert = document.createElement('div');
        var text = document.createTextNode('ladygaga');
        insert.appendChild(text);
        document.getElementById('outer').appendChild(insert);
        var e = document.getElementById('asdf')
        console.log(e.innerHTML);
        e.innerHTML = e.innerHTML;
        console.log(e.innerHTML); // This is different than 2 lines above!!
    </script>
</body>

7 个答案:

答案 0 :(得分:4)

如果您需要能够保存和恢复无效的HTML结构,可以通过XML实现。后面的代码来自this fiddle

要保存,您需要创建一个新的XML文档,添加要序列化的节点:

var asdf = document.getElementById("asdf");
var outer = document.getElementById("outer");
var add = document.getElementById("add");
var save = document.getElementById("save");
var restore = document.getElementById("restore");

var saved = undefined;
save.addEventListener("click", function () {
  if (saved !== undefined)
    return; /// Do not overwrite

  // Create a fake document with a single top-level element, as 
  // required by XML.    
  var parser = new DOMParser();
  var doc = parser.parseFromString("<top/>", "text/xml");

  // We could skip the cloning and just move the nodes to the XML
  // document. This would have the effect of saving and removing 
  // at the same time but I wanted to show what saving while 
  // preserving the data would look like    
  var clone = asdf.cloneNode(true);
  var top = doc.firstChild;
  var child = asdf.firstChild;
  while (child) {
    top.appendChild(child);
    child = asdf.firstChild;
  }
  saved = top.innerHTML;
  console.log("saved as: ", saved);

  // Perform the removal here.
  asdf.innerHTML = "";
});

要恢复,您需要创建一个XML文档来反序列化保存的内容,然后将节点添加到文档中:

restore.addEventListener("click", function () {
  if (saved === undefined)
      return; // Don't restore undefined data!

  // We parse the XML we saved.
  var parser = new DOMParser();
  var doc = parser.parseFromString("<top>" + saved + "</top>", "text/xml");
  var top = doc.firstChild;

  var child = top.firstChild;
  while (child) {
    asdf.appendChild(child);
    // Remove the extra junk added by the XML parser.
    child.removeAttribute("xmlns");
    child = top.firstChild;
  }
  saved = undefined;
  console.log("inner html after restore", asdf.innerHTML);
});

使用小提琴,你可以:

  1. 按&#34;添加LadyGaga ...&#34;用于创建无效HTML的按钮。

  2. 按&#34;保存并从文档中删除&#34;保存asdf中的结构并清除其内容。这会将保存的内容打印到控制台。

  3. 按&#34;恢复&#34;恢复已保存的结构。

  4. 上面的代码旨在通用。如果可以对要保存的HTML结构做出一些假设,则可以简化代码。例如,blah不是格式良好的XML文档,因为您需要XML中的单个顶级元素。因此,上面的代码很难添加顶级元素(top)以防止出现此问题。通常也不可能只将XML序列化解析为XML,因此保存操作序列化为XML。

    这是一个概念验证,而不是任何事情。将HTML文档中创建的节点移动到XML文档可能会产生副作用,或者我没有预料到的另一种方式。我在Chrome和FF上运行了上面的代码。我手头没有IE可以在那里运行。

答案 1 :(得分:1)

这不适用于您最近的澄清,必须具有字符串副本。但是,对于那些可能具有更大灵活性的人来说,还是留下了它。

由于使用DOM似乎允许您在某种程度上保留无效结构,并且使用innerHTML涉及使用(如您所观察到的)副作用进行重新分析,我们必须考虑不使用innerHTML

您可以克隆原始内容,然后交换克隆:

var e = document.getElementById('asdf')
snippet.log("1: " + e.innerHTML);
var clone = e.cloneNode(true);
var insert = document.createElement('div');
var text = document.createTextNode('ladygaga');
insert.appendChild(text);
document.getElementById('outer').appendChild(insert);
snippet.log("2: " + e.innerHTML);
e.parentNode.replaceChild(clone, e);
e = clone;
snippet.log("3: " + e.innerHTML);

直播示例:

var e = document.getElementById('asdf')
snippet.log("1: " + e.innerHTML);
var clone = e.cloneNode(true);
var insert = document.createElement('div');
var text = document.createTextNode('ladygaga');
insert.appendChild(text);
document.getElementById('outer').appendChild(insert);
snippet.log("2: " + e.innerHTML);
e.parentNode.replaceChild(clone, e);
e = clone;
snippet.log("3: " + e.innerHTML);
<div id="asdf">
  <p id="outer">
    <div>ladygaga</div>
  </p>
</div>

<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

请注意,就像innerHTML解决方案一样,这将消除有问题元素的事件处理程序。您可以通过创建文档片段并将其子项克隆到最外层元素来保留最外层元素的处理程序,但这仍然会丢失子项上的处理程序。

此早期解决方案不适用于您,但未来可能适用于其他人:

我之前的解决方案是跟踪您更改的内容,然后逐个撤消更改。因此,在您的示例中,这意味着删除insert元素:

var e = document.getElementById('asdf')
console.log("1: " + e.innerHTML);
var insert = document.createElement('div');
var text = document.createTextNode('ladygaga');
insert.appendChild(text);
var outer = document.getElementById('outer');
outer.appendChild(insert);
console.log("2: " + e.innerHTML);
outer.removeChild(insert);
console.log("3: " + e.innerHTML);

var e = document.getElementById('asdf')
snippet.log("1: " + e.innerHTML);
var insert = document.createElement('div');
var text = document.createTextNode('ladygaga');
insert.appendChild(text);
var outer = document.getElementById('outer');
outer.appendChild(insert);
snippet.log("2: " + e.innerHTML);
outer.removeChild(insert);
snippet.log("3: " + e.innerHTML);
<div id="asdf">
  <p id="outer">
    <div>ladygaga</div>
  </p>
</div>

<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

答案 2 :(得分:0)

尝试使用BlobURL.createObjectURL导出html;在导出的script中添加html代码,从已呈现的<div></div><p></p>文档中删除html个元素

HTML

<body>
    <div id="asdf">
        <p id="outer"></p>
    </div>
    <script>
        var insert = document.createElement("div");
        var text = document.createTextNode("ladygaga");
        insert.appendChild(text);
        document.getElementById("outer").appendChild(insert);
        var elem = document.getElementById("asdf");
        var r = document.querySelectorAll("[id=outer] ~ *");
        // remove last `div` , `p` elements from `#asdf`
        for (var i = 0; i < r.length; ++i) {
            elem.removeChild(r[i])
        }
    </script>
</body>

JS

var e = document.getElementById("asdf");   
var html = e.outerHTML;  
console.log(document.body.outerHTML);   
var blob = new Blob([document.body.outerHTML], {
    type: "text/html"
});   
var objUrl = window.URL.createObjectURL(blob);
var popup = window.open(objUrl, "popup", "width=300, height=200");

jsfiddle http://jsfiddle.net/b2x7rnfm/11/

答案 3 :(得分:0)

请参阅此示例:http://jsfiddle.net/kevalbhatt18/1Lcgaprc/

MDN cloneNode

var e = document.getElementById('asdf')
console.log(e.innerHTML);
backupElem = e.cloneNode(true);
// Your tinkering with the original
e.parentNode.replaceChild(backupElem, e);
console.log(e.innerHTML);

答案 4 :(得分:0)

您不能指望将HTML解析为不合规的HTML。但是由于编译的不兼容HTML的结构是非常可预测的,你可以创建一个使HTML不兼容的功能,如下所示:

function ruinTheHtml() {

var allElements = document.body.getElementsByTagName( "*" ),
    next,
    afterNext;

Array.prototype.map.call( allElements,function( el,i ){

    if( el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE' ) {

        if(el.textContent === '') {

            next = el.nextSibling;

            afterNext = next.nextSibling;

            if( afterNext.textContent === '' ) {

                el.parentNode.removeChild( afterNext );
                el.appendChild( next );

            }

        }

    }
});

}

看小提琴: http://jsfiddle.net/pqah8e25/3/

答案 5 :(得分:0)

您必须克隆节点而不是复制html。在查看p时,解析规则会强制浏览器关闭div

如果你真的需要从该字符串获取html并且它是有效的xml,那么你可以使用以下代码($jQuery):

var html = "<p><div></div></p>";
var div = document.createElement("div");
var xml = $.parseXML(html);
div.appendChild(xml.documentElement);
div.innerHTML === html // true

答案 6 :(得分:0)

您可以使用outerHTML,它坚持原始结构:

(基于您的原始样本)

<div id="asdf"><p id="outer"></p></div>

<script type="text/javascript">
    var insert = document.createElement('div');
    var text = document.createTextNode('ladygaga');
    insert.appendChild(text);
    document.getElementById('outer').appendChild(insert);
    var e = document.getElementById('asdf')
    console.log(e.outerHTML);
    e.outerHTML = e.outerHTML;
    console.log(e.outerHTML);
</script>

演示:http://jsfiddle.net/b2x7rnfm/7