/**
 * @file xml.c
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @author Michal Vasko <mvasko@cesnet.cz>
 * @brief Generic XML parser implementation for libyang
 *
 * Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
 *
 * This source code is licensed under BSD 3-Clause License (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/BSD-3-Clause
 */

#define _GNU_SOURCE

#include "xml.h"

#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "compat.h"
#include "in_internal.h"
#include "ly_common.h"
#include "out_internal.h"
#include "tree.h"
#include "tree_schema_internal.h"

/* Move input p by s characters, if EOF log with lyxml_ctx c */
#define move_input(c, s) \
    ly_in_skip(c->in, s); \
    LY_CHECK_ERR_RET(!c->in->current[0], LOGVAL(c->ctx, LY_VCODE_EOF), LY_EVALID)

/* Ignore whitespaces in the input string p */
#define ign_xmlws(c) \
    while (is_xmlws(*(c)->in->current)) { \
        if (*(c)->in->current == '\n') {  \
            LY_IN_NEW_LINE((c)->in);      \
        }                                 \
        ly_in_skip(c->in, 1);             \
    }

static LY_ERR lyxml_next_attr_content(struct lyxml_ctx *xmlctx, const char **value, size_t *value_len, ly_bool *ws_only,
        ly_bool *dynamic);

/**
 * @brief Ignore and skip any characters until the delim of the size delim_len is read, including the delim
 *
 * @param[in] xmlctx XML parser context to provide input handler and libyang context
 * @param[in] in input handler to read the data, it is updated only in case the section is correctly terminated.
 * @param[in] delim Delimiter to detect end of the section.
 * @param[in] delim_len Length of the delimiter string to use.
 * @param[in] sectname Section name to refer in error message.
 */
LY_ERR
skip_section(struct lyxml_ctx *xmlctx, const char *delim, size_t delim_len, const char *sectname)
{
    size_t i;
    register const char *input, *a, *b;
    uint64_t parsed = 0, newlines = 0;

    for (input = xmlctx->in->current; *input; ++input, ++parsed) {
        if (*input != *delim) {
            if (*input == '\n') {
                ++newlines;
            }
            continue;
        }
        a = input;
        b = delim;
        for (i = 0; i < delim_len; ++i) {
            if (*a++ != *b++) {
                break;
            }
        }
        if (i == delim_len) {
            /* delim found */
            xmlctx->in->line += newlines;
            ly_in_skip(xmlctx->in, parsed + delim_len);
            return LY_SUCCESS;
        }
    }

    /* delim not found,
     * do not update input handler to refer to the beginning of the section in error message */
    LOGVAL(xmlctx->ctx, LY_VCODE_NTERM, sectname);
    return LY_EVALID;
}

/**
 * @brief Check/Get an XML identifier from the input string.
 *
 * The identifier must have at least one valid character complying the name start character constraints.
 * The identifier is terminated by the first character, which does not comply to the name character constraints.
 *
 * See https://www.w3.org/TR/xml-names/#NT-NCName
 *
 * @param[in] xmlctx XML context.
 * @param[out] start Pointer to the start of the identifier.
 * @param[out] end Pointer ot the end of the identifier.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_parse_identifier(struct lyxml_ctx *xmlctx, const char **start, const char **end)
{
    const char *s, *in;
    uint32_t c;
    uint32_t parsed;
    LY_ERR rc;

    in = s = xmlctx->in->current;

    /* check NameStartChar (minus colon) */
    LY_CHECK_ERR_RET(ly_getutf8(&in, &c, &parsed),
            LOGVAL(xmlctx->ctx, LY_VCODE_INCHAR, in[0]),
            LY_EVALID);
    LY_CHECK_ERR_RET(!is_xmlqnamestartchar(c),
            LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Identifier \"%s\" starts with an invalid character.", in - parsed),
            LY_EVALID);

    /* check rest of the identifier */
    do {
        /* move only successfully parsed bytes */
        ly_in_skip(xmlctx->in, parsed);

        rc = ly_getutf8(&in, &c, &parsed);
        LY_CHECK_ERR_RET(rc, LOGVAL(xmlctx->ctx, LY_VCODE_INCHAR, in[0]), LY_EVALID);
    } while (is_xmlqnamechar(c));

    *start = s;
    *end = xmlctx->in->current;
    return LY_SUCCESS;
}

/**
 * @brief Add namespace definition into XML context.
 *
 * Namespaces from a single element are supposed to be added sequentially together (not interleaved by a namespace from other
 * element). This mimic namespace visibility, since the namespace defined in element E is not visible from its parents or
 * siblings. On the other hand, namespace from a parent element can be redefined in a child element. This is also reflected
 * by lyxml_ns_get() which returns the most recent namespace definition for the given prefix.
 *
 * When leaving processing of a subtree of some element (after it is removed from xmlctx->elements), caller is supposed to call
 * lyxml_ns_rm() to remove all the namespaces defined in such an element from the context.
 *
 * @param[in] xmlctx XML context to work with.
 * @param[in] prefix Pointer to the namespace prefix. Can be NULL for default namespace.
 * @param[in] prefix_len Length of the prefix.
 * @param[in] uri Namespace URI (value) to store directly. Value is always spent.
 * @return LY_ERR values.
 */
