Skip to content

Commit

Permalink
#1 #6 Work in progress:
Browse files Browse the repository at this point in the history
-Parser minor modifications
-Schema checking example file
-Schema checking documentation

[ci skip]
  • Loading branch information
FABallemand committed Aug 31, 2023
1 parent ea576ea commit 775d14f
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 44 deletions.
3 changes: 2 additions & 1 deletion doc/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ Tutorials
parsing.rst
plotting.rst
modifying.rst
writing.rst
writing.rst
schemas.rst
47 changes: 47 additions & 0 deletions doc/tutorials/schemas.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Checking Schemas
----------------

There are tree ways to check if a GPX file follows the XML schemas.
In all cases, the GPX file will be checked for the relevant Topographix GPX schema (either version 1.0 or 1.1).
If the :py:arg:`~extensions_schemas` is set to :py:val:`~True`, then the GPX file will be tested for all schemas that are listed.

During Parsing
^^^^^^^^^^^^^^

It is possible to check during parsing, in which case an invalid GPX file will raise an error.

::

import ezgpx

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

Test a :py:class:`~ezgpx.gpx.GPX` Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A :py:class:`~ezgpx.gpx.GPX` object can directly be checked.

::

import ezgpx

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

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

After Writting
^^^^^^^^^^^^^^

It is possible to check whether a written GPX file follows XML schemas.

::

import ezgpx

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

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

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

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

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

def __init__(self, file_path: Optional[str] = None) -> None:
if file_path is not None:
def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True, extensions_schemas: bool = False) -> None:
"""
Initialise GPX 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.
extensions_schemas (bool, optional): Toggle extensions schema verificaton during parsing. Requires internet connection and is not guaranted to work. Defaults to False.
"""
if file_path is not None and os.path.exists(file_path):
self.file_path: str = file_path
self.parser: Parser = Parser(file_path)
self.parser: Parser = Parser(file_path, check_schemas, extensions_schemas)
self.gpx: Gpx = self.parser.gpx
self.writer: Writer = Writer(
self.gpx, precisions=self.parser.precisions, time_format=self.parser.time_format)
else:
logging.warning("File path does not exist")
pass

def __str__(self) -> str:
return f"file_path = {self.file_path}\nparser = {self.parser}\ngpx = {self.gpx}\nwriter = {self.writer}"

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

def file_name(self) -> Union[str, None]:
"""
Expand Down Expand Up @@ -439,14 +448,19 @@ def to_dataframe(
"""
return self.gpx.to_dataframe(projection, elevation, speed, pace, ascent_rate, ascent_speed)

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

