/**********************************************************************
 *
 * documentaction.cpp -- 
 * Copyright (C) 1999  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include <string.h>

#include "documentaction.h"
#include "recptprototools.h"
#include "OIDtools.h"
#include "querytools.h"
#include "unitool.h"
#include "gsdltools.h"
#include "highlighttext.h"
#include "browsetoolsclass.h"

documentaction::documentaction () {
  recpt = NULL;

  // this action uses cgi variables "a", "d", "cl",
  // "x", "gc", "gt", "gp", and "hl"
  cgiarginfo arg_ainfo;
  arg_ainfo.shortname = "a";
  arg_ainfo.longname = "action";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "p";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  arg_ainfo.shortname = "d";
  arg_ainfo.longname = "document OID";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::none;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::can;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // whether or not a document should be retrieved from the 
  // library or the Web.
  arg_ainfo.shortname = "il";
  arg_ainfo.longname = "internal link preference";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "l";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  arg_ainfo.shortname = "cl";
  arg_ainfo.longname = "classification OID";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::none;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::can;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // in this action "gc" controls the expand/contract
  // contents function
  arg_ainfo.shortname = "gc";
  arg_ainfo.longname = "expand contents";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::can;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // in this action "gt" controls the expand/contract
  // text function 0 = not expanded, 1 = expand unless 
  // there are more than 10 sections containing text,
  // 2 = expand all
  arg_ainfo.shortname = "gt";
  arg_ainfo.longname = "expand text";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::can;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // in this action "gp" is the "go to page" control
  // used by the Book type of toc
  arg_ainfo.shortname = "gp";
  arg_ainfo.longname = "go to page";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::none;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // in this action "hl" is the "highlighting on/
  // highlighting off control ("hl" == "2" is a
  // special case that forces phrase highlighting
  // to be used even if the query string doesn't
  // appear to be a phrase)
  arg_ainfo.shortname = "hl";
  arg_ainfo.longname = "highlighting on/off";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "1";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "x" is 0 normally or 1 if page
  // has been "detached"
  arg_ainfo.shortname = "x";
  arg_ainfo.longname = "detached page";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // "xx" is 0 normally or 1 if documents should be detached by default
  arg_ainfo.shortname = "xx";
  arg_ainfo.longname = "detach all doc pages";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  
  // f arg is set to 1 if document is to 
  // be displayed in a frame
  arg_ainfo.shortname = "f";
  arg_ainfo.longname = "frame";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::mustnot;
  argsinfo.addarginfo (NULL, arg_ainfo);

  // fc arg is "1" if search bar is to be included (i.e. if "fc" == 1
  // the httpdocument macro will include "&f=1"
  arg_ainfo.shortname = "fc";
  arg_ainfo.longname = "include search bar";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "1";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  //rd is whether a document will be displayed
  //with a relevant document list
  arg_ainfo.shortname = "rd";
  arg_ainfo.longname = "include relevant documents";
  arg_ainfo.multiplechar = false;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = "0";
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

  //dm is the metadata that has been used for the datelist
  arg_ainfo.shortname = "dm";
  arg_ainfo.longname = "date metadata";
  arg_ainfo.multiplechar = true;
  arg_ainfo.multiplevalue = false;
  arg_ainfo.defaultstatus = cgiarginfo::weak;
  arg_ainfo.argdefault = g_EmptyText;
  arg_ainfo.savedarginfo = cgiarginfo::must;
  argsinfo.addarginfo (NULL, arg_ainfo);

}

documentaction::~documentaction()
{
}

bool documentaction::check_cgiargs (cgiargsinfoclass &argsinfo, cgiargsclass &args, 
				    recptprotolistclass *protos, ostream &logout) {

   if(!args["d"].empty())
      {
	 text_t docTop;
	 get_top(args["d"],docTop);
	 
	 recptproto* collectproto = protos->getrecptproto (args["c"], logout);
	 if (collectproto != NULL)
	    {
	       ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, args["c"], logout);
	       
	       if(cinfo->authenticate == "document") 
		  {
		     // both are either commented out or uncomment and are empty
		     if (cinfo->public_documents.empty() && cinfo->private_documents.empty())
			{
			   //deny everything
			   args["uan"] = "1";
			   args["ug"] = cinfo->auth_group;
			}

		     // both public_documents and private_documents are turned on !
		     else if (!cinfo->public_documents.empty() && !cinfo->private_documents.empty())
			{
			   //deny everything
			   args["uan"] = "1";
			   args["ug"] = cinfo->auth_group;
			}
		     
		     // if public_documents is set but this document isn't
		     // listed in it we need to authenticate
		     else if ((!cinfo->public_documents.empty()) && 
			      (cinfo->public_documents.find(docTop) == cinfo->public_documents.end()))
		       {
			 args["uan"] = "1";
			 args["ug"] = cinfo->auth_group;
		       }
		     
		     // if private_documents is set and this document is
		     // listed in it we need to authenticate
		     else if ((!cinfo->private_documents.empty()) &&
			      (cinfo->private_documents.find(docTop) != cinfo->private_documents.end()))
		       {
			 args["uan"] = "1";
			 args["ug"] = cinfo->auth_group;
		       }
		     
		  }
	    }
      }
   // check gc argument
   int arg_gc = args.getintarg("gc");
  if (arg_gc < 0 || arg_gc > 2) {
    logout << "Warning: \"gc\" argument out of range (" << arg_gc << ")\n";
    cgiarginfo *gcinfo = argsinfo.getarginfo ("gc");
    if (gcinfo != NULL) args["gc"] = gcinfo->argdefault;
  }

  // check gt argument (may be either 0, 1 or 2)
  int arg_gt = args.getintarg("gt");
  if (arg_gt != 0 && arg_gt != 1 && arg_gt != 2) {
    logout << "Warning: \"gt\" argument out of range (" << arg_gt << ")\n";
    cgiarginfo *gtinfo = argsinfo.getarginfo ("gt");
    if (gtinfo != NULL) args["gt"] = gtinfo->argdefault;
  }

  // check hl argument
  int arg_hl = args.getintarg("hl");
  if (arg_hl < 0 || arg_hl > 2) {
    logout << "Warning: \"hl\" argument out of range (" << arg_hl << ")\n";
    cgiarginfo *hlinfo = argsinfo.getarginfo ("hl");
    if (hlinfo != NULL) args["hl"] = hlinfo->argdefault;
  }

  // check x argument
  int arg_x = args.getintarg("x");
  if (arg_x != 0 && arg_x != 1) {
    logout << "Warning: \"x\" argument out of range (" << arg_x << ")\n";
    cgiarginfo *xinfo = argsinfo.getarginfo ("x");
    if (xinfo != NULL) args["x"] = xinfo->argdefault;
  }

  //checks whether rd arg is valid
  int arg_rd = args.getintarg("rd");
  if (arg_rd != 0 && arg_rd != 1) {
    logout << "Warning: \"rd\" argument out of range (" << arg_rd << ")\n";
    cgiarginfo *rdinfo = argsinfo.getarginfo ("rd");
    if (rdinfo != NULL) args["rd"] = rdinfo->argdefault;
  }


  return true;
}

void documentaction::get_cgihead_info (cgiargsclass &args, recptprotolistclass *protos,
				       response_t &response,text_t &response_data, 
				       ostream &logout) {

  if ((args["il"] == "w") && (!args["d"].empty())) {

    recptproto* collectproto = protos->getrecptproto (args["c"], logout);
    if (collectproto != NULL) {
      
      text_tset metadata;
      FilterResponse_t filt_response;
      text_t top;

      metadata.insert ("URL");
      
      // get metadata for parent document
      get_top (args["d"], top);
      if (get_info (top, args["c"], args["l"], metadata, false, collectproto, filt_response, logout)) {
	text_t url = filt_response.docInfo[0].metadata["URL"].values[0];
	
	response = location;
	response_data = url;
	return;
      } else {
	// error, no URL
	logout << "Error: documentaction::get_cgihead_info failed on get_info" << endl;
      }
    }
  }
  response = content;
  response_data = "text/html";
}

// set_widthtspace calculates how wide the spaces in the nav bar should
// be and sets the appropriate macro
void documentaction::set_spacemacro (displayclass &disp, FilterResponse_t &response,
				     bool has_search_button) {

  text_t width;
  int twidth, swidth, iwidth = 0;

  int numc = response.docInfo.size();
  ResultDocInfo_tarray::iterator dochere = response.docInfo.begin();
  ResultDocInfo_tarray::iterator docend = response.docInfo.end();
	
  disp.expandstring (displayclass::defaultpackage, "_pagewidth_", width);
  twidth = width.getint();

  if (has_search_button) {
    disp.expandstring ("query", "_searchwidth_", width);
    iwidth += width.getint();
  } else {
    numc -= 1;
  }
  
  while (dochere != docend) {
    const text_t &title = (*dochere).metadata["Title"].values[0];

    disp.expandstring ("document", "_" + title + "width_", width);
    if (width == ("_" + title + "width_")) {
      disp.expandstring ("document", "_defaultwidth_", width);
    }
    iwidth += width.getint();
    ++dochere;
  }

  if ((twidth - iwidth) < numc) swidth = 2;
  else {
    swidth = twidth - iwidth;
    if (numc > 0) swidth = swidth / numc;
  }
  disp.setmacro ("widthtspace", displayclass::defaultpackage, swidth);
}

// set_navbarmacros sets _navigationbar_ and _httpbrowseXXX_ macros
// reponse contains 1 metadata field (Title)
void documentaction::set_navbarmacros (displayclass &disp, FilterResponse_t &response, 
				       bool has_search_button, cgiargsclass &args,
				       ColInfoResponse_t &cinfo) {


   bool use_pulldown = false;
   text_tmap::iterator it = cinfo.format.find("NavigationBar");
   if (it != cinfo.format.end()) {
      if (it->second == "pulldown") {
	 use_pulldown = true;
      }
   }

   text_t topparent;
   text_t &arg_d = args["d"];
   text_t navigationbar = "<!-- Navigation Bar -->\n";

   get_top (args["cl"], topparent);
   int numc = response.docInfo.size();
   ResultDocInfo_tarray::iterator dochere = response.docInfo.begin();
   ResultDocInfo_tarray::iterator docend = response.docInfo.end();

   if (!use_pulldown) {
      if (has_search_button) {
	// search uses its own navtab, as it may be suppressed based on other args
	navigationbar += "_navtabsearch_";
	if (args["a"] == "q") {
	  navigationbar += "(selected)";
	}
      }
      
      if (has_search_button || numc == 0) navigationbar += "_navbarspacer_";
      
      bool first = true;
      while (dochere != docend) {
	if (!first) navigationbar += "_navbarspacer_";
	
	text_t title = (*dochere).metadata["Title"].values[0];
	
	navigationbar += "_navtab_(_httpbrowse"+(*dochere).OID+"_,"+title;
	// if we are at this classifier
	if (arg_d.empty() && ((*dochere).OID == topparent)) {
	   // don't link to this classifier as it is the current one
	   navigationbar += ",selected";
	 }
	 navigationbar += ")";

	// set the _httpbrowseCLX_ macro for this classification
	disp.setmacro ("httpbrowse" + (*dochere).OID, displayclass::defaultpackage, "_httpdocument_&amp;cl=" + (*dochere).OID);
	 ++dochere;
	 first = false;
      }
      
      navigationbar += "_dynamicclassifiernavbarentries_";
      navigationbar += "\n<!-- End of Navigation Bar -->\n";

   } else {

      navigationbar = "<form method=\"get\" name=\"navform\">\n";
      navigationbar += "<select name=\"navbar\" onChange=\"location.href=";
      navigationbar += "document.navform.navbar.options[document.navform.navbar.selectedIndex].value\">\n";

      if (args["a"] != "q" && args["cl"].empty()) {
	 navigationbar += "<option value=\"\" selected>_textselectpage_</option>\n";
      }
      
      if (has_search_button) {
	 navigationbar += "<option value=\"_httpquery_\"";
	 if (args["a"] == "q") {
	    navigationbar += " selected";
	 }
	 navigationbar += ">_labelSearch_</option>\n";
      }

      while (dochere != docend) {
	 text_t title = dochere->metadata["Title"].values[0];	 

	 navigationbar += "<option value=\"_httpdocument_&amp;cl=" + dochere->OID + "\"";
	 if (topparent == dochere->OID) {
	    navigationbar += " selected";
	 }
	 navigationbar += ">_navlinktitle_(" + title + ")</option>\n";
	 ++dochere;
      }

      navigationbar += "</select>\n";
      navigationbar += "</form>\n";
   }

   disp.setmacro ("navigationbar", displayclass::defaultpackage, navigationbar);
}

// define all the macros which might be used by other actions
// to produce pages. 
void documentaction::define_external_macros (displayclass &disp, cgiargsclass &args, 
					     recptprotolistclass *protos, ostream &logout) {
  
  // define_external_macros sets the following macros:

  // _navigationbar_     this is the navigation bar containing the search button
  //                     and any classification buttons - it goes at the top of 
  //                     most pages. for now we're assuming that there'll always 
  //                     be a search button - we should probably check that there
  //                     is a query action before making this assumption

  // _httpbrowseXXX_     the http macros for each classification (i.e. if there
  //                     are Title and Creator classifications _httpbrowseTitle_
  //                     and _httpbrowseCreator_ will be set

  // _widthtspace_       the width of the spacers between buttons in navigation
  //                     bar
  
  // _httpdocument_      has '&f=1' added if displaying document inside a frame

  // _gsdltop_           macro to replace _top targets with

  // _httppagehome_      overridden home url if html collections have own homepage

  // _usability_         macros for remote usability reporting: if
  // _usabinterface_     "format Usability [multi|textonly|stepwise]" is in 
  // _usabilityscript_   collect.cfg

  // _versionnnum_   greenstone version number (hard-coded)
  // must have a valid collection server to continue
   
  text_t &collection = args["c"];
  if (collection.empty()) return;
  recptproto *collectproto = protos->getrecptproto (collection, logout);
  if (collectproto == NULL) return;
  
  if (recpt == NULL) {
    logout << "ERROR (documentaction::define_external_macros): This action does not contain\n"
	   << "      information about any receptionists. The method set_receptionist was\n"
	   << "      probably not called from the module which instantiated this action.\n";
    return;
  }

  outconvertclass text_t2ascii;
  comerror_t err;
  InfoFiltersResponse_t filterinfo;
  FilterResponse_t response;
  text_tset metadata;

  // get info on current collection and load up formatinfo
  // I'd prefer not to do this here as we're getting 
  // collection info every time (and probably also getting
  // it in other places some of the time) - One day I'll
  // fix it ... maybe - Stefan.
  ColInfoResponse_t cinfo;

  collectproto->get_collectinfo (collection, cinfo, err, logout);
  load_formatinfo (cinfo.format, args.getintarg("gt"));
  //  ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
  //  if (cinfo == NULL) {
  //    logout << "ERROR (documentaction::define_external_macros): get_collectinfo_ptr returned NULL\n";
  //    return;
  //  }
  //load_formatinfo (cinfo->format, args.getintarg("gt"));

  if (formatinfo.DocumentUseHTML) {

    // frame stuff
    if (args["fc"] == "1") {
      text_t httpdocument;
      disp.expandstring (displayclass::defaultpackage, "_httpdocument_", httpdocument);
      httpdocument += "&amp;f=1";
      disp.setmacro ("httpdocument", displayclass::defaultpackage, httpdocument);
      disp.setmacro ("gsdltop", displayclass::defaultpackage, "documenttop");
      formatinfo.DocumentText = "[Text]";
    }
    text_tmap::iterator it = cinfo.format.find ("homepage");
    if (it != cinfo.format.end()) {
      text_t httppagehome;
      if (get_link (args, protos, (*it).second, httppagehome, logout))
	disp.setmacro ("httppagehome", displayclass::defaultpackage, httppagehome);
    } 
  }

  // don't want navigation bar if page is 'detached'
  if (!args.getintarg("x")) {

    collectproto->get_filterinfo (collection, filterinfo, err, logout);
    if (err == noError) {
      // check that there's a browse filter
      if (filterinfo.filterNames.find ("BrowseFilter") != filterinfo.filterNames.end()) {
	
	metadata.insert ("Title");
	bool getParents = false;
	get_children ("", collection, args["l"], metadata, getParents, collectproto, response, logout);
	
	bool has_search_button = true;
	collectproto->is_searchable(collection, has_search_button, err, logout);
	if (err != noError) has_search_button = false;

	// calculate width of spacers and set _widthtspace_ macro
	if (args.getintarg("v") == 0) set_spacemacro (disp, response, has_search_button);

	// set _navigationbar_ macro
	set_navbarmacros (disp, response, has_search_button, args, cinfo);
   
      }
    } else {
      logout << text_t2ascii 
	     << "Error (documentaction::define_external_macros()) in call to get_filterinfo() " 
	     << get_comerror_string (err);
    }
  }

  // send feedback button and pages
  text_tmap::iterator usability = cinfo.format.find("Usability");
  if(usability!=cinfo.format.end()){
    disp.setmacro("usability",displayclass::defaultpackage,"_usablink_");
    disp.setmacro("usabinterface",displayclass::defaultpackage,("_usab"+(*usability).second+"_"));
    disp.setmacro("usabilityscript", displayclass::defaultpackage, "_usabshowscript_");
  }

  // version num
  disp.setmacro("versionnum", displayclass::defaultpackage, GSDL_VERSION);

}

bool documentaction::get_link (cgiargsclass &args, recptprotolistclass *protos,
			       const text_t &inlink, text_t &outlink, ostream &logout) {

  FilterResponse_t response;
  text_tset metadata;
  metadata.insert ("section");
  
  // check current collection first
  recptproto *collectproto = protos->getrecptproto (args["c"], logout);

  if (get_info (inlink, args["c"], args["l"], metadata, false, collectproto, response, logout)) {
    if (!response.docInfo[0].metadata["section"].values[0].empty()) {
#ifndef DOCHANDLE
      outlink = "_httpdocument_&amp;d=" + response.docInfo[0].metadata["section"].values[0];
#else
      outlink = "_httpdocumenthandle_("+encodeForURL(args["c"])+","+response.docInfo[0].metadata["section"].values[0]+")";
#endif

      return true;
    }
  }

  // check all the other enabled collections

  if (args["ccs"] == "1" && !args["cc"].empty()) {
    text_tarray collections;
    splitchar (args["cc"].begin(), args["cc"].end(), ',', collections);
    
    text_tarray::const_iterator col_here = collections.begin();
    text_tarray::const_iterator col_end = collections.end();

    while (col_here != col_end) {
	  
      // don't need to check current collection again
      if (*col_here == args["c"]) {++col_here; continue;}
	  
      collectproto = protos->getrecptproto (*col_here, logout);
      
      if (get_info (inlink, *col_here, args["l"], metadata, false, collectproto, response, logout)) {
	if (!response.docInfo[0].metadata["section"].values[0].empty()) {
#ifndef DOCHANDLE
	  outlink = "_httpdocument_&amp;c=" + *col_here + "&amp;d=" +
	    response.docInfo[0].metadata["section"].values[0];
#else
	  outlink = "_httpdocumenthandle_("+*col_here+","+response.docInfo[0].metadata["section"].values[0]+")";
#endif

	  return true;
	}
      }
      ++col_here;
    }
  }
  return false;
}

void documentaction::load_formatinfo (const text_tmap &colformat, int gt) {

  formatinfo.clear();
  text_tmap::const_iterator format_here = colformat.begin();
  text_tmap::const_iterator format_end = colformat.end();

  while (format_here != format_end) {
    if (((*format_here).first == "DocumentImages") && 
	((*format_here).second == "true"))
      formatinfo.DocumentImages = true;
    else if (((*format_here).first == "DocumentTitles") && 
	     ((*format_here).second == "false"))
      formatinfo.DocumentTitles = false;
    else if ((*format_here).first == "DocumentHeading") 
      formatinfo.DocumentHeading = (*format_here).second;
    else if (((*format_here).first == "DocumentContents") && 
	     ((*format_here).second == "false"))
      formatinfo.DocumentContents = false;
    else if (((*format_here).first == "DocumentArrowsBottom") && 
	     ((*format_here).second == "false"))
      formatinfo.DocumentArrowsBottom = false;
    else if (((*format_here).first == "DocumentArrowsTop") && 
	     ((*format_here).second == "true"))
      formatinfo.DocumentArrowsTop = true;
    else if ((*format_here).first == "DocumentButtons")
      splitchar ((*format_here).second.begin(), (*format_here).second.end(),
		 '|', formatinfo.DocumentButtons);
    else if ((*format_here).first == "DocumentText") 
      formatinfo.DocumentText = (*format_here).second;  
    else if ((*format_here).first == "RelatedDocuments")
      formatinfo.RelatedDocuments = (*format_here).second; 
    else if (((*format_here).first == "DocumentUseHTML") && 
	     ((*format_here).second == "true"))
      formatinfo.DocumentUseHTML = true;
    else if (((*format_here).first == "AllowExtendedOptions") && 
	     ((*format_here).second == "true"))
      formatinfo.AllowExtendedOptions = true;
    else 
      formatinfo.formatstrings[(*format_here).first] = (*format_here).second;
    
    ++format_here;
  }

  // never want arrows when text is expanded
  if (gt) {
    formatinfo.DocumentArrowsBottom = false;
    formatinfo.DocumentArrowsTop = false;
  }
}


// define all the macros which are related to pages generated
// by this action. we also load up the formatinfo structure 
// here (it's used in do_action as well as here)
void documentaction::define_internal_macros (displayclass &disp, cgiargsclass &args, 
					     recptprotolistclass *protos, ostream &logout) {
  
  // define_internal_macros sets the following macros:
  
  // _pagetitle_            the title to be displayed at the top of the browser window
  
  // _imagethispage_        the title image to be displayed at top right of page

  // _navarrowsbottom_      this may be overridden to "" when format option 
  //                        DocumentArrowsBottom is false

  // _navarrowstop_         likewise for DocumentArrowsTop

  // _httpprevarrow_        links to next and previous sections of document - used
  // _httpnextarrow_        by DocumentArrowsBottom

  // _header_               the header macro is overridden if we're not at a top level 
  //                        classification to remove the title block

  // _thisOID_              the OID (directory) of the current document - this corresponds
  //                        to the assocfilepath metadata element

  // _documentheader_       Custom header info for the specified document
  // must have a valid collection server to continue

  // _allowusercomments_      Whether the User Comments area is to be shown for this doc or not

  text_t &collection = args["c"];
  if (collection.empty()) return;
  recptproto *collectproto = protos->getrecptproto (collection, logout);
  if (collectproto == NULL) return;

  text_tset metadata;
  FilterResponse_t response;
  text_t &arg_d = args["d"];
  text_t &arg_cl = args["cl"];

  if (!formatinfo.DocumentArrowsBottom) {
    disp.setmacro("navarrowsbottom", "document", "");
  } else if (!formatinfo.DocumentArrowsBottom) {
    disp.setmacro("navarrowstop", "document", "");
  } 

  if (!arg_d.empty() && (formatinfo.DocumentArrowsBottom || formatinfo.DocumentArrowsTop)) {
    // set _httpprevarrow_ and _httpnextarrow_
    set_arrow_macros (args, collectproto, disp, logout);
  }

  metadata.insert ("Title");
  metadata.insert ("gs.DocumentHeader");
  metadata.insert ("DocumentHeader");
  
  bool fulltoc = false;

  if (args["cl"] != "search") {
    // see if there's a FullTOC string
    text_t cl_top, full_toc;
    get_top (arg_cl, cl_top);
    if (get_formatstring (cl_top, "FullTOC", formatinfo.formatstrings, full_toc))
      if (full_toc == "true") fulltoc = true;
    disp.setmacro("cltop", "document", cl_top);
  }
  
  if (!arg_d.empty() && !fulltoc) {
    // we're at document level

    metadata.insert ("assocfilepath");
    metadata.insert("archivedir"); // backwards compatibility
    comerror_t err;
    OptionValue_tarray options;
    // we need to do the query again for the z3950proto
    if (collectproto->get_protocol_name(err)=="z3950proto") {
      OptionValue_t opt;
      opt.name="Term";opt.value=args["q"];options.push_back(opt);
      opt.name="QueryType";
      opt.value=(args.getintarg("t")) ? "ranked" : "boolean";
      options.push_back(opt);
      opt.name="Index";opt.value=args["h"];options.push_back(opt);
    }
    
    //do not display relation metadata
    disp.setmacro ("relateddoc", "document", "");  

    // Whether the UserComments section is supposed to be displayed when a document is loaded or not
    if (collectproto != NULL) {
      ColInfoResponse_t *cinfo = NULL;

      cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
      
      text_tmap::const_iterator user_comments_it = cinfo->format.find ("AllowUserComments");
      if ((user_comments_it != cinfo->format.end()) && ((*user_comments_it).second == "true")) {
	disp.setmacro ("allowusercomments", "document", "1"); // document package
	//disp.setmacro ("allowusercomments", displayclass::defaultpackage, "1"); // Global
      }
    }
   
    //if preferences indicate relevant docs should be collected
    //and there is no particular format specified then display
    //this default format.
     if(args["rd"] == "1" && formatinfo.RelatedDocuments.empty()){

       text_t relation = g_EmptyText; //string for displaying relation metadata

       //call function in formattools.cpp which will return the text of the 
       //related documents in a vertical list. This is the default format.

       if (get_info (arg_d, collection, args["l"], metadata, options, false, collectproto, response, logout)) 
	 relation += get_related_docs(collection, collectproto, response.docInfo[0], logout);
       
       //set macro to be the related document string

       disp.setmacro ("relateddoc", "document", relation); 
     }
     
  
    // get metadata for this document and it's parents
    if (get_info (arg_d, collection, args["l"], metadata, options, 
		  true, collectproto, response, logout)) {

      disp.setmacro ("header", "document", "_textheader_");
      text_tarray pagetitlearray;
      if (!response.docInfo[0].metadata["Title"].values[0].empty())
	pagetitlearray.push_back (response.docInfo[0].metadata["Title"].values[0]);
      
      if (args["gt"] != "1") {
	MetadataInfo_t *parenttitle = response.docInfo[0].metadata["Title"].parent;
	while (parenttitle != NULL) {
	  if (!parenttitle->values[0].empty())
	    pagetitlearray.push_back (parenttitle->values[0]);
	  parenttitle = parenttitle->parent;
	}
      }
      reverse (pagetitlearray.begin(), pagetitlearray.end());
      text_t pagetitle;
      joinchar (pagetitlearray, ": ", pagetitle);
      // remove html tags from the title
      text_t::iterator open_tag=pagetitle.begin();
      while (open_tag < pagetitle.end()) {
	if (*open_tag == '<') {
	  text_t::iterator close_tag=open_tag+1;
	  text_t::iterator donechar=pagetitle.end();
	  while (close_tag < donechar) {
	    if (*close_tag == '>')
	      break;
	    ++close_tag;
	  }
	  if (close_tag < donechar) // remove this html tag, replace with space
	    *close_tag=' ';
	  pagetitle.erase(open_tag, close_tag);
	}
	++open_tag;
      }
      disp.setmacro ("pagetitle", "document", pagetitle);

      // custom header info
      text_t custom_header;
      if (is_top (arg_d)) {
	custom_header = response.docInfo[0].metadata["gs.DocumentHeader"].values[0];
	if (custom_header.empty()) {
	  // try ex header
	  custom_header = response.docInfo[0].metadata["DocumentHeader"].values[0]; 
	}
      } else {
	MetadataInfo_t *parentch = response.docInfo[0].metadata["gs.DocumentHeader"].parent;
	while (parentch != NULL) {
	  custom_header = parentch->values[0];
	  parentch = parentch->parent;
	}
	if (custom_header.empty()) {
	  // try ex header
	  MetadataInfo_t *parentch = response.docInfo[0].metadata["DocumentHeader"].parent;
	  while (parentch != NULL) {
	    custom_header = parentch->values[0];
	    parentch = parentch->parent;
	  }
	}
      }
      if (!custom_header.empty()) {
	disp.setmacro("documentheader", "document", custom_header);
      }
	
      if (is_top (arg_d)) {
        text_t thisOID = response.docInfo[0].metadata["assocfilepath"].values[0];
        if (thisOID.empty()) {
          thisOID = response.docInfo[0].metadata["archivedir"].values[0];
        }
	disp.setmacro ("thisOID", displayclass::defaultpackage, dm_safe(thisOID));
      }
        else {
	MetadataInfo_t *parentad = response.docInfo[0].metadata["assocfilepath"].parent;
	text_t thisOID;
	while (parentad != NULL) {
	  thisOID = parentad->values[0];
	  parentad = parentad->parent;
	}
        if (thisOID.empty()) {
          // try archivedir
          MetadataInfo_t *parentad = response.docInfo[0].metadata["archivedir"].parent;
          while (parentad != NULL) {
            thisOID = parentad->values[0];
            parentad = parentad->parent;
          }
        }
	disp.setmacro ("thisOID", displayclass::defaultpackage, dm_safe(thisOID));
      }
    }
  } else {
    if (!arg_cl.empty()) {
	
      // create the currentnodevalue macro - this is the value of the node
      // in the hierarchy that is currently open.
      if (get_info (arg_cl, collection, args["l"], metadata, false, collectproto, response, logout)) {
	text_t &title = response.docInfo[0].metadata["Title"].values[0];
	disp.setmacro ("currentnodevalue", "document", title);
      }
      // get metadata for top level classification
      text_t classtop;
      get_top (arg_cl, classtop);
      metadata.insert ("childtype");
      metadata.insert ("parameters");

      if (get_info (classtop, collection, args["l"], metadata, false, collectproto, response, logout)) {
	  
	text_t &title = response.docInfo[0].metadata["Title"].values[0];
	bool unknown = false;

	// test if we have macros defined for this classification's
	// metadata name - if not we'll just display the name
	text_t tmp;
	text_t macroname="_label" + title + "_";
	disp.expandstring ("Global", macroname, tmp);
	if (tmp == macroname) unknown = true; // it wasn't expanded

	if (unknown) {
	  disp.setmacro ("pagetitle", "document", title);
	} else {
	  disp.setmacro ("pagetitle", "document", macroname);
	}
	/* for ns4/image layout compatibility. when this is no longer
	 * needed, set imagethispage to _label+title+_ */
	text_t titlemacroname="_titleimage" + title + "_";
	disp.expandstring ("Global", titlemacroname, tmp);
	if (tmp == titlemacroname) { /* _titleimage$META_ isn't defined */
	  if (!unknown) /* _label$META_ is defined */
	    disp.setmacro ("imagethispage", "document", macroname);
	  else
	    disp.setmacro ("imagethispage", "document", title);
	} else { /* _titleimage$META_ is defined */
	  disp.setmacro ("imagethispage", "document", titlemacroname);
	}

      	//if the document is not a document from a collection
	//we must set the macro to be an empty string
	disp.setmacro ("relateddoc", "document", ""); 

	// Add macros specific to the Collage/Phind classifiers 
	text_t &childtype = response.docInfo[0].metadata["childtype"].values[0];
	if (childtype == "Collage") {

	  text_t::iterator a = arg_cl.begin();
	  text_t::iterator b = arg_cl.end();

	  bool collage = true;
	  while (a != b) {
	    if (*a == 46) collage = false;
	    ++a;
	  }

	  if (collage) {
	    disp.setmacro ("collageclassifier", "document", "_collageapplet_");

            // Next, macros that control the way the classifier is displayed
            text_t parameters = response.docInfo[0].metadata["parameters"].values[0];

            // extract key=value pairs and set as macros
            text_t::iterator here = parameters.begin();
            text_t::iterator end = parameters.end();
            text_t key, value;

            while (here != end) {
              // get the next key and value pair
              here = getdelimitstr (here, end, '=', key);
              here = getdelimitstr (here, end, ';', value);

              // store this key=value pair
              if (!key.empty() && !value.empty()) {
                disp.setmacro ("collage"+key, "document", value);
              }
            }

	    
	  }
	}

	if (childtype == "Phind") {

	  // First, a macro to display the phind classifier
	  disp.setmacro ("phindclassifier", "document", "_phindapplet_");

	  // Next, macros that control the way the classifier is displayed
	  text_t parameters = response.docInfo[0].metadata["parameters"].values[0];

	  // extract key=value pairs and set as macros
	  text_t::iterator here = parameters.begin();
	  text_t::iterator end = parameters.end();
	  text_t key, value;
	  
	  while (here != end) {
	    // get the next key and value pair
	    here = getdelimitstr (here, end, '=', key);
	    here = getdelimitstr (here, end, ';', value);

	    // store this key=value pair
	    if (!key.empty() && !value.empty()) {
	      disp.setmacro (key, "document", value);
	    }
	  }
	} // end if (childtype == "Phind")
      }
    } // end if (!arg_cl.empty()) {
  }
}