LY_ERR
lyxml_ns_add(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, char *uri)
{
    LY_ERR rc = LY_SUCCESS;
    struct lyxml_ns *ns;
    uint32_t i;

    /* check for duplicates */
    if (xmlctx->ns.count) {
        i = xmlctx->ns.count;
        do {
            --i;
            ns = xmlctx->ns.objs[i];
            if (ns->depth < xmlctx->elements.count) {
                /* only namespaces of parents, no need to check further */
                break;
            } else if (prefix && ns->prefix && !ly_strncmp(ns->prefix, prefix, prefix_len)) {
                if (!strcmp(ns->uri, uri)) {
                    /* exact same prefix and namespace, ignore */
                    goto cleanup;
                }

                LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Duplicate XML NS prefix \"%s\" used for namespaces \"%s\" and \"%s\".",
                        ns->prefix, ns->uri, uri);
                rc = LY_EVALID;
                goto cleanup;
            } else if (!prefix && !ns->prefix) {
                if (!strcmp(ns->uri, uri)) {
                    /* exact same default namespace, ignore */
                    goto cleanup;
                }

                LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Duplicate default XML namespaces \"%s\" and \"%s\".", ns->uri, uri);
                rc = LY_EVALID;
                goto cleanup;
            }
        } while (i);
    }

    ns = malloc(sizeof *ns);
    LY_CHECK_ERR_GOTO(!ns, LOGMEM(xmlctx->ctx); rc = LY_EMEM, cleanup);

    /* we need to connect the depth of the element where the namespace is defined with the
     * namespace record to be able to maintain (remove) the record when the parser leaves
     * (to its sibling or back to the parent) the element where the namespace was defined */
    ns->depth = xmlctx->elements.count;

    ns->uri = uri;
    if (prefix) {
        ns->prefix = strndup(prefix, prefix_len);
        LY_CHECK_ERR_GOTO(!ns->prefix, LOGMEM(xmlctx->ctx); free(ns); rc = LY_EMEM, cleanup);
    } else {
        ns->prefix = NULL;
    }

    rc = ly_set_add(&xmlctx->ns, ns, 1, NULL);
    LY_CHECK_ERR_GOTO(rc, free(ns->prefix); free(ns), cleanup);

    /* successfully stored */
    uri = NULL;

cleanup:
    free(uri);
    return rc;
}

void
lyxml_ns_rm(struct lyxml_ctx *xmlctx)
{
    struct lyxml_ns *ns;
    uint32_t u;

    if (!xmlctx->ns.count) {
        return;
    }

    u = xmlctx->ns.count;
    do {
        --u;
        ns = (struct lyxml_ns *)xmlctx->ns.objs[u];

        if (ns->depth != xmlctx->elements.count + 1) {
            /* we are done, the namespaces from a single element are supposed to be together */
            break;
        }

        /* remove the ns structure */
        free(ns->prefix);
        free(ns->uri);
        free(ns);
        --xmlctx->ns.count;
    } while (u);

    if (!xmlctx->ns.count) {
        /* cleanup the xmlctx's namespaces storage */
        ly_set_erase(&xmlctx->ns, NULL);
    }
}

const struct lyxml_ns *
lyxml_ns_get(const struct ly_set *ns_set, const char *prefix, size_t prefix_len)
{
    struct lyxml_ns *ns;
    uint32_t u;

    if (!ns_set->count) {
        return NULL;
    }

    u = ns_set->count;
    do {
        --u;
        ns = (struct lyxml_ns *)ns_set->objs[u];

        if (prefix && prefix_len) {
            if (ns->prefix && !ly_strncmp(ns->prefix, prefix, prefix_len)) {
                return ns;
            }
        } else if (!ns->prefix) {
            /* default namespace */
            return ns;
        }
    } while (u);

    return NULL;
}

/**
 * @brief Skip in the input until EOF or just after the opening tag.
 * Handles special XML constructs (comment, cdata, doctype).
 *
 * @param[in] xmlctx XML context to use.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_skip_until_end_or_after_otag(struct lyxml_ctx *xmlctx)
{
    const struct ly_ctx *ctx = xmlctx->ctx; /* shortcut */
    const char *endtag, *sectname;
    size_t endtag_len;

    while (1) {
        ign_xmlws(xmlctx);

        if (xmlctx->in->current[0] == '\0') {
            /* EOF */
            if (xmlctx->elements.count) {
                LOGVAL(ctx, LY_VCODE_EOF);
                return LY_EVALID;
            }
            return LY_SUCCESS;
        } else if (xmlctx->in->current[0] != '<') {
            LOGVAL(ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current), xmlctx->in->current,
                    "element tag start ('<')");
            return LY_EVALID;
        }
        move_input(xmlctx, 1);

        if (xmlctx->in->current[0] == '!') {
            move_input(xmlctx, 1);
            /* sections to ignore */
            if (!strncmp(xmlctx->in->current, "--", 2)) {
                /* comment */
                move_input(xmlctx, 2);
                sectname = "Comment";
                endtag = "-->";
                endtag_len = ly_strlen_const("-->");
            } else if (!strncmp(xmlctx->in->current, "DOCTYPE", ly_strlen_const("DOCTYPE"))) {
                /* Document type declaration - not supported */
                LOGVAL(ctx, LY_VCODE_NSUPP, "Document Type Declaration");
                return LY_EVALID;
            } else {
                LOGVAL(ctx, LYVE_SYNTAX, "Unknown XML section \"%.20s\".", &xmlctx->in->current[-2]);
                return LY_EVALID;
            }
            LY_CHECK_RET(skip_section(xmlctx, endtag, endtag_len, sectname));
        } else if (xmlctx->in->current[0] == '?') {
            LY_CHECK_RET(skip_section(xmlctx, "?>", 2, "Declaration"));
        } else {
            /* other non-WS character */
            break;
        }
    }

    return LY_SUCCESS;
}

