Добрый день. В первую очередь хочу сказать, что все, что будет описано ниже, конечно же, является колхозом и костылями, но до тех пор, пока разрабы не сделают что-то годное, это решение оказалось самым универсальным в сравнении с тем, что находил на просторах. Также нужно сказать, что материал сырой и его можно дорабатывать хоть до посинения, начиная от параметров ячеек таблицы, заканчивая типами доп. реквизитов и количеством табличных частей.
Итак, к истории. Необходимость ТЧ в документе и шаблоне файлов очень давно остро преследовала меня и конторку в которой я это и реализовывал. Основным тезисом к разработке являлось универсальность ТЧ, т.к. лепить новую к каждому виду документа в конфигураторе – прямо скажем так себе (одну таки прилепить нужно будет). Значит необходима привязка к доп. реквизитам. С этого и начал.
1. В расширение тянем ДокументыПредприятия и добавляем ТЧ без конкретного наименования. В нее закладываем реквизиты а1 – а5 (количество выбрал произвольно, по факту определяет максимальное количество столбцов в ТЧ). Тип всех реквизитов устанавливаем ЛюбаяСсылка, Булево, Строка (длина ограниченная), Дата, Число. Добавляем на форму и скрываем видимость. Позже будем по условию выводить.
2. В расширение тянем справочник ВидыДокументов, добавляем реквизит Расш1_ДобавитьТаблицу тип Булево. Добавляем на форму настроек вида документа. Думаю, можно было пробовать сделать это через доп. реквизиты, но повторюсь – разработка сырая.
3. Теперь настроим видимость таблицы в процедуре формы Расш1_ПриОткрытииПеред. Есть возможность добавлять саму таблицу и устанавливать видимость в общем модуле УправлениеСвойствами процедуре Расш1_ПрисозданииНаСервере, но мне было лень :) , поэтому все делал прямо на форме:
&НаКлиенте
Процедура Расш1_ПриОткрытииПеред(Отказ)
Если ВидимостьТаблицы(Объект.ВидДокумента) Тогда
ДобавитьТЧ();
КонецЕсли;
КонецПроцедуры
//Определяет необходимость изменения видимости таблицы
&НаСервере
Функция ВидимостьТаблицы(ВидДокумента)
Возврат ВидДокумента.Расш1_ДобавитьТаблицу;
КонецФункции
&НаКлиенте
Процедура ДобавитьТЧ();
МассивКолонок = ПолучитьТаблицуКолонок();
Если МассивКолонок.Количество() <> 0 Тогда
ЭтаФорма.Элементы.Расш1_ТабличнаяЧасть1.Видимость = Истина;
Для каждого Элемент из МассивКолонок Цикл
Переменная = 0;
КолонкаТаблицы = ЭтаФорма.Элементы.Найти("Расш1_ТабличнаяЧасть1а" + Элемент.Порядок);
КолонкаТаблицы.Видимость = Истина;
КолонкаТаблицы.Заголовок = Элемент.Представление;
Если Элемент.Тип.Типы().Количество() = 1 Тогда //Если 1 Тогда тупо строка
КолонкаТаблицы.ДоступныеТипы = Элемент.Тип;
КолонкаТаблицы.ОграничениеТипа = Элемент.Тип;
Иначе
Индекс = 0;
МассивТипов = Новый Массив;
Для каждого ЭлементТип из Элемент.Тип.Типы() Цикл
Если ЭлементТип = Тип("Строка") Тогда
Иначе
МассивТипов.Добавить(ЭлементТип);
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТипов);
КолонкаТаблицы.ДоступныеТипы = НовоеОписаниеТипов;
КолонкаТаблицы.ОграничениеТипа = НовоеОписаниеТипов;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПолучитьТаблицуКолонок()
ДопРеквизиты = Объект.ДополнительныеРеквизиты;
ТаблицаКолонок = Новый ТаблицаЗначений;
ТаблицаКолонок.Колонки.Добавить("Наименование");
ТаблицаКолонок.Колонки.Добавить("Порядок");
ТаблицаКолонок.Колонки.Добавить("Тип");
ТаблицаКолонок.Колонки.Добавить("Представление");
Для каждого ДопРеквизит из ДопРеквизиты Цикл
СтруктураКолонки = Новый Структура;
НоваяСтрока = ТаблицаКолонок.Добавить();
ТаблицаНомер = Лев(ДопРеквизит.Свойство.Наименование,СтрНайти(ДопРеквизит.Свойство.Наименование,"(")-1);
НоваяСтрока.Порядок = Сред(ТаблицаНомер,8);
НоваяСтрока.Наименование = Сред(ДопРеквизит.Свойство.Наименование,СтрНайти(ДопРеквизит.Свойство.Наименование,"(")+1,
СтрНайти(ДопРеквизит.Свойство.Наименование,")")-СтрНайти(ДопРеквизит.Свойство.Наименование,"(")-1);
НоваяСтрока.Тип = ДопРеквизит.Свойство.ТипЗначения;
НоваяСтрока.Представление = ДопРеквизит.Свойство.Комментарий
КонецЦикла;
ТаблицаКолонок.Сортировать("Порядок, Наименование, Тип, Представление");
МассивКолонок = Новый Массив;
Для каждого Строка из ТаблицаКолонок Цикл
СтруктураКолонки = Новый Структура;
СтруктураКолонки.Вставить("Порядок", Строка.Порядок);
СтруктураКолонки.Вставить("Наименование",Строка.Наименование);
СтруктураКолонки.Вставить("Тип",Строка.Тип);
СтруктураКолонки.Вставить("Представление",Строка.Представление);
МассивКолонок.Добавить(СтруктураКолонки);
КонецЦикла;
Возврат МассивКолонок;
КонецФункции
4. Теперь переходим от программной части к пользовательской (админской скорее). Создаем вид документа. Для примера создадим вид документа предложение для работы в выходной (для скрина убрал видимость части реквизитов настроек вида документа). Установим галочку Добавить таблицу.
5. Создадим доп. реквизиты для документа. Наименование реквизита состоит из: якоря в виде ключевого слова «Таблица», порядковым номером столбца и назначением. При этом наименование колонки, выводимое в форму, необходимо прописать в комментарии к доп. реквизиту. Если тип ячейки мы хотим ссылочный – устанавливаем составной тип данных и устанавливаем необходимую ссылку и строку. В случаях где тип данных Булево, Строка, Число или Дата необходимости устанавливать составной тип нет. Видимость реквизитов необходимо скрыть, для этого установим видимость с невозможным условием.
6. Далее в настройках вида документа в создании документов на странице Свойства необходимо задать значение для реквизитов. Везде выбираем строку, текст строки не важен, я для примера ввел "0". Это нужно для того, чтобы при открытии увидеть реквизиты и обозвать колонки.
7. На этом самый легкий этап закончен. Мы добавили универсальную таблицу с нужными колонками, типами, количеством и порядком колонок.
8. Небольшая подготовка к выводу ТЧ в word. Создадим шаблон файла для будущей таблицы. В файле делаем якорь с закладкой «СделатьТаблицу» и текстом « ». Создаем нужные нам колонки таблицы, выставляем ширину строк тоже тут. Если развивать далее то и цвет и другие параметры можно тут настроить, ток надо будет код далее доработать))). Порядок колонок должен соответствовать порядку колонок ТЧ добавленной в документ (номер строки игнорируется).
9. Также нам понадобится заполнить наш «СделатьТаблицу», для этого в настройках заполнения файла на закладке «Скрипты» добавляем для нашего поля скрипт, где РезультатОбработки = «»; Готово. Можно писать обработку.
10. Далее самое сложное – засунуть в ворд файл данные нашей универсальной таблицы. Для этого использовал процедуру ЗаполнитьПоляФайлаДаннымиВладельца общего модуля АвтозаполнениеШаблоновФайловКлиентСервер. По условию необходимости таблицы (проверят по файлу также вид документа на наличия необходимости таблицы).
Если ФорматMSWord(РасширениеФайла) Тогда
#Вставка
Если Расш1_Один.НеобходимаТаблица(ФайлСсылка, МассивЗамен) Тогда
#Если Клиент Тогда
ДвоичныеДанныеЗаполненногоФайла = Расш1_ОдинКлиент.ЗаполнитьФайлMSWordПоДвоичнымДаннымТаблица(РасширениеФайла, МассивЗамен,
ДвоичныеДанныеФайла);
ДанныеВозврата.Вставить("Результат", Истина);
#КонецЕсли
Иначе
ДвоичныеДанныеЗаполненногоФайла = ЗаполнитьФайлMSWordПоДвоичнымДанным(РасширениеФайла, МассивЗамен,
ДвоичныеДанныеФайла);
ДанныеВозврата.Вставить("Результат", Истина);
КонецЕсли;
#КонецВставки
#Удаление
ДвоичныеДанныеЗаполненногоФайла = ЗаполнитьФайлMSWordПоДвоичнымДанным(РасширениеФайла, МассивЗамен,
ДвоичныеДанныеФайла);
ДанныеВозврата.Вставить("Результат", Истина);
#КонецУдалени
В функции, проверяющей наличие таблицы, мы заодно добавляем нашу таблицу в массив структур для передачи далее по клиенту до ЧтенияXML
Функция НеобходимаТаблица(ФайлСсылка, МассивЗамен) Экспорт
Попытка
Результат = ФайлСсылка.ВладелецФайла.ВидДокумента.Расш1_ДобавитьТаблицу;
Для каждого Элемент из МассивЗамен Цикл
Если Элемент.ТермДляЗамены = "СделатьТаблицу" Тогда
Элемент.Вставить("ТаблицаЗначений", ОбщегоНазначения.ТаблицаЗначенийВМассив(ФайлСсылка.ВладелецФайла.Расш1_ТабличнаяЧасть1.Выгрузить()));
КонецЕсли;
КонецЦикла;
Возврат Результат;
Исключение
Возврат Ложь;
КонецПопытки;
КонецФункции
11. Дальше я откопировал типовой функционал вплоть до процедуры ВыполнитьЗаменуПолейИСтрокВДокументеMSOfficeOpenXML, где выполняется ЧтениеXML, которую и дописывал. Функция большая не хочу вписывать сюда, оставлю все функции в файлах темы. Основная задача тут заключается в том, чтобы: 1) найти таблицу; 2) определить параметры колонок (я для примера определял только ширину, но как и говорил – материал сырой можно развивать до посинения); 3) заполнить таблицу по данным структуры параметров, которую мы загрузили на этапе проверки необходимости таблицы; 4) перезаписывать таблицу, а не записывать новую (т.к. по сути каждый раз при заполнении таблицы мы создаем копию таблицы, тогда при повторном выполнении сей процедуры мы прочитаем старые повторно и если ничего не сделаем процедура сама их перезаполнит в документ).
1) Самое простое, в процедуре ВыполнитьЗаменуПолейИСтрокВДокументеMSOfficeOpenXML есть кусок, где и происходят замены, значит необходимо в этом куске сделать проверку на наш якорь «СделатьТаблицу»
Если НастройкаЗамены.ТермДляЗамены = "СделатьТаблицу" И НастройкаЗамены.ЗначениеЗамены = " " Тогда
ТаблицаДалее = Истина;
КонецЕсли;
2) Если мы установили факт, что таблица начнется далее, значит следующее начало элемента ЧтенияXML будет содержать параметры колонок и строк, которые мы и запишем. Для этого необходимо несколько якорей, во-первых на начало моей таблицы (у меня в примере две таблицы, вторая должна остаться неизменной), и первой строки ЧтениеXML.Имя = "w:tr" (для того, чтобы не записывать следующие строки), во-вторых на начало ячейки ЧтениеXML.Имя = "w:tc" (для того, чтобы создать структуру параметров колонок которую потом при обходе атрибутов будем заполнять, я записал только ширину, но можно доработать до всего), и наконец нам нужен якорь на открытие параметров этой ячейки ЧтениеXML.Имя = "w:tcPr" (для записи параметров ячейки при чтении атрибутов).
//Для красоты убрал родной код в примере, по факту все вставлено в родную процедуру, ничего из нее не удалял
ТаблицаДалее = Ложь;
НайденаТаблицаМоя = Ложь;
ЗаписатьПараметрыСтроки = Ложь;
МассивКолонок = Новый Массив;
ЗаписатьПараметрыЯчейки = Ложь;
НайденаЯчейка = Ложь;
КолвоКолонок = 0;
СоздатьТаблицу = Ложь;
ПропускатьСтроки = Ложь;
Пока ЧтениеXML.Прочитать() Цикл
Если ПропускатьСтроки И ЧтениеXML.Имя <> "w:tbl" Тогда // 4)
Продолжить; // 4)
КонецЕсли;// 4)
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если ЧтениеXML.Имя = "w:tbl" И ТаблицаДалее Тогда
НайденаТаблицаМоя = Истина;
КонецЕсли;
Если ЧтениеXML.Имя = "w:tr" И НайденаТаблицаМоя Тогда
ЗаписатьПараметрыСтроки = Истина;
КонецЕсли;
Если ЧтениеXML.Имя = "w:tc" И НайденаТаблицаМоя Тогда
НайденаЯчейка = Истина;
СтуктураПараметровЯчейки = Новый Структура;
КолвоКолонок = КолвоКолонок + 1;
КонецЕсли;
Если ЧтениеXML.Имя = "w:tcPr" И НайденаТаблицаМоя Тогда
ЗаписатьПараметрыЯчейки = Истина;
КонецЕсли;
Если ЧтениеXML.КоличествоАтрибутов() > 0 Тогда
Пока ЧтениеXML.ПрочитатьАтрибут() Цикл
Если ЗаписатьПараметрыЯчейки И ЧтениеXML.Имя = "w:w" Тогда
СтуктураПараметровЯчейки.Вставить("Номер",КолвоКолонок);
СтуктураПараметровЯчейки.Вставить("Длина",ЧтениеXML.Значение);
МассивКолонок.Добавить(СтуктураПараметровЯчейки);
КонецЕсли;
3) Вывод таблицы. Для вывода таблицы определяем ЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента и тег «w:tbl».
ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
Если ЧтениеXML.Имя = "w:tbl" И ТаблицаДалее Тогда // 3)
НайденаТаблицаМоя = Ложь;
ТаблицаДалее = Ложь;
ПропускатьСтроки = Ложь;
СоздатьТаблицуВXML(ЗаписьXML, МассивДанныхДляАвтоЗамен, МассивКолонок);
КонецЕсли;
Если ЧтениеXML.Имя = "w:tr" И НайденаТаблицаМоя Тогда // 4)
ЗаписатьПараметрыСтроки = Ложь;
СоздатьТаблицу = Истина;
ПропускатьСтроки = Истина;
КонецЕсли;
Если ЧтениеXML.Имя = "w:tc" И НайденаТаблицаМоя Тогда
НайденаЯчейка = Ложь;
КонецЕсли;
Если ЧтениеXML.Имя = "w:tcPr" И НайденаТаблицаМоя Тогда
ЗаписатьПараметрыЯчейки = Ложь;
КонецЕсли;
Процедура СоздатьТаблицуВXML(ЗаписьXML, МассивДанныхДляАвтоЗамен, МассивКолонок)
Для каждого Элемент из МассивДанныхДляАвтоЗамен Цикл
Если Элемент.ТермДляЗамены = "СделатьТаблицу" Тогда
МассивТаблицы = Элемент.ТаблицаЗначений;
Прервать;
КонецЕсли;
КонецЦикла;
Строка = "";
МаксимумКолонок = МассивКолонок.Количество();
Для каждого Элемент из МассивТаблицы Цикл
Строка = Строка + "<w:tr w:rsidR=""00FA556A"" w:rsidTr=""00FA556A"">";
Счетчик = 0;
Для каждого ЭлементСтруктуры из Элемент Цикл
Если МаксимумКолонок <= Счетчик Тогда
Прервать;
КонецЕсли;
Если ЭлементСтруктуры.Ключ = "НомерСтроки" Тогда
Продолжить;
КонецЕсли;
Строка = Строка + "<w:tc><w:tcPr><w:tcW w:w=""" + МассивКолонок[Счетчик].Длина + """ w:type=""dxa""/></w:tcPr><w:p w:rsidR=""00FA556A"" w:rsidRPr=""00D30E55"" w:rsidRDefault=""00FA556A"" w:rsidP=""00660C27""><w:pPr><w:rPr><w:sz w:val=""28""/><w:szCs w:val=""28""/></w:rPr></w:pPr><w:r><w:rPr><w:sz w:val=""28""/><w:szCs w:val=""28""/></w:rPr><w:t>" + ЭлементСтруктуры.Значение + "</w:t></w:r></w:p></w:tc>";
Счетчик = Счетчик + 1;
КонецЦикла;
Строка = Строка + "</w:tr>";
КонецЦикла;
ЗаписьXML.ЗаписатьБезОбработки(Строка);
КонецПроцедуры
4) Таким образом при первом заполнении данных документа мы перезапишем имеющийся word файл, добавив туда не только данные документа, но и данные нашей таблицы. Остается одна проблема: при повторном выполнении процедуры мы фактический добавим к текущим строкам новые, т.к. Чтение будет читать все добавленные ранее строки, пока не дойдет в конец элемента «w:tbl», где и происходи добавление новой таблицы. Значит необходимо пропустить все строки после шапки, для этого находим ЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента и по ЧтениеXML.Имя = "w:tr" установим новый якорь ПропускатьСтроки (также выше при вызове процедуры заполнения таблицы, якорь нужно будет снять, чтобы остальные строки не пропустились).
В коде подпункта "3)" прописано.
А в начале цикла ЧтенияXML добавить проверку независимую от типа узла (до условия проверки начало, текста и конца) на якорь ПропускатьСтроки и все, кроме «w:tbl», т.к. следующий w:tbl будет закрытием таблицы, где мы заполним данные (в коде подпункта 2 прописал).
12. Готово. Все это дело работает от кнопки «Заполнить файл данными документа». Ограничением этой схемы является конечно порядок колонок (в шаблон файла шапку таблицы необходимо рисовать в соответствии порядком колонок в ТЧ).
В заключение: надеюсь, было интересно, мне самому очень понравилось это все мучить (мучить буквально: я раз двадцать ломал файлы, пока понимал, где ошибка в xml коде). Буду рад новым взглядам к теме. Из того что хочу доработать тут: вывод ТЧ и реквизита НужнаТаблица через общий модуль УправлениеСвойствами, а также придумать как сопоставить колонки из шапки с колонками ТЧ, чтобы не приходилось рисовать таблицу ворда в соответствии с ТЧ.
Тестировалось на платформе 8.3.27.1508, версия ДО 3.0.16.33.
Вступайте в нашу телеграмм-группу Инфостарт