/* $Id: ruby_xml_sax_parser.c,v 1.4 2006/04/14 23:46:06 roscopeco Exp $ */

/* Please see the LICENSE file for copyright and distribution information */

#include "libxml.h"
#include "ruby_xml_sax_parser.h"

VALUE cXMLSaxParser;
VALUE callsym;

#include "sax_parser_callbacks.inc"

void
ruby_xml_sax_parser_free(ruby_xml_sax_parser *rxsp) {
  /* Apparently this isn't needed: time will tell */
  /* if (rxsp->xsh != NULL) */
  /* xmlFreeSax_Parser(rxsp->sax_parser); */
}

#define mark_handler(rxsp, handler) \
    if (rxsp->cbp->handler && (rxsp->cbp->handler != Qnil)) \
      rb_gc_mark(rxsp->cbp->handler)
  
void
ruby_xml_sax_parser_mark(ruby_xml_sax_parser *rxsp) {
  mark_handler(rxsp, internalSubset);
  mark_handler(rxsp, isStandalone);
  mark_handler(rxsp, hasInternalSubset);
  mark_handler(rxsp, hasExternalSubset);
  mark_handler(rxsp, startDocument);
  mark_handler(rxsp, endDocument);
  mark_handler(rxsp, startElement);
  mark_handler(rxsp, endElement);
  mark_handler(rxsp, reference);
  mark_handler(rxsp, characters);
  mark_handler(rxsp, processingInstruction);
  mark_handler(rxsp, comment);
  mark_handler(rxsp, xmlParserWarning);
  mark_handler(rxsp, xmlParserError);
  mark_handler(rxsp, xmlParserFatalError);
  mark_handler(rxsp, cdataBlock);
}


/*
 * call-seq:
 *    XML::SaxParser.new => sax_parser
 * 
 * Create a new XML::SaxParser instance.
 */
VALUE
ruby_xml_sax_parser_new(VALUE class) {
  ruby_xml_sax_parser *rxsp;
  
  rxsp = ALLOC(ruby_xml_sax_parser);
  rxsp->cbp = ALLOC(ruby_xml_sax_parser_callbacks);
  memset(rxsp->cbp, 0, sizeof(ruby_xml_sax_parser_callbacks));
  rxsp->xsh = &rubySAXHandlerStruct;
  
  rxsp->xpc = NULL;
  rxsp->filename = Qnil;
  rxsp->str = Qnil;

  return(Data_Wrap_Struct(class, ruby_xml_sax_parser_mark, 
                              ruby_xml_sax_parser_free, rxsp));
}


/*
 * call-seq:
 *    sax_parser.filename => "filename"
 * 
 * Obtain the filename this parser reads from.
 */
VALUE
ruby_xml_sax_parser_filename_get(VALUE self) {
  ruby_xml_sax_parser *rxsp;
  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp);
  return(rxsp->filename);
}


/*
 * call-seq:
 *    sax_parser.filename = "filename"
 * 
 * Set the filename this parser reads from.
 */
VALUE
ruby_xml_sax_parser_filename_set(VALUE self, VALUE filename) {
  ruby_xml_sax_parser *rxsp;
  Check_Type(filename, T_STRING);
  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp);
  rxsp->filename = filename;
  return(rxsp->filename);
}

#define set_handler(self, argc, argv, handler)      \
  VALUE proc;                                       \
  rb_scan_args(argc, argv, "0&", &proc);            \
  ruby_xml_sax_parser *rxsp;                        \
  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp); \
  rxsp->cbp->handler = proc;                        \
  return(Qnil);


/*
 * call-seq:
 *    parser.on_internal_subset { |name, external_id, system_id| ... } => nil
 *
 * Set the callback block for an internal subset event.
 */
VALUE
ruby_xml_sax_parser_on_internal_subset(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, internalSubset);
}
 
 
/*
 * call-seq:
 *    parser.on_is_standalone { || ... } => nil
 *
 * Set the callback proc for 'is standalone' event.
 */
VALUE
ruby_xml_sax_parser_on_is_standalone(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, isStandalone);
}
 
 
/*
 * call-seq:
 *    parser.on_has_internal_subset { || ... } => nil
 *
 * Set the callback proc for an internal subset notification event.
 */
