IE2017

Вытаскиваем метаданные из буфера обмена 1С

Программирование - Практика программирования

Однажды мне стало интересно, как 1С:Предприятие хранит в буфере обмена метаданные при их копировании в конфигураторе. Решил провести маленькое исследование. :)

Буфер обмена Windows поддерживает несколько стандартных форматов данных. Например текст и точечные рисунки. Кроме того, приложение может зарегистрировать свой собственный формат и скорее всего 1С:Предприятие пользуется этой возможностью.

Давайте проверим это! Узнать, какие форматы в данный момент содержит буфер обмена, можно с помощью стандартной утилиты Windows 2k/XP clipbrd.exe

Запускаем утилиту и давим ctrl+c на любом объекте дерева метаданных в конфигураторе:

clipbrd.exe

Что мы видим?

Я копировал реквизиты документа, и платформа поместила в буфер текст "Реквизиты". Кроме того, в меню "Вид" помимо стандартных форматов присутствуют 4 формата с префиксом "1С". Видимо это то что нам нужно :)

Плохо только, что эти форматы недоступны для просмотра. Что в общем логично, т.к. внутреннее их устройство "знает" только платформа 1С.

Чтобы увидеть содержимое нестандартных форматов нам понадобится более функциональная утилита. Например CLCL, которая умеет показывать неизвестные ей форматы в шестнадцатиричном виде. Скачать ее можно по этой ссылке: http://www.nakka.com/soft/clcl/index_rus.html

CLCL при запуске сворачивается в трей с иконкой в виде канцелярской скрепки. Открыть основное окно утилиты можно щелчком на этой скрепке.

ОК. Что нам показывает CLCL?

CLCL

Наиболее интересен формат "1C:MD8 Data". У него говорящее название и данных он содержит больше, чем все остальные форматы, вместе взятые. Внутри явно текст, только CLCL не знает кодировку. Щелкаем правой кнопкой мыши на подопытном формате, выбираем "сохранить как", и сохраняем в текстовый файл.

1С:MD8 Data

Открываем в любимом текстовом редакторе (на скрине Sublime Text 2) и видим, что это действительно текст. Кодировка "родная" для платформы 1С UTF-8 с маркером порядка байтов (BOM)

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

Структура текста довольно простая. Это простой список внутри фигурных скобок. Элементы списка разделяются запятыми. В качестве элементов списка выступают: числа, строки в кавычках, UID'ы и вложенные списки.

Довольно легко можно написать парсер. Например так:

Функция РазобратьТекст(Источник) Экспорт
   
Дерево = Новый ДеревоЗначений;
   
Дерево.Колонки.Добавить("Значение");
   
ТекущаяСтрока = Дерево.Строки.Добавить();
   
ТекущаяСтрока = ТекущаяСтрока.Строки.Добавить();
   
ТекущийРодитель = ТекущаяСтрока.Родитель;
   
ИсходныйТекст = Источник.ПолучитьТекст();
   
КоличествоСимволов = СтрДлина(ИсходныйТекст);
   
ТекущийСимвол = "";
   
Позиция = 0;
    Пока
ТекущийСимвол <> "{" И Позиция < КоличествоСимволов Цикл
       
Позиция = Позиция + 1;
       
ТекущийСимвол = Сред(ИсходныйТекст, Позиция, 1);
    КонецЦикла;
    Если
ТекущийСимвол = "{" Тогда
       
Буфер = "";
       
РежимЧтенияСтроки = Ложь;
        Пока
Позиция < КоличествоСимволов Цикл
           
Позиция = Позиция + 1;
           
ТекущийСимвол = Сред(ИсходныйТекст, Позиция, 1);
            Если НЕ
РежимЧтенияСтроки И ТекущийСимвол = "{" Тогда
               
ТекущийРодитель = ТекущаяСтрока; ТекущийРодитель.Значение = "{...}";
               
ТекущаяСтрока = ТекущаяСтрока.Строки.Добавить();
            ИначеЕсли НЕ
РежимЧтенияСтроки И ТекущийСимвол = "," Тогда
                Если
