
/**********************************************************************
 *
 * browsetools.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.
 *
 * $Id: browsetoolsclass.cpp 38924 2024-04-11 00:44:19Z kjdon $
 *
 *********************************************************************/

#include "browsetoolsclass.h"
#include "recptprototools.h"
#include "OIDtools.h"
#include "gsdlunicode.h"
#include "securitytools.h"

#if defined(GSDL_USE_IOS_H)
#  if defined(__WIN32__)
#    include <strstrea.h> // vc4
#  else
#    include <strstream.h>
#  endif
#else
#  include <sstream>
#endif


browsetoolsclass::browsetoolsclass()
{
}

browsetoolsclass::~browsetoolsclass()
{
}

// output_controls displays the detach, expand/contract contents,
// expand/contract text and highlighting/no highlighting buttons

void browsetoolsclass::output_controls(cgiargsclass &args, const text_tarray &ibuttons,
                                       recptproto *collectproto, displayclass &disp, 
                                       outconvertclass &outconvert, ostream &textout, 
                                       ostream &logout)
{
  if (args["u"] != "1") {
    
    FilterResponse_t response;
    text_tarray metadata;
    text_tarray buttons;
    
    text_tarray::const_iterator here = ibuttons.begin();
    text_tarray::const_iterator end = ibuttons.end();
    
    while (here != end) {
      
      if (*here == "Detach")
	buttons.push_back ("_document:imagedetach_");
      else if(*here == "Print")
        buttons.push_back ("_document:imageprint_"); 
      else if (*here == "Highlight") {
	if (args["hl"] == "1") 
	  buttons.push_back ("_document:imagenohighlight_");
	else 
	  buttons.push_back ("_document:imagehighlight_");
      } else if (*here == "Expand Contents") {
	if (args["gc"] == "1")
	  buttons.push_back ("_document:imagecontracttoc_");
	else 
	  buttons.push_back ("_document:imageexpandtoc_");
      } else if (*here == "Expand Text") {
	if (args.getintarg("gt"))
	  buttons.push_back ("_document:imagecontracttext_");
	else 
	  buttons.push_back ("_document:imageexpandtext_");
      }
      ++here;
    }
    
    here = buttons.begin();
    end = buttons.end();
    while (here != end) {
      textout << outconvert << disp << *here;
      ++here;
    }
  }
}

text_t browsetoolsclass::get_cover_image()
{
  return "_httpprefix_/collect/[collection]/index/assoc/{Or}{[parent(Top):assocfilepath],[assocfilepath],[parent(Top):archivedir],[archivedir]}/cover.jpg";
}

text_t browsetoolsclass::get_assocfile_path()
{
  return "_httpprefix_/collect/[collection]/index/assoc/{Or}{[parent(Top):assocfilepath],[assocfilepath],[parent(Top):archivedir],[archivedir]}/";

}
// at the moment this just writes out the html to display 
// the cover image (assuming it's called cover.jpg)
// this whole thing should be done with a call to the collection
// server which would send a link to the cover image if there
// was one otherwise send title, author and stuff
void browsetoolsclass::output_cover_image(cgiargsclass &args, recptproto *collectproto, 
                                          displayclass &disp, outconvertclass &outconvert, 
                                          ostream &textout, ostream &logout)
{
  if (args["d"].empty()) return;
  
  textout << outconvert << disp << "_document:coverimage_";
}

void browsetoolsclass::output_titles(cgiargsclass &args, recptproto *collectproto, 
                                     browsermapclass *browsermap, formatinfo_t &formatinfo,
                                     displayclass &disp, outconvertclass &outconvert,
                                     ostream &textout, ostream &logout)
{
  if (args["d"].empty()) return;
  
  text_tset metadata;
  bool getParents;
  FilterResponse_t response;

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

  if (!get_info (args["d"], args["c"], args["l"], metadata, getParents, collectproto, response, logout))
    return;

  text_tmap options;
  if (formatinfo.AllowExtendedOptions) {
    load_extended_options(options, args, browsermap, formatinfo, 
			  collectproto, disp, outconvert, logout);
  }
  textout << outconvert << disp 
	  << get_formatted_string (args["c"],collectproto, response.docInfo[0], disp,
				   formatlistptr, options, logout);
}

