Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension handling and refactor #105

Merged
merged 33 commits into from
Apr 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
74a3fb9
Refactoring XMLParser
nawagers Dec 10, 2017
e4675f4
Simplify get_first_child, unittest only once
nawagers Dec 10, 2017
d8d894c
Remove XMLParser
nawagers Dec 11, 2017
228a1c3
Regression with cElementTree support
nawagers Dec 11, 2017
a8f9c4b
Regression: circular import, removed unused parser args
nawagers Dec 11, 2017
d4e8e27
Removing gpx_check_slots_and_default_values
nawagers Dec 11, 2017
79dec0d
Added GPXParser.__library() for unittests
nawagers Dec 11, 2017
d10f571
Switch to super()
nawagers Dec 11, 2017
107a244
Roll back super(), forgot about Python2
nawagers Dec 11, 2017
2761769
Future proof version check
nawagers Dec 11, 2017
fdcdeff
switched first_child to find()
nawagers Dec 11, 2017
909c4de
Switch to .format() and .join() for strings
nawagers Dec 11, 2017
654f1d3
Use explicit positions in format for 2.6 compat
nawagers Dec 11, 2017
c5243d7
I hate namespaces [skip ci]
nawagers Dec 12, 2017
437d351
namespace prefix map cleanup [skip ci]
nawagers Dec 12, 2017
ef381b2
Changed extension to ETree
nawagers Dec 12, 2017
fc56a2b
Regression: no maxsplit in older versions of Python
nawagers Dec 12, 2017
93babf7
Regression: Python 2.6 dict syntax
nawagers Dec 12, 2017
174448a
Regression 2.6 being whiny about some syntax
nawagers Dec 12, 2017
902d712
Regression: 2.6 dict syntax
nawagers Dec 12, 2017
fd1bf9d
Regression: Typo
nawagers Dec 12, 2017
48f2590
Drop 2.6 & STDLIB from travis
nawagers Dec 12, 2017
f607dd7
.travis format
nawagers Dec 12, 2017
f472c14
Revert ".travis format"
nawagers Dec 12, 2017
688e889
Drop 2.6
nawagers Dec 12, 2017
c0aa992
Attrib serialization, switching computers [skip ci]
nawagers Dec 13, 2017
055ccc2
Serialization changes
nawagers Dec 13, 2017
07bbbe0
Pretty Print
nawagers Dec 14, 2017
60eaca9
Add tests, switch extensions to list
nawagers Dec 14, 2017
45defc2
lxml doesn't support saving attributes as dict
nawagers Dec 14, 2017
1776646
Typo...
nawagers Dec 14, 2017
6c0dfa2
Default version, 1.0 extension test, small bug
nawagers Dec 14, 2017
93ee2d8
Remove extensions.py
nawagers Dec 14, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pip-log.txt
.coverage
.tox
nosetests.xml
htmlcov/

# Translations
*.mo
Expand Down
9 changes: 4 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ sudo: false

# Those without lxml wheels first because building lxml is slow
python:
- 3.5
- 3.2
- 2.7
- 2.6
- 3.4
- 3.3
- 3.4
- 3.5
- 3.6

env:
- XMLPARSER=LXML
- XMLPARSER=STDLIB


install:
# Check whether to install lxml
# Pin versions 2.6 and 3.2 to lxml 3.6 (end of support)
Expand All @@ -25,7 +25,7 @@ install:
"2.7"|"3.3"|"3.4"|"3.5"|"3.6")
pip install lxml
;;
"2.6"|"3.2")
"3.2")
pip install lxml==3.6
;;
esac
Expand All @@ -40,7 +40,6 @@ install:


script:
- python -m unittest test
- coverage run --source=gpxpy ./test.py

