diff --git a/docs/HISTORY.rst b/docs/HISTORY.rst index 360ef1f5..4e171fa8 100644 --- a/docs/HISTORY.rst +++ b/docs/HISTORY.rst @@ -6,6 +6,7 @@ Changelog - Add support for ScreenOverlay and Model. - allow parsing kml files without namespace declarations. +- Add support for NetworkLinkControl. [Apurva Banka] 1.0 (2024/11/19) diff --git a/docs/network.kml b/docs/network.kml new file mode 100644 index 00000000..48f6004d --- /dev/null +++ b/docs/network.kml @@ -0,0 +1,16 @@ + + + + 43200 + -1 + + A snippet of XHTML

+ ]]> +
+ 2008-05-30 +
+
\ No newline at end of file diff --git a/fastkml/__init__.py b/fastkml/__init__.py index bf8afff5..e94dae15 100644 --- a/fastkml/__init__.py +++ b/fastkml/__init__.py @@ -58,6 +58,7 @@ from fastkml.model import Orientation from fastkml.model import ResourceMap from fastkml.model import Scale +from fastkml.network_link_control import NetworkLinkControl from fastkml.overlays import GroundOverlay from fastkml.overlays import ImagePyramid from fastkml.overlays import LatLonBox @@ -119,6 +120,7 @@ "Model", "MultiGeometry", "NetworkLink", + "NetworkLinkControl", "Orientation", "OuterBoundaryIs", "OverlayXY", diff --git a/fastkml/kml.py b/fastkml/kml.py index aa9c4db1..aee6f9cd 100644 --- a/fastkml/kml.py +++ b/fastkml/kml.py @@ -51,6 +51,7 @@ from fastkml.features import Placemark from fastkml.helpers import xml_subelement_list from fastkml.helpers import xml_subelement_list_kwarg +from fastkml.network_link_control import NetworkLinkControl from fastkml.overlays import GroundOverlay from fastkml.overlays import PhotoOverlay from fastkml.registry import RegistryItem @@ -59,7 +60,14 @@ logger = logging.getLogger(__name__) -kml_children = Union[Folder, Document, Placemark, GroundOverlay, PhotoOverlay] +kml_children = Union[ + Folder, + Document, + Placemark, + GroundOverlay, + PhotoOverlay, + NetworkLinkControl, +] def lxml_parse_and_validate( @@ -285,9 +293,20 @@ def write( registry.register( KML, RegistryItem( - ns_ids=("kml", ""), - classes=(Document, Folder, Placemark, GroundOverlay, PhotoOverlay, NetworkLink), - node_name="Document,Folder,Placemark,GroundOverlay,PhotoOverlay,NetworkLink", + ns_ids=("kml",), + classes=( + Document, + Folder, + Placemark, + GroundOverlay, + PhotoOverlay, + NetworkLink, + NetworkLinkControl, + ), + node_name=( + "Document,Folder,Placemark,GroundOverlay,PhotoOverlay,NetworkLink," + "NetworkLinkControl" + ), attr_name="features", get_kwarg=xml_subelement_list_kwarg, set_element=xml_subelement_list, diff --git a/fastkml/network_link_control.py b/fastkml/network_link_control.py new file mode 100644 index 00000000..01d2d066 --- /dev/null +++ b/fastkml/network_link_control.py @@ -0,0 +1,261 @@ +# Copyright (C) 2024 Christian Ledermann +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +""" +NetworkLinkControl class. + +Controls the behavior of files fetched by a . + +https://developers.google.com/kml/documentation/kmlreference#networklinkcontrol +""" + +import logging +from typing import Any +from typing import Dict +from typing import Optional +from typing import Union + +from fastkml import config +from fastkml.base import _XMLObject +from fastkml.helpers import clean_string +from fastkml.helpers import datetime_subelement +from fastkml.helpers import datetime_subelement_kwarg +from fastkml.helpers import float_subelement +from fastkml.helpers import subelement_float_kwarg +from fastkml.helpers import subelement_text_kwarg +from fastkml.helpers import text_subelement +from fastkml.helpers import xml_subelement +from fastkml.helpers import xml_subelement_kwarg +from fastkml.registry import RegistryItem +from fastkml.registry import registry +from fastkml.times import KmlDateTime +from fastkml.views import Camera +from fastkml.views import LookAt + +__all__ = [ + "NetworkLinkControl", +] + +logger = logging.getLogger(__name__) + + +class NetworkLinkControl(_XMLObject): + """Controls the behavior of files fetched by a .""" + + _default_nsid = config.KML + + min_refresh_period: Optional[float] + max_session_length: Optional[float] + cookie: Optional[str] + message: Optional[str] + link_name: Optional[str] + link_description: Optional[str] + link_snippet: Optional[str] + expires: Optional[KmlDateTime] + view: Union[Camera, LookAt, None] + + def __init__( + self, + ns: Optional[str] = None, + name_spaces: Optional[Dict[str, str]] = None, + min_refresh_period: Optional[float] = None, + max_session_length: Optional[float] = None, + cookie: Optional[str] = None, + message: Optional[str] = None, + link_name: Optional[str] = None, + link_description: Optional[str] = None, + link_snippet: Optional[str] = None, + expires: Optional[KmlDateTime] = None, + view: Optional[Union[Camera, LookAt]] = None, + **kwargs: Any, + ) -> None: + """ + Create a NetworkLinkControl object. + + Parameters + ---------- + ns : str, optional + The namespace to use for the NetworkLinkControl object. + name_spaces : dict, optional + A dictionary of namespaces to use for the NetworkLinkControl object. + min_refresh_period : float, optional + The minimum number of seconds between fetches. A value of -1 indicates that + the NetworkLinkControl object should be fetched only once. + max_session_length : float, optional + The maximum number of seconds that the link should be followed. + cookie : str, optional + A string value that can be used to identify the client request. + message : str, optional + A message to be displayed to the user in case of a failure. + link_name : str, optional + The name of the link. + link_description : str, optional + A description of the link. + link_snippet : str, optional + A snippet of text to be displayed in the link. + expires : KmlDateTime, optional + The time at which the link should expire. + view : Camera or LookAt, optional + The view to be used when the link is followed. + **kwargs : Any, optional + Additional keyword arguments. + + """ + super().__init__( + ns=ns, + name_spaces=name_spaces, + **kwargs, + ) + self.min_refresh_period = min_refresh_period + self.max_session_length = max_session_length + self.cookie = clean_string(cookie) + self.message = clean_string(message) + self.link_name = clean_string(link_name) + self.link_description = clean_string(link_description) + self.link_snippet = clean_string(link_snippet) + self.expires = expires + self.view = view + + def __repr__(self) -> str: + """ + Return a string representation of the NetworkLinkControl object. + + Returns + ------- + str: A string representation of the NetworkLinkControl object. + + """ + return ( + f"{self.__class__.__module__}.{self.__class__.__name__}(" + f"ns={self.ns!r}, " + f"name_spaces={self.name_spaces!r}, " + f"min_refresh_period={self.min_refresh_period!r}, " + f"max_session_length={self.max_session_length!r}, " + f"cookie={self.cookie!r}, " + f"message={self.message!r}, " + f"link_name={self.link_name!r}, " + f"link_description={self.link_description!r}, " + f"link_snippet={self.link_snippet!r}, " + f"expires={self.expires!r}, " + f"view={self.view!r}, " + f"**{self._get_splat()!r}," + ")" + ) + + +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="min_refresh_period", + node_name="minRefreshPeriod", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + default=0, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="max_session_length", + node_name="maxSessionLength", + classes=(float,), + get_kwarg=subelement_float_kwarg, + set_element=float_subelement, + default=-1, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="cookie", + node_name="cookie", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="message", + node_name="message", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="link_name", + node_name="linkName", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="link_description", + node_name="linkDescription", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="link_snippet", + node_name="linkSnippet", + classes=(str,), + get_kwarg=subelement_text_kwarg, + set_element=text_subelement, + ), +) +registry.register( + NetworkLinkControl, + item=RegistryItem( + ns_ids=("kml",), + classes=(KmlDateTime,), + attr_name="expires", + node_name="expires", + get_kwarg=datetime_subelement_kwarg, + set_element=datetime_subelement, + ), +) +registry.register( + NetworkLinkControl, + RegistryItem( + ns_ids=("kml",), + attr_name="view", + node_name="Camera,LookAt", + classes=( + Camera, + LookAt, + ), + get_kwarg=xml_subelement_kwarg, + set_element=xml_subelement, + ), +) diff --git a/tests/hypothesis/network_link_control_test.py b/tests/hypothesis/network_link_control_test.py new file mode 100644 index 00000000..3ff1d963 --- /dev/null +++ b/tests/hypothesis/network_link_control_test.py @@ -0,0 +1,122 @@ +# Copyright (C) 2024 Christian Ledermann +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +"""Hypothesis tests for the fastkml.network_link_control module.""" + +import typing + +from hypothesis import given +from hypothesis import strategies as st + +import fastkml +import fastkml.enums +import fastkml.model +import fastkml.views +from tests.base import Lxml +from tests.hypothesis.common import assert_repr_roundtrip +from tests.hypothesis.common import assert_str_roundtrip +from tests.hypothesis.common import assert_str_roundtrip_terse +from tests.hypothesis.common import assert_str_roundtrip_verbose +from tests.hypothesis.strategies import kml_datetimes +from tests.hypothesis.strategies import xml_text + + +class TestLxml(Lxml): + @given( + min_refresh_period=st.one_of( + st.none(), + st.floats(allow_nan=False, allow_infinity=False).filter(lambda x: x != 0), + ), + max_session_length=st.one_of( + st.none(), + st.floats(allow_nan=False, allow_infinity=False).filter(lambda x: x != -1), + ), + cookie=st.one_of(st.none(), xml_text()), + message=st.one_of(st.none(), xml_text()), + link_name=st.one_of(st.none(), xml_text()), + link_description=st.one_of(st.none(), xml_text()), + link_snippet=st.one_of(st.none(), xml_text()), + expires=st.one_of( + st.none(), + kml_datetimes(), + ), + view=st.one_of( + st.none(), + st.builds( + fastkml.views.Camera, + longitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ).filter(lambda x: x != 0), + latitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-90, + max_value=90, + ).filter(lambda x: x != 0), + altitude=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + ), + st.builds( + fastkml.views.LookAt, + longitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-180, + max_value=180, + ).filter(lambda x: x != 0), + latitude=st.floats( + allow_nan=False, + allow_infinity=False, + min_value=-90, + max_value=90, + ).filter(lambda x: x != 0), + altitude=st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != 0, + ), + ), + ), + ) + def test_fuzz_network_link_control( + self, + min_refresh_period: typing.Optional[float], + max_session_length: typing.Optional[float], + cookie: typing.Optional[str], + message: typing.Optional[str], + link_name: typing.Optional[str], + link_description: typing.Optional[str], + link_snippet: typing.Optional[str], + expires: typing.Optional[fastkml.KmlDateTime], + view: typing.Union[fastkml.Camera, fastkml.LookAt, None], + ) -> None: + nlc = fastkml.NetworkLinkControl( + min_refresh_period=min_refresh_period, + max_session_length=max_session_length, + cookie=cookie, + message=message, + link_name=link_name, + link_description=link_description, + link_snippet=link_snippet, + expires=expires, + view=view, + ) + + assert_repr_roundtrip(nlc) + assert_str_roundtrip(nlc) + assert_str_roundtrip_terse(nlc) + assert_str_roundtrip_verbose(nlc) diff --git a/tests/network_link_control_test.py b/tests/network_link_control_test.py new file mode 100644 index 00000000..5cfa0ac4 --- /dev/null +++ b/tests/network_link_control_test.py @@ -0,0 +1,81 @@ +# Copyright (C) 2021 - 2022 Christian Ledermann +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Test the Network Link Control classes.""" + +import datetime + +from dateutil.tz import tzutc + +from fastkml import views +from fastkml.network_link_control import NetworkLinkControl +from fastkml.times import KmlDateTime +from tests.base import StdLibrary + + +class TestStdLibrary(StdLibrary): + """Test with the standard library.""" + + def test_network_link_control_obj(self) -> None: + dt = datetime.datetime.now(tz=tzutc()) + kml_datetime = KmlDateTime(dt=dt) + view = views.Camera() + + network_control_obj = NetworkLinkControl( + min_refresh_period=1.1, + max_session_length=100.1, + cookie="cookie", + message="message", + link_name="link_name", + link_description="link_description", + link_snippet="link_snippet", + expires=kml_datetime, + view=view, + ) + + assert network_control_obj.min_refresh_period == 1.1 + assert network_control_obj.max_session_length == 100.1 + assert network_control_obj.cookie == "cookie" + assert network_control_obj.message == "message" + assert network_control_obj.link_name == "link_name" + assert network_control_obj.link_description == "link_description" + assert network_control_obj.link_snippet == "link_snippet" + assert str(network_control_obj.expires) == str(kml_datetime) + assert str(network_control_obj.view) == str(view) + + def test_network_link_control_kml(self) -> None: + doc = ( + '' + "432000" + "-1" + "A Snippet" + "2008-05-30" + "" + ) + + nc = NetworkLinkControl.from_string(doc) + + dt = datetime.date(2008, 5, 30) + kml_datetime = KmlDateTime(dt=dt) + + nc_obj = NetworkLinkControl( + min_refresh_period=432000, + max_session_length=-1, + link_snippet="A Snippet", + expires=kml_datetime, + ) + + assert nc == nc_obj