void browsetoolsclass::recurse_contents(ResultDocInfo_t &section, cgiargsclass &args, bool fulltoc,
                                        browserclass *bptr, text_tset &metadata, bool &getParents, 
                                        format_t *formatlistptr, format_tmap &formatlistmap, 
                                        formatinfo_t &formatinfo, browsermapclass *browsermap, 
                                        int tabcount, recptproto *collectproto, displayclass &disp, 
                                        outconvertclass &outconvert, ostream &textout, ostream &logout)
{
  text_t formatstring;

  bool is_classify = false;
  if (args["d"].empty() || fulltoc) is_classify = true;

  // output this section 
  bool use_table = is_table_content (formatlistptr);
  tabcount += bptr->output_section_group (section, args, args["c"], tabcount, formatlistptr, use_table, 
					  metadata, getParents, collectproto, disp, outconvert, 
					  textout, logout);

  text_t classification;
  if (!is_classify) classification = "Document";
  else get_top (args["cl"], classification);

  int haschildren = section.metadata["haschildren"].values[0].getint();
  const text_t &doctype = section.metadata["doctype"].values[0];
  text_t classifytype = section.metadata["childtype"].values[0];
  // HLists and DateLists are displayed as VLists when contents 
  // are expanded, Paged documents are displayed as HLists
  if (classifytype == "HList" || classifytype == "DateList") classifytype = "VList";
  if (classifytype == "Paged") classifytype = "HList";

  // recurse through children
  if ((haschildren == 1) && (!is_classify || fulltoc || doctype == "classify")) {

    // get browser for displaying children
    bptr = browsermap->getbrowser (classifytype);
    bptr->load_metadata_defaults (metadata);

    // get the formatstring if there is one 
    if (!get_formatstring (classification, classifytype, 
			   formatinfo.formatstrings, formatstring))
      formatstring = bptr->get_default_formatstring();

    format_tmap::const_iterator it = formatlistmap.find (formatstring);
    // check if formatlistptr is cached
    if (it != formatlistmap.end()) formatlistptr = (*it).second;
    else {
      formatlistptr = new format_t();
      parse_formatstring (formatstring, formatlistptr, metadata, getParents);
      formatlistmap[formatstring] = formatlistptr;
    }

    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, fulltoc, bptr, metadata, getParents, 
			formatlistptr, formatlistmap, formatinfo, browsermap, 
			tabcount, collectproto, disp, outconvert, textout, logout);
      ++thisdoc;
    }
  }
}


// expanded_contents recurses through all contents. there's a special case
// for an HList when contents are expanded (i.e. it's displayed as a VList)
//
// if we're inside a document we expand all contents from the top (unless
// expand_from_current_level is true), if we're at classification level we'll just
// expand out those contents below the current one

