XRegExp:Rozšířený konstruktor regulárního výrazu JavaScriptu

Aktualizace: Tato verze XRegExp je zastaralá. Viz XRegExp.com pro nejnovější, nejlepší verzi.

Poměrně často používám regulární výrazy v JavaScriptu, a přestože je metoda exec() špatná a miluji možnost použít funkci pro generování náhrady v metodě replace(), postrádají regulární výrazy JavaScriptu některé velmi významné funkce dostupné v mnoha jiných jazycích. . Mezi mé největší nelibosti patří nedostatek podpory pro s a x příznaky, které by měly povolit režim „odpovídají všem“ (také znám jako jednořádkový) a režim „volné mezery a komentáře“. Tyto modifikátory jsou dostupné téměř v každé jiné moderní knihovně regulárních výrazů.

Abych to napravil, vytvořil jsem (velmi malý) skript, který rozšiřuje JavaScript RegExp konstruktor, umožňující výše uvedené příznaky. V podstatě vám dává konstruktor nazvaný XRegExp který funguje přesně jako RegExp konstruktor kromě toho, že také přijímá s a x jako příznaky, navíc k již podporovanému g (globální), i (nerozlišují se malá a velká písmena) a m (víceřádkový, tj. ^ a $ zápas na zalomení řádků). Jako bonus XRegExp také zlepšuje konzistenci syntaxe regulárního výrazu napříč prohlížeči.

Funguje to takto:

var regex = new XRegExp("te?", "gi");
var value = "Test".replace(regex, "z"); // value is now "zsz"

Vypadat povědomě? Pokud jste v JavaScriptu dříve používali regulární výrazy, mělo by to být – je to přesně jako použití RegExp . Zatím jsme však neudělali nic, co by nebylo možné provést pomocí nativního RegExp konstruktér. s příznak je docela samozřejmý (konkrétní podrobnosti lze nalézt ve FAQ níže), takže zde je příklad s x příznak:

// Turn email addresses into links
var email = new XRegExp(
    "\\b                                      " +
    "# Capture the address to $1            \n" +
    "(                                        " +
    "  \\w[-.\\w]*               # username \n" +
    "  @                                      " +
    "  [-.a-z0-9]+\\.(?:com|net) # hostname \n" +
    ")                                        " +
    "\\b                                      ", "gix");

value = value.replace(email, "<a href=\"mailto:$1\">$1</a>");

To je určitě jiné! Pár poznámek:

  • Při použití XRegExp , normální pravidla escape řetězce (před speciálními znaky s "\ ") jsou nezbytné, stejně jako u RegExp . Tedy tři výskyty \n jsou metasekvence v rámci samotného řetězcového literálu. JavaScript je převede na znaky nového řádku (které ukončují komentáře) před XRegExp vidí řetězec.
  • Regulační výraz e-mailu je příliš zjednodušený a je určen pouze pro demonstrativní účely.

To je docela šikovné, ale můžeme to ještě zjednodušit. Pokud spustíte následující řádek kódu:

XRE.overrideNative();

…Jako kouzlo, RegExp samotný konstruktor bude podporovat s a x vlajky od tohoto okamžiku. Kompromisem je, že pak již nebudete mít přístup k informacím o poslední shodě jako vlastnostem globálního RegExp objekt. Všechny tyto vlastnosti jsou však stejně oficiálně zastaralé a ke všem stejným informacím máte přístup kombinací vlastností v instancích regulárního výrazu a použití exec() metoda.

Zde je rychlý FAQ. U prvních dvou otázek jsem si vypůjčil části vysvětlení z O'Reillyho Mastering Regular Expressions, 3 rd Edice .

Co přesně znamená s vlajka udělat?
Tečka obvykle neodpovídá novému řádku. Režim, ve kterém se tečka shoduje s novým řádkem, však může být stejně užitečný jako režim, ve kterém tečka neodpovídá. s flag umožňuje výběr režimu na základě jednotlivých regulárních výrazů. Všimněte si, že tečky ve třídách znaků (např. [.a-z] ) jsou vždy ekvivalentní doslovným tečkám.

Pokud jde o to, co přesně je považováno za znak nového řádku (a proto neodpovídají tečkám mimo třídy znaků, pokud nepoužíváte s flag), podle Mozilla Developer Center obsahuje čtyři znaky odpovídající následujícímu regulárnímu výrazu:[\n\r\u2028\u2029]
Co přesně znamená x vlajka udělat?
Za prvé způsobí, že většina bílých znaků bude ignorována, takže můžete výraz "volně naformátovat" pro čitelnost. Za druhé, umožňuje komentáře s úvodním # .

