gifts2017

Рисунки табличного документа: вставить, куда надо

Опубликовал Яков Коган (Yashazz) в раздел Программирование - Практика программирования

Надо вывести рисунки в некоторых ячейках таб.документа, но как эти ячейки и области найти? Всем прайс-листам с картинками товаров и тому подобному посвящается...

Тривиальная задача вывода в табличный документ рисунков, того или иного вида и формата, начиная с 7.7 и до сих пор не имеет красивого универсального решения. Да, существует несколько общеизвестных приёмов, но большинство их базируется либо на собственном, заранее подготовленном макете, и значит, неуниверсально, либо на обработке уже сформированного таб.документа путём его поячейного рассмотрения, что долго и не очень надёжно. Кроме того, сложностью является и размещение рисунка, т.к. однозначной привязки координат рисунка к областям таб.документа не существует, равно как и корреляции размеров рисунка, задаваемых в миллиметрах, с шириной области, задаваемой в условных символах, и особенно с высотой (задаваемой в пунктах). Учтём также, что, несмотря на заверения СП, при свойстве "АвтоВысота" = Истина высота строки всегда нулевая, и узнать её, реальную, невозможно. В этих условиях размещение рисунка превращается в стрельбу по тарелочкам ночью вслепую.

Идея, использованная в публикации, баянна и банальна - сериализовать табличный документ в строку, сделать те действия, которые минимально необходимы и/или единственно возможны только с xml-текстом, и сериализовать обратно, после чего уже с помощью языка 1С доустановить нужное. Как показали эксперименты, привязка рисунка к области ячейки всё же есть, но только в xml, поэтому она там и указывается. Там же создаётся сам рисунок, и выставляются основные свойства по умолчанию, которые потом уже можно переустановить, а также имя и тип, которые поменять позже нельзя.

Разумеется, можно добиться нужного формата прямо в тексте xml, но это более громоздко, менее надёжно и вообще для любителей - например, xml чувствителен к порядку блоков <format>, а рисунки ссылаются на них по их индексам как по порядку расположения в тексте, чуть перепутаете - съедет всё форматирование. В этом смысле приятно, что <namedItem> имеет чёткую привязку по индексу. Также, например, можно устанавливать картинку для "картинки", указав её Base64 в элементе <picture>, но там тоже важен порядок блоков, да и громоздко это.

Реализация ближе к парсингу готового документа, но всё-таки не по ячейкам в цикле, а с помощью XPath. Не стоит пугаться, в большинстве случаев XPath-запросы окажутся просты и понятны. Например, в приведённом примере я ищу вхождения ссылок на справочник "Номенклатура" в расшифровках ячеек (тег "d", атрибут "xsi:type"). Это позволяет быстро найти именно нужные узлы. Исходя из узлов, определяются № строки и № колонки, где таковая ячейка нашлась. А это значит, что мы не зависим от формата документа, и табличный документ может иметь любой вид, быть любым выходным результатом СКД - и неважно, какие там уровни, да группировки, да колонки, объёдинённые и не очень. Найти содержание в xml можно однозначно, если правильно составить запрос XPath.

Конечно, дальше могут начаться некоторые сложности. Например, высоту строки всё равно придётся либо подгонять, либо делать с запасом. Объединённые ячейки могут привнести свою специфику позиционирования. И, увы, то, что мне победить не удалось - размеры рисунка придётся всё так же подгонять методом тыка, зависимостей я не делал. Но хоть размещён рисунок будет правильно, уже что-то)

Итак, что может процедура: вставить рисунки нужных типов в нужные области табличного документа, установить свойства этих рисунков, и всё это с гораздо меньшей зависимостью от формата и наполнения этого таб.документа.

Зачем публикация - поделиться способом; а ещё продемонстрировать, что xml-ипостась привычных нам общих объектов 1С в некоторых случаях даёт интересные возможности, но не является панацеей, и что совместное применение разных приёмов очень даже нормальный способ решения задачи.

