1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2007-2010, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
package scala.util.matching
import java.util.regex.{Pattern, Matcher}
import collection.immutable.List
import collection.{Iterator, Seq}
/** This class provides methods for creating and using regular expressions.
* It is based on the regular expressions of the JDK since 1.4.
*
* <p>
* You can use special pattern syntax construct <code>(?idmsux-idmsux)</code> to switch
* various regex compilation options like <code>CASE_INSENSITIVE</code> or <code>UNICODE_CASE</code>.
* See <code>java.util.regex.Pattern</code> javadoc for details.
* </p>
*
* @author Thibaud Hottelier
* @author Philipp Haller
* @author Martin Odersky
* @version 1.1, 29/01/2008
*
* @param regex A string representing a regular expression
* @param groupNames A mapping from names to indices in capture groups
*/
@SerialVersionUID(-2094783597747625537L)
class Regex(regex: String, groupNames: String*) extends Serializable {
import Regex._
/** The compiled pattern */
val pattern = Pattern.compile(regex)
/** Tries to match target (whole match) and returns
* the matches.
*
* @param target The string to match
* @return The matches
*/
def unapplySeq(target: Any): Option[List[String]] = target match {
case s: java.lang.CharSequence =>
val m = pattern.matcher(s)
if (m.matches) Some((1 to m.groupCount).toList map m.group)
else None
case Match(s) =>
unapplySeq(s)
case _ =>
None
}
/** Return all matches of this regexp in given character sequence as an iterator
*/
def findAllIn(source: java.lang.CharSequence) = new Regex.MatchIterator(source, this, groupNames)
/** Return optionally first matching string of this regexp in given character sequence,
* None if it does not exist.
*/
def findFirstIn(source: java.lang.CharSequence): Option[String] = {
val m = pattern.matcher(source)
if (m.find) Some(m.group) else None
}
/** Return optionally first match of this regexp in given character sequence,
* None if it does not exist.
*/
def findFirstMatchIn(source: java.lang.CharSequence): Option[Match] = {
val m = pattern.matcher(source)
if (m.find) Some(new Match(source, m, groupNames)) else None
}
/** Return optionally match of this regexp at the beginning of the
* given character sequence, or None if regexp matches no prefix
* of the character sequence.
*/
def findPrefixOf(source: java.lang.CharSequence): Option[String] = {
val m = pattern.matcher(source)
if (m.lookingAt) Some(m.group) else None
}
/** Return optionally match of this regexp at the beginning of the
* given character sequence, or None if regexp matches no prefix
* of the character sequence.
*/
def findPrefixMatchOf(source: java.lang.CharSequence): Option[Match] = {
val m = pattern.matcher(source)
if (m.lookingAt) Some(new Match(source, m, groupNames)) else None
}
/** Replaces all matches by a string.
*
* @param target The string to match
* @param replacement The string that will replace each match
* @return The resulting string
*/
def replaceAllIn(target: java.lang.CharSequence, replacement: String): String = {
val m = pattern.matcher(target)
m.replaceAll(replacement)
}
/**
* Replaces all matches using a replacer function.
*
* @param target The string to match.
* @param replacer The function which maps a match to another string.
* @return The target string after replacements.
*/
def replaceAllIn(target: java.lang.CharSequence, replacer: Match => String): String = {
val it = new Regex.MatchIterator(target, this, groupNames).replacementData
while (it.hasNext) {
val matchdata = it.next
it.replace(replacer(matchdata))
}
it.replaced
}
def replaceSomeIn(target: java.lang.CharSequence, replacer: Match => Option[String]): String = {
val it = new Regex.MatchIterator(target, this, groupNames).replacementData
while (it.hasNext) {
val matchdata = it.next
val replaceopt = replacer(matchdata)
if (replaceopt != None) it.replace(replaceopt.get)
}
it.replaced
}
/** Replaces the first match by a string.
*
* @param target The string to match
* @param replacement The string that will replace the match
* @return The resulting string
*/
def replaceFirstIn(target: java.lang.CharSequence, replacement: String): String = {
val m = pattern.matcher(target)
m.replaceFirst(replacement)
}
/** Splits the provided character sequence around matches of this
* regexp.
*
* @param toSplit The character sequence to split
* @return The array of strings computed by splitting the
* input around matches of this regexp
*/
def split(toSplit: java.lang.CharSequence): Array[String] =
pattern.split(toSplit)
/** The string defining the regular expression */
override def toString = regex
}
/** This object defines inner classes that describe
* regex matches. The class hierarchy is as follows.
*
* MatchData
* | \
* MatchIterator Match
*/
object Regex {
/** This class provides methods to access
* the details of a match.
*/
trait MatchData {
/** The source from where the match originated */
val source: java.lang.CharSequence
/** The names of the groups, or some empty sequence if one defined */
val groupNames: Seq[String]
/** The number of subgroups in the pattern (not all of these need to match!) */
def groupCount: Int
/** The index of the first matched character, or -1 if nothing was matched */
def start: Int
/** The index of the first matched character in group <code>i</code>,
* or -1 if nothing was matched for that group */
def start(i: Int): Int
/** The index of the last matched character, or -1 if nothing was matched */
def end: Int
/** The index following the last matched character in group <code>i</code>,
* or -1 if nothing was matched for that group */
def end(i: Int): Int
/** The matched string,
* of <code>null</code> if nothing was matched */
def matched: String =
if (start >= 0) source.subSequence(start, end).toString
else null
/** The matched string in group <code>i</code>,
* or <code>null</code> if nothing was matched */
def group(i: Int): String =
if (start(i) >= 0) source.subSequence(start(i), end(i)).toString
else null
/** All matched subgroups, i.e. not including group(0) */
def subgroups: List[String] = (1 to groupCount).toList map group
/** The char sequence before first character of match,
* or <code>null</code> if nothing was matched */
def before: java.lang.CharSequence =
if (start >= 0) source.subSequence(0, start)
else null
/** The char sequence before first character of match in group <code>i</code>,
* or <code>null</code> if nothing was matched for that group */
def before(i: Int): java.lang.CharSequence =
if (start(i) >= 0) source.subSequence(0, start(i))
else null
/** Returns char sequence after last character of match,
* or <code>null</code> if nothing was matched */
def after: java.lang.CharSequence =
if (end >= 0) source.subSequence(end, source.length)
else null
/** The char sequence after last character of match in group <code>i</code>,
* or <code>null</code> if nothing was matched for that group */
def after(i: Int): java.lang.CharSequence =
if (end(i) >= 0) source.subSequence(end(i), source.length)
else null
private lazy val nameToIndex: Map[String, Int] = Map[String, Int]() ++ ("" :: groupNames.toList).zipWithIndex
/** Returns the group with given name
*
* @param id The group name
* @return The requested group
* @throws <code>NoSuchElementException</code> if the requested
* group name is not defined
*/
def group(id: String): String = nameToIndex.get(id) match {
case None => throw new NoSuchElementException("group name "+id+" not defined")
case Some(index) => group(index)
}
/** The matched string; equivalent to <code>matched.toString</code> */
override def toString = matched
}
/** A case class for a successful match.
*/
class Match(val source: java.lang.CharSequence,
matcher: Matcher,
val groupNames: Seq[String]) extends MatchData {
/** The index of the first matched character */
val start = matcher.start
/** The index following the last matched character */
val end = matcher.end
/** The number of subgroups */
def groupCount = matcher.groupCount
private lazy val starts: Array[Int] =
((0 to groupCount) map matcher.start).toArray
private lazy val ends: Array[Int] =
((0 to groupCount) map matcher.end).toArray
/** The index of the first matched character in group <code>i</code> */
def start(i: Int) = starts(i)
/** The index following the last matched character in group <code>i</code> */
def end(i: Int) = ends(i)
/** The match itself with matcher-dependent lazy vals forced,
* so that match is valid even once matcher is advanced
*/
def force: this.type = { starts; ends; this }
}
/** An extractor object for Matches, yielding the matched string */
object Match {
def unapply(m: Match): Some[String] = Some(m.matched)
}
/** An extractor object that yields groups in the match. */
object Groups {
def unapplySeq(m: Match): Option[Seq[String]] = if (m.groupCount > 0) Some(1 to m.groupCount map m.group) else None
}
/** A class to step through a sequence of regex matches
*/
class MatchIterator(val source: java.lang.CharSequence, val regex: Regex, val groupNames: Seq[String])
extends Iterator[String] with MatchData { self =>
protected val matcher = regex.pattern.matcher(source)
private var nextSeen = false
/** Is there another match? */
def hasNext: Boolean = {
if (!nextSeen) nextSeen = matcher.find()
nextSeen
}
/** The next matched substring of `source' */
def next: String = {
if (!hasNext) throw new NoSuchElementException
nextSeen = false
matcher.group
}
override def toString = super[Iterator].toString
/** The index of the first matched character */
def start: Int = matcher.start
/** The index of the first matched character in group <code>i</code> */
def start(i: Int): Int = matcher.start(i)
/** The index of the last matched character */
def end: Int = matcher.end
/** The index following the last matched character in group <code>i</code> */
def end(i: Int): Int = matcher.end(i)
/** The number of subgroups */
def groupCount = matcher.groupCount
/** Convert to an iterator that yields MatchData elements instead of Strings */
def matchData = new Iterator[Match] {
def hasNext = self.hasNext
def next = { self.next; new Match(source, matcher, groupNames).force }
}
/** Convert to an iterator that yields MatchData elements instead of Strings and has replacement support */
private[matching] def replacementData = new Iterator[Match] with Replacement {
def matcher = self.matcher
def hasNext = self.hasNext
def next = { self.next; new Match(source, matcher, groupNames).force }
}
}
/**
* A trait able to build a string with replacements assuming it has a matcher.
* Meant to be mixed in with iterators.
*/
private[matching] trait Replacement {
protected def matcher: Matcher
private var sb = new java.lang.StringBuffer
def replaced = {
val newsb = new java.lang.StringBuffer(sb)
matcher.appendTail(newsb)
newsb.toString
}
def replace(rs: String) = matcher.appendReplacement(sb, rs)
}
}
|