home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / SOAP / Parser.php < prev    next >
Encoding:
PHP Script  |  2008-07-02  |  19.0 KB  |  505 lines

  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.  
  25. require_once 'SOAP/Base.php';
  26. require_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.  */
  40. class 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.         if ($response) {
  224.             $nqn = new QName($this->message[$pos]['name'],
  225.                              $this->message[$pos]['namespace']);
  226.             $tqn = new QName($this->message[$pos]['type'],
  227.                              $this->message[$pos]['type_namespace']);
  228.             $response = new SOAP_Value($nqn->fqn(),
  229.                                        $tqn->fqn(),
  230.                                        $response,
  231.                                        $attrs);
  232.             if (isset($this->message[$pos]['arrayType'])) {
  233.                 $response->arrayType = $this->message[$pos]['arrayType'];
  234.             }
  235.         } else {
  236.             $nqn = new QName($this->message[$pos]['name'],
  237.                              $this->message[$pos]['namespace']);
  238.             $tqn = new QName($this->message[$pos]['type'],
  239.                              $this->message[$pos]['type_namespace']);
  240.             // Check if value is an empty array
  241.             if ($tqn->name == 'Array') {
  242.                 $response =& new SOAP_Value($nqn->fqn(), $tqn->fqn(),
  243.                                             array(), $attrs);
  244.             } else {
  245.                 $response = new SOAP_Value($nqn->fqn(),
  246.                                             $tqn->fqn(),
  247.                                             $this->message[$pos]['cdata'],
  248.                                             $attrs);
  249.             }
  250.         }
  251.  
  252.         // Handle header attribute that we need.
  253.         if (array_key_exists('actor', $this->message[$pos])) {
  254.             $response->actor = $this->message[$pos]['actor'];
  255.         }
  256.         if (array_key_exists('mustUnderstand', $this->message[$pos])) {
  257.             $response->mustunderstand = $this->message[$pos]['mustUnderstand'];
  258.         }
  259.         return $response;
  260.     }
  261.  
  262.     /**
  263.      * Start element handler used with the XML parser.
  264.      */
  265.     function _startElement($parser, $name, $attrs)
  266.     {
  267.         // Position in a total number of elements, starting from 0.
  268.         // Update class level position.
  269.         $pos = $this->position++;
  270.  
  271.         // And set mine.
  272.         $this->message[$pos] = array(
  273.             'type' => '',
  274.             'type_namespace' => '',
  275.             'cdata' => '',
  276.             'pos' => $pos,
  277.             'id' => '');
  278.  
  279.         // Parent/child/depth determinations.
  280.  
  281.         // depth = How many levels removed from root?
  282.         // Set mine as current global depth and increment global depth value.
  283.         $this->message[$pos]['depth'] = $this->depth++;
  284.  
  285.         // Else add self as child to whoever the current parent is.
  286.         if ($pos != 0) {
  287.             if (isset($this->message[$this->parent]['children'])) {
  288.                 $this->message[$this->parent]['children'] .= '|' . $pos;
  289.             } else {
  290.                 $this->message[$this->parent]['children'] = $pos;
  291.             }
  292.         }
  293.  
  294.         // Set my parent.
  295.         $this->message[$pos]['parent'] = $this->parent;
  296.  
  297.         // Set self as current value for this depth.
  298.         $this->depth_array[$this->depth] = $pos;
  299.         // Set self as current parent.
  300.         $this->parent = $pos;
  301.         $qname = new QName($name);
  302.         // Set status.
  303.         if (strcasecmp('envelope', $qname->name) == 0) {
  304.             $this->status = 'envelope';
  305.         } elseif (strcasecmp('header', $qname->name) == 0) {
  306.             $this->status = 'header';
  307.             $this->header_struct_name[] = $this->curent_root_struct_name = $qname->name;
  308.             $this->header_struct[] = $this->curent_root_struct = $pos;
  309.             $this->message[$pos]['type'] = 'Struct';
  310.         } elseif (strcasecmp('body', $qname->name) == 0) {
  311.             $this->status = 'body';
  312.             $this->bodyDepth = $this->depth;
  313.  
  314.         // Set method
  315.         } elseif ($this->status == 'body') {
  316.             // Is this element allowed to be a root?
  317.             // TODO: this needs to be optimized, we loop through $attrs twice
  318.             // now.
  319.             $can_root = $this->depth == $this->bodyDepth + 1;
  320.             if ($can_root) {
  321.                 foreach ($attrs as $key => $value) {
  322.                     if (stristr($key, ':root') && !$value) {
  323.                         $can_root = false;
  324.                     }
  325.                 }
  326.             }
  327.  
  328.             if ($can_root) {
  329.                 $this->status = 'method';
  330.                 $this->root_struct_name[] = $this->curent_root_struct_name = $qname->name;
  331.                 $this->root_struct[] = $this->curent_root_struct = $pos;
  332.                 $this->message[$pos]['type'] = 'Struct';
  333.             }
  334.         }
  335.  
  336.         // Set my status.
  337.         $this->message[$pos]['status'] = $this->status;
  338.  
  339.         // Set name.
  340.         $this->message[$pos]['name'] = htmlspecialchars($qname->name);
  341.  
  342.         // Set attributes.
  343.         $this->message[$pos]['attrs'] = $attrs;
  344.  
  345.         // Loop through attributes, logging ns and type declarations.
  346.         foreach ($attrs as $key => $value) {
  347.             // If ns declarations, add to class level array of valid
  348.             // namespaces.
  349.             $kqn = new QName($key);
  350.             if ($kqn->ns == 'xmlns') {
  351.                 $prefix = $kqn->name;
  352.  
  353.                 if (in_array($value, $this->_XMLSchema)) {
  354.                     $this->_setSchemaVersion($value);
  355.                 }
  356.  
  357.                 $this->_namespaces[$value] = $prefix;
  358.  
  359.             // Set method namespace.
  360.             } elseif ($key == 'xmlns') {
  361.                 $qname->ns = $this->_getNamespacePrefix($value);
  362.                 $qname->namespace = $value;
  363.             } elseif ($kqn->name == 'actor') {
  364.                 $this->message[$pos]['actor'] = $value;
  365.             } elseif ($kqn->name == 'mustUnderstand') {
  366.                 $this->message[$pos]['mustUnderstand'] = $value;
  367.  
  368.             // If it's a type declaration, set type.
  369.             } elseif ($kqn->name == 'type') {
  370.                 $vqn = new QName($value);
  371.                 $this->message[$pos]['type'] = $vqn->name;
  372.                 $this->message[$pos]['type_namespace'] = $this->_getNamespaceForPrefix($vqn->ns);
  373.  
  374.                 // Should do something here with the namespace of specified
  375.                 // type?
  376.  
  377.             } elseif ($kqn->name == 'arrayType') {
  378.                 $vqn = new QName($value);
  379.                 $this->message[$pos]['type'] = 'Array';
  380.                 if (isset($vqn->arraySize)) {
  381.                     $this->message[$pos]['arraySize'] = $vqn->arraySize;
  382.                 }
  383.                 $this->message[$pos]['arrayType'] = $vqn->name;
  384.  
  385.             } elseif ($kqn->name == 'offset') {
  386.                 $this->message[$pos]['arrayOffset'] = split(',', substr($value, 1, strlen($value) - 2));
  387.  
  388.             } elseif ($kqn->name == 'id') {
  389.                 // Save id to reference array.
  390.                 $this->references[$value] = $pos;
  391.                 $this->message[$pos]['id'] = $value;
  392.  
  393.             } elseif ($kqn->name == 'href') {
  394.                 if ($value[0] == '#') {
  395.                     $ref = substr($value, 1);
  396.                     if (isset($this->references[$ref])) {
  397.                         // cdata, type, inval.
  398.                         $ref_pos = $this->references[$ref];
  399.                         $this->message[$pos]['children'] = &$this->message[$ref_pos]['children'];
  400.                         $this->message[$pos]['cdata'] = &$this->message[$ref_pos]['cdata'];
  401.                         $this->message[$pos]['type'] = &$this->message[$ref_pos]['type'];
  402.                         $this->message[$pos]['arraySize'] = &$this->message[$ref_pos]['arraySize'];
  403.                         $this->message[$pos]['arrayType'] = &$this->message[$ref_pos]['arrayType'];
  404.                     } else {
  405.                         // Reverse reference, store in 'need reference'.
  406.                         if (!isset($this->need_references[$ref])) {
  407.                             $this->need_references[$ref] = array();
  408.                         }
  409.                         $this->need_references[$ref][] = $pos;
  410.                     }
  411.                 } elseif (isset($this->attachments[$value])) {
  412.                     $this->message[$pos]['cdata'] = $this->attachments[$value];
  413.                 }
  414.             }
  415.         }
  416.         // See if namespace is defined in tag.
  417.         if (isset($attrs['xmlns:' . $qname->ns])) {
  418.             $namespace = $attrs['xmlns:' . $qname->ns];
  419.         } elseif ($qname->ns && !$qname->namespace) {
  420.             $namespace = $this->_getNamespaceForPrefix($qname->ns);
  421.         } else {
  422.             // Get namespace.
  423.             $namespace = $qname->namespace ? $qname->namespace : $this->default_namespace;
  424.         }
  425.         $this->message[$pos]['namespace'] = $namespace;
  426.         $this->default_namespace = $namespace;
  427.     }
  428.  
  429.     /**
  430.      * End element handler used with the XML parser.
  431.      */
  432.     function _endElement($parser, $name)
  433.     {
  434.         // Position of current element is equal to the last value left in
  435.         // depth_array for my depth.
  436.         $pos = $this->depth_array[$this->depth];
  437.  
  438.         // Bring depth down a notch.
  439.         $this->depth--;
  440.         $qname = new QName($name);
  441.  
  442.         // Get type if not explicitly declared in an xsi:type attribute.
  443.         // TODO: check on integrating WSDL validation here.
  444.         if ($this->message[$pos]['type'] == '') {
  445.             if (isset($this->message[$pos]['children'])) {
  446.                 /* this is slow, need to look at some faster method
  447.                 $children = explode('|', $this->message[$pos]['children']);
  448.                 if (count($children) > 2 &&
  449.                     $this->message[$children[1]]['name'] == $this->message[$children[2]]['name']) {
  450.                     $this->message[$pos]['type'] = 'Array';
  451.                 } else {
  452.                     $this->message[$pos]['type'] = 'Struct';
  453.                 }*/
  454.                 $this->message[$pos]['type'] = 'Struct';
  455.             } else {
  456.                 $parent = $this->message[$pos]['parent'];
  457.                 if ($this->message[$parent]['type'] == 'Array' &&
  458.                     isset($this->message[$parent]['arrayType'])) {
  459.                     $this->message[$pos]['type'] = $this->message[$parent]['arrayType'];
  460.                 } else {
  461.                     $this->message[$pos]['type'] = 'string';
  462.                 }
  463.             }
  464.         }
  465.  
  466.         // If tag we are currently closing is the method wrapper.
  467.         if ($pos == $this->curent_root_struct) {
  468.             $this->status = 'body';
  469.         } elseif ($qname->name == 'Body' || $qname->name == 'Header') {
  470.             $this->status = 'envelope';
  471.         }
  472.  
  473.         // Set parent back to my parent.
  474.         $this->parent = $this->message[$pos]['parent'];
  475.  
  476.         // Handle any reverse references now.
  477.         $idref = $this->message[$pos]['id'];
  478.  
  479.         if ($idref != '' && isset($this->need_references[$idref])) {
  480.             foreach ($this->need_references[$idref] as $ref_pos) {
  481.                 // XXX is this stuff there already?
  482.                 $this->message[$ref_pos]['children'] = &$this->message[$pos]['children'];
  483.                 $this->message[$ref_pos]['cdata'] = &$this->message[$pos]['cdata'];
  484.                 $this->message[$ref_pos]['type'] = &$this->message[$pos]['type'];
  485.                 $this->message[$ref_pos]['arraySize'] = &$this->message[$pos]['arraySize'];
  486.                 $this->message[$ref_pos]['arrayType'] = &$this->message[$pos]['arrayType'];
  487.             }
  488.         }
  489.     }
  490.  
  491.     /**
  492.      * Element content handler used with the XML parser.
  493.      */
  494.     function _characterData($parser, $data)
  495.     {
  496.         $pos = $this->depth_array[$this->depth];
  497.         if (isset($this->message[$pos]['cdata'])) {
  498.             $this->message[$pos]['cdata'] .= $data;
  499.         } else {
  500.             $this->message[$pos]['cdata'] = $data;
  501.         }
  502.     }
  503.  
  504. }
  505.