tree data FEATURE compare on opaque nodes
diff --git a/src/tree_data.c b/src/tree_data.c
index 8704a96..7ab2607 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -1166,6 +1166,82 @@
 }
 
 /**
+ * @brief Compare 2 nodes values including opaque node values.
+ *
+ * @param[in] node1 First node to compare.
+ * @param[in] node2 Second node to compare.
+ * @return LY_SUCCESS if equal.
+ * @return LY_ENOT if not equal.
+ * @return LY_ERR on error.
+ */
+static LY_ERR
+lyd_compare_single_value(const struct lyd_node *node1, const struct lyd_node *node2)
+{
+    const struct lyd_node_opaq *opaq1 = NULL, *opaq2 = NULL;
+    const char *val1, *val2, *col;
+    const struct lys_module *mod;
+    char *val_dyn = NULL;
+    LY_ERR rc = LY_SUCCESS;
+
+    if (!node1->schema) {
+        opaq1 = (struct lyd_node_opaq *)node1;
+    }
+    if (!node2->schema) {
+        opaq2 = (struct lyd_node_opaq *)node2;
+    }
+
+    if (opaq1 && opaq2 && (opaq1->format == LY_VALUE_XML) && (opaq2->format == LY_VALUE_XML)) {
+        /* opaque XML and opaque XML node */
+        if (lyxml_value_compare(LYD_CTX(node1), opaq1->value, opaq1->val_prefix_data, LYD_CTX(node2), opaq2->value,
+                opaq2->val_prefix_data)) {
+            return LY_ENOT;
+        }
+        return LY_SUCCESS;
+    }
+
+    /* get their values */
+    if (opaq1 && ((opaq1->format == LY_VALUE_XML) || (opaq1->format == LY_VALUE_STR_NS)) && (col = strchr(opaq1->value, ':'))) {
+        /* XML value with a prefix, try to transform it into a JSON (canonical) value */
+        mod = ly_resolve_prefix(LYD_CTX(node1), opaq1->value, col - opaq1->value, opaq1->format, opaq1->val_prefix_data);
+        if (!mod) {
+            /* unable to compare */
+            return LY_ENOT;
+        }
+
+        if (asprintf(&val_dyn, "%s%s", mod->name, col) == -1) {
+            LOGMEM(LYD_CTX(node1));
+            return LY_EMEM;
+        }
+        val1 = val_dyn;
+    } else {
+        val1 = lyd_get_value(node1);
+    }
+    if (opaq2 && ((opaq2->format == LY_VALUE_XML) || (opaq2->format == LY_VALUE_STR_NS)) && (col = strchr(opaq2->value, ':'))) {
+        mod = ly_resolve_prefix(LYD_CTX(node2), opaq2->value, col - opaq2->value, opaq2->format, opaq2->val_prefix_data);
+        if (!mod) {
+            return LY_ENOT;
+        }
+
+        assert(!val_dyn);
+        if (asprintf(&val_dyn, "%s%s", mod->name, col) == -1) {
+            LOGMEM(LYD_CTX(node2));
+            return LY_EMEM;
+        }
+        val2 = val_dyn;
+    } else {
+        val2 = lyd_get_value(node2);
+    }
+
+    /* compare values */
+    if (strcmp(val1, val2)) {
+        rc = LY_ENOT;
+    }
+
+    free(val_dyn);
+    return rc;
+}
+
+/**
  * @brief Internal implementation of @ref lyd_compare_single.
  * @copydoc lyd_compare_single
  * @param[in] parental_schemas_checked Flag used for optimization.
@@ -1174,14 +1250,13 @@
  * recursive calls, and this is accomplished by setting to 1 in the lyd_compare_single_ body.
  */
 static LY_ERR
-lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2,
-        uint32_t options, ly_bool parental_schemas_checked)
+lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options,
+        ly_bool parental_schemas_checked)
 {
     const struct lyd_node *iter1, *iter2;
-    struct lyd_node_term *term1, *term2;
     struct lyd_node_any *any1, *any2;
-    struct lyd_node_opaq *opaq1, *opaq2;
     int len1, len2;
+    LY_ERR r;
 
     if (!node1 || !node2) {
         if (node1 == node2) {
@@ -1193,8 +1268,14 @@
 
     if (LYD_CTX(node1) == LYD_CTX(node2)) {
         /* same contexts */
-        if (node1->schema != node2->schema) {
-            return LY_ENOT;
+        if (options & LYD_COMPARE_OPAQ) {
+            if (lyd_node_schema(node1) != lyd_node_schema(node2)) {
+                return LY_ENOT;
+            }
+        } else {
+            if (node1->schema != node2->schema) {
+                return LY_ENOT;
+            }
         }
     } else {
         /* different contexts */
@@ -1209,39 +1290,22 @@
         }
     }
 
-    if (node1->hash != node2->hash) {
+    if (!(options & LYD_COMPARE_OPAQ) && (node1->hash != node2->hash)) {
         return LY_ENOT;
     }
     /* equal hashes do not mean equal nodes, they can be just in collision so the nodes must be checked explicitly */
 
-    if (!node1->schema) {
-        opaq1 = (struct lyd_node_opaq *)node1;
-        opaq2 = (struct lyd_node_opaq *)node2;
-        if ((strcmp(opaq1->name.name, opaq2->name.name)) || (opaq1->format != opaq2->format) ||
-                (strcmp(opaq1->name.module_ns, opaq2->name.module_ns))) {
+    if (!node1->schema || !node2->schema) {
+        if (!(options & LYD_COMPARE_OPAQ) && ((node1->schema && !node2->schema) || (!node1->schema && node2->schema))) {
             return LY_ENOT;
         }
-        switch (opaq1->format) {
-        case LY_VALUE_XML:
-            if (lyxml_value_compare(LYD_CTX(node1), opaq1->value, opaq1->val_prefix_data, LYD_CTX(node2), opaq2->value,
-                    opaq2->val_prefix_data)) {
-                return LY_ENOT;
-            }
-            break;
-        case LY_VALUE_JSON:
-            /* prefixes in JSON are unique, so it is not necessary to canonize the values */
-            if (strcmp(opaq1->value, opaq2->value)) {
-                return LY_ENOT;
-            }
-            break;
-        default:
-            /* not allowed */
-            LOGINT(LYD_CTX(node1));
-            return LY_EINT;
+        if ((r = lyd_compare_single_value(node1, node2))) {
+            return r;
         }
+
         if (options & LYD_COMPARE_FULL_RECURSION) {
-            iter1 = opaq1->child;
-            iter2 = opaq2->child;
+            iter1 = lyd_child(node1);
+            iter2 = lyd_child(node2);
             goto all_children_compare;
         }
         return LY_SUCCESS;
@@ -1254,19 +1318,10 @@
                     return LY_ENOT;
                 }
             }
-
-            term1 = (struct lyd_node_term *)node1;
-            term2 = (struct lyd_node_term *)node2;
-
-            /* same contexts */
-            if (LYD_CTX(node1) == LYD_CTX(node2)) {
-                return term1->value.realtype->plugin->compare(&term1->value, &term2->value);
+            if ((r = lyd_compare_single_value(node1, node2))) {
+                return r;
             }
 
-            /* different contexts */
-            if (strcmp(lyd_get_value(node1), lyd_get_value(node2))) {
-                return LY_ENOT;
-            }
             return LY_SUCCESS;
         case LYS_CONTAINER:
         case LYS_RPC:
@@ -2508,7 +2563,7 @@
     } else {
         /* no children hash table */
         for ( ; siblings; siblings = siblings->next) {
-            if (!lyd_compare_single(siblings, target, 0)) {
+            if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) {
                 break;
             }
         }
@@ -2640,7 +2695,7 @@
     } else {
         /* no children hash table */
         LY_LIST_FOR(siblings, siblings) {
-            if (!lyd_compare_single(target, siblings, 0)) {
+            if (!lyd_compare_single(target, siblings, LYD_COMPARE_OPAQ)) {
                 ly_set_add(*set, (void *)siblings, 1, NULL);
             }
         }
diff --git a/src/tree_data.h b/src/tree_data.h
index 39a3344..ff98f60 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -1857,13 +1857,16 @@
  * @{
  * Various options to change the ::lyd_compare_single() and ::lyd_compare_siblings() behavior.
  */
-#define LYD_COMPARE_FULL_RECURSION 0x01 /* lists and containers are the same only in case all they children
+#define LYD_COMPARE_FULL_RECURSION 0x01 /* Lists and containers are the same only in case all they children
                                            (subtree, so direct as well as indirect children) are the same. By default,
                                            containers are the same in case of the same schema node and lists are the same
                                            in case of equal keys (keyless lists do the full recursion comparison all the time). */
 #define LYD_COMPARE_DEFAULTS 0x02       /* By default, implicit and explicit default nodes are considered to be equal. This flag
                                            changes this behavior and implicit (automatically created default node) and explicit
                                            (explicitly created node with the default value) default nodes are considered different. */
+#define LYD_COMPARE_OPAQ 0x04           /* Opaque nodes can normally be never equal to data nodes. Using this flag even
+                                           opaque nodes members are compared to data node schema and value and can result
+                                           in a match. */
 /** @} datacompareoptions */
 
 /**
diff --git a/src/tree_data_common.c b/src/tree_data_common.c
index 9b01fdc..acaf5a1 100644
--- a/src/tree_data_common.c
+++ b/src/tree_data_common.c
@@ -1039,6 +1039,7 @@
 {
     struct lyd_node **match_p;
     struct lyd_node_inner *parent;
+    const struct lysc_node *cur_schema;
     uint32_t hash;
     lyht_value_equal_cb ht_cb;
 
@@ -1083,13 +1084,21 @@
 
         /* search manually without hashes */
         for ( ; siblings; siblings = siblings->next) {
-            /* schema match is enough */
-            if (siblings->schema == schema) {
-                break;
+            cur_schema = lyd_node_schema(siblings);
+            if (!cur_schema) {
+                /* some unknown opaque node */
+                continue;
             }
-            if ((LYD_CTX(siblings) != schema->module->ctx) && !strcmp(siblings->schema->name, schema->name) &&
-                    !strcmp(siblings->schema->module->name, schema->module->name)) {
-                break;
+
+            /* schema match is enough */
+            if (cur_schema->module->ctx == schema->module->ctx) {
+                if (cur_schema == schema) {
+                    break;
+                }
+            } else {
+                if (!strcmp(cur_schema->name, schema->name) && !strcmp(cur_schema->module->name, schema->module->name)) {
+                    break;
+                }
             }
         }
     }