/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlTokenizer.cpp  -  description
                             -------------------
    begin                : Sun May 22 2003
    based on the code    : Mikko Lahteenmaki <Laza@Flashmail.com>
    copyright            : (C) 2003-2006 by Alexey S.Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/xml/CXml.h>
#include <sptk3/xml/CXmlTokenizer.h>

using namespace sptk;

std::string CXmlLocator::errorLine(const char *filename, const CXmlLocator &locator)
{
   FILE *fp = fopen(filename, "r");
   if(!fp) return "";

   int line = locator.line();
   int col  = locator.col();

   char linebuffer[1024];
   for(int i=0; i<line-1 && !feof(fp);) {
      int c = fgetc(fp);
      if(c=='\n') i++;
   }
   fread(linebuffer, 1, 1024, fp);
   fclose(fp);

   std::string line_str(linebuffer);
   int crPos = line_str.find("\n");
   if (crPos > 0)
      line_str = line_str.substr(0,crPos);

   int strLen = line_str.length();
   if (strLen >= 79)
      line_str.erase(line_str.begin()+79, line_str.end());

   line_str += "\n";
   for(int j=2; j<col; j++)
      line_str += "-";
   line_str += "^";

   return line_str;
}

//////////////////////////////

CXmlTokenizer::CXmlTokenizer()
{
   m_locator = 0;

   putback_char = -1;
   cdata_mode_ = auto_cdata_ = false;
   prolog_mode_ = false;
   attr_mode_ = false;
   pre_mode_ = false;

   read_buf_pos = 0;
   read_buf_len = -1;
}

void CXmlTokenizer::fill_buffer()
{
   if(read_buf_pos>=read_buf_len) {
      read_buf_len = stream_read(read_buf, BUF_SIZE);
      read_buf_pos = 0;
   }
}

char CXmlTokenizer::read_char()
{
   if(locator()) locator()->m_col++;
   read_buf_pos++;
   fill_buffer();
   return read_buf[read_buf_pos];
}

void CXmlTokenizer::read_next()
{  
   if(!putback_stack.empty()) {
      curtoken = putback_stack.top();     
      putback_stack.pop();     
      return;
   }

   bool ws_added = false;
   write_buf_pos = 0;
   curtoken = "";

   char c;
   while(!eos())
   {     
      if (write_buf_pos>=BUF_SIZE) {
         // Flush buffer!
         std::string tmp(write_buf, write_buf_pos);
         curtoken += tmp;
         write_buf_pos = 0;
      }

      if (putback_char == -1) {
         c = read_char();              
      } else {
         c = putback_char;
         putback_char = -1;            
      }

      if (is_newline(c)) {
         if(locator()) locator()->m_line++;
         if(locator()) locator()->m_col = 1;
      }

        // Is it a literal?
      if (is_literal(c)) {
         if(!cdata_mode_) auto_cdata_ = false;
         if(write_buf_pos==0) {
            curtoken = c;
            if(c=='>')
               if(!cdata_mode_) auto_cdata_ = true;
            return;
         }              
         putback_char = c;            
         break;
      }

      if (cdata_mode_ || (pre_mode() && cdata_mode())) {
            // Fixed CDATA, comments and preformatted text.
            // Add straight to write buffer
         write_buf[write_buf_pos++] = c;
         continue;
      }

        // String delimiter ("/') and in attribute mode?
      if(is_delimiter(c) && attr_mode()) 
      {
         write_buf_pos = 0;
         char delim = c;
         c = read_char();

         while(c!=delim) {
            if(eos()) break;
            write_buf[write_buf_pos++] = c;
            c = read_char();                
         }

         break;
      }

        // Newline char?
      if(is_newline(c)) c=' ';

        // Whitespace?
      if(is_whitespace(c)) 
      {
         if(write_buf_pos==0)
            continue;
         else if(!cdata_mode() || attr_mode())
            break;

         if(ws_added)   continue;
         else        ws_added=true;
      }
      else ws_added=false;    

        // Add to write buffer
      write_buf[write_buf_pos++] = c;

        // Do we have an eos?
      if(eos()) break;
   }

   std::string tmp(write_buf, write_buf_pos);
   curtoken += tmp;
}

// returns if we have a literal char
bool CXmlTokenizer::is_literal( char c ) const
{
   switch(c)
   {
      case '?':
         if (prolog_mode()) return true;
      case '/':      
      case '=':      
      case '!':
         if (attr_mode() || cdata_mode()) return false;
      case '>':      
      case '<':
         return true;
   }

   return false;
}

// returns if we have a white space char
bool CXmlTokenizer::is_whitespace( char c ) const
{
   switch(c)
   {
      case ' ':
      case '\t':
         return true;
   }

   return false;
}

// returns if we have a newline
bool CXmlTokenizer::is_newline( char c ) const
{
   switch(c)
   {
      case '\n':
      case '\r':
         return true;
   }

   return false;
}

// returns if we have a std::string delimiter (separating " and ')
bool CXmlTokenizer::is_delimiter( char c ) const
{
   switch(c)
   {
      case '\"':
      case '\'':
         return true;
   }

   return false;
}

CXmlDefaultTokenizer::CXmlDefaultTokenizer(const CBuffer& buffer)
: CXmlTokenizer()
{
   m_buffer = &buffer;
   m_bufferPosition = 0;
}

bool CXmlDefaultTokenizer::stream_eos() const
{
   return m_bufferPosition >= m_buffer->bytes();
}

int CXmlDefaultTokenizer::stream_read(char *buf, int length)
{
   int available = m_buffer->bytes() - m_bufferPosition;
   if(available > 0) {
      if (available < length) length = available;
      memcpy(buf,m_buffer->data()+m_bufferPosition,length);
      m_bufferPosition += length;
      if (length > 0) return length;
   }
   return -1;
}
