YANG parser CHANGE support for including submodules
diff --git a/src/common.h b/src/common.h
index afd4d0b..fe00f7b 100644
--- a/src/common.h
+++ b/src/common.h
@@ -26,6 +26,7 @@
 
 #include "config.h"
 #include "log.h"
+#include "context.h"
 #include "tree_schema.h"
 #include "set.h"
 #include "hash_table.h"
@@ -151,11 +152,26 @@
     struct dict_table dict;           /**< dictionary to effectively store strings used in the context related structures */
     struct ly_set search_paths;       /**< set of directories where to search for schema's imports/includes */
     struct ly_set list;               /**< set of YANG schemas */
+    ly_module_imp_clb imp_clb;        /**< Optional callback for retrieving missing included or imported models in a custom way. */
+    void *imp_clb_data;               /**< Optional private data for imp_clb() */
     uint16_t module_set_id;           /**< ID of the current set of schemas */
     uint16_t flags;                   /**< context settings, see @ref contextoptions. */
     pthread_key_t errlist_key;        /**< key for the thread-specific list of errors related to the context */
 };
 
+/**
+ * @brief Try to find submodule in the context. Submodules are present only in the parsed (lysp_) schema trees, if only
+ * the compiled versions of the schemas are present, the submodule cannot be returned even if it was used to compile
+ * some of the currently present schemas.
+ *
+ * @param[in] ctx Context where to search
+ * @param[in] module Name of the module where the submodule is supposed to belongs-to.
+ * @param[in] submodule Name of the submodule to find.
+ * @param[in] revision Optional revision of the submodule to find. If not specified, the latest revision is returned.
+ * @return Pointer to the specified submodule if it is present in the context.
+ */
+struct lysp_module *ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *module, const char *submodule, const char *revision);
+
 /******************************************************************************
  * Parsers
  *****************************************************************************/
diff --git a/src/context.c b/src/context.c
index 5dcc621..585504c 100644
--- a/src/context.c
+++ b/src/context.c
@@ -261,6 +261,26 @@
     return ctx->module_set_id;
 }
 
+API void
+ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data)
+{
+    LY_CHECK_ARG_RET(ctx, ctx,);
+
+    ctx->imp_clb = clb;
+    ctx->imp_clb_data = user_data;
+}
+
+API ly_module_imp_clb
+ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data)
+{
+    LY_CHECK_ARG_RET(ctx, ctx, NULL);
+
+    if (user_data) {
+        *user_data = ctx->imp_clb_data;
+    }
+    return ctx->imp_clb;
+}
+
 /**
  * @brief Iterate over the modules in the given context. Returned modules must match the given key at the offset of
  * lysp_module and lysc_module structures (they are supposed to be placed at the same offset in both structures).
@@ -416,6 +436,35 @@
     return ly_ctx_get_module_implemented_by(ctx, ns, offsetof(struct lysp_module, ns));
 }
 
+struct lysp_module *
+ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *module, const char *submodule, const char *revision)
+{
+    const struct lys_module *mod;
+    struct lysp_include *inc;
+    unsigned int index = 0, u;
+
+    while ((mod = ly_ctx_get_module_by_iter(ctx, module, offsetof(struct lysp_module, name), &index))) {
+        if (!mod->parsed) {
+            continue;
+        }
+
+        LY_ARRAY_FOR(mod->parsed->includes, u) {
+            if (!strcmp(submodule, mod->parsed->includes[u].submodule->name)) {
+                inc = &mod->parsed->includes[u];
+                if (!revision) {
+                    if (inc->submodule->latest_revision) {
+                        return inc->submodule;
+                    }
+                } else if (inc->submodule->revs && !strcmp(revision, inc->submodule->revs[0].date)) {
+                    return inc->submodule;
+                }
+            }
+        }
+    }
+
+    return NULL;
+}
+
 API void
 ly_ctx_destroy(struct ly_ctx *ctx, void (*private_destructor)(const struct lysc_node *node, void *priv))
 {
diff --git a/src/context.h b/src/context.h
index 2333bb3..31493f2 100644
--- a/src/context.h
+++ b/src/context.h
@@ -152,6 +152,50 @@
 uint16_t ly_ctx_get_module_set_id(const struct ly_ctx *ctx);
 
 /**
+ * @brief Callback for retrieving missing included or imported models in a custom way.
+ *
+ * When submod_name is provided, the submodule is requested instead of the module (in this case only
+ * the module name without its revision is provided).
+ *
+ * If an @arg free_module_data callback is provided, it will be used later to free the allegedly const data
+ * which were returned by this callback.
+ *
+ * @param[in] mod_name Missing module name.
+ * @param[in] mod_rev Optional missing module revision.
+ * @param[in] submod_name Optional missing submodule name.
+ * @param[in] submod_rev Optional missing submodule revision.
+ * @param[in] user_data User-supplied callback data.
+ * @param[out] format Format of the returned module data.
+ * @param[out] module_data Requested module data.
+ * @param[out] free_module_data Callback for freeing the returned module data. If not set, the data will be left untouched.
+ * @return LY_ERR value. If the returned value differs from LY_SUCCESS, libyang continue in trying to get the module data
+ * according to the settings of its mechanism to search for the imported/included schemas.
+ */
+typedef LY_ERR (*ly_module_imp_clb)(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev,
+                                    void *user_data, LYS_INFORMAT *format, const char **module_data,
+                                    void (**free_module_data)(void *model_data, void *user_data));
+
+/**
+ * @brief Get the custom callback for missing import/include module retrieval.
+ *
+ * @param[in] ctx Context to read from.
+ * @param[in] user_data Optional pointer for getting the user-supplied callback data.
+ * @return Callback or NULL if not set.
+ */
+ly_module_imp_clb ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data);
+
+/**
+ * @brief Set missing include or import module callback. It is meant to be used when the models
+ * are not locally available (such as when downloading modules from a NETCONF server), it should
+ * not be required in other cases.
+ *
+ * @param[in] ctx Context that will use this callback.
+ * @param[in] clb Callback responsible for returning the missing model.
+ * @param[in] user_data Arbitrary data that will always be passed to the callback \p clb.
+ */
+void ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data);
+
+/**
  * @brief Get YANG module of the given name and revision.
  *
  * @param[in] ctx Context to work in.
diff --git a/src/hash_table.c b/src/hash_table.c
index a60c3da..ca93d76 100644
--- a/src/hash_table.c
+++ b/src/hash_table.c
@@ -139,8 +139,6 @@
     struct dict_rec rec, *match = NULL;
     char *val_p;
 
-    LY_CHECK_ARG_RET(ctx, ctx, value,);
-
     if (!value) {
         return;
     }
diff --git a/src/log.c b/src/log.c
index 57da747..b576437 100644
--- a/src/log.c
+++ b/src/log.c
@@ -34,6 +34,19 @@
 /* how many bytes add when enlarging buffers */
 #define LY_BUF_STEP 128
 
