PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DocBlox/Transformer.php

https://github.com/androa/Docblox
PHP | 423 lines | 301 code | 25 blank | 97 comment | 13 complexity | 04a261b6fcc4d5273e4513ab6b8460ec MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * DocBlox
  4. *
  5. * PHP Version 5
  6. *
  7. * @category DocBlox
  8. * @package Transformer
  9. * @author Mike van Riel <mike.vanriel@naenius.com>
  10. * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
  11. * @license http://www.opensource.org/licenses/mit-license.php MIT
  12. * @link http://docblox-project.org
  13. */
  14. /**
  15. * Core class responsible for transforming the structure.xml file to a set of artifacts.
  16. *
  17. * @category DocBlox
  18. * @package Transformer
  19. * @author Mike van Riel <mike.vanriel@naenius.com>
  20. * @license http://www.opensource.org/licenses/mit-license.php MIT
  21. * @link http://docblox-project.org
  22. */
  23. class DocBlox_Transformer extends DocBlox_Core_Abstract
  24. {
  25. /** @var string|null Target location where to output the artifacts */
  26. protected $target = null;
  27. /** @var DOMDocument|null DOM of the structure as generated by the parser. */
  28. protected $source = null;
  29. /** @var string[] */
  30. protected $templates = array();
  31. /** @var DocBlox_Transformer_Transformation[] */
  32. protected $transformations = array();
  33. /**
  34. * Initialize the transformations.
  35. */
  36. public function __construct()
  37. {
  38. $this->loadTransformations();
  39. }
  40. /**
  41. * Sets the target location where to output the artifacts.
  42. *
  43. * @throws Exception if the target is not a valid writable directory.
  44. *
  45. * @param string $target The target location where to output the artifacts.
  46. *
  47. * @return void
  48. */
  49. public function setTarget($target)
  50. {
  51. $path = realpath($target);
  52. if (!file_exists($path) && !is_dir($path) && !is_writable($path)) {
  53. throw new Exception('Given target directory (' . $target . ') does not exist or is not writable');
  54. }
  55. $this->target = $path;
  56. }
  57. /**
  58. * Returns the location where to store the artifacts.
  59. *
  60. * @return string
  61. */
  62. public function getTarget()
  63. {
  64. return $this->target;
  65. }
  66. /**
  67. * Sets the location of the structure file.
  68. *
  69. * @throws Exception if the source is not a valid readable file.
  70. *
  71. * @param string $source The location of the structure file as full path (may be relative).
  72. *
  73. * @return void
  74. */
  75. public function setSource($source)
  76. {
  77. $source = trim($source);
  78. $xml = new DOMDocument();
  79. if (substr($source, 0, 5) === '<?xml') {
  80. $xml->loadXML($source);
  81. } else {
  82. $path = realpath($source);
  83. if (!file_exists($path) || !is_readable($path) || !is_file($path)) {
  84. throw new Exception('Given source (' . $source . ') does not exist or is not readable');
  85. }
  86. // convert to dom document so that the writers do not need to
  87. $xml->load($path);
  88. }
  89. $this->source = $xml;
  90. }
  91. /**
  92. * Returns the source Structure.
  93. *
  94. * @return null|DOMDocument
  95. */
  96. public function getSource()
  97. {
  98. return $this->source;
  99. }
  100. /**
  101. * Sets one or more templates as basis for the transformations.
  102. *
  103. * @param string|string[] $template
  104. *
  105. * @return void
  106. */
  107. public function setTemplates($template)
  108. {
  109. // reset
  110. $this->templates = array();
  111. $this->transformations = array();
  112. if (!is_array($template)) {
  113. $template = array($template);
  114. }
  115. foreach ($template as $item)
  116. {
  117. $this->addTemplate($item);
  118. }
  119. }
  120. /**
  121. * Returns the list of templates which are going to be adopted.
  122. *
  123. * @return string[]
  124. */
  125. public function getTemplates()
  126. {
  127. return $this->templates;
  128. }
  129. /**
  130. * Loads the transformation from the configuration and from the given templates and/or transformations.
  131. *
  132. * @param string[] $templates Array of template names.
  133. * @param Transformation[]|array[] $transformations Array of transformations
  134. * or arrays representing transformations.
  135. *
  136. * @see self::addTransformation() for more details regarding the array structure.
  137. *
  138. * @return void
  139. */
  140. public function loadTransformations(array $templates = array(), array $transformations = array())
  141. {
  142. /** @var Zend_Config_Xml[] $config_transformations */
  143. $config_transformations = $this->getConfig()->get('transformations', array());
  144. foreach ($config_transformations as $transformation)
  145. {
  146. // if a writer is defined then it is a template; otherwise it is a template
  147. if (isset($transformation->writer)) {
  148. $this->addTransformation($transformation->toArray());
  149. continue;
  150. }
  151. $this->addTemplate($transformation->name);
  152. }
  153. array_walk($templates, array($this, 'addTemplate'));
  154. array_walk($transformations, array($this, 'addTransformation'));
  155. }
  156. /**
  157. * Loads a template by name, if an additional array with details is provided it will try to load parameters from it.
  158. *
  159. * @param string $name
  160. * @param string[]|null $details
  161. *
  162. * @return void
  163. */
  164. public function addTemplate($name)
  165. {
  166. // if the template is already loaded we do not reload it.
  167. if (in_array($name, $this->getTemplates())) {
  168. return;
  169. }
  170. $config = $this->getConfig();
  171. $path = null;
  172. // if this is an absolute path; load the template into the configuration
  173. // Please note that this _could_ override an existing template when
  174. // you have a template in a subfolder with the same name as a default
  175. // template; we have left this in on purpose to allow people to override
  176. // templates should they choose to.
  177. $config_path = rtrim($name, DIRECTORY_SEPARATOR) . '/template.xml';
  178. if (file_exists($config_path) && is_readable($config_path)) {
  179. $path = rtrim($name, DIRECTORY_SEPARATOR);
  180. $template_name_part = basename($path);
  181. $cache_path = rtrim($config->paths->themes, '/\\')
  182. . DIRECTORY_SEPARATOR . 'cache'
  183. . DIRECTORY_SEPARATOR . $template_name_part;
  184. // move the files to a cache location and then change the path
  185. // variable to match the new location
  186. $this->copyRecursive($path, $cache_path);
  187. $path = $cache_path;
  188. // transform all directory separators to underscores and lowercase
  189. $name = strtolower(str_replace(
  190. DIRECTORY_SEPARATOR, '_',
  191. rtrim($name, DIRECTORY_SEPARATOR)
  192. ));
  193. $config->templates->$name = new Zend_Config_Xml($config_path);
  194. }
  195. if (!isset($config->templates->$name)) {
  196. throw new InvalidArgumentException('Template "' . $name . '" could not be found');
  197. }
  198. // track templates to be able to refer to them later
  199. $this->templates[] = $name;
  200. // template does not have transformations; return
  201. if (!isset($config->templates->$name->transformations)) {
  202. return;
  203. }
  204. $transformations = $config->templates->$name->transformations->transformation->toArray();
  205. // if the array key is not numeric; then there is a single value instead of an array of transformations
  206. $transformations = (is_numeric(key($transformations)))
  207. ? $transformations
  208. : array($transformations);
  209. foreach ($transformations as $transformation)
  210. {
  211. // if this is an externally loaded template we add the template_path
  212. // as parameter as this is used as extra option when determining
  213. // where the source of a transformation may lie.
  214. if ($path !== null)
  215. {
  216. if (isset($transformation['parameters']))
  217. {
  218. $transformation['parameters'] = array();
  219. }
  220. $transformation['parameters']['template_path'] = $path;
  221. }
  222. $this->addTransformation($transformation);
  223. }
  224. }
  225. /**
  226. * Adds the given transformation to the transformer for execution.
  227. *
  228. * It is also allowed to pass an array notation for the transformation; then this method will create
  229. * a transformation object out of it.
  230. *
  231. * The structure for this array must be:
  232. * array(
  233. * 'query' => <query>,
  234. * 'writer' => <writer>,
  235. * 'source' => <source>,
  236. * 'artifact' => <artifact>,
  237. * 'parameters' => array(<parameters>),
  238. * 'dependencies' => array(<dependencies>)
  239. * )
  240. *
  241. * @param Transformation|array $transformation
  242. *
  243. * @return void
  244. */
  245. public function addTransformation($transformation)
  246. {
  247. if (is_array($transformation)) {
  248. // check if all required items are present
  249. if (!key_exists('query', $transformation)
  250. || !key_exists('writer', $transformation)
  251. || !key_exists('source', $transformation)
  252. || !key_exists('artifact', $transformation)) {
  253. throw new InvalidArgumentException(
  254. 'Transformation array is missing elements, received: ' . var_export($transformation, true)
  255. );
  256. }
  257. $transformation_obj = new DocBlox_Transformer_Transformation(
  258. $this,
  259. $transformation['query'],
  260. $transformation['writer'],
  261. $transformation['source'],
  262. $transformation['artifact']
  263. );
  264. if (isset($transformation['parameters']) && is_array($transformation['parameters'])) {
  265. $transformation_obj->setParameters($transformation['parameters']);
  266. }
  267. $transformation = $transformation_obj;
  268. }
  269. // if it is still not an object; fail
  270. if (!is_object($transformation)) {
  271. throw new InvalidArgumentException(
  272. 'Only transformations of type (or descended from) DocBlox_Transformer_Transformation can be used in the '
  273. . 'transformation process; received: ' . gettype($transformation)
  274. );
  275. }
  276. // if the object is not a DocBlox_Transformer_Transformation; we cannot use it
  277. if (!$transformation instanceof DocBlox_Transformer_Transformation) {
  278. throw new InvalidArgumentException(
  279. 'Only transformations of type (or descended from) DocBlox_Transformer_Transformation can be used in the '
  280. . 'transformation process; received: ' . get_class($transformation)
  281. );
  282. }
  283. $this->transformations[] = $transformation;
  284. }
  285. /**
  286. * Returns the transformation which this transformer will process.
  287. *
  288. * @return DocBlox_Transformer_Transformation[]
  289. */
  290. public function getTransformations()
  291. {
  292. return $this->transformations;
  293. }
  294. /**
  295. * Executes each transformation.
  296. *
  297. * @return void
  298. */
  299. public function execute()
  300. {
  301. $xml = $this->getSource();
  302. if ($xml)
  303. {
  304. $behaviours = new DocBlox_Transformer_Behaviour_Collection(array(
  305. new DocBlox_Transformer_Behaviour_GeneratePaths(),
  306. new DocBlox_Transformer_Behaviour_AddLinkInformation(),
  307. new DocBlox_Transformer_Behaviour_Inherit(),
  308. ));
  309. $behaviours->setLogger(DocBlox_Core_Abstract::$logger);
  310. $xml = $behaviours->process($xml);
  311. }
  312. foreach ($this->getTransformations() as $transformation)
  313. {
  314. $this->log('Applying transformation query ' . $transformation->getQuery()
  315. . ' using writer ' . get_class($transformation->getWriter()));
  316. $transformation->execute($xml);
  317. }
  318. }
  319. /**
  320. * Converts a source file name to the name used for generating the end result.
  321. *
  322. * @param string $file
  323. *
  324. * @return string
  325. */
  326. public function generateFilename($file)
  327. {
  328. $info = pathinfo(str_replace(DIRECTORY_SEPARATOR, '_', trim($file, DIRECTORY_SEPARATOR . '.')));
  329. return '_' . $info['filename'] . '.html';
  330. }
  331. /**
  332. * Copies a file or folder recursively to another location.
  333. *
  334. * @param string $src The source location to copy
  335. * @param string $dst The destination location to copy to
  336. *
  337. * @throws Exception if $src does not exist or $dst is not writable
  338. *
  339. * @return void
  340. */
  341. public function copyRecursive($src, $dst)
  342. {
  343. // if $src is a normal file we can do a regular copy action
  344. if (is_file($src))
  345. {
  346. copy($src, $dst);
  347. return;
  348. }
  349. $dir = opendir($src);
  350. if (!$dir) {
  351. throw new Exception('Unable to locate path "' . $src . '"');
  352. }
  353. // check if the folder exists, otherwise create it
  354. if ((!file_exists($dst)) && (false === mkdir($dst))) {
  355. throw new Exception('Unable to create folder "' . $dst . '"');
  356. }
  357. while (false !== ($file = readdir($dir)))
  358. {
  359. if (($file != '.') && ($file != '..')) {
  360. if (is_dir($src . '/' . $file)) {
  361. $this->copyRecursive($src . '/' . $file, $dst . '/' . $file);
  362. }
  363. else
  364. {
  365. copy($src . '/' . $file, $dst . '/' . $file);
  366. }
  367. }
  368. }
  369. closedir($dir);
  370. }
  371. }