I think it is best to not assume that the text will be an immediate child of the passed-in selector. I also see it as a bit chancy to assume that the text you are searching for cannot appear inside a descendant HTML tag. All other answers given so far have issues in at least one of these areas.
Furthermore, it is even better to make the code reusable. The below easily reusable functions will do exactly what you need, won't be messed up by stray HTML tag attributes, and work!
function descendantContents($el, textToFind) { // this could be made a jQuery plugin
var result = $el
.find(':not(script)')
.contents()
.filter(function () {
return this.nodeType === 3 && $(this).text().indexOf(textToFind) !== -1;
});
return result;
}
function replaceText(scopeSelector, textToFind, replacementHtml) {
descendantContents($(scopeSelector), textToFind)
.each(function () {
var element = $(this);
var parts = element.text().split(textToFind);
element.before(document.createTextNode(parts[0]));
for (var i = 1, l = parts.length; i < l; i += 1) {
element.before(replacementHtml);
element.before(document.createTextNode(parts[i]));
}
element.remove();
});
};
These functions have been tested in Firefox 25.0.1, Chrome 30.0.1599.101 m, and 10.0.9200.16721 using jQuery 1.6.1 (I know, that's an old version, but that should make you feel better, not worse).
For anyone wishing to do better, try your code against this HTML:
<div>
<div>will this <span title="alt:Private">really</span> work :P</div>
</div>