Когда шаблоны рвутся, или Вывод в Word по-простому

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

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

Поскольку шаблон далеко не всегда позволяет динамически построить нужный конечный документ, будем делать его сами поэтапно, а для этого нам понадобится инструментарий. Напомню, речь о совершенно обычном com-соединении с приложением MS Word, не ниже 2007 (ниже не проверял), установленном на клиентском ПК.

И, т.к. 90% задач сводятся к довольно простым и обыденным действиям, в т.ч. по форматированию, предлагаю набор процедур, реализующих вывод в документ Word нужных данных с минимальными возможностями оформления. Разумеется, знающий VBA посоветует массу других решений, особенно в части весьма "жадного" вывода данных в ячейки таблицы, а пристальное изучение Object Browser'а поможет узнать массу интересного, но за основу можно принять уже и такое. Кстати, далеко не все действия можно записать как макросы и увидеть потом их "потроха", кое-что придётся искать и узнавать на просторах интернета.

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

Делалось всё на 8.3.6.2014 - но, думаю, на более ранних тоже заработает. Все действия, естественно, только на клиенте.

Собственно процедуры: 

#Область ВыводДанныхВДокументWord

#Область Вспомогательные

// Исходя из данных переданного аргумента, готовит и возвращает текстовую строку,
// пригодную для вывода в область документа.
// При ошибке возвращает пустую строку.
//
// Параметры:
//    исхТекст - строка, форматированная строка или форматированный документ (в случае
//       форматированного документа производится разбор по параграфам с выделением текстовых
//       фрагментов и их соединением через символы переносов строк.
//
&НаКлиенте
Функция ПодготовитьТекстовыйФрагмент(исхТекст)
Попытка
    рТип=ТипЗнч(исхТекст);
    Если рТип=Тип("Строка") Тогда
        рТекст=исхТекст;
    ИначеЕсли рТип=Тип("ФорматированнаяСтрока") Тогда
        рТекст=Строка(исхТекст);
    ИначеЕсли рТип=Тип("ФорматированныйДокумент") Тогда
        рТекст=""; разд="";
        Для каждого рПараграф Из исхТекст.Элементы Цикл
            Для каждого эл Из рПараграф.Элементы Цикл
                Если ТипЗнч(эл)=Тип("ТекстФорматированногоДокумента") Тогда
                    рТекст=рТекст+разд+эл.Текст; разд=Символы.ПС;
                КонецЕсли;
            КонецЦикла;
        КонецЦикла;
        Пока Истина Цикл
            ОбработкаПрерыванияПользователя();
            рТекст=СтрЗаменить(рТекст,"  "," ");
            Если Найти(рТекст,"  ")=0 Тогда Прервать КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    Возврат рТекст;