VALUE
ruby_xml_sax_parser_on_has_internal_subset(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, hasInternalSubset);
}
 
 
/*
 * call-seq:
 *    parser.on_has_external_subset { || ... } => nil
 *
 * Set the callback proc for an external subset notification event.
 */
VALUE
ruby_xml_sax_parser_on_has_external_subset(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, hasExternalSubset);
}
 
 
/*
 * call-seq:
 *    parser.on_start_document { || ... } => nil
 *
 * Set the callback proc for a start document event.
 */
VALUE
ruby_xml_sax_parser_on_start_document(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, startDocument);
}
 
 
/*
 * call-seq:
 *    parser.on_end_document { || ... } => nil
 *
 * Set the callback proc for an end document event.
 */
VALUE
ruby_xml_sax_parser_on_end_document(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, endDocument);
}
 
 
/*
 * call-seq:
 *    parser.on_start_element { |name, attr_hash| ... } => nil
 * 
 * Set the callback proc for an element start event.
 */
VALUE
ruby_xml_sax_parser_on_start_element(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, startElement);
}
 
 
/*
 * call-seq:
 *    parser.on_end_element { |name| ... } => nil
 *
 * Set the callback proc for an element end event.
 */
VALUE
ruby_xml_sax_parser_on_end_element(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, endElement);
}
 
 
/*
 * call-seq:
 *    parser.on_reference { |name| ... } => nil
 *
 * Set the callback proc for a reference event.
 */
VALUE
ruby_xml_sax_parser_on_reference(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, reference);
}
 
 
/*
 * call-seq:
 *    parser.on_characters { |chars| ... } => nil
 *
 * Set the callback proc for a characters event.
 */
VALUE
ruby_xml_sax_parser_on_characters(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, characters);
}
 
 
/*
 * call-seq:
 *    parser.on_processing_instruction { |target, data| ... } => nil
 *
 * Set the callback proc for an processing instruction event.
 */
VALUE
ruby_xml_sax_parser_on_processing_instruction(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, processingInstruction);
}
 
 
/*
 * call-seq:
 *    parser.on_comment { |msg| ... } => nil
 *
 * Set the callback proc for a comment event.
 */
VALUE
ruby_xml_sax_parser_on_comment(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, comment);
}


/*
 * call-seq:
 *    parser.on_parser_warning { |msg| ... } => nil
 *
 * Set the callback proc that receives parser warnings.
 */
VALUE
ruby_xml_sax_parser_on_parser_warning(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, xmlParserWarning);
}
 
 
/*
 * call-seq:
 *    parser.on_parser_error { |msg| ... } => nil
 *
 * Set the callback proc that receives parser errors.
 */
VALUE
ruby_xml_sax_parser_on_parser_error(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, xmlParserError);
}
 
  
/*
 * call-seq:
 *    parser.on_parser_fatal_error { |msg| ... } => nil
 *
 * Set the callback proc that receives fatal parser errors.
 */
VALUE
ruby_xml_sax_parser_on_parser_fatal_error(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, xmlParserFatalError);
}
 
/*
 * call-seq:
 *    parser.on_cdata_block { |cdata| ... } => nil
 *
 * Set the callback proc for a CDATA block event.
 */
VALUE
ruby_xml_sax_parser_on_cdata_block(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, cdataBlock);
}
 
/*
 * call-seq:
 *    parser.on_external_subset { |name, external_id, system_id| ... } => nil
 *
 * Set the callback proc for an external subset event.
 */
VALUE
ruby_xml_sax_parser_on_external_subset(int argc, VALUE *argv, VALUE self) {
  set_handler(self, argc, argv, externalSubset);
}
 
  
/*
 * call-seq:
 *    parser.parse => (true|false)
 * 
 * Parse the input XML, generating callbacks to the procs
 * registered with the parser (via the on_xxxx attributes).
 */
VALUE
ruby_xml_sax_parser_parse(VALUE self) {
  char *str;
  int status = 1;
  ruby_xml_sax_parser *rxsp;

  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp);

  if (rxsp->filename != Qnil) {
    status = xmlSAXUserParseFile(rxsp->xsh, rxsp->cbp, StringValuePtr(rxsp->filename));
  } else if (rxsp->str != Qnil) {
    str = StringValuePtr(rxsp->str);
    status = //ruby_xml_document_new(cXMLDocument,
				   xmlSAXUserParseMemory(rxsp->xsh, rxsp->cbp,
                 str, strlen(str)); //);
  }
  
  /* XXX This should return an exception for the various error codes
   * that can come back in status, but I'm too lazy to do that right
   * now. */
  if (status)
    return(Qfalse);
  else
    return(Qtrue);
}


