Index: /gsdl/trunk/src/colservr/collectserver.cpp
===================================================================
--- /gsdl/trunk/src/colservr/collectserver.cpp	(revision 15001)
+++ /gsdl/trunk/src/colservr/collectserver.cpp	(revision 15002)
@@ -98,4 +98,7 @@
       if (value == "true") collectinfo.isBeta = true;
       else collectinfo.isBeta = false;
+    } else if (key == "collectgroup") {
+      if (value == "true") collectinfo.isCollectGroup = true;
+      else collectinfo.isCollectGroup = false;
     } else if ((key == "ccscols") || (key == "supercollection")) collectinfo.ccsCols = cfgline;
     else if (key == "supercollectionoptions") {
Index: /gsdl/trunk/src/colservr/collectserver.h
===================================================================
--- /gsdl/trunk/src/colservr/collectserver.h	(revision 15001)
+++ /gsdl/trunk/src/colservr/collectserver.h	(revision 15002)
@@ -101,4 +101,6 @@
   const colservrconf &get_configinfo () {return configinfo;}
   text_t get_collection_name () {return configinfo.collection;}
+  bool is_collection_group () {return collectinfo.isCollectGroup;}
+
 
   // ping indicates whether the collection has appropriate data structures
Index: /gsdl/trunk/src/colservr/collectset.cpp
===================================================================
--- /gsdl/trunk/src/colservr/collectset.cpp	(revision 15001)
+++ /gsdl/trunk/src/colservr/collectset.cpp	(revision 15002)
@@ -51,4 +51,5 @@
   // get gsdlhome (if we fail the error will be picked up later -- in
   // cgiwrapper)
+
   if (site_cfg_read (gsdlhome, httpdomain, httpprefix)) {
     if (!gsdlhome.empty() && directory_exists(gsdlhome)) {
@@ -66,104 +67,10 @@
 	  }
 
-	  // read config file to see if built with mg, mgpp, or lucene
-	  text_t buildtype = "mg"; // mg is default
-	  text_tarray cfgline;
-	  text_t key;
-	  text_t filename = filename_cat(collectdir, *thiscol, "index" , "build.cfg");
-	  ifstream confin(filename.getcstr());
-
-	  if (confin) {
-	    while (read_cfg_line(confin, cfgline) >= 0) {
-	      if (cfgline.size() ==2 ) {
-		key = cfgline[0];
-		cfgline.erase(cfgline.begin());
-		if (key =="buildtype") {
-		  buildtype = cfgline[0];
-		  break;
-		}
-	      }
-	    }
-	  }
-	  
-	  confin.close();
-
-	  // this memory is created but never destroyed
-	  // we're also not doing any error checking to make sure we didn't
-	  // run out of memory
-	  collectserver *cserver = new collectserver();
-	  gdbmclass *gdbmhandler = new gdbmclass();
-    
-	  // add a null filter
-	  filterclass *filter = new filterclass ();
-	  cserver->add_filter (filter);
-	  
-	  // add a browse filter
-	  browsefilterclass *browsefilter = new browsefilterclass();
-	  browsefilter->set_gdbmptr (gdbmhandler);
-	  cserver->add_filter (browsefilter);
-	  
-	  if (buildtype == "mg") {
-	    mgsearch = new mgsearchclass();
-	    
-	    // add a query filter
-	    mgqueryfilterclass *queryfilter = new mgqueryfilterclass();
-	    queryfilter->set_gdbmptr (gdbmhandler);
-	    queryfilter->set_textsearchptr (mgsearch);
-	    cserver->add_filter (queryfilter);
-	    
-	    // add a mg and gdbm source
-	    mggdbmsourceclass *mggdbmsource = new mggdbmsourceclass ();
-	    mggdbmsource->set_gdbmptr (gdbmhandler);
-	    mggdbmsource->set_textsearchptr (mgsearch);
-	    cserver->add_source (mggdbmsource);
-
-	  }
-	  else if (buildtype == "mgpp") {
-	    
-	    mgppsearch = new mgppsearchclass();
-	    	    
-	    // add a query filter
-	    mgppqueryfilterclass *queryfilter = new mgppqueryfilterclass();
-	    queryfilter->set_gdbmptr (gdbmhandler);
-	    queryfilter->set_textsearchptr (mgppsearch);
-	    cserver->add_filter (queryfilter);
-	    
-	    // add a mg and gdbm source
-	    mggdbmsourceclass *mggdbmsource = new mggdbmsourceclass ();
-	    mggdbmsource->set_gdbmptr (gdbmhandler);
-	    mggdbmsource->set_textsearchptr (mgppsearch);
-	    cserver->add_source (mggdbmsource);   
-	  }
-	  else if (buildtype == "lucene") {
-	    lucenesearch = new lucenesearchclass();
-	    lucenesearch->set_gsdlhome(gsdlhome);
-
-	    // add a query filter
-	    lucenequeryfilterclass *queryfilter = new lucenequeryfilterclass();
-	    queryfilter->set_gdbmptr (gdbmhandler);
-	    queryfilter->set_textsearchptr (lucenesearch);
-	    cserver->add_filter (queryfilter);
-	    
-	    // add a lucene and gdbm source
-	    lucenegdbmsourceclass *lucenegdbmsource = new lucenegdbmsourceclass ();
-	    lucenegdbmsource->set_gdbmptr (gdbmhandler);
-	    lucenegdbmsource->set_textsearchptr (lucenesearch);
-	    cserver->add_source (lucenegdbmsource);   
-	  }
-
-
-	  // inform collection server and everything it contains about its
-	  // collection name
-	  cserver->configure ("collection", *thiscol);
-	  // AZIZ: added on 10/10/00
-	  //       the cserver object does not have a reference to gsdlhome
-	  cserver->configure ("gsdlhome", gsdlhome);
-    
-	  // GRB: removed    proto.add_collectserver (cserver);
-	  // GRB: added to build our own cservers list
-	  cservers.addcollectserver (cserver);
+	  this->add_collection (*thiscol, gsdlhome);
 
 	  ++thiscol;
 	}
+
+	this->add_all_collection_groups(gsdlhome);
       }
     }
