xml parser & printer FEATURE support for special filter attrs

Fixes #1619
diff --git a/src/parser_xml.c b/src/parser_xml.c
index a827ab7..fd2f856 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -67,30 +67,60 @@
     free(ctx);
 }
 
+/**
+ * @brief Parse and create XML metadata.
+ *
+ * @param[in] lydctx XML data parser context.
+ * @param[in] parent_exts Extension instances of the parent node.
+ * @param[out] meta List of created metadata instances.
+ * @return LY_ERR value.
+ */
 static LY_ERR
-lydxml_metadata(struct lyd_xml_ctx *lydctx, struct lyd_meta **meta)
+lydxml_metadata(struct lyd_xml_ctx *lydctx, struct lysc_ext_instance *parent_exts, struct lyd_meta **meta)
 {
     LY_ERR ret = LY_SUCCESS;
     const struct lyxml_ns *ns;
     struct lys_module *mod;
     const char *name;
     size_t name_len;
+    LY_ARRAY_COUNT_TYPE u;
+    ly_bool filter_attrs = 0;
     struct lyxml_ctx *xmlctx = lydctx->xmlctx;
 
     *meta = NULL;
 
+    /* check for NETCONF filter unqualified attributes */
+    LY_ARRAY_FOR(parent_exts, u) {
+        if (!strcmp(parent_exts[u].def->name, "get-filter-element-attributes") &&
+                !strcmp(parent_exts[u].def->module->name, "ietf-netconf")) {
+            filter_attrs = 1;
+            break;
+        }
+    }
+
     while (xmlctx->status == LYXML_ATTRIBUTE) {
         if (!xmlctx->prefix_len) {
-            /* in XML, all attributes must be prefixed
-             * TODO exception for NETCONF filters which are supposed to map to the ietf-netconf without prefix */
+            /* in XML all attributes must be prefixed except NETCONF filter ones marked by an extension */
+            if (filter_attrs && (!ly_strncmp("type", xmlctx->name, xmlctx->name_len) ||
+                    !ly_strncmp("select", xmlctx->name, xmlctx->name_len))) {
+                mod = ly_ctx_get_module_implemented(xmlctx->ctx, "ietf-netconf");
+                if (!mod) {
+                    LOGVAL(xmlctx->ctx, LYVE_REFERENCE,
+                            "Missing (or not implemented) YANG module \"ietf-netconf\" for special filter attributes.");
+                    ret = LY_ENOTFOUND;
+                    goto cleanup;
+                }
+                goto create_meta;
+            }
+
             if (lydctx->parse_opts & LYD_PARSE_STRICT) {
-                ret = LY_EVALID;
                 LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Missing mandatory prefix for XML metadata \"%.*s\".",
                         (int)xmlctx->name_len, xmlctx->name);
+                ret = LY_EVALID;
                 goto cleanup;
             }
 
-skip_attr:
+            /* skip attr */
             LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
             assert(xmlctx->status == LYXML_ATTR_CONTENT);
             LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
@@ -100,25 +130,32 @@
         /* get namespace of the attribute to find its annotation definition */
         ns = lyxml_ns_get(&xmlctx->ns, xmlctx->prefix, xmlctx->prefix_len);
         if (!ns) {
-            ret = LY_ENOTFOUND;
             /* unknown namespace, XML error */
             LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)xmlctx->prefix_len, xmlctx->prefix);
+            ret = LY_ENOTFOUND;
             goto cleanup;
         }
+
+        /* get the module with metadata definition */
         mod = ly_ctx_get_module_implemented_ns(xmlctx->ctx, ns->uri);
         if (!mod) {
-            /* module is not implemented or not present in the schema */
             if (lydctx->parse_opts & LYD_PARSE_STRICT) {
-                ret = LY_ENOTFOUND;
                 LOGVAL(xmlctx->ctx, LYVE_REFERENCE,
                         "Unknown (or not implemented) YANG module with namespace \"%s\" for metadata \"%.*s%s%.*s\".",
                         ns->uri, (int)xmlctx->prefix_len, xmlctx->prefix, xmlctx->prefix_len ? ":" : "",
                         (int)xmlctx->name_len, xmlctx->name);
+                ret = LY_ENOTFOUND;
                 goto cleanup;
             }
-            goto skip_attr;
+
+            /* skip attr */
+            LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
+            assert(xmlctx->status == LYXML_ATTR_CONTENT);
+            LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup);
+            continue;
         }
 
+create_meta:
         /* remember meta name and get its content */
         name = xmlctx->name;
         name_len = xmlctx->name_len;
@@ -489,7 +526,7 @@
     /* create metadata/attributes */
     if (xmlctx->status == LYXML_ATTRIBUTE) {
         if (snode) {
-            ret = lydxml_metadata(lydctx, &meta);
+            ret = lydxml_metadata(lydctx, snode->exts, &meta);
             LY_CHECK_GOTO(ret, error);
         } else {
             assert(lydctx->parse_opts & LYD_PARSE_OPAQ);
diff --git a/src/printer_xml.c b/src/printer_xml.c
index 32114f5..c6f5341 100644
--- a/src/printer_xml.c
+++ b/src/printer_xml.c
@@ -166,16 +166,8 @@
     struct lyd_meta *meta;
     const struct lys_module *mod;
     struct ly_set ns_list = {0};
-
-#if 0
-    const char **prefs, **nss;
-    const char *xml_expr = NULL, *mod_name;
-    uint32_t ns_count, i;
-    ly_bool rpc_filter = 0;
-    char *p;
-    size_t len;
-#endif
-    ly_bool dynamic;
+    LY_ARRAY_COUNT_TYPE u;
+    ly_bool dynamic, filter_attrs = 0;
 
     /* with-defaults */
     if (node->schema->nodetype & LYD_NODE_TERM) {
@@ -188,50 +180,36 @@
             }
         }
     }
-#if 0
-    /* technically, check for the extension get-filter-element-attributes from ietf-netconf */
-    if (!strcmp(node->schema->name, "filter") &&
-            (!strcmp(node->schema->module->name, "ietf-netconf") || !strcmp(node->schema->module->name, "notifications"))) {
-        rpc_filter = 1;
+
+    /* check for NETCONF filter unqualified attributes */
+    LY_ARRAY_FOR(node->schema->exts, u) {
+        if (!strcmp(node->schema->exts[u].def->name, "get-filter-element-attributes") &&
+                !strcmp(node->schema->exts[u].def->module->name, "ietf-netconf")) {
+            filter_attrs = 1;
+            break;
+        }
     }
-#endif
+
     for (meta = node->meta; meta; meta = meta->next) {
         const char *value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list,
                 &dynamic, NULL);
 
         /* print namespaces connected with the value's prefixes */
-        for (uint32_t u = 0; u < ns_list.count; ++u) {
-            mod = (const struct lys_module *)ns_list.objs[u];
+        for (uint32_t i = 0; i < ns_list.count; ++i) {
+            mod = ns_list.objs[i];
             xml_print_ns(ctx, mod->ns, mod->prefix, 1);
         }
         ly_set_erase(&ns_list, NULL);
 
-#if 0
-        if (rpc_filter) {
-            /* exception for NETCONF's filter's attributes */
-            if (!strcmp(meta->name, "select")) {
-                /* xpath content, we have to convert the JSON format into XML first */
-                xml_expr = transform_json2xml(node->schema->module, meta->value_str, 0, &prefs, &nss, &ns_count);
-                if (!xml_expr) {
-                    /* error */
-                    return EXIT_FAILURE;
-                }
-
-                for (i = 0; i < ns_count; ++i) {
-                    ly_print_(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
-                }
-                free(prefs);
-                free(nss);
-            }
-            ly_print_(out, " %s=\"", meta->name);
-        } else {
-#endif
-        /* print the metadata with its namespace */
         mod = meta->annotation->module;
-        ly_print_(ctx->out, " %s:%s=\"", xml_print_ns(ctx, mod->ns, mod->prefix, 1), meta->name);
-#if 0
-    }
-#endif
+        if (filter_attrs && !strcmp(mod->name, "ietf-netconf") && (!strcmp(meta->name, "type") ||
+                !strcmp(meta->name, "select"))) {
+            /* print special NETCONF filter unqualified attributes */
+            ly_print_(ctx->out, " %s=\"", meta->name);
+        } else {
+            /* print the metadata with its namespace */
+            ly_print_(ctx->out, " %s:%s=\"", xml_print_ns(ctx, mod->ns, mod->prefix, 1), meta->name);
+        }
 
         /* print metadata value */
         if (value && value[0]) {
diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c
index 1e74bb9..4183799 100644
--- a/tests/utests/data/test_parser_xml.c
+++ b/tests/utests/data/test_parser_xml.c
@@ -734,6 +734,44 @@
 }
 
 static void
+test_filter_attributes(void **state)
+{
+    const char *data;
+    struct ly_in *in;
+    struct lyd_node *tree;
+    const struct lyd_node *node;
+    const char *dsc;
+    const char *ref = "RFC 6241, Section 7.7";
+    const char *feats[] = {"writable-running", NULL};
+
+    assert_non_null((ly_ctx_load_module(UTEST_LYCTX, "ietf-netconf", "2011-06-01", feats)));
+
+    data = "<get xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "  <filter type=\"xpath\" select=\"/*\"/>\n"
+            "</get>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_RPC_YANG, &tree, NULL));
+    ly_in_free(in, 0);
+    assert_non_null(tree);
+
+    node = tree;
+    dsc = "Retrieve running configuration and device state information.";
+    CHECK_LYSC_ACTION((struct lysc_node_action *)node->schema, dsc, 0, LYS_STATUS_CURR,
+            1, 0, 0, 1, "get", LYS_RPC,
+            1, 0, 0, 0, 0, ref, 0);
+    node = lyd_child(node);
+    dsc = "This parameter specifies the portion of the system\nconfiguration and state data to retrieve.";
+    CHECK_LYSC_NODE(node->schema, dsc, 1, LYS_STATUS_CURR | LYS_IS_INPUT, 1, "filter", 0, LYS_ANYXML, 1, 0, NULL, 0);
+
+    CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS,
+            "<get xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "  <filter type=\"xpath\" select=\"/*\"/>\n"
+            "</get>\n");
+
+    lyd_free_all(tree);
+}
+
+static void
 test_data_skip(void **state)
 {
     const char *data;
@@ -775,6 +813,7 @@
         UTEST(test_netconf_rpc, setup),
         UTEST(test_netconf_action, setup),
         UTEST(test_netconf_reply_or_notification, setup),
+        UTEST(test_filter_attributes, setup),
         UTEST(test_data_skip, setup),
     };