+API LY_ERR
+ly_errcode(const struct ly_ctx *ctx)
+{
+    struct ly_err_item *i;
+
+    i = ly_err_first(ctx);
+    if (i) {
+        return i->prev->no;
+    }
+
+    return LY_SUCCESS;
+}
+
 API LY_VECODE
 ly_vecode(const struct ly_ctx *ctx)
 {
diff --git a/src/log.h b/src/log.h
index 95232bc..8613b18 100644
--- a/src/log.h
+++ b/src/log.h
@@ -146,6 +146,7 @@
     LY_ESYS,        /**< System call failure */
     LY_EINVAL,      /**< Invalid value */
     LY_EEXIST,      /**< Item already exists */
+    LY_ENOTFOUND,   /**< Item does not exists */
     LY_EINT,        /**< Internal error */
     LY_EVALID,      /**< Validation failure */
     LY_EPLUGIN,     /**< Error reported by a plugin */
@@ -191,6 +192,14 @@
 LY_VECODE ly_vecode(const struct ly_ctx *ctx);
 
 /**
+ * @brief Get the last (thread, context-specific) error code.
+ *
+ * @param[in] ctx Relative context.
+ * @return LY_ERR value of the last error code.
+ */
+LY_ERR ly_errcode(const struct ly_ctx *ctx);
+
+/**
  * @brief Get the last (thread, context-specific) error message. If the coresponding module defined
  * a specific error message, it will be used instead the default one.
  *
diff --git a/src/parser_yang.c b/src/parser_yang.c
index ffdcdc8..c0ef806 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -52,8 +52,6 @@
 
 #define MOVE_INPUT(CTX, DATA, COUNT) (*(data))+=COUNT;(CTX)->indent+=COUNT
 
-#define LOGVAL_YANG(CTX, ...) LOGVAL((CTX)->ctx, LY_VLOG_LINE, &(CTX)->line, __VA_ARGS__)
-
 /**
  * @brief Loop through all substatements providing, return if there are none.
  *
@@ -1291,24 +1289,31 @@
  * @return LY_ERR values.
  */
 static LY_ERR
-parse_include(struct ly_parser_ctx *ctx, const char **data, struct lysp_include **includes)
+parse_include(struct ly_parser_ctx *ctx, const char **data, struct lysp_module *mod)
 {
-    LY_ERR ret = 0;
+    LY_ERR ret = LY_SUCCESS;
     char *buf, *word;
+    const char *name = NULL;
     size_t word_len;
     enum yang_keyword kw;
     struct lysp_include *inc;
 
-    LY_ARRAY_NEW_RET(ctx->ctx, *includes, inc, LY_EMEM);
+    LY_ARRAY_NEW_RET(ctx->ctx, mod->includes, inc, LY_EMEM);
 
     /* get value */
     ret = get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len);
     LY_CHECK_RET(ret);
+    INSERT_WORD(ctx, buf, name, word, word_len);
 
-    INSERT_WORD(ctx, buf, inc->name, word, word_len);
-    YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret) {
-        LY_CHECK_RET(ret);
-
+    ret = get_keyword(ctx, data, &kw, &word, &word_len);
+    LY_CHECK_GOTO(ret, cleanup);
+    LY_CHECK_GOTO(kw == YANG_SEMICOLON, parse_include);
+    LY_CHECK_ERR_GOTO(kw != YANG_LEFT_BRACE,
+                      LOGVAL_YANG(ctx, LYVE_SYNTAX_YANG, "Invalid keyword \"%s\", expected \";\" or \"{\".", ly_stmt2str(kw));
+                      ret = LY_EVALID, cleanup);
+    for (ret = get_keyword(ctx, data, &kw, &word, &word_len);
+            !ret && (kw != YANG_RIGHT_BRACE);
+            ret = get_keyword(ctx, data, &kw, &word, &word_len)) {
         switch (kw) {
         case YANG_DESCRIPTION:
             ret = parse_text_field(ctx, data, LYEXT_SUBSTMT_DESCRIPTION, 0, &inc->dsc, Y_STR_ARG, &inc->exts);
@@ -1324,12 +1329,18 @@
             break;
         default:
             LOGVAL_YANG(ctx, LY_VCODE_INCHILDSTMT, ly_stmt2str(kw), "include");
+            lydict_remove(ctx->ctx, name);
             return LY_EVALID;
         }
-        LY_CHECK_RET(ret);
+        LY_CHECK_GOTO(ret, cleanup);
     }
-    LY_CHECK_RET(ret);
+    LY_CHECK_GOTO(ret, cleanup);
 
+parse_include:
+    ret = lysp_parse_include(ctx, mod, name, inc);
+
+cleanup:
+    lydict_remove(ctx->ctx, name);
     return ret;
 }
 
