/* $Id: ruby_xml_document.c,v 1.34 2003/02/06 10:50:57 sean Exp $ */

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

#include "libxml.h"
#include "ruby_xml_document.h"

VALUE cXMLDocument;

VALUE ruby_xml_document_compression_get(VALUE self) {
#ifdef HAVE_ZLIB_H
  ruby_xml_document *rxd;
  int compmode;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  compmode = xmlGetDocCompressMode(rxd->doc);
  if (compmode == -1)
    return(Qnil);
  else
    return(INT2NUM(compmode));  
#else
  rb_warn("libxml not compiled with zlib support");
  return(Qfalse);
#endif
}


VALUE ruby_xml_document_compression_set(VALUE self, VALUE num) {
#ifdef HAVE_ZLIB_H
  ruby_xml_document *rxd;
  int compmode;
  Check_Type(num, T_FIXNUM);
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc == NULL) {
    return(Qnil);
  } else {
    xmlSetDocCompressMode(rxd->doc, NUM2INT(num));

    compmode = xmlGetDocCompressMode(rxd->doc);
    if (compmode == -1)
      return(Qnil);
    else
      return(INT2NUM(compmode));
  }
#else
  rb_warn("libxml compiled without zlib support");
  return(Qfalse);
#endif
}


VALUE ruby_xml_document_compression_q(VALUE self) {
#ifdef HAVE_ZLIB_H
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->compression != -1)
    return(Qtrue);
  else
    return(Qfalse);
#else
  rb_warn("libxml compiled without zlib support");
  return(Qfalse);
#endif
}


VALUE ruby_xml_document_child_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;

  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->children == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, rxd->doc->children);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_child_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->children == NULL)
    return(Qfalse);
  else
    return(Qtrue);
}


VALUE ruby_xml_document_dump(int argc, VALUE *argv, VALUE self) {
  OpenFile *fptr;
  VALUE io;
  FILE *out;
  ruby_xml_document *rxd;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc == NULL)
    return(Qnil);

  switch (argc) {
  case 0:
    io = rb_stdout;
    break;
  case 1:
    io = argv[0];
    if (!rb_obj_is_kind_of(io, rb_cIO))
      rb_raise(rb_eTypeError, "need an IO object");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  xmlDocDump(out, rxd->doc);
  return(Qtrue);
}


VALUE ruby_xml_document_debug_dump(int argc, VALUE *argv, VALUE self) {
#ifdef LIBXML_DEBUG_ENABLED
  OpenFile *fptr;
  VALUE io;
  FILE *out;
  ruby_xml_document *rxd;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc == NULL)
    return(Qnil);

  switch (argc) {
  case 0:
    io = rb_stderr;
    break;
  case 1:
    io = argv[0];
    if (!rb_obj_is_kind_of(io, rb_cIO))
      rb_raise(rb_eTypeError, "need an IO object");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  xmlDebugDumpDocument(out, rxd->doc);
  return(Qtrue);
#else
  rb_warn("libxml was compiled without debugging support.  Please recompile libxml and ruby-libxml");
  return(Qfalse);
#endif
}


VALUE ruby_xml_document_debug_dump_head(int argc, VALUE *argv, VALUE self) {
#ifdef LIBXML_DEBUG_ENABLED
  OpenFile *fptr;
  VALUE io;
  FILE *out;
  ruby_xml_document *rxd;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc == NULL)
    return(Qnil);

  switch (argc) {
  case 0:
    io = rb_stdout;
    break;
  case 1:
    io = argv[0];
    if (!rb_obj_is_kind_of(io, rb_cIO))
      rb_raise(rb_eTypeError, "need an IO object");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  xmlDebugDumpDocumentHead(out, rxd->doc);
  return(Qtrue);
#else
  rb_warn("libxml was compiled without debugging support.  Please recompile libxml and ruby-libxml");
  return(Qfalse);
#endif
}


VALUE ruby_xml_document_format_dump(int argc, VALUE *argv, VALUE self) {
  OpenFile *fptr;
  VALUE bool, io;
  FILE *out;
  ruby_xml_document *rxd;
  int size, spacing;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc == NULL)
    return(Qnil);

  switch (argc) {
  case 0:
    io = rb_stdout;
    spacing = 1;
    break;
  case 1:
    io = argv[0];
    if (!rb_obj_is_kind_of(io, rb_cIO))
      rb_raise(rb_eTypeError, "need an IO object");
    spacing = 1;
    break;
  case 2:
    io = argv[0];
    if (!rb_obj_is_kind_of(io, rb_cIO))
      rb_raise(rb_eTypeError, "need an IO object");
    bool = argv[1];
    if (TYPE(bool) == T_TRUE)
      spacing = 1;
    else if (TYPE(bool) == T_FALSE)
      spacing = 0;
    else
      rb_raise(rb_eTypeError, "incorect argument type, second argument must be bool");

    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  size = xmlDocFormatDump(out, rxd->doc, spacing);
  return(INT2NUM(size));
}


VALUE ruby_xml_document_debug_format_dump(int argc, VALUE *argv, VALUE self) {
  rb_warn("debug_format_dump has been deprecaited, use format_dump instead");
  return(ruby_xml_document_format_dump(argc, argv, self));
}


VALUE ruby_xml_document_encoding_get(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc->encoding == NULL)
    return(Qnil);
  else
    return(rb_str_new2(rxd->doc->encoding));
}


VALUE ruby_xml_document_encoding_set(VALUE self, VALUE encoding) {
  ruby_xml_document *rxd;

  Check_Type(encoding, T_STRING);
  Data_Get_Struct(self, ruby_xml_document, rxd);
  rxd->doc->encoding = ruby_strdup(STR2CSTR(encoding));
  return(ruby_xml_document_encoding_get(self));
}


VALUE ruby_xml_document_filename_get(VALUE self) {
  ruby_xml_document *rxd;
  rx_file_data *data;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->data == NULL)
    return(Qnil);

  switch (rxd->data_type) {
  case RUBY_LIBXML_SRC_TYPE_NULL:
    return(Qnil);
  case RUBY_LIBXML_SRC_TYPE_FILE:
    data = (rx_file_data *)rxd->data;
    return(data->filename);
  default:
    rb_fatal("Unknown document type in libxml");
  }

  return(Qnil);
}


VALUE ruby_xml_document_find(VALUE self, VALUE xpath_str) {
  ruby_xml_document *doc;
  VALUE root;

  Data_Get_Struct(self, ruby_xml_document, doc);
  root = ruby_xml_node_new2(cXMLNode, self, xmlDocGetRootElement(doc->doc));
  return(ruby_xml_xpath_find2(root, xpath_str));
}


void ruby_xml_document_free(ruby_xml_document *rxd) {
  void *data;

  if (rxd->doc != NULL && !rxd->is_ptr) {
    xmlFreeDoc(rxd->doc);
    ruby_xml_parser_count--;
  }

  if (ruby_xml_parser_count == 0)
    xmlCleanupParser();

  switch(rxd->data_type) {
  case RUBY_LIBXML_SRC_TYPE_NULL:
    break;
  case RUBY_LIBXML_SRC_TYPE_FILE:
    (rx_file_data *)data = (rx_file_data *)rxd->data;
    free((rx_file_data *)data);
    break;
  case RUBY_LIBXML_SRC_TYPE_STRING:
    (rx_string_data *)data = (rx_string_data *)rxd->data;
    free((rx_string_data *)data);
    break;
  case RUBY_LIBXML_SRC_TYPE_IO:
    (rx_io_data *)data = (rx_io_data *)rxd->data;
    free((rx_io_data *)data);
    break;
  default:
    rb_fatal("Unknown data type, %d", rxd->data_type);
  }

  free(rxd);
}


VALUE ruby_xml_document_initialize(int argc, VALUE *argv, VALUE class) {
  VALUE docobj, xmlver;

  switch (argc) {
  case 0:
    xmlver = rb_str_new2("1.0");
    break;
  case 1:
    rb_scan_args(argc, argv, "01", &xmlver);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (need 0 or 1)");
  }

  docobj = ruby_xml_document_new2(cXMLDocument, xmlver);
  return(docobj);
}


VALUE ruby_xml_document_last_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;

  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->last == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, rxd->doc->last);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_last_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->last == NULL)
    return(Qfalse);
  else
    return(Qtrue);  
}


static void ruby_xml_document_mark(ruby_xml_document *rxd) {
  if (rxd == NULL) return;
  if (!NIL_P(rxd->xmlver)) rb_gc_mark(rxd->xmlver);
}


VALUE ruby_xml_document_new(VALUE class, xmlDocPtr doc) {
  ruby_xml_document *rxd;

  rxd = ALLOC(ruby_xml_document);
  ruby_xml_parser_count++;

  rxd->data = NULL;
  rxd->data_type = RUBY_LIBXML_SRC_TYPE_NULL;
  rxd->doc = doc;
  rxd->is_ptr = 0;
  rxd->xmlver = Qnil;

  return(Data_Wrap_Struct(cXMLDocument, ruby_xml_document_mark,
			  ruby_xml_document_free, rxd));
}


VALUE ruby_xml_document_new2(VALUE class, VALUE xmlver) {
  ruby_xml_document *rxd;

  Check_Type(xmlver, T_STRING);
  rxd = ALLOC(ruby_xml_document);
  ruby_xml_parser_count++;

  rxd->data = NULL;
  rxd->data_type = RUBY_LIBXML_SRC_TYPE_NULL;
  rxd->doc = xmlNewDoc(STR2CSTR(xmlver));
  rxd->is_ptr = 0;
  rxd->xmlver = xmlver;

  if (rxd->doc == NULL)
    rb_fatal("bad");

  return(Data_Wrap_Struct(cXMLDocument, ruby_xml_document_mark,
			  ruby_xml_document_free, rxd));
}


VALUE ruby_xml_document_new3(VALUE class) {
  return(ruby_xml_document_new2(class, rb_str_new2("1.0")));
}


VALUE ruby_xml_document_new4(VALUE class, xmlDocPtr doc) {
  ruby_xml_document *rxd;

  rxd = ALLOC(ruby_xml_document);

  rxd->data = NULL;
  rxd->data_type = RUBY_LIBXML_SRC_TYPE_NULL;
  rxd->doc = doc;
  rxd->is_ptr = 1;
  rxd->xmlver = Qnil;

  return(Data_Wrap_Struct(cXMLDocument, ruby_xml_document_mark,
			  ruby_xml_document_free, rxd));
}


VALUE ruby_xml_document_new_file(VALUE class, VALUE filename) {
  VALUE parser;

  parser = ruby_xml_parser_new(cXMLParser);
  ruby_xml_parser_filename_set(parser, filename);
  return(ruby_xml_parser_parse(parser));
}


VALUE ruby_xml_document_next_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;

  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->next == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, rxd->doc->next);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_next_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->next == NULL)
    return(Qfalse);
  else
    return(Qtrue);  
}


VALUE ruby_xml_document_parent_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;

  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->parent == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, rxd->doc->parent);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_parent_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->parent == NULL)
    return(Qfalse);
  else
    return(Qtrue);  
}


VALUE ruby_xml_document_prev_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;

  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->prev == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, rxd->doc->prev);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_prev_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);

  if (rxd->doc->prev == NULL)
    return(Qfalse);
  else
    return(Qtrue);  
}


VALUE ruby_xml_document_property_get(VALUE self, VALUE key) {
  VALUE node;

  node = ruby_xml_document_root_get(self);
  return(ruby_xml_node_property_get(node, key));
}


