a helper function that will replace a start/end node pair
    by a new containing element, effectively moving all in-between siblings
    This is particularly helpful to replace for /for loops in tables
    with the content resulting from the iteration

    This function call returns None. The parent xml tree is modified in place

    @param start: the starting xml node
    @type start: lxml.etree.Element

    @param end: the ending xml node
    @type end: lxml.etree.Element

    @param new_: the new xml element that will replace the start/end pair
    @type new_: lxlm.etree.Element

    @param keep_start_boundary: Flag to let the function know if it copies
    your start tag to the new_ node or not, Default value is False
    @type keep_start_boundary: bool

    @param keep_end_boundary: Flag to let the function know if it copies
    your end tag to the new_ node or not, Default value is False
    @type keep_end_boundary: bool

    @returns: None

def move_siblings(
        start, end, new_,
        keep_start_boundary=False,
        keep_end_boundary=False
):
    """a helper function that will replace a start/end node pair
    by a new containing element, effectively moving all in-between siblings
    This is particularly helpful to replace for /for loops in tables
    with the content resulting from the iteration

    This function call returns None. The parent xml tree is modified in place

    @param start: the starting xml node
    @type start: lxml.etree.Element

    @param end: the ending xml node
    @type end: lxml.etree.Element

    @param new_: the new xml element that will replace the start/end pair
    @type new_: lxlm.etree.Element

    @param keep_start_boundary: Flag to let the function know if it copies
    your start tag to the new_ node or not, Default value is False
    @type keep_start_boundary: bool

    @param keep_end_boundary: Flag to let the function know if it copies
    your end tag to the new_ node or not, Default value is False
    @type keep_end_boundary: bool

    @returns: None
    """
    old_ = start.getparent()
    if keep_start_boundary:
        new_.append(copy(start))

    else:
        if start.tail:
            # copy the existing tail as text
            new_.text = start.tail

    # get all siblings
    for node in start.itersiblings():
        if node is not end:
            new_.append(node)

        elif node is end:
            # if this is already the end boundary, then we are done
            if keep_end_boundary:
                new_.append(copy(node))

            break

    # replace start boundary with new node
    old_.replace(start, new_)

    # remove ending boundary we already copied it if needed
    old_.remove(end)