initial commit

This commit is contained in:
catarrh committed 2026-07-23 02:41:15 +03:00
commit 58b379d6e3
47 files changed
+8664

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
/**
* Convert calendar XML data (triodion, pentecostarion, menaion, commemorations) to JSON.
*/
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { XMLParser } from 'fast-xml-parser';
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
allowBooleanAttributes: true,
parseTagValue: false,
trimValues: true,
});
function parseFile(path: string): Record<string, unknown> | null {
if (!existsSync(path)) return null;
const xml = readFileSync(path, 'utf-8');
try {
return parser.parse(xml);
} catch {
return null;
}
}
function extractDayData(data: Record<string, unknown> | null): unknown[] {
if (!data) return [];
const result: unknown[] = [];
// Handle <DAY><SAINT .../></DAY> structure (menaion per-language files)
if (data['DAY']) {
const day = data['DAY'];
if (typeof day === 'object' && day !== null) {
const saints = day['SAINT'];
if (saints) {
const saintArr = Array.isArray(saints) ? saints : [saints];
for (const saint of saintArr) {
if (typeof saint === 'object' && saint !== null) {
// fast-xml-parser puts attributes at top level with @_ prefix
const sId = saint['@_SId'] || saint['@_SID'] || '';
const cId = saint['@_CId'] || saint['@_CID'] || '';
if (cId) {
result.push({ SId: sId, CId: cId });
}
}
}
}
}
}
// Handle <LANGUAGE>...</LANGUAGE> structure (triodion/pentecostarion shared files)
if (data['LANGUAGE']) {
const lang = data['LANGUAGE'];
if (typeof lang === 'object' && lang !== null) {
for (const [key, value] of Object.entries(lang as Record<string, unknown>)) {
if (key === '@_attributes') continue;
if (typeof value === 'object' && value !== null) {
const attrs = (value as Record<string, unknown>)['@_attributes'] as Record<string, string> | undefined;
if (attrs && Object.keys(attrs).length > 0) {
result.push(attrs);
}
}
}
}
}
return result;
}
function convertCommemorationFile(data: Record<string, unknown>): Record<string, unknown> | null {
if (!data) return null;
const comm = data['COMMEMORATION'];
if (!comm || typeof comm !== 'object') return null;
const result: Record<string, unknown> = {};
function extractAttrs(obj: Record<string, unknown>): Record<string, unknown> {
const attrs: Record<string, unknown> = {};
for (const [k, v] of Object.entries(obj)) {
if (k.startsWith('@_')) {
attrs[k.slice(2)] = v; // strip @_ prefix
}
}
return attrs;
}
function processElement(obj: Record<string, unknown>): Record<string, unknown> {
const attrs = extractAttrs(obj);
const text = obj['#text'];
const out = { ...attrs };
if (typeof text === 'string' && text.trim()) {
out.text = text.trim();
}
return out;
}
for (const [key, value] of Object.entries(comm as Record<string, unknown>)) {
if (key.startsWith('@_')) continue; // skip root attributes
if (typeof value !== 'object' || value === null) continue;
if (Array.isArray(value)) {
result[key] = value.map(item =>
typeof item === 'object' && item !== null ? processElement(item as Record<string, unknown>) : item
);
} else if (typeof value === 'object') {
result[key] = processElement(value as Record<string, unknown>);
}
}
return Object.keys(result).length > 0 ? result : null;
}
export async function convertCalendar(SRC: string, OUT: string) {
console.log('Converting calendar data...');
// --- Triodion (shared, language-agnostic) ---
const triodionDir = join(SRC, 'languages', 'xml', 'triodion');
if (existsSync(triodionDir)) {
const outDir = join(OUT, 'shared', 'triodion');
mkdirSync(outDir, { recursive: true });
const files = readdirSync(triodionDir).filter(f => f.endsWith('.xml')).sort();
for (const file of files) {
const data = parseFile(join(triodionDir, file));
const dayData = extractDayData(data);
writeFileSync(join(outDir, file.replace('.xml', '.json')), JSON.stringify(dayData));
}
console.log(` triodion: ${files.length} files`);
}
// --- Pentecostarion (shared, language-agnostic) ---
const pentecostarionDir = join(SRC, 'languages', 'xml', 'pentecostarion');
if (existsSync(pentecostarionDir)) {
const outDir = join(OUT, 'shared', 'pentecostarion');
mkdirSync(outDir, { recursive: true });
const files = readdirSync(pentecostarionDir).filter(f => f.endsWith('.xml')).sort();
for (const file of files) {
const data = parseFile(join(pentecostarionDir, file));
const dayData = extractDayData(data);
writeFileSync(join(outDir, file.replace('.xml', '.json')), JSON.stringify(dayData));
}
console.log(` pentecostarion: ${files.length} files`);
}
// --- Commemorations (shared, indexed by CId) ---
const commBase = join(SRC, 'languages', 'xml', 'Commemorations');
if (existsSync(commBase)) {
const outDir = join(OUT, 'shared', 'commemorations');
mkdirSync(outDir, { recursive: true });
let count = 0;
const subdirs = readdirSync(commBase);
for (const sub of subdirs) {
const subDir = join(commBase, sub);
if (!existsSync(subDir)) continue;
const files = readdirSync(subDir).filter(f => f.endsWith('.xml'));
for (const file of files) {
const data = parseFile(join(subDir, file));
const commData = convertCommemorationFile(data);
if (commData) {
const cid = file.replace('.xml', '');
writeFileSync(join(outDir, `${cid}.json`), JSON.stringify(commData));
count++;
}
}
}
console.log(` commemorations: ${count} files`);
}
// --- Menaion (per-language monthly day files) ---
const MENAION_SOURCES = [
{ id: 'en', prefix: 'en' },
{ id: 'cu', prefix: 'cu' },
{ id: 'el', prefix: 'el' },
{ id: 'fr', prefix: 'fr' },
{ id: 'zh', prefix: 'zh' },
];
for (const { id, prefix } of MENAION_SOURCES) {
const xmlDir = join(SRC, 'languages', prefix, 'xml');
if (!existsSync(xmlDir)) continue;
const outDir = join(OUT, id, 'menaion');
mkdirSync(outDir, { recursive: true });
let count = 0;
const months = readdirSync(xmlDir).filter(f => /^\d{2}$/.test(f));
for (const month of months) {
const monthDir = join(xmlDir, month);
if (!existsSync(monthDir)) continue;
const days = readdirSync(monthDir).filter(f => f.endsWith('.xml'));
for (const day of days) {
const data = parseFile(join(monthDir, day));
const dayData = extractDayData(data);
if (dayData) {
const mm = month;
const dd = day.replace('.xml', '');
writeFileSync(join(outDir, `${mm}-${dd}.json`), JSON.stringify(dayData));
count++;
}
}
}
console.log(` ${id}: ${count} menaion day files`);
// Build menaion bundle (all days in one file for fast loading)
if (count > 0) {
const bundle: Record<string, unknown> = {};
const files = readdirSync(outDir).filter(f => f.endsWith('.json'));
for (const f of files) {
try {
bundle[f.replace('.json', '')] = JSON.parse(readFileSync(join(outDir, f), 'utf-8'));
} catch {}
}
writeFileSync(join(outDir, '..', 'menaion-bundle.json'), JSON.stringify(bundle));
}
}
}