--- loncom/interface/lonwishlist.pm 2010/08/10 14:30:20 1.1 +++ loncom/interface/lonwishlist.pm 2010/08/20 10:38:41 1.7 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Routines to control the wishlist # -# $Id: lonwishlist.pm,v 1.1 2010/08/10 14:30:20 wenzelju Exp $ +# $Id: lonwishlist.pm,v 1.7 2010/08/20 10:38:41 wenzelju Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,7 +26,20 @@ # http://www.lon-capa.org/ # +=pod +=head1 NAME + +Apache::lonwishlist - Wishlist-Module + +=head1 SYNOPSIS + +The wishlist offers a possibility to store links to resources from the resource-pool and external websites in a hierarchical list. +It is only available for user with access to the resource-pool. The list can be structured by folders. + +The wishlist-module uses the CPAN-module "Tree" for easily handling the directory-structure of the wishlist. Each node in the tree has an index to be referenced by. + +=cut package Apache::lonwishlist; @@ -36,6 +49,7 @@ use Apache::lonnet; use Apache::loncommon(); use Apache::lonhtmlcommon; use Apache::lonlocal; +use LONCAPA; use Tree; @@ -48,19 +62,42 @@ my @allFolders; my @allNodes; my $indentConst = 20; +=pod + +=head2 Routines for getting and putting the wishlist data from and accordingly to users data. + +=over 4 + +=item * &getWishlist() + + Get the wishlist-data via lonnet::dump() and returns the got data in a hash. + + +=item * &putWishlist(wishlist) + + Parameter is a reference to a hash. Puts the wishlist-data contained in the given hash via lonnet::put() to user-data. + + +=item * &deleteWishlist() + + Deletes all entries from the user-data for wishlist. Do this before putting in new data. + + +=back + +=cut + # Read wishlist from user-data sub getWishlist { - my @wishlistkeys = &Apache::lonnet::getkeys('wishlist'); - my %wishlist = &Apache::lonnet::get('wishlist',\@wishlistkeys); - foreach my $i (%wishlist) { + my %wishlist = &Apache::lonnet::dump('wishlist'); + foreach my $i ( keys %wishlist) { #File not found. This appears at the first time using the wishlist #Create file and put 'root' into it if ($i =~m/^error:No such file/) { &Apache::lonnet::logthis($i.'! Create file by putting in the "root" of the directory tree.'); &Apache::lonnet::put('wishlist', {'root' => ''}); - @wishlistkeys = &Apache::lonnet::getkeys('wishlist'); - %wishlist = &Apache::lonnet::get('wishlist',\@wishlistkeys); + %wishlist = &Apache::lonnet::dump('wishlist'); } elsif ($i =~ /^(con_lost|error|no_such_host)/i) { &Apache::lonnet::logthis('ERROR while attempting to get wishlist: '.$i); @@ -68,7 +105,8 @@ sub getWishlist { } } - #If hash is empty, put 'root' into it, so we got a node to start the tree + # if we got no keys in hash returned by dump(), return error. + # wishlist will not be loaded, instead the user will be asked to try again later if ((keys %wishlist) == 0) { &Apache::lonnet::logthis('ERROR while attempting to get wishlist: no keys retrieved!'); return 'error'; @@ -92,6 +130,57 @@ sub deleteWishlist { } +=pod + +=head2 Routines for changing the directory struture of the wishlist. + +=over 4 + +=item * &newEntry(title, path, note) + + Creates a new entry in the wishlist containing the given informations. Additionally saves the date of creation in the entry. + + +=item * &deleteEntries(marked) + + Parameter is a reference to an array containing the indices of all nodes that should be removed from the tree. + + +=item * &sortEntries(indexNode, at) + + Changes the position of a node given by indexNode within its siblings. New position is given by at. + + +=item * &moveEntries(indexNodesToMove, indexParent) + + Parameter is a reference to an array containing the indices of all nodes that should be moved. indexParent specifies the node that will become the new Parent for these nodes. + + +=item * &setNewTitle(nodeindex, newTitle) + + Sets the title for the node given by nodeindex to newTitle. + + +=item * &setNewPath(nodeindex, newPath) + + Sets the path for the node given by nodeindex to newPath. + + +=item * &setNewNote(nodeindex, newNote) + + Sets the note for the node given by nodeindex to newNote. + + +=item * &saveChanges() + + Prepares the wishlist-hash to save it via &putWishlist(wishlist). + + +=back + +=cut + + # Create a new entry sub newEntry() { my ($title, $path, $note) = @_; @@ -148,6 +237,7 @@ sub moveEntries { my $indexParent = shift; my @nodesToMove = (); + # get all nodes that should be moved &getNodesToArray(\@childrenRt); foreach my $index (@$indexNodesToMove) { my $foundNode = &Tree::getNodeByIndex($index, \@allNodes); @@ -188,6 +278,20 @@ sub setNewTitle { } +# Set a new path for an entry +sub setNewPath { + my ($nodeindex, $newPath) = @_; + &getNodesToArray(\@childrenRt); + my $found = &Tree::getNodeByIndex($nodeindex, \@allNodes); + if ($found->value()->path()) { + $found->value()->path($newPath); + return 1; + } + @allNodes = (); + return 0; +} + + # Set a new note for an entry sub setNewNote { my ($nodeindex, $newNote) = @_; @@ -211,6 +315,40 @@ sub saveChanges { } +=pod + +=head2 Routines for handling the directory structure + +=over 4 + +=item * &getFoldersForOption(nodes) + + Return the titles for all exiting folders in an option-tag, used to offer the users a possibility to create a new link or folder in an existing folder. + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &getfoldersOption() + + Returns the option-tag build by &getFoldersForOption(nodes). Use it to transfer this to other modules (e.g. lonmenu.pm). + + +=item * &getFoldersToArray(children) + + Puts all nodes that represent folders in the wishlist into an array. + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &getNodesToArray(children) + + Puts all existing nodes into an array (apart from the root node, because this one does not represent an entry in the wishlist). + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + + =back + +=cut + + # Return the names for all exiting folders in option-tags, so # a new link or a new folder can be created in an existing folder my $indent = 0; @@ -241,7 +379,7 @@ sub getfoldersOption { $root = &Tree::HashToTree(); @childrenRt = $root->children(); &getFoldersForOption(\@childrenRt); - my $options = ''.$foldersOption; + my $options = ''.$foldersOption; $foldersOption = ''; return $options; } @@ -279,6 +417,67 @@ sub getNodesToArray { } +=pod + +=head2 Routines for the user-interface of the wishlist + +=over 4 + +=item * &JSforWishlist() + + Returns JavaScript-functions needed for wishlist actions like open and close folders. + + +=item * &wishlistView(nodes) + + Returns the table-HTML-markup for the wishlist in mode "view". + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &wishlistEdit(nodes) + + Returns the table-HTML-markup for the wishlist in mode "edit". + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &wishlistMove(nodes, marked) + + Returns the table-HTML-markup for the wishlist in mode "move". Highlights all entry "selected to move" contained in marked (reference to array). + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &wishlistImport(nodes) + + Returns the table-HTML-markup for the wishlist in mode "import". + Recursive call starting with all children of the root of the tree (parameter nodes is reference to an array containing the nodes of the current level). + + +=item * &makePage(mode, marked) + + Returns the HTML-markup for the whole wishlist depending on mode. If mode is "move" we need the marked entries to be highlighted a "selected to move". + Calls &wishlistView(nodes), &wishlistEdit(nodes) or &wishlistMove(nodes, marked). + + +=item * &makePageSet() + + Returns the HTML-Markup for the page shown when a link was set by using the icon when viewing a resource. + + +=item * &makePageImport() + + Returns the HTML-Markup for the page shown when links should be imported into courses. + + +=item * &makeErrorPage () + + Returns the HTML-Markup for an error-page shown if the wishlist could not be loaded. + + +=back + +=cut + + # Return a script-tag containing Javascript-function # needed for wishlist actions like 'new link' ect. sub JSforWishlist { @@ -321,7 +520,7 @@ sub JSforWishlist { '

