def clean_node(parent_map, node, ignored=None):
    """
    Remove the node from its parent if it has no attribute but the ignored ones, no text and no child.
    Recursively called up to the document root to ensure no empty node is left.

    :param parent_map: dictionary mapping each node to its parent
    :param node: the node to clean
    :param ignored: a list of ignored attributes.
    :return: True if anything has been removed, False otherwise
    """
    has_text = node.text is not None and node.text.strip()
    parent = parent_map.get(node)
    removed = False
    if (
        len(node.attrib.keys() - (ignored or [])) == 0
        and not list(node)
        and not has_text
        and parent
    ):
        parent.remove(node)
        removed = True
    # Clean parent nodes if needed
    if parent is not None:
        parent_cleaned = clean_node(parent_map, parent, ignored)
        removed = removed or parent_cleaned
    return removed