tkrajina / gpxpy

gpx-py is a python GPX parser. GPX (GPS eXchange Format) is an XML based file format for GPS tracks.
Apache License 2.0
993 stars 223 forks source link

Error writing CDATA #189

Closed slackline closed 4 years ago

slackline commented 4 years ago

I'm trying to convert tcx files to gpx and have found gpxpy incredibly useful, however I've encountered a problem when trying to write CDATA. I'm not entirely sure whether its an issue with gpxpy or not, but thought I'd report it here in case it is.

A self-contained example is...

from lxml.etree import CDATA
from gpxpy import gpx

track = gpx.GPX()
track.name = CDATA('2020-01-05T07:11:05Z')
track.description = CDATA('')
gpx_track = gpx.GPXTrack(name=CDATA('2020-01-05T07:11:05Z'),
                         description=CDATA(''))
gpx_track.type = CDATA('running')
track.tracks.append(gpx_track)
gpx_segment = gpx.GPXTrackSegment()
gpx_track.segments.append(gpx_segment)

print(track.to_xml())

Which results in...

: print(track.to_xml())
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-486-50a8e75d27b4> in <module>
----> 1 import sys, codecs, os, ast;__pyfile = codecs.open('''/tmp/py54SvrR''', encoding='''utf-8''');__code = __pyfile.read().encode('''utf-8''');__pyfile.close();os.remove('''/tmp/py54SvrR''');__block = ast.parse(__code, '''/home/neil/work/python/tcx2gpx/tmp/test.py''', mode='exec'); __block.body = (__block.body if not isinstance(__block.body[0], ast.If) else __block.body if not isinstance(__block.body[0].test, ast.Name) else __block.body if not __block.body[0].test.id == 'True' else __block.body[0].body) if sys.version_info[0] < 3 else (__block.body if not isinstance(__block.body[0], ast.If) else __block.body if not isinstance(__block.body[0].test, ast.NameConstant) else __block.body if not __block.body[0].test.value is True else __block.body[0].body);__last = __block.body[-1];__isexpr = isinstance(__last,ast.Expr);_ = __block.body.pop() if __isexpr else None;exec(compile(__block, '''/home/neil/work/python/tcx2gpx/tmp/test.py''', mode='exec'));eval(compile(ast.Expression(__last.value), '''/home/neil/work/python/tcx2gpx/tmp/test.py''', mode='eval')) if __isexpr else None

~/work/python/tcx2gpx/tmp/test.py in <module>

~/.virtualenvs/default/lib/python3.6/site-packages/gpxpy/gpx.py in to_xml(self, version, prettyprint)
   2724             },
   2725             nsmap=self.nsmap,
-> 2726             prettyprint=prettyprint
   2727         )
   2728 

~/.virtualenvs/default/lib/python3.6/site-packages/gpxpy/gpxfield.py in gpx_fields_to_xml(instance, tag, version, custom_attributes, nsmap, prettyprint, indent)
    545                 xml_value = gpx_field.to_xml(value, version, nsmap,
    546                                              prettyprint=prettyprint,
--> 547                                              indent=indent + '  ')
    548                 if xml_value:
    549                     body.append(xml_value)

~/.virtualenvs/default/lib/python3.6/site-packages/gpxpy/gpxfield.py in to_xml(self, value, version, nsmap, prettyprint, indent)
    218             value = self.type_converter.to_string(value)
    219         return mod_utils.to_xml(self.tag, content=value, escape=True,
--> 220                                 prettyprint=prettyprint, indent=indent)
    221 
    222 

~/.virtualenvs/default/lib/python3.6/site-packages/gpxpy/utils.py in to_xml(tag, attributes, content, default, escape, prettyprint, indent)
     40     else:
     41         if escape:
---> 42             result.append(make_str('>%s</%s>' % (mod_saxutils.escape(content), tag)))
     43         else:
     44             result.append(make_str('>%s</%s>' % (content, tag)))

/usr/lib64/python3.6/xml/sax/saxutils.py in escape(data, entities)
     25 
     26     # must do ampersand first
---> 27     data = data.replace("&", "&amp;")
     28     data = data.replace(">", "&gt;")
     29     data = data.replace("<", "&lt;")

AttributeError: 'lxml.etree.CDATA' object has no attribute 'replace'

I was expecting the following to be written...

  <metadata>
    <name><![CDATA[2020-01-05T07:11:05Z]]></name>
    <desc><![CDATA[]]></desc>
  </metadata>
  <trk>
    <name><![CDATA[2020-01-05T07:11:05Z]]></name>
    <desc><![CDATA[]]></desc>
    <type><![CDATA[running]]></type>

I had originally tried writing the CDATA explicitly as in...

track.name = '//<![CDATA[2020-01-05T07:11:05Z]]>'

...but when printing this results in...

    <name>//&lt;![CDATA[2019-11-08T08:47:06Z]]&gt;</name>

I found the suggestion of using lxml.etree.CDATA() here, is this the wrong approach to be taking perhaps?

I'm using...

print(gpxpy.__version__)
1.3.5

print(lxml.etree.__version__)
4.4.1

print(sys.version)
3.6.10 (default, Dec 22 2019, 21:38:55) 
[GCC 9.2.0]
tkrajina commented 4 years ago

Unfortunately, this is not possible at the moment.

BTW, Why do you need this? 2020-01-05T07:11:05Z is a perfectly valid name even without CNAME.

slackline commented 4 years ago

@tkrajina thanks for taking the time to look at this.

I'm trying to convert tcx files dumped by endomondo.com to gpx for importing to OpenTracks.

I exported a gpx from OpenTracks to check what fields are included and it had...

<metadata>
<name><![CDATA[2019-12-24 08:36]]></name>
<desc><![CDATA[]]></desc>
</metadata>
<trk>
<name><![CDATA[2019-12-24 08:36]]></name>
<desc><![CDATA[]]></desc>
<type><![CDATA[biking]]></type>
<extensions><topografix:color>c0c0c0</topografix:color></extensions>
<trkseg>

...so I was trying to output the tcx in the exact same format.

I hadn't checked whether I had to have the fields exactly but have now tried importing a converted tcx file with the fields as text rather than CDATA and can successfully import a converted gpx.

I've another issue with writing an .extensions field but thats minor (it just dictates the colour the track is plotted when opened on a map), but it basically works now which I'm happy about.

My very crude package is here, still need to write some tests for it (can not get my head around Test Driven Development!).

Thanks again for your time.