1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153:
154: class CWsdlGenerator extends CComponent
155: {
156: const STYLE_RPC = 'rpc';
157: const STYLE_DOCUMENT = 'document';
158: const USE_ENCODED = 'encoded';
159: const USE_LITERAL = 'literal';
160: 161: 162: 163:
164: public $namespace;
165: 166: 167: 168:
169: public $serviceName;
170: 171: 172: 173:
174: public $operationBodyStyle = array(
175: 'use' => self::USE_ENCODED,
176: 'encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/',
177: );
178: 179: 180: 181:
182: public $bindingStyle = self::STYLE_RPC;
183: 184: 185: 186:
187: public $bindingTransport = 'http://schemas.xmlsoap.org/soap/http';
188:
189: protected static $typeMap=array(
190: 'string'=>'xsd:string',
191: 'str'=>'xsd:string',
192: 'int'=>'xsd:int',
193: 'integer'=>'xsd:integer',
194: 'float'=>'xsd:float',
195: 'double'=>'xsd:float',
196: 'bool'=>'xsd:boolean',
197: 'boolean'=>'xsd:boolean',
198: 'date'=>'xsd:date',
199: 'time'=>'xsd:time',
200: 'datetime'=>'xsd:dateTime',
201: 'array'=>'soap-enc:Array',
202: 'object'=>'xsd:struct',
203: 'mixed'=>'xsd:anyType',
204: );
205:
206: 207: 208: 209:
210: protected $operations;
211:
212: 213: 214: 215: 216:
217: protected $types;
218:
219: 220: 221:
222: protected $elements;
223:
224: 225: 226:
227: protected $messages;
228:
229: 230: 231: 232: 233: 234: 235:
236: public function generateWsdl($className, $serviceUrl, $encoding='UTF-8')
237: {
238: $this->operations=array();
239: $this->types=array();
240: $this->elements=array();
241: $this->messages=array();
242: if($this->serviceName===null)
243: $this->serviceName=$className;
244: if($this->namespace===null)
245: $this->namespace='urn:'.str_replace('\\','/',$className).'wsdl';
246:
247: $reflection=new ReflectionClass($className);
248: foreach($reflection->getMethods() as $method)
249: {
250: if($method->isPublic())
251: $this->processMethod($method);
252: }
253:
254: $wsdl=$this->buildDOM($serviceUrl,$encoding)->saveXML();
255:
256: if(isset($_GET['makedoc']))
257: $this->buildHtmlDocs();
258:
259: return $wsdl;
260: }
261:
262: 263: 264:
265: protected function processMethod($method)
266: {
267: $comment=$method->getDocComment();
268: if(strpos($comment,'@soap')===false)
269: return;
270: $comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n"));
271:
272: $methodName=$method->getName();
273: $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
274: $params=$method->getParameters();
275: $message=array();
276: $headers=array();
277: $n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
278: if($n>count($params))
279: $n=count($params);
280: if ($this->bindingStyle == self::STYLE_RPC)
281: {
282: for($i=0;$i<$n;++$i)
283: $message[$params[$i]->getName()]=array(
284: 'type'=>$this->processType($matches[1][$i]),
285: 'doc'=>trim($matches[3][$i]),
286: );
287: }
288: else
289: {
290: $this->elements[$methodName] = array();
291: for($i=0;$i<$n;++$i)
292: $this->elements[$methodName][$params[$i]->getName()]=array(
293: 'type'=>$this->processType($matches[1][$i]),
294: 'nillable'=>$params[$i]->isOptional(),
295: );
296: $message['parameters'] = array('element'=>'tns:'.$methodName);
297: }
298:
299: $this->messages[$methodName.'In']=$message;
300:
301: $n=preg_match_all('/^@header\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches);
302: for($i=0;$i<$n;++$i)
303: {
304: $name = $matches[1][$i];
305: $type = $this->processType($matches[1][$i]);
306: $doc = trim($matches[3][$i]);
307: if ($this->bindingStyle == self::STYLE_RPC)
308: {
309: $headers[$name]=array($type,$doc);
310: }
311: else
312: {
313: $this->elements[$name][$name]=array('type'=>$type);
314: $headers[$name] = array('element'=>$type);
315: }
316: }
317:
318: if ($headers !== array())
319: {
320: $this->messages[$methodName.'Headers']=$headers;
321: $headerKeys = array_keys($headers);
322: $firstHeaderKey = reset($headerKeys);
323: $firstHeader = $headers[$firstHeaderKey];
324: }
325: else
326: {
327: $firstHeader = null;
328: }
329:
330: if ($this->bindingStyle == self::STYLE_RPC)
331: {
332: if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
333: $return=array(
334: 'type'=>$this->processType($matches[1]),
335: 'doc'=>trim($matches[2]),
336: );
337: else
338: $return=null;
339: $this->messages[$methodName.'Out']=array('return'=>$return);
340: }
341: else
342: {
343: if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches))
344: {
345: $this->elements[$methodName.'Response'][$methodName.'Result']=array(
346: 'type'=>$this->processType($matches[1]),
347: );
348: }
349: $this->messages[$methodName.'Out']=array('parameters'=>array('element'=>'tns:'.$methodName.'Response'));
350: }
351:
352: if(preg_match('/^\/\*+\s*([^@]*?)\n@/s',$comment,$matches))
353: $doc=trim($matches[1]);
354: else
355: $doc='';
356: $this->operations[$methodName]=array(
357: 'doc'=>$doc,
358: 'headers'=>$firstHeader === null ? null : array('input'=>array($methodName.'Headers', $firstHeaderKey)),
359: );
360: }
361:
362: 363: 364:
365: protected function processType($type)
366: {
367: if(isset(self::$typeMap[$type]))
368: return self::$typeMap[$type];
369: elseif(isset($this->types[$type]))
370: return is_array($this->types[$type]) ? 'tns:'.$type : $this->types[$type];
371: elseif(($pos=strpos($type,'[]'))!==false)
372: {
373: $type=substr($type,0,$pos);
374: $this->types[$type.'[]']='tns:'.$type.'Array';
375: $this->processType($type);
376: return $this->types[$type.'[]'];
377: }
378: else
379: {
380: $type=Yii::import($type,true);
381: $class=new ReflectionClass($type);
382:
383: $comment=$class->getDocComment();
384: $comment=strtr($comment,array("\r\n"=>"\n","\r"=>"\n"));
385: $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment);
386:
387:
388:
389: if(preg_match('/^@soap-indicator\s+(\w+)\s*?(.*)$/im', $comment, $matches))
390: {
391: $indicator=$matches[1];
392: $attributes=$this->getWsdlElementAttributes($matches[2]);
393: }else{
394: $indicator='all';
395: $attributes=$this->getWsdlElementAttributes('');
396: }
397:
398: $custom_wsdl=false;
399: if(preg_match_all('/^@soap-wsdl\s+(\S.*)$/im',$comment,$matches)>0)
400: $custom_wsdl=implode("\n", $matches[1]);
401:
402: $this->types[$type]=array(
403: 'indicator'=>$indicator,
404: 'nillable'=>$attributes['nillable'],
405: 'minOccurs'=>$attributes['minOccurs'],
406: 'maxOccurs'=>$attributes['maxOccurs'],
407: 'custom_wsdl'=>$custom_wsdl,
408: 'properties'=>array()
409: );
410:
411: foreach($class->getProperties() as $property)
412: {
413: $comment=$property->getDocComment();
414: if($property->isPublic() && strpos($comment,'@soap')!==false)
415: {
416: if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/mi',$comment,$matches))
417: {
418: $attributes=$this->getWsdlElementAttributes($matches[3]);
419:
420: if(preg_match('/{(.+)}/',$comment,$attr))
421: $matches[3]=str_replace($attr[0],'',$matches[3]);
422:
423:
424: $example='';
425: if(preg_match("/@example[:]?(.+)/mi",$comment,$match))
426: $example=trim($match[1]);
427:
428: $this->types[$type]['properties'][$property->getName()]=array(
429: $this->processType($matches[1]),
430: trim($matches[3]),
431: $attributes['nillable'],
432: $attributes['minOccurs'],
433: $attributes['maxOccurs'],
434: $example
435: );
436: }
437: }
438: }
439: return 'tns:'.$type;
440: }
441: }
442:
443: 444: 445: 446:
447: protected function getWsdlElementAttributes($comment) {
448: $nillable=$minOccurs=$maxOccurs=null;
449: if(preg_match('/{(.+)}/',$comment,$attr))
450: {
451: if(preg_match_all('/((\w+)\s*=\s*(\w+))/mi',$attr[1],$attr))
452: {
453: foreach($attr[2] as $id=>$prop)
454: {
455: $prop=strtolower($prop);
456: $val=strtolower($attr[3][$id]);
457: if($prop=='nillable'){
458: if($val=='false' || $val=='true')
459: $nillable=$val;
460: else
461: $nillable=$val ? 'true' : 'false';
462: }elseif($prop=='minoccurs')
463: $minOccurs=intval($val);
464: elseif($prop=='maxoccurs')
465: $maxOccurs=($val=='unbounded') ? 'unbounded' : intval($val);
466: }
467: }
468: }
469: return array(
470: 'nillable'=>$nillable,
471: 'minOccurs'=>$minOccurs,
472: 'maxOccurs'=>$maxOccurs
473: );
474: }
475:
476: 477: 478: 479: 480: 481:
482: protected function injectDom(DOMDocument $dom, DOMElement $target, DOMNode $source)
483: {
484: if ($source->nodeType!=XML_ELEMENT_NODE)
485: return;
486:
487: $import=$dom->createElement($source->nodeName);
488:
489: foreach($source->attributes as $attr)
490: $import->setAttribute($attr->name,$attr->value);
491:
492: foreach($source->childNodes as $child)
493: $this->injectDom($dom,$import,$child);
494:
495: $target->appendChild($import);
496: }
497:
498: 499: 500: 501:
502: protected function buildDOM($serviceUrl,$encoding)
503: {
504: $xml="<?xml version=\"1.0\" encoding=\"$encoding\"?>
505: <definitions name=\"{$this->serviceName}\" targetNamespace=\"{$this->namespace}\"
506: xmlns=\"http://schemas.xmlsoap.org/wsdl/\"
507: xmlns:tns=\"{$this->namespace}\"
508: xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"
509: xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
510: xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\"
511: xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"></definitions>";
512:
513: $dom=new DOMDocument();
514: $dom->formatOutput=true;
515: $dom->loadXml($xml);
516: $this->addTypes($dom);
517:
518: $this->addMessages($dom);
519: $this->addPortTypes($dom);
520: $this->addBindings($dom);
521: $this->addService($dom,$serviceUrl);
522:
523: return $dom;
524: }
525:
526: 527: 528:
529: protected function addTypes($dom)
530: {
531: if($this->types===array() && $this->elements===array())
532: return;
533: $types=$dom->createElement('wsdl:types');
534: $schema=$dom->createElement('xsd:schema');
535: $schema->setAttribute('targetNamespace',$this->namespace);
536: foreach($this->types as $phpType=>$xmlType)
537: {
538: if(is_string($xmlType) && strrpos($xmlType,'Array')!==strlen($xmlType)-5)
539: continue;
540: $complexType=$dom->createElement('xsd:complexType');
541: if(is_string($xmlType))
542: {
543: if(($pos=strpos($xmlType,'tns:'))!==false)
544: $complexType->setAttribute('name',substr($xmlType,4));
545: else
546: $complexType->setAttribute('name',$xmlType);
547:
548: $arrayType = ($dppos=strpos($xmlType,':')) !==false ? substr($xmlType,$dppos + 1) : $xmlType;
549: $arrayType = substr($arrayType,0,-5);
550: if ($this->operationBodyStyle['use'] == self::USE_ENCODED)
551: {
552: $complexContent=$dom->createElement('xsd:complexContent');
553: $restriction=$dom->createElement('xsd:restriction');
554: $restriction->setAttribute('base','soap-enc:Array');
555: $attribute=$dom->createElement('xsd:attribute');
556: $attribute->setAttribute('ref','soap-enc:arrayType');
557: $attribute->setAttribute('arrayType',(isset(self::$typeMap[$arrayType]) ? 'xsd:' : 'tns:') .$arrayType.'[]');
558:
559: $restriction->appendChild($attribute);
560: $complexContent->appendChild($restriction);
561: $complexType->appendChild($complexContent);
562: }
563: else
564: {
565: $sequence=$dom->createElement('xsd:sequence');
566: $element=$dom->createElement('xsd:element');
567: $element->setAttribute('name','item');
568: $element->setAttribute('type',(isset(self::$typeMap[$arrayType]) ? self::$typeMap[$arrayType] : 'tns:'.$arrayType));
569: $element->setAttribute('minOccurs','0');
570: $element->setAttribute('maxOccurs','unbounded');
571: $sequence->appendChild($element);
572: $complexType->appendChild($sequence);
573: }
574: }
575: elseif(is_array($xmlType))
576: {
577: $complexType->setAttribute('name',$phpType);
578: if($xmlType['custom_wsdl']!==false)
579: {
580: $custom_dom=new DOMDocument();
581: $custom_dom->loadXML('<root xmlns:xsd="http://www.w3.org/2001/XMLSchema">'.$xmlType['custom_wsdl'].'</root>');
582: foreach($custom_dom->documentElement->childNodes as $el)
583: $this->injectDom($dom,$complexType,$el);
584: }else{
585: $all=$dom->createElement('xsd:' . $xmlType['indicator']);
586:
587: if(!is_null($xmlType['minOccurs']))
588: $all->setAttribute('minOccurs',$xmlType['minOccurs']);
589: if(!is_null($xmlType['maxOccurs']))
590: $all->setAttribute('maxOccurs',$xmlType['maxOccurs']);
591: if(!is_null($xmlType['nillable']))
592: $all->setAttribute('nillable',$xmlType['nillable']);
593:
594: foreach($xmlType['properties'] as $name=>$type)
595: {
596: $element=$dom->createElement('xsd:element');
597: if(!is_null($type[3]))
598: $element->setAttribute('minOccurs',$type[3]);
599: if(!is_null($type[4]))
600: $element->setAttribute('maxOccurs',$type[4]);
601: if(!is_null($type[2]))
602: $element->setAttribute('nillable',$type[2]);
603: $element->setAttribute('name',$name);
604: $element->setAttribute('type',$type[0]);
605: $all->appendChild($element);
606: }
607: $complexType->appendChild($all);
608: }
609: }
610: $schema->appendChild($complexType);
611: }
612: foreach($this->elements as $name=>$parameters)
613: {
614: $element=$dom->createElement('xsd:element');
615: $element->setAttribute('name',$name);
616: $complexType=$dom->createElement('xsd:complexType');
617: if (!empty($parameters))
618: {
619: $sequence=$dom->createElement('xsd:sequence');
620: foreach($parameters as $paramName=>$paramOpts)
621: {
622: $innerElement=$dom->createElement('xsd:element');
623: $innerElement->setAttribute('name',$paramName);
624: $innerElement->setAttribute('type',$paramOpts['type']);
625: if (isset($paramOpts['nillable']) && $paramOpts['nillable'])
626: {
627: $innerElement->setAttribute('nillable','true');
628: }
629: $sequence->appendChild($innerElement);
630: }
631: $complexType->appendChild($sequence);
632: }
633: $element->appendChild($complexType);
634: $schema->appendChild($element);
635: }
636: $types->appendChild($schema);
637: $dom->documentElement->appendChild($types);
638: }
639:
640: 641: 642:
643: protected function addMessages($dom)
644: {
645: foreach($this->messages as $name=>$message)
646: {
647: $element=$dom->createElement('wsdl:message');
648: $element->setAttribute('name',$name);
649: foreach($this->messages[$name] as $partName=>$part)
650: {
651: if(is_array($part))
652: {
653: $partElement=$dom->createElement('wsdl:part');
654: $partElement->setAttribute('name',$partName);
655: if (isset($part['type']))
656: {
657: $partElement->setAttribute('type',$part['type']);
658: }
659: if (isset($part['element']))
660: {
661: $partElement->setAttribute('element',$part['element']);
662: }
663: $element->appendChild($partElement);
664: }
665: }
666: $dom->documentElement->appendChild($element);
667: }
668: }
669:
670: 671: 672:
673: protected function addPortTypes($dom)
674: {
675: $portType=$dom->createElement('wsdl:portType');
676: $portType->setAttribute('name',$this->serviceName.'PortType');
677: $dom->documentElement->appendChild($portType);
678: foreach($this->operations as $name=>$operation)
679: $portType->appendChild($this->createPortElement($dom,$name,$operation['doc']));
680: }
681:
682: 683: 684: 685: 686:
687: protected function createPortElement($dom,$name,$doc)
688: {
689: $operation=$dom->createElement('wsdl:operation');
690: $operation->setAttribute('name',$name);
691:
692: $input=$dom->createElement('wsdl:input');
693: $input->setAttribute('message', 'tns:'.$name.'In');
694: $output=$dom->createElement('wsdl:output');
695: $output->setAttribute('message', 'tns:'.$name.'Out');
696:
697: $operation->appendChild($dom->createElement('wsdl:documentation',$doc));
698: $operation->appendChild($input);
699: $operation->appendChild($output);
700:
701: return $operation;
702: }
703:
704: 705: 706:
707: protected function addBindings($dom)
708: {
709: $binding=$dom->createElement('wsdl:binding');
710: $binding->setAttribute('name',$this->serviceName.'Binding');
711: $binding->setAttribute('type','tns:'.$this->serviceName.'PortType');
712:
713: $soapBinding=$dom->createElement('soap:binding');
714: $soapBinding->setAttribute('style',$this->bindingStyle);
715: $soapBinding->setAttribute('transport',$this->bindingTransport);
716: $binding->appendChild($soapBinding);
717:
718: $dom->documentElement->appendChild($binding);
719:
720: foreach($this->operations as $name=>$operation)
721: $binding->appendChild($this->createOperationElement($dom,$name,$operation['headers']));
722: }
723:
724: 725: 726: 727: 728:
729: protected function createOperationElement($dom,$name,$headers=null)
730: {
731: $operation=$dom->createElement('wsdl:operation');
732: $operation->setAttribute('name', $name);
733: $soapOperation=$dom->createElement('soap:operation');
734: $soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name);
735: if ($this->bindingStyle == self::STYLE_RPC)
736: {
737: $soapOperation->setAttribute('style', self::STYLE_RPC);
738: }
739:
740: $input=$dom->createElement('wsdl:input');
741: $output=$dom->createElement('wsdl:output');
742:
743: $soapBody=$dom->createElement('soap:body');
744: $operationBodyStyle=$this->operationBodyStyle;
745: if ($this->bindingStyle == self::STYLE_RPC && !isset($operationBodyStyle['namespace']))
746: {
747: $operationBodyStyle['namespace'] = $this->namespace;
748: }
749: foreach($operationBodyStyle as $attributeName=>$attributeValue)
750: {
751: $soapBody->setAttribute($attributeName, $attributeValue);
752: }
753: $input->appendChild($soapBody);
754: $output->appendChild(clone $soapBody);
755: if (is_array($headers))
756: {
757: if (isset($headers['input']) && is_array($headers['input']) && count($headers['input'])==2)
758: {
759: $soapHeader = $dom->createElement('soap:header');
760: foreach($operationBodyStyle as $attributeName=>$attributeValue) {
761: $soapHeader->setAttribute($attributeName, $attributeValue);
762: }
763: $soapHeader->setAttribute('message', $headers['input'][0]);
764: $soapHeader->setAttribute('part', $headers['input'][1]);
765: $input->appendChild($soapHeader);
766: }
767: if (isset($headers['output']) && is_array($headers['output']) && count($headers['output'])==2)
768: {
769: $soapHeader = $dom->createElement('soap:header');
770: foreach($operationBodyStyle as $attributeName=>$attributeValue) {
771: $soapHeader->setAttribute($attributeName, $attributeValue);
772: }
773: $soapHeader->setAttribute('message', $headers['output'][0]);
774: $soapHeader->setAttribute('part', $headers['output'][1]);
775: $output->appendChild($soapHeader);
776: }
777: }
778:
779: $operation->appendChild($soapOperation);
780: $operation->appendChild($input);
781: $operation->appendChild($output);
782:
783: return $operation;
784: }
785:
786: 787: 788: 789:
790: protected function addService($dom,$serviceUrl)
791: {
792: $service=$dom->createElement('wsdl:service');
793: $service->setAttribute('name', $this->serviceName.'Service');
794:
795: $port=$dom->createElement('wsdl:port');
796: $port->setAttribute('name', $this->serviceName.'Port');
797: $port->setAttribute('binding', 'tns:'.$this->serviceName.'Binding');
798:
799: $soapAddress=$dom->createElement('soap:address');
800: $soapAddress->setAttribute('location',$serviceUrl);
801: $port->appendChild($soapAddress);
802: $service->appendChild($port);
803: $dom->documentElement->appendChild($service);
804: }
805:
806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823:
824: public function buildHtmlDocs($return=false)
825: {
826: $html='<html><head>';
827: $html.='<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
828: $html.='<style type="text/css">
829: table{border-collapse: collapse;background-color: #DDDDDD;}
830: tr{background-color: #FFFFFF;}
831: th{background-color: #EEEEEE;}
832: th, td{font-size: 12px;font-family: courier;padding: 3px;}
833: </style>';
834: $html.='</head><body>';
835: $html.='<h2>WSDL documentation for service '.$this->serviceName.'</h2>';
836: $html.='<p>Generated on '.date('d.m.Y H:i:s').'</p>';
837: $html.='<table border="0" cellspacing="1" cellpadding="1">';
838: $html.='<tr><td>';
839:
840: if(!empty($this->types))
841: {
842: foreach($this->types as $object=>$options){
843: if(!is_array($options) || empty($options) || !is_array($options['properties']) || empty($options['properties'])){
844: continue;
845: }
846: $params=$options['properties'];
847: $html.="\n\n<h3>Object: {$object}</h3>";
848: $html.='<table border="1" cellspacing="1" cellpadding="1">';
849: $html.='<tr><th>#</th><th>Attribute</th><th>Type</th><th>Nill</th><th>Min</th><th>Max</th><th>Description</th><th>Example</th></tr>';
850: $c=0;
851: foreach($params as $param=>$prop){
852: ++$c;
853: $html.="\n<tr>"
854: ."\n\t<td>{$c}</td>"
855: ."\n\t<td>{$param}</td>"
856: ."\n\t<td>".(str_replace('xsd:','',$prop[0]))."</td>"
857: ."\n\t<td>".$prop[2]."</td>"
858: ."\n\t<td>".($prop[3]==null ? ' ' : $prop[3])."</td>"
859: ."\n\t<td>".($prop[4]==null ? ' ' : $prop[4])."</td>"
860: ."\n\t<td>{$prop[1]}</td>"
861: ."\n\t<td>".(trim($prop[5])=='' ? ' ' : $prop[5])."</td>"
862: ."\n</tr>";
863: }
864: $html.="\n</table><br/>";
865: }
866: }
867: else
868: $html.='No complex data type found!';
869:
870: $html.='</td></tr></table></body></html>';
871:
872: if($return)
873: return $html;
874:
875: echo $html;
876: Yii::app()->end();
877: }
878: }
879: