-
Notifications
You must be signed in to change notification settings - Fork 0
/
ypath.py
125 lines (106 loc) · 4.77 KB
/
ypath.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import re
def ypath2xml(ypath, xmlns='', operation=None):
#transforms xpath-like string (ypath) e.g. "/System/eps-items/epId-items/Ep-list/epId=1/nws-items/vni-items/Nw-list[]/vni=10444"
#to xml-like sequence of elements tags:
# <System xmlns="http://cisco.com/ns/yang/cisco-nx-os-device">
# <eps-items>
# <epId-items>
# <Ep-list>
# <epId>1</epId>
# <nws-items>
# <vni-items>
# <Nw-list operation="remove">
# <vni>10444</vni>
# </Nw-list>
# </vni-items>
# </nws-items>
# </Ep-list>
# </epId-items>
# </eps-items>
# </System>
#
# it has optional parameter 'operation' that adds the string 'operation="value"' to the element marked with square brackets '[]'
# operation values corresponds to RFC6241 https://tools.ietf.org/html/rfc6241#page-37
#
# there is a problem with parsing elements that contain the '/' separator within like this tDn element has:
# /System/intf-items/svi-items/If-list/rtvrfMbr-items/tDn=/System/inst-items/Inst-list[name='{vrf_name}']
# the same case if you want to configure a physical interface like Eth103/1/20
#
# as a workaround I mark all the slashes with additional one, then replace that doubleslashes with # sign
# then split ypath and unmark them back
ypath = re.sub(r'//', '#', ypath) # <-- replace doubleslashes with '#'
pl = ypath.split('/')
xmls = f'<{pl[1]} xmlns:x="{xmlns}">' if xmlns else f'<{pl[1]}>'
xmle = f'</{pl[1]}>'
def _ypath2xml(pl):
key = ''
xmls = ''
xmle = ''
operation_set = ("merge", "replace", "create", "delete", "remove")
for i in range(len(pl)):
elem = pl[i]
if "=" in elem:
elem,key = elem.split("=", 1)
key = re.sub(r'#', '/', key) # <-- replace '#' with '/'
xmls += f'<{elem}>{key}</{elem}>'
break
if "[]" in elem:
elem = elem[:-2]
if operation:
if operation not in operation_set:
raise ValueError(f'Incorrect operation value\nmust be one of the following: {", ".join(operation_set)}')
xmls += f'<{elem} operation="{operation}">'
else:
xmls += f'<{elem}>'
else:
xmls += f'<{elem}>'
xmle = f'</{elem}>' + xmle
if key and i < len(pl)-1:
return xmls + _ypath2xml(pl[(i+1)::]) + xmle #recursion
else:
return xmls + xmle
return xmls + _ypath2xml(pl[2::]) + xmle
# decorator is used to wrap xml with outer tags
from functools import wraps
def wrap_in_tag(tag, xmlns=None):
xmls = f'<{tag} xmlns="{xmlns}">' if xmlns else f'<{tag}>'
xmle = f'</{tag}>'
def decorator(func):
@wraps(func)
def wrapped(*args, **kwargs):
if isinstance(args[0], str):
return xmls + f'{func(*args, **kwargs)}' + xmle
if isinstance(args[0], list):
return xmls + ''.join([func(ypath, **kwargs) for ypath in args[0]]) + xmle
else:
raise ValueError('ypath arg should be string or list of strings')
return wrapped
return decorator
@wrap_in_tag("config")
def ypath_config(ypath, xmlns='', operation=None):
return ypath2xml(ypath, xmlns, operation)
@wrap_in_tag("System", "http://cisco.com/ns/yang/cisco-nx-os-device")
def ypath_system(ypath, xmlns='', operation=None):
return ypath2xml(ypath, xmlns, operation)
# pretty print xml string
import xml.dom.minidom
def ppxml(xmlstr):
print(xml.dom.minidom.parseString(xmlstr).toprettyxml(indent=" "))
# helper function to strip ns from xml
# provided by Jeremy Shulman https://github.com/jeremyschulman/xml-tutorial/blob/master/strip-namespaces.md
from lxml import etree
def strip_ns(root: etree.Element) -> etree.Element:
"""
This function removes all namespace information from an XML Element tree
so that a Caller can then use the `xpath` function without having
to deal with the complexities of namespaces.
"""
# first we visit each node in the tree and set the tag name to its localname
# value; thus removing its namespace prefix
for elem in root.getiterator():
if isinstance(elem.tag, str):
elem.tag = etree.QName(elem).localname
# at this point there are no tags with namespaces, so we run the cleanup
# process to remove the namespace definitions from within the tree.
etree.cleanup_namespaces(root)
return root