Passed
Push — main ( ac26d8...411128 )
by torrua
01:37
created

loglan_db.model_html   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 187
dl 0
loc 358
ccs 131
cts 131
cp 1
rs 9.28
c 0
b 0
f 0
wmc 39

12 Methods

Rating   Name   Duplication   Size   Complexity  
A HTMLExportDefinition.highlight_key() 0 18 1
A HTMLExportDefinition.format_body() 0 18 1
B HTMLExportWord.html_origin() 0 25 6
A HTMLExportWord._get_stylized_words() 0 25 3
A HTMLExportWord.html_definitions() 0 7 1
A HTMLExportWord.html_meaning() 0 23 4
A HTMLExportWord.get_styled_values() 0 30 2
B HTMLExportWord.html_all_by_name() 0 50 7
A HTMLExportWord.meaning() 0 18 1
A HTMLExportDefinition.export_for_loglan() 0 22 4
B HTMLExportDefinition.export_for_english() 0 48 6
A HTMLExportWord.translation_by_key() 0 33 3
1
# -*- coding: utf-8 -*-
2 1
"""
3
HTML Export extensions of LOD database models
4
"""
5 1
import os
6 1
from itertools import groupby
7 1
from typing import Optional, Union, List
8
9 1
from sqlalchemy import or_
10
11 1
from loglan_db.model_db.base_event import BaseEvent
12 1
from loglan_db.model_export import ExportWord, ExportDefinition
13
14 1
DEFAULT_HTML_STYLE = os.getenv("DEFAULT_HTML_STYLE", "ultra")
15
16
17 1
class HTMLExportDefinition(ExportDefinition):
18
    """
19
    HTMLExportDefinition Class
20
    """
21 1
    @staticmethod
22 1
    def format_body(body: str) -> str:
23
        """
24
        Substitutes tags in the definition's body
25
        Formats punctuation signs
26
        :param body:
27
        :return:
28
        """
29 1
        to_key = '<k>'  # key
30 1
        tc_key = '</k>'
31 1
        to_log = '<l>'  # log
32 1
        tc_log = '</l>'
33
34 1
        return body \
35
            .replace("<", "&lt;").replace(">", "&gt;") \
36
            .replace("«", to_key).replace("»", tc_key) \
37
            .replace("{", to_log).replace("}", tc_log) \
38
            .replace("...", "…").replace("--", "—")
39
40 1
    @staticmethod
41 1
    def highlight_key(def_body, word) -> str:
42
        """
43
        Highlights the current key from the list, deselecting the rest
44
        :param def_body:
45
        :param word:
46
        :return:
47
        """
48
49 1
        to_key = '<k>'  # key
50 1
        tc_key = '</k>'
51
52 1
        word_template_original = f'{to_key}{word}{tc_key}'
53 1
        word_template_temp = f'<do_not_delete>{word}</do_not_delete>'
54 1
        def_body = def_body.replace(word_template_original, word_template_temp)
55 1
        def_body = def_body.replace(to_key, "").replace(tc_key, "")
56 1
        def_body = def_body.replace(word_template_temp, word_template_original)
57 1
        return def_body
58
59 1
    def export_for_english(self, word: str, style: str = DEFAULT_HTML_STYLE) -> str:
60
        """
61
62
        :param word:
63
        :param style:
64
        :return:
65
        """
66
67 1
        def _word_origin_x(d_source_word, tag):
68 1
            w_origin_x = d_source_word.origin_x \
69
                if d_source_word.origin_x and d_source_word.type.group == "Cpx" else ''
70 1
            return tag % w_origin_x if w_origin_x else ''
71
72 1
        def _word_name(usage, d_source_word, tag):
73 1
            w_name = d_source_word.name if not usage \
74
                else usage.replace("%", d_source_word.name)
75 1
            return tag % w_name
76
77
        # de = definition english
78 1
        tags = {
79
            "normal": [
80
                '<span class="dg">(%s)</span>',
81
                '<span class="dt">[%s]</span> ',
82
                ' <span class="db">%s</span>',
83
                f'<span class="definition eng" id={self.id}>%s</span>',
84
                '<div class="d_line">%s</div>',
85
                '<span class="w_name">%s</span>, ',
86
                '<span class="w_origin">&lt;%s&gt;</span> ',
87
            ],
88
            "ultra": [
89
                '(%s)', '[%s] ', ' %s', '<de>%s</de>',
90
                '<ld>%s</ld>', '<wn>%s</wn>, ', '<o>&lt;%s&gt;</o> ', ],
91
        }
92 1
        t_d_gram, t_d_tags, t_d_body, t_def, t_def_line, t_word_name, t_word_origin = tags[style]
93
94 1
        gram_form = f'{str(self.slots) if self.slots else ""}' + self.grammar_code
95 1
        def_gram = t_d_gram % gram_form if gram_form else ''
96 1
        def_tags = t_d_tags % self.case_tags.replace("-", "&zwj;-&zwj;") if self.case_tags else ''
97
98 1
        def_body = HTMLExportDefinition.format_body(self.body)
99 1
        def_body = HTMLExportDefinition.highlight_key(def_body, word)
