def get_failing_line(xml_string, exc_msg):
    """
    Extract the failing line from the XML string, as indicated by the
    line/column information in the exception message.

    Returns a tuple (lineno, colno, new_pos, line), where lineno and colno
    and marker_pos may be None.
    """
    max_before = 500  # max characters before reported position
    max_after = 500  # max characters after reported position
    max_unknown = 1000  # max characters when position cannot be determined
    assert isinstance(xml_string, six.binary_type)
    m = re.search(r':(\d+):(\d+):', exc_msg)
    if not m:
        xml_string, _ = truncate_line(xml_string, 1, 0, max_unknown - 1)
        return None, None, None, xml_string
    lineno = int(m.group(1))
    colno = int(m.group(2))
    if not xml_string.endswith(b'\n'):
        xml_string += b'\n'
    xml_lines = xml_string.splitlines()
    if len(xml_lines) < lineno:
        # This really should not happen; it means the line parsing went wrong
        # or SAX reported incorrect lines. We do not particularly care for
        # this case and simply truncate the string.
        xml_string, _ = truncate_line(xml_string, 1, 0, max_unknown - 1)
        return None, None, None, xml_string
    line = xml_lines[lineno - 1]
    line, new_pos = truncate_line(line, colno, max_before, max_after)
    return lineno, colno, new_pos, line
Extract the failing line from the XML string, as indicated by the
    line/column information in the exception message.

    Returns a tuple (lineno, colno, new_pos, line), where lineno and colno
    and marker_pos may be None.