Невидимые символы в TXT-расшифровке: как найти пробелы и форматирующие знаки через Python
Локальный отчёт Unicode с номером строки, кодовой точкой и контекстом. Проверка неразрывных пробелов, табуляции и знаков нулевой ширины без массового удаления.
Когда текст выглядит правильно, но работает иначе
После копирования расшифровки между редакторами поиск или разбиение на слова может вести себя неожиданно. Между словом и числом стоит не обычный пробел, внутри слова — знак нулевой ширины, перед фрагментом — форматирующий символ. Их трудно различить визуально, поэтому проверять нужно конкретные символы.
Невидимость не делает знак ошибкой. Неразрывный пробел может удерживать число с единицей на одной строке; форматирующие знаки участвуют в письме других языков и сложных последовательностях символов. Сначала сформулируйте проблему: поиск, перенос, импорт или сравнение, а затем оцените нужность знака в этой операции.
Разделите разные причины несовпадения
Обычный пробел U+0020, неразрывный U+00A0 и узкий неразрывный U+202F — разные символы. Знак U+200B может разделять последовательность без заметной ширины. В отчёте важны кодовая точка и место, а не предположение по виду строки.
Это отдельная задача от нормализации NFC и смешения латиницы с кириллицей. Не используйте одну массовую замену для всех трёх причин. Подтвердите точный знак в проблемном фрагменте.
Составьте локальный отчёт без изменения текста
Сохраните transcript.txt в UTF-8 и код в hidden_characters.py. Выполните python hidden_characters.py. Отдельный hidden-characters.csv создаётся в режиме x. Категория Cf — форматирующие символы, Zs — разделители-пробелы; обычный пробел исключён, табуляция отмечается отдельно.
Скрипт читает UTF-8 с возможной начальной BOM, поэтому сама начальная метка кодировки не попадёт в отчёт. Обычные переносы используются для адресации и не перечисляются. Это выбранный набор кандидатов, а не полный анализ всех управляющих и невидимых знаков Unicode. Номера строк и позиций начинаются с единицы.
import csv, unicodedata
from pathlib import Path
text = Path("transcript.txt").read_text(encoding="utf-8-sig")
with Path("hidden-characters.csv").open("x", encoding="utf-8-sig", newline="") as target:
writer = csv.writer(target)
writer.writerow(["line", "column", "codepoint", "name", "category", "context"])
for line_no, line in enumerate(text.splitlines(), 1):
for column, char in enumerate(line, 1):
category = unicodedata.category(char)
if category == "Cf" or (category == "Zs" and char != " ") or char == "\t":
context = line[max(0,column-16):column+15]
writer.writerow([line_no, column, f"U+{ord(char):04X}",
unicodedata.name(char, "UNNAMED"), category,
context.encode("unicode_escape").decode("ascii")])
print("Отчёт сохранён. Исходный текст не изменён")
Рассмотрите кандидаты по назначению
context записывается с экранированием unicode_escape: русские буквы тоже могут отображаться как последовательности \u.... Это диагностическое представление, а не повреждённая кодировка исходника. Откройте соответствующую строку TXT, проверьте ближайшие слова и причину сбоя. Позиция относится к символам строки Python, а не байтам файла или ширине на экране.
Если неразрывный пробел нужен для оформления, оставьте его в итоговом документе и настройте операцию поиска отдельно. Если знак нулевой ширины ошибочно разрывает обычное русское слово, обоснованно исправьте рабочую копию и проверьте поиск снова. Форматирующие символы в других языках и эмодзи требуют понимания конкретной последовательности.
Текст записи можно подготовить через Транскрибатор. После копирования сохраняйте исходную версию и журнал подтверждённых замен. Отчёт содержит контекст реплик и требует того же доступа, что исходник. Пустой список означает отсутствие выбранных категорий, а не отсутствие всех текстовых проблем.
Учебный пример: разные назначения похожего промежутка
Кодовые точки помогают принять адресное решение, не меняя все пробелы.
| Символ | Возможное назначение | Проверка |
|---|---|---|
| U+00A0 | Неразрывный пробел | Нужна ли связь числа и единицы? |
| U+200B | Знак нулевой ширины | Почему стоит внутри токена? |
| U+0009 | Табуляция | Разделитель или случайный отступ? |
Не удаляйте категорию Cf целиком: в некоторых последовательностях её знаки участвуют в корректном отображении.
Лист проверки невидимого знака
Версия TXT: [имя] Строка и позиция: [числа] Кодовая точка: [U+....] Имя и категория: [значения] Сбой операции: [описание] Назначение знака: [проверка] Решение и основание: [оставить / заменить] Повторная проверка: [результат]
Частые вопросы
Невидимый знак всегда лишний?
Нет. Некоторые знаки обеспечивают переносы и корректное отображение.
Почему русский контекст выглядит как \u....?
Отчёт намеренно использует экранированное представление символов. Исходник не изменяется.
Скрипт проверяет все управляющие знаки?
Нет. Он отмечает Cf, необычные Zs и табуляцию по выбранному правилу.
Позиция — это номер байта?
Нет. Это номер символа в строке Python, начиная с единицы.