Removed php_test.
[siap.git] / nusoap / lib / class.xmlschema.php
1 <?php
2
3
4
5
6 /**
7 * parses an XML Schema, allows access to it's data, other utility methods.
8 * imperfect, no validation... yet, but quite functional.
9 *
10 * @author   Dietrich Ayala <dietrich@ganx4.com>
11 * @author   Scott Nichol <snichol@users.sourceforge.net>
12 * @version  $Id: class.xmlschema.php,v 1.53 2010/04/26 20:15:08 snichol Exp $
13 * @access   public
14 */
15 class nusoap_xmlschema extends nusoap_base  {
16         
17         // files
18         var $schema = '';
19         var $xml = '';
20         // namespaces
21         var $enclosingNamespaces;
22         // schema info
23         var $schemaInfo = array();
24         var $schemaTargetNamespace = '';
25         // types, elements, attributes defined by the schema
26         var $attributes = array();
27         var $complexTypes = array();
28         var $complexTypeStack = array();
29         var $currentComplexType = null;
30         var $elements = array();
31         var $elementStack = array();
32         var $currentElement = null;
33         var $simpleTypes = array();
34         var $simpleTypeStack = array();
35         var $currentSimpleType = null;
36         // imports
37         var $imports = array();
38         // parser vars
39         var $parser;
40         var $position = 0;
41         var $depth = 0;
42         var $depth_array = array();
43         var $message = array();
44         var $defaultNamespace = array();
45     
46         /**
47         * constructor
48         *
49         * @param    string $schema schema document URI
50         * @param    string $xml xml document URI
51         * @param        string $namespaces namespaces defined in enclosing XML
52         * @access   public
53         */
54         function nusoap_xmlschema($schema='',$xml='',$namespaces=array()){
55                 parent::nusoap_base();
56                 $this->debug('nusoap_xmlschema class instantiated, inside constructor');
57                 // files
58                 $this->schema = $schema;
59                 $this->xml = $xml;
60
61                 // namespaces
62                 $this->enclosingNamespaces = $namespaces;
63                 $this->namespaces = array_merge($this->namespaces, $namespaces);
64
65                 // parse schema file
66                 if($schema != ''){
67                         $this->debug('initial schema file: '.$schema);
68                         $this->parseFile($schema, 'schema');
69                 }
70
71                 // parse xml file
72                 if($xml != ''){
73                         $this->debug('initial xml file: '.$xml);
74                         $this->parseFile($xml, 'xml');
75                 }
76
77         }
78
79     /**
80     * parse an XML file
81     *
82     * @param string $xml path/URL to XML file
83     * @param string $type (schema | xml)
84         * @return boolean
85     * @access public
86     */
87         function parseFile($xml,$type){
88                 // parse xml file
89                 if($xml != ""){
90                         $xmlStr = @join("",@file($xml));
91                         if($xmlStr == ""){
92                                 $msg = 'Error reading XML from '.$xml;
93                                 $this->setError($msg);
94                                 $this->debug($msg);
95                         return false;
96                         } else {
97                                 $this->debug("parsing $xml");
98                                 $this->parseString($xmlStr,$type);
99                                 $this->debug("done parsing $xml");
100                         return true;
101                         }
102                 }
103                 return false;
104         }
105
106         /**
107         * parse an XML string
108         *
109         * @param    string $xml path or URL
110     * @param    string $type (schema|xml)
111         * @access   private
112         */
113         function parseString($xml,$type){
114                 // parse xml string
115                 if($xml != ""){
116
117                 // Create an XML parser.
118                 $this->parser = xml_parser_create();
119                 // Set the options for parsing the XML data.
120                 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
121
122                 // Set the object for the parser.
123                 xml_set_object($this->parser, $this);
124
125                 // Set the element handlers for the parser.
126                         if($type == "schema"){
127                         xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
128                         xml_set_character_data_handler($this->parser,'schemaCharacterData');
129                         } elseif($type == "xml"){
130                                 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
131                         xml_set_character_data_handler($this->parser,'xmlCharacterData');
132                         }
133
134                     // Parse the XML file.
135                     if(!xml_parse($this->parser,$xml,true)){
136                         // Display an error message.
137                                 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
138                                 xml_get_current_line_number($this->parser),
139                                 xml_error_string(xml_get_error_code($this->parser))
140                                 );
141                                 $this->debug($errstr);
142                                 $this->debug("XML payload:\n" . $xml);
143                                 $this->setError($errstr);
144                 }
145             
146                         xml_parser_free($this->parser);
147                 } else{
148                         $this->debug('no xml passed to parseString()!!');
149                         $this->setError('no xml passed to parseString()!!');
150                 }
151         }
152
153         /**
154          * gets a type name for an unnamed type
155          *
156          * @param       string  Element name
157          * @return      string  A type name for an unnamed type
158          * @access      private
159          */
160         function CreateTypeName($ename) {
161                 $scope = '';
162                 for ($i = 0; $i < count($this->complexTypeStack); $i++) {
163                         $scope .= $this->complexTypeStack[$i] . '_';
164                 }
165                 return $scope . $ename . '_ContainedType';
166         }
167         
168         /**
169         * start-element handler
170         *
171         * @param    string $parser XML parser object
172         * @param    string $name element name
173         * @param    string $attrs associative array of attributes
174         * @access   private
175         */
176         function schemaStartElement($parser, $name, $attrs) {
177                 
178                 // position in the total number of elements, starting from 0
179                 $pos = $this->position++;
180                 $depth = $this->depth++;
181                 // set self as current value for this depth
182                 $this->depth_array[$depth] = $pos;
183                 $this->message[$pos] = array('cdata' => ''); 
184                 if ($depth > 0) {
185                         $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
186                 } else {
187                         $this->defaultNamespace[$pos] = false;
188                 }
189
190                 // get element prefix
191                 if($prefix = $this->getPrefix($name)){
192                         // get unqualified name
193                         $name = $this->getLocalPart($name);
194                 } else {
195                 $prefix = '';
196         }
197                 
198         // loop thru attributes, expanding, and registering namespace declarations
199         if(count($attrs) > 0){
200                 foreach($attrs as $k => $v){
201                 // if ns declarations, add to class level array of valid namespaces
202                                 if(preg_match('/^xmlns/',$k)){
203                         //$this->xdebug("$k: $v");
204                         //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
205                         if($ns_prefix = substr(strrchr($k,':'),1)){
206                                 //$this->xdebug("Add namespace[$ns_prefix] = $v");
207                                                 $this->namespaces[$ns_prefix] = $v;
208                                         } else {
209                                                 $this->defaultNamespace[$pos] = $v;
210                                                 if (! $this->getPrefixFromNamespace($v)) {
211                                                         $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
212                                                 }
213                                         }
214                                         if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
215                                                 $this->XMLSchemaVersion = $v;
216                                                 $this->namespaces['xsi'] = $v.'-instance';
217                                         }
218                                 }
219                 }
220                 foreach($attrs as $k => $v){
221                 // expand each attribute
222                 $k = strpos($k,':') ? $this->expandQname($k) : $k;
223                 $v = strpos($v,':') ? $this->expandQname($v) : $v;
224                         $eAttrs[$k] = $v;
225                 }
226                 $attrs = $eAttrs;
227         } else {
228                 $attrs = array();
229         }
230                 // find status, register data
231                 switch($name){
232                         case 'all':                     // (optional) compositor content for a complexType
233                         case 'choice':
234                         case 'group':
235                         case 'sequence':
236                                 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
237                                 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
238                                 //if($name == 'all' || $name == 'sequence'){
239                                 //      $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
240                                 //}
241                         break;
242                         case 'attribute':       // complexType attribute
243                 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
244                 $this->xdebug("parsing attribute:");
245                 $this->appendDebug($this->varDump($attrs));
246                                 if (!isset($attrs['form'])) {
247                                         // TODO: handle globals
248                                         $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
249                                 }
250                 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
251                                         $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
252                                         if (!strpos($v, ':')) {
253                                                 // no namespace in arrayType attribute value...
254                                                 if ($this->defaultNamespace[$pos]) {
255                                                         // ...so use the default
256                                                         $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
257                                                 }
258                                         }
259                 }
260                 if(isset($attrs['name'])){
261                                         $this->attributes[$attrs['name']] = $attrs;
262                                         $aname = $attrs['name'];
263                                 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
264                                         if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
265                                 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
266                         } else {
267                                 $aname = '';
268                         }
269                                 } elseif(isset($attrs['ref'])){
270                                         $aname = $attrs['ref'];
271                     $this->attributes[$attrs['ref']] = $attrs;
272                                 }
273                 
274                                 if($this->currentComplexType){  // This should *always* be
275                                         $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
276                                 }
277                                 // arrayType attribute
278                                 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
279                                         $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
280                         $prefix = $this->getPrefix($aname);
281                                         if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
282                                                 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
283                                         } else {
284                                                 $v = '';
285                                         }
286                     if(strpos($v,'[,]')){
287                         $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
288                     }
289                     $v = substr($v,0,strpos($v,'[')); // clip the []
290                     if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
291                         $v = $this->XMLSchemaVersion.':'.$v;
292                     }
293                     $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
294                                 }
295                         break;
296                         case 'complexContent':  // (optional) content for a complexType
297                                 $this->xdebug("do nothing for element $name");
298                         break;
299                         case 'complexType':
300                                 array_push($this->complexTypeStack, $this->currentComplexType);
301                                 if(isset($attrs['name'])){
302                                         // TODO: what is the scope of named complexTypes that appear
303                                         //       nested within other c complexTypes?
304                                         $this->xdebug('processing named complexType '.$attrs['name']);
305                                         //$this->currentElement = false;
306                                         $this->currentComplexType = $attrs['name'];
307                                         $this->complexTypes[$this->currentComplexType] = $attrs;
308                                         $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
309                                         // This is for constructs like
310                                         //           <complexType name="ListOfString" base="soap:Array">
311                                         //                <sequence>
312                                         //                    <element name="string" type="xsd:string"
313                                         //                        minOccurs="0" maxOccurs="unbounded" />
314                                         //                </sequence>
315                                         //            </complexType>
316                                         if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
317                                                 $this->xdebug('complexType is unusual array');
318                                                 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
319                                         } else {
320                                                 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
321                                         }
322                                 } else {
323                                         $name = $this->CreateTypeName($this->currentElement);
324                                         $this->xdebug('processing unnamed complexType for element ' . $this->currentElement . ' named ' . $name);
325                                         $this->currentComplexType = $name;
326                                         //$this->currentElement = false;
327                                         $this->complexTypes[$this->currentComplexType] = $attrs;
328                                         $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
329                                         // This is for constructs like
330                                         //           <complexType name="ListOfString" base="soap:Array">
331                                         //                <sequence>
332                                         //                    <element name="string" type="xsd:string"
333                                         //                        minOccurs="0" maxOccurs="unbounded" />
334                                         //                </sequence>
335                                         //            </complexType>
336                                         if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
337                                                 $this->xdebug('complexType is unusual array');
338                                                 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
339                                         } else {
340                                                 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
341                                         }
342                                 }
343                                 $this->complexTypes[$this->currentComplexType]['simpleContent'] = 'false';
344                         break;
345                         case 'element':
346                                 array_push($this->elementStack, $this->currentElement);
347                                 if (!isset($attrs['form'])) {
348                                         if ($this->currentComplexType) {
349                                                 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
350                                         } else {
351                                                 // global
352                                                 $attrs['form'] = 'qualified';
353                                         }
354                                 }
355                                 if(isset($attrs['type'])){
356                                         $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
357                                         if (! $this->getPrefix($attrs['type'])) {
358                                                 if ($this->defaultNamespace[$pos]) {
359                                                         $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
360                                                         $this->xdebug('used default namespace to make type ' . $attrs['type']);
361                                                 }
362                                         }
363                                         // This is for constructs like
364                                         //           <complexType name="ListOfString" base="soap:Array">
365                                         //                <sequence>
366                                         //                    <element name="string" type="xsd:string"
367                                         //                        minOccurs="0" maxOccurs="unbounded" />
368                                         //                </sequence>
369                                         //            </complexType>
370                                         if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
371                                                 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
372                                                 $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
373                                         }
374                                         $this->currentElement = $attrs['name'];
375                                         $ename = $attrs['name'];
376                                 } elseif(isset($attrs['ref'])){
377                                         $this->xdebug("processing element as ref to ".$attrs['ref']);
378                                         $this->currentElement = "ref to ".$attrs['ref'];
379                                         $ename = $this->getLocalPart($attrs['ref']);
380                                 } else {
381                                         $type = $this->CreateTypeName($this->currentComplexType . '_' . $attrs['name']);
382                                         $this->xdebug("processing untyped element " . $attrs['name'] . ' type ' . $type);
383                                         $this->currentElement = $attrs['name'];
384                                         $attrs['type'] = $this->schemaTargetNamespace . ':' . $type;
385                                         $ename = $attrs['name'];
386                                 }
387                                 if (isset($ename) && $this->currentComplexType) {
388                                         $this->xdebug("add element $ename to complexType $this->currentComplexType");
389                                         $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
390                                 } elseif (!isset($attrs['ref'])) {
391                                         $this->xdebug("add element $ename to elements array");
392                                         $this->elements[ $attrs['name'] ] = $attrs;
393                                         $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
394                                 }
395                         break;
396                         case 'enumeration':     //      restriction value list member
397                                 $this->xdebug('enumeration ' . $attrs['value']);
398                                 if ($this->currentSimpleType) {
399                                         $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
400                                 } elseif ($this->currentComplexType) {
401                                         $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
402                                 }
403                         break;
404                         case 'extension':       // simpleContent or complexContent type extension
405                                 $this->xdebug('extension ' . $attrs['base']);
406                                 if ($this->currentComplexType) {
407                                         $ns = $this->getPrefix($attrs['base']);
408                                         if ($ns == '') {
409                                                 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $this->schemaTargetNamespace . ':' . $attrs['base'];
410                                         } else {
411                                                 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
412                                         }
413                                 } else {
414                                         $this->xdebug('no current complexType to set extensionBase');
415                                 }
416                         break;
417                         case 'import':
418                             if (isset($attrs['schemaLocation'])) {
419                                         $this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
420                     $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
421                                 } else {
422                                         $this->xdebug('import namespace ' . $attrs['namespace']);
423                     $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
424                                         if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
425                                                 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
426                                         }
427                                 }
428                         break;
429                         case 'include':
430                             if (isset($attrs['schemaLocation'])) {
431                                         $this->xdebug('include into namespace ' . $this->schemaTargetNamespace . ' from ' . $attrs['schemaLocation']);
432                     $this->imports[$this->schemaTargetNamespace][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
433                                 } else {
434                                         $this->xdebug('ignoring invalid XML Schema construct: include without schemaLocation attribute');
435                                 }
436                         break;
437                         case 'list':    // simpleType value list
438                                 $this->xdebug("do nothing for element $name");
439                         break;
440                         case 'restriction':     // simpleType, simpleContent or complexContent value restriction
441                                 $this->xdebug('restriction ' . $attrs['base']);
442                                 if($this->currentSimpleType){
443                                         $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
444                                 } elseif($this->currentComplexType){
445                                         $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
446                                         if(strstr($attrs['base'],':') == ':Array'){
447                                                 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
448                                         }
449                                 }
450                         break;
451                         case 'schema':
452                                 $this->schemaInfo = $attrs;
453                                 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
454                                 if (isset($attrs['targetNamespace'])) {
455                                         $this->schemaTargetNamespace = $attrs['targetNamespace'];
456                                 }
457                                 if (!isset($attrs['elementFormDefault'])) {
458                                         $this->schemaInfo['elementFormDefault'] = 'unqualified';
459                                 }
460                                 if (!isset($attrs['attributeFormDefault'])) {
461                                         $this->schemaInfo['attributeFormDefault'] = 'unqualified';
462                                 }
463                         break;
464                         case 'simpleContent':   // (optional) content for a complexType
465                                 if ($this->currentComplexType) {        // This should *always* be
466                                         $this->complexTypes[$this->currentComplexType]['simpleContent'] = 'true';
467                                 } else {
468                                         $this->xdebug("do nothing for element $name because there is no current complexType");
469                                 }
470                         break;
471                         case 'simpleType':
472                                 array_push($this->simpleTypeStack, $this->currentSimpleType);
473                                 if(isset($attrs['name'])){
474                                         $this->xdebug("processing simpleType for name " . $attrs['name']);
475                                         $this->currentSimpleType = $attrs['name'];
476                                         $this->simpleTypes[ $attrs['name'] ] = $attrs;
477                                         $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
478                                         $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
479                                 } else {
480                                         $name = $this->CreateTypeName($this->currentComplexType . '_' . $this->currentElement);
481                                         $this->xdebug('processing unnamed simpleType for element ' . $this->currentElement . ' named ' . $name);
482                                         $this->currentSimpleType = $name;
483                                         //$this->currentElement = false;
484                                         $this->simpleTypes[$this->currentSimpleType] = $attrs;
485                                         $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
486                                 }
487                         break;
488                         case 'union':   // simpleType type list
489                                 $this->xdebug("do nothing for element $name");
490                         break;
491                         default:
492                                 $this->xdebug("do not have any logic to process element $name");
493                 }
494         }
495
496         /**
497         * end-element handler
498         *
499         * @param    string $parser XML parser object
500         * @param    string $name element name
501         * @access   private
502         */
503         function schemaEndElement($parser, $name) {
504                 // bring depth down a notch
505                 $this->depth--;
506                 // position of current element is equal to the last value left in depth_array for my depth
507                 if(isset($this->depth_array[$this->depth])){
508                 $pos = $this->depth_array[$this->depth];
509         }
510                 // get element prefix
511                 if ($prefix = $this->getPrefix($name)){
512                         // get unqualified name
513                         $name = $this->getLocalPart($name);
514                 } else {
515                 $prefix = '';
516         }
517                 // move on...
518                 if($name == 'complexType'){
519                         $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
520                         $this->xdebug($this->varDump($this->complexTypes[$this->currentComplexType]));
521                         $this->currentComplexType = array_pop($this->complexTypeStack);
522                         //$this->currentElement = false;
523                 }
524                 if($name == 'element'){
525                         $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
526                         $this->currentElement = array_pop($this->elementStack);
527                 }
528                 if($name == 'simpleType'){
529                         $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
530                         $this->xdebug($this->varDump($this->simpleTypes[$this->currentSimpleType]));
531                         $this->currentSimpleType = array_pop($this->simpleTypeStack);
532                 }
533         }
534
535         /**
536         * element content handler
537         *
538         * @param    string $parser XML parser object
539         * @param    string $data element content
540         * @access   private
541         */
542         function schemaCharacterData($parser, $data){
543                 $pos = $this->depth_array[$this->depth - 1];
544                 $this->message[$pos]['cdata'] .= $data;
545         }
546
547         /**
548         * serialize the schema
549         *
550         * @access   public
551         */
552         function serializeSchema(){
553
554                 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
555                 $xml = '';
556                 // imports
557                 if (sizeof($this->imports) > 0) {
558                         foreach($this->imports as $ns => $list) {
559                                 foreach ($list as $ii) {
560                                         if ($ii['location'] != '') {
561                                                 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
562                                         } else {
563                                                 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
564                                         }
565                                 }
566                         } 
567                 } 
568                 // complex types
569                 foreach($this->complexTypes as $typeName => $attrs){
570                         $contentStr = '';
571                         // serialize child elements
572                         if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
573                                 foreach($attrs['elements'] as $element => $eParts){
574                                         if(isset($eParts['ref'])){
575                                                 $contentStr .= "   <$schemaPrefix:element ref=\"$element\"/>\n";
576                                         } else {
577                                                 $contentStr .= "   <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
578                                                 foreach ($eParts as $aName => $aValue) {
579                                                         // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
580                                                         if ($aName != 'name' && $aName != 'type') {
581                                                                 $contentStr .= " $aName=\"$aValue\"";
582                                                         }
583                                                 }
584                                                 $contentStr .= "/>\n";
585                                         }
586                                 }
587                                 // compositor wraps elements
588                                 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
589                                         $contentStr = "  <$schemaPrefix:$attrs[compositor]>\n".$contentStr."  </$schemaPrefix:$attrs[compositor]>\n";
590                                 }
591                         }
592                         // attributes
593                         if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
594                                 foreach($attrs['attrs'] as $attr => $aParts){
595                                         $contentStr .= "    <$schemaPrefix:attribute";
596                                         foreach ($aParts as $a => $v) {
597                                                 if ($a == 'ref' || $a == 'type') {
598                                                         $contentStr .= " $a=\"".$this->contractQName($v).'"';
599                                                 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
600                                                         $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
601                                                         $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
602                                                 } else {
603                                                         $contentStr .= " $a=\"$v\"";
604                                                 }
605                                         }
606                                         $contentStr .= "/>\n";
607                                 }
608                         }
609                         // if restriction
610                         if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
611                                 $contentStr = "   <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr."   </$schemaPrefix:restriction>\n";
612                                 // complex or simple content
613                                 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
614                                         $contentStr = "  <$schemaPrefix:complexContent>\n".$contentStr."  </$schemaPrefix:complexContent>\n";
615                                 }
616                         }
617                         // finalize complex type
618                         if($contentStr != ''){
619                                 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
620                         } else {
621                                 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
622                         }
623                         $xml .= $contentStr;
624                 }
625                 // simple types
626                 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
627                         foreach($this->simpleTypes as $typeName => $eParts){
628                                 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n  <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\">\n";
629                                 if (isset($eParts['enumeration'])) {
630                                         foreach ($eParts['enumeration'] as $e) {
631                                                 $xml .= "  <$schemaPrefix:enumeration value=\"$e\"/>\n";
632                                         }
633                                 }
634                                 $xml .= "  </$schemaPrefix:restriction>\n </$schemaPrefix:simpleType>";
635                         }
636                 }
637                 // elements
638                 if(isset($this->elements) && count($this->elements) > 0){
639                         foreach($this->elements as $element => $eParts){
640                                 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
641                         }
642                 }
643                 // attributes
644                 if(isset($this->attributes) && count($this->attributes) > 0){
645                         foreach($this->attributes as $attr => $aParts){
646                                 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
647                         }
648                 }
649                 // finish 'er up
650                 $attr = '';
651                 foreach ($this->schemaInfo as $k => $v) {
652                         if ($k == 'elementFormDefault' || $k == 'attributeFormDefault') {
653                                 $attr .= " $k=\"$v\"";
654                         }
655                 }
656                 $el = "<$schemaPrefix:schema$attr targetNamespace=\"$this->schemaTargetNamespace\"\n";
657                 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
658                         $el .= " xmlns:$nsp=\"$ns\"";
659                 }
660                 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
661                 return $xml;
662         }
663
664         /**
665         * adds debug data to the clas level debug string
666         *
667         * @param    string $string debug data
668         * @access   private
669         */
670         function xdebug($string){
671                 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
672         }
673
674     /**
675     * get the PHP type of a user defined type in the schema
676     * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
677     * returns false if no type exists, or not w/ the given namespace
678     * else returns a string that is either a native php type, or 'struct'
679     *
680     * @param string $type name of defined type
681     * @param string $ns namespace of type
682     * @return mixed
683     * @access public
684     * @deprecated
685     */
686         function getPHPType($type,$ns){
687                 if(isset($this->typemap[$ns][$type])){
688                         //print "found type '$type' and ns $ns in typemap<br>";
689                         return $this->typemap[$ns][$type];
690                 } elseif(isset($this->complexTypes[$type])){
691                         //print "getting type '$type' and ns $ns from complexTypes array<br>";
692                         return $this->complexTypes[$type]['phpType'];
693                 }
694                 return false;
695         }
696
697         /**
698     * returns an associative array of information about a given type
699     * returns false if no type exists by the given name
700     *
701         *       For a complexType typeDef = array(
702         *       'restrictionBase' => '',
703         *       'phpType' => '',
704         *       'compositor' => '(sequence|all)',
705         *       'elements' => array(), // refs to elements array
706         *       'attrs' => array() // refs to attributes array
707         *       ... and so on (see addComplexType)
708         *       )
709         *
710         *   For simpleType or element, the array has different keys.
711     *
712     * @param string $type
713     * @return mixed
714     * @access public
715     * @see addComplexType
716     * @see addSimpleType
717     * @see addElement
718     */
719         function getTypeDef($type){
720                 //$this->debug("in getTypeDef for type $type");
721                 if (substr($type, -1) == '^') {
722                         $is_element = 1;
723                         $type = substr($type, 0, -1);
724                 } else {
725                         $is_element = 0;
726                 }
727
728                 if((! $is_element) && isset($this->complexTypes[$type])){
729                         $this->xdebug("in getTypeDef, found complexType $type");
730                         return $this->complexTypes[$type];
731                 } elseif((! $is_element) && isset($this->simpleTypes[$type])){
732                         $this->xdebug("in getTypeDef, found simpleType $type");
733                         if (!isset($this->simpleTypes[$type]['phpType'])) {
734                                 // get info for type to tack onto the simple type
735                                 // TODO: can this ever really apply (i.e. what is a simpleType really?)
736                                 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
737                                 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
738                                 $etype = $this->getTypeDef($uqType);
739                                 if ($etype) {
740                                         $this->xdebug("in getTypeDef, found type for simpleType $type:");
741                                         $this->xdebug($this->varDump($etype));
742                                         if (isset($etype['phpType'])) {
743                                                 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
744                                         }
745                                         if (isset($etype['elements'])) {
746                                                 $this->simpleTypes[$type]['elements'] = $etype['elements'];
747                                         }
748                                 }
749                         }
750                         return $this->simpleTypes[$type];
751                 } elseif(isset($this->elements[$type])){
752                         $this->xdebug("in getTypeDef, found element $type");
753                         if (!isset($this->elements[$type]['phpType'])) {
754                                 // get info for type to tack onto the element
755                                 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
756                                 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
757                                 $etype = $this->getTypeDef($uqType);
758                                 if ($etype) {
759                                         $this->xdebug("in getTypeDef, found type for element $type:");
760                                         $this->xdebug($this->varDump($etype));
761                                         if (isset($etype['phpType'])) {
762                                                 $this->elements[$type]['phpType'] = $etype['phpType'];
763                                         }
764                                         if (isset($etype['elements'])) {
765                                                 $this->elements[$type]['elements'] = $etype['elements'];
766                                         }
767                                         if (isset($etype['extensionBase'])) {
768                                                 $this->elements[$type]['extensionBase'] = $etype['extensionBase'];
769                                         }
770                                 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
771                                         $this->xdebug("in getTypeDef, element $type is an XSD type");
772                                         $this->elements[$type]['phpType'] = 'scalar';
773                                 }
774                         }
775                         return $this->elements[$type];
776                 } elseif(isset($this->attributes[$type])){
777                         $this->xdebug("in getTypeDef, found attribute $type");
778                         return $this->attributes[$type];
779                 } elseif (preg_match('/_ContainedType$/', $type)) {
780                         $this->xdebug("in getTypeDef, have an untyped element $type");
781                         $typeDef['typeClass'] = 'simpleType';
782                         $typeDef['phpType'] = 'scalar';
783                         $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
784                         return $typeDef;
785                 }
786                 $this->xdebug("in getTypeDef, did not find $type");
787                 return false;
788         }
789
790         /**
791     * returns a sample serialization of a given type, or false if no type by the given name
792     *
793     * @param string $type name of type
794     * @return mixed
795     * @access public
796     * @deprecated
797     */
798     function serializeTypeDef($type){
799         //print "in sTD() for type $type<br>";
800         if($typeDef = $this->getTypeDef($type)){
801                 $str .= '<'.$type;
802             if(is_array($typeDef['attrs'])){
803                 foreach($typeDef['attrs'] as $attName => $data){
804                     $str .= " $attName=\"{type = ".$data['type']."}\"";
805                 }
806             }
807             $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
808             if(count($typeDef['elements']) > 0){
809                 $str .= ">";
810                 foreach($typeDef['elements'] as $element => $eData){
811                     $str .= $this->serializeTypeDef($element);
812                 }
813                 $str .= "</$type>";
814             } elseif($typeDef['typeClass'] == 'element') {
815                 $str .= "></$type>";
816             } else {
817                 $str .= "/>";
818             }
819                         return $str;
820         }
821         return false;
822     }
823
824     /**
825     * returns HTML form elements that allow a user
826     * to enter values for creating an instance of the given type.
827     *
828     * @param string $name name for type instance
829     * @param string $type name of type
830     * @return string
831     * @access public
832     * @deprecated
833         */
834         function typeToForm($name,$type){
835                 // get typedef
836                 if($typeDef = $this->getTypeDef($type)){
837                         // if struct
838                         if($typeDef['phpType'] == 'struct'){
839                                 $buffer .= '<table>';
840                                 foreach($typeDef['elements'] as $child => $childDef){
841                                         $buffer .= "
842                                         <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
843                                         <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
844                                 }
845                                 $buffer .= '</table>';
846                         // if array
847                         } elseif($typeDef['phpType'] == 'array'){
848                                 $buffer .= '<table>';
849                                 for($i=0;$i < 3; $i++){
850                                         $buffer .= "
851                                         <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
852                                         <td><input type='text' name='parameters[".$name."][]'></td></tr>";
853                                 }
854                                 $buffer .= '</table>';
855                         // if scalar
856                         } else {
857                                 $buffer .= "<input type='text' name='parameters[$name]'>";
858                         }
859                 } else {
860                         $buffer .= "<input type='text' name='parameters[$name]'>";
861                 }
862                 return $buffer;
863         }
864         
865         /**
866         * adds a complex type to the schema
867         * 
868         * example: array
869         * 
870         * addType(
871         *       'ArrayOfstring',
872         *       'complexType',
873         *       'array',
874         *       '',
875         *       'SOAP-ENC:Array',
876         *       array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
877         *       'xsd:string'
878         * );
879         * 
880         * example: PHP associative array ( SOAP Struct )
881         * 
882         * addType(
883         *       'SOAPStruct',
884         *       'complexType',
885         *       'struct',
886         *       'all',
887         *       array('myVar'=> array('name'=>'myVar','type'=>'string')
888         * );
889         * 
890         * @param name
891         * @param typeClass (complexType|simpleType|attribute)
892         * @param phpType: currently supported are array and struct (php assoc array)
893         * @param compositor (all|sequence|choice)
894         * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
895         * @param elements = array ( name = array(name=>'',type=>'') )
896         * @param attrs = array(
897         *       array(
898         *               'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
899         *               "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
900         *       )
901         * )
902         * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
903         * @access public
904         * @see getTypeDef
905         */
906         function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
907                 $this->complexTypes[$name] = array(
908             'name'              => $name,
909             'typeClass' => $typeClass,
910             'phpType'   => $phpType,
911                 'compositor'=> $compositor,
912             'restrictionBase' => $restrictionBase,
913                 'elements'      => $elements,
914             'attrs'             => $attrs,
915             'arrayType' => $arrayType
916                 );
917                 
918                 $this->xdebug("addComplexType $name:");
919                 $this->appendDebug($this->varDump($this->complexTypes[$name]));
920         }
921         
922         /**
923         * adds a simple type to the schema
924         *
925         * @param string $name
926         * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
927         * @param string $typeClass (should always be simpleType)
928         * @param string $phpType (should always be scalar)
929         * @param array $enumeration array of values
930         * @access public
931         * @see nusoap_xmlschema
932         * @see getTypeDef
933         */
934         function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
935                 $this->simpleTypes[$name] = array(
936             'name'                      => $name,
937             'typeClass'         => $typeClass,
938             'phpType'           => $phpType,
939             'type'                      => $restrictionBase,
940             'enumeration'       => $enumeration
941                 );
942                 
943                 $this->xdebug("addSimpleType $name:");
944                 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
945         }
946
947         /**
948         * adds an element to the schema
949         *
950         * @param array $attrs attributes that must include name and type
951         * @see nusoap_xmlschema
952         * @access public
953         */
954         function addElement($attrs) {
955                 if (! $this->getPrefix($attrs['type'])) {
956                         $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
957                 }
958                 $this->elements[ $attrs['name'] ] = $attrs;
959                 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
960                 
961                 $this->xdebug("addElement " . $attrs['name']);
962                 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
963         }
964 }
965
966 /**
967  * Backward compatibility
968  */
969 class XMLSchema extends nusoap_xmlschema {
970 }
971
972
973 ?>