Описание формата внутреннего представления данных 1С в контексте обмена данными

Публикация № 1116103

Разработка - Практика программирования

интеграция обмен формат ЗначениеВСтрокуВнутр ЗначениеИзСтрокиВнутр

131
Фирма 1С не рекомендует использовать внутреннее представление данных для любых целей, которые отличны от обмена с 1С:Предприятием 7.7. Но сама возможность заглянуть на "внутреннюю кухню" платформы с помощью функций ЗначениеВСтрокуВнутр(), ЗначениеВФайл(), ЗначениеИзСтрокиВнутр() и ЗначениеИзФайла(), дала возможность сообществу программистов 1С разработать новые приемы разработки и анализа. Так, именно на использовании внутреннего представления был построен алгоритм "быстрого массива", который позволяет практически мгновенно создать массив в памяти на основании строки с разделителями. С помощью разбора внутреннего представления можно "на лету" программным кодом выполнить анализ обычной формы и даже сделать редактор графической схемы. Во внутреннем формате сохраняют свои данные между сеансами различные популярные внешние обработки. А еще это возможность сделать быстрый обмен с внешними системами.
Содержание.

 

Зачем нужна эта статья? 

Несколько лет назад я работал над проектом логистического ПО. И у нас был расчетный сервер, который на вход получал из торговой 1С сотни точек пути с графиками их работы, тысячи заказов на доставки с параметрами совместимости товаров для возможности совместной доставки, описание парка из десятков машин с различными параметрами и разными тарифами; а на выход выдавал наиболее оптимальное решение по критериям времени/денег/выполнимости в виде сложной древовидной структуры сформированных маршрутов. Объем потока информации был огромен и важным стал вопрос формата для выполнения обмена. 

В первых релизах я пробовал обмен сначала с помощью XML, а потом и на JSON. Но слишком много времени уходило на конвертацию. Попробовал вместо типовых функций написать собственный "велосипед" с потоковым парсингом, но и тут получил лишь незначительный выигрыш.

Тогда я вспомнил про внутреннее представление. По моему ТЗ наш сишник сделал задачу парсинга "формата 1С" на расчетном сервере и в результате у нас получилось практически мгновенное превращение расчетного задания в текст для сервера, а затем не менее быстрое превращение результатов расчета в объект, готовый для демонстрации логисту на карте.

К сожаления, я тогда не сохранил свои наработки, так как посчитал, что у нас весьма экзотический случай и ценность для других данной темы не высока. И был крайне удивлен, когда на днях на Toster.ru (сайт вопросов-ответов) один php-программист попросил помощи с парсингом формата 1С. Внезапно оказалось, что:

  1. До сих пор появляются задачи обмена именно в "формате 1С",
  2. До сих пор отсутствует документация, которая может помочь специалистам, не знающим внутреннюю кухню платформы 1С.

 

Общие правила и Рекомендации.

Строка внутреннего представления данных, получаемая с помощью ЗначениеВСтрокуВнутр() или ЗначениеВФайл() - это текст, обычно в формате UTF-8. Отличительная особенность от других текстовых форматов - синтаксис фигурных кавычек. Описание данных, которые представлены внутренним форматом 1С, начинается с открывающейся фигурной скобки "{" и заканчивается закрывающейся фигурной скобкой "}". Содержимое между фигурными скобками - это инструкции парсеру данных (далее - параметры). Если параметров больше одного, то они разделяются символом запятой. Первый параметр - это всегда один символ признака типа в двойных кавычках. Остальные параметры - это или уточнение типа, или указание на количество элементов, или указание на конкретные элементы из множества, или описание значения свойств, или другие инструкции парсеру по сборке.

По моему опыту, для полноценного обмена вполне достаточно описать такие типы как Число, Строка, Дата, Булево, Массив, Соответствие и Неопределено. Эти простые типы, которые интуитивно понятны современным системным и веб-программистам, в которых те увидят аналогии с базовыми типами в своих языках. Тип Структура - это частный случай Соответствия и с точки зрения внутренней структуры они полностью идентичны, а потому я его не рекомендую. СписокЗначений, ТаблицыЗначений и особенно ДереваЗначений - будут мало интересны из-за своей направленности на интерактивную работу с пользователем, а потому имеющие сложную внутреннюю структуру, которую запросто можно повторить многомерным Массивом или Массивом Соответствий. Ссылочные типы я бы тоже не рекомендовал использовать из-за избыточности представляемой информации - те же код/номер или УИД из ссылки будут намного компактнее.

Важные замечание из моего опыта парсинга:

  1. Регистр символов для обозначения типов (первый параметр) имеет важное значение!
  2. Игнорируются все пробельные символы (пробелы, табуляции, переносы строк) в произвольных местах между фигурными скобками и параметрами - т.е. руки развязаны и для эстетов, и для любителей покомпактнее.
  3. Выражения в неправильном формате, но с правильной структурой (ошибочный тип или идентификатор) распознаются как Неопределено!
  4. Нарушение структуры или неправильные типы параметров на предопределенных позициях приводят к выводу ошибки: "Ошибка преобразования: Ошибка формата потока"
  5. Лишние параметры игнорируются и не считаются нарушением структуры. Для примера {"N", 5, "S", "Test"} - это все равно будет число 5.

 

Примитивные типы.

