PageRenderTime 27ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/generator/lib/builder/util/XmlToAppData.php

http://github.com/propelorm/Propel
PHP | 424 lines | 272 code | 65 blank | 87 comment | 36 complexity | 68c8a4c8d6c04935c9153b9f5b2ad3b4 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. *
  7. * @license MIT License
  8. */
  9. require_once dirname(__FILE__) . '/../../model/AppData.php';
  10. require_once dirname(__FILE__) . '/../../exception/SchemaException.php';
  11. /**
  12. * A class that is used to parse an input xml schema file and creates an AppData
  13. * PHP object.
  14. *
  15. * @author Hans Lellelid <hans@xmpl.org> (Propel)
  16. * @author Leon Messerschmidt <leon@opticode.co.za> (Torque)
  17. * @author Jason van Zyl <jvanzyl@apache.org> (Torque)
  18. * @author Martin Poeschl <mpoeschl@marmot.at> (Torque)
  19. * @author Daniel Rall <dlr@collab.net> (Torque)
  20. * @version $Revision$
  21. * @package propel.generator.builder.util
  22. */
  23. class XmlToAppData
  24. {
  25. /** enables debug output */
  26. const DEBUG = false;
  27. private $app;
  28. private $currDB;
  29. private $currTable;
  30. private $currColumn;
  31. private $currFK;
  32. private $currIndex;
  33. private $currUnique;
  34. private $currValidator;
  35. private $currBehavior;
  36. private $currVendorObject;
  37. private $isForReferenceOnly;
  38. private $currentPackage;
  39. private $currentXmlFile;
  40. private $defaultPackage;
  41. private $encoding;
  42. /**
  43. * two-dimensional array,
  44. * first dimension is for schemas(key is the path to the schema file),
  45. * second is for tags within the schema
  46. *
  47. * @var array
  48. */
  49. private $schemasTagsStack = array();
  50. /**
  51. * Creates a new instance for the specified database type.
  52. *
  53. * @param PropelPlatformInterface $defaultPlatform The default database platform for the application.
  54. * @param string $defaultPackage the default PHP package used for the om
  55. * @param string $encoding The database encoding.
  56. */
  57. public function __construct(PropelPlatformInterface $defaultPlatform = null, $defaultPackage = null, $encoding = 'iso-8859-1')
  58. {
  59. $this->app = new AppData($defaultPlatform);
  60. $this->defaultPackage = $defaultPackage;
  61. $this->firstPass = true;
  62. $this->encoding = $encoding;
  63. }
  64. /**
  65. * Set the AppData generator configuration
  66. *
  67. * @param GeneratorConfigInterface $generatorConfig
  68. */
  69. public function setGeneratorConfig(GeneratorConfigInterface $generatorConfig)
  70. {
  71. $this->app->setGeneratorConfig($generatorConfig);
  72. }
  73. /**
  74. * Parses a XML input file and returns a newly created and
  75. * populated AppData structure.
  76. *
  77. * @param string $xmlFile The input file to parse.
  78. *
  79. * @return AppData populated by <code>xmlFile</code>.
  80. */
  81. public function parseFile($xmlFile)
  82. {
  83. // we don't want infinite recursion
  84. if ($this->isAlreadyParsed($xmlFile)) {
  85. return;
  86. }
  87. return $this->parseString(file_get_contents($xmlFile), $xmlFile);
  88. }
  89. /**
  90. * Parses a XML input string and returns a newly created and
  91. * populated AppData structure.
  92. *
  93. * @param string $xmlString The input string to parse.
  94. * @param string $xmlFile The input file name.
  95. *
  96. * @return AppData populated by <code>xmlFile</code>.
  97. * @throws Exception
  98. */
  99. public function parseString($xmlString, $xmlFile = null)
  100. {
  101. // we don't want infinite recursion
  102. if ($this->isAlreadyParsed($xmlFile)) {
  103. return;
  104. }
  105. // store current schema file path
  106. $this->schemasTagsStack[$xmlFile] = array();
  107. $this->currentXmlFile = $xmlFile;
  108. $parser = xml_parser_create();
  109. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  110. xml_set_object($parser, $this);
  111. xml_set_element_handler($parser, 'startElement', 'endElement');
  112. if (!xml_parse($parser, $xmlString)) {
  113. throw new Exception(sprintf("XML error: %s at line %d",
  114. xml_error_string(xml_get_error_code($parser)),
  115. xml_get_current_line_number($parser))
  116. );
  117. }
  118. xml_parser_free($parser);
  119. array_pop($this->schemasTagsStack);
  120. return $this->app;
  121. }
  122. /**
  123. * Handles opening elements of the xml file.
  124. *
  125. * @param string $uri
  126. * @param string $localName The local name (without prefix), or the empty string if
  127. * Namespace processing is not being performed.
  128. * @param string $rawName The qualified name (with prefix), or the empty string if
  129. * qualified names are not available.
  130. * @param string $attributes The specified or defaulted attributes
  131. *
  132. * @throws SchemaException
  133. */
  134. public function startElement($parser, $name, $attributes)
  135. {
  136. $parentTag = $this->peekCurrentSchemaTag();
  137. if ($parentTag === false) {
  138. switch ($name) {
  139. case "database":
  140. if ($this->isExternalSchema()) {
  141. $this->currentPackage = @$attributes["package"];
  142. if ($this->currentPackage === null) {
  143. $this->currentPackage = $this->defaultPackage;
  144. }
  145. } else {
  146. $this->currDB = $this->app->addDatabase($attributes);
  147. }
  148. break;
  149. default:
  150. $this->_throwInvalidTagException($parser, $name);
  151. }
  152. } elseif ($parentTag == "database") {
  153. switch ($name) {
  154. case "external-schema":
  155. $xmlFile = @$attributes["filename"];
  156. // "referenceOnly" attribute is valid in the main schema XML file only,
  157. // and it's ignored in the nested external-schemas
  158. if (!$this->isExternalSchema()) {
  159. $isForRefOnly = @$attributes["referenceOnly"];
  160. $this->isForReferenceOnly = ($isForRefOnly !== null ? (strtolower($isForRefOnly) === "true") : true); // defaults to TRUE
  161. }
  162. if (!$this->isAbsolutePath($xmlFile)) {
  163. $xmlFile = realpath(dirname($this->currentXmlFile) . DIRECTORY_SEPARATOR . $xmlFile);
  164. if (!file_exists($xmlFile)) {
  165. throw new SchemaException(sprintf('Unknown include external "%s"', $xmlFile));
  166. }
  167. }
  168. $this->parseFile($xmlFile);
  169. break;
  170. case "domain":
  171. $this->currDB->addDomain($attributes);
  172. break;
  173. case "table":
  174. $this->currTable = $this->currDB->addTable($attributes);
  175. if ($this->isExternalSchema()) {
  176. $this->currTable->setForReferenceOnly($this->isForReferenceOnly);
  177. $this->currTable->setPackage($this->currentPackage);
  178. }
  179. break;
  180. case "vendor":
  181. $this->currVendorObject = $this->currDB->addVendorInfo($attributes);
  182. break;
  183. case "behavior":
  184. $this->currBehavior = $this->currDB->addBehavior($attributes);
  185. break;
  186. default:
  187. $this->_throwInvalidTagException($parser, $name);
  188. }
  189. } elseif ($parentTag == "table") {
  190. switch ($name) {
  191. case "column":
  192. $this->currColumn = $this->currTable->addColumn($attributes);
  193. break;
  194. case "foreign-key":
  195. $this->currFK = $this->currTable->addForeignKey($attributes);
  196. break;
  197. case "index":
  198. $this->currIndex = $this->currTable->addIndex($attributes);
  199. break;
  200. case "unique":
  201. $this->currUnique = $this->currTable->addUnique($attributes);
  202. break;
  203. case "vendor":
  204. $this->currVendorObject = $this->currTable->addVendorInfo($attributes);
  205. break;
  206. case "validator":
  207. $this->currValidator = $this->currTable->addValidator($attributes);
  208. break;
  209. case "id-method-parameter":
  210. $this->currTable->addIdMethodParameter($attributes);
  211. break;
  212. case "behavior":
  213. $this->currBehavior = $this->currTable->addBehavior($attributes);
  214. break;
  215. default:
  216. $this->_throwInvalidTagException($parser, $name);
  217. }
  218. } elseif ($parentTag == "column") {
  219. switch ($name) {
  220. case "inheritance":
  221. $this->currColumn->addInheritance($attributes);
  222. break;
  223. case "vendor":
  224. $this->currVendorObject = $this->currColumn->addVendorInfo($attributes);
  225. break;
  226. default:
  227. $this->_throwInvalidTagException($parser, $name);
  228. }
  229. } elseif ($parentTag == "foreign-key") {
  230. switch ($name) {
  231. case "reference":
  232. $this->currFK->addReference($attributes);
  233. break;
  234. case "vendor":
  235. $this->currVendorObject = $this->currUnique->addVendorInfo($attributes);
  236. break;
  237. default:
  238. $this->_throwInvalidTagException($parser, $name);
  239. }
  240. } elseif ($parentTag == "index") {
  241. switch ($name) {
  242. case "index-column":
  243. $this->currIndex->addColumn($attributes);
  244. break;
  245. case "vendor":
  246. $this->currVendorObject = $this->currIndex->addVendorInfo($attributes);
  247. break;
  248. default:
  249. $this->_throwInvalidTagException($parser, $name);
  250. }
  251. } elseif ($parentTag == "unique") {
  252. switch ($name) {
  253. case "unique-column":
  254. $this->currUnique->addColumn($attributes);
  255. break;
  256. case "vendor":
  257. $this->currVendorObject = $this->currUnique->addVendorInfo($attributes);
  258. break;
  259. default:
  260. $this->_throwInvalidTagException($parser, $name);
  261. }
  262. } elseif ($parentTag == "behavior") {
  263. switch ($name) {
  264. case "parameter":
  265. $this->currBehavior->addParameter($attributes);
  266. break;
  267. default:
  268. $this->_throwInvalidTagException($parser, $name);
  269. }
  270. } elseif ($parentTag == "validator") {
  271. switch ($name) {
  272. case "rule":
  273. $this->currValidator->addRule($attributes);
  274. break;
  275. default:
  276. $this->_throwInvalidTagException($parser, $name);
  277. }
  278. } elseif ($parentTag == "vendor") {
  279. switch ($name) {
  280. case "parameter":
  281. $this->currVendorObject->addParameter($attributes);
  282. break;
  283. default:
  284. $this->_throwInvalidTagException($parser, $name);
  285. }
  286. } else {
  287. // it must be an invalid tag
  288. $this->_throwInvalidTagException($parser, $name);
  289. }
  290. $this->pushCurrentSchemaTag($name);
  291. }
  292. public function _throwInvalidTagException($parser, $tag_name)
  293. {
  294. $location = '';
  295. if ($this->currentXmlFile !== null) {
  296. $location .= sprintf('file %s,', $this->currentXmlFile);
  297. }
  298. $location .= sprintf('line %d', xml_get_current_line_number($parser));
  299. if ($col = xml_get_current_column_number($parser)) {
  300. $location .= sprintf(', column %d', $col);
  301. }
  302. throw new SchemaException(sprintf('Unexpected tag <%s> in %s', $tag_name, $location));
  303. }
  304. /**
  305. * Handles closing elements of the xml file.
  306. *
  307. * @param uri
  308. * @param localName The local name (without prefix), or the empty string if
  309. * Namespace processing is not being performed.
  310. * @param rawName The qualified name (with prefix), or the empty string if
  311. * qualified names are not available.
  312. */
  313. public function endElement($parser, $name)
  314. {
  315. if (self::DEBUG) {
  316. print("endElement(" . $name . ") called\n");
  317. }
  318. $this->popCurrentSchemaTag();
  319. }
  320. protected function peekCurrentSchemaTag()
  321. {
  322. $keys = array_keys($this->schemasTagsStack);
  323. return end($this->schemasTagsStack[end($keys)]);
  324. }
  325. protected function popCurrentSchemaTag()
  326. {
  327. $keys = array_keys($this->schemasTagsStack);
  328. array_pop($this->schemasTagsStack[end($keys)]);
  329. }
  330. protected function pushCurrentSchemaTag($tag)
  331. {
  332. $keys = array_keys($this->schemasTagsStack);
  333. $this->schemasTagsStack[end($keys)][] = $tag;
  334. }
  335. protected function isExternalSchema()
  336. {
  337. return count($this->schemasTagsStack) > 1;
  338. }
  339. protected function isAlreadyParsed($filePath)
  340. {
  341. return isset($this->schemasTagsStack[$filePath]);
  342. }
  343. /**
  344. * See: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Filesystem/Filesystem.php#L379
  345. */
  346. protected function isAbsolutePath($file)
  347. {
  348. if (strspn($file, '/\\', 0, 1)
  349. || (strlen($file) > 3 && ctype_alpha($file[0])
  350. && substr($file, 1, 1) === ':'
  351. && (strspn($file, '/\\', 2, 1))
  352. )
  353. || null !== parse_url($file, PHP_URL_SCHEME)
  354. ) {
  355. return true;
  356. }
  357. return false;
  358. }
  359. }