diff --git a/include/odtphp/library/Segment.php b/include/odtphp/library/Segment.php new file mode 100755 index 00000000..4b055994 --- /dev/null +++ b/include/odtphp/library/Segment.php @@ -0,0 +1,219 @@ +name = (string) $name; + $this->xml = (string) $xml; + $this->odf = $odf; + $zipHandler = $this->odf->getConfig('ZIP_PROXY'); + $this->file = new $zipHandler(); + $this->_analyseChildren($this->xml); + } + /** + * Returns the name of the segment + * + * @return string + */ + public function getName() + { + return $this->name; + } + /** + * Does the segment have children ? + * + * @return bool + */ + public function hasChildren() + { + return $this->getIterator()->hasChildren(); + } + /** + * Countable interface + * + * @return int + */ + public function count() + { + return count($this->children); + } + /** + * IteratorAggregate interface + * + * @return Iterator + */ + public function getIterator() + { + return new RecursiveIteratorIterator(new SegmentIterator($this->children), 1); + } + /** + * Replace variables of the template in the XML code + * All the children are also called + * + * @return string + */ + public function merge() + { + $this->xmlParsed .= str_replace(array_keys($this->vars), array_values($this->vars), $this->xml); + if ($this->hasChildren()) { + foreach ($this->children as $child) { + $this->xmlParsed = str_replace($child->xml, ($child->xmlParsed=="")?$child->merge():$child->xmlParsed, $this->xmlParsed); + $child->xmlParsed = ''; + //Store all image names used in child segments in current segment array + foreach ($child->manif_vars as $file) + $this->manif_vars[] = $file; + $child->manif_vars=array(); + } + } + $reg = "/\[!--\sBEGIN\s$this->name\s--\](.*)\[!--\sEND\s$this->name\s--\]/sm"; + $this->xmlParsed = preg_replace($reg, '$1', $this->xmlParsed); + $this->file->open($this->odf->getTmpfile()); + foreach ($this->images as $imageKey => $imageValue) { + if ($this->file->getFromName('Pictures/' . $imageValue) === false) { + $this->file->addFile($imageKey, 'Pictures/' . $imageValue); + } + } + + $this->file->close(); + return $this->xmlParsed; + } + /** + * Analyse the XML code in order to find children + * + * @param string $xml + * @return Segment + */ + protected function _analyseChildren($xml) + { + // $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](?:<\/text:p>)?(.*)(?:)?\[!--\sEND\s(\\1)\s--\]#sm"; + $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](.*)\[!--\sEND\s(\\1)\s--\]#sm"; + preg_match_all($reg2, $xml, $matches); + for ($i = 0, $size = count($matches[0]); $i < $size; $i++) { + if ($matches[1][$i] != $this->name) { + $this->children[$matches[1][$i]] = new self($matches[1][$i], $matches[0][$i], $this->odf); + } else { + $this->_analyseChildren($matches[2][$i]); + } + } + return $this; + } + /** + * Assign a template variable to replace + * + * @param string $key + * @param string $value + * @throws SegmentException + * @return Segment + */ + public function setVars($key, $value, $encode = true, $charset = 'ISO-8859') + { + if (strpos($this->xml, $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT')) === false) { + throw new SegmentException("var $key not found in {$this->getName()}"); + } + $value = $encode ? htmlspecialchars($value) : $value; + $value = ($charset == 'ISO-8859') ? utf8_encode($value) : $value; + $this->vars[$this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT')] = str_replace("\n", "", $value); + return $this; + } + /** + * Assign a template variable as a picture + * + * @param string $key name of the variable within the template + * @param string $value path to the picture + * @throws OdfException + * @return Segment + */ + public function setImage($key, $value) + { + $filename = strtok(strrchr($value, '/'), '/.'); + $file = substr(strrchr($value, '/'), 1); + $size = @getimagesize($value); + if ($size === false) { + throw new OdfException("Invalid image"); + } + list ($width, $height) = $size; + $width *= Odf::PIXEL_TO_CM; + $height *= Odf::PIXEL_TO_CM; + $xml = << +IMG; + $this->images[$value] = $file; + $this->manif_vars[] = $file; //save image name as array element + $this->setVars($key, $xml, false); + return $this; + } + /** + * Shortcut to retrieve a child + * + * @param string $prop + * @return Segment + * @throws SegmentException + */ + public function __get($prop) + { + if (array_key_exists($prop, $this->children)) { + return $this->children[$prop]; + } else { + throw new SegmentException('child ' . $prop . ' does not exist'); + } + } + /** + * Proxy for setVars + * + * @param string $meth + * @param array $args + * @return Segment + */ + public function __call($meth, $args) + { + try { + return $this->setVars($meth, $args[0]); + } catch (SegmentException $e) { + throw new SegmentException("method $meth nor var $meth exist"); + } + } + /** + * Returns the parsed XML + * + * @return string + */ + public function getXmlParsed() + { + return $this->xmlParsed; + } +} + +?> diff --git a/include/odtphp/library/SegmentIterator.php b/include/odtphp/library/SegmentIterator.php new file mode 100755 index 00000000..673dbdc2 --- /dev/null +++ b/include/odtphp/library/SegmentIterator.php @@ -0,0 +1,56 @@ +ref = $ref; + $this->key = 0; + $this->keys = array_keys($this->ref); + } + public function hasChildren() + { + return $this->valid() && $this->current() instanceof Segment; + } + public function current() + { + return $this->ref[$this->keys[$this->key]]; + } + function getChildren() + { + return new self($this->current()->children); + } + public function key() + { + return $this->key; + } + public function valid() + { + return array_key_exists($this->key, $this->keys); + } + public function rewind() + { + $this->key = 0; + } + public function next() + { + $this->key ++; + } +} + +?> \ No newline at end of file diff --git a/include/odtphp/library/odf.php b/include/odtphp/library/odf.php new file mode 100755 index 00000000..0c40c230 --- /dev/null +++ b/include/odtphp/library/odf.php @@ -0,0 +1,344 @@ + 'PclZipProxy', + 'DELIMITER_LEFT' => '{', + 'DELIMITER_RIGHT' => '}', + 'PATH_TO_TMP' => null + ); + protected $file; + protected $contentXml; // To store content of content.xml file + protected $manifestXml; // To store content of manifest.xml file + protected $stylesXml; // To store content of styles.xml file + protected $tmpfile; + protected $images = array(); + protected $vars = array(); + protected $manif_vars = array(); // array to store image names + protected $segments = array(); + const PIXEL_TO_CM = 0.026458333; + /** + * Class constructor + * + * @param string $filename the name of the odt file + * @throws OdfException + */ + public function __construct($filename, $config = array()) + { + if (! is_array($config)) { + throw new OdfException('Configuration data must be provided as array'); + } + foreach ($config as $configKey => $configValue) { + if (array_key_exists($configKey, $this->config)) { + $this->config[$configKey] = $configValue; + } + } + if (! class_exists($this->config['ZIP_PROXY'])) { + throw new OdfException($this->config['ZIP_PROXY'] . ' class not found - check your php settings'); + } + $zipHandler = $this->config['ZIP_PROXY']; + $this->file = new $zipHandler(); + if ($this->file->open($filename) !== true) { + throw new OdfException("Error while Opening the file '$filename' - Check your odt file"); + } + if (($this->contentXml = $this->file->getFromName('content.xml')) === false) { + throw new OdfException("Nothing to parse - check that the content.xml file is correctly formed"); + } + if (($this->stylesXml = $this->file->getFromName('styles.xml')) === false) { + throw new OdfException("Nothing to parse - Check that the styles.xml file is correctly formed in source file '$filename'"); + } + if (($this->manifestXml = $this->file->getFromName('META-INF/manifest.xml')) === false) { + throw new OdfException("Something is wrong with META-INF/manifest.xm in source file '$filename'"); + } + + + $this->file->close(); + + $tmp = tempnam($this->config['PATH_TO_TMP'], md5(uniqid())); + copy($filename, $tmp); + $this->tmpfile = $tmp; + $this->_moveRowSegments(); + } + /** + * Assing a template variable + * + * @param string $key name of the variable within the template + * @param string $value replacement value + * @param bool $encode if true, special XML characters are encoded + * @throws OdfException + * @return odf + */ + public function setVars($key, $value, $encode = true, $charset = 'ISO-8859') + { + $tag= $this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT']; + if (strpos($this->contentXml, $tag) === false && strpos($this->stylesXml , $tag) === false) { + throw new OdfException("var $key not found in the document"); + } + $value = $encode ? htmlspecialchars($value) : $value; + $value = ($charset == 'ISO-8859') ? utf8_encode($value) : $value; + $this->vars[$tag] = str_replace("\n", "", $value); + return $this; + } + /** + * Assign a template variable as a picture + * + * @param string $key name of the variable within the template + * @param string $value path to the picture + * @throws OdfException + * @return odf + */ + public function setImage($key, $value) + { + $filename = strtok(strrchr($value, '/'), '/.'); + $file = substr(strrchr($value, '/'), 1); + $size = @getimagesize($value); + if ($size === false) { + throw new OdfException("Invalid image"); + } + list ($width, $height) = $size; + $width *= self::PIXEL_TO_CM; + $height *= self::PIXEL_TO_CM; + $xml = << +IMG; + $this->images[$value] = $file; + $this->manif_vars[] = $file; //save image name as array element + $this->setVars($key, $xml, false); + return $this; + } + /** + * Move segment tags for lines of tables + * Called automatically within the constructor + * + * @return void + */ + private function _moveRowSegments() + { + // Search all possible rows in the document + $reg1 = "#]*>(.*)#smU"; + preg_match_all($reg1, $this->contentXml, $matches); + for ($i = 0, $size = count($matches[0]); $i < $size; $i++) { + // Check if the current row contains a segment row.* + $reg2 = '#\[!--\sBEGIN\s(row.[\S]*)\s--\](.*)\[!--\sEND\s\\1\s--\]#sm'; + if (preg_match($reg2, $matches[0][$i], $matches2)) { + $balise = str_replace('row.', '', $matches2[1]); + // Move segment tags around the row + $replace = array( + '[!-- BEGIN ' . $matches2[1] . ' --]' => '', + '[!-- END ' . $matches2[1] . ' --]' => '', + ' '[!-- BEGIN ' . $balise . ' --]' => '[!-- END ' . $balise . ' --]' + ); + $replacedXML = str_replace(array_keys($replace), array_values($replace), $matches[0][$i]); + $this->contentXml = str_replace($matches[0][$i], $replacedXML, $this->contentXml); + } + } + } + /** + * Merge template variables + * Called automatically for a save + * + * @return void + */ + private function _parse() + { + $this->contentXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->contentXml); + $this->stylesXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->stylesXml); + } + /** + * Add the merged segment to the document + * + * @param Segment $segment + * @throws OdfException + * @return odf + */ + public function mergeSegment(Segment $segment) + { + if (! array_key_exists($segment->getName(), $this->segments)) { + throw new OdfException($segment->getName() . 'cannot be parsed, has it been set yet ?'); + } + $string = $segment->getName(); + // $reg = '@]*>\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]<\/text:p>@smU'; + $reg = '@\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]@smU'; + $this->contentXml = preg_replace($reg, $segment->getXmlParsed(), $this->contentXml); + foreach ($segment->manif_vars as $val) + $this->manif_vars[] = $val; //copy all segment image names into current array + return $this; + } + /** + * Display all the current template variables + * + * @return string + */ + public function printVars() + { + return print_r('
' . print_r($this->vars, true) . '
', true); + } + /** + * Display the XML content of the file from odt document + * as it is at the moment + * + * @return string + */ + public function __toString() + { + return $this->contentXml; + } + /** + * Display loop segments declared with setSegment() + * + * @return string + */ + public function printDeclaredSegments() + { + return '
' . print_r(implode(' ', array_keys($this->segments)), true) . '
'; + } + /** + * Declare a segment in order to use it in a loop + * + * @param string $segment + * @throws OdfException + * @return Segment + */ + public function setSegment($segment) + { + if (array_key_exists($segment, $this->segments)) { + return $this->segments[$segment]; + } + // $reg = "#\[!--\sBEGIN\s$segment\s--\]<\/text:p>(.*)\[!--\sEND\s$segment\s--\]#sm"; + $reg = "#\[!--\sBEGIN\s$segment\s--\](.*)\[!--\sEND\s$segment\s--\]#sm"; + if (preg_match($reg, html_entity_decode($this->contentXml), $m) == 0) { + throw new OdfException("'$segment' segment not found in the document"); + } + $this->segments[$segment] = new Segment($segment, $m[1], $this); + return $this->segments[$segment]; + } + /** + * Save the odt file on the disk + * + * @param string $file name of the desired file + * @throws OdfException + * @return void + */ + public function saveToDisk($file = null) + { + if ($file !== null && is_string($file)) { + if (file_exists($file) && !(is_file($file) && is_writable($file))) { + throw new OdfException('Permission denied : can\'t create ' . $file); + } + $this->_save(); + copy($this->tmpfile, $file); + } else { + $this->_save(); + } + } + /** + * Internal save + * + * @throws OdfException + * @return void + */ + private function _save() + { + $this->file->open($this->tmpfile); + $this->_parse(); + if (! $this->file->addFromString('content.xml', $this->contentXml) || ! $this->file->addFromString('styles.xml' , $this->stylesXml ) ) { + throw new OdfException('Error during file export addFromString'); + } + $lastpos=strrpos($this->manifestXml, "\n", -15); //find second last newline in the manifest.xml file + $manifdata = ""; + + //Enter all images description in $manifdata variable + + foreach ($this->manif_vars as $val) + { + $ext = substr(strrchr($val, '.'), 1); + $manifdata = $manifdata.''."\n"; + } + //Place content of $manifdata variable in manifest.xml file at appropriate place + $this->manifestXml = substr_replace($this->manifestXml, "\n".$manifdata, $lastpos+1, 0); + //$this->manifestXml = $this->manifestXml ."\n".$manifdata; + + if (! $this->file->addFromString('META-INF/manifest.xml', $this->manifestXml)) { + throw new OdfException('Error during manifest file export'); + } + foreach ($this->images as $imageKey => $imageValue) { + $this->file->addFile($imageKey, 'Pictures/' . $imageValue); + } + $this->file->close(); // seems to bug on windows CLI sometimes + } + /** + * Export the file as attached file by HTTP + * + * @param string $name (optionnal) + * @throws OdfException + * @return void + */ + public function exportAsAttachedFile($name="") + { + $this->_save(); + if (headers_sent($filename, $linenum)) { + throw new OdfException("headers already sent ($filename at $linenum)"); + } + + if( $name == "" ) + { + $name = md5(uniqid()) . ".odt"; + } + + header('Content-type: application/vnd.oasis.opendocument.text'); + header('Content-Disposition: attachment; filename="'.$name.'"'); + readfile($this->tmpfile); + } + /** + * Returns a variable of configuration + * + * @return string The requested variable of configuration + */ + public function getConfig($configKey) + { + if (array_key_exists($configKey, $this->config)) { + return $this->config[$configKey]; + } + return false; + } + /** + * Returns the temporary working file + * + * @return string le chemin vers le fichier temporaire de travail + */ + public function getTmpfile() + { + return $this->tmpfile; + } + /** + * Delete the temporary file when the object is destroyed + */ + public function __destruct() { + if (file_exists($this->tmpfile)) { + unlink($this->tmpfile); + } + } +} + +?>