REGEXP_REPLACE Text Cleanup: Replacing Tags Is Not Parsing HTML

REGEXP_REPLACE can remove controlled tag-like text, but that operation is not HTML parsing. Test the exact input contract before using a pattern. Removing matched brackets does not establish safe rendering or correct extracted content.

A cut rose branch with thorns, pruning shears and a clay pot on a greenhouse workbench.

Define the limited replacement

The pattern <[^>]*> matches an opening angle bracket, zero or more nonclosing characters and a closing angle bracket. Replacing that match with an empty string removes the matched span. It has no knowledge of HTML nesting, attributes or element meaning.

First, set up the ten sample inputs used in this post, plus a table for the results.

DROP TABLE IF EXISTS #MarkupCases;
DROP TABLE IF EXISTS #MarkupResult;

CREATE TABLE #MarkupCases
(
    Id int PRIMARY KEY,
    CaseLabel nvarchar(60),
    InputText nvarchar(max) NULL
);

INSERT #MarkupCases VALUES
(1, N'Controlled tags', N'<b>Hello</b> world'),
(2, N'Adjacent words', N'<b>Hello</b><i>world</i>'),
(3, N'Quoted greater-than', N'<a title="x>y">Link</a>'),
(4, N'Angle-bracket prose', N'2 < 3 > 1'),
(5, N'Unclosed tag', N'<bHello'),
(6, N'Script body survives', N'<script>alert(1)</script>safe'),
(7, N'Entities retained', N'<b>&lt;x&gt; &amp; &#169;</b>'),
(8, N'Unicode', N'<b>café 東京</b>'),
(9, N'NULL', NULL),
(10, N'Empty', N'');

CREATE TABLE #MarkupResult
(
    Id int PRIMARY KEY,
    CaseLabel nvarchar(60),
    InputText nvarchar(max),
    OutputText nvarchar(max)
);

Then run the replacement on every sample.

INSERT #MarkupResult
    SELECT Id, CaseLabel, InputText, REGEXP_REPLACE(InputText, N'<[^>]*>', N'')
    FROM #MarkupCases;

SELECT Id, CaseLabel, InputText, OutputText,
       DATALENGTH(InputText) AS InputBytes,
       DATALENGTH(OutputText) AS OutputBytes
FROM #MarkupResult
ORDER BY Id;

These examples run on SQL Server 2025. They show complete Unicode inputs and outputs, including NULL and empty text, and one large-object input further down. Earlier SQL Server versions need a different supported approach.

Read the cases that break the shortcut

Adjacent <b>Hello</b><i>world</i> elements become Helloworld. The replacement supplies no word separator. A quoted greater-than sign inside an attribute ends this pattern’s match early. Angle-bracket prose can also lose text that was never a tag.

Ten markup-cleanup cases, selected entity decoding and MAX text lengths of 12020 input bytes and 12006 output bytes.

The result includes all ten complete inputs with their outputs, plus the selected one-pass entity demonstration. The MAX input measured 12020 bytes and the output measured 12006 bytes. These observations show the pattern limits on this build. View the native result at full size.

These are all ten cases on SQL Server 2025. Quoted text shows exact string boundaries. NULL is separate from an empty string. Byte counts describe the complete Unicode values.

CaseComplete inputComplete outputInput bytesOutput bytes
Controlled tags“<b>Hello</b> world”“Hello world”3622
Adjacent words“<b>Hello</b><i>world</i>”“Helloworld”4820
Quoted greater-than“<a title=\”x>y\”>Link</a>”“y\”>Link”4614
Angle-bracket prose“2 < 3 > 1”“2 1”188
Unclosed tag“<bHello”“<bHello”1414
Script body survives“<script>alert(1)</script>safe”“alert(1)safe”5824
Entities retained“<b>&lt;x&gt; &amp; &#169;</b>”“&lt;x&gt; &amp; &#169;”5844
Unicode“<b>café 東京</b>”“café 東京”2814
NULLNULLNULLNULLNULL
Empty“”“”00

Removing <script> and </script> leaves the script body. An unclosed opening span can remain unmatched. These outputs follow the pattern’s limited definition. They are reasons to reject a general parsing or sanitizing claim.

Where bracket removal goes wrong

Keep entity replacement separate

The cases above preserve entities after the tag-like replacement. A separate example replaces a small explicit set of entities. Ampersand replacement runs last, and only one pass is applied. That contract does not decode every named or numeric HTML entity.

For example, a double-encoded &amp;lt; becomes &lt; after that pass. It does not become an opening angle bracket. Numeric entities and nonbreaking-space entities remain unless explicitly handled. Test those exact outputs instead of calling the result browser-equivalent text.

Here is the one-pass selected-entity example.

DECLARE @Entity nvarchar(max) = N'&amp;lt;b&amp;gt; &lt;b&gt; &quot; &#169; &nbsp;';
DECLARE @Decoded nvarchar(max) =
    REPLACE(REPLACE(REPLACE(REPLACE(@Entity,
        N'&lt;', N'<'), N'&gt;', N'>'), N'&quot;', N'"'), N'&amp;', N'&');

SELECT N'Selected entities, ampersand last, one pass' AS Demonstration,
       @Entity AS InputText,
       @Decoded AS DecodedText;

It kept its complete input and output. It did not decode every HTML entity.

Selected-entity inputOne-pass output
“&amp;lt;b&amp;gt; &lt;b&gt; &quot; &#169; &nbsp;”“&lt;b&gt; <b> \” &#169; &nbsp;”

Qualify large-object and compatibility support

The regular-expression functions accepted a large-object input on the measured build. Declaring nvarchar(max) alone does not promise unlimited document size. This example shows one bounded input and claims nothing beyond it.

DECLARE @Large nvarchar(max) = REPLICATE(CONVERT(nvarchar(max), N'x'), 6000) + N'<b>end</b>';
DECLARE @LargeOut nvarchar(max) = REGEXP_REPLACE(@Large, N'<[^>]*>', N'');

SELECT N'MAX input below documented 2 MB boundary' AS Demonstration,
       DATALENGTH(@Large) AS InputBytes,
       DATALENGTH(@LargeOut) AS OutputBytes;

REGEXP_LIKE has a compatibility-level requirement of 170. The other scalar regular-expression functions have a different compatibility boundary. Do not infer one function’s requirement from another. These examples change no database compatibility setting.

Choose a parser when the contract needs one

For arbitrary HTML, use a supported application parser that understands the required content and safety rules. Specify word boundaries, entity handling and unwanted-element removal. The limited SQL pattern only answers its stated replacement question. It makes no universal speed claim against a character loop or parser.

The measured MAX input contained 12,020 bytes. Its exact output contained 12,006 bytes. The output kept the leading x characters and the final word end.

When you are done, clean up the temporary tables.

DROP TABLE IF EXISTS #MarkupResult;
DROP TABLE IF EXISTS #MarkupCases;

When the input is real HTML, hand it to a real parser.

A pattern that removes brackets is not an HTML parser, it is only text replacement.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Function, SQL Scripts, SQL Server, SQL String
Previous Post
SQL SERVER – Creating All New Database with Full Recovery Model
Next Post
Building a Test Server From a Script

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.