Passed
Push — main ( 2fdcc0...4cd557 )
by torrua
01:38
created

loglan_db.model_html.HTMLExportWord.meaning()   A

Complexity

Conditions 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 18
ccs 5
cts 5
cp 1
rs 9.85
c 0
b 0
f 0
cc 1
nop 2
crap 1
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
8
9 1
from sqlalchemy import or_
10
11 1
from loglan_db.model_base 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
        # de = definition english
67 1
        tags = {
68
            "normal": [
69
                '<span class="dg">(%s)</span>',
70
                '<span class="dt">[%s]</span> ',
71
                ' <span class="db">%s</span>',
72
                f'<span class="definition eng" id={self.id}>%s</span>',
73
                '<div class="d_line">%s</div>',
74
                '<span class="w_name">%s</span>, ',
75
                '<span class="w_origin">&lt;%s&gt;</span> ',
76
            ],
77
            "ultra": [
78
                '(%s)', '[%s] ', ' %s', '<de>%s</de>',
79
                '<ld>%s</ld>', '<wn>%s</wn>, ', '<o>&lt;%s&gt;</o> ', ],
80
        }
81 1
        t_d_gram, t_d_tags, t_d_body, t_def, t_def_line, t_word_name, t_word_origin = tags[style]
82
83 1
        gram_form = f'{str(self.slots) if self.slots else ""}' + self.grammar_code
84 1
        def_gram = t_d_gram % gram_form if gram_form else ''
85 1
        def_tags = t_d_tags % self.case_tags.replace("-", "&zwj;-&zwj;") if self.case_tags else ''
86
87 1
        def_body = HTMLExportDefinition.format_body(self.body)
88 1
        def_body = HTMLExportDefinition.highlight_key(def_body, word)
89 1
        def_body = t_d_body % def_body
90
91 1
        d_source_word = self.source_word
92 1
        w_name = d_source_word.name if not self.usage \
93
            else self.usage.replace("%", d_source_word.name)
94 1
        word_name = t_word_name % w_name
95
96 1
        w_origin_x = d_source_word.origin_x \
97
            if d_source_word.origin_x and d_source_word.type.group == "Cpx" else ''
98 1
        word_origin_x = t_word_origin % w_origin_x if w_origin_x else ''
99
100 1
        definition = t_def % f'{def_tags}{def_gram}{def_body}'
101 1
        return t_def_line % f'{word_name}{word_origin_x}{definition}'
102
103 1
    def export_for_loglan(self, style: str = DEFAULT_HTML_STYLE) -> str:
104
        """
105
106
        :param style:
107
        :return:
108
        """
109 1
        tags = {
110
            # usage, gram, body, tags, definition
111
            "normal": [
112
                '<span class="du">%s</span> ', '<span class="dg">(%s)</span> ',
113
                '<span class="db">%s</span>', ' <span class="dt">[%s]</span>',
114
                f'<div class="definition log" id={self.id}>%s</div>', ],
115
            "ultra": ['<du>%s</du> ', '(%s) ', '%s', ' [%s]', '<dl>%s</dl>', ],
116
        }
117 1
        t_d_usage, t_d_gram, t_d_body, t_d_tags, t_definition = tags[style]
118
119 1
        def_usage = t_d_usage % self.usage.replace("%", "—") if self.usage else ''
120 1
        gram_form = f"{str(self.slots) if self.slots else ''}" + self.grammar_code
121 1
        def_gram = t_d_gram % gram_form if gram_form else ''
122 1
        def_body = t_d_body % HTMLExportDefinition.format_body(self.body)
123 1
        def_tags = t_d_tags % self.case_tags.replace("-", "&zwj;-&zwj;") if self.case_tags else ''
124 1
        return t_definition % f'{def_usage}{def_gram}{def_body}{def_tags}'
125
126
127 1
class HTMLExportWord(ExportWord):
128
    """
129
    HTMLExportWord Class
130
    """
131
132 1
    @classmethod
