def get_payload(self, update): """Construct dict that will be converted to json_val in Update. Args: update (list): dicts with xpath in gNMI format, nodetypes, values. Returns: dict """ json_val = {} processed_xp = [] for node in update: xp = node['xpath'] if xp.endswith(']'): continue if xp in processed_xp: continue jval = json_val collect_key = False key_elem = None tokenized = xpath_tokenizer_re.findall(xp) for i, seg in enumerate(tokenized, 1): token, elem = seg if token in ['/', '=']: continue if not token and not collect_key and elem: if len(tokenized) == i: jval[elem] = node['value'] else: if elem not in jval: jval[elem] = {} jval = jval[elem] continue if token == '[': collect_key = True continue if token == ']': collect_key = False continue if key_elem is not None and token: jval[key_elem] = token.strip('"') key_elem = None continue if collect_key and elem: key_elem = elem continue processed_xp.append(xp) return json_val
def parse_xpath_to_gnmi_path(self, xpath, origin=None): """Parses an XPath to proto.gnmi_pb2.Path. This function should be overridden by any child classes for origin logic. Effectively wraps the std XML XPath tokenizer and traverses the identified groups. Parsing robustness needs to be validated. Probably best to formalize as a state machine sometime. TODO: Formalize tokenizer traversal via state machine. """ if not isinstance(xpath, string_types): raise Exception("xpath must be a string!") path = proto.gnmi_pb2.Path() if origin: if not isinstance(origin, string_types): raise Exception("origin must be a string!") path.origin = origin curr_elem = proto.gnmi_pb2.PathElem() in_filter = False just_filtered = False curr_key = None # TODO: Lazy xpath = xpath.strip("/") xpath_elements = xpath_tokenizer_re.findall(xpath) for index, element in enumerate(xpath_elements): # stripped initial /, so this indicates a completed element if element[0] == "/": if not curr_elem.name: raise Exception( "Current PathElem has no name yet is trying to be pushed to path! Invalid XPath?" ) path.elem.append(curr_elem) curr_elem = proto.gnmi_pb2.PathElem() continue # We are entering a filter elif element[0] == "[": in_filter = True continue # We are exiting a filter elif element[0] == "]": in_filter = False continue # If we're not in a filter then we're a PathElem name elif not in_filter: curr_elem.name = element[1] # Skip blank spaces elif not any([element[0], element[1]]): continue # If we're in the filter and just completed a filter expr, # "and" as a junction should just be ignored. elif in_filter and just_filtered and element[1] == "and": just_filtered = False continue # Otherwise we're in a filter and this term is a key name elif curr_key is None: curr_key = element[1] continue # Otherwise we're an operator or the key value elif curr_key is not None: # I think = is the only possible thing to support with PathElem syntax as is if element[0] in [">", "<"]: raise Exception("Only = supported as filter operand!") if element[0] == "=": continue else: # We have a full key here, put it in the map if curr_key in curr_elem.key.keys(): raise Exception("Key already in key map!") curr_elem.key[curr_key] = element[0].strip("'\"") curr_key = None just_filtered = True # Keys/filters in general should be totally cleaned up at this point. if curr_key: raise Exception("Hanging key filter! Incomplete XPath?") # If we have a dangling element that hasn't been completed due to no # / element then let's just append the final element. if curr_elem: path.elem.append(curr_elem) curr_elem = None if any([curr_elem, curr_key, in_filter]): raise Exception("Unfinished elements in XPath parsing!") return path