/**
 * @brief Parse QName.
 *
 * @param[in] xmlctx XML context to use.
 * @param[out] prefix Parsed prefix, may be NULL.
 * @param[out] prefix_len Length of @p prefix.
 * @param[out] name Parsed name.
 * @param[out] name_len Length of @p name.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_parse_qname(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len)
{
    const char *start, *end;

    *prefix = NULL;
    *prefix_len = 0;

    LY_CHECK_RET(lyxml_parse_identifier(xmlctx, &start, &end));
    if (end[0] == ':') {
        /* we have prefixed identifier */
        *prefix = start;
        *prefix_len = end - start;

        move_input(xmlctx, 1);
        LY_CHECK_RET(lyxml_parse_identifier(xmlctx, &start, &end));
    }

    *name = start;
    *name_len = end - start;
    return LY_SUCCESS;
}

/**
 * @brief Prepare buffer for new data.
 *
 * @param[in] ctx Context for logging.
 * @param[in,out] in XML input data.
 * @param[in,out] offset Current offset in @p in.
 * @param[in] need_space Needed additional free space that is allocated.
 * @param[in,out] buf Dynamic buffer.
 * @param[in,out] len Current @p buf length (used characters).
 * @param[in,out] size Current @p buf size (allocated characters).
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_parse_value_use_buf(const struct ly_ctx *ctx, const char **in, size_t *offset, size_t need_space, char **buf,
        size_t *len, size_t *size)
{
#define BUFSIZE 24
#define BUFSIZE_STEP 128

    if (!*buf) {
        /* prepare output buffer */
        *buf = malloc(BUFSIZE);
        LY_CHECK_ERR_RET(!*buf, LOGMEM(ctx), LY_EMEM);
        *size = BUFSIZE;
    }

    /* allocate needed space */
    while (*len + *offset + need_space >= *size) {
        *buf = ly_realloc(*buf, *size + BUFSIZE_STEP);
        LY_CHECK_ERR_RET(!*buf, LOGMEM(ctx), LY_EMEM);
        *size += BUFSIZE_STEP;
    }

    if (*offset) {
        /* store what we have so far */
        memcpy(&(*buf)[*len], *in, *offset);
        *len += *offset;
        *in += *offset;
        *offset = 0;
    }

    return LY_SUCCESS;

#undef BUFSIZE
#undef BUFSIZE_STEP
}

/**
 * @brief Parse XML text content (value).
 *
 * @param[in] xmlctx XML context to use.
 * @param[in] endchar Expected character to mark value end.
 * @param[out] value Parsed value.
 * @param[out] length Length of @p value.
 * @param[out] ws_only Whether the value is empty/white-spaces only.
 * @param[out] dynamic Whether the value was dynamically allocated.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t *length, ly_bool *ws_only, ly_bool *dynamic)
{
    const struct ly_ctx *ctx = xmlctx->ctx; /* shortcut */
    const char *in = xmlctx->in->current, *start, *in_aux, *p;
    char *buf = NULL;
    size_t offset;   /* read offset in input buffer */
    size_t len;      /* length of the output string (write offset in output buffer) */
    size_t size = 0; /* size of the output buffer */
    uint32_t n, u;
    ly_bool ws = 1;

    assert(xmlctx);

    /* init */
    start = in;
    offset = len = 0;

    /* parse */
    while (in[offset]) {
        if (in[offset] == '&') {
            /* non WS */
            ws = 0;

            /* use buffer and allocate enough for the offset and next character,
             * we will need 4 bytes at most since we support only the predefined
             * (one-char) entities and character references */
            LY_CHECK_RET(lyxml_parse_value_use_buf(ctx, &in, &offset, 4, &buf, &len, &size));

            ++offset;
            if (in[offset] != '#') {
                /* entity reference - only predefined references are supported */
                if (!strncmp(&in[offset], "lt;", ly_strlen_const("lt;"))) {
                    buf[len++] = '<';
                    in += ly_strlen_const("&lt;");
                } else if (!strncmp(&in[offset], "gt;", ly_strlen_const("gt;"))) {
                    buf[len++] = '>';
                    in += ly_strlen_const("&gt;");
                } else if (!strncmp(&in[offset], "amp;", ly_strlen_const("amp;"))) {
                    buf[len++] = '&';
                    in += ly_strlen_const("&amp;");
                } else if (!strncmp(&in[offset], "apos;", ly_strlen_const("apos;"))) {
                    buf[len++] = '\'';
                    in += ly_strlen_const("&apos;");
                } else if (!strncmp(&in[offset], "quot;", ly_strlen_const("quot;"))) {
                    buf[len++] = '\"';
                    in += ly_strlen_const("&quot;");
                } else {
                    LOGVAL(ctx, LYVE_SYNTAX, "Entity reference \"%.*s\" not supported, only predefined references allowed.",
                            10, &in[offset - 1]);
                    goto error;
                }
                offset = 0;
            } else {
                p = &in[offset - 1];
                /* character reference */
                ++offset;
                if (isdigit(in[offset])) {
                    for (n = 0; isdigit(in[offset]); offset++) {
                        n = (LY_BASE_DEC * n) + (in[offset] - '0');
                    }
                } else if ((in[offset] == 'x') && isxdigit(in[offset + 1])) {
                    for (n = 0, ++offset; isxdigit(in[offset]); offset++) {
                        if (isdigit(in[offset])) {
                            u = (in[offset] - '0');
                        } else if (in[offset] > 'F') {
                            u = LY_BASE_DEC + (in[offset] - 'a');
                        } else {
                            u = LY_BASE_DEC + (in[offset] - 'A');
                        }
                        n = (LY_BASE_HEX * n) + u;
                    }
                } else {
                    LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.12s\".", p);
                    goto error;

                }

                if (in[offset] != ';') {
                    LOGVAL(ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(&in[offset]), &in[offset], ";");
                    goto error;
                }
                ++offset;
                if (ly_pututf8(&buf[len], n, &u)) {
                    LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.12s\" (0x%08" PRIx32 ").", p, n);
                    goto error;
                }
                len += u;
                in += offset;
                offset = 0;
            }
        } else if (!strncmp(in + offset, "<![CDATA[", ly_strlen_const("<![CDATA["))) {
            /* CDATA, find the end */
            in_aux = strstr(in + offset + ly_strlen_const("<![CDATA["), "]]>");
            if (!in_aux) {
                LOGVAL(xmlctx->ctx, LY_VCODE_NTERM, "CDATA");
                goto error;
            }
            u = in_aux - (in + offset + ly_strlen_const("<![CDATA["));

            /* use buffer, allocate enough for the whole CDATA */
            LY_CHECK_RET(lyxml_parse_value_use_buf(ctx, &in, &offset, u, &buf, &len, &size));

            /* skip CDATA tag */
            in += ly_strlen_const("<![CDATA[");
            assert(!offset);

            /* analyze CDATA for non WS and newline chars */
            for (n = 0; n < u; ++n) {
                if (in[n] == '\n') {
                    LY_IN_NEW_LINE(xmlctx->in);
                } else if (!is_xmlws(in[n])) {
                    ws = 0;
                }
            }

            /* copy CDATA */
            memcpy(buf + len, in, u);
            len += u;

            /* move input skipping the end tag */
            in += u + ly_strlen_const("]]>");
        } else if (in[offset] == endchar) {
            /* end of string */
            if (buf) {
                /* realloc exact size string */
                buf = ly_realloc(buf, len + offset + 1);
                LY_CHECK_ERR_RET(!buf, LOGMEM(ctx), LY_EMEM);
                size = len + offset + 1;
                if (offset) {
                    memcpy(&buf[len], in, offset);
                }

                /* set terminating NULL byte */
                buf[len + offset] = '\0';
            }
            len += offset;
            in += offset;
            goto success;
        } else {
            if (!is_xmlws(in[offset])) {
                /* non WS */
                ws = 0;
            }

            /* log lines */
            if (in[offset] == '\n') {
                LY_IN_NEW_LINE(xmlctx->in);
            }

            /* continue */
            in_aux = &in[offset];
            LY_CHECK_ERR_GOTO(ly_getutf8(&in_aux, &n, &u),
                    LOGVAL(ctx, LY_VCODE_INCHAR, in[offset]), error);
            offset += u;
        }
    }

    /* EOF reached before endchar */
    LOGVAL(ctx, LY_VCODE_EOF);

