Sorcerer's IsleCode cfRegex / files

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