Тип Шаблон Описание типа и формат значения Примеры
Null {"L"} Без значения. Символизирует пустые значения, полученные из запроса как результат соединения или обращения к вложенным полям пустых ссылок через точку. {"L"}
Неопределено {"U"} Без значения. Символизирует неинициализированные переменную или свойство объекта составного типа. {"U"}
Число {"N",<?>} Тип для описания вещественных чисел. Во второй позиции представления находится число в десятеричной системе счисления. При наличии дробной части разделителем выступает символ точки. {"N", 3.14}
{"N", 777}
Строка {"S","<?>"} Строковое значение заключается в двойные кавычки. Символы экранирования не используются за исключением двойного вывода самой двойной кавычки, если она должна быть в строке. {"S", "тест"} = тест
{"S", " ""} "} = "}
Булево {"B",<?>} Для False/Ложь значение равно 0, а для True/Истина будет равно 1 {"B", 0}
{"B", 1}
Дата {"D",<?>} Значение даты указывается с точностью до секунды в формате YYYYMMDDhhmmss. В 1С пустая дата - это 1 января 1 года н.е. {"D",00010101000000}  = пустая дата
{"D",20190830203450}  = 20:34:50 30 августа 2019 года

 

Примечание 1. Еще раз обращаю внимание на замечания из предыдущего пункта. Так при парсинге из выражения {"N" , 7     } - мы получаем цифру 7, но если напишем {"n",7} - то в результате будет значение Неопределено!

Примечание 2. С точки зрения логики внутреннего формата к примитивным типам так же относятся Типы (идентификаторы классов данных). Описание Типа состоит из двух параметров - "T" и внутреннего идентификатора. Примеры: тип Строка = {"T",9b6abf8b-0173-48e5-b0a0-83b21fcf63c5}, тип Число = {"T",b0be78f2-0ee6-4d31-a3bb-77dd32ba5bec}, тип Массив = {"T",51e7a0d2-530b-11d4-b98a-008048da3034}.

 

Коллекции.

Чтобы описать коллекцию значений, необходимо в качестве первого параметра указать символ типа "#", а вторым параметром записать идентификатор типа. Для коллекций (также как и для Ссылок, Объектов, Списков, Выборок и так далее), в качестве идентификатора используется 32-разрядное 16-ричное число. Регистр для символов A-F (цифры с 10 по 15) не имеет значение - хоть большие и маленькие вперемешку. Но важно наличие разделение дефисами на 5 групп символов по правилу 8-4-4-4-12 и отсутствие пробельных символов внутри идентификатора - нарушение этих правил приводит к ошибке потока.

Для коллекций значений предопределены (со времен 7.7, а может даже раньше) следующие идентификаторы:

Тип Идентификатор
Массив 51e7a0d2-530b-11d4-b98a-008048da3034
Структура 4238019d-7e49-4fc9-91db-b6b951d5cf8e
Соответствие 3d48feae-a9c6-4c5a-a099-9eb6477630c6
СписокЗначений 4772b3b4-f4a3-49c0-a1a5-8cb5961511a3
ТаблицаЗначений acf6192e-81ca-46ef-93a6-5a6968b78663
ДеревоЗначений e603c0f2-92fb-4d47-8f38-a44a381cf235
ФиксированныйМассив 4500381b-db30-4a10-9db4-990038032acf
ФиксированнаяСтруктура 3ee983d7-ace7-40f9-bb7e-2e916fcddd56
ФиксированноеСоответствие 220455ea-6c85-4513-996f-bbe79ed07774

 

Примечание. Если заменить символ "#" на "T", то получим вместо объекта значения объект его типа.

 
 Если использовать параметр "T" и идентификатор, то получим значение типа Тип:

 

Массив.

Это популярный во всех языках программирования тип, который предназначен для хранения некоторого множества данных с доступом по индексу. Так как язык 1С не является строго типизированным языком, то и массив не требует описания типа своих элементов и даже допускается размещение в едином массиве значений различных типов.

Структурно внутреннее описание массива состоит из 3 параметров - "#", 51e7a0d2-530b-11d4-b98a-008048da3034 и содержимого массива в фигурных скобках. Содержимое в свою очередь состоит из количества элементов массива, а потом перечисления этих элементов через запятую, в формате, который соответствует их типу. 

Для пустого массива содержимое будет {0}. Если в массиве только одно число 12, то его содержимое равно {1,{"N",12}}. Эти же примеры полностью: 

  • {"#",51e7a0d2-530b-11d4-b98a-008048da3034,{0}}
  • {"#",51e7a0d2-530b-11d4-b98a-008048da3034,{1,{"N",12}}}

Примечание. Практика показала, что точное указание количества на платформах 8.х не важно. Главное написать в первой позиции описания какое-то целое число равное или больше нулю, а далее платформа будет создавать Массив по фактически перечисленным элементам; и при вызов у нового объекта метода Количество() вернет верное фактическое значение. Указание в позиции количества любого значения кроме положительного целочисленного значения или нуля будет приводить к ошибке чтения потока, а если указать отрицательное число - даже к краху платформы с записью дампа.

 
 Создание Массива (с игнорированием указанного количества)

 

Структура и Соответствие

Структура и Соответствие - это типы, которые позволяют получать некие значения по указанным ключам. Особенностью этих коллекций является то, что это фактически множество пар Ключа и Значения, которые можно обойти с помощью итератора в цикле. Внутреннее представление отражает эту особенность - описание состоит из троих параметров - "#", идентификатора (4238019d-7e49-4fc9-91db-b6b951d5cf8e или 3d48feae-a9c6-4c5a-a099-9eb6477630c6) и содержимого в фигурных скобках. Содержимое в свою очередь состоит из количества пар Ключ-Значение, а потом перечисления этих пар через запятую. Описывается пара Ключ-Значение с помощью фигурных скобок, где указаны через запятую значение Ключа и значение Значения. Пример описания пары Ключ-Значение вида ID=12 имеет вид: {{"S","ID"},{"N","12"}}