error:
    free(buf);
    return LY_EVALID;

success:
    if (buf) {
        *value = buf;
        *dynamic = 1;
    } else {
        *value = (char *)start;
        *dynamic = 0;
    }
    *length = len;
    *ws_only = ws;

    xmlctx->in->current = in;
    return LY_SUCCESS;
}

/**
 * @brief Parse XML closing element and match it to a stored starting element.
 *
 * @param[in] xmlctx XML context to use.
 * @param[in] prefix Expected closing element prefix.
 * @param[in] prefix_len Length of @p prefix.
 * @param[in] name Expected closing element name.
 * @param[in] name_len Length of @p name.
 * @param[in] empty Whether we are parsing a special "empty" element (with joined starting and closing tag) with no value.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_close_element(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, const char *name, size_t name_len,
        ly_bool empty)
{
    struct lyxml_elem *e;

    /* match opening and closing element tags */
    if (!xmlctx->elements.count) {
        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Stray closing element tag (\"%.*s\").",
                (int)name_len, name);
        return LY_EVALID;
    }

    e = (struct lyxml_elem *)xmlctx->elements.objs[xmlctx->elements.count - 1];
    if ((e->prefix_len != prefix_len) || (e->name_len != name_len) ||
            (prefix_len && strncmp(prefix, e->prefix, e->prefix_len)) || strncmp(name, e->name, e->name_len)) {
        LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Opening (\"%.*s%s%.*s\") and closing (\"%.*s%s%.*s\") elements tag mismatch.",
                (int)e->prefix_len, e->prefix ? e->prefix : "", e->prefix ? ":" : "", (int)e->name_len, e->name,
                (int)prefix_len, prefix ? prefix : "", prefix ? ":" : "", (int)name_len, name);
        return LY_EVALID;
    }

    /* opening and closing element tags matches, remove record from the opening tags list */
    ly_set_rm_index(&xmlctx->elements, xmlctx->elements.count - 1, free);

    /* remove also the namespaces connected with the element */
    lyxml_ns_rm(xmlctx);

    /* skip WS */
    ign_xmlws(xmlctx);

    /* special "<elem/>" element */
    if (empty && (xmlctx->in->current[0] == '/')) {
        move_input(xmlctx, 1);
    }

    /* parse closing tag */
    if (xmlctx->in->current[0] != '>') {
        LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current),
                xmlctx->in->current, "element tag termination ('>')");
        return LY_EVALID;
    }

    /* move after closing tag without checking for EOF */
    ly_in_skip(xmlctx->in, 1);

    return LY_SUCCESS;
}

