<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def inline(root_text, base_dir="", replacer=None, ifexists_replacer=None):
 """Inline all input latex files. The inlining is accomplished recursively. All files are opened as UTF-8 unicode files. Parameters root_txt : unicode Text to process (and include in-lined files). base_dir : str Base directory of file containing ``root_text``. Defaults to the current working directory. replacer : function Function called by :func:`re.sub` to replace ``\input`` expressions with a latex document. Changeable only for testing purposes. ifexists_replacer : function Function called by :func:`re.sub` to replace ``\InputIfExists`` expressions with a latex document. Changeable only for testing purposes. Returns ------- txt : unicode Text with referenced files included. """

    def _sub_line(match):
        """Function to be used with re.sub to inline files for each match."""
        fname = match.group(1)
        if not fname.endswith('.tex'):
            full_fname = ".".join((fname, 'tex'))
        else:
            full_fname = fname
        full_path = os.path.abspath(os.path.join(base_dir, full_fname))
        try:
            with codecs.open(full_path, 'r', encoding='utf-8') as f:
                included_text = f.read()
        except IOError:
            # TODO actually do logging here
            print("Cannot open {0} for in-lining".format(full_path))
            return u""
        else:
            # Recursively inline files
            included_text = inline(included_text, base_dir=base_dir)
            return included_text

    def _sub_line_ifexists(match):
        """Function to be used with re.sub for the input_ifexists_pattern."""
        fname = match.group(1)
        if not fname.endswith('.tex'):
            full_fname = ".".join((fname, 'tex'))
        else:
            full_fname = fname
        full_path = os.path.abspath(os.path.join(base_dir, full_fname))

        if os.path.exists(full_path):
            with codecs.open(full_path, 'r', encoding='utf-8') as f:
                included_text = f.read()
            # Append extra info after input
            included_text = "\n".join((included_text, match.group(2)))
        else:
            # Use the fall-back clause in InputIfExists
            included_text = match.group(3)
        # Recursively inline files
        included_text = inline(included_text, base_dir=base_dir)
        return included_text

    # Text processing pipline
    result = remove_comments(root_text)
    result = input_pattern.sub(_sub_line, result)
    result = include_pattern.sub(_sub_line, result)
    result = input_ifexists_pattern.sub(_sub_line_ifexists, result)
    return result