我想用JavaScript或jQuery 将#hashtag
文本替换为<a href="http://example.com/foo=hashtag"> #hasgtag</a>
我试过了:
<!DOCTYPE html>
<html>
<body>
<button onclick="myFunction()">Try it</button>
<p id="demo">Please visit #Microsoft! #facebook <a href="#link"> Somelink</a>
</p>
<script>
function myFunction() {
var str = document.getElementById("demo").innerHTML;
var txt = str.replace(/#w+.?w+/g,"<a href="http://example.com?hashtag=selectedteg">#Selected</a> ");
document.getElementById("demo").innerHTML = txt;
}
</script>
</body>
</html>
但是这个结果回来了。。。
<p id="demo">Please visit <a href="http://example.com?hashtag=selectedteg">#Selected</a> ! <a href="http://example.com?hashtag=selectedteg">#Selected</a> <a href="<a href=" http:="" example.com?hashtag="selectedteg"">#Selected</a> "> Somelink
</p>
我希望结果像一样
<p id="demo">Please visit <a href="http://example.com?hashtag=Microsoft">#Microsoft</a> ! <a href="http://example.com?hashtag=facebook">#facebook</a> <a href="#link">Somelink</a>
</p>
哇!这是一个令人惊讶的难题,尽管乍一看应该很简单。
问题是,严格来说,您的需求要求只处理文本节点,以将标签转换为链接。现有的HTML不应该被触摸。
一种天真的方法(见其他答案)会试图设计一个复杂的正则表达式来躲避HTML。尽管这似乎适用于某些情况,甚至几乎所有的实际情况,但它绝对不是万无一失的。正则表达式的功能根本不足以完全解析HTML;这是一门过于复杂的语言。请参阅RegEx中出色且相当著名的Stack Overflow答案,它匹配除XHTML自包含标记之外的开放标记。它不可能完美地完成,而且应该永远不要。
相反,正确的方法是使用递归JavaScript函数遍历HTML树,并将所有目标文本节点替换为其自身的处理版本,重要的是,这可能涉及在文本节点内引入(非文本)HTML标记。
jQuery可以用最小的复杂性来完成这项任务,尽管任务本身需要一定的复杂性,老实说,这是无法避免的。正如我所说,这是一个令人惊讶的难题。
HTML
<button onclick="tryItClick()">Try it</button>
<p id="demo">Please visit #Microsoft! #facebook <a href="#link">Somelink</a>
</p>
JavaScript
if (!window.Node) {
window.Node = {
ELEMENT_NODE : 1,
ATTRIBUTE_NODE : 2,
TEXT_NODE : 3,
CDATA_SECTION_NODE : 4,
ENTITY_REFERENCE_NODE : 5,
ENTITY_NODE : 6,
PROCESSING_INSTRUCTION_NODE : 7,
COMMENT_NODE : 8,
DOCUMENT_NODE : 9,
DOCUMENT_TYPE_NODE : 10,
DOCUMENT_FRAGMENT_NODE : 11,
NOTATION_NODE : 12
};
} // end if
window.linkify = function($textNode) {
$textNode.replaceWith($textNode.text().replace(/#(w+.?w+)/g,'<a href="http://example.com?hashtag=$1">#$1</a>'));
}; // end linkify()
window.processByNodeType = function($cur, nodeTypes, callback, payload ) {
if (!nodeTypes.length)
nodeTypes = [nodeTypes];
for (var i = 0; i < $cur.length; ++i) {
if ($.inArray($cur.get(i).nodeType, nodeTypes ) >= 0)
callback($cur.eq(i), $cur, i, payload );
processByNodeType($cur.eq(i).contents(), nodeTypes, callback, payload );
} // end for
} // end processByNodeType()
window.tryItClick = function(ev) {
var $top = $('#demo');
processByNodeType($top, Node.TEXT_NODE, linkify );
}; // end tryItClick()
http://jsfiddle.net/3u6jt988/
在可能的情况下编写通用代码总是很好的,以最大限度地提高可重用性,而且通常是简单性(尽管过多的通用性可能会导致过度的复杂性;这是有代价的)。我编写processByNodeType()
是一个非常通用的函数,它使用jQuery遍历HTML节点树的子树,从给定的顶部节点开始,向下遍历。该函数的目的是做一件事,也只是做一件事情:为遍历过程中遇到的所有节点调用给定的callback()
函数,这些节点的nodeType
等于nodeTypes
中给定的白名单值之一。这就是为什么我在代码的顶部包含了节点类型常量的枚举;看见http://code.stephenmorley.org/javascript/dom-nodetype-constants/.
此函数的功能非常强大,可以在响应单击事件时调用一次,将#demo
元素作为顶部节点传递给它,仅将Node.TEXT_NODE
节点列入白名单,并提供linkify()
作为回调。
当调用linkify()
时,它只接受它的第一个参数,即节点本身,并进行您设计的完全替换(尽管必须添加捕获组反向引用才能用标签正确替换文本)。最后一块拼图是用任何需要的新节点结构来替换文本节点,以实现替换,如果确实有一个标签要替换,这将涉及在旧的纯文本节点上引入新的HTML结构。幸运的是,jQuery的惊人之处是无限的,它让这件事变得非常容易,只需一句漂亮的话就可以完成:
$textNode.replaceWith($textNode.text().replace(/#(w+.?w+)/g,'<a href="http://example.com?hashtag=$1">#$1</a>'));
正如您所看到的,对text()
的一次调用可以获得纯文本节点的文本内容,然后调用字符串对象上的replace()
函数,用HTML替换任何标签,然后jQuery的replaceWith()
方法允许我们用生成的HTML替换整个文本节点,或者在不执行替换的情况下保留原始纯文本。
参考文献
- http://blog.alexanderdickson.com/javascript-replacing-text
- http://api.jquery.com/children/
- http://code.stephenmorley.org/javascript/dom-nodetype-constants/
- http://api.jquery.com/replacewith/
- RegEx匹配除XHTML自包含标记之外的开放标记
您必须捕获带括号的文本,但也必须只捕获文本,而不是html标记中的内容。请参阅函数中的注释。
function hashtagReplace() {
var text = document.getElementById("demo").innerHTML;
//you have first to capture the text, to avoid the capture of #link in your example
//The text is somewhare between the start of the input, or ">" and the end of the input and "<"
var result = text.replace( /(^.|>)([^<]*)(<|.$)/g ,function(match, start, capture, end ){
//then you capture the hashtag text, and replace all the hashtag (#+hashtag_word) by the link.
//you set the text captured by the parentethis with $1
var hashtagsReplaced= (start+capture+end).replace(/#(w+)/g,"<a href="http://example.com?hashtag=$1">#$1</a>")
//you return all the html
return hashtagsReplaced;
});
//finally you replace the html in the document
document.getElementById("demo").innerHTML = result;
}
<!DOCTYPE html>
<html>
<body>
<button onclick="hashtagReplace()">Try it</button>
<p id="demo">#Microsoft Please visit #Microsoft ! #facebook <a href="#link"> Somelink</a>
</p>
</body>
</html>
您需要捕获组,然后在替换中使用它。类似于:
var txt = str.replace(/#(w+.?w+)/g,"<a href="http://example.com?hashtag=$1">#$1</a> ");
在要捕获的零件周围放上括号使其成为一个捕获组,然后捕获的组将插入替换字符串中的$1
标记处。
当然,更大的问题是您的regex与您现有的链接相匹配,并试图在其中进行替换,这完全会把事情搞砸。这就是为什么使用正则表达式解析HTML不是一个好主意的原因。您可以使用正则表达式来排除现有链接,但这很快就会让人头疼。请改用DOM操作。
您可以将正则表达式更改为:
/s(?!href=")#(w+.?w+)/g
它利用了现有链接中的#link
没有空格的事实。所以你会得到这样的东西:
function myFunction() {
var str = document.getElementById("demo").innerHTML;
var txt = str.replace(/s(?!href=")#(S+)/g, "<a href="http://example.com?hashtag=$1"> #$1</a> ");
document.getElementById("demo").innerHTML = txt;
}
<button onclick="myFunction()">Try it</button>
<p id="demo">Please visit #Microsoft! #facebook
<a href="#link"> Somelink</a>
</p>