Примеры. Пустое соответствие: {"#",3d48feae-a9c6-4c5a-a099-9eb6477630c6,{0}}. Структура с двумя ключами: {"#",4238019d-7e49-4fc9-91db-b6b951d5cf8e,{2,{{"S","id_local"},{"N",10}},{{"S","id_global"},{"N",215}}}}

Примечание 1. Точно так же, как и в массиве, не важно какое именно целочисленное количество вы укажите (или ноль) - при парсинге платформа игнорирует ваше число и для созданного объекта указывает реальное фактическое. Указание любых других значений в позиции количества приведет к ошибке потока. Только в отличии от массивов - тут можно указывать отрицательное количество и это нормально отработает и не приведет к краху платформы.

 
 Пример создания Соответствия с игнорированием ошибок

Примечание 2. И еще важный момент - это соблюдение уникальности ключей. Если в описании сделать несколько пар Ключ-Значение, то в результирующую структуру попадет только один из них. В моих экспериментах - это была всегда первая найденная в текстовом описании пара, но не рекомендую надеяться, что так будет во всех версиях платформы.

 

Фиксированные массив, структура и соответствие

Как уже понятно из названия - это типы, которые не поддерживают изменение после своего создания. Они предназначены для использования в качестве параметров сеанса и в некоторых сценариях работы с формами - т.е. вероятнность встретится с ними в "выгрузке из 1С" очень мала. 

С точки зрения описания внутреннего формата фиксированные коллекции полностью идентичны своим обычным аналогам. Буквально стоит заменить в описании массива, структуры или соответствия идентификатор на нужный эквивалент и получаем уже фиксированную версию объектов.

Примечание 1. Все мои замечания относительно количества элементов остаются в силе. Все идентично вплоть до того, что отрицательное количество нормально принимается фиксированными структурой и соответствием, а попытка использовать его для создания фиксированного массива вызывает крах платформы.

Примечание 2. Кстати, на будущее получаем лайфхак. Средствами платформы можно преобразовывать только пары Структура с Фиксированной Структурой и Соответствие с Фиксированным Соответствием. Для остальных комбинаций нужно делать циклы и перекидывать пары Ключей-Значений в новый объект. С помощью внутреннего представления и замены идентификатора можно преобразовать намного быстрее, да еще и вернуть результат в ту же переменную :)

 
 Валидное описание Структуры с легкостью превращаем в описание Фиксированного Соответствия

 

Список значений

Это особый вид коллекции значений, который предназначен для формирования вариантов выбора для элементов форм или может самостоятельно выводится на форму или даже в отдельное окно. Интересно, что один заранее сформированный список значений может использоваться для формирования доступных значений в другом списке значений, который интерактивно редактирует пользователь. Так же с помощью специального объекта ОписаниеТипов можно ограничить диапазон допустимых значений (только справочник Склады, или только справочники Покупатели и Физлица, или только строки и числа с точностью до 2 знака). Еще значения иногда могут иметь вид непонятный для пользователей (к примеру, вложенные массивы или коды операций), то в списке поддерживается отдельное хранение понятного строкового представления. Так же для каждого значения можно назначить вывод пиктограммы (из библиотеки предопределенных или из конфигурации). Для необходимости отметить в списке некоторые из значений предусмотрено дополнительное поле, которое при показе связывается с флажком. Все вышеперечисленные возможности ожидаемо отразились на структуре хранимого значения.

Как и другие коллекции, данная состоит из троих параметров - "#", идентификатора (4772b3b4-f4a3-49c0-a1a5-8cb5961511a3) и содержимого в фигурных скобках. Содержимое списка значений состоит из таких элементов: число 6 (или другое целое число большего значения), идентификатор 1e512aab-1b41-4ef6-9375-f0137be9dd91 (можно любой другой), число 0 (или 1), число 0 (или любое другое целое число), описание перечня элементов списка в фигурных кавычках (см. ниже), описание доступных типов  или можно так {""}, признак наличия ограничивающего списка - 0 если нет и 1 если да. Если есть ограничивающий значения список, то добавляется еще один параметр - описание вложенного списка (то описание, которое начинается с 6).

Описание перечня элементов списка состоит из количества этих элементов (на самом деле подходит любое целое число) и через запятую описание строк списка в фигурных скобках. Строка списка состоит из двух параметров: идентификатор 1e512aab-1b41-4ef6-9375-f0137be9dd91 (если указать другой, то при парсинге этот элемент будет проигнорирован; при этом тот идентификатор, который был ранее после числа 6, может отличаться) и описание элемента списка в фигурных скобках.

Элемент списка состоит из таких параметров: текстовое описание элемента, отметка выбора (0 - нет, 1 - да), полное описание значения (если там был список, то описание начинается с "#"), описание пиктограммы (для пустого значения:  {4,0,{0},"",-1,-1,0,0,""} ). Далее еще могут быть параметры, но их отсутствие ни на что не влияет.

Пример. Если нужно сделать список значений из пары элементов 100 и 200, один из которых с пометкой, то в результате получаем такое описание:

{"#",4772b3b4-f4a3-49c0-a1a5-8cb5961511a3,
    {6,1e512aab-1b41-4ef6-9375-f0137be9dd91,0,0,

    {2,
        {
            1e512aab-1b41-4ef6-9375-f0137be9dd91,
            {"Сотня", 0,  {"N",100},  {4,0,{0},"",-1,-1,0,0,""} }
        },

        {
            1e512aab-1b41-4ef6-9375-f0137be9dd91,
            {"Двести", 1,  {"N",200},  {4,0,{0},"",-1,-1,0,0,""} }
        }
    },

    {""},0}
}
 
 Парсинг примера Списка Значений

 

