#include <strstream.h>
#include <time.h>
#include "cccc_htm.h"
#include "cccc_utl.h"
#include "cccc.h"

CCCC_Html_Stream::CCCC_Html_Stream(
  CCCC_Project& project, const char* fname, 
  int report_mask, const char* libdir) 
  : prj(project), fstr(fname), libdir(libdir) 
{ 
  if(fstr.good() == 0)
  {
    cerr << "failed to open " << fname << " for output" << endl;
    exit(1);
  }

  fstr << "<HTML><HEAD><TITLE>" << endl
       << "CCCC Report" << endl;

  if(strcmp(prj.name(nlSIMPLE),"")!=0) 
  {
    fstr << " for project "
	 << prj.name(nlSIMPLE)
	 << endl;
  }

  time(&time_generated);
  fstr << " generated " << ctime(&time_generated) << endl
       << "</TITLE></HEAD>" << endl;
  fstr << "<BODY>" << endl;

  if(report_mask & rtCONTENTS)
  {
    Table_Of_Contents(report_mask);
  }

  if(report_mask & rtSUMMARY)
  {
    Project_Summary();
  }

  if(report_mask & rtPROC1)
  {
    Procedural_Summary();
  }

  if(report_mask & rtPROC2)
  {
    Procedural_Detail();
  }
    
  if(report_mask & rtSTRUCT1)
  {
    Structural_Summary();
  }
    
  if(report_mask & rtSTRUCT2)
  {
    Structural_Detail();
  }

  if(report_mask & rtREJECTED)
  {
    Rejected_Extents();
  }

  if(report_mask & rtCCCC)
  {
    Put_Section_Heading("About CCCC","infocccc",1);
    Include_Library_File("cccc_inf.dat");
  }

  fstr << "</BODY></HTML>" << endl; 
}

void CCCC_Html_Stream::Table_Of_Contents(int report_mask)
{
  // record the number of report parts in the table, and the 
  // stream put pointer
  // if we find that we have only generated a single part, we supress
  // the TOC by seeking to the saved stream offset
  int number_of_report_parts=0;
  int saved_stream_offset=fstr.tellp();

  fstr << "<TABLE BORDER>" << endl
       << "<TR><TH COLSPAN=2>" << endl
       << "CCCC Software Metrics Report";
  if( strlen(prj.name(nlSIMPLE)) > 0 )
  {
    fstr << " on project " << prj.name(nlSIMPLE); 
  }
  fstr << endl 
       << "<BR> generated " << ctime(&time_generated) << endl

       << "</TR>" << endl;

  if(report_mask & rtSUMMARY)
  {
    Put_Section_TOC_Entry(
      "Project Summary","projsum",
      "Summary table of high level measures summed over all files processed "
      "in the current run.");
    number_of_report_parts++;
  }


  if(report_mask & rtPROC1)
  {
    Put_Section_TOC_Entry(
      "Procedural Metrics Summary", "procsum",
      "Table of procedural measures (i.e. lines of code, lines of comment, "
      "McCabe's cyclomatic complexity summed over each module.");
    number_of_report_parts++;
  }

  if(report_mask & rtPROC2)
  {
    Put_Section_TOC_Entry(
      "Procedural Metrics Detail", "procdet",
      "The same procedural metrics as in the procedural metrics summary, "
      "reported for individual functions, grouped by module.");
    number_of_report_parts++;
  }
 
  if(report_mask & rtSTRUCT1)
  {
    Put_Section_TOC_Entry(
      "Structural Metrics Summary", "structsum",
      "Structural metrics based on the relationships of each module with "
      "others.  Includes fan-out (i.e. number of other modules the current "
      "module uses), fan-in (number of other modules which use the current "
      "module), and the Henry-Kafura/Shepperd measure which combines these "
      "to give a measure of coupling for the module.");
    number_of_report_parts++;
  }
 
  if(report_mask & rtSTRUCT2)
  {
    Put_Section_TOC_Entry(
      "Structural Metrics Detail", "structdet",
      "The names of the modules included as clients and suppliers in the "
      "counts for the Structural Metrics Summary.");
    number_of_report_parts++;
  }

  if(report_mask & rtREJECTED)
  {
    Put_Section_TOC_Entry(
      "Rejected Extents", "rejected",
      "Extents of submitted source files which the analyser was unable to parse."
      );
    number_of_report_parts++;
  }

  if(report_mask & rtCCCC)
  {
    Put_Section_TOC_Entry(
      "About CCCC", "infocccc",
      "A description of the CCCC program.");
    number_of_report_parts++;
  }

  fstr << "</TR></TABLE>" << endl;
  if(number_of_report_parts<2)
  {
    fstr.seekp(saved_stream_offset);
  }
}

