ClassesBuilder.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <?php
  2. /**
  3. * This file is part of the PHPExiftool package.
  4. *
  5. * (c) Alchemy <support@alchemy.fr>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace PHPExiftool\Tool\Command;
  11. use Monolog\Handler\NullHandler;
  12. use Monolog\Handler\StreamHandler;
  13. use Monolog\Logger;
  14. use PHPExiftool\ClassUtils\Builder;
  15. use PHPExiftool\ClassUtils\TagProviderBuilder;
  16. use PHPExiftool\Exiftool;
  17. use PHPExiftool\InformationDumper;
  18. use Symfony\Component\Console\Command\Command;
  19. use Symfony\Component\Console\Helper\ProgressBar;
  20. use Symfony\Component\Console\Input\InputInterface;
  21. use Symfony\Component\Console\Output\ConsoleOutput;
  22. use Symfony\Component\Console\Output\OutputInterface;
  23. use Symfony\Component\DomCrawler\Crawler;
  24. /**
  25. *
  26. * @author Romain Neutron - imprec@gmail.com
  27. * @license http://opensource.org/licenses/MIT MIT
  28. */
  29. class ClassesBuilder extends Command
  30. {
  31. /**
  32. * Output interface for Command
  33. *
  34. * @var ConsoleOutput
  35. */
  36. protected $output;
  37. /**
  38. *
  39. * @var array
  40. */
  41. protected $classes = array();
  42. /**
  43. *
  44. * @var array
  45. */
  46. protected $types = array();
  47. /**
  48. * @see Console\Command\Command
  49. */
  50. protected function configure()
  51. {
  52. $this
  53. ->setName('classes-builder')
  54. ->setDescription('Build Tags classes from exiftool documentation.')
  55. ->addOption('with-mwg', '', null, 'Include MWG tags')
  56. ->addOption('write', 'w', null, 'Write classes on disk')
  57. ->addOption('force', 'f', null, 'Force classes write whenever files already exists');
  58. return $this;
  59. }
  60. /**
  61. * @see Console\Command\Command
  62. */
  63. protected function execute(InputInterface $input, OutputInterface $output)
  64. {
  65. $start = microtime(true);
  66. $this->output = $output;
  67. $this->output->write('Extracting datas... ');
  68. $logger = new Logger('Builder');
  69. $logger->pushHandler(new NullHandler());
  70. if ($input->getOption('verbose')) {
  71. $logger->pushHandler(new StreamHandler('php://stdout'));
  72. }
  73. $dumper = new InformationDumper(new Exiftool($logger));
  74. $options = array();
  75. if($input->getOption('with-mwg')) {
  76. $options[] = InformationDumper::LISTOPTION_MWG;
  77. }
  78. $dump = $dumper->listDatas(InformationDumper::LISTTYPE_SUPPORTED_XML, $options);
  79. $this->output->writeln('Done !');
  80. $this->output->writeln('Generating classes... ');
  81. $this->extractDump($dump, $output);
  82. if ( ! $input->getOption('write')) {
  83. $this->output->writeln(
  84. 'These classes were not written. Use --write to write on disk'
  85. );
  86. } else {
  87. $this->output->writeln('Erasing previous files... ');
  88. system('rm -R ' . __DIR__ . '/../../Driver/Tag/*');
  89. system('rm -R ' . __DIR__ . '/../../Driver/Type/*');
  90. $this->output->writeln('Writing files... ');
  91. $this->writeClasses($input->getOption('force'));
  92. }
  93. $this->output->writeln(
  94. sprintf(
  95. '%d classes generated in %d seconds (%d Mb)'
  96. , count($this->classes), (microtime(true) - $start), memory_get_peak_usage() >> 20
  97. )
  98. );
  99. }
  100. /**
  101. *
  102. * @return ClassesBuilder
  103. */
  104. protected function writeClasses($force = false)
  105. {
  106. $n = 0;
  107. $classesBuffer = new TagProviderBuilder('', 'TagProvider', array(), '\\Pimple');
  108. $buffer = array();
  109. foreach ($this->classes as $class) {
  110. try {
  111. $class->write($force);
  112. if (strpos($class->getNamespace(), 'PHPExiftool\\Driver\\Tag') === 0) {
  113. if ( ! isset($buffer[$class->getProperty('GroupName')])) {
  114. $buffer[$class->getProperty('GroupName')] = array();
  115. }
  116. $buffer[$class->getProperty('GroupName')][$class->getProperty('Name')] = $class->getNamespace() . '\\' . $class->getClassname();
  117. }
  118. $this->output->write(sprintf("\rwriting class #%5d", $n ++ ));
  119. } catch (\Exception $e) {
  120. $this->output->writeln(
  121. sprintf("\n<error>Error while writing class %s</error>", $class->getPathfile())
  122. );
  123. }
  124. }
  125. $classesBuffer->setClasses($buffer);
  126. $classesBuffer->write(true);
  127. $this->output->writeln('');
  128. return $this;
  129. }
  130. protected function generateTypes()
  131. {
  132. foreach ($this->types as $type => $data) {
  133. if ($type == '?')
  134. $type = 'unknown';
  135. $classname = self::generateClassname($type);
  136. $properties = array(
  137. 'ExiftoolName' => $data,
  138. 'PHPMap' => $this->getTypeMap($type),
  139. );
  140. $classpath = sprintf('%s', $classname);
  141. $this->classes[$classpath] = new Builder('Type', $classname, $properties, 'AbstractType', array('\\PHPExiftool\\Driver\\AbstractType'));
  142. }
  143. return;
  144. }
  145. protected function getTypeMap($type)
  146. {
  147. /**
  148. * Some of these types are described here:
  149. * http://trac.greenstone.org/browser/main/trunk/greenstone2/perllib/cpan/Image/ExifTool/README
  150. * http://cpansearch.perl.org/src/EXIFTOOL/Image-ExifTool-9.13/lib/Image/ExifTool/PICT.pm
  151. */
  152. switch ($type) {
  153. # Formats defined in the wiki
  154. case 'int8s':
  155. case 'int8u':
  156. case 'int16s':
  157. case 'int16u':
  158. case 'int16uRev':
  159. case 'int32s':
  160. case 'int32u':
  161. case 'int32uRev':
  162. case 'int64s':
  163. case 'int64u':
  164. case 'rational32s':
  165. case 'rational32u':
  166. case 'rational64s':
  167. case 'rational64u':
  168. case 'fixed16s':
  169. case 'fixed32s':
  170. case 'fixed32u':
  171. case 'var_int16u':
  172. # Apple data structures in PICT images
  173. case 'Int8uText':
  174. case 'Int8u2Text':
  175. case 'Int16Data':
  176. case 'Int32uData':
  177. # Source unknown ...
  178. case 'var_int8u':
  179. case 'rational':
  180. case 'integer':
  181. case 'real':
  182. case 'digits':
  183. case 'signed':
  184. case 'unsigned':
  185. return 'int';
  186. break;
  187. # Formats defined in the wiki
  188. case 'float':
  189. case 'double':
  190. case 'extended':
  191. return 'float';
  192. break;
  193. # Formats defined in the wiki
  194. case 'undef':
  195. case 'binary':
  196. # Source unknown ...
  197. case 'var_undef':
  198. case '?':
  199. case 'null':
  200. case 'unknown':
  201. case 'Unknown':
  202. return 'binary';
  203. break;
  204. # Formats defined in the wiki
  205. case 'string':
  206. case 'pstring':
  207. case 'var_string':
  208. case 'var_pstr32':
  209. # Apple data structures in PICT images
  210. case 'Arc':
  211. case 'BitsRect#': # version-depended
  212. case 'BitsRgn#': # version-depended
  213. case 'CompressedQuickTime':
  214. case 'DirectBitsRect':
  215. case 'DirectBitsRgn':
  216. case 'FontName':
  217. case 'PixPat':
  218. case 'Point':
  219. case 'PointText':
  220. case 'Polygon':
  221. case 'Rect':
  222. case 'RGBColor':
  223. case 'Rgn':
  224. case 'ShortLine':
  225. # Source unknown ...
  226. case 'lang-alt':
  227. case 'resize':
  228. case 'utf8':
  229. return 'string';
  230. break;
  231. # Source unknown ...
  232. case 'date':
  233. return 'date';
  234. break;
  235. # Source unknown ...
  236. case 'boolean':
  237. return 'boolean';
  238. break;
  239. default:
  240. $this->output->writeln(sprintf("No type found for %s", $type));
  241. break;
  242. }
  243. return;
  244. }
  245. protected function createTagClass($namespace, $classname, array $properties)
  246. {
  247. if ($classname == 'Reserved') {
  248. return;
  249. }
  250. $namespace = self::generateNamespace('Tag\\' . $namespace);
  251. $classpath = sprintf('%s\\%s', $namespace, $classname);
  252. if (isset($this->classes[$classpath])) {
  253. foreach ($properties as $property => $value) {
  254. if ($this->classes[$classpath]->getProperty($property) != $value) {
  255. if (in_array($property, array('Writable', 'flag_Binary', 'flag_List'))) {
  256. $this->classes[$classpath]->setProperty($property, 'false');
  257. } elseif ($property === 'Values') {
  258. $new_value = array();
  259. if ( ! is_array($this->classes[$classpath]->getProperty($property))) {
  260. if (is_array($value)) {
  261. $new_value = $value;
  262. }
  263. } else {
  264. if (is_array($value) && $this->classes[$classpath]->getProperty($property) != $value) {
  265. $new_value = array_merge($this->classes[$classpath]->getProperty($property), $value);
  266. } else {
  267. $new_value = $this->classes[$classpath]->getProperty($property);
  268. }
  269. }
  270. $this->classes[$classpath]->setProperty($property, $new_value);
  271. } else {
  272. $this->classes[$classpath]->setProperty($property, 'mixed');
  273. }
  274. }
  275. }
  276. } else {
  277. $this->classes[$classpath] = new Builder($namespace, $classname, $properties, 'AbstractTag', array('JMS\\Serializer\\Annotation\\ExclusionPolicy', '\\PHPExiftool\\Driver\\AbstractTag'), array('@ExclusionPolicy("all")'));
  278. }
  279. return;
  280. }
  281. protected function extractDump($dump, OutputInterface $output)
  282. {
  283. $crawler = new Crawler();
  284. $crawler->addContent($dump);
  285. $tag_count = null;
  286. if ($output->getVerbosity() >= OutputInterface::VERBOSITY_QUIET) {
  287. $output->write('Compute tag count...');
  288. $tag_count = count($crawler->filter('table>tag'));
  289. $output->writeln(sprintf('%d', $tag_count));
  290. }
  291. if (!$this->getHelperSet()->has('progress')) {
  292. $progress = new ProgressBar($output);
  293. $progress->start($tag_count);
  294. } else {
  295. $progress = $this->getHelper('progress');
  296. $progress->start($output, $tag_count);
  297. }
  298. foreach ($crawler->filter('table') as $table) {
  299. $table_crawler = new Crawler();
  300. $table_crawler->addNode($table);
  301. $tag_group_name = $table_crawler->attr('g1');
  302. $tag_full_name = $table_crawler->attr('name');
  303. $tag_g0 = $table_crawler->attr('g0');
  304. $tag_g2 = $table_crawler->attr('g2');
  305. $tags = $table_crawler->filter('tag');
  306. foreach ($tags as $tag) {
  307. $tag_crawler = new Crawler();
  308. $tag_crawler->addNode($tag);
  309. $extra = array();
  310. if ($tag_crawler->attr('g0')) {
  311. $extra['local_g0'] = $tag_crawler->attr('g0');
  312. }
  313. if ($tag_crawler->attr('g1') && ! in_array($tag_crawler->attr('g1'), array('MakerNotes', 'Chapter#'))) {
  314. $g_name = $tag_crawler->attr('g1');
  315. $extra['local_g1'] = $tag_crawler->attr('g1');
  316. } else {
  317. $g_name = $tag_group_name;
  318. }
  319. if ($tag_crawler->attr('g2')) {
  320. $extra['local_g2'] = $tag_crawler->attr('g2');
  321. }
  322. $flags = explode(',', $tag_crawler->attr('flags'));
  323. if (in_array('Avoid', $flags)) {
  324. $extra['flag_Avoid'] = 'true';
  325. }
  326. if (in_array('Binary', $flags)) {
  327. $extra['flag_Binary'] = 'true';
  328. }
  329. if (in_array('Permanent', $flags)) {
  330. $extra['flag_Permanent'] = 'true';
  331. }
  332. if (in_array('Protected', $flags)) {
  333. $extra['flag_Protected'] = 'true';
  334. }
  335. if (in_array('Unsafe', $flags)) {
  336. $extra['flag_Unsafe'] = 'true';
  337. }
  338. if (in_array('List', $flags)) {
  339. $extra['flag_List'] = 'true';
  340. }
  341. if (in_array('Mandatory', $flags)) {
  342. $extra['flag_Mandatory'] = 'true';
  343. }
  344. if (in_array('Bag', $flags)) {
  345. $extra['flag_Bag'] = 'true';
  346. }
  347. if (in_array('Seq', $flags)) {
  348. $extra['flag_Seq'] = 'true';
  349. }
  350. if (in_array('Alt', $flags)) {
  351. $extra['flag_Alt'] = 'true';
  352. }
  353. $subspace = str_replace('::', '\\', $g_name);
  354. $tag_name = $tag_crawler->attr('name');
  355. $classname = self::generateClassname($tag_name);
  356. $tag_id = $tag_crawler->attr('id');
  357. $properties = array_merge(array(
  358. 'Id' => $tag_id,
  359. 'Name' => $tag_name,
  360. 'FullName' => $tag_full_name,
  361. 'GroupName' => $g_name,
  362. 'g0' => $tag_g0,
  363. 'g1' => $tag_group_name,
  364. 'g2' => $tag_g2,
  365. 'Type' => $tag_crawler->attr('type'),
  366. 'Writable' => $tag_crawler->attr('writable'),
  367. 'Description' => $tag_crawler->filter('desc[lang="en"]')->first()->text(),
  368. ), $extra);
  369. if ($tag_crawler->attr('count')) {
  370. $properties['MaxLength'] = $tag_crawler->attr('count');
  371. }
  372. $this->types[$tag_crawler->attr('type')] = $tag_crawler->attr('type');
  373. if ($tag_crawler->attr('index')) {
  374. $properties['Index'] = $tag_crawler->attr('index');
  375. }
  376. if (count($tag_crawler->filter('values')) > 0) {
  377. $values = array();
  378. $values_tag = $tag_crawler->filter('values')->first();
  379. $Keys = $values_tag->filter('key');
  380. foreach ($Keys as $Key) {
  381. $KeyCrawler = new Crawler();
  382. $KeyCrawler->addNode($Key);
  383. $Id = $KeyCrawler->attr('id');
  384. $Label = $KeyCrawler->filter('val[lang="en"]')->first()->text();
  385. $values[$Id] = array('Id' => $Id, 'Label' => $Label);
  386. }
  387. $properties['Values'] = $values;
  388. }
  389. $this->createTagClass($subspace, $classname, $properties);
  390. $progress->advance();
  391. }
  392. }
  393. $progress->finish();
  394. $this->generateTypes();
  395. }
  396. protected static $reservedNames = array(
  397. 'abstract',
  398. 'and',
  399. 'array',
  400. 'as',
  401. 'bool',
  402. 'break',
  403. 'case',
  404. 'catch',
  405. 'class',
  406. 'clone',
  407. 'const',
  408. 'continue',
  409. 'declare',
  410. 'default',
  411. 'do',
  412. 'else',
  413. 'elseif',
  414. 'enddeclare',
  415. 'endfor',
  416. 'endforeach',
  417. 'endif',
  418. 'endswitch',
  419. 'endwhile',
  420. 'extends',
  421. 'false',
  422. 'final',
  423. 'float',
  424. 'for',
  425. 'foreach',
  426. 'function',
  427. 'function',
  428. 'global',
  429. 'goto',
  430. 'if',
  431. 'implements',
  432. 'instanceof',
  433. 'int',
  434. 'interface',
  435. 'mixed',
  436. 'namespace',
  437. 'new',
  438. 'null',
  439. 'numeric',
  440. 'object',
  441. 'old_function',
  442. 'or',
  443. 'private',
  444. 'protected',
  445. 'public',
  446. 'resource',
  447. 'static',
  448. 'string',
  449. 'switch',
  450. 'throw',
  451. 'true',
  452. 'try',
  453. 'use',
  454. 'var',
  455. 'while',
  456. 'xor',
  457. 'yield',
  458. );
  459. /**
  460. *
  461. * @param type $name
  462. * @return type
  463. */
  464. public static function generateClassname($name)
  465. {
  466. $values = preg_split('/\\ |-|_|\\#/', ltrim($name, '0123456789'));
  467. foreach ($values as $key => $value) {
  468. $values[$key] = ucfirst($value);
  469. }
  470. $retval = implode('', $values);
  471. if (in_array(strtolower($retval), static::$reservedNames)) {
  472. $retval = $retval . '0';
  473. }
  474. return $retval;
  475. }
  476. public static function generateNamespace($namespace)
  477. {
  478. $values = explode('\\', $namespace);
  479. foreach ($values as $key => $value) {
  480. $values[$key] = ucfirst(self::generateClassname($value));
  481. }
  482. return implode('\\', $values);
  483. }
  484. }