utils.py 9.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#

Skia's avatar
Skia committed
25
26
import re

27
28
29
# Image utils

from io import BytesIO
Krophil's avatar
Krophil committed
30
from datetime import date
31

Krophil's avatar
Krophil committed
32
from PIL import ExifTags
Sli's avatar
Sli committed
33

Krophil's avatar
Krophil committed
34
# from exceptions import IOError
Skia's avatar
Skia committed
35
import PIL
36
37

from django.conf import settings
Skia's avatar
Skia committed
38
from django.core.files.base import ContentFile
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53

def get_start_of_semester(d=date.today()):
    """
    This function computes the start date of the semester with respect to the given date (default is today),
    and the start date given in settings.SITH_START_DATE.
    It takes the nearest past start date.
    Exemples: with SITH_START_DATE = (8, 15)
        Today      -> Start date
        2015-03-17 -> 2015-02-15
        2015-01-11 -> 2014-08-15
    """
    today = d
    year = today.year
    start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1])
Krophil's avatar
Krophil committed
54
    start2 = start.replace(month=(start.month + 6) % 12)
55
56
57
    if start > start2:
        start, start2 = start2, start
    if today < start:
Krophil's avatar
Krophil committed
58
        return start2.replace(year=year - 1)
59
60
61
62
63
    elif today < start2:
        return start
    else:
        return start2

Krophil's avatar
Krophil committed
64

Skia's avatar
Skia committed
65
66
67
68
69
70
71
def get_semester(d=date.today()):
    start = get_start_of_semester(d)
    if start.month <= 6:
        return "P" + str(start.year)[-2:]
    else:
        return "A" + str(start.year)[-2:]

Krophil's avatar
Krophil committed
72

73
74
def scale_dimension(width, height, long_edge):
    if width > height:
Sli's avatar
Sli committed
75
        ratio = long_edge * 1.0 / width
76
    else:
Sli's avatar
Sli committed
77
        ratio = long_edge * 1.0 / height
78
79
    return int(width * ratio), int(height * ratio)

Krophil's avatar
Krophil committed
80

Skia's avatar
Skia committed
81
def resize_image(im, edge, format):
82
83
84
    (w, h) = im.size
    (width, height) = scale_dimension(w, h, long_edge=edge)
    content = BytesIO()
Skia's avatar
Skia committed
85
86
    im = im.resize((width, height), PIL.Image.ANTIALIAS)
    try:
Sli's avatar
Sli committed
87
88
89
90
91
92
93
        im.save(
            fp=content,
            format=format.upper(),
            quality=90,
            optimize=True,
            progressive=True,
        )
Skia's avatar
Skia committed
94
95
    except IOError:
        PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1]
Sli's avatar
Sli committed
96
97
98
99
100
101
102
        im.save(
            fp=content,
            format=format.upper(),
            quality=90,
            optimize=True,
            progressive=True,
        )
103
104
    return ContentFile(content.getvalue())

Skia's avatar
Skia committed
105

Krophil's avatar
Krophil committed
106
107
def exif_auto_rotate(image):
    for orientation in ExifTags.TAGS.keys():
Sli's avatar
Sli committed
108
        if ExifTags.TAGS[orientation] == "Orientation":
Krophil's avatar
Krophil committed
109
110
111
112
113
114
115
116
117
            break
    exif = dict(image._getexif().items())

    if exif[orientation] == 3:
        image = image.rotate(180, expand=True)
    elif exif[orientation] == 6:
        image = image.rotate(270, expand=True)
    elif exif[orientation] == 8:
        image = image.rotate(90, expand=True)
Skia's avatar
Skia committed
118
119

    return image
Skia's avatar
Skia committed
120

Krophil's avatar
Krophil committed
121

Skia's avatar
Skia committed
122
def doku_to_markdown(text):
Skia's avatar
Skia committed
123
    """This is a quite correct doku translator"""