Примечание. Может показаться, что 1e512aab-1b41-4ef6-9375-f0137be9dd91 из описания строки - это идентификатор строки списка, но на самом деле идентификатором строки является c27d99e0-2f53-11d5-a3be-0050bae0a776. Что же означает первый идентификатор (который после числа 6), я так и не смог выяснить.

 
 Тип строки списка значений

 

Таблица значений

Особенность этого вида коллекций заключается в наличии колонок. По-умолчанию, в колонку можно писать значения любого типа, но при необходимости можно ограничить допустимые типы. Еще каждой колонке можно назначить заголовок и ширину для красивого вывода на форму или для открытия в отдельном окне. В каждой строке таблицы можно заполнять ячейки соответствующих колонок, а явно неинициализированные ячейки примут значения по-умолчанию для типов своих колонок (если типы не заданы, то будет Неопределено). А еще таблицам значений можно создавать произвольные индексы по колонкам для дальнейшего быстрого поиска/сортировки. Эти особенности отразились на структуре хранимого значения.

Как и другие коллекции, данная состоит из троих параметров - "#", идентификатора (acf6192e-81ca-46ef-93a6-5a6968b78663) и содержимого в фигурных скобках. В содержимом таблицы первым параметром должно быть целое число 9 (на самом деле от 7 и выше), на втором месте блок с описанием колонок, на третьем месте блок с описанием строк, на четвертом месте блок с описание индексов.

В блоке описания колонок первым параметром идет количество колонок (тут важно, чтобы было реальное количество колонок!), а далее через запятую блоки описания конкретных колонок. В блоке описания конкретной колонки пять параметров - порядковый индекс (отсчет от нуля; порядок не обязательно прямой, но далее в описании строк нужно не ошибиться с позицией для размещения значения), внутренний идентификатор колонки по правилу наименования переменных (для обращения из программного кода), описание типа колонки (можно просто {""}), строка с заголовком для вывода на формы, ширина колонки для вывода на формы.

