#include "ruby_libxml.h"
#include <libxml/xmlerror.h>
VALUE eXMLError;
static ID CALL_METHOD;
static ID ERROR_HANDLER_ID;
/*
* Document-class: LibXML::XML::Error
*
* The XML::Error class exposes libxml errors as
* standard Ruby exceptions. When appropriate,
* libxml-ruby will raise an exception - for example,
* when parsing a non-well formed xml document.
*
* By default, warnings, errors and fatal errors that
* libxml generates are printed to STDERR via the
* XML::Error::VERBOSE_HANDLER proc.
*
* To disable this output you can install the quiet handler:
*
* XML::Error.set_handler(&XML::Error::QUIET_HANDLER)
*
* Get the current handler:
*
* proc = XML::Error.get_handler
*
* Install your own handler:
*
* XML::Error.set_handler do |error|
* puts error.to_s
* end
*
* Or remove all handlers:
*
* XML::Error.reset_handler
*/
/*
* call-seq:
* Error.get_error_handler
*
* Returns the proc that will be called when libxml generates
* warning, error or fatal error messages.
*/
static VALUE rxml_error_get_handler()
{
VALUE block = rb_cvar_get(eXMLError, ERROR_HANDLER_ID);
return block;
}
VALUE rxml_error_wrap(xmlErrorPtr xerror)
{
VALUE result = Qnil;
if (xerror->message)
result = rb_exc_new2(eXMLError, xerror->message);
else
result = rb_class_new_instance(0, NULL, eXMLError);
rb_iv_set(result, "@domain", INT2NUM(xerror->domain));
rb_iv_set(result, "@code", INT2NUM(xerror->code));
rb_iv_set(result, "@level", INT2NUM(xerror->level));
if (xerror->file)
rb_iv_set(result, "@file", rb_str_new2(xerror->file));
if (xerror->line)
rb_iv_set(result, "@line", INT2NUM(xerror->line));
if (xerror->str1)
rb_iv_set(result, "@str1", rb_str_new2(xerror->str1));
if (xerror->str2)
rb_iv_set(result, "@str2", rb_str_new2(xerror->str2));
if (xerror->str3)
rb_iv_set(result, "@str3", rb_str_new2(xerror->str3));
rb_iv_set(result, "@int1", INT2NUM(xerror->int1));
rb_iv_set(result, "@int2", INT2NUM(xerror->int2));
if (xerror->node)
{
/* Returning the original node is too dangerous because its
parent document is never returned to Ruby. So return a
copy of the node, which does not belong to any document,
and can free itself when Ruby calls its free method. Note
we just copy the node, and don't bother with the overhead
of a recursive query. */
xmlNodePtr xNode = xmlCopyNode((const xmlNodePtr)xerror->node, 2);
VALUE node = rxml_node_wrap(xNode);
rb_iv_set(result, "@node", node);
}
return result;
}
/* Hook that receives xml error message */
static void structuredErrorFunc(void *userData, xmlErrorPtr xerror)
{
VALUE error = rxml_error_wrap(xerror);
/* Wrap error up as Ruby object and send it off to ruby */
VALUE block = rxml_error_get_handler();
/* Now call global handler */
if (block != Qnil)
{
rb_funcall(block, CALL_METHOD, 1, error);
}
}
static void rxml_set_handler(VALUE self, VALUE block)
{
#ifdef RB_CVAR_SET_4ARGS
rb_cvar_set(self, ERROR_HANDLER_ID, block, 0);
#else
rb_cvar_set(self, ERROR_HANDLER_ID, block);
#endif
/* Intercept libxml error handlers */
xmlSetStructuredErrorFunc(NULL, structuredErrorFunc);
}
/*
* call-seq:
* Error.set_error_handler {|error| ... }
*
* Registers a block that will be called with an instance of
* XML::Error when libxml generates warning, error or fatal
* error messages.
*/
static VALUE rxml_error_set_handler(VALUE self)
{
VALUE block;
if (rb_block_given_p() == Qfalse)
rb_raise(rb_eRuntimeError, "No block given");
block = rb_block_proc();
/* Embed the block within the Error class to avoid it to be collected.
Previous handler will be overwritten if it exists. */
rxml_set_handler(self, block);
return self;
}
/*
* call-seq:
* Error.reset_error_handler
*
* Removes the current error handler. */
static VALUE rxml_error_reset_handler(VALUE self)
{
rxml_set_handler(self, Qnil);
return self;
}
NORETURN(void rxml_raise(xmlErrorPtr xerror))
{
/* Wrap error up as Ruby object and send it off to ruby */
VALUE error = rxml_error_wrap(xerror);
rb_exc_raise(error);
}
void rxml_init_error()
{
CALL_METHOD = rb_intern("call");
ERROR_HANDLER_ID = rb_intern("@@__error_handler_callback__");
/* Error class */
eXMLError = rb_define_class_under(mXML, "Error", rb_eStandardError);
rb_define_singleton_method(eXMLError, "get_handler", rxml_error_get_handler, 0);
rb_define_singleton_method(eXMLError, "set_handler", rxml_error_set_handler, 0);
rb_define_singleton_method(eXMLError, "reset_handler", rxml_error_reset_handler, 0);
/* Ruby callback to receive errors - set it to nil by default. */
rxml_set_handler(eXMLError, Qnil);
/* Error attributes */
rb_define_attr(eXMLError, "level", 1, 0);
rb_define_attr(eXMLError, "domain", 1, 0);
rb_define_attr(eXMLError, "code", 1, 0);
rb_define_attr(eXMLError, "file", 1, 0);
rb_define_attr(eXMLError, "line", 1, 0);
rb_define_attr(eXMLError, "str1", 1, 0);
rb_define_attr(eXMLError, "str2", 1, 0);
rb_define_attr(eXMLError, "str3", 1, 0);
rb_define_attr(eXMLError, "int1", 1, 0);
rb_define_attr(eXMLError, "int2", 1, 0);
rb_define_attr(eXMLError, "ctxt", 1, 0);
rb_define_attr(eXMLError, "node", 1, 0);
/* xml error levels */
rb_define_const(eXMLError, "NONE", INT2NUM(XML_ERR_NONE));
rb_define_const(eXMLError, "WARNING", INT2NUM(XML_ERR_WARNING));
rb_define_const(eXMLError, "ERROR", INT2NUM(XML_ERR_ERROR));
rb_define_const(eXMLError, "FATAL", INT2NUM(XML_ERR_FATAL));
/* xml error domains */
rb_define_const(eXMLError, "NO_ERROR", INT2NUM(XML_FROM_NONE));
rb_define_const(eXMLError, "PARSER", INT2NUM(XML_FROM_PARSER));
rb_define_const(eXMLError, "TREE", INT2NUM(XML_FROM_TREE));
rb_define_const(eXMLError, "NAMESPACE", INT2NUM(XML_FROM_NAMESPACE));
rb_define_const(eXMLError, "DTD", INT2NUM(XML_FROM_DTD));
rb_define_const(eXMLError, "HTML", INT2NUM(XML_FROM_HTML));
rb_define_const(eXMLError, "MEMORY", INT2NUM(XML_FROM_MEMORY));
rb_define_const(eXMLError, "OUTPUT", INT2NUM(XML_FROM_OUTPUT));
rb_define_const(eXMLError, "IO", INT2NUM(XML_FROM_IO));
rb_define_const(eXMLError, "FTP", INT2NUM(XML_FROM_FTP));
rb_define_const(eXMLError, "HTTP", INT2NUM(XML_FROM_HTTP));
rb_define_const(eXMLError, "XINCLUDE", INT2NUM(XML_FROM_XINCLUDE));
rb_define_const(eXMLError, "XPATH", INT2NUM(XML_FROM_XPATH));
rb_define_const(eXMLError, "XPOINTER", INT2NUM(XML_FROM_XPOINTER));
rb_define_const(eXMLError, "REGEXP", INT2NUM(XML_FROM_REGEXP));
rb_define_const(eXMLError, "DATATYPE", INT2NUM(XML_FROM_DATATYPE));
rb_define_const(eXMLError, "SCHEMASP", INT2NUM(XML_FROM_SCHEMASP));
rb_define_const(eXMLError, "SCHEMASV", INT2NUM(XML_FROM_SCHEMASV));
rb_define_const(eXMLError, "RELAXNGP", INT2NUM(XML_FROM_RELAXNGP));
rb_define_const(eXMLError, "RELAXNGV", INT2NUM(XML_FROM_RELAXNGV));
rb_define_const(eXMLError, "CATALOG", INT2NUM(XML_FROM_CATALOG));
rb_define_const(eXMLError, "C14N", INT2NUM(XML_FROM_C14N));
rb_define_const(eXMLError, "XSLT", INT2NUM(XML_FROM_XSLT));
rb_define_const(eXMLError, "VALID", INT2NUM(XML_FROM_VALID));
rb_define_const(eXMLError, "CHECK", INT2NUM(XML_FROM_CHECK));
rb_define_const(eXMLError, "WRITER", INT2NUM(XML_FROM_WRITER));
#if LIBXML_VERSION >= 20621
rb_define_const(eXMLError, "MODULE", INT2NUM(XML_FROM_MODULE));
#endif
#if LIBXML_VERSION >= 20632
rb_define_const(eXMLError, "I18N", INT2NUM(XML_FROM_I18N));
rb_define_const(eXMLError, "SCHEMATRONV", INT2NUM(XML_FROM_SCHEMATRONV));
#endif
/* errors */
rb_define_const(eXMLError, "OK", INT2NUM(XML_ERR_OK));
rb_define_const(eXMLError, "INTERNAL_ERROR", INT2NUM(XML_ERR_INTERNAL_ERROR));
rb_define_const(