@@ -198,6 +105,10 @@
       // are available to decode defaultindex, defaultsubcollection, and
       // defaultlanguage
+
+      bool failed_build_cfg = false;
       if (!build_cfg_read (*((*here).second.c), configinfo.gsdlhome,
 			   configinfo.collection)) {
+	failed_build_cfg = true;
+
 	outconvertclass text_t2ascii;
 	logout << text_t2ascii 
@@ -205,10 +116,10 @@
 	       << configinfo.collection << "\", gsdlhome=\""
 	       << configinfo.gsdlhome << "\"\n";
-	++here;
-	continue;
-      }
-
+      }
+
+      bool failed_collect_cfg = false;
       if (!collect_cfg_read (*((*here).second.c), configinfo.gsdlhome,
 			     configinfo.collection)) {
+	failed_collect_cfg = true;
 	outconvertclass text_t2ascii;
 	logout << text_t2ascii 
@@ -216,7 +127,19 @@
 	       << configinfo.collection << "\", gsdlhome=\"" 
 	       << configinfo.gsdlhome << "\"\n";
+      }
+
+      
+      bool is_colgroup = (*here).second.c->is_collection_group();
+
+      if (failed_collect_cfg) {
 	++here;
 	continue;
       }
+
+      if (failed_build_cfg && (!is_colgroup)) {
+	++here;
+	continue;
+      }
+      // let a failed build.cfg through if its 'collect.cfg' marks it as 'collectgroup true'
 
       if (!(*here).second.c->init (logout)) return false;
@@ -258,4 +181,6 @@
       ++thiscol;
     }
+
+    this->add_all_collection_groups(gsdlhome);
   }
 }
