PageRenderTime 24ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/GettextExtractor.php

http://github.com/karelklima/gettext-extractor
PHP | 361 lines | 202 code | 43 blank | 116 comment | 34 complexity | 95f66dbaafa014374e28de7cd2a3f143 MD5 | raw file
  1. <?php
  2. /**
  3. * GettextExtractor
  4. *
  5. * Cool tool for automatic extracting gettext strings for translation
  6. *
  7. * Works best with Nette Framework
  8. *
  9. * This source file is subject to the New BSD License.
  10. *
  11. * @copyright Copyright (c) 2009 Karel Klima
  12. * @license New BSD License
  13. * @package Nette Extras
  14. * @version GettextExtractor 2.0, 2009-10-21
  15. */
  16. if (version_compare(PHP_VERSION, '5.2.2', '<'))
  17. exit('GettextExtractor needs PHP 5.2.2 or newer');
  18. /**
  19. * GettextExtractor tool
  20. *
  21. * @author Karel Klima
  22. * @copyright Copyright (c) 2009 Karel Kl�ma
  23. * @package Nette Extras
  24. */
  25. class GettextExtractor
  26. {
  27. const LOG_FILE = '/extractor.log';
  28. const ESCAPE_CHARS = '"';
  29. /** @var resource */
  30. protected $logHandler;
  31. /** @var array */
  32. protected $inputFiles = array();
  33. /** @var array */
  34. protected $filters = array(
  35. 'php' => array('PHP'),
  36. 'phtml' => array('PHP', 'NetteLatte')
  37. );
  38. /** @var array */
  39. protected $comments = array(
  40. 'Gettext keys exported by GettextExtractor'
  41. );
  42. /** @var array */
  43. protected $meta = array(
  44. 'Content-Type' => 'text/plain; charset=UTF-8',
  45. 'Plural-Forms' => 'nplurals=2; plural=(n != 1);'
  46. );
  47. /** @var array */
  48. protected $data = array();
  49. /** @var array */
  50. protected $filterStore = array();
  51. /**
  52. * Log setup
  53. * @param string|bool $logToFile Bool or path of custom log file
  54. */
  55. public function __construct($logToFile = false)
  56. {
  57. if (is_string($logToFile)) { // custom log file
  58. $this->logHandler = fopen($logToFile, "w");
  59. } elseif ($logToFile) { // default log file
  60. $this->logHandler = fopen(dirname(__FILE__) . self::LOG_FILE, "w");
  61. }
  62. }
  63. /**
  64. * Close the log hangdler if needed
  65. */
  66. public function __destruct()
  67. {
  68. if (is_resource($this->logHandler)) fclose($this->logHandler);
  69. }
  70. /**
  71. * Writes messages into log or dumps them on screen
  72. * @param string $message
  73. */
  74. public function log($message)
  75. {
  76. if (is_resource($this->logHandler)) {
  77. fwrite($this->logHandler, $message . "\n");
  78. } else {
  79. echo $message . "\n";
  80. }
  81. }
  82. /**
  83. * Exception factory
  84. * @param string $message
  85. * @throws Exception
  86. */
  87. protected function throwException($message)
  88. {
  89. $message = $message ? $message : 'Something unexpected occured. See GettextExtractor log for details';
  90. $this->log($message);
  91. //echo $message;
  92. throw new Exception($message);
  93. }
  94. /**
  95. * Scans given files or directories and extracts gettext keys from the content
  96. * @param string|array $resource
  97. * @return GettetExtractor
  98. */
  99. public function scan($resource)
  100. {
  101. $this->inputFiles = array();
  102. if (!is_array($resource)) $resource = array($resource);
  103. foreach ($resource as $item) {
  104. $this->log("Scanning '$item'");
  105. $this->_scan($item);
  106. }
  107. $this->_extract($this->inputFiles);
  108. return $this;
  109. }
  110. /**
  111. * Scans given files or directories (recursively) and stores extracted gettext keys in a buffer
  112. * @param string $resource File or directory
  113. */
  114. protected function _scan($resource)
  115. {
  116. if (!is_dir($resource) && !is_file($resource)) {
  117. $this->throwException("Resource '$resource' is not a directory or file");
  118. }
  119. if (is_file($resource)) {
  120. $this->inputFiles[] = realpath($resource);
  121. return;
  122. }
  123. // It's a directory
  124. $resource = realpath($resource);
  125. if (!$resource) return;
  126. $iterator = dir($resource);
  127. if (!$iterator) return;
  128. while (FALSE !== ($entry = $iterator->read())) {
  129. if ($entry == '.' || $entry == '..') continue;
  130. if ($entry[0] == '.') continue; // do not browse into .git directories
  131. $path = $resource . '/' . $entry;
  132. if (!is_readable($path)) continue;
  133. if (is_dir($path)) {
  134. $this->_scan($path);
  135. continue;
  136. }
  137. if (is_file($path)) {
  138. $info = pathinfo($path);
  139. if (!isset($info['extension'])) continue; // "lockfile" has no extension.. raises notice
  140. if (!isset($this->filters[$info['extension']])) continue;
  141. $this->inputFiles[] = realpath($path);
  142. }
  143. }
  144. $iterator->close();
  145. }
  146. /**
  147. * Extracts gettext keys from input files
  148. * @param array $inputFiles
  149. * @return array
  150. */
  151. protected function _extract($inputFiles)
  152. {
  153. $inputFiles = array_unique($inputFiles);
  154. foreach ($inputFiles as $inputFile)
  155. {
  156. if (!file_exists($inputFile)) {
  157. $this->throwException('ERROR: Invalid input file specified: ' . $inputFile);
  158. }
  159. if (!is_readable($inputFile)) {
  160. $this->throwException('ERROR: Input file is not readable: ' . $inputFile);
  161. }
  162. $this->log('Extracting data from file ' . $inputFile);
  163. foreach ($this->filters as $extension => $filters)
  164. {
  165. // Check file extension
  166. $info = pathinfo($inputFile);
  167. if ($info['extension'] !== $extension) continue;
  168. $this->log('Processing file ' . $inputFile);
  169. foreach ($filters as $filterName)
  170. {
  171. $filter = $this->getFilter($filterName);
  172. $filterData = $filter->extract($inputFile);
  173. $this->log(' Filter ' . $filterName . ' applied');
  174. $this->data = array_merge_recursive($this->data, $filterData);
  175. }
  176. }
  177. }
  178. return $this->data;
  179. }
  180. /**
  181. * Gets an instance of a GettextExtractor filter
  182. * @param string $filter
  183. * @return iFilter
  184. */
  185. public function getFilter($filter)
  186. {
  187. $filter = $filter . 'Filter';
  188. if (isset($this->filterStore[$filter])) return $this->filterStore[$filter];
  189. if (!class_exists($filter)) {
  190. $filter_file = dirname(__FILE__) . '/Filters/' . $filter . ".php";
  191. if (!file_exists($filter_file)) {
  192. $this->throwException('ERROR: Filter file ' . $filter_file . ' not found');
  193. }
  194. require_once $filter_file;
  195. if (!class_exists($filter)) {
  196. $this->throwException('ERROR: Class ' . $filter . ' not found');
  197. }
  198. }
  199. $this->filterStore[$filter] = new $filter;
  200. $this->log('Filter ' . $filter . ' loaded');
  201. return $this->filterStore[$filter];
  202. }
  203. /**
  204. * Assigns a filter to an extension
  205. * @param string $extension
  206. * @param string $filter
  207. * @return GettextExtractor
  208. */
  209. public function setFilter($extension, $filter)
  210. {
  211. if (isset($this->filters[$extension]) && in_array($filter, $this->filters[$extension])) return $this;
  212. $this->filters[$extension][] = $filter;
  213. return $this;
  214. }
  215. /**
  216. * Removes all filter settings in case we want to define a brand new one
  217. * @return GettextExtractor
  218. */
  219. public function removeAllFilters()
  220. {
  221. $this->filters = array();
  222. return $this;
  223. }
  224. /**
  225. * Adds a comment to the top of the output file
  226. * @param string $value
  227. * @return GettextExtractor
  228. */
  229. public function addComment($value) {
  230. $this->comments[] = $value;
  231. return $this;
  232. }
  233. /**
  234. * Gets a value of a meta key
  235. * @param string $key
  236. */
  237. public function getMeta($key)
  238. {
  239. return isset($this->meta[$key]) ? $this->meta[$key] : NULL;
  240. }
  241. /**
  242. * Sets a value of a meta key
  243. * @param string $key
  244. * @param string $value
  245. * @return GettextExtractor
  246. */
  247. public function setMeta($key, $value)
  248. {
  249. $this->meta[$key] = $value;
  250. return $this;
  251. }
  252. /**
  253. * Saves extracted data into gettext file
  254. * @param string $outputFile
  255. * @param array $data
  256. * @return GettextExtractor
  257. */
  258. public function save($outputFile, $data = null)
  259. {
  260. $data = $data ? $data : $this->data;
  261. // Output file permission check
  262. if (file_exists($outputFile) && !is_writable($outputFile)) {
  263. $this->throwException('ERROR: Output file is not writable!');
  264. }
  265. $handle = fopen($outputFile, "w");
  266. fwrite($handle, $this->formatData($data));
  267. fclose($handle);
  268. $this->log("Output file '$outputFile' created");
  269. return $this;
  270. }
  271. /**
  272. * Formats fetched data to gettext syntax
  273. * @param array $data
  274. * @return string
  275. */
  276. protected function formatData($data)
  277. {
  278. $output = array();
  279. foreach ($this->comments as $comment) {
  280. $output[] = '# ' . $comment;
  281. }
  282. $output[] = '# Created: ' . date('c');
  283. $output[] = 'msgid ""';
  284. $output[] = 'msgstr ""';
  285. foreach ($this->meta as $key => $value) {
  286. $output[] = '"' . $key . ': ' . $value . '\n"';
  287. }
  288. $output[] = '';
  289. ksort($data);
  290. foreach ($data as $key => $files)
  291. {
  292. ksort($files);
  293. foreach ($files as $file)
  294. $output[] = '# ' . $file;
  295. $output[] = 'msgid "' . $this->addSlashes($key) . '"';
  296. /*if (preg_match($this->pluralMatchRegexp, $key, $matches)) { // TODO: really export plurals? deprecated for now
  297. $output[] = 'msgid_plural "' . addslashes($key) . '"';
  298. //$output[] = 'msgid_plural ""';
  299. $output[] = 'msgstr[0] "' . addslashes($key) . '"';
  300. $output[] = 'msgstr[1] "' . addslashes($key) . '"';
  301. } else {
  302. $output[] = 'msgstr "' . addslashes($key) . '"';
  303. }*/
  304. $output[] = 'msgstr "' . $this->addSlashes($key) . '"';
  305. $output[] = '';
  306. }
  307. return join("\n", $output);
  308. }
  309. /**
  310. * Escape a sring not to break the gettext syntax
  311. * @param string $string
  312. * @return string
  313. */
  314. public function addSlashes($string)
  315. {
  316. return addcslashes($string, self::ESCAPE_CHARS);
  317. }
  318. }