1<?php
2	/**
3	* SOAP parser
4	* @author ?
5	* @copyright Copyright (C) ?
6	* @copyright Portions Copyright (C) 2003,2004 Free Software Foundation, Inc. http://www.fsf.org/
7	* @package phpgwapi
8	* @subpackage communication
9	* @version $Id: class.soap_parser.inc.php 14407 2004-02-10 13:51:20Z ceb $
10	*/
11
12	/**
13	* SOAPx4 parser
14	*
15	* @package phpgwapi
16	* @subpackage communication
17	*/
18	class soap_parser
19	{
20		function soap_parser($xml='',$encoding='UTF-8')
21		{
22			global $soapTypes;
23
24			$this->soapTypes = $soapTypes;
25			$this->xml = $xml;
26			$this->xml_encoding = $encoding;
27			$this->root_struct = "";
28			// options: envelope,header,body,method
29			// determines where in the message we are (envelope,header,body,method)
30			$this->status = '';
31			$this->position = 0;
32			$this->pos_stat = 0;
33			$this->depth = 0;
34			$this->default_namespace = '';
35			$this->namespaces = array();
36			$this->message = array();
37			$this->fault = false;
38			$this->fault_code = '';
39			$this->fault_str = '';
40			$this->fault_detail = '';
41			$this->eval_str = '';
42			$this->depth_array = array();
43			$this->debug_flag = True;
44			$this->debug_str = '';
45			$this->previous_element = '';
46
47			$this->entities = array (
48				'&' => '&amp;',
49				'<' => '&lt;',
50				'>' => '&gt;',
51				"'" => '&apos;',
52				'"' => '&quot;'
53			);
54
55			// Check whether content has been read.
56			if(!empty($xml))
57			{
58				$this->debug('Entering soap_parser()');
59				//$this->debug("DATA DUMP:\n\n$xml");
60				// Create an XML parser.
61				$this->parser = xml_parser_create($this->xml_encoding);
62				// Set the options for parsing the XML data.
63				//xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
64				xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
65				// Set the object for the parser.
66				xml_set_object($this->parser, &$this);
67				// Set the element handlers for the parser.
68				xml_set_element_handler($this->parser, 'start_element','end_element');
69				xml_set_character_data_handler($this->parser,'character_data');
70				xml_set_default_handler($this->parser, 'default_handler');
71
72				// Parse the XML file.
73				if(!xml_parse($this->parser,$xml,true))
74				{
75					// Display an error message.
76					$this->debug(sprintf("XML error on line %d: %s",
77						xml_get_current_line_number($this->parser),
78						xml_error_string(xml_get_error_code($this->parser))));
79					$this->fault = true;
80				}
81				else
82				{
83					// get final eval string
84					$this->eval_str = "\$response = ".trim($this->build_eval($this->root_struct)).";";
85				}
86				xml_parser_free($this->parser);
87			}
88			else
89			{
90				$this->debug("xml was empty, didn't parse!");
91			}
92		}
93
94		// loop through msg, building eval_str
95		function build_eval($pos)
96		{
97			$this->debug("inside build_eval() for $pos: ".$this->message[$pos]["name"]);
98			$eval_str = $this->message[$pos]['eval_str'];
99			// loop through children, building...
100			if($this->message[$pos]['children'] != '')
101			{
102				$this->debug('children string = '.$this->message[$pos]['children']);
103				$children = explode('|',$this->message[$pos]['children']);
104				$this->debug('it has '.count($children).' children');
105				@reset($children);
106				while(list($c,$child_pos) = @each($children))
107				/* foreach($children as $c => $child_pos) */
108				{
109					//$this->debug("child pos $child_pos: ".$this->message[$child_pos]["name"]);
110					if($this->message[$child_pos]['eval_str'] != '')
111					{
112						$this->debug('entering build_eval() for '.$this->message[$child_pos]['name'].", array pos $c, pos: $child_pos");
113						$eval_str .= $this->build_eval($child_pos).', ';
114					}
115				}
116				$eval_str = substr($eval_str,0,strlen($eval_str)-2);
117			}
118			// add current node's eval_str
119			$eval_str .= $this->message[$pos]['end_eval_str'];
120			return $eval_str;
121		}
122
123		// start-element handler
124		function start_element($parser, $name, $attrs)
125		{
126			// position in a total number of elements, starting from 0
127			// update class level pos
128			$pos = $this->position++;
129			// and set mine
130			$this->message[$pos]['pos'] = $pos;
131
132			// parent/child/depth determinations
133
134			// depth = how many levels removed from root?
135			// set mine as current global depth and increment global depth value
136			$this->message[$pos]['depth'] = $this->depth++;
137
138			// else add self as child to whoever the current parent is
139			if($pos != 0)
140			{
141				$this->message[$this->parent]['children'] .= "|$pos";
142			}
143			// set my parent
144			$this->message[$pos]['parent'] = $this->parent;
145			// set self as current value for this depth
146			$this->depth_array[$this->depth] = $pos;
147			// set self as current parent
148			$this->parent = $pos;
149
150			// set status
151			if(ereg(":Envelope$",$name))
152			{
153				$this->status = 'envelope';
154			}
155			elseif(ereg(":Header$",$name))
156			{
157				$this->status = 'header';
158			}
159			elseif(ereg(":Body$",$name))
160			{
161				$this->status = 'body';
162			// set method
163			}
164			elseif($this->status == 'body')
165			{
166				$this->status = 'method';
167				if(ereg(':',$name))
168				{
169					$this->root_struct_name = substr(strrchr($name,':'),1);
170				}
171				else
172				{
173					$this->root_struct_name = $name;
174				}
175				$this->root_struct = $pos;
176				$this->message[$pos]['type'] = 'struct';
177			}
178			// set my status
179			$this->message[$pos]['status'] = $this->status;
180
181			// set name
182			$this->message[$pos]['name'] = htmlspecialchars($name);
183			// set attrs
184			$this->message[$pos]['attrs'] = $attrs;
185			// get namespace
186			if(ereg(":",$name))
187			{
188				$namespace = substr($name,0,strpos($name,':'));
189				$this->message[$pos]['namespace'] = $namespace;
190				$this->default_namespace = $namespace;
191			}
192			else
193			{
194				$this->message[$pos]['namespace'] = $this->default_namespace;
195			}
196			// loop through atts, logging ns and type declarations
197			@reset($attrs);
198			while (list($key,$value) = @each($attrs))
199			/* foreach($attrs as $key => $value) */
200			{
201				// if ns declarations, add to class level array of valid namespaces
202				if(ereg('xmlns:',$key))
203				{
204					$namespaces[substr(strrchr($key,':'),1)] = $value;
205					if($name == $this->root_struct_name)
206					{
207						$this->methodNamespace = $value;
208					}
209				}
210				// if it's a type declaration, set type
211				elseif($key == 'xsi:type')
212				{
213					// then get attname and set $type
214					$type = substr(strrchr($value,':'),1);
215				}
216			}
217
218			// set type if available
219			if($type)
220			{
221				$this->message[$pos]['type'] = $type;
222			}
223
224			// debug
225			//$this->debug("parsed $name start, eval = '".$this->message[$pos]["eval_str"]."'");
226		}
227
228		// end-element handler
229		function end_element($parser, $name)
230		{
231			// position of current element is equal to the last value left in depth_array for my depth
232			$pos = $this->depth_array[$this->depth];
233			// bring depth down a notch
234			$this->depth--;
235
236			// get type if not set already
237			if($this->message[$pos]['type'] == '')
238			{
239//				if($this->message[$pos]['cdata'] == '' && $this->message[$pos]['children'] != '')
240				if($this->message[$pos]['children'] != '')
241				{
242					$this->message[$pos]['type'] = 'SOAPStruct';
243				}
244				else
245				{
246					$this->message[$pos]['type'] = 'string';
247				}
248			}
249
250			// set eval str start if it has a valid type and is inside the method
251			if($pos >= $this->root_struct)
252			{
253				$this->message[$pos]['eval_str'] .= "\n CreateObject(\"phpgwapi.soapval\",\"".htmlspecialchars($name)."\", \"".$this->message[$pos]["type"]."\" ";
254				$this->message[$pos]['end_eval_str'] = ')';
255				$this->message[$pos]['inval'] = 'true';
256				/*
257				if($this->message[$pos]["name"] == $this->root_struct_name){
258					$this->message[$pos]["end_eval_str"] .= " ,\"$this->methodNamespace\"";
259				}
260				*/
261				if($this->message[$pos]['children'] != '')
262				{
263					$this->message[$pos]['eval_str'] .= ', array( ';
264					$this->message[$pos]['end_eval_str'] .= ' )';
265				}
266			}
267
268			// if i have no children and have cdata...then i must be a scalar value, so add my data to the eval_str
269			if($this->status == 'method' && $this->message[$pos]['children'] == '')
270			{
271				// add cdata w/ no quotes if only int/float/dbl
272				if($this->message[$pos]['type'] == 'string')
273				{
274					$this->message[$pos]['eval_str'] .= ", \"".$this->message[$pos]['cdata']."\"";
275				}
276				elseif($this->message[$pos]['type'] == 'int' || $this->message[$pos]['type'] == 'float' || $this->message[$pos]['type'] == 'double')
277				{
278					//$this->debug("adding cdata w/o quotes");
279					$this->message[$pos]['eval_str'] .= ', '.trim($this->message[$pos]['cdata']);
280				}
281				elseif(is_string($this->message[$pos]['cdata']))
282				{
283					//$this->debug("adding cdata w/ quotes");
284					$this->message[$pos]['eval_str'] .= ", \"".$this->message[$pos]['cdata']."\"";
285				}
286			}
287			// if in the process of making a soap_val, close the parentheses and move on...
288			if($this->message[$pos]['inval'] == 'true')
289			{
290				$this->message[$pos]['inval'] == 'false';
291			}
292			// if tag we are currently closing is the method wrapper
293			if($pos == $this->root_struct)
294			{
295				$this->status = 'body';
296			}
297			elseif(ereg(':Body',$name))
298			{
299				$this->status = 'header';
300	 		}
301			elseif(ereg(':Header',$name))
302			{
303				$this->status = 'envelope';
304			}
305			// set parent back to my parent
306			$this->parent = $this->message[$pos]['parent'];
307			$this->debug("parsed $name end, type '".$this->message[$pos]['type']."'eval_str = '".trim($this->message[$pos]['eval_str'])."' and children = ".$this->message[$pos]['children']);
308		}
309
310		// element content handler
311		function character_data($parser, $data)
312		{
313			$pos = $this->depth_array[$this->depth];
314			$this->message[$pos]['cdata'] .= $data;
315			//$this->debug("parsed ".$this->message[$pos]["name"]." cdata, eval = '$this->eval_str'");
316		}
317
318		// default handler
319		function default_handler($parser, $data)
320		{
321			//$this->debug("DEFAULT HANDLER: $data");
322		}
323
324		// function to get fault code
325		function fault()
326		{
327			if($this->fault)
328			{
329				return true;
330			}
331			else
332			{
333				return false;
334			}
335		}
336
337		// have this return a soap_val object
338		function get_response()
339		{
340			$this->debug("eval()ing eval_str: $this->eval_str");
341			@eval("$this->eval_str");
342			if($response)
343			{
344				$this->debug("successfully eval'd msg");
345				return $response;
346			}
347			else
348			{
349				$this->debug('ERROR: did not successfully eval the msg');
350				$this->fault = true;
351				return CreateObject('phpgwapi.soapval',
352					'Fault',
353					'struct',
354					array(
355						CreateObject('phpgwapi.soapval',
356							'faultcode',
357							'string',
358							'SOAP-ENV:Server'
359						),
360						CreateObject('phpgwapi.soapval',
361							'faultstring',
362							'string',
363							"couldn't eval \"$this->eval_str\""
364						)
365					)
366				);
367			}
368		}
369
370		function debug($string)
371		{
372			if($this->debug_flag)
373			{
374				$this->debug_str .= "$string\n";
375			}
376		}
377
378		function decode_entities($text)
379		{
380			@reset($this->entities);
381			while(list($entity,$encoded) = @each($this->entities))
382			/* foreach($this->entities as $entity => $encoded) */
383			{
384				$text = str_replace($encoded,$entity,$text);
385			}
386			return $text;
387		}
388	}
389?>
390