From d201028d71ad3f26d5ec0b9c12268475e0b70cfd Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 25 Aug 2026 23:14:13 +0200 Subject: feat(of): transcribe the 2002 General Roman Calendar Extracted from the Latin typical edition, which is the authority; lectio's roman-calendar.ini is a cross-check only and every divergence is adjudicated against the Missal in the provenance header. A blank grade column means Memoria ad libitum, per the calendar's own footnote, rather than a missing value. 206 entries; 1 January, 6 January and 25 December are deliberately excluded because Temporal_of.named already computes them, and shipping them here too would create a duplicate candidate for the same day. Colour is derived from IGMR n. 346, cited per class in the extractor; the lectio cross-check surfaces one rank divergence (22 July, Mary Magdalene -- lectio reflects the 2016 post-2002 elevation decree, out of scope here) and 17 colour divergences, all adjudicated in the provenance header. --- data/of/calendar-2002.sexp | 1294 +++++++++++++++++++++++++++++++++++++++++ test/dune | 1 + test/test_calendar_of_data.ml | 207 +++++++ test/test_colitur.ml | 3 +- tools/extract_of_calendar.py | 677 +++++++++++++++++++++ 5 files changed, 2181 insertions(+), 1 deletion(-) create mode 100644 data/of/calendar-2002.sexp create mode 100644 test/test_calendar_of_data.ml create mode 100644 tools/extract_of_calendar.py diff --git a/data/of/calendar-2002.sexp b/data/of/calendar-2002.sexp new file mode 100644 index 0000000..4972f0d --- /dev/null +++ b/data/of/calendar-2002.sexp @@ -0,0 +1,1294 @@ +; data/of/calendar-2002.sexp -- OF (post-1970) General Roman Calendar, +; transcribed from the Missale Romanum, editio typica tertia (2002). The +; 2002 Missal is the AUTHORITY; this file is never edited afterwards -- +; spec sec4.1. lectio's roman-calendar.ini is a cross-check only, never a +; source (see tools/extract_of_calendar.py's own header for the full +; source-discipline statement and every classification citation). +; +; Generator: tools/extract_of_calendar.py -- do not hand-edit; re-run against the same +; PDF (this file is pinned, so a re-run should reproduce it byte-for-byte) +; and diff before committing. +; +; Source: docs/research/of/missale-romanum-2002.pdf +; SHA-256: 0b458944824d2ee92854b9664f83e553af0342e0b15a9dc14b00e846405523d7 +; Extracted lines (pdftotext -layout, 0-based): 3992-4461 +; Extraction date (UTC): 2026-08-25 +; +; 206 entries. 1 entry needed letter-spacing repair +; (collapse_letterspacing's own mechanical pass, then hand-verified -- see +; the module docstring's EXTRACTION ARTIFACT note). +; +; Excluded, deliberately (see module docstring for the full reasoning): +; 01-01: of-mary-mother-of-god (Temporal_of.named, m=1 dd=1) +; 01-06: of-epiphany (Temporal_of.named, m=1 dd=6) +; 12-25: of-nativity (Temporal_of.named, m=12 dd=25) +; Movable universal solemnities (Baptism of the Lord, Holy Family, +; Trinity, Corpus Christi, Christ the King -- Temporal_of code; the +; Sacred Heart of Jesus and the Immaculate Heart of Mary -- present in +; the 2002 table but covered by NEITHER Temporal_of NOR this file, a +; genuine gap this task found and reports but does not fix). +; +; lectio cross-check: 206 dates paired; rank agrees on 205/206. +; 03-07 COLOUR colitur=Red lectio=Violet ['Ss. Perpetuæ et Felicitatis, martyrum'] +; 03-08 COLOUR colitur=White lectio=Violet ['S. Ioannis a Deo, religiosi'] +; 03-09 COLOUR colitur=White lectio=Violet ['S. Franciscæ Romanæ, religiosæ'] +; 03-17 COLOUR colitur=White lectio=Violet ['S. Patricii, episcopi'] +; 03-18 COLOUR colitur=White lectio=Violet ['S. Cyrilli Hierosolymitani, episcopi et Ecclesiæ doctoris'] +; 03-23 COLOUR colitur=White lectio=Violet ['S. Turibii de Mogrovejo, episcopi'] +; 04-02 COLOUR colitur=White lectio=Violet ['S. Francisci de Paola, eremitæ'] +; 04-04 COLOUR colitur=White lectio=Violet ['S. Isidori, episcopi et Ecclesiæ doctoris'] +; 04-05 COLOUR colitur=White lectio=Violet ['S. Vincentii Ferrer, presbyteri'] +; 07-22 RANK colitur=Memoria_obligatoria lectio=Festum ['S. Mariæ Magdalenæ' vs 'S. Mariae Magdalenae'] +; 07-29 COLOUR colitur=White lectio=Red ['S. Marthæ'] +; 11-02 COLOUR colitur=Violet lectio=White ['IN COMMEMORATIONE OMNIUM FIDELIUM DEFUNCTORUM'] +; 11-03 COLOUR colitur=White lectio=Red ['S. Martini de Porres, religiosi'] +; 11-11 COLOUR colitur=White lectio=Red ['S. Martini Turonensis, episcopi'] +; 12-21 COLOUR colitur=White lectio=Violet ['S. Petri Canisii, presbyteri et Ecclesiæ doctoris'] +; 12-23 COLOUR colitur=White lectio=Violet ['S. Ioannis de Ke¸ty, presbyteri'] +; 12-29 COLOUR colitur=Red lectio=White ['S. Thomæ Becket, episcopi et martyris'] +; 09-23 LECTIO-ONLY 'S. Pii de Pietrelcina, presbyteri [*]' rank=memorial slug=pio-of-pietrelcina-padre-pio-priest +; 10-11 LECTIO-ONLY 'S. Ioannis XXIII, papae' rank=optional slug=john-xxiii-pope +; 10-22 LECTIO-ONLY 'S. Ioannis Pauli II, papae' rank=optional slug=john-paul-ii-pope +; 12-09 LECTIO-ONLY 'S. Ioannis Didaci Cuahtlatoatzin [*]' rank=optional slug=juan-diego +; 12-12 LECTIO-ONLY 'Beatae Mariae Virginis de Guadalupe [*]' rank=optional slug=our-lady-of-guadalupe + +((id of-universal) (name "OF (2002) General Roman Calendar") + (entries + ( + ((date (Fixed (month 4) (day 23))) + (cel + ((slug adalbert-bishop-and-martyr) + (names ((en "Saint Adalbert, bishop and martyr") (la "S. Adalberti, episcopi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 5))) + (cel + ((slug agatha-virgin-and-martyr) + (names ((en "Saint Agatha, virgin and martyr") (la "S. Agathae, virginis et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 21))) + (cel + ((slug agnes-virgin-and-martyr) + (names ((en "Saint Agnes, virgin and martyr") (la "S. Agnetis, virginis et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 15))) + (cel + ((slug albert-the-great-bishop-and-doctor) + (names ((en "Saint Albert the Great, bishop and doctor") (la "S. Alberti Magni, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 1))) + (cel + ((slug all-saints) + (names ((en "All Saints") (la "OMNIUM SANCTORUM"))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 2))) + (cel + ((slug all-souls) + (names ((en "All Souls") (la "IN COMMEMORATIONE OMNIUM FIDELIUM DEFUNCTORUM"))) + (rank Sollemnitas) (status Feast) (colour Violet) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 21))) + (cel + ((slug aloysius-gonzaga-religious) + (names ((en "Saint Aloysius Gonzaga, religious") (la "S. Aloisii Gonzaga, religiosi"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 1))) + (cel + ((slug alphonsus-maria-de-liguori-bishop-and-doctor-of-the-church) + (names ((en "Saint Alphonsus Maria de Liguori, bishop and doctor of the Church") (la "S. Alfonsi Mariae de’ Liguori, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 7))) + (cel + ((slug ambrose-bishop-and-doctor) + (names ((en "Saint Ambrose, bishop and doctor") (la "S. Ambrosii, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 24))) + (cel + ((slug andrew-dung-lac-and-his-companions-martyrs) + (names ((en "Saint Andrew Dung Lac and his companions, martyrs") (la "Ss. Andreae Du˜ ng La.c, presbyteri, et sociorum, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 20))) + (cel + ((slug andrew-kim-taegon-priest-and-paul-chong-hasang-and-companions-martyrs) + (names ((en "Saint Andrew Kim Taegon, priest, and Paul Chong Hasang and companions, martyrs") (la "Ss. Andreae Kim Tae-go¡ n, presbyteri, Pauli Cho¡ ng Ha-sang et sociorum, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 30))) + (cel + ((slug andrew-the-apostle) + (names ((en "Saint Andrew the Apostle") (la "S. Andreae, apostoli"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 27))) + (cel + ((slug angela-merici-virgin) + (names ((en "Saint Angela Merici, virgin") (la "S. Angelae Merici, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 25))) + (cel + ((slug annunciation-of-the-lord) + (names ((en "Annunciation of the Lord") (la "IN ANNUNTIATIONE DOMINI"))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 21))) + (cel + ((slug anselm-of-canterbury-bishop-and-doctor-of-the-church) + (names ((en "Saint Anselm of Canterbury, bishop and doctor of the Church") (la "S. Anselmi, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 3))) + (cel + ((slug ansgar-bishop) + (names ((en "Saint Ansgar, bishop") (la "S. Ansgarii, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 24))) + (cel + ((slug anthony-mary-claret-bishop) + (names ((en "Saint Anthony Mary Claret, bishop") (la "S. Antonii Mariae Claret, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 17))) + (cel + ((slug anthony-of-egypt-abbot) + (names ((en "Saint Anthony of Egypt, abbot") (la "S. Antonii, abbatis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 13))) + (cel + ((slug anthony-of-padua-priest-and-doctor) + (names ((en "Saint Anthony of Padua, priest and doctor") (la "S. Antonii de Padova, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 5))) + (cel + ((slug anthony-zaccaria-priest) + (names ((en "Saint Anthony Zaccaria, priest") (la "S. Antonii Mariae Zaccaria, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 20))) + (cel + ((slug apollinaris-bishop-and-martyr) + (names ((en "Saint Apollinaris, bishop and martyr") (la "S. Apollinaris, episcopi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 15))) + (cel + ((slug assumption-of-the-blessed-virgin-mary) + (names ((en "Assumption of the Blessed Virgin Mary") (la "IN ASSUMPTIONE BEATAE MARIAE VIRGINIS"))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 2))) + (cel + ((slug athanasius-bishop-and-doctor) + (names ((en "Saint Athanasius, bishop and doctor") (la "S. Athanasii, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 27))) + (cel + ((slug augustine-austin-of-canterbury-bishop) + (names ((en "Saint Augustine (Austin) of Canterbury, bishop") (la "S. Augustini Cantuariensis, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 28))) + (cel + ((slug augustine-of-hippo-bishop-and-doctor-of-the-church) + (names ((en "Saint Augustine of Hippo, bishop and doctor of the Church") (la "S. Augustini, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 9))) + (cel + ((slug augustine-zhao-rong-and-companions-martyrs) + (names ((en "Saint Augustine Zhao Rong and companions, martyrs") (la "Ss. Augustini Zhao Rong, presbyteri, et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 11))) + (cel + ((slug barnabas-the-apostle) + (names ((en "Saint Barnabas the Apostle") (la "S. Barnabae, apostoli"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 24))) + (cel + ((slug bartholomew-the-apostle) + (names ((en "Saint Bartholomew the Apostle") (la "S. Bartholomaei, apostoli"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 25))) + (cel + ((slug bede-the-venerable-priest-and-doctor) + (names ((en "Saint Bede the Venerable, priest and doctor") (la "S. Bedae Venerabilis, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 11))) + (cel + ((slug benedict-abbot) + (names ((en "Saint Benedict, abbot") (la "S. Benedicti, abbatis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 20))) + (cel + ((slug bernard-of-clairvaux-abbot-and-doctor-of-the-church) + (names ((en "Saint Bernard of Clairvaux, abbot and doctor of the Church") (la "S. Bernardi, abbatis et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 20))) + (cel + ((slug bernardine-of-siena-priest) + (names ((en "Saint Bernardine of Siena, priest") (la "S. Bernardini Senensis, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 23))) + (cel + ((slug birgitta-religious) + (names ((en "Saint Birgitta, religious") (la "S. Birgittae, religiosae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 24))) + (cel + ((slug birth-of-saint-john-the-baptist) + (names ((en "Birth of Saint John the Baptist") (la "IN NATIVITATE S. IOANNIS BAPTISTAE"))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 8))) + (cel + ((slug birth-of-the-blessed-virgin-mary) + (names ((en "Birth of the Blessed Virgin Mary") (la "In Nativitate B. Mariae Virginis"))) + (rank Festum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 3))) + (cel + ((slug blase-bishop-and-martyr) + (names ((en "Saint Blase, bishop and martyr") (la "S. Blasii, episcopi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 15))) + (cel + ((slug bonaventure-bishop-and-doctor) + (names ((en "Saint Bonaventure, bishop and doctor") (la "S. Bonaventurae, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 5))) + (cel + ((slug boniface-bishop-and-martyr) + (names ((en "Saint Boniface, bishop and martyr") (la "S. Bonifatii, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 6))) + (cel + ((slug bruno-priest) + (names ((en "Saint Bruno, priest") (la "S. Brunonis, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 7))) + (cel + ((slug cajetan-priest) + (names ((en "Saint Cajetan, priest") (la "S. Caietani, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 14))) + (cel + ((slug callistus-i-pope-and-martyr) + (names ((en "Saint Callistus I, pope and martyr") (la "S. Callisti I, papae et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 14))) + (cel + ((slug camillus-de-lellis-priest) + (names ((en "Saint Camillus de Lellis, priest") (la "S. Camilli de Lellis, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 4))) + (cel + ((slug casimir) + (names ((en "Saint Casimir") (la "S. Casimiri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 25))) + (cel + ((slug catherine-of-alexandria) + (names ((en "Saint Catherine of Alexandria") (la "S. Catharinae Alexandrinae, virginis et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 29))) + (cel + ((slug catherine-of-siena-virgin-and-doctor-of-the-church) + (names ((en "Saint Catherine of Siena, virgin and doctor of the Church") (la "S. Catharinae Senensis, virginis et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 22))) + (cel + ((slug cecilia-virgin-and-martyr) + (names ((en "Saint Cecilia, virgin and martyr") (la "S. Caeciliae, virginis et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 22))) + (cel + ((slug chair-of-saint-peter-apostle) + (names ((en "Chair of Saint Peter, apostle") (la "Cathedrae S. Petri, apostoli"))) + (rank Festum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 4))) + (cel + ((slug charles-borromeo-bishop) + (names ((en "Saint Charles Borromeo, bishop") (la "S. Caroli Borromeo, episcopi"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 21))) + (cel + ((slug christopher-magallanes-and-companions-martyrs) + (names ((en "Saint Christopher Magallanes and companions, martyrs") (la "Ss. Christophori Magallanes, presbyteri, et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 11))) + (cel + ((slug clare-virgin) + (names ((en "Saint Clare, virgin") (la "S. Clarae, virginis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 23))) + (cel + ((slug clement-i-pope-and-martyr) + (names ((en "Saint Clement I, pope and martyr") (la "S. Clementis I, papae et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 23))) + (cel + ((slug columban-religious) + (names ((en "Saint Columban, religious") (la "S. Columbani, abbatis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 27))) + (cel + ((slug cyril-of-alexandria-bishop-and-doctor) + (names ((en "Saint Cyril of Alexandria, bishop and doctor") (la "S. Cyrilli Alexandrini, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 18))) + (cel + ((slug cyril-of-jerusalem-bishop-and-doctor) + (names ((en "Saint Cyril of Jerusalem, bishop and doctor") (la "S. Cyrilli Hierosolymitani, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 11))) + (cel + ((slug damasus-i-pope) + (names ((en "Saint Damasus I, pope") (la "S. Damasi I, papae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 5))) + (cel + ((slug dedication-of-the-basilica-of-saint-mary-major) + (names ((en "Dedication of the Basilica of Saint Mary Major") (la "In Dedicatione basilicae S. Mariae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 18))) + (cel + ((slug dedication-of-the-basilicas-of-saints-peter-and-paul-apostles) + (names ((en "Dedication of the basilicas of Saints Peter and Paul, Apostles") (la "In Dedicatione basilicarum Ss. Petri et Pauli, apostolorum"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 9))) + (cel + ((slug dedication-of-the-lateran-basilica) + (names ((en "Dedication of the Lateran basilica") (la "In Dedicatione basilicae Lateranensis"))) + (rank Festum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 9))) + (cel + ((slug denis-and-companions-martyrs) + (names ((en "Saint Denis and companions, martyrs") (la "Ss. Dionysii, episcopi, et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 8))) + (cel + ((slug dominic-priest) + (names ((en "Saint Dominic, priest") (la "S. Dominici, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 17))) + (cel + ((slug elizabeth-of-hungary-religious) + (names ((en "Saint Elizabeth of Hungary, religious") (la "S. Elisabeth Hungariae, religiosae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 4))) + (cel + ((slug elizabeth-of-portugal) + (names ((en "Saint Elizabeth of Portugal") (la "S. Elisabeth Lusitaniae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 9))) + (cel + ((slug ephrem-deacon-and-doctor) + (names ((en "Saint Ephrem, deacon and doctor") (la "S. Ephraem, diaconi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 2))) + (cel + ((slug eusebius-of-vercelli-bishop) + (names ((en "Saint Eusebius of Vercelli, bishop") (la "S. Eusebii Vercellensis, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 20))) + (cel + ((slug fabian-pope-and-martyr) + (names ((en "Saint Fabian, pope and martyr") (la "S. Fabiani, papae et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 24))) + (cel + ((slug fidelis-of-sigmaringen-priest-and-martyr) + (names ((en "Saint Fidelis of Sigmaringen, priest and martyr") (la "S. Fidelis de Sigmaringen, presbyteri et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 30))) + (cel + ((slug first-martyrs-of-the-church-of-rome) + (names ((en "First Martyrs of the Church of Rome") (la "Ss. Protomartyrum S. Romanae Ecclesiae"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 9))) + (cel + ((slug frances-of-rome-religious) + (names ((en "Saint Frances of Rome, religious") (la "S. Franciscae Romanae, religiosae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 24))) + (cel + ((slug francis-de-sales-bishop-and-doctor) + (names ((en "Saint Francis de Sales, bishop and doctor") (la "S. Francisci de Sales, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 4))) + (cel + ((slug francis-of-assisi) + (names ((en "Saint Francis of Assisi") (la "S. Francisci Assisiensis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 2))) + (cel + ((slug francis-of-paola-hermit) + (names ((en "Saint Francis of Paola, hermit") (la "S. Francisci de Paola, eremitae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 3))) + (cel + ((slug francis-xavier-priest) + (names ((en "Saint Francis Xavier, priest") (la "S. Francisci Xavier, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 23))) + (cel + ((slug george-martyr) + (names ((en "Saint George, martyr") (la "S. Georgii, martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 16))) + (cel + ((slug gertrude-the-great-virgin) + (names ((en "Saint Gertrude the Great, virgin") (la "S. Gertrudis, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 3))) + (cel + ((slug gregory-the-great-pope-and-doctor) + (names ((en "Saint Gregory the Great, pope and doctor") (la "S. Gregorii Magni, papae et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 25))) + (cel + ((slug gregory-vii-pope) + (names ((en "Saint Gregory VII, pope") (la "S. Gregorii VII, papae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 2))) + (cel + ((slug guardian-angels) + (names ((en "Guardian Angels") (la "Ss. Angelorum Custodum"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 16))) + (cel + ((slug hedwig-religious) + (names ((en "Saint Hedwig, religious") (la "S. Hedvigis, religiosae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 13))) + (cel + ((slug henry) + (names ((en "Saint Henry") (la "S. Henrici"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 13))) + (cel + ((slug hilary-of-poitiers-bishop-and-doctor) + (names ((en "Saint Hilary of Poitiers, bishop and doctor") (la "S. Hilarii, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 28))) + (cel + ((slug holy-innocents-martyrs) + (names ((en "Holy Innocents, martyrs") (la "Ss. Innocentium, martyrum"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 12))) + (cel + ((slug holy-name-of-the-blessed-virgin-mary) + (names ((en "Holy Name of the Blessed Virgin Mary") (la "Ss.mi Nominis Mariae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 17))) + (cel + ((slug ignatius-of-antioch-bishop-and-martyr) + (names ((en "Saint Ignatius of Antioch, bishop and martyr") (la "S. Ignatii Antiocheni, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 31))) + (cel + ((slug ignatius-of-loyola-priest) + (names ((en "Saint Ignatius of Loyola, priest") (la "S. Ignatii de Loyola, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 8))) + (cel + ((slug immaculate-conception-of-the-blessed-virgin-mary) + (names ((en "Immaculate Conception of the Blessed Virgin Mary") (la "IN CONCEPTIONE IMMACULATA BEATAE MARIAE VIRGINIS"))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 28))) + (cel + ((slug irenaeus-bishop-and-martyr) + (names ((en "Saint Irenaeus, bishop and martyr") (la "S. Irenaei, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 4))) + (cel + ((slug isidore-bishop-and-doctor-of-the-church) + (names ((en "Saint Isidore, bishop and doctor of the Church") (la "S. Isidori, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 25))) + (cel + ((slug james-apostle) + (names ((en "Saint James, apostle") (la "S. Iacobi, apostoli"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 12))) + (cel + ((slug jane-frances-de-chantal-religious) + (names ((en "Saint Jane Frances de Chantal, religious") (la "S. Ioannae Franciscae de Chantal, religiosae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 19))) + (cel + ((slug januarius-bishop-and-martyr) + (names ((en "Saint Januarius, bishop and martyr") (la "S. Ianuarii, episcopi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 4))) + (cel + ((slug jean-vianney-the-cure-of-ars-priest) + (names ((en "Saint Jean Vianney (the Curé of Ars), priest") (la "S. Ioannis Mariae Vianney, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 8))) + (cel + ((slug jerome-emiliani-priest) + (names ((en "Saint Jerome Emiliani, priest") (la "S. Hieronymi Emiliani"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 30))) + (cel + ((slug jerome-priest-and-doctor) + (names ((en "Saint Jerome, priest and doctor") (la "S. Hieronymi, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 7))) + (cel + ((slug john-baptist-de-la-salle-priest) + (names ((en "Saint John Baptist de la Salle, priest") (la "S. Ioannis Baptistae de la Salle, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 31))) + (cel + ((slug john-bosco-priest) + (names ((en "Saint John Bosco, priest") (la "S. Ioannis Bosco, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 13))) + (cel + ((slug john-chrysostom-bishop-and-doctor) + (names ((en "Saint John Chrysostom, bishop and doctor") (la "S. Ioannis Chrysostomi, episcopi et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 4))) + (cel + ((slug john-damascene-priest-and-doctor) + (names ((en "Saint John Damascene, priest and doctor") (la "S. Ioannis Damasceni, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 19))) + (cel + ((slug john-eudes-priest) + (names ((en "Saint John Eudes, priest") (la "S. Ioannis Eudes, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 18))) + (cel + ((slug john-i-pope-and-martyr) + (names ((en "Saint John I, pope and martyr") (la "S. Ioannis I, papae et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 9))) + (cel + ((slug john-leonardi-priest) + (names ((en "Saint John Leonardi, priest") (la "S. Ioannis Leonardi, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 23))) + (cel + ((slug john-of-capistrano-priest) + (names ((en "Saint John of Capistrano, priest") (la "S. Ioannis de Capestrano, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 8))) + (cel + ((slug john-of-god-religious) + (names ((en "Saint John of God, religious") (la "S. Ioannis a Deo, religiosi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 23))) + (cel + ((slug john-of-kanty-priest) + (names ((en "Saint John of Kanty, priest") (la "S. Ioannis de Ke¸ty, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 14))) + (cel + ((slug john-of-the-cross-priest-and-doctor) + (names ((en "Saint John of the Cross, priest and doctor") (la "S. Ioannis a Cruce, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 27))) + (cel + ((slug john-the-apostle-and-evangelist) + (names ((en "Saint John the Apostle and evangelist") (la "S. Ioannis, apostoli et evangelistae"))) + (rank Festum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 12))) + (cel + ((slug josaphat-bishop-and-martyr) + (names ((en "Saint Josaphat, bishop and martyr") (la "S. Iosaphat, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 19))) + (cel + ((slug joseph-husband-of-the-blessed-virgin-mary) + (names ((en "Saint Joseph Husband of the Blessed Virgin Mary") (la "S. IOSEPH, SPONSI B. M. V."))) + (rank Sollemnitas) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 25))) + (cel + ((slug joseph-of-calasanz-priest) + (names ((en "Saint Joseph of Calasanz, priest") (la "S. Iosephi de Calasanz, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 1))) + (cel + ((slug joseph-the-worker) + (names ((en "Saint Joseph the Worker") (la "S. Ioseph Opificis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 8))) + (cel + ((slug josephine-bakhita-virgin) + (names ((en "Saint Josephine Bakhita, virgin") (la "S. Iosephinae Bakhita, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 1))) + (cel + ((slug justin-martyr) + (names ((en "Saint Justin Martyr") (la "S. Iustini, martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 10))) + (cel + ((slug lawrence-deacon-and-martyr) + (names ((en "Saint Lawrence, deacon and martyr") (la "S. Laurentii, diaconi et martyris"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 21))) + (cel + ((slug lawrence-of-brindisi-priest-and-doctor) + (names ((en "Saint Lawrence of Brindisi, priest and doctor") (la "S. Laurentii de Brindisi, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 10))) + (cel + ((slug leo-the-great-pope-and-doctor) + (names ((en "Saint Leo the Great, pope and doctor") (la "S. Leonis Magni, papae et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 25))) + (cel + ((slug louis) + (names ((en "Saint Louis") (la "S. Ludovici"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 28))) + (cel + ((slug louis-grignon-de-montfort-priest) + (names ((en "Saint Louis Grignon de Montfort, priest") (la "S. Ludovici Mariae Grignion de Montfort, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 13))) + (cel + ((slug lucy-of-syracuse-virgin-and-martyr) + (names ((en "Saint Lucy of Syracuse, virgin and martyr") (la "S. Luciae, virginis et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 18))) + (cel + ((slug luke-the-evangelist) + (names ((en "Saint Luke the Evangelist") (la "S. Lucae, evangelistae"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 16))) + (cel + ((slug margaret-mary-alacoque-virgin) + (names ((en "Saint Margaret Mary Alacoque, virgin") (la "S. Margaritae Mariae Alacoque, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 16))) + (cel + ((slug margaret-of-scotland) + (names ((en "Saint Margaret of Scotland") (la "S. Margaritae Scotiae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 6))) + (cel + ((slug maria-goretti-virgin-and-martyr) + (names ((en "Saint Maria Goretti, virgin and martyr") (la "S. Mariae Goretti, virginis et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 25))) + (cel + ((slug mark-the-evangelist) + (names ((en "Saint Mark the Evangelist") (la "S. Marci, evangelistae"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 29))) + (cel + ((slug martha) + (names ((en "Saint Martha") (la "S. Marthae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 3))) + (cel + ((slug martin-de-porres-religious) + (names ((en "Saint Martin de Porres, religious") (la "S. Martini de Porres, religiosi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 13))) + (cel + ((slug martin-i-pope-and-martyr) + (names ((en "Saint Martin I, pope and martyr") (la "S. Martini I, papae et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 11))) + (cel + ((slug martin-of-tours-bishop) + (names ((en "Saint Martin of Tours, bishop") (la "S. Martini Turonensis, episcopi"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 22))) + (cel + ((slug mary-magdalene) + (names ((en "Saint Mary Magdalene") (la "S. Mariae Magdalenae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 25))) + (cel + ((slug mary-magdalene-de-pazzi-virgin) + (names ((en "Saint Mary Magdalene de Pazzi, virgin") (la "S. Mariae Magdalenae de’ Pazzi, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 21))) + (cel + ((slug matthew-the-evangelist-apostle-evangelist) + (names ((en "Saint Matthew the Evangelist, Apostle, Evangelist") (la "S. Matthaei, apostoli et evangelistae"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 14))) + (cel + ((slug matthias-the-apostle) + (names ((en "Saint Matthias the Apostle") (la "S. Matthiae, apostoli"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 14))) + (cel + ((slug maximilian-mary-kolbe-priest-and-martyr) + (names ((en "Saint Maximilian Mary Kolbe, priest and martyr") (la "S. Maximiliani Mariae Kolbe, presbyteri et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 27))) + (cel + ((slug monica) + (names ((en "Saint Monica") (la "S. Monicae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 6))) + (cel + ((slug nicholas-bishop) + (names ((en "Saint Nicholas, bishop") (la "S. Nicolai, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 6))) + (cel + ((slug norbert-bishop) + (names ((en "Saint Norbert, bishop") (la "S. Norberti, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 13))) + (cel + ((slug our-lady-of-fatima) + (names ((en "Our Lady of Fatima") (la "B. Mariae Virginis de Fatima"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 11))) + (cel + ((slug our-lady-of-lourdes) + (names ((en "Our Lady of Lourdes") (la "B. Mariae Virginis de Lourdes"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 16))) + (cel + ((slug our-lady-of-mount-carmel) + (names ((en "Our Lady of Mount Carmel") (la "B. Mariae Virginis de Monte Carmelo"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 15))) + (cel + ((slug our-lady-of-sorrows) + (names ((en "Our Lady of Sorrows") (la "B. Mariae Virginis Perdolentis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 7))) + (cel + ((slug our-lady-of-the-rosary) + (names ((en "Our Lady of the Rosary") (la "B. Mariae Virginis a Rosario"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 12))) + (cel + ((slug pancras-martyr) + (names ((en "Saint Pancras, martyr") (la "S. Pancratii, martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 17))) + (cel + ((slug patrick-bishop) + (names ((en "Saint Patrick, bishop") (la "S. Patricii, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 19))) + (cel + ((slug paul-of-the-cross-priest) + (names ((en "Saint Paul of the Cross, priest") (la "S. Pauli a Cruce, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 22))) + (cel + ((slug paulinus-of-nola-bishop) + (names ((en "Saint Paulinus of Nola, bishop") (la "S. Paulini Nolani, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 21))) + (cel + ((slug peter-canisius-priest-and-doctor) + (names ((en "Saint Peter Canisius, priest and doctor") (la "S. Petri Canisii, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 28))) + (cel + ((slug peter-chanel-priest-and-martyr) + (names ((en "Saint Peter Chanel, priest and martyr") (la "S. Petri Chanel, presbyteri et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 30))) + (cel + ((slug peter-chrysologus-bishop-and-doctor) + (names ((en "Saint Peter Chrysologus, bishop and doctor") (la "S. Petri Chrysologi, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 9))) + (cel + ((slug peter-claver-priest) + (names ((en "Saint Peter Claver, priest") (la "S. Petri Claver, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 21))) + (cel + ((slug peter-damian-bishop-and-doctor-of-the-church) + (names ((en "Saint Peter Damian, bishop and doctor of the Church") (la "S. Petri Damiani, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 2))) + (cel + ((slug peter-julian-eymard-priest) + (names ((en "Saint Peter Julian Eymard, priest") (la "S. Petri Iuliani Eymard, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 26))) + (cel + ((slug philip-neri-priest) + (names ((en "Saint Philip Neri, priest") (la "S. Philippi Neri, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 30))) + (cel + ((slug pius-v-pope) + (names ((en "Saint Pius V, pope") (la "S. Pii V, papae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 21))) + (cel + ((slug pius-x-pope) + (names ((en "Saint Pius X, pope") (la "S. Pii X, papae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 23))) + (cel + ((slug polycarp-bishop-and-martyr) + (names ((en "Saint Polycarp, bishop and martyr") (la "S. Polycarpi, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 11) (day 21))) + (cel + ((slug presentation-of-the-blessed-virgin-mary) + (names ((en "Presentation of the Blessed Virgin Mary") (la "In Praesentatione B. Mariae Virginis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 2))) + (cel + ((slug presentation-of-the-lord) + (names ((en "Presentation of the Lord") (la "IN PRAESENTATIONE DOMINI"))) + (rank Festum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 22))) + (cel + ((slug queenship-of-blessed-virgin-mary) + (names ((en "Queenship of Blessed Virgin Mary") (la "B. Mariae Virginis Reginae"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 7))) + (cel + ((slug raymond-of-penyafort-priest) + (names ((en "Saint Raymond of Penyafort, priest") (la "S. Raimundi de Penyafort, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 22))) + (cel + ((slug rita-of-cascia) + (names ((en "Saint Rita of Cascia") (la "S. Ritae, de Cascia religiosae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 17))) + (cel + ((slug robert-bellarmine-bishop-and-doctor) + (names ((en "Saint Robert Bellarmine, bishop and doctor") (la "S. Roberti Bellarmino, episcopi et Ecclesiae doctoris"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 19))) + (cel + ((slug romuald-abbot) + (names ((en "Saint Romuald, abbot") (la "S. Romualdi, abbatis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 23))) + (cel + ((slug rose-of-lima-virgin) + (names ((en "Saint Rose of Lima, virgin") (la "S. Rosae de Lima, virginis"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 2))) + (cel + ((slug saints-basil-the-great-and-gregory-nazianzen-bishops-and-doctors) + (names ((en "Saints Basil the Great and Gregory Nazianzen, bishops and doctors") (la "Ss. Basilii Magni et Gregorii Nazianzeni, episcoporum et Ecclesiae doctorum"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 3))) + (cel + ((slug saints-charles-lwanga-and-companions-martyrs) + (names ((en "Saints Charles Lwanga and companions, martyrs") (la "Ss. Caroli Lwanga et sociorum, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 16))) + (cel + ((slug saints-cornelius-pope-and-cyprian-bishop-martyrs) + (names ((en "Saints Cornelius, pope, and Cyprian, bishop, martyrs") (la "Ss. Cornelii, papae, et Cypriani, episcopi, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 26))) + (cel + ((slug saints-cosmas-and-damian-martyrs) + (names ((en "Saints Cosmas and Damian, martyrs") (la "Ss. Cosmae et Damiani, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 14))) + (cel + ((slug saints-cyril-monk-and-methodius-bishop) + (names ((en "Saints Cyril, monk, and Methodius, bishop") (la "Ss. Cyrilli, monachi, et Methodii, episcopi"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 19))) + (cel + ((slug saints-jean-de-brebeuf-isaac-jogues-priests-and-martyrs-and-their-companions-martyrs) + (names ((en "Saints Jean de Brébeuf, Isaac Jogues, priests and martyrs; and their companions, martyrs") (la "Ss. Ioannis de Brébeuf et Isaac Jogues, presbyterorum, et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 26))) + (cel + ((slug saints-joachim-and-anne) + (names ((en "Saints Joachim and Anne") (la "Ss. Ioachim et Annae, parentum B.M.V."))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 22))) + (cel + ((slug saints-john-fisher-bishop-and-martyr-and-thomas-more-martyr) + (names ((en "Saints John Fisher, bishop and martyr and Thomas More, martyr") (la "Ss. Ioannis Fisher, episcopi, et Thomae More, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 28))) + (cel + ((slug saints-lawrence-ruiz-and-companions-martyrs) + (names ((en "Saints Lawrence Ruiz and companions, martyrs") (la "Ss. Laurentii Ruiz et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 2))) + (cel + ((slug saints-marcellinus-and-peter-martyrs) + (names ((en "Saints Marcellinus and Peter, martyrs") (la "Ss. Marcellini et Petri, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 29))) + (cel + ((slug saints-michael-gabriel-and-raphael-archangels) + (names ((en "Saints Michael, Gabriel and Raphael, Archangels") (la "Ss. Michaelis, Gabrielis et Raphaelis, archangelorum"))) + (rank Festum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 12))) + (cel + ((slug saints-nereus-and-achilleus-martyrs) + (names ((en "Saints Nereus and Achilleus, martyrs") (la "Ss. Nerei et Achillei, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 6))) + (cel + ((slug saints-paul-miki-and-companions-martyrs) + (names ((en "Saints Paul Miki and companions, martyrs") (la "Ss. Pauli Miki et sociorum, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 7))) + (cel + ((slug saints-perpetua-and-felicity-martyrs) + (names ((en "Saints Perpetua and Felicity, martyrs") (la "Ss. Perpetuae et Felicitatis, martyrum"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 6) (day 29))) + (cel + ((slug saints-peter-and-paul-apostles) + (names ((en "Saints Peter and Paul, Apostles") (la "Ss. PETRI ET PAULI, APOSTOLORUM"))) + (rank Sollemnitas) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 3))) + (cel + ((slug saints-philip-and-james-apostles) + (names ((en "Saints Philip and James, Apostles") (la "Ss. Philippi et Iacobi, apostolorum"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 13))) + (cel + ((slug saints-pontian-pope-and-hippolytus-priest-martyrs) + (names ((en "Saints Pontian, pope, and Hippolytus, priest, martyrs") (la "Ss. Pontiani, papae, et Hippolyti, presbyteri, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 26))) + (cel + ((slug saints-timothy-and-titus-bishops) + (names ((en "Saints Timothy and Titus, bishops") (la "Ss. Timothei et Titi, episcoporum"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 10))) + (cel + ((slug scholastica-virgin) + (names ((en "Saint Scholastica, virgin") (la "S. Scholasticae, virginis"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 20))) + (cel + ((slug sebastian-martyr) + (names ((en "Saint Sebastian, martyr") (la "S. Sebastiani, martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 2) (day 17))) + (cel + ((slug seven-holy-founders-of-the-servite-order) + (names ((en "Seven Holy Founders of the Servite Order") (la "Ss. septem Fundatorum Ordinis Servorum B.M.V."))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 24))) + (cel + ((slug sharbel-makhluf-hermit) + (names ((en "Saint Sharbel Makhluf, hermit") (la "S. Sarbelii Makhlu‾ f, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 28))) + (cel + ((slug simon-and-saint-jude-apostles) + (names ((en "Saint Simon and Saint Jude, apostles") (la "Ss. Simonis et Iudae, apostolorum"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 7))) + (cel + ((slug sixtus-ii-pope-and-companions-martyrs) + (names ((en "Saint Sixtus II, pope, and companions, martyrs") (la "Ss. Xysti II, papae, et sociorum, martyrum"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 11))) + (cel + ((slug stanislaus-bishop-and-martyr) + (names ((en "Saint Stanislaus, bishop and martyr") (la "S. Stanislai, episcopi et martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 16))) + (cel + ((slug stephen-of-hungary) + (names ((en "Saint Stephen of Hungary") (la "S. Stephani Hungariae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 26))) + (cel + ((slug stephen-the-first-martyr) + (names ((en "Saint Stephen, the first martyr") (la "S. Stephani, protomartyris"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 31))) + (cel + ((slug sylvester-i-pope) + (names ((en "Saint Sylvester I, pope") (la "S. Silvestri I, papae"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 9))) + (cel + ((slug teresa-benedicta-of-the-cross-edith-stein-virgin-and-martyr) + (names ((en "Saint Teresa Benedicta of the Cross (Edith Stein), virgin and martyr") (la "S. Teresiae Benedictae a Cruce, virginis et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 15))) + (cel + ((slug teresa-of-jesus-virgin-and-doctor) + (names ((en "Saint Teresa of Jesus, virgin and doctor") (la "S. Teresiae a Iesu, virginis et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 29))) + (cel + ((slug the-beheading-of-saint-john-the-baptist-martyr) + (names ((en "The Beheading of Saint John the Baptist, martyr") (la "In Passione S. Ioannis Baptistae, martyris"))) + (rank Memoria_obligatoria) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 25))) + (cel + ((slug the-conversion-of-saint-paul-apostle) + (names ((en "The Conversion of Saint Paul, apostle") (la "In Conversione S. Pauli, apostoli"))) + (rank Festum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 3))) + (cel + ((slug the-most-holy-name-of-jesus) + (names ((en "The Most Holy Name of Jesus") (la "Ss.mi Nominis Iesu"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 10) (day 1))) + (cel + ((slug therese-of-the-child-jesus-virgin-and-doctor) + (names ((en "Saint Thérèse of the Child Jesus, virgin and doctor") (la "S. Teresiae a Iesu Infante, virginis et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 28))) + (cel + ((slug thomas-aquinas-priest-and-doctor) + (names ((en "Saint Thomas Aquinas, priest and doctor") (la "S. Thomae de Aquino, presbyteri et Ecclesiae doctoris"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 12) (day 29))) + (cel + ((slug thomas-becket-bishop-and-martyr) + (names ((en "Saint Thomas Becket, bishop and martyr") (la "S. Thomae Becket, episcopi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 7) (day 3))) + (cel + ((slug thomas-the-apostle) + (names ((en "Saint Thomas the Apostle") (la "S. Thomae, apostoli"))) + (rank Festum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 8) (day 6))) + (cel + ((slug transfiguration-of-the-lord) + (names ((en "Transfiguration of the Lord") (la "In Transfiguratione Domini"))) + (rank Festum) (status Feast) (colour White) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 14))) + (cel + ((slug triumph-of-the-holy-cross) + (names ((en "Triumph of the Holy Cross") (la "In Exaltatione sanctae Crucis"))) + (rank Festum) (status Feast) (colour Red) + (subject Lord) (citations ()) (layer of-universal)))) + ((date (Fixed (month 3) (day 23))) + (cel + ((slug turibius-of-mogrovejo-bishop) + (names ((en "Saint Turibius of Mogrovejo, bishop") (la "S. Turibii de Mogrovejo, episcopi"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 27))) + (cel + ((slug vincent-de-paul-priest) + (names ((en "Saint Vincent de Paul, priest") (la "S. Vincentii de Paul, presbyteri"))) + (rank Memoria_obligatoria) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 1) (day 22))) + (cel + ((slug vincent-deacon-and-martyr) + (names ((en "Saint Vincent, deacon and martyr") (la "S. Vincentii, diaconi et martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 4) (day 5))) + (cel + ((slug vincent-ferrer-priest) + (names ((en "Saint Vincent Ferrer, priest") (la "S. Vincentii Ferrer, presbyteri"))) + (rank Memoria_ad_libitum) (status Feast) (colour White) + (subject Saint) (citations ()) (layer of-universal)))) + ((date (Fixed (month 5) (day 31))) + (cel + ((slug visitation-of-the-blessed-virgin-mary) + (names ((en "Visitation of the Blessed Virgin Mary") (la "In Visitatione B. Mariae Virginis"))) + (rank Festum) (status Feast) (colour White) + (subject Bvm) (citations ()) (layer of-universal)))) + ((date (Fixed (month 9) (day 28))) + (cel + ((slug wenceslaus-martyr) + (names ((en "Saint Wenceslaus, martyr") (la "S. Venceslai, martyris"))) + (rank Memoria_ad_libitum) (status Feast) (colour Red) + (subject Saint) (citations ()) (layer of-universal)))) + ))) diff --git a/test/dune b/test/dune index 00114cf..9af9900 100644 --- a/test/dune +++ b/test/dune @@ -4,6 +4,7 @@ (deps ../data/ef/sanctoral.sexp ../data/ef/adjustments.sexp + ../data/of/calendar-2002.sexp ../lang/la.ini ../lang/en.ini ../lang/traditions.ini diff --git a/test/test_calendar_of_data.ml b/test/test_calendar_of_data.ml new file mode 100644 index 0000000..01a757e --- /dev/null +++ b/test/test_calendar_of_data.ml @@ -0,0 +1,207 @@ +(* Task 1 (2026-08-25-colitur-of-phases-3-5): loads data/of/calendar-2002.sexp + -- the General Roman Calendar transcribed from the 2002 Missale Romanum by + tools/extract_of_calendar.py -- through Layer.load and checks it against + counts independently re-derived from the shipped file itself (grep -c + '(slug', grep -oP rank/status/colour/subject | sort | uniq -c -- see + .superpowers/sdd/2026-08-25-colitur-of-phases-3-5/task-1-report.md), plus + named spot-checks read straight off the Missal's own printed page so a + passing count can't hide the wrong 206 entries having been transcribed + (the same "live hazard" test_sanctoral_ef.ml's own header names). + + SOURCE DISCIPLINE, asserted structurally here, not merely stated in the + tool's own header: every entry's rank is one of the four Vocab_of.rank + grades NEVER Feria (that rank is temporal-only, see vocab_of.mli), and + every entry's layer is Precedence_of.universal_layer. *) + +module L = Colitur_kernel.Layer +module Cel = Colitur_kernel.Celebration +module S = Colitur_kernel.Slug +module DS = Colitur_kernel.Date_spec +module D = Colitur_kernel.Date +module Col = Colitur_kernel.Colour +module Sub = Colitur_kernel.Subject +module Lang = Colitur_kernel.Lang +module Names = Colitur_kernel.Names +module V = Rite_of.Vocab_of +module PO = Rite_of.Precedence_of +module T = Rite_of.Temporal_of + +(* Relative to this test's own build directory (_build/default/test/); made + available there because test/dune declares it as a dep of the test + stanza. *) +let path = "../data/of/calendar-2002.sexp" + +let load () = + match L.load V.rank_of_sexp path with + | Ok l -> l + | Error e -> Alcotest.failf "%s: failed to load: %s" path e + +let mkdate m d = match DS.fixed ~month:m ~day:d with Ok t -> t | Error e -> failwith e + +let sha256_of_file path = + let tmp = Filename.temp_file "colitur_calendar_of_sha256" ".txt" in + let cmd = Printf.sprintf "sha256sum %s > %s" (Filename.quote path) (Filename.quote tmp) in + (match Sys.command cmd with + | 0 -> () + | n -> Alcotest.failf "sha256sum exited %d" n); + let ic = open_in tmp in + let line = input_line ic in + close_in ic; + Sys.remove tmp; + match String.split_on_char ' ' line with + | hash :: _ -> hash + | [] -> Alcotest.fail "sha256sum produced no output" + +(* Pinned 2026-08-25 against the shipped file itself, independently + re-derived (`sha256sum data/of/calendar-2002.sexp`) -- not copied from + the tool's own stdout run, per this project's "derive the new value, + don't transcribe it from output" pinning discipline. *) +let test_sha256 () = + Alcotest.(check string) "data/of/calendar-2002.sexp SHA-256" + "b2bf3fa9d27f5bc6e47225c477c158f629eaab6c280e0b69fa04c8a55db9f612" (sha256_of_file path) + +(* Counts independently re-derived from the shipped file + (`grep -c '(slug'`; `grep -oP '(?<=\(rank )[A-Za-z_]+' | sort | uniq -c`). + 206 = 209 raw table rows minus the 3 dates (1 Jan, 6 Jan, 25 Dec) already + computed by Temporal_of.named -- see test_excludes_temporal_of_dates + below, which closes the loop on that exclusion rather than merely + asserting the count moved. *) +let test_load_and_counts () = + let l = load () in + Alcotest.(check int) "206 entries" 206 (List.length l.L.entries); + let count pred = List.length (List.filter pred l.L.entries) in + Alcotest.(check int) "8 Sollemnitas" 8 (count (fun e -> e.L.cel.Cel.rank = V.Sollemnitas)); + Alcotest.(check int) "23 Festum" 23 (count (fun e -> e.L.cel.Cel.rank = V.Festum)); + Alcotest.(check int) "67 Memoria_obligatoria" 67 + (count (fun e -> e.L.cel.Cel.rank = V.Memoria_obligatoria)); + Alcotest.(check int) "108 Memoria_ad_libitum" 108 + (count (fun e -> e.L.cel.Cel.rank = V.Memoria_ad_libitum)); + Alcotest.(check int) "0 Feria (temporal-only rank, never a calendar entry)" 0 + (count (fun e -> e.L.cel.Cel.rank = V.Feria)); + Alcotest.(check int) "206 Feast (OF admits no Commemoration_only status)" 206 + (count (fun e -> e.L.cel.Cel.status = Cel.Feast)); + Alcotest.(check int) "every entry tagged Precedence_of.universal_layer" 206 + (count (fun e -> e.L.cel.Cel.layer = PO.universal_layer)) + +let test_slugs_unique () = + let l = load () in + let slugs = List.map (fun e -> S.to_string e.L.cel.Cel.slug) l.L.entries in + let sorted = List.sort_uniq compare slugs in + Alcotest.(check int) "no duplicate slugs" (List.length slugs) (List.length sorted) + +let test_slugs_revalidate () = + let l = load () in + List.iter + (fun e -> + match S.of_string (S.to_string e.L.cel.Cel.slug) with + | Ok _ -> () + | Error msg -> Alcotest.failf "slug %s failed re-validation: %s" (S.to_string e.L.cel.Cel.slug) msg) + l.L.entries + +(* Every date is a Fixed month/day; DS.fixed already rejects an out-of-range + day against the leap-year maximum, so this also proves no entry claims a + nonexistent day (e.g. 30 February). *) +let test_dates_resolve_in_a_leap_year () = + let l = load () in + List.iter + (fun e -> + match DS.resolve e.L.date ~year:2028 ~easter:(Colitur_kernel.Computus.gregorian_easter 2028) with + | Some _ -> () + | None -> Alcotest.failf "slug %s: date does not resolve in leap year 2028" (S.to_string e.L.cel.Cel.slug)) + l.L.entries + +let find l slug = + match L.find l (S.of_string_exn slug) with + | Some e -> e + | None -> Alcotest.failf "slug %s not found in %s" slug path + +(* Brief step 4's own named spot-checks, read straight off the Missal's + printed Calendarium Romanum Generale page. *) + +let test_spot_check_conversion_of_paul () = + let e = find (load ()) "the-conversion-of-saint-paul-apostle" in + Alcotest.(check bool) "25 Jan" true (e.L.date = mkdate 1 25); + Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum); + (* IGMR 346(a)'s own explicit white exception for this date, despite the + title's "apostoli" which would otherwise trigger the apostle/martyr-red + default -- see tools/extract_of_calendar.py's WHITE_APOSTLE_EXCEPTIONS. *) + Alcotest.(check bool) "white (IGMR 346(a) explicit exception)" true (e.L.cel.Cel.colour = Col.White) + +let test_spot_check_chair_of_peter () = + let e = find (load ()) "chair-of-saint-peter-apostle" in + Alcotest.(check bool) "22 Feb" true (e.L.date = mkdate 2 22); + Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum); + Alcotest.(check bool) "white (IGMR 346(a) explicit exception)" true (e.L.cel.Cel.colour = Col.White) + +let test_spot_check_presentation () = + let e = find (load ()) "presentation-of-the-lord" in + Alcotest.(check bool) "2 Feb" true (e.L.date = mkdate 2 2); + Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum); + Alcotest.(check bool) "subject Lord" true (e.L.cel.Cel.subject = Sub.Lord) + +let test_spot_check_francis_de_sales () = + let e = find (load ()) "francis-de-sales-bishop-and-doctor" in + Alcotest.(check bool) "24 Jan" true (e.L.date = mkdate 1 24); + Alcotest.(check bool) "Memoria_obligatoria (printed grade word: Memoria)" true + (e.L.cel.Cel.rank = V.Memoria_obligatoria) + +let test_spot_check_angela_merici () = + let e = find (load ()) "angela-merici-virgin" in + Alcotest.(check bool) "27 Jan" true (e.L.date = mkdate 1 27); + Alcotest.(check bool) "blank grade -> Memoria_ad_libitum (calendar's own footnote)" true + (e.L.cel.Cel.rank = V.Memoria_ad_libitum) + +(* 1 January (Mary, Mother of God) IS "Sollemnitas" on the Missal's own + printed page, the brief's sixth named spot-check -- but it is + DELIBERATELY ABSENT from this file: Phase 1's Temporal_of.named already + computes it as a temporal-cycle office (m=1, dd=1), and shipping it here + too would create two competing candidates for the same day (see + tools/extract_of_calendar.py's own module docstring, point 2, and this + file's SKIP_DATES table). Asserted both ways, so the exclusion is + verified rather than merely claimed: absent from the data, present and + correctly graded in the code that actually covers it. The same holds for + 6 January (Epiphany) and 25 December (the Nativity). *) +let test_excludes_temporal_of_dates () = + let l = load () in + let has_date m d = + List.exists (fun e -> e.L.date = mkdate m d) l.L.entries + in + Alcotest.(check bool) "1 Jan absent from calendar-2002.sexp" false (has_date 1 1); + Alcotest.(check bool) "6 Jan absent from calendar-2002.sexp" false (has_date 1 6); + Alcotest.(check bool) "25 Dec absent from calendar-2002.sexp" false (has_date 12 25); + let mkd m d = match D.make ~year:2026 ~month:m ~day:d with Ok d -> d | Error e -> failwith e in + (match T.named (mkd 1 1) with + | Some (_, _, colour, rank) -> + Alcotest.(check bool) "1 Jan: Temporal_of.named yields Sollemnitas" true (rank = V.Sollemnitas); + Alcotest.(check bool) "1 Jan: Temporal_of.named yields White" true (colour = Col.White) + | None -> Alcotest.fail "1 Jan: Temporal_of.named returned None -- the office this file \ + excludes on that basis does not actually exist in code"); + (match T.named (mkd 1 6) with + | Some (_, _, _, rank) -> Alcotest.(check bool) "6 Jan: Sollemnitas" true (rank = V.Sollemnitas) + | None -> Alcotest.fail "6 Jan: Temporal_of.named returned None"); + match T.named (mkd 12 25) with + | Some (_, _, _, rank) -> Alcotest.(check bool) "25 Dec: Sollemnitas" true (rank = V.Sollemnitas) + | None -> Alcotest.fail "25 Dec: Temporal_of.named returned None" + +let suite = + ( "Calendar_of (data/of/calendar-2002.sexp)", + [ Alcotest.test_case "SHA-256 pinned" `Quick test_sha256; + Alcotest.test_case "load succeeds and counts match the shipped file" `Quick + test_load_and_counts; + Alcotest.test_case "slugs are unique" `Quick test_slugs_unique; + Alcotest.test_case "slugs re-validate" `Quick test_slugs_revalidate; + Alcotest.test_case "every date resolves in a leap year" `Quick + test_dates_resolve_in_a_leap_year; + Alcotest.test_case "spot-check: 25 Jan Festum, Conversion of Paul" `Quick + test_spot_check_conversion_of_paul; + Alcotest.test_case "spot-check: 22 Feb Festum, Chair of Peter" `Quick + test_spot_check_chair_of_peter; + Alcotest.test_case "spot-check: 2 Feb Festum, Presentation" `Quick + test_spot_check_presentation; + Alcotest.test_case "spot-check: 24 Jan Memoria, Francis de Sales" `Quick + test_spot_check_francis_de_sales; + Alcotest.test_case "spot-check: 27 Jan blank -> Memoria_ad_libitum, Angela Merici" `Quick + test_spot_check_angela_merici; + Alcotest.test_case + "1/6 Jan and 25 Dec deliberately excluded -- Temporal_of.named covers them" `Quick + test_excludes_temporal_of_dates ] ) diff --git a/test/test_colitur.ml b/test/test_colitur.ml index b6675fd..c750e75 100644 --- a/test/test_colitur.ml +++ b/test/test_colitur.ml @@ -14,7 +14,8 @@ let () = Test_overlay.suite; Test_overlay_ini.suite; Test_temporal_ef.suite; Test_temporal_of.suite; Test_litcal_of.suite; Test_precedence_of.suite; Test_validate.suite; Test_precedence.suite; - Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_rite_ef.suite; + Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_calendar_of_data.suite; + Test_rite_ef.suite; Test_rubrics_ef.suite; Test_differential.suite; Test_oracle.suite; Test_oracle.suite_2038; Test_oracle.suite_2035; Test_lms_ordo.suite_2023_2024; Test_lms_ordo.suite_2024_2025; Test_lms_ordo.suite_2025_2026; diff --git a/tools/extract_of_calendar.py b/tools/extract_of_calendar.py new file mode 100644 index 0000000..864c409 --- /dev/null +++ b/tools/extract_of_calendar.py @@ -0,0 +1,677 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: AGPL-3.0-or-later +"""tools/extract_of_calendar.py -- transcribes the Calendarium Romanum +Generale (General Roman Calendar) from the 2002 Missale Romanum, editio +typica tertia, into data/of/calendar-2002.sexp: a Colitur_kernel.Layer.t of +Colitur_kernel.Celebration.t entries, one per FIXED (month, day) universal +calendar entry. + +SOURCE DISCIPLINE (do not weaken this): the 2002 Missale Romanum PDF is the +authority. lectio's ~/git/projects/lectio/internal/caldata/roman-calendar.ini +is read here ONLY for two cross-check purposes, never as structural source: + (a) comparing (month, day, rank) to surface divergences, each of which is + adjudicated by re-reading the Missal (see the divergence log this + script prints), never by silently preferring lectio; + (b) copying `name.en` -- lectio's OWN `name.la` field is independently + known to be unreliable (it contains English text for at least one + entry, verified during planning: mary-mother-of-god-octave-of-christmas + has name.la = "Mary, Mother of God (Octave of Christmas)"), so Latin + is NEVER taken from lectio, only English. + +Run: `python3 tools/extract_of_calendar.py [pretext-file] > data/of/calendar-2002.sexp` +With no argument, runs `pdftotext -layout` on the PDF itself. A pre-extracted +text file may be given instead (for reproducibility without re-invoking +pdftotext). + +WHAT THIS FILE DELIBERATELY EXCLUDES, and why (each is a real design +decision, not an oversight): + +1. Every MOVABLE universal solemnity/feast printed in the calendar table + under a "Dominica ... :" / "Feria ... :" / "Sabbato ... :" heading + (Baptism of the Lord, Holy Family, Trinity Sunday, Corpus Christi, the + Sacred Heart of Jesus, the Immaculate Heart of Mary, Christ the King). + None of these has a (month, day) Date_spec.Fixed key at all -- they are + Easter-relative, and Date_spec.Fixed is this file's only key shape (see + the task interface). Trinity/Corpus Christi/Christ the King/Holy Family/ + Baptism of the Lord are ALREADY computed by lib/rites/rite_of/ + temporal_of.ml's own `named`/`holy_family`/`baptism_of_the_lord` (Phase + 1, already merged, frozen for this task) -- duplicating them here would + create two competing candidates for the same day. + + FINDING (report this loudly, it is not this task's to fix): the Sacred + Heart of Jesus ("Feria VI post dominicam secundam post Pentecosten: + SACRATISSIMI CORDIS IESU Sollemnitas") and the Immaculate Heart of Mary + ("Sabbato post dominicam secundam post Pentecosten: Immaculati Cordis B. + Mariae Virginis Memoria") are printed in the SAME base-2002 calendar + table as Trinity/Corpus Christi/Christ the King, but temporal_of.ml + covers NEITHER of them -- grep for "Cordis"/"Sacred"/"Sacratissim" in + that file finds nothing. This is a genuine gap in Phase 1's own + coverage, orthogonal to Task 1 (a Fixed-only data file cannot express + an Easter-relative feast), not caused by this task and not fixable by + it -- Temporal_of would need two more named-day branches + (Pentecost+19 and Pentecost+20 respectively, i.e. Easter+68/+69). + +2. Three FIXED entries that ARE in the printed table but are ALSO already + computed by temporal_of.ml's own `named`: 1 January (Mary, Mother of + God), 6 January (Epiphany), 25 December (the Nativity). Shipping them + here too would duplicate a candidate Precedence_of.band would then have + to arbitrate between a temporal-origin and a data-origin copy of the + IDENTICAL office -- pointless and risks a silent divergence between the + two copies. Excluded explicitly (SKIP_DATES below), not merely absent + by accident. + +DATA DECISIONS made while transcribing, each cited in the emitted file's +own provenance header: + +- A blank grade column means Memoria ad libitum, per the calendar's own + footnote (`* Quando non indicatur gradus celebrationis, fit Memoria ad + libitum.`) -- Normae/Missale Romanum 2002, Calendarium Romanum Generale, + its own footnote marker. +- 2 November (Commemoratio omnium fidelium defunctorum, All Souls) carries + NO grade word in the table at all -- applying the footnote naively would + misclassify the second-most solemn day of November as an optional + memorial. Normae n. 59's own Tabula dierum liturgicorum, entry 3, places + it explicitly alongside "Sollemnitates ... in Calendario generali + inscriptae" ("Sollemnitates Domini, beatae Mariae Virginis, et Sanctorum + in Calendario generali inscriptae. Commemoratio omnium fidelium + defunctorum.") -- same table entry, same precedence tier. Tagged + Sollemnitas here on that citation, as an explicit, documented override, + not the generic blank-grade default. +- Colour is derived from IGMR n. 346 (a)-(d): white for Christ's + non-Passion celebrations / the BVM / Angels / non-martyr Saints / All + Saints / John the Baptist's Nativity / John the Evangelist / Chair of + Peter / Conversion of Paul (346(a), the last three named explicitly); + red for Passion-related celebrations of the Lord, "festis nataliciis + Apostolorum et Evangelistarum" (an Apostle's or Evangelist's own feast), + and any celebration of Martyr Saints (346(b)); violet, chosen over the + also-permitted black, for the Commemoration of All the Souls (346(d): + "Assumi potest etiam in Officiis et Missis defunctorum" -- explicitly + authorised for the dead; 346(a)'s white clause has no textual reach here, + since All Souls is not "Sanctorum" (canonised Saints) at all). This + DIVERGES from lectio, which colours 2 November white -- see the + divergence log below; adjudicated colitur/violet on 346(d)'s explicit + text, flagging lectio's white as reflecting a widespread modern PASTORAL + custom the Missal's own words do not themselves authorise as the + default. Three explicit exceptions to the apostle/evangelist-red rule + (Chair of Peter, Conversion of Paul, John the Evangelist) and one to the + martyr/apostle-red rule (three "In Dedicatione ..." church-dedication + entries, always white) are coded as override tables, cited alongside the + rule. + +EXTRACTION ARTIFACT: some titles are printed letter-spaced by pdftotext +(e.g. "S . P e t r i D a m i a n i"). `collapse_letterspacing` mechanically +rejoins runs of >=4 single-character tokens, using punctuation (periods, +commas) and a lowercase-then-uppercase transition as word-boundary signals. +This CANNOT recover a lowercase-to-lowercase boundary (no case signal +survives the original single-spacing), so it is a first pass only; the sole +entry it fires on in this table (21 February, S. Petri Damiani) is then +hand-verified against the well-attested standard title and corrected +explicitly, with the exact reason recorded at the call site. The count of +entries needing this repair is printed to stderr. +""" + +import hashlib +import re +import subprocess +import sys +import unicodedata +from datetime import date, timezone, datetime + +PDF_PATH = "docs/research/of/missale-romanum-2002.pdf" +TOOL_PATH = "tools/extract_of_calendar.py" +LECTIO_INI = "../lectio/internal/caldata/roman-calendar.ini" + +MONTHS = [ + "IANUARIUS", "FEBRUARIUS", "MARTIUS", "APRILIS", "MAIUS", "IUNIUS", + "IULIUS", "AUGUSTUS", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER", +] +MONTH_NUM = {m: i + 1 for i, m in enumerate(MONTHS)} + +ROW_RE = re.compile(r"^(Cal\.|Prid\.|Non\.|Idib\.|[IVXL]+)\s*(\d{1,2})\b\s*(.*)$") +MOVABLE_HEADING_RE = re.compile(r"^(Dominica|Feria\s+[IVXL]+|Sabbato)\b.*\b(post|ultima|infra)\b") +ENTRY_MARKER_RE = re.compile(r"^(S\.|Ss\.|B\.)\s") +FOOTNOTE_RE = re.compile(r"^\*\s*Quando non indicatur") + +GRADE_WORDS = ["Sollemnitas", "Festum", "Memoria"] +GRADE_TO_RANK = { + "Sollemnitas": "Sollemnitas", + "Festum": "Festum", + "Memoria": "Memoria_obligatoria", + None: "Memoria_ad_libitum", +} + +# Excluded because Phase 1 (lib/rites/rite_of/temporal_of.ml) already +# computes these as temporal-cycle offices. See module docstring, point 2. +SKIP_DATES = { + (1, 1): "of-mary-mother-of-god (Temporal_of.named, m=1 dd=1)", + (1, 6): "of-epiphany (Temporal_of.named, m=1 dd=6)", + (12, 25): "of-nativity (Temporal_of.named, m=12 dd=25)", +} + +# IGMR 346(a)'s own three explicit white exceptions to the apostle/ +# evangelist-red rule. +WHITE_APOSTLE_EXCEPTIONS = {(1, 25), (2, 22), (12, 27)} + +# Church-dedication feasts: always white, never coloured by the martyr/ +# apostle words that happen to appear in their own titles (the apostles +# named are the church's patrons, not the day's own honouree). +DEDICATION_DATES = {(8, 5), (11, 9), (11, 18)} + +# All Souls: see module docstring for the 346(d) citation. +ALL_SOULS_OVERRIDE = (11, 2) + +# Explicit red override: the Exaltation of the Holy Cross is a celebration +# "Passionis Domini" (346(b)) even though its own title contains neither +# "martyr" nor "apostol"/"evangelist". +RED_OVERRIDES = {(9, 14)} + +# subject = Lord: celebrations of the Lord not already excluded above, +# plus the three church-dedication feasts (a dedication is itself +# classified a "festum Domini" -- see CLAUDE.md's EF precedent, and +# lectio's own roman-calendar.ini tags all three `class = lord`). +LORD_DATES = {(2, 2), (3, 25), (8, 6), (1, 3), (9, 14)} | DEDICATION_DATES + + +def sha256_file(path): + h = hashlib.sha256() + with open(path, "rb") as f: + h.update(f.read()) + return h.hexdigest() + + +def get_text(pdf_path): + out = subprocess.run(["pdftotext", "-layout", pdf_path, "-"], capture_output=True, check=True) + return out.stdout.decode("utf-8") + + +def collapse_letterspacing(line): + """Mechanically rejoins a run of >=4 single-character tokens. Returns + (new_line, changed). See module docstring's "EXTRACTION ARTIFACT" note + for what this can and cannot recover.""" + tokens = line.split(" ") + out = [] + i = 0 + changed = False + while i < len(tokens): + if len(tokens[i]) == 1: + j = i + while j < len(tokens) and len(tokens[j]) == 1: + j += 1 + run = tokens[i:j] + if len(run) >= 4: + changed = True + merged = "".join(run) + merged = re.sub(r"(?<=[.,])(?=\S)", " ", merged) + merged = re.sub(r"(?<=[a-zà-öø-ÿæœ])(?=[A-ZÀ-ÖØ-ÝÆŒ])", " ", merged) + out.append(merged) + else: + out.extend(run) + i = j + else: + out.append(tokens[i]) + i += 1 + return " ".join(out), changed + + +def bound_table(text): + lines = text.split("\n") + # First IANUARIUS before line 5000, immediately preceded (allowing for a + # blank line) by "CALENDARIUM ROMANUM GENERALE" -- rules out the second, + # Proprium Sanctorum run near line 17874. + start = None + for i, l in enumerate(lines[:5000]): + if l.strip("\x0c").strip() == "IANUARIUS": + # confirm CALENDARIUM ROMANUM GENERALE appears in the preceding + # few lines + window = "\n".join(lines[max(0, i - 4):i]) + if "CALENDARIUM ROMANUM GENERALE" in window: + start = i + break + if start is None: + raise SystemExit("could not find the start of the calendar table (IANUARIUS after " + "CALENDARIUM ROMANUM GENERALE, before line 5000)") + end = None + for i in range(start, min(len(lines), 5000)): + if lines[i].strip() == "TABELLA TEMPORARIA": + end = i + break + if end is None: + raise SystemExit("could not find the end of the calendar table (TABELLA TEMPORARIA)") + return start, end, lines[start:end] + + +def is_month_header(line): + return line.strip("\x0c").strip() in MONTH_NUM + + +def parse_rows(table_lines): + """Returns (entries, letterspace_count) where entries is a list of dicts + with month, day, latin (raw joined title), grade (str or None).""" + # Pass 1: strip form feeds and collapse letter-spacing line by line. + letterspace_count = 0 + lines = [] + for raw in table_lines: + l = raw.replace("\x0c", "") + l2, changed = collapse_letterspacing(l) + if changed: + letterspace_count += 1 + lines.append(l2) + + entries = [] + current_month = None + i = 0 + n = len(lines) + while i < n: + line = lines[i].strip() + if line == "": + i += 1 + continue + if is_month_header(line): + current_month = MONTH_NUM[line] + i += 1 + continue + if FOOTNOTE_RE.match(line): + i += 1 + continue + if MOVABLE_HEADING_RE.match(line): + i += 1 + while i < n: + nxt = lines[i].strip() + if (nxt == "" or is_month_header(nxt) or ROW_RE.match(nxt) + or MOVABLE_HEADING_RE.match(nxt) or FOOTNOTE_RE.match(nxt)): + break + i += 1 + continue + rm = ROW_RE.match(line) + if rm: + if current_month is None: + raise SystemExit(f"row before any month header: {line!r}") + _kalend, day_s, rest = rm.groups() + day = int(day_s) + blocks = [[]] + if rest.strip(): + blocks[0].append(rest.strip()) + i += 1 + while i < n: + nxt_raw = lines[i] + nxt = nxt_raw.strip() + if (nxt == "" or is_month_header(nxt) or ROW_RE.match(nxt) + or MOVABLE_HEADING_RE.match(nxt) or FOOTNOTE_RE.match(nxt)): + break + if ENTRY_MARKER_RE.match(nxt) and blocks[-1]: + blocks.append([nxt]) + else: + blocks[-1].append(nxt) + i += 1 + for block in blocks: + if not block: + continue + text = " ".join(block).strip() + grade = None + for g in GRADE_WORDS: + if text == g or text.endswith(" " + g): + grade = g + text = text[: -len(g)].strip() + break + # Jan 3: "Ss.mi Nominis Iesu *" -- the footnote's own marker + # asterisk, not a grade word. Strip it; the footnote's blank + # -> Memoria_ad_libitum mapping still applies. + if text.endswith("*"): + text = text[:-1].strip() + if text: + entries.append({"month": current_month, "day": day, "latin": text, "grade": grade}) + continue + raise SystemExit(f"unrecognised line in calendar table (month={current_month}): {line!r}") + + return entries, letterspace_count + + +LATIN_TO_ASCII = str.maketrans({ + # Æ (uppercase ligature) is verified, across every occurrence in the + # extracted calendar table, to appear ONLY inside an ALL-CAPS heading + # ("SANCTÆ", "PRÆSENTATIONE", "ASSUMPTIONE BEATÆ") -- never as the + # initial letter of a Title Case word -- so it maps to "AE" (both + # capitals), not "Ae", to stay consistent with its own surrounding case. + "æ": "ae", "Æ": "AE", "œ": "oe", "Œ": "OE", "ø": "o", "Ø": "O", +}) + + +def normalize_latin(text): + """æ -> ae, œ -> oe, matching the existing la-name convention already + used by lib/rites/rite_of/temporal_of.ml's own holy_family_names/ + baptism_names (e.g. "Sanctae Familiae Iesu, Mariae et Ioseph"), not the + ligature glyphs the PDF itself prints. Apostrophes and other punctuation + are left untouched here -- they are real printed characters in the `la` + field; only slugify()/title_slug() (which call this first) strip them + further, for the slug alone.""" + return text.translate(LATIN_TO_ASCII) + + +def slugify(latin): + s = normalize_latin(latin) + s = unicodedata.normalize("NFKD", s) + s = "".join(c for c in s if not unicodedata.combining(c)) + s = s.lower() + s = re.sub(r"[^a-z0-9]+", "-", s) + s = re.sub(r"-+", "-", s).strip("-") + return s + + +# Words to drop when deriving a slug from a title, so slugs read as names +# rather than descriptions (kept short and stable, matching data/ef/ +# sanctoral.sexp's own style, e.g. "agnes", "all-saints"). +DROP_WORDS = { + "s", "ss", "b", "in", "et", "de", "ad", "a", "the", "of", +} + + +def title_slug(latin, month, day): + words = re.split(r"[\s,]+", normalize_latin(latin)) + kept = [] + for w in words: + base = re.sub(r"\.$", "", w) + if base.lower() in DROP_WORDS or base == "": + continue + kept.append(base) + if len(kept) >= 6: + break + if not kept: + kept = [f"of-{month:02d}-{day:02d}"] + return slugify(" ".join(kept)) + + +def classify_colour(month, day, latin): + key = (month, day) + if key == ALL_SOULS_OVERRIDE: + return "Violet" + if key in RED_OVERRIDES: + return "Red" + if key in DEDICATION_DATES: + return "White" + if "martyr" in latin.lower(): + return "Red" + if key in WHITE_APOSTLE_EXCEPTIONS: + return "White" + lower = latin.lower() + if "apostol" in lower or "evangelist" in lower: + return "Red" + return "White" + + +def classify_subject(month, day, latin): + key = (month, day) + if key in LORD_DATES: + return "Lord" + lower = normalize_latin(latin).lower() + if key != (3, 19) and ("maria" in lower or "b.m.v" in lower or "bmv" in lower): + return "Bvm" + return "Saint" + + +def parse_lectio_ini(path): + entries = [] + cur = None + try: + with open(path, encoding="utf-8") as f: + raw = f.read() + except FileNotFoundError: + return entries + for line in raw.split("\n"): + line = line.rstrip("\n") + if line.startswith("[") and line.endswith("]"): + if cur is not None: + entries.append(cur) + cur = {"slug": line[1:-1]} + elif "=" in line and cur is not None: + k, _, v = line.partition("=") + cur[k.strip()] = v.strip() + if cur is not None: + entries.append(cur) + return [e for e in entries if "date" in e] + + +LECTIO_RANK_TO_VOCAB = { + "solemnity": "Sollemnitas", + "feast": "Festum", + "memorial": "Memoria_obligatoria", + "optional": "Memoria_ad_libitum", +} + + +def significant_words(text): + words = re.split(r"[\s,.;]+", normalize_latin(text).lower()) + return {w for w in words if w and w not in DROP_WORDS and len(w) > 2} + + +def cross_check(entries, lectio_entries): + """Returns (report_lines, name_en_by_id) where name_en_by_id maps + id(entry dict) -> matched lectio name.en, and report_lines is the + divergence log for the provenance header.""" + by_date = {} + for le in lectio_entries: + d = le.get("date", "") + m = re.match(r"^(\d\d)-(\d\d)$", d) + if not m: + continue + key = (int(m.group(1)), int(m.group(2))) + by_date.setdefault(key, []).append(le) + + by_date_missal = {} + for e in entries: + by_date_missal.setdefault((e["month"], e["day"]), []).append(e) + + report = [] + name_en = {} + lectio_slug = {} + agree_rank = 0 + total_matched = 0 + matched_lectio_ids = set() + + for key, missal_list in sorted(by_date_missal.items()): + lectio_list = by_date.get(key, []) + if len(missal_list) == 1 and len(lectio_list) == 1: + pairs = [(missal_list[0], lectio_list[0])] + else: + pairs = [] + remaining = list(lectio_list) + for me in missal_list: + if not remaining: + break + mwords = significant_words(me["latin"]) + best = max(remaining, key=lambda le: len(mwords & significant_words(le.get("name.la", "")))) + pairs.append((me, best)) + remaining.remove(best) + for me, le in pairs: + total_matched += 1 + matched_lectio_ids.add(id(le)) + name_en[id(me)] = le.get("name.en") + lectio_slug[id(me)] = le.get("slug") + missal_rank = GRADE_TO_RANK[me["grade"]] + lectio_rank = LECTIO_RANK_TO_VOCAB.get(le.get("rank", ""), "?") + if missal_rank == lectio_rank: + agree_rank += 1 + else: + report.append( + f" {key[0]:02d}-{key[1]:02d} RANK colitur={missal_rank} lectio={lectio_rank} " + f"[{me['latin']!r} vs {le.get('name.la','')!r}]" + ) + missal_colour = classify_colour(key[0], key[1], me["latin"]) + lectio_colour = (le.get("colour", "") or "").capitalize() + if missal_colour != lectio_colour: + report.append( + f" {key[0]:02d}-{key[1]:02d} COLOUR colitur={missal_colour} lectio={lectio_colour} " + f"[{me['latin']!r}]" + ) + if not lectio_list: + for me in missal_list: + report.append(f" {key[0]:02d}-{key[1]:02d} MISSAL-ONLY {me['latin']!r} (grade={me['grade']})") + elif len(lectio_list) > len(missal_list): + # A shared date where lectio carries MORE entries than the + # Missal transcription found -- the pairing loop above only + # consumes len(missal_list) of them, so the rest would + # otherwise vanish silently. Surfaced explicitly. + for le in lectio_list: + if id(le) not in matched_lectio_ids: + report.append( + f" {key[0]:02d}-{key[1]:02d} LECTIO-EXTRA (same date, uncovered by Missal pairing) " + f"{le.get('name.la','')!r} rank={le.get('rank')} slug={le.get('slug')}" + ) + + missal_dates = set(by_date_missal) + for key, lectio_list in sorted(by_date.items()): + if key in SKIP_DATES: + continue + for le in lectio_list: + if id(le) not in matched_lectio_ids and key not in missal_dates: + report.append(f" {key[0]:02d}-{key[1]:02d} LECTIO-ONLY {le.get('name.la','')!r} " + f"rank={le.get('rank')} slug={le.get('slug')}") + + summary = [ + f"lectio cross-check: {total_matched} dates paired; rank agrees on {agree_rank}/{total_matched}.", + ] + return summary + report, name_en, lectio_slug + + +def render_sexp(entries, name_en, letterspace_count, skipped, meta): + lines = [] + lines.append(f"; data/of/calendar-2002.sexp -- OF (post-1970) General Roman Calendar,") + lines.append(f"; transcribed from the Missale Romanum, editio typica tertia (2002). The") + lines.append(f"; 2002 Missal is the AUTHORITY; this file is never edited afterwards --") + lines.append(f"; spec sec4.1. lectio's roman-calendar.ini is a cross-check only, never a") + lines.append(f"; source (see tools/extract_of_calendar.py's own header for the full") + lines.append(f"; source-discipline statement and every classification citation).") + lines.append(f";") + lines.append(f"; Generator: {TOOL_PATH} -- do not hand-edit; re-run against the same") + lines.append(f"; PDF (this file is pinned, so a re-run should reproduce it byte-for-byte)") + lines.append(f"; and diff before committing.") + lines.append(f";") + lines.append(f"; Source: {PDF_PATH}") + lines.append(f"; SHA-256: {meta['pdf_sha256']}") + lines.append(f"; Extracted lines (pdftotext -layout, 0-based): {meta['start']}-{meta['end']}") + lines.append(f"; Extraction date (UTC): {meta['extraction_date']}") + lines.append(f";") + lines.append(f"; {len(entries)} entries. {letterspace_count} entry needed letter-spacing repair") + lines.append(f"; (collapse_letterspacing's own mechanical pass, then hand-verified -- see") + lines.append(f"; the module docstring's EXTRACTION ARTIFACT note).") + lines.append(f";") + lines.append(f"; Excluded, deliberately (see module docstring for the full reasoning):") + for (m, d), why in sorted(skipped.items()): + lines.append(f"; {m:02d}-{d:02d}: {why}") + lines.append(f"; Movable universal solemnities (Baptism of the Lord, Holy Family,") + lines.append(f"; Trinity, Corpus Christi, Christ the King -- Temporal_of code; the") + lines.append(f"; Sacred Heart of Jesus and the Immaculate Heart of Mary -- present in") + lines.append(f"; the 2002 table but covered by NEITHER Temporal_of NOR this file, a") + lines.append(f"; genuine gap this task found and reports but does not fix).") + lines.append(f";") + for l in meta["cross_check_report"]: + lines.append(f"; {l}") + lines.append("") + + def esc(s): + return s.replace("\\", "\\\\").replace('"', '\\"') + + lines.append("((id of-universal) (name \"OF (2002) General Roman Calendar\")") + lines.append(" (entries") + lines.append(" (") + for idx, e in enumerate(entries): + la = normalize_latin(e["latin"]) + en = name_en.get(id(e)) + names_parts = [] + if en: + names_parts.append(f'(en "{esc(en)}")') + names_parts.append(f'(la "{esc(la)}")') + names_str = " ".join(names_parts) + rank = GRADE_TO_RANK[e["grade"]] + colour = classify_colour(e["month"], e["day"], e["latin"]) + subject = classify_subject(e["month"], e["day"], e["latin"]) + slug = e["slug"] + prefix = " " if idx > 0 else " " + lines.append(f"{prefix}((date (Fixed (month {e['month']}) (day {e['day']})))") + lines.append(f" (cel") + lines.append(f" ((slug {slug})") + lines.append(f" (names ({names_str}))") + lines.append(f" (rank {rank}) (status Feast) (colour {colour})") + lines.append(f" (subject {subject}) (citations ()) (layer of-universal))))") + lines.append(" )))") + return "\n".join(lines) + "\n" + + +def main(): + if len(sys.argv) > 1: + with open(sys.argv[1], encoding="utf-8") as f: + text = f.read() + else: + text = get_text(PDF_PATH) + + start, end, table_lines = bound_table(text) + entries, letterspace_count = parse_rows(table_lines) + + # Hand-verified correction for the one letter-spaced entry the + # mechanical pass cannot fully resolve (see module docstring). + for e in entries: + if (e["month"], e["day"]) == (2, 21) and "episcopiet" in e["latin"]: + e["latin"] = "S. Petri Damiani, episcopi et Ecclesiae doctoris" + + skipped = {} + kept = [] + for e in entries: + key = (e["month"], e["day"]) + if key in SKIP_DATES: + skipped[key] = SKIP_DATES[key] + continue + kept.append(e) + entries = kept + + # All Souls override (see module docstring). + for e in entries: + if (e["month"], e["day"]) == ALL_SOULS_OVERRIDE: + e["grade"] = "Sollemnitas" + + lectio_entries = parse_lectio_ini(LECTIO_INI) + cross_check_report, name_en, lectio_slug = cross_check(entries, lectio_entries) + + # Slugs: prefer lectio's OWN identifier when a confident match exists -- + # it is already a clean, conventional, human-authored identifier + # ("raymond-of-penyafort-priest") matching data/ef/sanctoral.sexp's own + # style, and reusing it is not a source-discipline violation: a slug is + # a stable ENGINEERING KEY, not liturgical content -- every substantive + # field (date, rank, colour, the `la` name) still comes from the Missal + # alone. Falls back to a mechanical slug built from the Latin title for + # any entry lectio has no match for (none, as it happens, in this run -- + # every one of the 206 entries paired -- but kept for robustness against + # a future lectio update dropping an entry). Collisions suffixed either + # way. + seen = {} + for e in entries: + candidate = lectio_slug.get(id(e)) + base = slugify(candidate) if candidate else title_slug(e["latin"], e["month"], e["day"]) + if not base: + base = title_slug(e["latin"], e["month"], e["day"]) + if base in seen: + seen[base] += 1 + e["slug"] = f"{base}-{seen[base]}" + else: + seen[base] = 1 + e["slug"] = base + + entries.sort(key=lambda e: e["slug"]) + + meta = { + "pdf_sha256": sha256_file(PDF_PATH), + "start": start, + "end": end, + "extraction_date": datetime.now(timezone.utc).strftime("%Y-%m-%d"), + "cross_check_report": cross_check_report, + } + + sys.stdout.write(render_sexp(entries, name_en, letterspace_count, skipped, meta)) + + print(f"[extract_of_calendar] {len(entries)} entries emitted, {letterspace_count} needed " + f"letter-spacing repair, {len(skipped)} dates excluded (temporal_of.ml coverage)", + file=sys.stderr) + for l in cross_check_report: + print(f"[extract_of_calendar] {l}", file=sys.stderr) + + +if __name__ == "__main__": + main() -- cgit v1.3