<?php // encoding="UTF-8"
/**

Transform XML with cache

© 2010, École nationale des chartes, licence CeCILL-C (LGPL compatible droit français)

Static methods to transform XML. Caching on timestamps of source files, but also possible dependencies. An object allows some performance gains (keep in memory last parsed transformation).

*/
class Transform { /** XSLT processor */ static $proc; /** Current XSL DOM doc */ static $xsl; /** Current XML DOM doc */ static $xml; /** If a transformation have be done (not from cache) */ public static $done; /** Last message */ public static $message;
/** $src : source XML file to transform, or XML string $xsl : XSL filepath $destFile : ? destination file, used as timestamp for cache $params : ? array of parameters for XSLT $deps : ? array of filepath, dependencies to test if file has to be regenerated $force : ? if true, force transformation return : the $destFile filepath (maybe be modified as a cache file in tmp if not writable), or string result of the transform if no $destFile provided */
static function xsl($src, $xsl, $destFile, $params=array(), $deps=array(), $force=false) { // reset log self::$message=null; self::$done=false; // if no dest dir, try to make it, let error go out if ($destFile) { if(!is_dir(dirname($destFile))) mkdir(dirname($destFile), 0775, true); if(is_writable($destFile)); // file exists and writable, OK else if (!is_writable(dirname($destFile))) return touch($destFile); // file not exists and dir not writable, pb } // check if someone is working on this file $lockFile=$destFile.".lock"; if (!is_writable($destFile) && file_exists($lockFile) && is_writable($lockFile)) unlink($lockFile); // delete lockFile if rights problem if (file_exists($lockFile)) { date_default_timezone_set("Europe/Paris"); print "\n<!-- $lockFile, transformation started ".date ("Y-m-d H:i:sP", filemtime($lockFile)) . " -->"; return; } if ($destFile && file_exists($destFile)) $destMtime=filemtime($destFile); else $destMtime=0; // if source is a file, add it to dependencies if (file_exists($src)) $deps[]=$src; // if no deps, force transform if (!count($deps)) $force=true; // loop on deps to see if transform is needed else { // an XSL may be grapped from the http if (is_file($xsl)) $deps[]=$xsl; foreach($deps as $file) { // laisser sortir l'erreur si une dépendance n'existe pas ? if ($destMtime < filemtime($file)) { $force=true; break; } } if (!$force) return $destFile; } // transform will be done, nothing coming from cache, say it to the world self::$done=true; // load xml_file if (file_exists($src)) self::$xml->load($src, LIBXML_NOERROR | LIBXML_NOWARNING); // xml string ? else self::$xml->loadXML($src); // argument more than one line, seems to not be a filepath if (strpos($xsl, "\n") !== false) self::$xsl->loadXML($xsl); else self::$xsl->load($xsl); // Si en contexte diple, réécrire les liens relatif d'import xsl if(class_exists("Diple")) { // modifier seulement l'uri de base de l'xsl est trop risqué en cas d'autres imports relatifs // self::$xsl->documentURI=Diple::$dipleDir.'transform/sous/'; foreach(self::$xsl->getElementsByTagName ("import") as $node) { $node->setAttribute('href', preg_replace('@^(../)*diple/@', strtr(Diple::$dipleDir, array('\\'=>"/")), $node->getAttribute('href'))); } } self::$proc->importStyleSheet(self::$xsl); // transpose params if(isset($params) && count($params)) foreach ($params as $key => $value) self::$proc->setParameter('', $key, $value); $result=$destFile; // change current directory do not affect link resolution for <xsl:document href=""> // changing error_handler allow redirection of <xsl:message>, default behavior is output lines to workaround apache timeout $oldError=set_error_handler(array(__CLASS__,"xsl_message"), E_WARNING); // try commandline if src is big to avoid time limit ? // if no dest file provide, return the result as a string if (!$destFile) $result = self::$proc->transformToXML(self::$xml); // transform to destFile else { @touch($lockFile); @chmod($lockFile, 0666); self::$proc->transformToUri(self::$xml, $destFile); chmod($destFile, 0664); // let group delete what php has done if (file_exists($lockFile)) unlink($lockFile); } if (self::$message) print"\n-->"; restore_error_handler(); return $result; }
/** Handle xsl:message as a custom error handler To avoid Apache time limit, php should output some bytes during long transformations */
static function xsl_message( $errno, $errstr, $errfile, $errline, $errcontext) { if (!self::$message) print "\n<!--"; self::$message=preg_replace("/XSLTProcessor::transformToUri[^:]*:/", "", $errstr); print "\n".self::$message; } } Transform::$proc=new XSLTProcessor(); // politique de sécurité pour <xsl:document> if (version_compare(PHP_VERSION,'5.4',"<")) $oldval = ini_set("xsl.security_prefs",0); else $oldval = Transform::$proc->setSecurityPrefs(0); Transform::$proc->registerPHPFunctions(); Transform::$xsl=new DOMDocument(); Transform::$xml=new DOMDocument(); ?>