Chyba IE lastIndex se shodami regulárního výrazu s nulovou délkou

Závěrem tohoto blogového příspěvku je, že Internet Explorer nesprávně zvyšuje hodnotu 09 regulárního objektu. vlastnost po úspěšné shodě s nulovou délkou. Nicméně pro každého, kdo si není jistý, o čem mluvím, nebo se zajímá o to, jak problém obejít, popíšu problém s příklady opakování každé shody v řetězci pomocí 11 metoda. Právě tam jsem se s chybou setkal nejčastěji a myslím, že to pomůže vysvětlit, proč problém existuje.

Za prvé, pokud ještě nevíte, jak používat 28 iterovat přes řetězec, přicházíte o některé velmi výkonné funkce. Zde je základní konstrukce:

var	regex = /.../g,
	subject = "test",
	match = regex.exec(subject);

while (match != null) {
	// matched text: match[0]
	// match start: match.index
	// match end: regex.lastIndex
	// capturing group n: match[n]

	...

	match = regex.exec(subject);
}

Když 38 metoda je volána pro regex, který používá 45 (globální) modifikátor, vyhledává od bodu v řetězci předmětu určeného v regulárním výrazu 53 vlastnost (která je zpočátku nula, takže hledá od začátku řetězce). Pokud 68 metoda najde shodu, aktualizuje 72 regulárního výrazu vlastnost na znakový index na konci shody a vrátí pole obsahující odpovídající text a všechny zachycené podvýrazy. Pokud od bodu v řetězci, kde hledání začalo, žádná shoda, 85 je resetováno na nulu a 97 je vráceno.

Výše uvedený kód můžete zpřesnit přesunutím 104 volání metody do 117 stav smyčky, například takto:

var	regex = /.../g,
	subject = "test",
	match;

while (match = regex.exec(subject)) {
	...
}

Tato čistší verze funguje v podstatě stejně jako předchozí. Jakmile 124 nemůže najít žádné další shody, a proto vrátí 135 , smyčka končí. U obou verzí tohoto kódu však existuje několik problémů s různými prohlížeči. Jedním z nich je, že pokud regulární výraz obsahuje zachycující skupiny, které se neúčastní shody, některé hodnoty ve vráceném poli mohou být buď 140 nebo prázdný řetězec. O tomto problému jsem již dříve podrobně diskutoval v příspěvku o tom, co jsem nazval nezúčastněné zachytávající skupiny.

Další problém (téma tohoto post) nastane, když váš regulární výraz odpovídá prázdnému řetězci. Existuje mnoho důvodů, proč byste to mohli regulárnímu výrazu povolit, ale pokud vás žádný nenapadá, zvažte případy, kdy přijímáte regulární výrazy z vnějšího zdroje. Zde je jednoduchý příklad takového regulárního výrazu:

var	regex = /^/gm,
	subject = "A\nB\nC",
	match,
	endPositions = [];

while (match = regex.exec(subject)) {
	endPositions.push(regex.lastIndex);
}

Můžete očekávat 158 pole, které má být nastaveno na 167 , protože to jsou pozice znaků na začátku řetězce a hned za každým znakem nového řádku. Díky 176 modifikátor, to jsou pozice, kde se regulární výraz bude shodovat; a protože regulární výraz odpovídá prázdným řetězcům, 180 by měl být stejný jako 199 . Internet Explorer (testováno s v5.5–7) však nastavuje 203 na 218 . Ostatní prohlížeče přejdou do nekonečné smyčky, dokud nezkratujete kód.

Tak co se tu děje? Pamatujte si to pokaždé, když 222 běží, pokusí se najít shodu v řetězci předmětu počínaje pozicí určenou 234 vlastnost regulárního výrazu. Protože náš regulární výraz odpovídá řetězci nulové délky, 243 zůstává přesně tam, kde jsme hledání začali. Při každém průchodu smyčkou se tedy náš regulární výraz bude shodovat na stejné pozici – na začátku řetězce. Internet Explorer se snaží být nápomocný a této situaci se vyhnout automatickým zvýšením 255 když se shoduje řetězec nulové délky. To se může zdát jako dobrý nápad (ve skutečnosti jsem viděl lidi neústupně argumentovat, že jde o chybu, kterou Firefox nedělá stejně), ale znamená to, že v Internet Exploreru je 266 nelze spoléhat na přesné určení konečné pozice zápasu.

Tuto situaci můžeme napravit v různých prohlížečích pomocí následujícího kódu:

var	regex = /^/gm,
	subject = "A\nB\nC",
	match,
	endPositions = [];

while (match = regex.exec(subject)) {
	var zeroLengthMatch = !match[0].length;
	// Fix IE's incorrect lastIndex
	if (zeroLengthMatch && regex.lastIndex > match.index)
		regex.lastIndex--;

	endPositions.push(regex.lastIndex);

	// Avoid an infinite loop with zero-length matches
	if (zeroLengthMatch)
		regex.lastIndex++;
}

Příklad výše uvedeného kódu můžete vidět v metodě rozdělení mezi prohlížeči, kterou jsem před časem zveřejnil. Mějte na paměti, že zde není potřeba žádný další kód, pokud váš regulární výraz nemůže odpovídat prázdnému řetězci.

Dalším způsobem, jak tento problém vyřešit, je použít 279 iterovat přes předmětový řetězec. 283 metoda se po shodách s nulovou délkou automaticky posune vpřed, čímž se tomuto problému zcela vyhne. Bohužel ve třech největších prohlížečích (IE, Firefox, Safari) 293 nezdá se, že by se zabýval 305 vlastnost kromě resetování na nulu. Opera to udělá správně (podle mého čtení specifikace) a aktualizuje 317 při cestě. Vzhledem k současné situaci se nemůžete na 326 spolehnout ve vašem kódu při iteraci přes řetězec pomocí 331 , ale přesto můžete snadno odvodit hodnotu pro konec každého zápasu. Zde je příklad:

var	regex = /^/gm,
	subject = "A\nB\nC",
	endPositions = [];

subject.replace(regex, function (match) {
	// Not using a named argument for the index since capturing
	// groups can change its position in the list of arguments
	var	index = arguments[arguments.length - 2],
		lastIndex = index + match.length;

	endPositions.push(lastIndex);
});

To je možná méně přehledné než dříve (protože ve skutečnosti nic nenahrazujeme), ale tady to máte… dva způsoby, jak obejít málo známý problém, který by jinak mohl způsobit záludné, latentní chyby ve vašem kódu.