@staticmethod<EOL><INDENT>def parse_bug_activity(raw_html):<DEDENT>
def is_activity_empty(bs):<EOL><INDENT>EMPTY_ACTIVITY = "<STR_LIT>"<EOL>tag = bs.find(text=re.compile(EMPTY_ACTIVITY))<EOL>return tag is not None<EOL><DEDENT>def find_activity_table(bs):<EOL><INDENT>tables = bs.find_all('<STR_LIT>')<EOL>for tb in tables:<EOL><INDENT>nheaders = len(tb.tr.find_all('<STR_LIT>', recursive=False))<EOL>if nheaders == <NUM_LIT:5>:<EOL><INDENT>return tb<EOL><DEDENT><DEDENT>raise ParseError(cause="<STR_LIT>")<EOL><DEDENT>def remove_tags(bs):<EOL><INDENT>HTML_TAGS_TO_REMOVE = ['<STR_LIT:a>', '<STR_LIT:i>', '<STR_LIT>']<EOL>for tag in bs.find_all(HTML_TAGS_TO_REMOVE):<EOL><INDENT>tag.replaceWith(tag.text)<EOL><DEDENT><DEDENT>def format_text(bs):<EOL><INDENT>strings = [s.strip('<STR_LIT>') for s in bs.stripped_strings]<EOL>s = '<STR_LIT:U+0020>'.join(strings)<EOL>return s<EOL><DEDENT>bs = bs4.BeautifulSoup(raw_html, '<STR_LIT>')<EOL>if is_activity_empty(bs):<EOL><INDENT>fields = []<EOL><DEDENT>else:<EOL><INDENT>activity_tb = find_activity_table(bs)<EOL>remove_tags(activity_tb)<EOL>fields = activity_tb.find_all('<STR_LIT>')<EOL><DEDENT>while fields:<EOL><INDENT>who = fields.pop(<NUM_LIT:0>)<EOL>when = fields.pop(<NUM_LIT:0>)<EOL>n = int(who.get('<STR_LIT>'))<EOL>for _ in range(n):<EOL><INDENT>what = fields.pop(<NUM_LIT:0>)<EOL>removed = fields.pop(<NUM_LIT:0>)<EOL>added = fields.pop(<NUM_LIT:0>)<EOL>event = {'<STR_LIT>': format_text(who),<EOL>'<STR_LIT>': format_text(when),<EOL>'<STR_LIT>': format_text(what),<EOL>'<STR_LIT>': format_text(removed),<EOL>'<STR_LIT>': format_text(added)}<EOL>yield event<EOL><DEDENT><DEDENT>
Parse a Bugzilla bug activity HTML stream.

        This method extracts the information about activity from the
        given HTML stream. The bug activity is stored into a HTML
        table. Each parsed activity event is returned into a dictionary.

        If the given HTML is invalid, the method will raise a ParseError
        exception.

        :param raw_html: HTML string to parse

        :returns: a generator of parsed activity events

        :raises ParseError: raised when an error occurs parsing
            the given HTML stream