void CCCC_Html_Stream::Put_Section_Heading(
  const CCCC_String& heading_title, 
  const CCCC_String& heading_tag, 
  int heading_level)
{
  cerr << "Generating " << heading_title << endl;
  fstr << "<H" << heading_level << ">" 
       << "<A NAME=\"" << heading_tag << "\">" 
       << heading_title 
       << "</A></H" << heading_level
       << ">" << endl;
} 

void  CCCC_Html_Stream::Project_Summary() {
  Put_Section_Heading("Project Summary","projsum",1);

  // calculate the counts on which all displayed data will be based
  int nom=prj.get_count("NOM");  // number of modules 
  int loc=prj.get_count("LOC");  // lines of code
  int mvg=prj.get_count("MVG");  // McCabes cyclomatic complexity
  int com=prj.get_count("COM");  // lines of comment
  int hksv=prj.get_count("HKSv");  // intermodule complexity (visible only)
  int hksc=prj.get_count("HKSc");  // intermodule complexity (concrete only)
  int hks=prj.get_count("HKS");    // intermodule complexity (all couplings)
  int rej=prj.rejected_extent_table.get_count("LOC");
  
  fstr << "<TABLE BORDER WIDTH=100%>" << endl
       << "<TR>" << endl;
  Put_Header_Cell("Metric",0);
  Put_Header_Cell("Tag",0);
  Put_Header_Cell("Grand Total",0);
  Put_Header_Cell("Per Module",0);
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Number of modules");
  Put_Label_Cell("NOM");
  Put_Metric_Cell(nom);
  Put_Label_Cell("");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Lines of Code",700);
  Put_Label_Cell("LOC",120);
  Put_Metric_Cell(loc,"LOCp");
  Put_Metric_Cell(loc,nom,"LOCm");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("McCabe's Cyclomatic Number");
  Put_Label_Cell("MVG");
  Put_Metric_Cell(mvg,"MVGp");
  Put_Metric_Cell(mvg,nom,"MVGm");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Lines of Comment");
  Put_Label_Cell("COM");
  Put_Metric_Cell(com);
  Put_Metric_Cell(com,nom);
  fstr << "</TR>" << endl;
  
  fstr << "<TR>" << endl;
  Put_Label_Cell("LOC/COM");
  Put_Label_Cell("L_C");
  Put_Metric_Cell(loc,com,"L_C");
  Put_Label_Cell("");
  fstr << "</TR>" << endl;
  
  fstr << "<TR>" << endl;
  Put_Label_Cell("MVG/COM");
  Put_Label_Cell("M_C");
  Put_Metric_Cell(mvg,com,"M_C");
  Put_Label_Cell("");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Henry-Kafura/Shepperd measure (visible)");
  Put_Label_Cell("HKSv");
  Put_Metric_Cell(hksv);
  Put_Metric_Cell(hksv,nom,"HKSv");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Henry-Kafura/Shepperd measure (concrete)");
  Put_Label_Cell("HKSc");
  Put_Metric_Cell(hksc);
  Put_Metric_Cell(hksc,nom,"HKSc");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Henry-Kafura/Shepperd measure (inclusive)");
  Put_Label_Cell("HKS");
  Put_Metric_Cell(hks);
  Put_Metric_Cell(hks,nom,"HKS");
  fstr << "</TR>" << endl;

  fstr << "<TR>" << endl;
  Put_Label_Cell("Lines of Code rejected by parser");
  Put_Label_Cell("REJ");
  Put_Metric_Cell(rej,"REJ");
  Put_Metric_Cell(0);
  fstr << "</TR>" << endl;

  fstr << "</TABLE>" << endl;
}

