1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
""" |
3
|
|
|
This module contains a HTMLExportWord Model |
4
|
|
|
""" |
5
|
|
|
|
6
|
|
|
from itertools import groupby |
7
|
|
|
from typing import Iterator |
8
|
|
|
|
9
|
|
|
from loglan_core import Word, Event, Definition |
10
|
|
|
from loglan_core.addons.exporter import ExportWordConverter |
11
|
|
|
from loglan_core.addons.word_selector import WordSelector |
12
|
|
|
from sqlalchemy import Select |
13
|
|
|
|
14
|
|
|
from app.site.compose import DEFAULT_HTML_STYLE, Item |
15
|
|
|
from app.site.compose.definition_formatter import DefinitionFormatter |
16
|
|
|
|
17
|
|
|
|
18
|
|
|
class Meaning(Item): |
19
|
|
|
def __init__(self, word: Word, style: str = DEFAULT_HTML_STYLE): |
20
|
|
|
self.word = word |
21
|
|
|
self.style = style |
22
|
|
|
self._ewc = ExportWordConverter(word) |
23
|
|
|
|
24
|
|
|
def export_definition_for_loglan(self, d: Definition) -> str: |
25
|
|
|
""" |
26
|
|
|
style: |
27
|
|
|
:return: |
28
|
|
|
""" |
29
|
|
|
tags = { |
30
|
|
|
# usage, gram, body, tags, definition |
31
|
|
|
"normal": [ |
32
|
|
|
'<span class="du">%s</span> ', |
33
|
|
|
'<span class="dg">(%s)</span> ', |
34
|
|
|
'<span class="db">%s</span>', |
35
|
|
|
' <span class="dt">[%s]</span>', |
36
|
|
|
f'<div class="definition log" id={d.id}>%s</div>', |
37
|
|
|
], |
38
|
|
|
"ultra": [ |
39
|
|
|
"<du>%s</du> ", |
40
|
|
|
"(%s) ", |
41
|
|
|
"%s", |
42
|
|
|
" [%s]", |
43
|
|
|
"<d>%s</d>", |
44
|
|
|
], |
45
|
|
|
} |
46
|
|
|
t_d_usage, t_d_gram, t_d_body, t_d_tags, t_definition = tags[self.style] |
47
|
|
|
|
48
|
|
|
def_usage = t_d_usage % d.usage.replace("%", "—") if d.usage else "" |
49
|
|
|
gram_form = f"{d.slots or ''}" + d.grammar_code |
50
|
|
|
def_gram = t_d_gram % gram_form if gram_form else "" |
51
|
|
|
def_body = t_d_body % DefinitionFormatter(d).body_formatted |
52
|
|
|
def_tags = ( |
53
|
|
|
t_d_tags % d.case_tags.replace("-", "‍-‍") if d.case_tags else "" |
54
|
|
|
) |
55
|
|
|
return t_definition % f"{def_usage}{def_gram}{def_body}{def_tags}" |
56
|
|
|
|
57
|
|
|
def html_origin(self): |
58
|
|
|
""" |
59
|
|
|
Returns: |
60
|
|
|
""" |
61
|
|
|
orig = self.word.origin |
62
|
|
|
orig_x = self.word.origin_x |
63
|
|
|
|
64
|
|
|
if not (orig or orig_x): |
65
|
|
|
return str() |
66
|
|
|
|
67
|
|
|
origin = self._compose_origin(orig, orig_x) |
68
|
|
|
|
69
|
|
|
if self.style == "normal": |
70
|
|
|
return f'<span class="m_origin"><{origin}></span> ' |
71
|
|
|
return f"<o><{origin}></o> " |
72
|
|
|
|
73
|
|
|
@staticmethod |
74
|
|
|
def _compose_origin(orig: str, orig_x: str) -> str: |
75
|
|
|
""" |
76
|
|
|
Generate basic 'origin' string |
77
|
|
|
Args: |
78
|
|
|
orig: |
79
|
|
|
orig_x: |
80
|
|
|
|
81
|
|
|
Returns: |
82
|
|
|
|
83
|
|
|
""" |
84
|
|
|
if orig_x: |
85
|
|
|
return f"{orig}={orig_x}" if orig else orig_x |
86
|
|
|
return orig |
87
|
|
|
|
88
|
|
|
def export_as_html(self) -> str: |
89
|
|
|
""" |
90
|
|
|
Returns: |
91
|
|
|
""" |
92
|
|
|
n_l = "\n" |
93
|
|
|
mid, technical, definitions, used_in = self.generate_meaning() |
94
|
|
|
if self.style == "normal": |
95
|
|
|
used_in_list = ( |
96
|
|
|
f'<div class="used_in">Used In: ' f"{used_in}</div>\n</div>" |
97
|
|
|
if used_in |
98
|
|
|
else "</div>" |
99
|
|
|
) |
100
|
|
|
return ( |
101
|
|
|
f'<div class="meaning" id="{mid}">\n' |
102
|
|
|
f'<div class="technical">{technical}</div>\n' |
103
|
|
|
f'<div class="definitions">{n_l}' |
104
|
|
|
f"{n_l.join(definitions)}\n</div>\n{used_in_list}" |
105
|
|
|
) |
106
|
|
|
|
107
|
|
|
used_in_list = f"<us>Used In: {used_in}</us>\n</m>" if used_in else "</m>" |
108
|
|
|
return ( |
109
|
|
|
f"<m>\n<t>{technical}</t>\n" |
110
|
|
|
f"<ds>{n_l}" |
111
|
|
|
f"{n_l.join(definitions)}\n</ds>\n{used_in_list}" |
112
|
|
|
) |
113
|
|
|
|
114
|
|
|
def generate_meaning(self) -> tuple: |
115
|
|
|
""" |
116
|
|
|
:return: |
117
|
|
|
""" |
118
|
|
|
( |
119
|
|
|
html_affixes, |
120
|
|
|
html_match, |
121
|
|
|
html_rank, |
122
|
|
|
html_source, |
123
|
|
|
html_type, |
124
|
|
|
html_used_in, |
125
|
|
|
html_year, |
126
|
|
|
t_technical, |
127
|
|
|
) = self.get_styled_values() |
128
|
|
|
|
129
|
|
|
html_tech = ( |
130
|
|
|
t_technical % f"{html_match}{html_type}{html_source}{html_year}{html_rank}" |
131
|
|
|
) |
132
|
|
|
html_tech = f"{html_affixes}{self.html_origin()}{html_tech}" |
133
|
|
|
return self.word.id, html_tech, self.html_definitions(), html_used_in |
134
|
|
|
|
135
|
|
|
def html_definitions(self): |
136
|
|
|
""" |
137
|
|
|
:return: |
138
|
|
|
""" |
139
|
|
|
return [self.export_definition_for_loglan(d) for d in self.word.definitions] |
140
|
|
|
|
141
|
|
|
@staticmethod |
142
|
|
|
def _tagger(tag: str, value: str | None, default_value: str | None = str()): |
143
|
|
|
return tag % value if value else default_value |
144
|
|
|
|
145
|
|
|
def used_in_as_html(self) -> str: |
146
|
|
|
tags = { |
147
|
|
|
"normal": '<a class="m_cpx">%s</a>', |
148
|
|
|
"ultra": "<cpx>%s</cpx>", |
149
|
|
|
} |
150
|
|
|
return " | ".join( |
151
|
|
|
sorted( |
152
|
|
|
{ |
153
|
|
|
tags[self.style] % cpx.name |
154
|
|
|
for cpx in filter(None, self.word.complexes) |
155
|
|
|
} |
156
|
|
|
) |
157
|
|
|
) |
158
|
|
|
|
159
|
|
|
def get_styled_values(self) -> tuple: |
160
|
|
|
""" |
161
|
|
|
|
162
|
|
|
Returns: |
163
|
|
|
|
164
|
|
|
""" |
165
|
|
|
tags = { |
166
|
|
|
"normal": [ |
167
|
|
|
'<span class="m_afx">%s</span> ', |
168
|
|
|
'<span class="m_match">%s</span> ', |
169
|
|
|
'<span class="m_rank">%s</span>', |
170
|
|
|
'<span class="m_author">%s</span> ', |
171
|
|
|
'<span class="m_type">%s</span> ', |
172
|
|
|
'<span class="m_use">%s</span>', |
173
|
|
|
'<span class="m_year">%s</span> ', |
174
|
|
|
'<span class="m_technical">%s</span>', |
175
|
|
|
], |
176
|
|
|
"ultra": [ |
177
|
|
|
"<afx>%s</afx> ", |
178
|
|
|
"%s ", |
179
|
|
|
"%s", |
180
|
|
|
"%s ", |
181
|
|
|
"%s ", |
182
|
|
|
"<use>%s</use>", |
183
|
|
|
"%s ", |
184
|
|
|
"<tec>%s</tec>", |
185
|
|
|
], |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
values = [ |
189
|
|
|
self._ewc.e_affixes, |
190
|
|
|
self.word.match, |
191
|
|
|
self.word.rank, |
192
|
|
|
self._ewc.e_source, |
193
|
|
|
self.word.type.type, |
194
|
|
|
self.used_in_as_html(), |
195
|
|
|
self._ewc.e_year, |
196
|
|
|
None, |
197
|
|
|
] |
198
|
|
|
default_values = [ |
199
|
|
|
str(), |
200
|
|
|
str(), |
201
|
|
|
str(), |
202
|
|
|
str(), |
203
|
|
|
str(), |
204
|
|
|
None, |
205
|
|
|
str(), |
206
|
|
|
tags[self.style][-1], |
207
|
|
|
] |
208
|
|
|
|
209
|
|
|
return tuple( |
210
|
|
|
self._tagger(tag, value, default_value) |
211
|
|
|
for tag, value, default_value in zip( |
212
|
|
|
tags[self.style], values, default_values |
213
|
|
|
) |
214
|
|
|
) |
215
|
|
|
|
216
|
|
|
|
217
|
|
|
class LoglanItem(Item): |
218
|
|
|
""" |
219
|
|
|
LoglanItem Class |
220
|
|
|
""" |
221
|
|
|
|
222
|
|
|
def __init__(self, words: list[Word], style: str = DEFAULT_HTML_STYLE): |
223
|
|
|
self.words = words |
224
|
|
|
self.style = style |
225
|
|
|
|
226
|
|
|
@staticmethod |
227
|
|
|
def query_select_words( |
228
|
|
|
name, case_sensitive: bool = False, event_id: Event | int | str = None |
229
|
|
|
) -> Select: |
230
|
|
|
words = ( |
231
|
|
|
WordSelector() |
232
|
|
|
.by_name(name=name, case_sensitive=case_sensitive) |
233
|
|
|
.by_event(event_id=event_id) |
234
|
|
|
) |
235
|
|
|
return words |
236
|
|
|
|
237
|
|
|
def export_as_html(self) -> str: |
238
|
|
|
word_template = { |
239
|
|
|
"normal": '<div class="word" wid="%s">\n' |
240
|
|
|
'<div class="word_line"><span class="word_name">%s</span>,</div>\n' |
241
|
|
|
'<div class="meanings">\n%s\n</div>\n</div>', |
242
|
|
|
"ultra": '<w wid="%s"><wl>%s,</wl>\n<ms>\n%s\n</ms>\n</w>', |
243
|
|
|
} |
244
|
|
|
word_name = self.words[0].name |
245
|
|
|
meanings = "\n".join( |
246
|
|
|
[Meaning(word, self.style).export_as_html() for word in self.words] |
247
|
|
|
) |
248
|
|
|
return word_template[self.style] % (word_name.lower(), word_name, meanings) |
249
|
|
|
|
250
|
|
|
|
251
|
|
|
class Composer(Item): |
252
|
|
|
def __init__(self, words: list[Word], style: str = DEFAULT_HTML_STYLE): |
253
|
|
|
self.words = words |
254
|
|
|
self.style = style |
255
|
|
|
|
256
|
|
|
def group_iterator(self) -> Iterator[list[Word]]: |
257
|
|
|
grouped_words = groupby(self.words, lambda ent: ent.name) |
258
|
|
|
for _, linked_words in grouped_words: |
259
|
|
|
yield list(linked_words) |
260
|
|
|
|
261
|
|
|
def export_as_html(self) -> str: |
262
|
|
|
""" |
263
|
|
|
Convert all words into one HTML string |
264
|
|
|
Args: |
265
|
|
|
Returns: |
266
|
|
|
|
267
|
|
|
""" |
268
|
|
|
|
269
|
|
|
words_template = { |
270
|
|
|
"normal": '<div class="words">\n%s\n</div>\n', |
271
|
|
|
"ultra": "<ws>\n%s\n</ws>\n", |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
iterator = self.group_iterator() |
275
|
|
|
items = [ |
276
|
|
|
LoglanItem(words_list, self.style).export_as_html() |
277
|
|
|
for words_list in iterator |
278
|
|
|
] |
279
|
|
|
return words_template[self.style] % "\n".join(items) |
280
|
|
|
|