You can do this using XML functions:
WITH data ( xmlstring ) AS (
SELECT '<html><body><HIDE><HIDE>Anti-</HIDE></HIDE>Skull</body></html>' FROM DUAL
)
SELECT UPDATEXML(
xml,
'//HIDE/HIDE/..',
XMLQUERY( '//HIDE/HIDE' PASSING xml RETURNING CONTENT )
).getClobVal() AS updatedXML
FROM ( SELECT XMLTYPE( xmlstring ) AS xml FROM data );
Outputs:
<html><body><HIDE>Anti-</HIDE>Skull</body></html>
You should also be able to use the more generic XPath '//*[name()=../name()]/..'
; however, while XMLQUERY
parses it successfully, UPDATEXML
does not.
Update:
WITH data ( xmlstring ) AS (
SELECT '<HIDE><HIDE>Anti-</HIDE></HIDE>Skull' FROM DUAL
)
SELECT SUBSTR(
updatedXML,
7,
LENGTH( updatedXML ) - 13
)
FROM (
SELECT UPDATEXML(
xml,
'//HIDE/HIDE/..',
XMLQUERY( '//HIDE/HIDE' PASSING xml RETURNING CONTENT )
).getClobVal() AS updatedXML
FROM (
SELECT XMLTYPE( '<root>' || xmlstring || '</root>' ) AS xml
FROM data
)
);
Update 2:
Before using a regular expression, you should understand the limitations - a regular expression cannot parse arbitrary HTML but will only work on a limited subset. In this case it will not match attributes or match the correct level of tags (and you should use an XML parser for that):
WITH data ( html ) AS (
SELECT '<HIDE><HIDE>Anti-</HIDE></HIDE>Skull' FROM DUAL UNION ALL
SELECT '<HIDE><HIDE>first</HIDE></HIDE>between<HIDE><HIDE>second</HIDE></HIDE>' FROM DUAL UNION ALL
SELECT '<HIDE><HIDE>before<HIDE><HIDE>inner</HIDE></HIDE>after</HIDE></HIDE>outer' FROM DUAL
)
SELECT REGEXP_REPLACE(
html,
'<([A-Za-z][A-Za-z0-9-]*)><\1>(.*)</\1></\1>',
'<\1>\2</\1>'
)
FROM data;
Output:
<HIDE>Anti-</HIDE>Skull
<HIDE>first</HIDE></HIDE>between<HIDE><HIDE>second</HIDE>
<HIDE>before<HIDE><HIDE>inner</HIDE></HIDE>after</HIDE>outer
The regular expression will match the simple case but you will find it difficult (or impossible) to correctly handle both the second and third cases.