Сразу извиняюсь, что выкладываю без "разукрашки" кода, но она съедает теги.

 

Тестировалось под толстым клиентом, но, подозреваю, всё это спокойно компилится как на клиенте, так и на сервере во всех вариациях. Если только в параметрах попадётся нечто, непригодное к передаче на сервер, но это уж на ваше усмотрение.

// Выводит рисунки в табличный документ согласно указанным данным, позволяет вывести рисунки в те ячейки (области), которые, будучи сериализованы,
// удовлетворяют указанным условиям по XPath (т.е. найти по тексту и/или расшифровке, или по другим данным-тегам); не изменяет в формате документа
// ничего, кроме высоты строк (чтобы поместились рисунки).
// Процедура изменяет табличный документ внутри себя.
//
// Параметры:
// тд - обрабатываемый табличный документ произвольного вида;
// ОбрабатываемыеОбласти - массив структур,
// где обязательны ключи:
// ВыражениеXPath - строка, поисковое выражение XPath, позволяющее определить нужную область (ячейку) в сериализованном таб.документе,
// ко всем областям, удовлетворяющим выражению, будет применено добавление рисунка с соответственно указанными данными и свойствами;
// ВысотаСтроки - число, в пунктах, обязательно указывать так, чтобы добавляемый рисунок по высоте помещался в нужную область;
// НомерКолонки - число, от 1 до N, куда выводить рисунки; если не указано, то рисунки выводятся в колонки, где найдено по выражению поиска;
// КлючПолученияКлючаДанных - строка, имя атрибута найденного узла или "ТекстовоеСодержимое"), указание на источник сведений, которые
// будут использованы для поиска данных, соответствующих конкретному найденному узлу, чтобы установить свойства именно "его" рисунка;
// Данные - соответствие; в нём выполняется поиск по ключу, полученному с помощью КлючПолученияКлючаДанных; если данные не найдены,
// то рисунок не добавляется и обработка не происходит; в соответствии должны быть:
// ключи - некие уникальные данные, обычно это GUID той ссылки, которая сериализована как расшифровка, или строковое значение тех данных,
// что отображены в тексте ячейки;
// значения - причём значение соответствия это обязательно структура:
// * обязательно наличие ключей ЛевоВЯчейке, ПравоВЯчейке, ВерхВЯчейке, НизВЯчейке - они определяют размещение рисунка в его области;
// * прочие ключи этой структуры должны совпадать с именами свойств объекта "РисунокТабличногоДокумента", и будут ему установлены;
// * ПроизвольноеВыражение - строка, содержащая выполняемый код на 1С, пригодный для исполнения в "Выполнить"; на вход может получать
// любые сведения, которые доступны в контексте процедуры; в частности, "тд" как таб.документ, "рРисунок" как конкретный рисунок и т.д;
// может выполняться только в режимах, отличных от ВебКлиента.
// где необязательны ключи:
// СвойстваОбласти - структура с ключами, совпадающими с именами системных свойств Области,
// будет применено непосредственно к той области, для которйо выполнится поисковое выражение и куда будет помещён рисунок.
//
Процедура ВставитьРисункиВТабличныйДокумент(тд,рПараметры)
Попытка
// сериализуем табличный документ и получаем его DOM
хмлЗапись=Новый ЗаписьXML;
хмлЗапись.УстановитьСтроку("UTF-8");
СериализаторXDTO.ЗаписатьXML(хмлЗапись,тд); // можно без явного назначения типа
тдСтрокой=хмлЗапись.Закрыть();
//
тдСтрокой=СтрЗаменить(тдСтрокой,"xmlns=","xmlns:doit="); // иначе не будет работать XPath
//
хмлЧтение=Новый ЧтениеXML;
хмлЧтение.УстановитьСтроку(тдСтрокой);
постр=Новый ПостроительDOM;
докдом=постр.Прочитать(хмлЧтение);
//
рРазыменователь=Новый РазыменовательПространствИменDOM(докдом);

рШиринаТД=тд.ШиринаТаблицы;

