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