As Viorel says, the best solution string_agg; then you don't have to fight these restrictions. But I have a recollection that you need to support anitque versions of SQL Server. string_agg reqiures SQL 2017.
I'm leaning towards that it is better to get the garbage out before you do the FOR XML thing. Here is code to remove all control characters from 0 to 31:
drop table temp.#test
create table #test (recid int identity, col1 varchar(126))
insert into #test (col1)
values ('aaa'),
('ccc' + cast(0x1A as varchar(30)) + 'bbb'),
('"ddd <e' )
-- Code to remove control characters
declare @pat char(7) = concat('%[', char(0), '-', char(31) + ']%')
while 1 = 1
begin
update #test
set col1 = substring(col1, 1, patindex(@pat collate French_BIN2, col1) - 1) +
substring(col1, patindex(@pat collate French_BIN2, col1) + 1, len(col1))
where col1 LIKE @pat collate French_BIN2
if @@rowcount = 0
break
end
/****** Use STUFF, FOR XML to concatenate rows *******/
drop table temp.#list_complete
declare @SQLStuff nvarchar(max) = ''
SELECT @SQLStuff =
STUFF((SELECT '; ' + ltrim(rtrim(col1))
FROM #test
FOR XML PATH (''), TYPE).value('(/text())[1]', 'varchar(max)'), 1, 2, '')
(select cast( @SQLStuff as varchar(128)) as codes)a
SELECT * FROM #list_complete
Don't worry about the French. We want a binary collation, and French is shorter than Latin1_General.
The exact range of characters may need some fine-tuning. There may be more characters to add. On the other hand, you may want to permit for tag, carriage return and line feed.