133 1
    def html_all_by_name(
134
            cls, name: str, style: str = DEFAULT_HTML_STYLE,
135
            event_id: Union[BaseEvent, int, str] = None,
136
            case_sensitive: bool = False,
137
            partial_results: bool = False) -> Optional[str]:
138
        """
139
        Convert all words found by name into one HTML string
140
        Args:
141
            name: Name of the search word
142
            style: HTML design style
143
            event_id:
144
            case_sensitive:
145
            partial_results:
146
        Returns:
147
148
        """
149
150 1
        word_template = {
151
            "normal": '<div class="word" wid="%s">\n'
152
                      '<div class="word_line"><span class="word_name">%s</span>,</div>\n'
153
                      '<div class="meanings">\n%s\n</div>\n</div>',
154
            "ultra": '<w wid="%s"><wl>%s,</wl>\n<ms>\n%s\n</ms>\n</w>',
155
        }
156 1
        words_template = {
157
            "normal": '<div class="words">\n%s\n</div>\n',
158
            "ultra": '<ws>\n%s\n</ws>\n',
159
        }
160
161 1
        if not event_id:
162 1
            event_id = BaseEvent.latest().id
163
164 1
        event_id = int(event_id) if isinstance(event_id, (int, str)) else BaseEvent.id
165
166 1
        words = cls.query.filter(cls.event_start_id <= event_id) \
167
            .filter(or_(cls.event_end_id > event_id, cls.event_end_id.is_(None)))
168
169 1
        if case_sensitive:
170 1
            if partial_results:
171 1
                words = words.filter(cls.name.like(f"{name}%"))
172
            else:
173 1
                words = words.filter(cls.name == name)
174
        else:
175 1
            if partial_results:
176 1
                words = words.filter(cls.name.ilike(f"{name}%"))
177
            else:
178 1
                words = words.filter(cls.name.ilike(name))
179
180 1
        words = words.order_by(cls.name).all()
181
182 1
        if not words:
183 1
            return None
184
185 1
        grouped_words = groupby(words, lambda ent: ent.name)
186 1
        group_words = {k: list(g) for k, g in grouped_words}
187
188 1
        items = []
189 1
        for word_name, words in group_words.items():
190 1
            meanings = "\n".join([word.html_meaning(style) for word in words])
191 1
            items.append(word_template[style] % (word_name.lower(), word_name, meanings))
192
193 1
        return words_template[style] % "\n".join(items)
194
195 1
    def html_origin(self, style: str = DEFAULT_HTML_STYLE):
196
        """
197
198
        Args:
199
            style:
200
201
        Returns:
202
203
        """
204 1
        orig = self.origin
205 1
        orig_x = self.origin_x
206
207 1
        if (not orig) and (not orig_x):
208 1
            return str()
209
210 1
        if not orig_x:
211 1
            origin = orig
212 1
        elif not orig:
213 1
            origin = orig_x
214
        else:
215 1
            origin = f'{orig}={orig_x}'
216
217 1
        if style == "normal":
218 1
            return f'<span class="m_origin">&lt;{origin}&gt;</span> '
219 1
        return f'<o>&lt;{origin}&gt;</o> '
220
221 1
    def html_definitions(self, style: str = DEFAULT_HTML_STYLE):
222
        """
223
224
        :param style:
225
        :return:
226
        """
227 1
        return [HTMLExportDefinition.export_for_loglan(d, style=style) for d in self.definitions]
228
229 1
    def meaning(self, style: str = DEFAULT_HTML_STYLE) -> dict:
230
        """
231
232
        :param style:
233
        :return:
234
        """
235 1
        html_affixes, html_match, html_rank,\
236
            html_source, html_type, html_used_in,\
237
            html_year, t_technical = self.get_styled_values(style)
238
239 1
        html_tech = t_technical % f'{html_match}{html_type}{html_source}{html_year}{html_rank}'
240 1
        html_tech = f'{html_affixes}{self.html_origin(style)}{html_tech}'
241
242 1
        return {
243
            "mid": self.id,
244
            "technical": html_tech,
245
            "definitions": self.html_definitions(style),
246
            "used_in": html_used_in
247
        }
