def resolve_replace2(prev_item, replace, next_item, junk=[]):
    items_actual = replace.items_actual

    i = 0
    phrases = []

    # For a replace, distinguish between (sub)phrases that could be rearranged 
    # and that couldn't be rearranged.
    # (simplified) example: Let 'items_actual' = "foo bar baz boo" and say the 
    # phrase "bar baz" could be rearranged and the phrase "boo" could be 
    # rearranged (to another position).
    # So, compute the phrases ["foo", "bar baz", "boo"].
    while i < len(items_actual):
        item = items_actual[i]
        if item.run:
            rearr = Rearr()
            while i < len(items_actual) and items_actual[i].run and items_actual[i].run == item.run:
                rearr.append(items_actual[i])
                i += 1
            phrases.append(rearr)
        else:
            repl = Repl()
            while i < len(items_actual) and not items_actual[i].run:
                repl.append(items_actual[i])
                i += 1
            phrases.append(repl)

    # Setup the diff.Replace and diff.Rearrange objects.


    result = []
    current_rearrange = None
    current_replace = None
    last_rearrange = None
    for i in range(0, len(phrases)):
        prev_phrase = phrases[i - 1] if i > 0 else None
        phrase = phrases[i]
        next_phrase = phrases[i + 1] if i < len(phrases) - 1 else None

        if isinstance(phrase, Repl):
            # Process the phrases that couldn't be rearranged:
            if prev_phrase:
                # prev_phrase is definitely a rearrange!
                # Check if the rearrange position of prev phrase is followed by
                # a junk item.
                last_target_item = prev_phrase[-1].match
                next_target_item = last_target_item.next
                if util.ignore_item(next_target_item, junk):
                    continue
            elif next_phrase:
                # next_phrase is definitely a rearrange!
                # Check if the rearrange position of prev phrase is preceded by
                # a junk item.
                first_target_item = next_phrase[0].match
                prev_target_item = first_target_item.prev
                if util.ignore_item(prev_target_item, junk):
                    continue

            # Create new replace (consider remaining target items!).
            if not current_replace:
                current_replace = diff.Replace([], [], 0, 0)
                result.append(current_replace)
            current_replace.items_actual.extend(phrase)
            current_rearrange = None
        elif isinstance(phrase, Rearr):
            # Rearrange
            if not current_rearrange:
                current_rearrange = Rearrange()
                current_rearrange.items_actual.extend(phrase)
                # TODO: Which target items?
                current_rearrange.items_target.extend([x.match for x in phrase])
                #current_rearrange.run = item.run
                result.append(current_rearrange)
            else:
                if last_rearrange:
                    xxx = last_rearrange[-1].match
                    while True:
                        if xxx and util.ignore_item(xxx.next, junk):
                            xxx = xxx.next
                        else:
                            break

                    if xxx and xxx == phrase[0].match.prev:
                        # Append
                        current_rearrange.items_actual.extend(phrase)
                        # TODO: Which target items?
                        current_rearrange.items_target.extend([x.match for x in phrase])
                    else:
                        current_rearrange = Rearrange()
                        current_rearrange.items_actual.extend(phrase)
                        # TODO: Which target items?
                        current_rearrange.items_target.extend([x.match for x in phrase])
                        #current_rearrange.run = item.run
                        result.append(current_rearrange)

            last_rearrange = phrase

    # Don't forget remaining target items.
    x = [y for y in replace.items_target if not y.match]
    if x:
        if not current_replace:
            current_replace = diff.Replace([], [], 0, 0)
            result.append(current_replace)
        current_replace.items_target.extend(replace.items_target)

    return result
def resolve_replace(prev_item, replace, next_item, junk=[]):
    """ Resolves the given replace to a sequence of diff.Replace and 
    rearrange.Rearrange objects. """

    # __________________________________________________________________________
    # Identify the items that couldn't be rearranged and all items that could
    # be rearranged in any run. The result is a list of all "unrearranged"
    # items and a list of list of all rearranged items.
    #    
    # simplified example: 
    # Let items_actual = "foo bar baz boo far faz" and lets say "bar baz" and 
    # "boo far" could be rearranged to (different) positions. We compute:
    # 
    # unrearranged_items = ["foo", "faz"] and
    # rearranged_items_lists = [["bar", "baz"], ["boo", "far"]]

    i = 0
    unrearranged_items = []
    rearranged_items_lists = []
    while i < len(replace.items_actual):
        item = replace.items_actual[i]

        if item.run:
            # The item has a run and thus could be rearranged. Proceed as long
            # next items belongs to the same run.
            rearranged_items = [item]
            while i + 1 < len(replace.items_actual):
                next_item = replace.items_actual[i + 1]

                # Abort if next item has no run or belongs to another run.
                if not next_item.run or next_item.run != item.run:
                    break

                rearranged_items.append(next_item)
                i += 1
            # Append the rearranged items to result if it is non-empty.            
            if rearranged_items:         
                rearranged_items_lists.append(rearranged_items)
        else:
            # The item has no run and thus couldn't be rearranged. Add it to 
            # unrearranged items.
            unrearranged_items.append(item)
        i += 1

    # __________________________________________________________________________
    # Split the (unmatched) target items into sequences of junk and non-junk 
    # items. 
    #    
    # simplified example: 
    # Let items_target = "foo bar JUNK1 baz JUNK2" with junk words "JUNK1" and 
    # "JUNK2". We compute:
    # 
    # junk_target_items = ["JUNK1", "JUNK2"] and 
    # non_junk_target_items = ["foo", "bar", "baz"]

    # Obtain all unmatched target items.
    unmatched_target_items = [x for x in replace.items_target if not x.match]

    junk_target_items = []
    non_junk_target_items = []
        
    for i in range(0, len(unmatched_target_items)):
        item = unmatched_target_items[i]

        if util.ignore_item(item, junk):
            # The item is a junk item.
            junk_target_items.append(item)
        else:
            # The item is not a junk item.
            non_junk_target_items.append(item)

    # __________________________________________________________________________
    # Setup the sequence of diff.Replace and rearrange.Rearrange objects.

    result = []
     
    if not junk_target_items:    
        # There are no junk items. So create a single replace object with the
        # unrearranged_items as actual items and the non_junk_target_items as 
        # the target items.        
        replace = diff.Replace([], [], 0, 0)
        replace.items_actual.extend(unrearranged_items)          
        replace.items_target.extend(non_junk_target_items)
        result.append(replace)
    else:
        # There is at least one junk item. We will assign each unrearranged 
        # actual item to a junk item. Because the junk items will be ignored 
        # from further process, we can ignore all unrearranged items.
        for i in range(0, len(junk_target_items)):
            replace = diff.Replace([], [], 0, 0)
            replace.items_target.append(junk_target_items[i])
            result.append(replace)

        # Create a single replace object that considers the remaining non junk
        # target items.
        if non_junk_target_items:
            replace = diff.Replace([], [], 0, 0)
            replace.items_target.extend(non_junk_target_items)
            result.append(replace)

    # Iterate through the rearranged lists and setup the rearrange.Rearrange 
    # objects.
    for i in range(0, len(rearranged_items_lists)):  
        rearrange_items = rearranged_items_lists[i]
        rearrange = Rearrange()
        rearrange.items_actual.extend(rearrange_items)
        # Setup the target items (that are the matched items).            
        rearrange.items_target.extend([x.match for x in rearrange_items])
        result.append(rearrange)

    return result