Sli's avatar
Sli committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    text = re.sub(
        r"([^:]|^)\/\/(.*?)\/\/", r"*\2*", text
    )  # Italic (prevents protocol:// conflict)
    text = re.sub(
        r"<del>(.*?)<\/del>", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike (may be multiline)
    text = re.sub(
        r"<sup>(.*?)<\/sup>", r"^\1^", text
    )  # Superscript (multiline not supported, because almost never used)
    text = re.sub(r"<sub>(.*?)<\/sub>", r"_\1_", text)  # Subscript (idem)

    text = re.sub(r"^======(.*?)======", r"#\1", text, flags=re.MULTILINE)  # Titles
    text = re.sub(r"^=====(.*?)=====", r"##\1", text, flags=re.MULTILINE)
    text = re.sub(r"^====(.*?)====", r"###\1", text, flags=re.MULTILINE)
    text = re.sub(r"^===(.*?)===", r"####\1", text, flags=re.MULTILINE)
    text = re.sub(r"^==(.*?)==", r"#####\1", text, flags=re.MULTILINE)
    text = re.sub(r"^=(.*?)=", r"######\1", text, flags=re.MULTILINE)

    text = re.sub(r"<nowiki>", r"<nosyntax>", text)
    text = re.sub(r"</nowiki>", r"</nosyntax>", text)
    text = re.sub(r"<code>", r"```\n", text)
    text = re.sub(r"</code>", r"\n```", text)
    text = re.sub(r"article://", r"page://", text)
    text = re.sub(r"dfile://", r"file://", text)
Skia's avatar
Skia committed
148
149

    i = 1
Sli's avatar
Sli committed
150
151
    for fn in re.findall(r"\(\((.*?)\)\)", text):  # Footnotes
        text = re.sub(r"\(\((.*?)\)\)", r"[^%s]" % i, text, count=1)
Skia's avatar
Skia committed
152
153
154
        text += "\n[^%s]: %s\n" % (i, fn)
        i += 1

Sli's avatar
Sli committed
155
    text = re.sub(r"\\{2,}[\s]", r"   \n", text)  # Carriage return
Skia's avatar
Skia committed
156

Sli's avatar
Sli committed
157
158
159
160
161
162
163
    text = re.sub(r"\[\[(.*?)\|(.*?)\]\]", r"[\2](\1)", text)  # Links
    text = re.sub(r"\[\[(.*?)\]\]", r"[\1](\1)", text)  # Links 2
    text = re.sub(r"{{(.*?)\|(.*?)}}", r'![\2](\1 "\2")', text)  # Images
    text = re.sub(r"{{(.*?)(\|(.*?))?}}", r'![\1](\1 "\1")', text)  # Images 2
    text = re.sub(
        r"{\[(.*?)(\|(.*?))?\]}", r"[\1](\1)", text
    )  # Video (transform to classic links, since we can't integrate them)
Skia's avatar
Skia committed
164

Sli's avatar
Sli committed
165
    text = re.sub(r"###(\d*?)###", r"[[[\1]]]", text)  # Progress bar
Skia's avatar
Skia committed
166

Sli's avatar
Sli committed
167
168
169
    text = re.sub(
        r"(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)", r"```\1\n```", text, flags=re.DOTALL
    )  # Block code without lists
Skia's avatar
Skia committed
170

Sli's avatar
Sli committed
171
    text = re.sub(r"( +)-(.*)", r"1.\2", text)  # Ordered lists
Skia's avatar
Skia committed
172
173
174

    new_text = []
    quote_level = 0
Krophil's avatar
Krophil committed
175
    for line in text.splitlines():  # Tables and quotes
Sli's avatar
Sli committed
176
177
178
179
        enter = re.finditer(r"\[quote(=(.+?))?\]", line)
        quit = re.finditer(r"\[/quote\]", line)
        if re.search(r"\A\s*\^(([^\^]*?)\^)*", line):  # Table part
            line = line.replace("^", "|")
Skia's avatar
Skia committed
180
            new_text.append("> " * quote_level + line)
Sli's avatar
Sli committed
181
182
183
            new_text.append(
                "> " * quote_level + "|---|"
            )  # Don't keep the text alignement in tables it's really too complex for what it's worth
Krophil's avatar
Krophil committed
184
185
        elif enter or quit:  # Quote part
            for quote in enter:  # Enter quotes (support multiple at a time)
Skia's avatar
Skia committed
186
187
188
189
190
                quote_level += 1
                try:
                    new_text.append("> " * quote_level + "##### " + quote.group(2))
                except:
                    new_text.append("> " * quote_level)
Sli's avatar
Sli committed
191
                line = line.replace(quote.group(0), "")
Sli's avatar
Sli committed
192
            final_quote_level = quote_level  # Store quote_level to use at the end, since it will be modified during quit iteration
Skia's avatar
Skia committed
193
            final_newline = False
Krophil's avatar
Krophil committed
194
            for quote in quit:  # Quit quotes (support multiple at a time)
Sli's avatar
Sli committed
195
                line = line.replace(quote.group(0), "")
Skia's avatar
Skia committed
196
197
                quote_level -= 1
                final_newline = True
Krophil's avatar
Krophil committed
198
199
            new_text.append("> " * final_quote_level + line)  # Finally append the line
            if final_newline:
Sli's avatar
Sli committed
200
201
202
                new_text.append(
                    "\n"
                )  # Add a new line to ensure the separation between the quote and the following text
Skia's avatar
Skia committed
203
204
205
206
207
        else:
            new_text.append(line)

    return "\n".join(new_text)

Krophil's avatar
Krophil committed
208

Skia's avatar
Skia committed
209
210
def bbcode_to_markdown(text):
    """This is a very basic BBcode translator"""
Sli's avatar
Sli committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
    text = re.sub(r"\[b\](.*?)\[\/b\]", r"**\1**", text, flags=re.DOTALL)  # Bold
    text = re.sub(r"\[i\](.*?)\[\/i\]", r"*\1*", text, flags=re.DOTALL)  # Italic
    text = re.sub(r"\[u\](.*?)\[\/u\]", r"__\1__", text, flags=re.DOTALL)  # Underline
    text = re.sub(
        r"\[s\](.*?)\[\/s\]", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike (may be multiline)
    text = re.sub(
        r"\[strike\](.*?)\[\/strike\]", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike 2

    text = re.sub(r"article://", r"page://", text)
    text = re.sub(r"dfile://", r"file://", text)

    text = re.sub(r"\[url=(.*?)\](.*)\[\/url\]", r"[\2](\1)", text)  # Links
    text = re.sub(r"\[url\](.*)\[\/url\]", r"\1", text)  # Links 2
    text = re.sub(r"\[img\](.*)\[\/img\]", r'![\1](\1 "\1")', text)  # Images
Skia's avatar
Skia committed
227
228
229

    new_text = []
    quote_level = 0
Krophil's avatar
Krophil committed
230
    for line in text.splitlines():  # Tables and quotes
Sli's avatar
Sli committed
231
232
        enter = re.finditer(r"\[quote(=(.+?))?\]", line)
        quit = re.finditer(r"\[/quote\]", line)
Krophil's avatar
Krophil committed
233
234
        if enter or quit:  # Quote part
            for quote in enter:  # Enter quotes (support multiple at a time)
Skia's avatar
Skia committed
235
236
237
238
239
                quote_level += 1
                try:
                    new_text.append("> " * quote_level + "##### " + quote.group(2))
                except:
                    new_text.append("> " * quote_level)
Sli's avatar
Sli committed
240
                line = line.replace(quote.group(0), "")
Sli's avatar
Sli committed
241
            final_quote_level = quote_level  # Store quote_level to use at the end, since it will be modified during quit iteration
Skia's avatar
Skia committed
242
            final_newline = False
Krophil's avatar
Krophil committed
243
            for quote in quit:  # Quit quotes (support multiple at a time)
Sli's avatar
Sli committed
244
                line = line.replace(quote.group(0), "")
Skia's avatar
Skia committed
245
                quote_level -= 1
Skia's avatar
Skia committed
246
                final_newline = True
Krophil's avatar
Krophil committed
247
248
            new_text.append("> " * final_quote_level + line)  # Finally append the line
            if final_newline:
Sli's avatar
Sli committed
249
250
251
                new_text.append(
                    "\n"
                )  # Add a new line to ensure the separation between the quote and the following text
Skia's avatar
Skia committed
252
253
254
255
        else:
            new_text.append(line)

    return "\n".join(new_text)