static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL,
		const char *ID, xmlParserCtxtPtr context)
{
	xmlParserInputPtr	ret			= NULL;
	const char			*resource	= NULL;
	zval 				*ctxzv, retval;
	zval				params[3];
	int					status;
	zend_fcall_info		*fci;

	fci = &LIBXML(entity_loader).fci;

	if (fci->size == 0) {
		/* no custom user-land callback set up; delegate to original loader */
		return _php_libxml_default_entity_loader(URL, ID, context);
	}

	if (ID != NULL) {
		ZVAL_STRING(&params[0], ID);
	} else {
		ZVAL_NULL(&params[0]);
	}
	if (URL != NULL) {
		ZVAL_STRING(&params[1], URL);
	} else {
		ZVAL_NULL(&params[1]);
	}
	ctxzv = &params[2];
	array_init_size(ctxzv, 4);

#define ADD_NULL_OR_STRING_KEY(memb) \
	if (context->memb == NULL) { \
		add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \
	} else { \
		add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \
				(char *)context->memb); \
	}

	ADD_NULL_OR_STRING_KEY(directory)
	ADD_NULL_OR_STRING_KEY(intSubName)
	ADD_NULL_OR_STRING_KEY(extSubURI)
	ADD_NULL_OR_STRING_KEY(extSubSystem)

#undef ADD_NULL_OR_STRING_KEY

	fci->retval	= &retval;
	fci->params	= params;
	fci->param_count = sizeof(params)/sizeof(*params);
	fci->no_separation	= 1;

	status = zend_call_function(fci, &LIBXML(entity_loader).fcc);
	if (status != SUCCESS || Z_ISUNDEF(retval)) {
		php_libxml_ctx_error(context,
				"Call to user entity loader callback '%s' has failed",
				Z_STRVAL(fci->function_name));
	} else {
		/*
		retval_ptr = *fci->retval_ptr_ptr;
		if (retval_ptr == NULL) {
			php_libxml_ctx_error(context,
					"Call to user entity loader callback '%s' has failed; "
					"probably it has thrown an exception",
					fci->function_name);
		} else */ if (Z_TYPE(retval) == IS_STRING) {
is_string:
			resource = Z_STRVAL(retval);
		} else if (Z_TYPE(retval) == IS_RESOURCE) {
			php_stream *stream;
			php_stream_from_zval_no_verify(stream, &retval);
			if (stream == NULL) {
				php_libxml_ctx_error(context,
						"The user entity loader callback '%s' has returned a "
						"resource, but it is not a stream",
						Z_STRVAL(fci->function_name));
			} else {
				/* TODO: allow storing the encoding in the stream context? */
				xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
				xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc);
				if (pib == NULL) {
					php_libxml_ctx_error(context, "Could not allocate parser "
							"input buffer");
				} else {
					/* make stream not being closed when the zval is freed */
					GC_ADDREF(stream->res);
					pib->context = stream;
					pib->readcallback = php_libxml_streams_IO_read;
					pib->closecallback = php_libxml_streams_IO_close;

					ret = xmlNewIOInputStream(context, pib, enc);
					if (ret == NULL) {
						xmlFreeParserInputBuffer(pib);
					}
				}
			}
		} else if (Z_TYPE(retval) != IS_NULL) {
			/* retval not string nor resource nor null; convert to string */
			convert_to_string(&retval);
			goto is_string;
		} /* else is null; don't try anything */
	}

	if (ret == NULL) {
		if (resource == NULL) {
			if (ID == NULL) {
				ID = "NULL";
			}
			php_libxml_ctx_error(context,
					"Failed to load external entity \"%s\"\n", ID);
		} else {
			/* we got the resource in the form of a string; open it */
			ret = xmlNewInputFromFile(context, resource);
		}
	}

	zval_ptr_dtor(&params[0]);
	zval_ptr_dtor(&params[1]);
	zval_ptr_dtor(&params[2]);
	zval_ptr_dtor(&retval);
	return ret;
}
static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL,
		const char *ID, xmlParserCtxtPtr context)
{
	xmlParserInputPtr	ret			= NULL;
	const char			*resource	= NULL;
	zval 				*ctxzv, retval;
	zval				params[3];
	int					status;
	zend_fcall_info		*fci;

	fci = &LIBXML(entity_loader).fci;

	if (fci->size == 0) {
		 
		return _php_libxml_default_entity_loader(URL, ID, context);
	}

	if (ID != NULL) {
		ZVAL_STRING(&params[0], ID);
	} else {
		ZVAL_NULL(&params[0]);
	}
	if (URL != NULL) {
		ZVAL_STRING(&params[1], URL);
	} else {
		ZVAL_NULL(&params[1]);
	}
	ctxzv = &params[2];
	array_init_size(ctxzv, 4);

#define ADD_NULL_OR_STRING_KEY(memb) \
	if (context->memb == NULL) { \
		add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \
	} else { \
		add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \
				(char *)context->memb); \
	}

	ADD_NULL_OR_STRING_KEY(directory)
	ADD_NULL_OR_STRING_KEY(intSubName)
	ADD_NULL_OR_STRING_KEY(extSubURI)
	ADD_NULL_OR_STRING_KEY(extSubSystem)

#undef ADD_NULL_OR_STRING_KEY

	fci->retval	= &retval;
	fci->params	= params;
	fci->param_count = sizeof(params)/sizeof(*params);
	fci->no_separation	= 1;

	status = zend_call_function(fci, &LIBXML(entity_loader).fcc);
	if (status != SUCCESS || Z_ISUNDEF(retval)) {
		php_libxml_ctx_error(context,
				"Call to user entity loader callback '%s' has failed",
				Z_STRVAL(fci->function_name));
	} else {
		  if (Z_TYPE(retval) == IS_STRING) {
is_string:
			resource = Z_STRVAL(retval);
		} else if (Z_TYPE(retval) == IS_RESOURCE) {
			php_stream *stream;
			php_stream_from_zval_no_verify(stream, &retval);
			if (stream == NULL) {
				php_libxml_ctx_error(context,
						"The user entity loader callback '%s' has returned a "
						"resource, but it is not a stream",
						Z_STRVAL(fci->function_name));
			} else {
				 
				xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
				xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc);
				if (pib == NULL) {
					php_libxml_ctx_error(context, "Could not allocate parser "
							"input buffer");
				} else {
					 
					GC_ADDREF(stream->res);
					pib->context = stream;
					pib->readcallback = php_libxml_streams_IO_read;
					pib->closecallback = php_libxml_streams_IO_close;

					ret = xmlNewIOInputStream(context, pib, enc);
					if (ret == NULL) {
						xmlFreeParserInputBuffer(pib);
					}
				}
			}
		} else if (Z_TYPE(retval) != IS_NULL) {
			 
			convert_to_string(&retval);
			goto is_string;
		}  
	}

	if (ret == NULL) {
		if (resource == NULL) {
			if (ID == NULL) {
				ID = "NULL";
			}
			php_libxml_ctx_error(context,
					"Failed to load external entity \"%s\"\n", ID);
		} else {
			 
			ret = xmlNewInputFromFile(context, resource);
		}
	}

	zval_ptr_dtor(&params[0]);
	zval_ptr_dtor(&params[1]);
	zval_ptr_dtor(&params[2]);
	zval_ptr_dtor(&retval);
	return ret;
}