248
249 1
    def get_styled_values(self, style: str = DEFAULT_HTML_STYLE) -> tuple:
250
        """
251
252
        Args:
253
            style:
254
255
        Returns:
256
257
        """
258 1
        tags = {
259
            "normal": [
260
                '<span class="m_afx">%s</span> ', '<span class="m_match">%s</span> ',
261
                '<span class="m_type">%s</span> ', '<span class="m_author">%s</span> ',
262
                '<span class="m_year">%s</span> ', '<span class="m_rank">%s</span>',
263
                '<span class="m_use">%s</span>', '<span class="m_technical">%s</span>'],
264
            "ultra": [
265
                '<afx>%s</afx> ', '%s ', '%s ', '%s ',
266
                '%s ', '%s', '<use>%s</use>', '<tec>%s</tec>'],
267
        }
268 1
        t_afx, t_match, t_type, t_author, t_year, t_rank, t_use, t_technical = tags[style]
269 1
        html_affixes = t_afx % self.e_affixes if self.e_affixes else str()
270 1
        html_match = t_match % self.match if self.match else str()
271 1
        html_type = t_type % self.type.type if self.type.type else str()
272 1
        html_source = t_author % self.e_source if self.e_source else str()
273 1
        html_year = t_year % self.e_year if self.e_year else str()
274 1
        html_rank = t_rank % self.rank if self.rank else str()
275 1
        html_used_in = t_use % self.e_usedin.replace("| ", "|&nbsp;") if self.e_usedin else None
276 1
        return html_affixes, html_match, html_rank, html_source, \
277
            html_type, html_used_in, html_year, t_technical
278
279 1
    def html_meaning(self, style: str = DEFAULT_HTML_STYLE) -> str:
280
        """
281
282
        Args:
283
            style:
284
285
        Returns:
286
287
        """
288 1
        n_l = "\n"
289 1
        meaning_dict = self.meaning(style)
290 1
        if style == "normal":
291 1
            used_in_list = f'<div class="used_in">Used In: ' \
292
                           f'{meaning_dict.get("used_in")}</div>\n</div>' \
293
                if meaning_dict.get("used_in") else "</div>"
294 1
            return f'<div class="meaning" id="{meaning_dict.get("mid")}">\n' \
295
                   f'<div class="technical">{meaning_dict.get("technical")}</div>\n' \
296
                   f'<div class="definitions">{n_l}' \
297
                   f'{n_l.join(meaning_dict.get("definitions"))}\n</div>\n{used_in_list}'
298
299 1
        used_in_list = f'<us>Used In: {meaning_dict.get("used_in")}</us>\n</m>' \
300
            if meaning_dict.get("used_in") else '</m>'
301 1
        return f'<m>\n<t>{meaning_dict.get("technical")}</t>\n' \
302
               f'<ds>{n_l}' \
303
               f'{n_l.join(meaning_dict.get("definitions"))}\n</ds>\n{used_in_list}'
304
305 1
    @classmethod
306 1
    def translation_by_key(
307
            cls, key: str, language: str = None,
308
            style: str = DEFAULT_HTML_STYLE) -> Optional[str]:
309
        """
310
        Get information about loglan words by key in a foreign language
311
        Args:
312
            key:
313
            language:
314
            style:
315
316
        Returns:
317
318
        """
319
320 1
        words = cls.by_key(key, language).order_by(cls.name).all()
321
322 1
        if not words:
323 1
            return None
324
325 1
        result = {}
326
327 1
        for word in words:
328 1
            result[word.name] = []
329 1
            definitions = [
330
                HTMLExportDefinition.export_for_english(d, word=key, style=style)
331
                for d in word.definitions if key.lower() in [key.word.lower() for key in d.keys]]
332 1
            result[word.name].extend(definitions)
333
334 1
        new = '\n'
335
336 1
        return new.join([f"{new.join(definitions)}"
337
                         for _, definitions in result.items()]).strip()
338