def parse(self):
        """Produce a `TxtFile` object from the .TXT file"""
        log.debug('Parsing PocketTopo .TXT file %s ...', self.txtfilename)
        SurveyClass = MergingSurvey if self.merge_duplicate_shots else Survey
        txtobj = None

        with codecs.open(self.txtfilename, 'rb', self.encoding) as txtfile:
            lines = txtfile.read().splitlines()

            # first line is cave name and units
            first_line_re = re.compile(r'^([\w\s]*)\(([\w\s]*),([\w\s]*)')
            first_line = lines.pop(0)
            cave_name, length_units, angle_units = first_line_re.search(first_line).groups()
            cave_name, angle_units = cave_name.strip(), int(angle_units)
            txtobj = TxtFile(cave_name, length_units, angle_units)

            while not lines[0]:
                lines.pop(0)  # skip blanks

            # next block identifies surveys (trip) metadata
            while lines[0].startswith('['):
                toks = lines.pop(0).split(None, 3)
                id, date, declination = toks[:3]
                id = id.strip('[]:')
                date = datetime.strptime(date, '%Y/%m/%d').date()
                declination = float(declination)
                comment = toks[3].strip('"') if len(toks) == 4 else ''
                survey = SurveyClass(id, date, comment, declination, cave_name)
                txtobj.add_survey(survey)

            while not lines[0]:
                lines.pop(0)  # skip blanks

            # finally actual survey data
            while lines:
                line = lines.pop(0).strip()
                if not line:
                    continue

                if '"' in line:
                    line, comment = line.split('"', 1)
                    comment = comment.rstrip('"')
                else:
                    comment = None

                if '[' not in line:
                    # this is either a Reference Point or a zero-length fake shot
                    toks = line.split()
                    if len(toks) != 4:  # ??
                        log.debug('Skipping unrecognized shot:  %s %s', line, '"%s"' % comment if comment else '')
                        continue
                    station, vals = toks[0], list(map(float, toks[1:]))
                    if vals[0] == 0.0:  # fake shot
                        log.debug('Skipping zero-length shot:  %s %s', line, '"%s"' % comment if comment else '')
                    else:  # reference point
                        easting, northing, altitude = vals
                        reference_point = UTMLocation(easting, northing, altitude, comment)
                        log.debug('Reference point:  %s', reference_point)
                        txtobj.add_reference_point(station, reference_point)
                    continue

                line, survey_id = line.split('[')
                survey_id = survey_id.rstrip().rstrip(']')
                toks = line.split()
                from_to, (length, azm, inc) = toks[:-3], (float(tok) for tok in toks[-3:])

                if len(from_to) == 2:
                    from_, to = tuple(from_to)  # shot
                elif len(from_to) == 1:
                    from_, to = from_to[0], None  # splay
                elif not from_to and length == 0.0:
                    continue  # skip junk zero-length placeholder shots
                else:
                    raise Exception()

                shot = Shot([('FROM',from_), ('TO',to), ('LENGTH',length), ('AZM',azm), ('INC',inc), ('COMMENT',comment)])
                txtobj[survey_id].add_shot(shot)

        return txtobj