void browsetoolsclass::expanded_contents(cgiargsclass &args, int tabcount, bool fulltoc,
                                         browsermapclass *browsermap, formatinfo_t &formatinfo, 
                                         recptproto *collectproto, displayclass &disp, 
                                         outconvertclass &outconvert, ostream &textout, 
                                         ostream &logout, bool expand_from_current_level)
{
  if (args["d"].empty() && args["cl"].empty()) return;
  text_t OID;
  
  FilterResponse_t response;
  bool getParents = false;
  text_tset metadata;
  text_t classifytype, classification, formatstring;

  if (!args["d"].empty())
    {
      // document level
      if (fulltoc)
        {
          get_top(args["cl"], OID);
          classification = OID;
        }
      else 
        {
          classification = "Document";
          if (expand_from_current_level)
            {
              OID = args["d"];
            }
          else
            {
              // Always expand document level from top, unless
              // expand_from_current_level is true.
              get_top(args["d"], OID);
            }
        }
    }
  else
    {
      // classification level
      OID = args["cl"];
      get_top(args["cl"], classification);
    }

  // get classifytype of this level
  text_t tOID;
  if (is_top(OID)) {
    classifytype = "thistype";
    tOID = OID;
  } else {
    classifytype = "childtype";
    tOID = get_parent (OID);
  }
  metadata.insert (classifytype);

  if (!get_info (tOID, args["c"], args["l"], metadata, getParents, collectproto, response, logout))
    return;
  classifytype = response.docInfo[0].metadata[classifytype].values[0];
  // if we still don't have a classifytype we'll use the default
  if (classifytype.empty()) {
    browserclass *bptr = browsermap->get_default_browser ();
    classifytype = bptr->get_browser_name ();
  }

  // HLists are displayed as VLists when contents are expanded,
  // Paged documents are displayed as HLists
  if (classifytype == "HList") {
    classifytype = "VList";
    text_t pOID = get_parent (OID);
    if (!pOID.empty()) {
      OID = pOID;
      // this is assuming that top levels are always 'Invisible' !!!
      if (is_top (OID)) classifytype = "Invisible";
    }
  }
  if (classifytype == "Paged") classifytype = "HList";

  metadata.erase (metadata.begin(), metadata.end());
		  
  // metadata elements needed by recurse_contents
  metadata.insert ("childtype");
  metadata.insert ("doctype");
  metadata.insert ("haschildren");

  // load up metadata array with browser defaults
  browserclass *bptr = browsermap->getbrowser (classifytype);
  bptr->load_metadata_defaults (metadata);

  // get the formatstring if there is one or use the browsers default
  if (!get_formatstring (classification, classifytype, 
			 formatinfo.formatstrings, formatstring))
    formatstring = bptr->get_default_formatstring();

  format_t *formatlistptr = new format_t();
  parse_formatstring (formatstring, formatlistptr, metadata, getParents);

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

  format_tmap formatlistmap;
  formatlistmap[formatstring] = formatlistptr;

  recurse_contents (response.docInfo[0], args, fulltoc, bptr, metadata, 
		    getParents, formatlistptr, formatlistmap, formatinfo, browsermap, 
		    tabcount, collectproto, disp, outconvert, textout, logout);

  // clean up format list pointers
  format_tmap::const_iterator here = formatlistmap.begin();
  format_tmap::const_iterator end = formatlistmap.end();
  while (here != end) {
    delete (*here).second;
    ++here;
  }
}

void browsetoolsclass::load_formatstring(const text_t &classifytype, text_tset &metadata,
                                         bool &getParents, const text_t &classification, 
                                         browsermapclass *browsermap, formatinfo_t &formatinfo,
                                         format_tmap &formatlistmap)
{
  text_t formatstring;

  // load up metadata array with browser defaults
  browserclass *bptr = browsermap->getbrowser (classifytype);
  bptr->load_metadata_defaults (metadata);

  // get the formatstring if there is one or use the browsers default
  if (!get_formatstring (classification, classifytype, 
			 formatinfo.formatstrings, formatstring))
    formatstring = bptr->get_default_formatstring();

  // see if it's cached
  format_tmap::const_iterator it = formatlistmap.find (formatstring);
  if (it == formatlistmap.end()) {
    format_t *formatlistptr = new format_t();
    parse_formatstring (formatstring, formatlistptr, metadata, getParents);
    formatlistmap[formatstring] = formatlistptr;
  }
}

