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

Documentation generated on Mon, 11 Mar 2019 14:20:04 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.