VALUE ruby_xml_document_property_set(VALUE self, VALUE key, VALUE val) {
  VALUE node;

  node = ruby_xml_document_root_get(self);
  return(ruby_xml_node_property_set(node, key, val));
}


VALUE ruby_xml_document_root_get(VALUE self) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE node;
  xmlNodePtr root;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  root = xmlDocGetRootElement(rxd->doc);

  if (root == NULL)
    return(Qnil);

  node = ruby_xml_node_new2(cXMLNode, self, root);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  rxn->xd = self;
  return(node);
}


VALUE ruby_xml_document_root_set(VALUE self, VALUE node) {
  ruby_xml_document *rxd;
  ruby_xml_node *rxn;
  VALUE retnode;
  xmlNodePtr root;

  if (rb_obj_is_kind_of(node, cXMLNode) == Qfalse)
    rb_raise(rb_eTypeError, "must pass an XML::Node type object");

  Data_Get_Struct(self, ruby_xml_document, rxd);
  Data_Get_Struct(node, ruby_xml_node, rxn);
  ruby_xml_node_set_ptr(node, 1);
  root = xmlDocSetRootElement(rxd->doc, rxn->node);
  if (root == NULL)
    return(Qnil);

  retnode = ruby_xml_node_new2(cXMLNode, self, root);
  return(retnode);
}


VALUE ruby_xml_document_save(int argc, VALUE *argv, VALUE self) {
  ruby_xml_document *rxd;
  xmlChar *filename;
  int format, len;

  format = 0;
  switch (argc) {
  case 1:
    break;
  case 2:
    if (TYPE(argv[1]) == T_TRUE)
      format = 1;
    else if (TYPE(argv[1]) == T_FALSE)
      format = 0;
    else
      rb_raise(rb_eTypeError, "wrong type of argument, must be bool");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  Check_Type(argv[0], T_STRING);
  filename = STR2CSTR(argv[0]);

  Data_Get_Struct(self, ruby_xml_document, rxd);
  len = xmlSaveFormatFileEnc(filename, rxd->doc, rxd->doc->encoding, format);
  if (len == -1)
    rb_fatal("Unable to write out file");
  else
    return(INT2NUM(len));
}


VALUE ruby_xml_document_standalone_q(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc->standalone)
    return(Qtrue);
  else
    return(Qfalse);
}


VALUE ruby_xml_document_to_s(int argc, VALUE *argv, VALUE self) {
  ruby_xml_document *rxd;
  xmlChar *result;
  int format, len;

  switch (argc) {
  case 0:
    format = 1;
    break;
  case 1:
    if (TYPE(argv[0]) == T_TRUE)
      format = 1;
    else if (TYPE(argv[0]) == T_FALSE)
      format = 0;
    else
      rb_raise(rb_eTypeError, "wrong type of argument, must be bool");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc == NULL) {
    return(Qnil);
  } else if (rxd->doc->encoding != NULL) {
    if (format) {
      xmlDocDumpFormatMemoryEnc(rxd->doc, &result, &len,
				rxd->doc->encoding, format);
    } else {
      xmlDocDumpMemoryEnc(rxd->doc, &result, &len,
			  rxd->doc->encoding);
    }
  } else {
    if (format)
      xmlDocDumpFormatMemory(rxd->doc, &result, &len, format);
    else
      xmlDocDumpMemory(rxd->doc, &result, &len);
  }

  return(rb_str_new2(result));
}


VALUE ruby_xml_document_url_get(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc->URL == NULL)
    return(Qnil);
  else
    return(rb_str_new2(rxd->doc->URL));
}


VALUE ruby_xml_document_version_get(VALUE self) {
  ruby_xml_document *rxd;
  Data_Get_Struct(self, ruby_xml_document, rxd);
  if (rxd->doc->version == NULL)
    return(Qnil);
  else
    return(rb_str_new2(rxd->doc->version));
}