void browsetoolsclass::load_formatstrings(FilterResponse_t &response, text_tset &metadata,
                                          bool &getParents, const text_t &classification,
                                          browsermapclass *browsermap, formatinfo_t &formatinfo, 
                                          format_tmap &formatlistmap)
{
  text_tset cache;

  ResultDocInfo_tarray::iterator thisdoc = response.docInfo.begin();
  ResultDocInfo_tarray::iterator lastdoc = response.docInfo.end();

  while (thisdoc != lastdoc) {

    if (is_top ((*thisdoc).OID)) {
      load_formatstring ((*thisdoc).metadata["thistype"].values[0], metadata, 
			 getParents, classification, browsermap, formatinfo, 
			 formatlistmap);
    }
    
    // sometimes childtype is not set - why??
    if ((*thisdoc).metadata.find("childtype") != (*thisdoc).metadata.end()) {
      text_t &childtype = (*thisdoc).metadata["childtype"].values[0];

      text_tset::const_iterator it = cache.find (childtype);
      if (it == cache.end()) {
	load_formatstring (childtype, metadata, getParents, classification,
			   browsermap, formatinfo, formatlistmap);
	cache.insert (childtype);
      }
    }
    ++thisdoc;
  }
}

void browsetoolsclass::output_parents(FilterResponse_t &response, cgiargsclass &args, 
                                      browsermapclass *browsermap, formatinfo_t &formatinfo, 
                                      format_tmap &formatlistmap, const text_t &classification,
                                      int &tabcount, text_tset &metadata, bool &getParents,
                                      recptproto *collectproto, displayclass &disp, 
                                      outconvertclass &outconvert, ostream &textout, 
                                      ostream &logout)
{
  format_t *formatlistptr = NULL;
  text_t classifytype, formatstring;
  bool use_table, first = true;
  ResultDocInfo_tarray::iterator thisparent = response.docInfo.begin();
  ResultDocInfo_tarray::iterator lastparent = response.docInfo.end();
  while (thisparent != lastparent) {

    // get classifytype of this level
    if (is_top ((*thisparent).OID)) classifytype = (*thisparent).metadata["thistype"].values[0];
    else if (!first && (*(thisparent-1)).metadata.find("childtype") != (*(thisparent-1)).metadata.end()) classifytype = (*(thisparent-1)).metadata["childtype"].values[0];
    
    // if we still don't have a classifytype we'll use the default
    if (classifytype.empty()) {
      browserclass *bptr = browsermap->get_default_browser ();
      classifytype = bptr->get_browser_name ();
    }
    
    browserclass *bptr = browsermap->getbrowser (classifytype);

    // get the formatstring if there is one or use the browsers default
    if (!get_formatstring (classification, classifytype, 
			   formatinfo.formatstrings, formatstring))
      formatstring = bptr->get_default_formatstring();
    
    // see if it's cached
    format_tmap::const_iterator it = formatlistmap.find (formatstring);
    if (it != formatlistmap.end()) formatlistptr = (*it).second;
    else {
      logout << "browsetools error\n";
      return;
    }
    
    use_table = is_table_content (formatlistptr);
    tabcount += bptr->output_section_group (*thisparent, args, args["c"], tabcount, formatlistptr, 
					    use_table, metadata, getParents, collectproto, 
					    disp, outconvert, textout, logout);
    first = false;
    ++thisparent;
  }
}

