MOBI_RET mobi_reconstruct_links_kf7(const MOBIRawml *rawml) {
    MOBIResult result;
    MOBIArray *links = array_init(25);
    if (links == NULL) {
        debug_print("%s\n", "Memory allocation failed");
        return MOBI_MALLOC_FAILED;
    }
    MOBIPart *part = rawml->markup;
    /* get array of link target offsets */
    MOBI_RET ret = mobi_get_filepos_array(links, part);
    if (ret != MOBI_SUCCESS) {
        array_free(links);
        return ret;
    }
    ret = mobi_get_ncx_filepos_array(links, rawml);
    if (ret != MOBI_SUCCESS) {
        array_free(links);
        return ret;
    }
    array_sort(links, true);
    unsigned char *data_in = part->data;
    MOBIFragment *first = NULL;
    MOBIFragment *curr = NULL;
    size_t new_size = 0;
    /* build MOBIResult list */
    result.start = part->data;
    const unsigned char *data_end = part->data + part->size - 1;
    while (true) {
        mobi_search_links_kf7(&result, result.start, data_end);
        if (result.start == NULL) {
            break;
        }
        char *attribute = (char *) result.value;
        unsigned char *data_cur = result.start;
        result.start = result.end;
        char link[MOBI_ATTRVALUE_MAXSIZE + 1];
        const char *numbers = "0123456789";
        char *value = strpbrk(attribute, numbers);
        if (value == NULL) {
            debug_print("Unknown link target: %s\n", attribute);
            continue;
        }
        size_t target;
        switch (attribute[0]) {
            case 'f':
                /* filepos=0000000000 */
                /* replace link with href="#0000000000" */
                target = strtoul(value, NULL, 10);
                snprintf(link, MOBI_ATTRVALUE_MAXSIZE + 1, "href=\"#%010u\"", (uint32_t)target);
                break;
            case 'h':
            case 'l':
                data_cur += 2;
                /* falls through */
            case 'r':
                /* (hi|lo)recindex="00000" */
                /* replace link with src="resource00000.ext" */
                target = strtoul(value, NULL, 10);
                if (target > 0) {
                    target--;
                }
                MOBIFiletype filetype = mobi_get_resourcetype_by_uid(rawml, target);
                MOBIFileMeta filemeta = mobi_get_filemeta_by_type(filetype);
                snprintf(link, MOBI_ATTRVALUE_MAXSIZE + 1, "src=\"resource%05u.%s\"", (uint32_t) target, filemeta.extension);
                break;
            default:
                debug_print("Unknown link target: %s\n", attribute);
                continue;
        }
        
        /* first chunk */
        if (data_cur < data_in) {
            mobi_list_del_all(first);
            array_free(links);
            return MOBI_DATA_CORRUPT;
        }
        size_t size = (size_t) (data_cur - data_in);
        size_t raw_offset = (size_t) (data_in - part->data);
        curr = mobi_list_add(curr, raw_offset, data_in, size, false);
        if (curr == NULL) {
            mobi_list_del_all(first);
            array_free(links);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        if (!first) { first = curr; }
        new_size += curr->size;
        /* second chunk */
        curr = mobi_list_add(curr, SIZE_MAX,
                             (unsigned char *) strdup(link),
                             strlen(link), true);
        if (curr == NULL) {
            mobi_list_del_all(first);
            array_free(links);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        new_size += curr->size;
        data_in = result.end;
    }
    if (first) {
        /* last chunk */
        if (part->data + part->size < data_in) {
            mobi_list_del_all(first);
            array_free(links);
            return MOBI_DATA_CORRUPT;
        }
        size_t size = (size_t) (part->data + part->size - data_in);
        size_t raw_offset = (size_t) (data_in - part->data);
        curr = mobi_list_add(curr, raw_offset, data_in, size, false);
        if (curr == NULL) {
            mobi_list_del_all(first);
            array_free(links);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        new_size += curr->size;
    } else {
        /* add whole part as one fragment */
        first = mobi_list_add(first, 0, part->data, part->size, false);
        if (first == NULL) {
            array_free(links);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        new_size += first->size;
    }
    /* insert chunks from links array */
    curr = first;
    size_t i = 0;
    while (i < links->size) {
        const uint32_t offset = links->data[i];
        char anchor[MOBI_ATTRVALUE_MAXSIZE + 1];
        snprintf(anchor, MOBI_ATTRVALUE_MAXSIZE + 1, "<a id=\"%010u\"></a>", offset);
        curr = mobi_list_insert(curr, SIZE_MAX,
                               (unsigned char *) strdup(anchor),
                                strlen(anchor), true, offset);
        if (curr == NULL) {
            mobi_list_del_all(first);
            array_free(links);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        new_size += curr->size;
        i++;
    }
    array_free(links);
    /* insert dictionary markup if present */
    if (rawml->orth) {
        ret = mobi_reconstruct_orth(rawml, first, &new_size);
        if (ret != MOBI_SUCCESS) {
            mobi_list_del_all(first);
            return ret;
        }
    }
    if (first && first->next) {
        /* save */
        debug_print("Inserting links%s", "\n");
        unsigned char *new_data = malloc(new_size);
        if (new_data == NULL) {
            mobi_list_del_all(first);
            debug_print("%s\n", "Memory allocation failed");
            return MOBI_MALLOC_FAILED;
        }
        unsigned char *data_out = new_data;
        MOBIFragment *fragdata = first;
        while (fragdata) {
            memcpy(data_out, fragdata->fragment, fragdata->size);
            data_out += fragdata->size;
            fragdata = mobi_list_del(fragdata);
        }
        free(part->data);
        part->data = new_data;
        part->size = new_size;
    } else {
        mobi_list_del(first);
    }
    return MOBI_SUCCESS;
}