root/trunk/XJConf/XmlParser.php

Revision 141, 10.9 kB (checked in by mikey, 5 months ago)

merge changes from PRE_NAMESPACES branch

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