bool documentaction::do_action(cgiargsclass &args, recptprotolistclass *protos, 
                               browsermapclass *browsers, displayclass &disp, 
                               outconvertclass &outconvert, ostream &textout, 
                               ostream &logout)
{
  if ((args["book"] == "flashxml") && (!args["d"].empty()))
  {
  	return do_action_flashxml(args,protos,browsers,disp,outconvert,textout,logout);
  }
  else if ((args["book"] == "on") && (!args["d"].empty())) 
  {
  	return do_action_book(args,protos,browsers,disp,outconvert,textout,logout);
  }
  else 
  {
   	return do_action_html(args,protos,browsers,disp,outconvert,textout,logout);
  }

}

//displaying the normal display when bookswitch=0(html)
bool documentaction::do_action_html(cgiargsclass &args, recptprotolistclass *protos, 
                               browsermapclass *browsers, displayclass &disp, 
                               outconvertclass &outconvert, ostream &textout, 
                               ostream &logout)
{
  // must have a valid collection server
  recptproto *collectproto = protos->getrecptproto (args["c"], logout);
  if (collectproto == NULL) {
    logout << "documentaction::do_action called with NULL collectproto\n";
    textout << outconvert << disp << "_document:header_\n" 
	    << "Error: Attempt to get document without setting collection\n"
	    << "_document:footer_\n";
  } else {  
    
    text_t OID = args["d"];
    if (OID.empty()) OID = args["cl"];
    if (OID.empty()) {
      textout << outconvert << disp << "Document contains no data_document:footer_\n";
      return true;
    }

  
    if (formatinfo.DocumentUseHTML && !args["d"].empty()) {
      
      if (args["f"] == "1") {
	textout << outconvert << disp 
		<< "<html><head></head>\n"
		<< "<frameset rows=\"68,*\" noresize border=0>\n"
		<< "<frame scrolling=no frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=p&p=nav\">\n"
#ifndef DOCHANDLE
		<< "<frame name=\"documenttop\" frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=d&d=" 
		<< encodeForURL(args["d"]) << "\">"
#else
		<< "<frame name=\"documenttop\" frameborder=0 src=\"_httpdocumenthandle_(" 
		<< encodeForURL(args["c"]) << "," << encodeForURL(args["d"]) << ")\">"
#endif
		<< "<noframes>\n"
		<< "<p>You must have a frame enabled browser to view this.</p>\n"
		<< "</noframes>\n"
		<< "</frameset>\n"
		<< "</html>\n";
      } else {
	output_document (OID, args, collectproto, browsers, disp, outconvert, textout, logout);	
      }
      return true;
    }

   
    textout << outconvert << disp << "_document:header_\n"
	    << "_document:content_\n";
      
    // output the table of contents
    browsetoolsclass b;
    b.output_toc(args, browsers, formatinfo, collectproto, 
                 disp, outconvert, textout, logout);
   
    if (formatinfo.DocumentArrowsTop && !args["d"].empty()) {
      textout << outconvert << disp << "_document:navarrowstop_\n";
    }

    //output the related documents (may be the empty string)
    //will not output the docs if a format string is specified
    textout << outconvert << disp << "_document:relateddoc_\n";

    // output the document text
    if (!args["d"].empty()) {
      output_document (OID, args, collectproto, browsers, disp, outconvert, textout, logout);
    }

    textout << outconvert << disp << "_document:footer_\n";
  }
  return true;
}

//displaying the book display when bookswitch=on
bool documentaction::do_action_book(cgiargsclass &args, recptprotolistclass *protos, 
                               browsermapclass *browsers, displayclass &disp, 
                               outconvertclass &outconvert, ostream &textout, 
                               ostream &logout)
{
  // must have a valid collection server
  recptproto *collectproto = protos->getrecptproto (args["c"], logout);
  if (collectproto == NULL) 
  {
    	logout << "documentaction::do_action called with NULL collectproto\n";
    	textout << outconvert << disp << "_document:header_\n" 
		<< "Error: Attempt to get document without setting collection\n";
  } 
  else 
  {  
    	text_t OID = args["d"];
    	if (OID.empty()) OID = args["cl"];
    	if (OID.empty()) 
	{
      		textout << outconvert << disp << "Document contains no data\n";
      		return true;
    	}

    	if (formatinfo.DocumentUseHTML && !args["d"].empty()) 
	{
      		if (args["f"] == "1") 
		{
			textout << outconvert << disp 
				<< "<html><head></head>\n"
				<< "<frameset rows=\"68,*\" noresize border=0>\n"
				<< "<frame scrolling=no frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=p&p=nav\">\n"
			#ifndef DOCHANDLE
				<< "<frame name=\"documenttop\" frameborder=0 src=\"_gwcgi_?_optsite_e=_compressedoptions_&a=d&d=" 
				<< encodeForURL(args["d"]) << "\">"
			#else
				<< "<frame name=\"documenttop\" frameborder=0 src=\"_httpdocumenthandle_(" 
				<< encodeForURL(args["c"]) << "," << encodeForURL(args["d"]) << ")\">"
			#endif
				<< "<noframes>\n"
				<< "<p>You must have a frame enabled browser to view this.</p>\n"
				<< "</noframes>\n"
				<< "</frameset>\n"
				<< "</html>\n";
      		} 
		else 
		{
			output_document (OID, args, collectproto, browsers, disp, outconvert, textout, logout);	
      		}
      		
		return true;
    	}


	//the header bit and the navigation button   
    	textout << outconvert << disp << "_document:header_\n" << "_document:content_\n";
       
	//display the book
	textout << outconvert << disp << "_document:flashbook_\n";
	
	//the footer bit without the document arrow
	textout << outconvert << disp << "</div> <!-- id=document -->\n";
	textout << outconvert << disp << "_endspacer__htmlfooter_\n";
  }
  
  return true;
}

