Sorcerer's IsleCode cfRegex / files

  1<!--- cfRegex v0.4 | (c) Peter Boughton | License: LGPLv3 | Website: https://www.sorcerersisle.com/software/cfregex --->
  2<cfcomponent output=false >
  3
  4	<!---
  5		NOTE: This is a dual-purpose Object CFC and CustomTag CFC
  6	--->
  7
  8
  9	<cffunction name="init" returntype="any" output=false>
 10		<cfset StructDelete(This,'init') />
 11
 12		<cfset Variables.Modes =
 13			{ UNIX_LINES       = 1
 14			, CASE_INSENSITIVE = 2
 15			, COMMENTS         = 4
 16			, MULTILINE        = 8
 17			, DOTALL           = 32
 18			, UNICODE_CASE     = 64
 19			, CANON_EQ         = 128
 20			}/>
 21
 22		<!--- TODO: Get defaults from admin settings. --->
 23		<!--- If custom tag, default COMMENTS on, otherwise no defaults. --->
 24		<cfset Variables.DefaultModes = Variables.Modes['COMMENTS'] * StructKeyExists(Arguments,'HasEndTag') />
 25
 26		<!--- INFO: If FuncName is escape or quote, don't compile regex, just return object. --->
 27		<cfif StructKeyExists(Arguments,'FuncName') AND ListFindNoCase('escape,quote',Arguments.FuncName)>
 28			<cfif StructKeyExists(Arguments,'Text') >
 29				<cfset Variables.PatternText = Arguments.Text />
 30			<cfelse>
 31				<cfset Variables.PatternText = Arguments.Pattern />
 32			</cfif>
 33			<cfif NOT StructKeyExists(Arguments,'Modes')>
 34				<cfset Arguments.Modes = Variables.DefaultModes />
 35			</cfif>
 36			<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
 37			<cfreturn This />
 38		</cfif>
 39
 40		<!--- INFO: If not tag, pass to .compile(...) --->
 41		<cfif NOT StructKeyExists(Arguments,'HasEndTag')>
 42			<cfreturn This.compile(ArgumentCollection=Arguments) />
 43		</cfif>
 44
 45		<cfif NOT Arguments.HasEndTag >
 46			<cfthrow
 47				message = "The cfregex tag must have a closing tag."
 48				type    = "cfRegex.Tag.MissingEndTag"
 49			/>
 50		</cfif>
 51
 52	</cffunction>
 53
 54
 55	<!---
 56		\\\ TAG FUNCS \\\
 57	--->
 58
 59	<cffunction name="onStartTag" returntype="boolean" output=false >
 60		<cfargument name="Attributes" type="Struct" required=true />
 61		<cfargument name="Caller"     type="Struct" required=true />
 62
 63		<cfreturn true />
 64	</cffunction>
 65
 66
 67	<cffunction name="onEndTag" returntype="boolean" output=false >
 68		<cfargument name="Attributes" type="Struct" required=true />
 69		<cfargument name="Caller"     type="Struct" required=true />
 70		<cfargument name="GeneratedContent" type="String" required=true />
 71
 72		<cfif StructKeyExists(Arguments.Attributes,'Action')>
 73
 74			<!--- TODO: Consider Modifiers.
 75			~NoCase
 76			~First
 77			--->
 78
 79			<cfif StructKeyExists(This,Arguments.Attributes.Action)
 80				AND StructKeyExists(getMetaData(This[Arguments.Attributes.Action]),'Action')
 81				>
 82				<!--- TODO: Validate. --->
 83			<cfelse>
 84				<cfthrow
 85					message = "Invalid Action of '#Arguments.Attributes.Action#'"
 86					detail  = "Please see cfRegex documentation for valid Action values."
 87					type    = "cfRegex.Tag.InvalidAction"
 88				/>
 89			</cfif>
 90		<cfelseif StructKeyExists(Arguments.Attributes,'Text')>
 91			<!--- Input Text exists - check main actions. --->
 92			<cfset var CurAction = "" />
 93			<cfloop index="CurAction" list="#StructKeyList(This)#">
 94				<cfif ListFindNoCase('onEndTag,onStartTag',CurAction)><cfcontinue /></cfif>
 95				<cfif StructKeyExists(getMetaData(This[CurAction]),'Action') AND StructKeyExists(Arguments.Attributes,CurAction)>
 96					<cfset Arguments.Attributes.Action = CurAction />
 97				</cfif>
 98			</cfloop>
 99			<cfif NOT StructKeyExists(Arguments.Attributes,'Action')>
