1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * This file is part of the PEAR Console_CommandLine package.
7 *
8 * PHP version 5
9 *
10 * LICENSE: This source file is subject to the MIT license that is available
11 * through the world-wide-web at the following URI:
12 * http://opensource.org/licenses/mit-license.php
13 *
14 * @category  Console
15 * @package   Console_CommandLine
16 * @author    David JEAN LOUIS <izimobil@gmail.com>
17 * @copyright 2007 David JEAN LOUIS
18 * @license   http://opensource.org/licenses/mit-license.php MIT License
19 * @version   CVS: $Id$
20 * @link      http://pear.php.net/package/Console_CommandLine
21 * @since     File available since release 0.1.0
22 * @filesource
23 */
24
25/**
26 * Required file
27 */
28require_once 'Console/CommandLine.php';
29
30/**
31 * Parser for command line xml definitions.
32 *
33 * @category  Console
34 * @package   Console_CommandLine
35 * @author    David JEAN LOUIS <izimobil@gmail.com>
36 * @copyright 2007 David JEAN LOUIS
37 * @license   http://opensource.org/licenses/mit-license.php MIT License
38 * @version   Release: @package_version@
39 * @link      http://pear.php.net/package/Console_CommandLine
40 * @since     Class available since release 0.1.0
41 */
42class Console_CommandLine_XmlParser
43{
44    // parse() {{{
45
46    /**
47     * Parses the given xml definition file and returns a
48     * Console_CommandLine instance constructed with the xml data.
49     *
50     * @param string $xmlfile The xml file to parse
51     *
52     * @return Console_CommandLine A parser instance
53     */
54    public static function parse($xmlfile)
55    {
56        if (!is_readable($xmlfile)) {
57            Console_CommandLine::triggerError('invalid_xml_file',
58                E_USER_ERROR, array('{$file}' => $xmlfile));
59        }
60        $doc = new DomDocument();
61        $doc->load($xmlfile);
62        self::validate($doc);
63        $nodes = $doc->getElementsByTagName('command');
64        $root  = $nodes->item(0);
65        return self::_parseCommandNode($root, true);
66    }
67
68    // }}}
69    // parseString() {{{
70
71    /**
72     * Parses the given xml definition string and returns a
73     * Console_CommandLine instance constructed with the xml data.
74     *
75     * @param string $xmlstr The xml string to parse
76     *
77     * @return Console_CommandLine A parser instance
78     */
79    public static function parseString($xmlstr)
80    {
81        $doc = new DomDocument();
82        $doc->loadXml($xmlstr);
83        self::validate($doc);
84        $nodes = $doc->getElementsByTagName('command');
85        $root  = $nodes->item(0);
86        return self::_parseCommandNode($root, true);
87    }
88
89    // }}}
90    // validate() {{{
91
92    /**
93     * Validates the xml definition using Relax NG.
94     *
95     * @param DomDocument $doc The document to validate
96     *
97     * @return boolean Whether the xml data is valid or not.
98     * @throws Console_CommandLine_Exception
99     * @todo use exceptions
100     */
101    public static function validate($doc)
102    {
103        $pkgRoot  = __DIR__ . '/../../';
104        $paths = array(
105            // PEAR/Composer
106            '@data_dir@/Console_CommandLine/data/xmlschema.rng',
107            // Composer
108            $pkgRoot . 'data/Console_CommandLine/data/xmlschema.rng',
109            $pkgRoot . 'data/console_commandline/data/xmlschema.rng',
110            // Git
111            $pkgRoot . 'data/xmlschema.rng',
112            'xmlschema.rng',
113        );
114
115        foreach ($paths as $path) {
116            if (is_readable($path)) {
117                return $doc->relaxNGValidate($path);
118            }
119        }
120        Console_CommandLine::triggerError(
121            'invalid_xml_file',
122            E_USER_ERROR, array('{$file}' => $rngfile));
123    }
124
125    // }}}
126    // _parseCommandNode() {{{
127
128    /**
129     * Parses the root command node or a command node and returns the
130     * constructed Console_CommandLine or Console_CommandLine_Command instance.
131     *
132     * @param DomDocumentNode $node       The node to parse
133     * @param bool            $isRootNode Whether it is a root node or not
134     *
135     * @return mixed Console_CommandLine or Console_CommandLine_Command
136     */
137    private static function _parseCommandNode($node, $isRootNode = false)
138    {
139        if ($isRootNode) {
140            $obj = new Console_CommandLine();
141        } else {
142            include_once 'Console/CommandLine/Command.php';
143            $obj = new Console_CommandLine_Command();
144        }
145        foreach ($node->childNodes as $cNode) {
146            $cNodeName = $cNode->nodeName;
147            switch ($cNodeName) {
148            case 'name':
149            case 'description':
150            case 'version':
151                $obj->$cNodeName = trim($cNode->nodeValue);
152                break;
153            case 'add_help_option':
154            case 'add_version_option':
155            case 'force_posix':
156                $obj->$cNodeName = self::_bool(trim($cNode->nodeValue));
157                break;
158            case 'option':
159                $obj->addOption(self::_parseOptionNode($cNode));
160                break;
161            case 'argument':
162                $obj->addArgument(self::_parseArgumentNode($cNode));
163                break;
164            case 'command':
165                $obj->addCommand(self::_parseCommandNode($cNode));
166                break;
167            case 'aliases':
168                if (!$isRootNode) {
169                    foreach ($cNode->childNodes as $subChildNode) {
170                        if ($subChildNode->nodeName == 'alias') {
171                            $obj->aliases[] = trim($subChildNode->nodeValue);
172                        }
173                    }
174                }
175                break;
176            case 'messages':
177                $obj->messages = self::_messages($cNode);
178                break;
179            default:
180                break;
181            }
182        }
183        return $obj;
184    }
185
186    // }}}
187    // _parseOptionNode() {{{
188
189    /**
190     * Parses an option node and returns the constructed
191     * Console_CommandLine_Option instance.
192     *
193     * @param DomDocumentNode $node The node to parse
194     *
195     * @return Console_CommandLine_Option The built option
196     */
197    private static function _parseOptionNode($node)
198    {
199        include_once 'Console/CommandLine/Option.php';
200        $obj = new Console_CommandLine_Option($node->getAttribute('name'));
201        foreach ($node->childNodes as $cNode) {
202            $cNodeName = $cNode->nodeName;
203            switch ($cNodeName) {
204            case 'choices':
205                foreach ($cNode->childNodes as $subChildNode) {
206                    if ($subChildNode->nodeName == 'choice') {
207                        $obj->choices[] = trim($subChildNode->nodeValue);
208                    }
209                }
210                break;
211            case 'messages':
212                $obj->messages = self::_messages($cNode);
213                break;
214            default:
215                if (property_exists($obj, $cNodeName)) {
216                    $obj->$cNodeName = trim($cNode->nodeValue);
217                }
218                break;
219            }
220        }
221        if ($obj->action == 'Password') {
222            $obj->argument_optional = true;
223        }
224        return $obj;
225    }
226
227    // }}}
228    // _parseArgumentNode() {{{
229
230    /**
231     * Parses an argument node and returns the constructed
232     * Console_CommandLine_Argument instance.
233     *
234     * @param DomDocumentNode $node The node to parse
235     *
236     * @return Console_CommandLine_Argument The built argument
237     */
238    private static function _parseArgumentNode($node)
239    {
240        include_once 'Console/CommandLine/Argument.php';
241        $obj = new Console_CommandLine_Argument($node->getAttribute('name'));
242        foreach ($node->childNodes as $cNode) {
243            $cNodeName = $cNode->nodeName;
244            switch ($cNodeName) {
245            case 'description':
246            case 'help_name':
247            case 'default':
248                $obj->$cNodeName = trim($cNode->nodeValue);
249                break;
250            case 'multiple':
251                $obj->multiple = self::_bool(trim($cNode->nodeValue));
252                break;
253            case 'optional':
254                $obj->optional = self::_bool(trim($cNode->nodeValue));
255                break;
256            case 'choices':
257                foreach ($cNode->childNodes as $subChildNode) {
258                    if ($subChildNode->nodeName == 'choice') {
259                        $obj->choices[] = trim($subChildNode->nodeValue);
260                    }
261                }
262                break;
263            case 'messages':
264                $obj->messages = self::_messages($cNode);
265                break;
266            default:
267                break;
268            }
269        }
270        return $obj;
271    }
272
273    // }}}
274    // _bool() {{{
275
276    /**
277     * Returns a boolean according to true/false possible strings.
278     *
279     * @param string $str The string to process
280     *
281     * @return boolean
282     */
283    private static function _bool($str)
284    {
285        return in_array(strtolower((string)$str), array('true', '1', 'on', 'yes'));
286    }
287
288    // }}}
289    // _messages() {{{
290
291    /**
292     * Returns an array of custom messages for the element
293     *
294     * @param DOMNode $node The messages node to process
295     *
296     * @return array an array of messages
297     *
298     * @see Console_CommandLine::$messages
299     * @see Console_CommandLine_Element::$messages
300     */
301    private static function _messages(DOMNode $node)
302    {
303        $messages = array();
304
305        foreach ($node->childNodes as $cNode) {
306            if ($cNode->nodeType == XML_ELEMENT_NODE) {
307                $name  = $cNode->getAttribute('name');
308                $value = trim($cNode->nodeValue);
309
310                $messages[$name] = $value;
311            }
312        }
313
314        return $messages;
315    }
316
317    // }}}
318}
319