source: branches/version-2_12-dev/data/module/SOAP/Parser.php @ 21461

Revision 21461, 18.7 KB checked in by Seasoft, 12 years ago (diff)

#1522 (PEAR::SOAP をバージョンアップ)

  • 0.12.0 -> 0.13.0
  • SOAP_WSDL により、「Yahoo!デベロッパーネットワーク」の「オークション SOAP」を取得できることを確認した。
  • Property svn:eol-style set to LF
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
Line 
1<?php
2/**
3 * This file contains the code for the SOAP message parser.
4 *
5 * PHP versions 4 and 5
6 *
7 * LICENSE: This source file is subject to version 2.02 of the PHP license,
8 * that is bundled with this package in the file LICENSE, and is available at
9 * through the world-wide-web at http://www.php.net/license/2_02.txt.  If you
10 * did not receive a copy of the PHP license and are unable to obtain it
11 * through the world-wide-web, please send a note to license@php.net so we can
12 * mail you a copy immediately.
13 *
14 * @category   Web Services
15 * @package    SOAP
16 * @author     Dietrich Ayala <dietrich@ganx4.com> Original Author
17 * @author     Shane Caraveo <Shane@Caraveo.com>   Port to PEAR and more
18 * @author     Chuck Hagenbuch <chuck@horde.org>   Maintenance
19 * @author     Jan Schneider <jan@horde.org>       Maintenance
20 * @copyright  2003-2005 The PHP Group
21 * @license    http://www.php.net/license/2_02.txt  PHP License 2.02
22 * @link       http://pear.php.net/package/SOAP
23 */
24
25require_once 'SOAP/Base.php';
26require_once 'SOAP/Value.php';
27
28/**
29 * SOAP Parser
30 *
31 * This class is used by SOAP::Message and SOAP::Server to parse soap
32 * packets. Originally based on SOAPx4 by Dietrich Ayala
33 * http://dietrich.ganx4.com/soapx4
34 *
35 * @access public
36 * @package SOAP
37 * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
38 * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
39 */
40class SOAP_Parser extends SOAP_Base
41{
42    var $status = '';
43    var $position = 0;
44    var $depth = 0;
45    var $default_namespace = '';
46    var $message = array();
47    var $depth_array = array();
48    var $parent = 0;
49    var $root_struct_name = array();
50    var $header_struct_name = array();
51    var $curent_root_struct_name = '';
52    var $root_struct = array();
53    var $header_struct = array();
54    var $curent_root_struct = 0;
55    var $references = array();
56    var $need_references = array();
57
58    /**
59     * Used to handle non-root elements before root body element.
60     *
61     * @var integer
62     */
63    var $bodyDepth;
64
65    /**
66     * Constructor.
67     *
68     * @param string $xml         XML content.
69     * @param string $encoding    Character set encoding, defaults to 'UTF-8'.
70     * @param array $attachments  List of attachments.
71     */
72    function SOAP_Parser($xml, $encoding = SOAP_DEFAULT_ENCODING,
73                         $attachments = null)
74    {
75        parent::SOAP_Base('Parser');
76        $this->_setSchemaVersion(SOAP_XML_SCHEMA_VERSION);
77
78        $this->attachments = $attachments;
79
80        // Check the XML tag for encoding.
81        if (preg_match('/<\?xml[^>]+encoding\s*?=\s*?(\'([^\']*)\'|"([^"]*)")[^>]*?[\?]>/', $xml, $m)) {
82            $encoding = strtoupper($m[2] ? $m[2] : $m[3]);
83        }
84
85        // Determine where in the message we are (envelope, header, body,
86        // method). Check whether content has been read.
87        if (!empty($xml)) {
88            // Prepare the XML parser.
89            $parser = xml_parser_create($encoding);
90            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
91            xml_set_object($parser, $this);
92            xml_set_element_handler($parser, '_startElement', '_endElement');
93            xml_set_character_data_handler($parser, '_characterData');
94
95            // Some lame SOAP implementations add nul bytes at the end of the
96            // SOAP stream, and expat chokes on that.
97            if ($xml[strlen($xml) - 1] == 0) {
98                $xml = trim($xml);
99            }
100
101            // Parse the XML file.
102            if (!xml_parse($parser, $xml, true)) {
103                $err = sprintf('XML error on line %d col %d byte %d %s',
104                               xml_get_current_line_number($parser),
105                               xml_get_current_column_number($parser),
106                               xml_get_current_byte_index($parser),
107                               xml_error_string(xml_get_error_code($parser)));
108                $this->_raiseSoapFault($err, htmlspecialchars($xml));
109            }
110            xml_parser_free($parser);
111        }
112    }
113
114    /**
115     * Returns an array of responses.
116     *
117     * After parsing a SOAP message, use this to get the response.
118     *
119     * @return array
120     */
121    function getResponse()
122    {
123        if (!empty($this->root_struct[0])) {
124            return $this->_buildResponse($this->root_struct[0]);
125        } else {
126            return $this->_raiseSoapFault('Cannot build response');
127        }
128    }
129
130    /**
131     * Returns an array of header responses.
132     *
133     * After parsing a SOAP message, use this to get the response.
134     *
135     * @return array
136     */
137    function getHeaders()
138    {
139        if (!empty($this->header_struct[0])) {
140            return $this->_buildResponse($this->header_struct[0]);
141        } else {
142            // We don't fault if there are no headers; that can be handled by
143            // the application if necessary.
144            return null;
145        }
146    }
147
148    /**
149     * Recurses to build a multi dimensional array.
150     *
151     * @see _buildResponse()
152     */
153    function _domulti($d, &$ar, &$r, &$v, $ad = 0)
154    {
155        if ($d) {
156            $this->_domulti($d - 1, $ar, $r[$ar[$ad]], $v, $ad + 1);
157        } else {
158            $r = $v;
159        }
160    }
161
162    /**
163     * Loops through the message, building response structures.
164     *
165     * @param integer $pos  Position.
166     *
167     * @return SOAP_Value
168     */
169    function _buildResponse($pos)
170    {
171        $response = null;
172
173        if (isset($this->message[$pos]['children'])) {
174            $children = explode('|', $this->message[$pos]['children']);
175            foreach ($children as $c => $child_pos) {
176                if ($this->message[$child_pos]['type'] != null) {
177                    $response[] = $this->_buildResponse($child_pos);
178                }
179            }
180            if (isset($this->message[$pos]['arraySize'])) {
181                $ardepth = count($this->message[$pos]['arraySize']);
182                if ($ardepth > 1) {
183                    $ar = array_pad(array(), $ardepth, 0);
184                    if (isset($this->message[$pos]['arrayOffset'])) {
185                        for ($i = 0; $i < $ardepth; $i++) {
186                            $ar[$i] += $this->message[$pos]['arrayOffset'][$i];
187                        }
188                    }
189                    $elc = count($response);
190                    for ($i = 0; $i < $elc; $i++) {
191                        // Recurse to build a multi dimensional array.
192                        $this->_domulti($ardepth, $ar, $newresp, $response[$i]);
193
194                        // Increment our array pointers.
195                        $ad = $ardepth - 1;
196                        $ar[$ad]++;
197                        while ($ad > 0 &&
198                               $ar[$ad] >= $this->message[$pos]['arraySize'][$ad]) {
199                            $ar[$ad] = 0;
200                            $ad--;
201                            $ar[$ad]++;
202                        }
203                    }
204                    $response = $newresp;
205                } elseif (isset($this->message[$pos]['arrayOffset']) &&
206                          $this->message[$pos]['arrayOffset'][0] > 0) {
207                    // Check for padding.
208                    $pad = $this->message[$pos]['arrayOffset'][0] + count($response) * -1;
209                    $response = array_pad($response, $pad, null);
210                }
211            }
212        }
213
214        // Build attributes.
215        $attrs = array();
216        foreach ($this->message[$pos]['attrs'] as $atn => $atv) {
217            if (!strstr($atn, 'xmlns') && !strpos($atn, ':')) {
218                $attrs[$atn] = $atv;
219            }
220        }
221
222        // Add current node's value.
223        $nqn = new QName($this->message[$pos]['name'],
224                         $this->message[$pos]['namespace']);
225        $tqn = new QName($this->message[$pos]['type'],
226                         $this->message[$pos]['type_namespace']);
227        if ($response) {
228            $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), $response,
229                                       $attrs);
230            if (isset($this->message[$pos]['arrayType'])) {
231                $response->arrayType = $this->message[$pos]['arrayType'];
232            }
233        } else {
234            // Check if value is an empty array
235            if ($tqn->name == 'Array') {
236                $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(), array(),
237                                           $attrs);
238                //if ($pos == 4) var_dump($this->message[$pos], $response);
239            } else {
240                $response = new SOAP_Value($nqn->fqn(), $tqn->fqn(),
241                                           $this->message[$pos]['cdata'],
242                                           $attrs);
243            }
244        }
245
246        // Handle header attribute that we need.
247        if (array_key_exists('actor', $this->message[$pos])) {
248            $response->actor = $this->message[$pos]['actor'];
249        }
250        if (array_key_exists('mustUnderstand', $this->message[$pos])) {
251            $response->mustunderstand = $this->message[$pos]['mustUnderstand'];
252        }
253
254        return $response;
255    }
256
257    /**
258     * Start element handler used with the XML parser.
259     */
260    function _startElement($parser, $name, $attrs)
261    {
262        // Position in a total number of elements, starting from 0.
263        // Update class level position.
264        $pos = $this->position++;
265
266        // And set mine.
267        $this->message[$pos] = array(
268            'type' => '',
269            'type_namespace' => '',
270            'cdata' => '',
271            'pos' => $pos,
272            'id' => '');
273
274        // Parent/child/depth determinations.
275
276        // depth = How many levels removed from root?
277        // Set mine as current global depth and increment global depth value.
278        $this->message[$pos]['depth'] = $this->depth++;
279
280        // Else add self as child to whoever the current parent is.
281        if ($pos != 0) {
282            if (isset($this->message[$this->parent]['children'])) {
283                $this->message[$this->parent]['children'] .= '|' . $pos;
284            } else {
285                $this->message[$this->parent]['children'] = $pos;
286            }
287        }
288
289        // Set my parent.
290        $this->message[$pos]['parent'] = $this->parent;
291
292        // Set self as current value for this depth.
293        $this->depth_array[$this->depth] = $pos;
294        // Set self as current parent.
295        $this->parent = $pos;
296        $qname = new QName($name);
297        // Set status.
298        if (strcasecmp('envelope', $qname->name) == 0) {
299            $this->status = 'envelope';
300        } elseif (strcasecmp('header', $qname->name) == 0) {
301            $this->status = 'header';
302            $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name;
303            $this->header_struct[] = $this->curent_root_struct = $pos;
304            $this->message[$pos]['type'] = 'Struct';
305        } elseif (strcasecmp('body', $qname->name) == 0) {
306            $this->status = 'body';
307            $this->bodyDepth = $this->depth;
308
309        // Set method
310        } elseif ($this->status == 'body') {
311            // Is this element allowed to be a root?
312            // TODO: this needs to be optimized, we loop through $attrs twice
313            // now.
314            $can_root = $this->depth == $this->bodyDepth + 1;
315            if ($can_root) {
316                foreach ($attrs as $key => $value) {
317                    if (stristr($key, ':root') && !$value) {
318                        $can_root = false;
319                    }
320                }
321            }
322
323            if ($can_root) {
324                $this->status = 'method';
325                $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name;
326                $this->root_struct[] = $this->curent_root_struct = $pos;
327                $this->message[$pos]['type'] = 'Struct';
328            }
329        }
330
331        // Set my status.
332        $this->message[$pos]['status'] = $this->status;
333
334        // Set name.
335        $this->message[$pos]['name'] = htmlspecialchars($qname->name);
336
337        // Set attributes.
338        $this->message[$pos]['attrs'] = $attrs;
339
340        // Loop through attributes, logging ns and type declarations.
341        foreach ($attrs as $key => $value) {
342            // If ns declarations, add to class level array of valid
343            // namespaces.
344            $kqn = new QName($key);
345            if ($kqn->prefix == 'xmlns') {
346                $prefix = $kqn->name;
347
348                if (in_array($value, $this->_XMLSchema)) {
349                    $this->_setSchemaVersion($value);
350                }
351
352                $this->_namespaces[$value] = $prefix;
353
354            // Set method namespace.
355            } elseif ($key == 'xmlns') {
356                $qname->prefix = $this->_getNamespacePrefix($value);
357                $qname->namespace = $value;
358            } elseif ($kqn->name == 'actor') {
359                $this->message[$pos]['actor'] = $value;
360            } elseif ($kqn->name == 'mustUnderstand') {
361                $this->message[$pos]['mustUnderstand'] = $value;
362
363            // If it's a type declaration, set type.
364            } elseif ($kqn->name == 'type') {
365                $vqn = new QName($value);
366                $this->message[$pos]['type'] = $vqn->name;
367                $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->prefix);
368
369                // Should do something here with the namespace of specified
370                // type?
371
372            } elseif ($kqn->name == 'arrayType') {
373                $vqn = new QName($value);
374                $this->message[$pos]['type'] = 'Array';
375                if (isset($vqn->arraySize)) {
376                    $this->message[$pos]['arraySize'] = $vqn->arraySize;
377                }
378                $this->message[$pos]['arrayType'] = $vqn->name;
379
380            } elseif ($kqn->name == 'offset') {
381                $this->message[$pos]['arrayOffset'] = explode(',', substr($value, 1, strlen($value) - 2));
382
383            } elseif ($kqn->name == 'id') {
384                // Save id to reference array.
385                $this->references[$value] = $pos;
386                $this->message[$pos]['id'] = $value;
387
388            } elseif ($kqn->name == 'href') {
389                if ($value[0] == '#') {
390                    $ref = substr($value, 1);
391                    if (isset($this->references[$ref])) {
392                        // cdata, type, inval.
393                        $ref_pos = $this->references[$ref];
394                        $this->message[$pos]['children'] = &$this->message[$ref_pos]['children'];
395                        $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata'];
396                        $this->message[$pos]['type'] = &$this->message[$ref_pos]['type'];
397                        $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize'];
398                        $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType'];
399                    } else {
400                        // Reverse reference, store in 'need reference'.
401                        if (!isset($this->need_references[$ref])) {
402                            $this->need_references[$ref] = array();
403                        }
404                        $this->need_references[$ref][] = $pos;
405                    }
406                } elseif (isset($this->attachments[$value])) {
407                    $this->message[$pos]['cdata'] = $this->attachments[$value];
408                }
409            }
410        }
411        // See if namespace is defined in tag.
412        if (isset($attrs['xmlns:' . $qname->prefix])) {
413            $namespace = $attrs['xmlns:' . $qname->prefix];
414        } elseif ($qname->prefix && !$qname->namespace) {
415            $namespace = $this->_getNamespaceForPrefix($qname->prefix);
416        } else {
417            // Get namespace.
418            $namespace = $qname->namespace ? $qname->namespace : $this->default_namespace;
419        }
420        $this->message[$pos]['namespace'] = $namespace;
421        $this->default_namespace = $namespace;
422    }
423
424    /**
425     * End element handler used with the XML parser.
426     */
427    function _endElement($parser, $name)
428    {
429        // Position of current element is equal to the last value left in
430        // depth_array for my depth.
431        $pos = $this->depth_array[$this->depth];
432
433        // Bring depth down a notch.
434        $this->depth--;
435        $qname = new QName($name);
436
437        // Get type if not explicitly declared in an xsi:type attribute.
438        // TODO: check on integrating WSDL validation here.
439        if ($this->message[$pos]['type'] == '') {
440            if (isset($this->message[$pos]['children'])) {
441                /* this is slow, need to look at some faster method
442                $children = explode('|', $this->message[$pos]['children']);
443                if (count($children) > 2 &&
444                    $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) {
445                    $this->message[$pos]['type'] = 'Array';
446                } else {
447                    $this->message[$pos]['type'] = 'Struct';
448                }*/
449                $this->message[$pos]['type'] = 'Struct';
450            } else {
451                $parent = $this->message[$pos]['parent'];
452                if ($this->message[$parent]['type'] == 'Array' &&
453                    isset($this->message[$parent]['arrayType'])) {
454                    $this->message[$pos]['type'] = $this->message[$parent]['arrayType'];
455                } else {
456                    $this->message[$pos]['type'] = 'string';
457                }
458            }
459        }
460
461        // If tag we are currently closing is the method wrapper.
462        if ($pos == $this->curent_root_struct) {
463            $this->status = 'body';
464        } elseif ($qname->name == 'Body' || $qname->name == 'Header') {
465            $this->status = 'envelope';
466        }
467
468        // Set parent back to my parent.
469        $this->parent = $this->message[$pos]['parent'];
470
471        // Handle any reverse references now.
472        $idref = $this->message[$pos]['id'];
473
474        if ($idref != '' && isset($this->need_references[$idref])) {
475            foreach ($this->need_references[$idref] as $ref_pos) {
476                // XXX is this stuff there already?
477                $this->message[$ref_pos]['children'] = &$this->message[$pos]['children'];
478                $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata'];
479                $this->message[$ref_pos]['type'] = &$this->message[$pos]['type'];
480                $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize'];
481                $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType'];
482            }
483        }
484    }
485
486    /**
487     * Element content handler used with the XML parser.
488     */
489    function _characterData($parser, $data)
490    {
491        $pos = $this->depth_array[$this->depth];
492        if (isset($this->message[$pos]['cdata'])) {
493            $this->message[$pos]['cdata'] .= $data;
494        } else {
495            $this->message[$pos]['cdata'] = $data;
496        }
497    }
498
499}
Note: See TracBrowser for help on using the repository browser.