diff --git a/src/sources/gbfsStations.js b/src/sources/gbfsStations.js index 74f2efd23..dd4eff898 100644 --- a/src/sources/gbfsStations.js +++ b/src/sources/gbfsStations.js @@ -51,15 +51,25 @@ export function extractStationsArray(payload) { return []; } +/** + * Station name text as a reader expects it. JCDecaux's Spanish contracts + * (Valenbisi, Sevici) prefix every name with "_" and Valenbisi joins words + * with it ("_PLAZA_MUSICO_LOPEZ_CHAVARRI"), so an underscore reads as a word + * break. Other spacing is the operator's own and stays as published. + */ +function tidyStationName(text) { + return text.replace(/\s*_+\s*/g, ' ').trim(); +} + /** Read legacy text or a GBFS v3 Localized String, preferring usable English text. */ function stationName(value) { - if (typeof value === 'string') return value.trim(); + if (typeof value === 'string') return tidyStationName(value); if (!Array.isArray(value)) return ''; let fallback = ''; for (const translation of value) { if (typeof translation?.text !== 'string') continue; - const text = translation.text.trim(); + const text = tidyStationName(translation.text); if (!text) continue; if (!fallback) fallback = text; if ( diff --git a/src/sources/gbfsStations.test.mjs b/src/sources/gbfsStations.test.mjs index 8c60da90c..6f12db258 100644 --- a/src/sources/gbfsStations.test.mjs +++ b/src/sources/gbfsStations.test.mjs @@ -22,6 +22,42 @@ test('GBFS v2 station names remain trimmed strings', () => { ); }); +test('underscores in station names read as word breaks', () => { + // As published by JCDecaux's Spanish contracts (checked 2026-10-17). + for (const [published, shown] of [ + ['_PLAZA_MUSICO_LOPEZ_CHAVARRI', 'PLAZA MUSICO LOPEZ CHAVARRI'], + ['_GLORIETA OLIMPICA', 'GLORIETA OLIMPICA'], + ['_CAMPAMENTO_81', 'CAMPAMENTO 81'], + ['Plaza__Mayor_ ', 'Plaza Mayor'], + ]) { + assert.equal(parseName({ name: published }), shown); + } +}); + +test('spacing without underscores stays as the operator published it', () => { + assert.equal(parseName({ name: 'C/ NÀPOLS, 82' }), 'C/ NÀPOLS, 82'); +}); + +test('a name of only underscores falls back to short_name, then to empty', () => { + assert.equal(parseName({ name: '_', short_name: '_S_1_' }), 'S 1'); + assert.equal(parseName({ name: '__', short_name: '_' }), ''); +}); + +test('underscores are read the same in GBFS v3 localized names', () => { + assert.equal( + parseName( + { + name: [ + { language: 'es', text: '_' }, + { language: 'en', text: '_COLON_I' }, + ], + }, + '3.0', + ), + 'COLON I', + ); +}); + test('GBFS v3 station names prefer English over the first translation', () => { assert.equal( parseName(