void CCCC_Html_Stream::Procedural_Summary() {
  Put_Section_Heading("Procedural Metrics Summary","procsum",1);
  fstr << "<TABLE BORDER WIDTH=100%>" << endl
       << "<TR>" << endl;
  Put_Header_Cell("Module Name",200);
  Put_Header_Cell("Variety",80);
  Put_Header_Cell("LOC",80);
  Put_Header_Cell("MVG",80);
  Put_Header_Cell("COM",80);
  Put_Header_Cell("L_C",80);
  Put_Header_Cell("M_C",80);
  Put_Header_Cell("WMC",80);

  fstr << "</TR>" << endl;

  int i;
  for(i=0; i<prj.module_table.records();i++)
  {
    CCCC_Module* mod_ptr=(CCCC_Module*)(prj.module_table.record_ptr(i));

    if( mod_ptr->is_trivial() == 0)
    {
      fstr << "<TR>" << endl;
      Put_Label_Cell(mod_ptr->name(nlSIMPLE),0,"procsum","procdet");
      Put_Label_Cell(mod_ptr->name(nlMODULE_TYPE));
      int loc=mod_ptr->get_count("LOC");
      int mvg=mod_ptr->get_count("MVG");
      int com=mod_ptr->get_count("COM");
      CCCC_Metric mloc(loc,"LOCm");
      CCCC_Metric mmvg(mvg,"MVGm");
      CCCC_Metric ml_c(loc,com,"L_C");
      CCCC_Metric mm_c(mvg,com,"M_C");
      
      int wmc=mod_ptr->member_table.records();
      //      int use=mod_ptr->userel_table.records();

      Put_Metric_Cell(mloc);
      Put_Metric_Cell(mmvg);
      Put_Metric_Cell(com);
      Put_Metric_Cell(ml_c);
      Put_Metric_Cell(mm_c);
      Put_Metric_Cell(wmc);

      fstr << "</TR>" << endl;

    }
  }

  fstr << "</TABLE>" << endl;
  
}

void CCCC_Html_Stream::Structural_Summary() 
{
  Put_Section_Heading("Structural Metrics Summary","structsum",1);
  fstr << "<TABLE BORDER WIDTH=100%>" << endl;
  fstr  << "<TR>" << endl;
  Put_Header_Cell("Module Name",300);
  Put_Header_Cell("FOv",35);
  Put_Header_Cell("FOc",35);
  Put_Header_Cell("FO",35);
  Put_Header_Cell("FIv",35);
  Put_Header_Cell("FIc",35);
  Put_Header_Cell("FI",35);
  Put_Header_Cell("HKSv",35);
  Put_Header_Cell("HKSc",35);
  Put_Header_Cell("HKS",35);

  fstr << "</TR>" << endl;

  int i=0;
  while(i<prj.module_table.records())
  {
    CCCC_Module* module_ptr=prj.module_table.record_ptr(i);
    if(!module_ptr->is_trivial())
    {
      fstr << "<TR>" << endl;

      int fov=module_ptr->get_count("FOv");
      int foc=module_ptr->get_count("FOc");
      int fo=module_ptr->get_count("FO");

      int fiv=module_ptr->get_count("FIv");
      int fic=module_ptr->get_count("FIc");
      int fi=module_ptr->get_count("FI");

      int hksv=module_ptr->get_count("HKSv");
      int hksc=module_ptr->get_count("HKSc");
      int hks=module_ptr->get_count("HKS");

      // the last two arguments here turn on links to enable jumping between
      // the summary and detail cells for the same module
      Put_Label_Cell(module_ptr->name(nlSIMPLE), 0, "structsum","structdet");
      Put_Metric_Cell(CCCC_Metric(fov,"FOv"));
      Put_Metric_Cell(CCCC_Metric(foc,"FOc"));
      Put_Metric_Cell(CCCC_Metric(fo,"FO"));
      Put_Metric_Cell(CCCC_Metric(fiv,"FIv"));
      Put_Metric_Cell(CCCC_Metric(fic,"FIc"));
      Put_Metric_Cell(CCCC_Metric(fi,"FI"));
      Put_Metric_Cell(CCCC_Metric(hksv,"HKSv"));
      Put_Metric_Cell(CCCC_Metric(hksc,"HKSc"));
      Put_Metric_Cell(CCCC_Metric(hks,"HKS"));

      fstr << "</TR>" << endl;
    }
    i++;
  }  
  fstr << "</TABLE>" << endl;
  
}

