From 3f63340920cb9f1888d0710c5a58e95c958ad630 Mon Sep 17 00:00:00 2001 From: IceDragon Date: Sun, 1 Feb 2026 19:02:11 +0200 Subject: [PATCH] Fix appendages parsing to handle multiple appendages --- js/decoder.js | 79 +++++++++++++++++++++++++++++++++++++++++++++- js/parser.js | 45 ++++++++++++++++++-------- tests/test-data.js | 3 +- 3 files changed, 112 insertions(+), 15 deletions(-) diff --git a/js/decoder.js b/js/decoder.js index e44d2ef..ca07fe7 100644 --- a/js/decoder.js +++ b/js/decoder.js @@ -298,9 +298,86 @@ function decodeSimpleTag(label, tag, descriptions) { }; } +// Decode a single appendage +function decodeSingleAppendage(appendage) { + const baseDescription = TAG_DESCRIPTIONS.appendages.base[appendage.baseType]; + + if (!baseDescription) { + return appendage.baseType; + } + + // If no modifiers, return base description + if (!appendage.modifiers || appendage.modifiers.length === 0) { + return baseDescription; + } + + // Check for modifier + const modifier = appendage.modifiers[0]; + const modifierDescription = TAG_DESCRIPTIONS.appendages.modifiers[modifier]; + + if (modifierDescription) { + // Replace placeholders in modifier description + let value = modifierDescription; + + // Determine singular and plural forms + let singular = baseDescription.toLowerCase().replace(/^a pair of /, '').replace(/^a /, ''); + let plural = singular; + + // Handle specific cases + if (singular === 'legs' || singular === 'wings' || singular === 'arms') { + // already plural + } else if (singular === 'head') { + plural = 'heads'; + singular = 'head'; + } else if (singular === 'tail') { + plural = 'tails'; + singular = 'tail'; + } else if (singular.endsWith('limbs')) { + plural = singular; + singular = singular.replace(/s$/, ''); + } + + value = value.replace(/{type}/g, singular); + value = value.replace(/{plural}/g, plural.charAt(0).toUpperCase() + plural.slice(1)); + + return value; + } + + return baseDescription; +} + // Decode appendages function decodeAppendages(appendages) { - // Get base description + // Handle new multi-appendage structure + if (appendages.appendages && Array.isArray(appendages.appendages)) { + const decodedAppendages = appendages.appendages.map((app, index) => { + const decoded = decodeSingleAppendage(app); + // Lowercase the first letter for all items after the first + if (index > 0 && decoded) { + return decoded.charAt(0).toLowerCase() + decoded.slice(1); + } + return decoded; + }); + + // Combine with commas and "and" + let value; + if (decodedAppendages.length === 1) { + value = decodedAppendages[0]; + } else if (decodedAppendages.length === 2) { + value = decodedAppendages.join(' and '); + } else { + const lastAppendage = decodedAppendages.pop(); + value = decodedAppendages.join(', ') + ', and ' + lastAppendage; + } + + return { + label: 'Appendages', + value: value, + tag: appendages.raw + }; + } + + // Legacy support for old structure const baseDescription = TAG_DESCRIPTIONS.appendages.base[appendages.baseType]; if (!baseDescription) { diff --git a/js/parser.js b/js/parser.js index b2df281..b550a4f 100644 --- a/js/parser.js +++ b/js/parser.js @@ -486,23 +486,42 @@ function parseAppendages(token) { // Remove 'P' prefix let content = token.substring(1); - // Check for Pw' (wings as arms) special case - if (content.startsWith("w'")) { - const modifiers = content.substring(2); - return { - baseType: "w'", - modifiers: modifiers, - raw: token + const appendagesList = []; + let i = 0; + + while (i < content.length) { + let appendage = { + baseType: null, + modifiers: '' }; + + // Check for Pw' (wings as arms) special case + if (content.substring(i, i + 2) === "w'") { + appendage.baseType = "w'"; + i += 2; + } else { + // Get base type (single character) + appendage.baseType = content[i]; + i++; + } + + // Collect modifiers for this appendage (until we hit another letter that's a base type) + while (i < content.length) { + const char = content[i]; + // Check if this is a new appendage base type (a, f, h, k, l, p, t, v, w) + if ('afhklptvw'.includes(char.toLowerCase())) { + break; + } + // Otherwise it's a modifier + appendage.modifiers += char; + i++; + } + + appendagesList.push(appendage); } - // Get base type (first character after P) - const baseType = content[0]; - const modifiers = content.substring(1); - return { - baseType: baseType, - modifiers: modifiers, + appendages: appendagesList, raw: token }; } diff --git a/tests/test-data.js b/tests/test-data.js index b18b689..82de4fc 100644 --- a/tests/test-data.js +++ b/tests/test-data.js @@ -404,7 +404,8 @@ const TEST_CASES = { { code: 'Ph+', expected: 'One more head than normal - \'I got ahead in advertising!\'' }, { code: 'Pw-', expected: 'One less wing than normal - \'I have only one wing!\'' }, { code: 'Pl!', expected: 'Many legs - \'I am a millipede!\'' }, - { code: 'Pl^', expected: 'Legs end in webbed feet - \'Don\'t call me a frog!\'' } + { code: 'Pl^', expected: 'Legs end in webbed feet - \'Don\'t call me a frog!\'' }, + { code: 'Phvwalt', expected: 'A head, a pair of horns or spines on the head, a pair of wings, a pair of arms, a pair of legs, and a tail' } ] };