def _read_xml_from_config(self, xml_config): ''' Extract XML information from the XML Configuration object ''' self.xml_config = xml_config self._root_node = xml_config.full_tree.getroot() if xml_config.inherited_tree is None: self._inherited_root = XMLConfiguration().full_tree.getroot() else: self._inherited_root = xml_config.inherited_tree # map id's to nodes for the inherited and the local nodes inherited_ids_to_nodes = dict( (node_identity_string(n), n) for n in self._inherited_root.getiterator()) local_ids_to_nodes = dict((node_identity_string(n), n) for n in self._root_node.getiterator() if not n.get('inherited')) self._shadowing_nodes = {} # join the local and inherited nodes on id-match for id_, node in local_ids_to_nodes.items(): if id_ in inherited_ids_to_nodes: self._shadowing_nodes[node] = inherited_ids_to_nodes[id_] if self.find('./general/project_name') is not None: self.name = self.find('./general/project_name').text else: self.name = 'unnamed_project' os.environ['OPUSPROJECTNAME'] = self.name self.dirty = False
def _read_xml_from_config(self, xml_config): ''' Extract XML information from the XML Configuration object ''' self.xml_config = xml_config self._root_node = xml_config.full_tree.getroot() if xml_config.inherited_tree is None: self._inherited_root = XMLConfiguration().full_tree.getroot() else: self._inherited_root = xml_config.inherited_tree # map id's to nodes for the inherited and the local nodes inherited_ids_to_nodes = dict((node_identity_string(n), n) for n in self._inherited_root.getiterator()) local_ids_to_nodes = dict((node_identity_string(n), n) for n in self._root_node.getiterator() if not n.get('inherited')) self._shadowing_nodes = {} # join the local and inherited nodes on id-match for id_, node in local_ids_to_nodes.items(): if id_ in inherited_ids_to_nodes: self._shadowing_nodes[node] = inherited_ids_to_nodes[id_] if self.find('./general/project_name') is not None: self.name = self.find('./general/project_name').text else: self.name = 'unnamed_project' os.environ['OPUSPROJECTNAME'] = self.name self.dirty = False
def _add_shadowing_nodes(self, root_node, root_node_id): # map id's to nodes for the inherited and the local nodes inherited_ids_to_nodes = dict((node_identity_string(n), n) for n in self._inherited_root.getiterator()) local_ids_to_nodes = dict((root_node_id + node_identity_string(n), n) for n in root_node.getiterator() if not n.get('inherited')) # join the local and inherited nodes on id-match for id_, node in local_ids_to_nodes.items(): if id_ in inherited_ids_to_nodes: shadowing_node = inherited_ids_to_nodes[id_] assert node.tag == shadowing_node.tag self._shadowing_nodes[node] = shadowing_node
def _add_shadowing_nodes(self, root_node, root_node_id): # map id's to nodes for the inherited and the local nodes inherited_ids_to_nodes = dict( (node_identity_string(n), n) for n in self._inherited_root.getiterator()) local_ids_to_nodes = dict((root_node_id + node_identity_string(n), n) for n in root_node.getiterator() if not n.get('inherited')) # join the local and inherited nodes on id-match for id_, node in local_ids_to_nodes.items(): if id_ in inherited_ids_to_nodes: shadowing_node = inherited_ids_to_nodes[id_] assert node.tag == shadowing_node.tag self._shadowing_nodes[node] = shadowing_node
def get_insert_node(merge_node, nodes): nodes.append(merge_node) ins_node = self.find_by_id_string(node_identity_string(merge_node), parent_root) if ins_node is not None: return ins_node merge_node = merge_node.getparent() return get_insert_node(merge_node, nodes)
def get_insert_node(merge_node, nodes): nodes.append(merge_node) ins_node = self.find_by_id_string(node_identity_string(merge_node), parent_root) if ins_node is not None: return ins_node merge_node = merge_node.getparent() return get_insert_node(merge_node, nodes)
def can_copy_to_parent(self, node): if not self.get_last_writable_parent_file(): return False if node.get('inherited'): return False if node_identity_string(node) in self.IMMUTABLE_NODE_IDS: return False return True
def can_copy_to_parent(self, node): if not self.get_last_writable_parent_file(): return False if node.get('inherited'): return False if node_identity_string(node) in self.IMMUTABLE_NODE_IDS: return False return True
def _add_to_shadowing_nodes(self, node): ''' Convenience method to add a node to the shadow map with error checking @param node (Element) node to add to shadow map ''' id_ = node_identity_string(node) inherited_node = self.find_by_id_string(id_, self._inherited_root) if inherited_node is not None: self._shadowing_nodes[node] = inherited_node
def _add_to_shadowing_nodes(self, node): ''' Convenience method to add a node to the shadow map with error checking @param node (Element) node to add to shadow map ''' id_ = node_identity_string(node) inherited_node = self.find_by_id_string(id_, self._inherited_root) if inherited_node is not None: self._shadowing_nodes[node] = inherited_node
def delete_or_update_node(self, node, new_node): ''' Delete or update a node from the XML DOM. If the node was shadowing an inherited node, the inherited node is (re-)inserted into the DOM (after merging with new_node in the case of an update) and returned. Calling delete_or_update_node on an inherited node has no effect. @node (Element) node to remove @new_node (Element) node to insert instead of the removed element; None to remove only @return the (re-inserted) node (Element) or None ''' # The three cases of deleting/updating a node: # The node is local (simplest case -- just remove it) # - if updating, simply add the new node # The node is inherited (no, wait, this is the simplest case -- do nothing) # The node is shadowing an inherited node (remove the node) # - if removing, reinsert the inherited node # - if updating, merge the new node with the inherited node and insert # helper function to clean out all child nodes from shadowing_nodes def clean_shadownodes(node): for child_node in node: clean_shadownodes(child_node) if node in self._shadowing_nodes: del self._shadowing_nodes[node] assert node not in self._shadowing_nodes parent = node.getparent() node_index = parent.index(node) inherited_node = None reinserted_node = new_node if node in self._shadowing_nodes: inherited_node = copy.deepcopy(self._shadowing_nodes[node]) if new_node is None: reinserted_node = inherited_node inherited_node = None elif node.get('inherited'): return else: pass clean_shadownodes(node) node_id = node_identity_string(node) parent.remove(node) if inherited_node is not None: assert new_node is not None XMLConfiguration._merge_nodes(inherited_node, new_node) if new_node is not None: self._add_shadowing_nodes(new_node, node_id) if reinserted_node is not None: parent.insert(node_index, reinserted_node) return reinserted_node
def delete_or_update_node(self, node, new_node): ''' Delete or update a node from the XML DOM. If the node was shadowing an inherited node, the inherited node is (re-)inserted into the DOM (after merging with new_node in the case of an update) and returned. Calling delete_or_update_node on an inherited node has no effect. @node (Element) node to remove @new_node (Element) node to insert instead of the removed element; None to remove only @return the (re-inserted) node (Element) or None ''' # The three cases of deleting/updating a node: # The node is local (simplest case -- just remove it) # - if updating, simply add the new node # The node is inherited (no, wait, this is the simplest case -- do nothing) # The node is shadowing an inherited node (remove the node) # - if removing, reinsert the inherited node # - if updating, merge the new node with the inherited node and insert # helper function to clean out all child nodes from shadowing_nodes def clean_shadownodes(node): for child_node in node: clean_shadownodes(child_node) if node in self._shadowing_nodes: del self._shadowing_nodes[node] assert node not in self._shadowing_nodes parent = node.getparent() node_index = parent.index(node) inherited_node = None reinserted_node = new_node if node in self._shadowing_nodes: inherited_node = copy.deepcopy(self._shadowing_nodes[node]) if new_node is None: reinserted_node = inherited_node inherited_node = None elif node.get('inherited'): return else: pass clean_shadownodes(node) node_id = node_identity_string(node) parent.remove(node) if inherited_node is not None: assert new_node is not None XMLConfiguration._merge_nodes(inherited_node, new_node) if new_node is not None: self._add_shadowing_nodes(new_node, node_id) if reinserted_node is not None: parent.insert(node_index, reinserted_node) return reinserted_node
def merge_templated_nodes_with_project(templated_nodes, new_project): ''' Merge a set of user configured template nodes into a new project. @param templated_nodes ( [xml_node:Element, ...] ) - list of nodes to merge @param new_project (OpusProject) the project in which new nodes are created Created nodes in the new_project are guaranteed to have the same id-path as in the parent tree of the templated node and thus will overwrite any parents configuration in those places. When the node path doesn't exist in the new project, it is created with empty nodes all the way down to the templated node. This causes the new project to inherit attributes and values from the parent project when loaded. ''' for templated_node in templated_nodes: node_id_string = node_identity_string(templated_node) # try to fetch the existing node in the new project node_to_edit = new_project.find_by_id_string(node_id_string) # create it if it isn't there if node_to_edit is None: # get a list of all the nodes between the project root and the templated node. This list can then # be traversed to figure out which nodes that we need to create in the new project in order for # an inserted node to have the same id-path in the templated project and in the new project nodes_to_parent = [] walker_node = templated_node.getparent() while walker_node.getparent() is not None: # walker.parent == None -> walker is project root nodes_to_parent.append(walker_node) walker_node = walker_node.getparent() del walker_node # nodes_to_parent.reverse() # we want to traverse from closest parent to furthest # fetches a reference to the created or existing parent node that we want to attach to node_to_edit_parent_node = _create_or_get_parent(nodes_to_parent, new_project) # attach the node to the parent node_to_edit = SubElement(node_to_edit_parent_node, templated_node.tag, {'name': templated_node.get('name') or ''}) # copy the text from the templated node to the node in the new project node_to_edit.text = templated_node.text
def merge_templated_nodes_with_project(templated_nodes, new_project): ''' Merge a set of user configured template nodes into a new project. @param templated_nodes ( [xml_node:Element, ...] ) - list of nodes to merge @param new_project (OpusProject) the project in which new nodes are created Created nodes in the new_project are guaranteed to have the same id-path as in the parent tree of the templated node and thus will overwrite any parents configuration in those places. When the node path doesn't exist in the new project, it is created with empty nodes all the way down to the templated node. This causes the new project to inherit attributes and values from the parent project when loaded. ''' for templated_node in templated_nodes: node_id_string = node_identity_string(templated_node) # try to fetch the existing node in the new project node_to_edit = new_project.find_by_id_string(node_id_string) # create it if it isn't there if node_to_edit is None: # get a list of all the nodes between the project root and the templated node. This list can then # be traversed to figure out which nodes that we need to create in the new project in order for # an inserted node to have the same id-path in the templated project and in the new project nodes_to_parent = [] walker_node = templated_node.getparent() while walker_node.getparent() is not None: # walker.parent == None -> walker is project root nodes_to_parent.append(walker_node) walker_node = walker_node.getparent() del walker_node # nodes_to_parent.reverse() # we want to traverse from closest parent to furthest # fetches a reference to the created or existing parent node that we want to attach to node_to_edit_parent_node = _create_or_get_parent(nodes_to_parent, new_project) # attach the node to the parent node_to_edit = SubElement(node_to_edit_parent_node, templated_node.tag, {'name': templated_node.get('name') or ''}) # copy the text from the templated node to the node in the new project node_to_edit.text = templated_node.text
def _create_or_get_parent(nodes_to_parent, new_project): '''helper method to create a node path in the new project like the one of the templated node''' if nodes_to_parent == []: # reached the project root return new_project.root_node() # check if the new project contains a node at the current position current_node_id_string = node_identity_string(nodes_to_parent[0]) current_node = new_project.find_by_id_string(current_node_id_string) if current_node is not None: return current_node # this was an existing node so we can build down from here # the current_node doesn't existing in the new project. Extract the missing node's tag and name from the # templated node, get the (created or existing) parent node, and create the current node with the missing # tag and name missing_node = nodes_to_parent.pop(0) # consume the closest node missing_node_tag, missing_node_name = missing_node.tag, missing_node.get('name', None) parent_node = _create_or_get_parent(nodes_to_parent, new_project) # continue up the tree if missing_node_name is not None: current_node = SubElement(parent_node, missing_node_tag, {'name': missing_node_name}) else: # parent node did not have a name attribute so this node shouldn't either current_node = SubElement(parent_node, missing_node_tag) return current_node
def _create_or_get_parent(nodes_to_parent, new_project): '''helper method to create a node path in the new project like the one of the templated node''' if nodes_to_parent == []: # reached the project root return new_project.root_node() # check if the new project contains a node at the current position current_node_id_string = node_identity_string(nodes_to_parent[0]) current_node = new_project.find_by_id_string(current_node_id_string) if current_node is not None: return current_node # this was an existing node so we can build down from here # the current_node doesn't existing in the new project. Extract the missing node's tag and name from the # templated node, get the (created or existing) parent node, and create the current node with the missing # tag and name missing_node = nodes_to_parent.pop(0) # consume the closest node missing_node_tag, missing_node_name = missing_node.tag, missing_node.get('name', None) parent_node = _create_or_get_parent(nodes_to_parent, new_project) # continue up the tree if missing_node_name is not None: current_node = SubElement(parent_node, missing_node_tag, {'name': missing_node_name}) else: # parent node did not have a name attribute so this node shouldn't either current_node = SubElement(parent_node, missing_node_tag) return current_node
def insert_node(self, node, parent_node, row = 0): ''' Insert a node into the XML DOM tree. Can only insert nodes that are not already in the tree (identified by their id string). @param node (Element) node to insert (all child nodes are also inserted) @param parent_node (Element) the parent to attach the node to @param row (int) insert the new node as row:th child of parent_node @return the inserted node (Element) or None if the node was not inserted (already exists) ''' # check if there are any nodes with this nodes id string future_id = node_identity_string(parent_node) + element_id(node) try: existing_node = self.find_by_id_string(future_id, parent_node.getroottree().getroot()) except LookupError: # nodes with this id already exists print "LookupError" return None if existing_node is not None: return None # don't allow overwriting existing nodes # Insert the node parent_node.insert(row, node) self.make_local(node) return node
def insert_node(self, node, parent_node, row=0): ''' Insert a node into the XML DOM tree. Can only insert nodes that are not already in the tree (identified by their id string). @param node (Element) node to insert (all child nodes are also inserted) @param parent_node (Element) the parent to attach the node to @param row (int) insert the new node as row:th child of parent_node @return the inserted node (Element) or None if the node was not inserted (already exists) ''' # check if there are any nodes with this nodes id string future_id = node_identity_string(parent_node) + element_id(node) try: existing_node = self.find_by_id_string( future_id, parent_node.getroottree().getroot()) except LookupError: # nodes with this id already exists print "LookupError" return None if existing_node is not None: return None # don't allow overwriting existing nodes # Insert the node parent_node.insert(row, node) self.make_local(node) return node
def copy_to_parent(self, node): ''' Copies a local node to the parent configuration without deleting it. @node (Element) to copy to parent ''' # Helper routines: # Never copy description and parent nodes to parent def delete_immutable(): id_strings = self.IMMUTABLE_NODE_IDS for id_string in id_strings: for n in self.find_all_by_id_string(id_string, clone): n.getparent().remove(n) # Find deepest parent node of to-be-inserted node that is also in the parent config def get_insert_node(merge_node, nodes): nodes.append(merge_node) ins_node = self.find_by_id_string(node_identity_string(merge_node), parent_root) if ins_node is not None: return ins_node merge_node = merge_node.getparent() return get_insert_node(merge_node, nodes) # Remove all children for all nodes in the nodes list def strip_children(nodes): for n in nodes[:-1]: for subelement in n.getparent().getchildren(): if subelement is not n: n.getparent().remove(subelement) # Remove "inherited" attribute from all nodes below tree_node that were # introduced by the immediate parent. Remove all inherited nodes # that were introduced by a grandparent -- copying them to the parent # leads to incorrect results. def clear_inherited_attribute_or_delete_inherited_nodes(tree_node): nodes_to_delete = [] for node in tree_node.iterchildren(): if node.get('inherited') is not None: if node.get('inherited') == parent_name: del node.attrib['inherited'] clear_inherited_attribute_or_delete_inherited_nodes( node) else: nodes_to_delete.append(node) else: clear_inherited_attribute_or_delete_inherited_nodes(node) for node in nodes_to_delete: tree_node.remove(node) #work on clone_node id_string = node_identity_string(node) clone = copy.deepcopy(self.root_node()) node = self.find_by_id_string(id_string, clone) #get parent project parent_file = self.get_last_writable_parent_file() parent_project = OpusProject() parent_project.open(parent_file) parent_name = parent_project.xml_config.name parent_root = parent_project.xml_config.tree.getroot() delete_immutable() parents_to_insert = [] if node is not clone: insert_node = get_insert_node(node, parents_to_insert) node = parents_to_insert[-1] strip_children(parents_to_insert) clear_inherited_attribute_or_delete_inherited_nodes(node) XMLConfiguration._merge_nodes(insert_node, node) insert_parent = insert_node.getparent() insert_parent.replace(insert_node, node) # using parent_project.save() adds unnecessary attributes for some reason. parent_project.xml_config.save_as(parent_file)
def copy_to_parent(self, node): ''' Copies a local node to the parent configuration without deleting it. @node (Element) to copy to parent ''' # Helper routines: # Never copy description and parent nodes to parent def delete_immutable(): id_strings = self.IMMUTABLE_NODE_IDS for id_string in id_strings: for n in self.find_all_by_id_string(id_string, clone): n.getparent().remove(n) # Find deepest parent node of to-be-inserted node that is also in the parent config def get_insert_node(merge_node, nodes): nodes.append(merge_node) ins_node = self.find_by_id_string(node_identity_string(merge_node), parent_root) if ins_node is not None: return ins_node merge_node = merge_node.getparent() return get_insert_node(merge_node, nodes) # Remove all children for all nodes in the nodes list def strip_children(nodes): for n in nodes[:-1]: for subelement in n.getparent().getchildren(): if subelement is not n: n.getparent().remove(subelement) # Remove "inherited" attribute from all nodes below tree_node that were # introduced by the immediate parent. Remove all inherited nodes # that were introduced by a grandparent -- copying them to the parent # leads to incorrect results. def clear_inherited_attribute_or_delete_inherited_nodes(tree_node): nodes_to_delete = [] for node in tree_node.iterchildren(): if node.get('inherited') is not None: if node.get('inherited') == parent_name: del node.attrib['inherited'] clear_inherited_attribute_or_delete_inherited_nodes(node) else: nodes_to_delete.append(node) else: clear_inherited_attribute_or_delete_inherited_nodes(node) for node in nodes_to_delete: tree_node.remove(node) #work on clone_node id_string = node_identity_string(node) clone = copy.deepcopy(self.root_node()) node = self.find_by_id_string(id_string, clone) #get parent project parent_file = self.get_last_writable_parent_file() parent_project = OpusProject() parent_project.open(parent_file) parent_name = parent_project.xml_config.name parent_root = parent_project.xml_config.tree.getroot() delete_immutable() parents_to_insert = [] if node is not clone: insert_node = get_insert_node(node, parents_to_insert) node = parents_to_insert[-1] strip_children(parents_to_insert) clear_inherited_attribute_or_delete_inherited_nodes(node) XMLConfiguration._merge_nodes(insert_node, node) insert_parent = insert_node.getparent() insert_parent.replace(insert_node, node) # using parent_project.save() adds unnecessary attributes for some reason. parent_project.xml_config.save_as(parent_file)