1041 lines
30 KiB
Python
1041 lines
30 KiB
Python
"""
|
|
Java text standard library APIs implemented in Python.
|
|
java.text package - DateFormat, SimpleDateFormat, DecimalFormat, NumberFormat, etc.
|
|
"""
|
|
|
|
from .._core import Stub
|
|
from .lang import RuntimeException
|
|
import datetime as _datetime
|
|
import time as _time
|
|
import re as _re
|
|
import math as _py_math
|
|
import locale as _locale
|
|
from decimal import Decimal as _Decimal
|
|
|
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
|
|
|
__all__ = [
|
|
"SimpleDateFormat", "DateFormat", "DateFormatSymbols",
|
|
"DecimalFormat", "DecimalFormatSymbols", "NumberFormat",
|
|
"ChoiceFormat", "MessageFormat", "FieldPosition", "ParsePosition",
|
|
"ParseException", "Normalizer",
|
|
"StringCharacterIterator", "CharacterIterator",
|
|
"BreakIterator", "Collator", "CollationKey", "RuleBasedCollator",
|
|
"Annotation", "AttributedCharacterIterator", "AttributedString",
|
|
"Bidi", "Format",
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# ParseException
|
|
# ============================================================
|
|
|
|
class ParseException(Exception):
|
|
def __init__(self, s, error_offset):
|
|
super().__init__(s)
|
|
self._error_offset = error_offset
|
|
|
|
def getErrorOffset(self):
|
|
return self._error_offset
|
|
|
|
|
|
# ============================================================
|
|
# ParsePosition
|
|
# ============================================================
|
|
|
|
class ParsePosition(Stub):
|
|
def __init__(self, index):
|
|
super().__init__()
|
|
self._index = index
|
|
self._error_index = -1
|
|
|
|
def getIndex(self):
|
|
return self._index
|
|
|
|
def setIndex(self, index):
|
|
self._index = index
|
|
|
|
def getErrorIndex(self):
|
|
return self._error_index
|
|
|
|
def setErrorIndex(self, ei):
|
|
self._error_index = ei
|
|
|
|
|
|
# ============================================================
|
|
# FieldPosition
|
|
# ============================================================
|
|
|
|
class FieldPosition(Stub):
|
|
def __init__(self, field):
|
|
super().__init__()
|
|
self._field = field
|
|
self._begin = 0
|
|
self._end = 0
|
|
|
|
def getField(self):
|
|
return self._field
|
|
|
|
def getBeginIndex(self):
|
|
return self._begin
|
|
|
|
def getEndIndex(self):
|
|
return self._end
|
|
|
|
def setBeginIndex(self, bi):
|
|
self._begin = bi
|
|
|
|
def setEndIndex(self, ei):
|
|
self._end = ei
|
|
|
|
|
|
# ============================================================
|
|
# Format (base)
|
|
# ============================================================
|
|
|
|
class Format(Stub):
|
|
def format(self, obj, to_append_to=None, pos=None):
|
|
raise NotImplementedError
|
|
|
|
def parseObject(self, source, pos=None):
|
|
raise NotImplementedError
|
|
|
|
|
|
# ============================================================
|
|
# DateFormat
|
|
# ============================================================
|
|
|
|
class DateFormat(Format):
|
|
SHORT = 3
|
|
MEDIUM = 2
|
|
LONG = 1
|
|
FULL = 0
|
|
DEFAULT = 2
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._calendar = None
|
|
self._number_format = None
|
|
self._lenient = False
|
|
self._timezone = None
|
|
|
|
@staticmethod
|
|
def getDateInstance(style=2, locale=None):
|
|
return SimpleDateFormat("yyyy-MM-dd")
|
|
|
|
@staticmethod
|
|
def getTimeInstance(style=2, locale=None):
|
|
return SimpleDateFormat("HH:mm:ss")
|
|
|
|
@staticmethod
|
|
def getDateTimeInstance(date_style=2, time_style=2, locale=None):
|
|
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
|
|
|
@staticmethod
|
|
def getInstance():
|
|
return SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
|
|
|
def format(self, date, to_append_to=None, pos=None):
|
|
raise NotImplementedError
|
|
|
|
def parse(self, source, pos=None):
|
|
raise NotImplementedError
|
|
|
|
def setLenient(self, lenient):
|
|
self._lenient = lenient
|
|
|
|
def isLenient(self):
|
|
return self._lenient
|
|
|
|
def getCalendar(self):
|
|
return self._calendar
|
|
|
|
def setCalendar(self, cal):
|
|
self._calendar = cal
|
|
|
|
def getNumberFormat(self):
|
|
return self._number_format
|
|
|
|
def setNumberFormat(self, nf):
|
|
self._number_format = nf
|
|
|
|
def getTimeZone(self):
|
|
return self._timezone
|
|
|
|
def setTimeZone(self, tz):
|
|
self._timezone = tz
|
|
|
|
|
|
# ============================================================
|
|
# DateFormatSymbols
|
|
# ============================================================
|
|
|
|
class DateFormatSymbols(Stub):
|
|
def __init__(self, locale=None):
|
|
super().__init__()
|
|
self._months = [
|
|
"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December",
|
|
]
|
|
self._short_months = [
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
]
|
|
self._weekdays = [
|
|
"", "Sunday", "Monday", "Tuesday", "Wednesday",
|
|
"Thursday", "Friday", "Saturday",
|
|
]
|
|
self._short_weekdays = [
|
|
"", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
|
|
]
|
|
self._ampm = ["AM", "PM"]
|
|
self._eras = ["BC", "AD"]
|
|
self._zone_strings = []
|
|
self._local_pattern_chars = "GyMdkHmsSEDFwWahKzZ"
|
|
|
|
def getMonths(self):
|
|
return self._months
|
|
|
|
def setMonths(self, months):
|
|
self._months = months
|
|
|
|
def getShortMonths(self):
|
|
return self._short_months
|
|
|
|
def setShortMonths(self, months):
|
|
self._short_months = months
|
|
|
|
def getWeekdays(self):
|
|
return self._weekdays
|
|
|
|
def setWeekdays(self, wd):
|
|
self._weekdays = wd
|
|
|
|
def getShortWeekdays(self):
|
|
return self._short_weekdays
|
|
|
|
def setShortWeekdays(self, wd):
|
|
self._short_weekdays = wd
|
|
|
|
def getAmPmStrings(self):
|
|
return self._ampm
|
|
|
|
def setAmPmStrings(self, ap):
|
|
self._ampm = ap
|
|
|
|
def getZoneStrings(self):
|
|
return self._zone_strings
|
|
|
|
def setZoneStrings(self, zs):
|
|
self._zone_strings = zs
|
|
|
|
def getLocalPatternChars(self):
|
|
return self._local_pattern_chars
|
|
|
|
def setLocalPatternChars(self, chars):
|
|
self._local_pattern_chars = chars
|
|
|
|
def getEras(self):
|
|
return self._eras
|
|
|
|
def setEras(self, eras):
|
|
self._eras = eras
|
|
|
|
|
|
# ============================================================
|
|
# SimpleDateFormat
|
|
# ============================================================
|
|
|
|
_JAVA_TO_PYTHON_FORMAT = {
|
|
"yyyy": "%Y", "yy": "%y",
|
|
"MM": "%m",
|
|
"dd": "%d",
|
|
"HH": "%H",
|
|
"mm": "%M",
|
|
"ss": "%S",
|
|
"SSS": "%f",
|
|
"a": "%p",
|
|
"EEE": "%a", "EEEE": "%A",
|
|
"D": "%j",
|
|
"zzz": "%Z", "z": "%Z",
|
|
"Z": "%z",
|
|
}
|
|
|
|
class SimpleDateFormat(DateFormat):
|
|
def __init__(self, pattern, locale=None):
|
|
super().__init__()
|
|
self._pattern = pattern
|
|
self._locale = locale
|
|
self._dfs = DateFormatSymbols(locale)
|
|
self._lenient = False
|
|
|
|
def applyPattern(self, pattern):
|
|
self._pattern = pattern
|
|
|
|
def toPattern(self):
|
|
return self._pattern
|
|
|
|
def toLocalizedPattern(self):
|
|
return self._pattern
|
|
|
|
def applyLocalizedPattern(self, pattern):
|
|
self._pattern = pattern
|
|
|
|
def _java_to_py(self, pattern):
|
|
py_pattern = pattern
|
|
sorted_keys = sorted(_JAVA_TO_PYTHON_FORMAT.keys(), key=lambda x: -len(x))
|
|
for java_fmt in sorted_keys:
|
|
py_fmt = _JAVA_TO_PYTHON_FORMAT[java_fmt]
|
|
py_pattern = py_pattern.replace(java_fmt, py_fmt)
|
|
py_pattern = py_pattern.replace("'", "")
|
|
return py_pattern
|
|
|
|
def _to_datetime(self, date):
|
|
if isinstance(date, _datetime.datetime):
|
|
return date
|
|
if isinstance(date, _datetime.date):
|
|
return _datetime.datetime.combine(date, _datetime.time())
|
|
try:
|
|
ts = date.getTime() / 1000.0
|
|
return _datetime.datetime.fromtimestamp(ts)
|
|
except AttributeError:
|
|
return _datetime.datetime.now()
|
|
|
|
def format(self, date, to_append_to=None, pos=None):
|
|
py_pattern = self._java_to_py(self._pattern)
|
|
dt = self._to_datetime(date)
|
|
result = dt.strftime(py_pattern)
|
|
if to_append_to is not None:
|
|
sb = str(to_append_to)
|
|
idx = len(sb)
|
|
to_append_to.append(result)
|
|
if pos is not None:
|
|
pos.setBeginIndex(idx)
|
|
pos.setEndIndex(idx + len(result))
|
|
return to_append_to
|
|
return result
|
|
|
|
def parse(self, source, pos=None):
|
|
py_pattern = self._java_to_py(self._pattern)
|
|
try:
|
|
dt = _datetime.datetime.strptime(source, py_pattern)
|
|
if pos is not None:
|
|
pos.setIndex(len(source))
|
|
return dt
|
|
except ValueError:
|
|
if pos is not None:
|
|
pos.setErrorIndex(0)
|
|
raise ParseException(f"Unparseable date: {source}", 0)
|
|
|
|
def setLenient(self, lenient):
|
|
self._lenient = lenient
|
|
|
|
def isLenient(self):
|
|
return self._lenient
|
|
|
|
def setTimeZone(self, tz):
|
|
self._timezone = tz
|
|
|
|
def getTimeZone(self):
|
|
return self._timezone
|
|
|
|
def setCalendar(self, cal):
|
|
self._calendar = cal
|
|
|
|
def getCalendar(self):
|
|
return self._calendar
|
|
|
|
def setNumberFormat(self, nf):
|
|
self._number_format = nf
|
|
|
|
def getNumberFormat(self):
|
|
return self._number_format
|
|
|
|
def getDateFormatSymbols(self):
|
|
return self._dfs
|
|
|
|
def setDateFormatSymbols(self, dfs):
|
|
self._dfs = dfs
|
|
|
|
|
|
# ============================================================
|
|
# DecimalFormatSymbols
|
|
# ============================================================
|
|
|
|
class DecimalFormatSymbols(Stub):
|
|
def __init__(self, locale=None):
|
|
super().__init__()
|
|
self._decimal_sep = "."
|
|
self._grouping_sep = ","
|
|
self._pattern_sep = ";"
|
|
self._minus_sign = "-"
|
|
self._infinity = "\u221e"
|
|
self._nan = "NaN"
|
|
self._percent = "%"
|
|
self._per_mill = "\u2030"
|
|
self._zero_digit = "0"
|
|
self._digit = "#"
|
|
self._exponential_sep = "E"
|
|
self._monetary_decimal_sep = "."
|
|
self._intl_currency_symbol = "USD"
|
|
self._currency = "USD"
|
|
|
|
def getDecimalSeparator(self): return self._decimal_sep
|
|
def setDecimalSeparator(self, c): self._decimal_sep = c
|
|
def getGroupingSeparator(self): return self._grouping_sep
|
|
def setGroupingSeparator(self, c): self._grouping_sep = c
|
|
def getPatternSeparator(self): return self._pattern_sep
|
|
def setPatternSeparator(self, c): self._pattern_sep = c
|
|
def getMinusSign(self): return self._minus_sign
|
|
def setMinusSign(self, c): self._minus_sign = c
|
|
def getInfinity(self): return self._infinity
|
|
def setInfinity(self, s): self._infinity = s
|
|
def getNaN(self): return self._nan
|
|
def setNaN(self, s): self._nan = s
|
|
def getPercent(self): return self._percent
|
|
def setPercent(self, c): self._percent = c
|
|
def getPerMill(self): return self._per_mill
|
|
def setPerMill(self, c): self._per_mill = c
|
|
def getZeroDigit(self): return self._zero_digit
|
|
def setZeroDigit(self, c): self._zero_digit = c
|
|
def getDigit(self): return self._digit
|
|
def setDigit(self, c): self._digit = c
|
|
def getExponentialSeparator(self): return self._exponential_sep
|
|
def setExponentialSeparator(self, s): self._exponential_sep = s
|
|
def getMonetaryDecimalSeparator(self): return self._monetary_decimal_sep
|
|
def setMonetaryDecimalSeparator(self, c): self._monetary_decimal_sep = c
|
|
def getInternationalCurrencySymbol(self): return self._intl_currency_symbol
|
|
def setInternationalCurrencySymbol(self, s): self._intl_currency_symbol = s
|
|
def getCurrency(self): return self._currency
|
|
def setCurrency(self, c): self._currency = c
|
|
|
|
|
|
# ============================================================
|
|
# DecimalFormat
|
|
# ============================================================
|
|
|
|
class NumberFormat(Format):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._grouping_used = True
|
|
self._max_int_digits = 2000000000
|
|
self._min_int_digits = 1
|
|
self._max_frac_digits = 3
|
|
self._min_frac_digits = 0
|
|
self._parse_integer_only = False
|
|
self._rounding_mode = 4
|
|
|
|
@staticmethod
|
|
def getInstance(locale=None):
|
|
return DecimalFormat("#,##0.###")
|
|
|
|
@staticmethod
|
|
def getNumberInstance(locale=None):
|
|
return DecimalFormat("#,##0.###")
|
|
|
|
@staticmethod
|
|
def getCurrencyInstance(locale=None):
|
|
return DecimalFormat("#,##0.00")
|
|
|
|
@staticmethod
|
|
def getPercentInstance(locale=None):
|
|
return DecimalFormat("#,##0%")
|
|
|
|
@staticmethod
|
|
def getIntegerInstance(locale=None):
|
|
nf = DecimalFormat("#,##0")
|
|
nf.setMaximumFractionDigits(0)
|
|
return nf
|
|
|
|
@staticmethod
|
|
def getCompactNumberInstance(locale=None, style=None):
|
|
return DecimalFormat("#,##0")
|
|
|
|
def format(self, number, to_append_to=None, pos=None):
|
|
raise NotImplementedError
|
|
|
|
def parse(self, source, pos=None):
|
|
raise NotImplementedError
|
|
|
|
def isGroupingUsed(self): return self._grouping_used
|
|
def setGroupingUsed(self, v): self._grouping_used = v
|
|
def getMaximumIntegerDigits(self): return self._max_int_digits
|
|
def setMaximumIntegerDigits(self, v): self._max_int_digits = v
|
|
def getMinimumIntegerDigits(self): return self._min_int_digits
|
|
def setMinimumIntegerDigits(self, v): self._min_int_digits = v
|
|
def getMaximumFractionDigits(self): return self._max_frac_digits
|
|
def setMaximumFractionDigits(self, v): self._max_frac_digits = v
|
|
def getMinimumFractionDigits(self): return self._min_frac_digits
|
|
def setMinimumFractionDigits(self, v): self._min_frac_digits = v
|
|
def setParseIntegerOnly(self, v): self._parse_integer_only = v
|
|
def isParseIntegerOnly(self): return self._parse_integer_only
|
|
def setRoundingMode(self, v): self._rounding_mode = v
|
|
def getRoundingMode(self): return self._rounding_mode
|
|
def getCurrency(self): return "USD"
|
|
def setCurrency(self, c): pass
|
|
|
|
@staticmethod
|
|
def getAvailableLocales():
|
|
return list(_locale.locale_alias.keys())
|
|
|
|
|
|
# ============================================================
|
|
# ChoiceFormat
|
|
# ============================================================
|
|
|
|
class ChoiceFormat(NumberFormat):
|
|
def __init__(self, formats=None, limits=None):
|
|
super().__init__()
|
|
self._formats = formats if formats is not None else []
|
|
self._limits = limits if limits is not None else []
|
|
|
|
def format(self, number, to_append_to=None, pos=None):
|
|
val = float(number)
|
|
idx = 0
|
|
for i, lim in enumerate(self._limits):
|
|
if val >= lim:
|
|
idx = i
|
|
result = str(self._formats[idx]) if idx < len(self._formats) else ""
|
|
if to_append_to is not None:
|
|
sb = str(to_append_to)
|
|
idx2 = len(sb)
|
|
to_append_to.append(result)
|
|
if pos is not None:
|
|
pos.setBeginIndex(idx2)
|
|
pos.setEndIndex(idx2 + len(result))
|
|
return to_append_to
|
|
return result
|
|
|
|
def parse(self, source, pos=None):
|
|
raise NotImplementedError
|
|
|
|
@staticmethod
|
|
def nextDouble(d, upwards=True):
|
|
if upwards:
|
|
return d + _py_math.ulp(d)
|
|
return d - _py_math.ulp(d)
|
|
|
|
@staticmethod
|
|
def previousDouble(d):
|
|
return ChoiceFormat.nextDouble(d, False)
|
|
|
|
def getLimits(self):
|
|
return self._limits
|
|
|
|
def getFormats(self):
|
|
return self._formats
|
|
|
|
|
|
# ============================================================
|
|
# MessageFormat
|
|
# ============================================================
|
|
# NumberFormat
|
|
# ============================================================
|
|
|
|
# ============================================================
|
|
# DecimalFormat
|
|
# ============================================================
|
|
|
|
class DecimalFormat(NumberFormat):
|
|
def __init__(self, pattern=None, symbols=None):
|
|
super().__init__()
|
|
self._pattern = pattern or "#,##0.###"
|
|
self._symbols = symbols or DecimalFormatSymbols()
|
|
self._decimal_sep_always = False
|
|
self._grouping_size = 3
|
|
self._grouping_used = True
|
|
self._max_int_digits = 2000000000
|
|
self._min_int_digits = 1
|
|
self._max_frac_digits = 2000000000
|
|
self._min_frac_digits = 0
|
|
self._neg_prefix = "-"
|
|
self._neg_suffix = ""
|
|
self._pos_prefix = ""
|
|
self._pos_suffix = ""
|
|
self._multiplier = 1
|
|
self._rounding_mode = 4
|
|
self._parse_bigdecimal = False
|
|
|
|
def applyPattern(self, pattern):
|
|
self._pattern = pattern
|
|
|
|
def toPattern(self):
|
|
return self._pattern
|
|
|
|
def toLocalizedPattern(self):
|
|
return self._pattern
|
|
|
|
def applyLocalizedPattern(self, pattern):
|
|
self._pattern = pattern
|
|
|
|
def format(self, number, to_append_to=None, pos=None):
|
|
if isinstance(number, (int, float)):
|
|
val = number * self._multiplier
|
|
elif isinstance(number, _Decimal):
|
|
val = float(str(number)) * self._multiplier
|
|
else:
|
|
val = float(str(number)) * self._multiplier
|
|
if self._grouping_used and self._grouping_size > 0:
|
|
result = f"{val:,.{self._max_frac_digits}f}"
|
|
else:
|
|
fmt = "{{:.{}f}}".format(self._max_frac_digits)
|
|
result = fmt.format(val)
|
|
prefix = self._pos_prefix if val >= 0 else self._neg_prefix
|
|
suffix = self._pos_suffix if val >= 0 else self._neg_suffix
|
|
result = prefix + result + suffix
|
|
if to_append_to is not None:
|
|
sb = str(to_append_to)
|
|
idx = len(sb)
|
|
to_append_to.append(result)
|
|
if pos is not None:
|
|
pos.setBeginIndex(idx)
|
|
pos.setEndIndex(idx + len(result))
|
|
return to_append_to
|
|
return result
|
|
|
|
def parse(self, source, pos=None):
|
|
text = source.strip()
|
|
try:
|
|
val = float(text)
|
|
if pos is not None:
|
|
pos.setIndex(len(source))
|
|
if self._parse_bigdecimal:
|
|
return _Decimal(str(val))
|
|
return val
|
|
except ValueError:
|
|
if pos is not None:
|
|
pos.setErrorIndex(0)
|
|
raise Exception(f"Unparseable: {source}")
|
|
|
|
def setDecimalSeparatorAlwaysShown(self, v): self._decimal_sep_always = v
|
|
def getDecimalSeparatorAlwaysShown(self): return self._decimal_sep_always
|
|
def setGroupingSize(self, v): self._grouping_size = v
|
|
def getGroupingSize(self): return self._grouping_size
|
|
def setGroupingUsed(self, v): self._grouping_used = v
|
|
def isGroupingUsed(self): return self._grouping_used
|
|
def setMaximumIntegerDigits(self, v): self._max_int_digits = v
|
|
def getMaximumIntegerDigits(self): return self._max_int_digits
|
|
def setMinimumIntegerDigits(self, v): self._min_int_digits = v
|
|
def getMinimumIntegerDigits(self): return self._min_int_digits
|
|
def setMaximumFractionDigits(self, v): self._max_frac_digits = v
|
|
def getMaximumFractionDigits(self): return self._max_frac_digits
|
|
def setMinimumFractionDigits(self, v): self._min_frac_digits = v
|
|
def getMinimumFractionDigits(self): return self._min_frac_digits
|
|
def setNegativePrefix(self, v): self._neg_prefix = v
|
|
def getNegativePrefix(self): return self._neg_prefix
|
|
def setNegativeSuffix(self, v): self._neg_suffix = v
|
|
def getNegativeSuffix(self): return self._neg_suffix
|
|
def setPositivePrefix(self, v): self._pos_prefix = v
|
|
def getPositivePrefix(self): return self._pos_prefix
|
|
def setPositiveSuffix(self, v): self._pos_suffix = v
|
|
def getPositiveSuffix(self): return self._pos_suffix
|
|
def setDecimalFormatSymbols(self, v): self._symbols = v
|
|
def getDecimalFormatSymbols(self): return self._symbols
|
|
def setParseBigDecimal(self, v): self._parse_bigdecimal = v
|
|
def isParseBigDecimal(self): return self._parse_bigdecimal
|
|
def setMultiplier(self, v): self._multiplier = v
|
|
def getMultiplier(self): return self._multiplier
|
|
def setRoundingMode(self, v): self._rounding_mode = v
|
|
def getRoundingMode(self): return self._rounding_mode
|
|
|
|
|
|
# ============================================================
|
|
# MessageFormat
|
|
# ============================================================
|
|
|
|
_MSG_PATTERN_RE = _re.compile(r"\{(\d+)(?:,(\w+)(?:,(.+))?)?\}")
|
|
|
|
class MessageFormat(Format):
|
|
def __init__(self, pattern, locale=None):
|
|
super().__init__()
|
|
self._pattern = pattern
|
|
self._locale = locale
|
|
self._formats = {}
|
|
|
|
def applyPattern(self, pattern):
|
|
self._pattern = pattern
|
|
|
|
def toPattern(self):
|
|
return self._pattern
|
|
|
|
def _format_impl(self, objects):
|
|
def replacer(m):
|
|
idx = int(m.group(1))
|
|
if idx < len(objects):
|
|
return str(objects[idx])
|
|
return m.group(0)
|
|
return _MSG_PATTERN_RE.sub(replacer, self._pattern)
|
|
|
|
def format(self, objects, to_append_to=None, pos=None):
|
|
result = self._format_impl(objects)
|
|
if to_append_to is not None:
|
|
sb = str(to_append_to)
|
|
idx = len(sb)
|
|
to_append_to.append(result)
|
|
if pos is not None:
|
|
pos.setBeginIndex(idx)
|
|
pos.setEndIndex(idx + len(result))
|
|
return to_append_to
|
|
return result
|
|
|
|
@staticmethod
|
|
def format_static(pattern, *args):
|
|
mf = MessageFormat(pattern)
|
|
return mf.format(args)
|
|
|
|
def parse(self, source, pos=None):
|
|
parts = _MSG_PATTERN_RE.split(self._pattern)
|
|
result = []
|
|
idx = 0
|
|
for i, part in enumerate(parts):
|
|
if i % 2 == 0:
|
|
if not source[idx:].startswith(part):
|
|
if pos is not None:
|
|
pos.setErrorIndex(idx)
|
|
return result
|
|
idx += len(part)
|
|
else:
|
|
arg_idx = int(part)
|
|
remaining = source[idx:]
|
|
next_part = parts[i + 1] if i + 1 < len(parts) else ""
|
|
end = remaining.find(next_part) if next_part else len(remaining)
|
|
if end < 0:
|
|
end = len(remaining)
|
|
result.append(remaining[:end])
|
|
idx += end
|
|
if pos is not None:
|
|
pos.setIndex(idx)
|
|
return result
|
|
|
|
def setLocale(self, locale):
|
|
self._locale = locale
|
|
|
|
def getLocale(self):
|
|
return self._locale
|
|
|
|
def setFormats(self, formats):
|
|
self._formats = formats
|
|
|
|
def getFormats(self):
|
|
return self._formats
|
|
|
|
def setFormat(self, idx, fmt):
|
|
self._formats[idx] = fmt
|
|
|
|
def getFormat(self, idx):
|
|
return self._formats.get(idx)
|
|
|
|
|
|
# ============================================================
|
|
# CharacterIterator
|
|
# ============================================================
|
|
|
|
class CharacterIterator(Stub):
|
|
DONE = chr(0xFFFF)
|
|
|
|
def first(self):
|
|
raise NotImplementedError
|
|
|
|
def last(self):
|
|
raise NotImplementedError
|
|
|
|
def current(self):
|
|
raise NotImplementedError
|
|
|
|
def next(self):
|
|
raise NotImplementedError
|
|
|
|
def previous(self):
|
|
raise NotImplementedError
|
|
|
|
def setIndex(self, position):
|
|
raise NotImplementedError
|
|
|
|
def getBeginIndex(self):
|
|
raise NotImplementedError
|
|
|
|
def getEndIndex(self):
|
|
raise NotImplementedError
|
|
|
|
def getIndex(self):
|
|
raise NotImplementedError
|
|
|
|
def clone(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
# ============================================================
|
|
# StringCharacterIterator
|
|
# ============================================================
|
|
|
|
class StringCharacterIterator(CharacterIterator):
|
|
def __init__(self, text):
|
|
super().__init__()
|
|
self._text = text
|
|
self._begin = 0
|
|
self._end = len(text)
|
|
self._pos = 0
|
|
|
|
def first(self):
|
|
self._pos = self._begin
|
|
return self.current()
|
|
|
|
def last(self):
|
|
self._pos = self._end - 1 if self._end > self._begin else self._begin
|
|
return self.current()
|
|
|
|
def current(self):
|
|
if self._pos >= self._begin and self._pos < self._end:
|
|
return self._text[self._pos]
|
|
return CharacterIterator.DONE
|
|
|
|
def next(self):
|
|
self._pos += 1
|
|
if self._pos >= self._end:
|
|
self._pos = self._end
|
|
return CharacterIterator.DONE
|
|
return self._text[self._pos]
|
|
|
|
def previous(self):
|
|
self._pos -= 1
|
|
if self._pos < self._begin:
|
|
self._pos = self._begin
|
|
return CharacterIterator.DONE
|
|
return self._text[self._pos]
|
|
|
|
def setIndex(self, pos):
|
|
self._pos = max(self._begin, min(pos, self._end))
|
|
return self.current()
|
|
|
|
def getBeginIndex(self):
|
|
return self._begin
|
|
|
|
def getEndIndex(self):
|
|
return self._end
|
|
|
|
def getIndex(self):
|
|
return self._pos
|
|
|
|
def clone(self):
|
|
return StringCharacterIterator(self._text)
|
|
|
|
|
|
# ============================================================
|
|
# BreakIterator
|
|
# ============================================================
|
|
|
|
class BreakIterator(Stub):
|
|
DONE = -1
|
|
|
|
@staticmethod
|
|
def getWordInstance(locale=None):
|
|
return BreakIterator()
|
|
|
|
@staticmethod
|
|
def getLineInstance(locale=None):
|
|
return BreakIterator()
|
|
|
|
@staticmethod
|
|
def getCharacterInstance(locale=None):
|
|
return BreakIterator()
|
|
|
|
@staticmethod
|
|
def getSentenceInstance(locale=None):
|
|
return BreakIterator()
|
|
|
|
def setText(self, text):
|
|
self._text = text
|
|
|
|
def first(self):
|
|
return 0
|
|
|
|
def last(self):
|
|
return len(self._text) if hasattr(self, "_text") else 0
|
|
|
|
def next(self, n=1):
|
|
return BreakIterator.DONE
|
|
|
|
def previous(self):
|
|
return BreakIterator.DONE
|
|
|
|
def current(self):
|
|
return 0
|
|
|
|
def following(self, offset):
|
|
return BreakIterator.DONE
|
|
|
|
def preceding(self, offset):
|
|
return BreakIterator.DONE
|
|
|
|
def isBoundary(self, offset):
|
|
return True
|
|
|
|
|
|
# ============================================================
|
|
# Collator / CollationKey / RuleBasedCollator
|
|
# ============================================================
|
|
|
|
class CollationKey(Stub):
|
|
def __init__(self, source, key):
|
|
super().__init__()
|
|
self._source = source
|
|
self._key = key
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, CollationKey):
|
|
return (self._key > other._key) - (self._key < other._key)
|
|
return -1
|
|
|
|
def getSourceString(self):
|
|
return self._source
|
|
|
|
def toByteArray(self):
|
|
return self._key.encode("utf-8")
|
|
|
|
|
|
class Collator(Stub):
|
|
PRIMARY = 0
|
|
SECONDARY = 1
|
|
TERTIARY = 2
|
|
IDENTICAL = 3
|
|
NO_DECOMPOSITION = 0
|
|
CANONICAL_DECOMPOSITION = 1
|
|
FULL_DECOMPOSITION = 2
|
|
|
|
@staticmethod
|
|
def getInstance(locale=None):
|
|
return RuleBasedCollator()
|
|
|
|
def compare(self, source, target):
|
|
return (source > target) - (source < target)
|
|
|
|
def getCollationKey(self, source):
|
|
return CollationKey(source, source)
|
|
|
|
def setStrength(self, strength):
|
|
self._strength = strength
|
|
|
|
def getStrength(self):
|
|
return getattr(self, "_strength", Collator.TERTIARY)
|
|
|
|
def setDecomposition(self, decomposition):
|
|
self._decomposition = decomposition
|
|
|
|
def getDecomposition(self):
|
|
return getattr(self, "_decomposition", Collator.NO_DECOMPOSITION)
|
|
|
|
|
|
class RuleBasedCollator(Collator):
|
|
def __init__(self, rules=None):
|
|
super().__init__()
|
|
self._rules = rules or ""
|
|
|
|
|
|
# ============================================================
|
|
# Normalizer
|
|
# ============================================================
|
|
|
|
class Normalizer(Stub):
|
|
class Form(Stub):
|
|
NFC = "NFC"
|
|
NFD = "NFD"
|
|
NFKC = "NFKC"
|
|
NFKD = "NFKD"
|
|
|
|
@staticmethod
|
|
def normalize(src, form):
|
|
import unicodedata
|
|
form_map = {
|
|
Normalizer.Form.NFC: "NFC",
|
|
Normalizer.Form.NFD: "NFD",
|
|
Normalizer.Form.NFKC: "NFKC",
|
|
Normalizer.Form.NFKD: "NFKD",
|
|
}
|
|
return unicodedata.normalize(form_map.get(form, "NFC"), src)
|
|
|
|
@staticmethod
|
|
def isNormalized(src, form):
|
|
return Normalizer.normalize(src, form) == src
|
|
|
|
|
|
# ============================================================
|
|
# Annotation / AttributedCharacterIterator / AttributedString
|
|
# ============================================================
|
|
|
|
class Annotation(Stub):
|
|
def __init__(self, value):
|
|
super().__init__()
|
|
self._value = value
|
|
|
|
def getValue(self):
|
|
return self._value
|
|
|
|
def toString(self):
|
|
return str(self._value)
|
|
|
|
|
|
class AttributedCharacterIterator(CharacterIterator):
|
|
pass
|
|
|
|
|
|
class AttributedString(Stub):
|
|
def __init__(self, text, attributes=None):
|
|
super().__init__()
|
|
self._text = text
|
|
|
|
def getIterator(self):
|
|
return StringCharacterIterator(self._text)
|
|
|
|
|
|
# ============================================================
|
|
# Bidi
|
|
# ============================================================
|
|
|
|
class Bidi(Stub):
|
|
DIRECTION_LEFT_TO_RIGHT = 0
|
|
DIRECTION_RIGHT_TO_LEFT = 1
|
|
DIRECTION_DEFAULT_LEFT_TO_RIGHT = -2
|
|
DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1
|
|
|
|
def __init__(self, text, flags=0):
|
|
super().__init__()
|
|
self._text = text
|
|
self._base_level = 0 if flags >= 0 else 1
|
|
|
|
def isLeftToRight(self):
|
|
return self._base_level == 0
|
|
|
|
def isRightToLeft(self):
|
|
return self._base_level == 1
|
|
|
|
def getLength(self):
|
|
return len(self._text)
|
|
|
|
def getLevelAt(self, offset):
|
|
return self._base_level
|
|
|
|
def getRunCount(self):
|
|
return 1
|
|
|
|
def getRunLevel(self, run):
|
|
return self._base_level
|
|
|
|
def getRunLimit(self, run):
|
|
return len(self._text)
|
|
|
|
def getRunStart(self, run):
|
|
return 0
|
|
|
|
def requiresBidi(self, text):
|
|
for ch in text:
|
|
if ord(ch) > 0x590:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def reorderVisually(levels, objects, index, count):
|
|
pass
|
|
|
|
@staticmethod
|
|
def baseIsLeftToRight():
|
|
return True |