findAndReplaceDOMText icon indicating copy to clipboard operation
findAndReplaceDOMText copied to clipboard

How to use capture groups in combination with find/wrap attributes?

Open ardcore opened this issue 8 years ago • 8 comments

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?

ardcore avatar Dec 04 '17 11:12 ardcore

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.

padolsey avatar Dec 04 '17 15:12 padolsey

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)

ardcore avatar Dec 04 '17 15:12 ardcore

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..

padolsey avatar Dec 04 '17 15:12 padolsey

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.

ardcore avatar Dec 04 '17 16:12 ardcore

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)

ardcore avatar Dec 04 '17 17:12 ardcore

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.

padolsey avatar Dec 04 '17 18:12 padolsey

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

ardcore avatar Dec 05 '17 07:12 ardcore

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.

padolsey avatar Dec 06 '17 09:12 padolsey