При работе с двоичными файлами в 1С часто возникает задача извлечения текстовых данных, которые могут быть представлены в различных форматах. Мы сталкиваемся с двумя распространенными сценариями: чтение строк, завершающихся нулевым символом (так называемые C-строки или zero-terminated strings), и чтение многострочных текстов, содержащих обычные символы переноса строки. Стандартные методы 1С не всегда интуитивно понятны для таких задач, и попытки использовать их напрямую могут привести к ошибкам или неоптимальным решениям. Рассмотрим, как эффективно и корректно читать такие данные, избегая посимвольного анализа.
Когда мы пытаемся прочитать zero-terminated строку с помощью метода ЧтениеДанных.ПрочитатьСтроку(), передавая в качестве разделителя Строка(Символ(0)), мы получаем ошибку "неверный параметр 2". Это происходит потому, что метод ПрочитатьСтроку() предназначен для чтения до стандартных разделителей строк (например, Символы.ПС) или явно указанных строк-разделителей, но не всегда корректно обрабатывает одиночный нулевой символ как специальный терминатор. Кроме того, нам хотелось бы прочитать весь блок данных до нулевого символа "одним махом", а не перебирать каждый символ в цикле.
ЧтениеДанных.ПрочитатьДо()Для чтения zero-terminated строк в 1С идеально подходит метод ЧтениеДанных.ПрочитатьДо(). Этот метод позволяет прочитать данные из потока до первого вхождения одного из указанных маркеров. Мы можем использовать его, чтобы найти нулевой символ (Символ(0)), который является терминатором нашей C-строки.
Сначала мы открываем двоичный файл для чтения с помощью ФайловыеПотоки.ОткрытьДляЧтения() и создаем объект ЧтениеДанных.
ПотокИсходный = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
ЧтениеДанных = Новый ЧтениеДанных(ПотокИсходный);
Нам потребуется массив маркеров, даже если мы ищем только один. В этот массив мы добавим строковое представление нулевого символа.
Маркеры = Новый Массив;
Маркеры.Добавить(Строка(Символ(0)));
Теперь мы используем метод ПрочитатьДо(), передавая ему наш массив маркеров. Этот метод вернет объект типа РезультатЧтенияДанных.
РезультатЧтенияЧасть = ЧтениеДанных.ПрочитатьДо(Маркеры);
После чтения нам необходимо убедиться, что маркер был найден. Если это так, мы можем получить прочитанный фрагмент как новый поток, а затем прочитать его содержимое как строку. Крайне важно при создании нового объекта ЧтениеДанных указать правильную кодировку текста. В нашем случае это кодировка OEM (CP866), часто используемая в старых двоичных файлах.
Функция ПрочитатьСтрокуИзБинарногоПотока(ЧтениеДанных)
Маркеры = Новый Массив;
Маркеры.Добавить(Строка(Символ(0)));
РезультатЧтенияЧасть = ЧтениеДанных.ПрочитатьДо(Маркеры);
Если НЕ РезультатЧтенияЧасть.МаркерНайден Тогда
Возврат ""; // Маркер не найден, строка пуста или поток завершился
КонецЕсли;
// Создаем новый объект ЧтениеДанных из потока с прочитанными данными
// и указываем корректную кодировку, например, КодировкаТекста.OEM (CP866)
ЧтениеСтрока = Новый ЧтениеДанных(РезультатЧтенияЧасть.ОткрытьПотокДляЧтения(), КодировкаТекста.OEM);
// Читаем всю строку из этого временного потока
// Важно: ПрочитатьСтроку() здесь без параметров, так как она уже читает из готового фрагмента
Возврат ЧтениеСтрока.ПрочитатьСтроку();
КонецФункции
// Пример использования
// ... (открытие файла и создание ЧтениеДанных) ...
СчитаннаяCСтрока = ПрочитатьСтрокуИзБинарногоПотока(ЧтениеДанных);
// ... (закрытие потока) ...
В этом примере мы создаем временный объект ЧтениеДанных для уже извлеченного фрагмента данных и используем ПрочитатьСтроку() без параметров. Это позволяет корректно интерпретировать байты в прочитанном куске как строку в заданной кодировке (в данном случае, КодировкаТекста.OEM).
Хотя подход с ПрочитатьДо(Символ(0)) прекрасно работает для zero-terminated строк, он не подходит для многострочных текстов, которые содержат стандартные символы переноса строки (например, Символы.ПС или Символы.ВК) внутри себя, до нулевого символа-терминатора. Метод ПрочитатьСтроку() по умолчанию считывает данные до первого встреченного разделителя строк. Если многострочный текст состоит из нескольких строк с переносами, каждый вызов ПрочитатьСтроку() будет возвращать только одну строку.
Для чтения всего многострочного текста, состоящего из нескольких строк с переносами, до определенного маркера (например, Символ(0)), нам все равно придется использовать цикл.
Сначала мы читаем весь блок данных до нулевого символа, используя уже знакомый нам метод ПрочитатьДо(). Это позволит нам получить весь многострочный текст как единый фрагмент данных.
Маркеры = Новый Массив;
Маркеры.Добавить(Строка(Символ(0))); // Или любой другой терминатор для всего блока текста
РезультатЧтенияВсегоБлока = ЧтениеДанных.ПрочитатьДо(Маркеры);
Если Не РезультатЧтенияВсегоБлока.МаркерНайден Тогда
Возврат ""; // Блок не найден
КонецЕсли;
// Создаем ЧтениеДанных для обработки извлеченного блока
ЧтениеЧасти = Новый ЧтениеДанных(РезультатЧтенияВсегоБлока.ОткрытьПотокДляЧтения(), КодировкаТекста.OEM);
После того как мы получили весь многострочный блок в виде отдельного потока (ЧтениеЧасти), мы можем читать его построчно, используя ПрочитатьСтроку() в цикле. Каждая итерация будет возвращать одну строку из многострочного текста до следующего символа переноса строки. Все прочитанные строки затем можно собрать в массив и объединить с помощью функции СтрСоединить().
Функция ПрочитатьМногострочныйТекстИзБинарногоПотока(ЧтениеДанных)
МаркерыДляБлока = Новый Массив;
МаркерыДляБлока.Добавить(Строка(Символ(0))); // Пример маркера, ограничивающего весь многострочный блок
РезультатЧтенияБлока = ЧтениеДанных.ПрочитатьДо(МаркерыДляБлока);
Если Не РезультатЧтенияБлока.МаркерНайден Тогда
Возврат "";
КонецЕсли;
ЧтениеИзБлока = Новый ЧтениеДанных(РезультатЧтенияБлока.ОткрытьПотокДляЧтения(), КодировкаТекста.OEM);
СтрокиДанных = Новый Массив;
Пока Истина Цикл
Стр = ЧтениеИзБлока.ПрочитатьСтроку();
Если Стр = "" И ЧтениеИзБлока.ЧтениеЗавершено Тогда
// Достигнут конец потока внутри блока
Прервать;
КонецЕсли;
// Добавляем строку. Если она пустая, но поток не завершен,
// это может быть пустая строка между переносами или в конце
СтрокиДанных.Добавить(Стр);
КонецЦикла;
Возврат СтрСоединить(СтрокиДанных, Символы.ПС);
КонецФункции
// Пример использования
// ... (открытие файла и создание ЧтениеДанных) ...
МногострочныйТекст = ПрочитатьМногострочныйТекстИзБинарногоПотока(ЧтениеДанных);
// ... (закрытие потока) ...
Мы используем Символы.ПС для объединения строк, предполагая, что исходные строки были разделены именно этим символом или его эквивалентом, который ПрочитатьСтроку() интерпретирует как разделитель.
Критически важно всегда указывать правильную кодировку при создании объекта ЧтениеДанных, особенно когда мы работаем с двоичными файлами и не-UTF-кодировками, такими как OEM (КодировкаТекста.OEM или Кодировка.Cp866). Неправильно выбранная кодировка приведет к "кракозябрам" вместо читаемого текста. В нашем случае, мы знаем, что данные в кодировке CP866.
Хотя мы и стремимся к чтению "одним махом", для сложных структур, таких как многострочные тексты, приходится прибегать к итеративным методам. Однако, предложенные решения (ПрочитатьДо() для извлечения блока и циклическое ПрочитатьСтроку() внутри него) значительно эффективнее, чем посимвольное чтение. Методы 1С по работе с потоками оптимизированы для блочного чтения, поэтому эти подходы обеспечивают приемлемую производительность, даже если не являются однострочными.
ЧтениеДанных.Прочитать():
Если нам нужно прочитать весь остаток потока как двоичные данные или определенное количество байт, мы можем использовать метод ЧтениеДанных.Прочитать(). Он возвращает объект РезультатЧтенияДанных, из которого затем можно получить ДвоичныеДанные. Однако этот метод не предназначен для автоматического преобразования многострочного текста в единую строку с корректными переносами, если мы хотим получить именно текстовое представление.
// Чтение всего оставшегося потока как двоичных данных
ДвоичныеДанныеИзПотока = ЧтениеДанных.Прочитать().ПолучитьДвоичныеДанные();
Всегда помните о необходимости закрытия файловых потоков после завершения работы с ними. Это освобождает системные ресурсы и предотвращает блокировку файлов.
// ... после всех операций чтения ...
ЧтениеДанных.Закрыть();
ПотокИсходный.Закрыть();
Таким образом, для чтения zero-terminated строк мы используем ЧтениеДанных.ПрочитатьДо() с Символ(0) как маркером, а затем создаем новый ЧтениеДанных для прочитанного фрагмента, указывая кодировку и вызывая ПрочитатьСтроку(). Для многострочных текстов, ограниченных определенным маркером (например, тем же Символ(0)), мы также используем ПрочитатьДо() для извлечения всего блока, а затем внутри этого блока циклически вызываем ПрочитатьСтроку() и объединяем полученные части.