findAndReplaceDOMText
findAndReplaceDOMText copied to clipboard
How to use capture groups in combination with find/wrap attributes?
Hi,
I need to emulate a regular expression with a negative lookahead, which is, AFAIK, not supported in JS. This can be achieved with capture groups, but, when using the wrap attribute, I have no access to them.
Example: in the expression /(This)(\s+)(document)/ I need to wrap the document part with an element, but only if it's preceded by This.
I can't find a way to achieve this with the current API, any suggestions?
I usually recommend passing a replace function if you want finer control over what's replaced. But in this case, a quick hack is much simpler / less painful:
findAndReplaceDOMText(document.body, {
find: /(This)(\s+)(document)/,
wrap: 'em'
});
findAndReplaceDOMText(document.body, {
find: /(This)(\s+)(document)/,
replace: '$3'
});
We're first wrapping the entire match and then replacing the innards with the latter capture group.
Correct me if I'm wrong, but as I understand, this would wrap the entire match and then replace the text with the capture group, which is not my goal -- I don't need to replace anything, just to wrap the specific capture group (leaving other groups intact)
Ah whoops. Is document a fixed term? Because if so you could just match the entire regex and replace the last capture group with a token, and then replace that token with document:
findAndReplaceDOMText(document.body, {
find: /(This)(\s+)(document)/g,
replace: '$1$2__UNIQ_TOKEN__'
});
findAndReplaceDOMText(document.body, {
find: /__UNIQ_TOKEN__/g,
wrap: 'em',
replace: 'document'
});
This isn't ideal, but it's quite tricky to do otherwise. As mentioned you can pass a replace function and provide the replacement nodes yourself, but that gets painful quite quickly, especially when having to consider matches across node boundaries..
No, unfortunately it's not a fixed term; I'm generating the whole expression dynamically based on the user text selection, and working across node boundaries is the main reason I've decided to go with findAndReplaceDOMText :) To make things even more complicated: double-running findAndReplaceDOMText may be hard in my case, because I'm also using filterElements and check against to selection.containsNode, and wrapping/replacing will influence the selection.
I gave it a try -- actually the solution/hack you proposed would work in my case, even with document not being fixed, however, using replace as the first pass breaks the structure/formatting (because it strips HTML, and the expression may also span over multiple nodes)
Maybe on the first pass, instead of replacing it wholesale with a unique token, you can insert unique delimiters and then replace them later:
findAndReplaceDOMText(document.body, {
find: /(This)(\s+)(document)/g,
replace: '$1$2<<<$3>>>'
});
findAndReplaceDOMText(document.body, {
find: /<<<(.+?)>>>/g,
replace: '$1',
wrap: 'em'
})
Seems like an ugly approach but TBH I can't think of one less painful.
Thanks, although hacky, this seems to help! I've noticed one more thing when testing it and created a separate issue: https://github.com/padolsey/findAndReplaceDOMText/issues/70
Gonna add this to the 1.0 milestone as I'd like this kind of thing to be simpler in the future and less reliant on hacky code.