'. ''. ''. ''. @@ -341,7 +540,7 @@ sub JSforWishlist { '

'. ''. ''. ''. @@ -351,6 +550,17 @@ sub JSforWishlist { $inPageNewLink =~ s/\n//g; $inPageNewFolder =~ s/\n//g; + # it is checked, wether a path links to a LON-CAPA-resource or an external website. links to course-contents are not allowed + # because they probably will return a kind of 'no access' (unless the user is already in the course, the path links to). + # also importing these kind of links into a course does not make much sense. + # to find out if a path (not starting with /res/...) links to course-contents, the same filter as in lonwrapper is used, + # that means that it is checked wether a path contains .problem, .quiz, .exam etc. + # this is good for most cases but crashes as soon as a real external website contains one of this pattern in its URL. + # so maybe there's a better way to find out wether a given URL belongs to a LON-CAPA-server or not ...? + my $warningLinkNotAllowed1 = &mt('You can only insert links to LON-CAPA resources from the resource-pool '. + 'or to external websites. Paths to LON-CAPA resources must be of the form /res/dom/usr... . '. + 'Paths to external websites must contain the network protocol (e.g. http://...).'); + my $warningLinkNotAllowed2 = &mt('The following link is not allowed: '); my $warningLink = &mt('You must insert a title and a path!'); my $warningFolder = &mt('You must insert a title!'); my $warningDelete = &mt('Are you sure you want to delete the selected entries? Deleting a folder also deletes all entries within this folder!'); @@ -369,6 +579,15 @@ sub JSforWishlist { +'if (!path || !title) {' +'alert("$warningLink");' +'return false;}' + +'var linkOK = (path.match(/^http:(\\\\/\\\\/)/) || path.match(/^https:(\\\\/\\\\/)/))' + +'&& !(path.match(/\\.problem/) || path.match(/\\.exam/)' + +'|| path.match(/\\.quiz/) || path.match(/\\.assess/)' + +'|| path.match(/\\.survey/) || path.match(/\\.form/)' + +'|| path.match(/\\.library/) || path.match(/\\.page/)' + +'|| path.match(/\\.sequence/));' + +'if (!path.match(/^(\\\\/res\\\\/)/) && !linkOK) {' + +'alert("$warningLinkNotAllowed1");' + +'return false;}' +'else {' +'window.close();' +'return true;}}' @@ -408,12 +627,21 @@ sub JSforWishlist { if (d) { if (!confirm('$warningSave')) { setAction('noSave'); + r = true; + } + else { + r = linksOK(); } } - r = true; + } + else if (action == 'saveOK') { + r = linksOK(); + } + else if (action == 'move') { + r = selectDestinationFolder(); } document.getElementsByName('list')[0].setAttribute("action", "/adm/wishlist?mode="+mode); - if(r){ + if (r) { document.getElementsByName('list')[0].submit(); } } @@ -432,6 +660,15 @@ sub JSforWishlist { return true; } } + var newpath = document.getElementsByName('newpath'); + var i = 0; + for (i=0;i'. + 'value()->path()."'".');">'. 'link'. - ''.$n->value()->title().''; + $n->value()->title().''; } # note-icon, different icons for an entries with note and those without @@ -910,7 +1254,9 @@ sub wishlistEdit { ''. ''. ''. - 'folder'; + 'folder'. + ''. + ''; } # entry is a link @@ -918,12 +1264,11 @@ sub wishlistEdit { $wishlistHTMLedit .= ''. ''. - 'link'; + 'link'. + ''. + ''; } - - # input-field for title - $wishlistHTMLedit .= ''; - + # note-icon, different icons for an entries with note and those without my $noteIMG = 'anot.png'; @@ -939,7 +1284,7 @@ sub wishlistEdit { # start row containing the textarea for the note $wishlistHTMLedit .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index). - ''. + ''. ''; @@ -999,19 +1344,17 @@ sub wishlistMove { } # entry is a link else { - if (!$isIn) { - $wishlistHTMLmove .= ''. - ''; - } # higlight the title, if the link was selected to be moved - else { - $wishlistHTMLmove .= ''. - ''; + my $highlight = ''; + if ($isIn) { + $highlight = 'style="color:red;"'; } # link-image and title - $wishlistHTMLmove .= 'link'. - $n->value()->title().''; + $wishlistHTMLmove .= ''. + ''. + 'value()->path()."'".');" '.$highlight.'>'. + 'link'. + $n->value()->title().''; } # note-icon, different icons for an entries with note and those without @@ -1045,6 +1388,81 @@ sub wishlistMove { } } + + +# HTML-Markup for table if in import-mode +my $wishlistHTMLimport; +my $indent = $indentConst; +my $form = 1; +sub wishlistImport { + my $nodes = shift; + + foreach my $n (@$nodes) { + my $index = $n->value()->nindex(); + + # start row, use data_table routines to set class to LC_even or LC_odd automatically. this row contains a checkbox, the title and the note-icon. + # only display the top level entries on load + $wishlistHTMLimport .= ($n->parent()->value() eq 'root')?&Apache::loncommon::start_data_table_row('','row'.$index) + :&Apache::loncommon::continue_data_table_row('LC_hidden','row'.$index); + + + # checkboxes + $wishlistHTMLimport .= ''. + ''. + ''. + ''. + ''. + ''; + + # entry is a folder + if ($n->value()->path() eq '') { + $wishlistHTMLimport .= ''. + ''. + ''. + 'folder'. + $n->value()->title().''; + } + # entry is a link + else { + $wishlistHTMLimport .= ''. + 'value()->path()."'".');">'. + 'link'. + $n->value()->title().''; + $form++; + } + + # note-icon, different icons for an entries with note and those without + my $noteIMG = 'anot.png'; + + if ($n->value()->note() ne '') { + $noteIMG = 'anot2.png'; + } + + $wishlistHTMLimport .= ''. + ''.&mt('Note').''; + + $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row(); + + # start row containing the textarea for the note, do not display note on default, readonly in import-mode + $wishlistHTMLimport .= &Apache::loncommon::continue_data_table_row('LC_hidden','note'.$index). + ''. + ''; + $wishlistHTMLimport .= &Apache::loncommon::end_data_table_row(); + + # if the entry is a folder, it could have other entries as content. if it has, call wishlistImport for those entries + my @children = $n->children(); + if ($#children >=0) { + $indent += 20; + &wishlistImport(\@children); + $indent -= 20; + } + } +} + # Returns the HTML-Markup for wishlist sub makePage { my $mode = shift; @@ -1060,10 +1478,7 @@ sub makePage { 'onload' => 'javascript:onLoadAction('."'".$mode."'".');', 'onunload' => 'javascript:window.name = '."'loncapaclient'"}}); - my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs('Wishlist '. - ''. - ''.&mt('Help').''); + my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs(&mt('Wishlist').&Apache::loncommon::help_open_topic('Wishlist')); # get javascript-code for wishlist-interactions my $js = &JSforWishlist(); @@ -1084,7 +1499,7 @@ sub makePage { # icon for edit-mode, display when in view-mode if ($mode eq 'view') { $functions .= &Apache::lonhtmlcommon::add_item_funclist(''. + 'onclick="setFormAction('."'save','edit'".');" class="LC_menubuttons_link">'. ''.$lt{'ed'}.' '. ''.$lt{'ed'}.''); @@ -1092,7 +1507,7 @@ sub makePage { # icon for view-mode, display when in edit-mode else { $functions .= &Apache::lonhtmlcommon::add_item_funclist(''. + 'onclick="setFormAction('."'save','view'".');" class="LC_menubuttons_link">'. ''.$lt{'vw'}.' '. ''.$lt{'vw'}.''); @@ -1128,7 +1543,7 @@ sub makePage { # icon for saving changes $functions .= &Apache::lonhtmlcommon::add_item_funclist(''. + 'onclick="setFormAction('."'saveOK','".$mode."'".'); " class="LC_menubuttons_link">'. ''.$lt{'sv'}.''. ''.$lt{'sv'}.''); @@ -1142,7 +1557,7 @@ sub makePage { ''; # only display subbox in view- or edit-mode - if ($mode ne 'move') { + if ($mode eq 'view' || $mode eq 'edit') { $inner .= $subbox; } @@ -1155,7 +1570,7 @@ sub makePage { $inner .= &Apache::loncommon::end_data_table(); } else { - $inner .= ''.&mt("Your wihlist ist currently empty.").''; + $inner .= ''.&mt("Your wishlist ist currently empty.").''; } $wishlistHTMLedit = ''; } @@ -1165,7 +1580,7 @@ sub makePage { $inner .= ''.$wishlistHTMLview.'
'; } else { - $inner .= ''.&mt("Your wihlist ist currently empty.").''; + $inner .= ''.&mt("Your wishlist ist currently empty.").''; } $wishlistHTMLview = ''; } @@ -1177,18 +1592,18 @@ sub makePage { if ($markStr) { $markStr = substr($markStr, 0, length($markStr)-1); $inner .= ''; - $inner .= ''.&mt('You have selected the red marked entries to be moved to another folder. '. - 'Now choose the new destination folder.').'

