'Recursively compares nodes within the XML tree.'

def _compare_node(self, expected, actual, state, idx):

    if (expected.tag != actual.tag):
        return XMLTagMismatch(state, idx, expected.tag, actual.tag)
    with state.node(expected.tag, idx):
        expected_attrs = set(expected.attrib.keys())
        actual_attrs = set(actual.attrib.keys())
        if (expected_attrs != actual_attrs):
            expected_only = (expected_attrs - actual_attrs)
            actual_only = (actual_attrs - expected_attrs)
            return XMLAttrKeysMismatch(state, expected_only, actual_only)
        for key in expected_attrs:
            expected_value = expected.attrib[key]
            actual_value = actual.attrib[key]
            if self.skip_values.intersection([expected_value, actual_value]):
                continue
            elif (expected_value != actual_value):
                return XMLAttrValueMismatch(state, key, expected_value, actual_value)
        text_nodes_mismatch = self._compare_text_nodes(expected, actual, state)
        if text_nodes_mismatch:
            return text_nodes_mismatch
        matched_actual_child_idxs = set()
        first_actual_child_idx = 0
        for expected_child in expected:
            if (expected_child.tag in self.SKIP_TAGS):
                continue
            related_actual_child_idx = None
            if self.allow_mixed_nodes:
                first_actual_child_idx = 0
            for actual_child_idx in range(first_actual_child_idx, len(actual)):
                if (actual[actual_child_idx].tag in self.SKIP_TAGS):
                    first_actual_child_idx += 1
                    continue
                if (actual_child_idx in matched_actual_child_idxs):
                    continue
                result = self._compare_node(expected_child, actual[actual_child_idx], state, actual_child_idx)
                first_actual_child_idx += 1
                if (result is not True):
                    if self.allow_mixed_nodes:
                        continue
                    else:
                        return result
                else:
                    related_actual_child_idx = actual_child_idx
                    break
            if (related_actual_child_idx is not None):
                matched_actual_child_idxs.add(actual_child_idx)
            else:
                return XMLExpectedChild(state, expected_child.tag, (actual_child_idx + 1))
        for (actual_child_idx, actual_child) in enumerate(actual):
            if ((actual_child.tag not in self.SKIP_TAGS) and (actual_child_idx not in matched_actual_child_idxs)):
                return XMLUnexpectedChild(state, actual_child.tag, actual_child_idx)
    return True
