character-records-generator/src/lib/data/parse.ts

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
};
}