#.-*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function) import csv import re import datetime, time import os import pickle from django.conf import settings from django.template.defaultfilters import slugify from troggle.core.models import DataIssue, Expedition import troggle.core.models as models from parsers.people import GetPersonExpeditionNameLookup from parsers.cavetab import GetCaveLookup from utils import save_carefully # # When we edit logbook entries, allow a "?" after any piece of data to say we've frigged it and # it can be checked up later from the hard-copy if necessary; or it's not possible to determin (name, trip place, etc) # # # the logbook loading section # def GetTripPersons(trippeople, expedition, logtime_underground): res = [ ] author = None round_bracket_regex = re.compile(r"[\(\[].*?[\)\]]") for tripperson in re.split(r",|\+|&|&(?!\w+;)| and ", trippeople): tripperson = tripperson.strip() mul = re.match(r"(.*?)$(?i)", tripperson) if mul: tripperson = mul.group(1).strip() if tripperson and tripperson[0] != '*': #assert tripperson in personyearmap, "'%s' << %s\n\n %s" % (tripperson, trippeople, personyearmap) tripperson = re.sub(round_bracket_regex, "", tripperson).strip() personyear = GetPersonExpeditionNameLookup(expedition).get(tripperson.lower()) if not personyear: print(" - No name match for: '%s'" % tripperson) message = "No name match for: '%s' in year '%s'" % (tripperson, expedition.year) models.DataIssue.objects.create(parser='logbooks', message=message) res.append((personyear, logtime_underground)) if mul: author = personyear if not author: if not res: return None, None author = res[-1][0] return res, author def GetTripCave(place): #need to be fuzzier about matching here. Already a very slow function... # print "Getting cave for " , place try: katastNumRes=[] katastNumRes=list(models.Cave.objects.filter(kataster_number=int(place))) except ValueError: pass officialNameRes=list(models.Cave.objects.filter(official_name=place)) tripCaveRes=officialNameRes+katastNumRes if len(tripCaveRes)==1: # print "Place " , place , "entered as" , tripCaveRes[0] return tripCaveRes[0] elif models.OtherCaveName.objects.filter(name=place): tripCaveRes=models.OtherCaveName.objects.filter(name__icontains=place)[0].cave # print "Place " , place , "entered as" , tripCaveRes return tripCaveRes elif len(tripCaveRes)>1: print("Ambiguous place " + str(place) + " entered. Choose from " + str(tripCaveRes)) correctIndex=input("type list index of correct cave") return tripCaveRes[correctIndex] else: print("No cave found for place " , place) return logentries = [] # the entire logbook is a single object: a list of entries noncaveplaces = [ "Journey", "Loser Plateau" ] def EnterLogIntoDbase(date, place, title, text, trippeople, expedition, logtime_underground, entry_type="wiki"): """ saves a logbook entry and related persontrips """ global logentries entrytuple = (date, place, title, text, trippeople, expedition, logtime_underground, entry_type) logentries.append(entrytuple) trippersons, author = GetTripPersons(trippeople, expedition, logtime_underground) if not author: print(" * Skipping logentry: " + title + " - no author for entry") message = "Skipping logentry: %s - no author for entry in year '%s'" % (title, expedition.year) models.DataIssue.objects.create(parser='logbooks', message=message) return #tripCave = GetTripCave(place) lplace = place.lower() if lplace not in noncaveplaces: cave=GetCaveLookup().get(lplace) #Check for an existing copy of the current entry, and save expeditionday = expedition.get_expedition_day(date) lookupAttribs={'date':date, 'title':title} nonLookupAttribs={'place':place, 'text':text, 'expedition':expedition, 'cave':cave, 'slug':slugify(title)[:50], 'entry_type':entry_type} lbo, created=save_carefully(models.LogbookEntry, lookupAttribs, nonLookupAttribs) for tripperson, time_underground in trippersons: lookupAttribs={'personexpedition':tripperson, 'logbook_entry':lbo} nonLookupAttribs={'time_underground':time_underground, 'is_logbook_entry_author':(tripperson == author)} save_carefully(models.PersonTrip, lookupAttribs, nonLookupAttribs) def ParseDate(tripdate, year): """ Interprets dates in the expo logbooks and returns a correct datetime.date object """ mdatestandard = re.match(r"(\d\d\d\d)-(\d\d)-(\d\d)", tripdate) mdategoof = re.match(r"(\d\d?)/0?(\d)/(20|19)?(\d\d)", tripdate) if mdatestandard: assert mdatestandard.group(1) == year, (tripdate, year) year, month, day = int(mdatestandard.group(1)), int(mdatestandard.group(2)), int(mdatestandard.group(3)) elif mdategoof: assert not mdategoof.group(3) or mdategoof.group(3) == year[:2], mdategoof.groups() yadd = int(year[:2]) * 100 day, month, year = int(mdategoof.group(1)), int(mdategoof.group(2)), int(mdategoof.group(4)) + yadd else: assert False, tripdate return datetime.date(year, month, day) # 2006, 2008 - 2009 def Parselogwikitxt(year, expedition, txt): trippara = re.findall(r"===(.*?)===([\s\S]*?)(?====)", txt) for triphead, triptext in trippara: tripheadp = triphead.split("|") assert len(tripheadp) == 3, (tripheadp, triptext) tripdate, tripplace, trippeople = tripheadp tripsplace = tripplace.split(" - ") tripcave = tripsplace[0].strip() tul = re.findall(r"T/?U:?\s*(\d+(?:\.\d*)?|unknown)\s*(hrs|hours)?", triptext) if tul: tu = tul[0][0] else: tu = "" ldate = ParseDate(tripdate.strip(), year) EnterLogIntoDbase(date = ldate, place = tripcave, title = tripplace, text = triptext, trippeople=trippeople, expedition=expedition, logtime_underground=0) # 2002, 2004, 2005, 2007, 2010 - now def Parseloghtmltxt(year, expedition, txt): #print(" - Starting log html parser") tripparas = re.findall(r"
)? # second date \s*(?:\s*)? \s*
)? \s*
", "", ltriptext).strip() EnterLogIntoDbase(date = ldate, place = tripcave, title = triptitle, text = ltriptext, trippeople=trippeople, expedition=expedition, logtime_underground=0, entry_type="html") # main parser for 1991 - 2001. simpler because the data has been hacked so much to fit it def Parseloghtml01(year, expedition, txt): tripparas = re.findall(r"
]*>(T/?U.*)', triptext) if mtu: tu = mtu.group(1) triptext = triptext[:mtu.start(0)] + triptext[mtu.end():] else: tu = "" triptitles = triptitle.split(" - ") tripcave = triptitles[0].strip() ltriptext = triptext mtail = re.search(r'(?:[^<]*|\s|/|-|&|?p>|\((?:same day|\d+)\))*$', ltriptext) if mtail: ltriptext = ltriptext[:mtail.start(0)] ltriptext = re.sub(r"
", "", ltriptext) ltriptext = re.sub(r"\s*?\n\s*", " ", ltriptext) ltriptext = re.sub(r"|
", "\n\n", ltriptext).strip()
ltriptext = re.sub(r"?u>", "_", ltriptext)
ltriptext = re.sub(r"?i>", "''", ltriptext)
ltriptext = re.sub(r"?b>", "'''", ltriptext)
EnterLogIntoDbase(date=ldate, place=tripcave, title=triptitle, text=ltriptext,
trippeople=trippeople, expedition=expedition, logtime_underground=0,
entry_type="html")
# parser for 2003
def Parseloghtml03(year, expedition, txt):
tripparas = re.findall(r"
", "\n\n", ltriptext).strip() ltriptext = re.sub(r"[^\s0-9a-zA-Z\-.,:;'!&()\[\]<>?=+*%]", "_NONASCII_", ltriptext) EnterLogIntoDbase(date = ldate, place = tripcave, title = triptitle, text = ltriptext, trippeople=trippeople, expedition=expedition, logtime_underground=0, entry_type="html") def SetDatesFromLogbookEntries(expedition): """ Sets the date_from and date_to field for an expedition based on persontrips. Then sets the expedition date_from and date_to based on the personexpeditions. """ for personexpedition in expedition.personexpedition_set.all(): persontrips = personexpedition.persontrip_set.order_by('logbook_entry__date') # sequencing is difficult to do lprevpersontrip = None for persontrip in persontrips: persontrip.persontrip_prev = lprevpersontrip if lprevpersontrip: lprevpersontrip.persontrip_next = persontrip lprevpersontrip.save() persontrip.persontrip_next = None lprevpersontrip = persontrip persontrip.save() def LoadLogbookForExpedition(expedition): """ Parses all logbook entries for one expedition """ global logentries logbook_parseable = False logbook_cached = False yearlinks = settings.LOGBOOK_PARSER_SETTINGS expologbase = os.path.join(settings.EXPOWEB, "years") if expedition.year in yearlinks: logbookfile = os.path.join(expologbase, yearlinks[expedition.year][0]) parsefunc = yearlinks[expedition.year][1] else: logbookfile = os.path.join(expologbase, expedition.year, settings.DEFAULT_LOGBOOK_FILE) parsefunc = settings.DEFAULT_LOGBOOK_PARSER cache_filename = logbookfile + ".cache" try: bad_cache = False now = time.time() cache_t = os.path.getmtime(cache_filename) if os.path.getmtime(logbookfile) - cache_t > 2: # at least 2 secs later bad_cache= True if now - cache_t > 30*24*60*60: bad_cache= True if bad_cache: print(" - ! Cache is either stale or more than 30 days old. Deleting it.") os.remove(cache_filename) logentries=[] print(" ! Removed stale or corrupt cache file") raise print(" - Reading cache: " + cache_filename, end='') try: with open(cache_filename, "rb") as f: logentries = pickle.load(f) print(" -- Loaded ", len(logentries), " log entries") logbook_cached = True except: print("\n ! Failed to load corrupt cache. Deleting it.\n") os.remove(cache_filename) logentries=[] raise except : # no cache found #print(" - No cache \"" + cache_filename +"\"") try: file_in = open(logbookfile,'rb') txt = file_in.read().decode("latin1") file_in.close() logbook_parseable = True print((" - Using: " + parsefunc + " to parse " + logbookfile)) except (IOError): logbook_parseable = False print((" ! Couldn't open logbook " + logbookfile)) if logbook_parseable: parser = globals()[parsefunc] parser(expedition.year, expedition, txt) SetDatesFromLogbookEntries(expedition) # and this has also stored all the log entries in logentries[] if len(logentries) >0: print(" - Cacheing " , len(logentries), " log entries") with open(cache_filename, "wb") as fc: pickle.dump(logentries, fc, 2) else: print(" ! NO TRIP entries found in logbook, check the syntax.") logentries=[] # flush for next year if logbook_cached: i=0 for entrytuple in range(len(logentries)): date, place, title, text, trippeople, expedition, logtime_underground, \ entry_type = logentries[i] EnterLogIntoDbase(date, place, title, text, trippeople, expedition, logtime_underground,\ entry_type) i +=1 #return "TOLOAD: " + year + " " + str(expedition.personexpedition_set.all()[1].logbookentry_set.count()) + " " + str(models.PersonTrip.objects.filter(personexpedition__expedition=expedition).count()) def LoadLogbooks(): """ This is the master function for parsing all logbooks into the Troggle database. """ DataIssue.objects.filter(parser='logbooks').delete() expos = Expedition.objects.all() nologbook = ["1976", "1977","1978","1979","1980","1980","1981","1983","1984", "1985","1986","1987","1988","1989","1990",] for expo in expos: if expo.year not in nologbook: print((" - Logbook for: " + expo.year)) LoadLogbookForExpedition(expo) dateRegex = re.compile(r'(\d\d\d\d)-(\d\d)-(\d\d)', re.S) expeditionYearRegex = re.compile(r'(.*?)', re.S) titleRegex = re.compile(r'