/*
 * call-seq:
 *    parser.string => "xml"
 * 
 * Obtain the parser's input string.
 */
VALUE
ruby_xml_sax_parser_str_get(VALUE self) {
  ruby_xml_sax_parser *rxsp;
  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp);
  return(rxsp->str);
}


/*
 * call-seq:
 *    parser.string = "xml"
 * 
 * Set the parser's input string.
 */
VALUE
ruby_xml_sax_parser_str_set(VALUE self, VALUE str) {
  ruby_xml_sax_parser *rxsp;
  Check_Type(str, T_STRING);
  Data_Get_Struct(self, ruby_xml_sax_parser, rxsp);
  rxsp->str = str;
  return(rxsp->str);
}

// Rdoc needs to know 
#ifdef RDOC_NEVER_DEFINED
  mXML = rb_define_module("XML");
#endif

void
ruby_init_xml_sax_parser(void) {
  cXMLSaxParser = rb_define_class_under(mXML, "SaxParser", rb_cObject);
  callsym = rb_intern("call");

  rb_define_singleton_method(cXMLSaxParser, "new", ruby_xml_sax_parser_new, 0);

  rb_define_method(cXMLSaxParser, "filename",
		   ruby_xml_sax_parser_filename_get, 0);
  rb_define_method(cXMLSaxParser, "filename=",
		   ruby_xml_sax_parser_filename_set, 1);
  rb_define_method(cXMLSaxParser, "parse", ruby_xml_sax_parser_parse, 0);
  rb_define_method(cXMLSaxParser, "string", ruby_xml_sax_parser_str_get, 0);
  rb_define_method(cXMLSaxParser, "string=", ruby_xml_sax_parser_str_set, 1);

  rb_define_method(cXMLSaxParser, "on_internal_subset", 
      ruby_xml_sax_parser_on_internal_subset, -1);        
  rb_define_method(cXMLSaxParser, "on_is_standalone", 
      ruby_xml_sax_parser_on_is_standalone, -1);        
  rb_define_method(cXMLSaxParser, "on_has_internal_subset", 
      ruby_xml_sax_parser_on_has_internal_subset, -1);        
  rb_define_method(cXMLSaxParser, "on_has_external_subset", 
      ruby_xml_sax_parser_on_has_external_subset, -1);        
  rb_define_method(cXMLSaxParser, "on_start_document", 
      ruby_xml_sax_parser_on_start_document, -1);  
  rb_define_method(cXMLSaxParser, "on_end_document", 
      ruby_xml_sax_parser_on_end_document, -1);  
  rb_define_method(cXMLSaxParser, "on_start_element", 
      ruby_xml_sax_parser_on_start_element, -1);  
  rb_define_method(cXMLSaxParser, "on_end_element", 
      ruby_xml_sax_parser_on_end_element, -1);        
  rb_define_method(cXMLSaxParser, "on_reference", 
      ruby_xml_sax_parser_on_reference, -1);        
  rb_define_method(cXMLSaxParser, "on_characters", 
      ruby_xml_sax_parser_on_characters, -1);        
  rb_define_method(cXMLSaxParser, "on_processing_instruction", 
      ruby_xml_sax_parser_on_processing_instruction, -1);        
  rb_define_method(cXMLSaxParser, "on_comment", 
      ruby_xml_sax_parser_on_comment, -1);        
  rb_define_method(cXMLSaxParser, "on_parser_warning", 
      ruby_xml_sax_parser_on_parser_warning, -1);        
  rb_define_method(cXMLSaxParser, "on_parser_error", 
      ruby_xml_sax_parser_on_parser_error, -1);        
  rb_define_method(cXMLSaxParser, "on_parser_fatal_error", 
      ruby_xml_sax_parser_on_parser_fatal_error, -1);        
  rb_define_method(cXMLSaxParser, "on_cdata_block", 
      ruby_xml_sax_parser_on_cdata_block, -1);        
  rb_define_method(cXMLSaxParser, "on_external_subset", 
      ruby_xml_sax_parser_on_external_subset, -1);              
}