after_success:
Expand Down
94 changes: 41 additions & 53 deletions gpxpy/gpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
mod_gpxfield.GPXField('dgps_id', 'dgpsid'),
]
GPX_11_POINT_FIELDS = [
# See GPX for description of text fields
mod_gpxfield.GPXField('latitude', attribute='lat', type=mod_gpxfield.FLOAT_TYPE, mandatory=True),
mod_gpxfield.GPXField('longitude', attribute='lon', type=mod_gpxfield.FLOAT_TYPE, mandatory=True),
mod_gpxfield.GPXField('elevation', 'ele', type=mod_gpxfield.FLOAT_TYPE),
Expand All @@ -81,7 +82,7 @@
mod_gpxfield.GPXField('comment', 'cmt'),
mod_gpxfield.GPXField('description', 'desc'),
mod_gpxfield.GPXField('source', 'src'),
'link',
'link:@link',
mod_gpxfield.GPXField('link', attribute='href'),
mod_gpxfield.GPXField('link_text', tag='text'),
mod_gpxfield.GPXField('link_type', tag='type'),
Expand All @@ -95,7 +96,7 @@
mod_gpxfield.GPXField('position_dilution', 'pdop', type=mod_gpxfield.FLOAT_TYPE),
mod_gpxfield.GPXField('age_of_dgps_data', 'ageofdgpsdata', type=mod_gpxfield.FLOAT_TYPE),
mod_gpxfield.GPXField('dgps_id', 'dgpsid'),
mod_gpxfield.GPXExtensionsField('extensions'),
mod_gpxfield.GPXExtensionsField('extensions', is_list=True),
]

# GPX1.0 track points have two more fields after time
Expand Down Expand Up @@ -155,9 +156,6 @@ def __init__(self, min_latitude=None, max_latitude=None, min_longitude=None, max
def __iter__(self):
return (self.min_latitude, self.max_latitude, self.min_longitude, self.max_longitude,).__iter__()

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)


class GPXXMLSyntaxException(GPXException):
"""
Expand Down Expand Up @@ -210,7 +208,7 @@ def __init__(self, latitude=None, longitude=None, elevation=None, time=None,
self.position_dilution = position_dilution
self.age_of_dgps_data = None
self.dgps_id = None
self.extensions = None
self.extensions = []

def __str__(self):
return '[wpt{%s}:%s,%s@%s]' % (self.name, self.latitude, self.longitude, self.elevation)
Expand All @@ -230,9 +228,6 @@ def get_max_dilution_of_precision(self):
"""
return max(self.horizontal_dilution, self.vertical_dilution, self.position_dilution)

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)


class GPXRoutePoint(mod_geo.Location):
gpx_10_fields = GPX_10_POINT_FIELDS
Expand Down Expand Up @@ -274,7 +269,7 @@ def __init__(self, latitude=None, longitude=None, elevation=None, time=None, nam
self.age_of_dgps_data = None
self.dgps_id = None
self.link_type = None
self.extensions = None
self.extensions = []

def __str__(self):
return '[rtept{%s}:%s,%s@%s]' % (self.name, self.latitude, self.longitude, self.elevation)
Expand All @@ -288,9 +283,6 @@ def __repr__(self):
representation += ', %s=%s' % (attribute, repr(value))
return 'GPXRoutePoint(%s)' % representation

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)


class GPXRoute:
gpx_10_fields = [
Expand All @@ -304,18 +296,19 @@ class GPXRoute:
mod_gpxfield.GPXComplexField('points', tag='rtept', classs=GPXRoutePoint, is_list=True),
]
gpx_11_fields = [
# See GPX for description of text fields
mod_gpxfield.GPXField('name'),
mod_gpxfield.GPXField('comment', 'cmt'),
mod_gpxfield.GPXField('description', 'desc'),
mod_gpxfield.GPXField('source', 'src'),
'link',
'link:@link',
mod_gpxfield.GPXField('link', attribute='href'),
mod_gpxfield.GPXField('link_text', tag='text'),
mod_gpxfield.GPXField('link_type', tag='type'),
'/link',
mod_gpxfield.GPXField('number', type=mod_gpxfield.INT_TYPE),
mod_gpxfield.GPXField('type'),
mod_gpxfield.GPXExtensionsField('extensions'),
mod_gpxfield.GPXExtensionsField('extensions', is_list=True),
mod_gpxfield.GPXComplexField('points', tag='rtept', classs=GPXRoutePoint, is_list=True),
]

Expand All @@ -334,7 +327,7 @@ def __init__(self, name=None, description=None, number=None):
self.points = []
self.link_type = None
self.type = None
self.extensions = None
self.extensions = []

def remove_elevation(self):
""" Removes elevation data from route """
Expand Down Expand Up @@ -428,9 +421,6 @@ def move(self, location_delta):
for route_point in self.points:
route_point.move(location_delta)

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)