Исключение
    Сообщить("ПодготовитьТекстовыйФрагмент, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
    Возврат "";
КонецПопытки;
КонецФункции

// Находит первый (!) из подходящих под условия поиска диапазонов документа и возвращает его;
// При ошибке, или если ничего не найдено, возвращает Неопределено.
//
// Параметры:
//    комДокумент - объект Document в COM-объекте Word;
//    комДиапазон - объект Range в COM-объекте Word (необязательный, если не указан, поиск идёт во всём документе);
//    Поискуха - строка, содержимое которых собственно разыскивается.
//
&НаКлиенте
Функция НайтиПодстроку(комДокумент,комДиапазон=Неопределено,Поискуха)
Попытка
    Если комДиапазон=Неопределено Тогда комДиапазон=комДокумент.Range() КонецЕсли; // по умолчанию весь документ
    комПоиск=комДиапазон.Find;
    комПоиск.ClearFormatting();
    комПоиск.Text=Поискуха;
    комПоиск.Forward=Истина;
    комПоиск.Wrap=1; //wdFindContinue
    комПоиск.Format=Ложь;
    комПоиск.MatchCase=Ложь;
    комПоиск.MatchWholeWord=Ложь;
    комПоиск.MatchAllWordForms=Ложь;
    комПоиск.MatchSoundsLike=Ложь;
    комПоиск.MatchWildcards=Истина;
    Если комПоиск.Execute() Тогда
        Возврат комДокумент.Range(комДиапазон.Start,комДиапазон.End);
    Иначе
        Возврат Неопределено;
    КонецЕсли;
Исключение
    Сообщить("НайтиПодстроку, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
    Возврат Неопределено;
КонецПопытки;
КонецФункции

// Выводит в COM-объект КомВорд разделитель в виде нового параграфа
&НаКлиенте
Процедура ВывестиРазделитель(КомВорд)
    // выход в область дальнейшего вывода
    КомВорд.Selection.EndKey(6);
    КомВорд.Selection.TypeParagraph();
КонецПроцедуры

// Выводит в COM-объект КомВорд разделитель в виде разрыва страницы
&НаКлиенте
Процедура ВставитьРазрывСтраницы(КомВорд)
    КомВорд.Selection.EndKey(6);
    КомВорд.Selection.InsertBreak(7); // wdPageBreak
КонецПроцедуры

// Устанавливает для шрифта документа переданные свойства из структуры
//
// Параметры
//    комШрифт - объект Font в COM-объекте Word;
//    рШрифт - объект "Шрифт" или структура, обязательно содержащая следующие значения:
//        Имя - строковое имя, совпадающее с именами согласно нотации Word,
//        Размер - реальное числовое значение или "-1", если надо установить по умолчанию;
//        Жирный, Наклонный, Подчеркивание (булево) - соответственно.
//
&НаКлиенте
Процедура УстановитьСвойстваШрифта(комШрифт,рШрифт);
Попытка
    wdToggle=9999998;
    wdColorAutomatic=-16777216;
    комШрифт.Name=рШрифт.Имя;
    Если рШрифт.Размер=-1 Тогда // по умолчанию
        комШрифт.Size=12;
    Иначе
        комШрифт.Size=рШрифт.Размер;
    КонецЕсли;
    Если рШрифт.Жирный=Истина Тогда
        комШрифт.Bold=wdToggle;
    КонецЕсли;
    Если рШрифт.Наклонный=Истина Тогда
        комШрифт.Italic=wdToggle;
    КонецЕсли;
    Если рШрифт.Подчеркивание=Истина Тогда
        комШрифт.UnderlineColor=wdColorAutomatic;
        комШрифт.Underline=1; // wdUnderlineSingle
    КонецЕсли;

    #Область ПрочиеСвойствШрифта
    //комШрифт.StrikeThrough=wdUndefined
    //комШрифт.Subscript=wdUndefined
    //комШрифт.Superscript=wdUndefined
    //комШрифт.Shadow=wdUndefined
    //комШрифт.Outline=wdUndefined
    //комШрифт.Emboss=wdUndefined
    //комШрифт.Engrave=wdUndefined
    //комШрифт.AllCaps=wdUndefined
    //комШрифт.Hidden=wdUndefined
    //комШрифт.Color=wdUndefined
    //комШрифт.Animation=wdUndefined
    //комШрифт.DoubleStrikeThrough=wdUndefined
    #КонецОбласти

Исключение
    Сообщить("УстановитьСвойстваШрифта, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
КонецПопытки;
КонецПроцедуры

// Устанавливает всем элементам коллекции Borders одинаковое значение
//
// Параметры:
//    комОбъект - любой объект, имеющий свойство Borders, в COM-объекте Word;
//    рСтиль - числовое значение согласно нотации wdLineStyle.
//
&НаКлиенте
Процедура УстановитьОдинаковыеГраницы(комОбъект,рСтиль=0)
    Для й=1 По 8 Цикл // см. значения констант wdBorderType
        Попытка // не все номера в этом семействе существуют всегда
            комОбъект.Borders(-1*й).LineStyle=рСтиль; // см. wdLineStyle
        Исключение
        КонецПопытки;
    КонецЦикла;
КонецПроцедуры

// Устанавливает соответствие между константами Word и значениями 1С; не использована
// в текущем коде и приведена просто как пример работы с гориз.выравниваниями.
//
Функция ПолучитьВыравнивание();
    соот=Новый Соответствие;
    соот.Вставить(ПредопределенноеЗначение("ГоризонтальноеПоложение.Лево"),0);
    соот.Вставить(ПредопределенноеЗначение("ГоризонтальноеПоложение.Центр"),1);
    соот.Вставить(ПредопределенноеЗначение("ГоризонтальноеПоложение.Право"),2);
    соот.Вставить(ПредопределенноеЗначение("ГоризонтальноеПоложение.ПоШирине"),3);
    Возврат соот;
КонецФункции

#КонецОбласти


#Область ПримерВызова_ДобавитьНумерованныйСписок
//рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // по умолчанию
//
//мспис=Новый Массив;
//мспис.Добавить(Новый Структура("Номер,Текст",1,"Первый"));
//мспис.Добавить(Новый Структура("Номер,Текст",2,"Второй"));
//мспис.Добавить(Новый Структура("Номер,Текст,Шрифт",3,"Жирный",Новый Шрифт(,,Истина)));
//мспис.Добавить(Новый Структура("Номер,Текст",4,"Четвёртый"));
//
//пар=Новый Структура;
//пар.Вставить("Содержание",мспис);
//пар.Вставить("КомВорд",КомВорд);
//пар.Вставить("КомДокумент",d);
//пар.Вставить("ДиапазонПолучатель",КомВорд.Selection.Range);
//пар.Вставить("ВыравниваниеНомеров",wdAlignParagraphRight);
//пар.Вставить("ВыравниваниеТекста",wdAlignParagraphLeft);
//пар.Вставить("ШиринаНомеров",45);
//пар.Вставить("ШиринаТекста",370);
//пар.Вставить("ШрифтПоУмолчанию",рШрифтПоУмолчанию);
//
//ДобавитьНумерованныйСписок(пар);
//
#КонецОбласти
//
// Выводит не настоящий маркированный список с номерами, а его эмуляцию в виде двухколоночной таблицы
//
// Параметры:
//    рПараметры - структура,
//    обязательно содержащая ключи:
//        КомВорд - собственно COM-объект Word.Application;
//        КомДокумент - объект Document в COM-объекте Word;
//        ДиапазонПолучатель - объект Range в COM-объекте Word;
//        Содержание - данные формы или массив структур, т.е. коллекция, имеющая следующий внутренний вид:
//            Номер - порядковый номер как строка (если не указан, то по возрастанию, начиная с 1);
//            Текст - содержимое пункта, строка;
//            Шрифт - объект Шрифт, уточняющий вывод конкретной позиции;
//    необязательно содержащая ключи:
//        ВыравниваниеНомеров - см. ПолучитьВыравнивание, касается возрастающих №№ выводимого списка;
//        ВыравниваниеТекста - см. ПолучитьВыравнивание, касается содержимого собственно пунктов списка;
//        ШиринаНомеров - ширина колонки, где выводятся №№ (рекомендуется делать небольшой);
//        ШиринаТекста - ширина колонки, где выводится содержание пунктов списка;
//        ШрифтПоУмолчанию - объект Шрифт, которым будет выведено содержимое, если нет уточнений в Содержании.
//
&НаКлиенте
Процедура ДобавитьНумерованныйСписок(рПараметры)
Попытка
    рСписок=рПараметры.Содержание; // таблица значений или массив структур
    комДокумент=рПараметры.КомДокумент;
    квоКолонок=2; // для № и для текста
    комТаблица=комДокумент.Tables.Add(рПараметры.ДиапазонПолучатель,рСписок.Количество(),квоКолонок); // изначально без рамок
    //
    рВыравниваниеНомеров=?(рПараметры.Свойство("ВыравниваниеНомеров"),рПараметры.ВыравниваниеНомеров,0);
    рВыравниваниеТекста=?(рПараметры.Свойство("ВыравниваниеТекста"),рПараметры.ВыравниваниеТекста,3);
    рШиринаНомеров=?(рПараметры.Свойство("ШиринаНомеров"),рПараметры.ШиринаНомеров,45);
    рШиринаТекста=?(рПараметры.Свойство("ШиринаТекста"),рПараметры.ШиринаТекста,370);
    рШрифтПоУмолчанию=?(рПараметры.Свойство("ШрифтПоУмолчанию"),рПараметры.ШрифтПоУмолчанию,Неопределено);
    Если ТипЗнч(рШрифтПоУмолчанию)<>Тип("Шрифт") Тогда
        рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // -1, если по умолчанию как в общем стиле
    КонецЕсли;
    //
    Попытка
        комТаблица.Columns(1).SetWidth(рШиринаНомеров,0);
        комТаблица.Columns(2).SetWidth(рШиринаТекста,0);
    Исключение
    КонецПопытки;
    //
    Для й=1 По рСписок.Количество() Цикл
        строспис=рСписок.Получить(й-1);
        рНомер=?(строспис.Свойство("Номер"),строспис.Номер,Строка(й));
        комТаблица.Cell(й,1).Range.Text=СокрЛП(рНомер);
        комТаблица.Cell(й,2).Range.Text=строспис.Текст;
        комТаблица.Cell(й,1).Range.ParagraphFormat.Alignment=рВыравниваниеНомеров;
        комТаблица.Cell(й,2).Range.ParagraphFormat.Alignment=рВыравниваниеТекста;
        рШрифт=?(строспис.Свойство("Шрифт"),строспис.Шрифт,рШрифтПоУмолчанию);
        комШрифт=комТаблица.Rows(й).Range.Font;
        УстановитьСвойстваШрифта(комШрифт,рШрифт);
    КонецЦикла;
    //
    УстановитьОдинаковыеГраницы(комТаблица);
Исключение
    Сообщить("ДобавитьНумерованныйСписок, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
КонецПопытки;
КонецПроцедуры


#Область ПримерВызова_ДобавитьМаркированныйСписок
//мспис=Новый Массив;
//мспис.Добавить(Новый Структура("Текст","Первая строка"));
//мспис.Добавить(Новый Структура("Текст","Второй пункт"));
//мспис.Добавить(Новый Структура("Текст","Третья позиция"));
//пар=Новый Структура;
//пар.Вставить("Содержание",мспис);
//пар.Вставить("КомВорд",КомВорд);
//пар.Вставить("КомДокумент",d);
//пар.Вставить("ДиапазонПолучатель",КомВорд.Selection.Range);
//пар.Вставить("ШрифтПоУмолчанию",рШрифт);
//ДобавитьМаркированныйСписок(пар);
//
#КонецОбласти
//
// Выводит классический маркированный список Word
//
// Параметры:
//    рПараметры - структура,
//    обязательно содержащая ключи:
//        КомВорд - собственно COM-объект Word.Application;
//        КомДокумент - объект Document в COM-объекте Word;
//        ДиапазонПолучатель - объект Range в COM-объекте Word;
//        Содержание - таблица значений или массив структур, т.е. коллекция, имеющая следующий внутренний вид:
//            Текст - содержимое пункта, строка.
//    необязательно содержащая ключи:
//        ШрифтПоУмолчанию - объект Шрифт, которым будет выведено содержимое безальтернативно на весь список.
//
&НаКлиенте
Процедура ДобавитьМаркированныйСписок(рПараметры)
Попытка
    рСписок=рПараметры.Содержание; // массив структур
    КомВорд=рПараметры.КомВорд;
    рШрифтПоУмолчанию=?(рПараметры.Свойство("ШрифтПоУмолчанию"),рПараметры.ШрифтПоУмолчанию,Неопределено);
    Если ТипЗнч(рШрифтПоУмолчанию)<>Тип("Шрифт") Тогда
        рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // -1, если по умолчанию как в общем стиле
    КонецЕсли;

    комДиапазон=рПараметры.ДиапазонПолучатель;
    комУровниЛиста=КомВорд.ListGalleries(1).ListTemplates(1).ListLevels(1);
    //комУровниЛиста.NumberFormat=ChrW(61623)
    //комУровниЛиста.NumberFormat="'"+"%1.";
    комУровниЛиста.TrailingCharacter=0;
    комУровниЛиста.NumberStyle=23;
    комУровниЛиста.NumberPosition=КомВорд.CentimetersToPoints(0.63);
    комУровниЛиста.Alignment=0;
    комУровниЛиста.TextPosition=КомВорд.CentimetersToPoints(1.27);
    //комУровниЛиста.TabPosition=wdUndefined
    комУровниЛиста.ResetOnHigher=0;
    комУровниЛиста.StartAt=1;
    комУровниЛиста.LinkedStyle="";

    УстановитьСвойстваШрифта(комУровниЛиста.Font,рШрифтПоУмолчанию); // шрифт пока без вариантов

    КомВорд.ListGalleries(1).ListTemplates(1).Name="";
    // применяем к диапазону
    комДиапазон.ListFormat.ApplyListTemplateWithLevel(КомВорд.ListGalleries(1).ListTemplates(1),False,0,2);
    комДиапазон.Select();
    комворд.Keyboard(1049);
    //
    // собственно вносим
    квоспис=рСписок.Количество();
    Для й=0 По квоспис-1 Цикл
        знч=рСписок.Получить(й);
        КомВорд.Selection.TypeText(СокрЛП(знч.Текст));
        КомВорд.Selection.TypeParagraph();
    КонецЦикла;
    //
    КомВорд.Selection.Range.ListFormat.RemoveNumbers(1); //wdNumberParagraph

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


#Область ПримерВызова_ДобавитьТекст
//рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // по умолчанию
//рРазмерОтступа=КомВорд.CentimetersToPoints(1.00);
//
//зак1=Новый Структура("Имя","z1");
//гип1=Новый Структура("ИмяЗакладки,Текст","z1","парня скромного и простого");
//мспис=Новый Массив;
//мспис.Добавить(Новый Структура("Текст,Закладка","Флит-лейтенант Бокасса задумчиво хмыкнул. Будучи человеком образованным (все же, бакалавр Университета Антильских островов по экономической социологии) он, конечно, помнил фильм «День сурка». И было сходство сценария год и неделю назад на Таити с сегодняшним сценарием в Порт-Морсби. Авиа-перелет, погрузка на паром, скрытное перемещение к целевому берегу, танковый десант, и штурм города. Но, если смотреть немного глубже, то сегодня все по-другому.",зак1));
//мспис.Добавить(Новый Структура("Текст","Противник - не французские легионеры, а невнятная армия Республики Папуа, которая состоит примерно из 3000, как бы, солдат, обученных, как попало, и вооруженных, чем попало. И вообще, они не противник. Ведь коммодор Гремлин уже связался с главным дежурным офицером гарнизона Порт-Морсби, и разъяснил, что не следует папуасским солдатам лезть не в свое дело."));
//мспис.Добавить(Новый Структура("Текст","Посольство Великобритании в Папуа (Высокая Комиссия Соединенного Королевства) расположено в комплексе, построенном в 5 км от берега, около площади Коука. На  площадь выходят несколько отелей и офисных зданий, включая знаменитую башню «Somare Foundation», известную как «Папуасская Падающая башня» (она построена с дефектами, так что не используется из-за высокого риска обрушения). А вообще, это элитный район, патрулируемый дисциплинированным"));
//мспис.Добавить(Новый Структура("Текст","меньшинством полиции. Здесь безопасно и тихо. Точнее, обычно здесь безопасно и тихо, но не в эту ночь. Персонал посольства уже начал догадываться, что эта ночь – особенная. Стрельба на улицах не редкость в ночном Порт-Морсби, но сейчас стрельбы было многовато, и еще внезапно пропала сотовая связь. В общем, что-то было не так, и старший офицер безопасности посольства приказал перевести охрану на усиленный режим несения службы,"));
//мспис.Добавить(Новый Структура("Текст,Гиперссылка","и это случилось из-за одного человека, парня скромного и простого до невозможности.",гип1));
//
//пар=Новый Структура;
//пар.Вставить("Содержание",мспис);
//пар.Вставить("КомДокумент",d);
//пар.Вставить("КомВорд",КомВорд);
//пар.Вставить("ДиапазонПолучатель",КомВорд.Selection.Range);
//пар.Вставить("ВыравниваниеТекста",wdAlignParagraphJustify);
//пар.Вставить("ШиринаТекста",410);
//пар.Вставить("ШрифтПоУмолчанию",рШрифтПоУмолчанию);
//пар.Вставить("ОтступПоУмолчанию",рРазмерОтступа);
//
//ДобавитьТекст(пар);
//
#КонецОбласти
//
// Выводит текстовые фрагменты из некой коллекции в единственную колонку невидимой таблицы
//
// Параметры:
//    рПараметры - структура,
//    обязательно содержащая ключи:
//        КомВорд - собственно COM-объект Word.Application;
//        КомДокумент - объект Document в COM-объекте Word;
//        ДиапазонПолучатель - объект Range в COM-объекте Word;
//        Содержание - коллекция формы или массив структур, т.е. коллекция, имеющая следующий внутренний вид:
//            Текст - содержимое выводимого текстового фрагмента (внутреннее форматирование не предусмотрено);
//            Выравнивание - см. ПолучитьВыравнивание, для конкретного фрагмента;
//            Отступ - числовое значение отступа абзаца для конкретного фрагмента;
//            Шрифт - объект Шрифт, уточняющий шрифт фрагмента;
//            Заголовок - строка вида "Заголовок 1", "Заголовок 3" итд, определяющая структурный уровень фрагмента;
//            Закладка - структура с ключом "Имя", содержащим правильное имя закладки, вставляемой в начало фрагмента;
//            Гиперссылка - структура с ключами "ИмяЗакладки" (должна быть такая закладка) и "Текст" (гиперссылки);
//    необязательно содержащая ключи:
//        ВыравниваниеТекста - см. ПолучитьВыравнивание, которое будет установлено по умолчанию (если нет уточнений);
//        ШиринаТекста - ширина единственной колонки, где выводится собственно содержимое как текст;
//        ШрифтПоУмолчанию - объект Шрифт, которым будет выведено содержимое (если нет уточнений);
//        ОтступПоУмолчанию - числовое значение отступа абзаца для содержимого (если нет уточнений).
//
&НаКлиенте
Процедура ДобавитьТекст(рПараметры)
Попытка
    рСписок=рПараметры.Содержание;
    комДокумент=рПараметры.КомДокумент;
    квоКолонок=1; // для текста
    комТаблица=комДокумент.Tables.Add(рПараметры.ДиапазонПолучатель,рСписок.Количество(),квоКолонок);
    //
    рВыравниваниеПоУмолчанию=?(рПараметры.Свойство("ВыравниваниеТекста"),рПараметры.ВыравниваниеТекста,3);
    рШиринаТекста=?(рПараметры.Свойство("ШиринаТекста"),рПараметры.ШиринаТекста,410);
    рШрифтПоУмолчанию=?(рПараметры.Свойство("ШрифтПоУмолчанию"),рПараметры.ШрифтПоУмолчанию,Неопределено);
    Если ТипЗнч(рШрифтПоУмолчанию)<>Тип("Шрифт") Тогда
        рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // -1, если по умолчанию как в общем стиле
    КонецЕсли;
    рОтступПоУмолчанию=?(рПараметры.Свойство("ОтступПоУмолчанию"),рПараметры.ОтступПоУмолчанию,Неопределено);
    Если ТипЗнч(рОтступПоУмолчанию)<>Тип("Число") Тогда
        рОтступПоУмолчанию=-1;
    КонецЕсли;
    //
    Попытка
        комТаблица.Columns(1).SetWidth(рШиринаТекста,0);
    Исключение
    КонецПопытки;
    //
    Для й=1 По рСписок.Количество() Цикл
        строспис=рСписок.Получить(й-1);
        //
        комТаблица.Cell(й,1).Range.Text=ПодготовитьТекстовыйФрагмент(строспис.Текст);
        //
        рФорматПараграфа=комТаблица.Cell(й,1).Range.ParagraphFormat;
        рФорматПараграфа.Alignment=?(строспис.Свойство("Выравнивание"),строспис.Выравнивание,рВыравниваниеПоУмолчанию);
        //
        рОтступ=?(строспис.Свойство("Отступ"),строспис.Отступ,рОтступПоУмолчанию);
        Если рОтступ<>-1 Тогда
            рФорматПараграфа.SpaceBeforeAuto=Ложь;
            рФорматПараграфа.SpaceAfterAuto=Ложь;
            рФорматПараграфа.FirstLineIndent=рОтступ;
        КонецЕсли;
        //
        рШрифт=?(строспис.Свойство("Шрифт"),строспис.Шрифт,рШрифтПоУмолчанию);
        комШрифт=комТаблица.Cell(й,1).Range.Font;
        УстановитьСвойстваШрифта(комШрифт,рШрифт);
        //
        Если строспис.Свойство("Заголовок") Тогда
            комТаблица.Cell(й,1).Range.Select();
            рПараметры.КомВорд.Selection.Style=рПараметры.КомДокумент.Styles(строспис.Заголовок);
        КонецЕсли;
        //
        Если строспис.Свойство("Закладка") Тогда
            КомЗакладки=рПараметры.КомДокумент.Bookmarks;
            КомЗакладки.Add(строспис.Закладка.Имя,комТаблица.Cell(й,1).Range);
            КомЗакладки.DefaultSorting=0; //wdSortByName
            КомЗакладки.ShowHidden=Ложь;
            мВсеЗакладки=?(рПараметры.Свойство("ВсеЗакладки"),рПараметры.ВсеЗакладки,Новый Массив);
            мВсеЗакладки.Добавить(Новый Структура("КомДиапазон,Имя",комТаблица.Cell(й,1).Range,строспис.Закладка.Имя));
            рПараметры.Вставить("ВсеЗакладки",мВсеЗакладки);
        КонецЕсли;
        //
        Если строспис.Свойство("Гиперссылка") Тогда
            рДиапазонГиперссылки=НайтиПодстроку(рПараметры.КомДокумент,,строспис.Гиперссылка.Текст);
            рПараметры.КомДокумент.Hyperlinks.Add(рДиапазонГиперссылки,"",строспис.Гиперссылка.ИмяЗакладки,"",строспис.Гиперссылка.Текст);
        КонецЕсли;
        //
    КонецЦикла;
Исключение
    Сообщить("ДобавитьТекст, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
КонецПопытки;
КонецПроцедуры


#Область ПримерВызова_ДобавитьТаблицу
//рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // по умолчанию
//рРазмерОтступа=КомВорд.CentimetersToPoints(1.00);
//
//мспис=Новый Массив(2,2);
//мспис[0][0]=Новый Структура("Картинка,Текст,Шрифт",БиблиотекаКартинок.ЗаставкаВнешнейОперации,"Это заставка 1С",Новый Шрифт(,,,Истина));
//мспис[0][1]=Новый Структура("Картинка,ВыравниваниеТекста,ВыравниваниеКартинки",БиблиотекаКартинок.КомпьютерСервер,wdAlignParagraphRight=2,2);
//мспис[1][0]=Новый Структура("ВложенныйОбъект",рПараметрыВложенногоОбъекта); // обязательно наличие свойства ВидОбъекта, остальное см."пар"-примеры
//мспис[1][1]=Новый Структура("Текст","Просто некое содержание");
// примеры указания гиперссылок-на-закладки см. в выводе текста
//
//мШирин=Новый Массив;
//мШирин.Добавить(190);
//мШирин.Добавить(210);
//
//пар=Новый Структура;
//пар.Вставить("Содержание",мспис);
//пар.Вставить("КомДокумент",d);
//пар.Вставить("ДиапазонПолучатель",КомВорд.Selection.Range);
//пар.Вставить("ВыравниваниеТекста",wdAlignParagraphCenter);
//пар.Вставить("ВыравниваниеКартинки",wdAlignParagraphCenter);
//пар.Вставить("ШиринаТекста",410);
//пар.Вставить("ШрифтПоУмолчанию",рШрифтПоУмолчанию);
//пар.Вставить("ОтступПоУмолчанию",рРазмерОтступа);
//пар.Вставить("ШириныКолонок",мШирин);
//пар.Вставить("БезГраниц",Истина);
//
//ДобавитьТаблицу(пар);
//
#КонецОбласти
//
// Выводит данные из некой коллекции в таблицу документа, допустимы текст, картинки, вложенные объекты.
//
// Картинки выводятся наиболее правильно, если они в формате PNG, для других форматов производится попытка
// преобразовать в этот формат; используется запись во временный файл, так что должны быть права на TMP-папку.
//
// Параметры:
//    рПараметры - структура,
//    обязательно содержащая ключи:
//        КомВорд - собственно COM-объект Word.Application;
//        КомДокумент - объект Document в COM-объекте Word;
//        ДиапазонПолучатель - объект Range в COM-объекте Word;
//        Содержание - коллекция формы или массив структур, т.е. коллекция, имеющая следующий внутренний вид:
//            Картинка - объект Картинка, желательно формата png, может быть наряду с текстом (обтекание по умолчанию);
//            Текст - содержимое выводимого текстового фрагмента (внутреннее форматирование не предусмотрено);
//            Выравнивание - см. ПолучитьВыравнивание, для конкретной ячейки;
//            Отступ - числовое значение отступа абзаца для конкретной ячейки;
//            Шрифт - объект Шрифт, уточняющий шрифт ячейки;
//            Фон - структура с обязательными ключами "Текстура","ЦветТекстуры","ЦветФона" (согласно нотации Word);
//            Заголовок - строка вида "Заголовок 1", "Заголовок 3" итд, определяющая структурный уровень ячейки;
//            Закладка - структура с ключом "Имя", содержащим правильное имя закладки, вставляемой в начало ячейки;
//            Гиперссылка - структура с ключами "ИмяЗакладки" (должна быть такая закладка) и "Текст" (гиперссылки);
//            Вложенный объект - структура параметров, описывающих некий другой объект для вывода в этой ячейке -
//                например, параметры для маркированного списка, который должен быть в этой ячейке;
//                обязательно наличие строкового ключа "ВидОбъекта",
//                допустимые значения: "НумерованныйСписок", "МаркированныйСписок", "Текст", "Таблица".
//    необязательно содержащая ключи:
//        ВыравниваниеКартинки - см. ПолучитьВыравнивание, которое будет установлено для картинок в ячейках таблицы;
//        ВыравниваниеТекста - см. ПолучитьВыравнивание, которое будет установлено для текста в ячейках таблицы;
//        ШрифтПоУмолчанию - объект Шрифт, которым будет выведено содержимое (если нет уточнений);
//        ОтступПоУмолчанию - числовое значение отступа абзаца для содержимого (если нет уточнений).
//        ШириныКолонок - массив, последовательно содержащий числовые ширины колонок таблицы (если не задан, то
//           считается, что колонка в таблице одна и её ширина задаётся как 410 по умолчанию). Ширину менее 20 не ставьте.
//           Без правильно указанного этого массива таблица верно не выведется!
//        БезГраниц - булево, определяет, будет ли выведено обрамление каждой ячейки таблицы.
//
&НаКлиенте
Процедура ДобавитьТаблицу(рПараметры)
Попытка
    рСписок=рПараметры.Содержание;
    комДокумент=рПараметры.КомДокумент;
    //
    рБезГраниц=?(рПараметры.Свойство("БезГраниц"),рПараметры.БезГраниц,Истина);
    рВыравниваниеКартинки=?(рПараметры.Свойство("ВыравниваниеКартинки"),рПараметры.ВыравниваниеКартинки,1);
    рВыравниваниеТекста=?(рПараметры.Свойство("ВыравниваниеТекста"),рПараметры.ВыравниваниеТекста,1);
    рШрифтПоУмолчанию=?(рПараметры.Свойство("ШрифтПоУмолчанию"),рПараметры.ШрифтПоУмолчанию,Неопределено);
    Если ТипЗнч(рШрифтПоУмолчанию)<>Тип("Шрифт") Тогда
        рШрифтПоУмолчанию=Новый Шрифт("Times New Roman",12); // -1, если по умолчанию как в общем стиле
    КонецЕсли;
    рОтступПоУмолчанию=?(рПараметры.Свойство("ОтступПоУмолчанию"),рПараметры.ОтступПоУмолчанию,Неопределено);
    Если ТипЗнч(рОтступПоУмолчанию)<>Тип("Число") Тогда
        рОтступПоУмолчанию=-1;
    КонецЕсли;
    //
    Если рПараметры.Свойство("ШириныКолонок") и ТипЗнч(рПараметры.ШириныКолонок)=Тип("Массив") Тогда
        квоКолонок=рПараметры.ШириныКолонок.Количество();
        комТаблица=комДокумент.Tables.Add(рПараметры.ДиапазонПолучатель,рСписок.Количество(),квоКолонок,1,0);
        Для ы=1 По квоКолонок Цикл
            Попытка
                рШирина=рПараметры.ШириныКолонок.Получить(ы-1);
                Если рШирина<20 Тогда рШирина=20 КонецЕсли; // найдено экспериментально для Word2012
                комТаблица.Columns(ы).SetWidth(рШирина,0);
            Исключение
                Сообщить("Ошибка при установке ширины, равной "+СокрЛП(рШирина)+" для колонки № "+СокрЛП(ы)+", всего колонок "+СокрЛП(квоКолонок)+": "+ОписаниеОшибки(),СтатусСообщения.Важное);
                Прервать;
            КонецПопытки;
        КонецЦикла;
    Иначе // считается, что колонка одна и её ширина задана по умолчанию
        квоКолонок=1;
        комТаблица=комДокумент.Tables.Add(рПараметры.ДиапазонПолучатель,рСписок.Количество(),квоКолонок,1,0);
        рШиринаТекста=?(рПараметры.Свойство("ШиринаТекста"),рПараметры.ШиринаТекста,410);
        комТаблица.Columns(1).SetWidth(рШиринаТекста,0);
    КонецЕсли;
    //
    имяф=ПолучитьИмяВременногоФайла("png");
    Для й=1 По рСписок.Количество() Цикл
        Для ы=1 По квоКолонок Цикл
            элсод=рСписок[й-1][ы-1];
            Если ТипЗнч(элсод)<>Тип("Структура") Тогда Продолжить КонецЕсли;
            //
            Если элсод.Свойство("Картинка") Тогда
                комТаблица.Cell(й,ы).Range.ParagraphFormat.Alignment=?(элсод.Свойство("ВыравниваниеКартинки"),элсод.ВыравниваниеКартинки,рВыравниваниеКартинки);
                рКартинка=элсод.Картинка;
                Если рКартинка.Формат()<>ФорматКартинки.PNG Тогда
                    Попытка
                        рКартинка.Преобразовать(ФорматКартинки.PNG);
                    Исключение
                        Сообщить("Ошибка преобразования формата: "+ОписаниеОшибки(),СтатусСообщения.Важное);
                        Продолжить;
                    КонецПопытки;
                КонецЕсли;
                рКартинка.Записать(имяф);
                комФигура=комТаблица.Cell(й,ы).Range.InlineShapes.AddPicture(имяф,Ложь,Истина);
                //УстановитьОдинаковыеГраницы(комФигура,1); // включить при необходимости
                рПараграф=комТаблица.Cell(й,ы).Range.Paragraphs.Add();
            Иначе
                рПараграф=комТаблица.Cell(й,ы).Range.Paragraphs.Item(1);
            КонецЕсли;
            //
            Если элсод.Свойство("Текст") Тогда
                рПараграф.Range.Text=ПодготовитьТекстовыйФрагмент(элсод.Текст);
                рФорматПараграфа=рПараграф.Format;
                рФорматПараграфа.Alignment=?(элсод.Свойство("ВыравниваниеТекста"),элсод.ВыравниваниеТекста,рВыравниваниеТекста);
                //
                рОтступ=?(элсод.Свойство("Отступ"),элсод.Отступ,рОтступПоУмолчанию);
                Если рОтступ<>-1 Тогда
                    рФорматПараграфа.SpaceBeforeAuto=Ложь;
                    рФорматПараграфа.SpaceAfterAuto=Ложь;
                    рФорматПараграфа.FirstLineIndent=рОтступ;
                КонецЕсли;
                //
                рШрифт=?(элсод.Свойство("Шрифт"),элсод.Шрифт,рШрифтПоУмолчанию);
                комШрифт=рПараграф.Range.Font;
                УстановитьСвойстваШрифта(комШрифт,рШрифт);
            КонецЕсли;
            //
            Если элсод.Свойство("Фон") Тогда
                комФон=комТаблица.Cell(й,ы).Shading;
                комФон.Texture=элсод.Фон.Текстура;
                комФон.ForegroundPatternColor=элсод.Фон.ЦветТекстуры;
                комФон.BackgroundPatternColor=элсод.Фон.ЦветФона;
            КонецЕсли;
            //
            Если элсод.Свойство("Заголовок") Тогда
                комТаблица.Cell(й,ы).Range.Select();
                рПараметры.КомВорд.Selection.Style=рПараметры.КомДокумент.Styles(элсод.Заголовок);
            КонецЕсли;
            //
            Если элсод.Свойство("Закладка") Тогда
                КомЗакладки=рПараметры.КомДокумент.Bookmarks;
                КомЗакладки.Add(элсод.Закладка.Имя,комТаблица.Cell(й,1).Range);
                КомЗакладки.DefaultSorting=0; //wdSortByName
                КомЗакладки.ShowHidden=Ложь;
                мВсеЗакладки=?(рПараметры.Свойство("ВсеЗакладки"),рПараметры.ВсеЗакладки,Новый Массив);
                мВсеЗакладки.Добавить(Новый Структура("КомДиапазон,Имя",комТаблица.Cell(й,1).Range,элсод.Закладка.Имя));
                рПараметры.Вставить("ВсеЗакладки",мВсеЗакладки);
            КонецЕсли;
            //
            Если элсод.Свойство("Гиперссылка") Тогда
                рДиапазонГиперссылки=НайтиПодстроку(рПараметры.КомДокумент,,элсод.Гиперссылка.Текст);
                рПараметры.КомДокумент.Hyperlinks.Add(рДиапазонГиперссылки,"",элсод.Гиперссылка.ИмяЗакладки,"",элсод.Гиперссылка.Текст);
            КонецЕсли;
            //
            Если элсод.Свойство("ВложенныйОбъект") и ТипЗнч(элсод.ВложенныйОбъект)=Тип("Структура") Тогда
                парвлож=элсод.ВложенныйОбъект;
                парвлож.Вставить("ДиапазонПолучатель",комТаблица.Cell(й,ы).Range);
                Если парвлож.ВидОбъекта="НумерованныйСписок" Тогда
                    ДобавитьНумерованныйСписок(парвлож);
                ИначеЕсли парвлож.ВидОбъекта="МаркированныйСписок" Тогда
                    ДобавитьМаркированныйСписок(парвлож);
                ИначеЕсли парвлож.ВидОбъекта="Текст" Тогда
                    ДобавитьТекст(парвлож);
                ИначеЕсли парвлож.ВидОбъекта="Таблица" Тогда
                    ДобавитьТаблицу(парвлож);
                КонецЕсли;
            КонецЕсли;
            //
        КонецЦикла;
    КонецЦикла;
    //
    Если рБезГраниц Тогда
        УстановитьОдинаковыеГраницы(комТаблица);
    КонецЕсли;
    //
Исключение
    Сообщить("ДобавитьТаблицу, ошибка: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
КонецПопытки;
КонецПроцедуры

#КонецОбласти

Ну и собственно подключение и вызов: 
    Попытка
        КомДокумент=КомВорд.Documents.Add();
        // Здесь выводим нужные данные
        КомВорд.Visible=1;
        КомВорд.Activate();
        КомВорд.ActiveWindow.WindowState=1;
    Исключение
        КомВорд.Quit(0);
        КомВорд=0;
        Сообщить("Ошибка при выводе данных в MS Word: "+ОписаниеОшибки(),СтатусСообщения.ОченьВажное);
    КонецПопытки;

 

Если кому пригодится, то и хорошо.

См. также

Комментарии
1. Павел Жихарев (palsergeich) 25.08.15 01:13 Сейчас в теме
Замечательная статья, попадись она мне на глаза месяц назад, очень сильно помогла бы, а так как надо было срочно, и со знанием других языков у меня небольшие проблемы, пришлось изобретать велосипеды.
От себя могу добавить, один из костылей, мной используемый, замещающий текст в методе find имеет ограничение по количеству символов, обошел через использование буфера обмена, методом find устанавливаю курсор, и туда вставляю из буфера...
2. velll111 velll111 (velll111) 26 25.08.15 09:28 Сейчас в теме
Спасибо, очень интересная статья, Ctrl+C и Ctr+V уже сделал)))
3. Евгений (Пользователь 1С) 2 25.08.15 09:43 Сейчас в теме
С шаблонами проще текст набивать. Здесь, я так понял, весь текст документа нужно программно добавлять. Возможно, удобно реализовать через чтение внешнего текстового файла с заготовками содержания документа. Ну а в целом, полезная статья, спасибо!
4. Алексей 1 (AlX0id) 25.08.15 10:30 Сейчас в теме
По-настоящему тру было бы собрать docx с нуля как xml-ку ) И не надо было бы этих чудо-ком-объектов с их заморочками с правами и разрядностью..
cool.vlad4; N!ghtmare; madonov; +3 Ответить 5
5. Яков Коган (Yashazz) 1977 25.08.15 11:25 Сейчас в теме
(4) AlX0id, да, это было бы реально тру. Но даже простейший экселевский файл так собирать нелегко, много малоизвестных граблей (намучился уже с этим), тем более вордовский - тут надо досконально знать внутреннюю структуру. Ну и с картинками неясно - через Base64 их гонять, что ли? Не факт, что получится.
...а вот интересно, для OpenOffice такое возможно, собрать через xml?
6. Алексей 1 (AlX0id) 26.08.15 23:53 Сейчас в теме
(5) Yashazz,
С картинками-то как раз все довольно понятно - они просто в отдельной папке хранятся в архиве. А в xml-ке документа описываются элементом <w:drawing>. Что, в общем-то легко проверить, создав элементарный документ и посмотрев, что же лежит в архиве )
А по поводу малоизведанных граблей - это, конечно, да, без этого вообще никуда )
Но мне кажется, что задача вполне решаема. по крайней мере, на уровне несложных объектов типа списков, абзацев и т.п. Времени только займет куда как больше, нежели использование чудо-ком-объектов - это точно.
7. Евгений Мадонов (madonov) 142 31.08.15 07:30 Сейчас в теме
(4) AlX0id, Да!
Давайте напишем свой опен офис на языке 1С!

