def parse_extinstance(xml_element, context): # get extinstance reference id ref = InstanceId(xml_element.text, None) # pull referred instance element referred_elements = xml_element.xpath( f"//{INSTANCE}[{ID_ATTR}='{ref.id}']") if not referred_elements: # TODO make a single call? msg = f"Dangling reference {ref}" warnings.warn(msg, SyntaxWarning) LOG.warning(msg) return None referred_element = referred_elements[0] # process extinstance template # - we want the individual instances in this case. # may be getting appended to local instances and needs to be a list of instances instance = read_instance(referred_element, context) instances = instance.unroll() # FOREIGNKEY = constrains returned instance set has_foreign_key = len( referred_element.xpath(f'./child::{CONTAINER}/{FOREIGNKEY}')) > 0 if not has_foreign_key: # No screening criteria, return List of resolved instances result = instances else: element = get_children(referred_element, CONTAINER)[0] result = group_extinstances(instances, element, context) return result
def parse_id(context, xml_element, instance_class): keys = None primary_key_elements = xml_element.xpath(f"./{PRIMARYKEY}") if primary_key_elements: keys = parse_identifier_field(context, primary_key_elements[0]) ids = xml_element.xpath(ID_ATTR) if not ids: # Randomly generate an ID for each instance that doesn't have any. id = f"{instance_class.vodml_id}-{str(uuid.uuid4())}" else: id = ids[0] return InstanceId(id, keys)
def parse_idref(xml_element, context): ref = InstanceId(xml_element.text, None) referred_elements = xml_element.xpath( f"//{INSTANCE}[{ID_ATTR}='{ref.id}']") if not referred_elements: # TODO make a single call? msg = f"Dangling reference {ref}" warnings.warn(msg, SyntaxWarning) LOG.warning(msg) return None referred_element = referred_elements[0] return SingleReferenceWrapper(read_instance(referred_element, context))
def parse_foreign_key(xml_element, context): ref = InstanceId(None, parse_identifier_field(context, xml_element)) target_id = xml_element.xpath(f"./{TARGETID}")[0].text referred_elements = xml_element.xpath( f"//*[{ID_ATTR}='{target_id}']/{INSTANCE}") instances = [ read_instance(referred_element, context) for referred_element in referred_elements ] instances_index = { tuple(instance.__vo_id__.keys): instance for instance in instances } references = [instances_index.get(tuple(key), None) for key in ref.keys] return RowReferenceWrapper(references)
def group_extinstances(instances, xml_element, context): # ---------------------------------------------------------------------- # instances: List of resolved EXTINSTANCEs # xml_element: CONTAINER element #---------------------------------------------------------------------- # CONTAINER is a backward connection to a parent instance. # - Contains one of: IDREF, FOREIGNKEY, REMOTEREF # - implementing FOREIGNKEY fk_elements = get_children(xml_element, FOREIGNKEY) for fk_element in fk_elements: target_id = fk_element.xpath(f"./{TARGETID}")[0].text # Get keys associated with each instance instance_keys = parse_identifier_field(context, fk_element) #print("DEBUG: EXTINSTANCE KEYS = %s"%(str(instance_keys))) # Get target keys target = context.get_instance_by_id(InstanceId(target_id, None)) if target is not None: # Untested target_instance_keys = target.__vo_id__.keys else: # Target instance not already processed.. # NOTE: do not want to 'make' it, can set up recursive loop # ie: we are currently processing the Target instance. target_elements = xml_element.xpath( f"//*[{ID_ATTR}='{target_id}']") target_element = target_elements[0] # Resolve PRIMARYKEY value set from target element: # pulled from read_instance() and make() type_id = resolve_type(target_element) element_class = context.get_type_by_id(type_id) target_instance_id = parse_id(context, target_element, element_class) target_instance_keys = target_instance_id.keys #print("DEBUG: TARGET instance keys = %s"%(str(target_instance_keys))) # Have keys resolved.. sort instances # target instance keys are the selection criteria sorted_instances = {} for key in target_instance_keys: matches = [ instances[n] for n in range(len(instances)) if (tuple(key) == tuple(instance_keys[n])) ] sorted_instances[tuple(key)] = matches # We want to return slices of the sorted_instances, with 1 instance per target 'row' # - determine maximumn # matches; this is # slices to return` # - pad each entry to the same length (with None) # - generate slices result = [] max_matches = max([len(matches) for matches in sorted_instances.values()]) for values in sorted_instances.values(): values += [None] * (max_matches - len(values)) for n in range(max_matches): group = [ sorted_instances[tuple(key)][n] for key in target_instance_keys ] result.append(group) return result