Merge branch 'master' of http://git.roojs.com/roobuilder
[roobuilder] / tools / flutter_nodes.php
1 <?php
2
3 class Obj {
4     
5     static $out_props = array(
6         'eClass' => array('props', 'events', 'methods'),
7         'eMixin' =>  array('props', 'events', 'methods'),
8         'eTypedef' => array('name', 'type', 'sig', 'static', 'desc','memberOf'),
9         'eProperty' => array('name', 'type', 'desc','memberOf'),
10         'eConstant' => array('name', 'type', 'desc','memberOf'),
11         'eMethod' => array('name', 'type', 'sig', 'static', 'desc','memberOf', 'isConstructor'),
12         'eConstructor' => array('name', 'type', 'sig', 'static', 'desc','memberOf', 'isConstructor'),
13         'eEnum' =>  array('name', 'type',   'desc','memberOf'), // fixme .. .values.?
14         'eFunction' =>  array('name', 'type', 'sig', 'static', 'desc','memberOf', 'isConstructor'),// fixme .. .memberof == package.
15         'Prop' => array('name', 'type', 'desc','memberOf'),
16     );
17     
18     var $href = '';
19     var $desc = '';
20     var $example = '';
21     var $isDeprecated = false;
22     
23     function __construct($ar)
24     {
25         foreach($ar as $k=>$v) {
26             $this->{$k} = $v;
27         }
28          
29     }
30     
31     
32     function parseHTML()
33     {
34         $dom = new DomDocument();
35         libxml_use_internal_errors(true);
36         echo "Reading : {$this->href}\n";
37         $dom->loadHTMLFile(FDIR . $this->href);
38         libxml_clear_errors();
39         $xp = new DomXPath($dom);
40         $desc = $this->getElementsByClassName($dom, 'desc');
41         if ($desc->length) {
42             $this->desc = $this->innerHTML($desc->item(0));
43         }
44         $sc = $this->getElementsByClassName($dom, 'source-code');
45         if ($sc->length) {
46             $this->example = $this->innerHTML($sc->item(0));
47         }
48         return $dom;
49     }
50     
51     function getElementsByClassName($dom, $class)
52     {
53         $xp = new DomXPath($dom);
54         return $xp->query("//*[contains(concat(' ', @class, ' '), ' ".$class." ')]");
55     }
56     function innerHTML($node)
57     {
58         if (!$node) {
59             print_r($this); 
60         }
61         $dom= $node->ownerDocument;
62         $ret = '';
63         foreach ($node->childNodes as $child) 
64         { 
65             $ret.= $dom->saveHTML($child);
66         }
67         return $ret;
68         
69     }
70     function parseType($sp)
71     {
72         if (!$sp) {
73             print_R($this);
74             echo "parseType got invalid value";
75          }
76         $ar = $sp->getElementsByTagName('a');
77         if (!$ar->length) {
78             $this->type = $sp->textContent;
79         }
80         if ($ar->length == 1) {
81             $this->type = eClass::$url_map[$ar->item(0)->getAttribute('href')]->name;
82             return;
83         } 
84         $this->types = array();
85         $t = '';
86         for($i =0;$i<$ar->length;$i++) {
87             $add = eClass::$url_map[$ar->item($i)->getAttribute('href')]->name;;
88             $this->types[] = $add;
89              
90             $t .= ($i == 0) ? $add : ('<'. $add );
91              
92         }
93         for($i =0;$i<$ar->length-1;$i++) {
94             $t .= '>';
95         }
96         $this->type = $t;
97         
98          
99     }
100     function toSummaryArray()
101     {
102         $ret = array();
103         if (!isset(self::$out_props[get_class($this)] )) {
104             die("don't know how to handle class: " . get_class($this));
105         }
106         foreach(self::$out_props[get_class($this)] as $k) {
107             $out = $this->{$k};
108             if (is_array($out)) {
109                 $out = array();
110                 foreach($this->{$k} as $v) {
111                     $out[] = $v->toSummaryArray();
112                 }
113             }
114             $ret[$k] = $out;
115         }
116         return $ret;
117     }
118     function isA($str)
119     {
120         return false;
121     }
122     
123 }
124
125 class Ns extends Obj {
126     static $tree = array();
127     static $kv = array();
128     var $name = '';
129     var $href = '';
130     var $cn = array();
131     var $isFakeNamespace = false;
132     function __construct($ar)
133     {
134         parent::__construct($ar);
135         
136         if ($this->isFakeNamespace) {
137             return;
138         }
139         
140         $bits=  explode('.', $this->name);
141         
142         self::$kv[$this->name] = $this;
143         
144         if (count($bits) == 1 ) {
145             self::$tree[] = $this;
146             return;
147         } 
148         array_pop($bits);
149         $par = implode('.', $bits);
150         $this->memberOf = $par;
151         self::add($this);
152         
153         
154     }
155     static function add($cls)
156     {
157         self::$kv[$cls->memberOf]->cn[] = $cls;
158     }
159     
160     function fakeTree()
161     {
162          
163         foreach($this->cn as $c) {
164             if (!isset($c->shortname))  {
165                 continue;
166             }
167             $map[$c->shortname] = $c;
168              
169               
170             
171         }
172         
173         $cn = array();
174         foreach($this->cn as $c) {
175             if (!isset($c->shortname))  {
176                 continue;
177             }
178             $bits = preg_split('/(?<=[a-z])(?=[A-Z])|(?=[A-Z][a-z])/',
179                                  $c->shortname, -1, PREG_SPLIT_NO_EMPTY);
180             //print_r($bits);
181             if (count($bits) < 2 ) {
182                 $cn[] = $c;
183                 continue;   
184             }
185             if (!isset($map[$bits[0]])) {
186                 $add = new Ns(array(
187                     'name' => $c->memberOf .'.'. $bits[0],
188                     'isFakeNamespace' => true,
189                     
190                 ));
191                 $map[$bits[0]] = $add;
192                 $cn[] = $add;
193             }
194             
195             $map[$bits[0]]->cn[] = $c;
196             
197         }
198         
199         // finally remove from tree if it's saving '1'
200         $cc = array();
201         foreach($cn as $c) {
202             if (empty($c->isFakeNamespace)) {
203                 $cc[] = $c;
204                 continue;
205             }
206             if (count($c->cn) < 2) {
207                 $cc[] = $c->cn[0];
208                 continue;
209             }
210             $cc[] = $c;
211             
212         }
213         
214         $this->cn = $cc;
215         
216  
217     }
218     
219     function toTreeArray()
220     {
221         // tidy the tree before starting...
222         
223         
224         
225         $ret = array(
226             'name' => $this->name,
227             'is_class' => false,
228             'cn' => array()
229         );
230         // in theory flutter has a flat tree... ?
231         foreach($this->cn as $e) {
232            if (in_array(get_class($e) , array('eClass', 'eMixin', 'Ns'))) {
233                 $ret['cn'][] = $e->toTreeArray();
234             }
235             
236         }
237         return $ret;
238     }
239 }
240
241
242 class eClass extends Obj {
243     
244     static $all = array();
245     static $url_map = array();
246     var $name;
247     var $extends = array();
248     var $memberOf; /// really the package..
249     var $events = array();
250     var $methods = array();
251     var $props = array();
252     var $isMixin = false;
253     var $isEnum = false;
254     var $isTypedef = false;
255     var $isConstant = false;
256     var $isAbstract = false;
257     var $implementors = array();
258     var $realImplementors = array();
259     var $cn = array();
260     function __construct($ar)
261     {
262         parent::__construct($ar);
263         $bits = explode('.', $this->name);
264         $this->shortname  = array_pop($bits);
265         
266         self::$all[$this->name] = $this;
267         self::$url_map[$this->href] = $this;
268         Ns::add($this);
269     }
270     
271     function parseHTML()
272     {
273         // do children first..
274         
275         
276         
277         $dom = parent::parseHTML();
278         
279         $sc = $this->getElementsByClassName($dom,'self-crumb');
280         if ($sc->length) {
281             // abstracts actually impletment stuff in flutter...
282             if (preg_match('/abstract class/', $this->innerHTML($sc->item(0)))) {
283                 $this->isAbstract = true;
284             }
285              
286         }
287         
288         
289         
290         $dl = $dom->getElementsByTagName('dl')->item(0);
291         if ($dl->getAttribute('class') != 'dl-horizontal') {
292             $this->extends = array();
293             return;
294         }
295         
296         if (strpos($this->innerHTML($dl), '@deprecated')) {
297              $this->isDeprecated = true;
298         }
299         
300         
301         
302         $dt = $dl->getElementsByTagName('dt');
303         if (!$dt->length) {
304             return;
305         }
306         if ($this->innerHTML($dt->item(0)) != 'Inheritance') {
307             return;
308         }
309         
310         $dd = $dl->getElementsByTagName('dd');
311         if (!$dd->length) {
312             return;
313         }
314         $as = $dd->item(0)->getElementsByTagName('a');
315         $this->extends = array();        
316         for($i = $as->length-1;$i > -1; $i--) {
317
318             if (!isset(self::$url_map[$as->item($i)->getAttribute('href')])) {
319                 die("could not find " . $as->item($i)->getAttribute('href') . " when parsing" . $this->href);
320             }
321             
322             $this->extends[] = self::$url_map[$as->item($i)->getAttribute('href')]->name;
323             self::$url_map[$as->item($i)->getAttribute('href')]->addImplementor($this->name);
324         }
325         
326         
327         
328          
329         
330     }
331     function addImplementor($n)
332     {
333         if (!in_array($n, $this->implementors)) {
334             $this->implementors[] = $n;
335         }
336     }
337     function expandImplementors($exclude = array())
338     {
339         $exclude[] = $this->name;
340         $add = array();
341         $orig = $this->implementors;
342         foreach($orig as $c) {
343             if (in_array($c, $exclude)) {
344                 continue;
345             }
346             $cl = self::$all[$c]->expandImplementors($exclude);
347             foreach($cl as $cc) {
348                 if (!in_array($cc, $this->implementors)) {
349                     $this->implementors[]= $cc;
350                 }
351             }
352         }
353         return $this->implementors;
354         
355     }
356     function realImplementors()
357     {
358         
359         $this->realImplementors  = array();
360         foreach($this->implementors as $c) {
361             if (self::$all[$c]->isAbstract) {
362                 return;
363             }
364             $this->realImplementors[] = $c;
365         }
366         
367     }
368     
369     function readDocs()
370     {
371         $this->parseHTML();
372         foreach($this->events as $e) {
373             $e->parseHTML();
374         }
375         foreach($this->methods as $e) {
376             $e->parseHTML();
377         }
378         foreach($this->props as $e) {
379             $e->parseHTML();
380         }
381         // loop through children.
382         
383     }
384     function toTreeArray()
385     {
386         $cn = array();
387         foreach($this->cn as $e) {
388             if (in_array(get_class($e) , array('eClass', 'eMixin'))) {
389                 $cn[] = $e->toTreeArray();
390             }
391         }
392         $child = $this->prop('child');
393         $child = $child ? $child : $this->prop('children');
394         $child = $child ? $child : $this->prop('home'); // MaterialApp??
395         // above might be a constant... - technically we could work out the type of that..
396         if ($child && !in_array(get_class($child), array( 'eProperty', 'Prop')))  {
397             $child = false;
398         }
399         $childtypes = 0;
400         $childtype = '';
401         
402         
403         // to complicated to check if these are widget children ... some are wrappers around
404         
405         if ($child ) {
406             $childtypes = $child->isA('dart:core.List') ? 2 : 1;
407             $childtype = count($child->types) ? array_pop($child->types) : $child->type;
408             
409         }  
410         
411         return array(
412             'name' => $this->name,
413             'is_class' => true,
414             'cn' => $cn,
415             'extends' => $this->extends,
416             'is_abstract' => $this->isAbstract,
417             'childtypes' => $childtypes,
418             'childtype' => $childtype,
419             'implementors' => $this->realImplementors, // this is not really complete...
420         );
421     }
422     function isA($name)
423     {
424         return in_array($name,$this->extends);
425     }
426     function prop($name)
427     {
428         foreach($this->props as $p) {
429             if ($p->name == $name) {
430                 return $p;
431             }
432         }
433         return false;
434     }
435     
436     
437 }
438 class eMixin extends eClass
439 {
440     function parseHTML()
441     {   
442         $dom = Obj::parseHTML();
443     }
444 }
445 class eConstant extends Obj
446 {
447     var $type = '';
448     function parseHTML()
449     {   
450         $dom = Obj::parseHTML();
451     }
452     
453 }
454 class eEnum extends eClass // enums look alot like classes..
455 {
456     var $type = '';
457     function parseHTML()
458     {   
459         $dom = Obj::parseHTML();
460     }
461 }
462
463 class eProperty extends Obj
464 {
465      var $name = '';
466     var $type = '?';
467     var $desc = '';
468     var $memberOf = '';
469     function parseHTML()
470     {   
471         $dom = Obj::parseHTML();
472     }
473 }
474
475 class  Prop extends Obj {
476     var $name = '';
477     var $type = '';
478     var $types = array(); // generics...
479     var $desc = '';
480     var $memberOf = '';
481     var $isConstant = false;
482     function parseHTML()
483     {   
484         $dom = Obj::parseHTML();
485         // work out the type..
486         $rt = $this->getElementsByClassName($dom, 'returntype');
487         $this->parseType($rt->item(0));
488     }
489     function isA($name)
490     {
491         if (empty($this->types)) {
492             return $name == $this->type;
493         }
494         
495         if (in_array($name,$this->types)) {
496             return true;
497         }
498         foreach($this->types as $ty) {
499             if (!isset(eClass::$all[$ty])) {
500                 print_R($this);
501                 die("could not find type $ty\n");
502             }
503             if (in_array($name, eClass::$all[$ty]->extends)) {
504                 return true;
505             }
506         }
507         return false;
508     }
509 }
510
511
512
513 class  eMethod extends Obj {  // doubles up for events? - normally 'on' is the name
514     var $name = '';
515     var $type = ''; // return...
516     var $desc = '';
517     var $static = false;
518     var $memberOf = '';
519     var $sig = '';
520     var $params  = array();
521     
522       //"isStatic" : false,
523     var $isConstructor = false;
524     //"example" : "",
525     //  "deprecated" : "",
526     //  "since" : "",
527      // "see" : "",
528     // return_desc
529     function parseHTML()
530     {
531         
532         $dom = parent::parseHTML();
533         $sp = $this->getElementsByClassName($dom,'returntype')->item(0);
534         $this->parseType($sp);
535             
536         // params...
537         $ar = $this->getElementsByClassName($dom,'parameter');
538         for($i =0;$i<$ar->length;$i++) {
539             $this->params[] = new Param( $ar->item($i) );
540         }
541         
542         return $dom;
543     }
544     
545 }
546 class eConstructor extends eMethod {
547     function parseHTML()
548     {
549         
550         $dom = Obj::parseHTML();
551         // doesnt have a 'type'    
552         // params...
553         $ar = $this->getElementsByClassName($dom,'parameter');
554         for($i =0;$i<$ar->length;$i++) {
555             $this->params[] = new Param( $ar->item($i) );
556         }
557         
558         return $dom;
559     }
560 }
561
562
563 class  eFunction extends eMethod
564 {
565     function __construct($ar)
566     {
567         parent::__construct($ar);
568         eClass::$all[$this->name] = $this;
569         eClass::$url_map[$this->href] = $this;
570         Ns::add($this);
571     }
572     function readDocs()
573     {
574         $this->parseHTML();
575         // loop through children.
576         
577     }
578     function parseHTML()
579     {
580         
581         $dom = parent::parseHTML();
582     }
583 }
584 class eTypedef extends eFunction
585 {
586      
587 }
588 class Param extends Obj {
589     var $name = '';
590     var $type = '';
591     var $desc = '';
592     var $isOptional = true;
593     function __construct($node)
594     {
595         
596         $ar  = $node->getElementsByTagName('span');
597         if (!$ar->length) {
598             echo "mssing paramter info", $this->innerHTML($node); exit;
599         }
600         for($i = 0; $i < $ar->length; $i++) {
601             
602             switch($ar->item($i)->getAttribute('class')) {
603                 case 'parameter-name':
604                     $this->name = $ar->item($i)->textContent;
605                     break;
606                 case 'type-annotation':
607                     $this->parseType($ar->item($i));
608                     break;
609                 
610             }
611         }
612         
613         
614         
615     }
616 }