Как-то раз передо мной стояла задача реализовать генерацию договоров для клиентов с нашего корпоративного сайта.
Сначала задача была решена просто ужасно — был заготовлен html шаблон договора, а пользователю выдавалась конвертация шаблона в pdf. Само собой это выливалось в кучу неудобств, в том числе, если требовалось что-то поменять в договоре.
Следующим решением было генерация odt документа. Это позволило редактировать документ нашим менеджерам независимо от сайта и программистов.
Полностью генерировать с нуля смысла нет. Почему бы не поработать с уже имеющимся файлом (отредактированным в OpenOffice) и просто заменить в нем необходимые элементы?
Этим мы с вами и займемся.
Но для начала…
UPD! Вторая статья с исправлением ошибок этой — habrahabr.ru/blogs/php/87254
Пару комментариев:
— Мы сильно ограничим нашу задачу изменением данных только в тексте документа, а также только текстовых переменных.
— Для решения данной задачи лично я использовал SimpleXML, ZIPArchive. Никто Вам не запрещает пользоваться другими инструментами.
— То, что описано в статье — упрощенный и урезанный пример, а не готовый инструмент.
Создание шаблона в OpenOffice:
Создаем обычный документ odt и в нужных местах вставляем пользовательские переменные:
Меню «Вставка» -> Поля -> Дополнительно
Вкладка «Переменные»
Выбираем «поле пользователя» и добавляем/вставляем поля как показано на изображении.
Сохраняем наш файл. В моем примере это файл test.odt.
Загружаем файл на сервер:
ODT как и любой ODF файл, как многим наверно известно, является обычным ZIP архивом.
upload.php
При успешной загрузке мы получаем папку с содержимым odt файла и doc.list со списком файлов.
Отдаем измененный файл пользователю:
Нам нужно заменить значения пользовательских полей и сжать все обратно в архив.
download.php
Вуаля.
Важные замечания и ссылки:
— В примере я использовал только текстовые поля, но Вы можете использовать также и другие типы полей.
— В ODT есть также возможность использоваться условные элементы (например часть текста показывается или не показывается в зависимости от условия — например значения пользовательского поля)
— В примере я менял значения полей только в content.xml. Но поля могут использоваться и в других файлах, например в styles.xml находятся колонтитулы.
— спецификация ODF (точнее Open Document)
— Как и всё в мире — пример можно оптимизировать. Например, если требуется менять только content.xml, то никто не запрещает подготовить заранее архив, а при запросе пользователя заменять/добавлять в него этот файл.
Скачать исходник целиком
Сначала задача была решена просто ужасно — был заготовлен html шаблон договора, а пользователю выдавалась конвертация шаблона в pdf. Само собой это выливалось в кучу неудобств, в том числе, если требовалось что-то поменять в договоре.
Следующим решением было генерация odt документа. Это позволило редактировать документ нашим менеджерам независимо от сайта и программистов.
Полностью генерировать с нуля смысла нет. Почему бы не поработать с уже имеющимся файлом (отредактированным в OpenOffice) и просто заменить в нем необходимые элементы?
Этим мы с вами и займемся.
Но для начала…
UPD! Вторая статья с исправлением ошибок этой — habrahabr.ru/blogs/php/87254
Пару комментариев:
— Мы сильно ограничим нашу задачу изменением данных только в тексте документа, а также только текстовых переменных.
— Для решения данной задачи лично я использовал SimpleXML, ZIPArchive. Никто Вам не запрещает пользоваться другими инструментами.
— То, что описано в статье — упрощенный и урезанный пример, а не готовый инструмент.
Создание шаблона в OpenOffice:
Создаем обычный документ odt и в нужных местах вставляем пользовательские переменные:
Меню «Вставка» -> Поля -> Дополнительно
Вкладка «Переменные»
Выбираем «поле пользователя» и добавляем/вставляем поля как показано на изображении.
Сохраняем наш файл. В моем примере это файл test.odt.
Загружаем файл на сервер:
ODT как и любой ODF файл, как многим наверно известно, является обычным ZIP архивом.
upload.php
<?php
//путь к временному архиву
$tmpfile='upload/temp.zip';
//сохраняем полученный документ
if (isset($_FILES['document']) and move_uploaded_file($_FILES['document']['tmp_name'], $tmpfile)) {
// функция удаления директории
function deleteDirectory($dir) {
if (!file_exists($dir)) return true;
if (!is_dir($dir) || is_link($dir)) return unlink($dir);
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') continue;
if (!deleteDirectory($dir . "/" . $item)) {
chmod($dir . "/" . $item, 0777);
if (!deleteDirectory($dir . "/" . $item)) return false;
};
}
return rmdir($dir);
}
// удаляем директорию с содержимым документа и создаем заново
// при желании, можно перемещать старую версию куда-либо, сделав тем самым версионность документа
deleteDirectory('doc/');
mkdir('doc/');
// извлекаем архив
$zip = new ZipArchive;
if ($zip->open($tmpfile) === TRUE) {
// Сохраняем пути к файлам в нужной последовательности
// Это нам понадобится в будущем.
// Например, по требованию формата odf , файл mimetype должен быть первым в архиве.
$files=array();
for($i = 0; $i < $zip->numFiles; $i++) {
$files[]=$zip->getNameIndex($i);
}
file_put_contents("doc.list",implode("\n",$files));
//извлекаем
$zip->extractTo('doc/');
$zip->close();
} else {
die("zip error");
}
unlink ($tmpfile);
$print='Файл успешно загружен';
}
else {
$print='
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="document"><br>
<input type="submit" value="Загрузить"><br>
</form>';
}
print '<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Загрузка документа</title>
</head>
<body>
'.$print.'
</body>
</html>';
?>
При успешной загрузке мы получаем папку с содержимым odt файла и doc.list со списком файлов.
Отдаем измененный файл пользователю:
Нам нужно заменить значения пользовательских полей и сжать все обратно в архив.
download.php
<?php
//путь к временному файлу
$tmpfile='download/doc.odt';
//файл, который будем отдавать
$outname='zayavlenie.odt';
//удаляем старый файл
unlink($tmpfile);
//создаем новый архив
$zip = new ZipArchive;
if ($zip->open($tmpfile,ZIPARCHIVE::CREATE) === TRUE) {
//проходимся по структуре нашего архива
$files=file('doc.list');
foreach ($files as $filename) {
$filename=trim($filename);
//если директория - добавляем ее
if (is_dir('doc/'.$filename)) {
$zip->addEmptyDir($filename);
}
//иначе добавляем файл
else {
//если нужный файл, то проводим в нем подстановку пользовательских полей
if ($filename=="content.xml") {
//значения полей
$vars=array(
'ФИО'=>'Иванова И.И.',
'Дата'=>date('d.m.Y'),
'Планета'=>'Юпитер'
);
//создаем объект simplexml
$xml = new SimpleXMLElement(file_get_contents('doc/'.$filename));
//получаем заранее нужные namespace
$ns=$xml->getNamespaces(true);
// две переменные, необходимые для доступа к элементам xml и к атрибутам
$usr="user-field-decls";
$str="string-value";
//проверяем есть ли в файле пользовательские поля
if ($fields=$xml->children($ns["office"])->body->text->children($ns["text"])->$usr) {
//если есть, пробегаемся по ним и заменяем их атрибут string-value на новый
foreach ($fields->children($ns["text"]) as $field) {
if (isset($vars[(string)$field->attributes($ns["text"])->name])) {
$field->attributes($ns["office"])->$str = $vars[(string)$field->attributes($ns["text"])->name];
}
}
}
//добавляем в архив
$zip->addFromString($filename, $xml->asXML());
}
else {
//добавляем в архив из файла
$zip->addFile('doc/'.$filename,$filename );
}
}
}
$zip->close();
} else {
die("zip error");
}
//очищаем буфер и выдаем файл
ob_clean();
header('Content-Disposition: attachment; filename="'.$outname.'"');
header('Content-type: application/odt');
print file_get_contents($tmpfile);
?>
Вуаля.
Важные замечания и ссылки:
— В примере я использовал только текстовые поля, но Вы можете использовать также и другие типы полей.
— В ODT есть также возможность использоваться условные элементы (например часть текста показывается или не показывается в зависимости от условия — например значения пользовательского поля)
— В примере я менял значения полей только в content.xml. Но поля могут использоваться и в других файлах, например в styles.xml находятся колонтитулы.
— спецификация ODF (точнее Open Document)
— Как и всё в мире — пример можно оптимизировать. Например, если требуется менять только content.xml, то никто не запрещает подготовить заранее архив, а при запросе пользователя заменять/добавлять в него этот файл.
Скачать исходник целиком