Add type annotations to CldrAccess
Task-number: QTBUG-129613 Pick-to: 6.8 Change-Id: I8a00cca718554909b7ab9dcad15cc9b9ac702e94 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
parent
adc4ec9d39
commit
defd1549de
@ -10,11 +10,11 @@ The former should normally be all you need to access.
|
|||||||
See individual classes for further detail.
|
See individual classes for further detail.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Iterable, TextIO
|
from typing import Callable, Iterable, Iterator, TextIO
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
from weakref import WeakValueDictionary as CacheDict
|
from weakref import WeakValueDictionary as CacheDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from ldml import Error, Node, XmlScanner, Supplement, LocaleScanner
|
from ldml import Error, Node, XmlScanner, Supplement, LocaleScanner
|
||||||
from localetools import names_clash
|
from localetools import names_clash
|
||||||
@ -309,7 +309,7 @@ class CldrReader (object):
|
|||||||
# the cache. If a process were to instantiate this class with distinct
|
# the cache. If a process were to instantiate this class with distinct
|
||||||
# roots, each cache would be filled by the first to need it !
|
# roots, each cache would be filled by the first to need it !
|
||||||
class CldrAccess (object):
|
class CldrAccess (object):
|
||||||
def __init__(self, root: Path):
|
def __init__(self, root: Path) -> None:
|
||||||
"""Set up a master object for accessing CLDR data.
|
"""Set up a master object for accessing CLDR data.
|
||||||
|
|
||||||
Single parameter, root, is the file-system path to the root of
|
Single parameter, root, is the file-system path to the root of
|
||||||
@ -317,20 +317,20 @@ class CldrAccess (object):
|
|||||||
contain dtd/, main/ and supplemental/ sub-directories."""
|
contain dtd/, main/ and supplemental/ sub-directories."""
|
||||||
self.root = root
|
self.root = root
|
||||||
|
|
||||||
def xml(self, relative_path: str):
|
def xml(self, relative_path: str) -> XmlScanner:
|
||||||
"""Load a single XML file and return its root element as an XmlScanner.
|
"""Load a single XML file and return its root element as an XmlScanner.
|
||||||
|
|
||||||
The path is interpreted relative to self.root"""
|
The path is interpreted relative to self.root"""
|
||||||
return XmlScanner(Node(self.__xml(relative_path)))
|
return XmlScanner(Node(self.__xml(relative_path)))
|
||||||
|
|
||||||
def supplement(self, name):
|
def supplement(self, name: str) -> Supplement:
|
||||||
"""Loads supplemental data as a Supplement object.
|
"""Loads supplemental data as a Supplement object.
|
||||||
|
|
||||||
The name should be that of a file in common/supplemental/, without path.
|
The name should be that of a file in common/supplemental/, without path.
|
||||||
"""
|
"""
|
||||||
return Supplement(Node(self.__xml(f'common/supplemental/{name}')))
|
return Supplement(Node(self.__xml(f'common/supplemental/{name}')))
|
||||||
|
|
||||||
def locale(self, name):
|
def locale(self, name: str) -> LocaleScanner:
|
||||||
"""Loads all data for a locale as a LocaleScanner object.
|
"""Loads all data for a locale as a LocaleScanner object.
|
||||||
|
|
||||||
The name should be a locale name; adding suffix '.xml' to it
|
The name should be a locale name; adding suffix '.xml' to it
|
||||||
@ -340,7 +340,7 @@ class CldrAccess (object):
|
|||||||
inheritance, where relevant."""
|
inheritance, where relevant."""
|
||||||
return LocaleScanner(name, self.__localeRoots(name), self.__rootLocale)
|
return LocaleScanner(name, self.__localeRoots(name), self.__rootLocale)
|
||||||
|
|
||||||
def englishNaming(self, tag): # see QLocaleXmlWriter.enumData()
|
def englishNaming(self, tag: str) -> Callable[[str], str]: # see QLocaleXmlWriter.enumData()
|
||||||
return self.__codeMap(tag).get
|
return self.__codeMap(tag).get
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -354,18 +354,18 @@ class CldrAccess (object):
|
|||||||
yield path.stem
|
yield path.stem
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def defaultContentLocales(self):
|
def defaultContentLocales(self) -> Iterator[str]:
|
||||||
"""Generator for the default content locales."""
|
"""Generator for the default content locales."""
|
||||||
for name, attrs in self.supplement('supplementalMetadata.xml').find('metadata/defaultContent'):
|
for name, attrs in self.supplement('supplementalMetadata.xml').find('metadata/defaultContent'):
|
||||||
try:
|
try:
|
||||||
locales = attrs['locales']
|
locales: str = attrs['locales']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for locale in locales.split():
|
for locale in locales.split():
|
||||||
yield locale
|
yield locale
|
||||||
|
|
||||||
def likelySubTags(self):
|
def likelySubTags(self) -> Iterator[tuple[str, str]]:
|
||||||
for ignore, attrs in self.supplement('likelySubtags.xml').find('likelySubtags'):
|
for ignore, attrs in self.supplement('likelySubtags.xml').find('likelySubtags'):
|
||||||
yield attrs['from'], attrs['to']
|
yield attrs['from'], attrs['to']
|
||||||
|
|
||||||
@ -380,7 +380,7 @@ class CldrAccess (object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise Error(f'Unsupported number system: {system}')
|
raise Error(f'Unsupported number system: {system}')
|
||||||
|
|
||||||
def weekData(self, territory):
|
def weekData(self, territory: str) -> tuple[str, str, str]:
|
||||||
"""Data on the weekly cycle.
|
"""Data on the weekly cycle.
|
||||||
|
|
||||||
Returns a triple (W, S, E) of en's short names for week-days;
|
Returns a triple (W, S, E) of en's short names for week-days;
|
||||||
@ -393,7 +393,7 @@ class CldrAccess (object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return self.__weekData['001']
|
return self.__weekData['001']
|
||||||
|
|
||||||
def currencyData(self, territory):
|
def currencyData(self, territory: str) -> tuple[str, int, int]:
|
||||||
"""Returns currency data for the given territory code.
|
"""Returns currency data for the given territory code.
|
||||||
|
|
||||||
Return value is a tuple (ISO4217 code, digit count, rounding
|
Return value is a tuple (ISO4217 code, digit count, rounding
|
||||||
@ -405,7 +405,9 @@ class CldrAccess (object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return '', 2, 1
|
return '', 2, 1
|
||||||
|
|
||||||
def codesToIdName(self, language, script, territory, variant = ''):
|
def codesToIdName(self, language: str, script: str, territory: str, variant: str = ''
|
||||||
|
) -> tuple[tuple[int, str], tuple[int, str],
|
||||||
|
tuple[int, str], tuple[int, str]]:
|
||||||
"""Maps each code to the appropriate ID and name.
|
"""Maps each code to the appropriate ID and name.
|
||||||
|
|
||||||
Returns a 4-tuple of (ID, name) pairs corresponding to the
|
Returns a 4-tuple of (ID, name) pairs corresponding to the
|
||||||
@ -417,7 +419,7 @@ class CldrAccess (object):
|
|||||||
Until we implement variant support (QTBUG-81051), the fourth
|
Until we implement variant support (QTBUG-81051), the fourth
|
||||||
member of the returned tuple is always 0 paired with a string
|
member of the returned tuple is always 0 paired with a string
|
||||||
that should not be used."""
|
that should not be used."""
|
||||||
enum = self.__enumMap
|
enum: Callable[[str], dict[str, tuple[int, str]]] = self.__enumMap
|
||||||
try:
|
try:
|
||||||
return (enum('language')[language],
|
return (enum('language')[language],
|
||||||
enum('script')[script],
|
enum('script')[script],
|
||||||
@ -428,8 +430,9 @@ class CldrAccess (object):
|
|||||||
|
|
||||||
parts, values = [], [language, script, territory, variant]
|
parts, values = [], [language, script, territory, variant]
|
||||||
for index, key in enumerate(('language', 'script', 'territory', 'variant')):
|
for index, key in enumerate(('language', 'script', 'territory', 'variant')):
|
||||||
naming, enums = self.__codeMap(key), enum(key)
|
naming: dict[str, str] = self.__codeMap(key)
|
||||||
value = values[index]
|
enums: dict[str, tuple[int, str]] = enum(key)
|
||||||
|
value: str = values[index]
|
||||||
if value not in enums:
|
if value not in enums:
|
||||||
text = f'{key} code {value}'
|
text = f'{key} code {value}'
|
||||||
name = naming.get(value)
|
name = naming.get(value)
|
||||||
@ -447,21 +450,22 @@ class CldrAccess (object):
|
|||||||
language, script, territory, variant)
|
language, script, territory, variant)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __checkEnum(given, proper, scraps):
|
def __checkEnum(given: dict[str, str], proper: dict[str, str], scraps: set[str]
|
||||||
|
) -> Iterator[tuple[str, str]]:
|
||||||
# Each is a { code: full name } mapping
|
# Each is a { code: full name } mapping
|
||||||
for code, name in given.items():
|
for code, name in given.items():
|
||||||
try: right = proper[code]
|
try: right: str = proper[code]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No en.xml name for this code, but supplementalData's
|
# No en.xml name for this code, but supplementalData's
|
||||||
# parentLocale may still believe in it:
|
# parentLocale may still believe in it:
|
||||||
if code not in scraps:
|
if code not in scraps:
|
||||||
yield name, f'[Found no CLDR name for code {code}]'
|
yield name, f'[Found no CLDR name for code {code}]'
|
||||||
continue
|
continue
|
||||||
cleaned = names_clash(right, name)
|
cleaned: None | str = names_clash(right, name)
|
||||||
if cleaned:
|
if cleaned:
|
||||||
yield name, cleaned
|
yield name, cleaned
|
||||||
|
|
||||||
def checkEnumData(self, grumble):
|
def checkEnumData(self, grumble: Callable[[str], int]) -> None:
|
||||||
scraps = set()
|
scraps = set()
|
||||||
for k in self.__parentLocale.keys():
|
for k in self.__parentLocale.keys():
|
||||||
for f in k.split('_'):
|
for f in k.split('_'):
|
||||||
@ -492,7 +496,7 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
+ '\n')
|
+ '\n')
|
||||||
grumble('\n')
|
grumble('\n')
|
||||||
|
|
||||||
def bcp47Aliases(self):
|
def bcp47Aliases(self) -> tuple[dict[str, str], dict[str, str]]:
|
||||||
"""Reads the mapping from CLDR IDs to IANA IDs
|
"""Reads the mapping from CLDR IDs to IANA IDs
|
||||||
|
|
||||||
CLDR identifies timezones in various ways but its standard
|
CLDR identifies timezones in various ways but its standard
|
||||||
@ -530,7 +534,8 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
# If we ever need a mapping back to CLDR ID, we can make
|
# If we ever need a mapping back to CLDR ID, we can make
|
||||||
# (description, space-joined-list) the naming values.
|
# (description, space-joined-list) the naming values.
|
||||||
alias, naming = {}, {} # { alias: iana }, { iana: description }
|
alias: dict[str, str] = {} # { alias: iana }
|
||||||
|
naming: dict[str, str] = {} # { iana: description }
|
||||||
for item, attrs in root.find('keyword/key/type', exclude=('deprecated',)):
|
for item, attrs in root.find('keyword/key/type', exclude=('deprecated',)):
|
||||||
assert 'description' in attrs, item
|
assert 'description' in attrs, item
|
||||||
assert 'alias' in attrs, item
|
assert 'alias' in attrs, item
|
||||||
@ -545,7 +550,8 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
return alias, naming
|
return alias, naming
|
||||||
|
|
||||||
def readWindowsTimeZones(self, alias):
|
def readWindowsTimeZones(self, alias: dict[str, str]) -> tuple[dict[str, str],
|
||||||
|
list[tuple[str, str, str]]]:
|
||||||
"""Digest CLDR's MS-Win time-zone name mapping.
|
"""Digest CLDR's MS-Win time-zone name mapping.
|
||||||
|
|
||||||
Single argument, alias, should be the first part of the pair
|
Single argument, alias, should be the first part of the pair
|
||||||
@ -582,7 +588,8 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
mapZone element and the last is s, its cleaned-up list of IANA
|
mapZone element and the last is s, its cleaned-up list of IANA
|
||||||
IDs."""
|
IDs."""
|
||||||
|
|
||||||
defaults, windows = {}, []
|
defaults: dict[str, str] = {}
|
||||||
|
windows: list[tuple[str, str, str]] = []
|
||||||
zones = self.supplement('windowsZones.xml')
|
zones = self.supplement('windowsZones.xml')
|
||||||
for name, attrs in zones.find('windowsZones/mapTimezones'):
|
for name, attrs in zones.find('windowsZones/mapTimezones'):
|
||||||
if name != 'mapZone':
|
if name != 'mapZone':
|
||||||
@ -602,7 +609,10 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
return defaults, windows
|
return defaults, windows
|
||||||
|
|
||||||
def readMetaZoneMap(self, alias):
|
def readMetaZoneMap(self, alias: dict[str, str]
|
||||||
|
) -> tuple[dict[str, dict[str, str]],
|
||||||
|
dict[str, tuple[tuple[int, int, str], ...]],
|
||||||
|
dict[str, str]]:
|
||||||
"""Digests the metaZones supplemental data.
|
"""Digests the metaZones supplemental data.
|
||||||
|
|
||||||
Required argument, alias, should be the first of
|
Required argument, alias, should be the first of
|
||||||
@ -633,9 +643,9 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
locale."""
|
locale."""
|
||||||
metaZones = self.supplement('metaZones.xml') # Doesn't appear to use draft attribute
|
metaZones = self.supplement('metaZones.xml') # Doesn't appear to use draft attribute
|
||||||
# Map CLDR name to IANA name (or use CLDR name if unknown to alias):
|
# Map CLDR name to IANA name (or use CLDR name if unknown to alias):
|
||||||
zoneName = lambda n, g=alias.get: g(n, n)
|
zoneName: Callable[[str], str] = lambda n, g=alias.get: g(n, n)
|
||||||
|
|
||||||
metaMap = {} # { meta: { territory code: zoneId } }
|
metaMap: dict[str, dict[str, str]] = {} # { meta: { territory code: zoneId } }
|
||||||
# Entry with territory 001 is "golden zone" for the metazone.
|
# Entry with territory 001 is "golden zone" for the metazone.
|
||||||
for mapMeta in metaZones.findNodes('metaZones/mapTimezones'):
|
for mapMeta in metaZones.findNodes('metaZones/mapTimezones'):
|
||||||
attrs = mapMeta.attributes()
|
attrs = mapMeta.attributes()
|
||||||
@ -646,13 +656,13 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
raise Error('Version of metazone map type is not 2018e', attrs)
|
raise Error('Version of metazone map type is not 2018e', attrs)
|
||||||
|
|
||||||
for node in mapMeta.findAllChildren('mapZone'):
|
for node in mapMeta.findAllChildren('mapZone'):
|
||||||
attrs = node.attributes()
|
attrs: dict[str, str] = node.attributes()
|
||||||
try:
|
try:
|
||||||
meta, code, zone = attrs['other'], attrs['territory'], attrs['type']
|
meta, code, zone = attrs['other'], attrs['territory'], attrs['type']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bok = metaMap.setdefault(meta, {})
|
bok: dict[str, str] = metaMap.setdefault(meta, {})
|
||||||
assert code not in bok, (meta, code)
|
assert code not in bok, (meta, code)
|
||||||
bok[code] = zoneName(zone)
|
bok[code] = zoneName(zone)
|
||||||
# Territories not named in a metaMap entry fall back on the
|
# Territories not named in a metaMap entry fall back on the
|
||||||
@ -660,16 +670,16 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
# entry:
|
# entry:
|
||||||
assert all('001' in bok for bok in metaMap.values())
|
assert all('001' in bok for bok in metaMap.values())
|
||||||
|
|
||||||
def scanUses(zone, check=metaMap):
|
def scanUses(zone: Node, check=metaMap) -> Iterator[tuple[str|None, str|None, str]]:
|
||||||
for node in zone.findAllChildren('usesMetazone'):
|
for node in zone.findAllChildren('usesMetazone'):
|
||||||
attrs = node.attributes()
|
attrs: dict[str, str] = node.attributes()
|
||||||
mzone = attrs['mzone']
|
mzone: str = attrs['mzone']
|
||||||
if mzone not in check:
|
if mzone not in check:
|
||||||
raise Error('Unknown metazone', mzone)
|
raise Error('Unknown metazone', mzone)
|
||||||
# These are UTC date-times.
|
# These are UTC date-times.
|
||||||
yield attrs.get('from'), attrs.get('to'), mzone
|
yield attrs.get('from'), attrs.get('to'), mzone
|
||||||
|
|
||||||
def sortKey(triple):
|
def sortKey(triple: tuple[str|None, str|None, str]) -> str | None:
|
||||||
start, stop, mzone = triple
|
start, stop, mzone = triple
|
||||||
# The start = None entry should sort first; since its key
|
# The start = None entry should sort first; since its key
|
||||||
# is its stop, which is likely the next entry's start, we
|
# is its stop, which is likely the next entry's start, we
|
||||||
@ -680,11 +690,11 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
# in the list, so the sorting is fatuous and the key
|
# in the list, so the sorting is fatuous and the key
|
||||||
# doesn't matter).
|
# doesn't matter).
|
||||||
|
|
||||||
def timeRep(text, notime, epoch=datetime(1970, 1, 1, 0, 0)):
|
def timeRep(text: str, notime: bool, epoch=datetime(1970, 1, 1, 0, 0)) -> int:
|
||||||
"""Map a 'yyyy-MM-dd HH:mm' string to epoch minutes.
|
"""Map a 'yyyy-MM-dd HH:mm' string to epoch minutes.
|
||||||
|
|
||||||
If the HH:mm part is omitted, second parameter notime is true to
|
If the HH:mm part is omitted, second parameter notime is true to
|
||||||
use the end of the day, false for the start. LDM specifies this
|
use the end of the day, false for the start. LDML specifies this
|
||||||
reading of the pure-date values for start and stop attributes. If
|
reading of the pure-date values for start and stop attributes. If
|
||||||
the HH:mm part is 24:00, the end of the day is also used; LDML
|
the HH:mm part is 24:00, the end of the day is also used; LDML
|
||||||
specifies this but python's datetime.fromisoformat() doesn't like
|
specifies this but python's datetime.fromisoformat() doesn't like
|
||||||
@ -704,16 +714,20 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
assert len(text) == 16, text
|
assert len(text) == 16, text
|
||||||
|
|
||||||
# If it's given with HH:mm as 24:00, this throws:
|
# If it's given with HH:mm as 24:00, this throws:
|
||||||
diff = datetime.fromisoformat(text) - epoch
|
diff: timedelta = datetime.fromisoformat(text) - epoch
|
||||||
except ValueError:
|
except ValueError:
|
||||||
diff = datetime.fromisoformat(text[:10]) - epoch
|
diff = datetime.fromisoformat(text[:10]) - epoch
|
||||||
diff += diff.__class__(days=1)
|
diff += diff.__class__(days=1)
|
||||||
|
|
||||||
assert diff.days >= 0 and diff.seconds >= 0, (diff, text)
|
assert diff.days >= 0 and diff.seconds >= 0, (diff, text)
|
||||||
assert diff.seconds % 60 == 0, (diff, text)
|
mins, secs = divmod(diff.seconds, 60)
|
||||||
return diff.days * 1440 + int(diff.seconds / 60)
|
assert secs == 0, (diff, text)
|
||||||
|
return diff.days * 1440 + mins
|
||||||
|
|
||||||
def mapTimes(triple, alpha=0, omega=(1<<32)-1, torep=timeRep):
|
def mapTimes(triple: tuple[str|None, str|None, str],
|
||||||
|
alpha: int = 0, omega: int = (1<<32) - 1,
|
||||||
|
torep: Callable[[str, bool, datetime], int] = timeRep
|
||||||
|
) -> tuple[int, int, str]:
|
||||||
start, stop, mzone = triple
|
start, stop, mzone = triple
|
||||||
start = alpha if start is None else torep(start, False)
|
start = alpha if start is None else torep(start, False)
|
||||||
stop = omega if stop is None else torep(stop, True)
|
stop = omega if stop is None else torep(stop, True)
|
||||||
@ -723,10 +737,11 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
stop = omega
|
stop = omega
|
||||||
return start, stop, mzone
|
return start, stop, mzone
|
||||||
|
|
||||||
zones = {} # { ianaId: ( (from, to, meta), ... ) }
|
# zones is { ianaId: ( (from, to, meta), ... ) }
|
||||||
|
zones: dict[str, tuple[tuple[int, int, str], ...]] = {}
|
||||||
for metaInfo in metaZones.findNodes('metaZones/metazoneInfo'):
|
for metaInfo in metaZones.findNodes('metaZones/metazoneInfo'):
|
||||||
for zone in metaInfo.findAllChildren('timezone'):
|
for zone in metaInfo.findAllChildren('timezone'):
|
||||||
iana = zoneName(zone.dom.attributes['type'].value)
|
iana: str = zoneName(zone.dom.attributes['type'].value)
|
||||||
story = tuple(sorted(scanUses(zone), key=sortKey))
|
story = tuple(sorted(scanUses(zone), key=sortKey))
|
||||||
# Only {first,last} entry can have None for {from,to}:
|
# Only {first,last} entry can have None for {from,to}:
|
||||||
assert not any(s[0] is None for s in story[1:]), (iana, story)
|
assert not any(s[0] is None for s in story[1:]), (iana, story)
|
||||||
@ -743,7 +758,7 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
for zone in bok.values())
|
for zone in bok.values())
|
||||||
for metaz, bok in metaMap.items())
|
for metaz, bok in metaMap.items())
|
||||||
|
|
||||||
territorial = {} # { territory code: IANA ID }
|
territorial: dict[str, str] = {} # { territory code: IANA ID }
|
||||||
for prime in metaZones.findNodes('primaryZones/primaryZone'):
|
for prime in metaZones.findNodes('primaryZones/primaryZone'):
|
||||||
code = prime.attributes()['iso3166']
|
code = prime.attributes()['iso3166']
|
||||||
assert code not in territorial, code
|
assert code not in territorial, code
|
||||||
@ -752,36 +767,36 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
return metaMap, zones, territorial
|
return metaMap, zones, territorial
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cldrVersion(self):
|
def cldrVersion(self) -> str:
|
||||||
# Evaluate so as to ensure __cldrVersion is set:
|
# Evaluate so as to ensure __cldrVersion is set:
|
||||||
self.__unDistinguishedAttributes
|
self.__unDistinguishedAttributes
|
||||||
return self.__cldrVersion
|
return self.__cldrVersion
|
||||||
|
|
||||||
# Implementation details
|
# Implementation details
|
||||||
def __xml(self, relative_path: str, cache = CacheDict(), read = minidom.parse):
|
def __xml(self, relPath: str, cache = CacheDict(), read = minidom.parse) -> minidom.Element:
|
||||||
try:
|
try:
|
||||||
doc = cache[relative_path]
|
doc: minidom.Element = cache[relPath]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
cache[relative_path] = doc = read(str(self.root.joinpath(relative_path))).documentElement
|
cache[relPath] = doc = read(str(self.root.joinpath(relPath))).documentElement
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
def __open(self, relative_path: str) -> TextIO:
|
def __open(self, relative_path: str) -> TextIO:
|
||||||
return self.root.joinpath(relative_path).open()
|
return self.root.joinpath(relative_path).open()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __rootLocale(self, cache = []):
|
def __rootLocale(self, cache: list[XmlScanner] = []) -> XmlScanner:
|
||||||
if not cache:
|
if not cache:
|
||||||
cache.append(self.xml('common/main/root.xml'))
|
cache.append(self.xml('common/main/root.xml'))
|
||||||
return cache[0]
|
return cache[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __supplementalData(self, cache = []):
|
def __supplementalData(self, cache: list[Supplement] = []) -> Supplement:
|
||||||
if not cache:
|
if not cache:
|
||||||
cache.append(self.supplement('supplementalData.xml'))
|
cache.append(self.supplement('supplementalData.xml'))
|
||||||
return cache[0]
|
return cache[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __numberSystems(self, cache = {}):
|
def __numberSystems(self, cache: dict[str, dict[str, str]] = {}) -> dict[str, dict[str, str]]:
|
||||||
if not cache:
|
if not cache:
|
||||||
for ignore, attrs in self.supplement('numberingSystems.xml').find('numberingSystems'):
|
for ignore, attrs in self.supplement('numberingSystems.xml').find('numberingSystems'):
|
||||||
cache[attrs['id']] = attrs
|
cache[attrs['id']] = attrs
|
||||||
@ -789,20 +804,22 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
return cache
|
return cache
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __weekData(self, cache = {}):
|
def __weekData(self, cache: dict[str, tuple[str, str, str]] = {}
|
||||||
|
) -> dict[str, tuple[str, str, str]]:
|
||||||
if not cache:
|
if not cache:
|
||||||
|
# firstDay, weStart and weEnd are all dict[str, str]
|
||||||
firstDay, weStart, weEnd = self.__getWeekData()
|
firstDay, weStart, weEnd = self.__getWeekData()
|
||||||
# Massage those into an easily-consulted form:
|
# Massage those into an easily-consulted form:
|
||||||
# World defaults given for code '001':
|
# World defaults given for code '001':
|
||||||
mon, sat, sun = firstDay['001'], weStart['001'], weEnd['001']
|
mon, sat, sun = firstDay['001'], weStart['001'], weEnd['001']
|
||||||
lands = set(firstDay) | set(weStart) | set(weEnd)
|
lands: set[str] = set(firstDay) | set(weStart) | set(weEnd)
|
||||||
cache.update((land,
|
cache.update((land,
|
||||||
(firstDay.get(land, mon), weStart.get(land, sat), weEnd.get(land, sun)))
|
(firstDay.get(land, mon), weStart.get(land, sat), weEnd.get(land, sun)))
|
||||||
for land in lands)
|
for land in lands)
|
||||||
assert cache
|
assert cache
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
def __getWeekData(self):
|
def __getWeekData(self) -> Iterator[dict[str, str]]:
|
||||||
"""Scan for data on the weekly cycle.
|
"""Scan for data on the weekly cycle.
|
||||||
|
|
||||||
Yields three mappings from locales to en's short names for
|
Yields three mappings from locales to en's short names for
|
||||||
@ -811,12 +828,12 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
gives the day on which the week starts, the second gives the
|
gives the day on which the week starts, the second gives the
|
||||||
day on which the week-end starts, the third gives the last day
|
day on which the week-end starts, the third gives the last day
|
||||||
of the week-end."""
|
of the week-end."""
|
||||||
source = self.__supplementalData
|
source: Supplement = self.__supplementalData
|
||||||
for key in ('firstDay', 'weekendStart', 'weekendEnd'):
|
for key in ('firstDay', 'weekendStart', 'weekendEnd'):
|
||||||
result = {}
|
result: dict[str, str] = {}
|
||||||
for ignore, attrs in source.find(f'weekData/{key}'):
|
for ignore, attrs in source.find(f'weekData/{key}'):
|
||||||
assert ignore == key
|
assert ignore == key
|
||||||
day = attrs['day']
|
day: str = attrs['day']
|
||||||
assert day in ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'), day
|
assert day in ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'), day
|
||||||
if 'alt' in attrs:
|
if 'alt' in attrs:
|
||||||
continue
|
continue
|
||||||
@ -825,7 +842,8 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
yield result
|
yield result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __currencyData(self, cache = {}):
|
def __currencyData(self, cache: dict[str, tuple[str, int, int]] = {}
|
||||||
|
) -> dict[str, tuple[str, int, int]]:
|
||||||
if not cache:
|
if not cache:
|
||||||
source = self.__supplementalData
|
source = self.__supplementalData
|
||||||
for elt in source.findNodes('currencyData/region'):
|
for elt in source.findNodes('currencyData/region'):
|
||||||
@ -850,15 +868,16 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
if iso:
|
if iso:
|
||||||
for tag, data in source.find(
|
for tag, data in source.find(
|
||||||
f'currencyData/fractions/info[iso4217={iso}]'):
|
f'currencyData/fractions/info[iso4217={iso}]'):
|
||||||
digits = data['digits']
|
digits = int(data['digits'])
|
||||||
rounding = data['rounding']
|
rounding = int(data['rounding'])
|
||||||
cache[territory] = iso, digits, rounding
|
cache[territory] = iso, digits, rounding
|
||||||
assert cache
|
assert cache
|
||||||
|
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __unDistinguishedAttributes(self, cache = {}):
|
def __unDistinguishedAttributes(self, cache: dict[str, tuple[str, ...]] = {}
|
||||||
|
) -> dict[str, tuple[str, ...]]:
|
||||||
"""Mapping from tag names to lists of attributes.
|
"""Mapping from tag names to lists of attributes.
|
||||||
|
|
||||||
LDML defines some attributes as 'distinguishing': if a node
|
LDML defines some attributes as 'distinguishing': if a node
|
||||||
@ -878,7 +897,7 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
def __scanLdmlDtd(self):
|
def __scanLdmlDtd(self) -> Iterator[tuple[str, tuple[str, ...]]]:
|
||||||
"""Scan the LDML DTD, record CLDR version
|
"""Scan the LDML DTD, record CLDR version
|
||||||
|
|
||||||
Yields (tag, attrs) pairs: on elements with a given tag,
|
Yields (tag, attrs) pairs: on elements with a given tag,
|
||||||
@ -920,7 +939,8 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
if tag and ignored:
|
if tag and ignored:
|
||||||
yield tag, tuple(ignored)
|
yield tag, tuple(ignored)
|
||||||
|
|
||||||
def __enumMap(self, key, cache = {}):
|
def __enumMap(self, key: str, cache: dict[str, dict[str, tuple[int, str]]] = {}
|
||||||
|
) -> dict[str, tuple[int, str]]:
|
||||||
if not cache:
|
if not cache:
|
||||||
cache['variant'] = {'': (0, 'This should never be seen outside ldml.py')}
|
cache['variant'] = {'': (0, 'This should never be seen outside ldml.py')}
|
||||||
# They're mappings from numeric value to pairs of full
|
# They're mappings from numeric value to pairs of full
|
||||||
@ -943,19 +963,19 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
return cache[key]
|
return cache[key]
|
||||||
|
|
||||||
def __codeMap(self, key, cache = {},
|
def __codeMap(self, key: str, cache: dict[str, dict[str, str]] = {},
|
||||||
# Maps our name for it to CLDR's name:
|
# Maps our name for it to CLDR's name:
|
||||||
naming = {'language': 'languages', 'script': 'scripts',
|
naming = {'language': 'languages', 'script': 'scripts',
|
||||||
'territory': 'territories', 'variant': 'variants'}):
|
'territory': 'territories', 'variant': 'variants'}) -> dict[str, str]:
|
||||||
if not cache:
|
if not cache:
|
||||||
root = self.xml('common/main/en.xml').root.findUniqueChild('localeDisplayNames')
|
root: Node = self.xml('common/main/en.xml').root.findUniqueChild('localeDisplayNames')
|
||||||
for dst, src in naming.items():
|
for dst, src in naming.items():
|
||||||
cache[dst] = dict(self.__codeMapScan(root.findUniqueChild(src)))
|
cache[dst] = dict(self.__codeMapScan(root.findUniqueChild(src)))
|
||||||
assert cache
|
assert cache
|
||||||
|
|
||||||
return cache[key]
|
return cache[key]
|
||||||
|
|
||||||
def __codeMapScan(self, node):
|
def __codeMapScan(self, node: Node) -> Iterator[tuple[str, str]]:
|
||||||
"""Get mapping from codes to element values.
|
"""Get mapping from codes to element values.
|
||||||
|
|
||||||
Passed in node is a <languages>, <scripts>, <territories> or
|
Passed in node is a <languages>, <scripts>, <territories> or
|
||||||
@ -986,23 +1006,23 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
|
|
||||||
# CLDR uses inheritance between locales to save repetition:
|
# CLDR uses inheritance between locales to save repetition:
|
||||||
@property
|
@property
|
||||||
def __parentLocale(self, cache = {}):
|
def __parentLocale(self, cache: dict[str, str] = {}) -> dict[str, str]:
|
||||||
# see http://www.unicode.org/reports/tr35/#Parent_Locales
|
# see http://www.unicode.org/reports/tr35/#Parent_Locales
|
||||||
if not cache:
|
if not cache:
|
||||||
for tag, attrs in self.__supplementalData.find('parentLocales',
|
for tag, attrs in self.__supplementalData.find('parentLocales',
|
||||||
('component',)):
|
('component',)):
|
||||||
parent = attrs.get('parent', '')
|
parent: str = attrs.get('parent', '')
|
||||||
for child in attrs['locales'].split():
|
for child in attrs['locales'].split():
|
||||||
cache[child] = parent
|
cache[child] = parent
|
||||||
assert cache
|
assert cache
|
||||||
|
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
def __scanLocaleRoots(self, name: str):
|
def __scanLocaleRoots(self, name: str) -> Iterator[Node]:
|
||||||
while name and name != 'root':
|
while name and name != 'root':
|
||||||
path = f'common/main/{name}.xml'
|
path = f'common/main/{name}.xml'
|
||||||
if self.root.joinpath(path).exists():
|
if self.root.joinpath(path).exists():
|
||||||
elt = self.__xml(path) # which has no top-level alias children:
|
elt: minidom.Element = self.__xml(path) # which has no top-level alias children:
|
||||||
assert not any(True
|
assert not any(True
|
||||||
for child in Node(elt).findAllChildren(
|
for child in Node(elt).findAllChildren(
|
||||||
'alias', allDull=True)
|
'alias', allDull=True)
|
||||||
@ -1019,11 +1039,11 @@ enumdata.py (keeping the old name as an alias):
|
|||||||
break
|
break
|
||||||
|
|
||||||
class __Seq (list): pass # No weakref for tuple and list, but list sub-class is ok.
|
class __Seq (list): pass # No weakref for tuple and list, but list sub-class is ok.
|
||||||
def __localeRoots(self, name, cache = CacheDict()):
|
def __localeRoots(self, name: str, cache = CacheDict()) -> __Seq:
|
||||||
try:
|
try:
|
||||||
chain = cache[name]
|
chain: CldrAccess.__Seq = cache[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
cache[name] = chain = self.__Seq(self.__scanLocaleRoots(name))
|
cache[name] = chain = CldrAccess.__Seq(self.__scanLocaleRoots(name))
|
||||||
return chain
|
return chain
|
||||||
|
|
||||||
# Unpolute the namespace: we don't need to export these.
|
# Unpolute the namespace: we don't need to export these.
|
||||||
|
@ -64,7 +64,7 @@ def wrap_list(lst, perline=20):
|
|||||||
yield head
|
yield head
|
||||||
return ",\n".join(", ".join(x) for x in split(lst, perline))
|
return ",\n".join(", ".join(x) for x in split(lst, perline))
|
||||||
|
|
||||||
def names_clash(cldr, enum):
|
def names_clash(cldr: str, enum: str) -> None | str:
|
||||||
"""True if the reader might not recognize cldr as the name of enum
|
"""True if the reader might not recognize cldr as the name of enum
|
||||||
|
|
||||||
First argument, cldr, is the name CLDR gives for some language,
|
First argument, cldr, is the name CLDR gives for some language,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user