стрВставляемых="";

мКОбработке=Новый Массив;
Для каждого обрОбласть Из рПараметры.ОбрабатываемыеОбласти Цикл
// разыскиваем согласно поисковому выражению
рВыражение=СокрЛП(СтрЗаменить(обрОбласть.ВыражениеXPath,"""","'"));
рРезультат=докдом.ВычислитьВыражениеXPath(рВыражение,докдом,рРазыменователь);
Пока Истина Цикл
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
рУзелДанных=рРезультат.ПолучитьСледующий();
Если рУзелДанных=Неопределено Тогда Прервать КонецЕсли;
Если ТипЗнч(рУзелДанных)<>Тип("ЭлементDOM") Тогда Продолжить КонецЕсли;

// получаем рабочие ключевые данные (сведения из БД, связанные с рисунком)
рКлюч=СокрЛП(обрОбласть.КлючПолученияКлючаДанных);
рКлючДляДанных=Неопределено;
Если рКлюч="ТекстовоеСодержимое" Тогда
рКлючДляДанных=рУзелДанных.ТекстовоеСодержимое;
ИначеЕсли рУзелДанных.ЕстьАтрибут(рКлюч) Тогда // рассматриваем как атрибут
рКлючДляДанных=рУзелДанных.ПолучитьАтрибут(рКлюч);
КонецЕсли;
Если не ЗначениеЗаполнено(рКлючДляДанных) Тогда Продолжить КонецЕсли;
// ищем в данных (это соответствие)
рДанныеДляРисунка=обрОбласть.Данные.Получить(рКлючДляДанных);
Если ТипЗнч(рДанныеДляРисунка)<>Тип("Структура") Тогда Продолжить КонецЕсли;

// определяем вышестоящие рабочие узлы
рРодитель=рУзелДанных;
рДанныеКолонки=Неопределено; рДанныеСтроки=Неопределено;
Пока Истина Цикл
рТекущийУзел=рРодитель;
рРодитель=рРодитель.РодительскийУзел;
Если рРодитель.ИмяУзла="row" Тогда
рДанныеКолонки=рТекущийУзел;
рДанныеСтроки=рРодитель;
Прервать;
КонецЕсли;
Если ТипЗнч(рРодитель)<>Тип("ЭлементDOM") Тогда Прервать КонецЕсли;
КонецЦикла;
Если рДанныеКолонки=Неопределено или рДанныеСтроки=Неопределено Тогда Продолжить КонецЕсли;
//
рСтрока=рДанныеСтроки.РодительскийУзел; // rowsItem
Если ТипЗнч(рСтрока)<>Тип("ЭлементDOM") Тогда Продолжить КонецЕсли;
Если рСтрока.ИмяУзла<>"rowsItem" Тогда Продолжить КонецЕсли;
//
// к сожалению, у объекта СписокУзловДОМ нет метода Индекс...
рИндексСтроки=-1;
Для каждого рУзелСтроки Из рСтрока.ДочерниеУзлы Цикл
Если рУзелСтроки.ИмяУзла="index" Тогда
Попытка рИндексСтроки=Число(СокрЛП(рУзелСтроки.ТекстовоеСодержимое)) Исключение КонецПопытки;
Прервать;
КонецЕсли;
КонецЦикла;
//
Если обрОбласть.Свойство("НомерКолонки") и ЗначениеЗаполнено(обрОбласть.НомерКолонки) Тогда
рИндексКолонки=обрОбласть.НомерКолонки; // и это не обязательно будет та же колонка, в которой найдено поисковое значение
Иначе
рИндексКолонки=-1; й=0;
Для каждого кол Из рДанныеСтроки.ДочерниеУзлы Цикл
Если кол.ИмяУзла<>"c" Тогда Продолжить КонецЕсли;
Если кол.УзелИдентичен(рДанныеКолонки) Тогда рИндексКолонки=й; Прервать КонецЕсли;
й=й+1;
КонецЦикла;
КонецЕсли;
//
// смотрим, что получили
Если рИндексСтроки>-1 и рИндексКолонки>-1 Тогда
мКОбработке.Добавить(Новый Структура("Строка,Колонка,ДанныеДляРисунка",рИндексСтроки,рИндексКолонки,рДанныеДляРисунка));
//Сообщить("Строка "+рИндексСтроки+", колонка "+СокрЛП(рИндексКолонки));
КонецЕсли;
//
КонецЦикла; // по найденным узлам

// смотрим, какие координаты областей нашли по поисковому выражению XPath
// (случай обнаружения N колонок в одной строке не оптимизируем),
// собственно вставляем рисунки; все их свойства ставим по умолчанию (обработаем их потом)
//
Для й=0 По мКОбработке.Количество()-1 Цикл
коорд=мКОбработке.Получить(й);
стрй=СокрЛП(й+1);
//
рТипРисунка="";
Если коорд.ДанныеДляРисунка.Свойство("ТипРисунка") Тогда
рТипРисунка=СокрЛП(СериализаторXDTO.XMLСтрока(коорд.ДанныеДляРисунка.ТипРисунка));
КонецЕсли;
Если ПустаяСтрока(рТипРисунка) Тогда рТипРисунка="Text" КонецЕсли; // ну пусть так
//
стрВставляемых=стрВставляемых+"
|<drawing>
| <drawingType>"+рТипРисунка+"</drawingType>
| <id>"+стрй+"</id>
| <text>
| <v8:item>
| <v8:lang>#</v8:lang>
| <v8:content></v8:content>
| </v8:item>
| </text>
| <beginRow>"+Строка(коорд.Строка)+"</beginRow>
| <beginRowOffset>"+Строка(коорд.ДанныеДляРисунка.ЛевоВЯчейке)+"</beginRowOffset>
| <endRow>"+Строка(коорд.Строка)+"</endRow>
| <endRowOffset>"+Строка(коорд.ДанныеДляРисунка.ПравоВЯчейке)+"</endRowOffset>
| <beginColumn>"+Строка(коорд.Колонка)+"</beginColumn>
| <beginColumnOffset>"+Строка(коорд.ДанныеДляРисунка.ВерхВЯчейке)+"</beginColumnOffset>
| <endColumn>"+Строка(коорд.Колонка)+"</endColumn>
| <endColumnOffset>"+Строка(коорд.ДанныеДляРисунка.НизВЯчейке)+"</endColumnOffset>
| <autoSize>false</autoSize>
| <pictureSize>Stretch</pictureSize>
| <zOrder>0</zOrder>
|</drawing>
|<namedItem xsi:type=""NamedItemDrawing"">
| <name>AddPct"+стрй+"</name>
| <drawingID>"+стрй+"</drawingID>
|</namedItem>
|";
// текст, для рисунка-текста, сразу не вставляем, т.к. там могут оказаться несериализуемые символы
КонецЦикла; // по найденным координатам

обрОбласть.Вставить("ДанныеКОбработке",мКОбработке);
КонецЦикла; // по обрабатываемым областям

тдСтрокой=СтрЗаменить(тдСтрокой,"<defaultFormatIndex>",стрВставляемых+"<defaultFormatIndex>");

тдСтрокой=СтрЗаменить(тдСтрокой,"xmlns:doit=","xmlns="); // иначе не сработает десериализация

// преобразуем строку обратно в табличный документ
хмлЧтение=Новый ЧтениеXML;
хмлЧтение.УстановитьСтроку(тдСтрокой);
тд=СериализаторXDTO.ПрочитатьXML(хмлЧтение,Тип("ТабличныйДокумент"));

// устанавливаем свойства областей и рисунков уже в объектной технике 1С, так проще
рРисунки=тд.Рисунки;
Для каждого обрОбласть Из рПараметры.ОбрабатываемыеОбласти Цикл
мКОбработке=обрОбласть.ДанныеКОбработке;

Для й=0 По мКОбработке.Количество()-1 Цикл
коорд=мКОбработке.Получить(й);
//
// устанавливаем высоту всей строки, как сказано
облСтрока=тд.Область(коорд.Строка+1,1,коорд.Строка+1,рШиринаТД);
облСтрока.ВысотаСтроки=обрОбласть.ВысотаСтроки;
//
Если обрОбласть.Свойство("СвойстваОбласти") Тогда
облОбласть=тд.Область(коорд.Строка+1,коорд.Колонка+1,коорд.Строка+1,коорд.Колонка+1);
ЗаполнитьЗначенияСвойств(облОбласть,обрОбласть.СвойстваОбласти);
КонецЕсли;
//
рРисунок=рРисунки["AddPct"+СокрЛП(й+1)];
Если рРисунок<>Неопределено Тогда
ЗаполнитьЗначенияСвойств(рРисунок,коорд.ДанныеДляРисунка);
КонецЕсли;
//
#Если не ВебКлиент Тогда
Если коорд.ДанныеДляРисунка.Свойство("ПроизвольноеВыражение")
и не ПустаяСтрока(коорд.ДанныеДляРисунка.ПроизвольноеВыражение)
Тогда
Попытка
Выполнить(СокрЛП(коорд.ДанныеДляРисунка.ПроизвольноеВыражение));
Исключение
КонецПопытки;
КонецЕсли;
#КонецЕсли
//
КонецЦикла;
КонецЦикла;

Исключение
Сообщить("ВставитьРисункиВТабличныйДокумент, ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

Я с помощью этой штуковины вывел текстовые подписи о скидках и картинки из карточек номенклатуры в УТ 10.3, пример вызова таков:

    // настраиваем свойства рисунка
   
рСвойстваРисунка1=Новый Структура;
   
рСвойстваРисунка1.Вставить("ТипРисунка",ТипРисункаТабличногоДокумента.Текст);
   
рСвойстваРисунка1.Вставить("Текст","СКИДКИ!!!");
   
рСвойстваРисунка1.Вставить("АвтоРазмер",Истина);
   
рСвойстваРисунка1.Вставить("ВертикальноеПоложение",ВертикальноеПоложение.Низ);
   
рСвойстваРисунка1.Вставить("ЛевоВЯчейке",140);
   
рСвойстваРисунка1.Вставить("ПравоВЯчейке",180);
   
рСвойстваРисунка1.Вставить("ВерхВЯчейке",60);
   
рСвойстваРисунка1.Вставить("НизВЯчейке",230);
   
рСвойстваРисунка1.Вставить("ГраницаСверху",Ложь);
   
рСвойстваРисунка1.Вставить("ГраницаСлева",Ложь);
   
рСвойстваРисунка1.Вставить("ГраницаСнизу",Ложь);
   
рСвойстваРисунка1.Вставить("ГраницаСправа",Ложь);
   
рСвойстваРисунка1.Вставить("ЦветТекста",WebЦвета.Коричневый);
   
//
   
соотТоваров1=Новый Соответствие;
   
соотТоваров2=Новый Соответствие;
    Для каждого
стротрез Из тРезультатНекоегоЗапроса Цикл
       
рТовар=стротрез.Товар;
       
гуид=Строка(рТовар.УникальныйИдентификатор());
       
соотТоваров1.Вставить(гуид,рСвойстваРисунка1);
        Если не
рТовар.ОсновноеИзображение.Пустая() Тогда
           
рСвойстваРисунка2=Новый Структура;
           
рСвойстваРисунка2.Вставить("ТипРисунка",ТипРисункаТабличногоДокумента.Картинка);
           
рСвойстваРисунка2.Вставить("АвтоРазмер",Истина);
           
рСвойстваРисунка2.Вставить("ЛевоВЯчейке",1);
           
рСвойстваРисунка2.Вставить("ПравоВЯчейке",280);
           
рСвойстваРисунка2.Вставить("ВерхВЯчейке",1);
           
рСвойстваРисунка2.Вставить("НизВЯчейке",230);
           
рСвойстваРисунка2.Вставить("Ширина",30);
           
рСвойстваРисунка2.Вставить("Высота",30);
           
рСвойстваРисунка2.Вставить("Картинка",рТовар.ОсновноеИзображение.Хранилище.Получить());
           
соотТоваров2.Вставить(гуид,рСвойстваРисунка2);
        КонецЕсли;
    КонецЦикла;
   
//
    // готовим прочие параметры
   
рСвойстваОбласти=Новый Структура;
   
рСвойстваОбласти.Вставить("ВертикальноеПоложение",ВертикальноеПоложение.Верх);
   
//
   
стру1=Новый Структура;
   
стру1.Вставить("ВыражениеXPath","//d[@xsi:type='d6p1:CatalogRef.Номенклатура']"); // ищем по участию ссылок в расшифровках
   
стру1.Вставить("ВысотаСтроки",90);
   
стру1.Вставить("СвойстваОбласти",рСвойстваОбласти);
   
стру1.Вставить("КлючПолученияКлючаДанных","ТекстовоеСодержимое");
   
стру1.Вставить("Данные",соотТоваров1);
   
//
   
стру2=Новый Структура;
   
стру2.Вставить("ВыражениеXPath","//d[@xsi:type='d6p1:CatalogRef.Номенклатура']"); // ищем по участию ссылок в расшифровках
   
стру2.Вставить("ВысотаСтроки",87);
   
стру2.Вставить("НомерКолонки",3);
   
стру2.Вставить("КлючПолученияКлючаДанных","ТекстовоеСодержимое");
   
стру2.Вставить("Данные",соотТоваров2);
   
//
   
мстру=Новый Массив;
   
мстру.Добавить(стру1);
   
мстру.Добавить(стру2);
   
пар=Новый Структура("ОбрабатываемыеОбласти",мстру);
   
//
   
ВставитьРисункиВТабличныйДокумент(ТабДокумент,пар);

 

Кому пригодится - велкам. Кто доведёт до ума привязку размеров - тому респект.

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Максим Кузнецов (Makushimo) 21.06.16 05:45
мешанина зеленого текста - это жестоко )))
2. Яков Коган (Yashazz) 21.06.16 08:16
(1) Makushimo, "спасибо" новому редактору Инфостарт ((( Я изначально хотел просто как текст выложить, т.к. старый редактор портит xml-теги, но модератор не пропустил; говорит, юзай новый редактор. Вот, поюзал... Приношу извинения.
3. A X (ditp) 21.06.16 09:04
Интересная идея.
По ссылке http://pastebin.com/0W5UcTPr распарсенный текст.
4. Яков Коган (Yashazz) 21.06.16 10:40
(3) ditp, спасибо. Сейчас выясняю, что можно с этим зелёным ужасом сделать.
5. Яков Коган (Yashazz) 22.06.16 13:36
В итоге так и остался просто неразукрашенный текст. Но он хотя бы читабелен.
6. Яков Коган (Yashazz) 23.06.16 00:27
А, ну и естественно, речь не о подходе с использованием свойства "ПараметрКартинки", или самой картинке ячейки, а именно о вставке объекта "Рисунок".
7. ivanov660 ivanov660 (ivanov660) 23.06.16 09:31
Приложите обработку. Большое количество кода не читабельно, обычно выделяют интересные моменты.
8. Яков Коган (Yashazz) 23.06.16 09:59
(7) ivanov660, да в нём всё интересно с той или иной точки зрения, вот в чём дело. А за обработку стартмани тратить придётся... Впрочем, это мысль. Иллюстративный материал для применения попробую приложить как файлы.
9. Сергей (necropunk) 23.06.16 12:40
(8) Yashazz, проще потратить стартмани и быстро разобраться в коде с нормальной подсветкой и отладчиком. В любом случае, приятно когда есть выбор - хочешь, качаешь, хочешь - так читаешь.
10. Сергей (necropunk) 23.06.16 12:40
Вообще, идея интересная. поковыряю обязательно.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа