MOBI_RET mobi_build_opf(MOBIRawml *rawml, const MOBIData *m) {
    debug_print("Reconstructing opf%s", "\n");
    /* initialize libXML2 */
    LIBXML_TEST_VERSION
    /* initialize OPF structure */
    OPF opf = {
        .metadata = NULL,
        .manifest = NULL,
        .guide = NULL,
        .spine = NULL
    };
    MOBI_RET ret = mobi_build_opf_metadata(&opf, m, rawml);
    if (ret != MOBI_SUCCESS) {
        mobi_free_opf(&opf);
        return ret;
    }
    mobi_build_ncx(rawml, &opf);
    if (rawml->guide) {
        ret = mobi_build_opf_guide(&opf, rawml);
        if (ret != MOBI_SUCCESS) {
            mobi_free_opf(&opf);
            return ret;
        }
    }

    /* build OPF xml document */
    int xml_ret;
    const xmlChar * OPFNamespace = BAD_CAST "http://www.idpf.org/2007/opf";
    const xmlChar * DCNamespace = BAD_CAST "http://purl.org/dc/elements/1.1/";
    xmlBufferPtr buf = xmlBufferCreate();
    if (buf == NULL) {
        mobi_free_opf(&opf);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    xmlTextWriterPtr writer = xmlNewTextWriterMemory(buf, 0);
    if (writer == NULL) {
        xmlBufferFree(buf);
        mobi_free_opf(&opf);
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    xmlTextWriterSetIndent(writer, 1);
    xml_ret = xmlTextWriterStartDocument(writer, NULL, NULL, NULL);
    if (xml_ret < 0) { goto cleanup; }
    /* <package/> */
    xml_ret = xmlTextWriterStartElementNS(writer, NULL, BAD_CAST "package", OPFNamespace);
    if (xml_ret < 0) { goto cleanup; }
    xml_ret = xmlTextWriterWriteAttribute(writer, BAD_CAST "version", BAD_CAST "2.0");
    if (xml_ret < 0) { goto cleanup; }
    xml_ret = xmlTextWriterWriteAttribute(writer, BAD_CAST "unique-identifier", BAD_CAST "uid");
    if (xml_ret < 0) { goto cleanup; }
    /* <metadata /> */
    xml_ret = xmlTextWriterStartElementNS(writer, NULL, BAD_CAST "metadata", NULL);
    if (xml_ret < 0) { goto cleanup; }
    /* <dc-metadata/> */
    //xml_ret = xmlTextWriterStartElementNS(writer, NULL, BAD_CAST "dc-metadata", NULL);
    //if (xml_ret < 0) { goto cleanup; }
    xml_ret = xmlTextWriterWriteAttributeNS(writer, BAD_CAST "xmlns", BAD_CAST "opf", NULL, OPFNamespace);
    if (xml_ret < 0) { goto cleanup; }
    xml_ret = xmlTextWriterWriteAttributeNS(writer, BAD_CAST "xmlns", BAD_CAST "dc", NULL, DCNamespace);
    if (xml_ret < 0) { goto cleanup; }
    /* Dublin Core elements */
    OPFdcmeta *dc_meta = opf.metadata->dc_meta;
    ret = mobi_xml_write_dcmeta(writer, "title", (const char **) dc_meta->title);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "description", (const char **) dc_meta->description);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "language", (const char **) dc_meta->language);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "publisher", (const char **) dc_meta->publisher);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "rights", (const char **) dc_meta->rights);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "source", (const char **) dc_meta->source);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta(writer, "type", (const char **) dc_meta->type);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta_identifier(writer, (const OPFidentifier **) dc_meta->identifier);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta_creator(writer, (const OPFcreator **) dc_meta->creator, "creator");
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta_creator(writer, (const OPFcreator **) dc_meta->contributor, "contributor");
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta_subject(writer, (const OPFsubject **) dc_meta->subject);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_dcmeta_date(writer, (const OPFdate **) dc_meta->date);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    //xml_ret = xmlTextWriterEndElement(writer);
    //if (xml_ret < 0) { goto cleanup; }
    /* <x-metadata/> */
    //xml_ret = xmlTextWriterStartElement(writer, BAD_CAST "x-metadata");
    //if (xml_ret < 0) { goto cleanup; }
    OPFxmeta *x_meta = opf.metadata->x_meta;
    /* custom elements */
    ret = mobi_xml_write_xmeta_srp(writer, (const OPFsrp **) x_meta->srp);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "adult", (const char **) x_meta->adult);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "DefaultLookupIndex", (const char **) x_meta->default_lookup_index);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "DictionaryVeryShortName", (const char **) x_meta->dict_short_name);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "DictionaryInLanguage", (const char **) x_meta->dictionary_in_lang);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "DictionaryOutLanguage", (const char **) x_meta->dictionary_out_lang);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "EmbeddedCover", (const char **) x_meta->embedded_cover);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "imprint", (const char **) x_meta->imprint);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    ret = mobi_xml_write_xmeta(writer, "review", (const char **) x_meta->review);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    /* <meta/> */
    ret = mobi_xml_write_opfmeta(writer, (const OPFmeta **) opf.metadata->meta);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    //xml_ret = xmlTextWriterEndElement(writer);
    //if (xml_ret < 0) { goto cleanup; }
    xml_ret = xmlTextWriterEndElement(writer);
    if (xml_ret < 0) { goto cleanup; }
    /* <manifest/> */
    xml_ret = xmlTextWriterStartElement(writer, BAD_CAST "manifest");
    if (xml_ret < 0) { goto cleanup; }
    ret = mobi_xml_write_manifest(writer, rawml);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    xml_ret = xmlTextWriterEndElement(writer);
    if (xml_ret < 0) { goto cleanup; }
    /* <spine/> */
    ret = mobi_xml_write_spine(writer, rawml);
    if (ret != MOBI_SUCCESS) { goto cleanup; }
    /* <guide/> */
    if (opf.guide) {
        xml_ret = xmlTextWriterStartElement(writer, BAD_CAST "guide");
        if (xml_ret < 0) { goto cleanup; }
        ret = mobi_xml_write_reference(writer, (const OPFreference **) opf.guide->reference);
        if (ret != MOBI_SUCCESS) { goto cleanup; }
        xml_ret = xmlTextWriterEndElement(writer);
        if (xml_ret < 0) { goto cleanup; }
    }
    xml_ret = xmlTextWriterEndDocument(writer);
    if (xml_ret < 0) { goto cleanup; }
    
    xmlFreeTextWriter(writer);
    const char *opf_xml = (const char *) buf->content;
    mobi_opf_add_to_rawml(opf_xml, rawml);
    xmlBufferFree(buf);
    mobi_free_opf(&opf);
    /* cleanup function for the XML library */
    xmlCleanupParser();
    return MOBI_SUCCESS;
    
cleanup:
    xmlFreeTextWriter(writer);
    xmlBufferFree(buf);
    mobi_free_opf(&opf);
    xmlCleanupParser();
    debug_print("%s\n", "XML writing failed");
    return MOBI_XML_ERR;
}