
Предыстория
Моя команда занимается разработкой медицинского ПО: приложения для передачи направлений пациентов в системе здравоохранения Австралии.
Принцип заключается в следующем: если терапевту нужно направить пациента для предоставления вторичных услуг здравоохранения, например, в стационар или в специализированную клинику, он должен написать направление с информацией о пациенте, его истории и причине направления. В прошлом эти операции выполнялись по факсу; это явно устаревшая технология, к тому же многие направления отклонялись, потому что в них отсутствовала часть необходимой информации.
Система электронных направлений удобна для всех. Когда врач создаёт новое направление, мы автоматически извлекаем информацию из PMS (patient management software, системы управления пациентами, которой владеет наша компания) с данными пациента, этнической принадлежностью, индексом массы тела, принимаемыми лекарствами, медицинской историей и всем остальным, что необходимо для направления. В бланке направления есть валидируемая «форма специализации» с полями, относящимися к конкретной услуге, для которой выписывается направление.
Благодаря этому в направлении содержится вся необходимая информация. И, разумеется, в форме также есть большое текстовое поле для свободного текста (referral letter), в котором терапевт может объяснить, почему он решил выписать пациенту направление.
При отправке направления данные передаются в цифровом виде. Они преобразуются в один из нескольких форматов в зависимости от получателя. Некоторые получатели используют для приёма направлений наш продукт Referral Manager; в этом случае нам не нужно ничего преобразовывать, они просто получают доступ в веб-интерфейсе к тем данным, которые хранятся в базе данных. Однако обычно данные преобразуются в HL7 (старый текстовый формат файлов медицинской информации), CDA (XML-документ с отдельной таблицей стилей) или просто в PDF с человекочитаемой информацией. Благодаря этому данные совместимы со множеством различных электронных систем других компаний.
Также выполняется сохранение PDF-копии направления в PMS врача для ведения его собственной картотеки. Это позволяет ему просматривать всю историю пациента в PMS без необходимости поиска документов в куче приложений разных разработчиков.
Моя роль
Когда я работал в компании, моя должность находилась где-то между разработчиком и поддержкой. Я занимался обслуживанием системы и обеспечением её работоспособности.
Кого-то из читателей может удивить то, что подчистка старых файлов логов, чтобы на серверах не закончилось место, во многих компаниях-разработчиках ПО выполняется вручную. Можно подумать, что эту задачу легко автоматизировать, и что вы сами автоматизировали бы её на своих серверах. Да, это легко, если вы делаете это на собственных серверах, которые вы знаете, как свои пять пальцев, и где можете делать всё, что пожелаете. Однако в контексте нашей компании всё было немного сложнее.
Также я непосредственно работал с командой поддержки. Если у врачей всплывали какие-то баги в ПО или проблемы, слишком технические для решения службой техподдержки, их перенаправляли мне. Я изучал скриншоты и все логи ошибок, обсуждал с другими членами команды, правильное ли это поведение. Если это был баг, то я трассировал его по серверам, чтобы найти первопричину, и исследовал исходный код, чтобы понять, как возникло такое состояние. Далее я писал тикет Jira с подробным описанием бага и способом его устранения. Обычно устранением самих багов занимался не я, и меня это вполне устраивало.
Я программист, и время от времени вносил изменения в код, когда считал, что это оправдано, но обычно кодом занимались другие участники команды, а я оставался на своей маленькой роли.
Вы бы удивились, как много глупых, монотонных, шаблонных задач мне приходилось выполнять, чтобы всё работало без проблем. Мне нравилось, когда можно было при помощи интуиции докапываться до причин новых багов, но чаще всего ситуации повторялись многократно, и можно было найти вики-страницу с конкретным пошаговым списком решения проблемы. Приходилось много копипастить GUID направлений в шаблоны SQL.
Самый загадочный баг
Один из таких повторяющихся багов, требовавших ручного вмешательства, повторялся каждые 2-4 недели. Направление успешно отправлялось врачом и сохранялось в нашей базе данных, и на стороне врача всё выглядело нормально. Но при удалении направления из очереди и попытке отправить его получателю возникала ошибка преобразования в один из перечисленных выше форматов:
Illegal Character entity: expansion character (code 0x2) not a valid XML character
[Введён недопустимый символ: символ расширения (код 0x2) не является допустимым символом XML]
Что это вообще значит?
Когда я только пришёл на эту работу, для решения проблемы мне нужно было открыть соответствующую вики-страницу и выполнить инструкции с неё. По инструкциям нужно было запустить команду SQL, которая, по сути, находит и заменяет все вхождения \u0002 (последовательности из этих шести символов) пустой строкой, таким образом полностью удаляя недопустимый символ. Затем я добавлял направление обратно в очередь, и оно успешно обрабатывалось.
Чем дольше я работал с этими системами, тем глубже я их понимал. У меня возникало всё больше и больше вопросов:
Что это за символ 0x2?
Где он находится в данных?
Почему он в них, как он туда попадает? Что его создаёт?
Должен ли он там находиться? Плохо ли то, что мы решаем проблему, удаляя его?
Почему это вызывает проблемы?
Почему это происходит с такой регулярностью (каждые 2-4 недели)?
Много месяцев меня вполне устраивало то, что я просто должен решать проблему для каждого поломанного направления, по одному за раз; но каждый раз эти вопросы всё больше меня тяготили.
Когда эта загадка стала невыносимой, я решил потратить время на более подробное изучение системы, и каждая моя попытка добавляла в список всё более странные вопросы.
Поломанные данные?
Возможно, этот странный символ возникает при извлечении из PMS поломанных данных. Такое случается нередко; время от времени я диагностирую в продакшене баг, возникающий из-за содержащихся в PMS недопустимых данных, которые невозможно ввести, например, лекарство без названия или вычисление индекса массы тела без веса. Такие данные невозможно ввести с нуля, поэтому мы не смогли бы воссоздать эти проблемы в нашем тестовом окружении, и я, честно говоря, не знаю, как они появляются в продакшен-системах PMS. Возможно, клиенты обновились с более старой версии, где валидация выполнялась менее тщательно? Как бы то ни было, мы аккуратно брали эти данные из PMS (вне зависимости от их валидности) и вводили их в нашу форму. Когда врач создавал направление, оно пыталось передать плохие данные, которые не пропускали наши валидации, из-за чего направление невозможно было передать в бэкенд. Возможно, этот символ 0x2 появлялся из-за загрузки поломанных данных?
Я внимательно изучил SQL-скрипт, удалявший эти \u0002, чтобы понять, какое поле он редактировал. Так бы я мог разобраться, что искать. Выяснилось, что он удалял все вхождения в ответе на форму специализации. Это означало, что в полях формы специализации, в том числе и тех, которые автоматически подтягивались из PMS, мог оказаться этот символ. Неплохо для начала.
Я открыл данные направления, чтобы понять, в каком поле формы специализации они встречаются, и у меня появилось ещё больше вопросов. Это было поле referral letter, информацию в которое вводит врач, оно не заполняется автоматически PMS. То есть символ 0x2 находится там, потому что его ввёл врач! Что? Почему?
Что это вообще такое?
Наверно, стоит объяснить, что это за символ 0x2, чтобы вы поняли, почему ввод врачом этого символа крайне маловероятен.
Компьютеры хранят всё в байтах. Каждая буква текста этой статьи представлена в компьютере в виде числа. Например, латинская «A» — это 65, и так далее, вплоть до 127. Этого места достаточно для хранения всех букв, символов и чисел английского языка, и остаётся ещё немного свободных позиций. Числа от 31 и ниже не нужны для отображаемых на экране символов. При проектировании этой системы какие-то учёные мужи решили поместить в позиции до 31 «управляющие символы». Это символы, невидимые для читающего текст человека. Они сообщают компьютеру, как размещать идущий после них текст.
Во времена телетайпов эти символы были очень важны. При выводе телетайпом текста на бумагу, без управляющих символов в потоке текста он не будет знать, когда переместиться на следующую строку. Управляющий символ 0xD сообщает, что нужно переместить печатающую головку обратно влево, после чего 0xA сообщает, что нужно выполнить подачу бумаги на одну строку, чтобы печать началась со следующей строки. Есть даже символы наподобие 0x7, сообщающие, что нужно издать звуковой сигнал, но если попробовать использовать его сегодня, компьютер, вероятно, печально пискнет.
Большинство этих символов сегодня необязательно, потому что у компьютеров есть гораздо более совершенные способы создания структуры и форматирования текста. Пожалуй, единственный символ, который мы встречаем — это 0xA, начинающий новую строку текста, но само значение его изменилось: компьютеры достаточно умны, чтобы понимать, когда текст становится слишком длинным, и могут переносить его на следующую строку без прямой команды. Но вы всё равно можете нажать на Enter, чтобы вставить 0xA для принудительного создания новой строки.
Что же такое 0x2? Этот символ называется «начало текста». Есть также 0x3, «конец текста». Их можно использовать для ограничения определённых разделов текста. Это сложно объяснить без примера, но я не смог найти ни одного случая их применения в реальном мире, потому что, как уже было сказано, теперь у нас есть более совершенные способы форматирования, и 0x2 не используется. Медицинские технологии бывают довольно старыми; возможно, есть какая-то компьютерная система, до сих пор кодирующая данные подобным образом?
Если бы символ использовался именно так, то можно было бы ожидать встретить в referral letter соответствующий символ 0x3, обозначающий конец текста. Но его нет. В referral letter присутствует только 0x2. Очень странно.
Подведём итог: символ 0x2 — это «начало текста», но этот символ не передаёт своего семантического значения, не используется ни в одной из известных мне систем и даже не применяется в данном случае для обозначения раздела текста.
...Теперь вы можете понять, почему при исследовании этой проблемы у меня возникали всё более странные вопросы.
Почему каждые несколько недель?
Отчаянно пытаясь найти паттерн, я начал изучать сводку по всем направлениям, в которых возникала эта проблема. Я заметил, что эти направления обычно отправляли одни и те же врачи с одними и теми же пациентами. Это определённо указывало на наличие плохих данных в файле пациента в PMS. Но я уже исключил эту возможность, потому что символ встречался в referral letter.
Мне стало интересно, можно ли извлечь ещё какие-то сведения из этой сводки. Возможно, у некоторых врачей есть какое-то ПО или процессы, повышающие вероятность возникновения бага, и поэтому он не встречается у других врачей?
Я решил попробовать почитать текст referral letter с проблемными данными, и сразу же заметил что-то странное: в тексте присутствовали жёсткие переносы (hard wrap).
Обычно текст заполняет свободное пространство и переносится на следующую строку автоматически. Но в этом referral letter, в каждой строке встречался символ новой строки, как будто врач вводил текст в поле и нажимал на Enter, чтобы перейти на следующую строку.
Зачем он это делал? Я знаю, что многие врачи на «вы» с компьютерами; возможно, кто-то из них считает, что нужно контролировать строки самостоятельно, как на печатной машинке. Но ведь наверняка он замечал, даже случайно, что если написать слишком много текста, то он переносится на следующую строку?
Какой могла быть причина жёстких переносов в том тексте?
...Все мои исследования приводили лишь к новым вопросам.
Что означает наличие жёстких переносов?
Мысль о том, что одновременно так могли вести себя несколько слабо владеющих компьютером врачей, казалась мне маловероятной, но всё же... Я открыл ещё одно referral letter того же врача и прочитал его, ожидая увидеть те же самые паттерны нажатий на Enter, что могло бы подтвердить мою теорию.
Но я увидел точно такое же referral letter.
Тот же текст. Те же жёсткие переносы. Тот же недопустимый символ, который нужно было удалить.
Чтобы удостовериться, я выбрал произвольное валидное referral letter, и в его тексте не нашлось никаких символов новой строки, как и ожидалось. Возможно ли, что жёсткие переносы как-то коррелируют с символом 0x2?
Я пригляделся к жёстким переносам внимательнее, и заметил нечто подобное:
I'm referring Liam here because of consistent history of slow speech development. Physically, his well♦being is healthy, so likely needs special tutoring, could be related to psychological issues.
(Этот пример я придумал сам. Он не скопирован и не воспроизведён из реального направления.)
Во всех строках есть жёсткие переносы, за исключением строки с символом 0x2 (я обозначил его символом ромба). Символ 0x2 встретился там, где я бы ожидал символ новой строки. Как будто символ новой строки был повреждён и заменён чем-то недопустимым. Но как такое может происходить и почему это стабильно происходит во всех referral letter по конкретному пациенту, не влияя на остальную часть текста?
Я решил, что жёсткие переносы — лучшая моя зацепка, поэтому сосредоточился на анализе причин их возникновения. Возможно, врач сначала решил написать письмо в документе Word, а затем вставил его в форму, из-за чего и появились жёсткие переносы?... Нет, так как врач отправил несколько одинаковых писем, то, вероятно, скопипастил текст из какого-то более постоянного источника. Возможно, из файла PDF в медицинской карте пациента? Файлы PDF своеобразны, поэтому было бы логично, что при копировании PDF в концах строк добавляются символы новой строки. Я попробовал составить PDF в Adobe Acrobat, сохранить его и скопировать текст в «Блокнот». И я оказался прав! PDF добавил в текст жёсткие переносы в местах разрыва строк.
Из какого PDF врач мог скопировать текст? Так как письмо писалось врачом от первого лица и в нём излагались основания для направления пациента, единственный логичный вариант заключался в том, что он скопировал текст из referral letter. Врач создал направление, ввёл текст referral letter, отправил, PDF-копия referral letter сохранилась в его PMS, после чего он открыл PDF, скопировал текст и вставил его в новое referral letter!
Всё наконец-то начало обретать смысл. Я ощутил невероятный всплеск воодушевления, как будто через Матрицу заглянул в мозг врача и его глазами наблюдал то, что делал он.
Но как это связано с символом?
Исследовав несколько referral letter от других врачей, я заметил кое-что ещё. В местах, где встречался 0x2 вместо символа новой строки, похоже, строка должна была заканчиваться дефисом. В показанном выше примере «well-being» пишется с дефисом. Если это слово встречалось в конце строки, компьютер переносил бы его так:
development. Physically, his well- being is healthy, so likely needs
Дефис превращался в 0x2. Значит, если дефис встречается в конце строки PDF и переносится, то каким-то образом повреждается? Может, проблема в ПО, с помощью которого мы создаём отправляемые PDF, и в этом нишевом случае оно генерирует PDF неправильно?
Я попробовал воспроизвести действия врача. Открыл свою тестовую копию направлений, написал направление, в котором referral letter состояло из набора слов с дефисами, понадеявшись, что одно из них будет оказано разбито на разные строки при преобразовании в PDF. Мне понадобилось несколько попыток, но наконец удалось получить нужный PDF. Но при его просмотре в PMS он выглядел нормально, дефисы рендерились без ошибок.
Я создал новое направление и скопипастил текст из PDF в новое referral letter, поймав, наконец, требуемый текст. Одна из строк не имела жёсткого переноса. Посередине строки находился символ прямоугольника. А когда я отправил новое направление и посмотрел логи, то увидел ошибку:
Illegal Character entity: expansion character (code 0x2) not a valid XML character.
Но почему так происходит?
Наша библиотека записи PDF настолько поломана, что создавала PDF, в котором символ отображался правильно, но копировался странно? Наверно, это возможно. Я видел PDF книг с отсканированным и распознанным текстом, где при выделении и копировании копировался некорректный вывод OCR.
Я не знаю, как заниматься отладкой PDF, поэтому сделал то, что пришло в голову первым: открыл поломанный PDF в Firefox и при помощи devtools исследовал каждую строку текста. На строке с дефисом я изучил символы в конце, но они казались нормальными, и визуально, и в devtools. Хм. Не этого я ожидал. Чтобы проверить ещё раз, я снова попробовал скопировать текст из основного PDF, но на этот раз получил реальный дефис. Копирование PDF во второй раз не дало мне 0x2. Почему? Это ведь тот же PDF!
Я воспроизвёл свои шаги и снова скопировал из PDF, отправленного PMS. В буфере обмена оказался 0x2.
То есть в самом PDF всё нормально. Возможно, символ появляется из-за программ просмотра PDF?
Я попробовал скопировать этот текст во всех программах просмотра PDF, до которых смог дотянуться: Firefox, Chrome, Edge и Adobe Acrobat. (Я проверил и в Opera, но, разумеется, в ней используется та же программа, что и в Chrome. Удивительно, что в Edge она своя, хотя это в буквальном смысле Chrome.) В этих четырёх PDF-программах я наблюдал четыре различающихся поведения при копировании дефиса:
Копирование переноса строки и дефиса (отлично)
Копирование только дефиса (норм)
Ничего не копируется (хм...)
Копируется 0x2 (что???)
При копировании дефиса в конце строки с переносом текста в программе просмотра PDF Microsoft Edge в буфер обмена попадает 0x2. В большинстве Windows-систем эта программа используется для открытия файлов PDF по умолчанию. PMS использует PDF-программу Edge.
После этого я изложил свои открытия в тикете Jira и отправил его команде разработчиков. Теперь, когда мы знали, что 0x2 не имеет никакого смысла и что мы не можем контролировать его появление в текстовом поле, они предложили автоматически удалять 0x2 отовсюду. Я настоял, что код нужно изменить так, чтобы 0x2 заменялся на дефис.
Позже была выпущена новая версия, и проблема больше не возникала. Я решил эту загадку. Это была самая дурацкая кроличья нора, в которую я когда-либо нырял, и самый интересный баг, который я устранял на работе.
Комментарии (55)
Dusty77
06.08.2025 14:04Там наверное вообще фильтрации входных данных нет. Хотя да, это же просто какое-то медицинское приложение.
qiper
06.08.2025 14:04Компьютеры хранят всё в байтах. Каждая буква текста этой статьи представлена в компьютере в виде числа. Например, латинская «A» — это 65, и так далее, вплоть до 127. Этого места достаточно для хранения всех букв, символов и чисел английского языка, и остаётся ещё немного свободных позиций. Числа от 31 и ниже не нужны для отображаемых на экране символов. При проектировании этой системы какие-то учёные мужи решили поместить в позиции до 31 «управляющие символы». Это символы, невидимые для читающего текст человека. Они сообщают компьютеру, как размещать идущий после них текст.
Уровень статей для Хабра 2025 года :(
Aggle
06.08.2025 14:04Увы. А Волга впадает в Каспийское море... (хотя, сейчас это уже далеко не для всех очевидный факт).
Wesha
06.08.2025 14:04(хотя, сейчас это уже далеко не для всех очевидный факт).
Тот факт, что запятая здесь не нужна — это уже давно далеко не для всех очевидный факт...
Grey83
06.08.2025 14:04А Волга впадает в Каспийское море
докуда доедет - туда и впадает, если вовремя её не затормозить =)
iskatel-tut
06.08.2025 14:04Ну я, например, просто пропустил эти несколько абзацев, подумав: "возможно, кому-то, действительно, это надо объяснять" и всё) - кстати, конкретно код 02, естественно, я не помнил, в отличие от многих других
DrGluck07
06.08.2025 14:04Так это ж наверное перевод. Я тоже сначала триггернулся, но потом подумал, что из оригинала слов не выкинешь.
nektopme
06.08.2025 14:04Не Австралия, Дальний Восток РФ, г. Хабаровск, Судостроительный техникум, год, примерно 1998.
Привезли меня в технику починить странность - неизвестная мне самопальная программа под MS-DOS.
Работала нормально, но стала показывать русские символы кракозябрами.
Программу уже исследует другой спец, безуспешно.Чем запомнился именно этот случай?
Спец сидит за компом, я прошу разрешения запустить программу - русские буквы нормально.
Запускает тот мужик - кракозябры.
И так пару раз.Уже не помню нюансов, сделал в config.sys запуск драйвера клавиатуры с более жёсткими опциями и программа перестала баловаться.
Что объединяет историю топикстартера и мою - далеко от Москвы, про символы.
Wesha
06.08.2025 14:04Stanislavvv
06.08.2025 14:04Иногда надо было ещё и локаль поставить... Непомню, уже как опцию зовут...
Overphase
06.08.2025 14:04MS DOS 6.22
config.sysdevice=C:\dos\display.sys con=(ega,,1)
country=007,866,C:\dos\country.sys
autoexec.batmode con codepage prepare=((866) c:\dos\ega3.cpi)
mode con codepage select=866
keyb ru,,C:\dos\keybrd3.sys
Aggle
06.08.2025 14:04Понравилось. Захватывающе читающийся Bug Mystery.
Автору - респект за проявленные терпение, настойчивость и ответственность (за последнее - особенно). 99 % на его месте просто бы забили на проблему, ибо, во-первых, баг не такой частый, во-вторых - есть рабочий SQL-костыль. А автор всерьёз задумался, не приведёт ли использование этого костыля к другим проблемам (с другой стороны, если на решение этого вопроса было потрачено столько времени и сил - там других проблем вообще почти не было?).время от времени я диагностирую в продакшене баг, возникающий из-за содержащихся в PMS недопустимых данных, которые невозможно ввести, например, лекарство без названия или вычисление индекса массы тела без веса.
Не совсем понятно - там присутствуют недопустимые данные или их невозможно ввести (т. е. их там нет). Если присутствуют - то как насчёт data entry control? ИМТ без веса, кстати, просто выдаст 0, а лекарство без названия - NULL, безо всяких там 0x2.
aamonster
06.08.2025 14:04Автор очень странный. Первым делом делается санитайзинг или эскейпинг пользовательского ввода (это всё равно жёсткая необходимость и это уже решает проблему), а потом можно удовлетворять своё любопытство, выясняя, откуда там 0x02.
Не удивлюсь, если они там отфильтровали только 0x02, а если юзер скопипастит туда 0x03 – опять что-то навернётся. А то и вообще придёт маленький Бобби Тэйблс (xkcd#327).
Asterris
06.08.2025 14:04Одна из наиболее частых причин, по которой 0x02 или другие непечатаемые символы могут попасть в текстовый файл, — это копирование текста из программ с расширенным форматированием (например, Microsoft Word) и вставка его в обычный текстовый редактор или поле ввода. При таком копировании могут "захватываться" скрытые символы форматирования, которые затем отображаются как 0x02 или другие управляющие символы, если их кодировка совпадает.
Maxroy
06.08.2025 14:04Крупная компания, 2012 год. У одного инженера возникла проблема - внезапно его учетная запись постоянно блокировалась в Active Directory. Её разблокировали, как она тут же опять блокировалась.
Сперва думали кто-то ломает, нет, активности извне. Стали слушать трафик внутри - нормально все, нет активности. Уже идеи закончились.
Ответ нашелся весьма не ожиданно: этот инженер однажды работал за столом (там стоит системный блок), потом он переехал в другой кабинет, а потом сисадмин забрал монитор. Клавиатура осталась. Потом, на этот стол стали складывать бумаги, стопка росла, потом кто-то ее уронил и одна папка попала на кнопку Enter и придавила её. А когда включили монитор на экране было вбито имя этого инженера.
Короче комп беспрерывно пытался логиниться с пустым паролем. Естественно AD блокировала аккаунт.
Ржали всем отделом, потому что на расследование потратили 2 полных дня.nektopme
06.08.2025 14:04В уголке задумчивао курит бухгалтер с выдающимся бюстом. ...
dartraiden
06.08.2025 14:04В моём случае - с выдающимся пузом.
При наборе текста периодически начинали вводиться ряды пробелов. Причину найти не удавалось. Баг возникал рандомно, то текст вводится нормально, то поехали пробелы.
В очередной раз, когда начали ехать пробелы, я замер и попытался понять, что сейчас стриггерило баг. Взгляд упал вниз... Причиной оказалось сочетание стола, имевшего полочку для клавиатуры, низкопрофильной клавиатуры и моего отросшего пуза. Во время напряжённых срачей в интернете я слегка подаюсь вперед и краешек пуза ложится на пробел. Этим и объяснялась рандомность: когда я печатаю спокойно, то сижу ровно.
phaggi
06.08.2025 14:04Это абсолютно реальная история из жизни мэрии Москвы. Я тогда там в поддержке работал. И мы всем отделом ходили к тетке разбираться, почему база у нее не работает. Приходим - работает, уходим - не работает. И только один сотрудник понял, в чем дело…
itstranger
06.08.2025 14:04Вроде стандартные баги. Я за всё время работы подобного видел много. Например, по теме автора могу сказать, что существует два символа тире. Длинное тире и короткое. Длинное поддерживается далеко не всеми программами. Так же проблемы могут возникать из-за разных кодировок.
Это ещё автор с кавычками не встречался, которых лично я 5 видов по памяти назвать смогу и многие из них в подобных случаях без должных валидаций приводят к подобным ошибкам.
Подозреваю, что символ 0х2, скорее всего, подставляется в системе с которой работал автор для любого нерабочего символа, поэтому всегда заменять на тире, если честно, не совсем верное решение. Я бы узнал, какую кодировку использует программа, взял десяток другой спец символов и проверил какие из них ещё вызывают 0х2, а потом набросал бы валидатор для формы ввода, что сразу бы врачу показывал проблемные символы.
В общем, замена на 0х2 на пустоту или пробел, как предлагали разработчики, как по мне более правильный хотфикс, но это не решает проблему до конца.
Aggle
06.08.2025 14:04Латинские "C/c" и русские "С/с" - вообще классика.
aamonster
06.08.2025 14:04МЕЯ ВИДО? (c)
(Если кто не застал – это прикол времён Фидонета, когда буква 'Н' сжиралась, если я не путаю, GoldEdом. Ещё буква 'р' была проклята – в результате зачастую даже раскладку русификатора делали так, что гни заменялись на латинские 'H' и 'p' соответственно... Думаю, если сейчас погуглить русские слова с такой заменой – найдётся много текстов с артефактами тех времён).
CrashLogger
06.08.2025 14:04Буква р даже в каких-то старых версиях Norton Commander не вводилась. Не знаю, почему именно с ней были проблемы.
Overphase
06.08.2025 14:04код 0xE0 (буква "р" в кодовой странице DOS 866) - префикс расширенных сканкодов клавиатуры, см раздел 1.5 в https://aeb.win.tue.nl/linux/kbd/scancodes-1.html а старый Norton Commander зачем-то путал сканкоды с кодами символов.
sergeyns
06.08.2025 14:04два символа тире. Длинное тире и короткое. Длинное поддерживается далеко не всеми программами.
о да! Приколы начинаются когда из какого-нибудь экселя-ворда начинают копировать строки с такими тире.... Причем обычно такое тире не поддерживают всякие финансовые проги...
Aggle
06.08.2025 14:04А если на полный штык копать, то есть минус, дефис, короткое тире (полутире) и длинное тире (просто тире).
AgentFire
06.08.2025 14:04Я бы вайт-лист сделал бы для символов. Все равно врачи не будут Юникод смайлики в свои эпилоги вставлять
+Отдельный список маппинга/авто замены для миллионов видов тире/кавычек/пробелов/и и.п.
mayorovp
06.08.2025 14:04Например, по теме автора могу сказать, что существует два символа тире. Длинное тире и короткое. Длинное поддерживается далеко не всеми программами.
Ха-ха-ха. Тире есть длинное и среднее (оно же короткое, вроде бы), а ещё дефис. И еще несколько менее популярных разновидностей. Но по части поддержки они все в одной лодке, потому что на клавиатуре в стандартной раскладке их вообще нет - ни тире, ни даже дефиса. Только минус.
belch84
06.08.2025 14:04Первым делом делается санитайзинг или эскейпинг пользовательского ввода (это всё равно жёсткая необходимость и это уже решает проблему)
Не очень знаю, что такое санитайзинг и эскейпинг, но подозреваю, что речь идет о проверке пользовательского ввода. На мой взгляд, удобнее применять принудительное перекодирование символов перед помещением данных в базу или преобразованием в формат для обмена
FUNCTION xml_tran PARAMETER m.S * удаление ненужных символов из строки PRIVATE T T = chrtran(S, chr(0), ' ') T = strtran(T, '&', '&') T = strtran(T, '<', '<') T = strtran(T, '>', '>') T = strtran(T, "'", ''') T = strtran(T, '"', '"') T = strtran(T, '№', '№') T = strtran(T, CHR(0x11), '"') T = strtran(T, CHR(0x12), '"') RETURN m.T
aamonster
06.08.2025 14:04Санитайзинг – чистка от запрещённого (удалением или заменой, например 0x02 удалить или заменить на тире, как в статье). Эскейпинг – обратимая замена запрещённого (как в вашем примере для символов пунктуации).
В общем, всё вы знаете, только термины почему-то мимо прошли :-)
belch84
06.08.2025 14:04В общем, всё вы знаете, только термины почему-то мимо прошли
Конечно, я начинал программировать в седой древности, когда таких терминов не существовало
Эскейпинг – обратимая замена запрещённого (как в вашем примере для символов пунктуации)
Если быть совсем уж точным, то в моем примере замена не обратимая, поскольку открывающие и закрывающие кавычки, а также просто двойные кавычки заменяются на один и тот же символ XML
mayorovp
06.08.2025 14:04Вот не надо делать XML-кодирование перед помещением в базу! Потому что прочитать данные может понадобиться и не через XML, а в другом формате - и тогда их придётся декодировать обратно.
Особенно бесполезным является кодирование символа номера. Потому что если выходная кодировка поддерживает Юникод - то этот символ можно оставить как есть. А если выходная кодировка Юникод не поддерживает - то этот символ является далеко не единственным "проблемным". Ну и выходная кодировка в общем случае неизвестна пока вы не начинаете формировать, собственно, выходной файл.
belch84
06.08.2025 14:04Если внимательно прочитать мой комментарий, то в нем написано
удобнее применять принудительное перекодирование символов перед помещением данных в базу или преобразованием в формат для обмена
Приведенная функция использовалась именно для создания XML файла, который посылался в систему ЭДО. Без этого перекодирования система просто не принимала файл - в ней не было предусмотрено использование, например, открывающих или закрывающих кавычек
А если выходная кодировка Юникод не поддерживает - то этот символ является далеко не единственным "проблемным"
В данном случае я не занимался созданием универсальной системы. Оно преспокойно себе работало, но иногда XML файл отвергался. В этом случае нужно было просто найти символ, вызвавший проблему и добавить его в функцию перекодирования. Такой подход несколько отличается от описанного в статье, когда нужно было каждый раз ВРУЧНУЮ запускать скрипт и заменять один известный символ
BasilioCat
06.08.2025 14:04Еще чуть-чуть воды добавить, и можно как книгу издать.
Тем не менее, причина возникновения НЕ была обнаружена. А, вероятно, она всего лишь в замене кодов символов при формировании PDF, обычно ПО использует это для font subsetting (когда глифы для букв шрифта включаются в PDF только если буква встречается в тексте), а тут могло быть следствиемкосяковособенностей библиотеки
ncix
06.08.2025 14:04Я тогда занимался софтом для кассовых компьютеров. И вот создают мне тикет, что при закрытии большого чека в сотню позиций вываливается ошибка драйвера такая-то. Проверяю - все ок. Выясняю модель кассы, беру на складе, проверяю - все ок. Иду к тестеру, беру его кассу (точно такую же) - проверяю всё ок. А у него воспроизводится прям на моих глазах. Меняли провода - не помогает.
Уже не помню как до этого дошли - но в какой-то момент воткнули блок питания кассы в другую розетку - нет ошибки! Оказалось что на розетке без заземления баг проявляется, на розетке с заземлением - нет.
Что-то там не так было в схеме блока питания, и при печати большого чека и незаземленной розетке из-за большой потребляемой мощности (а термоголовка если не ошибаюсь потребляет ватт 40-50) что-то куда-то пробивало и процессор кассы сбоил.
Примечательно что на небольших чеках баг не проявлялся.
vasiaplaton
06.08.2025 14:04Два варианта
Может розетки без заземления были дальше от щита и подведены кабелем поменьше сечением? Тогда все проще - начиналась печать, а не очень хороший БП просаживал питание, ловил шумы, и касса спотыкалась.
Или из-за разных БП принтера и компа, без заземления и при потреблении на них появлялась разница потенциалов, замыкалась через коннектор, и летела прямиком в мозги компа
ncix
06.08.2025 14:04Может розетки без заземления были дальше от щита и подведены кабелем поменьше сечением?
Не, прям в одном "пилоте". Там были розетки с заземлением и без.
> Или из-за разных БП принтера и компа
RS-232 по-идее должен эту проблему решать. Проявлялось только при длинном чеке, то есть термоголовка "съедала" много мощности и (сейчас будет дилетантская мысль) - не хватало емкости стабилизирующих конденсаторов для поддержания нужных уровней напряжения на процессоре. Какую роль в этом играло защитное заземление - мне не ясно, скорее всего какой-то "баг" в схеме.
Впрочем, эту проблему я сдал в отдел разработки железа и дальше ее судьба мне неизвестна.
i86com
06.08.2025 14:04Ко мне тоже однажды обратились с багом - программа не запускается, жалуется на какую-то ерунду, хотя по коду всё чисто. Заняло не так много времени, но результат был неожиданным - японские(!) пробелы.
Да. У них даже пробелы свои есть (широкие). И вот один из таких пробелов, видимо, был в комментарии (естественно, написанном на японском). Коммент удалили, а пробел - не заметили. Вот компилятор за него и цеплялся.
В наши дни во всех известных мне IDE такое подчеркивается, так что, видимо, не только я сталкивался.
Aggle
06.08.2025 14:04Ну в том же ворде есть просто пробел (anykey при наборе) и неразрывный пробел (ctrl+shift+anykey).
iskatel-tut
06.08.2025 14:04Первый в жизни Ассемблер (программа-транслятор, а не язык, который пишется с прописной буквы). Так вот, нашли-таки транслятор (а раньше писали в машинных кодах) - кажется, для ДВК, но это не точно. И в некоторых строках с комментариями он выдавал ошибку именно из-за комментария. Но удаление коммента не помогало. Ну, анализ и т.п. А тогда русских раскладок клавиатуры было много. На наших клавах были к тому же две клавише: РУС и ЛАТ - для перехода туда и обратно. Оказалось, что они шлют в текст символы (уж не помню коды - что-то вроде 16 и 17). Коммент на русском языке приводил иногда к тому, что символ ЛАТ уже был в начале команды на следующей строке. Всё просто. Но с тех пор уже полвека не пишу комменты на русском.
Opaspap
06.08.2025 14:04Ещё распространенный случай при копипасте забыл какой то utf вставляется, если с некоторых web страниц копировать, ссылку не copy link, а select-copy у нас это было в тикетной системе и операторы постоянно его копировали в разные места где им надо было вставить ссылку. Ещё помню ве6эндор тоц тикет системы не хотел фиксить свой вывод, мотивируя какой то неведомрй семантикой чтоли.
ncix
06.08.2025 14:04Однажды был интересный баг: на компе тестировщика не работал поиск товара по отсканированному ШК, а на компах разработчиков (как обычно) всё работало отлично. Баг проявлялся только на сканере с эмуляцией клавиатурного ввода, что дало первую зацепку. Не помню уже деталей расследования, но виновником оказалась установленная на компе тестировщика популярная в 00х программа PuntoSwitcher, которая умела замечать текст, введенный не в той раскладке, автоматически его удалять и заменять корректным.
EugeneVRN
В Австралии настолько ничего не происходит что из обычной ошибки форматирования текста создали целое расследование.