// igraph_read_graph_graphml [c]
igraph_error_t igraph_read_graph_graphml(igraph_t *graph, FILE *instream, igraph_integer_t index) {

#if HAVE_LIBXML == 1
    xmlParserCtxtPtr ctxt;
    xmlGenericErrorFunc libxml_old_generic_error_handler;
    void* libxml_old_generic_error_context;
    xmlStructuredErrorFunc libxml_old_structured_error_handler;
    void* libxml_old_structured_error_context;
    xmlDocPtr doc;

    struct igraph_i_graphml_parser_state state;
    int res;
    char buffer[4096];
    igraph_bool_t parsing_successful;
    char* error_message;

    if (index < 0) {
        IGRAPH_ERROR("Graph index must be non-negative.", IGRAPH_EINVAL);
    }

    xmlInitParser();

    IGRAPH_CHECK(igraph_i_graphml_parser_state_init(&state, graph, index));
    IGRAPH_FINALLY(igraph_i_graphml_parser_state_destroy, &state);

    /* Create a progressive parser context and use the first 4K to detect the
     * encoding */
    res = (int) fread(buffer, 1, sizeof(buffer), instream);
    if (res < (int) sizeof buffer && !feof(instream)) {
        IGRAPH_ERROR("IO error while reading GraphML data.", IGRAPH_EFILE);
    }

    /* Retrieve the current libxml2 error handlers and temporarily replace them
     * with ones that do not print anything to stdout/stderr */
    libxml_old_generic_error_handler = xmlGenericError;
    libxml_old_generic_error_context = xmlGenericErrorContext;
    libxml_old_structured_error_handler = xmlStructuredError;
    libxml_old_structured_error_context = xmlStructuredErrorContext;
    xmlSetGenericErrorFunc(&state, &igraph_i_libxml_generic_error_handler);
    xmlSetStructuredErrorFunc(&state, &igraph_i_libxml_structured_error_handler);

    /* Okay, parsing will start now. The parser might do things that eventually
     * trigger the igraph error handler, but we want the parser state to
     * survive whatever happens here. So, we put a barrier on the FINALLY stack
     * that prevents IGRAPH_ERROR() from freeing the parser state, and then we
     * do this ourselves when needed */
    IGRAPH_FINALLY_ENTER();
    {
        ctxt = xmlCreatePushParserCtxt(&igraph_i_graphml_sax_handler,
                                       &state,
                                       buffer,
                                       res,
                                       NULL);
        if (ctxt) {
            if (xmlCtxtUseOptions(ctxt,
                                  XML_PARSE_NOENT | XML_PARSE_NOBLANKS |
                                  XML_PARSE_NONET | XML_PARSE_NSCLEAN |
                                  XML_PARSE_NOCDATA | XML_PARSE_HUGE
                                  )) {
                xmlFreeParserCtxt(ctxt);
                ctxt = NULL;
            }
        }

        /* Do the parsing */
        if (ctxt) {
            while ((res = (int) fread(buffer, 1, sizeof buffer, instream)) > 0) {
                xmlParseChunk(ctxt, buffer, res, 0);
                if (!state.successful) {
                    break;
                }
            }
            xmlParseChunk(ctxt, buffer, res, 1);
        }
    }
    IGRAPH_FINALLY_EXIT();

    /* Restore error handlers */
    xmlSetGenericErrorFunc(libxml_old_generic_error_context, libxml_old_generic_error_handler);
    xmlSetStructuredErrorFunc(libxml_old_structured_error_context, libxml_old_structured_error_handler);

    /* Free the context */
    if (ctxt) {
        doc = ctxt->myDoc;
        xmlFreeParserCtxt(ctxt);
        if (doc) {
            /* In theory this should not be necessary, but it looks like certain malformed
             * GraphML files leave a partially-parsed doc in memory */
            xmlFreeDoc(doc);
        }
    } else {
        /* We could not create the context earlier so no parsing was done */
        IGRAPH_ERROR("Cannot create XML parser context.", IGRAPH_FAILURE);
    }

    /* Extract the error message from the parser state (if any), and make a
     * copy so we can safely destroy the parser state before triggering the
     * error */
    parsing_successful = state.successful;
    error_message = parsing_successful || state.error_message == NULL ? NULL : strdup(state.error_message);

    /* ...and we can also put the error message pointer on the FINALLY stack */
    if (error_message != NULL) {
        IGRAPH_FINALLY(free, error_message);
    }

    /* Trigger the stored error if needed */
    if (!parsing_successful) {
        if (error_message != NULL) {
            size_t len = strlen(error_message);
            if (error_message[len-1] == '\n') {
                error_message[len-1] = '\0';
            }
            IGRAPH_ERROR(error_message, IGRAPH_PARSEERROR);
        } else {
            IGRAPH_ERROR("Malformed GraphML file.", IGRAPH_PARSEERROR);
        }
    }

    /* Did we actually manage to reach the graph to be parsed, given its index?
     * If not, that's an error as well. */
    if (state.index >= 0) {
        IGRAPH_ERROR("Graph index was too large.", IGRAPH_EINVAL);
    }

    /* Okay, everything seems good. We can now take the parser state and
     * construct our graph from the data gathered during the parsing */
    IGRAPH_CHECK(igraph_i_graphml_parser_state_finish_parsing(&state));

    igraph_i_graphml_parser_state_destroy(&state);
    IGRAPH_FINALLY_CLEAN(1);

    return IGRAPH_SUCCESS;
#else
    IGRAPH_UNUSED(graph);
    IGRAPH_UNUSED(instream);
    IGRAPH_UNUSED(index);

    IGRAPH_ERROR("GraphML support is disabled.", IGRAPH_UNIMPLEMENTED);
#endif
}