@@ -4512,7 +4523,7 @@
 
         /* linkage */
         case YANG_INCLUDE:
-            ret = parse_include(ctx, data, &mod->includes);
+            ret = parse_include(ctx, data, mod);
             break;
         case YANG_IMPORT:
             ret = parse_import(ctx, data, mod);
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 29e418e..e058755 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -15,6 +15,7 @@
 #include "common.h"
 
 #include <ctype.h>
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/limits.h>
@@ -84,7 +85,9 @@
 static void
 lysp_include_free(struct ly_ctx *ctx, struct lysp_include *include, int dict)
 {
-    FREE_STRING(ctx, include->name, dict);
+    if (include->submodule && !(--include->submodule->refcount)) {
+        lysp_module_free(include->submodule);
+    }
     FREE_STRING(ctx, include->dsc, dict);
     FREE_STRING(ctx, include->ref, dict);
     FREE_ARRAY(ctx, include->exts, lysp_ext_instance_free);
@@ -1087,10 +1090,11 @@
     }
 }
 
-static const struct lys_module *
+struct lys_module *
 lys_parse_mem_(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, const char *revision, int implement)
 {
     struct lys_module *mod = NULL, *latest;
+    struct lysp_module *latest_p;
     LY_ERR ret;
 
     LY_CHECK_ARG_RET(ctx, ctx, data, NULL);
@@ -1136,40 +1140,38 @@
         }
     }
 
