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

Source for file Parser.php

Documentation is available at Parser.php

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

Documentation generated on Mon, 11 Mar 2019 13:59:46 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.