100 1
        def_body = t_d_body % def_body
101
102 1
        word_name = _word_name(self.usage, self.source_word, t_word_name)
103 1
        word_origin_x = _word_origin_x(self.source_word, t_word_origin)
104
105 1
        definition = t_def % f'{def_tags}{def_gram}{def_body}'
106 1
        return t_def_line % f'{word_name}{word_origin_x}{definition}'
107
108 1
    def export_for_loglan(self, style: str = DEFAULT_HTML_STYLE) -> str:
109
        """
110
111
        :param style:
112
        :return:
113
        """
114 1
        tags = {
115
            # usage, gram, body, tags, definition
116
            "normal": [
117
                '<span class="du">%s</span> ', '<span class="dg">(%s)</span> ',
118
                '<span class="db">%s</span>', ' <span class="dt">[%s]</span>',
119
                f'<div class="definition log" id={self.id}>%s</div>', ],
120
            "ultra": ['<du>%s</du> ', '(%s) ', '%s', ' [%s]', '<dl>%s</dl>', ],
121
        }
122 1
        t_d_usage, t_d_gram, t_d_body, t_d_tags, t_definition = tags[style]
123
124 1
        def_usage = t_d_usage % self.usage.replace("%", "—") if self.usage else ''
125 1
        gram_form = f"{str(self.slots) if self.slots else ''}" + self.grammar_code
126 1
        def_gram = t_d_gram % gram_form if gram_form else ''
127 1
        def_body = t_d_body % HTMLExportDefinition.format_body(self.body)
128 1
        def_tags = t_d_tags % self.case_tags.replace("-", "&zwj;-&zwj;") if self.case_tags else ''
129 1
        return t_definition % f'{def_usage}{def_gram}{def_body}{def_tags}'
130
131
132 1
class HTMLExportWord(ExportWord):
133
    """
134
    HTMLExportWord Class
135
    """
136
137 1
    @classmethod
138 1
    def html_all_by_name(
139
            cls, name: str, style: str = DEFAULT_HTML_STYLE,
140
            event_id: Union[BaseEvent, int, str] = None,
141
            case_sensitive: bool = False,
142
            partial_results: bool = False) -> Optional[str]:
143
        """
144
        Convert all words found by name into one HTML string
145
        Args:
146
            name: Name of the search word
147
            style: HTML design style
148
            event_id:
149
            case_sensitive:
150
            partial_results:
151
        Returns:
152
153
        """
154
155 1
        words_template = {
156
            "normal": '<div class="words">\n%s\n</div>\n',
157
            "ultra": '<ws>\n%s\n</ws>\n',
158
        }
159
160 1
        if not event_id:
161 1
            event_id = BaseEvent.latest().id
162
163 1
        event_id = int(event_id) if isinstance(event_id, (int, str)) else BaseEvent.id
164
165 1
        words = cls.query.filter(cls.event_start_id <= event_id) \
166
            .filter(or_(cls.event_end_id > event_id, cls.event_end_id.is_(None)))
167
168 1
        if case_sensitive:
169 1
            if partial_results:
170 1
                words = words.filter(cls.name.like(f"{name}%"))
171
            else:
172 1
                words = words.filter(cls.name == name)
173
        else:
174 1
            if partial_results:
175 1
                words = words.filter(cls.name.ilike(f"{name}%"))
176
            else:
177 1
                words = words.filter(cls.name.ilike(name))
178
179 1
        words = words.order_by(cls.name).all()
180
181 1
        if not words:
182 1
            return None
183
184 1
        items = cls._get_stylized_words(words, style)
185
186 1
        return words_template[style] % "\n".join(items)
187
188 1
    @staticmethod
189 1
    def _get_stylized_words(
190
            words: list, style: str = DEFAULT_HTML_STYLE) -> List[str]:
191
        """
192
193
        Args:
194
            words:
195
            style:
196
197
        Returns:
198
199
        """
200 1
        word_template = {
201
            "normal": '<div class="word" wid="%s">\n'
202
                      '<div class="word_line"><span class="word_name">%s</span>,</div>\n'
203
                      '<div class="meanings">\n%s\n</div>\n</div>',
204
            "ultra": '<w wid="%s"><wl>%s,</wl>\n<ms>\n%s\n</ms>\n</w>',
205
        }
206 1
        grouped_words = groupby(words, lambda ent: ent.name)
207 1
        group_words = {k: list(g) for k, g in grouped_words}
208 1
        items = []
209 1
        for word_name, words_list in group_words.items():
210 1
            meanings = "\n".join([word.html_meaning(style) for word in words_list])
211 1
            items.append(word_template[style] % (word_name.lower(), word_name, meanings))
212 1
        return items
213
214 1
    def html_origin(self, style: str = DEFAULT_HTML_STYLE):
215
        """
216
217
        Args:
218
            style:
219
220
        Returns:
221
222
        """
223 1
        orig = self.origin
224 1
        orig_x = self.origin_x
225
226 1
        if (not orig) and (not orig_x):
227 1
            return str()
228
229 1
        if not orig_x:
230 1
            origin = orig
231 1
        elif not orig:
232 1
            origin = orig_x
233
        else:
234 1
            origin = f'{orig}={orig_x}'
235
236 1
        if style == "normal":
237 1
            return f'<span class="m_origin">&lt;{origin}&gt;</span> '
238 1
        return f'<o>&lt;{origin}&gt;</o> '
239
240 1
    def html_definitions(self, style: str = DEFAULT_HTML_STYLE):
241
        """
242
243
        :param style:
244
        :return:
245
        """
246 1
        return [HTMLExportDefinition.export_for_loglan(d, style=style) for d in list(self.definitions)]
247
248 1
    def meaning(self, style: str = DEFAULT_HTML_STYLE) -> dict:
249
        """
250
251
        :param style:
252
        :return:
253
        """
254 1
        html_affixes, html_match, html_rank,\
255
            html_source, html_type, html_used_in,\
256
            html_year, t_technical = self.get_styled_values(style)
257
258 1
        html_tech = t_technical % f'{html_match}{html_type}{html_source}{html_year}{html_rank}'
259 1
        html_tech = f'{html_affixes}{self.html_origin(style)}{html_tech}'
260
261 1
        return {
262
            "mid": self.id,
263
            "technical": html_tech,
264
            "definitions": self.html_definitions(style),
265
            "used_in": html_used_in
266
        }
267
268 1
    def get_styled_values(self, style: str = DEFAULT_HTML_STYLE) -> tuple:
269
        """
270
271
        Args:
272
            style:
273
274
        Returns:
275
276
        """
277 1
        tags = {
278
            "normal": [
279
                '<span class="m_afx">%s</span> ', '<span class="m_match">%s</span> ',
280
                '<span class="m_rank">%s</span>', '<span class="m_author">%s</span> ',
281
                '<span class="m_type">%s</span> ', '<span class="m_use">%s</span>',
282
                '<span class="m_year">%s</span> ', '<span class="m_technical">%s</span>'],
283
            "ultra": [
284
                '<afx>%s</afx> ', '%s ', '%s', '%s ', '%s ',
285
                '<use>%s</use>', '%s ', '<tec>%s</tec>'],
286
        }
287
288 1
        def _stringer(tag: str, value: Optional[str], default_value: Optional[str] = str()):
289 1
            return tag % value if value else default_value
290
291
        # t_afx, t_match, t_type, t_author, t_year, t_rank, t_use, t_technical = tags[style]
292 1
        values = [self.e_affixes, self.match, self.rank, self.e_source, self.type.type,
293
                  self.e_usedin.replace("| ", "|&nbsp;"), self.e_year, None]
294 1
        default_values = [str(), str(), str(), str(), str(), None, str(), tags[style][-1]]
295
296 1
        return tuple([_stringer(tag, value, default_value) for tag, value, default_value in
297
                      zip(tags[style], values, default_values)])
298
299 1
    def html_meaning(self, style: str = DEFAULT_HTML_STYLE) -> str:
300
        """
301
302
        Args:
303
            style:
304
305
        Returns:
306
307
        """
308 1
        n_l = "\n"
309 1
        meaning_dict = self.meaning(style)
310 1
        if style == "normal":
311 1
            used_in_list = f'<div class="used_in">Used In: ' \
312
                           f'{meaning_dict.get("used_in")}</div>\n</div>' \
313
                if meaning_dict.get("used_in") else "</div>"
314 1
            return f'<div class="meaning" id="{meaning_dict.get("mid")}">\n' \
315
                   f'<div class="technical">{meaning_dict.get("technical")}</div>\n' \
316
                   f'<div class="definitions">{n_l}' \
317
                   f'{n_l.join(meaning_dict.get("definitions"))}\n</div>\n{used_in_list}'
318
319 1
        used_in_list = f'<us>Used In: {meaning_dict.get("used_in")}</us>\n</m>' \
320
            if meaning_dict.get("used_in") else '</m>'
321 1
        return f'<m>\n<t>{meaning_dict.get("technical")}</t>\n' \
322
               f'<ds>{n_l}' \
323
               f'{n_l.join(meaning_dict.get("definitions"))}\n</ds>\n{used_in_list}'
324
325 1
    @classmethod
326 1
    def translation_by_key(
327
            cls, key: str, language: str = None,
328
            style: str = DEFAULT_HTML_STYLE) -> Optional[str]:
329
        """
330
        Get information about loglan words by key in a foreign language
331
        Args:
332
            key:
333
            language:
334
            style:
335
336
        Returns:
337
338
        """
339
340 1
        words = cls.by_key(key, language).order_by(cls.name).all()
341
342 1
        if not words:
343 1
            return None
344
345 1
        result = {}
346
347 1
        for word in words:
348 1
            result[word.name] = []
349 1
            definitions = [
350
                HTMLExportDefinition.export_for_english(d, word=key, style=style)
351
                for d in word.definitions if key.lower() in [key.word.lower() for key in d.keys]]
352 1
            result[word.name].extend(definitions)
353
354 1
        new = '\n'
355
356 1
        return new.join([f"{new.join(definitions)}"
357
                         for _, definitions in result.items()]).strip()
358