-    /* check for duplicity in the context */
-    if (ly_ctx_get_module(ctx, mod->parsed->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL)) {
-        if (mod->parsed->revs) {
-            LOGERR(ctx, LY_EEXIST, "Module \"%s\" of revision \"%s\" is already present in the context.",
-                   mod->parsed->name, mod->parsed->revs[0].date);
-        } else {
-            LOGERR(ctx, LY_EEXIST, "Module \"%s\" with no revision is already present in the context.",
-                   mod->parsed->name);
-        }
-        lys_module_free(mod, NULL);
-        return NULL;
-    }
-
-    /* decide the latest revision */
-    latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->parsed->name);
-    if (latest) {
-        if (mod->parsed->revs) {
-            if ((latest->parsed && !latest->parsed->revs) || (!latest->parsed && !latest->compiled->revs)) {
-                /* latest has no revision, so mod is anyway newer */
-                mod->parsed->latest_revision = 1;
-                lys_latest_unset(latest);
-            } else {
-                if (strcmp(mod->parsed->revs[0].date, latest->parsed ? latest->parsed->revs[0].date : latest->compiled->revs[0].date) > 0) {
+    if (mod->parsed->submodule) { /* submodule */
+        /* decide the latest revision */
+        latest_p = ly_ctx_get_submodule(ctx, mod->parsed->belongsto, mod->parsed->name, NULL);
+        if (latest_p) {
+            if (mod->parsed->revs) {
+                if (!latest_p->revs) {
+                    /* latest has no revision, so mod is anyway newer */
                     mod->parsed->latest_revision = 1;
-                    lys_latest_unset(latest);
+                    latest_p->latest_revision = 0;
+                } else {
+                    if (strcmp(mod->parsed->revs[0].date, latest_p->revs[0].date) > 0) {
+                        mod->parsed->latest_revision = 1;
+                        latest_p->latest_revision = 0;
+                    }
                 }
             }
+        } else {
+            mod->parsed->latest_revision = 1;
         }
-    } else {
-        mod->parsed->latest_revision = 1;
-    }
-
-    /* add into context */
-    ly_set_add(&ctx->list, mod, LY_SET_OPT_USEASLIST);
+    } else { /* module */
+        /* check for duplicity in the context */
+        if (ly_ctx_get_module(ctx, mod->parsed->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL)) {
+            if (mod->parsed->revs) {
+                LOGERR(ctx, LY_EEXIST, "Module \"%s\" of revision \"%s\" is already present in the context.",
+                       mod->parsed->name, mod->parsed->revs[0].date);
+            } else {
+                LOGERR(ctx, LY_EEXIST, "Module \"%s\" with no revision is already present in the context.",
+                       mod->parsed->name);
+            }
+            lys_module_free(mod, NULL);
+            return NULL;
+        }
 
 #if 0
     /* hack for NETCONF's edit-config's operation attribute. It is not defined in the schema, but since libyang
@@ -1185,13 +1187,46 @@
     }
 #endif
 
+        /* decide the latest revision */
+        latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->parsed->name);
+        if (latest) {
+            if (mod->parsed->revs) {
+                if ((latest->parsed && !latest->parsed->revs) || (!latest->parsed && !latest->compiled->revs)) {
+                    /* latest has no revision, so mod is anyway newer */
+                    mod->parsed->latest_revision = 1;
+                    lys_latest_unset(latest);
+                } else {
+                    if (strcmp(mod->parsed->revs[0].date, latest->parsed ? latest->parsed->revs[0].date : latest->compiled->revs[0].date) > 0) {
+                        mod->parsed->latest_revision = 1;
+                        lys_latest_unset(latest);
+                    }
+                }
+            }
+        } else {
+            mod->parsed->latest_revision = 1;
+        }
+
+        /* add into context */
+        ly_set_add(&ctx->list, mod, LY_SET_OPT_USEASLIST);
+
+    }
+
     return mod;
 }
 
 API const struct lys_module *
 lys_parse_mem(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format)
 {
-    return lys_parse_mem_(ctx, data, format, NULL, 1);
+    struct lys_module *result;
+
+    result = lys_parse_mem_(ctx, data, format, NULL, 1);
+    if (result && result->parsed->submodule) {
+        LOGERR(ctx, LY_EDENIED, "Input data contains submodule \"%s\" which cannot be parsed directly without its main module.",
+               result->parsed->name);
+        lys_module_free(result, NULL);
+        return NULL;
+    }
+    return result;
 }
 
 static void
@@ -1217,10 +1252,10 @@
 #endif
 }
 
-const struct lys_module *
+struct lys_module *
 lys_parse_fd_(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, const char *revision, int implement)
 {
-    const struct lys_module *mod;
+    struct lys_module *mod;
     size_t length;
     char *addr;
 
@@ -1249,14 +1284,23 @@
 API const struct lys_module *
 lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format)
 {
-    return lys_parse_fd_(ctx, fd, format, NULL, 1);
+    struct lys_module *result;
+
+    result = lys_parse_fd_(ctx, fd, format, NULL, 1);
+    if (result && result->parsed->submodule) {
+        LOGERR(ctx, LY_EDENIED, "Input data contains submodule \"%s\" which cannot be parsed directly without its main module.",
+               result->parsed->name);
+        lys_module_free(result, NULL);
+        return NULL;
+    }
+    return result;
 }
 
-API const struct lys_module *
-lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format)
+struct lys_module *
+lys_parse_path_(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, const char *revision, int implement)
 {
     int fd;
-    const struct lys_module *mod;
+    struct lys_module *mod;
     const char *rev, *dot, *filename;
     size_t len;
 
@@ -1265,7 +1309,7 @@
     fd = open(path, O_RDONLY);
     LY_CHECK_ERR_RET(fd == -1, LOGERR(ctx, LY_ESYS, "Opening file \"%s\" failed (%s).", path, strerror(errno)), NULL);
 
-    mod = lys_parse_fd(ctx, fd, format);
+    mod = lys_parse_fd_(ctx, fd, format, revision, implement);
     close(fd);
     LY_CHECK_RET(!mod, NULL);
 
@@ -1306,3 +1350,276 @@
     return mod;
 }
 