В блоке описания строк сначала идет магическое целое число, которое при значениях меньше 2 не создает строки у результирующего объекта ТаблицаЗначений (а при значениях выше не заметил никаких различий в поведении). Далее вторым параметром идет указание сколько колонок из описанных в блоке колонок требуется взять для создания результирующего объекта (т.е. можно описать больше, но взять не все - и они не будут доступны через свойство-коллекцию Колонки у результирующего объекта); после чего указываются через запятую парами индекс колонки для описания позиции значения из расположенного ниже описания строки и соответствующий ей индекс колонки из описания колонок. Таким образом для одной колонки получим {2,1,0,0,  ; для двух колонок {2,2,0,0,1,1 ; для троих можно так: {2,3,2,2,0,0,1,1 

Примечание. Если напутать порядок индексов для колонок и позиций в данных, то результат будет не совсем ожидаемый (я смог добиться результата, когда у таблицы из троих колонок в коллекции колонок было три идентичных описания, по которым получали одно и тоже значение, но если обратится через индексы, то можем прочитать "скрытые" колонки). Возможно эту фичу можно использовать для быстрого создания таблиц, где разным колонкам нужно дать одинаковое значение, но такую таблицу лучше не редактировать так как тут есть опасность в том, что, изменив значение в одной из колонок, получим изменение в других (разные колонки будут ссылаться на одну и туже ячейку в памяти).

Продолжаем описание блока строк. После перечисления всех нужных нам колонок, открывается новый блок - с содержимым строк. В этом блоке первым параметром снова идет магическое целое число, значение которого ни на что не влияет и может быть даже отрицательным (обычно 1). Вторым параметром блока содержимого строк идет число количества строк - важно указать правильное количество, так как при указании меньшего значения, вы столько и получите (потеряв остальные), а при указании большего числа получите только ошибку формата потока на попытке парсинга такого описания. После числа количества идут через запятую блоки самих строк, после которых указываются еще два числа - максимальный индекс использованной колонки и максимальный индекс строки (индексы начинаются с нуля и потому будут меньше количества на единицу) - хотя практика показала, что для корректного парсинга важно просто поставить там хоть какие-то два числа, хоть нули.

Блок строки таблицы снова открывает какое-то магическое целое число, которое просто должно быть (а дальше хоть в каждой строке свое и даже отрицательное подходит). Вторым параметром указывается индекс строки (практика показала, что этот параметр при загрузке игнорируется - можно хоть одинаковый указать во всех строках). Третьим параметром указывается количество описываемых значений в данной строке - важно, так как количество значений в строке может быть меньше или больше чем колонок (если меньше, то в результирующей таблице в ячейках получим значения по-умолчанию для типа колонок, а если больше чем сделано связок с колонками - лишние значения будут просто проигнорированы). После указания количества значений строки идет перечисление этих значений через запятую, а замыкает строку ноль - число без которого происходит ошибка парсинга (если вместо нуля другое число - тоже ошибка).

Третий блок описания таблицы значений - это блок индексов. По моему мнению, тут не нужно заморачиваться. Программист 1С сам должен решать, что делать с полученной из внешнего источника таблицей значений - если там понадобятся индексы, то пусть он их сам создаст. Если же веб-программисту для загрузки на сайт отдали описание с блоком индексов - при написании своего парсинга проигнорируйте тот блок. Блок без индексов выглядит так - {0,0}.

Пример. Теперь можно создать валидное представление таблицы значений. Для простоты возьмем выгрузку прайса, где есть только артикул и цена. Выйдет что-то такое:

{"#",acf6192e-81ca-46ef-93a6-5a6968b78663,
{9,
    {2,
        {0,"Артикул",{""},"",0},
        {1,"Цена",{""},"",0}
    },

    {2,2,0,0,1,1,
        {1,3,
            {2,0,2, {"S","Часы"}, {"N",750.99}, 0},
            {2,1,2, {"S","Кеды"}, {"N",150.99}, 0},
            {2,2,2, {"S","Шапка"}, {"N",200}, 0}
        },
    1,2},

    {0,0}
}}
 
 Парсинг примера Таблицы Значений

 

Дерево значений

Дерево Значений - это коллекция значений, которая очень похожа на Таблицу Значений. Принципиальное различие - это возможность подчинять строки друг к другу. Т.е. у каждой строки помимо свойств для хранения своих значений еще есть доступ к множеству описаний подчиненных строк. Второе отличие от таблицы - отсутствие индексов. В результате внутреннее представление получилось очень похожее на описание Таблицы Значений, но с небольшими различиями.

Как и другие коллекции, данная состоит из троих параметров - "#", идентификатора (e603c0f2-92fb-4d47-8f38-a44a381cf235) и содержимого в фигурных скобках. В содержимом первым параметром должно быть целое число 1 (на самом деле любое, даже отрицательное), на втором месте блок с описанием колонок, на третьем месте блок с описанием строк.

В блоке описания колонок первым параметром идет количество колонок (тут важно, чтобы было реальное количество колонок!), а далее через запятую блоки описания конкретных колонок. В блоке описания конкретной колонки пять параметров - порядковый индекс (отсчет от нуля; порядок не обязательно прямой, но далее в описании строк нужно не ошибиться с позицией для размещения значения), внутренний идентификатор колонки по правилу наименования переменных (для обращения из программного кода), описание типа колонки (можно просто {""}), строка с заголовком для вывода на формы, ширина колонки для вывода на формы.

В блоке описания строк сначала идет магическое целое число, которое при значениях меньше 2 не создает строки у результирующего объекта ДеревоЗначений (тут все как у ТаблицыЗначений). Далее вторым параметром идет указание сколько колонок из описанных в блоке колонок требуется взять для создания результирующего объекта, после чего указываются через запятую парами индекс колонки для описания позиции значения из расположенного ниже описания строки и соответствующий ей индекс колонки из описания колонок (снова все идентично ТаблицеЗначений - подробности смотрите ранее в соответствующем разделе).

После перечисления всех нужных нам колонок открывается новый блок - с содержимым строк. В этом блоке первым параметром снова идет магическое целое число, значение которого ни на что не влияет и может быть даже отрицательным (обычно 1). Вторым параметром блока содержимого строк идет число количества строк - важно указать правильное количество, так как при указании меньшего значения, вы столько и получите, потеряв остальные, а при указании большего числа получите только ошибку формата потока на попытке парсинга такого описания. После числа количества строк через запятую идут блоки самих строк, после которых указываются еще два числа - максимальный индекс использованной колонки и максимальный индекс строки (индексы начинаются с нуля и потому будут меньше количества на единицу) - хотя практика показала, что для корректного парсинга важно просто поставить там хоть какие-то два числа, хоть нули (снова все идентично ТаблицеЗначений).

Блок строки таблицы снова открывает какое-то целое число (значение не важно, как и в ТаблицеЗначений - обычно 2). Вторым параметром указывается индекс строки (практика показала, что этот параметр при загрузке игнорируется - можно хоть одинаковый указать во всех строках). Третьим параметром указывается количество описываемых значений в данной строке - важно, так как количество значений в строке может быть меньше или больше чем колонок (если далее указать не то количество значений как мы тут объявили, то получим ошибку парсинга). После указания количества значений строки идет перечисление этих значений через запятую. Потом идет признак наличия подстрок: 0 - нет подстрок и на том описание блока строки закрывается, 1 - подстроки есть и далее следует еще один параметр с блоком описания этих подстрок.

Пример. На практике все довольно просто. Пусть мы хотим описать состав склада по категориям: у нас есть какие-то запчасти в количестве 1000 штук и бытовая техника из 100 изделий, где 10 видов утюгов, 20 стиральных машин, 30 холодильников и 50 смартфонов, из которых 10 моделей айфонов и 40 - андроидов. Описание дерева значений получается таким:

{"#",e603c0f2-92fb-4d47-8f38-a44a381cf235,
{1,
    {2,
        {0,"Категория",{""},"",0},
        {1,"Количество",{""},"",0}
    },
    {2,2,0,0,1,1,
        {1,2,
            {2,0,2, {"S","Запчасти"}, {"N",1000}, 0 },
            {2,1,2, {"S","Бытовая техника"}, {"N",100}, 1,
                {1,4, 
                    {2,2,2, {"S","Утюги"}, {"N",10}, 0},
                    {2,3,2, {"S","Стиральные машины"}, {"N",20}, 0},
                    {2,4,2, {"S","Холодильники"}, {"N",30}, 0},
                    {2,5,2, {"S","Смартфоны"}, {"N",50}, 1,
                        {1,2,
                            {2,6,2, {"S","iPhone"}, {"N",10}, 0},
                            {2,7,2, {"S","Android"}, {"N",40}, 0}
                        }
                    }
                }
            }
        },1,7
    }
}
}
 
 Парсинг примера Дерева Значений

 

Ссылочные типы.

Если в выгрузке "формата 1С" вам попадается сущность вида: {"#", <идентификатор_1>, <число>: <идентификатор_2>}, то знайте - вам передали значение ссылочного вида, назначение которого ссылаться на конкретное значение в базе данных. Если вы запрашиваете данные по задолженностям, то вам запросто могут выдать таблицу, где вместо кодов клиентов и номеров документов будут именно такие значения.

В представлении ссылочного типа первым идентификатором указывается идентификатор вида данных, который хранится в конфигурации 1С, а второй идентификатор - то указатель внутри таблицы данных, которая связана с указанным видом данных. Еще в представлении есть число - это суффикс физической таблицы, которая создается в СУБД для хранения содержимого данного вида данных.

Сформировать подобное значение можно и без функции ЗначениеВСтрокуВнутр(). Для того нужно выгрузить конфигурацию и выбрать для интересующего вида данных идентификатор для ссылочного типа (там могут быть и другие идентификаторы - объектов, менеджеров, выборок и т.д.). Далее с помощью ПолучитьСтруктуруХраненияБазыДанных() узнаем суффикс. А последний идентификатор получаем из ссылки с помощью метода УникальныйИдентификатор() с последующим преобразование в перевернуты формат (тут подробно про форматы идентификаторов в 1С).

Давайте попробуем получить ссылочное представление для Доллара из справочника Валюты. Начнем с выгрузки конфигурации (стандартная выгрузка в файлы или с помощью v8unpack.exe) и получения идентификатора ссылочного типа справочника Валюты - это 3a87ef2a-9de1-4d34-9e5f-3c8cdf53b3ab

 
 скриншот файла описания справочника Валюты

Далее узнаем, что в СУБД суффикс справочника валют будет 53

 
 скриншот получения суффикса

И узнаем идентификатор из ссылки: 68c80f28-24ce-11e6-8f41-e91b9a8c6dd6 -> 8f41e91b9a8c6dd611e624ce68c80f28

 
 скриншот получения идентификатора из ссылки

В результате получаем конструкцию вида: {"#", 3a87ef2a-9de1-4d34-9e5f-3c8cdf53b3ab,  53: 8f41e91b9a8c6dd611e624ce68c80f28}, верность которой проверяем с помощью ЗначениеИзСтрокиВнутр()

 
 скриншот проверки описания ссылочного типа

 

Заключение.

Я описал далеко не все, что можно представить во внутреннем формате. За рамками статьи остались Запросы, Выборки, Списки, Формы, Графические Схемы, Схемы Компоновки, Элементы Отборов и многое другое. Но я надеюсь, что смог именно для целей обмена дать все необходимые базовые знания по внутреннему формату 1С.

Чтобы любой мог попробовать повторить примеры из статьи, самостоятельно поэкспериментировать (особенно в плане создания таблиц с невидимыми колонками) или проверить валидность результатов выгрузки из внешней системы, я добавляю свою обработку, с которой делал скриншоты для этой статьи. Обработка поддерживает работу в обычном и управляемом интерфейсе. Для демонстрации там уже есть вставка всех популярных типов значений. Результаты парсинга показываются в понятном виде. А если хочется программно создать какой-то произвольный объект и увидеть его представление, то в обработке есть вкладка "Исполняемый код", где нужно установить требуемое значение переменной Значение.

Обработка была проверена в режимах управляемого и обычного интерфейсов на платформе 8.3.15.1565. Но должна работать и на прочих 8.3.* 

 
 Пример проверки данных, которые озадачили PHP-программиста на Toster.ru
131

Скачать файлы

Наименование Файл Версия Размер
Обработка для изучения формата внутреннего представления данных
.epf 14,23Kb
06.09.19
3
.epf 1.0 14,23Kb 3 Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. Evil Beaver 6240 07.09.19 09:22 Сейчас в теме
Кто-то наконец уже должен был это задокументировать!
Несмотря на общеизвестность формата и его простоту, до сих пор не было твердой копии описания. Спасибо.

Однако
задачи обмена именно в "формате 1С"
- это как? Почти 2020 год на дворе, Скайнет давно победил людишек, а у нас на полном серьезе требуют обмен в формате сериализации 1С? Поделитесь, зачем? Кому это нужно?
3. Perfolenta 165 07.09.19 12:08 Сейчас в теме
(1) бывает удобно иногда... например, мне однажды понадобилось загрузить для анализа данные из 7.7 в 8, а в 7.7 уже был отчет, внутри которого формировалась ТЗ как раз с нужными данными...
Еще из Native ВК передавать данные в 1с тоже было бы удобно в некоторых случаях...
Если были бы полноценные чтение/запись такого формата во внешнем мире, то пригодились бы при случае...
4. Dementor 566 07.09.19 15:15 Сейчас в теме
(1)

у нас на полном серьезе требуют обмен в формате сериализации 1С? Поделитесь, зачем? Кому это нужно?

Может так же как в моем случае просто обмен во внутреннем формате быстрее? Или может там такой объем данных, что при записи в JSON/XML оперативки не хватает и сильно свопит? ХЗ, просто есть факт потребности. А если есть потребность, то пусть будет статья.
Fox-trot; +1 Ответить
11. Evil Beaver 6240 09.09.19 11:46 Сейчас в теме
(4) сильно сомневаюсь что формат "скобок" обгоняет по требованиям к памяти, например CSV. Да и Json радикально не сильно тяжелее будет
8. Yashazz 2521 09.09.19 11:03 Сейчас в теме
(1) Знаете, я это в 2008-м задокументировал, и меня забанили на одном замечательном интернет-ресурсе, посвящённом 1С. Да ещё злобно наезжали, что мол, не рекомендует 1С это делать, вот и нехрен делать.
Так что, есть такая документация. У меня на старом харде. 11 лет уже, как есть, и полагаю, мало что с той поры изменилось. А вот желающие прыгать на грабли и огребать за нарушение рекомендаций, смотрю, всё не переводятся) То к скулю напрямую обращаются, то во внутренний формат лезут...
Evil Beaver; +1 Ответить
15. Dementor 566 09.09.19 15:18 Сейчас в теме
(8) спасибо за мнение. Но есть такое понятие как "целевая аудитория", к которой вы просто не относитесь. Лично для меня было интересно пройтись по недокументированному "минному полю" - узнал некоторые интересные вещи о платформе.

P.S. О том, что 1С не рекомендует использовать данный механизм я сам написал в статье и тем самым предупредил читателей.
P.S.S. Тут не "замечательный интернет-ресурс", так как моя статья успешно прошла премодерацию. Можно выдыхать :)
2. for_sale 769 07.09.19 10:10 Сейчас в теме
Не совсем понял посыл статьи, кроме общего описания формата. Но, вдохновлённый фразой "быстрый массив", решил проверить на одном месте в моём коде, где несколько десятков тысяч строк сохраняются в джейсон, а потом - в файл, и, соответственно, обратно извлекаются по надобности.

В общем, извлечение джейсона (стандартными средствами платформы) такого объёма заняло 5 секунд, ЗначениеИзСтрокиВнутр - 12 секунд.
21. fomix 26 11.09.19 11:30 Сейчас в теме
(2) Что JSON, что XML (он же внутр.формат) - суть не важно, все равно и то и другое избыточно по составу. Другое дело скорость выгрузки из 1С всего этого бреда! А тут еще попутно надо #С-нику или PHP-шнику объяснять что и как в 1С устроено.
6. SeiOkami 1095 08.09.19 08:34 Сейчас в теме
(0), очень интересный материал, спасибо за публикацию!
7. VIA_1C 50 09.09.19 08:48 Сейчас в теме
(0) Автору спасибо за наглядное и очень понятное изложение материала. Так и хочется сказать: аффтар пиши есчо! ))
9. Yashazz 2521 09.09.19 11:04 Сейчас в теме
(7) Автор, не пиши такого. Есть инструментарий, вот им и следует пользоваться. А не лазить, куда не рекомендовано.
10. VIA_1C 50 09.09.19 11:08 Сейчас в теме
(9) если Вы не умеете это "готовить", то это сугубо Ваши личные проблемы
12. Evil Beaver 6240 09.09.19 11:48 Сейчас в теме
(10) О, а вы разве умеете? Или вы просто не очень осознаете риски написания в продакшене парсера под недокументированный формат, являющийся, как говорят, "subject-to-change"?
13. VIA_1C 50 09.09.19 12:09 Сейчас в теме
(12) я возможности этого недокументированного формата еще в 7.7 использовал, при необходимости... А вообще каждый сам для себя решает нужно оно ему или нет. Мне - иногда нужно и не надо меня убеждать в том, что мне это не нужно. ))
Dementor; +1 Ответить
17. Yashazz 2521 09.09.19 22:35 Сейчас в теме
(13) Ну многие использовали, но флагом махать зачем? Я тоже много интересного могу рассказать про разницу между ЗначениеВСтроку и ЗначениеВСтрокуВнутр, особенно в плане производительности, и?
16. Yashazz 2521 09.09.19 22:34 Сейчас в теме
(10) Я-то дофига всего умею. Но если предлагают готовить крысятину, то да, я скажу, "не надо" и меня поддержат, несмотря на отличное изложение книги "101 рецепт из крысиного мяса".
14. vadim1011985 65 09.09.19 13:01 Сейчас в теме
Этот формат очень похож (или даже скорее всего это он и есть) на формат описания самой конфигурации 1с (легко можно убедиться через Tools 1CD посмотрев таблицу CONFIG поле BINARYDATA)
Dementor; +1 Ответить
19. DrZombi 10.09.19 11:04 Сейчас в теме
(0) Бред, форменный... поставил бы минус, но вам повезло :)
20. Dementor 566 10.09.19 14:08 Сейчас в теме
(19) Борис Георгиевич, перелогиньтесь :))
22. Alex17 12.09.19 16:16 Сейчас в теме
Еще бы автор выложил функцию, которой бы заменой значений можно было исправить типы, ссылочных данных, при перегрузках на разных конфигурациях 1С, цены бы не было. За статью спасибо.
23. Dementor 566 13.09.19 10:27 Сейчас в теме
(22) написать можно, но как вы видите такую замену? Идентификаторы объектов метаданных - разные, уникальные идентификаторы ссылок - тоже разные. Когда мы значение ссылки открываем в чужой базе, мы не только не сможем определить, что это был за справочник, но даже не сможем понять, что это вообще Справочник, а не Документ или характеристика.