/**
 * @brief Store parsed opening element and parse any included namespaces.
 *
 * @param[in] xmlctx XML context to use.
 * @param[in] prefix Parsed starting element prefix.
 * @param[in] prefix_len Length of @p prefix.
 * @param[in] name Parsed starting element name.
 * @param[in] name_len Length of @p name.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_open_element(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, const char *name, size_t name_len)
{
    LY_ERR ret = LY_SUCCESS;
    struct lyxml_elem *e;
    const char *prev_input;
    uint64_t prev_line;
    char *value;
    size_t value_len;
    ly_bool ws_only, dynamic, is_ns;
    uint32_t c, parsed;

    /* store element opening tag information */
    e = malloc(sizeof *e);
    LY_CHECK_ERR_RET(!e, LOGMEM(xmlctx->ctx), LY_EMEM);
    e->name = name;
    e->prefix = prefix;
    e->name_len = name_len;
    e->prefix_len = prefix_len;

    LY_CHECK_RET(ly_set_add(&xmlctx->elements, e, 1, NULL));
    if (xmlctx->elements.count > LY_MAX_BLOCK_DEPTH) {
        LOGERR(xmlctx->ctx, LY_EINVAL, "The maximum number of open elements has been exceeded.");
        return LY_EINVAL;
    }

    /* skip WS */
    ign_xmlws(xmlctx);

    /* parse and store all namespaces */
    prev_input = xmlctx->in->current;
    prev_line = xmlctx->in->line;
    is_ns = 1;
    while ((xmlctx->in->current[0] != '\0') && !(ret = ly_getutf8(&xmlctx->in->current, &c, &parsed))) {
        if (!is_xmlqnamestartchar(c)) {
            break;
        }
        xmlctx->in->current -= parsed;

        /* parse attribute name */
        LY_CHECK_GOTO(ret = lyxml_parse_qname(xmlctx, &prefix, &prefix_len, &name, &name_len), cleanup);

        /* parse the value */
        LY_CHECK_GOTO(ret = lyxml_next_attr_content(xmlctx, (const char **)&value, &value_len, &ws_only, &dynamic), cleanup);

        /* store every namespace */
        if ((prefix && !ly_strncmp("xmlns", prefix, prefix_len)) || (!prefix && !ly_strncmp("xmlns", name, name_len))) {
            ret = lyxml_ns_add(xmlctx, prefix ? name : NULL, prefix ? name_len : 0,
                    dynamic ? value : strndup(value, value_len));
            dynamic = 0;
            LY_CHECK_GOTO(ret, cleanup);
        } else {
            /* not a namespace */
            is_ns = 0;
        }
        if (dynamic) {
            free(value);
        }

        /* skip WS */
        ign_xmlws(xmlctx);

        if (is_ns) {
            /* we can actually skip all the namespaces as there is no reason to parse them again */
            prev_input = xmlctx->in->current;
            prev_line = xmlctx->in->line;
        }
    }

cleanup:
    if (!ret) {
        xmlctx->in->current = prev_input;
        xmlctx->in->line = prev_line;
    }
    return ret;
}

/**
 * @brief Move parser to the attribute content and parse it.
 *
 * @param[in] xmlctx XML context to use.
 * @param[out] value Parsed attribute value.
 * @param[out] value_len Length of @p value.
 * @param[out] ws_only Whether the value is empty/white-spaces only.
 * @param[out] dynamic Whether the value was dynamically allocated.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_next_attr_content(struct lyxml_ctx *xmlctx, const char **value, size_t *value_len, ly_bool *ws_only, ly_bool *dynamic)
{
    char quot;

    /* skip WS */
    ign_xmlws(xmlctx);

    /* skip '=' */
    if (xmlctx->in->current[0] == '\0') {
        LOGVAL(xmlctx->ctx, LY_VCODE_EOF);
        return LY_EVALID;
    } else if (xmlctx->in->current[0] != '=') {
        LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current),
                xmlctx->in->current, "'='");
        return LY_EVALID;
    }
    move_input(xmlctx, 1);

    /* skip WS */
    ign_xmlws(xmlctx);

    /* find quotes */
    if (xmlctx->in->current[0] == '\0') {
        LOGVAL(xmlctx->ctx, LY_VCODE_EOF);
        return LY_EVALID;
    } else if ((xmlctx->in->current[0] != '\'') && (xmlctx->in->current[0] != '\"')) {
        LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current),
                xmlctx->in->current, "either single or double quotation mark");
        return LY_EVALID;
    }

    /* remember quote */
    quot = xmlctx->in->current[0];
    move_input(xmlctx, 1);

    /* parse attribute value */
    LY_CHECK_RET(lyxml_parse_value(xmlctx, quot, (char **)value, value_len, ws_only, dynamic));

    /* move after ending quote (without checking for EOF) */
    ly_in_skip(xmlctx->in, 1);

    return LY_SUCCESS;
}

/**
 * @brief Move parser to the next attribute and parse it.
 *
 * @param[in] xmlctx XML context to use.
 * @param[out] prefix Parsed attribute prefix.
 * @param[out] prefix_len Length of @p prefix.
 * @param[out] name Parsed attribute name.
 * @param[out] name_len Length of @p name.
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_next_attribute(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len)
{
    const char *in;
    char *value;
    uint32_t c, parsed;
    size_t value_len;
    ly_bool ws_only, dynamic;

    /* skip WS */
    ign_xmlws(xmlctx);

    /* parse only possible attributes */
    while ((xmlctx->in->current[0] != '>') && (xmlctx->in->current[0] != '/')) {
        in = xmlctx->in->current;
        if (in[0] == '\0') {
            LOGVAL(xmlctx->ctx, LY_VCODE_EOF);
            return LY_EVALID;
        } else if ((ly_getutf8(&in, &c, &parsed) || !is_xmlqnamestartchar(c))) {
            LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(in - parsed), in - parsed,
                    "element tag end ('>' or '/>') or an attribute");
            return LY_EVALID;
        }

        /* parse attribute name */
        LY_CHECK_RET(lyxml_parse_qname(xmlctx, prefix, prefix_len, name, name_len));

        if ((!*prefix || ly_strncmp("xmlns", *prefix, *prefix_len)) && (*prefix || ly_strncmp("xmlns", *name, *name_len))) {
            /* standard attribute */
            break;
        }

        /* namespace, skip it */
        LY_CHECK_RET(lyxml_next_attr_content(xmlctx, (const char **)&value, &value_len, &ws_only, &dynamic));
        if (dynamic) {
            free(value);
        }

        /* skip WS */
        ign_xmlws(xmlctx);
    }

    return LY_SUCCESS;
}

