📜 ⬆️ ⬇️

The simplest generation of an odt file from an existing one

Once I was faced with the task of implementing the generation of contracts for clients from our corporate site.
At first, the task was solved just awfully - a contract template was prepared by the html template, and the user was issued a template conversion to pdf. Of course, this resulted in a bunch of inconveniences, including if it was necessary to change something in the contract.

The next solution was to generate the odt document. This allowed our managers to edit the document, regardless of the site and programmers.
There is no sense to completely generate from scratch. Why not work with an existing file (edited in OpenOffice) and just replace the necessary elements in it?

This is what we are going to do.

But for starters ...
UPD! The second article with the correction of errors of this - habrahabr.ru/blogs/php/87254
A couple of comments:
- We will severely limit our task to changing data only in the text of the document, as well as only text variables.
- To solve this problem, I personally used SimpleXML , ZIPArchive . Nobody forbids you to use other tools.
- What is described in the article is a simplified and trimmed example, and not a ready-made tool.
')
Creating a template in OpenOffice:
Create a regular odt document and insert user variables in the right places:
Insert menu -> Fields -> Advanced
Variables tab
Select the "user field" and add / insert fields as shown in the image.


Save our file. In my example, this is the test.odt file.

Upload the file to the server:
ODT, like any ODF file, as many probably know, is a regular ZIP archive.

upload.php
<? php

// path to temporary archive
$ tmpfile = 'upload / temp.zip' ;

// save the received document
if ( isset ( $ _FILES [ 'document' ] ) and move_uploaded_file ( $ _FILES [ 'document' ] [ 'tmp_name' ] , $ tmpfile ) ) {

// delete directory function
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 ) ;
}

// delete the directory with the contents of the document and create anew
// if you wish, you can move the old version somewhere, thereby making the document version
deleteDirectory ( 'doc /' ) ;
mkdir ( 'doc /' ) ;


// extract the archive
$ zip = new ZipArchive ;
if ( $ zip -> open ( $ tmpfile ) === TRUE ) {
// Save the file paths in the correct sequence
// We will need this in the future.
// For example, as requested by the odf format, the mimetype file should be the first in the archive.
$ files = array ( ) ;
for ( $ i = 0 ; $ i < $ zip -> numFiles ; $ i ++ ) {
$ files [ ] = $ zip -> getNameIndex ( $ i ) ;
}
file_put_contents ( "doc.list" , implode ( " \ n " , $ files ) ) ;

// retrieve
$ zip -> extractTo ( 'doc /' ) ;
$ zip -> close ( ) ;
} else {
die ( "zip error" ) ;
}

unlink ( $ tmpfile ) ;

$ print = 'File uploaded successfully' ;
}
else {
$ print = '
<form action = "" method = "post" enctype = "multipart / form-data">
<input type = "file" name = "document"> <br>
<input type = "submit" value = "Download"> <br>
</ form> ' ;
}
print '<html>
<head>
<meta http-equiv = "content-type" content = "text / html; charset = utf-8" />
<title> Document Download </ title>
</ head>
<body>
' . $ print . '
</ body>
</ html> ' ;

?>


Upon successful download, we get a folder with the contents of the odt file and doc.list with a list of files.

We give the modified file to the user:
We need to replace custom field values ​​and compress everything back to the archive.

download.php

<? php

// path to temporary file
$ tmpfile = 'download / doc.odt' ;
// file that will be given
$ outname = 'zayavlenie.odt' ;


// delete the old file
unlink ( $ tmpfile ) ;


// create a new archive
$ zip = new ZipArchive ;
if ( $ zip -> open ( $ tmpfile , ZIPARCHIVE :: CREATE ) === TRUE ) {
// walk through our archive structure
$ files = file ( 'doc.list' ) ;
foreach ( $ files as $ filename ) {
$ filename = trim ( $ filename ) ;

// if the directory - add it
if ( is_dir ( 'doc /' . $ filename ) ) {
$ zip -> addEmptyDir ( $ filename ) ;
}
// otherwise add the file
else {

// if the required file, then we make in it the substitution of custom fields
if ( $ filename == "content.xml" ) {

// field values
$ vars = array (
'Name' => 'Ivanova I.I.' ,
'Date' => date ( 'dmY' ) ,
'Planet' => 'Jupiter'
) ;

// create object simplexml
$ xml = new SimpleXMLElement ( file_get_contents ( 'doc /' . $ filename ) ) ;

// we receive in advance necessary namespace
$ ns = $ xml -> getNamespaces ( true ) ;

// two variables needed to access xml elements and attributes
$ usr = "user-field-decls" ;
$ str = "string-value" ;

// check if there are user fields in the file
if ( $ fields = $ xml -> children ( $ ns [ "office" ] ) -> body -> text -> children ( $ ns [ "text" ] ) -> $ usr ) {
// if there is, run over them and replace their string-value attribute with a new one
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 ] ;
}
}

}
// add to archive
$ zip -> addFromString ( $ filename , $ xml -> asXML ( ) ) ;
}
else {
// add to archive from file
$ zip -> addFile ( 'doc /' . $ filename , $ filename ) ;
}
}
}


$ zip -> close ( ) ;
} else {
die ( "zip error" ) ;
}

// clear the buffer and issue the file
ob_clean ( ) ;

header ( 'Content-Disposition: attachment; filename = "' . $ outname . '"' ) ;
header ( 'Content-type: application / odt' ) ;
print file_get_contents ( $ tmpfile ) ;

?>


Voila

Important notes and links:
- In the example, I used only text fields, but you can also use other types of fields.
- In ODT, it is also possible to use conditional elements (for example, a portion of the text is shown or not displayed depending on the condition — for example, the value of a custom field)
- In the example, I changed the field values ​​only in content.xml. But the fields can be used in other files as well, for example in styles.xml there are footers.
- ODF specification (more precisely, Open Document)
- Like everything in the world - an example can be optimized. For example, if you only need to change content.xml, then no one forbids preparing the archive in advance, and when prompted by the user to replace / add this file to it.

Download the full source

Source: https://habr.com/ru/post/69616/


All Articles