def __repr__(self):
representation = ''
for attribute in 'name', 'description', 'number':
Expand Down Expand Up @@ -481,7 +471,7 @@ def __init__(self, latitude=None, longitude=None, elevation=None, time=None, sym
self.position_dilution = position_dilution
self.age_of_dgps_data = None
self.dgps_id = None
self.extensions = None
self.extensions = []

def __repr__(self):
representation = '%s, %s' % (self.latitude, self.longitude)
Expand Down Expand Up @@ -570,24 +560,21 @@ def speed_between(self, track_point):
def __str__(self):
return '[trkpt:%s,%s@%s@%s]' % (self.latitude, self.longitude, self.elevation, self.time)

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)


class GPXTrackSegment:
gpx_10_fields = [
mod_gpxfield.GPXComplexField('points', tag='trkpt', classs=GPXTrackPoint, is_list=True),
]
gpx_11_fields = [
mod_gpxfield.GPXComplexField('points', tag='trkpt', classs=GPXTrackPoint, is_list=True),
mod_gpxfield.GPXExtensionsField('extensions'),
mod_gpxfield.GPXExtensionsField('extensions', is_list=True),
]

__slots__ = ('points', 'extensions', )

def __init__(self, points=None):
self.points = points if points else []
self.extensions = None
self.extensions = []

def simplify(self, max_distance=None):
"""
Expand Down Expand Up @@ -1293,8 +1280,6 @@ def has_elevations(self):

return len(self.points) > 2 and float(found) / float(len(self.points)) > .75

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)

def __repr__(self):
return 'GPXTrackSegment(points=[%s])' % ('...' if self.points else '')
Expand All @@ -1315,18 +1300,19 @@ class GPXTrack:
mod_gpxfield.GPXComplexField('segments', tag='trkseg', classs=GPXTrackSegment, is_list=True),
]
gpx_11_fields = [
# See GPX for text field description
mod_gpxfield.GPXField('name'),
mod_gpxfield.GPXField('comment', 'cmt'),
mod_gpxfield.GPXField('description', 'desc'),
mod_gpxfield.GPXField('source', 'src'),
'link',
'link:@link',
mod_gpxfield.GPXField('link', attribute='href'),
mod_gpxfield.GPXField('link_text', tag='text'),
mod_gpxfield.GPXField('link_type', tag='type'),
'/link',
mod_gpxfield.GPXField('number', type=mod_gpxfield.INT_TYPE),
mod_gpxfield.GPXField('type'),
mod_gpxfield.GPXExtensionsField('extensions'),
mod_gpxfield.GPXExtensionsField('extensions', is_list=True),
mod_gpxfield.GPXComplexField('segments', tag='trkseg', classs=GPXTrackSegment, is_list=True),
]

Expand All @@ -1345,7 +1331,7 @@ def __init__(self, name=None, description=None, number=None):
self.segments = []
self.link_type = None
self.type = None
self.extensions = None
self.extensions = []

def simplify(self, max_distance=None):
"""
Expand Down Expand Up @@ -1850,8 +1836,6 @@ def get_nearest_location(self, location):
def clone(self):
return mod_copy.deepcopy(self)

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)

