Skip to content

Commit

Permalink
Add SimpleArrayData class and related helper functions for GX data ha…
Browse files Browse the repository at this point in the history
…ndling #259
  • Loading branch information
cleder committed Dec 14, 2024
1 parent 029b813 commit a0b6924
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
108 changes: 108 additions & 0 deletions fastkml/gx_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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

"""GX SimpleArrayData Extension."""

from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional

from fastkml import config
from fastkml.base import _XMLObject
from fastkml.helpers import attribute_text_kwarg
from fastkml.helpers import clean_string
from fastkml.helpers import subelement_text_list_kwarg
from fastkml.helpers import text_attribute
from fastkml.helpers import text_subelement_list
from fastkml.registry import RegistryItem
from fastkml.registry import registry

__all__ = ["SimpleArrayData"]


class SimpleArrayData(_XMLObject):
"""
A SimpleArrayData element.
This element is used to define an array of string values. It is used in
conjunction with the gx:SimpleArrayField element to specify how the array
values are to be displayed.
"""

_default_nsid = config.GX
name: Optional[str]
data: List[str]

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[Dict[str, str]] = None,
name: Optional[str] = None,
data: Optional[Iterable[str]] = None,
) -> None:
"""
Create a SimpleArrayData element.
Args:
ns: The namespace to use.
name_spaces: A dictionary of namespace prefixes to namespace URIs.
name: The name of the element.
data: A list of string values.
"""
super().__init__(ns=ns, name_spaces=name_spaces)
self.data = list(data) if data is not None else []
self.name = clean_string(name)

def __repr__(self) -> str:
"""Create a string (c)representation for SimpleArrayData."""
return (
f"{self.__class__.__module__}.{self.__class__.__name__}("
f"ns={self.ns!r}, "
f"name_spaces={self.name_spaces!r}, "
f"name={self.name!r}, "
f"data={self.data!r}, "
")"
)

def __bool__(self) -> bool:
"""Check if the element is named and has any data."""
return bool(self.data) and bool(self.name)


registry.register(
SimpleArrayData,
RegistryItem(
ns_ids=("gx", ""),
classes=(str,),
attr_name="data",
node_name="value",
get_kwarg=subelement_text_list_kwarg,
set_element=text_subelement_list,
),
)
registry.register(
SimpleArrayData,
RegistryItem(
ns_ids=("gx", ""),
classes=(str,),
attr_name="name",
node_name="name",
get_kwarg=attribute_text_kwarg,
set_element=text_attribute,
),
)
76 changes: 76 additions & 0 deletions fastkml/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,48 @@ def text_subelement(
subelement.text = value


def text_subelement_list(
obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
verbosity: Verbosity,
default: Optional[str],
) -> None:
"""
Set the value of an attribute from subelements with a text node.
Args:
----
obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the subelement to create.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
default (Optional[str]): The default value for the attribute.
Returns:
-------
None
"""
if value := get_value(
obj,
attr_name=attr_name,
verbosity=verbosity,
default=default,
):
for item in value:
subelement = config.etree.SubElement(
element,
f"{obj.ns}{node_name}",
)
subelement.text = item


def text_attribute(
obj: "_XMLObject",
*,
Expand Down Expand Up @@ -710,6 +752,40 @@ def subelement_text_kwarg(
return {kwarg: node.text.strip()} if node.text and node.text.strip() else {}


def subelement_text_list_kwarg(
*,
element: Element,
ns: str,
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, List[str]]:
"""
Extract the text content of subelements and return it as a dictionary.
Args:
----
element (Element): The parent element.
ns (str): The namespace of the subelement.
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and URIs.
node_name (str): The name of the subelement.
kwarg (str): The key to use in the returned dictionary.
classes (Tuple[Type[object], ...]): A tuple of known types.
strict (bool): A flag indicating whether to enforce strict parsing.
Returns:
-------
Dict[str, List[str]]: A dictionary containing the extracted text contents,
with the specified key.
"""
if nodes := element.findall(f"{ns}{node_name}"):
return {kwarg: [node.text.strip() for node in nodes if node.text.strip()]}
return {}


def attribute_text_kwarg(
*,
element: Element,
Expand Down
56 changes: 56 additions & 0 deletions tests/hypothesis/gx_data_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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
"""Test gx SimpleArrayData."""

import typing

from hypothesis import given
from hypothesis import strategies as st

import fastkml
import fastkml.gx_data
import fastkml.types
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 nc_name
from tests.hypothesis.strategies import xml_text


class TestGx(Lxml):
@given(
name=st.one_of(st.none(), nc_name()),
data=st.one_of(
st.none(),
st.lists(xml_text().filter(lambda x: x.strip() != "")),
),
)
def test_fuzz_simple_array_data(
self,
name: typing.Optional[str],
data: typing.Optional[typing.Iterable[str]],
) -> None:
simple_array_data = fastkml.gx_data.SimpleArrayData(
name=name,
data=data,
)

assert_repr_roundtrip(simple_array_data)
assert_str_roundtrip(simple_array_data)
assert_str_roundtrip_terse(simple_array_data)
assert_str_roundtrip_verbose(simple_array_data)

0 comments on commit a0b6924

Please sign in to comment.