/* call-seq:
 *  doc.canonicalize(mode=XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false)
 *  doc.canonicalize { |obj, parent| ... }
 *
 * Canonicalize a document and return the results.  Takes an optional block
 * that takes two parameters: the +obj+ and that node's +parent+.  
 * The  +obj+ will be either a Nokogiri::XML::Node, or a Nokogiri::XML::Namespace
 * The block must return a non-nil, non-false value if the +obj+ passed in 
 * should be included in the canonicalized document.
 */
static VALUE canonicalize(int argc, VALUE* argv, VALUE self)
{
  VALUE mode;
  VALUE incl_ns;
  VALUE with_comments;
  xmlChar **ns;
  long ns_len, i;

  xmlDocPtr doc;
  xmlOutputBufferPtr buf;
  xmlC14NIsVisibleCallback cb = NULL;
  void * ctx = NULL;

  VALUE rb_cStringIO;
  VALUE io;

  rb_scan_args(argc, argv, "03", &mode, &incl_ns, &with_comments);

  Data_Get_Struct(self, xmlDoc, doc);

  rb_cStringIO = rb_const_get_at(rb_cObject, rb_intern("StringIO"));
  io           = rb_class_new_instance(0, 0, rb_cStringIO);
  buf          = xmlAllocOutputBuffer(NULL);

  buf->writecallback = (xmlOutputWriteCallback)io_write_callback;
  buf->closecallback = (xmlOutputCloseCallback)io_close_callback;
  buf->context       = (void *)io;

  if(rb_block_given_p()) {
    cb = block_caller;
    ctx = (void *)rb_block_proc();
  }

  if(NIL_P(incl_ns)){
    ns = NULL;
  }
  else{
    ns_len = RARRAY_LEN(incl_ns);
    ns = calloc((size_t)ns_len+1, sizeof(xmlChar *));
    for (i = 0 ; i < ns_len ; i++) {
      VALUE entry = rb_ary_entry(incl_ns, i);
      const char * ptr = StringValuePtr(entry);
      ns[i] = (xmlChar*) ptr;
    }
  }


  xmlC14NExecute(doc, cb, ctx, 
    (int)      (NIL_P(mode)        ? 0 : NUM2INT(mode)), 
    ns,
    (int)      (NIL_P(with_comments)        ? 0 : 1),
    buf);

  xmlOutputBufferClose(buf);

  return rb_funcall(io, rb_intern("string"), 0);
}