/**
 * @brief Move parser to the next element and parse it.
 *
 * @param[in] xmlctx XML context to use.
 * @param[out] prefix Parsed element prefix.
 * @param[out] prefix_len Length of @p prefix.
 * @param[out] name Parse element name.
 * @param[out] name_len Length of @p name.
 * @param[out] closing Flag if the element is closing (includes '/').
 * @return LY_ERR value.
 */
static LY_ERR
lyxml_next_element(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len,
        ly_bool *closing)
{
    /* skip WS until EOF or after opening tag '<' */
    LY_CHECK_RET(lyxml_skip_until_end_or_after_otag(xmlctx));
    if (xmlctx->in->current[0] == '\0') {
        /* set return values */
        *prefix = *name = NULL;
        *prefix_len = *name_len = 0;
        return LY_SUCCESS;
    }

    if (xmlctx->in->current[0] == '/') {
        move_input(xmlctx, 1);
        *closing = 1;
    } else {
        *closing = 0;
    }

    /* skip WS */
    ign_xmlws(xmlctx);

    /* parse element name */
    LY_CHECK_RET(lyxml_parse_qname(xmlctx, prefix, prefix_len, name, name_len));

    return LY_SUCCESS;
}

LY_ERR
lyxml_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyxml_ctx **xmlctx_p)
{
    LY_ERR ret = LY_SUCCESS;
    struct lyxml_ctx *xmlctx;
    ly_bool closing;

    /* new context */
    xmlctx = calloc(1, sizeof *xmlctx);
    LY_CHECK_ERR_RET(!xmlctx, LOGMEM(ctx), LY_EMEM);
    xmlctx->ctx = ctx;
    xmlctx->in = in;

    ly_log_location(NULL, NULL, NULL, in);

    /* parse next element, if any */
    LY_CHECK_GOTO(ret = lyxml_next_element(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name,
            &xmlctx->name_len, &closing), cleanup);

    if (xmlctx->in->current[0] == '\0') {
        /* update status */
        xmlctx->status = LYXML_END;
    } else if (closing) {
        LOGVAL(ctx, LYVE_SYNTAX, "Stray closing element tag (\"%.*s\").", (int)xmlctx->name_len, xmlctx->name);
        ret = LY_EVALID;
        goto cleanup;
    } else {
        /* open an element, also parses all enclosed namespaces */
        LY_CHECK_GOTO(ret = lyxml_open_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len), cleanup);

        /* update status */
        xmlctx->status = LYXML_ELEMENT;
    }

cleanup:
    if (ret) {
        lyxml_ctx_free(xmlctx);
    } else {
        *xmlctx_p = xmlctx;
    }
    return ret;
}

LY_ERR
lyxml_ctx_next(struct lyxml_ctx *xmlctx)
{
    LY_ERR ret = LY_SUCCESS;
    ly_bool closing;
    struct lyxml_elem *e;

    /* if the value was not used, free it */
    if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) {
        free((char *)xmlctx->value);
        xmlctx->value = NULL;
        xmlctx->dynamic = 0;
    }

    switch (xmlctx->status) {
    case LYXML_ELEM_CONTENT:
        /* content |</elem> */

        /* handle special case when empty content for "<elem/>" was returned */
        if (xmlctx->in->current[0] == '/') {
            assert(xmlctx->elements.count);
            e = (struct lyxml_elem *)xmlctx->elements.objs[xmlctx->elements.count - 1];

            /* close the element (parses closing tag) */
            ret = lyxml_close_element(xmlctx, e->prefix, e->prefix_len, e->name, e->name_len, 1);
            LY_CHECK_GOTO(ret, cleanup);

            /* update status */
            xmlctx->status = LYXML_ELEM_CLOSE;
            break;
        }
    /* fall through */
    case LYXML_ELEM_CLOSE:
        /* </elem>| <elem2>* */

        /* parse next element, if any */
        ret = lyxml_next_element(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name, &xmlctx->name_len, &closing);
        LY_CHECK_GOTO(ret, cleanup);

        if (xmlctx->in->current[0] == '\0') {
            /* update status */
            xmlctx->status = LYXML_END;
        } else if (closing) {
            /* close an element (parses also closing tag) */
            ret = lyxml_close_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len, 0);
            LY_CHECK_GOTO(ret, cleanup);

            /* update status */
            xmlctx->status = LYXML_ELEM_CLOSE;
        } else {
            /* open an element, also parses all enclosed namespaces */
            ret = lyxml_open_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len);
            LY_CHECK_GOTO(ret, cleanup);

            /* update status */
            xmlctx->status = LYXML_ELEMENT;
        }
        break;

    case LYXML_ELEMENT:
    /* <elem| attr='val'* > content  */
    case LYXML_ATTR_CONTENT:
        /* attr='val'| attr='val'* > content */

        /* parse attribute name, if any */
        ret = lyxml_next_attribute(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name, &xmlctx->name_len);
        LY_CHECK_GOTO(ret, cleanup);

        if (xmlctx->in->current[0] == '>') {
            /* no attributes but a closing tag */
            ly_in_skip(xmlctx->in, 1);
            if (!xmlctx->in->current[0]) {
                LOGVAL(xmlctx->ctx, LY_VCODE_EOF);
                ret = LY_EVALID;
                goto cleanup;
            }

            /* parse element content */
            ret = lyxml_parse_value(xmlctx, '<', (char **)&xmlctx->value, &xmlctx->value_len, &xmlctx->ws_only,
                    &xmlctx->dynamic);
            LY_CHECK_GOTO(ret, cleanup);

            if (!xmlctx->value_len) {
                /* empty value should by alocated staticaly, but check for in any case */
                if (xmlctx->dynamic) {
                    free((char *) xmlctx->value);
                }
                /* use empty value, easier to work with */
                xmlctx->value = "";
                xmlctx->dynamic = 0;
            }

            /* update status */
            xmlctx->status = LYXML_ELEM_CONTENT;
        } else if (xmlctx->in->current[0] == '/') {
            /* no content but we still return it */
            xmlctx->value = "";
            xmlctx->value_len = 0;
            xmlctx->ws_only = 1;
            xmlctx->dynamic = 0;

            /* update status */
            xmlctx->status = LYXML_ELEM_CONTENT;
        } else {
            /* update status */
            xmlctx->status = LYXML_ATTRIBUTE;
        }
        break;

    case LYXML_ATTRIBUTE:
        /* attr|='val' */

        /* skip formatting and parse value */
        ret = lyxml_next_attr_content(xmlctx, &xmlctx->value, &xmlctx->value_len, &xmlctx->ws_only, &xmlctx->dynamic);
        LY_CHECK_GOTO(ret, cleanup);

        /* update status */
        xmlctx->status = LYXML_ATTR_CONTENT;
        break;

    case LYXML_END:
        /* </elem>   |EOF */
        /* nothing to do */
        break;
    }

cleanup:
    if (ret) {
        /* invalidate context */
        xmlctx->status = LYXML_END;
    }
    return ret;
}

LY_ERR
lyxml_ctx_peek(struct lyxml_ctx *xmlctx, enum LYXML_PARSER_STATUS *next)
{
    LY_ERR ret = LY_SUCCESS;
    const char *prefix, *name, *prev_input;
    size_t prefix_len, name_len;
    ly_bool closing;

    prev_input = xmlctx->in->current;

    switch (xmlctx->status) {
    case LYXML_ELEM_CONTENT:
        if (xmlctx->in->current[0] == '/') {
            *next = LYXML_ELEM_CLOSE;
            break;
        }
    /* fall through */
    case LYXML_ELEM_CLOSE:
        /* parse next element, if any */
        ret = lyxml_next_element(xmlctx, &prefix, &prefix_len, &name, &name_len, &closing);
        LY_CHECK_GOTO(ret, cleanup);

        if (xmlctx->in->current[0] == '\0') {
            *next = LYXML_END;
        } else if (closing) {
            *next = LYXML_ELEM_CLOSE;
        } else {
            *next = LYXML_ELEMENT;
        }
        break;
    case LYXML_ELEMENT:
    case LYXML_ATTR_CONTENT:
        /* parse attribute name, if any */
        ret = lyxml_next_attribute(xmlctx, &prefix, &prefix_len, &name, &name_len);
        LY_CHECK_GOTO(ret, cleanup);

        if ((xmlctx->in->current[0] == '>') || (xmlctx->in->current[0] == '/')) {
            *next = LYXML_ELEM_CONTENT;
        } else {
            *next = LYXML_ATTRIBUTE;
        }
        break;
    case LYXML_ATTRIBUTE:
        *next = LYXML_ATTR_CONTENT;
        break;
    case LYXML_END:
        *next = LYXML_END;
        break;
    }

cleanup:
    xmlctx->in->current = prev_input;
    return ret;
}

/**
 * @brief Free all namespaces in XML context.
 *
 * @param[in] xmlctx XML context to use.
 */
static void
lyxml_ns_rm_all(struct lyxml_ctx *xmlctx)
{
    struct lyxml_ns *ns;
    uint32_t i;

    for (i = 0; i < xmlctx->ns.count; ++i) {
        ns = xmlctx->ns.objs[i];

        free(ns->prefix);
        free(ns->uri);
        free(ns);
    }
    ly_set_erase(&xmlctx->ns, NULL);
}

void
lyxml_ctx_free(struct lyxml_ctx *xmlctx)
{
    if (!xmlctx) {
        return;
    }

    ly_log_location_revert(0, 0, 0, 1);

    if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) {
        free((char *)xmlctx->value);
    }
    ly_set_erase(&xmlctx->elements, free);
    lyxml_ns_rm_all(xmlctx);
    free(xmlctx);
}

/**
 * @brief Duplicate an XML element.
 *
 * @param[in] elem Element to duplicate.
 * @return Element duplicate.
 * @return NULL on error.
 */
static struct lyxml_elem *
lyxml_elem_dup(const struct lyxml_elem *elem)
{
    struct lyxml_elem *dup;

    dup = malloc(sizeof *dup);
    LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL);

    memcpy(dup, elem, sizeof *dup);

    return dup;
}

/**
 * @brief Duplicate an XML namespace.
 *
 * @param[in] ns Namespace to duplicate.
 * @return Namespace duplicate.
 * @return NULL on error.
 */
static struct lyxml_ns *
lyxml_ns_dup(const struct lyxml_ns *ns)
{
    struct lyxml_ns *dup;

    dup = malloc(sizeof *dup);
    LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL);

    if (ns->prefix) {
        dup->prefix = strdup(ns->prefix);
        LY_CHECK_ERR_RET(!dup->prefix, LOGMEM(NULL); free(dup), NULL);
    } else {
        dup->prefix = NULL;
    }
    dup->uri = strdup(ns->uri);
    LY_CHECK_ERR_RET(!dup->uri, LOGMEM(NULL); free(dup->prefix); free(dup), NULL);
    dup->depth = ns->depth;

    return dup;
}