//Displaying the xml when bookswitch=flashxml
bool documentaction::do_action_flashxml(cgiargsclass &args, recptprotolistclass *protos, 
                               browsermapclass *browsers, displayclass &disp, 
                               outconvertclass &outconvert, ostream &textout, 
                               ostream &logout)
{
  // must have a valid collection server
  recptproto *collectproto = protos->getrecptproto (args["c"], logout);
  if (collectproto == NULL) 
  {
    logout << "documentaction::do_action called with NULL collectproto\n";
    textout << outconvert << disp 
	    << "<Message>\n"
	    << "  Error: Attempt to get document without setting collection\n"
	    << "</Message>\n";
  } 
  else 
  {  
    text_t OID = args["d"];
    if (OID.empty()) OID = args["cl"];
    if (OID.empty()) 
    {
      textout << outconvert << disp 
	      << "<Message>\n"
	      << "  Document contains no data\n"
	      << "</Message>\n";
      return true;
    }

    if (formatinfo.DocumentUseHTML && !args["d"].empty())
    {  
      if (args["f"] == "1") 
      {
	textout << outconvert << disp 
		<< "<Message>\n"
		<< "  Using frames makes no sense for XML output\n"
		<< "</Message>\n";
      } 
      else 
      {
	output_document_flashxml (OID, args, collectproto, browsers, disp, outconvert, textout, logout);	
      }

      return true;
    }

    // output the document text
    if (!args["d"].empty()) 
    {
      output_document_flashxml (OID, args, collectproto, browsers, disp, outconvert, textout, logout);
    }
  }

  return true;
}