void CCCC_Html_Stream::Put_Structural_Details_Cell(
  CCCC_Module *mod, CCCC_Project *prj, int mask, UserelNameLevel nl)
{
  CCCC_UseRelationship **peers;
  int relnum=mod->get_relationships(prj, mask, peers);

  fstr << "<TD>" << endl;
  int j;
  for(j=0; j<relnum; j++)
  {
    CCCC_UseRelationship *ur_ptr=peers[j];
    fstr << ur_ptr->name(nl) << " ";
    AugmentedBool vis=ur_ptr->is_visible();
    AugmentedBool con=ur_ptr->is_concrete();

    if( (vis != abTRUE) && (con != abTRUE) )
    {
      fstr << "[CV] ";
    }
    else if(vis != abTRUE)
    {
      fstr << "[V] ";
    }
    else if(con != abTRUE)
    {
      fstr << "[C] ";
    }
    fstr << "<BR>" << endl;  
    Put_Extent_List(*ur_ptr);
    fstr << "<BR>" << endl;
  }
  // put a non-breaking space in to avoid the unpleasantness which
  // goes with completely empty cells
  fstr << "&nbsp;" << endl;

  fstr << "</TD>" << endl;

}
void CCCC_Html_Stream::Structural_Detail() 
{
  Put_Section_Heading("Structural Metrics Detail","structdet",1);
  fstr << "<TABLE BORDER WIDTH=100%>" << endl;
  fstr  << "<TR>" << endl;
  Put_Header_Cell("Module Name",200);
  Put_Header_Cell("Clients",250);
  Put_Header_Cell("Suppliers",250);
  fstr << "</TR>" << endl;

  int i=0;
  while(i<prj.module_table.records())
  {
    CCCC_Module* module_ptr=prj.module_table.record_ptr(i);
    if(!module_ptr->is_trivial())
    {
      fstr << "<TR>" << endl;
      CCCC_UseRelationship **fi_peers=NULL, **fo_peers=NULL;

      Put_Label_Cell(module_ptr->name(nlSIMPLE), 0, "structdet","structsum");

      int client_mask=rmeCLIENT|rmeHIDDEN_OR_VISIBLE|rmeABSTRACT_OR_CONCRETE;
      Put_Structural_Details_Cell(module_ptr, &prj, client_mask, nlCLIENT);

      int supplier_mask=
	rmeSUPPLIER|rmeHIDDEN_OR_VISIBLE|rmeABSTRACT_OR_CONCRETE;
      Put_Structural_Details_Cell(module_ptr, &prj, supplier_mask, nlSUPPLIER);

      fstr << "</TR>" << endl;
    }
    i++;
  }  
  fstr << "</TABLE>" << endl;
  
}

void CCCC_Html_Stream::Procedural_Detail() {
  Put_Section_Heading("Procedural Metrics Detail","procdet",1);

  fstr << "<TABLE BORDER>" << endl;

  int i;
  for(i=0; i<prj.module_table.records();i++)
  {
    CCCC_Module* mod_ptr=prj.module_table.record_ptr(i);

    if( 
      (strcmp(mod_ptr->name(nlMODULE_TYPE),"builtin") != 0) &&
      (strcmp(mod_ptr->name(nlMODULE_TYPE),"enum") != 0) &&
      (strcmp(mod_ptr->name(nlMODULE_TYPE),"union") != 0) &&
      (mod_ptr->member_table.records()>0) 
    )
    {

      fstr  << "<TR>" << endl;
      Put_Label_Cell(mod_ptr->name(nlSIMPLE),200,"procdet","procsum",mod_ptr);
      Put_Header_Cell("LOC",80);
      Put_Header_Cell("MVG",80);
      Put_Header_Cell("COM",80);
      Put_Header_Cell("L_C",80);
      Put_Header_Cell("M_C",80);

      fstr << "</TR>" << endl;

      int j;
      if(mod_ptr->member_table.records()==0)
      {
	fstr << "<TR><TD COLSPAN=6>"
	     << "No prodedural members have been identified for this module"
	     << "</TD></TR>" << endl;
      }
      else
      {
	for(j=0; j<mod_ptr->member_table.records();j++)
	{
	  CCCC_Member* mem_ptr=
	    (CCCC_Member*)(mod_ptr->member_table.record_ptr(j));


	  fstr << "<TR>" << endl;
	  Put_Label_Cell(mem_ptr->name(nlLOCAL),0,"","",mem_ptr);
	  int loc=mem_ptr->get_count("LOC");
	  int mvg=mem_ptr->get_count("MVG");
	  int com=mem_ptr->get_count("COM");
	  CCCC_Metric mloc(loc,"LOCf");
	  CCCC_Metric mmvg(mvg,"MVGf");
	  CCCC_Metric ml_c(loc,com,"L_C");
	  CCCC_Metric mm_c(mvg,com,"M_C");


	  Put_Metric_Cell(mloc);
	  Put_Metric_Cell(mmvg);
	  Put_Metric_Cell(com);
	  Put_Metric_Cell(ml_c);
	  Put_Metric_Cell(mm_c);
	  fstr << "</TR>" << endl;

	}
      }
      fstr << "<TR><TD HEIGHT=12 COLSPAN=6></TD></TR>" << endl;
    }
  }  
  fstr << "</TABLE>" << endl;
}

