root/tags/RELEASE_0_2_0/XJConf/DefinitionParser.php

Revision 98, 8.5 kB (checked in by mikey, 2 years ago)

added possibility to have a default classloader which applies for every namespace if there is no specialized class loader for this namespace

Line 
1 <?php
2 /**
3  * Parse tag definitions files.
4  *
5  * @author  Stephan Schmidt <stephan.schmidt@schlund.de>
6  * @author  Frank Kleine <frank.kleine@schlund.de>
7  */
8 XJConfLoader::load('definitions.AttributeDefinition',
9                    'definitions.CDataDefinition',
10                    'definitions.ChildDefinition',
11                    'definitions.ConstructorDefinition',
12                    'definitions.FactoryMethodDefinition',
13                    'definitions.MethodCallTagDefinition',
14                    'definitions.NamespaceDefinition',
15                    'definitions.NamespaceDefinitions',
16                    'definitions.TagDefinition',
17                    'definitions.handler.DefinitionHandlerFactory',
18                    'exceptions.InvalidNamespaceDefinitionException'
19 );
20 /**
21  * Parse tag definitions files.
22  *
23  * This parser reads xml files that define the tags used by other
24  * xml documents which describe a data structure.
25  *
26  * @package  XJConf
27  */
28 class DefinitionParser
29 {
30     /**
31      * this tag defines a namespace
32      */
33     const TAG_NAMESPACE      = 'namespace';
34     /**
35      * stack for currently open definitions
36      *
37      * @var  array<Definition>
38      */
39     private $defStack        = array();
40     /**
41      * stack for currently opened definition handlers
42      *
43      * @var  DefinitionHandler
44      */
45     private $defHandlerStack = array();
46     /**
47      * Constant for the default namespace
48      */
49     const DEFAULT_NAMESPACE  = '__default';
50     /**
51      * The current namespace
52      *
53      * @var  string
54      */
55     private $currentNamespace;
56     /**
57      * All extracted namespace definitions
58      *
59      * @var  NamespaceDefinitions
60      */
61     private $defs;
62     /**
63      * the real xml parser
64      *
65      * @var  XMLReader
66      */
67     private $reader;
68     /**
69      * list of node types, used for compatibility between PHP 5.0 and 5.1
70      *
71      * @var  array
72      */
73     private $nodeTypes       = array();
74     /**
75      * hashmap of class loaders where the key is the namespace the class loader
76      *  should be used for
77      *
78      * @var  array<String,XJConfClassLoader>
79      */
80     private $classLoaders    = array();
81
82     /**
83      * constructor
84      *
85      * Sets the node types depending on your PHP version using the constants
86      * defined by the XMLReader PHP extension.
87      *
88      * @param  array<String,XJConfClassLoader>  $classLoaders  optional
89      */
90     public function __construct($classLoaders = array())
91     {
92         $this->defs             = new NamespaceDefinitions();
93         $this->currentNamespace = self::DEFAULT_NAMESPACE;
94         $this->classLoaders     = $classLoaders;
95
96         if (!defined('XMLREADER_ELEMENT')) {
97             $this->nodeTypes = array('startTag' => XMLReader::ELEMENT,
98                                      'text'     => XMLReader::TEXT,
99                                      'endTag'   => XMLReader::END_ELEMENT
100                                );
101         } else {
102             $this->nodeTypes = array('startTag' => XMLREADER_ELEMENT,
103                                      'text'     => XMLREADER_TEXT,
104                                      'endTag'   => XMLREADER_END_ELEMENT
105                                );
106         }
107     }
108
109     /**
110      * returns the current namespace
111      *
112      * @return  string
113      */
114     public function getCurrentNamespace()
115     {
116         return $this->currentNamespace;
117     }
118
119     /**
120      * returns the list of created namespace definitions
121      *
122      * @return  NamespaceDefinitions
123      */
124     public function getNamespaceDefinitions()
125     {
126         return $this->defs;
127     }
128
129     /**
130      * check whether a class loader exists for given namespace
131      *
132      * @param   string  $namespace
133      * @return  bool
134      */
135     public function hasClassLoader($namespace)
136     {
137         return (isset($this->classLoaders[$namespace]) == true || isset($this->classLoaders['__default']) == true);
138     }
139
140     /**
141      * return the class loader for the given namespace
142      *
143      * @param   string             $namespace
144      * @return  XJConfClassLoader
145      */
146     public function getClassLoader($namespace)
147     {
148         if (isset($this->classLoaders[$namespace]) == true) {
149             return $this->classLoaders[$namespace];
150         }
151         
152         if (isset($this->classLoaders['__default']) == true) {
153             return $this->classLoaders['__default'];
154         }
155
156         return null;
157     }
158
159     /**
160      * returns the definition stack
161      *
162      * @return  array<Definition>
163      */
164     public function &getDefStack()
165     {
166         return $this->defStack;
167     }
168
169     /**
170      * initializes the parser
171      */
172     private function initParser()
173     {
174         if (null == $this->reader) {
175             $this->reader = new XMLReader();
176         }
177     }
178
179     /**
180      * parse a tag definitions file and return
181      * an instance of NamespaceDefinition
182      *
183      * @param   string               $filename  filename of the defintions file
184      * @return  NamespaceDefinition
185      * @throws  InvalidNamespaceDefinitionException
186      */
187     public function parse($filename)
188     {
189         $this->initParser();
190         $this->reader->open($filename);
191         while ($this->reader->read()) {
192             switch ($this->reader->nodeType) {
193                 case $this->nodeTypes['startTag']:
194                     $nameSpaceURI = $this->reader->namespaceURI;
195                     $elementName  = $this->reader->localName;
196                     $attributes   = array();
197                     $empty = $this->reader->isEmptyElement;
198                     if (TRUE == $this->reader->hasAttributes) {
199                         // go to first attribute
200                         $attribute = $this->reader->moveToFirstAttribute();
201                         // save data of all attributes
202                         while (TRUE == $attribute) {
203                             $attributes[$this->reader->localName] = $this->reader->value;
204                             $attribute = $this->reader->moveToNextAttribute();
205                         }
206                     }
207                     $this->startElement($nameSpaceURI, $elementName, $attributes);
208                     if (true === $empty) {
209                         $this->endElement($nameSpaceURI, $elementName);
210                     }
211                     break;
212
213                 case $this->nodeTypes['text']:
214                     $this->characters($this->reader->value);
215                     break;
216
217                 case $this->nodeTypes['endTag']:
218                     $this->endElement($this->reader->namespaceURI, $this->reader->localName);
219                     break;
220             }
221         }
222
223         $this->reader->close($filename);
224
225         return $this->defs;
226     }
227
228     /**
229      * Start Element handler
230      *
231      * Creates the Definition object and places it on
232      * the stack.
233      *
234      * @param   string  $namespaceURI  namespace of start tag
235      * @param   string  $sName         name of start tag
236      * @param   array   $atts          attributes of tag
237      * @throws  InvalidNamespaceDefinitionException
238      */
239     private function startElement($namespaceURI, $sName, $atts)
240     {
241         // a new namespace
242         if (self::TAG_NAMESPACE  == $sName) {
243             if (isset($atts['uri']) == false) {
244                 throw new InvalidNamespaceDefinitionException('The <' . self::TAG_NAMESPACE . '> tag is missing the uri attribute.');
245             }
246
247             // change current namespace to new namespace
248             $this->currentNamespace = $atts['uri'];
249             return;
250         }
251
252         // create the appropriate definition handler and use this
253         // to create the required definition
254         $defHandler = DefinitionHandlerFactory::create($sName, $this);
255         $def        = $defHandler->startElement($namespaceURI, $sName, $atts);
256         if (null != $def) {
257             array_push($this->defStack, $def);
258         }
259
260         array_push($this->defHandlerStack, $defHandler);
261     }
262
263     /**
264      * End Element handler
265      *
266      * Fetches the Definition from the stack and
267      * adds it to the NamespaceDefinition object.
268      *
269      * @param   string  $namespaceURI  namespace of end tag
270      * @param   string  $sName         name of end tag
271      */
272     private function endElement($namespaceURI, $sName)
273     {
274         // namespace definition ends, switch back to default namespace
275         if (self::TAG_NAMESPACE  == $sName) {
276             $this->currentNamespace = self::DEFAULT_NAMESPACE;
277             return;
278         }
279
280         // use definition handler to finalize the definition of the current tag
281         $defHandler = array_pop($this->defHandlerStack);
282         $defHandler->endElement($namespaceURI, $sName);
283     }
284 }
285 ?>
Note: See TracBrowser for help on using the browser.