+API const struct lys_module *
+lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format)
+{
+    struct lys_module *result;
+
+    result = lys_parse_path_(ctx, path, format, NULL, 1);
+    if (result && result->parsed->submodule) {
+        LOGERR(ctx, LY_EDENIED, "Input file \"%s\" contains submodule \"%s\" which cannot be parsed directly without its main module.",
+               path, result->parsed->name);
+        lys_module_free(result, NULL);
+        return NULL;
+    }
+    return result;
+}
+
+API LY_ERR
+lys_search_localfile(const char * const *searchpaths, int cwd, const char *name, const char *revision,
+                     char **localfile, LYS_INFORMAT *format)
+{
+    size_t len, flen, match_len = 0, dir_len;
+    int i, implicit_cwd = 0, ret = EXIT_FAILURE;
+    char *wd, *wn = NULL;
+    DIR *dir = NULL;
+    struct dirent *file;
+    char *match_name = NULL;
+    LYS_INFORMAT format_aux, match_format = 0;
+    struct ly_set *dirs;
+    struct stat st;
+
+    LY_CHECK_ARG_RET(NULL, localfile, LY_EINVAL);
+
+    /* start to fill the dir fifo with the context's search path (if set)
+     * and the current working directory */
+    dirs = ly_set_new();
+    if (!dirs) {
+        LOGMEM(NULL);
+        return EXIT_FAILURE;
+    }
+
+    len = strlen(name);
+    if (cwd) {
+        wd = get_current_dir_name();
+        if (!wd) {
+            LOGMEM(NULL);
+            goto cleanup;
+        } else {
+            /* add implicit current working directory (./) to be searched,
+             * this directory is not searched recursively */
+            if (ly_set_add(dirs, wd, 0) == -1) {
+                goto cleanup;
+            }
+            implicit_cwd = 1;
+        }
+    }
+    if (searchpaths) {
+        for (i = 0; searchpaths[i]; i++) {
+            /* check for duplicities with the implicit current working directory */
+            if (implicit_cwd && !strcmp(dirs->objs[0], searchpaths[i])) {
+                implicit_cwd = 0;
+                continue;
+            }
+            wd = strdup(searchpaths[i]);
+            if (!wd) {
+                LOGMEM(NULL);
+                goto cleanup;
+            } else if (ly_set_add(dirs, wd, 0) == -1) {
+                goto cleanup;
+            }
+        }
+    }
+    wd = NULL;
+
+    /* start searching */
+    while (dirs->count) {
+        free(wd);
+        free(wn); wn = NULL;
+
+        dirs->count--;
+        wd = (char *)dirs->objs[dirs->count];
+        dirs->objs[dirs->count] = NULL;
+        LOGVRB("Searching for \"%s\" in %s.", name, wd);
+
+        if (dir) {
+            closedir(dir);
+        }
+        dir = opendir(wd);
+        dir_len = strlen(wd);
+        if (!dir) {
+            LOGWRN(NULL, "Unable to open directory \"%s\" for searching (sub)modules (%s).", wd, strerror(errno));
+        } else {
+            while ((file = readdir(dir))) {
+                if (!strcmp(".", file->d_name) || !strcmp("..", file->d_name)) {
+                    /* skip . and .. */
+                    continue;
+                }
+                free(wn);
+                if (asprintf(&wn, "%s/%s", wd, file->d_name) == -1) {
+                    LOGMEM(NULL);
+                    goto cleanup;
+                }
+                if (stat(wn, &st) == -1) {
+                    LOGWRN(NULL, "Unable to get information about \"%s\" file in \"%s\" when searching for (sub)modules (%s)",
+                           file->d_name, wd, strerror(errno));
+                    continue;
+                }
+                if (S_ISDIR(st.st_mode) && (dirs->count || !implicit_cwd)) {
+                    /* we have another subdirectory in searchpath to explore,
+                     * subdirectories are not taken into account in current working dir (dirs->set.g[0]) */
+                    if (ly_set_add(dirs, wn, 0) == -1) {
+                        goto cleanup;
+                    }
+                    /* continue with the next item in current directory */
+                    wn = NULL;
+                    continue;
+                } else if (!S_ISREG(st.st_mode)) {
+                    /* not a regular file (note that we see the target of symlinks instead of symlinks */
+                    continue;
+                }
+
+                /* here we know that the item is a file which can contain a module */
+                if (strncmp(name, file->d_name, len) ||
+                        (file->d_name[len] != '.' && file->d_name[len] != '@')) {
+                    /* different filename than the module we search for */
+                    continue;
+                }
+
+                /* get type according to filename suffix */
+                flen = strlen(file->d_name);
+                if (!strcmp(&file->d_name[flen - 4], ".yin")) {
+                    format_aux = LYS_IN_YIN;
+                } else if (!strcmp(&file->d_name[flen - 5], ".yang")) {
+                    format_aux = LYS_IN_YANG;
+                } else {
+                    /* not supportde suffix/file format */
+                    continue;
+                }
+
+                if (revision) {
+                    /* we look for the specific revision, try to get it from the filename */
+                    if (file->d_name[len] == '@') {
+                        /* check revision from the filename */
+                        if (strncmp(revision, &file->d_name[len + 1], strlen(revision))) {
+                            /* another revision */
+                            continue;
+                        } else {
+                            /* exact revision */
+                            free(match_name);
+                            match_name = wn;
+                            wn = NULL;
+                            match_len = dir_len + 1 + len;
+                            match_format = format_aux;
+                            goto success;
+                        }
+                    } else {
+                        /* continue trying to find exact revision match, use this only if not found */
+                        free(match_name);
+                        match_name = wn;
+                        wn = NULL;
+                        match_len = dir_len + 1 +len;
+                        match_format = format_aux;
+                        continue;
+                    }
+                } else {
+                    /* remember the revision and try to find the newest one */
+                    if (match_name) {
+                        if (file->d_name[len] != '@' ||
+                                lysp_check_date(NULL, &file->d_name[len + 1], flen - (format_aux == LYS_IN_YANG ? 5 : 4) - len - 1, NULL)) {
+                            continue;
+                        } else if (match_name[match_len] == '@' &&
+                                (strncmp(&match_name[match_len + 1], &file->d_name[len + 1], LY_REV_SIZE - 1) >= 0)) {
+                            continue;
+                        }
+                        free(match_name);
+                    }
+
+                    match_name = wn;
+                    wn = NULL;
+                    match_len = dir_len + 1 + len;
+                    match_format = format_aux;
+                    continue;
+                }
+            }
+        }
+    }
+
+success:
+    (*localfile) = match_name;
+    match_name = NULL;
+    if (format) {
+        (*format) = match_format;
+    }
+    ret = EXIT_SUCCESS;
+
+cleanup:
+    free(wn);
+    free(wd);
+    if (dir) {
+        closedir(dir);
+    }
+    free(match_name);
+    ly_set_free(dirs, free);
+
+    return ret;
+}
+
+LY_ERR
+lys_module_localfile(struct ly_ctx *ctx, const char *name, const char *revision, int implement,
+                     struct lys_module **result)
+{
+    size_t len;
+    int fd;
+    char *filepath = NULL, *dot, *rev, *filename;
+    LYS_INFORMAT format;
+    struct lys_module *mod = NULL;
+    LY_ERR ret = LY_SUCCESS;
+
+    LY_CHECK_RET(lys_search_localfile(ly_ctx_get_searchdirs(ctx), !(ctx->flags & LY_CTX_DISABLE_SEARCHDIR_CWD), name, revision,
+                                      &filepath, &format));
+    LY_CHECK_ERR_RET(!filepath, LOGERR(ctx, LY_ENOTFOUND, "Data model \"%s%s%s\" not found in local searchdirs.",
+                                       name, revision ? "@" : "", revision ? revision : ""), LY_ENOTFOUND);
+
+
+    LOGVRB("Loading schema from \"%s\" file.", filepath);
+
+    /* open the file */
+    fd = open(filepath, O_RDONLY);
+    LY_CHECK_ERR_GOTO(fd < 0, LOGERR(ctx, LY_ESYS, "Unable to open data model file \"%s\" (%s).",
+                                     filepath, strerror(errno)); ret = LY_ESYS, cleanup);
+
+    mod = lys_parse_fd_(ctx, fd, format, revision, implement);
+    close(fd);
+    LY_CHECK_ERR_GOTO(!mod, ly_errcode(ctx), cleanup);
+
+    /* check that name and revision match filename */
+    filename = strrchr(filepath, '/');
+    if (!filename) {
+        filename = filepath;
+    } else {
+        filename++;
+    }
+    /* name */
+    len = strlen(mod->parsed->name);
+    rev = strchr(filename, '@');
+    dot = strrchr(filepath, '.');
+    if (strncmp(filename, mod->parsed->name, len) ||
+            ((rev && rev != &filename[len]) || (!rev && dot != &filename[len]))) {
+        LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->parsed->name);
+    }
+    /* revision */
+    if (rev) {
+        len = dot - ++rev;
+        if (!mod->parsed->revs || len != 10 || strncmp(mod->parsed->revs[0].date, rev, len)) {
+            LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename,
+                   mod->parsed->revs ? mod->parsed->revs[0].date : "none");
+        }
+    }
+
+    if (!mod->parsed->filepath) {
+        char rpath[PATH_MAX];
+        if (realpath(filepath, rpath) != NULL) {
+            mod->parsed->filepath = lydict_insert(ctx, rpath, 0);
+        } else {
+            mod->parsed->filepath = lydict_insert(ctx, filepath, 0);
+        }
+    }
+
+    *result = mod;
+
+    /* success */
+cleanup:
+    free(filepath);
+    return ret;
+}
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 3b939ea..6bcbda1 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -174,7 +174,7 @@
  * @brief YANG include-stmt
  */
 struct lysp_include {
-    const char *name;                /**< name of the submodule to include (mandatory) */
+    struct lysp_module *submodule;   /**< pointer to the parsed submodule structure (mandatory) */
     const char *dsc;                 /**< description */
     const char *ref;                 /**< reference */
     struct lysp_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
@@ -772,6 +772,7 @@
     uint8_t latest_revision:1;       /**< flag if the module was loaded without specific revision and is
                                           the latest revision found */
     uint8_t version:4;               /**< yang-version (LYS_VERSION values) */
+    uint16_t refcount;               /**< 0 in modules, number of includes of a submodules */
 };
 
 /**
@@ -965,7 +966,7 @@
 const struct lys_module *lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format);
 
 /**
- * @brief Load a schema into the specified context from a file.
+ * @brief Read a schema into the specified context from a file.
  *
  * @param[in] ctx libyang context where to process the data model.
  * @param[in] path Path to the file with the model in the specified format.
@@ -975,6 +976,23 @@
 const struct lys_module *lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format);
 
 /**
+ * @brief Search for the schema file in the specified searchpaths.
+ *
+ * @param[in] searchpaths NULL-terminated array of paths to be searched (recursively). Current working
+ * directory is searched automatically (but non-recursively if not in the provided list). Caller can use
+ * result of the ly_ctx_get_searchdirs().
+ * @param[in] cwd Flag to implicitly search also in the current working directory (non-recursively).
+ * @param[in] name Name of the schema to find.
+ * @param[in] revision Revision of the schema to find. If NULL, the newest found schema filepath is returned.
+ * @param[out] localfile Mandatory output variable containing absolute path of the found schema. If no schema
+ * complying the provided restriction is found, NULL is set.
+ * @param[out] format Optional output variable containing expected format of the schema document according to the
+ * file suffix.
+ * @return LY_ERR value (LY_SUCCESS is returned even if the file is not found, then the *localfile is NULL).
+ */
+LY_ERR lys_search_localfile(const char * const *searchpaths, int cwd, const char *name, const char *revision, char **localfile, LYS_INFORMAT *format);
+
+/**
  * @defgroup scflags Schema compile flags
  * @ingroup schematree
  *
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index b95505a..6657702 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -81,7 +81,9 @@
     return LY_SUCCESS;
 
 error:
-    LOGVAL(ctx, LY_VLOG_NONE, NULL, LY_VCODE_INVAL, date_len, date, stmt);
+    if (stmt) {
+        LOGVAL(ctx, LY_VLOG_NONE, NULL, LY_VCODE_INVAL, date_len, date, stmt);
+    }
     return LY_EINVAL;
 }
 
@@ -105,6 +107,76 @@
     }
 }
 
+LY_ERR
+lysp_parse_include(struct ly_parser_ctx *ctx, struct lysp_module *mod, const char *name, struct lysp_include *inc)
+{
+    struct lys_module *submod;
+    const char *submodule_data = NULL;
+    LYS_INFORMAT format = LYS_IN_UNKNOWN;
+    void (*submodule_data_free)(void *module_data, void *user_data) = NULL;
+
+    /* Try to get submodule from the context, if already present */
+    inc->submodule = ly_ctx_get_submodule(ctx->ctx, mod->name, name, inc->rev[0] ? inc->rev : NULL);
+    if (!inc->submodule) {
+        /* submodule not present in the context, get the input data and parse it */
+        if (!(ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+search_clb:
+            if (ctx->ctx->imp_clb) {
+                if (ctx->ctx->imp_clb(mod->name, NULL, name, inc->rev, ctx->ctx->imp_clb_data,
+                                      &format, &submodule_data, &submodule_data_free) == LY_SUCCESS) {
+                    submod = lys_parse_mem_(ctx->ctx, submodule_data, format, inc->rev[0] ? inc->rev : NULL, mod->implemented);
+                }
+            }
+            if (!submod && !(ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+                goto search_file;
+            }
+        } else {
+search_file:
+            if (!(ctx->ctx->flags & LY_CTX_DISABLE_SEARCHDIRS)) {
+                /* module was not received from the callback or there is no callback set */
+                lys_module_localfile(ctx->ctx, name, inc->rev[0] ? inc->rev : NULL, mod->implemented, &submod);
+                if (inc->submodule) {
+                    ++inc->submodule->refcount;
+                }
+            }
+            if (!submod && (ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+                goto search_clb;
+            }
+        }
+        if (submod) {
+            /* check that we have really a submodule */
+            if (!submod->parsed->submodule) {
+                /* submodule is not a submodule */
+                LOGVAL_YANG(ctx, LYVE_REFERENCE, "Included \"%s\" schema from \"%s\" is actually not a submodule.", name, mod->name);
+                lys_module_free(submod, NULL);
+                /* fix list of modules in context, since it was already changed */
+                --ctx->ctx->list.count;
+                return LY_EVALID;
+            }
+            /* check that the submodule belongs-to our module */
+            if (strcmp(mod->name, submod->parsed->belongsto)) {
+                LOGVAL_YANG(ctx, LYVE_REFERENCE, "Included \"%s\" submodule from \"%s\" belongs-to a different module \"%s\".",
+                            name, mod->name, submod->parsed->belongsto);
+                lys_module_free(submod, NULL);
+                return LY_EVALID;
+            }
+            inc->submodule = submod->parsed;
+            ++inc->submodule->refcount;
+            free(submod);
+        }
+    }
+    if (!inc->submodule) {
+        if (ly_errcode(ctx->ctx) != LY_EVALID) {
+            LOGVAL_YANG(ctx, LY_VCODE_INVAL, strlen(name), name, "include");
+        } else {
+            LOGVAL_YANG(ctx, LYVE_REFERENCE, "Including \"%s\" submodule into \"%s\" failed.", name, mod->name);
+        }
+        return LY_EVALID;
+    }
+
+    return LY_SUCCESS;
+}
+
 struct lysc_module *
 lysc_module_find_prefix(struct lysc_module *mod, const char *prefix, size_t len)
 {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index da8789c..46727d4 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -15,6 +15,8 @@
 #ifndef LY_TREE_SCHEMA_INTERNAL_H_
 #define LY_TREE_SCHEMA_INTERNAL_H_
 
+#define LOGVAL_YANG(CTX, ...) LOGVAL((CTX)->ctx, LY_VLOG_LINE, &(CTX)->line, __VA_ARGS__)
+
 /**
  * @brief List of YANG statement groups - the (sub)module's substatements
  */
@@ -73,6 +75,18 @@
 void lysp_sort_revisions(struct lysp_revision *revs);
 
 /**
+ * @brief Parse included submodule into the simply parsed YANG module.
+ *
+ * @param[in] ctx yang parser context.
+ * @param[in] mod Module including a submodule.
+ * @param[in] name Name of the submodule to include.
+ * @param[in,out] inc Include structure holding all available information about the include statement, the parsed
+ * submodule is stored into this structure.
+ * @return LY_ERR value.
+ */
+LY_ERR lysp_parse_include(struct ly_parser_ctx *ctx, struct lysp_module *mod, const char *name, struct lysp_include *inc);
+
+/**
  * @brief Find the module referenced by prefix in the provided mod.
  *
  * @param[in] mod Schema module where the prefix was used.
@@ -83,6 +97,80 @@
 struct lysc_module *lysc_module_find_prefix(struct lysc_module *mod, const char *prefix, size_t len);
 
 /**
+ * @brief Parse YANG module and submodule from a string.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] data The string containing the dumped data model in the specified
+ * format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_mem_(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Parse YANG module and submodule from a file descriptor.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * \note Current implementation supports only reading data from standard (disk) file, not from sockets, pipes, etc.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the schema
+ *            in the specified format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_fd_(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Parse YANG module and submodule from a file descriptor.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * \note Current implementation supports only reading data from standard (disk) file, not from sockets, pipes, etc.
+ *
+ * @brief REad a schema into the specified context from a file.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] path Path to the file with the model in the specified format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_path_(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Load the (sub)module into the context.
+ *
+ * This function does not check the presence of the (sub)module in context, it should be done before calling this function.
+ *
+ * module_name and submodule_name are alternatives - only one of the
+ *
+ * @param[in] ctx libyang context where to work.
+ * @param[in] name Name of the (sub)module to load.
+ * @param[in] revision Optional revision of the (sub)module to load, if NULL the newest revision is being loaded.
+ * @param[in] implement Flag if the (sub)module is supposed to be marked as implemented.
+ * @param[out] result Parsed YANG schema tree of the requested module. If it is a module, it is already in the context!
+ * @return LY_ERR value, in case of LY_SUCCESS, the \arg result is always provided.
+ */
+LY_ERR lys_module_localfile(struct ly_ctx *ctx, const char *name, const char *revision, int implement, struct lys_module **result);
+/**
  * @brief Free the schema structure. It just frees, it does not remove the schema from its context.
  * @param[in,out] module Schema module structure to free.
  * @param[in] private_destructor Function to remove private data from the compiled schema tree.