OpenDocument Dateien mit PHP ändern
Das erste was man wissen muss, wenn man OpenDocument Dateien bearbeiten will, ist dass es sich dabei um ein offenes Format handelt, welches aus einem ZIP Archiv mit vielen einzelnen Dateien besteht. Wie dieses Archiv aufgebaut ist, kann man hier nachlesen: http://de.wikipedia.org/wiki/OpenDocument
Nachdem man das Archiv also mit einem Programm seiner Wahl entpackt hat (z.B. mit 7-Zip: http://www.7-zip.org/), kann man die einzelnen Dateien ganz einfach mit PHP bearbeiten. Zur Unterstützung habe ich dafür eine kleine PHP-Klasse geschrieben (siehe weiter unten). Als Beispiel bearbeite ich hier mal eine odt-Datei aus OpenOffice Writer.
Der Platzhalter ###BEISPIEL###, den ich vorher mit OpenOffice an die entsprechende Stelle gesetzt habe soll hier durch den String “Hat geklappt” ersetzt werden:
<? include 'opendocument.class.php'; // klasse einbinden // Als erstes erstelle ich ein neues Odf - Objekt. Im Ordner 'beispiel' befinden sie die // unkomprimierten Dateien meiner OpenDocument-Text Datei $Odf = new opendocument('/www/beispiel/'); // Inhalt von content.xml einlesen: // (Hier steht der eigentliche Inhalt des Dokuments drin --> siehe wikipedia) $content = $Odf -> get_file('content.xml'); // Platzhalter ###beispiel### durch String 'Hat geklappt' ersetzen: $content = str_replace('###BEISPIEL###','Hat geklappt',$content); // Neuen Content "setzen": $Odf -> set_file($content,'content.xml'); // Header senden ($Odf->get_mime gibt den mime-Typ zurück): header("Content-type: ".$Odf->get_mime); header("Content-Disposition: attachment; filename=beispiel.odt"); // Manipuliertes Dokument ausgeben: echo $Odf -> output(); ?>
und hier die dazu benötigte Klasse:
<? class opendocument { /* * opendocument class * written by D.Lehmann (www.quirks-modus.de) * * zip file support based on 'zip.lib.php' of the phpMyAdmin project * official zip file format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT * */ private $source; // dir of the unzipped opendocument file private $files = array(); // array to store changed files private $datasec = array(); // array to store compressed data private $ctrl_dir = array(); // central directory (zip-file) private $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; // end of central directory record (zip file) private $old_offset = 0; //last offset position (zip-file) private function unix2DosTime($unixtime = 0) { // converts an unix timestamp to a four byte DOS date and time format (date // in high two bytes, time in low two bytes allowing magnitude comparison). $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime); if ($timearray['year'] < 1980) { $timearray['year'] = 1980; $timearray['mon'] = 1; $timearray['mday'] = 1; $timearray['hours'] = 0; $timearray['minutes'] = 0; $timearray['seconds'] = 0; } return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1); } private function zipAddFile($data, $name, $time = 0) { // adds "file" to archive $name = str_replace('\\', '/', $name); $dtime = dechex($this->unix2DosTime($time)); $hexdtime = '\x' . $dtime[6] . $dtime[7] . '\x' . $dtime[4] . $dtime[5] . '\x' . $dtime[2] . $dtime[3] . '\x' . $dtime[0] . $dtime[1]; eval('$hexdtime = "' . $hexdtime . '";'); $fr = "\x50\x4b\x03\x04"; $fr .= "\x14\x00"; // ver needed to extract $fr .= "\x00\x00"; // gen purpose bit flag $fr .= "\x08\x00"; // compression method $fr .= $hexdtime; // last mod time and date // "local file header" segment $unc_len = strlen($data); $crc = crc32($data); $zdata = gzcompress($data); $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug $c_len = strlen($zdata); $fr .= pack('V', $crc); // crc32 $fr .= pack('V', $c_len); // compressed filesize $fr .= pack('V', $unc_len); // uncompressed filesize $fr .= pack('v', strlen($name)); // length of filename $fr .= pack('v', 0); // extra field length $fr .= $name; // "file data" segment $fr .= $zdata; // add this entry to array $this -> datasec[] = $fr; // now add to central directory record $cdrec = "\x50\x4b\x01\x02"; $cdrec .= "\x00\x00"; // version made by $cdrec .= "\x14\x00"; // version needed to extract $cdrec .= "\x00\x00"; // gen purpose bit flag $cdrec .= "\x08\x00"; // compression method $cdrec .= $hexdtime; // last mod time & date $cdrec .= pack('V', $crc); // crc32 $cdrec .= pack('V', $c_len); // compressed filesize $cdrec .= pack('V', $unc_len); // uncompressed filesize $cdrec .= pack('v', strlen($name)); // length of filename $cdrec .= pack('v', 0); // extra field length $cdrec .= pack('v', 0); // file comment length $cdrec .= pack('v', 0); // disk number start $cdrec .= pack('v', 0); // internal file attributes $cdrec .= pack('V', 32); // external file attributes - 'archive' bit set $cdrec .= pack('V', $this -> old_offset); // relative offset of local header $this -> old_offset += strlen($fr); $cdrec .= $name; // optional extra field, file comment goes here // save to central directory $this -> ctrl_dir[] = $cdrec; } private function zipDumpFile() { // dumps out zipfile $data = implode('', $this -> datasec); $ctrldir = implode('', $this -> ctrl_dir); return $data . $ctrldir . $this -> eof_ctrl_dir . pack('v', sizeof($this -> ctrl_dir)) . // total # of entries "on this disk" pack('v', sizeof($this -> ctrl_dir)) . // total # of entries overall pack('V', strlen($ctrldir)) . // size of central dir pack('V', strlen($data)) . // offset to start of central dir "\x00\x00"; // .zip file comment length } private function zipAddDir($verzeichnis, $excludedFiles) { // adds a directory to zip file $handle = opendir($verzeichnis); while ($datei = readdir($handle)) { if ($datei != "." && $datei != ".." && !in_array($datei, $excludedFiles)) { if (is_dir($verzeichnis.$datei)) { // ... if you want you can create empty directories here ... $this->zipAddDir($verzeichnis.$datei.'/', $excludedFiles); } else { // is file: $this->zipAddFile(file_get_contents($verzeichnis.$datei), substr($verzeichnis.$datei,strlen($this->source))); } } } closedir($handle); } public function opendocument($source) { if ($source=='') throw new Exception('no source defined'); if (!is_dir($source)) throw new Exception('source must be a directory with uncompressed opendocument files'); if (!is_file($source.'mimetype')) throw new Exception('no mimetype file found in '.$source); $this -> source = $source; $this->files['mimetype'] = file_get_contents($this->source.'mimetype'); } public function get_file($filename, $overwrite = false) { $file = $this->source.$filename; if (is_file($file)) { if (!array_key_exists($filename,$this->files) || $overwrite) { $this->files[$filename] = file_get_contents($file); return $this->files[$filename]; } } else { if (!array_key_exists($filename,$this->files)) { throw new Exception('File "'.$file.'" not found'); } else { return $this->files[$filename]; } } } public function set_file($content, $file) { $this->files[$file] = $content; } public function get_mime() { return $this->files['mimetype']; } public function output() { $this->zipAddFile($this->files['mimetype'], 'mimetype'); // set mimetyp first $keys = array_keys($this->files); for ($i=0; $i<count($this->files); $i++) { if ($keys[$i] != 'mimetype') { $this -> zipAddFile($this->files[$keys[$i]],$keys[$i]); } } $this->zipAddDir($this->source, $keys); return $this->zipDumpFile(); } } ?>