XmlFileLoader.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <?php
  2. namespace Symfony\Components\Routing\Loader;
  3. use Symfony\Components\Routing\RouteCollection;
  4. use Symfony\Components\Routing\Route;
  5. use Symfony\Components\Routing\FileResource;
  6. /*
  7. * This file is part of the Symfony framework.
  8. *
  9. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  10. *
  11. * This source file is subject to the MIT license that is bundled
  12. * with this source code in the file LICENSE.
  13. */
  14. /**
  15. * XmlFileLoader loads XML routing files.
  16. *
  17. * @package Symfony
  18. * @subpackage Components_Routing
  19. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  20. */
  21. class XmlFileLoader extends FileLoader
  22. {
  23. /**
  24. * Loads an XML file.
  25. *
  26. * @param string $file A XML file path
  27. *
  28. * @return RouteCollection A RouteCollection instance
  29. *
  30. * @throws \InvalidArgumentException When a tag can't be parsed
  31. */
  32. public function load($file)
  33. {
  34. $path = $this->findFile($file);
  35. $xml = $this->loadFile($path);
  36. $collection = new RouteCollection();
  37. $collection->addResource(new FileResource($path));
  38. // process routes and imports
  39. foreach ($xml->documentElement->childNodes as $node)
  40. {
  41. if (!$node instanceof \DOMElement)
  42. {
  43. continue;
  44. }
  45. switch ($node->tagName)
  46. {
  47. case 'route':
  48. $this->parseRoute($collection, $node, $path);
  49. break;
  50. case 'import':
  51. $this->parseImport($collection, $node, $path);
  52. break;
  53. default:
  54. throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
  55. }
  56. }
  57. return $collection;
  58. }
  59. protected function parseRoute(RouteCollection $collection, $definition, $file)
  60. {
  61. $defaults = array();
  62. $requirements = array();
  63. $options = array();
  64. foreach ($definition->childNodes as $node)
  65. {
  66. if (!$node instanceof \DOMElement)
  67. {
  68. continue;
  69. }
  70. switch ($node->tagName)
  71. {
  72. case 'default':
  73. $defaults[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  74. break;
  75. case 'option':
  76. $options[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  77. break;
  78. case 'requirement':
  79. $requirements[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue);
  80. break;
  81. default:
  82. throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
  83. }
  84. }
  85. $route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options);
  86. $collection->addRoute((string) $definition->getAttribute('id'), $route);
  87. }
  88. protected function parseImport(RouteCollection $collection, $node, $file)
  89. {
  90. $class = null;
  91. if ($node->hasAttribute('class') && $import->getAttribute('class') !== get_class($this))
  92. {
  93. $class = (string) $node->getAttribute('class');
  94. }
  95. else
  96. {
  97. // try to detect loader with the extension
  98. switch (pathinfo((string) $node->getAttribute('resource'), PATHINFO_EXTENSION))
  99. {
  100. case 'yml':
  101. $class = 'Symfony\\Components\\Routing\\Loader\\YamlFileLoader';
  102. break;
  103. }
  104. }
  105. $loader = null === $class ? $this : new $class($this->paths);
  106. $importedFile = $this->getAbsolutePath((string) $node->getAttribute('resource'), dirname($file));
  107. $collection->addCollection($loader->load($importedFile), (string) $node->getAttribute('prefix'));
  108. }
  109. /**
  110. * @throws \InvalidArgumentException When loading of XML file returns error
  111. */
  112. protected function loadFile($path)
  113. {
  114. $dom = new \DOMDocument();
  115. libxml_use_internal_errors(true);
  116. if (!$dom->load($path, LIBXML_COMPACT))
  117. {
  118. throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
  119. }
  120. $dom->validateOnParse = true;
  121. $dom->normalizeDocument();
  122. libxml_use_internal_errors(false);
  123. $this->validate($dom, $path);
  124. return $dom;
  125. }
  126. /**
  127. * @throws \InvalidArgumentException When xml doesn't validate its xsd schema
  128. */
  129. protected function validate($dom, $file)
  130. {
  131. libxml_use_internal_errors(true);
  132. if (!$dom->schemaValidate(__DIR__.'/schema/routing/routing-1.0.xsd'))
  133. {
  134. throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
  135. }
  136. libxml_use_internal_errors(false);
  137. }
  138. protected function getXmlErrors()
  139. {
  140. $errors = array();
  141. foreach (libxml_get_errors() as $error)
  142. {
  143. $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
  144. LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
  145. $error->code,
  146. trim($error->message),
  147. $error->file ? $error->file : 'n/a',
  148. $error->line,
  149. $error->column
  150. );
  151. }
  152. libxml_clear_errors();
  153. return $errors;
  154. }
  155. }