Konkrétně to změní většinu bílých znaků na metaznak „ignorujte mě“ a # do metaznaku „ignorujte mě a vše ostatní až do dalšího řádku“. Nejsou brány jako metaznaky v rámci třídy znaků (což znamená, že třídy nejsou ve volném formátu, dokonce i s x ) a stejně jako u jiných metaznaků můžete uniknout mezerám a # že chcete být bráni doslova. Samozřejmě můžete vždy použít \s aby odpovídaly mezerám, jako v new XRegExp("<a \\s+ href=…>", "x") . Všimněte si, že popisování mezer a komentářů jako metaznaků ignorujte mě není zcela přesné; možná by bylo lepší považovat je za nicnedělání metaznaky. Tento rozdíl je důležitý u něčeho jako \12 3 , který s kódem x příznak se bere jako \12 následuje 3 , nikoli \123 . Nakonec nenásledujte ihned mezery nebo komentář s kvantifikátorem (např. * nebo ? ), nebo kvantifikujete metaznak nedělat nic.

Pokud jde o to, co přesně je mezera, podle Mozilla Developer Center je ekvivalentní všem znakům odpovídajícím následujícímu regulárnímu výrazu:
[\t\n\v\f\r \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]
Může s a x používat společně příznaky?
Ano. Můžete kombinovat všechny podporované příznaky (g , i , m , s , x ) v libovolném pořadí.
Jakou syntaxi regulárního výrazu podporuje XRegExp?
Cokoli váš prohlížeč nativně podporuje.
Zmínil jste něco o zlepšení konzistence syntaxe regulárního výrazu v různých prohlížečích?
Při použití XRegExp , úvodní, bez kódování ] v rámci třídy znaků je považován za doslovný znak, a proto třídu nekončí. To je v souladu s ostatními motory regulárních výrazů, které jsem použil, a nativně to platí v Internet Exploreru. Ve Firefoxu však lze zaznamenat následující zvláštnosti (chyby?):
  • [^] je ekvivalentní [\S\s] , ačkoli by to mělo vyvolat chybu.
  • [^]] je ekvivalentní [\S\s]] , ačkoli by měl být ekvivalentní [^\]] nebo (?!])[\S\s] .
  • [] je ekvivalentní (?!) (který se nikdy nebude shodovat), i když by měl vyvolat chybu.
  • []] je ekvivalentní (?!)] (který se nikdy nebude shodovat), ačkoli by měl být ekvivalentní [\]] nebo ] .
Při použití XRegExp (nebo RegExp s XRE.overrideNative() ), nemusíte se starat o to, jak to zvládají různé prohlížeče, protože přední ] v rámci třídy znaků třída nikdy nekončí.
Které metody související s regulárním výrazem XRegExp podporuje?
Všechny.
Jsou regulární výrazy sestavovány pomocí XRegExp pomaleji, než by byly jinak?
Ne.
Trvá déle, než se sestaví regexy pomocí XRegExp, než by tomu bylo jinak?
Ano, v malém množství. Z osobního testování vytváření regexů pomocí XRegExp obvykle trvá méně než milisekundu déle, než by tomu bylo jinak. To je obzvláště triviální vzhledem k tomu, že regulární výrazy by neměly být konstruovány v rámci smyček. Místo toho by měl být regulární výraz přiřazen k proměnné před vstupem do smyčky, aby nedošlo k jejímu opětovnému sestavení během každé iterace smyčky.
V jakých prohlížečích to bylo testováno?
Firefox 2, Internet Explorer 5.5–7 a Opera 9.
Jak velký je soubor skriptu?
Minifikované, má méně než 1 kB. Gzipping to dále snižuje.
Pod jakou licencí je tato verze vydána?
Licence MIT.
Ovlivňuje XRegExp literály regulárních výrazů?
Ne. I při použití XRE.overrideNative() , literály regulárních výrazů ve stylu Perl (např. /pattern/gi ) nejsou ovlivněny.
Našel jsem chybu. Proč tak špatně saješ?
Jste si jisti, že chyba je v XRegExp? Syntaxe regulárních výrazů je poněkud složitá a často mění svůj význam v daném kontextu. Navíc metasekvence v řetězcových literálech JavaScriptu mohou věci změnit dříve, než XRegExp vůbec uvidí váš regulární výraz. V každém případě, ať už jste si jisti, že víte, co problém způsobuje, neváhejte zanechat komentář a já se na to co nejdříve podívám.

Zde je skript s komentáři:

/*----------------------------------------------------------------------
XRegExp 0.1, by Steven Levithan <http://stevenlevithan.com>
MIT-style license
------------------------------------------------------------------------
Adds support for the following regular expression features:
  - The "s" flag: Dot matches all (a.k.a, single-line) mode.
  - The "x" flag: Free-spacing and comments mode.

XRegExp also offers consistent, cross-browser handling when "]" is used
as the first character within a character class (e.g., "[]]" or "[^]]").
----------------------------------------------------------------------*/

