Files
calendario_dei_santi/calendario_dei_santi.py
Andrea Rigoni ee3094c83b Initial commit
2023-12-10 15:57:44 +01:00

470 lines
18 KiB
Python
Executable File

#! /usr/bin/python
'''
Copyright (C) 2017 Artem Synytsyn a.synytsyn@gmail.com
#TODO: Code cleaning and refactoring
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
'''
import inkex
import sys
from lxml import etree
from math import *
import re
from typing import Any
import wikipedia
import openpyxl
import math, decimal, datetime
dec = decimal.Decimal
## __ ___ _ _ ____ _ _
## \ \ / (_) | _(_) ___| __ _(_)_ __ | |_ ___
## \ \ /\ / /| | |/ / \___ \ / _` | | '_ \| __/ __|
## \ V V / | | <| |___) | (_| | | | | | |_\__ \
## \_/\_/ |_|_|\_\_|____/ \__,_|_|_| |_|\__|___/
##
class WikiSaints:
def __init__(self, get_from_wiki=False):
if get_from_wiki:
self.saints_per_month = self.get_data()
else:
self.saints_per_month = {}
self.it_month_name = [ "Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"]
def get_data(self):
wikipedia.set_lang("it")
lines = wikipedia.page('Calendario_dei_santi').content.split('\n')
# print(lines)
reg = re.compile(r'(==)*(==)')
cat = {}
month_name = self.it_month_name
month_strg = [ "== "+m+" ==" for m in month_name]
d = None
for n,l in enumerate(lines):
if reg.search(l):
cat[l] = []
d = cat[l]
elif d is not None:
d.append(l)
clean_cat = {}
for m in month_name:
clean_cat[m] = cat[f"== {m} =="]
return clean_cat
def save(self, file_name):
wb = openpyxl.Workbook(file_name)
for k,data in self.saints_per_month.items():
ws = wb.create_sheet(k)
# add column headings. NB. these must be strings
ws.append(["Giorno", "Santo", "altri santi"])
for i,s in enumerate(data):
try:
g = s.split(":")[0]
S = s.split(":")[1].split(";")[0]
o = s.split(":")[1].split(";")[1]
except:
g = S = o = ""
ws.append([i+1, S, o])
wb.save(file_name)
def open(self, file_name):
wb = openpyxl.open(file_name)
for ws in wb.worksheets:
self.saints_per_month[ws.title] = []
for i,row in enumerate(ws.rows):
if i==0: continue # skip header
elements = [ el.value for el in row][1:]
if len(elements) and elements[0] is not None:
self.saints_per_month[ws.title].append(elements)
def print_month(self, month: str):
try:
m = self.saints_per_month[month]
for i,el in enumerate(m):
print(i+1, el)
except:
pass
def get_saint(self, month : str, day: int):
try:
return self.saints_per_month[month][day-1]
except:
return None
def get_date(self, day : int, month : str, year : int = None) -> str :
if year is None: year = datetime.date.today().year
month = self.it_month_name.index(month)
return datetime.date(year, month+1, day)
def get_weekday(self, date) -> str :
wday = date.weekday()
it_wday = [
'Lunedì',
'Martedì',
'Mercoledì',
'Giovedì',
'Venerdì',
'Sabato',
'Domenica'
]
it_wday_short = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom']
return it_wday[wday]
def get_moon_position(self, date=None):
if date is None:
date = datetime.date.today()
diff = date - datetime.date(2001, 1, 1)
days = dec(diff.days) + (dec(diff.seconds) / dec(86400))
lunations = dec("0.20439731") + (days * dec("0.03386319269"))
return lunations % dec(1)
def get_moon_phase(self, date=None, phases=4):
index = ((self.get_moon_position(date)+dec(0.26))%dec(1) * dec(phases))# + dec("0.5")
if index - math.floor(index) < phases/30:
index = math.floor(index)
return int(index)
else:
return None
def _get_easter(self, year):
"""
Use easter date algorithm to calculate the day of easter for a given year.
If the year given is in a special range(special_years) then subtract 7 from the final date.
return easter as offset from March 1 -> goes in April if day > 31
"""
special_years = ['1954', '1981', '2049', '2076']
specyr_sub = 7
a = year % 19
b = year % 4
c = year % 7
d = (19 * a + 24) % 30
e = ((2 * b) + (4 * c) + (6 * d) + 5) % 7
if year in special_years:
dateofeaster = (22 + d + e) - specyr_sub
else:
dateofeaster = 22 + d + e
return dateofeaster
def get_mobile_fest(self, date):
"""
### https://it.cathopedia.org/wiki/Calcolo_delle_feste_mobili_legate_alla_Pasqua
"""
mobile_fests = {
'easter' : "Resurrezione del Signore",
'mc' : "Mercoledì delle Ceneri", # -46
'dc' : "Domenica delle Palme", # -7
'as' : "Ascensione di nostro Signore", # 42
'cd' : "Solennità del Corpus Domini", # 63 (dal 1978)
'cr' : "Festa del Ctristo Re", # prima di avvento ( metti a mano )
}
# easter
eday = self._get_easter(date.year)
emonth = 3
if eday > 31:
eday -= 31
emonth = 4
easter = datetime.date(date.year, emonth, eday)
if date == easter: return mobile_fests['easter']
if date == easter - datetime.timedelta(days=46): return mobile_fests['mc'] # mercoledi delle ceneri
if date == easter - datetime.timedelta(days=7): return mobile_fests['dc'] # domenica delle palme
if date == easter + datetime.timedelta(days=42): return mobile_fests['as'] # ascensione
if date == easter + datetime.timedelta(days=63): return mobile_fests['cd'] # corpus domini
# AVVENTO ( da rifare )
# xm = datetime.date(date.year, 12, 25)
# a4 = xm - datetime.timedelta(days=7)
# a3 = a4 - datetime.timedelta(days=7)
# a2 = a3 - datetime.timedelta(days=7)
# a1 = a2 - datetime.timedelta(days=7)
# if date == a1: return mobile_fests['a1']
# if date == a2: return mobile_fests['a2']
# if date == a3: return mobile_fests['a3']
# if date == a4: return mobile_fests['a4']
return None
## ____ _ _ _ ____ _ _
## / ___|__ _| | ___ _ __ __| | __ _ _ __(_) ___/ ___| __ _ _ __ | |_(_)
## | | / _` | |/ _ \ '_ \ / _` |/ _` | '__| |/ _ \___ \ / _` | '_ \| __| |
## | |__| (_| | | __/ | | | (_| | (_| | | | | (_) |__) | (_| | | | | |_| |
## \____\__,_|_|\___|_| |_|\__,_|\__,_|_| |_|\___/____/ \__,_|_| |_|\__|_|
##
class _Spacing():
size = 0.
padding = 0.
margin_top = 0.
margin_bottom = 0.
margin_left = 0.
margin_right = 0.
class CalendarioSanti(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
# General settings
self.arg_parser.add_argument("--x",
type=float,
dest="x", default=0.0,
help="Center X")
self.arg_parser.add_argument("--y",
type=float,
dest="y", default=0.0,
help="Center Y")
self.arg_parser.add_argument("--columnwidth",
type=float,
dest="columnwidth", default=300,
help="Calendar Column width")
self.arg_parser.add_argument("--linewidth",
type=float,
dest="linewidth", default=1,
help="day separation line width")
self.arg_parser.add_argument("--rowheight",
type=float,
dest="rowheight", default=12,
help="row height")
self.arg_parser.add_argument("-u", "--units",
type=str,
dest="units", default="mm",
help="units to measure size of knob")
# Dummy
self.arg_parser.add_argument("--tab")
# Label settings
self.arg_parser.add_argument("--saints_file",
type=str,
dest="saints_file", default='',
help="xlsx file to load saints")
self.arg_parser.add_argument("--font_family",
type=str,
dest="font_family", default="Apple Garamond",
help="Font family to use for days and labels")
self.arg_parser.add_argument("--year",
type=int,
dest="year", default=1970,
help="Calendar Year")
self.arg_parser.add_argument("-m", "--month",
type=str,
dest="month", default="Gennaio",
help="month to render")
self.arg_parser.add_argument("--dayfrom",
type=int,
dest="dayfrom", default=1,
help="Range to render day from")
self.arg_parser.add_argument("--dayto",
type=int,
dest="dayto", default=31,
help="Range to render day to")
self.arg_parser.add_argument("--day_text_size",
type=float,
dest="day_text_size", default=12,
help="Text size of day")
self.arg_parser.add_argument("--saint_text_size",
type=float,
dest="saint_text_size", default=8,
help="Text size of saint")
# spacing
self.__elements = ['day', 'wday', 'saint', 'moon']
self.__spacing = {
'size' : 8,
'padding' : 0,
'margin-top' : 0,
'margin-bottom' : 0,
'margin-left' : 0,
'margin-right' : 0
}
for el in self.__elements:
for sk in self.__spacing.keys():
sk = sk.replace('-','_')
self.arg_parser.add_argument("--" + el + "_" + sk,
type=float,
dest=el + "_" + sk,
help="Spacing " + el + " " + sk)
def draw_text(self, textvalue, pos, text_size, parent, bold=False, italic=False ):
# Create text element
x,y = pos
font = str(self.options.font_family)
font_specs = str(self.options.font_family)
if bold: font_specs += ", Bold"
else: font_specs += ", Normal"
# Center text horizontally with CSS style.
style = {
'text-align' : 'right',
'text-anchor': 'start',
'alignment-baseline' : 'baseline',
'font-family' : f"'{font}'",
'-inkscape-font-specification' : f"'{font_specs}'",
'font-weight' : 'bold' if bold else 'normal',
'font-size' : str(text_size),
'vertical-align' : 'middle'
}
attribs = {'style' : str(inkex.Style(style)),
'x' : str(x), 'y' : str(y), 'text' : str(textvalue) }
text = etree.SubElement(parent, inkex.addNS('text','svg'), attribs )
text.text = textvalue
text.set('style', str(inkex.Style(style)))
# parent.append(text)
return text
def draw_moon_text(self, phase, pos, text_size, parent):
moon_phase = "○◑●◐"
textvalue = moon_phase[(phase+1)%4]
return self.draw_text(textvalue, pos, text_size, parent)
def draw_hline(self, pos, length, parent):
x1,y1 = pos
x2,y2 = pos[0] + length, pos[1]
line_style = { 'stroke': '#000000',
'stroke-width': str(self.spacing['linewidth']),
'fill': 'none'
}
line_attribs = {'style' : str(inkex.Style(line_style)),
inkex.addNS('label','inkscape') : "none",
'd' : 'M '+str(x1) +',' +
str(y1) +' L '+str(x2)
+','+str(y2) }
line = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
return line
def draw_day(self, cal_date, pos, parent, fest = False):
date = self._wss.get_date(cal_date['day'], cal_date['month'], cal_date['year'])
bold = date.weekday() == 6 # Domenica
# import feste fisse da Excel
if cal_date['fest'] != 'None':
fest = True
saint_textvalue = cal_date['fest'] if fest else str(cal_date['saint'])
# calcolo feste mobili
mobile_fest = self._wss.get_mobile_fest(date)
if mobile_fest is not None:
fest = True
saint_textvalue += " - " + mobile_fest
if fest: bold = True
wday = self._wss.get_weekday(date)
mphs = self._wss.get_moon_phase(date)
day_text = self.draw_text(str(cal_date['day']), pos['day'], self.spacing['day'].size, parent, bold=bold)
wdl_text = self.draw_text(str(wday), pos['wday'], self.spacing['wday'].size, parent, bold=bold)
ssn_text = self.draw_text(saint_textvalue, pos['saint'], self.spacing['saint'].size, parent, bold=bold)
if mphs is not None:
mph_text = self.draw_moon_text( mphs, pos['moon'], self.spacing['moon'].size, parent)
h_line = self.draw_hline( pos['hline'], self.spacing['columnwidth'],parent)
pass
def _compute_spacing(self, x, y, it):
d = self.spacing['day']
w = self.spacing['wday']
s = self.spacing['saint']
m = self.spacing['moon']
dbb = 1.5*d.size + 2*d.padding + d.margin_left + d.margin_right, \
d.size + 2*d.padding + d.margin_top + d.margin_bottom
sby = s.size + 2*s.padding + s.margin_top + s.margin_bottom
wby = w.size + 2*w.padding + w.margin_top + w.margin_bottom
lw = self.spacing['linewidth']
cw = self.spacing['columnwidth']
rh = self.spacing['rowheight']
y = y + max(rh, dbb[1]+lw) * it
ans = {
'day' : (x+ d.padding+d.margin_left , y+ dbb[1]-d.padding-d.margin_bottom),
'wday' : (x+ dbb[0] + w.padding+w.margin_left , y+ dbb[1]-sby-w.padding-w.margin_bottom),
'saint': (x+ dbb[0] + s.padding+s.margin_left , y+ dbb[1]-s.padding-s.margin_bottom),
'hline': (x , y+ dbb[1]),
'moon' : (x + cw - m.size-2*m.padding-m.margin_right, y+ m.size+2*m.padding+m.margin_top),
}
return ans
def effect(self):
parent = self.svg.get_current_layer()
selected = self.svg.selected
if selected:
bbox = selected.bounding_box()
self.x_offset = bbox.left
self.y_offset = bbox.top
else:
self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units)
self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units)
# populating spacing with options
self.spacing = {}
for el in self.__elements:
self.spacing[el] = _Spacing()
for sk in self.__spacing.keys():
sk = sk.replace('-','_')
self.spacing[el].__dict__[sk] = self.svg.unittouu( str(getattr(self.options ,el+'_'+sk)) + self.options.units)
self.spacing['linewidth'] = getattr(self.options ,'linewidth')
self.spacing['columnwidth'] = self.svg.unittouu( str(getattr(self.options ,'columnwidth')) + self.options.units)
self.spacing['rowheight'] = self.svg.unittouu( str(getattr(self.options ,'rowheight')) + self.options.units)
if len(self.options.saints_file):
ss = WikiSaints()
ss.open(str(self.options.saints_file))
else:
ss = WikiSaints(get_from_wiki=True)
self._wss = ss
compute_date = lambda d: { 'day' : d, 'month' : self.options.month, 'year': self.options.year,
'saint' : str(ss.get_saint(self.options.month, d)[0]), 'fest' : str(ss.get_saint(self.options.month, d)[2]) }
for n,d in enumerate(range(self.options.dayfrom, self.options.dayto + 1)):
cal_date = compute_date(d)
pos = self._compute_spacing(self.x_offset, self.y_offset, n)
self.draw_day(cal_date, pos, parent)
if __name__ == '__main__':
e = CalendarioSanti()
e.run()