100				<cfthrow
101					message = "No action specified, unable to detect correct action."
102					detail  = "Please see cfRegex documentation for valid Action values."
103					type    = "cfRegex.Tag.UnknownAction"
104				/>
105			</cfif>
106		<cfelseif StructKeyExists(Arguments.Attributes,'escape')>
107			<cfset Arguments.Attributes.Action = "Escape" />
108		<cfelseif StructKeyExists(Arguments.Attributes,'quote')>
109			<cfset Arguments.Attributes.Action = "Quote" />
110		<cfelse>
111			<cfset Arguments.Attributes.Action = 'Compile' />
112		</cfif>
113
114
115		<cfif NOT StructKeyExists(Arguments.Attributes,'Pattern')>
116			<cfset Arguments.Attributes.Pattern = Arguments.GeneratedContent />
117		</cfif>
118		<cfif NOT StructKeyExists(Arguments.Attributes,'Modes')>
119			<cfset Arguments.Attributes.Modes = Variables.DefaultModes />
120		</cfif>
121
122		<cfif Arguments.Attributes.Action NEQ 'Compile'>
123			<cfset compilePattern(ArgumentCollection=Arguments.Attributes) />
124		</cfif>
125
126		<cfset var Result = This[Arguments.Attributes.Action](ArgumentCollection=Arguments.Attributes) />
127
128		<cfif StructKeyExists(Arguments.Attributes,'Name')>
129			<cfset SetVariable("Caller.#Arguments.Attributes.Name#",Result) />
130		<cfelseif StructKeyExists(Arguments.Attributes,'Variable')>
131			<cfset SetVariable("Caller.#Arguments.Attributes.Variable#",Result) />
132		<cfelse>
133			<cfset SetVariable("Caller.cfregex",Result) />
134		</cfif>
135
136		<cfreturn false />
137	</cffunction>
138
139	<!---
140		/// TAG FUNCS ///
141	--->
142
143	<!---
144		\\\ INTERNAL \\\
145	--->
146
147	<cffunction name="parseModes" returntype="Numeric" output="false" access="private">
148		<cfargument name="ModeList"           type="String"  required=true />
149		<cfargument name="IgnoreInvalidModes" type="Boolean" default="false"/>
150		<cfset var CurrentMode = ""/>
151		<cfset var ResultMode = 0/>
152
153		<cfloop index="CurrentMode" list="#Arguments.ModeList#">
154
155			<cfif isNumeric(CurrentMode)>
156				<cfset ResultMode = BitOr( ResultMode , CurrentMode )/>
157
158			<cfelseif StructKeyExists( Variables.Modes , CurrentMode )>
159				<cfset ResultMode = BitOr( ResultMode , Variables.Modes[CurrentMode] )/>
160
161			<cfelseif NOT Arguments.IgnoreInvalidModes>
162				<cfthrow
163					message = "Invalid Mode!"
164					detail  = "Mode [#CurrentMode#] is not supported."
165					type    = "cfRegex.Compile.InvalidMode"
166				/>
167
168			</cfif>
169
170		</cfloop>
171
172		<cfreturn ResultMode />
173	</cffunction>
174
175
176	<cffunction name="compilePattern" returntype="void" output="false" access="private">
177		<cfargument name="Pattern" type="String" required=true />
178		<cfargument name="Modes"   type="String" required=true />
179
180		<cfset Variables.PatternText = Arguments.Pattern />
181
182		<cfset Variables.ActiveModes = parseModes(Arguments.Modes) />
183
184		<cfset Variables.PatternObject = createObject("java","java.util.regex.Pattern")
185			.compile( Arguments.Pattern , Variables.ActiveModes ) />
186			
187		<cfset StructDelete(Variables,'PatternGroupNames') />
188
189	</cffunction>
190
191
192	<cffunction name="buildMatchInfo" returntype="Struct" output="false" access="private">
193		<cfargument name="Matcher"    type="any"     required=true />
194		<cfargument name="PosOffset"  type="Numeric" optional />
195		<cfargument name="GroupNames" type="any"     optional />
196
197		<cfset var MatchInfo =
198			{ Match  = Matcher.group()
199			, Groups = []
200			} />
201
202		<cfif StructKeyExists(Arguments,'PosOffset')>
203			<cfset MatchInfo.Pos    = Arguments.PosOffset+Matcher.start() />
204			<cfset MatchInfo.Len    = Matcher.end()-Matcher.start() />
205		</cfif>
206
207		<cfset var CurGroup = 0 />
208		<cfloop index="CurGroup" from=1 to=#Matcher.groupCount()#>
209			<cfif StructKeyExists(Arguments,'PosOffset')>
210				<cfset MatchInfo.Groups[CurGroup] =
211					{ Pos   = Arguments.PosOffset+Matcher.start(CurGroup)
212					, Len   = Matcher.end(CurGroup)-Matcher.start(CurGroup)
213					, Match = Matcher.group(JavaCast('int',CurGroup))
214					} />
215			<cfelse>
216				<cfset MatchInfo.Groups[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
217			</cfif>
218		</cfloop>
219
220		<cfif not StructKeyExists(Arguments,'GroupNames') and find('(?<',Variables.PatternText) >
221			<cfset Arguments.GroupNames = extractGroupNames() />
222		</cfif>
223
224		<cfif StructKeyExists(Arguments,'GroupNames')>
225			<cfif isSimpleValue(Arguments.GroupNames)>
226				<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
227			</cfif>
228			<cfif ArrayLen(Arguments.GroupNames)>
229				<cfset var i = 0 />
230				<cfset MatchInfo.NamedGroups = {} />
231				<cfloop index="i" from="1" to="#Min(ArrayLen(Arguments.GroupNames),ArrayLen(MatchInfo.Groups))#">
232					<cfset MatchInfo.NamedGroups[ Arguments.GroupNames[i] ] = MatchInfo.Groups[i] />
233				</cfloop>
234			</cfif>
235		</cfif>
236
237		<cfreturn MatchInfo />
238	</cffunction>
239
240
241	<cffunction name="extractGroupNames" returntype="Array" output="false" access="private">
242		<cfif not StructKeyExists(Variables,'PatternGroupNames') >
243			<!--- Need to extract group names from pattern, because Java doesn't provide any Matcher methods for it. --->
244			<!--- The handling of \Q..\E should be improved, but is very rarely used.  --->
245			<cfset Variables.PatternGroupNames = new Regex('(?<=\(\?<)[A-Za-z][A-Za-z0-9]*(?=>)')
246				.match( variables.PatternText.replaceAll('(?<!\\)\\Q([^\\]+|\\(?!E))*+\\E','') )
247				/>
248		</cfif>
249		<cfreturn Variables.PatternGroupNames />
250	</cffunction>
251
252
253	<!---
254		/// INTERNAL ///
255	--->
256
257
258	<cffunction name="compile" returntype="Regex" output="false" access="public" action>
259		<cfargument name="Pattern" type="String" required=true />
260		<cfargument name="Modes"   type="String" default="#Variables.DefaultModes#" />
261		<cfset StructDelete(This,'compile') />
262		<cfset StructDelete(This,'onStartTag') />
263		<cfset StructDelete(This,'onEndTag') />
264
265		<cfset compilePattern(ArgumentCollection=Arguments) />
266
267		<cfreturn this />
268	</cffunction>
269
270	<!---
271		\\\ EXTERNAL \\\
272	--->
273
274	<cffunction name="find" returntype="Array" output="false" access="public" action>
275		<cfargument name="Text"       type="String"  required=true  />
276		<cfargument name="Start"      type="Numeric" default=1  />
277		<cfargument name="Limit"      type="Numeric" default=0 />
278		<cfargument name="ReturnType" type="String"  default="pos" />
279
280		<cfif NOT ListFindNoCase('pos,sub,info',Arguments.ReturnType)>
281			<cfthrow message="Unknown returntype" />
282		</cfif>
283
284		<cfset var Offset = Max(1,Arguments.Start) />
285		<cfif Offset GT 1>
286			<cfset Arguments.Text = mid(Arguments.Text,Offset,Len(Arguments.Text)) />
287		</cfif>
288
289		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
290		<cfset var Results = [] />
291
292		<cfloop condition="Matcher.find()">
293			<cfswitch expression=#LCase(Arguments.ReturnType)#>
294				<cfcase value="pos">
295					<cfset var CurMatch = Offset+Matcher.start() />
296				</cfcase>
297				<cfcase value="sub">
298					<cfset var CurMatch =
299						{ pos = [Offset+Matcher.start()]
300						, len = [Matcher.end()-Matcher.start()]
301						} />
302					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
303						<cfset ArrayAppend(CurMatch.pos,Offset+Matcher.start(CurGroup)) />
304						<cfset ArrayAppend(CurMatch.len,Matcher.end(CurGroup)-Matcher.start(CurGroup)) />
305					</cfloop>
306				</cfcase>
307				<cfcase value="info">
308					<cfset var CurMatch = buildMatchInfo(Matcher,Offset) />
309				</cfcase>
310			</cfswitch>
311			<cfset ArrayAppend( Results , CurMatch ) />
312
313			<cfif ArrayLen(Results) EQ Arguments.Limit>
314				<cfbreak />
315			</cfif>
316		</cfloop>
317
318		<cfreturn Results />
319	</cffunction>
320
321
322	<cffunction name="match" returntype="Array" output="false" access="public" action>
323		<cfargument name="Text"         type="String"   required=true  />
324		<cfargument name="Start"        type="Numeric"  optional  />
325		<cfargument name="Limit"        type="Numeric"  default=0 />
326		<cfargument name="ReturnType"   type="String"   default="match" hint="match|groups|namedgroups|full" />
327		<cfargument name="GroupNames"   type="any"      default="" hint="Required if returnType=NamedGroup and no native named groups in pattern." />
328		<cfargument name="Callback"     type="any"      optional   hint="Function called to determine if a match is included in results." />
329		<cfargument name="CallbackData" type="Struct"   optional   hint="Extra data which is passed in to callback function." />
330
331		<cfif NOT ListFindNoCase('match,groups,namedgroups,full',Arguments.ReturnType)>
332			<cfthrow message="Unknown returntype" />
333		</cfif>
334
335		<cfset var Offset = 1 />
336		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start>
337			<cfset Arguments.Text = mid(Arguments.Text,Arguments.Start,Len(Arguments.Text)) />
338			<cfset Offset = Arguments.Start+1 />
339		</cfif>
340
341		<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
342		<cfset var Results = [] />
343
344		<cfif StructKeyExists(Arguments,'GroupNames') AND isSimpleValue(Arguments.GroupNames)>
345			<cfset Arguments.GroupNames = ListToArray(Arguments.GroupNames) />
346		</cfif>
347		<cfif isEmpty(Arguments.GroupNames) and Arguments.ReturnType eq 'namedgroups' and find('(?<',Variables.PatternText) >
348			<cfset Arguments.GroupNames = extractGroupNames() />
349		</cfif>
350		<cfif Arguments.ReturnType eq 'namedgroups' and (not StructKeyExists(Arguments,'GroupNames') or isEmpty(Arguments.GroupNames) )>
351			<cfthrow message="No named groups in pattern, and missing or empty GroupNames argument." />
352		</cfif>
353
354		<cfloop condition="Matcher.find()">
355
356			<cfif StructKeyExists(Arguments,'Callback')>
357				<cfif NOT StructKeyExists(Arguments,'CallbackData')>
358					<cfset Arguments.CallbackData = {} />
359				</cfif>
360				<cfif NOT Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
361					<cfcontinue />
362				</cfif>
363			</cfif>
364
365			<cfswitch expression=#Arguments.ReturnType#>
366				<cfcase value="match">
367					<cfset var CurMatch = Matcher.Group() />
368				</cfcase>
369				<cfcase value="groups">
370					<cfset var CurMatch = [] />
371					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
372						<cfset CurMatch[CurGroup] = Matcher.group(JavaCast('int',CurGroup)) />
373					</cfloop>
374				</cfcase>
375				<cfcase value="namedgroups">
376					<cfset var CurMatch = {} />
377					<cfloop index="local.CurGroup" from=1 to=#Matcher.groupCount()#>
378						<cfset CurMatch[Arguments.GroupNames[CurGroup]] = Matcher.group(JavaCast('int',CurGroup)) />
379					</cfloop>
380				</cfcase>
381				<cfcase value="full">
382					<cfset var CurMatch = buildMatchInfo(Matcher=Matcher,GroupNames=Arguments.GroupNames) />
383				</cfcase>
384			</cfswitch>
385
386			<cfset ArrayAppend( Results , CurMatch ) />
387
388			<cfif ArrayLen(Results) EQ Arguments.Limit>
389				<cfbreak />
390			</cfif>
391		</cfloop>
392
393		<cfreturn Results />
394	</cffunction>
395
396
397	<cffunction name="matches" returntype="any" output="false" access="public" action>
398		<cfargument name="Text"       type="String"  required=true />
399		<cfargument name="ReturnType" type="String"  optional hint="exact,partial,start,end,count" />
400
401		<cfif StructKeyExists(Arguments,'ReturnType')>
402			<cfset Arguments.ReturnType = LCase(Arguments.ReturnType) />
403
404		<cfelse>
405			<cfif StructKeyExists(Arguments,'Exact') AND Arguments.Exact >
406				<cfset Arguments.ReturnType = "exact" />
407			<cfelseif StructKeyExists(Arguments,'Count') AND Arguments.Count >
408				<cfset Arguments.ReturnType = "count" />
409			<cfelse>
410				<cfif StructKeyExists(Arguments,'at')>
411					<cfif Arguments.At EQ 'anywhere'>
412						<cfset Arguments.ReturnType = 'partial' />
413					<cfelse>
414						<cfset Arguments.ReturnType = LCase(Arguments.At) />
415					</cfif>
416				<cfelseif StructKeyExists(Arguments,'Partial') AND Arguments.Partial >
417					<cfset Arguments.ReturnType = "partial" />
418				</cfif>
419			</cfif>
420			<cfif NOT StructKeyExists(Arguments,'ReturnType')>
421				<cfset Arguments.ReturnType = 'exact' />
422			</cfif>
423		</cfif>
424
425		<cfswitch expression="#Arguments.ReturnType#">
426			<cfcase value="exact">
427				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).matches() />
428			</cfcase>
429			<cfcase value="count">
430				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
431				<cfset local.Count = 0 />
432				<cfloop condition="Matcher.find()">
433					<cfset local.Count++ />
434				</cfloop>
435				<cfreturn local.Count />
436			</cfcase>
437			<cfcase value="start">
438				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).lookingAt() />
439			</cfcase>
440			<cfcase value="end">
441				<cfset var Matcher = Variables.PatternObject.Matcher(Arguments.Text) />
442				<cfset var LastPos = -1 />
443				<cfloop condition="Matcher.find()">
444					<cfset LastPos = Matcher.end() />
445				</cfloop>
446				<cfreturn (LastPos EQ Len(Arguments.Text)) />
447			</cfcase>
448			<cfcase value="partial">
449				<cfreturn Variables.PatternObject.Matcher(Arguments.Text).find() />
450			</cfcase>
451			<cfdefaultcase>
452				<cfthrow
453					message = "Invalid ReturnType '#Arguments.ReturnType#' for matches"
454					type    = "cfRegex.Match.InvalidArgument.ReturnType"
455				/>
456			</cfdefaultcase>
457		</cfswitch>
458	</cffunction>
459
460
461	<cffunction name="escape" returntype="String" output="false" access="public" action>
462		<cfargument name="ReturnType" type="String" default=REGEX hint="regex|class" />
463		<cfif NOT ListFind('regex,class',LCase(Arguments.ReturnType))>
464			<cfthrow
465				message = "Invalid Argument ReturnType, received [#Arguments.ReturnType#]"
466				detail  = "ReturnType value must be one of 'regex' OR 'class'."
467				type    = "cfRegex.Escape.InvalidArgument.ReturnType"
468			/>
469		</cfif>
470		<cfif NOT StructKeyExists(Variables,'Escaped#Arguments.ReturnType#')>
471			<cfif Arguments.ReturnType EQ 'regex'>
472				<cfset Variables.EscapedRegex = Variables.PatternText.replaceAll('[$^*()+\[\]{}.?\\|]','\\$0') />
473			<cfelse>
474				<cfset Variables.EscapedClass = Variables.PatternText
475					.replaceAll('(.)(?=.*?\1)','')
476					.replaceAll('(^\^|[\\\-\[\]])','\\$0')
477					.replaceAll(chr(9),'\t')
478					.replaceAll(chr(10),'\n')
479					.replaceAll(chr(13),'\r')
480					/>
481			</cfif>
482			<cfif BitAnd(Variables.ActiveModes,Variables.Modes['COMMENTS']) >
483				<cfset Variables['Escaped#Arguments.ReturnType#'] = Variables['Escaped#Arguments.ReturnType#'].replaceAll('##| ','\\$0') />
484			</cfif>
485		</cfif>
486		<cfreturn Variables['Escaped#Arguments.ReturnType#'] />
487	</cffunction>
488
489
490	<cffunction name="quote" returntype="String" output="false" access="public" action>
491		<cfif NOT StructKeyExists(Variables,'Quoted')>
492			<cfset Variables.Quoted = createObject("java","java.util.regex.Pattern").quote(Variables.PatternText) />
493		</cfif>
494		<cfreturn Variables.Quoted />
495	</cffunction>
496
497
498	<cffunction name="replace" returntype="String" output="false" access="public" action>
499		<cfargument name="Text"         type="String"  required=true  />
500		<cfargument name="Replacement"  type="Any"     optional hint="String,Array,Function"/>
501		<cfargument name="Start"        type="Numeric" optional  />
502		<cfargument name="Limit"        type="Numeric" default=0 />
503		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
504		<cfargument name="CallbackData" type="Struct"  optional   hint="Extra data which is passed in to callback function." />
505
506		<cfif StructKeyExists(Arguments,'Callback') >
507			<cfset Arguments.Replacement = Arguments.Callback />
508		<cfelseif NOT StructKeyExists(Arguments,'Replacement')>
509			<cfthrow
510				message = "Missing Argument Replacement"
511				type    = "cfRegex.Replace.MissingArgument"
512			/>
513		</cfif>
514
515		<cfset var Prefix = "" />
516		<cfset var Offset = 1 />
517		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
518			<cfset Offset = Arguments.Start+1 />
519			<cfset Prefix = Left(Arguments.Text,Arguments.Start) />
520			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
521		</cfif>
522
523		<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
524		<cfset var Results = createObject("java","java.lang.StringBuffer").init(Prefix)/>
525		<cfset var ReplacementsMade = 0 />
526		<cfset var ReplacePos = 1 />
527
528		<cfif NOT StructKeyExists(Arguments,'CallbackData')>
529			<cfset Arguments.CallbackData = {} />
530		</cfif>
531
532		<cfloop condition="Matcher.find()">
533
534			<cfif isSimpleValue(Arguments.Replacement)>
535				<cfset Matcher.appendReplacement( Results , Arguments.Replacement )/>
536
537			<cfelseif isArray(Arguments.Replacement)>
538
539				<cfif isSimpleValue(Arguments.Replacement[ReplacePos])>
540					<cfset Matcher.appendReplacement( Results , Arguments.Replacement[ReplacePos] )/>
541				<cfelse>
542					<cfset var CurrentReplaceFunc = Arguments.Replacement[ReplacePos] />
543					<cfset Matcher.appendReplacement
544						( Results
545						, CurrentReplaceFunc( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
546						)/>
547				</cfif>
548
549				<cfif ++ReplacePos GT ArrayLen(Arguments.Replacement)>
550					<cfset ReplacePos = 1 />
551				</cfif>
552
553			<cfelse>
554
555				<cfset Matcher.appendReplacement
556					( Results
557					, Arguments.Replacement( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data = Arguments.CallbackData )
558					)/>
559
560			</cfif>
561
562			<cfif ++ReplacementsMade EQ Arguments.Limit>
563				<cfbreak/>
564			</cfif>
565
566		</cfloop>
567
568		<cfset Matcher.appendTail(Results)/>
569
570		<cfreturn Results.toString() />
571	</cffunction>
572
573
574	<cffunction name="split" returntype="Array" output="false" access="public" action>
575		<cfargument name="Text"         type="String"  required=true />
576		<cfargument name="Start"        type="Numeric" optional  />
577		<cfargument name="Limit"        type="Numeric" default=0  hint="The maximum number of times a split is made (i.e. limit+1=max array size)"/>
578		<cfargument name="GroupNames"   type="any"     default="" hint="Passed into Callback function if provided" />
579		<cfargument name="Callback"     type="any"     optional />
580		<cfargument name="CallbackData" type="Struct"  optional hint="Extra data which is passed in to callback function." />
581
582		<cfset var Offset = 1 />
583		<cfif StructKeyExists(Arguments,'Start') AND Arguments.Start >
584			<cfset var Prefix = Left(Arguments.Text,Arguments.Start) />
585			<cfset Offset = 1+Arguments.Start />
586			<cfset Arguments.Text = Mid(Arguments.Text,Arguments.Start+1,Len(Arguments.Text)) />
587		</cfif>
588
589		<cfif StructKeyExists(Arguments,'Callback')>
590			<cfset var Matcher = Variables.PatternObject.Matcher( Arguments.Text )/>
591			<cfset var TextPos = 1 />
592			<cfset var ArrayPos = 1 />
593			<cfset var Results = [''] />
594			<cfif NOT StructKeyExists(Arguments,'CallbackData')>
595				<cfset Arguments.CallbackData = {} />
596			</cfif>
597
598			<cfloop condition="Matcher.find(TextPos-1)">
599
600				<cfif Arguments.Callback( ArgumentCollection=buildMatchInfo(Matcher,Offset,Arguments.GroupNames) , Data=Arguments.CallbackData )>
601
602					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.start()+1-TextPos) />
603					<cfset TextPos = Matcher.end()+1 />
604
605					<cfset ArrayPos++ />
606					<cfset Results[ArrayPos] = '' />
607
608					<cfif Arguments.Limit AND ArrayLen(Results) GT Arguments.Limit>
609						<cfbreak />
610					</cfif>
611				<cfelse>
612					<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,Matcher.end()+1-TextPos) />
613					<cfset TextPos = Matcher.end()+1 />
614				</cfif>
615
616			</cfloop>
617
618			<cfset Results[ArrayPos] &= mid(Arguments.Text,TextPos,len(Arguments.Text)) />
619
620		<cfelse>
621			<cfif Arguments.Limit>
622				<!---
623					NOTE:
624					For java.util.regex, limit is array length.
625					For cfregex, limit is number of times the action occurs.
626					Therefor, must add one...
627				--->
628				<cfset var Results = Variables.PatternObject.split(Arguments.Text,Arguments.Limit+1) />
629			<cfelse>
630				<cfset var Results = Variables.PatternObject.split(Arguments.Text) />
631			</cfif>
632		</cfif>
633
634		<cfif isDefined('Prefix') AND ArrayLen(Results)>
635			<cfset Results[1] = Prefix & Results[1] />
636		</cfif>
637
638		<cfreturn Results />
639	</cffunction>
640
641	<cffunction name="findPos"          access="public"><cfreturn this.find   (argumentcollection=arguments,returntype='pos')        /></cffunction>
642	<cffunction name="findSub"          access="public"><cfreturn this.find   (argumentcollection=arguments,returntype='sub')        /></cffunction>
643	<cffunction name="findInfo"         access="public"><cfreturn this.find   (argumentcollection=arguments,returntype='info')       /></cffunction>
644	<cffunction name="matchGroups"      access="public"><cfreturn this.match  (argumentcollection=arguments,returntype='groups')     /></cffunction>
645	<cffunction name="matchNamedGroups" access="public"><cfreturn this.match  (argumentcollection=arguments,returntype='namedgroups')/></cffunction>
646	<cffunction name="matchFull"        access="public"><cfreturn this.match  (argumentcollection=arguments,returntype='full')       /></cffunction>
647	<cffunction name="matchesExact"     access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='exact')      /></cffunction>
648	<cffunction name="matchesPartial"   access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='partial')    /></cffunction>
649	<cffunction name="matchesStart"     access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='start')      /></cffunction>
650	<cffunction name="matchesEnd"       access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='end')        /></cffunction>
651	<cffunction name="matchesCount"     access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='count')      /></cffunction>
652	<cffunction name="count"            access="public"><cfreturn this.matches(argumentcollection=arguments,returntype='count')      /></cffunction>
653	<cffunction name="escapeClass"      access="public"><cfreturn this.escape (argumentcollection=arguments,returntype='class')      /></cffunction>
654
655	<!---
656		/// EXTERNAL ///
657	--->
658
659
660
661	<!---
662		CALLBACK SAMPLES
663
664		A callback function can be used with the following functions:
665		.replace
666		.match
667		.split
668
669		A callback is called each time a match is found, and allows for
670		conditional behaviour to be executed at this point,
671		to change how the function behaves towards the match.
672
673		A Replace Callback determines what text to use for replacement.
674		A Match Callback determines whether to include or exclude the match in results.
675		A Split Callback determines whether to split or not at the match.
676
677		The callbacks are identical except for returntype.
678		(For Replace it returns text, for everything else, it returns a boolean.)
679
680		See http://docs.cfregex.net/Callbacks.html
681
682		<cffunction name="ReplaceCallback" returntype="String" output="false">
683			<cfargument name="Pos"         type="Numeric" required=true  hint="The start position of the match."  />
684			<cfargument name="Len"         type="Numeric" required=true  hint="The length of the match."          />
685			<cfargument name="Match"       type="String"  required=true  hint="The text of the match."            />
686			<cfargument name="Groups"      type="Array"   required=true  hint="Array of group information."       />
687			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
688			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
689
690			<cfreturn 'replacement text' />
691		</cffunction>
692
693
694		<cffunction name="BooleanCallback" returntype="Boolean" output="false">
695			<cfargument name="Pos"         type="Numeric" required=true  hint="The start position of the match."  />
696			<cfargument name="Len"         type="Numeric" required=true  hint="The length of the match."          />
697			<cfargument name="Match"       type="String"  required=true  hint="The text of the match."            />
698			<cfargument name="Groups"      type="Array"   required=true  hint="Array of group information."       />
699			<cfargument name="NamedGroups" type="Struct"  optional  hint="Struct of named group information." />
700			<cfargument name="Data"        type="Struct"  optional  hint="Struct containing passed-in data." />
701
702			<cfreturn true />
703		</cffunction>
704
705	--->
706
707
708</cfcomponent>