Начать можно с Word и Excel.

ЗЫ. Особенно забавно было бы реализовать функционал Access внутри 1С - трушность бы просто зашкалила =)) .
investec; lx@; +2 Ответить
8. Сергей Ожерельев (Поручик) 3492 01.09.15 11:38 Сейчас в теме
(4) В одном нашем проекте есть сборка документа из вороха xml на сервере. И оно работает в нескольких организациях.
9. Вадим Латышев (pro1c@inbox.ru) 156 19.09.15 15:47 Сейчас в теме
Плюсую. Только такое понадобилось, а вы тут как тут!
10. Ийон Тихий (cool.vlad4) 41 26.10.15 01:23 Сейчас в теме
(4) AlX0id, однозначно. в дотнете например это делается достаточно просто (через openxml sdk , плюс есть еще некоторые библиотеки вокруг этого) . жаль только нет такого для 1С . (можно написать компоненту , иметь лишнюю зависимость и лишний гемор при развертывание решения)
11. Ийон Тихий (cool.vlad4) 41 26.10.15 01:25 Сейчас в теме
(4) AlX0id, кстати проблема не столько в правах и заморочках com объектов (это было бы полбеды), сколько в конкретной избыточной и тормозной реализации в офисе. многие вещи делаются через Selection , что тормоз, что тот еще глюк . (попробуйте параллельно при формировании большого документа, работать в другом word-е и при это что-то активно копировать и вставлять)
12. Ийон Тихий (cool.vlad4) 41 26.10.15 01:27 Сейчас в теме
(1) palsergeich, добавлю еще , что при некоторых условиях find иногда в цикле (а если мне не изменяет память, так сделано в типовых на основе БСП) может уходить в бесконечный цикл. такое случается и проблема гуглится.
13. Ийон Тихий (cool.vlad4) 41 26.10.15 01:37 Сейчас в теме
(5) Yashazz,
...а вот интересно, для OpenOffice такое возможно, собрать через xml?

естественно. это можно сказать под их некоторым влиянием потом уже микрософт решил реализовывать открытый формат openxml для word/excel и т.д. см https://ru.wikipedia.org/wiki/OpenDocument
14. Владислав Лисовенко (VladC#) 59 10.02.16 05:09 Сейчас в теме
Автор определённо погорячился, добавив в заголовок фразу "по простому", в чём простота, если код занимает несколько экранов?
15. Яков Коган (Yashazz) 1977 10.02.16 18:09 Сейчас в теме
(14) VladC#, ну, наверное, в эксплуатации? )))

...И правда, забавно: привёл, понимаешь, читателям исходник функции, которую 1 раз скопипастить надо, а дальше просто вызывать. Думал, это просто. Но, оказывается, скопипастить - мегасложно. И вызывать потом, следуя нехитрым примерам - архисложно.
Наверное, я-таки погорячился.
investec; +1 Ответить