def __repr__(self):
representation = ''
Expand Down Expand Up @@ -1880,27 +1864,34 @@ class GPX:
mod_gpxfield.GPXComplexField('routes', classs=GPXRoute, tag='rte', is_list=True),
mod_gpxfield.GPXComplexField('tracks', classs=GPXTrack, tag='trk', is_list=True),
]
# Text fields serialize as empty container tags, dependents are
# are listed after as 'tag:dep1:dep2:dep3'. If no dependents are
# listed, it will always serialize. The container is closed with
# '/tag'. Required dependents are preceded by an @. If a required
# dependent is empty, nothing in the container will serialize. The
# format is 'tag:@dep2'. No optional dependents need to be listed.
# Extensions not yet supported
gpx_11_fields = [
mod_gpxfield.GPXField('version', attribute=True),
mod_gpxfield.GPXField('creator', attribute=True),
'metadata',
'metadata:name:description:author_name:author_email:author_link:copyright_author:copyright_year:copyright_license:link:time:keywords:bounds',
mod_gpxfield.GPXField('name', 'name'),
mod_gpxfield.GPXField('description', 'desc'),
'author',
'author:author_name:author_email:author_link',
mod_gpxfield.GPXField('author_name', 'name'),
mod_gpxfield.GPXEmailField('author_email', 'email'),
'link',
'link:@author_link',
mod_gpxfield.GPXField('author_link', attribute='href'),
mod_gpxfield.GPXField('author_link_text', tag='text'),
mod_gpxfield.GPXField('author_link_type', tag='type'),
'/link',
'/author',
'copyright',
'copyright:copyright_author:copyright_year:copyright_license',
mod_gpxfield.GPXField('copyright_author', attribute='author'),
mod_gpxfield.GPXField('copyright_year', tag='year'),
mod_gpxfield.GPXField('copyright_license', tag='license'),
'/copyright',
'link',
'link:@link',
mod_gpxfield.GPXField('link', attribute='href'),
mod_gpxfield.GPXField('link_text', tag='text'),
mod_gpxfield.GPXField('link_type', tag='type'),
Expand All @@ -1913,15 +1904,15 @@ class GPX:
mod_gpxfield.GPXComplexField('waypoints', classs=GPXWaypoint, tag='wpt', is_list=True),
mod_gpxfield.GPXComplexField('routes', classs=GPXRoute, tag='rte', is_list=True),
mod_gpxfield.GPXComplexField('tracks', classs=GPXTrack, tag='trk', is_list=True),
mod_gpxfield.GPXExtensionsField('extensions'),
mod_gpxfield.GPXExtensionsField('extensions', is_list=True),
]

__slots__ = ('version', 'creator', 'name', 'description', 'author_name',
'author_email', 'link', 'link_text', 'time', 'keywords',
'bounds', 'waypoints', 'routes', 'tracks', 'author_link',
'author_link_text', 'author_link_type', 'copyright_author',
'copyright_year', 'copyright_license', 'link_type',
'metadata_extensions', 'extensions')
'metadata_extensions', 'extensions', 'nsmap')

def __init__(self):
self.version = None
Expand All @@ -1942,11 +1933,12 @@ def __init__(self):
self.copyright_author = None
self.copyright_year = None
self.copyright_license = None
self.metadata_extensions = None
self.extensions = None
self.metadata_extensions = []
self.extensions = []
self.waypoints = []
self.routes = []
self.tracks = []
self.nsmap = {}

def simplify(self, max_distance=None):
"""
Expand Down Expand Up @@ -2564,15 +2556,15 @@ def move(self, location_delta):
for track in self.tracks:
track.move(location_delta)

def to_xml(self, version=None):
def to_xml(self, version=None, prettyprint=True):
"""
FIXME: Note, this method will change self.version
"""
if not version:
if self.version:
version = self.version
else:
version = '1.0'
version = '1.1'

if version != '1.0' and version != '1.1':
raise GPXException('Invalid version %s' % version)
Expand All @@ -2581,14 +2573,13 @@ def to_xml(self, version=None):
if not self.creator:
self.creator = 'gpx.py -- https://github.com/tkrajina/gpxpy'

v = version.replace('.', '/')
xml_attributes = (
('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'),
('xmlns', 'http://www.topografix.com/GPX/%s' % v),
('xsi:schemaLocation', 'http://www.topografix.com/GPX/%s http://www.topografix.com/GPX/%s/gpx.xsd' % (v, v))
)
self.nsmap['xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
self.nsmap['defaultns'] = 'http://www.topografix.com/GPX/{0}'.format(version.replace('.', '/'))
schemalocs = 'http://www.topografix.com/GPX/{0} http://www.topografix.com/GPX/{0}/gpx.xsd'
xml_attributes = {'xsi:schemaLocation':
schemalocs.format(version.replace('.', '/'))}

content = mod_gpxfield.gpx_fields_to_xml(self, 'gpx', version, custom_attributes=xml_attributes)
content = mod_gpxfield.gpx_fields_to_xml(self, 'gpx', version, custom_attributes=xml_attributes, nsmap=self.nsmap, prettyprint=prettyprint)

return '<?xml version="1.0" encoding="UTF-8"?>\n' + content.strip()

Expand All @@ -2614,9 +2605,6 @@ def has_elevations(self):

return result

def __hash__(self):
return mod_utils.hash_object(self, self.__slots__)

def __repr__(self):
representation = ''
for attribute in 'waypoints', 'routes', 'tracks':
Expand Down
Loading