Parses the DOM and returns anime character attributes in the sidebar.

    :type character_page: :class:`bs4.BeautifulSoup`
    :param character_page: MAL anime character page's DOM

    :rtype: dict
    :return: anime character attributes

    :raises: :class:`.InvalidAnimeError`, :class:`.MalformedAnimePageError`

def parse_characters(self, character_page):
    """Parses the DOM and returns anime character attributes in the sidebar.

    :type character_page: :class:`bs4.BeautifulSoup`
    :param character_page: MAL anime character page's DOM

    :rtype: dict
    :return: anime character attributes

    :raises: :class:`.InvalidAnimeError`, :class:`.MalformedAnimePageError`

    """
    anime_info = self.parse_sidebar(character_page)

    try:
      character_title = filter(lambda x: 'Characters & Voice Actors' in x.text, character_page.find_all(u'h2'))
      anime_info[u'characters'] = {}
      anime_info[u'voice_actors'] = {}
      if character_title:
        character_title = character_title[0]
        curr_elt = character_title.nextSibling
        while True:
          if curr_elt.name != u'table':
            break
          curr_row = curr_elt.find(u'tr')
          # character in second col, VAs in third.
          (_, character_col, va_col) = curr_row.find_all(u'td', recursive=False)

          character_link = character_col.find(u'a')
          character_name = ' '.join(reversed(character_link.text.split(u', ')))
          link_parts = character_link.get(u'href').split(u'/')
          # of the form /character/7373/Holo
          character = self.session.character(int(link_parts[2])).set({'name': character_name})
          role = character_col.find(u'small').text
          character_entry = {'role': role, 'voice_actors': {}}

          va_table = va_col.find(u'table')
          if va_table:
            for row in va_table.find_all(u'tr'):
              va_info_cols = row.find_all(u'td')
              if not va_info_cols:
                # don't ask me why MAL has an extra blank table row i don't know!!!
                continue
              va_info_col = va_info_cols[0]
              va_link = va_info_col.find(u'a')
              if va_link:
                va_name = ' '.join(reversed(va_link.text.split(u', ')))
                link_parts = va_link.get(u'href').split(u'/')
                # of the form /people/70/Ami_Koshimizu
                person = self.session.person(int(link_parts[2])).set({'name': va_name})
                language = va_info_col.find(u'small').text
                anime_info[u'voice_actors'][person] = {'role': role, 'character': character, 'language': language}
                character_entry[u'voice_actors'][person] = language
          anime_info[u'characters'][character] = character_entry
          curr_elt = curr_elt.nextSibling
    except:
      if not self.session.suppress_parse_exceptions:
        raise

    try:
      staff_title = filter(lambda x: 'Staff' in x.text, character_page.find_all(u'h2'))
      anime_info[u'staff'] = {}
      if staff_title:
        staff_title = staff_title[0]
        staff_table = staff_title.nextSibling.nextSibling
        for row in staff_table.find_all(u'tr'):
          # staff info in second col.
          info = row.find_all(u'td')[1]
          staff_link = info.find(u'a')
          staff_name = ' '.join(reversed(staff_link.text.split(u', ')))
          link_parts = staff_link.get(u'href').split(u'/')
          # of the form /people/1870/Miyazaki_Hayao
          person = self.session.person(int(link_parts[2])).set({'name': staff_name})
          # staff role(s).
          anime_info[u'staff'][person] = set(info.find(u'small').text.split(u', '))
    except:
      if not self.session.suppress_parse_exceptions:
        raise
    
    return anime_info