SOAP
[ class tree: SOAP ] [ index: SOAP ] [ all elements ]

Source for file Parser.php

Documentation is available at Parser.php

  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($parserXML_OPTION_CASE_FOLDING0);
  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$xmltrue)) {
  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($errhtmlspecialchars($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()$ardepth0);
  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'][0count($response* -1;
  209.                     $response array_pad($response$padnull);
  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->ns == '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->ns = $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->ns);
  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'split(','substr($value1strlen($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($value1);
  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->ns])) {
  413.             $namespace $attrs['xmlns:' $qname->ns];
  414.         elseif ($qname->ns && !$qname->namespace{
  415.             $namespace $this->_getNamespaceForPrefix($qname->ns);
  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[$idrefas $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. }

Documentation generated on Mon, 04 Aug 2008 20:00:27 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.