@@ -266,17 +191,6 @@
 				 const text_t &gsdlhome) {
 
-  // if an old collection server exists for this collection we should
-  // delete it first
-  collectservermapclass::iterator here = cservers.begin();
-  collectservermapclass::iterator end = cservers.end();
-  while (here != end) {
-    if ((*here).second.c != NULL && (*here).first == collection) {
-      delete (*here).second.c;
-      cservers.erase (here);
-      break;
-    }
-    ++here;
-  }
-      
+  this->remove_collection(collection);
+  
   // read config file to see if built with mg, mgpp, or lucene
   // -- we can rely on the collection (and therefore the build.cfg)
@@ -371,11 +285,4 @@
   cserver->configure ("gsdlhome", gsdlhome);
 
-  /* Removed from add_collection 24/11/2000; already done elsewhere in collectset.  
-  // configure receptionist's collectinfo structure 
-  text_tarray colinfo;
-  colinfo.push_back (collection);
-  colinfo.push_back (gsdlhome);
-  colinfo.push_back (gdbmhome);
-  */
   cservers.addcollectserver (cserver);
 }
@@ -399,4 +306,99 @@
   }
   cservers.clear();
+}
+
+void collectset::add_collection_group(const text_t& collection, 
+				      const text_t& gsdlhome)
+{
+  text_tarray group;
+  text_t collect_group_dir = filename_cat (gsdlhome, "collect", collection);
+
+  // need to read collect.cfg for 'collectgroup' as class hasn't been initialised through 'init' yet
+  text_t is_collect_group;
+  text_tarray cfgline;
+  text_t key;
+  text_t build_cfg = filename_cat(gsdlhome, "collect", collection, "etc", "collect.cfg");
+  char *collect_cfgc = build_cfg.getcstr();
+  ifstream confin(collect_cfgc);
+
+  if (confin) {
+    while (read_cfg_line(confin, cfgline) >= 0) {
+      if (cfgline.size() == 2) {
+	key = cfgline[0];
+	cfgline.erase(cfgline.begin());
+	if (key == "collectgroup") {
+	  is_collect_group = cfgline[0];
+	  break;
+	}
+      }
+    }
+    confin.close();
+  }
+  delete []collect_cfgc;
+
+  if (is_collect_group == "true") {
+
+    if (read_dir (collect_group_dir, group)) {
+      
+      text_tarray::const_iterator thiscol = group.begin();
+      text_tarray::const_iterator endcol = group.end();
+      
+      while (thiscol != endcol) {
+	// ignore the modelcol
+	if (*thiscol == "etc") {
+	  ++thiscol;
+	  continue;
+	}
+	
+	this->add_collection (collection + "/" + *thiscol, gsdlhome);
+	
+	++thiscol;
+      }
+    }
+  }
+}
+
+void collectset::add_all_collection_groups (const text_t& gsdlhome) 
+
+{
+  collectservermapclass::iterator here = cservers.begin();
+  collectservermapclass::iterator end = cservers.end();
+  
+  while (here != end) {
+    text_t collection = (*here).second.c->get_collection_name();
+    this->add_collection_group(collection,gsdlhome);
+
+    ++here;
+  }
+}
+
+
+// remove_collection deletes the collection server of collection.
+// This only needs to be called if a collectionserver is to be
+// removed while the library is running. The destructor function
+// cleans up all collectservers when the program exits.
+void collectset::remove_collection (const text_t &collection) {
+
+  // do nothing if no collection server exists for this collection 
+  if (cservers.getcollectserver(collection) == NULL) return;
+
+  // first unload any cached mg databases - we may need to do something
+  // similar to this for mgpp and lucene too
+  if (mgsearch != NULL) {
+    mgsearch->unload_database();
+  }
+
+  // now delete the collection server object
+  collectservermapclass::iterator here = cservers.begin();
+  collectservermapclass::iterator end = cservers.end();
+
+  while (here != end) {
+    if ((*here).second.c != NULL && (*here).first == collection) {
+      delete (*here).second.c;
+      cservers.erase (here);
+      return;
+    }
+    ++here;
+  }
 }
 
