root/branches/PRE_NAMESPACES/XJConf/XmlParser.php

Revision 139, 11.1 kB (checked in by mikey, 6 months ago)

bugfix: throw exception if opening of xml files fails

Line 
1 <?php
2 /**
3  * Parser that reads xml files and generates the data structure.
4  *
5  * @author  Stephan Schmidt <stephan.schmidt@schlund.de>
6  * @author  Frank Kleine <frank.kleine@schlund.de>
7  */
8 XJConfLoader::load('DefinedTag',
9                    'GenericTag',
10                    'definitions::NamespaceDefinition',
11                    'definitions::NamespaceDefinitions',
12                    'exceptions::UnknownNamespaceException',
13                    'exceptions::UnknownTagException',
14                    'exceptions::XJConfException'
15 );
16 /**
17  * Parser that reads xml files and generates the data structure.
18  *
19  * This parser reads xml files using the tag definitions and
20  * created the data structure and objects described by tag
21  * definitions and the xml file.
22  *
23  * @package  XJConf
24  */
25 class XmlParser
26 {
27     /**
28      * the list of tags that have to be processed
29      *
30      * @var  array<Tag>
31      */
32     private $tagStack    = array();
33     /**
34      * hashmap of generated data types
35      *
36      * @var  array<String, mixed>
37      */
38     private $config      = array();
39     /**
40      * a listof defined namespaces
41      *
42      * @var  NamespaceDefinitions
43      */
44     private $tagDefs;
45     /**
46      * current depth within the parsed document
47      *
48      * @var  int
49      */
50     private $depth       = 0;
51     /**
52      * list of extensions to use for the namespace
53      *
54      * @var  array<String, Extension>
55      */
56     private $extensions  = array();
57     /**
58      * the default namespace if none is set
59      *
60      * @var  string
61      */
62     private $myNamespace = 'http://xjconf.net/XJConf';
63     /**
64      * stack of currently opened files
65      *
66      * @var  array<String>
67      */
68     private $openFiles   = array();
69
70     /**
71      * list of node types, used for compatibility between PHP 5.0 and 5.1
72      *
73      * @var  array
74      */
75     private $nodeTypes   = array();
76
77     /**
78      * constructor
79      *
80      * Sets the node types depending on your PHP version using the constants
81      * defined by the XMLReader PHP extension.
82      */
83     public function __construct()
84     {
85         if (!defined('XMLREADER_ELEMENT')) {
86             $this->nodeTypes = array('startTag' => XMLReader::ELEMENT,
87                                      'text'     => XMLReader::TEXT,
88                                      'endTag'   => XMLReader::END_ELEMENT
89                                );
90         } else {
91             $this->nodeTypes = array('startTag' => XMLREADER_ELEMENT,
92                                      'text'     => XMLREADER_TEXT,
93                                      'endTag'   => XMLREADER_END_ELEMENT
94                                );
95         }
96     }
97
98     /**
99      * set the list of namespace defintions
100      *
101      * @param  NamespaceDefinitions  $tagDefs
102      */
103     public function setTagDefinitions(NamespaceDefinitions $tagDefs)
104     {
105         $this->tagDefs = $tagDefs;
106     }
107
108     /**
109      * add some more namespace definitions
110      *
111      * @param  NamespaceDefinitions  $tagDefs
112      */
113     public function addTagDefinitions(NamespaceDefinitions $tagDefs)
114     {
115         if (null == $this->tagDefs) {
116             $this->setTagDefinitions($tagDefs);
117             return;
118         }
119
120         $this->tagDefs->appendNamespaceDefinitions($tagDefs);
121     }
122
123     /**
124      * add an extension that handles all tags in given namespace
125      *
126      * @param  string     $namespace  handle all tags in this namespace with given extension
127      * @param  Extension  $ext        use this extension to handle all tags in given namespace
128      */
129     public function addExtension(Extension $ext, $namespace = null)
130     {
131         if ($namespace == null) {
132             $namespace = $ext->getNamespace();
133         }
134         $this->extensions[$namespace] = $ext;
135     }
136
137     /**
138      * parses a given file and creates the data structure described in this file
139      *
140      * @param   string  $filename
141      * @throws  XJConfException
142      */
143     public function parse($filename)
144     {
145         $reader = $this->initParser();
146         array_push($this->openFiles, $filename);
147         if (@$reader->open($filename) === false) {
148             throw new XJConfException('Can not open file ' . $filename);
149         }
150         
151         while ($reader->read()) {
152             switch ($reader->nodeType) {
153                 case $this->nodeTypes['startTag']:
154                     $empty = $reader->isEmptyElement;
155                     $nameSpaceURI = $reader->namespaceURI;
156                     $elementName  = $reader->localName;
157                     $attributes   = array();
158                     if (TRUE == $reader->hasAttributes) {
159                         // go to first attribute
160                         $attribute = $reader->moveToFirstAttribute();
161                         // save data of all attributes
162                         while (TRUE == $attribute) {
163                             $attributes[$reader->localName] = $reader->value;
164                             $attribute = $reader->moveToNextAttribute();
165                         }
166                     }
167
168                     $this->startElement($nameSpaceURI, $elementName, $attributes);
169                     if (true === $empty) {
170                         $this->endElement($nameSpaceURI, $elementName);
171                     }
172                     break;
173
174                 case $this->nodeTypes['text']:
175                     $this->characters($reader->value);
176                     break;
177
178                 case $this->nodeTypes['endTag']:
179                     $this->endElement($reader->namespaceURI, $reader->localName);
180                     break;
181             }
182         }
183
184         $reader->close($filename);
185         array_pop($this->openFiles);
186
187     }
188     
189     /**
190      * checks whether a config value exists or not
191      *
192      * @return  bool
193      */
194     public function hasConfigValue($name)
195     {
196         return isset($this->config[$name]);
197     }
198
199     /**
200      * returns the data structure associated with this name
201      *
202      * @param   string  $name
203      * @return  mixed
204      */
205     public function getConfigValue($name)
206     {
207         if ($this->hasConfigValue($name) == true) {
208             return $this->config[$name];
209         }
210         
211         return null;
212     }
213     
214     /**
215      * returns all config values as array
216      *
217      * @return  array
218      */
219     public function getConfigValues()
220     {
221         return $this->config;
222     }
223
224     /**
225      * clears parsed config values
226      */
227     public function clearConfigValues()
228     {
229         $this->config = array();
230     }
231
232     /**
233      * returns the name of the file that is currently parsed
234      *
235      * @return  string
236      */
237     public function getCurrentFile()
238     {
239         return end($this->openFiles);
240     }
241
242     /**
243      * initializes the parser
244      */
245     private function initParser()
246     {
247         $reader = new XMLReader();
248         return $reader;
249     }
250
251     /**
252      * handles the start element
253      *
254      * Creates a new Tag object and pushes it
255      * onto the stack.
256      *
257      * @param  string  $namespaceURI  namespace of start tag
258      * @param  string  $sName         name of start tag
259      * @param  array   $atts          attributes of tag
260      */
261     private function startElement($namespaceURI, $sName, $atts)
262     {
263         // do not handle stuff in our own namespace
264         if ($this->myNamespace == $namespaceURI && 0 < $this->depth) {
265             return;
266         }
267         $this->depth++;
268
269         // no namespace defined, use the default namespace
270         if (strlen($namespaceURI) == 0) {
271             $namespaceURI = '__default';
272         }
273
274         // ignore the root tag
275         if (1 == $this->depth) {
276             return;
277         }
278
279         // This tag needs to be handled by an extension
280         if (isset($this->extensions[$namespaceURI]) == true) {
281             $tag = new GenericTag($sName, $atts);
282             $this->extensions[$namespaceURI]->startElement($this, $tag);
283         // This tag has been defined internally
284         } else {
285             if ($this->tagDefs->isNamespaceDefined($namespaceURI) == false) {
286                 throw new UnknownNamespaceException('Unknown namespace ' . $namespaceURI . ' in file ' . end($this->openFiles));
287             }
288
289             $newDef = null;
290             $lastTag = end($this->tagStack);
291             if ($lastTag != null) {
292                 $lastDef = $lastTag->getDefinition();
293                 if ($lastDef != null) {
294                     $newDef = $lastDef->getChildDefinitionByTagName($sName);
295                 }
296             }
297             if ($newDef === null) {
298                 if ($this->tagDefs->isTagDefined($namespaceURI, $sName) == false) {
299                     throw new UnknownTagException('Unknown tag ' . $sName . ' in namespace ' . $namespaceURI);
300                 }
301                 $newDef = $this->tagDefs->getTagDefinition($namespaceURI, $sName);
302             }
303
304             $tag = new DefinedTag($sName, $atts);
305             // fetch the defintion for this tag
306             $tag->setDefinition($newDef);
307         }
308
309         array_push($this->tagStack, $tag);
310     }
311
312     /**
313      * handles the end element
314      *
315      * Fetches the current element from the stack and
316      * converts it to the correct type.
317      *
318      * @param  string  $namespaceURI  namespace of end tag
319      * @param  string  $sName         name of end tag
320      */
321     private function endElement($namespaceURI, $sName)
322     {
323         // do not handle stuff in our own namespace
324         if ($this->myNamespace == $namespaceURI && 0 < $this->depth) {
325             return;
326         }
327         $this->depth--;
328
329         // no namespace defined, use the default namespace
330         if (strlen($namespaceURI) == 0) {
331             $namespaceURI = '__default';
332         }
333
334         // ignore the root tag
335         if (0 == $this->depth) {
336             return;
337         }
338
339         // get the last tag from the stack
340         $tag = array_pop($this->tagStack);
341
342         // This tag needs to be handled by an extension
343         if (isset($this->extensions[$namespaceURI]) == true) {
344             $result = $this->extensions[$namespaceURI]->endElement($this, $tag);
345             if (null != $result) {
346                 if (1 == $this->depth) {
347                     $this->config[$tag->getKey()] = $result->getConvertedValue();
348                 } else {
349                     $parent = array_pop($this->tagStack);
350                     if ($result->getKey() == null && $parent->supportsIndexedChildren() == false) {
351                         $parent->setContent($result->getConvertedValue());
352                     } else {
353                         $parent->addChild($result);
354                     }
355                     array_push($this->tagStack, $parent);
356                 }
357             }
358         // last tag before returning to root
359         } elseif (1 == $this->depth) {
360             $this->config[$tag->getKey()] = $tag->getConvertedValue();
361         // add this tag to the tag before as child
362         } else {
363             $parent = array_pop($this->tagStack);
364             $parent->addChild($tag);
365             array_push($this->tagStack, $parent);
366         }
367     }
368
369     /**
370      * Character data handler
371      *
372      * Fetches the current tag from the stack and
373      * appends the data.
374      *
375      * @param  string  $buf
376      */
377     private function characters($buf)
378     {
379         if (count($this->tagStack) == 0) {
380             return;
381         }
382
383         $tag = end($this->tagStack);
384         $tag->addData($buf);
385     }
386 }
387 ?>
Note: See TracBrowser for help on using the browser.