void browsetoolsclass::contracted_contents(cgiargsclass &args, int tabcount, bool fulltoc, 
                                           browsermapclass *browsermap, formatinfo_t &formatinfo, 
                                           recptproto *collectproto, displayclass &disp, 
                                           outconvertclass &outconvert, ostream &textout, 
                                           ostream &logout)
{
  FilterResponse_t response;
  text_tset metadata;
  bool getParents = false;
  text_t formatstring;
  text_tarray parents;
  text_t OID = args["d"];
  text_t classification = "Document";

  // if we're not outputting the TOC for a valid OID, then we should be giving
  // the TOC of a classification
  if (OID.empty()) {
    OID = args["cl"];
    get_top (OID, classification);
  } 
  // if we're to give the full TOC of a document, get the parent for the whole
  // document now
  else if (fulltoc)
    get_top (args["cl"], classification);    

  bool haschildren = has_children (OID, args["c"], args["l"], collectproto, logout);

  if ((!args["d"].empty()) && fulltoc)
    get_parents_array (args["cl"] + ".fc", parents);
  if (haschildren) get_parents_array (OID + ".fc", parents);
  else get_parents_array (OID, parents);

  if (!parents.empty()) {
    // get classifytypes of each parent
    metadata.insert ("thistype");
    metadata.insert ("childtype");
    metadata.insert("mdtype");
    
    if (!get_info (parents, args["c"], args["l"], metadata, getParents, collectproto, response, logout))
      return;
    
    // get formatstrings for all parents
    format_tmap formatlistmap;  
    load_formatstrings (response, metadata, getParents, classification, 
			browsermap, formatinfo, formatlistmap);

    if (!get_info (parents, args["c"], args["l"], metadata, getParents, collectproto, response, logout))
      return;
    
    // display each parent
    output_parents (response, args, browsermap, formatinfo, formatlistmap, 
		    classification, tabcount, metadata, getParents, 
		    collectproto, disp, outconvert, textout, logout);
    
    metadata.erase (metadata.begin(), metadata.end());
    
    // clean up cached format list pointers
    format_tmap::const_iterator here = formatlistmap.begin();
    format_tmap::const_iterator end = formatlistmap.end();
    while (here != end) {
      delete (*here).second;
      ++here;
    }
  }

  // get childrens classifytype
  text_t classifytype;
  int numparents = response.docInfo.size();
  if (!parents.empty() && (response.docInfo[numparents-1].metadata.find("childtype") != response.docInfo[numparents-1].metadata.end()))
    classifytype = response.docInfo[numparents-1].metadata["childtype"].values[0];
  else {
    // use the default
    browserclass *bptr = browsermap->get_default_browser ();
    classifytype = bptr->get_browser_name ();
  }
    
  // load up metadata array with browser defaults
  browserclass *bptr = browsermap->getbrowser (classifytype);
  bptr->load_metadata_defaults (metadata);

  if (classifytype == "DateList") {
    // get the mdtype
    text_t datelist = "Date";
    if (!parents.empty() && (response.docInfo[0].metadata.find("mdtype") != response.docInfo[0].metadata.end())) {
      datelist = response.docInfo[0].metadata["mdtype"].values[0];
    }
    args.setarg("dm", datelist);
    text_tarray dates;
    splitchar(datelist.begin(), datelist.end(), ',', dates);
    text_tarray::iterator begin = dates.begin();
    text_tarray::iterator end = dates.end();
    while (begin!= end) {
      metadata.insert(*begin);
      begin++;
    }
    
  }

  // get the formatstring if there is one or use the browsers default
  if (!get_formatstring (classification, classifytype, 
			 formatinfo.formatstrings, formatstring))
    formatstring = bptr->get_default_formatstring();

  format_t *formatlistptr = new format_t();
  parse_formatstring (formatstring, formatlistptr, metadata, getParents);

  if (haschildren) 
    get_children (OID, args["c"], args["l"], metadata, getParents,
		  collectproto, response, logout);
  else if (!is_top(OID)) {
    get_children (OID + ".pr", args["c"], args["l"], metadata, getParents, 
		  collectproto, response, logout);
    haschildren = true;
  }

  // display children
  if (haschildren) {
    bool use_table = is_table_content (formatlistptr);
    bptr->output_section_group (response, args, args["c"], tabcount, formatlistptr, use_table, 
				metadata, getParents, collectproto, disp, outconvert, 
				textout, logout);
  }
  delete formatlistptr;
}

/**
 * This function outputs contents of a classifier to the reader
 * including parent levels
 */