Можно выгружать метаданные из первой конфигурации, с помощью дополнительной обработки делать сопоставление объектов во второй (как минимум по наименованию и типу), а далее эту настройку использовать при загрузке. Но что, если нужных уидов не будет? В загруженных документах будет "объект не найден".
24. Alex17 13.09.19 10:56 Сейчас в теме
У меня задача проще. Был перенос данных из УПП в БУХ 3.0. в основном все справочники перенесли по UID. Сейчас идет выверка данных и постобработка документов ввода остатков. Очень удобно сохранить в исходнике во внешний файл срез регистра в ТЗ и потом читать в приемнике. Напр:
UID источника d4ba178b-3934-11e1-a59a-18a905e37a56
ЗначениеВСтрокуВнутр {"#",a86305d7-b8a1-4312-901f-2ade3efdb351,164:a59a18a905e37a5611e13934d4ba178b}

UID приемника d4ba178b-3934-11e1-a59a-18a905e37a56
ЗначениеВСтрокуВнутр {"#",c92a1124-c41e-42a4-bda7-3bbac9c6039e,188:a59a18a905e37a5611e13934d4ba178b}

Имея данную информацию можно ли сделать подмену
25. Alex17 13.09.19 11:19 Сейчас в теме
Правильно ли я понимаю что достаточно во всех местах заменить {"#",a86305d7-b8a1-4312-901f-2ade3efdb351,164: на {"#",c92a1124-c41e-42a4-bda7-3bbac9c6039e,188: и ссылка будет работоспособной, у меня не прокатило.
26. Alex17 13.09.19 11:35 Сейчас в теме
Вопрос снят, еще раз спасибо, помогло разобраться.
27. Alex17 13.09.19 18:28 Сейчас в теме
Вот собственно функции, может кому понадобятся

Процедура КоманднаяПанельРезультатСохратьТаблицуВФайлУниверсальная(Кнопка)
	
	ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
	ДиалогВыбораФайла.Фильтр = "Файл данных (*.txt)|*.txt";
	ДиалогВыбораФайла.Расширение = "txt";
	ДиалогВыбораФайла.ПредварительныйПросмотр = Ложь;
	ДиалогВыбораФайла.ИндексФильтра = 0;
	
	Если ДиалогВыбораФайла.Выбрать() Тогда
		
		СоответствиеЗамены = ПолучитьСоответствиеЗамены();
		Т = ЗначениеВСтрокуВнутр(РезультатТаблица);
		Для каждого стр Из СоответствиеЗамены Цикл
			Т = СтрЗаменить(Т,стр.Значение,стр.Имя);
			Т = СтрЗаменить(Т,Лев(стр.Значение,36),"_" + стр.Имя);    
		КонецЦикла; 
		
		 ТекстовыйФайл = Новый ТекстовыйДокумент;
		 ТекстовыйФайл.УстановитьТекст(Т);
		 ТекстовыйФайл.Записать(ДиалогВыбораФайла.ПолноеИмяФайла);
		 
	КонецЕсли;
КонецПроцедуры

Процедура КоманднаяПанельРезультатЗагрузитьТаблицуИЗФайлаУниверсальная­(Кнопка)
	
	ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
	ДиалогВыбораФайла.Фильтр = "Файл данных (*.txt)|*.txt";
	ДиалогВыбораФайла.Расширение = "txt";
	ДиалогВыбораФайла.ПредварительныйПросмотр = Ложь;
	ДиалогВыбораФайла.ИндексФильтра = 0;
	
	Если ДиалогВыбораФайла.Выбрать() Тогда
		ТекстовыйФайл = Новый ТекстовыйДокумент;
		ТекстовыйФайл.Прочитать(ДиалогВыбораФайла.ПолноеИмяФайла);
		Т = ТекстовыйФайл.ПолучитьТекст();
		СоответствиеЗамены = ПолучитьСоответствиеЗамены();
		Для каждого стр Из СоответствиеЗамены Цикл
			Т = СтрЗаменить(Т,"_" + стр.Имя,Лев(стр.Значение,36));
			Т = СтрЗаменить(Т,стр.Имя,стр.Значение);
		КонецЦикла;
		
		РезультатТаблица = ЗначениеИзСтрокиВнутр(Т);
		
		ЭлементыФормы.ТаблицаРезультата.СоздатьКолонки();
		ЭлементыФормы.КоманднаяПанельРезультат.Кнопки.ВыполнитьАлгоритм.Доступность = Истина;
	КонецЕсли;  
	ЭлементыФормы.НадписьКолСтрокРезультата.Заголовок = СокрЛП(РезультатТаблица.Количество()) + " строк";

КонецПроцедуры

Функция ПолучитьСоответствиеЗамены()
	
	СоответствиеТипов = Новый ТаблицаЗначений;
	СоответствиеТипов.Колонки.Добавить("Имя");
	СоответствиеТипов.Колонки.Добавить("Значение");
	
	ЗапросТекстИтог = "";
	
	Для каждого стр Из Метаданные.Справочники Цикл
		ЗапросТекст = 
		"ВЫБРАТЬ ПЕРВЫЕ 1
		|	Ссылка,
		|	""<Имя>"" КАК Имя
		|ИЗ
		|	<Имя>
		|ОБЪЕДИНИТЬ ВСЕ
		|/////////////////////////////
		|";
		ЗапросТекст = СтрЗаменить(ЗапросТекст, "<Имя>", "Справочник." + стр.Имя);
		
		ЗапросТекстИтог = ЗапросТекстИтог + ЗапросТекст;
		
	КонецЦикла;
	
	//Для каждого стр Из Метаданные.Документы Цикл
	//	ЗапросТекст = 
	//	"ВЫБРАТЬ ПЕРВЫЕ 1
	//	|	Ссылка,
	//	|	""<Имя>"" КАК Имя
	//	|ИЗ
	//	|	<Имя>
	//	|ОБЪЕДИНИТЬ ВСЕ
	//	|/////////////////////////////
	//	|";
	//	ЗапросТекст = СтрЗаменить(ЗапросТекст, "<Имя>", "Документ." + стр.Имя);
	//	
	//	ЗапросТекстИтог = ЗапросТекстИтог + ЗапросТекст;
	//	
	//КонецЦикла;
	
	ЗапросТекстИтог = Лев(ЗапросТекстИтог,СтрДлина(ЗапросТекстИтог) - 45);
	
	Запрос = Новый Запрос;
	Запрос.Текст = ЗапросТекстИтог;
	ТЗ = Запрос.Выполнить().Выгрузить();   
	
	Для каждого стр Из ТЗ Цикл
		//"{"#",0b5f521e-459d-4962-86a6-f3a45fe61010,34:810100155d08eb1f11e7e01fe335f24f}"
		Значение = ЗначениеВСтрокуВнутр(стр.Ссылка);
		раз = Найти(Значение,":");
		НоваяСтрока = СоответствиеТипов.Добавить();
		ЗаполнитьЗначенияСвойств(НоваяСтрока,стр);
		НоваяСтрока.Значение = Сред(Значение,6,раз - 6);
	КонецЦикла; 
	
	Возврат СоответствиеТипов;
	
КонецФункции
Показать
Оставьте свое сообщение