gifts2017

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

Опубликовал Борис Илов (ilov_boris) в раздел Программирование - Практика программирования

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

Я не предлагаю конкретное решение, а только лишь показываю как это работает и как это можно использовать.
3. Сергей Сытько (8SiriuS8) 25.11.12 19:20
В снегопате можно использовать такую схему. там как раз не хватает такого плана скриптов.
4. Михаил Ражиков (tango) 25.11.12 19:40
(3) 8SiriuS8, тут фишка как раз в том, что снегопат может покурить
ilov_boris; +1 Ответить 1
5. Сергей (sstar90) 26.11.12 16:31
Плюс. Надо попробовать для общего развития
ilov_boris; +1 Ответить
6. Александр Орефков (orefkov) 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) 28.11.12 09:49
(4)
А курить я больше года как бросил :)
8. Владимир Чаклин (vec435) 28.11.12 22:51
а если на основе Clcl сделать ВК то и использовать можно как снегопат
9. Борис Илов (ilov_boris) 28.11.12 23:55
(8) vec435, ВК - это уже режим предприятия (да и зависимость от оного). А Снегопат и скрипты на Lua позволяют получить профит находясь в конфигураторе.
ВК и обработку несущую такой функционал конечно можно сделать, но это будет дико неудобно.
10. Борис Илов (ilov_boris) 29.11.12 00:14
На этих выходных постараюсь еще пару скриптов сделать, чтобы больше осветить возможности.
Если у кого есть идеи, то высказывайтесь здесь. Попробую реализовать предложения :)

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

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

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

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

ps ... или вы просто потроллить заглянули? ;)
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа