diff --git a/cffi/cdefs.h b/cffi/cdefs.h index e5bcc935..681f6326 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -15,6 +15,7 @@ struct ly_ctx; #define LY_CTX_REF_IMPLEMENTED ... #define LY_CTX_SET_PRIV_PARSED ... #define LY_CTX_LEAFREF_EXTENDED ... +#define LY_CTX_LEAFREF_LINKING ... typedef enum { @@ -1117,5 +1118,14 @@ struct lyd_attr { LY_ERR lyd_new_attr(struct lyd_node *, const char *, const char *, const char *, struct lyd_attr **); void lyd_free_attr_single(const struct ly_ctx *ctx, struct lyd_attr *attr); +struct lyd_leafref_links_rec { + const struct lyd_node_term *node; + const struct lyd_node_term **leafref_nodes; + const struct lyd_node_term **target_nodes; +}; + +LY_ERR lyd_leafref_get_links(const struct lyd_node_term *e, const struct lyd_leafref_links_rec **); +LY_ERR lyd_leafref_link_node_tree(struct lyd_node *); + /* from libc, needed to free allocated strings */ void free(void *); diff --git a/libyang/context.py b/libyang/context.py index fa5eb5bf..58a5db47 100644 --- a/libyang/context.py +++ b/libyang/context.py @@ -29,6 +29,7 @@ def __init__( disable_searchdir_cwd: bool = True, explicit_compile: Optional[bool] = False, leafref_extended: bool = False, + leafref_linking: bool = False, yanglib_path: Optional[str] = None, yanglib_fmt: str = "json", cdata=None, # C type: "struct ly_ctx *" @@ -44,6 +45,8 @@ def __init__( options |= lib.LY_CTX_EXPLICIT_COMPILE if leafref_extended: options |= lib.LY_CTX_LEAFREF_EXTENDED + if leafref_linking: + options |= lib.LY_CTX_LEAFREF_LINKING # force priv parsed options |= lib.LY_CTX_SET_PRIV_PARSED diff --git a/libyang/data.py b/libyang/data.py index 2895daaf..7531ff01 100644 --- a/libyang/data.py +++ b/libyang/data.py @@ -18,7 +18,7 @@ SRpc, Type, ) -from .util import DataType, IOType, LibyangError, c2str, str2c +from .util import DataType, IOType, LibyangError, c2str, ly_array_iter, str2c LOG = logging.getLogger(__name__) @@ -972,6 +972,21 @@ def free(self, with_siblings: bool = True) -> None: finally: self.cdata = ffi.NULL + def leafref_link_node_tree(self) -> None: + if self.cdata is None or self.cdata == ffi.NULL: + return + lib.lyd_leafref_link_node_tree(self.cdata) + + def leafref_nodes(self) -> Iterator["DNode"]: + if self.cdata == ffi.NULL: + return + term_node = ffi.cast("struct lyd_node_term *", self.cdata) + out = ffi.new("const struct lyd_leafref_links_rec **") + if lib.lyd_leafref_get_links(term_node, out) != lib.LY_SUCCESS: + return + for n in ly_array_iter(out[0].leafref_nodes): + yield DNode.new(self.context, n) + def __repr__(self): cls = self.__class__ return "<%s.%s: %s>" % (cls.__module__, cls.__name__, str(self)) diff --git a/tests/test_data.py b/tests/test_data.py index a40056c0..e649930f 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -21,6 +21,7 @@ DRpc, IOType, LibyangError, + Module, ) from libyang.data import dict_to_dnode @@ -1036,3 +1037,28 @@ def test_dnode_attrs_set_and_remove_multiple(self): attrs.remove("ietf-netconf:operation") self.assertEqual(len(attrs), 0) + + def test_dnode_leafref_linking(self): + MAIN = """{ + "yolo-leafref-extended:list1": [{ + "leaf1": "val1", + "leaflist2": ["val2", "val3"] + }], + "yolo-leafref-extended:ref1": "val1" + }""" + self.ctx.destroy() + self.ctx = Context(YANG_DIR, leafref_extended=True, leafref_linking=True) + mod = self.ctx.load_module("yolo-leafref-extended") + self.assertIsInstance(mod, Module) + dnode1 = self.ctx.parse_data_mem(MAIN, "json", parse_only=True) + self.assertIsInstance(dnode1, DList) + dnode2 = next(dnode1.siblings(include_self=False)) + self.assertIsInstance(dnode2, DLeaf) + dnode3 = next(dnode1.children()) + self.assertIsInstance(dnode3, DLeaf) + self.assertIsNone(next(dnode3.leafref_nodes(), None)) + dnode2.leafref_link_node_tree() + dnode4 = next(dnode3.leafref_nodes()) + self.assertIsInstance(dnode4, DLeaf) + self.assertEqual(dnode4.cdata, dnode2.cdata) + dnode1.free()