diff --git a/web-app/django/VIM/apps/instruments/utils/validators.py b/web-app/django/VIM/apps/instruments/utils/validators.py index ec960f89..130e4685 100644 --- a/web-app/django/VIM/apps/instruments/utils/validators.py +++ b/web-app/django/VIM/apps/instruments/utils/validators.py @@ -135,9 +135,9 @@ def validate_hbs_classification(hbs_class: str) -> bool: Validate Hornbostel-Sachs classification format. Valid formats: - - Two digits minimum (e.g., "11", "21") - - With optional sub-classifications (e.g., "21.2", "311.121") - - First digit must be 1-5, second digit 0-9 + - At least 1 character, only digits (1-9), dot, dash, and plus permitted + - First character must be 1-5 + - If there is a second character, it must be 1-5 Args: hbs_class: Hornbostel-Sachs classification string to validate @@ -147,16 +147,25 @@ def validate_hbs_classification(hbs_class: str) -> bool: Example: >>> validate_hbs_classification("11") # True - >>> validate_hbs_classification("21.2") # True - >>> validate_hbs_classification("311.121") # True - >>> validate_hbs_classification("6") # False (needs 2 digits) - >>> validate_hbs_classification("11x") # False (invalid format) + >>> validate_hbs_classification("21.2+2") # True + >>> validate_hbs_classification("6") # False (first char not 1-5) + >>> validate_hbs_classification("11x") # False (invalid char) """ if not hbs_class: return False - # Pattern: one digit (1-5), followed by another digit, optionally followed by more .digits - pattern = r"^[1-5][0-9](\.[0-9]+)*$" - return bool(re.match(pattern, hbs_class)) and len(hbs_class) >= 2 + # Only digits (1-9), dot, dash, plus permitted + if not re.match(r"^[1-9.\-+]+$", hbs_class): + return False + # First character must be 1-5 + first_char = hbs_class[0] + if not re.match(r"[1-5]", first_char): + return False + # If there is a second character, it must be 1-5 + if len(hbs_class) > 1: + second_char = hbs_class[1] + if not re.match(r"[1-5]", second_char): + return False + return True def validate_image_file(image_file) -> Tuple[bool, str]: diff --git a/web-app/frontend/src/instruments/helpers/CreateInstrumentValidator.ts b/web-app/frontend/src/instruments/helpers/CreateInstrumentValidator.ts index 497580a4..6534562b 100644 --- a/web-app/frontend/src/instruments/helpers/CreateInstrumentValidator.ts +++ b/web-app/frontend/src/instruments/helpers/CreateInstrumentValidator.ts @@ -1,5 +1,6 @@ import { WikidataLanguage, ValidationResult, NameEntry } from '../Types'; import { DatabaseService } from './DatabaseService'; +import { isValidHBSClass } from './NameValidator'; export interface CreateInstrumentData { entries: NameEntry[]; @@ -41,13 +42,11 @@ export class CreateInstrumentValidator { }; } - // Pattern: starts with 1-5, followed by another digit, optionally more .digits - const pattern = /^[1-5][0-9](\.[0-9]+)*$/; - if (!pattern.test(value.trim())) { + if (!isValidHBSClass(value.trim())) { return { isValid: false, message: - 'Invalid format. Enter at least 2 digits (e.g., "11", "21.2", "311.121")', + 'Invalid format. Enter a valid HBS class (e.g., "11", "21.2", "311.121"). Must start with 1-5; only digits, dot, dash, and plus are permitted', type: 'error', }; } diff --git a/web-app/frontend/src/instruments/helpers/NameValidator.ts b/web-app/frontend/src/instruments/helpers/NameValidator.ts index 7a81e3c3..88e9e6d1 100644 --- a/web-app/frontend/src/instruments/helpers/NameValidator.ts +++ b/web-app/frontend/src/instruments/helpers/NameValidator.ts @@ -1,6 +1,21 @@ import { WikidataLanguage, ValidationResult } from '../Types'; import { WikidataService } from './WikidataService'; +// Helper function to validate HBS class input +export function isValidHBSClass(input: string): boolean { + // Only allow digits, dot, dash, and plus + if (!/^[1-9.\-+]+$/.test(input)) return false; + // The first and second characters must be a digit between 1 and 5 + if (input.length < 1) return false; + const firstChar = input.charAt(0); + if (!/[1-5]/.test(firstChar)) return false; + if (input.length > 1) { + const secondChar = input.charAt(1); + if (!/[1-5]/.test(secondChar)) return false; + } + return true; +} + export class NameValidator { private languages: WikidataLanguage[]; @@ -91,6 +106,40 @@ export class NameValidator { }; } + /** + * Validates Hornbostel-Sachs class input (for Add Class form) + */ + validateHBSClassInput(hbsClassInput: string): ValidationResult { + const input = hbsClassInput?.trim(); + if (!input) { + return { + isValid: false, + message: 'Please input an HBS number.', + type: 'error', + }; + } + if (input.length > 50) { + return { + isValid: false, + message: 'HBS number must be at most 50 characters.', + type: 'error', + }; + } + if (!isValidHBSClass(input)) { + return { + isValid: false, + message: + 'Only use digits (first two characters 1–5), and the symbols ".", "-", "+".', + type: 'error', + }; + } + return { + isValid: true, + message: `Proposed Classification: ${input}`, + type: 'success', + }; + } + /** * Validates that we have a valid name ID for deletion */