PageRenderTime 47ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/DocBlox/Transformer.php

http://github.com/mvriel/Docblox
PHP | 490 lines | 234 code | 51 blank | 205 comment | 28 complexity | 5f22f78140dc9f0f78b1b8b28f639122 MD5 | raw file
Possible License(s): LGPL-3.0, BSD-3-Clause, CC-BY-SA-3.0
  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
  16. * artifacts.
  17. *
  18. * @category DocBlox
  19. * @package Transformer
  20. * @author Mike van Riel <mike.vanriel@naenius.com>
  21. * @license http://www.opensource.org/licenses/mit-license.php MIT
  22. * @link http://docblox-project.org
  23. */
  24. class DocBlox_Transformer extends DocBlox_Transformer_Abstract
  25. {
  26. /** @var string|null Target location where to output the artifacts */
  27. protected $target = null;
  28. /** @var DOMDocument|null DOM of the structure as generated by the parser. */
  29. protected $source = null;
  30. /** @var DocBlox_Transformer_Template[] */
  31. protected $templates = array();
  32. /** @var string */
  33. protected $templates_path = '';
  34. /** @var DocBlox_Transformer_Behaviour_Collection */
  35. protected $behaviours = null;
  36. /** @var DocBlox_Transformer_Transformation[] */
  37. protected $transformations = array();
  38. /** @var boolean */
  39. protected $parsePrivate = false;
  40. /**
  41. * Array containing prefix => URL values.
  42. *
  43. * What happens is that the transformer knows where to find external API
  44. * docs for classes with a certain prefix.
  45. *
  46. * For example: having a prefix HTML_QuickForm2_ will link an unidentified
  47. * class that starts with HTML_QuickForm2_ to a (defined) URL
  48. * i.e. http://pear.php.net/package/HTML_QuickForm2/docs/
  49. * latest/HTML_QuickForm2/${class}.html
  50. *
  51. * @var string
  52. */
  53. protected $external_class_docs = array();
  54. /**
  55. * Sets the path for the templates to the DocBlox default.
  56. */
  57. public function __construct()
  58. {
  59. }
  60. /**
  61. * Sets the target location where to output the artifacts.
  62. *
  63. * @param string $target The target location where to output the artifacts.
  64. *
  65. * @throws Exception if the target is not a valid writable directory.
  66. *
  67. * @return void
  68. */
  69. public function setTarget($target)
  70. {
  71. $path = realpath($target);
  72. if (!file_exists($path) && !is_dir($path) && !is_writable($path)) {
  73. throw new InvalidArgumentException(
  74. 'Given target directory (' . $target . ') does not exist or '
  75. . 'is not writable'
  76. );
  77. }
  78. $this->target = $path;
  79. }
  80. /**
  81. * Returns the location where to store the artifacts.
  82. *
  83. * @return string
  84. */
  85. public function getTarget()
  86. {
  87. return $this->target;
  88. }
  89. /**
  90. * Sets the path where the templates are located.
  91. *
  92. * @param string $path Absolute path where the templates are.
  93. *
  94. * @return void
  95. */
  96. public function setTemplatesPath($path)
  97. {
  98. $this->templates_path = $path;
  99. }
  100. /**
  101. * Returns the path where the templates are located.
  102. *
  103. * @return string
  104. */
  105. public function getTemplatesPath()
  106. {
  107. return $this->templates_path;
  108. }
  109. /**
  110. * Sets the location of the structure file.
  111. *
  112. * @param string $source The location of the structure file as full path
  113. * (may be relative).
  114. *
  115. * @throws Exception if the source is not a valid readable file.
  116. *
  117. * @return void
  118. */
  119. public function setSource($source)
  120. {
  121. $source = trim($source);
  122. $xml = new DOMDocument();
  123. if (substr($source, 0, 5) === '<?xml') {
  124. $xml->loadXML($source);
  125. } else {
  126. $path = realpath($source);
  127. if (!file_exists($path) || !is_readable($path) || !is_file($path)) {
  128. throw new InvalidArgumentException(
  129. 'Given source (' . $source . ') does not exist or is not '
  130. . 'readable'
  131. );
  132. }
  133. // convert to dom document so that the writers do not need to
  134. $xml->load($path);
  135. }
  136. $this->source = $xml;
  137. }
  138. /**
  139. * Returns the source Structure.
  140. *
  141. * @return null|DOMDocument
  142. */
  143. public function getSource()
  144. {
  145. return $this->source;
  146. }
  147. /**
  148. * Sets flag indicating whether private members and/or elements tagged
  149. * as {@internal} need to be displayed.
  150. *
  151. * @param bool $val True if all needs to be shown, false otherwise.
  152. *
  153. * @return void
  154. */
  155. public function setParseprivate($val)
  156. {
  157. $this->parsePrivate = (boolean)$val;
  158. }
  159. /**
  160. * Returns flag indicating whether private members and/or elements tagged
  161. * as {@internal} need to be displayed.
  162. *
  163. * @return bool
  164. */
  165. public function getParseprivate()
  166. {
  167. return $this->parsePrivate;
  168. }
  169. /**
  170. * Sets one or more templates as basis for the transformations.
  171. *
  172. * @param string|string[] $template Name or names of the templates.
  173. *
  174. * @return void
  175. */
  176. public function setTemplates($template)
  177. {
  178. $this->templates = array();
  179. if (!is_array($template)) {
  180. $template = array($template);
  181. }
  182. foreach ($template as $item) {
  183. $this->addTemplate($item);
  184. }
  185. }
  186. /**
  187. * Returns the list of templates which are going to be adopted.
  188. *
  189. * @return string[]
  190. */
  191. public function getTemplates()
  192. {
  193. return $this->templates;
  194. }
  195. /**
  196. * Loads a template by name, if an additional array with details is
  197. * provided it will try to load parameters from it.
  198. *
  199. * @param string $name Name of the template to add.
  200. *
  201. * @return void
  202. */
  203. public function addTemplate($name)
  204. {
  205. // if the template is already loaded we do not reload it.
  206. if (isset($this->templates[$name])) {
  207. return;
  208. }
  209. $path = null;
  210. // if this is an absolute path; load the template into the configuration
  211. // Please note that this _could_ override an existing template when
  212. // you have a template in a subfolder with the same name as a default
  213. // template; we have left this in on purpose to allow people to override
  214. // templates should they choose to.
  215. $config_path = rtrim($name, DIRECTORY_SEPARATOR) . '/template.xml';
  216. if (file_exists($config_path) && is_readable($config_path)) {
  217. $path = rtrim($name, DIRECTORY_SEPARATOR);
  218. $template_name_part = basename($path);
  219. $cache_path = rtrim($this->getTemplatesPath(), '/\\')
  220. . DIRECTORY_SEPARATOR . $template_name_part;
  221. // move the files to a cache location and then change the path
  222. // variable to match the new location
  223. $this->copyRecursive($path, $cache_path);
  224. $path = $cache_path;
  225. // transform all directory separators to underscores and lowercase
  226. $name = strtolower(
  227. str_replace(
  228. DIRECTORY_SEPARATOR,
  229. '_',
  230. rtrim($name, DIRECTORY_SEPARATOR)
  231. )
  232. );
  233. }
  234. // if we load a default template
  235. if ($path === null) {
  236. $path = rtrim($this->getTemplatesPath(), '/\\')
  237. . DIRECTORY_SEPARATOR . $name;
  238. }
  239. if (!file_exists($path) || !is_readable($path)) {
  240. throw new InvalidArgumentException(
  241. 'The given template ' . $name.' could not be found or is not '
  242. . 'readable'
  243. );
  244. }
  245. // track templates to be able to refer to them later
  246. $this->templates[$name] = new DocBlox_Transformer_Template($name, $path);
  247. $this->templates[$name]->populate(
  248. $this,
  249. file_get_contents($path . DIRECTORY_SEPARATOR . 'template.xml')
  250. );
  251. }
  252. /**
  253. * Returns the transformation which this transformer will process.
  254. *
  255. * @return DocBlox_Transformer_Transformation[]
  256. */
  257. public function getTransformations()
  258. {
  259. $result = array();
  260. foreach ($this->templates as $template) {
  261. foreach ($template as $transformation) {
  262. $result[] = $transformation;
  263. }
  264. }
  265. return $result;
  266. }
  267. /**
  268. * Executes each transformation.
  269. *
  270. * @return void
  271. */
  272. public function execute()
  273. {
  274. $source = $this->getSource();
  275. if (!$source) {
  276. throw new DocBlox_Transformer_Exception(
  277. 'Unable to process transformations; the source was not set '
  278. . 'correctly'
  279. );
  280. }
  281. // invoke pre-transform actions (i.e. enhance source file with additional
  282. // meta-data)
  283. $this->dispatch('transformer.transform.pre', array('source' => $source));
  284. foreach ($this->getTransformations() as $transformation) {
  285. $this->log(
  286. 'Applying transformation'
  287. . ($transformation->getQuery()
  288. ? (' query "' . $transformation->getQuery() . '"') : '')
  289. . ' using writer ' . get_class($transformation->getWriter())
  290. . ' on '.$transformation->getArtifact()
  291. );
  292. $transformation->execute($source);
  293. }
  294. $this->dispatch(
  295. 'transformer.transform.post',
  296. array('source' => $source)
  297. );
  298. }
  299. /**
  300. * Converts a source file name to the name used for generating the end result.
  301. *
  302. * @param string $file Path of the file starting from the project root.
  303. *
  304. * @return string
  305. */
  306. public function generateFilename($file)
  307. {
  308. $info = pathinfo(
  309. str_replace(
  310. DIRECTORY_SEPARATOR, '_', trim($file, DIRECTORY_SEPARATOR . '.')
  311. )
  312. );
  313. return 'db_' . $info['filename'] . '.html';
  314. }
  315. /**
  316. * Copies a file or folder recursively to another location.
  317. *
  318. * @param string $src The source location to copy
  319. * @param string $dst The destination location to copy to
  320. *
  321. * @throws Exception if $src does not exist or $dst is not writable
  322. *
  323. * @return void
  324. */
  325. public function copyRecursive($src, $dst)
  326. {
  327. // if $src is a normal file we can do a regular copy action
  328. if (is_file($src)) {
  329. copy($src, $dst);
  330. return;
  331. }
  332. $dir = opendir($src);
  333. if (!$dir) {
  334. throw new Exception('Unable to locate path "' . $src . '"');
  335. }
  336. // check if the folder exists, otherwise create it
  337. if ((!file_exists($dst)) && (false === mkdir($dst))) {
  338. throw new Exception('Unable to create folder "' . $dst . '"');
  339. }
  340. while (false !== ($file = readdir($dir))) {
  341. if (($file != '.') && ($file != '..')) {
  342. if (is_dir($src . '/' . $file)) {
  343. $this->copyRecursive($src . '/' . $file, $dst . '/' . $file);
  344. } else {
  345. copy($src . '/' . $file, $dst . '/' . $file);
  346. }
  347. }
  348. }
  349. closedir($dir);
  350. }
  351. /**
  352. * Adds a link to external documentation.
  353. *
  354. * Please note that the prefix string is matched against the
  355. * start of the class name and that the preceding \ for namespaces
  356. * should NOT be included.
  357. *
  358. * You can augment the URI with the name of the found class by inserting
  359. * the param {CLASS}. By default the class is inserted as-is; to insert a
  360. * lowercase variant use the parameter {LOWERCASE_CLASS}
  361. *
  362. * @param string $prefix Class prefix to match, i.e. Zend_Config_
  363. * @param string $uri URI to link to when above prefix is encountered.
  364. *
  365. * @return void
  366. */
  367. public function setExternalClassDoc($prefix, $uri)
  368. {
  369. $this->external_class_docs[$prefix] = $uri;
  370. }
  371. /**
  372. * Sets a set of prefix -> url parts.
  373. *
  374. * @param string[] $external_class_docs Array containing prefix => URI pairs.
  375. *
  376. * @see self::setExternalClassDoc() for details on this feature.
  377. *
  378. * @return void
  379. */
  380. public function setExternalClassDocs($external_class_docs)
  381. {
  382. $this->external_class_docs = $external_class_docs;
  383. }
  384. /**
  385. * Returns the registered prefix -> url pairs.
  386. *
  387. * @return string[]
  388. */
  389. public function getExternalClassDocs()
  390. {
  391. return $this->external_class_docs;
  392. }
  393. /**
  394. * Retrieves the url for a given prefix.
  395. *
  396. * @param string $prefix Class prefix to retrieve a URL for.
  397. * @param string $class If provided will replace the {CLASS} param with
  398. * this string.
  399. *
  400. * @return string|null
  401. */
  402. public function getExternalClassDocumentLocation($prefix, $class = null)
  403. {
  404. if (!isset($this->external_class_docs[$prefix])) {
  405. return null;
  406. }
  407. $result = $this->external_class_docs[$prefix];
  408. if ($class !== null) {
  409. $result = str_replace(
  410. array('{CLASS}', '{LOWERCASE_CLASS}', '{UNPREFIXED_CLASS}'),
  411. array($class, strtolower($class), substr($class, strlen($prefix))),
  412. $result
  413. );
  414. }
  415. return $result;
  416. }
  417. /**
  418. * Returns the url for this class if it is registered.
  419. *
  420. * @param string $class FQCN to retrieve documentation URL for.
  421. *
  422. * @return null|string
  423. */
  424. public function findExternalClassDocumentLocation($class)
  425. {
  426. $class = ltrim($class, '\\');
  427. foreach (array_keys($this->external_class_docs) as $prefix) {
  428. if (strpos($class, $prefix) === 0) {
  429. return $this->getExternalClassDocumentLocation($prefix, $class);
  430. }
  431. }
  432. return null;
  433. }
  434. }