Sorcerer's IsleCode cfRegex / diff

8082ddb Initial commit of cfRegex at v0.3

 docs.pdf (new)                             | Bin 0 -> 452929 bytes
 license.txt (new)                          | 165 +++++
 readme.md (new)                            |  60 ++
 src/Regex.cfc (new)                        | 673 +++++++++++++++++++
 src/functions/RegexCompile.cfm (new)       |   5 +
 src/functions/RegexEscape.cfm (new)        |   6 +
 src/functions/RegexFind.cfm (new)          |   9 +
 src/functions/RegexMatch.cfm (new)         |  12 +
 src/functions/RegexMatches.cfm (new)       |   7 +
 src/functions/RegexQuote.cfm (new)         |   5 +
 src/functions/RegexReplace.cfm (new)       |  11 +
 src/functions/RegexSplit.cfm (new)         |  11 +
 src/legacy/Regex.cfc (new)                 | 675 ++++++++++++++++++++
 src/legacy/functions/include-all.cfm (new) |  10 +
 src/legacy/regex.cfm (new)                 | 122 ++++
 15 files changed, 1771 insertions(+)
diff --git a/docs.pdf b/docs.pdf
new file mode 100644
index 0000000..1630e6a
Binary files /dev/null and b/docs.pdf differ
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..02bbb60
--- /dev/null
+++ b/license.txt (view file)
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..5f0508a
--- /dev/null
+++ b/readme.md (view file)
@@ -0,0 +1,60 @@
+cfRegex
+
+* Version:       0.3
+* License:       LGPLv3
+* Homepage:      https://www.sorcerersisle.com/software/cfregex
+* Documentation: https://docs.sorcerersisle.com/cfregex
+* Repository:    https://code.sorcerersisle.com/cfregex.git
+* Issues:        https://github.com/boughtonp/cfregex/issues
+
+
+Description
+-----------
+cfRegex is a project to provide a complete set of regex functionality
+for CFML, with an aim to make regex a first-class feature which is
+easy for everyone to use.
+
+It provides a Regex object, cfregex tag, and a selection of functions,
+all designed to be used instead of the existing regex functions.
+
+For full details of what is provided see docs.pdf for documentation.
+
+
+Requirements
+------------
+
+cfRegex will run on any JVM-based CFML engine that supports CFCs, including:
+
+* Lucee 5.3 and above.
+* ColdFusion 9 and above.
+* OpenBD 2.x and above.
+* Railo / Lucee 4.x
+
+A minimum JRE/JDK version of 8 is recommended.
+
+cfRegex requires the ability to create Java objects, which may be
+disabled on some shared hosting.
+
+
+Licensing & Credits
+-------------------
+
+This project is available under the terms of the LGPLv3 license.
+See license.txt to understand your rights and obligations.
+
+cfRegex is created and maintained by Peter Boughton.
+
+
+Contributing
+------------
+
+This project aims to provide a complete set of regex functionality for CFML.
+
+Bug fixes, backwards-compatible improvements and additions are welcome,
+but please discuss first (either raise an issue or send an email).
+
+Changes are accepted as either pull requests or patch files.
+
+
+
+/eof
\ No newline at end of file
diff --git a/src/Regex.cfc b/src/Regex.cfc
new file mode 100644
index 0000000..a65eb09
--- /dev/null
+++ b/src/Regex.cfc (view file)
@@ -0,0 +1,673 @@
+<!--- cfRegex v0.3 | (c) Peter Boughton | License: LGPLv3 | Website: https://www.sorcerersisle.com/software/cfregex --->
+<cfcomponent output=false >
+
+	<!---
+		NOTE: This is a dual-purpose Object CFC and CustomTag CFC
+	--->
+
+
+	<cffunction name="init" returntype="any" output=false>
+		<cfset StructDelete(This,'init') />
+
+		<cfset Variables.Modes =
+			{ UNIX_LINES       = 1
+			, CASE_INSENSITIVE = 2
+			, COMMENTS         = 4
+			, MULTILINE        = 8
+			, DOTALL           = 32
+			, UNICODE_CASE     = 64
+			, CANON_EQ         = 128
+			}/>
+
+		<!--- TODO: Get defaults from admin settings. --->
+		<!--- If custom tag, default COMMENTS on, otherwise no defaults. --->
+		<cfset Variables.DefaultModes = Variables.Modes['COMMENTS'] * StructKeyExists(Arguments,'HasEndTag') />
+
+		<!--- INFO: If FuncName is escape or quote, don't compile regex, just return object. --->
+		<cfif StructKeyExists(Arguments,'FuncName') AND ListFindNoCase('escape,quote',Arguments.FuncName)>
+			<cfif StructKeyExists(Arguments,'Text') >
+				<cfset Variables.PatternText = Arguments.Text />
+			<cfelse>
+				<cfset Variables.PatternText = Arguments.Pattern />
+			</cfif>
+			<cfif NOT StructKeyExists(Arguments,'Modes')>
+				<cfset Arguments.Modes = Variables.DefaultModes />
+			</cfif>
+			<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
+			<cfreturn This />
+		</cfif>
+
+		<!--- INFO: If not tag, pass to .compile(...) --->
+		<cfif NOT StructKeyExists(Arguments,'HasEndTag')>
+			<cfreturn This.compile(ArgumentCollection=Arguments) />
+		</cfif>
+
+		<cfif NOT Arguments.HasEndTag >
+			<cfthrow
+				message = "The cfregex tag must have a closing tag."
+				type    = "cfRegex.Tag.MissingEndTag"
+			/>
+		</cfif>
+
+	</cffunction>
+
+
+	<!---
+		\\\ TAG FUNCS \\\
+	--->
+
+	<cffunction name="onStartTag" returntype="boolean" output=false >
+		<cfargument name="Attributes" type="Struct" required />
+		<cfargument name="Caller"     type="Struct" required />
+
+		<cfreturn true />
+	</cffunction>
+
+
+	<cffunction name="onEndTag" returntype="boolean" output=false >
+		<cfargument name="Attributes" type="Struct" required />
+		<cfargument name="Caller"     type="Struct" required />
+		<cfargument name="GeneratedContent" type="String" required />
+
+		<cfif StructKeyExists(Arguments.Attributes,'Action')>
+
+			<!--- TODO: Consider Modifiers.
+			~NoCase
+			~First
+			--->
+
+			<cfif StructKeyExists(This,Arguments.Attributes.Action)
+				AND StructKeyExists(getMetaData(This[Arguments.Attributes.Action]),'Action')
+				>
+				<!--- TODO: Validate. --->
+			<cfelse>
+				<cfthrow
+					message = "Invalid Action of '#Arguments.Attributes.Action#'"
+					detail  = "Please see cfRegex documentation for valid Action values."
+					type    = "cfRegex.Tag.InvalidAction"
+				/>
+			</cfif>
+		<cfelseif StructKeyExists(Arguments.Attributes,'Text')>
+			<!--- Input Text exists - check main actions. --->
+			<cfset var CurAction = "" />
+			<cfloop index="CurAction" list="#StructKeyList(This)#">
+				<cfif ListFindNoCase('onEndTag,onStartTag',CurAction)><cfcontinue /></cfif>
+				<cfif StructKeyExists(getMetaData(This[CurAction]),'Action') AND StructKeyExists(Arguments.Attributes,CurAction)>
+					<cfset Arguments.Attributes.Action = CurAction />
+				</cfif>
+			</cfloop>
+			<cfif NOT StructKeyExists(Arguments.Attributes,'Action')>
+				<cfthrow
+					message = "No action specified, unable to detect correct action."
+					detail  = "Please see cfRegex documentation for valid Action values."
+					type    = "cfRegex.Tag.UnknownAction"
+				/>
+			</cfif>
+		<cfelseif StructKeyExists(Arguments.Attributes,'escape')>
+			<cfset Arguments.Attributes.Action = "Escape" />
+		<cfelseif StructKeyExists(Arguments.Attributes,'quote')>
+			<cfset Arguments.Attributes.Action = "Quote" />
+		<cfelse>
+			<cfset Arguments.Attributes.Action = 'Compile' />
+		</cfif>
+
+
+		<cfif NOT StructKeyExists(Arguments.Attributes,'Pattern')>
+			<cfset Arguments.Attributes.Pattern = Arguments.GeneratedContent />
+		</cfif>
+		<cfif NOT StructKeyExists(Arguments.Attributes,'Modes')>
+			<cfset Arguments.Attributes.Modes = Variables.DefaultModes />
+		</cfif>
+
+		<cfif Arguments.Attributes.Action NEQ 'Compile'>
+			<cfset compilePattern(ArgumentCollection=Arguments.Attributes) />
+		</cfif>
+
+		<cfset var Result = This[Arguments.Attributes.Action](ArgumentCollection=Arguments.Attributes) />
+
+		<cfif StructKeyExists(Arguments.Attributes,'Name')>
+			<cfset SetVariable("Caller.#Arguments.Attributes.Name#",Result) />
+		<cfelseif StructKeyExists(Arguments.Attributes,'Variable')>
+			<cfset SetVariable("Caller.#Arguments.Attributes.Variable#",Result) />
+		<cfelse>
+			<cfset SetVariable("Caller.cfregex",Result) />
+		</cfif>
+
+		<cfreturn false />
+	</cffunction>
+
+	<!---
+		/// TAG FUNCS ///
+	--->
+
+	<!---
+		\\\ INTERNAL \\\
+	--->
+
+	<cffunction name="parseModes" returntype="Numeric" output="false" access="private">
+		<cfargument name="ModeList"           type="String"  required />
+		<cfargument name="IgnoreInvalidModes" type="Boolean" default="false"/>
+		<cfset var CurrentMode = ""/>
+		<cfset var ResultMode = 0/>
+
+		<cfloop index="CurrentMode" list="#Arguments.ModeList#">
+
+			<cfif isNumeric(CurrentMode)>
+				<cfset ResultMode = BitOr( ResultMode , CurrentMode )/>
+
+			<cfelseif StructKeyExists( Variables.Modes , CurrentMode )>
+				<cfset ResultMode = BitOr( ResultMode , Variables.Modes[CurrentMode] )/>
+
+			<cfelseif NOT Arguments.IgnoreInvalidModes>
+				<cfthrow
+					message = "Invalid Mode!"
+					detail  = "Mode [#CurrentMode#] is not supported."
+					type    = "cfRegex.Compile.InvalidMode"
+				/>
+
+			</cfif>
+
+		</cfloop>
+
+		<cfreturn ResultMode />
+	</cffunction>
+
+
+	<cffunction name="compilePattern" returntype="void" output="false" access="private">
+		<cfargument name="Pattern" type="String" required />
+		<cfargument name="Modes"   type="String" required />
+
+		<cfset Variables.PatternText = Arguments.Pattern />
+
+		<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
+
+		<cfset Variables.PatternObject = createObject("java","java.util.regex.Pattern")
+			.compile( Arguments.Pattern , Variables.ActiveModes ) />
+
+	</cffunction>
+
+
+	<cffunction name="buildMatchInfo" returntype="Struct" output="false" access="private">
+		<cfargument name="Matcher"    type="any"     required />
+		<cfargument name="PosOffset"  type="Numeric" optional />
+		<cfargument name="GroupNames" type="any"     optional />
+
+		<cfset var MatchInfo =
+			{ Match  = Matcher.group()
+			, Groups = []
+			} />
+
+		<cfif StructKeyExists(Arguments,'PosOffset')>
+			<cfset MatchInfo.Pos    = Arguments.PosOffset+Matcher.start() />
+			<cfset MatchInfo.Len    = Matcher.end()-Matcher.start() />
+		</cfif>
+
+		<cfset var CurGroup = 0 />
+		<cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
+			<cfif StructKeyExists(Arguments,'PosOffset')>
+				<cfset MatchInfo.Groups[CurGroup] =
+					{ Pos   = Arguments.PosOffset+Matcher.start(CurGroup)
+					, Len   = Matcher.end(CurGroup)-Matcher.start(CurGroup)
+					, Match = Matcher.group(JavaCast('int',CurGroup))
+					} />
+			<cfelse>
+				<cfset MatchInfo.Groups[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
+			</cfif>
+		</cfloop>
+
+		<cfif StructKeyExists(Arguments,'GroupNames')>
+			<cfif isSimpleValue(Arguments.GroupNames)>
+				<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
+			</cfif>
+			<cfif ArrayLen(Arguments.GroupNames)>
+				<cfset var i = 0 />
+				<cfset MatchInfo.NamedGroups = {} />
+				<cfloop index="i" from="1" to="#Min(ArrayLen(Arguments.GroupNames),ArrayLen(MatchInfo.Groups))#">
+					<cfset MatchInfo.NamedGroups[ Arguments.GroupNames[i] ] = MatchInfo.Groups[i] />
+				</cfloop>
+			</cfif>
+		</cfif>
+
+		<cfreturn MatchInfo />
+	</cffunction>
+
+
+	<!---
+		/// INTERNAL ///
+	--->
+
+
+	<cffunction name="compile" returntype="Regex" output="false" access="public" action>
+		<cfargument name="Pattern" type="String" required />
+		<cfargument name="Modes"   type="String" default="#Variables.DefaultModes#" />
+		<cfset StructDelete(This,'compile') />
+		<cfset StructDelete(This,'onStartTag') />
+		<cfset StructDelete(This,'onEndTag') />
+
+		<cfset compilePattern(ArgumentCollection=Arguments) />
+
+		<cfreturn this />
+	</cffunction>
+
+	<!---
+		\\\ EXTERNAL \\\
+	--->
+
+	<cffunction name="find" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"       type="String"  required  />
+		<cfargument name="Start"      type="Numeric" default=1  />
+		<cfargument name="Limit"      type="Numeric" default=0 />
+		<cfargument name="ReturnType" type="String"  default="pos" />
+
+		<cfif NOT ListFindNoCase('pos,sub,info',Arguments.ReturnType)>
+			<cfthrow message="Unknown returntype" />
+		</cfif>
+
+		<cfset var Offset = Max(1,Arguments.Start) />
+		<cfif Offset GT 1>
+			<cfset Arguments.Text = mid(Arguments.Text,Offset,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+		<cfset var Results = [] />
+
+		<cfloop condition="Matcher.find()">
+			<cfswitch expression=#LCase(Arguments.ReturnType)#>
+				<cfcase value="pos">
+					<cfset var CurMatch = Offset+Matcher.start() />
+				</cfcase>
+				<cfcase value="sub">
+					<cfset var CurMatch =
+						{ pos = [Offset+Matcher.start()]
+						, len = [Matcher.end()-Matcher.start()]
+						} />
+					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset ArrayAppend(CurMatch.pos,Offset+Matcher.start(CurGroup)) />
+						<cfset ArrayAppend(CurMatch.len,Matcher.end(CurGroup)-Matcher.start(CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="info">
+					<cfset var CurMatch = buildMatchInfo(Matcher,Offset) />
+				</cfcase>
+			</cfswitch>
+			<cfset ArrayAppend( Results , CurMatch ) />
+
+			<cfif ArrayLen(Results) EQ Arguments.Limit>
+				<cfbreak />
+			</cfif>
+		</cfloop>
+
+		<cfreturn Results />
+	</cffunction>
+
+
+	<cffunction name="match" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"         type="String"   required  />
+		<cfargument name="Start"        type="Numeric"  optional  />
+		<cfargument name="Limit"        type="Numeric"  default=0 />
+		<cfargument name="ReturnType"   type="String"   default="match" hint="match|groups|namedgroups|full" />
+		<cfargument name="GroupNames"   type="any"      default="" hint="Required if returnType=NamedGroup." />
+		<cfargument name="Callback"     type="any"      optional   hint="Function called to determine if a match is included in results." />
+		<cfargument name="CallbackData" type="Struct"   optional   hint="Extra data which is passed in to callback function." />
+
+		<cfif NOT ListFindNoCase('match,groups,namedgroups,full',Arguments.ReturnType)>
+			<cfthrow message="Unknown returntype" />
+		</cfif>
+
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start>
+			<cfset Arguments.Text = mid(Arguments.Text,Arguments.Start,Len(Arguments.Text)) />
+			<cfset Offset = Arguments.Start+1 />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+		<cfset var Results = [] />
+
+		<cfif StructKeyExists(Arguments,'GroupNames') AND isSimpleValue(Arguments.GroupNames)>
+			<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
+		</cfif>
+
+		<cfloop condition="Matcher.find()">
+
+			<cfif StructKeyExists(Arguments,'Callback')>
+				<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+					<cfset Arguments.CallbackData = {} />
+				</cfif>
+				<cfif NOT Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
+					<cfcontinue />
+				</cfif>
+			</cfif>
+
+			<cfswitch expression=#Arguments.ReturnType#>
+				<cfcase value="match">
+					<cfset var CurMatch = Matcher.Group() />
+				</cfcase>
+				<cfcase value="groups">
+					<cfset var CurMatch = [] />
+					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset CurMatch[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="namedgroups">
+					<cfset var CurMatch = {} />
+					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset CurMatch[Arguments.GroupNames[CurGroup]] = Matcher.group(JavaCast('int',CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="full">
+					<cfset var CurMatch = buildMatchInfo(Matcher=Matcher,GroupNames=Arguments.GroupNames) />
+				</cfcase>
+			</cfswitch>
+
+			<cfset ArrayAppend( Results , CurMatch ) />
+
+			<cfif ArrayLen(Results) EQ Arguments.Limit>
+				<cfbreak />
+			</cfif>
+		</cfloop>
+
+		<cfreturn Results />
+	</cffunction>
+
+
+	<cffunction name="matches" returntype="any" output="false" access="public" action>
+		<cfargument name="Text"       type="String"  required />
+		<cfargument name="ReturnType" type="String"  optional hint="exact,partial,start,end,count" />
+
+		<cfif StructKeyExists(Arguments,'ReturnType')>
+			<cfset Arguments.ReturnType = LCase(Arguments.ReturnType) />
+
+		<!--- INFO: If no unnamed args, don't waste time checking for them. --->
+		<cfelseif StructCount(arguments) EQ 2>
+			<cfset Arguments.ReturnType = 'exact' />
+
+		<cfelse>
+			<cfif StructKeyExists(Arguments,'Exact') AND Arguments.Exact >
+				<cfset Arguments.ReturnType = "exact" />
+			<cfelseif StructKeyExists(Arguments,'Count') AND Arguments.Count >
+				<cfset Arguments.ReturnType = "count" />
+			<cfelse>
+				<cfif StructKeyExists(Arguments,'at')>
+					<cfif Arguments.At EQ 'anywhere'>
+						<cfset Arguments.ReturnType = 'partial' />
+					<cfelse>
+						<cfset Arguments.ReturnType = LCase(Arguments.At) />
+					</cfif>
+				<cfelseif StructKeyExists(Arguments,'Partial') AND Arguments.Partial >
+					<cfset Arguments.ReturnType = "partial" />
+				</cfif>
+			</cfif>
+			<cfif NOT StructKeyExists(Arguments,'ReturnType')>
+				<cfset Arguments.ReturnType = 'exact' />
+			</cfif>
+		</cfif>
+
+		<cfswitch expression="#Arguments.ReturnType#">
+			<cfcase value="exact">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).matches() />
+			</cfcase>
+			<cfcase value="count">
+				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+				<cfset local.Count = 0 />
+				<cfloop condition="Matcher.find()">
+					<cfset local.Count++ />
+				</cfloop>
+				<cfreturn local.Count />
+			</cfcase>
+			<cfcase value="start">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).lookingAt() />
+			</cfcase>
+			<cfcase value="end">
+				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+				<cfset var LastPos = -1 />
+				<cfloop condition="Matcher.find()">
+					<cfset LastPos = Matcher.end() />
+				</cfloop>
+				<cfreturn (LastPos EQ Len(Arguments.Text)) />
+			</cfcase>
+			<cfcase value="partial">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).find() />
+			</cfcase>
+			<cfdefaultcase>
+				<cfthrow
+					message = "Invalid ReturnType '#Arguments.ReturnType#' for matches"
+					type    = "cfRegex.Match.InvalidArgument.ReturnType"
+				/>
+			</cfdefaultcase>
+		</cfswitch>
+	</cffunction>
+
+
+	<cffunction name="escape" returntype="String" output="false" access="public" action>
+		<cfargument name="ReturnType" type="String" default=REGEX hint="regex|class" />
+		<cfif NOT ListFind('regex,class',LCase(Arguments.ReturnType))>
+			<cfthrow
+				message = "Invalid Argument ReturnType, received [#Arguments.ReturnType#]"
+				detail  = "ReturnType value must be one of 'regex' OR 'class'."
+				type    = "cfRegex.Escape.InvalidArgument.ReturnType"
+			/>
+		</cfif>
+		<cfif NOT StructKeyExists(Variables,'Escaped#Arguments.ReturnType#')>
+			<cfif Arguments.ReturnType EQ 'regex'>
+				<cfset Variables.EscapedRegex = Variables.PatternText.replaceAll('[$^*()+\[\]{}.?\\|]','\\$0') />
+			<cfelse>
+				<cfset Variables.EscapedClass = Variables.PatternText
+					.replaceAll('(.)(?=.*?\1)','')
+					.replaceAll('(^\^|[\\\-\[\]])','\\$0')
+					.replaceAll(chr(9),'\t')
+					.replaceAll(chr(10),'\n')
+					.replaceAll(chr(13),'\r')
+					/>
+			</cfif>
+			<cfif BitAnd(Variables.ActiveModes,Variables.Modes['COMMENTS']) >
+				<cfset Variables['Escaped#Arguments.ReturnType#'] = Variables['Escaped#Arguments.ReturnType#'].replaceAll('##| ','\\$0') />
+			</cfif>
+		</cfif>
+		<cfreturn Variables['Escaped#Arguments.ReturnType#'] />
+	</cffunction>
+
+
+	<cffunction name="quote" returntype="String" output="false" access="public" action>
+		<cfif NOT StructKeyExists(Variables,'Quoted')>
+			<cfset Variables.Quoted = createObject("java","java.util.regex.Pattern").quote(Variables.PatternText) />
+		</cfif>
+		<cfreturn Variables.Quoted />
+	</cffunction>
+
+
+	<cffunction name="replace" returntype="String" output="false" access="public" action>
+		<cfargument name="Text"         type="String"  required  />
+		<cfargument name="Replacement"  type="Any"     optional hint="String,Array,Function"/>
+		<cfargument name="Start"        type="Numeric" optional  />
+		<cfargument name="Limit"        type="Numeric" default=0 />
+		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+		<cfargument name="CallbackData" type="Struct"  optional   hint="Extra data which is passed in to callback function." />
+
+		<cfif StructKeyExists(Arguments,'Callback') >
+			<cfset Arguments.Replacement = Arguments.Callback />
+		<cfelseif NOT StructKeyExists(Arguments,'Replacement')>
+			<cfthrow
+				message = "Missing Argument Replacement"
+				type    = "cfRegex.Replace.MissingArgument"
+			/>
+		</cfif>
+
+		<cfset var Prefix = "" />
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
+			<cfset Offset = Arguments.Start+1 />
+			<cfset Prefix = Left(Arguments.Text,Arguments.Start) />
+			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
+		<cfset var Results = createObject("java","java.lang.StringBuffer").init(Prefix)/>
+		<cfset var ReplacementsMade = 0 />
+		<cfset var ReplacePos = 1 />
+
+		<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+			<cfset Arguments.CallbackData = {} />
+		</cfif>
+
+		<cfloop condition="Matcher.find()">
+
+			<cfif isSimpleValue(Arguments.Replacement)>
+				<cfset Matcher.appendReplacement( Results , Arguments.Replacement )/>
+
+			<cfelseif isArray(Arguments.Replacement)>
+
+				<cfif isSimpleValue(Arguments.Replacement[ReplacePos])>
+					<cfset Matcher.appendReplacement( Results , Arguments.Replacement[ReplacePos] )/>
+				<cfelse>
+					<cfset Matcher.appendReplacement
+						( Results
+						, Arguments.Replacement[ReplacePos]( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
+						)/>
+				</cfif>
+
+				<cfif ++ReplacePos GT ArrayLen(Arguments.Replacement)>
+					<cfset ReplacePos = 1 />
+				</cfif>
+
+			<cfelse>
+
+				<cfset Matcher.appendReplacement
+					( Results
+					, Arguments.Replacement( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
+					)/>
+
+			</cfif>
+
+			<cfif ++ReplacementsMade EQ Arguments.Limit>
+				<cfbreak/>
+			</cfif>
+
+		</cfloop>
+
+		<cfset Matcher.appendTail(Results)/>
+
+		<cfreturn Results.toString() />
+	</cffunction>
+
+
+	<cffunction name="split" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"         type="String"  required />
+		<cfargument name="Start"        type="Numeric" optional  />
+		<cfargument name="Limit"        type="Numeric" default=0  hint="The maximum number of times a split is made (i.e. limit+1=max array size)"/>
+		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+		<cfargument name="Callback"     type="any"     optional />
+		<cfargument name="CallbackData" type="Struct"  optional hint="Extra data which is passed in to callback function." />
+
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
+			<cfset var Prefix = Left(Arguments.Text,Arguments.Start) />
+			<cfset Offset = 1+Arguments.Start />
+			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfif StructKeyExists(Arguments,'Callback')>
+			<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
+			<cfset var TextPos = 1 />
+			<cfset var ArrayPos = 1 />
+			<cfset var Results = [''] />
+			<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+				<cfset Arguments.CallbackData = {} />
+			</cfif>
+
+			<cfloop condition="Matcher.find(TextPos-1)">
+
+				<cfif Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
+
+					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.start()+1-TextPos) />
+					<cfset TextPos = Matcher.end()+1 />
+
+					<cfset ArrayPos++ />
+					<cfset Results[ArrayPos] = '' />
+
+					<cfif Arguments.Limit AND ArrayLen(Results) GT Arguments.Limit>
+						<cfbreak />
+					</cfif>
+				<cfelse>
+					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.end()+1-TextPos) />
+					<cfset TextPos = Matcher.end()+1 />
+				</cfif>
+
+			</cfloop>
+
+			<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,len(Arguments.Text)) />
+
+		<cfelse>
+			<cfif Arguments.Limit>
+				<!---
+					NOTE:
+					For java.util.regex, limit is array length.
+					For cfregex, limit is number of times the action occurs.
+					Therefor, must add one...
+				--->
+				<cfset var Results = Variables.PatternObject.split(Arguments.Text,Arguments.Limit+1) />
+			<cfelse>
+				<cfset var Results = Variables.PatternObject.split(Arguments.Text) />
+			</cfif>
+		</cfif>
+
+		<cfif isDefined('Prefix') AND ArrayLen(Results)>
+			<cfset Results[1] = Prefix & Results[1] />
+		</cfif>
+
+		<cfreturn Results />
+	</cffunction>
+
+	<!---
+		/// EXTERNAL ///
+	--->
+
+
+
+	<!---
+		CALLBACK SAMPLES
+
+		A callback function can be used with the following functions:
+		.replace
+		.match
+		.split
+
+		A callback is called each time a match is found, and allows for
+		conditional behaviour to be executed at this point,
+		to change how the function behaves towards the match.
+
+		A Replace Callback determines what text to use for replacement.
+		A Match Callback determines whether to include or exclude the match in results.
+		A Split Callback determines whether to split or not at the match.
+
+		The callbacks are identical except for returntype.
+		(For Replace it returns text, for everything else, it returns a boolean.)
+
+		See http://docs.cfregex.net/Callbacks.html
+
+		<cffunction name="ReplaceCallback" returntype="String" output="false">
+			<cfargument name="Pos"         type="Numeric" required  hint="The start position of the match."  />
+			<cfargument name="Len"         type="Numeric" required  hint="The length of the match."          />
+			<cfargument name="Match"       type="String"  required  hint="The text of the match."            />
+			<cfargument name="Groups"      type="Array"   required  hint="Array of group information."       />
+			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
+			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
+
+			<cfreturn 'replacement text' />
+		</cffunction>
+
+
+		<cffunction name="BooleanCallback" returntype="Boolean" output="false">
+			<cfargument name="Pos"         type="Numeric" required  hint="The start position of the match."  />
+			<cfargument name="Len"         type="Numeric" required  hint="The length of the match."          />
+			<cfargument name="Match"       type="String"  required  hint="The text of the match."            />
+			<cfargument name="Groups"      type="Array"   required  hint="Array of group information."       />
+			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
+			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
+
+			<cfreturn true />
+		</cffunction>
+
+	--->
+
+
+</cfcomponent>
\ No newline at end of file
diff --git a/src/functions/RegexCompile.cfm b/src/functions/RegexCompile.cfm
new file mode 100644
index 0000000..ae2461e
--- /dev/null
+++ b/src/functions/RegexCompile.cfm (view file)
@@ -0,0 +1,5 @@
+<cffunction name="RegexCompile" returntype="Regex" output="false">
+	<cfargument name="Pattern" type="String" required_ />
+	<cfargument name="Flags"   type="String" optional />
+	<cfreturn createObject("component","Regex").init(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexEscape.cfm b/src/functions/RegexEscape.cfm
new file mode 100644
index 0000000..25519cf
--- /dev/null
+++ b/src/functions/RegexEscape.cfm (view file)
@@ -0,0 +1,6 @@
+<cffunction name="RegexEscape" returntype="String" output="false" >
+	<cfargument name="Pattern"    type="String" required_ />
+	<cfargument name="ReturnType" type="String" default=REGEX hint="regex|class" />
+	<cfargument name="Flags"      type="String" optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="escape").escape(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexFind.cfm b/src/functions/RegexFind.cfm
new file mode 100644
index 0000000..fb216d8
--- /dev/null
+++ b/src/functions/RegexFind.cfm (view file)
@@ -0,0 +1,9 @@
+<cffunction name="RegexFind" returntype="Array" output="false" >
+	<cfargument name="Pattern"    type="String"  required_ />
+	<cfargument name="Text"       type="String"  required_ />
+	<cfargument name="Start"      type="Numeric" default=1 />
+	<cfargument name="Limit"      type="Numeric" default=0 />
+	<cfargument name="ReturnType" type="String"  default="pos" />
+	<cfargument name="Flags"      type="String"  optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="find").find(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexMatch.cfm b/src/functions/RegexMatch.cfm
new file mode 100644
index 0000000..00151ab
--- /dev/null
+++ b/src/functions/RegexMatch.cfm (view file)
@@ -0,0 +1,12 @@
+<cffunction name="RegexMatch" returntype="Array" output="false" >
+	<cfargument name="Pattern"      type="String"   required_ />
+	<cfargument name="Text"         type="String"   required_ />
+	<cfargument name="Start"        type="Numeric"  optional  />
+	<cfargument name="Limit"        type="Numeric"  default=0 />
+	<cfargument name="ReturnType"   type="String"   default="match" hint="match|groups|namedgroups|full" />
+	<cfargument name="GroupNames"   type="any"      default="" hint="Required if returnType=NamedGroup." />
+	<cfargument name="Callback"     type="any"      optional   hint="Function called to determine if a match is included in results." />
+	<cfargument name="CallbackData" type="Struct"   optional   hint="Extra data which is passed in to callback function." />
+	<cfargument name="Flags"        type="String"   optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="match").match(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexMatches.cfm b/src/functions/RegexMatches.cfm
new file mode 100644
index 0000000..549a39b
--- /dev/null
+++ b/src/functions/RegexMatches.cfm (view file)
@@ -0,0 +1,7 @@
+<cffunction name="RegexMatches" returntype="any" output="false" >
+	<cfargument name="Pattern"    type="String" required_ />
+	<cfargument name="Text"       type="String" required_ />
+	<cfargument name="ReturnType" type="String" optional hint="exact,partial,start,end,count" />
+	<cfargument name="Flags"      type="String" optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="matches").matches(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexQuote.cfm b/src/functions/RegexQuote.cfm
new file mode 100644
index 0000000..c73ae33
--- /dev/null
+++ b/src/functions/RegexQuote.cfm (view file)
@@ -0,0 +1,5 @@
+<cffunction name="RegexQuote" returntype="String" output="false" >
+	<cfargument name="Pattern" type="String" required_ />
+	<cfargument name="Flags"   type="String" optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="quote").quote(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexReplace.cfm b/src/functions/RegexReplace.cfm
new file mode 100644
index 0000000..795d698
--- /dev/null
+++ b/src/functions/RegexReplace.cfm (view file)
@@ -0,0 +1,11 @@
+<cffunction name="RegexReplace" returntype="String"  output="false" >
+	<cfargument name="Pattern"      type="String"  required_ />
+	<cfargument name="Text"         type="String"  required_ />
+	<cfargument name="Replacement"  type="Any"     optional hint="String,Array,Function"/>
+	<cfargument name="Start"        type="Numeric" optional  />
+	<cfargument name="Limit"        type="Numeric" default=0 />
+	<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+	<cfargument name="CallbackData" type="Struct"  optional   hint="Extra data which is passed in to callback function." />
+	<cfargument name="Flags"        type="String"  optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="replace").replace(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/functions/RegexSplit.cfm b/src/functions/RegexSplit.cfm
new file mode 100644
index 0000000..21dca93
--- /dev/null
+++ b/src/functions/RegexSplit.cfm (view file)
@@ -0,0 +1,11 @@
+<cffunction name="RegexSplit" returntype="Array" output="false" >
+	<cfargument name="Pattern"      type="String"  required_ />
+	<cfargument name="Text"         type="String"  required_ />
+	<cfargument name="Start"        type="Numeric" optional  />
+	<cfargument name="Limit"        type="Numeric" default=0  hint="The maximum number of times a split is made (i.e. limit+1=max array size)"/>
+	<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+	<cfargument name="Callback"     type="any"     optional />
+	<cfargument name="CallbackData" type="Struct"  optional hint="Extra data which is passed in to callback function." />
+	<cfargument name="Flags"        type="String"  optional />
+	<cfreturn new Regex(ArgumentCollection=Arguments,FuncName="split").split(ArgumentCollection=Arguments) />
+</cffunction>
\ No newline at end of file
diff --git a/src/legacy/Regex.cfc b/src/legacy/Regex.cfc
new file mode 100644
index 0000000..e73a950
--- /dev/null
+++ b/src/legacy/Regex.cfc (view file)
@@ -0,0 +1,675 @@
+<!--- cfRegex v0.3-legacy | (c) Peter Boughton | License: LGPLv3 | Website: https://www.sorcerersisle.com/software/cfregex --->
+<cfcomponent output=false >
+
+	<!---
+		NOTE: This is a dual-purpose Object CFC and CustomTag CFC
+	--->
+
+
+	<cffunction name="init" returntype="any" output=false>
+		<cfset StructDelete(This,'init') />
+
+		<cfset Variables.Modes =
+			{ UNIX_LINES       = 1
+			, CASE_INSENSITIVE = 2
+			, COMMENTS         = 4
+			, MULTILINE        = 8
+			, DOTALL           = 32
+			, UNICODE_CASE     = 64
+			, CANON_EQ         = 128
+			}/>
+
+		<!--- TODO: Get defaults from admin settings. --->
+		<!--- If custom tag, default COMMENTS on, otherwise no defaults. --->
+		<cfset Variables.DefaultModes = Variables.Modes['COMMENTS'] * StructKeyExists(Arguments,'HasEndTag') />
+
+		<!--- INFO: If FuncName is escape or quote, don't compile regex, just return object. --->
+		<cfif StructKeyExists(Arguments,'FuncName') AND ListFindNoCase('escape,quote',Arguments.FuncName)>
+			<cfif StructKeyExists(Arguments,'Text') >
+				<cfset Variables.PatternText = Arguments.Text />
+			<cfelse>
+				<cfset Variables.PatternText = Arguments.Pattern />
+			</cfif>
+			<cfif NOT StructKeyExists(Arguments,'Modes')>
+				<cfset Arguments.Modes = Variables.DefaultModes />
+			</cfif>
+			<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
+			<cfreturn This />
+		</cfif>
+
+		<!--- INFO: If not tag, pass to .compile(...) --->
+		<cfif NOT StructKeyExists(Arguments,'HasEndTag')>
+			<cfreturn This.compile(ArgumentCollection=Arguments) />
+		</cfif>
+
+		<cfif NOT Arguments.HasEndTag >
+			<cfthrow
+				message = "The cfregex tag must have a closing tag."
+				type    = "cfRegex.Tag.MissingEndTag"
+			/>
+		</cfif>
+
+	</cffunction>
+
+
+	<!---
+		\\\ TAG FUNCS \\\
+	--->
+
+	<cffunction name="onStartTag" returntype="boolean" output=false >
+		<cfargument name="Attributes" type="Struct" required_ />
+		<cfargument name="Caller"     type="Struct" required_ />
+
+		<cfreturn true />
+	</cffunction>
+
+
+	<cffunction name="onEndTag" returntype="boolean" output=false >
+		<cfargument name="Attributes" type="Struct" required_ />
+		<cfargument name="Caller"     type="Struct" required_ />
+		<cfargument name="GeneratedContent" type="String" required_ />
+
+		<cfif StructKeyExists(Arguments.Attributes,'Action')>
+
+			<!--- TODO: Consider Modifiers.
+			~NoCase
+			~First
+			--->
+
+			<cfif StructKeyExists(This,Arguments.Attributes.Action)
+				AND StructKeyExists(getMetaData(This[Arguments.Attributes.Action]),'Action')
+				>
+				<!--- TODO: Validate. --->
+			<cfelse>
+				<cfthrow
+					message = "Invalid Action of '#Arguments.Attributes.Action#'"
+					detail  = "Please see cfRegex documentation for valid Action values."
+					type    = "cfRegex.Tag.InvalidAction"
+				/>
+			</cfif>
+		<cfelseif StructKeyExists(Arguments.Attributes,'Text')>
+			<!--- Input Text exists - check main actions. --->
+			<cfset var CurAction = "" />
+			<cfloop index="CurAction" list="#StructKeyList(This)#">
+				<cfif ListFindNoCase('onEndTag,onStartTag',CurAction)><cfcontinue /></cfif>
+				<cfif StructKeyExists(getMetaData(This[CurAction]),'Action') AND StructKeyExists(Arguments.Attributes,CurAction)>
+					<cfset Arguments.Attributes.Action = CurAction />
+				</cfif>
+			</cfloop>
+			<cfif NOT StructKeyExists(Arguments.Attributes,'Action')>
+				<cfthrow
+					message = "No action specified, unable to detect correct action."
+					detail  = "Please see cfRegex documentation for valid Action values."
+					type    = "cfRegex.Tag.UnknownAction"
+				/>
+			</cfif>
+		<cfelseif StructKeyExists(Arguments.Attributes,'escape')>
+			<cfset Arguments.Attributes.Action = "Escape" />
+		<cfelseif StructKeyExists(Arguments.Attributes,'quote')>
+			<cfset Arguments.Attributes.Action = "Quote" />
+		<cfelse>
+			<cfset Arguments.Attributes.Action = 'Compile' />
+		</cfif>
+
+
+		<cfif NOT StructKeyExists(Arguments.Attributes,'Pattern')>
+			<cfset Arguments.Attributes.Pattern = Arguments.GeneratedContent />
+		</cfif>
+		<cfif NOT StructKeyExists(Arguments.Attributes,'Modes')>
+			<cfset Arguments.Attributes.Modes = Variables.DefaultModes />
+		</cfif>
+
+		<cfif Arguments.Attributes.Action NEQ 'Compile'>
+			<cfset compilePattern(ArgumentCollection=Arguments.Attributes) />
+		</cfif>
+
+		<cfset var Result = This[Arguments.Attributes.Action] />
+		<cfset Result = Result(ArgumentCollection=Arguments.Attributes) />
+
+		<cfif StructKeyExists(Arguments.Attributes,'Name')>
+			<cfset SetVariable("Caller.#Arguments.Attributes.Name#",Result) />
+		<cfelseif StructKeyExists(Arguments.Attributes,'Variable')>
+			<cfset SetVariable("Caller.#Arguments.Attributes.Variable#",Result) />
+		<cfelse>
+			<cfset SetVariable("Caller.cfregex",Result) />
+		</cfif>
+
+		<cfreturn false />
+	</cffunction>
+
+	<!---
+		/// TAG FUNCS ///
+	--->
+
+	<!---
+		\\\ INTERNAL \\\
+	--->
+
+	<cffunction name="parseModes" returntype="Numeric" output="false" access="private">
+		<cfargument name="ModeList"           type="String"  required_ />
+		<cfargument name="IgnoreInvalidModes" type="Boolean" default="false"/>
+		<cfset var CurrentMode = ""/>
+		<cfset var ResultMode = 0/>
+
+		<cfloop index="CurrentMode" list="#Arguments.ModeList#">
+
+			<cfif isNumeric(CurrentMode)>
+				<cfset ResultMode = BitOr( ResultMode , CurrentMode )/>
+
+			<cfelseif StructKeyExists( Variables.Modes , CurrentMode )>
+				<cfset ResultMode = BitOr( ResultMode , Variables.Modes[CurrentMode] )/>
+
+			<cfelseif NOT Arguments.IgnoreInvalidModes>
+				<cfthrow
+					message = "Invalid Mode!"
+					detail  = "Mode [#CurrentMode#] is not supported."
+					type    = "cfRegex.Compile.InvalidMode"
+				/>
+
+			</cfif>
+
+		</cfloop>
+
+		<cfreturn ResultMode />
+	</cffunction>
+
+
+	<cffunction name="compilePattern" returntype="void" output="false" access="private">
+		<cfargument name="Pattern" type="String" required_ />
+		<cfargument name="Modes"   type="String" required_ />
+
+		<cfset Variables.PatternText = Arguments.Pattern />
+
+		<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
+
+		<cfset Variables.PatternObject = createObject("java","java.util.regex.Pattern")
+			.compile( Arguments.Pattern , Variables.ActiveModes ) />
+
+	</cffunction>
+
+
+	<cffunction name="buildMatchInfo" returntype="Struct" output="false" access="private">
+		<cfargument name="Matcher"    type="any"     required_ />
+		<cfargument name="PosOffset"  type="Numeric" optional />
+		<cfargument name="GroupNames" type="any"     optional />
+
+		<cfset var MatchInfo =
+			{ Match  = Matcher.group()
+			, Groups = []
+			} />
+
+		<cfif StructKeyExists(Arguments,'PosOffset')>
+			<cfset MatchInfo.Pos    = Arguments.PosOffset+Matcher.start() />
+			<cfset MatchInfo.Len    = Matcher.end()-Matcher.start() />
+		</cfif>
+
+		<cfset var CurGroup = 0 />
+		<cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
+			<cfif StructKeyExists(Arguments,'PosOffset')>
+				<cfset MatchInfo.Groups[CurGroup] =
+					{ Pos   = Arguments.PosOffset+Matcher.start(CurGroup)
+					, Len   = Matcher.end(CurGroup)-Matcher.start(CurGroup)
+					, Match = Matcher.group(JavaCast('int',CurGroup))
+					} />
+			<cfelse>
+				<cfset MatchInfo.Groups[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
+			</cfif>
+		</cfloop>
+
+		<cfif StructKeyExists(Arguments,'GroupNames')>
+			<cfif isSimpleValue(Arguments.GroupNames)>
+				<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
+			</cfif>
+			<cfif ArrayLen(Arguments.GroupNames)>
+				<cfset var i = 0 />
+				<cfset MatchInfo.NamedGroups = {} />
+				<cfloop index="i" from="1" to="#Min(ArrayLen(Arguments.GroupNames),ArrayLen(MatchInfo.Groups))#">
+					<cfset MatchInfo.NamedGroups[ Arguments.GroupNames[i] ] = MatchInfo.Groups[i] />
+				</cfloop>
+			</cfif>
+		</cfif>
+
+		<cfreturn MatchInfo />
+	</cffunction>
+
+
+	<!---
+		/// INTERNAL ///
+	--->
+
+
+	<cffunction name="compile" returntype="Regex" output="false" access="public" action>
+		<cfargument name="Pattern" type="String" required_ />
+		<cfargument name="Modes"   type="String" default="#Variables.DefaultModes#" />
+		<cfset StructDelete(This,'compile') />
+		<cfset StructDelete(This,'onStartTag') />
+		<cfset StructDelete(This,'onEndTag') />
+
+		<cfset compilePattern(ArgumentCollection=Arguments) />
+
+		<cfreturn this />
+	</cffunction>
+
+	<!---
+		\\\ EXTERNAL \\\
+	--->
+
+	<cffunction name="find" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"       type="String"  required_  />
+		<cfargument name="Start"      type="Numeric" default=1  />
+		<cfargument name="Limit"      type="Numeric" default=0 />
+		<cfargument name="ReturnType" type="String"  default="pos" />
+
+		<cfif NOT ListFindNoCase('pos,sub,info',Arguments.ReturnType)>
+			<cfthrow message="Unknown returntype" />
+		</cfif>
+
+		<cfset var Offset = Max(1,Arguments.Start) />
+		<cfif Offset GT 1>
+			<cfset Arguments.Text = mid(Arguments.Text,Offset,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+		<cfset var Results = [] />
+
+		<cfloop condition="Matcher.find()">
+			<cfswitch expression=#LCase(Arguments.ReturnType)#>
+				<cfcase value="pos">
+					<cfset var CurMatch = Offset+Matcher.start() />
+				</cfcase>
+				<cfcase value="sub">
+					<cfset var CurMatch =
+						{ pos = [Offset+Matcher.start()]
+						, len = [Matcher.end()-Matcher.start()]
+						} />
+					<cfset var CurGroup = 0 /><cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset ArrayAppend(CurMatch.pos,Offset+Matcher.start(CurGroup)) />
+						<cfset ArrayAppend(CurMatch.len,Matcher.end(CurGroup)-Matcher.start(CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="info">
+					<cfset var CurMatch = buildMatchInfo(Matcher,Offset) />
+				</cfcase>
+			</cfswitch>
+			<cfset ArrayAppend( Results , CurMatch ) />
+
+			<cfif ArrayLen(Results) EQ Arguments.Limit>
+				<cfbreak />
+			</cfif>
+		</cfloop>
+
+		<cfreturn Results />
+	</cffunction>
+
+
+	<cffunction name="match" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"         type="String"   required_  />
+		<cfargument name="Start"        type="Numeric"  optional  />
+		<cfargument name="Limit"        type="Numeric"  default=0 />
+		<cfargument name="ReturnType"   type="String"   default="match" hint="match|groups|namedgroups|full" />
+		<cfargument name="GroupNames"   type="any"      default="" hint="Required if returnType=NamedGroup." />
+		<cfargument name="Callback"     type="any"      optional   hint="Function called to determine if a match is included in results." />
+		<cfargument name="CallbackData" type="Struct"   optional   hint="Extra data which is passed in to callback function." />
+
+		<cfif NOT ListFindNoCase('match,groups,namedgroups,full',Arguments.ReturnType)>
+			<cfthrow message="Unknown returntype" />
+		</cfif>
+
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start>
+			<cfset Arguments.Text = mid(Arguments.Text,Arguments.Start,Len(Arguments.Text)) />
+			<cfset Offset = Arguments.Start+1 />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+		<cfset var Results = [] />
+
+		<cfif StructKeyExists(Arguments,'GroupNames') AND isSimpleValue(Arguments.GroupNames)>
+			<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
+		</cfif>
+
+		<cfloop condition="Matcher.find()">
+
+			<cfif StructKeyExists(Arguments,'Callback')>
+				<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+					<cfset Arguments.CallbackData = {} />
+				</cfif>
+				<cfif NOT Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
+					<cfcontinue />
+				</cfif>
+			</cfif>
+
+			<cfswitch expression=#Arguments.ReturnType#>
+				<cfcase value="match">
+					<cfset var CurMatch = Matcher.Group() />
+				</cfcase>
+				<cfcase value="groups">
+					<cfset var CurMatch = [] />
+					<cfset var CurGroup = 0 /><cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset CurMatch[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="namedgroups">
+					<cfset var CurMatch = {} />
+					<cfset var CurGroup = 0 /><cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
+						<cfset CurMatch[Arguments.GroupNames[CurGroup]] = Matcher.group(JavaCast('int',CurGroup)) />
+					</cfloop>
+				</cfcase>
+				<cfcase value="full">
+					<cfset var CurMatch = buildMatchInfo(Matcher=Matcher,GroupNames=Arguments.GroupNames) />
+				</cfcase>
+			</cfswitch>
+
+			<cfset ArrayAppend( Results , CurMatch ) />
+
+			<cfif ArrayLen(Results) EQ Arguments.Limit>
+				<cfbreak />
+			</cfif>
+		</cfloop>
+
+		<cfreturn Results />
+	</cffunction>
+
+
+	<cffunction name="matches" returntype="any" output="false" access="public" action>
+		<cfargument name="Text"       type="String"  required_ />
+		<cfargument name="ReturnType" type="String"  optional hint="exact,partial,start,end,count" />
+
+		<cfif StructKeyExists(Arguments,'ReturnType')>
+			<cfset Arguments.ReturnType = LCase(Arguments.ReturnType) />
+
+		<!--- INFO: If no unnamed args, don't waste time checking for them. --->
+		<cfelseif StructCount(arguments) EQ 2>
+			<cfset Arguments.ReturnType = 'exact' />
+
+		<cfelse>
+			<cfif StructKeyExists(Arguments,'Exact') AND Arguments.Exact >
+				<cfset Arguments.ReturnType = "exact" />
+			<cfelseif StructKeyExists(Arguments,'Count') AND Arguments.Count >
+				<cfset Arguments.ReturnType = "count" />
+			<cfelse>
+				<cfif StructKeyExists(Arguments,'at')>
+					<cfif Arguments.At EQ 'anywhere'>
+						<cfset Arguments.ReturnType = 'partial' />
+					<cfelse>
+						<cfset Arguments.ReturnType = LCase(Arguments.At) />
+					</cfif>
+				<cfelseif StructKeyExists(Arguments,'Partial') AND Arguments.Partial >
+					<cfset Arguments.ReturnType = "partial" />
+				</cfif>
+			</cfif>
+			<cfif NOT StructKeyExists(Arguments,'ReturnType')>
+				<cfset Arguments.ReturnType = 'exact' />
+			</cfif>
+		</cfif>
+
+		<cfswitch expression="#Arguments.ReturnType#">
+			<cfcase value="exact">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).matches() />
+			</cfcase>
+			<cfcase value="count">
+				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+				<cfset local.Count = 0 />
+				<cfloop condition="Matcher.find()">
+					<cfset local.Count++ />
+				</cfloop>
+				<cfreturn local.Count />
+			</cfcase>
+			<cfcase value="start">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).lookingAt() />
+			</cfcase>
+			<cfcase value="end">
+				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
+				<cfset var LastPos = -1 />
+				<cfloop condition="Matcher.find()">
+					<cfset LastPos = Matcher.end() />
+				</cfloop>
+				<cfreturn (LastPos EQ Len(Arguments.Text)) />
+			</cfcase>
+			<cfcase value="partial">
+				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).find() />
+			</cfcase>
+			<cfdefaultcase>
+				<cfthrow
+					message = "Invalid ReturnType '#Arguments.ReturnType#' for matches"
+					type    = "cfRegex.Match.InvalidArgument.ReturnType"
+				/>
+			</cfdefaultcase>
+		</cfswitch>
+	</cffunction>
+
+
+	<cffunction name="escape" returntype="String" output="false" access="public" action>
+		<cfargument name="ReturnType" type="String" default=REGEX hint="regex|class" />
+		<cfif NOT ListFind('regex,class',LCase(Arguments.ReturnType))>
+			<cfthrow
+				message = "Invalid Argument ReturnType, received [#Arguments.ReturnType#]"
+				detail  = "ReturnType value must be one of 'regex' OR 'class'."
+				type    = "cfRegex.Escape.InvalidArgument.ReturnType"
+			/>
+		</cfif>
+		<cfif NOT StructKeyExists(Variables,'Escaped#Arguments.ReturnType#')>
+			<cfif Arguments.ReturnType EQ 'regex'>
+				<cfset Variables.EscapedRegex = Variables.PatternText.replaceAll('[$^*()+\[\]{}.?\\|]','\\$0') />
+			<cfelse>
+				<cfset Variables.EscapedClass = Variables.PatternText
+					.replaceAll('(.)(?=.*?\1)','')
+					.replaceAll('(^\^|[\\\-\[\]])','\\$0')
+					.replaceAll(chr(9),'\t')
+					.replaceAll(chr(10),'\n')
+					.replaceAll(chr(13),'\r')
+					/>
+			</cfif>
+			<cfif BitAnd(Variables.ActiveModes,Variables.Modes['COMMENTS']) >
+				<cfset Variables['Escaped#Arguments.ReturnType#'] = Variables['Escaped#Arguments.ReturnType#'].replaceAll('##| ','\\$0') />
+			</cfif>
+		</cfif>
+		<cfreturn Variables['Escaped#Arguments.ReturnType#'] />
+	</cffunction>
+
+
+	<cffunction name="quote" returntype="String" output="false" access="public" action>
+		<cfif NOT StructKeyExists(Variables,'Quoted')>
+			<cfset Variables.Quoted = createObject("java","java.util.regex.Pattern").quote(Variables.PatternText) />
+		</cfif>
+		<cfreturn Variables.Quoted />
+	</cffunction>
+
+
+	<cffunction name="replace" returntype="String" output="false" access="public" action>
+		<cfargument name="Text"         type="String"  required_  />
+		<cfargument name="Replacement"  type="Any"     optional hint="String,Array,Function"/>
+		<cfargument name="Start"        type="Numeric" optional  />
+		<cfargument name="Limit"        type="Numeric" default=0 />
+		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+		<cfargument name="CallbackData" type="Struct"  optional   hint="Extra data which is passed in to callback function." />
+
+		<cfif StructKeyExists(Arguments,'Callback') >
+			<cfset Arguments.Replacement = Arguments.Callback />
+		<cfelseif NOT StructKeyExists(Arguments,'Replacement')>
+			<cfthrow
+				message = "Missing Argument Replacement"
+				type    = "cfRegex.Replace.MissingArgument"
+			/>
+		</cfif>
+
+		<cfset var Prefix = "" />
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
+			<cfset Offset = Arguments.Start+1 />
+			<cfset Prefix = Left(Arguments.Text,Arguments.Start) />
+			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
+		<cfset var Results = createObject("java","java.lang.StringBuffer").init(Prefix)/>
+		<cfset var ReplacementsMade = 0 />
+		<cfset var ReplacePos = 1 />
+
+		<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+			<cfset Arguments.CallbackData = {} />
+		</cfif>
+
+		<cfloop condition="Matcher.find()">
+
+			<cfif isSimpleValue(Arguments.Replacement)>
+				<cfset Matcher.appendReplacement( Results , Arguments.Replacement )/>
+
+			<cfelseif isArray(Arguments.Replacement)>
+
+				<cfif isSimpleValue(Arguments.Replacement[ReplacePos])>
+					<cfset Matcher.appendReplacement( Results , Arguments.Replacement[ReplacePos] )/>
+				<cfelse>
+					<cfset var CurrentReplaceFunc = Arguments.Replacement[ReplacePos] />
+					<cfset Matcher.appendReplacement
+						( Results
+						, CurrentReplaceFunc( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
+						)/>
+				</cfif>
+
+				<cfif ++ReplacePos GT ArrayLen(Arguments.Replacement)>
+					<cfset ReplacePos = 1 />
+				</cfif>
+
+			<cfelse>
+
+				<cfset Matcher.appendReplacement
+					( Results
+					, Arguments.Replacement( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
+					)/>
+
+			</cfif>
+
+			<cfif ++ReplacementsMade EQ Arguments.Limit>
+				<cfbreak/>
+			</cfif>
+
+		</cfloop>
+
+		<cfset Matcher.appendTail(Results)/>
+
+		<cfreturn Results.toString() />
+	</cffunction>
+
+
+	<cffunction name="split" returntype="Array" output="false" access="public" action>
+		<cfargument name="Text"         type="String"  required_ />
+		<cfargument name="Start"        type="Numeric" optional  />
+		<cfargument name="Limit"        type="Numeric" default=0  hint="The maximum number of times a split is made (i.e. limit+1=max array size)"/>
+		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
+		<cfargument name="Callback"     type="any"     optional />
+		<cfargument name="CallbackData" type="Struct"  optional hint="Extra data which is passed in to callback function." />
+
+		<cfset var Offset = 1 />
+		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
+			<cfset var Prefix = Left(Arguments.Text,Arguments.Start) />
+			<cfset Offset = 1+Arguments.Start />
+			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
+		</cfif>
+
+		<cfif StructKeyExists(Arguments,'Callback')>
+			<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
+			<cfset var TextPos = 1 />
+			<cfset var ArrayPos = 1 />
+			<cfset var Results = [''] />
+			<cfif NOT StructKeyExists(Arguments,'CallbackData')>
+				<cfset Arguments.CallbackData = {} />
+			</cfif>
+
+			<cfloop condition="Matcher.find(TextPos-1)">
+
+				<cfif Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
+
+					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.start()+1-TextPos) />
+					<cfset TextPos = Matcher.end()+1 />
+
+					<cfset ArrayPos++ />
+					<cfset Results[ArrayPos] = '' />
+
+					<cfif Arguments.Limit AND ArrayLen(Results) GT Arguments.Limit>
+						<cfbreak />
+					</cfif>
+				<cfelse>
+					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.end()+1-TextPos) />
+					<cfset TextPos = Matcher.end()+1 />
+				</cfif>
+
+			</cfloop>
+
+			<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,len(Arguments.Text)) />
+
+		<cfelse>
+			<cfif Arguments.Limit>
+				<!---
+					NOTE:
+					For java.util.regex, limit is array length.
+					For cfregex, limit is number of times the action occurs.
+					Therefor, must add one...
+				--->
+				<cfset var Results = Variables.PatternObject.split(Arguments.Text,Arguments.Limit+1) />
+			<cfelse>
+				<cfset var Results = Variables.PatternObject.split(Arguments.Text) />
+			</cfif>
+		</cfif>
+
+		<cfif isDefined('Prefix') AND ArrayLen(Results)>
+			<cfset Results[1] = Prefix & Results[1] />
+		</cfif>
+
+		<cfreturn Results />
+	</cffunction>
+
+	<!---
+		/// EXTERNAL ///
+	--->
+
+
+
+	<!---
+		CALLBACK SAMPLES
+
+		A callback function can be used with the following functions:
+		.replace
+		.match
+		.split
+
+		A callback is called each time a match is found, and allows for
+		conditional behaviour to be executed at this point,
+		to change how the function behaves towards the match.
+
+		A Replace Callback determines what text to use for replacement.
+		A Match Callback determines whether to include or exclude the match in results.
+		A Split Callback determines whether to split or not at the match.
+
+		The callbacks are identical except for returntype.
+		(For Replace it returns text, for everything else, it returns a boolean.)
+
+		See http://docs.cfregex.net/Callbacks.html
+
+		<cffunction name="ReplaceCallback" returntype="String" output="false">
+			<cfargument name="Pos"         type="Numeric" required_  hint="The start position of the match."  />
+			<cfargument name="Len"         type="Numeric" required_  hint="The length of the match."          />
+			<cfargument name="Match"       type="String"  required_  hint="The text of the match."            />
+			<cfargument name="Groups"      type="Array"   required_  hint="Array of group information."       />
+			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
+			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
+
+			<cfreturn 'replacement text' />
+		</cffunction>
+
+
+		<cffunction name="BooleanCallback" returntype="Boolean" output="false">
+			<cfargument name="Pos"         type="Numeric" required_  hint="The start position of the match."  />
+			<cfargument name="Len"         type="Numeric" required_  hint="The length of the match."          />
+			<cfargument name="Match"       type="String"  required_  hint="The text of the match."            />
+			<cfargument name="Groups"      type="Array"   required_  hint="Array of group information."       />
+			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
+			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
+
+			<cfreturn true />
+		</cffunction>
+
+	--->
+
+
+</cfcomponent>
\ No newline at end of file
diff --git a/src/legacy/functions/include-all.cfm b/src/legacy/functions/include-all.cfm
new file mode 100644
index 0000000..800e589
--- /dev/null
+++ b/src/legacy/functions/include-all.cfm (view file)
@@ -0,0 +1,10 @@
+<cfif NOT isDefined('RegexCompile')>
+	<cfinclude template="RegexCompile.cfm"/>
+	<cfinclude template="RegexEscape.cfm"/>
+	<cfinclude template="RegexFind.cfm"/>
+	<cfinclude template="RegexMatch.cfm"/>
+	<cfinclude template="RegexMatches.cfm"/>
+	<cfinclude template="RegexQuote.cfm"/>
+	<cfinclude template="RegexReplace.cfm"/>
+	<cfinclude template="RegexSplit.cfm"/>
+</cfif>
\ No newline at end of file
diff --git a/src/legacy/regex.cfm b/src/legacy/regex.cfm
new file mode 100644
index 0000000..f82abca
--- /dev/null
+++ b/src/legacy/regex.cfm (view file)
@@ -0,0 +1,122 @@
+<!---
+	WHAT IS THIS?
+
+	Railo support custom tags written as CFCs, providing more
+	flexibility than traditional CFM-based custom tag, and also
+	allowing a CFC to act as both a tag and an object.
+
+	ACF and OBD do not support CFC-based custom tags (yet?), so this
+	traditional custom tag will act as a proxy to a CFC of the same
+	name, calling appropriate functions to replicate the behaviour
+	of a CFC-based tag (not all features can be implemented).
+
+	For details on Railo's core implementation, see:
+	http://wiki.getrailo.org/wiki/3-2:CFC_based_Custom_Tags
+
+	For the latest version of this file, see:
+	https://gist.github.com/1003819
+
+
+	LICENSING:
+
+	This file may be licensed under the following licenses:
+
+		GPL v3 or any later version:
+			http://www.gnu.org/licenses/gpl-3.0.html
+
+		LGPL v2.1 or any later version:
+			http://www.opensource.org/licenses/lgpl-2.1.php
+
+		Apache License v2:
+			http://www.apache.org/licenses/LICENSE-2.0
+
+	It is hoped that whichever license used, any improvements to this
+	file are published, so that all CFML programmers can benefit.
+
+
+	NOT SUPPORTED:
+
+		* Parent tags
+		    (not possible to implement?)
+
+		* Re-evaluating body
+		    (return from onEndTag() is ignored; too much effort)
+
+		* Modification of ThisTag.GeneratedContent
+		    (no known way to retrieve value from onEndTag?)
+
+
+	NOT YET IMPLEMENTED:
+
+		* Static Metadata validation
+		    (should be possible, but not needed now).
+
+--->
+<cftry>
+	<cfswitch expression=#ThisTag.ExecutionMode#>
+
+		<cfcase value="START">
+			<cfset ThisTag.CfcName = ListLast(getCurrentTemplatePath(),'/\').replaceAll('\.cfm$','') />
+			<cfset ThisTag.Object = createObject('component',ThisTag.CfcName) />
+
+			<cfif StructKeyExists(ThisTag.Object,'init')>
+				<!--- No support for parent CFCs --->
+				<cfset ThisTag.Object.init( HasEndTag = ThisTag.HasEndTag ) />
+			</cfif>
+
+			<!--- TODO: Validate metadata --->
+
+			<cfif StructKeyExists(ThisTag.Object,'onStartTag')>
+				<cfset ThisTag.RunEndTag = ThisTag.Object.onStartTag
+					( Attributes = Attributes
+					, Caller     = Caller
+					) />
+			<cfelse>
+				<cfset ThisTag.RunEndTag = ThisTag.HasEndTag />
+			</cfif>
+
+			<cfif (NOT ThisTag.HasEndTag) AND StructKeyExists(ThisTag.Object,'onFinally')>
+				<cfset ThisTag.Object.onFinally() />
+			</cfif>
+		</cfcase>
+
+		<cfcase value="END">
+			<cfif ThisTag.RunEndTag AND StructKeyExists(ThisTag.Object,'onEndTag')>
+				<cfset ThisTag.Object.onEndTag
+					( Attributes       = Attributes
+					, Caller           = Caller
+					, GeneratedContent = ThisTag.GeneratedContent
+					) />
+				<!--- TODO: Possible to obtain value from function? --->
+				<cfset ThisTag.GeneratedContent = '' />
+			</cfif>
+			<cfif StructKeyExists(ThisTag.Object,'onFinally')>
+				<cfset ThisTag.Object.onFinally() />
+			</cfif>
+		</cfcase>
+
+	</cfswitch>
+<cfcatch>
+	<!--- INFO: Don't output content on error. --->
+	<cfset ThisTag.GeneratedContent = '' />
+
+	<cfif StructKeyExists(ThisTag,'Object')>
+
+		<cfset ThisTag.ErrorRethrow =
+			StructKeyExists(ThisTag.Object,'onError')
+			AND ThisTag.Object.onError(cfcatch)
+			OR NOT StructKeyExists(ThisTag.Object,'onError')
+		/>
+
+		<cfif StructKeyExists(ThisTag.Object,'onFinally')>
+			<cfset ThisTag.Object.onFinally() />
+		</cfif>
+
+		<cfif ThisTag.ErrorRethrow >
+			<cfrethrow />
+		</cfif>
+	<cfelse>
+		<cfrethrow />
+	</cfif>
+</cfcatch>
+</cftry>
\ No newline at end of file