def to_csv(
self,
Expand All @@ -464,7 +478,7 @@ def to_csv(
Write the GPX object track coordinates to a .csv file.
Args:
projection (bool, optional): Toogle projected coordinates. Defaults to False.
projection (bool, optional): Toggle projected coordinates. Defaults to False.
elevation (bool, optional): Toggle elevation. Defaults to True.
speed (bool, optional): Toggle speed. Defaults to False.
pace (bool, optional): Toggle pace. Defaults to False.
Expand Down Expand Up @@ -546,7 +560,7 @@ def matplotlib_axes_plot(
axes (matplotlib.axes.Axes): Axes to plot on.
projection (Optional[str], optional): Projection. Defaults to None.
color (str, optional): A color string (ie: "#FF0000" or "red") or a track attribute ("elevation", "speed", "pace", "vertical_drop", "ascent_rate", "ascent_speed") Defaults to "#101010".
colorbar (bool, optional): Colorbar toggle. Defaults to False.
colorbar (bool, optional): Colorbar Toggle. Defaults to False.
elevation_color (bool, optional): Color track according to elevation. Defaults to False.
start_stop_colors (tuple[str, str], optional): Start and stop points colors. Defaults to False.
way_points_color (str, optional): Way point color. Defaults to False.
Expand Down Expand Up @@ -659,7 +673,7 @@ def matplotlib_plot(
Args:
projection (Optional[str], optional): Projection. Defaults to None.
color (str, optional): A color string (ie: "#FF0000" or "red") or a track attribute ("elevation", "speed", "pace", "vertical_drop", "ascent_rate", "ascent_speed") Defaults to "#101010".
colorbar (bool, optional): Colorbar toggle. Defaults to False.
colorbar (bool, optional): Colorbar Toggle. Defaults to False.
start_stop_colors (tuple[str, str], optional): Start and stop points colors. Defaults to False.
way_points_color (str, optional): Way point color. Defaults to False.
title (Optional[str], optional): Title. Defaults to None.
Expand Down
8 changes: 4 additions & 4 deletions ezgpx/gpx_elements/gpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,21 @@ def __init__(
self.tracks: list[Track] = tracks
self.extensions: Extensions = extensions

def check_schema(self, file_path: str, extensions_schema: bool = False) -> bool:
def check_schemas(self, file_path: str, extensions_schemas: bool = False) -> bool:
"""
Check XML schema.
Args:
file_path (str): File path.
extensions_schema (bool, optional): Toogle extensions schema verificaton. Requires internet connection and is not guaranted to work.Defaults to False.
extensions_schemas (bool, optional): Toggle extensions schema verificaton. Requires internet connection and is not guaranted to work.Defaults to False.
Returns:
bool: True if the file follows XML schemas.
"""
if extensions_schema:
if extensions_schemas:
gpx_schemas = [s for s in self.xsi_schema_location if s.endswith(".xsd")]
for gpx_schema in gpx_schemas:
print(f"schema = {gpx_schema}")
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}")
Expand Down
32 changes: 12 additions & 20 deletions ezgpx/gpx_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ class Parser():
GPX file parser.
"""

def __init__(self, file_path: Optional[str] = None) -> None:
def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True, extensions_schemas: bool = False) -> None:
"""
initialize Parser instance.
Initialize Parser 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.
extensions_schemas (bool, optional): Toggle extensions schema verificaton durign parsing. Requires internet connection and is not guaranted to work. Defaults to False.
"""
self.file_path: str = file_path
if not os.path.exists(self.file_path):
logging.warning("File path does not exist")
self.check_schemas: bool = check_schemas
self.extensions_schemass: bool = extensions_schemas

self.gpx_tree: ET.ElementTree = None
self.gpx_root: ET.Element = None
Expand All @@ -42,8 +44,10 @@ def __init__(self, file_path: Optional[str] = None) -> None:

self.gpx: Gpx = Gpx()

if self.file_path is not None:
if self.file_path is not None and os.path.exists(self.file_path):
self.parse()
else:
logging.warning("File path does not exist")

def find_precision(self, number: str) -> int:
"""
Expand Down Expand Up @@ -623,25 +627,13 @@ def parse_root_extensions(self):
extensions = self.gpx_root.find("topo:extensions", self.name_space)
self.gpx.extensions = self.parse_extensions(extensions)

def parse(self, file_path: Optional[str] = None, check_schema: bool = True, extensions_schema: bool = False) -> Gpx:
def parse(self) -> Gpx:
"""
Parse GPX file.
Args:
file_path (str, optional): Path to the file to parse. Defaults to None.
check_schema (bool, optional): Toogle schema verificaton. Defaults to True.
extensions_schema (bool, optional): Toogle extensions schema verificaton. Requires internet connection and is not guaranted to work.Defaults to False.
Returns:
Gpx: Gpx instance., self.name_space).text
"""
# File
if file_path is not None and os.path.exists(file_path):
self.file_path = file_path
elif self.file_path == "":
logging.error("No GPX file to parse.")
return

# Parse GPX file
try:
self.gpx_tree = ET.parse(self.file_path)
Expand All @@ -658,8 +650,8 @@ def parse(self, file_path: Optional[str] = None, check_schema: bool = True, exte
raise

# Check XML schema
if check_schema:
if not self.gpx.check_schema(self.file_path, extensions_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

Expand Down
21 changes: 13 additions & 8 deletions ezgpx/gpx_writer/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Writer():
def __init__(
self,
gpx: Gpx = None,
path: str = "",
path: str = None,
properties: bool = True,
metadata: bool = True,
way_points: bool = True,
Expand All @@ -30,7 +30,7 @@ def __init__(
Args:
gpx (Gpx, optional): Gpx instance to write. Defaults to None.
path (str, optional): Path to the file to write. Defaults to "".
path (str, optional): Path to the file to write. Defaults to None.
properties (bool, optional): Toggle properties writting. Defaults to True.
metadata (bool, optional): Toggle metadata writting. Defaults to True.
way_point (bool, optional): Toggle way points writting. Defaults to True.
Expand Down Expand Up @@ -552,13 +552,17 @@ def write_gpx(self):
f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
f.write(self.gpx_string)

def write(self, path: str, check_schema: bool = False):
def write(self, path: str, check_schemas: bool = False, extensions_schemas: bool = False) -> bool:
"""
Handle writing.
Args:
path (str): Path to write the GPX file.
check_schema (bool, optional): Toggle schema verification after writting. Defaults to False.
check_schemas (bool, optional): Toggle schema verification after writting. Defaults to False.
extensions_schemas (bool, optional): Toggle extensions schema verificaton after writing. Requires internet connection and is not guaranted to work. Defaults to False.
Returns:
bool: Return False if written file does not follow checked schemas. Return True otherwise.
"""
directory_path = os.path.dirname(os.path.realpath(path))
if not os.path.exists(directory_path):
Expand All @@ -569,9 +573,10 @@ def write(self, path: str, check_schema: bool = False):
self.gpx_to_string()
self.write_gpx()

if check_schema:
verification_gpx = Parser(path)
if not verification_gpx.check_schema(extensions_schema=False):
if check_schemas:
verification_gpx = Parser(path, check_schemas=False, extensions_schemas=False).gpx
if not verification_gpx.check_schemas(self.path, extensions_schemas=extensions_schemas):
logging.error("Invalid written file (does not follow schema)")
return
return False
return True

0 comments on commit 775d14f

Please sign in to comment.