LY_ERR
lyxml_ctx_backup(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup)
{
    uint32_t i;

    /* first make shallow copy */
    memcpy(backup, xmlctx, sizeof *backup);

    if ((xmlctx->status == LYXML_ELEM_CONTENT) && xmlctx->dynamic) {
        /* it was backed up, do not free */
        xmlctx->dynamic = 0;
    }

    /* backup in */
    backup->b_current = xmlctx->in->current;
    backup->b_line = xmlctx->in->line;

    /* duplicate elements */
    backup->elements.objs = malloc(xmlctx->elements.size * sizeof(struct lyxml_elem));
    LY_CHECK_ERR_RET(!backup->elements.objs, LOGMEM(xmlctx->ctx), LY_EMEM);
    for (i = 0; i < xmlctx->elements.count; ++i) {
        backup->elements.objs[i] = lyxml_elem_dup(xmlctx->elements.objs[i]);
        LY_CHECK_RET(!backup->elements.objs[i], LY_EMEM);
    }

    /* duplicate ns */
    backup->ns.objs = malloc(xmlctx->ns.size * sizeof(struct lyxml_ns));
    LY_CHECK_ERR_RET(!backup->ns.objs, LOGMEM(xmlctx->ctx), LY_EMEM);
    for (i = 0; i < xmlctx->ns.count; ++i) {
        backup->ns.objs[i] = lyxml_ns_dup(xmlctx->ns.objs[i]);
        LY_CHECK_RET(!backup->ns.objs[i], LY_EMEM);
    }

    return LY_SUCCESS;
}

void
lyxml_ctx_restore(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup)
{
    if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) {
        /* free dynamic value */
        free((char *)xmlctx->value);
    }

    /* free elements */
    ly_set_erase(&xmlctx->elements, free);

    /* free ns */
    lyxml_ns_rm_all(xmlctx);

    /* restore in */
    xmlctx->in->current = backup->b_current;
    xmlctx->in->line = backup->b_line;
    backup->in = xmlctx->in;

    /* restore backup */
    memcpy(xmlctx, backup, sizeof *xmlctx);
}

LY_ERR
lyxml_dump_text(struct ly_out *out, const char *text, ly_bool attribute)
{
    LY_ERR ret;

    if (!text) {
        return 0;
    }

    for (uint64_t u = 0; text[u]; u++) {
        switch (text[u]) {
        case '&':
            ret = ly_print_(out, "&amp;");
            break;
        case '<':
            ret = ly_print_(out, "&lt;");
            break;
        case '>':
            /* not needed, just for readability */
            ret = ly_print_(out, "&gt;");
            break;
        case '"':
            if (attribute) {
                ret = ly_print_(out, "&quot;");
                break;
            }
        /* fall through */
        default:
            ret = ly_write_(out, &text[u], 1);
            break;
        }
        LY_CHECK_RET(ret);
    }

    return LY_SUCCESS;
}

LY_ERR
lyxml_value_compare(const struct ly_ctx *ctx1, const char *value1, void *val_prefix_data1,
        const struct ly_ctx *ctx2, const char *value2, void *val_prefix_data2)
{
    const char *value1_iter, *value2_iter;
    const char *value1_next, *value2_next;
    uint32_t value1_len, value2_len;
    ly_bool is_prefix1, is_prefix2;
    const struct lys_module *mod1, *mod2;
    LY_ERR ret;

    if (!value1 && !value2) {
        return LY_SUCCESS;
    }
    if ((value1 && !value2) || (!value1 && value2)) {
        return LY_ENOT;
    }

    if (!ctx2) {
        ctx2 = ctx1;
    }

    ret = LY_SUCCESS;
    for (value1_iter = value1, value2_iter = value2;
            value1_iter && value2_iter;
            value1_iter = value1_next, value2_iter = value2_next) {
        if ((ret = ly_value_prefix_next(value1_iter, NULL, &value1_len, &is_prefix1, &value1_next))) {
            break;
        }
        if ((ret = ly_value_prefix_next(value2_iter, NULL, &value2_len, &is_prefix2, &value2_next))) {
            break;
        }

        if (is_prefix1 != is_prefix2) {
            ret = LY_ENOT;
            break;
        }

        if (!is_prefix1) {
            if (value1_len != value2_len) {
                ret = LY_ENOT;
                break;
            }
            if (strncmp(value1_iter, value2_iter, value1_len)) {
                ret = LY_ENOT;
                break;
            }
            continue;
        }

        mod1 = mod2 = NULL;
        if (val_prefix_data1) {
            /* find module of the first prefix, if any */
            mod1 = ly_resolve_prefix(ctx1, value1_iter, value1_len, LY_VALUE_XML, val_prefix_data1);
        }
        if (val_prefix_data2) {
            mod2 = ly_resolve_prefix(ctx2, value2_iter, value2_len, LY_VALUE_XML, val_prefix_data2);
        }
        if (!mod1 || !mod2) {
            /* not a prefix or maps to different namespaces */
            ret = LY_ENOT;
            break;
        }

        if (mod1->ctx == mod2->ctx) {
            /* same contexts */
            if ((mod1->name != mod2->name) || (mod1->revision != mod2->revision)) {
                ret = LY_ENOT;
                break;
            }
        } else {
            /* different contexts */
            if (strcmp(mod1->name, mod2->name)) {
                ret = LY_ENOT;
                break;
            }

            if (mod1->revision || mod2->revision) {
                if (!mod1->revision || !mod2->revision) {
                    ret = LY_ENOT;
                    break;
                }
                if (strcmp(mod1->revision, mod2->revision)) {
                    ret = LY_ENOT;
                    break;
                }
            }
        }
    }

    if (value1_iter || value2_iter) {
        ret = LY_ENOT;
    }

    return ret;
}
