XmlFileLoader.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  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 Symfony\Component\Routing\Loader;
  11. use Symfony\Component\Routing\RouteCollection;
  12. use Symfony\Component\Routing\Route;
  13. use Symfony\Component\Routing\Resource\FileResource;
  14. /**
  15. * XmlFileLoader loads XML routing files.
  16. *
  17. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  18. */
  19. class XmlFileLoader extends FileLoader
  20. {
  21. /**
  22. * Loads an XML file.
  23. *
  24. * @param string $file An XML file path
  25. * @param string $type The resource type
  26. *
  27. * @return RouteCollection A RouteCollection instance
  28. *
  29. * @throws \InvalidArgumentException When a tag can't be parsed
  30. */
  31. public function load($file, $type = null)
  32. {
  33. $path = $this->locator->locate($file);
  34. $xml = $this->loadFile($path);
  35. $collection = new RouteCollection();
  36. $collection->addResource(new FileResource($path));
  37. // process routes and imports
  38. foreach ($xml->documentElement->childNodes as $node) {
  39. if (!$node instanceof \DOMElement) {
  40. continue;
  41. }
  42. switch ($node->tagName) {
  43. case 'route':
  44. $this->parseRoute($collection, $node, $path);
  45. break;
  46. case 'import':
  47. $resource = (string) $node->getAttribute('resource');
  48. $type = (string) $node->getAttribute('type');
  49. $prefix = (string) $node->getAttribute('prefix');
  50. $this->currentDir = dirname($path);
  51. $collection->addCollection($this->import($resource, $type), $prefix);
  52. break;
  53. default:
  54. throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
  55. }
  56. }
  57. return $collection;
  58. }
  59. /**
  60. * Returns true if this class supports the given resource.
  61. *
  62. * @param mixed $resource A resource
  63. * @param string $type The resource type
  64. *
  65. * @return Boolean True if this class supports the given resource, false otherwise
  66. */
  67. public function supports($resource, $type = null)
  68. {
  69. return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
  70. }
  71. /**
  72. * Parses a route and adds it to the RouteCollection.
  73. *
  74. * @param RouteCollection $collection A RouteCollection instance
  75. * @param \DOMElement $definition Route definition
  76. * @param string $file An XML file path
  77. *
  78. * @throws \InvalidArgumentException When the definition cannot be parsed
  79. */
  80. protected function parseRoute(RouteCollection $collection, \DOMElement $definition, $file)
  81. {
  82. $defaults = array();
  83. $requirements = array();
  84. $options = array();
  85. foreach ($definition->childNodes as $node) {
  86. if (!$node instanceof \DOMElement) {
  87. continue;
  88. }
  89. switch ($node->tagName) {
  90. case 'default':
  91. $defaults[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  92. break;
  93. case 'option':
  94. $options[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  95. break;
  96. case 'requirement':
  97. $requirements[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  98. break;
  99. default:
  100. throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
  101. }
  102. }
  103. $route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options);
  104. $collection->add((string) $definition->getAttribute('id'), $route);
  105. }
  106. /**
  107. * Loads an XML file.
  108. *
  109. * @param string $file An XML file path
  110. *
  111. * @return \DOMDocument
  112. *
  113. * @throws \InvalidArgumentException When loading of XML file returns error
  114. */
  115. protected function loadFile($file)
  116. {
  117. $dom = new \DOMDocument();
  118. libxml_use_internal_errors(true);
  119. if (!$dom->load($file, LIBXML_COMPACT)) {
  120. throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
  121. }
  122. $dom->validateOnParse = true;
  123. $dom->normalizeDocument();
  124. libxml_use_internal_errors(false);
  125. $this->validate($dom);
  126. return $dom;
  127. }
  128. /**
  129. * Validates a loaded XML file.
  130. *
  131. * @param \DOMDocument $dom A loaded XML file
  132. *
  133. * @throws \InvalidArgumentException When XML doesn't validate its XSD schema
  134. */
  135. protected function validate(\DOMDocument $dom)
  136. {
  137. $parts = explode('/', str_replace('\\', '/', __DIR__.'/schema/routing/routing-1.0.xsd'));
  138. $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
  139. $location = 'file:///'.$drive.implode('/', $parts);
  140. $current = libxml_use_internal_errors(true);
  141. if (!$dom->schemaValidate($location)) {
  142. throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
  143. }
  144. libxml_use_internal_errors($current);
  145. }
  146. /**
  147. * Retrieves libxml errors and clears them.
  148. *
  149. * @return array An array of libxml error strings
  150. */
  151. protected function getXmlErrors()
  152. {
  153. $errors = array();
  154. foreach (libxml_get_errors() as $error) {
  155. $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
  156. LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
  157. $error->code,
  158. trim($error->message),
  159. $error->file ? $error->file : 'n/a',
  160. $error->line,
  161. $error->column
  162. );
  163. }
  164. libxml_clear_errors();
  165. return $errors;
  166. }
  167. }