Nach ein wenig überlegen, denke ich das geht ganz ohne Schleifen und temporäre Tabellen.
Der Fall von "einfachen" Größer/Kleiner Zeichen ist einfach, den kann man mit regexp_replace erledigen. Das escapen aller Tags die nicht richtig geschlossen sind, ist etwas trickreicher, aber ich denke mit der folgenden Funktion klappt es - zumindest Dein Beispiel wird richtig ersetzt:
[code]
CREATE OR REPLACE FUNCTION escape_html(p_input text)
RETURNS text AS
$$
DECLARE
l_clean_value text;
l_to_replace text[];
l_tag text;
BEGIN
-- ersetze alle "einfach" < und > zuerst
l_clean_value := regexp_replace(p_input, '(\s+)<(\s+)', '\1<\2', 'g');
l_clean_value := regexp_replace(l_clean_value, '(\s+)>(\s+)', '\1>\2', 'g');
-- finde alle tags die nicht geschlossen werden
select array_agg(tag)
into l_to_replace
from (
select translate(x2.open_tag, '<>', '') as tag
from regexp_matches(l_clean_value, '<\w+>', 'g') as x1(open_tags),
unnest(x1.open_tags) as x2(open_tag)
except
select translate(x3.close_tag, '</>', '') as close_tag
from regexp_matches(l_clean_value, '</\w+>', 'g') as x2(close_tags),
unnest(x2.close_tags) as x3(close_tag)
) x;
-- in dem array l_to_replace stehen jetzt alle tags die kein schliessendes Tag haben
-- diese können direkt ersetzt werden
foreach l_tag in array l_to_replace loop
l_clean_value := replace(l_clean_value, concat('<', l_tag, '>'), concat('<', l_tag, '>'));
end loop;
return l_clean_value;
END;
$$
LANGUAGE plpgsql;
[/code]
Wie funktioniert das Finden im Detail?
Die Funktion regexp_matches('<asdf>A < B<br>C</asdf>', '<\w+>', 'g') liefert alle "echten" Tags (also Zeichenketten die zwischen < und > stehen). Das Ergebnis der Funkion ist ein Resultat bei dem in jeder Zeile ein Array mit gefundenen Tags stehen, wenn man dann jedes Array wieder "auflöst" bekommt man eine Liste aller Tags:
Die folgende Abfrage[code]
select x2.*
from regexp_matches('<asdf>A < B<br>C</asdf>', '<\w+>', 'g') as x1(open_tags),
unnest(x1.open_tags) as x2(open_tag);[/code]liefert:[code]
open_tag
--------
<asdf>
<br> [/code]
Das translate() schmeisst dann einfach die spitzen Klammer weg.
Die zweite Abfrage liefert dann eine Liste alle schliessenden Tags. Diese Abfrage [code]
select x3.*
from regexp_matches('<asdf>A < B<br>C</asdf>', '</\w+>', 'g') as x2(close_tags),
unnest(x2.close_tags) as x3(close_tag) [/code]liefert also[code]
close_tag
---------
</asdf>
[/code]
Das translate entfernt dann wieder die spitzen Klammern und den Schrägstrich, so dass beide Teilabfragen nur den "nackten" Text der Tags liefern. EXCEPT lässt dann die übrig, die in der ersten Liste sind, aber nicht in der zweiten - damit haben wir alle Tags die nicht sauber geschlossen werden und deren spitze Klammern ersetzt werden müssen. Bei dem gegebenen Beispiel bleibt als Ergebnis also br übrig.
Die Abfrage speichert diese Liste in ein Array (select ... into l_to_replace ...). Über dieses Array wird iteriert und entsprechend im String ersetzt.