void browsetoolsclass::expand_show_contents(cgiargsclass &args, int tabcount, bool fulltoc,
                                            browsermapclass *browsermap, formatinfo_t &formatinfo,
                                            recptproto *collectproto, displayclass &disp,
                                            outconvertclass &outconvert, ostream &textout, 
                                            ostream &logout)
{
  int coloffset = 0;
  text_tarray parents;
  FilterResponse_t response;
  text_t OID = args["d"];
  if (OID.empty()) OID = args["cl"];

  bool haschildren = has_children(OID, args["c"], args["l"], collectproto, logout);

  // get parents list
  if (!is_top(OID)) get_parents_array(OID, parents);
  if (args["d"].empty() || haschildren || parents.empty()) parents.push_back(OID);

  // if inside a book top title is needed
  if (!args["d"].empty())
    {
      coloffset = 1;
      text_t classification, classifytype, formatstring, topOID = parents[0];
      text_tset metadata;
      format_t *formatlistptr = new format_t();
      bool use_table, getParents = false;
		
      get_top(args["cl"], classification);

      // getting information about top OID
      metadata.insert("thistype");

      get_info(topOID, args["c"], args["l"], metadata, getParents, collectproto, response, logout);
      if (!response.docInfo[0].metadata["thistype"].values.empty()) 
        {
          classifytype = response.docInfo[0].metadata["thistype"].values[0];
        }

      browserclass *bptr = browsermap->getbrowser (classifytype);

      if (classifytype.empty())
        {
          bptr = browsermap->get_default_browser();
        }

      // get the formatstring if there is one or use the browsers default
      if (!get_formatstring(classification, classifytype, formatinfo.formatstrings, formatstring))
        {
          formatstring = bptr->get_default_formatstring();
        }

      parse_formatstring(formatstring, formatlistptr, metadata, getParents);

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

      get_info(topOID, args["c"], args["l"], metadata, getParents, collectproto, response, logout);

      // ouput top OID section
      use_table = is_table_content(formatlistptr);
      bptr->output_section_group(response.docInfo[0], args, args["c"], tabcount, formatlistptr, use_table,
                                 metadata, getParents, collectproto, disp, outconvert, textout, logout);
    }

  recurse_contents_levels(parents, args, coloffset, tabcount, fulltoc, browsermap, 
                          formatinfo, collectproto, disp, outconvert, textout, logout);
}

