feat: dynamic field labels, optionally overriding field names per species

This commit is contained in:
Lewis Wynne 2026-04-07 18:17:41 +01:00
parent a0060ca4bb
commit a2b904811a
21 changed files with 322 additions and 73 deletions

View file

@ -0,0 +1,32 @@
import { describe, it, expect } from 'vitest';
import { parseSpecies } from './parse';
describe('parseSpecies', () => {
it('parses labels section into a record', () => {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<species id="tajara" name="Tajara">
<labels>
<label for="subspecies">Ethnicity</label>
<label for="skin-color">Fur Colour</label>
</labels>
<subspecies>
<entry id="hharar" name="Hharar">
<description>The typical Tajara.</description>
</entry>
</subspecies>
</species>`;
const result = parseSpecies(xml);
expect(result.labels).toEqual({
subspecies: 'Ethnicity',
'skin-color': 'Fur Colour'
});
});
it('returns empty labels when no labels section exists', () => {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<species id="unathi" name="Unathi">
</species>`;
const result = parseSpecies(xml);
expect(result.labels).toEqual({});
});
});

View file

@ -5,7 +5,7 @@ import type { Template, RecordDef, FieldDef, SelectOption } from '../types';
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
isArray: (name) => ['entry', 'ref', 'field', 'record', 'option', 'citizenship', 'language'].includes(name),
isArray: (name) => ['entry', 'ref', 'field', 'record', 'option', 'citizenship', 'language', 'label'].includes(name),
trimValues: true
});
@ -14,6 +14,15 @@ function extractRefs(container: any): string[] {
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 ?? [];
@ -22,7 +31,7 @@ export function parseSpecies(xml: string): SpeciesData {
id: root['@_id'],
name: root['@_name'],
description: root.description?.trim(),
subspeciesLabel: root['@_subspeciesLabel'],
labels: extractLabels(root.labels),
languages: extractRefs(root.languages),
citizenships: extractRefs(root.citizenships),
subspecies: subspecies.map((e: any) => ({

View file

@ -2,7 +2,7 @@ export interface SpeciesData {
id: string;
name: string;
description?: string;
subspeciesLabel: string;
labels: Record<string, string>;
subspecies: { id: string; name: string; description?: string }[];
languages: string[];
citizenships: string[];