root/tags/RELEASE_0_2_1/XJConf/XmlParser.php

Revision 99, 10.8 kB (checked in by mikey, 2 years ago)

added methods hasConfigValue($name) and getConfigValues()

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 );
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         if (!defined('XMLREADER_ELEMENT')) {
85             $this->nodeTypes = array('startTag' => XMLReader::ELEMENT,
86                                      'text'     => XMLReader::TEXT,
87                                      'endTag'   => XMLReader::END_ELEMENT
88                                );
89         } else {
90             $this->nodeTypes = array('startTag' => XMLREADER_ELEMENT,
91                                      'text'     => XMLREADER_TEXT,
92                                      'endTag'   => XMLREADER_END_ELEMENT
93                                );
94         }
95     }
96
97     /**
98      * set the list of namespace defintions
99      *
100      * @param  NamespaceDefinitions  $tagDefs
101      */
102     public function setTagDefinitions(NamespaceDefinitions $tagDefs)
103     {
104         $this->tagDefs = $tagDefs;
105     }
106
107     /**
108      * add some more namespace definitions
109      *
110      * @param  NamespaceDefinitions  $tagDefs
111      */
112     public function addTagDefinitions(NamespaceDefinitions $tagDefs)
113     {
114         if (null == $this->tagDefs) {
115             $this->setTagDefinitions($tagDefs);
116             return;
117         }
118
119         $this->tagDefs->appendNamespaceDefinitions($tagDefs);
120     }
121
122     /**
123      * add an extension that handles all tags in given namespace
124      *
125      * @param  string     $namespace  handle all tags in this namespace with given extension
126      * @param  Extension  $ext        use this extension to handle all tags in given namespace
127      */
128     public function addExtension(Extension $ext, $namespace = null)
129     {
130         if ($namespace == null) {
131             $namespace = $ext->getNamespace();
132         }
133         $this->extensions[$namespace] = $ext;
134     }
135
136     /**
137      * parses a given file and creates the data structure described in this file
138      *
139      * @param  string  $filename
140      */
141     public function parse($filename)
142     {
143         $reader = $this->initParser();
144         array_push($this->openFiles, $filename);
145         $reader->open($filename);
146         while ($reader->read()) {
147             switch ($reader->nodeType) {
148                 case $this->nodeTypes['startTag']:
149                     $empty = $reader->isEmptyElement;
150                     $nameSpaceURI = $reader->namespaceURI;
151                     $elementName  = $reader->localName;
152                     $attributes   = array();
153                     if (TRUE == $reader->hasAttributes) {
154                         // go to first attribute
155                         $attribute = $reader->moveToFirstAttribute();
156                         // save data of all attributes
157                         while (TRUE == $attribute) {
158                             $attributes[$reader->localName] = $reader->value;
159                             $attribute = $reader->moveToNextAttribute();
160                         }
161                     }
162
163                     $this->startElement($nameSpaceURI, $elementName, $attributes);
164                     if (true === $empty) {
165                         $this->endElement($nameSpaceURI, $elementName);
166                     }
167                     break;
168
169                 case $this->nodeTypes['text']:
170                     $this->characters($reader->value);
171                     break;
172
173                 case $this->nodeTypes['endTag']:
174                     $this->endElement($reader->namespaceURI, $reader->localName);
175                     break;
176             }
177         }
178
179         $reader->close($filename);
180         array_pop($this->openFiles);
181
182     }
183     
184     /**
185      * checks whether a config value exists or not
186      *
187      * @return  bool
188      */
189     public function hasConfigValue($name)
190     {
191         return isset($this->config[$name]);
192     }
193
194     /**
195      * returns the data structure associated with this name
196      *
197      * @param   string  $name
198      * @return  mixed
199      */
200     public function getConfigValue($name)
201     {
202         if ($this->hasConfigValue($name) == true) {
203             return $this->config[$name];
204         }
205         
206         return null;
207     }
208     
209     /**
210      * returns all config values as array
211      *
212      * @return  array
213      */
214     public function getConfigValues()
215     {
216         return $this->config;
217     }
218
219     /**
220      * returns the name of the file that is currently parsed
221      *
222      * @return  string
223      */
224     public function getCurrentFile()
225     {
226         return end($this->openFiles);
227     }
228
229     /**
230      * initializes the parser
231      */
232     private function initParser()
233     {
234         $reader = new XMLReader();
235         return $reader;
236     }
237
238     /**
239      * handles the start element
240      *
241      * Creates a new Tag object and pushes it
242      * onto the stack.
243      *
244      * @param  string  $namespaceURI  namespace of start tag
245      * @param  string  $sName         name of start tag
246      * @param  array   $atts          attributes of tag
247      */
248     private function startElement($namespaceURI, $sName, $atts)
249     {
250         // do not handle stuff in our own namespace
251         if ($this->myNamespace == $namespaceURI && 0 < $this->depth) {
252             return;
253         }
254         $this->depth++;
255
256         // no namespace defined, use the default namespace
257         if (strlen($namespaceURI) == 0) {
258             $namespaceURI = '__default';
259         }
260
261         // ignore the root tag
262         if (1 == $this->depth) {
263             return;
264         }
265
266         // This tag needs to be handled by an extension
267         if (isset($this->extensions[$namespaceURI]) == true) {
268             $tag = new GenericTag($sName, $atts);
269             $this->extensions[$namespaceURI]->startElement($this, $tag);
270         // This tag has been defined internally
271         } else {
272             if ($this->tagDefs->isNamespaceDefined($namespaceURI) == false) {
273                 throw new UnknownNamespaceException('Unknown namespace ' . $namespaceURI . ' in file ' . end($this->openFiles));
274             }
275
276             $newDef = null;
277             $lastTag = end($this->tagStack);
278             if ($lastTag != null) {
279                 $lastDef = $lastTag->getDefinition();
280                 if ($lastDef != null) {
281                     $newDef = $lastDef->getChildDefinitionByTagName($sName);
282                 }
283             }
284             if ($newDef === null) {
285                 if ($this->tagDefs->isTagDefined($namespaceURI, $sName) == false) {
286                     throw new UnknownTagException('Unknown tag ' . $sName . ' in namespace ' . $namespaceURI);
287                 }
288                 $newDef = $this->tagDefs->getTagDefinition($namespaceURI, $sName);
289             }
290
291             $tag = new DefinedTag($sName, $atts);
292             // fetch the defintion for this tag
293             $tag->setDefinition($newDef);
294         }
295
296         array_push($this->tagStack, $tag);
297     }
298
299     /**
300      * handles the end element
301      *
302      * Fetches the current element from the stack and
303      * converts it to the correct type.
304      *
305      * @param  string  $namespaceURI  namespace of end tag
306      * @param  string  $sName         name of end tag
307      */
308     private function endElement($namespaceURI, $sName)
309     {
310         // do not handle stuff in our own namespace
311         if ($this->myNamespace == $namespaceURI && 0 < $this->depth) {
312             return;
313         }
314         $this->depth--;
315
316         // no namespace defined, use the default namespace
317         if (strlen($namespaceURI) == 0) {
318             $namespaceURI = '__default';
319         }
320
321         // ignore the root tag
322         if (0 == $this->depth) {
323             return;
324         }
325
326         // get the last tag from the stack
327         $tag = array_pop($this->tagStack);
328
329         // This tag needs to be handled by an extension
330         if (isset($this->extensions[$namespaceURI]) == true) {
331             $result = $this->extensions[$namespaceURI]->endElement($this, $tag);
332             if (null != $result) {
333                 if (1 == $this->depth) {
334                     $this->config[$tag->getKey()] = $result->getConvertedValue();
335                 } else {
336                     $parent = array_pop($this->tagStack);
337                     if ($result->getKey() == null && $parent->supportsIndexedChildren() == false) {
338                         $parent->setContent($result->getConvertedValue());
339                     } else {
340                         $parent->addChild($result);
341                     }
342                     array_push($this->tagStack, $parent);
343                 }
344             }
345         // last tag before returning to root
346         } elseif (1 == $this->depth) {
347             $this->config[$tag->getKey()] = $tag->getConvertedValue();
348         // add this tag to the tag before as child
349         } else {
350             $parent = array_pop($this->tagStack);
351             $parent->addChild($tag);
352             array_push($this->tagStack, $parent);
353         }
354     }
355
356     /**
357      * Character data handler
358      *
359      * Fetches the current tag from the stack and
360      * appends the data.
361      *
362      * @param  string  $buf
363      */
364     private function characters($buf)
365     {
366         if (count($this->tagStack) == 0) {
367             return;
368         }
369
370         $tag = end($this->tagStack);
371         $tag->addData($buf);
372     }
373 }
374 ?>
Note: See TracBrowser for help on using the browser.