var XRegExp = function(pattern, flags){
	if(!flags) flags = "";
	
	/* If the "free-spacing and comments" modifier (x) is enabled, replace unescaped whitespace as well as unescaped pound
	signs (#) and any following characters up to and including the next newline character (\n) with "(?:)". Using "(?:)"
	instead of just removing matches altogether prevents, e.g., "\1 0" from becoming "\10" (which has different meanings
	depending on context). None of this applies within character classes, which are unaffected even when they contain
	whitespace or pound signs (which is consistent with pretty much every library except java.util.regex). */
	if(flags.indexOf("x") !== -1){
		pattern = pattern.replace(XRE.re.xMod, function($0, $1, $2){
			// If $2 is an empty string or its first character is "[", return the match unviolated (an effective no-op).
			return (/[^[]/.test($2.charAt(0)) ? $1 + "(?:)" : $0);
		});
	}
	
	/* Two steps (the order is not important):
	
	1. Since a regex literal will be used to return the final regex function/object, replace characters which are not
	   allowed within regex literals (carriage return, line feed) with the metasequences which represent them (\r, \n),
	   accounting for both escaped and unescaped literal characters within pattern. This step is only necessary to support
	   the XRE.overrideNative() method, since otherwise the RegExp constructor could be used to return the final regex.
	
	2. When "]" is the first character within a character class, convert it to "\]", for consistent, cross-browser handling.
	   This is included to workaround the following Firefox quirks (bugs?):
	     - "[^]" is equivalent to "[\S\s]", although it should throw an error.
	     - "[^]]" is equivalent to "[\S\s]]", although it should be equivalent to "[^\]]" or "(?!])[\S\s]".
	     - "[]" is equivalent to "(?!)" (which will never match), although it should throw an error.
	     - "[]]" is equivalent to "(?!)]" (which will never match), although it should be equvialent to "[\]]" or "]".
	   
	   Note that this step is not just an extra feature. It is in fact required in order to maintain correctness without
	   the aid of browser sniffing when constructing the regexes which deal with character classes (XRE.re.chrClass and
	   XRE.re.xMod). They treat a leading "]" within a character class as a non-terminating, literal character. */
	pattern = pattern.replace(XRE.re.badChr, function($0, $1, $2){
			return $1 + $2.replace(/\r/, "\\r").replace(/\n/, "\\n");
		}).
		replace(XRE.re.chrClass, function($0, $1, $2){
			return $1 + $2.replace(/^(\[\^?)]/, "$1\\]");
		});
	
	// If the "dot matches all" modifier (s) is enabled, replace unescaped dots outside of character classes with [\S\s]
	if(flags.indexOf("s") !== -1){
		pattern = pattern.replace(XRE.re.chrClass, function($0, $1, $2){
			return $1.replace(XRE.re.sMod, function($0, $1, $2){
					return $1 + ($2 === "." ? "[\\S\\s]" : "");
				}) + $2;
		});
	}
	
	// Use an evaluated regex literal to return the regular expression, in order to support the XRE.overrideNative() method.
	return eval("/" + pattern + "/" + flags.replace(/[sx]+/g, ""));
},
XRE = {
	overrideNative: function(){
		/* Override the global RegExp constructor/object with the enhanced XRegExp constructor. This precludes accessing
		properties of the last match via the global RegExp object. However, those properties are deprecated as of
		JavaScript 1.5, and the values are available on RegExp instances or via the exec() method. */
		RegExp = XRegExp;
	},
	re: {
		chrClass: /((?:[^[\\]+|\\(?:[\S\s]|$))*)((?:\[\^?]?(?:[^\\\]]+|\\(?:[\S\s]|$))*]?)?)/g,
		xMod: /((?:[^[#\s\\]+|\\(?:[\S\s]|$))*)((?:\[\^?]?(?:[^\\\]]+|\\(?:[\S\s]|$))*]?|\s*#[^\n\r]*[\n\r]?\s*|\s+)?)/g,
		sMod: /((?:[^\\.]+|\\(?:[\S\s]|$))*)(\.?)/g,
		badChr: /((?:[^\\\r\n]+|\\(?:[^\r\n]|$(?!\s)))*)\\?([\r\n]?)/g
	}
};
/* XRE.re is used to cache the more complex regexes so they don't have to be recompiled every time XRegExp runs. Note that
the included regexes match anything (if they didn't, they would have to be rewritten to avoid catastrophic backtracking on
failure). It's the backreferences, as well as where one match ends and the next begins, that's important. Also note that
the regexes are exactly as they are in order to account for all kinds of caveats involved in interpreting and working with
JavaScript regexes. Do not modify them! */

Stáhněte si jej zde .

Aktualizace: Tato verze XRegExp je zastaralá. Viz XRegExp.com pro nejnovější, nejlepší verzi.