array()); } $this->tree = $tree; } private function add_before_or_after($index_node, ApmTree $to_add, $after=true){ $index_node_found = false; $index_node_father = $this->get_node_father($index_node); if( $index_node_father !== false ){ array_splice($this->tree[$index_node_father],array_search($index_node,$this->tree[$index_node_father])+($after ? 1 : 0),0,$to_add->get_root()); $index_node_found = true; } if( $index_node_found && !$to_add->is_simple_node() ){ $this->tree = $this->tree + $to_add->get_tree(); } } public function add_before($before_node, ApmTree $to_add){ $this->add_before_or_after($before_node,$to_add,false); } public function add_after($after_node, ApmTree $to_add){ $this->add_before_or_after($after_node,$to_add); } public function add_child($father_node, ApmTree $to_add, $as_first_child=false){ if( $father_node === false || $father_node === '' || $father_node === null ){ //$father_node can be 0! $father_node = $this->get_root(); } if( array_key_exists($father_node,$this->tree) ){ if( $as_first_child ){ array_unshift($this->tree[$father_node],$to_add->get_root()); }else{ $this->tree[$father_node][] = $to_add->get_root(); } }else{ $this->tree = $this->tree + array($father_node=>array($to_add->get_root())); } if( !$to_add->is_simple_node() ){ $this->tree = $this->tree + $to_add->get_tree(); } } public function move_node_before($node_to_move,$before_node){ if( empty($node_to_move) || empty($before_node) ){ return false; } $forbidden_targets_nodes = $this->get_forbidden_target_nodes($node_to_move,'insert_before'); if( !in_array($before_node,$forbidden_targets_nodes) ){ $apm_tree_to_move = $this->get_sub_tree($node_to_move); $this->delete_sub_tree($node_to_move); $this->add_before($before_node,$apm_tree_to_move); }else{ return false; } return true; } public function move_node_after($node_to_move,$after_node){ if( empty($node_to_move) || empty($after_node) ){ return false; } $forbidden_targets_nodes = $this->get_forbidden_target_nodes($node_to_move,'insert_after'); if( !in_array($after_node,$forbidden_targets_nodes) ){ $apm_tree_to_move = $this->get_sub_tree($node_to_move); $this->delete_sub_tree($node_to_move); $this->add_after($after_node,$apm_tree_to_move); }else{ return false; } return true; } public function move_node_as_child($node_to_move,$father_node){ if( empty($node_to_move) || $father_node === false || $father_node === '' || $father_node === null ){ //$father_node can be 0! return false; } $forbidden_targets_nodes = $this->get_forbidden_target_nodes($node_to_move,'insert_child'); if( !in_array($father_node,$forbidden_targets_nodes) ){ $apm_tree_to_move = $this->get_sub_tree($node_to_move); $this->delete_sub_tree($node_to_move); $this->add_child($father_node,$apm_tree_to_move); }else{ return false; } return true; } public function get_root(){ reset($this->tree); return $this->is_empty() ? null : key($this->tree); } private function get_sub_tree_raw($sub_tree_root=''){ if( empty($sub_tree_root) ){ $sub_tree_root = $this->get_root(); } $sub_tree = array(); if( array_key_exists($sub_tree_root,$this->tree) ){ $sub_tree[$sub_tree_root] = array(); foreach($this->tree[$sub_tree_root] as $child){ $sub_tree[$sub_tree_root][] = $child; if( array_key_exists($child,$this->tree) ){ $sub_tree = $sub_tree + $this->get_sub_tree_raw($child); } } }else{ $sub_tree = array($sub_tree_root=>array()); } return $sub_tree; } public function get_sub_tree($sub_tree_root=''){ return new ApmTree($this->get_sub_tree_raw($sub_tree_root)); } public function get_node_order($node){ $node_order = 0; $siblings = $this->get_siblings($node); if( !empty($siblings) && count($siblings) > 1 ){ $node_order = array_search($node,$siblings); } return $node_order; } public function get_siblings($node,$node_included=true){ $siblings = array(); foreach($this->tree as $father=>$children){ $k = array_search($node,$children); if( $k !== false ){ if( !$node_included ){ unset($children[$k]); } $siblings = $children; break; } } $siblings = array_values($siblings); //Reindex return $siblings; } public function get_siblings_after($node,$node_included=true){ $siblings_after = array(); if( $node_included ){ $siblings_after[] = $node; } $siblings = $this->get_siblings($node); if( !empty($siblings) && count($siblings) > 1 ){ $node_position = array_search($node,$siblings); foreach($siblings as $k=>$sibling){ if( $k > $node_position ){ $siblings_after[] = $sibling; } } } return $siblings_after; } public function get_siblings_before($node,$node_included=true){ $siblings_before = array(); $siblings = $this->get_siblings($node); if( !empty($siblings) && count($siblings) > 1 ){ $node_position = array_search($node,$siblings); foreach($siblings as $k=>$sibling){ if( $k < $node_position ){ $siblings_before[] = $sibling; } } } if( $node_included ){ $siblings_before[] = $node; } return $siblings_before; } public function are_siblings($node1,$node2){ $are_siblings = false; $node1_siblings = $this->get_siblings($node1,false); if( !empty($node1_siblings) && in_array($node2,$node1_siblings) ){ $are_siblings = true; } return $are_siblings; } /* * Returns + 1 if $node1 is before $node2, -1 if $node2 is before $node1, * and false if $node1 and $node2 are not siblings. */ public function compare_siblings_order($node1,$node2){ $siblings_orger = false; $siblings = $this->get_siblings($node1); if( !empty($siblings) && in_array($node2,$siblings) ){ //They are siblings, check relative order : $node1_position = array_search($node1,$siblings); $node2_position = array_search($node2,$siblings); $siblings_order = $node1_position < $node2_position ? 1 : -1; } return $siblings_order; } public function get_siblings_interval($node_left,$node_right,$node_left_included=true,$node_right_included=true){ $interval = array(); if( $node_left == $node_right ) { return array(); } $siblings = $this->get_siblings($node_left); if( !empty($siblings) && in_array($node_right,$siblings) ){ $node_left_position = array_search($node_left,$siblings); $node_right_position = array_search($node_right,$siblings); if( $node_left_position > $node_right_position ){ $memory_left_position = $node_left_position; $node_left_position = $node_right_position; $node_right_position = $memory_left_position; $memory_left_included = $node_left_included; $node_left_included = $node_right_included; $node_right_included = $memory_left_included; } $offset = $node_left_included ? $node_left_position : $node_left_position + 1; $length = $node_right_position - $node_left_position + 1; if( !$node_left_included ){ $length--; } if( !$node_right_included ){ $length--; } $interval = array_slice($siblings, $offset, $length); }else{ return false; } return $interval; } public function get_children($node){ $children = array(); if( array_key_exists($node,$this->tree) ){ $children = $this->tree[$node]; } return $children; } public function get_nb_children($node){ return array_key_exists($node,$this->tree) ? count($this->tree[$node]) : 0; } public function get_node_father($node){ if( $node == $this->get_root() ){ return false; } foreach($this->tree as $father=>$children){ if( in_array($node,$children) ){ return $father; } } return false; } public function get_path_to_node($node_to_find){ $path = array(); if( $this->node_exists($node_to_find) ){ while( $node_to_find !== false ){ array_unshift($path,(int)$node_to_find); $node_to_find = $this->get_node_father($node_to_find); } } return $path; } public function node_exists($node){ foreach($this->tree as $father=>$children){ if( $node == $father || in_array($node,$children) ){ return true; } } return false; } public function get_nodes_flat($sub_tree_root=''){ $tree_nodes = array(); $apm_tree = $this->get_sub_tree($sub_tree_root); foreach($apm_tree->get_tree() as $root=>$children){ if( !in_array($root,$tree_nodes) ){ $tree_nodes[] = $root; } foreach($children as $child){ if( !in_array($child,$tree_nodes) ){ $tree_nodes[] = $child; } } } return $tree_nodes; } private function delete_node_descendance($sub_tree_root){ if( array_key_exists($sub_tree_root,$this->tree) ){ foreach($this->tree[$sub_tree_root] as $child){ if( array_key_exists($child,$this->tree) ){ $this->delete_node_descendance($child); } } unset($this->tree[$sub_tree_root]); } } public function delete_sub_tree($sub_tree_root,$delete_root=true){ $this->delete_node_descendance($sub_tree_root); if( $delete_root ){ $sub_tree_root_father = $this->get_node_father($sub_tree_root); if( $sub_tree_root_father !== false ){ unset($this->tree[$sub_tree_root_father][array_search($sub_tree_root,$this->tree[$sub_tree_root_father])]); $this->tree[$sub_tree_root_father] = array_values($this->tree[$sub_tree_root_father]); //To reindex the array after unset! if( $sub_tree_root_father != self::root_id && empty($this->tree[$sub_tree_root_father]) ){ unset($this->tree[$sub_tree_root_father]); } } } } public function is_empty($considered_empty_if_only_root = false){ $empty = empty($this->tree); if( $considered_empty_if_only_root ){ $empty |= count($this->tree) == 1 && array_key_exists(self::root_id,$this->tree) && empty($this->tree[self::root_id]); } return $empty; } private function is_simple_node(){ $count = count($this->tree); if( $count != 1 ){ return false; } return empty($this->tree[$this->get_root()]); } public function get_max_node(){ $max = 0; foreach($this->tree as $father=>$children){ $max = $father > $max ? $father : $max; foreach($children as $child){ $max = $child > $max ? $child : $max; } } return $max; } public function get_forbidden_target_nodes($moving_node,$action){ $forbidden_nodes = $this->get_nodes_flat($moving_node); switch( $action ){ case 'insert_before': case 'insert_after': $root = $this->get_root(); if( !in_array($root,$forbidden_nodes) ){ $forbidden_nodes[] = $root; } break; case 'insert_child': break; } return $forbidden_nodes; } public function get_allowed_target_nodes($moving_node,$action){ $forbidden = $this->get_forbidden_target_nodes($moving_node,$action); $all = $this->get_nodes_flat($this->get_root()); $allowed = array(); foreach($all as $node){ if( !in_array($node,$forbidden) ){ $allowed[] = $node; } } return $allowed; } public function get_tree(){ return $this->tree; } public function get_node_depth($node,$relative_to_node=self::root_id){ if( $node != $relative_to_node ){ $node_father = $this->get_node_father($node); if( $node_father !== false ){ return $this->get_node_depth($node_father,$relative_to_node) + 1; }else{ //We reached the top of the tree without finding our "$relative_to_node"... //So, $node is not a descendant of $relative_to_node, we can't compute its depth relatively to it! return false; } }else{ return 0; } } public function get_ready_to_display_tree($root=false,$depth=0,$parent=false,$unfolded_nodes=array()){ $to_display = array(); if( $root === false ){ $root = $this->get_root(); } $nb_children = array_key_exists($root,$this->tree) ? count($this->tree[$root]) : 0; $to_display[$root] = array('depth'=>$depth, 'parent'=>( $parent===false ? $this->get_node_father($root) : $parent ), 'nb_children'=>$nb_children, 'children'=>!empty($nb_children) ? $this->tree[$root] : array() ); if( !empty($unfolded_nodes) && in_array($root,$unfolded_nodes) ){ if( array_key_exists($root,$this->tree) ){ foreach($this->tree[$root] as $child){ if( array_key_exists($child,$this->tree) ){ if( !empty($unfolded_nodes) && in_array($child,$unfolded_nodes) ){ $to_display = $to_display + $this->get_ready_to_display_tree($child,$depth+1,false,$unfolded_nodes); }else{ $to_display[$child] = array('depth'=>$depth+1, 'parent'=>$root, 'nb_children'=>count($this->tree[$child]), 'children'=>$this->tree[$child] ); } }else{ $to_display[$child] = array('depth'=>$depth+1, 'parent'=>$root, 'nb_children'=>0, 'children'=>array() ); } } } } return $to_display; } /** * Same as get_ready_to_display_tree() but returns only nodes ids */ public function get_visible_nodes($root=false,$unfolded_nodes=array()){ $to_display = array(); if($root === false){ $root = $this->get_root(); } $nb_children = array_key_exists($root,$this->tree) ? count($this->tree[$root]) : 0; $to_display[] = $root; if( !empty($unfolded_nodes) && in_array($root,$unfolded_nodes) ){ if( array_key_exists($root,$this->tree) ){ foreach($this->tree[$root] as $child){ if( array_key_exists($child,$this->tree) ){ if( !empty($unfolded_nodes) && in_array($child,$unfolded_nodes) ){ $to_display = array_merge($to_display,$this->get_visible_nodes($child,$unfolded_nodes)); }else{ $to_display[] = $child; } }else{ $to_display[] = $child; } } } } return $to_display; } public function get_node_tree_infos($node,$extended_infos=false){ $children = $this->get_children($node); $ready_to_display_node = array('depth'=>$this->get_node_depth($node), 'parent'=>$this->get_node_father($node), 'nb_children'=>count($children), 'children'=>$children ); if( $extended_infos ){ $siblings_no_current = array(); $siblings_before = array(); $siblings_after = array(); $previous_sibling = false; $next_sibling = false; $order = 0; $siblings = $this->get_siblings($node); if( !empty($siblings) && count($siblings) > 1 ){ $node_position = array_search($node,$siblings); $order = $node_position; foreach($siblings as $k=>$sibling){ if( $sibling != $node ){ $siblings_no_current[] = $sibling; if( $k < $node_position ){ $siblings_before[] = $sibling; }else{ $siblings_after[] = $sibling; } } } $previous_sibling = isset($siblings[$node_position-1]) ? $siblings[$node_position-1] : false; $next_sibling = isset($siblings[$node_position+1]) ? $siblings[$node_position+1] : false; } $ready_to_display_node['siblings_no_current'] = $siblings_no_current; $ready_to_display_node['all_siblings'] = $siblings; $ready_to_display_node['siblings_before'] = $siblings_before; $ready_to_display_node['siblings_after'] = $siblings_after; $ready_to_display_node['previous_sibling'] = $previous_sibling; $ready_to_display_node['next_sibling'] = $next_sibling; $ready_to_display_node['order'] = $order; } return $ready_to_display_node; } public function get_nodes_tree_infos($nodes,$extended_infos=false){ $ready_to_display_nodes = array(); foreach($nodes as $node){ $ready_to_display_nodes[$node] = $this->get_node_tree_infos($node,$extended_infos); } return $ready_to_display_nodes; } public function __toString(){ $to_display = $this->get_ready_to_display_tree(); $str = ''; foreach($to_display as $node=>$node_tree_infos){ $str .= '