void documentaction::output_text (ResultDocInfo_t &docinfo, format_t *formatlistptr,
				  const TermInfo_tarray &terminfo, const text_t &OID,
				  bool highlight, int hastxt, int wanttext, 
				  text_t &collection, recptproto *collectproto,
				  browsermapclass *browsers, displayclass &disp,
				  outconvertclass &outconvert, ostream &textout,
				  ostream &logout, cgiargsclass &args) {
  
  DocumentRequest_t docrequest;
  DocumentResponse_t docresponse;
  comerror_t err;
  if (hastxt == 1) {

    if (wanttext) {
      // get the text
      docrequest.OID = OID;
      collectproto->get_document (collection, docrequest, docresponse, err, logout);

      // cut down on overhead by not using formattools if we only want the text
      // (wanttext will equal 2 if we want text and other stuff too)
      if (wanttext == 1)
	if (highlight) {
	  highlighttext(docresponse.doc, args, terminfo, disp, outconvert, textout);
	} else {
	  textout << outconvert << disp << docresponse.doc;
	}
    }
  
    if (wanttext != 1) {
      text_tmap options;
      options["text"] = docresponse.doc;

      browsetoolsclass b;
      options["assocfilepath"] = b.get_assocfile_path();
      if (formatinfo.AllowExtendedOptions) {
	b.load_extended_options(options, args, browsers, formatinfo, 
                                collectproto, disp, outconvert, logout);
      }
      
      text_t doctext 
	 = get_formatted_string (collection, collectproto, docinfo, disp,
				 formatlistptr, options, logout); 
      
      if (highlight) {
	highlighttext(doctext, args, terminfo, disp, outconvert, textout);
      } else {
	textout << outconvert << disp << doctext;
      }
    }
  }
}


void documentaction::output_document (const text_t &OID, cgiargsclass &args, 
				      recptproto *collectproto, browsermapclass *browsers,
				      displayclass &disp, outconvertclass &outconvert,
				      ostream &textout, ostream &logout) {
  FilterResponse_t inforesponse;
  FilterResponse_t queryresponse;
  text_tset metadata;
  bool getParents = false;
  bool highlight = false;
  int wanttext = 0;
  int arg_gt = args.getintarg("gt");
  text_t &collection = args["c"];
    
  // if we have a query string and highlighting is turned on we need
  // to redo the query to get the terms for highlighting
  
  if (!args["q"].empty() && args.getintarg("hl")) {

    ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
    bool segment = false;
    if (cinfo != NULL) {
      segment = cinfo->isSegmented;
    }
    FilterRequest_t request;
    comerror_t err;
    request.filterResultOptions = FRmatchTerms;
    text_t formattedstring = args["q"];
    format_querystring (formattedstring, args.getintarg("b"), segment);
    set_fulltext_queryfilter_options (request, formattedstring, args);
    args["q"] = formattedstring; // need to set this here for phrase 
                   // highlighting, where we look at the querystring
    collectproto->filter (args["c"], request, queryresponse, err, logout);
    if (err != noError) {
      outconvertclass text_t2ascii;
      logout << text_t2ascii
	     << "documentaction::output_document: call to QueryFilter failed "
	     << "for " << args["c"] << " collection (" << get_comerror_string (err) << ")\n";
      highlight = false;
    } else {
      highlight = true;
    }
  }

  format_t *formatlistptr = new format_t();
  parse_formatstring (formatinfo.DocumentText, formatlistptr, metadata, getParents);

  metadata.insert ("hastxt");
  metadata.insert ("haschildren");

  if (formatinfo.DocumentText == "[Text]")
    wanttext = 1;
  else {
    char *docformat = formatinfo.DocumentText.getcstr();
    if (strstr (docformat, "[Text]") != NULL)
      wanttext = 2;
    delete []docformat;
  }
    
  textout << outconvert << "<div class=\"documenttext\">\n";

  if (get_info (OID, collection, args["l"], metadata, getParents, collectproto, inforesponse, logout)) {
    int hastxt = inforesponse.docInfo[0].metadata["hastxt"].values[0].getint();
    int haschildren = inforesponse.docInfo[0].metadata["haschildren"].values[0].getint();
    
    if (arg_gt == 0) {
      output_text (inforesponse.docInfo[0], formatlistptr, queryresponse.termInfo, 
		   OID, highlight, hastxt, wanttext, collection, collectproto, 
		   browsers, disp, outconvert, textout, logout, args);

    } else {

      ResultDocInfo_t thisdocinfo = inforesponse.docInfo[0];
	
      // text is to be expanded
      text_t exOID = OID;
      if (haschildren != 1) exOID = get_parent (OID);
      if (exOID.empty()) exOID = OID;
	
      // if we're not in a document (i.e. we're in a top level classification)
      // we need to pass "is_classify = true" to get_contents so that it 
      // doesn't recurse all the way through each document in the classification
      bool is_classify = false;
      if (args["d"].empty()) is_classify = true;

      get_contents (exOID, is_classify, metadata, collection, args["l"], 
		    collectproto, inforesponse, logout);
	
      ResultDocInfo_tarray::iterator sechere = inforesponse.docInfo.begin();
      ResultDocInfo_tarray::iterator secend = inforesponse.docInfo.end();

      if (arg_gt == 1) {
	// check if there are more than 10 sections containing text to be expanded - 
	// if there are output warning message - this isn't a great way to do this
	// since the sections may be very large or very small - one day I'll fix it 
	// -- Stefan.
	int seccount = 0;
	while (sechere != secend) {
	  int shastxt = (*sechere).metadata["hastxt"].values[0].getint();
	  if (shastxt == 1) ++seccount;
	  if (seccount > 10) break;
	  ++sechere;
	}
	if (seccount > 10) {
	  // more than 10 sections so output warning message and text 
	  // for current section only
	  textout << outconvert << disp << "<div class=\"warning\">_document:textltwarning_</div>\n";

	  output_text (thisdocinfo, formatlistptr, queryresponse.termInfo, 
		       OID, highlight, hastxt, wanttext, collection, 
		       collectproto, browsers, disp, outconvert, textout, logout, args);
	  
	}
	else arg_gt = 2;
      }

      if (arg_gt == 2) {
	// get the text for each section
	sechere = inforesponse.docInfo.begin();
	int count = 0;
	while (sechere != secend) {
	  textout << outconvert << disp << "\n<p><a name=\"" << (*sechere).OID << "\"></a>\n";

	  int shastxt = (*sechere).metadata["hastxt"].values[0].getint();


	  output_text (*sechere, formatlistptr, queryresponse.termInfo, 
		       (*sechere).OID, highlight, shastxt, wanttext, collection, 
		       collectproto, browsers, disp, outconvert, textout, logout, args);

	  ++count;
	  ++sechere;
	}
      }
    }
  }
  textout << outconvert << "</div>\n";
  delete formatlistptr;
}

