131 lines
3.5 KiB
TypeScript
131 lines
3.5 KiB
TypeScript
import { XMLParser } from 'fast-xml-parser';
|
|
import type { SpeciesData, CitizenshipData, LanguageData } from './types';
|
|
import type { Template, RecordDef, FieldDef, SelectOption } from '../types';
|
|
|
|
const parser = new XMLParser({
|
|
ignoreAttributes: false,
|
|
attributeNamePrefix: '@_',
|
|
isArray: (name) => ['entry', 'ref', 'field', 'record', 'option', 'citizenship', 'language', 'label'].includes(name),
|
|
trimValues: true
|
|
});
|
|
|
|
function extractRefs(container: any): string[] {
|
|
if (!container?.ref) return [];
|
|
return container.ref.map((r: any) => r['@_id']);
|
|
}
|
|
|
|
function extractLabels(container: any): Record<string, string> {
|
|
if (!container?.label) return {};
|
|
const labels: Record<string, string> = {};
|
|
for (const l of container.label) {
|
|
labels[l['@_for']] = typeof l === 'string' ? l : l['#text'];
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
export function parseSpecies(xml: string): SpeciesData {
|
|
const root = parser.parse(xml).species;
|
|
const subspecies = root.subspecies?.entry ?? [];
|
|
|
|
return {
|
|
id: root['@_id'],
|
|
name: root['@_name'],
|
|
description: root.description?.trim(),
|
|
labels: extractLabels(root.labels),
|
|
languages: extractRefs(root.languages),
|
|
citizenships: extractRefs(root.citizenships),
|
|
subspecies: subspecies.map((e: any) => ({
|
|
id: e['@_id'],
|
|
name: e['@_name'],
|
|
description: e.description?.trim()
|
|
}))
|
|
};
|
|
}
|
|
|
|
export function parseCitizenships(xml: string): CitizenshipData[] {
|
|
const root = parser.parse(xml).citizenships;
|
|
return root.citizenship.map((c: any) => ({
|
|
id: c['@_id'],
|
|
name: c['@_name'],
|
|
description: c.description?.trim()
|
|
}));
|
|
}
|
|
|
|
export function parseLanguages(xml: string): LanguageData[] {
|
|
const root = parser.parse(xml).languages;
|
|
return root.language.map((l: any) => ({
|
|
id: l['@_id'],
|
|
name: l['@_name'],
|
|
description: l.description?.trim()
|
|
}));
|
|
}
|
|
|
|
function parseOptions(field: any): SelectOption[] {
|
|
if (!field.option) return [];
|
|
return field.option.map((o: any) => ({
|
|
value: o['@_value'],
|
|
label: o['@_label']
|
|
}));
|
|
}
|
|
|
|
function parseField(raw: any): FieldDef {
|
|
const base = {
|
|
label: raw['@_label'],
|
|
...(raw['@_required'] === 'true' && { required: true }),
|
|
...(raw['@_from'] && { from: raw['@_from'] })
|
|
};
|
|
const type = raw['@_type'];
|
|
|
|
switch (type) {
|
|
case 'name':
|
|
case 'text':
|
|
case 'textarea':
|
|
case 'date':
|
|
return { ...base, type, placeholder: raw['@_placeholder'] };
|
|
case 'list':
|
|
case 'height':
|
|
case 'weight':
|
|
case 'species':
|
|
case 'subspecies':
|
|
case 'citizenship':
|
|
case 'languages':
|
|
return { ...base, type };
|
|
case 'separator':
|
|
return { type: 'separator', label: raw['@_label'] ?? '' };
|
|
case 'number':
|
|
return {
|
|
...base,
|
|
type,
|
|
min: raw['@_min'] != null ? Number(raw['@_min']) : undefined,
|
|
max: raw['@_max'] != null ? Number(raw['@_max']) : undefined,
|
|
unit: raw['@_unit']
|
|
};
|
|
case 'select':
|
|
case 'multi-select':
|
|
case 'checkbox':
|
|
return { ...base, type, options: parseOptions(raw) };
|
|
default:
|
|
return { ...base, type: 'text' };
|
|
}
|
|
}
|
|
|
|
export function parseTemplate(xml: string, id: string): Template {
|
|
const root = parser.parse(xml).template;
|
|
|
|
const records: RecordDef[] = root.record.map((r: any) => ({
|
|
type: r['@_type'],
|
|
preamble: r.preamble?.trim(),
|
|
note: r.note?.trim(),
|
|
fields: r.field.map(parseField)
|
|
}));
|
|
|
|
const speciesAttr = root['@_species'];
|
|
return {
|
|
id,
|
|
name: root['@_name'],
|
|
description: root.description ?? '',
|
|
schemaVersion: Number(root['@_schemaVersion'] ?? 1),
|
|
...(speciesAttr && { species: speciesAttr.split(',').map((s: string) => s.trim()) }),
|
|
records
|
|
};
|
|
}
|