diff --git a/doc/tutorials/index.rst b/doc/tutorials/index.rst index 4a3ff21..397b73a 100644 --- a/doc/tutorials/index.rst +++ b/doc/tutorials/index.rst @@ -8,4 +8,5 @@ Tutorials parsing.rst plotting.rst modifying.rst - writing.rst \ No newline at end of file + writing.rst + schemas.rst \ No newline at end of file diff --git a/doc/tutorials/schemas.rst b/doc/tutorials/schemas.rst new file mode 100644 index 0000000..237acd7 --- /dev/null +++ b/doc/tutorials/schemas.rst @@ -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!!") \ No newline at end of file diff --git a/examples/check_schemas.py b/examples/check_schemas.py new file mode 100644 index 0000000..a8e5d33 --- /dev/null +++ b/examples/check_schemas.py @@ -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) \ No newline at end of file diff --git a/ezgpx/gpx/gpx.py b/ezgpx/gpx/gpx.py index 0b0d2f5..7ea8fde 100644 --- a/ezgpx/gpx/gpx.py +++ b/ezgpx/gpx/gpx.py @@ -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]: """ @@ -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, @@ -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. @@ -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. @@ -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. diff --git a/ezgpx/gpx_elements/gpx.py b/ezgpx/gpx_elements/gpx.py index d106fbe..50ea94e 100644 --- a/ezgpx/gpx_elements/gpx.py +++ b/ezgpx/gpx_elements/gpx.py @@ -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}") diff --git a/ezgpx/gpx_parser/parser.py b/ezgpx/gpx_parser/parser.py index 5cc6458..a0aac6c 100644 --- a/ezgpx/gpx_parser/parser.py +++ b/ezgpx/gpx_parser/parser.py @@ -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 @@ -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: """ @@ -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) @@ -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 diff --git a/ezgpx/gpx_writer/writer.py b/ezgpx/gpx_writer/writer.py index c680520..e46030b 100644 --- a/ezgpx/gpx_writer/writer.py +++ b/ezgpx/gpx_writer/writer.py @@ -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, @@ -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. @@ -552,13 +552,17 @@ def write_gpx(self): f.write("") 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): @@ -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