Буфер <> "" Тогда
                   
ТекущаяСтрока.Значение = Буфер;
                   
Буфер = "";
                КонецЕсли;
               
ТекущаяСтрока = ТекущийРодитель.Строки.Добавить();
            ИначеЕсли НЕ
РежимЧтенияСтроки И ТекущийСимвол = "}" Тогда
                Если
Буфер <> "" Тогда
                   
ТекущаяСтрока.Значение = Буфер;
                   
Буфер = "";
                КонецЕсли;
               
ТекущаяСтрока = ТекущийРодитель;
               
ТекущийРодитель = ТекущаяСтрока.Родитель;
            ИначеЕсли
ТекущийСимвол = """" Тогда
               
РежимЧтенияСтроки = НЕ РежимЧтенияСтроки;
               
Буфер = Буфер + ТекущийСимвол;
            ИначеЕсли
РежимЧтенияСтроки Тогда
               
Буфер = Буфер + ТекущийСимвол;
            ИначеЕсли НЕ
ПустаяСтрока(ТекущийСимвол)
                ИЛИ (
ТекущийСимвол = " ") Тогда
               
Буфер = Буфер + ТекущийСимвол;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    Возврат
Дерево;
КонецФункции

Парсер1С8х

Теперь у нас есть возможность "пощупать" структуру файла :)

На скрине видно, что корневой список содержит два вложенных списка. В первом из них хранится количество (первый элемент) и список реквизитов. Каждый реквизит предсталяет из себя структуру свойств. Т.е. все свойства реквизита, которые можно изменить в конфигураторе. Расположение свойств в структуре можно выяснить простым сравнением файлов до и после изменения этих самых свойств.

Пришло время воспользоваться нашими знаниями!

Мы сделаем утилиту в виде скрипта, которая будет вытаскивать имена реквизитов из метаданных в буфере обмена и помещать их обратно уже в виде простого текста.

Писать скрипт будем на языке Lua
Скачать дистрибутив для Windows можно по этой ссылке: http://code.google.com/p/luaforwindows/downloads/list

Кроме того нам нужна библиотека для работы с буфером обмена: http://files.luaforge.net/releases/jaslatrix/clipboard/1.0.0 
(установить можно простым копированием clipboard.dll в каталог "...\Lua\5.1\clibs\")

Язык Lua был выбран не просто так. Дело в том, что в Lua основным типом данных является хэш-таблица (в 1С аналогом является соответствие), которая имеет очень похожий на нашу структуру конструктор. Выглядит это так:

tbl = {1, 2, "three", {4, 5}}

Таблицу можно обойти циклом:

for key, value in pairs(tbl) do
 print(key, value)
end

Проверить, как работает этот код, можно с помощью онлайн интерпретатора: http://repl.it/Emn/1

На языке 1С это выглядело бы так:

Таблица = Новый Соответствие;
Таблица[1] = 1;
Таблица[2] = 2;
Таблица[3] = "three";
Таблица[4] = Новый Соответствие;
Таблица[4][1] = 4;
Таблица[4][2] = 5;

Для Каждого
Элемент Из Таблица Цикл
   
Сообщить("" + Элемент.Ключ + " " + Элемент.Значение);
КонецЦикла;

Т.е. Lua автоматически назначает целочисленные ключи, начиная с единицы. Таким образом, луашная таблица может эмулировать массив (настоящих массивов в Lua нет). Ключи можно и явно указывать, но нам сейчас это не нужно. Интересующиеся могут почитать документацию: http://www.lua.ru/doc/

Итак, давайте для начала вытащим из буфера обмена текст в формате 1С:MD8 Data

require'clipboard'-- подключаем библиотеку
format={}-- создаем пустую таблицу для хранения соответствия [ИмяФормата - КодФормата]
for k,v in ipairs(clipboard.getformats()or{})do
    formatname = clipboard.formatname(v)
    if formatname then
        format[formatname]= v
    end
end

data = clipboard.getdata(format["1C:MD8 Data"]) -- получаем данные

if data then
    print(data)
end

Теперь нужно заставить Lua работать с этими данными как с родной хэш-таблицей. В этом деле нам поможет одна луашная фича. У нас есть возможность динамически загрузить функцию из строки содержащей луашный код. Например так:

f = loadstring("i = i + 1") -- получаем функцию 
i 0
f()print(i--> выводит 1
f()print(i--> выводит 2

Чтобы функция возвращала значение, нужно добавить return:

f = loadstring("return i + 1") -- получаем функцию возвращающую значение
i 0
i = f(); print(i) --> выводит 1
i = f(); print(i) --> выводит 2

Также функция может создать и вернуть таблицу:

f = loadstring("return {1, 2, 'three', {4, 5}}")
tbl = f()
for key, value in pairs(tbl) do
    print
(key, value)
end

Думаю, вы уже догадались что мы будем делать :)

f = loadstring("return"..data) -- прим.: две точки означают конкатенацию строк
tbl = f()

Но это не будет работать... :(

Lua не понимает UID'ы в тексте и кроме того она не знает UTF-8. К счастью, обе проблемы легко победить. Все UID'ы в тексте можно найти с помощью регулярного выражения и заменить на пустые таблицы {} например.
А кодировка UTF-8 по большому счету вообще не проблема, т.к. все важные для разбора символы кодируются в ней одним байтом и не отличаются от таковых в ASCII. Нам будет мешать только маркер порядка байтов (BOM), но мы можем просто проигнорировать первые три байта:

s = "123456"
print(s:sub(4)) --> 456

Когда мы наконец получим метаданные в виде луашной таблицы, можно будет обращаться к любому элементу примерно так:

print(tbl[1][2][2][1][2][2][2][3])

Этот код выведет на экран имя первого реквизита.

Чтобы узнать "адрес" нужного вам элемента, можете воспользоваться этим принтером таблиц:

function print_r (t, indent, done)
    done = done or {}
    indent = indent or ''
    local nextIndent -- Storage for next indentation value
    for key, value in pairs (t) do
        if type
(value) == "table" and not done[value] then
            nextIndent = nextIndent or
                (indent .. string.rep(' ',string.len(tostring(key))+2)) -- Shortcut conditional allocation
            done[value] = true
            print
(indent .. "[" .. tostring(key) .. "] => Table");
            print_r(value, nextIndent .. string.rep(' ',2), done)
        else
            print
(indent .. "[" .. tostring (key) .. "] => " .. tostring(value).."")
        end
    end
end

Эта функция выводит таблицу в таком виде (жирным выделен путь к имени первого реквизита):

[1] => Table
     [1] => 4
     [2] => Table
          [1] => Table
          [2] => Table
               [1] => Table
                    [1] => 3
                    [2] => Table
                         [1] => 25
                         [2] => Table
                              [1] => 2
                              [2] => Table
                                   [1] => 0
                                   [2] => Table
                                        [1] => 0
                                        [2] => 0
                                        [3] => Table
                                   [3] => ВалютаДокумента
                                   [4] => Table
                                        [1] => 1
                                        [2] => ru
                                        [3] => Валюта документа
                                   [5] => (Общ)
                              [3] => Table
                                   [1] => Pattern
                                   [2] => Table
                                        [1] => #
                                        [2] => Table
и т.д. 


Ну вот и все :) Готовый к использованию скрипт приложен к данной публикации.


Надеюсь, было интересно Smile

Публикация смежной тематики: Создание табличных частей объектов конфигурации

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

Наименование Файл Версия Размер
clipb_1с.wlua
.wlua 0,56Kb
24.11.12
16
.wlua 0,56Kb 16 Скачать
Парсер1С8.epf
.epf 8,29Kb
24.11.12
9
.epf 8,29Kb 9 Скачать

См. также

Комментарии
1. Олег Сорокин (Oleg_nsk) 137 25.11.12 14:51 Сейчас в теме
Познавательно. Но зачем это может понадобиться? Не могли бы в начале обозначить спектр задач где ваши научные исследования структуры буфера обмена 1с могут пригодится простому разработчику.
2. Борис Илов (ilov_boris) 159 25.11.12 16:40 Сейчас в теме
(1) Oleg_nsk, я не очень понимаю что именно вы подразумеваете под словами "простой разработчик", но думаю что любому грамотному специалисту не составит труда найти практическое применение своим знаниям. Простейший вариант освещен в публикации (вытаскивание списка имен реквизитов для вставки в код например...)
Можно придумать кучу различных скриптов, помогающих в повседневной работе. Например можно написать скрипт, генерирующий код процедуры-шаблона для создания нового документа и заполнения всех его реквизитов. Скрипт будет брать необходимую информацию из буфера обмена, а обратно помещать код на встроенном языке.

Я не предлагаю конкретное решение, а только лишь показываю как это работает и как это можно использовать.
3. Сергей Сытько (8SiriuS8) 115 25.11.12 19:20 Сейчас в теме
В снегопате можно использовать такую схему. там как раз не хватает такого плана скриптов.
4. Михаил Ражиков (tango) 474 25.11.12 19:40 Сейчас в теме
(3) 8SiriuS8, тут фишка как раз в том, что снегопат может покурить
ilov_boris; +1 Ответить 1
5. Сергей (sstar90) 26.11.12 16:31 Сейчас в теме
Плюс. Надо попробовать для общего развития
ilov_boris; +1 Ответить
6. Александр Орефков (orefkov) 1464 28.11.12 09:48 Сейчас в теме
(3)
В снегопате это делается так:
Код
var file = metadata.current.rootObject.childObject("Документы", "АвансовыйОтчет").saveToFile()
file.seek(0, fsBegin)
Message(file.getString(dsUtf8))
Показать полностью

и все.
awa; bulpi; +2 Ответить 1
7. Александр Орефков (orefkov) 1464 28.11.12 09:49 Сейчас в теме
(4)
А курить я больше года как бросил :)
8. Владимир Чаклин (vec435) 15 28.11.12 22:51 Сейчас в теме
а если на основе Clcl сделать ВК то и использовать можно как снегопат
9. Борис Илов (ilov_boris) 159 28.11.12 23:55 Сейчас в теме
(8) vec435, ВК - это уже режим предприятия (да и зависимость от оного). А Снегопат и скрипты на Lua позволяют получить профит находясь в конфигураторе.
ВК и обработку несущую такой функционал конечно можно сделать, но это будет дико неудобно.
10. Борис Илов (ilov_boris) 159 29.11.12 00:14 Сейчас в теме
На этих выходных постараюсь еще пару скриптов сделать, чтобы больше осветить возможности.
Если у кого есть идеи, то высказывайтесь здесь. Попробую реализовать предложения :)

зы И еще... могу описать более подробно как установить и использовать Lua в связке с Sublime Text 2.
11. Александр Орефков (orefkov) 1464 29.11.12 00:28 Сейчас в теме
(10)
Из идей - прикрутить это к autoit (где-то тут был на нем мини опенконф для восьмерки)
12. Александр Орефков (orefkov) 1464 29.11.12 00:32 Сейчас в теме
И кстати да, в (6) описано было, как получить то, что в первой части статьи - внутреннее представление.
А список реквизитов там проще получается, без парсинга списка.
13. Борис Илов (ilov_boris) 159 29.11.12 00:33 Сейчас в теме
(11) orefkov, возможно я ошибаюсь, но в autoit вроде только текст из буфера обмена можно вытащить...
14. Борис Илов (ilov_boris) 159 29.11.12 00:43 Сейчас в теме
(11) "где-то тут был на нем мини опенконф для восьмерки"
Покажите где оно лежит плиз. Интересно однако :)
15. Александр Орефков (orefkov) 1464 29.11.12 00:51 Сейчас в теме
(13)
Ну так потом можно текст как-то обработать, поместить в буфер и послать контрол v
(14)
http://infostart.ru/public/65526/ здесь упоминается
16. Владимир Чаклин (vec435) 15 29.11.12 08:21 Сейчас в теме
при копирование объектов из конфы в конфу по Cntr+C копируется целиком объект.значит из буфера можно по-идее все вытащить.что-то вроде UnPack может получится прямо из конфигуратора. а обработать удобнее в предприятии.
17. Александр Орефков (orefkov) 1464 29.11.12 08:50 Сейчас в теме
Кстати, посмотрел, что лежит в "1C:MD8 External Data"
Если преобразовать из base64, получим текст модуля документа, в формате, понимаемом v8unpuck.
Но мне вот что интересно.
Если я в одной конфе нажал Ctrl+C допустим на документе, а в другой Ctrl+V, то док вставляется весь - все формы, модули и т.п. Но в клипборде я что-то не вижу этой инфы, а только структуру метаданных, да модуль дока. Откуда это передается?

А если я скопировал док, а в другой конфе встал на узел "Последовательности" и нажал Ctrl+V, то добавляется новая последовательность, с именем как у дока, а реквизиты дока стают измерениями последовательности.
18. Александр Орефков (orefkov) 1464 29.11.12 10:42 Сейчас в теме
+(17)
Хотя нет, погорячился насчет того, что в "1C:MD8 External Data" только модуль документа. Невнимательно я декодировал. Там для всех вложенных объектов метаданных все лежит в таком же 1Сном фигурном списке. Все формы, модули и т.д. и т.п. Надо просто аккуратно декодировать.
19. Борис Илов (ilov_boris) 159 29.11.12 11:06 Сейчас в теме
(18) Хотел уже было ответить вам, но вы меня опередили :)
Да, там все есть. И про модуль в External Data я знаю.

Кстати можно сделать скрипт, который выведет отчет по назначенным обработчикам в модулях объектов. (ПриЗаписи, ПередУдалением и т.д. и т.п.) На Lua с ее строковой либой должно быть элементарно. :)
20. Борис Илов (ilov_boris) 159 29.11.12 11:16 Сейчас в теме
Посмотрите еще как "Роли" закодированы. Забавно там :)
Использовать парсер писанный на 1С для такого объема уже не айс.
21. Александр Орефков (orefkov) 1464 29.11.12 11:33 Сейчас в теме
(19)
Обработчики умеешь получать и для толстых и для упр форм?
В снегопате было бы хорошо иметь эту инфу, тогда в обработчиках уже стал бы известен тип параметров.
22. Борис Илов (ilov_boris) 159 29.11.12 18:46 Сейчас в теме
(21) orefkov, не знаю :) нужно пробовать
23. Владимир (oberon355) 11 02.12.12 11:30 Сейчас в теме
походу уже не осталось мест, куда не проникал еще пытливый взгляд 1с ника.
ilov_boris; +1 Ответить
24. Александр Романов (alexandr1972_1) 10.12.12 00:51 Сейчас в теме
Очень интересно, особенно в плане использования Lua. Поставим в закладки, потом разберёмся.
25. Олег Шалимов (CaSH_2004) 344 20.01.13 20:34 Сейчас в теме
Занятно, из областей применения возможно подойдет такая задача: тут встречалась задача что нужно новую обработку конвертировать из 8.2 в 8.1 или даже 8.0, предложено было решение копировать из конфигуратора 8.2 в конфигуратор 8.1/8.0, однако иногда были проблемы при переносе копированием, по всей видимости из-за разной структуры. Этот механизм мог бы помочь решить данную проблему, но по сути проще самому все нарисовать чем все изучить и запрограммировать.
Интересно, а обратного конвертера так никто и не придумал? Может UnPack справиться?
26. anry mc (AnryMc) 713 19.03.13 16:13 Сейчас в теме
??? ЗначениеВСтрокуВнутр ???
27. Борис Илов (ilov_boris) 159 19.03.13 16:20 Сейчас в теме
28. anry mc (AnryMc) 713 19.03.13 16:30 Сейчас в теме
(27) ilov_boris,

Половина описаной работы решается этим
29. Борис Илов (ilov_boris) 159 19.03.13 16:41 Сейчас в теме
(28) AnryMc, какая половина и каким образом?

ps ... или вы просто потроллить заглянули? ;)
Оставьте свое сообщение