diff --git a/dokogen/importer.py b/dokogen/importer.py index b166d30..f28e5f6 100644 --- a/dokogen/importer.py +++ b/dokogen/importer.py @@ -15,6 +15,62 @@ except ImportError: from .import_settings import load_settings, parse_cell_ref, cell_value +# Известные аббревиатуры, которые сохраняются КАПСОМ при нормализации +_CAPS_ABBR = { + 'АРМ', 'СКУД', 'СЗИ', 'СКЗИ', 'СОВ', 'ЛВС', 'ИНН', 'КПП', 'ОГРН', 'ЦОД', + 'МБУ', 'МКУ', 'ООО', 'СНИЛС', 'СБИС', 'ЕМСЭД', 'ФИО', 'ИСПДН', 'ККТ', +} + + +def _normalize_caps(text: str) -> str: + """Приводит КАПС-текст к нормальному регистру по словам. + + Сохраняет: + - аббревиатуры из белого списка (АРМ, СКУД, ООО, ИНН и т.п.) + - инициалы и короткие служебные слова (И., Г., УЛ., №1) + - числа и номера (350000, №1) + - слова, уже содержащие строчные буквы + Длинные слова целиком заглавными (ДИРЕКТОР, КРАСНОДАР, ПЕТРОВ) → «Директор», «Краснодар». + """ + words = text.split() + result = [] + for w in words: + # отделяем пунктуацию по краям слова + pre = '' + post = '' + core = w + while core and not core[0].isalnum(): + pre += core[0] + core = core[1:] + while core and not core[-1].isalnum(): + post = core[-1] + post + core = core[:-1] + if not core: + result.append(w) + continue + # в слове уже есть строчные буквы — не трогаем + if any('а' <= c <= 'я' or c == 'ё' for c in core): + result.append(w) + continue + # нет заглавных кириллических букв (числа, латиница и т.п.) — не трогаем + if not any('А' <= c <= 'Я' or c == 'Ё' for c in core): + result.append(w) + continue + letters = [c for c in core if c.isalpha()] + # инициалы и служебные слова (1-2 буквы: И., Г., УЛ., ООО — но ООО в белом списке) + if len(letters) <= 2: + result.append(w) + continue + # известная аббревиатура — сохраняем как есть + if core.upper() in _CAPS_ABBR: + result.append(w) + continue + # обычное длинное слово: первая заглавная, остальные строчные + normalized = core[0].upper() + core[1:].lower() + result.append(pre + normalized + post) + return ' '.join(result) + + def clean_value(val): """Очистка и нормализация значения ячейки Excel.""" if val is None: @@ -33,20 +89,11 @@ def clean_value(val): text = str(val).strip() text = text.replace('\n', ' ').replace('\r', ' ') text = ' '.join(text.split()) - # Приведение регистра: если текст содержит только заглавные буквы РУ - # НО не трогаем аббревиатуры/номера (АРМ №1, ИС, ЕМСЭД, СКУД и т.п.) - if len(text) > 4 and any('А' <= c <= 'Я' or c == 'Ё' for c in text): + # Если весь текст КАПСОМ (нет строчных букв) — нормализуем по словам + if len(text) > 2 and any('А' <= c <= 'Я' or c == 'Ё' for c in text): has_lower = any('а' <= c <= 'я' or c == 'ё' for c in text) - has_digit_or_num = bool(re.search(r'[0-9№«»"\']', text)) - if not has_lower and not has_digit_or_num: - words = text.split() - # если есть короткая аббревиатура целиком заглавными — не трогаем - has_abbr = any(len(w) <= 4 and w.isupper() and any('А' <= c <= 'Я' for c in w) for w in words) - if not has_abbr: - if len(words) >= 3: - text = text.title() - else: - text = text.capitalize() + if not has_lower: + text = _normalize_caps(text) return text