void browsetoolsclass::recurse_contents_levels(text_tarray &parents, cgiargsclass &args, int coloffset, int tabcount,
                                               bool fulltoc, browsermapclass *browsermap, formatinfo_t &formatinfo,
                                               recptproto *collectproto, displayclass &disp, outconvertclass &outconvert,
                                               ostream &textout, ostream &logout)
{
  FilterResponse_t response;
  text_tset metadata;
  text_t OID, formatstring, classification, classifytype;;
  bool use_table, getParents = false;
  int haschildren = 0;
  format_t *formatlistptr = new format_t();

  // display children for last level
  if (tabcount == (parents.size() - 1))
    {
      // setting metadata fields 
      metadata.insert ("thistype");
      metadata.insert ("childtype");
      metadata.insert ("haschildren");

      OID = parents[tabcount];
      get_info(OID, args["c"], args["l"], metadata, getParents, collectproto, response, logout);
      get_top(OID, classification);

      if (!response.docInfo.empty())
        {
          haschildren = response.docInfo[0].metadata["haschildren"].values[0].getint();
        }

      // get childrens classifytype
      if (!response.docInfo.empty())
        {
          classifytype = response.docInfo[0].metadata["childtype"].values[0];
        }
      else
        {
          // use the default
          browserclass *bptr = browsermap->get_default_browser();
          classifytype = bptr->get_browser_name();
        }

      // load up metadata array with browser defaults
      browserclass *bptr = browsermap->getbrowser(classifytype);
      bptr->load_metadata_defaults(metadata);

      // get the formatstring if there is one or use the browsers default
      if (!get_formatstring(classification, classifytype, formatinfo.formatstrings, formatstring))
        {
          formatstring = bptr->get_default_formatstring();
        }

      parse_formatstring(formatstring, formatlistptr, metadata, getParents);

      if (haschildren) 
        {
          get_children(OID, args["c"], args["l"], metadata, getParents,
                       collectproto, response, logout);
        }
      else if (!is_top(OID))
        {
          get_children(OID + ".pr", args["c"], args["l"], metadata, getParents, 
                       collectproto, response, logout);
          haschildren = true;
        }

      // display children
      if (haschildren)
        {
          use_table = is_table_content (formatlistptr);
          bptr->output_section_group(response, args, args["c"], coloffset + tabcount, formatlistptr, use_table, 
                                     metadata, getParents, collectproto, disp, outconvert, textout, logout);
        }
    }
  else
    {
      text_t pOID;
      OID = parents[tabcount];
      get_top(OID, classification);

      // load metadata fields
      metadata.insert("thistype");
      metadata.insert("childtype");
      metadata.insert("haschildren");
      metadata.insert("doctype");

      if (tabcount) pOID = parents[tabcount-1];
      else pOID = OID;
      get_info(pOID, args["c"], args["l"], metadata, getParents, collectproto, response, logout);

      // get classifytype of this level
      if (is_top (pOID)) classifytype = response.docInfo[0].metadata["childtype"].values[0];
      else classifytype = response.docInfo[0].metadata["thistype"].values[0];

      // if we still don't have a classifytype we'll use the default
      if (classifytype.empty())
        {
          browserclass *bptr = browsermap->get_default_browser();
          classifytype = bptr->get_browser_name();
        }

      browserclass *bptr = browsermap->getbrowser (classifytype);

      // get the formatstring if there is one or use the browsers default
      if (!get_formatstring (classification, classifytype, formatinfo.formatstrings, formatstring))
        {
          formatstring = bptr->get_default_formatstring();
        }

      // parse format string 
      parse_formatstring(formatstring, formatlistptr, metadata, getParents);

      use_table = is_table_content(formatlistptr);

      get_children(OID, args["c"], args["l"], metadata, getParents, collectproto, response, logout);

      ResultDocInfo_tarray::iterator thissibling = response.docInfo.begin();
      ResultDocInfo_tarray::iterator lastsibling = response.docInfo.end();

      while (thissibling != lastsibling)
        {
          bptr->output_section_group(*thissibling, args, args["c"], coloffset + tabcount, formatlistptr, 
                                     use_table, metadata, getParents, collectproto, 
                                     disp, outconvert, textout, logout);
			
          if ((*thissibling).OID == parents[tabcount+1])
            {
              recurse_contents_levels(parents, args, coloffset, tabcount+1, fulltoc, browsermap,
                                      formatinfo, collectproto, disp, outconvert,textout, logout);
            }
          ++thissibling;
        }
    }
  delete formatlistptr;
}

/**
 * This function outputs the contents of a classifier list to the reader - 
 * the document will in fact be empty, so this does the "real" output
 */
