Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

390 network link control #398

Merged
merged 17 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/network.kml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
hint="A processing hint for a KML consumer">
<NetworkLinkControl>
<minRefreshPeriod>43200</minRefreshPeriod>
<maxSessionLength>-1</maxSessionLength>
<linkSnippet>
<![CDATA[
<p xmlns="http://www.w3.org/1999/xhtml">A snippet of <a href="http://www.w3.org/TR/xhtml-basic/">XHTML</a></p>
]]>
</linkSnippet>
<expires>2008-05-30</expires>
</NetworkLinkControl>
</kml>
2 changes: 2 additions & 0 deletions fastkml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -119,6 +120,7 @@
"Model",
"MultiGeometry",
"NetworkLink",
"NetworkLinkControl",
"Orientation",
"OuterBoundaryIs",
"OverlayXY",
Expand Down
27 changes: 23 additions & 4 deletions fastkml/kml.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from fastkml import validator
from fastkml.base import _XMLObject
from fastkml.containers import Document
from fastkml.network_link_control import NetworkLinkControl
from fastkml.containers import Folder
from fastkml.enums import Verbosity
from fastkml.features import NetworkLink
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
219 changes: 219 additions & 0 deletions fastkml/network_link_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Copyright (C) 2012-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
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 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):

_default_nsid = config.KML

min_refresh_period: Optional[float]
max_session_length: Optional[float]
Comment on lines +59 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure default values are consistent between __init__ and registry

The default values for min_refresh_period and max_session_length in the __init__ method are None, but in the registry registration, they default to 0 and -1, respectively. Aligning these defaults can prevent unexpected behavior.

Consider setting the same default values in the __init__ method:

 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,
+    min_refresh_period: Optional[float] = 0,
+    max_session_length: Optional[float] = -1,
     cookie: Optional[str] = None,
     # ... rest of the parameters
 ) -> None:

Committable suggestion skipped: line range outside the PR's diff.

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] # TODO: Add Update field to the parameters

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:
super().__init__(
ns=ns,
name_spaces=name_spaces,
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,
**kwargs,
)

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider consolidating the repeated registry.register() calls into a single helper function with a data-driven approach.

The repeated registry.register() calls can be simplified while maintaining type safety. Here's a suggested approach:

def register_network_link_controls(cls):
    registrations = [
        # (attr_name, node_name, classes, get_kwarg, set_element, default)
        ("min_refresh_period", "minRefreshPeriod", (float,), 
         subelement_float_kwarg, float_subelement, 0),
        ("max_session_length", "maxSessionLength", (float,),
         subelement_float_kwarg, float_subelement, -1),
        ("cookie", "cookie", (str,),
         subelement_text_kwarg, text_subelement, None),
        # ... other registrations
    ]

    for reg in registrations:
        attr_name, node_name, classes, get_kwarg, set_element, default = reg
        registry.register(
            cls,
            RegistryItem(
                ns_ids=("kml",),
                attr_name=attr_name,
                node_name=node_name,
                classes=classes,
                get_kwarg=get_kwarg,
                set_element=set_element,
                default=default if default is not None else None
            )
        )

# Usage:
register_network_link_controls(NetworkLinkControl)

This approach:

  • Reduces repetition while maintaining type safety
  • Makes it easier to add/modify registrations
  • Keeps all registration config in one place
  • Reduces potential for copy-paste errors

The registration data could alternatively be stored as a module-level constant if you prefer to separate the configuration from the registration logic.

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,
),
)
79 changes: 79 additions & 0 deletions tests/network_link_control_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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 fastkml.network_link_control import NetworkLinkControl
from fastkml.times import KmlDateTime
from tests.base import StdLibrary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.

from fastkml import views


class TestStdLibrary(StdLibrary):
"""Test with the standard library."""

def test_network_link_control_obj(self) -> None:
dt = datetime.datetime.now()
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 = (
'<kml:NetworkLinkControl xmlns:kml="http://www.opengis.net/kml/2.2">'
"<kml:minRefreshPeriod>432000</kml:minRefreshPeriod>"
"<kml:maxSessionLength>-1</kml:maxSessionLength>"
"<kml:linkSnippet>A Snippet</kml:linkSnippet>"
"<kml:expires>2008-05-30</kml:expires>"
"</kml:NetworkLinkControl>"
)

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
Comment on lines +59 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add more comprehensive KML parsing test cases

The current test only covers a basic KML structure. Consider adding test cases for more complex KML documents including nested elements, all possible attributes, and malformed KML to ensure robust parsing.

Suggested change
def test_network_link_control_kml(self) -> None:
doc = (
'<kml:NetworkLinkControl xmlns:kml="http://www.opengis.net/kml/2.2">'
"<kml:minRefreshPeriod>432000</kml:minRefreshPeriod>"
"<kml:maxSessionLength>-1</kml:maxSessionLength>"
"<kml:linkSnippet>A Snippet</kml:linkSnippet>"
"<kml:expires>2008-05-30</kml:expires>"
"</kml:NetworkLinkControl>"
)
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
def test_network_link_control_kml(self) -> None:
test_cases = [
('<kml:NetworkLinkControl xmlns:kml="http://www.opengis.net/kml/2.2"><kml:minRefreshPeriod>432000</kml:minRefreshPeriod></kml:NetworkLinkControl>', NetworkLinkControl(min_refresh_period=432000)),
('<kml:NetworkLinkControl xmlns:kml="http://www.opengis.net/kml/2.2"><kml:cookie>abc123</kml:cookie><kml:message>Test Message</kml:message></kml:NetworkLinkControl>', NetworkLinkControl(cookie="abc123", message="Test Message")),
('<kml:NetworkLinkControl xmlns:kml="http://www.opengis.net/kml/2.2"><kml:linkName>Test Link</kml:linkName><kml:linkDescription>Description</kml:linkDescription></kml:NetworkLinkControl>', NetworkLinkControl(link_name="Test Link", link_description="Description")),
]
for kml_str, expected_obj in test_cases:
assert NetworkLinkControl.from_string(kml_str) == expected_obj

Loading