#!/usr/bin/python3 # -*- coding: utf-8 -*- # Modules import sys import os import sqlite3 import csv import re import syslog # Configurable stuff # Other globals progname = sys.argv[0].rpartition("/")[2] del sys.argv[0] syslog_opened_flag = False DEVNULL = open(os.devnull, 'wb') db_file = None #db_file = '%s.sqlite' % (progname) use_syslog_flag = False waypoint_name_regex = '^camp(?:[0-9]{2,}|XXX)[*+]?$' float_regex = '^[-+]?[0-9]+(?:\.[0-9]+)?$' campsite_longitude_regex = float_regex campsite_latitude_regex = float_regex campsite_country_regex = '^(?:AT|CZ|CH|DE|SK|DK|HU)$' letters = 'a-zA-ZáéřýőíüÉóéÖúžěöčľÜßČšŽŠäåňÅůŘřøæØťÍďÚ´ô' campsite_name_regex = '^(?:[-%s 0-9/.()&+:"\']{3,}|[-?])$' % (letters) campsite_city_regex = '^(?:[-%s 0-9/.()]{3,}|[-?])$' % (letters) campsite_street_regex = '^(?:[-%s 0-9/.&]{5,}|[1-9][/0-9]*|[-?])$' % (letters) tel_regex = '\+[1-9][0-9]{1,2} [1-9][0-9 ]*[0-9]' campsite_tel_regex = '^(?:%s(?: or %s)*|\?)$' % (tel_regex, tel_regex) campsite_tents_ok_regex = '^(?:yes|no|\?)$' campsite_season_regex = '^((|mid)\d+|\?)-((|mid)\d+|\?)(|,((|mid)\d+|\?)-((|mid)\d+|\?))$' campsite_season_regex = '^(?:(?:|mid)\d+|\?)-(?:(?:|mid)\d+|\?)$' campsite_url_regex = '^(http://|https://|\?$)' # Classes # Functions def main(): global verboselevel, DEVNULL, cursor, db_file # Defaults for options verboselevel = 2 output_fmt = None # Process options, old school while True: if len(sys.argv) == 0: break # Application-specific options elif sys.argv[0].startswith('--format='): output_fmt = sys.argv[0][len('--format='):] if output_fmt not in ['csv', 'gpx', 'gpx2', 'html']: usage() # '-' is an argument not an option elif sys.argv[0] == '-': break # Standard options elif sys.argv[0].startswith('--debug='): verboselevel = sys.argv[0][len('--debug='):] if not re.search('^\d+$', verboselevel): usage() verboselevel = int(verboselevel) elif sys.argv[0] == '--verbose': verboselevel = 3 elif sys.argv[0] == '--help': usage(0) elif sys.argv[0] == '-v': verboselevel = 3 elif sys.argv[0] == '-d': if len(sys.argv) < 3: usage() verboselevel = sys.argv[1] del sys.argv[0] if not re.search('^\d+$', verboselevel): usage() verboselevel = int(verboselevel) elif sys.argv[0] == '-h': usage(0) elif sys.argv[0] == '--': del sys.argv[0] break elif sys.argv[0].startswith('-'): usage() else: break del sys.argv[0] # Process arguments input_files = sys.argv if len(sys.argv) > 0 else ['-'] debug(10, 'main: input_files=%s' % (input_files)) # Sanity checks and derivations if output_fmt is None: usage() if db_file is None: database_exists_flag = False elif os.path.isfile(db_file): database_exists_flag = True else: database_exists_flag = False # Guts # Connect to database sqlite3.enable_callback_tracebacks(True) conn = sqlite3.connect(db_file if db_file is not None else ':memory:') # I want to control transactions myself conn.isolation_level = None # Create REGEXP function. (REGEXP operator maps to REGEXP function automatically) def regexp(expr, item): #debug(10, 'regexp: expr=%s, item=%s' % (expr, item)) reg = re.compile(expr) return reg.search(item) is not None conn.create_function("REGEXP", 2, regexp) cursor = conn.cursor() # Intialise database if necessary if not database_exists_flag: sqlite_exec_wrapper('CREATE TABLE campsites (input_file char, input_line int, waypoint_name char, campsite_latitude char, campsite_longitude char, campsite_name char, campsite_country char, campsite_city char, campsite_street char, campsite_tel char, campsite_tents_ok char, campsite_season char, campsite_url, PRIMARY KEY(input_file, input_line));') # Normally I'd create a view to get the deduplicated list of campsites, but # views don't have rowids and I want to use rowid to build the waypoint name. # So we create a second copy of the table and we'll insert the deduplicated # rows into into it with a single INSERT statement. sqlite_exec_wrapper('CREATE TABLE deduplicated_campsites (campsite_latitude char, campsite_longitude char, campsite_name char, campsite_country char, campsite_city char, campsite_street char, campsite_tel char, campsite_tents_ok char, campsite_season char,campsite_url char);') # Slurp input with filenames and line numbers (useful for error messages) sqlite_exec_wrapper('BEGIN TRANSACTION;') for input_file in input_files: if input_file == '-': fh = sys.stdin else: fh = open(input_file, 'r') input_line = 0 for row in csv.reader(fh): input_line += 1 (waypt_name, campsite_latitude, campsite_longitude, campsite_name, campsite_country, campsite_city, campsite_street, campsite_tel, campsite_tents_ok, campsite_season, campsite_url) = row[5:-1] # Ignore all non-camp rows if not 'camp' in waypt_name: continue # Ignore knowingly duplicated camps. if 'see above' in campsite_name: continue debug(10, 'main: waypt_name=%s, campsite_latitude=%s, campsite_longitude=%s, campsite_name=%s, campsite_country=%s, campsite_city=%s, campsite_street=%s, campsite_tel=%s, campsite_tents_ok=%s, campsite_season=%s, campsite_url=%s' % (waypt_name, campsite_latitude, campsite_longitude, campsite_name, campsite_country, campsite_city, campsite_street, campsite_tel, campsite_tents_ok, campsite_season, campsite_url)) sqlite_exec_wrapper('INSERT INTO campsites VALUES (\'%s\', %d, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' % (input_file, input_line, sql_stringify(waypt_name), sql_stringify(campsite_latitude), sql_stringify(campsite_longitude), sql_stringify(campsite_name), sql_stringify(campsite_country), sql_stringify(campsite_city), sql_stringify(campsite_street), sql_stringify(campsite_tel), sql_stringify(campsite_tents_ok), sql_stringify(campsite_season), sql_stringify(campsite_url))) if input_file == '-': fh.close() sqlite_exec_wrapper('END TRANSACTION;') # Report badly formatted values. check_param_sets = [ { 'col':'waypoint_name', 'regex':waypoint_name_regex }, { 'col':'campsite_latitude', 'regex':campsite_latitude_regex }, { 'col':'campsite_longitude', 'regex':campsite_longitude_regex }, { 'col':'campsite_name', 'regex':campsite_name_regex }, { 'col':'campsite_country', 'regex':campsite_country_regex }, { 'col':'campsite_city', 'regex':campsite_city_regex }, { 'col':'campsite_street', 'regex':campsite_street_regex }, { 'col':'campsite_tel', 'regex':campsite_tel_regex }, { 'col':'campsite_tents_ok', 'regex':campsite_tents_ok_regex }, { 'col':'campsite_season', 'regex':campsite_season_regex }, { 'col':'campsite_url', 'regex':campsite_url_regex }, ] for check_param_set in check_param_sets: sql_statement = 'SELECT input_file, input_line as error FROM campsites WHERE %s IS NULL ORDER BY input_file, input_line;' % (check_param_set['col']) # Check first for NULL (else REGEXP bauks) for tuple in sqlite_exec_wrapper(sql_statement): (input_file, input_line) = tuple print("%-20s %s" % ('%s:%s:' % (input_file, input_line), 'empty %s' % (check_param_set['col']))) sql_statement = 'SELECT input_file, input_line, %s as whatever FROM campsites WHERE %s IS NOT NULL AND NOT %s REGEXP %s ORDER BY input_file, input_line;' % (check_param_set['col'], check_param_set['col'],check_param_set['col'], sql_stringify(check_param_set['regex'])) # Check second value is formatted correctly. for tuple in sqlite_exec_wrapper(sql_statement): (input_file, input_line, whatever) = tuple print('%-20s %s' % ('%s:%d:' % (input_file, input_line), '%s: bad %s' % (whatever, check_param_set['col']))) # Report mismatching values in lines occurring multiple times. columns = [ 'campsite_name', 'campsite_country', 'campsite_city', 'campsite_street', 'campsite_tel', 'campsite_tents_ok', 'campsite_season', 'campsite_url', ] for column in columns: # Cross the table with itself and look for unequal fields. Use # 'c1.whatever > c2.whatever' to ignore one of the two occurences # of a mismatch this join would otherwise produce. sql_statement = 'SELECT c1.input_file || \':\' || c1.input_line || \'/\' || c2.input_file || \':\' || c2.input_line AS location, \'mismatching %s\' AS error FROM campsites AS c1, campsites AS c2 WHERE c1.campsite_latitude == c2.campsite_latitude AND c1.campsite_longitude == c2.campsite_longitude AND c1.%s != c2.%s AND c1.%s > c2.%s;' % (column, column, column, column, column) for tuple in sqlite_exec_wrapper(sql_statement): (location, error) = tuple print("%-40s %s" % (location, error)) # Deduplicate in a way that gives a rowid. sqlite_exec_wrapper('INSERT INTO deduplicated_campsites SELECT DISTINCT campsite_latitude, campsite_longitude, campsite_name, campsite_country, campsite_city, campsite_street, campsite_tel, campsite_tents_ok, campsite_season, campsite_url FROM campsites ORDER BY campsite_latitude;') # Whatever format we output in, we use use the same ordering and # and selection in order to ensure that the rowid values correspond # across the differing output files. if output_fmt == 'gpx': def prologue(): print('') print('') sql_statement = ''' SELECT 'camp' || printf('%04d',rowid) || 'Triangle, Green' as gpx_line FROM deduplicated_campsites; ''' def process_row(tuple): (gpx_line,) = tuple print(gpx_line) def epilogue(): print('') elif output_fmt == 'gpx2': def prologue(): print('') print('') sql_statement = ''' SELECT campsite_latitude, campsite_longitude, waypoint_name FROM campsites ORDER BY waypoint_name; ''' def process_row(tuple): (campsite_latitude, campsite_longitude, waypoint_name) = tuple print(' ' % (campsite_latitude, campsite_longitude)) print(' %s' % (waypoint_name)) print(' Triangle, Green') print(' ') def epilogue(): print('') elif output_fmt == 'csv': def prologue(): global spamwriter spamwriter = csv.writer(sys.stdout, delimiter=',', quotechar='"', quoting=csv.QUOTE_NONNUMERIC) spamwriter.writerow(["marker","lat/lon","camp name & homepage","ctry","town","street","telephone","tents ok?","season"]) sql_statement = ''' SELECT rowid, campsite_latitude, campsite_longitude, campsite_name, campsite_url, campsite_country, campsite_city, campsite_street, campsite_tel, campsite_tents_ok, campsite_season FROM deduplicated_campsites; ''' def process_row(tuple): global spamwriter (rowid, campsite_latitude, campsite_longitude, campsite_name, campsite_url, campsite_country, campsite_city, campsite_street, campsite_tel, campsite_tents_ok, campsite_season) = tuple spamwriter.writerow([ 'camp%04d' % (rowid), '%s,%s
(GM, OSM, BM, geo)' % (campsite_latitude, campsite_longitude, campsite_latitude, campsite_longitude, campsite_latitude, campsite_longitude, campsite_latitude, campsite_longitude, campsite_latitude, campsite_longitude), '%s' % (campsite_url, campsite_name) if campsite_url.startswith('http') else campsite_name, campsite_country, campsite_city, campsite_street, ' or '.join(['%s' % (tel, tel) for tel in campsite_tel.split(' or ') if tel not in ['?','-']]), campsite_tents_ok, campsite_season]) def epilogue(): pass elif output_fmt == 'html': def prologue(): print('') print('') print('') print('camps') print('') print('') print('') print(' ') print(' ') sql_statement = ''' SELECT waypoint_name, campsite_name, campsite_city, campsite_street, campsite_url, campsite_latitude, campsite_longitude, campsite_tel FROM campsites ORDER BY waypoint_name; ''' def process_row(tuple): (waypoint_name, campsite_name, campsite_city, campsite_street, campsite_url, campsite_latitude, campsite_longitude, campsite_tel) = tuple print(' ' % ( waypoint_name, '%s' % (campsite_url, campsite_name) if campsite_url.startswith('http') else campsite_name, campsite_latitude, campsite_longitude, ', '.join([address_component for address_component in [campsite_street, campsite_city] if address_component not in ['?','-']]), ' or '.join(['%s' % (tel, tel) for tel in campsite_tel.split(' or ') if tel not in ['?','-']]) )) def epilogue(): print('
wayptnameaddrtel
%s%s%s%s
') print('') print('') # Generate report prologue() for tuple in sqlite_exec_wrapper(sql_statement): process_row(tuple) epilogue() # SQL functions def sqlite_exec_wrapper(statement): global cursor return cursor.execute(statement) def sql_stringify(s): return 'NULL' if s == '' else '\'%s\''%(s.replace('\'', '\'\'')) # Messaging functions def usage(rc=1): global progname fh = sys.stdout if rc == 0 else sys.stderr fh.write('Usage: %s [ ] --format= [ ... ]\n' % progname) fh.write('\n') fh.write('Formats: gpx: suitable only for directory of european campsites\n') fh.write(' gpx2: suitable only for single-holiday GPX files\n') fh.write(' html: suitable only for single-holiday HTML files\n') fh.write(' csv: intermediate filetype used by Makefiles\n') sys.exit(rc) def debug(level, text): global verboselevel if verboselevel < level: return msg_writer("DEBUG[%s]: %s" % (level, text), syslog.LOG_DEBUG) def info(text): global verboselevel if verboselevel < 3: return msg_writer("INFO: %s" % (text), syslog.LOG_INFO) def warning(text): global verboselevel if verboselevel < 2: return msg_writer("WARNING: %s" % (text), syslog.LOG_WARNING) def error(text): global errors_bubble_up_flag msg_writer("ERROR: %s" % (text), syslog.LOG_ERR) if not errors_bubble_up_flag: sys.exit(1) def internal(text): msg_writer("INTERNAL ERROR: %s" % (text), syslog.LOG_CRIT) sys.exit(2) def msg_writer(text, syslog_level): global use_syslog_flag msg_writer_stderr(text) if use_syslog_flag and not sys.stderr.isatty(): msg_writer_syslog(text, syslog_level) def msg_writer_stderr(text): global progname sys.stderr.write("%s: %s\n" % (progname, text)) def msg_writer_syslog(text, syslog_level): global syslog_opened_flag, progname if not syslog_opened_flag: if sys.version_info < (2, 7): syslog.openlog(progname, syslog.LOG_PID) else: syslog.openlog(progname, logoption=syslog.LOG_PID) syslog_opened_flag = True syslog.syslog((syslog.LOG_LOCAL0|syslog_level), text) # Entry point if __name__=="__main__": main() ## See http://stackoverflow.com/questions/10299682/how-to-unpack-tuple-of-length-n-to-mn-variables. ## This is needed because the spreadsheet for the current year typically has extra 'Alexis' and 'Suzie' ## columns where we write our draft plan for where to camp. #def just(n, seq): # it = iter(seq) # for _ in range(n - 1): # yield next(it, None) # yield tuple(it) # ## Initialise output dicts. #o_csv_camps = {} #o_gpx_camps = {} #o_html_camps = {} # #for i_row in csv.reader(sys.stdin): # #sys.stderr.write("i_row=%s\n" % (i_row)) # # # Search for leading and trailing spaces (we want to improve the spreadsheets) # for field in range(0,16): # if i_row[field].startswith(' ') or i_row[field].endswith(' '): # sys.stderr.write("%s: leading or trailing spaces\n" % i_row) # # # Extract first 17 fields. (python3 has nicer ways to do this, but not python2.) # (i_stage_start, i_stage_end, i_stage_km, i_total_km, i_page, i_camp_waypt, i_camp_n, i_camp_e, i_camp_name, i_camp_country, i_camp_town, i_camp_street, i_camp_phones, i_camp_tent_flag, i_camp_season, i_camp_url, i_camp_notes) = just(17, i_row) # # # Ignore all non-camp rows # if not i_camp_waypt.startswith("camp"): # continue # # # Ignore knowingly duplicated camps. # if i_camp_name.startswith("see above"): # continue # # # Copy over some fields unmodified. # (o_camp_n, o_camp_e, o_camp_waypt, o_camp_name, o_camp_country, o_camp_town, o_camp_street, o_camp_tent_flag, o_camp_season, o_camp_url, o_camp_notes) = (i_camp_n, i_camp_e, i_camp_waypt, i_camp_name, i_camp_country, i_camp_town, i_camp_street, i_camp_tent_flag, i_camp_season, i_camp_url, i_camp_notes) # # # Validate fields # if not re.search('^\d+\.\d+$', o_camp_n): # sys.stderr.write("%s: invalid N coordinate: %s\n" % (i_row, o_camp_n)) # if not re.search('^\d+\.\d+$', o_camp_e): # sys.stderr.write("%s: invalid E coordinate: %s\n" % (i_row, o_camp_e)) # if not re.search('^(AT|CH|CZ|DE|DK|HU|SK)$', o_camp_country): # sys.stderr.write("%s: invalid country: %s\n" % (i_row, o_camp_country)) # #sys.stderr.write("o_camp_tent_flag=%s\n" % (o_camp_tent_flag)) # if not re.search('^(yes|no|\?)$', o_camp_tent_flag): # sys.stderr.write("%s: invalid camping poss flag: [%s]\n" % (i_row, o_camp_tent_flag)) # if not re.search('^(http://|https://|\?$)', o_camp_url): # sys.stderr.write("%s: invalid URL: [%s]\n" % (i_row, o_camp_url)) # # season is X-X or X-X,X-X where X is , mid or ? # if not re.search('^((|mid)\d+|\?)-((|mid)\d+|\?)(|,((|mid)\d+|\?)-((|mid)\d+|\?))$', o_camp_season): # sys.stderr.write("%s: invalid season: [%s]\n" % (i_row, o_camp_season)) # if not re.search('^(yes|no|\?)$', o_camp_tent_flag): # sys.stderr.write("%s: invalid tent poss flag: [%s]\n" % (i_row, o_camp_tent_flag)) # # # Build the camp name. # # # Build the GPS coordinates. # o_camp_n_and_e = i_camp_n + "," + i_camp_e + "
(GM, OSM, BM)" # # # Rewrite telephone number # o_camp_phones = [] # for num in i_camp_phones.split(' or '): # num = re.sub('[-/]',' ',num) # num = re.sub(',',' or ',num) # num = re.sub(' +',' ',num) # num = re.sub('\(00420\)','+42 ',num) # num = re.sub('\(0\)','',num) # num = re.sub('\+ +','+',num) # num = re.sub('^ +','',num) # num = re.sub(' +$','',num) # if num == '': # pass # elif num == '?': # pass # # dutch telnos. # elif num == '+31 6 36 16 49 72' and i_camp_country == 'CZ': # pass # elif num == '+31 6 2638 0484' and i_camp_country == 'DE': # pass # elif num.startswith('+49 '): # pass # elif num.startswith('+420 '): # pass # elif num.startswith('+43 '): # pass # elif num.startswith('+41 '): # pass # elif num.startswith('+36 '): # pass # elif num.startswith('+421 '): # pass # elif num.startswith('+45 '): # pass # elif num.startswith('+'): # sys.stderr.write("bad number: %s\n" % (num)) # elif i_camp_country == 'DE' and num.startswith('0'): # num = '+49 ' + num[1:] # elif i_camp_country == 'CZ' and num.startswith('0'): # num = '+420 ' + num[1:] # elif i_camp_country == 'AT' and num.startswith('0'): # num = '+43 ' + num[1:] # elif i_camp_country == 'CH' and num.startswith('0'): # num = '+41 ' + num[1:] # elif i_camp_country == 'HU' and num.startswith('0'): # num = '+36 ' + num[1:] # elif i_camp_country == 'SK' and num.startswith('0'): # num = '+421 ' + num[1:] # elif i_camp_country == 'DK' and int(num[0]) in (range(1,10)): # num = '+45 ' + num[1:] # else: # sys.stderr.write("bad number/country: %s\n" % (num)) # o_camp_phones.append(str(num)) # # if sys.argv[1] == 'csv': # # Construct new CSV record # o_csv_key = "KEY" + o_camp_n + "," + o_camp_e # if i_camp_url.startswith("http"): # o_camp_name_and_url_in_html = "%s" % (o_camp_url, o_camp_name) # else: # o_camp_name_and_url_in_html = o_camp_name # o_csv_val = [ o_camp_n_and_e, o_camp_name_and_url_in_html, o_camp_country, o_camp_town, o_camp_street, ' or '.join(o_camp_phones), o_camp_tent_flag, o_camp_season ] # # # Do we have a record with this N/E already? # try: # o_csv_camps[o_csv_key] # # If not then insert and skip duplicate/near-duplicate checks # except KeyError: # o_csv_camps[o_csv_key] = o_csv_val # continue # # If we have the record already (i.e. we got here) and the new record is a perfect duplicate of the existing record then continue # if o_csv_camps[o_csv_key] == o_csv_val: # pass # # Else error # else: # sys.stderr.write("imperfect duplicates: \n %s\n %s\n" % (o_csv_camps[o_csv_key], o_csv_val)) # pass # # elif sys.argv[1] == 'gpx': # # Construct new GPX waypoint record # o_gpx_val = [ o_camp_n, o_camp_e ] # o_gpx_key = "KEY" + o_camp_n + "," + o_camp_e # #sys.stderr.write("o_gpx_key=%s, o_gpx_val=%s\n" % (o_gpx_key, o_gpx_val)) # # # Do we have a record with this N/E already? # try: # o_gpx_camps[o_gpx_key] # # If not then insert and skip duplicate/near-duplicate checks # except KeyError: # o_gpx_camps[o_gpx_key] = o_gpx_val # continue # # If we have the record already (i.e. we got here) and the new record is a perfect duplicate of the existing record then continue # if o_gpx_camps[o_gpx_key] == o_gpx_val: # pass # # Else error # else: # sys.stderr.write("imperfect duplicate: %s and %s\n" % (o_gpx_camps[o_gpx_key], o_gpx_val)) # pass # # elif sys.argv[1] == 'gpx2': # # In the Sieben Fluesse holiday, there are no 'see above' camp lines *that we are staying at*. # # For this reason there is no need for 'see above' rows to even detail the waypoint name, lat # # and long. Next holiday, I'll need to fix this here with some better logic. # o_gpx_key = o_camp_waypt # o_gpx_camps[o_gpx_key] = ' \n %s\n Triangle, Green\n ' % (o_camp_n, o_camp_e, o_camp_waypt) # # elif sys.argv[1] == 'html': # o_html_key = o_camp_waypt # if len(o_camp_phones) == 0 or o_camp_phones[0] == '?': # o_camp_phones_html_str = '' # else: # o_camp_phones_html_str = ' or '.join(['%s' % (x, x) for x in o_camp_phones]) # if o_camp_street == '-': # o_camp_addr = o_camp_town # else: # o_camp_addr = '%s, %s' % (o_camp_street, o_camp_town) # o_camp_uri = 'geo:%s,%s' % (o_camp_n,o_camp_e) # if i_camp_url.startswith("http"): # o_camp_name_and_url_in_html = "%s" % (o_camp_url, o_camp_name) # else: # o_camp_name_and_url_in_html = o_camp_name # o_html_camps[o_html_key] = '%s%s%s%s' % (o_camp_waypt, o_camp_name_and_url_in_html, o_camp_uri, o_camp_addr, o_camp_phones_html_str) # #if sys.argv[1] == 'csv': # # Dump records. # spamwriter = csv.writer(sys.stdout, delimiter=',', quotechar='"', quoting=csv.QUOTE_NONNUMERIC) # spamwriter.writerow(["lat/lon","camp name & homepage","ctry","town","street","telephone","tents ok?","season"]) # for o_csv_key in o_csv_camps: # spamwriter.writerow(o_csv_camps[o_csv_key]) # #elif sys.argv[1] == 'gpx': # print "" # print "" # i = 0 # # Note 'for' on a dictionary loops over *keys* not values. # for o_gpx_key in o_gpx_camps: # i += 1 # #sys.stderr.write("o_gpx_key=%s, o_gpx_camps[o_gpx_key]=%s\n" % (o_gpx_key, o_gpx_camps[o_gpx_key])) # print " " % tuple(o_gpx_camps[o_gpx_key]) # print " camp%04d" % i # print " Triangle, Green" # print " " # print "" # #elif sys.argv[1] == 'gpx2': # print "" # print "" # for o_gpx_key in sorted(o_gpx_camps): # print '%s' % (o_gpx_camps[o_gpx_key]) # print "" # #elif sys.argv[1] == 'html': # print '' # print '' # print '' # print 'camps' # print '' # print '' # print '' # print ' ' # print ' ' # for o_html_key in sorted(o_html_camps): # print ' %s' % (o_html_camps[o_html_key]) # print '
wayptnameaddrtel
' # print '' # print ''