void CCCC_Html_Stream::Rejected_Extents() 
{
  Put_Section_Heading("Rejected Extents","rejected",1);
  fstr << "<TABLE BORDER WIDTH=100%>" << endl;
  fstr << "<TR>" << endl;
  Put_Header_Cell("Location",150);
  Put_Header_Cell("Text",200);
  Put_Header_Cell("LOC",65);
  Put_Header_Cell("COM",65);
  Put_Header_Cell("MVG",65);
  fstr << "</TR>" << endl;

  if(prj.rejected_extent_table.records() == 0)
  {
    fstr << "<TR><TD COLSPAN=5>"
	 << "No extents were rejected in this run"
	 << "</TD></TR>" << endl;
  }
  else
  {
    int i;
    for(i=0; i<prj.rejected_extent_table.records();i++)
    {
      CCCC_Extent *extent_ptr=prj.rejected_extent_table.record_ptr(i);
      fstr << "<TR>";
      Put_Extent_Cell(*extent_ptr,0);
      Put_Label_Cell(extent_ptr->name(nlDESCRIPTION));
      Put_Metric_Cell(extent_ptr->get_count("LOC"),"");
      Put_Metric_Cell(extent_ptr->get_count("COM"),"");
      Put_Metric_Cell(extent_ptr->get_count("MVG"),"");
      fstr << "</TR>" << endl;
    }
  }
  fstr << "</TABLE>" << endl;
  
}


void CCCC_Html_Stream::Include_Library_File(char* fname) {
  int lines_appended=0;

  MAKE_STRSTREAM(fname_stream);
  fname_stream << libdir << "/" << fname << ends;
  ifstream istr(fname_stream.str());
  RELEASE_STRSTREAM(fname_stream);

  while(istr.good())
  {
    char buffer[1024];
    istr.getline(buffer,1024);
    fstr << buffer << endl;
    lines_appended++;
  }

  cerr << lines_appended 
       << " lines appended from library file " << fname 
       << endl;
}

void CCCC_Html_Stream::Put_Section_TOC_Entry(
  const CCCC_String& section_name, const CCCC_String& section_href,
  const CCCC_String& section_description)
{
  fstr << "<TR>" << endl
       << "<TH><H4><A HREF=\"#" << section_href << "\">"
       << section_name << "</A></H4></TH>" << endl 
       << "<TD>" << endl
       << section_description << endl
       << "</TR>" << endl;
}

void CCCC_Html_Stream::Put_Header_Cell(const CCCC_String& label, int width)
{
  fstr << "<TH BGCOLOR=\"AQUA\"";
  if(width>0)
  {
    fstr << " WIDTH=" << width;
  }
  fstr << ">" ;
  if(strlen(label)>0)
  {
    *this << label;
  }
  else
  {
    // put a non-breaking space in to avoid the strange
    // bevelling associated with empty cells
    fstr << "&nbsp;";

  }
  fstr << "</TH>";
}

void CCCC_Html_Stream::Put_Label_Cell(
  const CCCC_String& label, int width,
  const CCCC_String& ref_name, const CCCC_String& ref_href, 
  CCCC_Record *rec_ptr)
{
  fstr << "<TD";
  if(width>0)
  {
    fstr << " WIDTH=" << width;
  }
  fstr << ">" ;

  if(strlen(ref_name) > 0)
  {
    // we need to insert an HTML "<A NAME=...> tag for the current cell
    // this enables other locations to jump in
    fstr << "<A NAME=\"" << ref_name << "." << label << "\"></A>" << endl;
  }

  if(strlen(ref_href) > 0)
  {
    // we need to insert an HTML <A HREF=...> tag for the current cell
    // this enables this cell to be a link to jump out
    fstr << "<A HREF=\"#" << ref_href << "." << label << "\">" << endl;
    // this anchor will need to be closed after the label has been displayed
  }

  if(strlen(label)>0)
  {
    *this << label ;
  }
  else
  {
    // put a non-breaking space in to avoid the strange
    // bevelling associated with empty cells
    fstr << "&nbsp;";
  }

  if(strlen(ref_href) > 0)
  {
    // closing the anchor we opened above
    fstr << "</A>" << endl;
  }

  if(rec_ptr != 0)
  {
    fstr << "<BR>" << endl;
    Put_Extent_List(*rec_ptr);
  }

  fstr << "</TD>";
}
  