void documentaction::output_document_flashxml(const text_t &OID, cgiargsclass &args, 
				      recptproto *collectproto, browsermapclass *browsermap,
				      displayclass &disp, outconvertclass &outconvert,
				      ostream &textout, ostream &logout) {
	//if this is not at the document level --> do Nothing!!
	if (args["d"].empty())
		return;
		
	//if we have a query string and highlighting is turned on we need
  	//to redo the query to get the terms for highlighting
	FilterResponse_t queryresponse;
  	bool highlight = false;
	if (!args["q"].empty() && args.getintarg("hl")) 
	{
		text_t &collection = args["c"];
    		ColInfoResponse_t *cinfo = recpt->get_collectinfo_ptr (collectproto, collection, logout);
    		bool segment = false;
    		if (cinfo != NULL) 
		{
      			segment = cinfo->isSegmented;
    		}
    		FilterRequest_t request;
    		comerror_t err;
    		request.filterResultOptions = FRmatchTerms;
    		text_t formattedstring = args["q"];
    		format_querystring (formattedstring, args.getintarg("b"), segment);
    		set_fulltext_queryfilter_options (request, formattedstring, args);
    		args["q"] = formattedstring; 	//need to set this here for phrase 
                   				//highlighting, where we look at the querystring
    		collectproto->filter (args["c"], request, queryresponse, err, logout);
    		if (err != noError)
		{
      			outconvertclass text_t2ascii;
      			logout << text_t2ascii
	     			<< "documentaction::output_document: call to QueryFilter failed "
			        << "for " << args["c"] << " collection (" << get_comerror_string (err) << ")\n";
      			highlight = false;
    		} 
		else 
		{
     			highlight = true;
    		}
  	}
	
	FilterResponse_t response;
	bool getParents = false;
	text_tset metadata;
	text_t classifytype, classification, formatstring;
	
	//metadata elements needed by recurse_contents
  	metadata.insert ("Title");
  	metadata.insert ("gs.allowPrinting");
	metadata.insert ("haschildren");

  	//protocol call
  	if (!get_info (OID, args["c"], args["l"], metadata, getParents, collectproto, response, logout))
    		return;

  	recurse_contents (response.docInfo[0], args, metadata, 
			    getParents, collectproto, disp, outconvert, textout, logout, highlight, queryresponse.termInfo);
}

