/[cvs]/nfo/php/libs/org.netfrag.glib/Data/Lift/hash/topic/EasyTree.php
ViewVC logotype

Contents of /nfo/php/libs/org.netfrag.glib/Data/Lift/hash/topic/EasyTree.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (show annotations)
Tue May 13 15:25:44 2003 UTC (21 years, 4 months ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +498 -101 lines
revamped completely

1 <?
2 /**
3 * This file contains a Data::Lift actor component:
4 * Data::Lift::hash::topic::EasyTree
5 * Helper class to negotiate tree node browsing against a remote service.
6 *
7 * @author Andreas Motl <andreas.motl@ilo.de>
8 * @package org.netfrag.glib
9 * @name Data::Lift::hash::topic::EasyTree
10 *
11 */
12
13 /**
14 * Cvs-Log:
15 *
16 * $Id: EasyTree.php,v 1.1 2003/04/18 15:49:52 joko Exp $
17 *
18 * $Log: EasyTree.php,v $
19 * Revision 1.1 2003/04/18 15:49:52 joko
20 * initial commit
21 *
22 */
23
24 /**
25 * Data::Lift::hash::topic::EasyTree
26 *
27 * Helper class to establish tree node browsing
28 * negotiation against an api of a remote service
29 * responding with linked lists in XML::Parser::EasyTree
30 * style. (available on CPAN)
31 *
32 *
33 * [1.] This actor traverses a nested data structure in style of
34 * CPAN's XML::Parser::EasyTree and translates it into
35 * a representation which is possible to feed to the
36 * PEAR::Tree lift actor component.
37 * "NodeAnchoring" (see below) also occours at this stage.
38 *
39 * 2. The PEAR::Tree lift will translate the tree
40 * to yet another representation which is fed to the
41 * HTML_TreeMenu Class (also available from PEAR).
42 *
43 * 3. This step - you may guess it - is also performed by
44 * another lift actor:
45 * It's Data::Lift::object::tree::common::PEAR::HTML::TreeMenu
46 * and it's worker components TreeMenu::DHTML and TreeMenu::Listbox.
47 *
48 *
49 * By this - and as you may see above - the translation
50 * process is spawned over three differents steps.
51 * This may be memory hungry and/or a performance bottleneck.
52 * Please be aware of this. The implementation gears
53 * towards clean seperation of all steps required to
54 * recieve a nested data representation of an xml document
55 * beeing able to "anchor" each single node.
56 *
57 * These "anchors" are made up of XPath filter queries
58 * which in turn will handle the "DiveIntoNode"-negotiation
59 * with the backend transparently for your convenience.
60 *
61 * The creation/maintenance of the proper "anchor" to access
62 * while tree traversal is also the job of this class and/or
63 * its descendants. So be aware of this when mungling
64 * anything or inheriting from us overwriting methods.
65 * Most probably it will break behavior at some
66 * translation step. (If the tree doesn't even break completely,
67 * but this is what we try to avoid through breaking up the
68 * translation into some steps)
69 * TODO: This "Anchoring" should *also* be split up into
70 * a seperate component. Maybe we could reuse it for
71 * other data widgets besides trees?
72 * However - this would be the last stuff required to
73 * split up the functionality of *this* class into its
74 * topic parts and should increase stability as well
75 * when reusing and/or extending this class.
76 *
77 *
78 * If you are interested in "Anchoring" and want to change behavior:
79 *
80 * Anchoring is relatively simple. If we arrive at known parts of the
81 * nested data structure, we just push the appropriate "elements" onto
82 * the anchor stack. If we "dive out" of this part, we pop the element
83 * off it.
84 * The "elements" of the anchor stack are already in XPath syntax
85 * describing "the node we mean" relatively to the current level.
86 *
87 * What's that about?
88 * It's difficult to map "nodes" orthogonally from the xml document to
89 * its tree representation. The translator has to decide about how to
90 * wrap a) the attributes and b) possible childnodes inside the content
91 * area of each xml tag to a memory data structure representing this
92 * node. The easiest way to achieve this is by doing oo programming
93 * and making up (e.g.) a class "Node" having appropriate attributes
94 * and code to implement its behavior as an arbitrary tree node.
95 * Multiple instances of a nodes' class make up the tree at runtime.
96 *
97 * The problem with this approach is if it comes to the point you
98 * want to transport your tree via some RPC mechanism, where
99 * the data structure again has to be serialized somehow.
100 * How to do that with your object structure? It would require a
101 * convenient traverse-in, traverse-out api to solve this having
102 * KISS on every layer. Since the layercount is always increasing
103 * when trying to achieve some abstract/generic components
104 * working together in a *flexible* way (regardless of performance
105 * payoff), the tree should be kept in "simple" linked list style
106 * troughout the whole translation- and transportation stages.
107 *
108 * Otherwise we would mix oo and non-oo stuff at this middleware
109 * layer and this should be reserved to the very backend side (e.g.
110 * orm stuff) and the very frontend side (e.g. taglib stuff).
111 * The middleware just handles non-oo data structures which should
112 * be easy to rewrite or forward since ...
113 * It's always just a hash (blessed or not), isn't it?
114 *
115 * Assuming this (and there are lists, of course ...) it should be
116 * possible to translate or echo them in a generic way *without*
117 * requiring knowledge about each detail of their internal representation.
118 *
119 * More detailed layout of the translation: (Perl lingo)
120 * The tree nodes are automagically vivified at the xml-2-tree
121 * transformation stage at the remote side through the algorithm
122 * implemented in {@link XML::Parser::EasyTree}. It follows these rules
123 * to represent a xml document as a linked list:
124 * 1. A xml tag (node) is mapped to a hash having
125 * the keys qw( attrib content name type ).
126 * 2. Attributes of this tag are wrapped as hashref into 'attrib'.
127 * 3. Child nodes inside the tag's content area are wrapped as arrayref
128 * into 'content'.
129 * 4. 'name' and 'type' are just plain scalars. The former contains the
130 * tag's name and the latter a type identifier of the tag (one of 'e|.').
131 *
132 * {@link XML::Parser::EasyTree} is written by Eric Bohlman <ebohlman@omsdev.com>
133 * and acts as a "built-in" style for
134 * {@link http://search.cpan.org/author/COOPERCL/XML-Parser-2.31/Expat/Expat.pm XML::Parser::Expat}
135 * similar to XML::Parser::Tree.
136 * XML::Parser currently has version 2.31 and is
137 * Copyright (c) 1998-2000 Larry Wall and Clark Cooper.
138 *
139 *
140 * @author Andreas Motl <andreas.motl@ilo.de>
141 * @copyright (c) 2003 - All Rights reserved.
142 * @license GNU LGPL (GNU Lesser General Public License)
143 *
144 * @link http://www.netfrag.org/~joko/
145 * @link http://www.gnu.org/licenses/lgpl.txt
146 *
147 * @package org.netfrag.glib
148 * @subpackage DataLift
149 * @name Data::Lift::hash::topic::EasyTree
150 *
151 * @link http://cvs.netfrag.org/php/libs/org.netfrag.glib
152 *
153 */
154 class Data_Lift_hash_topic_EasyTree {
155
156 var $_limit_deepness;
157
158 function perform(&$data) {
159
160 $this->_limit_deepness = 2;
161 //$this->_limit_listcount = 10;
162
163 //print Dumper($data);
164
165 //array_multisort($data);
166 //asort($data);
167
168 // TODO: could this initial perform code be revamped into the recursion logic?
169
170 $caption = 'root';
171 if ($data[name]) {
172 $caption = $data[name];
173 }
174
175 // push root node to anchor array
176 //$this->push_anchor('/' . $data[name]);
177
178 // V1
179 $data = $this->transform_xml_node($data);
180 $data = $this->mkContainer( $data, $this->make_tree_node($caption) );
181
182 //print Dumper($data);
183
184 // V2
185 //$data = $this->mkNode('root', $data);
186
187 return $data;
188
189 }
190
191
192 function transform_xml_node($xml_node, $atnode = null) {
193
194 //print Dumper($source);
195 //$this->push_anchor('/' . $xml_node[name]);
196
197 static $level;
198 $level++;
199
200 if ($level > $this->_limit_deepness) { $level--; return; }
201
202 //print "level: $level" . "<br/>";
203
204 $buffer = array();
205
206 foreach ($xml_node as $key => $val) {
207
208 //$this->push_anchor('/' . $key);
209
210 // default node - acts as fallback if not furthermore modified somehow in this loop
211 //$node = $this->make_tree_node($key, $val);
212 $node = $this->make_tree_node($key);
213
214 // 1. encapsulate attribute hash into a node container
215 if ($key && ($key == 'attrib')) {
216
217 // aggregate element keys to build xpq query pointing to the node
218 $buf = array();
219 foreach ($val as $attr_key => $attr_val) {
220
221 // push attribute anchor
222 $this->push_anchor('[@' . $attr_key . '="' . $attr_val . '"]');
223
224 $attr_node = $this->make_tree_node($attr_key, $attr_val);
225 array_push($buf, $attr_node);
226
227 $this->pop_anchor();
228 }
229 $node = $this->mkContainer( $buf, $node );
230
231 // 2. encapsulate content node
232 } elseif ($key && ($key == 'content') ) {
233
234 //print Dumper($val);
235 //$node_name = $val[$key][name];
236
237 //$this->pop_anchor();
238 //$this->push_anchor('/' . $xml_node[name]);
239
240 // transform list of nodes
241 $nodelist = $this->transform_xml_nodelist($val);
242
243 // propagate this list
244 $buf = array();
245 foreach ($nodelist as $idx => $entry) {
246
247 //print Dumper($entry);
248
249 // build and push anchor
250 // dive down one level and use the name from there
251 //$sub_name = '/' . $subnode[name];
252 $parent_name = '/' . $val[$idx][name];
253 // increase index - XPath counts like humans do. (base=1)
254 $sub_index = '[' . ($idx + 1) . ']';
255 $this->push_anchor($parent_name . $sub_index);
256
257 // numeric indexed array(?) ...
258 $subnode = $this->make_tree_node($idx);
259
260 // ... encapsulate!
261 $subnode = $this->mkContainer( $entry, $subnode );
262
263 //print Dumper($subnode);
264
265 // ... aggregate!
266 array_push($buf, $subnode);
267
268 // release anchor
269 $this->pop_anchor();
270 }
271 $node = $this->mkContainer( $buf, $node );
272
273 //$this->pop_anchor();
274
275
276 // 3. make plain node from key <-> value pair
277 // These are not the xml-attributes! These are the node-tag names!
278 } else {
279 //$this->push_anchor('/' . $key);
280 $node = $this->make_tree_node($key, $val);
281 //$this->pop_anchor();
282 }
283
284 //$this->pop_anchor();
285
286 // aggregate all nodes into a flat list at this level
287 array_push($buffer, $node);
288
289 }
290
291 //$this->pop_anchor();
292
293 $level--;
294
295 return $buffer;
296
297 }
298
299 function transform_xml_nodelist($xml_nodes) {
300 $sub_buffer = array();
301 foreach ($xml_nodes as $xml_node) {
302 $this->push_anchor('/' . $xml_node[name]);
303 array_push($sub_buffer, $this->transform_xml_node($xml_node));
304 $this->pop_anchor();
305 }
306 return $sub_buffer;
307 }
308
309
310 function make_tree_node($name, $val = '') {
311
312 // The label of the node, shown to the user.
313 $caption = $name;
314 //$filter = $name;
315 if ($val) {
316 $caption .= ': ' . $val;
317 //$filter = $name . '_' . $val;
318 }
319
320 // The name of the xml-file in this case.
321 // This is *not* an arbitrary filename - so you *can't* access any file
322 // on the host which should be considered bad.
323 // In fact, the names are keys in some subnode table sitting on top of
324 // a storage handle bundling together some files to a FileSet.
325 // FIXME: HACK !!!
326 // Do we already have a Nirvana-API for such things?
327 // Propagating this through the lift seems impossible...
328 $identifier = rawurlencode($_GET[ecdid]);
329
330 // A filter expression (XPath lingo) anchoring each node.
331 //print Dumper($this->_anchors);
332 //print "anchor: " . join('', $this->_anchors) . '<br/>';
333 $anchor = join('', $this->_anchors);
334 //$filter = $anchor;
335 $filter = rawurlencode($anchor);
336
337 $parent_filter = rawurlencode(stripslashes($_GET[ecdf]));
338 //$parent_filter = $_GET[ecdf];
339
340 // Build proper link arguments to refer to a node as item to let it become view-/editable.
341 // The identfier (by now the filename) transitions to a meta-argument here
342 // to make room for the sub-nodename becoming the new identifier. ($name!)
343 //$link_args = array( ecat => 'item', ecdm => $parent_identifier, ecdf => $filter );
344 //$link_args = array( ecat => 'tree', ecdm => $parent_identifier, ecdf => $filter );
345 //$link_args = array( ecat => 'tree', ecdm => $parent_identifier, ecdf => $filter, ecdid => $parent_identifier );
346 $link_args = array( ecat => 'tree', ecdid => $identifier, ecdf => $filter, ecdm => $parent_filter );
347
348 $url = url::viewdatanode($name, $link_args);
349
350 // vivify single node
351 // V1 [2002]
352 //$node = array( name => $name, attributes => array( url => $url ) );
353 // NEW [2003-04-20]: behavior of underlying lift changed
354 $node = array( name => $caption, link => $url );
355
356 // TEST: default childnode injection
357 //$node[children] = array( array( name => '123' ) );
358
359 return $node;
360 }
361
362 function mkContainer($data, $parent) {
363
364 /*
365 print "<hr/>";
366 print Dumper($data);
367 print "<br/>";
368 print Dumper($parent);
369 */
370
371 // autocreate parent if undef
372 //if (!$parent) { $parent = $this->mkNode('dummy'); }
373
374 // sort keys of children alphabetically
375 // TODO: add behavior to this
376 //asort($data);
377
378 $p_children = $parent[children];
379 $c_children = $data;
380
381 if ($p_children && $c_children) {
382 //$children = php::array_join_merge($p_children, $c_children);
383 $children = array_merge($p_children, $c_children);
384 } else {
385 $children = $c_children;
386 }
387
388 // vivify list of children
389 /*
390 $node = array(
391 'children' => $data,
392 );
393 */
394 $parent[children] = $children;
395
396 //print "<hr/>";
397 //print "parent: " . Dumper($parent) . "<br/>";
398 //print "node: " . Dumper($node) . "<br/>";
399
400 // merge them together and return result
401 //$result = php::array_join_merge($parent, $node);
402 //$result = php::array_join_merge($node, $parent);
403
404 //print "result: " . Dumper($result);
405 //print "<hr/>";
406
407 //return $result;
408 return $parent;
409 }
410
411 function pop_anchor() {
412 array_pop($this->_anchors);
413 }
414
415 function push_anchor($anchor) {
416 if (!is_array($this->_anchors)) {
417 $this->_anchors = array();
418 }
419 array_push($this->_anchors, $anchor);
420 }
421
422
423
424
425
426
427
428 function mkNode_old($name, $payload = null) {
429
430 static $level;
431 static $anchor;
432
433 if (!is_array($anchor)) { $anchor = array(); }
434 //if (!$caption) { $caption = $name; }
435
436 // TODO: propagate to this place inside some argument container
437 // (make adjustable from View)
438 $level_max = 10;
439 //print "level: $level<br/>";
440
441 // build url to single node
442 //$url = linkargs::topic($name);
443
444
445 /*
446 $main_buf = array();
447 //for ($i = 0; $i < $level - 1; $i++) {
448 for ($i = 0; $i <= 2; $i++) {
449 if (!$anchor[$i]) { continue; }
450 array_push($main_buf, $anchor[$i]);
451 }
452 $main = join('.', $main_buf);
453
454 //$filter = join('.', $anchor);
455 // FIXME: this is limited to three levels only
456 //$appendix = $anchor[2] ? (':' . join('.', array( $anchor[2], $anchor[3] ))) : '';
457 $appendix = '';
458 $appendix_buf = array();
459 for ($i = 2; $i <= sizeof($anchor); $i++) {
460 //for ($i = $level - 1; $i <= sizeof($anchor); $i++) {
461 if (!$anchor[$i]) { continue; }
462 array_push($appendix_buf, $anchor[$i]);
463 }
464 if ($appendix_buf) {
465 $appendix = ':' . join('.', $appendix_buf);
466 }
467
468 //$filter = join('.', array( $anchor[0], $anchor[1] )) . $appendix;
469 $filter = $main . $appendix;
470 */
471
472 $buf = array();
473 for ($i = 0; $i <= $level; $i = $i + 3) {
474 $part = array();
475 for ($j = $i; $j <= $i + 3; $j++) {
476 if (!$anchor[$j]) { continue; }
477 array_push($part, $anchor[$j]);
478 }
479 array_push($buf, join('.', $part));
480 }
481 $filter = join(':', $buf);
482
483
484 if (is_array($payload) && sizeof($payload)) {
485
486 //print "payload: " . Dumper($payload) . "<br/>";
487
488 // count the tree-level
489 $level++;
490 array_push($anchor, $name);
491 if ($level <= $level_max) {
492
493 //print "payload: " . Dumper($payload) . "<hr/>";
494
495 // check xml node
496 $is_xml_node =
497 php::is_hash($payload) &&
498 isset($payload[attrib]) &&
499 isset($payload[content]) &&
500 isset($payload[type]) &&
501 isset($payload[name]);
502
503 if ($is_xml_node) {
504 $childnode = $this->transform($payload);
505 $node = $this->mkContainer($childnode, $node);
506
507 } elseif (is_array($payload) && !php::is_hash($payload)) {
508 //$childnode = $this->transform_xml_node($payload);
509 //$node = $this->mkContainer($childnode, $node);
510
511 // make up a virtual xml node
512 } elseif (php::is_hash($payload)) {
513
514 print "===================== HASH ============<br/>";
515
516 /*
517 $data = array(
518 attrib => array(),
519 content => $payload,
520 type => '',
521 name => 'test2',
522 );
523 //$data = $payload;
524 */
525
526 //$childnode = $this->transform($payload);
527
528 /*
529 $data = array(
530 name => $node[name],
531 attributes => array( url => $url ),
532 //children => array( $payload ),
533 //children => $this->mkNode('sub', $childnode),
534 children => array( $this->mkNode('teyt'), $this->mkNode('huhu') ),
535 );
536 */
537
538 /*
539 //$childnode = $this->transform(array($payload));
540 $childnode = $this->transform($data);
541 print Dumper($childnode);
542
543 //$node[children] = $childnode;
544 $node = $this->mkContainer($childnode, $node);
545 */
546
547 //$node = $this->transform($data);
548 //$node = $data;
549
550 //$node = $this->transform($data);
551 //$node = $this->mkContainer($childnode, $node);
552
553
554
555
556 //$childnode = $this->transform($data);
557 //$node2 = array( name => $name, attributes => array( url => $url ) );
558
559 //$childnode = array( $data );
560 //$childnode = $data;
561
562 //$node = $this->mkContainer($data, $node);
563 //$node = $this->mkContainer($childnode, $node);
564
565 /*
566 //$node = $this->mkContainer($data, $this->mkNode('node'));
567 $node[name] = 'hello';
568 $node[type] = '';
569 $node[attrib] = array();
570 $node[content] = array();
571 //$node[children] = array( array( name => 'hello' ) );
572 $node[children] = array( $data );
573 */
574
575 //$new = $this->mkNode('acme');
576 //$node = $this->mkContainer($new);
577
578 $attribs = array();
579 foreach ($payload as $key => $val) {
580 array_push($attribs, "$key=$val");
581 }
582 $attribs_serialized = join(', ', $attribs);
583
584 //$node[name] = "$name: " . $attribs_serialized;
585
586 //return $childnode;
587 //$node = $this->mkNode('test', array($payload));
588
589 //$childnode = $this->mkNode('attributes', $data);
590 //$subnodes = $this->transform($payload);
591 //$subnodes = $this->mkNode('attributes', $payload);
592 //$subnodes = array($payload);
593 //$childnode2 = $this->mkNode('attributes', $subnodes);
594 //$childnode = $this->mkContainer($subnodes, $node);
595 //print "payload: " . Dumper($payload) . "<hr/>";
596 //$childnode = $payload;
597 }
598
599 //$childnode->abc = 'def';
600 //print "childnode: " . Dumper($childnode) . "<hr/>";
601 /*
602 if (is_array($childnode)) {
603 $payload = $this->mkContainer($childnode, $node);
604 $childnode = $this->transform($payload);
605 }
606 */
607 //array_push($buffer, $cnode);
608 //array_push($buffer, $this->mkContainer(array()));
609 }
610 $level--;
611 array_pop($anchor);
612
613 } else {
614 // TODO: !!
615 //$node[attributes][alt] = $name;
616 //print "no array: " . Dumper($payload) . "<br/>";
617 $node[name] = "$name: $payload";
618 }
619
620 return $node;
621 }
622
623
624 }
625
626 ?>

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed