Skip to content

Commit

Permalink
#8 #11 Work in progress:
Browse files Browse the repository at this point in the history
-Update schema checking

[ci skip]
  • Loading branch information
FABallemand committed Dec 19, 2023
1 parent 5e1c6dd commit 5484d3d
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 139 deletions.
10 changes: 5 additions & 5 deletions docs/tutorials/schemas.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ It is possible to check during parsing, in which case an invalid GPX file will r
import ezgpx

# Check GPX schema when parsing file
gpx = ezgpx.GPX("file.gpx", check_schemas=True, extensions_schemas=False)
gpx = ezgpx.GPX("file.gpx", check_xml_schemas=True, extensions_schemas=False)

Test a :py:class:`~ezgpx.gpx.GPX` Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -26,10 +26,10 @@ A :py:class:`~ezgpx.gpx.GPX` object can directly be checked.

import ezgpx

gpx = GPX("file.gpx", check_schemas=False)
gpx = GPX("file.gpx", check_xml_schemas=False)

# Check GPX schema and extensions schemas from GPX instance
gpx.check_schemas(extensions_schemas=True)
gpx.check_xml_schemas(extensions_schemas=True)

After Writting
^^^^^^^^^^^^^^
Expand All @@ -40,8 +40,8 @@ It is possible to check whether a written GPX file follows XML schemas.

import ezgpx

gpx = GPX("file.gpx", check_schemas=False)
gpx = GPX("file.gpx", check_xml_schemas=False)

# Check if written file follow GPX schema
if gpx.to_gpx("new_file.gpx", check_schemas=True, extensions_schemas=False) == False:
if gpx.to_gpx("new_file.gpx", check_xml_schemas=True, extensions_schemas=False) == False:
print("new_file.gpx does not follow the GPX 1.1 schema!!")
6 changes: 3 additions & 3 deletions examples/check_schemas.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import ezgpx

# Check GPX schema when parsing file
gpx = ezgpx.GPX("file.gpx", check_schemas=True, extensions_schemas=False)
gpx = ezgpx.GPX("file.gpx", check_xml_schemas=True, extensions_schemas=False)

# Check GPX schema and extensions schemas from GPX instance
gpx.check_schemas(extensions_schemas=True)
gpx.check_xml_schemas(extensions_schemas=True)

# Check if written file follow GPX schema
gpx.to_gpx("new_file.gpx", check_schemas=True, extensions_schemas=False)
gpx.to_gpx("new_file.gpx", check_xml_schemas=True, extensions_schemas=False)
61 changes: 41 additions & 20 deletions ezgpx/gpx/gpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ class GPX():
High level GPX object.
"""

def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True, extensions_schemas: bool = False) -> None:
def __init__(
self,
file_path: Optional[str] = None,
xml_schema: bool = True,
xml_extensions_schemas: bool = False) -> None:
"""
Initialise GPX instance.
Parameters
----------
file_path : Optional[str], optional
Path to the file to parse, by default None
check_schemas : bool, optional
xml_schema : bool, optional
Toggle schema verification during parsing, by default True
extensions_schemas : bool, optional
Toggle extensions schema verificaton during parsing, by default False
Expand All @@ -60,14 +64,14 @@ def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True,

# GPX
if file_path.endswith(".gpx"):
self.gpx_parser = GPXParser(file_path, check_schemas, extensions_schemas)
self.gpx_parser = GPXParser(file_path, xml_schema, xml_extensions_schemas)
self.gpx = self.gpx_parser.gpx
self.precisions = self.gpx_parser.precisions
self.time_format = self.gpx_parser.time_format

# KML
elif file_path.endswith(".kml"):
self.kml_parser = KMLParser(file_path, check_schemas, extensions_schemas)
self.kml_parser = KMLParser(file_path, xml_schema, xml_extensions_schemas)
self.gpx = self.kml_parser.gpx
self.precisions = self.kml_parser.precisions
self.time_format = self.kml_parser.time_format
Expand All @@ -80,7 +84,7 @@ def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True,
logging.warning("Unable to parse this file: Expected to find doc.kml inside KMZ file.")
kml = kmz.open("doc.kml", 'r').read()
self.write_tmp_kml("tmp.kml", kml)
self.kml_parser = KMLParser("tmp.kml", check_schemas, extensions_schemas)
self.kml_parser = KMLParser("tmp.kml", xml_schema, xml_extensions_schemas)
self.gpx = self.kml_parser.gpx
self.precisions = self.kml_parser.precisions
self.time_format = self.kml_parser.time_format
Expand All @@ -105,23 +109,29 @@ def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True,
pass

def __str__(self) -> str:
return f"file_path = {self.file_path}\nparser = {self.gpx_parser}\ngpx = {self.gpx}\ngpx_writer = {self.gpx_writer}\nkml_writer = {self.kml_writer}"
return f"file_path = {self.file_path}\ngpx = {self.gpx}"

def check_schemas(self, extensions_schemas: bool = False) -> bool:
def check_xml_schema(self) -> bool:
"""
Check XML schema.
Parameters
----------
extensions_schemas : bool, optional
Toggle extensions schema verificaton. Requires internet connection and is not guaranted to work, by default False
Returns
-------
bool
True if the file follows XML schemas.
"""
return self.gpx.check_xml_schema(self.file_path)

def check_xml_extensions_schemas(self) -> bool:
"""
Check XML extension schemas.
Returns
-------
bool
True if the file follows XML schemas.
"""
return self.gpx.check_schemas(self.file_path, extensions_schemas)
return self.gpx.check_xml_extensions_schemas(self.file_path)

def file_name(self) -> Union[str, None]:
"""
Expand Down Expand Up @@ -638,27 +648,35 @@ def to_csv(
"""
return self.gpx.to_csv(path, sep, columns, header, index)

def to_gpx(self, path: str, check_schemas: bool = True, extensions_schemas: bool = False) -> bool:
def to_gpx(
self,
path: str,
xml_schema: bool = True,
xml_extensions_schemas: bool = False) -> bool:
"""
Write the GPX object to a .gpx file.
Parameters
----------
path : str
Path to the .gpx file.
check_schemas : bool, optional
xml_schema : bool, optional
Toggle schema verification after writting, by default True
extensions_schemas : bool, optional
xml_extensions_schemas : bool, optional
Toggle extensions schema verificaton after writing. Requires internet connection and is not guaranted to work, by default False
Returns
-------
bool
Return False if written file does not follow checked schemas. Return True otherwise.
"""
return self.gpx_writer.write(path, check_schemas, extensions_schemas)
return self.gpx_writer.write(path, xml_schema, xml_extensions_schemas)

def to_kml(self, path: str, styles: Optional[List[Tuple[str, Dict]]] = None, check_schemas: bool = True) -> bool:
def to_kml(
self,
path: str,
styles: Optional[List[Tuple[str, Dict]]] = None,
xml_schema: bool = True) -> bool:
"""
Write the GPX object to a .kml file.
Expand All @@ -668,15 +686,15 @@ def to_kml(self, path: str, styles: Optional[List[Tuple[str, Dict]]] = None, che
Path to the .gpx file.
styles : List[Tuple[str, Dict]], optional
List of (style_id, style) tuples, by default None
check_schemas : bool, optional
xml_schema : bool, optional
Toggle schema verification after writting, by default True
Returns
-------
bool
Return False if written file does not follow checked schemas. Return True otherwise.
"""
return self.kml_writer.write(path, styles, check_schemas)
return self.kml_writer.write(path, styles, xml_schema)

def _matplotlib_plot_text(
self,
Expand Down Expand Up @@ -1224,7 +1242,10 @@ def folium_plot(
if open:
webbrowser.open(file_path)

def write_tmp_kml(self, path: str ="tmp.kml", kml_string: Optional[bytes] = None):
def write_tmp_kml(
self,
path: str ="tmp.kml",
kml_string: Optional[bytes] = None):
"""
Write temproray .KML file in order to parse KMZ file.
"""
Expand Down
66 changes: 44 additions & 22 deletions ezgpx/gpx_elements/gpx.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from importlib_resources import files
from typing import Union, List, Tuple
import logging
import xmlschema
Expand Down Expand Up @@ -100,43 +101,64 @@ def __init__(
self.tracks: List[Track] = tracks
self.extensions: Extensions = extensions

def check_schemas(self, file_path: str, extensions_schemas: bool = False) -> bool:
def check_xml_schema(self, file_path: str) -> bool:
"""
Check XML schema.
Parameters
----------
file_path : str
File path
extensions_schemas : bool, optional
Toggle extensions schema verificaton. Requires internet connection and is not guaranted to work, by default False
Returns
-------
bool
True if the file follows XML schemas
"""
if extensions_schemas:
gpx_schemas = [s for s in self.xsi_schema_location if s.endswith(".xsd")]
for gpx_schema in gpx_schemas:
logging.debug(f"schema = {gpx_schema}")
schema = xmlschema.XMLSchema(gpx_schema)
if not schema.is_valid(file_path):
logging.error(f"File does not follow {gpx_schema}")
return False
else:
schema = None
current_file_path = os.path.dirname(os.path.abspath(__file__))
schema = None

# GPX
if file_path.endswith(".gpx"):
if self.version == "1.1":
schema = xmlschema.XMLSchema(os.path.join(current_file_path, "../schemas/gpx_1_1/gpx.xsd"))
schema = xmlschema.XMLSchema(files("ezgpx.schemas").joinpath("gpx_1_1/gpx.xsd"))
elif self.version == "1.0":
schema = xmlschema.XMLSchema(os.path.join(current_file_path, "../schemas/gpx_1_0/gpx.xsd"))

if schema is not None:
return schema.is_valid(file_path)
schema = xmlschema.XMLSchema(files("ezgpx.schemas").joinpath("gpx_1_0/gpx.xsd"))
else:
logging.error("Unable to check XML schema")
return True
logging.error("Unable to check XML schema (unsupported GPX version)")
return False

# KML
elif file_path.endswith(".kml"):
schema = xmlschema.XMLSchema(files("ezgpx.schemas").joinpath("kml_2_2/ogckml22.xsd"))

# KMZ
elif file_path.endswith(".kmz"):
return False

# FIT
elif file_path.endswith(".fit"):
logging.error("Unable to check XML schema (fit files are not XML files)")
return False

# NOT SUPPORTED
else:
logging.error("Unable to check XML schema (unable to identify file type)")
return False

if schema is not None:
return schema.is_valid(file_path)
else:
logging.error("Unable to check XML schema (unable to load XML schema)")
return False

def check_xml_extensions_schemas(self, file_path: str) -> bool:
gpx_schemas = [s for s in self.xsi_schema_location if s.endswith(".xsd")]
for gpx_schema in gpx_schemas:
logging.debug(f"schema = {gpx_schema}")
schema = xmlschema.XMLSchema(gpx_schema)
if not schema.is_valid(file_path):
logging.error(f"File does not follow {gpx_schema}")
return False

def name(self) -> str:
"""
Expand Down Expand Up @@ -308,7 +330,7 @@ def compute_points_ascent_rate(self) -> None:
ascent = track_point.ele - previous_point.ele
try:
track_point.ascent_rate = (ascent * 100) / distance
logging.info(f"distance={distance} | ascent={ascent} | ascent_rate={track_point.ascent_rate}")
logging.debug(f"distance={distance} | ascent={ascent} | ascent_rate={track_point.ascent_rate}")
except:
track_point.ascent_rate = 0.0
previous_point = track_point
Expand Down
19 changes: 10 additions & 9 deletions ezgpx/gpx_parser/gpx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,25 @@ class GPXParser(XMLParser):
GPX file parser.
"""

def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True, extensions_schemas: bool = False) -> None:
def __init__(
self,
file_path: Optional[str] = None,
check_xml_schemas: bool = True,
xml_extensions_schemas: bool = False) -> None:
"""
Initialize GPXParser instance.
Args:
file_path (str, optional): Path to the file to parse. Defaults to None.
check_schemas (bool, optional): Toggle schema verification during parsing. Defaults to True.
check_xml_schemas (bool, optional): Toggle schema verification during parsing. Defaults to True.
extensions_schemas (bool, optional): Toggle extensions schema verificaton durign parsing. Requires internet connection and is not guaranted to work. Defaults to False.
"""
if not file_path.endswith(".gpx"):
return
super().__init__(file_path,
{"topo": "http://www.topografix.com/GPX/1/1"},
check_schemas,
extensions_schemas)
check_xml_schemas,
xml_extensions_schemas)

if self.file_path is not None and os.path.exists(self.file_path):
self.parse()
Expand Down Expand Up @@ -478,11 +482,8 @@ def parse(self) -> Gpx:
logging.error("Unable to parse properties in GPX file.")
raise

# Check XML schema
if self.check_schemas:
if not self.gpx.check_schemas(self.file_path, self.extensions_schemas):
logging.error("Invalid GPX file (does not follow XML schema).")
raise
# Check XML schemas
self.check_xml_schemas()

# Find precisions
self.find_precisions()
Expand Down

0 comments on commit 5484d3d

Please sign in to comment.