VALUE ruby_xml_document_xinclude(VALUE self) {
#ifdef LIBXML_XINCLUDE_ENABLED
  ruby_xml_document *rxd;
  int ret;

  Data_Get_Struct(self, ruby_xml_document, rxd);
  ret = xmlXIncludeProcess(rxd->doc);
  if (ret >= 0)
    return(INT2NUM(ret));
  else
    rb_raise(eXMLXIncludeError, "error processing xinclude directives in document");
#else
  rb_warn("libxml was compiled without XInclude support.  Please recompile libxml and ruby-libxml");
  return(Qfalse);
#endif
}


void ruby_init_xml_document(void) {
  cXMLDocument = rb_define_class_under(mXML, "Document", rb_cObject);
  rb_define_singleton_method(cXMLDocument, "file", ruby_xml_document_new_file, 1);
  rb_define_singleton_method(cXMLDocument, "new", ruby_xml_document_initialize, -1);

  rb_define_method(cXMLDocument, "[]", ruby_xml_document_property_get, 1);
  rb_define_method(cXMLDocument, "[]=", ruby_xml_document_property_set, 2);
  rb_define_method(cXMLDocument, "child", ruby_xml_document_child_get, 0);
  rb_define_method(cXMLDocument, "child?", ruby_xml_document_child_q, 0);
  rb_define_method(cXMLDocument, "compression", ruby_xml_document_compression_get, 0);
  rb_define_method(cXMLDocument, "compression=", ruby_xml_document_compression_set, 1);
  rb_define_method(cXMLDocument, "compression?", ruby_xml_document_compression_q, 0);
  rb_define_method(cXMLDocument, "dump", ruby_xml_document_dump, -1);
  rb_define_method(cXMLDocument, "debug_dump", ruby_xml_document_debug_dump, -1);
  rb_define_method(cXMLDocument, "debug_dump_head", ruby_xml_document_debug_dump_head, -1);
  rb_define_method(cXMLDocument, "debug_format_dump", ruby_xml_document_debug_format_dump, -1);
  rb_define_method(cXMLDocument, "encoding", ruby_xml_document_encoding_get, 0);
  rb_define_method(cXMLDocument, "encoding=", ruby_xml_document_encoding_set, 1);
  rb_define_method(cXMLDocument, "filename", ruby_xml_document_filename_get, 0);
  rb_define_method(cXMLDocument, "find", ruby_xml_document_find, 1);
  rb_define_method(cXMLDocument, "format_dump", ruby_xml_document_format_dump, -1);
  rb_define_method(cXMLDocument, "last", ruby_xml_document_last_get, 0);
  rb_define_method(cXMLDocument, "last?", ruby_xml_document_last_q, 0);
  rb_define_method(cXMLDocument, "next", ruby_xml_document_next_get, 0);
  rb_define_method(cXMLDocument, "next?", ruby_xml_document_next_q, 0);
  rb_define_method(cXMLDocument, "parent", ruby_xml_document_parent_get, 0);
  rb_define_method(cXMLDocument, "parent?", ruby_xml_document_parent_q, 0);
  rb_define_method(cXMLDocument, "prev", ruby_xml_document_prev_get, 0);
  rb_define_method(cXMLDocument, "prev?", ruby_xml_document_prev_q, 0);
  rb_define_method(cXMLDocument, "root", ruby_xml_document_root_get, 0);
  rb_define_method(cXMLDocument, "root=", ruby_xml_document_root_set, 1);
  rb_define_method(cXMLDocument, "save", ruby_xml_document_save, -1);
  rb_define_method(cXMLDocument, "standalone?", ruby_xml_document_standalone_q, 0);
  rb_define_method(cXMLDocument, "to_s", ruby_xml_document_to_s, -1);
  rb_define_method(cXMLDocument, "url", ruby_xml_document_url_get, 0);
  rb_define_method(cXMLDocument, "version", ruby_xml_document_version_get, 0);
  rb_define_method(cXMLDocument, "xinclude", ruby_xml_document_xinclude, 0);
}