'; + $inner .= '

'.&mt('You have selected the red marked entries to be moved to another folder. '. + 'Now choose the new destination folder.').'

'; &wishlistMove(\@childrenRt, $marked); $inner .= ''.$wishlistHTMLmove.'


'; - $inner .= ''. + $inner .= ''. ''; $wishlistHTMLmove =''. ''.&mt('Top level').''; } else { - $inner .= ''.&mt("You haven't marked any entry to move.").'
'. + $inner .= '

'.&mt("You haven't marked any entry to move.").'

'. ''; } } @@ -1199,7 +1614,7 @@ sub makePage { # end_page my $endPage = &Apache::loncommon::end_page(); - # put all page-elements togther + # put all page-elements together my $page = $startPage.$breadcrumbs.$js.$inner.$endPage; return $page; @@ -1223,13 +1638,68 @@ sub makePageSet { # end_page my $endPage = &Apache::loncommon::end_page(); - # put all page-elements togther + # put all page-elements together my $page = $startPage.$inner.$endPage; return $page; } +# Returns the HTML-Markup for the page, shown when links should be imported into a course +sub makePageImport { + my $rat = shift; + # start_page + my $startPage = &Apache::loncommon::start_page('Wishlist',undef, + {'only_body' => 1}); + + # get javascript-code for wishlist-interactions + my $js = &JSforWishlist(); + $js .= &JSforImport($rat); + + my $inner = '

