node_sub

        Low-level api: Compute the delta of two configs. This method is
        recursive. Assume two configs are different.

        Parameters
        ----------

        node_self : `Element`
            A config node in a config tree that is being processed. node_self
            cannot be a leaf node.

        node_other : `Element`
            A config node in another config tree that is being processed.

        Returns
        -------

        None
            There is no return of this method.

def node_sub(self, node_self, node_other):
        '''node_sub

        Low-level api: Compute the delta of two configs. This method is
        recursive. Assume two configs are different.

        Parameters
        ----------

        node_self : `Element`
            A config node in a config tree that is being processed. node_self
            cannot be a leaf node.

        node_other : `Element`
            A config node in another config tree that is being processed.

        Returns
        -------

        None
            There is no return of this method.
        '''

        def same_leaf_list(tag):
            list_self = [c for c in list(node_self) if c.tag == tag]
            list_other = [c for c in list(node_other) if c.tag == tag]
            s_node = self.device.get_schema_node((list_self + list_other)[0])
            if s_node.get('ordered-by') == 'user':
                if [i.text for i in list_self] == [i.text for i in list_other]:
                    return True
                else:
                    return False
            else:
                if set([i.text for i in list_self]) == \
                   set([i.text for i in list_other]):
                    return True
                else:
                    return False

        if self.preferred_replace != 'merge':
            t_self = [c.tag for c in list(node_self) \
                      if self.device.get_schema_node(c).get('type') == \
                         'leaf-list']
            t_other = [c.tag for c in list(node_other) \
                       if self.device.get_schema_node(c).get('type') == \
                          'leaf-list']
            commonalities = set(t_self) & set(t_other)
            for commonality in commonalities:
                if not same_leaf_list(commonality):
                    node_self.set(operation_tag, 'replace')
                    node_other.set(operation_tag, 'replace')
                    return

        in_s_not_in_o, in_o_not_in_s, in_s_and_in_o = \
            self._group_kids(node_self, node_other)
        ordered_by_user = {}
        for child_self in in_s_not_in_o:
            child_other = etree.Element(child_self.tag,
                                        {operation_tag: self.preferred_delete},
                                        nsmap=child_self.nsmap)
            if self.preferred_create != 'merge':
                child_self.set(operation_tag, self.preferred_create)
            siblings = list(node_other.iterchildren(tag=child_self.tag))
            if siblings:
                siblings[-1].addnext(child_other)
            else:
                node_other.append(child_other)
            s_node = self.device.get_schema_node(child_self)
            if s_node.get('type') == 'leaf-list':
                if s_node.get('ordered-by') == 'user' and \
                   s_node.tag not in ordered_by_user:
                    ordered_by_user[s_node.tag] = 'leaf-list'
                child_other.text = child_self.text
            elif s_node.get('type') == 'list':
                keys = self._get_list_keys(s_node)
                if s_node.get('ordered-by') == 'user' and \
                   s_node.tag not in ordered_by_user:
                    ordered_by_user[s_node.tag] = keys
                for key in keys:
                    key_node = child_self.find(key)
                    e = etree.SubElement(child_other, key, nsmap=key_node.nsmap)
                    e.text = key_node.text
        for child_other in in_o_not_in_s:
            child_self = etree.Element(child_other.tag,
                                       {operation_tag: self.preferred_delete},
                                       nsmap=child_other.nsmap)
            if self.preferred_create != 'merge':
                child_other.set(operation_tag, self.preferred_create)
            siblings = list(node_self.iterchildren(tag=child_other.tag))
            if siblings:
                siblings[-1].addnext(child_self)
            else:
                node_self.append(child_self)
            s_node = self.device.get_schema_node(child_other)
            if s_node.get('type') == 'leaf-list':
                if s_node.get('ordered-by') == 'user' and \
                   s_node.tag not in ordered_by_user:
                    ordered_by_user[s_node.tag] = 'leaf-list'
                child_self.text = child_other.text
            elif s_node.get('type') == 'list':
                keys = self._get_list_keys(s_node)
                if s_node.get('ordered-by') == 'user' and \
                   s_node.tag not in ordered_by_user:
                    ordered_by_user[s_node.tag] = keys
                for key in keys:
                    key_node = child_other.find(key)
                    e = etree.SubElement(child_self, key, nsmap=key_node.nsmap)
                    e.text = key_node.text
        for child_self, child_other in in_s_and_in_o:
            s_node = self.device.get_schema_node(child_self)
            if s_node.get('type') == 'leaf':
                if child_self.text == child_other.text:
                    if not s_node.get('is_key'):
                        node_self.remove(child_self)
                        node_other.remove(child_other)
                else:
                    if self.preferred_replace != 'merge':
                        child_self.set(operation_tag, self.preferred_replace)
                        child_other.set(operation_tag, self.preferred_replace)
            elif s_node.get('type') == 'leaf-list':
                if s_node.get('ordered-by') == 'user':
                    if s_node.tag not in ordered_by_user:
                        ordered_by_user[s_node.tag] = 'leaf-list'
                else:
                    node_self.remove(child_self)
                    node_other.remove(child_other)
            elif s_node.get('type') == 'container':
                if self._node_le(child_self, child_other) and \
                   self._node_le(child_other, child_self):
                    node_self.remove(child_self)
                    node_other.remove(child_other)
                else:
                    self.node_sub(child_self, child_other)
            elif s_node.get('type') == 'list':
                if s_node.get('ordered-by') == 'user' and \
                   s_node.tag not in ordered_by_user:
                    ordered_by_user[s_node.tag] = self._get_list_keys(s_node)
                if self._node_le(child_self, child_other) and \
                   self._node_le(child_other, child_self):
                    if s_node.get('ordered-by') == 'user':
                        for child in child_self.getchildren():
                            schema_node = self.device.get_schema_node(child)
                            if not schema_node.get('is_key'):
                                child_self.remove(child)
                        for child in child_other.getchildren():
                            schema_node = self.device.get_schema_node(child)
                            if not schema_node.get('is_key'):
                                child_other.remove(child)
                    else:
                        node_self.remove(child_self)
                        node_other.remove(child_other)
                else:
                    self.node_sub(child_self, child_other)
            else:
                path = self.device.get_xpath(s_node)
                raise ModelError("unknown schema node type: type of node {}" \
                                 "is '{}'".format(path, s_node.get('type')))
        for tag in ordered_by_user:
            scope_s = in_s_not_in_o + in_s_and_in_o
            scope_o = in_o_not_in_s + in_s_and_in_o
            for sequence in self._get_sequence(scope_s, tag, node_self), \
                            self._get_sequence(scope_o, tag, node_other):
                for item in sequence:
                    # modifying the namespace mapping of a node is not possible
                    # in lxml. See https://bugs.launchpad.net/lxml/+bug/555602
                    # if 'yang' not in item.nsmap:
                    #     item.nsmap['yang'] = yang_url
                    i = sequence.index(item)
                    if i == 0:
                        item.set(insert_tag, 'first')
                    else:
                        item.set(insert_tag, 'after')
                        precursor = sequence[i - 1]
                        if ordered_by_user[tag] == 'leaf-list':
                            item.set(value_tag, precursor.text)
                        else:
                            keys = ordered_by_user[tag]
                            key_nodes = {k: precursor.find(k) for k in keys}
                            ids = {k: self._url_to_prefix(n, k) \
                                   for k, n in key_nodes.items()}
                            l = ["[{}='{}']".format(ids[k], key_nodes[k].text) \
                                 for k in keys]
                            item.set(key_tag, ''.join(l))