def parse(self):
        """Parse our string and return a Survey object, None, or raise :exc:`ParseException`"""
        if not self.survey_str:
            return None
        lines = self.survey_str.splitlines()
        if len(lines) < 10:
            raise ParseException("Expected at least 10 lines in a Compass Survey, only found %d!\nlines=%s" % (len(lines), lines))

        # undelimited Cave Name may be empty string and "skipped"
        first_line = lines.pop(0).strip()
        if first_line.startswith('SURVEY NAME:'):
            cave_name = ''
            name = first_line.strip('SURVEY NAME:').strip()
        else:
            cave_name = first_line
            name = lines.pop(0).split('SURVEY NAME:', 1)[1].strip()

        # Date and Comment on one line, Comment may be missing
        date_comment_toks = lines.pop(0).split('SURVEY DATE:', 1)[1].split('COMMENT:')
        date = CompassSurveyParser._parse_date(date_comment_toks[0])
        comment = date_comment_toks[1].strip() if len(date_comment_toks) > 1 else ''

        lines.pop(0)  # SURVEY TEAM:\n (actual team members are on the next line)
        team = [member.strip() for member in lines.pop(0).split(',')]  # We're already decoding from windows-1252 codec so we have unicode for names like 'Tanya Pietra\xdf'

        # TODO: implement format (units!), instrument correction(s)
        dec_fmt_corr = lines.pop(0)
        declination, fmt, corrections, corrections2 = CompassSurveyParser._parse_declination_line(dec_fmt_corr)

        lines.pop(0)
        shot_header = lines.pop(0).split()
        val_count = len(shot_header) - 2 if 'FLAGS' in shot_header else len(shot_header)  # 1998 vintage data has no FLAGS, COMMENTS at end
        lines.pop(0)

        survey = Survey(name=name, date=date, comment=comment, team=team, cave_name=cave_name,
                        shot_header=shot_header, declination=declination,
                        file_format=fmt, corrections=corrections, corrections2=corrections2)

        shots = []
        shot_lines = lines
        for shot_line in shot_lines:
            shot_vals = shot_line.split(None, val_count)

            if len(shot_vals) > val_count:  # last two spare columns are FLAGS and COMMENTS, either value may be missing
                flags_comment = shot_vals.pop()
                if not flags_comment.startswith('#|'):
                    flags, comment = '', flags_comment
                else:
                    try:
                        flags, comment = flags_comment.split('#|', 1)[1].split('#', 1)
                    except ValueError:
                        raise ParseException('Invalid flags in %s survey: %s' % (name, flags_comment))  # A 2013 bug in Compass inserted corrupt binary garbage into FLAGS column, causes parse to barf
                shot_vals += [flags, comment.strip()]

            shot_vals = [(header, self._coerce(header, val)) for (header, val) in zip(shot_header, shot_vals)]
            shot = Shot(shot_vals)
            survey.add_shot(shot)

        #log.debug("Survey: name=%s shots=%d length=%0.1f date=%s team=%s\n%s", name, len(shots), survey.length, date, team, '\n'.join([str(shot) for shot in survey.shots]))

        return survey
Parse our string and return a Survey object, None, or raise :exc:`ParseException`