这是我遇到的一些困难。我有一个本地客户端脚本,该脚本需要允许用户获取远程网页并在结果页面中搜索表单。为了做到这一点(不使用正则表达式),我需要将文档解析为一个完全可遍历的DOM对象。
我想强调一些限制:
getElementsByTagName
必须可用。假设我在变量中具有完整的HTML文档字符串(包括DOCTYPE声明)html
,这是到目前为止我已经尝试过的操作:
var frag = document.createDocumentFragment(),
div = frag.appendChild(document.createElement("div"));
div.outerHTML = html;
//-> results in an empty fragment
div.insertAdjacentHTML("afterEnd", html);
//-> HTML is not added to the fragment
div.innerHTML = html;
//-> Error (expected, but I tried it anyway)
var doc = new ActiveXObject("htmlfile");
doc.write(html);
doc.close();
//-> JavaScript executes
我也尝试过从HTML 提取<head>
and <body>
节点,并将它们添加到<HTML>
片段中的元素中,仍然没有运气。
有人有什么想法吗?
小提琴 :http :
//jsfiddle.net/JFSKe/6/
DocumentFragment
没有实现DOM方法。使用document.createElement
与结合
innerHTML
去除<head>
和<body>
标签(即使当创建的元素是一个根元素,<html>
)。因此,应该在其他地方寻求解决方案。我创建了一个
跨浏览器的 字符串到DOM函数,该函数利用了不可见的嵌入式框架。
所有外部资源和脚本将被禁用。有关更多信息,请参见 代码说明 。
/*
@param String html The string with HTML which has be converted to a DOM object
@param func callback (optional) Callback(HTMLDocument doc, function destroy)
@returns undefined if callback exists, else: Object
HTMLDocument doc DOM fetched from Parameter:html
function destroy Removes HTMLDocument doc. */
function string2dom(html, callback){
/* Sanitise the string */
html = sanitiseHTML(html); /*Defined at the bottom of the answer*/
/* Create an IFrame */
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
var doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write(html);
doc.close();
function destroy(){
iframe.parentNode.removeChild(iframe);
}
if(callback) callback(doc, destroy);
else return {"doc": doc, "destroy": destroy};
}
/* @name sanitiseHTML
@param String html A string representing HTML code
@return String A new string, fully stripped of external resources.
All "external" attributes (href, src) are prefixed by data- */
function sanitiseHTML(html){
/* Adds a <!-\"'--> before every matched tag, so that unterminated quotes
aren't preventing the browser from splitting a tag. Test case:
'<input style="foo;b:url(0);><input onclick="<input type=button onclick="too() href=;>">' */
var prefix = "<!--\"'-->";
/*Attributes should not be prefixed by these characters. This list is not
complete, but will be sufficient for this function.
(see http://www.w3.org/TR/REC-xml/#NT-NameChar) */
var att = "[^-a-z0-9:._]";
var tag = "<[a-z]";
var any = "(?:[^<>\"']*(?:\"[^\"]*\"|'[^']*'))*?[^<>]*";
var etag = "(?:>|(?=<))";
/*
@name ae
@description Converts a given string in a sequence of the
original input and the HTML entity
@param String string String to convert
*/
var entityEnd = "(?:;|(?!\\d))";
var ents = {" ":"(?:\\s| ?|�*32"+entityEnd+"|�*20"+entityEnd+")",
"(":"(?:\\(|�*40"+entityEnd+"|�*28"+entityEnd+")",
")":"(?:\\)|�*41"+entityEnd+"|�*29"+entityEnd+")",
".":"(?:\\.|�*46"+entityEnd+"|�*2e"+entityEnd+")"};
/*Placeholder to avoid tricky filter-circumventing methods*/
var charMap = {};
var s = ents[" "]+"*"; /* Short-hand space */
/* Important: Must be pre- and postfixed by < and >. RE matches a whole tag! */
function ae(string){
var all_chars_lowercase = string.toLowerCase();
if(ents[string]) return ents[string];
var all_chars_uppercase = string.toUpperCase();
var RE_res = "";
for(var i=0; i<string.length; i++){
var char_lowercase = all_chars_lowercase.charAt(i);
if(charMap[char_lowercase]){
RE_res += charMap[char_lowercase];
continue;
}
var char_uppercase = all_chars_uppercase.charAt(i);
var RE_sub = [char_lowercase];
RE_sub.push("�*" + char_lowercase.charCodeAt(0) + entityEnd);
RE_sub.push("�*" + char_lowercase.charCodeAt(0).toString(16) + entityEnd);
if(char_lowercase != char_uppercase){
RE_sub.push("�*" + char_uppercase.charCodeAt(0) + entityEnd);
RE_sub.push("�*" + char_uppercase.charCodeAt(0).toString(16) + entityEnd);
}
RE_sub = "(?:" + RE_sub.join("|") + ")";
RE_res += (charMap[char_lowercase] = RE_sub);
}
return(ents[string] = RE_res);
}
/*
@name by
@description second argument for the replace function.
*/
function by(match, group1, group2){
/* Adds a data-prefix before every external pointer */
return group1 + "data-" + group2
}
/*
@name cr
@description Selects a HTML element and performs a
search-and-replace on attributes
@param String selector HTML substring to match
@param String attribute RegExp-escaped; HTML element attribute to match
@param String marker Optional RegExp-escaped; marks the prefix
@param String delimiter Optional RegExp escaped; non-quote delimiters
@param String end Optional RegExp-escaped; forces the match to
end before an occurence of <end> when
quotes are missing
*/
function cr(selector, attribute, marker, delimiter, end){
if(typeof selector == "string") selector = new RegExp(selector, "gi");
marker = typeof marker == "string" ? marker : "\\s*=";
delimiter = typeof delimiter == "string" ? delimiter : "";
end = typeof end == "string" ? end : "";
var is_end = end && "?";
var re1 = new RegExp("("+att+")("+attribute+marker+"(?:\\s*\"[^\""+delimiter+"]*\"|\\s*'[^'"+delimiter+"]*'|[^\\s"+delimiter+"]+"+is_end+")"+end+")", "gi");
html = html.replace(selector, function(match){
return prefix + match.replace(re1, by);
});
}
/*
@name cri
@description Selects an attribute of a HTML element, and
performs a search-and-replace on certain values
@param String selector HTML element to match
@param String attribute RegExp-escaped; HTML element attribute to match
@param String front RegExp-escaped; attribute value, prefix to match
@param String flags Optional RegExp flags, default "gi"
@param String delimiter Optional RegExp-escaped; non-quote delimiters
@param String end Optional RegExp-escaped; forces the match to
end before an occurence of <end> when
quotes are missing
*/
function cri(selector, attribute, front, flags, delimiter, end){
if(typeof selector == "string") selector = new RegExp(selector, "gi");
flags = typeof flags == "string" ? flags : "gi";
var re1 = new RegExp("("+att+attribute+"\\s*=)((?:\\s*\"[^\"]*\"|\\s*'[^']*'|[^\\s>]+))", "gi");
end = typeof end == "string" ? end + ")" : ")";
var at1 = new RegExp('(")('+front+'[^"]+")', flags);
var at2 = new RegExp("(')("+front+"[^']+')", flags);
var at3 = new RegExp("()("+front+'(?:"[^"]+"|\'[^\']+\'|(?:(?!'+delimiter+').)+)'+end, flags);
var handleAttr = function(match, g1, g2){
if(g2.charAt(0) == '"') return g1+g2.replace(at1, by);
if(g2.charAt(0) == "'") return g1+g2.replace(at2, by);
return g1+g2.replace(at3, by);
};
html = html.replace(selector, function(match){
return prefix + match.replace(re1, handleAttr);
});
}
/* <meta http-equiv=refresh content=" ; url= " > */
html = html.replace(new RegExp("<meta"+any+att+"http-equiv\\s*=\\s*(?:\""+ae("refresh")+"\""+any+etag+"|'"+ae("refresh")+"'"+any+etag+"|"+ae("refresh")+"(?:"+ae(" ")+any+etag+"|"+etag+"))", "gi"), "<!-- meta http-equiv=refresh stripped-->");
/* Stripping all scripts */
html = html.replace(new RegExp("<script"+any+">\\s*//\\s*<\\[CDATA\\[[\\S\\s]*?]]>\\s*</script[^>]*>", "gi"), "<!--CDATA script-->");
html = html.replace(/<script[\S\s]+?<\/script\s*>/gi, "<!--Non-CDATA script-->");
cr(tag+any+att+"on[-a-z0-9:_.]+="+any+etag, "on[-a-z0-9:_.]+"); /* Event listeners */
cr(tag+any+att+"href\\s*="+any+etag, "href"); /* Linked elements */
cr(tag+any+att+"src\\s*="+any+etag, "src"); /* Embedded elements */
cr("<object"+any+att+"data\\s*="+any+etag, "data"); /* <object data= > */
cr("<applet"+any+att+"codebase\\s*="+any+etag, "codebase"); /* <applet codebase= > */
/* <param name=movie value= >*/
cr("<param"+any+att+"name\\s*=\\s*(?:\""+ae("movie")+"\""+any+etag+"|'"+ae("movie")+"'"+any+etag+"|"+ae("movie")+"(?:"+ae(" ")+any+etag+"|"+etag+"))", "value");
/* <style> and < style= > url()*/
cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:<\/style|$)/gi, "url", "\\s*\\(\\s*", "", "\\s*\\)");
cri(tag+any+att+"style\\s*="+any+etag, "style", ae("url")+s+ae("(")+s, 0, s+ae(")"), ae(")"));
/* IE7- CSS expression() */
cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:<\/style|$)/gi, "expression", "\\s*\\(\\s*", "", "\\s*\\)");
cri(tag+any+att+"style\\s*="+any+etag, "style", ae("expression")+s+ae("(")+s, 0, s+ae(")"), ae(")"));
return html.replace(new RegExp("(?:"+prefix+")+", "g"), prefix);
}
该sanitiseHTML
函数基于我的replace_all_rel_by_abs
函数(请参阅此答案)。sanitiseHTML
不过,该功能已完全重写,以实现最大的效率和可靠性。
此外,添加了一组新的RegExps以删除所有脚本和事件处理程序(包括CSS
expression()
,IE7-)。为确保所有标签均按预期进行解析,已调整标签的前缀为<!--'"-->
。要正确解析嵌套的“事件处理程序”以及未终止的引号,该前缀是必需的<a id="><input onclick="<div onmousemove=evil()>">
。
这些正则表达式是使用内部函数动态创建cr
/ cri
( ç reate ř E放置[ 我
n第])。这些函数接受参数列表,并创建和执行高级RE替换。为了确保HTML实体没有违反一个RegExp(refresh
在<meta http- equiv=refresh>
可以用各种方式来写的),动态创建的正则表达式的一部分被构造函数ae
( 一个 纽约 ê ntity)。
实际的替换是按功能完成的by
(替换 为 )。在此实现中,在所有匹配的属性之前by
添加data-
。
<script>//<[CDATA[ .. //]]></script>
出现的条带化。此步骤是必需的,因为CDATA
节允许 </script>
代码内包含字符串。执行此替换后,可以安全地进行下一个替换:<script>...</script>
标签将被删除。<meta http-equiv=refresh .. >
标记将被删除所有 的事件侦听器和外部指针/属性(href
,src
,url()
)由前缀data-
,如先前所描述。
IFrame
创建一个对象。iframe不太可能泄漏内存(与htmlfile ActiveXObject相反)。IFrame变得不可见,并附加到文档中,以便可以访问DOM。document.write()
用于将HTML写入IFrame。document.open()
和document.close()
用于清空文档的先前内容,以便生成的文档是给定html
字符串的精确副本。
如果指定了回调函数,则将使用两个参数来调用该函数。第 一个 参数是对所生成document
对象的引用。该 第二 参数是一个函数被调用时它破坏所生成的DOM树。当您不再需要树时,应调用此函数。
如果未指定回调函数,则该函数将返回一个由两个属性(doc
和destroy
)组成的对象,这两个属性的行为与前面提到的参数相同。
designMode
属性设置为“开”将阻止框架执行脚本(Chrome不支持)。如果<script>
由于特定原因必须保留标签,则可以使用iframe.designMode = "On"
代替脚本剥离功能。我找不到可靠的来源htmlfile activeXObject
。根据此消息来源,htmlfile
它比IFrame速度慢,并且更容易受到内存泄漏的影响。
所有受影响的属性(href
,,src
…)都以前缀data-
。获得/改变这些属性中的一个例子示出了用于data-href
:
elem.getAttribute("data-href")
和elem.setAttribute("data-href", "...")
elem.dataset.href
和elem.dataset.href = "..."
。
外部资源已被禁用。结果,页面看起来可能完全不同: 没有外部样式 没有脚本样式 没有图像:元素的大小可能完全不同。
<link rel="stylesheet" href="main.css" />
<script>document.body.bgColor="red";</script>
<img src="128x128.png" />
__
sanitiseHTML(html)
将此书签粘贴到位置栏中。它将提供一个选项来插入文本区域,以显示经过清理的HTML字符串。
javascript:void(function(){var s=document.createElement("script");s.src="http://rob.lekensteyn.nl/html-sanitizer.js";document.body.appendChild(s)})();
代码示例-string2dom(html)
:
string2dom("<html><head><title>Test</title></head></html>", function(doc, destroy){
alert(doc.title); /* Alert: "Test" */
destroy();
});
var test = string2dom("<div id='secret'></div>");
alert(test.doc.getElementById("secret").tagName); /* Alert: "DIV" */
test.destroy();
sanitiseHTML(html)
基于我之前创建的replace_all_rel_by_abs(html)
函数。<applet>
)假设我有两份文件。 一个是有大约2-3页的主模板。第二个只有一段文字与各种风格(粗体,斜体,下划线,字体大小等)。 我想用第二个文档中的段落替换模板中的一个参数。 null
问题内容: 我正在处理可处理大量Excel 2007文件的应用程序,并且正在使用OpenPyXL进行此操作。OpenPyXL有两种不同的读取Excel文件的方法:一种是将整个文档立即加载到内存中的“常规”方法,另一种是使用迭代器逐行读取的方法。 问题是,当我使用迭代器方法时,我没有得到任何文档元数据,例如列宽和行/列数,而我 确实 需要此数据。我假设此数据存储在顶部附近的Excel文档中,因此不必
问题内容: 在MDN的文档中: 题: 可以在元素的 属性下以的值显示图像 或在伪元素上使用属性,例如 是否可以渲染文档,元素位于 ? 另请参阅CSS生成的内容模块级别3 该属性指示在元素或伪元素内部呈现的内容。它需要用逗号分隔的URI列表,然后是用空格分隔的令牌列表。如果提供了多个URI,则依次尝试每个URI,直到找到一个既可用又受支持的值。如果其他值失败,则最后一个值用作备用。 对于值的最后一个
我们可以在从 BLOB 存储下载文件时在微软办公文档中添加水印文本吗? 我的分析正在使用 AIP - Azure 信息保护进行。
问题内容: 我正在执行类似下面的短语查询。它返回给我按相关性排序的突出显示的片段。自然,我希望用户单击一个片段,然后将文档滚动到相应的位置。但是,我在Elasticsearch中看不到任何方法来找出片段在原始文档中的位置。有任何想法吗? 问题答案: 在此期间,我们找不到合适的解决方案,并遭到了以下黑客攻击(对我们而言非常有效):在索引之前,我们用“ [index]”注释文本中的每个单词,以便“ 一
问题内容: Elasticsearch具有嵌套文档(出色)。我想用它来存储消息(顶级文档)及其作者(嵌套文档)。 由于一个作者可以拥有许多条消息-可以将一个版本的Author引用为多条消息的子级吗? 这样,如果您在一个地方更新Author数据,它将在所有引用它们的地方更新。 注意:这与以下内容有关:如何在Elasticsearch中或在Lucene级别进行联接-此处的答案也可以解决该问题。 问题答