void CCCC_Html_Stream::Put_Metric_Cell(
  int count, const CCCC_String& tag, int width)
{
  CCCC_Metric m(count, tag);
  Put_Metric_Cell(m, width);
}

void CCCC_Html_Stream::Put_Metric_Cell(
  int num, int denom, const CCCC_String& tag, int width)
{
  CCCC_Metric m(num,denom, tag);
  Put_Metric_Cell(m, width);
}

void  CCCC_Html_Stream::Put_Metric_Cell(const CCCC_Metric& metric, int width)
{
  fstr << "<TD ALIGN=RIGHT";
  switch(metric.emphasis_level())
  {
  case elMEDIUM:
    fstr << " BGCOLOR=\"YELLOW\"";
    break;
  case elHIGH:
    fstr << " BGCOLOR=\"RED\"";
    break;
  default:
    // no background colour
    break;
  }
  fstr << ">";

  if(width>0)
  {
    fstr << "<TAB INDENT=" << width << ">" ;
  }

  *this << metric;
  fstr << "</TD>";
}

void CCCC_Html_Stream::Put_Extent_URL(const CCCC_Extent& extent)
{
  fstr << "<CODE><A HREF=\"" << extent.name(nlFILENAME) << "\">";
  fstr << extent.name(nlFILENAME) << ":" ;
  fstr << extent.name(nlLINENUMBER) << "</A></CODE>" << endl;
}

void CCCC_Html_Stream::Put_Extent_Cell(const CCCC_Extent& extent, int width) {
  fstr << "<TD>" << endl;
  Put_Extent_URL(extent);
  fstr << "</TD>" << endl;
}

void CCCC_Html_Stream::Put_Extent_List(CCCC_Record& record) 
{
  int i;
  for(i=0; i<record.extent_table.records(); i++)
  {
    CCCC_Extent *ext_ptr=(CCCC_Extent*) ( record.extent_table.record_ptr(i) );
    Put_Extent_URL(*ext_ptr);
  }
  fstr << "<BR>" << endl;
}

// the next two methods define the two basic output operations through which
// all of the higher level output operations are composed
CCCC_Html_Stream& operator <<(CCCC_Html_Stream& os, const CCCC_String& stg) {
  // initialise a character pointer to the start of the string's buffer
  char *cptr=stg;
  while(*cptr!='\000') {
    char c=*cptr;
	
    // the purpose of this is to filter out the characters which
    // must be escaped in HTML
    switch(c) {
    case '>': os.fstr << "&gt;" ; break;
    case '<': os.fstr << "&lt;" ; break;
    case '&': os.fstr << "&amp;"; break;
      // commas and parentheses do not need to be escaped, but
      // we want to allow line breaking just inside
      // parameter lists and after commas
      // we insert a non-breaking space to guarantee a small indent
      // on the new line, and one before the right parenthesis for
      //symetry
    case ',': os.fstr << ", &nbsp;" ; break;
    case '(': os.fstr << "( &nbsp;" ; break;
    case ')': os.fstr << "&nbsp;)" ; break;
    default : os.fstr << c;
    }
    cptr++;
  }
  return os;
}

CCCC_Html_Stream& operator <<(CCCC_Html_Stream& os, const CCCC_Metric& mtc) {
  char *emphasis_prefix[]={"","<EM>","<STRONG>"};
  char *emphasis_suffix[]={"","</EM>","</STRONG>"};

  // by casting the stream to an ordinary ostream, we avoid the escape
  // functionality
  os.fstr << emphasis_prefix[mtc.emphasis_level()]
	  << mtc.value_string() 
	  << emphasis_suffix[mtc.emphasis_level()];
  return os;
}



#ifdef UNIT_TEST
int main()
{
  CCCC_Project *prj_ptr=test_project_ptr();
  CCCC_Html_Stream os(*prj_ptr,"cccc.htm",".");
  return 0;
}
#endif
