В платформе "1С:Предприятие" версии 8.3 появилось много дополнительных возможностей: в том числе вычисления результатов хеш-функций, что в практике программирования зачастую бывает востребовано. А что предпринять тем, кто по каким-либо причинам не перешёл на новую платформу? Вывод очевиден - расширять функционал своих прикладных решений методами используемой платформы. В этой публикации я расскажу, как методами встроенного языка реализовать алгоритм расчета контрольной суммы строки по алгоритму CRC32, а также получить индекс цвета на основе его составляющих.
Для начала нам потребуется написать несколько вспомогательных функций для перевода чисел с одним основанием в числа с другим основанием и выполнения расчета бинарных операций. Таких функций написано немало, поэтому на оригинальность не претендую. Но всё же код этих функций приведу.
Функция ТекстОшибки(ИмяФункции, ОписаниеОшибки) Экспорт
Возврат ИмяФункции + ": " + ОписаниеОшибки;
КонецФункции // ТекстОшибки()
Функция ПреобразоватьЧислоВСтрокуСДругимОснованием(Знач Значение, Основание, Результат, МинимальнаяДлинаТипаЧисла = 0) Экспорт
Перем ШаблонПреобразования;
Перем ИмяФункции;
Перем ОписаниеОшибки;
ИмяФункции = "ПреобразоватьЧислоВСтрокуСДругимОснованием";
ШаблонПреобразования = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
ОписаниеОшибки = "";
Результат = "";
Попытка
Значение = Число(Значение);
Если (Основание < 2) ИЛИ (Основание > 36) ИЛИ (НЕ (Основание - Цел(Основание) = 0)) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, "Основание числа должно находиться в диапазоне от 2 до 36!"));
ИначеЕсли Значение < 0 ИЛИ НЕ (Значение - Цел(Значение) = 0) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, "Значение числа должно быть целым положительным!"));
ИначеЕсли Значение = 0 Тогда
Результат = "0";
Иначе
// Перевод в систему с другим основанием
Пока Значение > 0 Цикл
Результат = Сред(ШаблонПреобразования, Значение % Основание + 1, 1) + Результат;
Значение = Цел(Значение / Основание);
КонецЦикла;
КонецЕсли;
// Если требуется, дополняем строку до требуемой длины нолями
Для Счетчик = СтрДлина(Результат) + 1 По МинимальнаяДлинаТипаЧисла Цикл
Результат = "0" + Результат;
КонецЦикла;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Возврат ОписаниеОшибки;
КонецФункции
Функция ПреобразоватьСтрокуСДругимОснованиемВЧисло(Знач Значение, Основание, Результат) Экспорт
Перем ШаблонПреобразования;
Перем ИмяФункции;
Перем ОписаниеОшибки;
ИмяФункции = "ПреобразоватьСтрокуСДругимОснованиемВЧисло";
ШаблонПреобразования = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
ОписаниеОшибки = "";
Результат = "";
Попытка
Результат = 0;
Значение = Врег(СокрЛП(Значение));
Если (Основание < 2) ИЛИ (Основание > 36) ИЛИ (НЕ (Основание - Цел(Основание) = 0)) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, "Основание числа должно быть целым и находиться в диапазоне от 2 до 36!"));
ИначеЕсли (Значение = "0") ИЛИ (СтрДлина(Значение) = 0) Тогда
Результат = 0;
Иначе
// Проверка правильности исходного значения
ШаблонПроверки = Лев(ШаблонПреобразования, Основание);
Для НомерСимвола = 1 По СтрДлина(Значение) Цикл
Если Не Найти(ШаблонПроверки, Сред(Значение, НомерСимвола, 1)) Тогда
ВызватьИсключение("Преобразуемое значение содержит недопустимые символы!");
КонецЕсли;
КонецЦикла;
// Перевод в систему с основанием 10
ДлинаЗначения = СтрДлина(Значение);
Для НомерСимвола = 1 По ДлинаЗначения Цикл
МножительПорядкаЧисла = 1;
Для СчетчикЦикла = 1 По ДлинаЗначения - НомерСимвола Цикл
МножительПорядкаЧисла = МножительПорядкаЧисла * Основание;
КонецЦикла;
Результат = Результат + (Найти(ШаблонПреобразования, Сред(Значение, НомерСимвола, 1)) - 1) * МножительПорядкаЧисла;
КонецЦикла;
КонецЕсли;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Возврат ОписаниеОшибки;
КонецФункции
Функция ПреобразоватьСтрокуСОднимОснованиемВСтрокуСДругимОснованием(ПредставлениеИсходногоЧисла, ИсходноеОснование, ЦелевоеОснование, МинимальнаяДлинаТипаЧисла = 0, РезультатПреобразования) Экспорт
Перем ОписаниеОшибки;
Перем ИмяФункции;
Перем ЧисловойРезультатПреобразования;
ОписаниеОшибки = "";
ИмяФункции = "ПреобразоватьСтрокуСОднимОснованиемВСтрокуСДругимОснованием";
Попытка
ОписаниеОшибки = ПреобразоватьСтрокуСДругимОснованиемВЧисло(ПредставлениеИсходногоЧисла, ИсходноеОснование, ЧисловойРезультатПреобразования);
Если Не ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ОписаниеОшибки);
КонецЕсли;
ОписаниеОшибки = ПреобразоватьЧислоВСтрокуСДругимОснованием(ЧисловойРезультатПреобразования, ЦелевоеОснование, РезультатПреобразования, МинимальнаяДлинаТипаЧисла);
Если Не ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ОписаниеОшибки);
КонецЕсли;
Исключение
ОписаниеОшибки = ИмяФункции + ": " + ОписаниеОшибки();
КонецПопытки;
Возврат ОписаниеОшибки;
КонецФункции // ПреобразоватьСтрокуСОднимОснованиемВСтрокуСДругимОснованием()
Функция ВыполнитьБинарнуюОперацию(Знач Операнд1, Знач Операнд2, Оператор, РезультатВычисления, ОснованиеОперандов = 10, ОснованиеРезультата = 10) Экспорт
Перем ПредставлениеОперанда1;
Перем ПредставлениеОперанда2;
Перем ИмяФункции;
Перем ОписаниеОшибки;
ИмяФункции = "ВыполнитьБинарнуюОперацию";
ОписаниеОшибки = "";
ПредставлениеРезультата = "";
РезультатВычисления = 0;
Попытка
Если НЕ ОснованиеОперандов = 2 Тогда
//{ Получаем двоичное представление переданных операндов
ОписаниеОшибки = ПреобразоватьЧислоВСтрокуСДругимОснованием(Операнд1, 2, ПредставлениеОперанда1);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
ОписаниеОшибки = ПреобразоватьЧислоВСтрокуСДругимОснованием(Операнд2, 2, ПредставлениеОперанда2);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
//} Получаем двоичное представление переданных операндов
Иначе
// проверяем переданные операнды
Если НЕ ТипЗнч(Операнд1) = Тип("Строка") ИЛИ НЕ ТипЗнч(Операнд2) = Тип("Строка") Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, "Переданный в качестве операндов параметры должны иметь строковый тип!"));
КонецЕсли;
ПредставлениеОперанда1 = Операнд1;
ПредставлениеОперанда2 = Операнд2;
КонецЕсли;
// длина операндов должна быть одинаковой
МаксимальнаяДлина = Макс(СтрДлина(ПредставлениеОперанда1), СтрДлина(ПредставлениеОперанда2));
Для СчетчикЦикла = СтрДлина(ПредставлениеОперанда1) + 1 По МаксимальнаяДлина Цикл
ПредставлениеОперанда1 = "0" + ПредставлениеОперанда1;
КонецЦикла;
Для СчетчикЦикла = СтрДлина(ПредставлениеОперанда2) + 1 По МаксимальнаяДлина Цикл
ПредставлениеОперанда2 = "0" + ПредставлениеОперанда2;
КонецЦикла;
// производим необходимые бинарные операции и получаем двоичное представление результата в 32-битном формате
Для НомерСимвола = 1 По МаксимальнаяДлина Цикл
Если Оператор = "XOR" Тогда
ПредставлениеРезультата = ПредставлениеРезультата + ?(Число(Сред(ПредставлениеОперанда1, НомерСимвола, 1)) + Число(Сред(ПредставлениеОперанда2, НомерСимвола, 1)) = 1, "1", "0");
ИначеЕсли Оператор = "AND" Тогда
ПредставлениеРезультата = ПредставлениеРезультата + ?(Число(Сред(ПредставлениеОперанда1, НомерСимвола, 1)) + Число(Сред(ПредставлениеОперанда2, НомерСимвола, 1)) = 2, "1", "0");
ИначеЕсли Оператор = "OR" Тогда
ПредставлениеРезультата = ПредставлениеРезультата + ?(Число(Сред(ПредставлениеОперанда1, НомерСимвола, 1)) + Число(Сред(ПредставлениеОперанда2, НомерСимвола, 1)) = 0, "0", "1");
КонецЕсли;
КонецЦикла;
Если НЕ ОснованиеРезультата = 2 Тогда
ОписаниеОшибки = ПреобразоватьСтрокуСДругимОснованиемВЧисло(ПредставлениеРезультата, 2, РезультатВычисления);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
Иначе
РезультатВычисления = ПредставлениеРезультата;
КонецЕсли;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Возврат ОписаниеОшибки;
КонецФункции
Функция ВыполнитьИнверсию(Знач Операнд, РезультатВычисления, ОснованиеОперанда = 10, ОснованиеРезультата = 10) Экспорт
Перем ПредставлениеОперанда;
Перем ИмяФункции;
Перем ОписаниеОшибки;
ИмяФункции = "ВыполнитьИнверсию";
ОписаниеОшибки = "";
ПредставлениеРезультата = "";
РезультатВычисления = 0;
Попытка
Если НЕ ОснованиеОперанда = 2 Тогда
//{ Получаем двоичное представление переданного операнда в двоичном формате
ОписаниеОшибки = ПреобразоватьЧислоВСтрокуСДругимОснованием(Операнд, 2, ПредставлениеОперанда, 32);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
//} Получаем двоичное представление переданного операнда в двоичном формате
Иначе
// проверяем переданный операнд
Если НЕ ТипЗнч(Операнд) = Тип("Строка") Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, "Переданный в качестве операнда параметр должен иметь строковый тип!"));
КонецЕсли;
ПредставлениеОперанда = Операнд;
КонецЕсли;
МаксимальнаяДлина = 32;
// Дополняем двоичное представление операнда до необходимой длины нулями
Для Счетчик = СтрДлина(ПредставлениеОперанда) + 1 По МаксимальнаяДлина Цикл
ПредставлениеОперанда = "0" + ПредставлениеОперанда;
КонецЦикла;
// производим необходимые унарные операции и получаем двоичное представление результата в строковом формате
Для НомерСимвола = 1 По МаксимальнаяДлина Цикл
ПредставлениеРезультата = ПредставлениеРезультата + ?(Число(Сред(ПредставлениеОперанда, НомерСимвола, 1)) = 1, "0", "1");
КонецЦикла;
Если НЕ ОснованиеРезультата = 2 Тогда
ОписаниеОшибки = ПреобразоватьСтрокуСДругимОснованиемВЧисло(ПредставлениеРезультата, 2, РезультатВычисления);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
Иначе
РезультатВычисления = ПредставлениеРезультата;
КонецЕсли;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Возврат ОписаниеОшибки;
КонецФункции
Для вычислений нам понадобится таблица символов ASCII для перевода символов в кодировке UTF-8 в символы кодовой страницы windows-1251
и массив чисел для реализации алгоритма CRC32. Эти данные находятся в макетах прилагаемой к публикации внешней обработки (для платформы 8.2).
Ну и наконец сами функции расчета:
Процедура ЗаполнитьТаблицуASCII()
мТаблицаASCII = Новый ТаблицаЗначений;
мТаблицаASCII.Колонки.Добавить("Код", Новый ОписаниеТипов("Число"));
мТаблицаASCII.Колонки.Добавить("Символ", Новый ОписаниеТипов("Строка"));
МакетТаблицыASCII = ПолучитьМакет("ТаблицаКодовASCII");
ОбластьТаблицыASCII = МакетТаблицыASCII.ПолучитьОбласть("RU");
Для НомерСтроки = 1 По ОбластьТаблицыASCII.ВысотаТаблицы Цикл
НоваяСтрока = мТаблицаASCII.Добавить();
НоваяСтрока.Код = Число(ОбластьТаблицыASCII.ПолучитьОбласть(НомерСтроки, 1).ТекущаяОбласть.Текст);
НоваяСтрока.Символ = ОбластьТаблицыASCII.ПолучитьОбласть(НомерСтроки, 2).ТекущаяОбласть.Текст;
КонецЦикла;
КонецПроцедуры // ЗаполнитьТаблицуASCII()
Функция ПолучитьКонтрольнуюСуммуСтроки(Знач ИсходнаяСтрока, КонтрольнаяСумма, РассчетСредстамиВстроенногоЯзыка = Истина) Экспорт
Перем ДвоичноеПредставлениеКонтрольнойСуммы;
Перем ИмяФункции;
Перем ОписаниеОшибки;
ИмяФункции = "ПолучитьКонтрольнуюСуммуСтроки";
ОписаниеОшибки = "";
Если Неопределено = мТаблицаASCII Тогда
ЗаполнитьТаблицуASCII();
КонецЕсли;
Если ПустаяСтрока(ИсходнаяСтрока) Тогда
КонтрольнаяСумма = 0;
Иначе
МакетТаблицыРасчета = ПолучитьМакет("ТаблицаРасчетаCRC32");
ОбластьТаблицыРасчета = МакетТаблицыРасчета.ПолучитьОбласть("CRC32TableBase2");
КонтрольнаяСумма = 4294967295; // 0xFFFFFFFF
Попытка
Для НомерСимвола = 1 По СтрДлина(ИсходнаяСтрока) Цикл
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
// Алгоритм расчета CRC32 в цикле (С++)
// dwCrc32 = ((dwCrc32) >> 8) ^ Crc32Table[(bSimbolCode) ^ ((dwCrc32) & 0x000000FF)];
Символ = Сред(ИсходнаяСтрока, НомерСимвола, 1);
// Получаем двоичное представление контрольной суммы в 32 битном формате
ОписаниеОшибки = ПреобразоватьЧислоВСтрокуСДругимОснованием(КонтрольнаяСумма, 2, ДвоичноеПредставлениеКонтрольнойСуммы, 32);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
КонтрольнаяСуммаВправо8 = Лев(ДвоичноеПредставлениеКонтрольнойСуммы, 24);
//{ Вычисление выражения Crc32Table[(bSimbolCode) ^ ((dwCrc32) & 0x000000FF)]
КодСимвола = КодСимвола(Символ);
Если КодСимвола > 127 Тогда
// Поскольку строка представлена в кодировке UTF-8
// попытаемся перевести символы в кодировку ASCII 1251
СтрокаТаблицы = мТаблицаASCII.Найти(Символ, "Символ");
Если СтрокаТаблицы = Неопределено Тогда
// Это - не символ кодовой таблицы 1251,
// заменим его на псевдосимвол из диапазона 1-256
ОписаниеОшибки = ВыполнитьБинарнуюОперацию(КодСимвола, 255, "AND", КодСимвола);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
КодСимвола = КодСимвола + 1;
Иначе
КодСимвола = СтрокаТаблицы.Код;
КонецЕсли;
КонецЕсли;
НомерСтрокиТаблицы = 0;
ПсевдослучайноеЧисло = 0;
ОписаниеОшибки = ВыполнитьБинарнуюОперацию(ДвоичноеПредставлениеКонтрольнойСуммы, "00000000000000000000000011111111", "AND", ПсевдослучайноеЧисло, 2);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
ОписаниеОшибки = ВыполнитьБинарнуюОперацию(КодСимвола, ПсевдослучайноеЧисло, "XOR", НомерСтрокиТаблицы);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
ЧислоТаблицыРасчета = ОбластьТаблицыРасчета.ПолучитьОбласть(НомерСтрокиТаблицы + 1, 1).ТекущаяОбласть.Текст;
//} Вычисление выражения Crc32Table[(bSimbolCode) ^ ((dwCrc32) & 0x000000FF)]}
//{ Расчет контрольной суммы
ОписаниеОшибки = ВыполнитьБинарнуюОперацию(КонтрольнаяСуммаВправо8, ЧислоТаблицыРасчета, "XOR", КонтрольнаяСумма, 2);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
//} Расчет контрольной суммы
КонецЦикла;
// Инверсия контрольной суммы
// dwCrc32 = ~dwCrc32
ОписаниеОшибки = ВыполнитьИнверсию(КонтрольнаяСумма, КонтрольнаяСумма);
Если НЕ ПустаяСтрока(ОписаниеОшибки) Тогда
ВызватьИсключение(ТекстОшибки(ИмяФункции, ОписаниеОшибки));
КонецЕсли;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
Возврат ОписаниеОшибки;
КонецФункции // ПолучитьКонтрольнуюСуммуСтроки()
Функция RGB(Знач R, Знач G, Знач B) Экспорт
// Пример перевода цветовых составляющих в числовое значение
// Pascal
// Result := (Integer(B) shl 16) + (Integer(G) shl 8) + R;
// C++
// #define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
Перем ДвоичноеПредставлениеR;
Перем ДвоичноеПредставлениеG;
Перем ДвоичноеПредставлениеB;
Перем Результат;
ПреобразоватьЧислоВСтрокуСДругимОснованием(R, 2, ДвоичноеПредставлениеR, 32);
ПреобразоватьЧислоВСтрокуСДругимОснованием(G, 2, ДвоичноеПредставлениеG, 32);
ПреобразоватьЧислоВСтрокуСДругимОснованием(B, 2, ДвоичноеПредставлениеB, 32);
ПреобразоватьСтрокуСДругимОснованиемВЧисло(Прав(ДвоичноеПредставлениеB, 16) + "0000000000000000", 2, B);
ПреобразоватьСтрокуСДругимОснованиемВЧисло(Прав(ДвоичноеПредставлениеG, 24) + "00000000", 2, G);
Возврат B + G + R;
КонецФункции // RGB()
Таким образом мы наделили функционал нашего прикладного решения возможностью вычисления бинарных операций, без чего невозможно организовать вычисление более сложных алгоритмов. Конечно, функционал этот не наделён высокой скоростью работы, зато реализован исключительно средствами платформы "1С:Предприятие".
Область применения вышеизложенного функционала приводить не буду - недаром публикация размещена в разделе "Практика программирования".
Расскажу лишь то, каким образом использовал его я.
В одной из организаций, где я работал, существовала централизованая база данных, аккумулирующая информацию из различных систем.
В конфигурации базы были реализваны регистры сведений с различными реквизитами и всего двумя измерениями: идентификатор информационной системы - импортера записи, и идентификатор записи таблицы системы-импортёра.
При импорте из систем с возможностью непосредственного обращения к таблицам баз данных сложностей не возникало - все они имели идентификатор записи в качестве первичного ключа. А вот при импорте записей регистра сведений из файлового варианта базы 1С сложности возникли - как получить инденитификатор записи регистра? Задача была решена следующим образом: ключевые поля записи преобразовывались к строковому типу, затем производилась их конкатенация, а результирующая строка хэшировалось алгоритмом CRC32, что и давало в результате необходимый идентификатор записи.
Функция же RGB использовалась для формирования палитры книги MS Excel с целью оформления её в необходимом стиле.