Этюды по программированию. Взаимодействие с Microsoft Word




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

23 Comments

  1. vladismi

    Запомним как упорядочение приемов.

    Однако предложенное 1С использование копипаста втыкает в файл-приемник всю ту грязь, которую пользователь копирует у себя пока программа готовит вордовый документ…

    «А мужики то не знают»…

    🙁

    Reply
  2. 🅵🅾️🆇

    Вся эта объектная модель взаимодействия требует установленного ворда.

    Была у меня идейка извратиться и написать взаимодействие с Word’овскими макетами через ЧтениеДанных для простых задач.

    Работать должно в разы быстрее и не требовать офиса.

    Раньше также делал дикую вложенность как у вас:

    Для Каждого Закладка из Word.ActiveDocument.Bookmarks Цикл
    Если Найти(Закладка.Name,ИмяЗакладки) Тогда
    Если ЗначениеЗаполнено(Файл) Тогда
    …
    КонецЕсли; // Если ЗначениеЗаполнено(Файл)
    КонецЕсли; // Если Найти(Закладка.Name,ИмяЗакладки)
    КонецЦикла; // Для Каждого Закладка из Word.ActiveDocument.Bookmarks
    

    Показать

    Попробуйте такую запись, она легче читается:

    Для Каждого Закладка из Word.ActiveDocument.Bookmarks Цикл
    Если Не Найти() Тогда Продолжить КонецЕсли;
    Если Не ЗначениеЗаполнено(Файл) Тогда Продолжить КонецЕсли;
    …
    КонецЦикла; // Для Каждого Закладка из Word.ActiveDocument.Bookmarks
    

    Можно и от цикла избавиться с помощью рекурсивных процедур или goto, но это уже извращение)))

    Reply
  3. s_vidyakin

    Я сделал с полпинка на node.js — передаешь по GET или POST набор параметров «Ключ:Значение» и имя шаблона, в 1С приходит готовый docx, удобно когда на сервере нет офиса или COMОбъект не работал как у нас (наверно 64битность имеет значение)

    Reply
  4. cool.vlad4

    (2) (3) я писал COM сервер на C# по прикручиванию https://habrahabr.ru/post/269307/ , работает быстрее некуда, работает на сервере, из минусов только, что нужно особым образом делать шаблоны и нет универсальности, поэтому и не выкладываю

    Reply
  5. 🅵🅾️🆇

    (4) Здорово, но все еще медленнее чем чтение из потока двоичных данных)

    Reply
  6. Rustig

    автор «собаку съел» на шаблонах Word и автоматизации взаимодействия 1с и Word

    респект

    Reply
  7. wonderboy

    Если разрешите — несколько замечаний / дополнений:

    1. Вы закладку ищете в цикле. Это лучше делать так:

    Если WordFile.Bookmarks.exists(ИмяЗакладки) Тогда
    ТекЗакладка = WordFile.Bookmarks.Item(ИмяЗакладки);
    …
    

    2. По замене маркеров на нужный текст

    У Вас предлагается поиск и замена, но не учитывается, что таким образом можно вставить текст только до 250 символов

    Если текст больше — будет исключение. Приходится его разбивать на части и вставлять кусками — так было реализовано в 1С: Документообороте раньше.

    Сейчас в БСП сделано концептуально правильно. См. общий модуль УправлениеПечатьюMSWordКлиент, процедура Заменить. Через Selection.TypeText(…).

    И я бы рекомендовал использовать все же закладки. В Word-документе кроме основного контента и колонтитулов есть еще и другие Story (например, текст в надписях графических объектов). В каждой Story нужно отдельно поиск выполнять.

    Есть универсальное решение

    https://infostart.ru/public/662990/

    Отлаженное, на его внедрениях тоже много Word’овских собак съедено 🙂

    Reply
  8. Serg O.

    Хорошая статья и бесплатная !

    Reply
  9. rpgshnik

    Добрый день.

    Смотрю вы работали плотно с WORD, может знаете как программно через 1С задать стиль всему документу по умолчанию 2003, вместо 2010?

    Reply
  10. milkers

    (9) Нет, не приходилось сталкиваться. Может кто-нибудь из коллег подскажет.

    Reply
  11. wonderboy

    (9) Дмитрий, а как бы вы это вручную сделали?

    Reply
  12. rpgshnik

    (11) в ручную просто. Скриншот прилагаю.

    Reply
  13. wonderboy

    (12) Если знаете как сделать вручную — тогда включаете запись макроса, выполняете действие, останавливаете запись. Потом этот макрос открываете, смотрите как на VB эти действия делаются. Обычно этот код можно из 1С выполнить (с небольшими адаптациями).

    Вот для вашего случая что получилось:

    ActiveDocument.ApplyQuickStyleSet («Word 2003»)

    Reply
  14. rpgshnik

    (13) спасибо за идею!

    Но решил отказаться от типового функционала по разбору ворда. Написать свой.

    Я как понял баг заключался в том, что типовой функционал копирует участок текста и вставляет в свежий открытый документ. И естественно слитают стили. Но я ведь не могу всегда быть увереным что будет точно 2003… по этому буду как автор публикации ваят свой парсер ворда.

    Хотя почти во всех конфигах даже старых типа КА1.1 есть модули по работе с шаблонами ворда. Эх.

    Reply
  15. sulitckaja

    Может сможет подсказать кто, как в документе Word скопировать и вставить ниже уже существующую таблицу.

    Делаю так:

    Шаблон = Новый COMОбъект(«Word.Application»);
    Шаблон.Documents.Open(ИмяФайлаПолное);
    Шаблон.Application.Documents(1).Content.Tables(1).Range.Copy();
    Шаблон.Application.Documents(1).Content.InsertParagraphAfter();
    Шаблон.Application.Documents(1).Content.Paste();

    В результате таблица копируется но весь текст и предыдущая таблица исчезает.

    Reply
  16. wonderboy

    (15) Предполагаю что Content.Paste() делает вставку вместо всего контента. Вам наверное нужно вставить вместо последнего параграфа, который вы добавили.

    Что-то вроде

    КоличествоПараграфов = Шаблон.Application.Documents(1).Paragraphs.Count;
    Шаблон.Application.Documents(1).Paragraphs(КоличествоПараграфов-1).Range.Paste()
    Reply
  17. sulitckaja

    (16)Спасибо большое. Сейчас попробую.

    Reply
  18. sulitckaja

    (16)

    КоличествоПараграфов = Шаблон.Application.Documents(1).Paragraphs.Count;

    Шаблон.Application.Documents(1).Paragraphs(КоличествоПараграфов-1).Range.Paste()

    (16)

    Все получилось! А можно еще один вопрос? Как сделать так, чтобы таблица копировалась с нового листа?

    Reply
  19. wonderboy

    (18) Я вам с ходу не подскажу. Наверное что-то вроде

    Шаблон.Application.Documents(1).Selection.EndKey(6);
    Шаблон.Application.Documents(1).Selection.InsertBreak();
    

    перед добавление параграфа.

    Reply
  20. sulitckaja

    (19)Спасибо!

    Reply
  21. rmarkovych

    Спасибо автору.

    Хотелось бы еще добавить (может кому пригодиться) поиск и замену колонтитулов в макете Word:

    MSWord = Новый COMОбъект(«Word.Application»);

    MSWord.ActiveDocument.Sections(1).Headers(1).Range.Find.Execute(«<ТекстИскомогоКолонтитула>»,,,,,,-1,,,<НужныйТекст>, 2); — верхний

    MSWord.ActiveDocument.Sections(1).Footers(1).Range.Find.Execute(«<ТекстИскомогоКолонтитула>»,,,,,,-1,,,<НужныйТекст>, 2); — нижний

    Reply
  22. coollerinc

    Зачем в типовом механизме пользовательских шаблонов делают через копирование шаблона? например при копировании не копируются оформление страницы. Сейчас придется изобретать велосипед

    Reply
  23. bercut0077

    Спасибо! Отличная статья. Вопрос автору — как можно вставить из файла HTML в Word информацию (с конвертацией)

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *