polyglossa 2026.11.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polyglossa/__init__.py +42 -0
- polyglossa/_catalog.py +486 -0
- polyglossa/_version.py +1 -0
- polyglossa/catalog.tsv +10450 -0
- polyglossa/py.typed +0 -0
- polyglossa-2026.11.6.dist-info/METADATA +46 -0
- polyglossa-2026.11.6.dist-info/RECORD +11 -0
- polyglossa-2026.11.6.dist-info/WHEEL +5 -0
- polyglossa-2026.11.6.dist-info/licenses/LICENSE +21 -0
- polyglossa-2026.11.6.dist-info/licenses/NOTICE +8 -0
- polyglossa-2026.11.6.dist-info/top_level.txt +1 -0
polyglossa/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""English Wiktionary's languages, language families and scripts.
|
|
2
|
+
|
|
3
|
+
>>> english = Language["en"]
|
|
4
|
+
>>> english.name
|
|
5
|
+
'English'
|
|
6
|
+
>>> english.family.name
|
|
7
|
+
'Anglic'
|
|
8
|
+
>>> Language.get("xx") is None
|
|
9
|
+
True
|
|
10
|
+
>>> "ang" in Language
|
|
11
|
+
True
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from polyglossa._catalog import (
|
|
15
|
+
Catalog,
|
|
16
|
+
Language,
|
|
17
|
+
LanguageFamily,
|
|
18
|
+
LanguageType,
|
|
19
|
+
NotACatalogError,
|
|
20
|
+
Script,
|
|
21
|
+
ScriptDirection,
|
|
22
|
+
UnknownFamilyError,
|
|
23
|
+
UnknownLanguageError,
|
|
24
|
+
UnknownScriptError,
|
|
25
|
+
Variety,
|
|
26
|
+
)
|
|
27
|
+
from polyglossa._version import __version__
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Catalog",
|
|
31
|
+
"Language",
|
|
32
|
+
"LanguageFamily",
|
|
33
|
+
"LanguageType",
|
|
34
|
+
"NotACatalogError",
|
|
35
|
+
"Script",
|
|
36
|
+
"ScriptDirection",
|
|
37
|
+
"UnknownFamilyError",
|
|
38
|
+
"UnknownLanguageError",
|
|
39
|
+
"UnknownScriptError",
|
|
40
|
+
"Variety",
|
|
41
|
+
"__version__",
|
|
42
|
+
]
|
polyglossa/_catalog.py
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""The catalog and what it holds, read from the tab-separated ``catalog.tsv`` the Kotlin library writes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import enum
|
|
6
|
+
import functools
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from importlib import resources
|
|
9
|
+
from typing import Dict, Iterator, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
HEADER = "polyglossa\t1"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UnknownLanguageError(KeyError):
|
|
15
|
+
"""Wiktionary has no language with this code."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UnknownFamilyError(KeyError):
|
|
19
|
+
"""Wiktionary has no family with this code."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnknownScriptError(KeyError):
|
|
23
|
+
"""Wiktionary has no script with this code."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotACatalogError(ValueError):
|
|
27
|
+
"""The text is not a catalog Polyglossa wrote."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LanguageType(enum.Enum):
|
|
31
|
+
"""What kind of language Wiktionary takes one to be, which decides where its entries may live."""
|
|
32
|
+
|
|
33
|
+
REGULAR = "regular"
|
|
34
|
+
RECONSTRUCTED = "reconstructed"
|
|
35
|
+
APPENDIX_CONSTRUCTED = "appendix-constructed"
|
|
36
|
+
ETYMOLOGY_ONLY = "etymology-only"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ScriptDirection(enum.Enum):
|
|
40
|
+
"""Which way a script runs."""
|
|
41
|
+
|
|
42
|
+
LEFT_TO_RIGHT = "ltr"
|
|
43
|
+
RIGHT_TO_LEFT = "rtl"
|
|
44
|
+
VERTICAL_LEFT_TO_RIGHT = "vertical-ltr"
|
|
45
|
+
VERTICAL_RIGHT_TO_LEFT = "vertical-rtl"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class Variety:
|
|
50
|
+
"""A variety of a language Wiktionary names without giving it a code, such as a dialect."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
other_names: Tuple[str, ...] = ()
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return self.name if not self.other_names else f"{self.name} ({', '.join(self.other_names)})"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _Lookup(type):
|
|
60
|
+
"""Lets a type be indexed and searched by code, ``Language["en"]`` and ``"en" in Language``."""
|
|
61
|
+
|
|
62
|
+
def __getitem__(cls, code: str):
|
|
63
|
+
return cls.of(code)
|
|
64
|
+
|
|
65
|
+
def __contains__(cls, code: object) -> bool:
|
|
66
|
+
return isinstance(code, str) and cls.get(code) is not None
|
|
67
|
+
|
|
68
|
+
def __iter__(cls) -> Iterator:
|
|
69
|
+
return iter(cls.all())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True, eq=True)
|
|
73
|
+
class Language(metaclass=_Lookup):
|
|
74
|
+
"""One language as English Wiktionary's ``Module:languages`` and ``Module:etymology languages`` describe it."""
|
|
75
|
+
|
|
76
|
+
code: str
|
|
77
|
+
name: str
|
|
78
|
+
type: LanguageType
|
|
79
|
+
wikidata: Optional[str]
|
|
80
|
+
aliases: Tuple[str, ...]
|
|
81
|
+
varieties: Tuple[Variety, ...]
|
|
82
|
+
_family_code: Optional[str] = field(repr=False)
|
|
83
|
+
_script_codes: Tuple[str, ...] = field(repr=False)
|
|
84
|
+
_ancestor_codes: Tuple[str, ...] = field(repr=False)
|
|
85
|
+
_parent_code: Optional[str] = field(repr=False)
|
|
86
|
+
_catalog: "Catalog" = field(repr=False, compare=False, hash=False)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def is_etymology_only(self) -> bool:
|
|
90
|
+
"""Whether it is named in etymologies only, with no section of its own in an entry."""
|
|
91
|
+
return self.type is LanguageType.ETYMOLOGY_ONLY
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def family(self) -> Optional["LanguageFamily"]:
|
|
95
|
+
"""The family it belongs to, or ``None``; an etymology-only variety has its language's."""
|
|
96
|
+
code = self._family_code or (self.parent._family_code if self.parent else None)
|
|
97
|
+
return self._catalog.family(code) if code else None
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def scripts(self) -> List["Script"]:
|
|
101
|
+
"""The scripts it is written in, most usual first, those the catalog knows."""
|
|
102
|
+
return [s for s in (self._catalog.script(c) for c in self._script_codes) if s is not None]
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def ancestors(self) -> List["Language"]:
|
|
106
|
+
"""The languages it descends from, those the catalog knows."""
|
|
107
|
+
return [a for a in (self._catalog.get(c) for c in self._ancestor_codes) if a is not None]
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def parent(self) -> Optional["Language"]:
|
|
111
|
+
"""The language an etymology-only variety belongs to, or ``None`` for any other."""
|
|
112
|
+
return self._catalog.get(self._parent_code) if self._parent_code else None
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def full(self) -> "Language":
|
|
116
|
+
"""The full language this stands for: itself, or for an etymology-only variety the one it belongs to."""
|
|
117
|
+
language, seen = self, {self.code}
|
|
118
|
+
while language.is_etymology_only and language.parent and language.parent.code not in seen:
|
|
119
|
+
language = language.parent
|
|
120
|
+
seen.add(language.code)
|
|
121
|
+
return language
|
|
122
|
+
|
|
123
|
+
def __str__(self) -> str:
|
|
124
|
+
return f"{self.name} ({self.code})"
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def of(cls, code: str) -> "Language":
|
|
128
|
+
"""The language with ``code``; raises :class:`UnknownLanguageError` when Wiktionary has none."""
|
|
129
|
+
language = Catalog.bundled().get(code)
|
|
130
|
+
if language is None:
|
|
131
|
+
raise UnknownLanguageError(code)
|
|
132
|
+
return language
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def get(cls, code: str) -> Optional["Language"]:
|
|
136
|
+
"""The language with ``code``, or ``None`` when Wiktionary has none."""
|
|
137
|
+
return Catalog.bundled().get(code)
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def named(cls, name: str) -> Optional["Language"]:
|
|
141
|
+
"""The language called ``name``, as its section in an entry is headed, or ``None``."""
|
|
142
|
+
return Catalog.bundled().named(name)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def all(cls) -> List["Language"]:
|
|
146
|
+
"""Every language and etymology-only variety, sorted by code."""
|
|
147
|
+
return Catalog.bundled().languages
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True, eq=True)
|
|
151
|
+
class LanguageFamily(metaclass=_Lookup):
|
|
152
|
+
"""One language family as English Wiktionary's ``Module:families`` describes it."""
|
|
153
|
+
|
|
154
|
+
code: str
|
|
155
|
+
name: str
|
|
156
|
+
wikidata: Optional[str]
|
|
157
|
+
aliases: Tuple[str, ...]
|
|
158
|
+
_parent_code: Optional[str] = field(repr=False)
|
|
159
|
+
_proto_language_code: Optional[str] = field(repr=False)
|
|
160
|
+
_catalog: "Catalog" = field(repr=False, compare=False, hash=False)
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def parent(self) -> Optional["LanguageFamily"]:
|
|
164
|
+
"""The family it belongs to, or ``None``."""
|
|
165
|
+
return self._catalog.family(self._parent_code) if self._parent_code else None
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def proto_language(self) -> Optional[Language]:
|
|
169
|
+
"""The language it descends from: the one Wiktionary names, or else ``<code>-pro`` when there is one."""
|
|
170
|
+
return self._catalog.get(self._proto_language_code or f"{self.code}-pro")
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def languages(self) -> List[Language]:
|
|
174
|
+
"""The languages in it directly, not those of the families under it, sorted by code."""
|
|
175
|
+
return self._catalog._in_family(self.code)
|
|
176
|
+
|
|
177
|
+
def __str__(self) -> str:
|
|
178
|
+
return f"{self.name} ({self.code})"
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def of(cls, code: str) -> "LanguageFamily":
|
|
182
|
+
"""The family with ``code``; raises :class:`UnknownFamilyError` when Wiktionary has none."""
|
|
183
|
+
family = Catalog.bundled().family(code)
|
|
184
|
+
if family is None:
|
|
185
|
+
raise UnknownFamilyError(code)
|
|
186
|
+
return family
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def get(cls, code: str) -> Optional["LanguageFamily"]:
|
|
190
|
+
"""The family with ``code``, or ``None`` when Wiktionary has none."""
|
|
191
|
+
return Catalog.bundled().family(code)
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def all(cls) -> List["LanguageFamily"]:
|
|
195
|
+
"""Every family, sorted by code."""
|
|
196
|
+
return Catalog.bundled().families
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass(frozen=True, eq=True)
|
|
200
|
+
class Script(metaclass=_Lookup):
|
|
201
|
+
"""One script as English Wiktionary's ``Module:scripts/data`` describes it."""
|
|
202
|
+
|
|
203
|
+
code: str
|
|
204
|
+
name: str
|
|
205
|
+
wikidata: Optional[str]
|
|
206
|
+
system: Optional[str]
|
|
207
|
+
aliases: Tuple[str, ...]
|
|
208
|
+
direction: ScriptDirection
|
|
209
|
+
_parent_code: Optional[str] = field(repr=False)
|
|
210
|
+
_local_name_codes: Tuple[Tuple[str, str], ...] = field(repr=False)
|
|
211
|
+
_catalog: "Catalog" = field(repr=False, compare=False, hash=False)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def parent(self) -> Optional["Script"]:
|
|
215
|
+
"""The script this is a variety of, such as Han for Simplified Han, or ``None``."""
|
|
216
|
+
return self._catalog.script(self._parent_code) if self._parent_code else None
|
|
217
|
+
|
|
218
|
+
def name_for(self, language: Language) -> str:
|
|
219
|
+
"""The name ``language`` gives this script, which is :attr:`name` unless it has its own."""
|
|
220
|
+
return dict(self._local_name_codes).get(language.code, self.name)
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def local_names(self) -> Dict[Language, str]:
|
|
224
|
+
"""The names some languages give it instead of :attr:`name`, such as Balbodh for Marathi's Devanagari."""
|
|
225
|
+
names = {}
|
|
226
|
+
for code, local in self._local_name_codes:
|
|
227
|
+
language = self._catalog.get(code)
|
|
228
|
+
if language is not None:
|
|
229
|
+
names[language] = local
|
|
230
|
+
return names
|
|
231
|
+
|
|
232
|
+
def __str__(self) -> str:
|
|
233
|
+
return f"{self.name} ({self.code})"
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def of(cls, code: str) -> "Script":
|
|
237
|
+
"""The script with ``code``; raises :class:`UnknownScriptError` when Wiktionary has none."""
|
|
238
|
+
script = Catalog.bundled().script(code)
|
|
239
|
+
if script is None:
|
|
240
|
+
raise UnknownScriptError(code)
|
|
241
|
+
return script
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def get(cls, code: str) -> Optional["Script"]:
|
|
245
|
+
"""The script with ``code``, or ``None`` when Wiktionary has none."""
|
|
246
|
+
return Catalog.bundled().script(code)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def named(cls, name: str) -> Optional["Script"]:
|
|
250
|
+
"""The script called ``name``, or ``None``; the one without a parent when several are."""
|
|
251
|
+
return Catalog.bundled().script_named(name)
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def all(cls) -> List["Script"]:
|
|
255
|
+
"""Every script, sorted by code."""
|
|
256
|
+
return Catalog.bundled().scripts
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Catalog:
|
|
260
|
+
"""A set of languages, families and scripts that know one another.
|
|
261
|
+
|
|
262
|
+
:meth:`bundled`, English Wiktionary's own, is the one :class:`Language`, :class:`LanguageFamily` and
|
|
263
|
+
:class:`Script` look up.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(self, tsv: str) -> None:
|
|
267
|
+
self.date: Optional[str] = None
|
|
268
|
+
self._languages: Dict[str, Language] = {}
|
|
269
|
+
self._families: Dict[str, LanguageFamily] = {}
|
|
270
|
+
self._scripts: Dict[str, Script] = {}
|
|
271
|
+
_read(tsv, self)
|
|
272
|
+
names: Dict[str, Language] = {}
|
|
273
|
+
for language in self._languages.values():
|
|
274
|
+
if language.is_etymology_only:
|
|
275
|
+
names[language.name] = language
|
|
276
|
+
for language in self._languages.values():
|
|
277
|
+
if not language.is_etymology_only:
|
|
278
|
+
names[language.name] = language
|
|
279
|
+
self._by_name = names
|
|
280
|
+
script_names: Dict[str, Script] = {}
|
|
281
|
+
for script in self._scripts.values():
|
|
282
|
+
if script._parent_code is not None:
|
|
283
|
+
script_names[script.name] = script
|
|
284
|
+
for script in self._scripts.values():
|
|
285
|
+
if script._parent_code is None:
|
|
286
|
+
script_names[script.name] = script
|
|
287
|
+
self._scripts_by_name = script_names
|
|
288
|
+
by_family: Dict[str, List[Language]] = {}
|
|
289
|
+
for language in sorted(self._languages.values(), key=lambda it: it.code):
|
|
290
|
+
if language._family_code:
|
|
291
|
+
by_family.setdefault(language._family_code, []).append(language)
|
|
292
|
+
self._by_family = by_family
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
@functools.lru_cache(maxsize=None)
|
|
296
|
+
def bundled() -> "Catalog":
|
|
297
|
+
"""English Wiktionary's languages, families and scripts as the package was built with them."""
|
|
298
|
+
text = resources.files("polyglossa").joinpath("catalog.tsv").read_text(encoding="utf-8")
|
|
299
|
+
return Catalog(text)
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def languages(self) -> List[Language]:
|
|
303
|
+
"""Every language and etymology-only variety, sorted by code."""
|
|
304
|
+
return sorted(self._languages.values(), key=lambda it: it.code)
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def families(self) -> List[LanguageFamily]:
|
|
308
|
+
"""Every family, sorted by code."""
|
|
309
|
+
return sorted(self._families.values(), key=lambda it: it.code)
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def scripts(self) -> List[Script]:
|
|
313
|
+
"""Every script, sorted by code."""
|
|
314
|
+
return sorted(self._scripts.values(), key=lambda it: it.code)
|
|
315
|
+
|
|
316
|
+
def get(self, code: Optional[str]) -> Optional[Language]:
|
|
317
|
+
"""The language with ``code``, or ``None``."""
|
|
318
|
+
return self._languages.get(code) if code is not None else None
|
|
319
|
+
|
|
320
|
+
def __getitem__(self, code: str) -> Language:
|
|
321
|
+
language = self.get(code)
|
|
322
|
+
if language is None:
|
|
323
|
+
raise UnknownLanguageError(code)
|
|
324
|
+
return language
|
|
325
|
+
|
|
326
|
+
def __contains__(self, code: object) -> bool:
|
|
327
|
+
return isinstance(code, str) and code in self._languages
|
|
328
|
+
|
|
329
|
+
def named(self, name: str) -> Optional[Language]:
|
|
330
|
+
"""The language called ``name``; a full language wins over an etymology-only variety of the same name."""
|
|
331
|
+
return self._by_name.get(name.strip())
|
|
332
|
+
|
|
333
|
+
def family(self, code: Optional[str]) -> Optional[LanguageFamily]:
|
|
334
|
+
"""The family with ``code``, or ``None``."""
|
|
335
|
+
return self._families.get(code) if code is not None else None
|
|
336
|
+
|
|
337
|
+
def script(self, code: Optional[str]) -> Optional[Script]:
|
|
338
|
+
"""The script with ``code``, or ``None``."""
|
|
339
|
+
return self._scripts.get(code) if code is not None else None
|
|
340
|
+
|
|
341
|
+
def script_named(self, name: str) -> Optional[Script]:
|
|
342
|
+
"""The script called ``name``; one that is no variety of another wins over one that is."""
|
|
343
|
+
return self._scripts_by_name.get(name.strip())
|
|
344
|
+
|
|
345
|
+
def _in_family(self, code: str) -> List[Language]:
|
|
346
|
+
return list(self._by_family.get(code, []))
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
_TYPES = {"": LanguageType.REGULAR, "r": LanguageType.RECONSTRUCTED, "a": LanguageType.APPENDIX_CONSTRUCTED,
|
|
350
|
+
"e": LanguageType.ETYMOLOGY_ONLY}
|
|
351
|
+
_DIRECTIONS = {"": ScriptDirection.LEFT_TO_RIGHT, "rtl": ScriptDirection.RIGHT_TO_LEFT,
|
|
352
|
+
"vertical-ltr": ScriptDirection.VERTICAL_LEFT_TO_RIGHT,
|
|
353
|
+
"vertical-rtl": ScriptDirection.VERTICAL_RIGHT_TO_LEFT}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _read(text: str, catalog: Catalog) -> None:
|
|
357
|
+
lines = text.split("\n")
|
|
358
|
+
header = lines[0] if lines else ""
|
|
359
|
+
if header != HEADER and not header.startswith(HEADER + "\t"):
|
|
360
|
+
raise NotACatalogError("not a catalog Polyglossa wrote")
|
|
361
|
+
catalog.date = header[len(HEADER) + 1:] or None
|
|
362
|
+
for line in lines[1:]:
|
|
363
|
+
if not line:
|
|
364
|
+
continue
|
|
365
|
+
cells = _Cells(line.split("\t"))
|
|
366
|
+
kind = cells.next()
|
|
367
|
+
if kind == "L":
|
|
368
|
+
language = _language(cells, catalog)
|
|
369
|
+
catalog._languages[language.code] = language
|
|
370
|
+
elif kind == "F":
|
|
371
|
+
family = _family(cells, catalog)
|
|
372
|
+
catalog._families[family.code] = family
|
|
373
|
+
elif kind == "S":
|
|
374
|
+
script = _script(cells, catalog)
|
|
375
|
+
catalog._scripts[script.code] = script
|
|
376
|
+
else:
|
|
377
|
+
raise NotACatalogError(f"not a language, a family or a script: {line}")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class _Cells:
|
|
381
|
+
"""The cells of one line, read in order; one left off the end reads as empty."""
|
|
382
|
+
|
|
383
|
+
def __init__(self, cells: List[str]) -> None:
|
|
384
|
+
self._cells = cells
|
|
385
|
+
self._at = 0
|
|
386
|
+
|
|
387
|
+
def next(self) -> str:
|
|
388
|
+
cell = self._cells[self._at] if self._at < len(self._cells) else ""
|
|
389
|
+
self._at += 1
|
|
390
|
+
return cell
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _language(cells: _Cells, catalog: Catalog) -> Language:
|
|
394
|
+
code = cells.next()
|
|
395
|
+
name = _unescape(cells.next())
|
|
396
|
+
mark = cells.next()
|
|
397
|
+
if mark not in _TYPES:
|
|
398
|
+
raise NotACatalogError(f"no language type {mark}")
|
|
399
|
+
wikidata = _item(cells.next())
|
|
400
|
+
family = cells.next() or None
|
|
401
|
+
scripts = _codes(cells.next())
|
|
402
|
+
ancestors = _codes(cells.next())
|
|
403
|
+
aliases = _names(cells.next())
|
|
404
|
+
parent = cells.next() or None
|
|
405
|
+
varieties = []
|
|
406
|
+
for variety in _split(cells.next(), ";"):
|
|
407
|
+
names = [_unescape(it) for it in _split(variety, "|")]
|
|
408
|
+
varieties.append(Variety(names[0], tuple(names[1:])))
|
|
409
|
+
return Language(code, name, _TYPES[mark], wikidata, aliases, tuple(varieties), family, scripts, ancestors,
|
|
410
|
+
parent, catalog)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _family(cells: _Cells, catalog: Catalog) -> LanguageFamily:
|
|
414
|
+
code = cells.next()
|
|
415
|
+
name = _unescape(cells.next())
|
|
416
|
+
wikidata = _item(cells.next())
|
|
417
|
+
parent = cells.next() or None
|
|
418
|
+
proto = cells.next() or None
|
|
419
|
+
aliases = _names(cells.next())
|
|
420
|
+
return LanguageFamily(code, name, wikidata, aliases, parent, proto, catalog)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _script(cells: _Cells, catalog: Catalog) -> Script:
|
|
424
|
+
code = cells.next()
|
|
425
|
+
name = _unescape(cells.next())
|
|
426
|
+
wikidata = _item(cells.next())
|
|
427
|
+
system = _unescape(cells.next()) or None
|
|
428
|
+
aliases = _names(cells.next())
|
|
429
|
+
parent = cells.next() or None
|
|
430
|
+
direction = _DIRECTIONS.get(cells.next(), ScriptDirection.LEFT_TO_RIGHT)
|
|
431
|
+
local = []
|
|
432
|
+
for entry in _names(cells.next()):
|
|
433
|
+
language, _, local_name = entry.partition("=")
|
|
434
|
+
local.append((language, local_name))
|
|
435
|
+
return Script(code, name, wikidata, system, aliases, direction, parent, tuple(local), catalog)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _item(cell: str) -> Optional[str]:
|
|
439
|
+
return "Q" + cell if cell else None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _codes(cell: str) -> Tuple[str, ...]:
|
|
443
|
+
return tuple(cell.split(",")) if cell else ()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _names(cell: str) -> Tuple[str, ...]:
|
|
447
|
+
return tuple(_unescape(it) for it in _split(cell, ";"))
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _split(cell: str, separator: str) -> List[str]:
|
|
451
|
+
"""``cell`` split at each ``separator`` that is not escaped, the pieces left escaped; none for an empty cell."""
|
|
452
|
+
if not cell:
|
|
453
|
+
return []
|
|
454
|
+
pieces, current, escaped = [], [], False
|
|
455
|
+
for character in cell:
|
|
456
|
+
if escaped:
|
|
457
|
+
current.append(character)
|
|
458
|
+
escaped = False
|
|
459
|
+
elif character == "\\":
|
|
460
|
+
current.append(character)
|
|
461
|
+
escaped = True
|
|
462
|
+
elif character == separator:
|
|
463
|
+
pieces.append("".join(current))
|
|
464
|
+
current = []
|
|
465
|
+
else:
|
|
466
|
+
current.append(character)
|
|
467
|
+
pieces.append("".join(current))
|
|
468
|
+
return pieces
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _unescape(text: str) -> str:
|
|
472
|
+
"""``text`` with the Kotlin library's escapes undone: ``\\t`` and ``\\n``, and any other character as itself."""
|
|
473
|
+
if "\\" not in text:
|
|
474
|
+
return text
|
|
475
|
+
out, escaped = [], False
|
|
476
|
+
for character in text:
|
|
477
|
+
if escaped:
|
|
478
|
+
out.append({"t": "\t", "n": "\n"}.get(character, character))
|
|
479
|
+
escaped = False
|
|
480
|
+
elif character == "\\":
|
|
481
|
+
escaped = True
|
|
482
|
+
else:
|
|
483
|
+
out.append(character)
|
|
484
|
+
if escaped:
|
|
485
|
+
out.append("\\")
|
|
486
|
+
return "".join(out)
|
polyglossa/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2026.11.6"
|