'.&mt('Import Resources from Wishlist').'

'; + if (!$rat) { + $inner .= '

'.&mt("Please note that you can use the checkboxes corresponding to a folder to ". + "easily check all links within this folder. The folder structure itself can't be imported. ". + "All checked links will be imported into the current folder of your course.").'

'; + } + else { + $inner .= '

'.&mt("Please note that you can use the checkboxes corresponding to a folder to ". + "easily check all links within this folder. The folder structure itself can't be imported. ") + .'

'; + } + my %wishlist = &getWishlist(); + my $fnum = (keys %wishlist)-1; + + $inner .= '
'. + ''. + ''. + ''. + ''. + '

'; + + + # wishlist-table + &wishlistImport(\@childrenRt); + if ($wishlistHTMLimport ne '') { + $inner .= ''.$wishlistHTMLimport.'
'; + } + else { + $inner .= ''.&mt("Your wishlist ist currently empty.").''; + } + $wishlistHTMLimport = ''; + + $inner .= '
'; + + # end_page + my $endPage = &Apache::loncommon::end_page(); + + # put all page-elements together + my $page = $startPage.$js.$inner.$endPage; + + return $page; +} + + # Returns the HTML-Markup for error-page sub makeErrorPage { # breadcrumbs and start_page @@ -1238,10 +1708,7 @@ sub makeErrorPage { text => 'Wishlist'}); my $startPage = &Apache::loncommon::start_page('Wishlist'); - my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs('Wishlist '. - ''. - ''.&mt('Help').''); + my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs(&mt('Wishlist').&Apache::loncommon::help_open_topic('Wishlist')); &Apache::lonhtmlcommon::clear_breadcrumbs(); # error-message @@ -1250,7 +1717,7 @@ sub makeErrorPage { # end_page my $endPage = &Apache::loncommon::end_page(); - # put all page-elements togther + # put all page-elements together my $page = $startPage.$breadcrumbs.$inner.$endPage; return $page; @@ -1274,7 +1741,7 @@ sub handler { } # get unprocessed_cgi (i.e. marked entries, mode ...) - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['action','mark','markedToMove','mode','newtitle','note']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['action','mark','markedToMove','mode','newtitle','note','rat']); # change the order of entries within a level, that means sorting the entries my $changeOrder = 0; @@ -1324,9 +1791,12 @@ sub handler { # only save, if user wants to save changes # do not save, when current action is 'delete' or 'sort' or 'move' my @newTitles = (); + my @newPaths = (); my @newNotes = (); - if ((defined $env{'form.newtitle'} || defined $env{'form.newnote'}) && ($env{'form.action'} ne 'noSave') && ($env{'form.action'} ne 'delete') && !$changeOrder) { + if ((defined $env{'form.newtitle'} || defined $env{'form.newpath'} || defined $env{'form.newnote'}) + && ($env{'form.action'} ne 'noSave') && ($env{'form.action'} ne 'delete') && !$changeOrder) { @newTitles = &Apache::loncommon::get_env_multiple('form.newtitle'); + @newPaths = &Apache::loncommon::get_env_multiple('form.newpath'); @newNotes = &Apache::loncommon::get_env_multiple('form.newnote'); my $node = 0; foreach my $t (@newTitles) { @@ -1334,6 +1804,14 @@ sub handler { $node++; } $node = 0; + my $path = 0; + for (my $i = 0; $i < ($#newTitles+1); $i++ ) { + if (&setNewPath($node, $newPaths[$path])) { + $path++; + } + $node++; + } + $node = 0; foreach my $n (@newNotes) { &setNewNote($node, $n); $node++; @@ -1349,6 +1827,9 @@ sub handler { elsif ($env{'form.mode'} eq 'move') { $page = &makePage("move", \@marked); } + elsif ($env{'form.mode'} eq 'import') { + $page = &makePageImport($env{'form.rat'}); + } elsif ($env{'form.mode'} eq 'set') { $page = &makePageSet(); } @@ -1370,6 +1851,65 @@ sub handler { # Extend CPAN-Module Tree by function like 'moveNode' or 'deleteNode' package Tree; +=pod + +=head2 Routines from package Tree + +=over 4 + +=item * &getNodeByIndex(index, nodes) + + Searches for a node, specified by the index, in nodes (reference to array) and returns it. + + +=item * &moveNode(node, at, newParent) + + Moves a given node to a new parent (if new parents is defined) or change the position from a node within its siblings (means sorting, at must be defined). + + +=item * &removeNode(node) + + Removes a node given by node from the tree. + + +=item * &TreeIndex(children) + + Sets an index for every node in the tree, beginning with 0. + Recursive call starting with all children of the root of the tree (parameter children is reference to an array containing the nodes of the current level). + + +=item * &setCountZero() + + Resets index counter. + + +=item * &RootToHash(childrenRt) + + Converts the root-node to a hash-entry: the key is root and values are just the indices of root's children. + + +=item * &TreeToHash(childrenRt) + + Converts all other nodes in the tree to hash. Each node is one hash-entry where the keys are the index of a node and the values are all other attributes (containing tile, path, note, date and indices for all direct children). + Recursive call starting with all children of the root of the tree (parameter childrenRT is reference to an array containing the nodes of the current level). + + +=item * &HashToTree() + + Converts the hash to a tree. Builds a tree-object for each entry in the hash. Afterwards call &buildTree(node, childrenIn, TreeNodes, TreeHash) to connect the tree-objects. + + +=item * &buildTree(node, childrenIn, TreeNodes, TreeHash) + + Joins the nodes to a tree. + Recursive call starting with root and all children of root (parameter childrenIn is reference to an array containing the nodes indices of the current level). + + +=back + +=cut + + # returns the node with a given index from a list of nodes sub getNodeByIndex { my $index = shift; @@ -1518,7 +2058,7 @@ sub HashToTree { } } # if there are nodes, build up the tree-structure - if (defined $TreeHash{'root'}) { + if (defined $TreeHash{'root'} && $TreeHash{'root'} ne '') { my @childrenRtIn = @{ $TreeHash{'root'} }; &buildTree(\$root, \@childrenRtIn,\@TreeNodes,\%TreeHash); }