root/trunk/XJConf/DefinitionParser.php

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

merge changes from PRE_NAMESPACES branch

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