287 lines
8.2 KiB
Python
287 lines
8.2 KiB
Python
"""
|
|
Java regex standard library APIs implemented in Python.
|
|
java.util.regex package - Pattern, Matcher, MatchResult, PatternSyntaxException.
|
|
"""
|
|
|
|
from .._core import Stub
|
|
from .io import IOException
|
|
from .lang import RuntimeException
|
|
import re as _re
|
|
|
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
|
IllegalStateException = type("IllegalStateException", (RuntimeException,), {})
|
|
|
|
__all__ = [
|
|
"Pattern", "Matcher", "MatchResult", "PatternSyntaxException",
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# PatternSyntaxException
|
|
# ============================================================
|
|
|
|
class PatternSyntaxException(IllegalArgumentException):
|
|
def __init__(self, desc, regex, index):
|
|
super().__init__(f"{desc}: {regex} at index {index}")
|
|
self._desc = desc
|
|
self._regex = regex
|
|
self._index = index
|
|
|
|
def getDescription(self):
|
|
return self._desc
|
|
|
|
def getPattern(self):
|
|
return self._regex
|
|
|
|
def getIndex(self):
|
|
return self._index
|
|
|
|
def getMessage(self):
|
|
return f"{self._desc} near index {self._index}: {self._regex}"
|
|
|
|
|
|
# ============================================================
|
|
# MatchResult
|
|
# ============================================================
|
|
|
|
class MatchResult(Stub):
|
|
def start(self, group=None):
|
|
raise NotImplementedError
|
|
|
|
def end(self, group=None):
|
|
raise NotImplementedError
|
|
|
|
def group(self, group=None):
|
|
raise NotImplementedError
|
|
|
|
def groupCount(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
# ============================================================
|
|
# Matcher
|
|
# ============================================================
|
|
|
|
class Matcher(MatchResult):
|
|
def __init__(self, pattern, input_str):
|
|
super().__init__()
|
|
self._pattern = pattern
|
|
self._input = input_str
|
|
self._re_pattern = pattern._pattern
|
|
self._match = None
|
|
self._region_start = 0
|
|
self._region_end = len(input_str)
|
|
self._transparent_bounds = False
|
|
self._anchoring_bounds = True
|
|
self._last_match = None
|
|
|
|
def matches(self):
|
|
self._match = self._re_pattern.fullmatch(self._input, self._region_start, self._region_end)
|
|
self._last_match = self._match
|
|
return self._match is not None
|
|
|
|
def find(self, start=None):
|
|
if start is not None:
|
|
self._match = self._re_pattern.search(self._input, start, self._region_end)
|
|
else:
|
|
if self._match is None:
|
|
self._match = self._re_pattern.search(self._input, self._region_start, self._region_end)
|
|
else:
|
|
pos = self._match.end()
|
|
self._match = self._re_pattern.search(self._input, pos, self._region_end)
|
|
if self._match:
|
|
self._last_match = self._match
|
|
return self._match is not None
|
|
|
|
def lookingAt(self):
|
|
self._match = self._re_pattern.match(self._input, self._region_start, self._region_end)
|
|
self._last_match = self._match
|
|
return self._match is not None
|
|
|
|
def start(self, group=0):
|
|
if self._match is None:
|
|
raise IllegalStateException("No match available")
|
|
if group == 0:
|
|
return self._match.start()
|
|
return self._match.start(group)
|
|
|
|
def end(self, group=0):
|
|
if self._match is None:
|
|
raise IllegalStateException("No match available")
|
|
if group == 0:
|
|
return self._match.end()
|
|
return self._match.end(group)
|
|
|
|
def group(self, group=0):
|
|
if self._match is None:
|
|
raise IllegalStateException("No match available")
|
|
if isinstance(group, str):
|
|
return self._match.group(group)
|
|
return self._match.group(group)
|
|
|
|
def groupCount(self):
|
|
return self._re_pattern.groups
|
|
|
|
def replaceAll(self, replacement):
|
|
return self._re_pattern.sub(replacement, self._input)
|
|
|
|
def replaceFirst(self, replacement):
|
|
return self._re_pattern.sub(replacement, self._input, count=1)
|
|
|
|
def appendReplacement(self, sb, replacement):
|
|
if self._match is None:
|
|
raise IllegalStateException("No match available")
|
|
self._append_pos = getattr(self, '_append_pos', 0)
|
|
sb.append(self._input[self._append_pos:self._match.start()])
|
|
sb.append(self._re_pattern.sub(replacement, self._match.group(), count=1))
|
|
self._append_pos = self._match.end()
|
|
return self
|
|
|
|
def appendTail(self, sb):
|
|
self._append_pos = getattr(self, '_append_pos', 0)
|
|
sb.append(self._input[self._append_pos:])
|
|
return sb
|
|
|
|
def reset(self, input_str=None):
|
|
if input_str is not None:
|
|
self._input = input_str
|
|
self._region_end = len(input_str)
|
|
self._match = None
|
|
self._last_match = None
|
|
return self
|
|
|
|
def region(self, start, end):
|
|
self._region_start = start
|
|
self._region_end = end
|
|
return self
|
|
|
|
def regionStart(self):
|
|
return self._region_start
|
|
|
|
def regionEnd(self):
|
|
return self._region_end
|
|
|
|
def hasTransparentBounds(self):
|
|
return self._transparent_bounds
|
|
|
|
def hasAnchoringBounds(self):
|
|
return self._anchoring_bounds
|
|
|
|
def useAnchoringBounds(self, value):
|
|
self._anchoring_bounds = value
|
|
return self
|
|
|
|
def useTransparentBounds(self, value):
|
|
self._transparent_bounds = value
|
|
return self
|
|
|
|
def hitEnd(self):
|
|
return self._match is not None and self._match.end() >= self._region_end
|
|
|
|
def requireEnd(self):
|
|
return self._match is not None and not self.hitEnd()
|
|
|
|
def toMatchResult(self):
|
|
result = Matcher(self._pattern, self._input)
|
|
result._match = self._match
|
|
result._last_match = self._last_match
|
|
return result
|
|
|
|
def pattern(self):
|
|
return self._pattern
|
|
|
|
def toString(self):
|
|
return f"Matcher[pattern={self._pattern.pattern()}]"
|
|
|
|
def namedGroups(self):
|
|
return self._re_pattern.groupindex
|
|
|
|
|
|
class IllegalStateException(RuntimeException):
|
|
pass
|
|
|
|
|
|
# ============================================================
|
|
# Pattern
|
|
# ============================================================
|
|
|
|
class Pattern(Stub):
|
|
CANON_EQ = 128
|
|
CASE_INSENSITIVE = 2
|
|
COMMENTS = 4
|
|
DOTALL = 32
|
|
LITERAL = 16
|
|
MULTILINE = 8
|
|
UNICODE_CASE = 64
|
|
UNICODE_CHARACTER_CLASS = 256
|
|
UNIX_LINES = 1
|
|
|
|
_re_flags_map = {
|
|
0: 0, # default
|
|
2: _re.IGNORECASE,
|
|
8: _re.MULTILINE,
|
|
32: _re.DOTALL,
|
|
64: _re.UNICODE,
|
|
4: _re.VERBOSE,
|
|
}
|
|
|
|
def __init__(self, regex, flags=0):
|
|
super().__init__()
|
|
self._regex = str(regex)
|
|
self._flags = flags
|
|
re_flags = 0
|
|
for java_flag, py_flag in self._re_flags_map.items():
|
|
if flags & java_flag:
|
|
re_flags |= py_flag
|
|
try:
|
|
self._pattern = _re.compile(self._regex, re_flags)
|
|
except _re.error as e:
|
|
raise PatternSyntaxException(str(e), self._regex, e.pos if hasattr(e, 'pos') else 0)
|
|
|
|
@staticmethod
|
|
def compile(regex, flags=0):
|
|
return Pattern(regex, flags)
|
|
|
|
def matcher(self, input_str):
|
|
return Matcher(self, input_str)
|
|
|
|
def split(self, input_str, limit=0):
|
|
if limit <= 0:
|
|
parts = self._pattern.split(str(input_str))
|
|
if limit == 0:
|
|
while parts and parts[-1] == "":
|
|
parts.pop()
|
|
else:
|
|
parts = self._pattern.split(str(input_str), maxsplit=limit - 1)
|
|
while len(parts) < limit:
|
|
parts.append("")
|
|
return parts
|
|
|
|
def splitAsStream(self, input_str):
|
|
return iter(self.split(input_str))
|
|
|
|
@staticmethod
|
|
def matches(regex, input_str):
|
|
return bool(_re.fullmatch(regex, str(input_str)))
|
|
|
|
@staticmethod
|
|
def quote(s):
|
|
return _re.escape(str(s))
|
|
|
|
def flags(self):
|
|
return self._flags
|
|
|
|
def pattern(self):
|
|
return self._regex
|
|
|
|
def toString(self):
|
|
return self._regex
|
|
|
|
def predicate(self):
|
|
return lambda s: bool(self._pattern.fullmatch(str(s)))
|
|
|
|
def asPredicate(self):
|
|
return self.predicate()
|
|
|
|
def asMatchPredicate(self):
|
|
return lambda s: bool(self._pattern.fullmatch(str(s))) |