You'd need a proper HTML parser to support nested tags, for example, blockquote tags could be nested.
Here is a solution if you can live with the limitation of not supporting corner cases. It uses nested replaces, the outer one to identify blockquote tags with its content, the inner one to take action on the blockquote content:
const lessonText = "<div><blockquote>""</blockquote><p>Heni "</p><blockquote>no quotation</blockquote><span>hi</span><blockquote><span style=\"font-family:Courier New,Courier,monospace;\">row.names(vol) <- c("</span></blockquote></div>"
let result = lessonText.replace(/(<blockquote>)(.*?)(<\/blockquote>)/g, function(m, g1, g2, g3) {
return g1 + g2.replace(/"/g, '"') + g3;
});
console.log('Result: ' + result);
Result: <div><blockquote>""</blockquote><p>Heni "</p><blockquote>no quotation</blockquote><span>hi</span><blockquote><span style="font-family:Courier New,Courier,monospace;">row.names(vol) <- c("</span></blockquote></div>
Explanation of outer replace regex:
(<blockquote>) -- capture group 1: opening blockquote tag
(.*?) -- capture group 2: non-greedy scan over content until:
(<\/blockquote>) -- capture group 3: closing blockquote tag
/g flag -- replace all patterns
Note:
- Limitation: This fails with nested tags
- If you expect attributes for the
blockquote you could use regex (<blockquote( [^>]*)?>) instead (which does not support corner cases like <blockquote tile="<gotcha>!">)
- If you expect newlines in the
blockquote content you can use regex ([\s\S]*?) instead.
Explanation of inner replace regex:
" -- capture literal " text (you could use string ""e" instead)
/g flag -- replace all patterns