#include <stdexcept>

using namespace std;

#include "entities.h"
#include "xmlerrors.h"
#include "message.h"
#include "strings.h"

extern ::find_first_not_of fo_find_not_name;

// global entities - we must put it into a function
// so it is initialised correctly - see Scott Meyers 
// "Effective C++" point 47.


StringMap & GetGlobalEntityMap ()
  {
  static StringMap GlobalEntityMap;
  return GlobalEntityMap;
  }


// add all entities to map - this is done once as these are global and don't change

 // note - these ARE case-sensitive

char * character_entities [] = 
  {
"quot  ", "\x22",    "acute ", "\xB4",  "Iacute", "\xCD", "aelig ", "\xE6",
"amp   ", "\x26",    "micro ", "\xB5",  "Icirc ", "\xCE", "ccedil", "\xE7",
"apos  ", "\x27",    "para  ", "\xB6",  "Iuml  ", "\xCF", "egrave", "\xE8",
"lt    ", "\x3C",    "middot", "\xB7",  "ETH   ", "\xD0", "eacute", "\xE9",
"gt    ", "\x3E",    "cedil ", "\xB8",  "Ntilde", "\xD1", "ecirc ", "\xEA",
"nbsp  ", "\xA0",    "sup1  ", "\xB9",  "Ograve", "\xD2", "euml  ", "\xEB",
"iexcl ", "\xA1",    "ordm  ", "\xBA",  "Oacute", "\xD3", "igrave", "\xEC",
"cent  ", "\xA2",    "raquo ", "\xBB",  "Ocirc ", "\xD4", "iacute", "\xED",
"pound ", "\xA3",    "frac14", "\xBC",  "Otilde", "\xD5", "icirc ", "\xEE",
"curren", "\xA4",    "frac12", "\xBD",  "Ouml  ", "\xD6", "iuml  ", "\xEF",
"yen   ", "\xA5",    "frac34", "\xBE",  "times ", "\xD7", "eth   ", "\xF0",
"brvbar", "\xA6",    "iquest", "\xBF",  "Oslash", "\xD8", "ntilde", "\xF1",
"sect  ", "\xA7",    "Agrave", "\xC0",  "Ugrave", "\xD9", "ograve", "\xF2",
"uml   ", "\xA8",    "Aacute", "\xC1",  "Uacute", "\xDA", "oacute", "\xF3",
"copy  ", "\xA9",    "Acirc ", "\xC2",  "Ucirc ", "\xDB", "ocirc ", "\xF4",
"ordf  ", "\xAA",    "Atilde", "\xC3",  "Uuml  ", "\xDC", "otilde", "\xF5",
"laquo ", "\xAB",    "Auml  ", "\xC4",  "Yacute", "\xDD", "ouml  ", "\xF6",
"not   ", "\xAC",    "Aring ", "\xC5",  "THORN ", "\xDE", "divide", "\xF7",
"shy   ", "\xAD",    "AElig ", "\xC6",  "szlig ", "\xDF", "oslash", "\xF8",
"reg   ", "\xAE",    "Ccedil", "\xC7",  "agrave", "\xE0", "ugrave", "\xF9",
"macr  ", "\xAF",    "Egrave", "\xC8",  "aacute", "\xE1", "uacute", "\xFA",
"deg   ", "\xB0",    "Eacute", "\xC9",  "acirc ", "\xE2", "ucirc ", "\xFB",
"plusmn", "\xB1",    "Ecirc ", "\xCA",  "atilde", "\xE3", "uuml  ", "\xFC",
"sup2  ", "\xB2",    "Euml  ", "\xCB",  "auml  ", "\xE4", "yacute", "\xFD",
"sup3  ", "\xB3",    "Igrave", "\xCC",  "aring ", "\xE5", "thorn ", "\xFE",
                                                          "yuml  ", "\xFF",                        
"", "",             // end of table marker
      
  };

