8082ddb Initial commit of cfRegex at v0.3
- Authored by Peter Boughton at Fri 22 Oct 2021, 20:17
- Committed by Peter Boughton at Fri 22 Oct 2021, 20:33
- tag: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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