void browsetoolsclass::output_toc(cgiargsclass &args, browsermapclass *browsermap,
                                  formatinfo_t &formatinfo, recptproto *collectproto,
                                  displayclass &disp, outconvertclass &outconvert,
                                  ostream &textout, ostream &logout)
{
  if (!args["d"].empty() && formatinfo.AllowExtendedOptions) {
    // If AllowExtendedOptions is set and we're viewing a document,
    // DocumentButtons, DocumentContent, and DocumentImages are effectively
    // disabled. We just output the DocumentHeading format string and
    // return
    output_titles(args, collectproto, browsermap, formatinfo, disp, outconvert, textout, logout);    
    return;
  }

  int tabcount = 0;
  bool fulltoc = false;
  text_t cl_top, full_toc;
  if (args["cl"] != "search") {
    // see if there's a FullTOC string
    get_top (args["cl"], cl_top);
    if (get_formatstring (cl_top, "FullTOC", formatinfo.formatstrings, full_toc))
      if (full_toc == "true") fulltoc = true;
  }
  
  // get the cover image (if there is one) and the control buttons
  // if we're inside a book
  if ((!fulltoc) && (!args["d"].empty())) {
    if (formatinfo.DocumentImages) {
      textout << outconvert << "<div class=\"heading_image\">\n";
      output_cover_image(args, collectproto, disp, outconvert, textout, logout);
      textout << outconvert << "</div>\n";
    } else if (formatinfo.DocumentTitles) {
      textout << outconvert << "<div class=\"heading_title\">\n";
      output_titles(args, collectproto, browsermap, formatinfo, disp, outconvert, textout, logout);
      textout << outconvert << "</div>\n";
    }
    textout << outconvert << "<div class=\"buttons\" id=\"toc_buttons\">\n"; 
    if (args["u"] != "1") {
      output_controls (args, formatinfo.DocumentButtons, collectproto, disp, 
		       outconvert, textout, logout);
    }
    textout << outconvert << "</div>\n\n"; 
  }

  if (formatinfo.DocumentContents || args["d"].empty()) {
    if (args["d"].empty()) {
      disp.setmacro("tocopenextra", "document", "id=\"" + encodeForHTMLAttr(cl_top) + "\"");
    } else {
      disp.setmacro("tocopenextra", "document", "");
    }

    textout << outconvert << disp << "_document:tocopen_\n";
    if (args.getintarg("gc") == 1)
      {
        // expanded table of contents
        expanded_contents(args, tabcount, fulltoc, browsermap, formatinfo, 
                          collectproto, disp, outconvert, textout, logout, false);
      }
    else if (args.getintarg("gc") == 2)
      {
        // expand visible levels of table of contents
        expand_show_contents(args, tabcount, fulltoc, browsermap, formatinfo,
                             collectproto, disp, outconvert, textout, logout);
      }
    else
      {
        // contracted table of contents
        contracted_contents(args, tabcount, fulltoc, browsermap, formatinfo, 
                            collectproto, disp, outconvert, textout, logout);
      }
    textout << outconvert << disp << "_document:tocclose_\n";
  }
}

void browsetoolsclass::load_extended_options(text_tmap &options, cgiargsclass &args, browsermapclass *browsers, 
                                             formatinfo_t &formatinfo, recptproto *collectproto, 
                                             displayclass &disp, outconvertclass &outconvert, ostream &logout)
{
  options["DocImage"] = get_cover_image();

#if defined(GSDL_USE_IOS_H)
  ostrstream *tmpstr = new ostrstream();
#else
  ostringstream *tmpstr = new ostringstream(ostringstream::binary);
#endif

  if (args["gc"] == "1")
    {
      expanded_contents(args, 0, false, browsers, formatinfo, collectproto,
                        disp, outconvert, *tmpstr, logout, false);
    }
  else
    {
      contracted_contents(args, 0, false, browsers, formatinfo, 
                          collectproto, disp, outconvert, *tmpstr, logout);
    }
#if defined(GSDL_USE_IOS_H)
  char *t = tmpstr->str();
  text_t tmp;
  tmp.setcarr(t, tmpstr->pcount());
  delete [] t;
#else
  text_t tmp = (char *)(tmpstr->str().c_str());
#endif
  int len = tmp.size();
  char *ctmp = tmp.getcstr();
  utf8inconvertclass utf82text_t;
  utf82text_t.setinput(ctmp, len);
  convertclass::status_t status;
  utf82text_t.convert(tmp, status);
  options["DocTOC"] = tmp;
  delete []ctmp;
  delete tmpstr;

  options["DocumentButtonDetach"] = "_document:imagedetach_";
  if (args["hl"] == "1")
    {
      options["DocumentButtonHighlight"] = "_document:imagenohighlight_";
    }
  else
    {
      options["DocumentButtonHighlight"] = "_document:imagehighlight_";
    }
  if (args["gc"] == "1")
    {
      options["DocumentButtonExpandContents"] = "_document:imagecontracttoc_";
    }
  else
    {
      options["DocumentButtonExpandContents"] = "_document:imageexpandtoc_";
    }
  if (args["gt"] == "1")
    {
      options["DocumentButtonExpandText"] = "_document:imagecontracttext_";
    }
  else
    {
      options["DocumentButtonExpandText"] = "_document:imageexpandtext_";
    }
}
