Ostatnio zgłaszałem BUGa:
http://4programmers.net/coyote/bug.php?id=262

Chodziło o złe zamykanie tagów w JavaScript w Coyote (funkcja closeTags(str)). Zamiast czekać, aż ktoś (nie koniecznie Ktoś ;) ) ją poprawi, postanowiłem samemu zaproponować jakieś rozwiązanie. Jest obszerne, ale działa szybko, poprawnie i ma kilka udogodnień.

Publikuję je tu w celach testowych.. Jak będą jakieś błędy, proszę zgłaszać w tym temacie. (Kod HTML pliku nie jest poprawny, ale to był tylko szkielet do testów). Wszystkie tagi w kodzie w części HTML poprzedzone są spacją, by udało się to bez problemów wrzucić na forum - przed testem usuńcie te spacje.

< html>
< body>

< script type="text/javascript">

// ['TAG' (1), czy_zamykać (2), czy_usuwać_puste (3), czy_analizowac_tagi_wewnętrzne (4)]
//
// (1) TAGi nie wyszczególnione w tablicy, są zamieniane na encje
// (2) jeśli true, konieczne będzie < /TAG>, w przeciwnym wypadku < TAG> będzie bez zamknięcia
// (3) jeśli true, pusty zapis < TAG>< /TAG> lub < TAG=parametr>< /TAG> zostanie usunięty
// (4) jeśli true, wewnętrzne TAGi są analizowane, jeśli nie - zamieniane na encje

var tags = [
		['b', true, true, true],
		['hr', false, true, true],
		['a', true, false, true],
		['code', true, true, false]
	]; 

function ZamknijTagi(tekst)
{
	var tagstack=new Array();
	//Otwarte TAGi wrzuca na stos, zamykane pobiera ze stosu
	//PARAMETRY: całość, /, tag, właściwość
	var tekst=tekst.replace(/<(\/)?([\w]+)([\s=][^>]*)?>/g, function(a, b, c, d)
	{
		if (typeof(b) == "undefined")
			b = '';
		if (typeof(c) == "undefined")
			c = '';
		if (typeof(d) == "undefined")
			d = '';
		if (b!='/')
		{
			var analizuj = true;
			if (tagstack.length>0)
			{
				for (i=0; i<tags.length; i++)
				{
					if ((tags[i][0] == tagstack[tagstack.length-1]) && (!tags[i][3]))
						analizuj = false;
				}
			}
			if (analizuj)
			{
				for (i=0; i<tags.length; i++)
				{
					if (tags[i][0] == c)
					{
						if (tags[i][1])
							tagstack.push(c);
						return '<'+c+d+'>';
					}
				}
			}
			//nieznane TAGi: zwraca encje
			return '&lt;'+c+d+'&gt;';
		}
		else
		{
                        var analizuj = true;
			var obecny = false;
			if (tagstack.length>0)
			{
				for (i=0; i<tags.length; i++)
				{
					if ((tags[i][0] == tagstack[tagstack.length-1]) && (!tags[i][3]))
					{
						analizuj = false;
						break;
					}
				}
				for (i=0; i<tagstack.length; i++)
				{
					if (tagstack[i] == c)
					{
						obecny = true;
						break;
					}
				}
			}
			if (((analizuj) || (tagstack[tagstack.length-1] == c)) && obecny)
			{
				var s='';
				while (tagstack.length>0)
				{
					var tag = tagstack.pop();
					s+='</'+tag+'>';
					if (tag == c)
						break;
				}
				return s;
			}
			return '&lt;/'+c+d+'&gt;';
		}
	}
	);
	//Uzupełnia zamknięcie TAGów na końcu tekstu
	while (tagstack.length>0)
	{
		tekst+='</'+tagstack.pop()+'>';
	}
	//Usuwa puste TAGi, które na to pozwalają
	var l=0;
	do {
		l=tekst.length;
		//PARAMETRY: całość, TAG1, właściwość, TAG2
		tekst=tekst.replace(/<([\w]+)([\s=][^>]*)?><\/([\w]*)>/g, function(a, b, c, d)
		{
			if (typeof(b) == "undefined")
				b = '';
			if (typeof(c) == "undefined")
				c = '';
			if (typeof(d) == "undefined")
				d = '';
			if (b == d)
			{
				for (i=0; i<tags.length; i++)
				{
					if (tags[i][0] == b)
					{
						if (!tags[i][2])
							return '<'+b+c+'></'+d+'>';
						break;
					}
				}
				return '';
			}
			else
				return '<'+b+c+'></'+d+'>';
		}
		);
	} while (l>tekst.length);

	return tekst;
}	

//TEST
alert(ZamknijTagi("< b=par1>A< b>< /b>< /b>< b>< /n1>< code>< n2>A< /n2>< n3>< /code>< hr>< a>< /a>< a>A"));
< /script>

< /body>
< /html>

Dodałem zabezpieczenie przed wypisywaniem undefined w Internet Explorer.

//Nie jest to w tagach plain, by zostawić kolorowanie (o wiele bardziej czytelne), ani w code, to znacznik ten jest użyty w przykładzie.

//Dodane: zmieniłem kod, by nie głupiał przy zamykaniu nieotwartych tagów, wtedy zamykając wszystkie otwarte.