void documentaction::recurse_contents(ResultDocInfo_t &section, cgiargsclass &args,
                                        text_tset &metadata, bool &getParents, 
                                        recptproto *collectproto, displayclass &disp, 
                                        outconvertclass &outconvert, ostream &textout, ostream &logout, 
					bool highlight, const TermInfo_tarray &terminfo)
{
 	text_t formatstring;

  	bool is_classify = false;
  	
  	//output this section 
  	text_t classification = "Document";
  	
	textout << outconvert << disp << "<Section>\n";
	
	//get the title
	textout << outconvert << disp << "<Description>\n";

	textout << outconvert << disp << "  <Metadata name=\"Title\">";
	text_t t_title = section.metadata["Title"].values[0];

	//if it is highlighted
	/*if (highlight == true)
	{
		highlighttext(t_title, args, terminfo, disp, outconvert, textout);
	}
	else
	{*/
		textout << outconvert << disp << t_title;
	//}

	textout << outconvert << disp << "</Metadata>\n";

	// if top-level document section, add in allowPrint metadata
	if (is_top(section.OID)) {
	  textout << outconvert << disp << "  <Metadata name=\"allowPrint\">";
	  text_t t_allowprint = "true";

	  if (section.metadata["gs.allowPrinting"].values.size()>0) {
	    text_t opt_allowprint = section.metadata["gs.allowPrinting"].values[0];
	    if (!opt_allowprint.empty()) {
	      t_allowprint = opt_allowprint;
	    }
	  }

	  textout << outconvert << disp << t_allowprint;
	  textout << outconvert << disp << "</Metadata>\n";
	}

	textout << outconvert << disp << "</Description>\n";

	//get the text
	DocumentRequest_t docrequest;
	DocumentResponse_t docresponse;
	comerror_t err;
	docrequest.OID = section.OID;
	text_t &collection = args["c"];
	collectproto->get_document(collection, docrequest, docresponse, err, logout);
	//if it is highlighted
	if (highlight == true)
	{
		highlighttext(docresponse.doc, args, terminfo, disp, outconvert, textout);
	}
	else
	{
		textout << outconvert << disp << docresponse.doc;
	}
	textout << outconvert << disp << "\n";
	
  	int haschildren = section.metadata["haschildren"].values[0].getint();
  	
  	// recurse through children
  	if (haschildren == 1)
	{
    		FilterResponse_t tmp;
    		get_children (section.OID, args["c"], args["l"], metadata, getParents, collectproto, tmp, logout);
    		ResultDocInfo_tarray::iterator thisdoc = tmp.docInfo.begin();
    		ResultDocInfo_tarray::iterator lastdoc = tmp.docInfo.end();

    		while (thisdoc != lastdoc) 
		{
      			recurse_contents (*thisdoc, args, metadata, getParents, collectproto, disp, outconvert, textout, logout, highlight, terminfo);
      			++thisdoc;
    		}
  	}
	
	textout << outconvert << disp << "</Section>\n";
}

void documentaction::set_arrow_macros (cgiargsclass &args, recptproto *collectproto,
				       displayclass &disp, ostream &logout) {

  text_tset metadata;
  FilterResponse_t response;
  FilterResponse_t presponse;

  int haschildren = 0;
  text_tarray next_siblings;
  text_t previous_sibling;
  text_t &arg_d = args["d"];

  // get info on current section
  metadata.insert("haschildren");
  if (!get_info(arg_d, args["c"], args["l"], metadata, false, collectproto, response, logout)) {
    logout << "error 1 in documentaction::set_arrow_macros\n";
    return;
  }
  haschildren = response.docInfo[0].metadata["haschildren"].values[0].getint();
      
  // get OIDs of next and previous siblings of current section and
  // all it's parents
  int parentcount = countchar(arg_d.begin(), arg_d.end(), '.');
  text_t thisoid = arg_d;
  while (parentcount > 0) {
    get_children (get_parent(thisoid), args["c"], args["l"], metadata, false, 
		  collectproto, response, logout);
    ResultDocInfo_tarray::iterator this_sibling = response.docInfo.begin();
    ResultDocInfo_tarray::iterator last_sibling = response.docInfo.end();
    bool first = true;
    while (this_sibling != last_sibling) {
      if ((*this_sibling).OID == thisoid) {
	if (!first && next_siblings.empty()) {
	  previous_sibling = (*(this_sibling-1)).OID;
	  int section_has_children = (*(this_sibling-1)).metadata["haschildren"].values[0].getint();
	  // if previous sibling has children we need to recurse
	  // down to the last descendant
	  while (section_has_children) {
	    get_children (previous_sibling, args["c"], args["l"], metadata, false, 
			  collectproto, presponse, logout);
	    if (!presponse.docInfo.empty()) {
	      ResultDocInfo_tarray::iterator it = presponse.docInfo.end() - 1;
	      previous_sibling = (*it).OID;
	      section_has_children = (*it).metadata["haschildren"].values[0].getint();
	    } else {
	      section_has_children = 0; // this should never happen
	    }
	  }
	}
	    
	if ((this_sibling+1) != last_sibling) {
	  next_siblings.push_back((*(this_sibling+1)).OID);
	} else {
	  next_siblings.push_back("");
	}
	break;
      }
      ++this_sibling;
      first = false;
    }
    thisoid = get_parent(thisoid);
    --parentcount;
  }

  // work out values for next link
  if (haschildren) {
#ifndef DOCHANLE
    disp.setmacro ("httpnextarrow", "document", "_httpdocument_&amp;cl=" + encodeForURL(args["cl"]) +
		   "&amp;d=" + encodeForURL(arg_d) + ".fc");
#else
    disp.setmacro ("httpnextarrow", "document", "_httpdocumenthandle_("+encodeForURL(args["c"])+","+encodeForURL(arg_d) + ".fc)";

#endif

  } else {
    text_tarray::const_iterator h = next_siblings.begin();
    text_tarray::const_iterator e = next_siblings.end();
    while (h != e) {
      if (!(*h).empty()) {
#ifndef DOCHANLE
	disp.setmacro ("httpnextarrow", "document", "_httpdocument_&amp;cl=" + encodeForURL(args["cl"]) +
		       "&amp;d=" + *h);
#else
	disp.setmacro ("httpnextarrow", "document", "_httpdocumenthandle_("+encodeForURL(args["c"])+","+*h+")";

#endif

	break;
      }
      ++h;
    }
  }

  // work out value for previous link
  if (!previous_sibling.empty()) {
#ifndef DOCHANDLE
    disp.setmacro ("httpprevarrow", "document", "_httpdocument_&amp;cl=" + encodeForURL(args["cl"]) +
		   "&amp;d=" + previous_sibling);
#else
    disp.setmacro ("httpprevarrow", "document", "_httpdocumenthandle_("+encodeForURL(args["c"])+","+ previous_sibling+")");

#endif

  } else {
    if (countchar(arg_d.begin(), arg_d.end(), '.')) {
#ifndef DOCHANDLE
      disp.setmacro ("httpprevarrow", "document", "_httpdocument_&amp;cl=" + encodeForURL(args["cl"]) +
		     "&amp;d=" + get_parent(arg_d));
#else
      disp.setmacro ("httpprevarrow", "document", "_httpdocumenthandle_("+encodeForURL(args["c"])+","+get_parent(arg_d)+")");

#endif

    }
  }
}