static int LoadXMLEntities ()
  {

  for (int j = 0; character_entities [j] [0] != 0; j += 2)
    GetGlobalEntityMap ().insert 
      (make_pair (trim (character_entities [j]), character_entities [j + 1]));

  return GetGlobalEntityMap ().size ();
 } // end of LoadXMLEntities

// this rather cute line here ensures that global entities are loaded at program
// startup time, once and only once
static int iGlobalEntitiesCount = LoadXMLEntities ();

// convert an entity name to its contents, including generic ones like:
//  &#123; and &#xab;
// NB - the name should *not* include the & or the ;
// eg, you might expect "lt", "amp", "#123" or "#xab"

// throws various exceptions if an error

string Get_XML_Entity (const string & strName, 
                       const StringMap * pCustomEntityMap)
  {

  // look for &#nnn;   or &#xnn; (hex)

  if (strName.size () >= 2 && strName [0] == '#')
    {
    int iResult = 0;

    // validate and work out number
    if (strName [1] == 'x')
      {
      istringstream s (strName.substr (2));
      s >> hex >> noskipws >> iResult;
      if (s.fail () || !s.eof ())
        {
        THROW_ERROR (XML_ERROR_INVALID_HEX_NUMBER, strName);
        return "";
        }

      } // end of hex entity
    else
      {
      istringstream s (strName.substr (1));
      s >> dec >> noskipws >> iResult;
      if (s.fail () || !s.eof ())
        {
        THROW_ERROR (XML_ERROR_INVALID_NUMBER, strName);
        return "";
        }
      } // end of decimal entity

    if (iResult < 32 ||   // don't allow nonprintable characters
        iResult > 255)   // don't allow characters more than 1 byte
        {
        THROW_ERROR (XML_ERROR_DISALLOWED_NUMBER, strName);
        return "";
        }
    string sResult;
    sResult = (unsigned char) iResult;    // note, has to be an assignment, not a constructor
    return sResult;
    } // end of entity starting with #
  
  StringMap::const_iterator iter;

  // look up global entities first
  iter = GetGlobalEntityMap ().find (strName);
  if (iter != GetGlobalEntityMap ().end ())
    return iter->second;

  // then try ones for this document, if any
  if (pCustomEntityMap)
    {
    iter = pCustomEntityMap->find (strName);
    if (iter != pCustomEntityMap->end ())
      return iter->second;
    }

  THROW_ERROR (XML_ERROR_UNKNOWN_ENTITY, strName);

  return "";    // to avoid warning
  }  // end of Get_XML_Entity

// replace all entities in a string, including global ones (eg. &lt;)
// and custom ones (eg. &nick; )

// throws various exceptions if an error

string ReplaceEntities (const string & strSource, 
                        const StringMap * pCustomEntityMap) 
    {


// look for entities imbedded in the string

const char * p;
const char * pStart = strSource.c_str ();   // where buffer starts
string sFixed;

  // allow for same size as source so we do less reallocations
  sFixed.reserve (strSource.size ());


  while ((p = strchr (pStart, '&')))
    {

    // string before ampersand
    sFixed += string (pStart, p++ - pStart);

    // p now points to the &xxx;
    pStart = p; // where entity starts

    // it might be &#23;
    if (*p == '#')
      p++;

    // find first non-name
    p = fo_find_not_name (p);

    // not found? error
    if (!p)
      THROW_ERROR (XML_ERROR_NO_CLOSING_SEMICOLON,
                    pStart); 
      
    // first non-name should be a semicolon
    if (*p != ';')
      THROW_ERROR (XML_ERROR_NO_CLOSING_SEMICOLON,
                   string (pStart, p - pStart)); 

    // look up entity name
    sFixed += Get_XML_Entity (string (pStart, p - pStart), pCustomEntityMap); 
    
    pStart = p + 1;   // move on past the entity
                 
    } // end of loop
     
  return sFixed + pStart;
  

  }   // end of CXMLtree::ReplaceEntities