@@ -408,25 +410,6 @@
 void collectset::remove_collection (const text_t &collection, ostream &logout) {
 
-  // do nothing if no collection server exists for this collection 
-  if (cservers.getcollectserver(collection) == NULL) return;
-
-  // first unload any cached mg databases - we may need to do something
-  // similar to this for mgpp and lucene too
-  if (mgsearch != NULL) {
-    mgsearch->unload_database();
-  }
-
-  // now delete the collection server object
-  collectservermapclass::iterator here = cservers.begin();
-  collectservermapclass::iterator end = cservers.end();
-
-  while (here != end) {
-    if ((*here).second.c != NULL && (*here).first == collection) {
-      delete (*here).second.c;
-      cservers.erase (here);
-      return;
-    }
-    ++here;
-  }
+  remove_collection(collection);
+
   outconvertclass text_t2ascii;
   logout << text_t2ascii << "nullproto::remove_collection: failed to remove collectserver for "
Index: /gsdl/trunk/src/colservr/collectset.h
===================================================================
--- /gsdl/trunk/src/colservr/collectset.h	(revision 15001)
+++ /gsdl/trunk/src/colservr/collectset.h	(revision 15002)
@@ -42,8 +42,14 @@
   void add_all_collections (const text_t &gsdlhome);
   void remove_all_collections ();
+ 
+  void remove_collection (const text_t &collection);
+  void remove_collection (const text_t &collection, ostream& logout);
 
-  void remove_collection (const text_t &collection, ostream &logout);
   void add_collection (const text_t &collection, 
 		       const text_t &gsdlhome);
+
+  void add_collection_group(const text_t& collection, 
+			    const text_t& gsdlhome);
+  void add_all_collection_groups (const text_t& gsdlhome);
 
   // return the total number of servers in the set
Index: /gsdl/trunk/src/recpt/comtypes.cpp
===================================================================
--- /gsdl/trunk/src/recpt/comtypes.cpp	(revision 15001)
+++ /gsdl/trunk/src/recpt/comtypes.cpp	(revision 15002)
@@ -55,4 +55,5 @@
   isBeta=false;
   isSegmented=false;
+  isCollectGroup=false;
   languages.erase(languages.begin(), languages.end());
   ccsCols.erase(ccsCols.begin(), ccsCols.end());
Index: /gsdl/trunk/src/recpt/comtypes.h
===================================================================
--- /gsdl/trunk/src/recpt/comtypes.h	(revision 15001)
+++ /gsdl/trunk/src/recpt/comtypes.h	(revision 15002)
@@ -88,4 +88,5 @@
   bool isBeta;
   bool isSegmented;
+  bool isCollectGroup; // used to group a set of collections as one item on the home page
   unsigned long buildDate;
   text_tarray ccsCols;    // empty if collection does not use cross-collection searching
Index: /gsdl/trunk/src/recpt/pageaction.cpp
===================================================================
--- /gsdl/trunk/src/recpt/pageaction.cpp	(revision 15001)
+++ /gsdl/trunk/src/recpt/pageaction.cpp	(revision 15002)
@@ -180,4 +180,5 @@
 	bool first = true;
 	while (collist_here != collist_end) {
+
 	  ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr ((*rprotolist_here).p, *collist_here, logout);
 	  
@@ -253,8 +254,52 @@
 	int count = 0;
 	while (collist_here != collist_end) {
+
 	  ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr ((*rprotolist_here).p, *collist_here, logout);
+
+	  text_t arg_g = args["g"];
+	  text_t colname_dir = *collist_here;
 	  
+	  if (arg_g.empty()) {
+	    // normal top-level home page
+	    text_t::const_iterator begin = colname_dir.begin();
+	    text_t::const_iterator end = colname_dir.end();
+
+	    if (findchar(begin,end,'/')!=end) {
+	      // no g argument, so generating top level home page
+	      // => if colname_dir has "/" in collection name, filter it out
+
+	      ++collist_here;
+	      continue;
+	    }
+	  }
+	  else {
+	    // show only collections in the named group
+
+	    
+	    if (colname_dir == arg_g) {
+	      // display about this collect text
+
+	      text_t collectionextra = cinfo->get_collectionmeta("collectionextra", args["l"]);
+	      if (!collectionextra.empty()) {
+		// prepend text on at start
+		homeextra = "<p>" + collectionextra + "</p>" + homeextra;
+	      }
+
+	      ++collist_here;
+	      continue;
+	    }
+
+
+	    // => filter out anything that does not start with the group prefix
+	    
+	    if (!starts_with(colname_dir,arg_g + "/")) {
+	      ++collist_here;
+	      continue;
+	    }
+	  }
+
+
 	  if (cinfo != NULL) {
-	    if (cinfo->isPublic && (cinfo->buildDate > 0)) {
+	    if (cinfo->isPublic && ((cinfo->buildDate > 0)) || (cinfo->isCollectGroup)) {
 
 	      found_valid_col = true;
@@ -264,4 +309,25 @@
 		alt = collectionname;
 	      }
+
+
+	      comerror_t err;
+	      text_t optsite = g_EmptyText;
+	      text_t site_name = (*rprotolist_here).p->get_site_name (err);
+	      if (!site_name.empty()) { optsite = "site="+site_name+"&amp;"; }
+
+	      text_t link;
+
+	      if (cinfo->isCollectGroup) {
+		link = "<a class=\"collectiontitle\" href=\"_gwcgi_?"+optsite+"a=p&amp;p=home&amp;g=" + *collist_here;
+		link += "&amp;l=" + args["l"] + "&amp;w=" + args["w"] + "\">";
+	      }
+	      else {
+		link = "<a class=\"collectiontitle\"href=\"_gwcgi_?"+optsite+"a=p&amp;p=about&amp;c=" + *collist_here;
+		link += "&amp;l=" + args["l"] + "&amp;w=" + args["w"] + "\">";
+	      }
+	      
+	      if (!cinfo->receptionist.empty()) 
+		link = "<a class=\"collectiontitle\"href=\"" + cinfo->receptionist + "\">";
+	      
 	      
 	      // url to image: try iconcollectionsmall, then iconcollection
@@ -291,24 +357,18 @@
 		
 		collectionname 
-		  = "<img width=\"150\" src=\"" + iconurl + "\" alt=\"" + alt + "\">";
-	      } else {
-		collectionname = alt;
+		  = link + "<img width=\"150\" src=\"" + iconurl + "\" alt=\"" + alt + "\">" + "</a>";
+	      } else {		
+
+		collectionname = "<div class=\"collecttitle\" style=\"width: 150px; border: 1px solid; \"><p class=\"collectiontitle\" style=\"font-size: 18px; width: 150px; white-space:normal; \">";
+		collectionname += alt + "</p></div>";
+
+		collectionname = link + collectionname + "</a>";
+
 	      }
 	      
 	      if (count%configinfo.HomePageCols == 0)
-		homeextra += "<tr valign=\"top\">\n";
-	      
-	      comerror_t err;
-	      text_t optsite = g_EmptyText;
-	      text_t site_name = (*rprotolist_here).p->get_site_name (err);
-	      if (!site_name.empty()) { optsite = "site="+site_name+"&amp;"; }
-
-	      text_t link = "<a href=\"_gwcgi_?"+optsite+"a=p&amp;p=about&amp;c=" + *collist_here;
-	      link += "&amp;l=" + args["l"] + "&amp;w=" + args["w"] + "\">";
-	      
-	      if (!cinfo->receptionist.empty()) 
-		link = "<a href=\"" + cinfo->receptionist + "\">";
-	      
-	      homeextra += "<td>" + link + collectionname + "</a></td>\n";
+		homeextra += "<tr valign=\"top\">\n";	      
+
+	      homeextra += "<td align=\"center\">" + collectionname + "</td>\n";
 	      
 	      ++count;
