diff --git a/fastkml/kml.py b/fastkml/kml.py index e5b4bbda..50f41692 100644 --- a/fastkml/kml.py +++ b/fastkml/kml.py @@ -26,7 +26,10 @@ """ import logging +from pathlib import Path +from typing import IO from typing import Any +from typing import AnyStr from typing import Dict from typing import Iterable from typing import List @@ -155,48 +158,54 @@ def append( self.features.append(kmlobj) @classmethod - def class_from_string( + def parse( cls, - string: str, + file: Union[Path, str, IO[AnyStr]], *, ns: Optional[str] = None, name_spaces: Optional[Dict[str, str]] = None, strict: bool = True, ) -> Self: """ - Create a kml object from a string. + Parse a KML file and return a KML object. Args: ---- - string: String representation (serialized XML) of the kml object - ns: Namespace of the element (default: None) - name_spaces: Dictionary of namespace prefixes and URIs (default: None) - strict: Whether to enforce strict parsing (default: True) + file: The file to parse. + Can be a file path (str or Path), or a file-like object. + + Keyword Args: + ------------ + ns (Optional[str]): The namespace of the KML file. + If not provided, it will be inferred from the root element. + name_spaces (Optional[Dict[str, str]]): Additional namespaces. + strict (bool): Whether to enforce strict parsing rules. Defaults to True. Returns: ------- - Geometry object + KML object: The parsed KML object. """ try: - element = config.etree.fromstring( - string, + tree = config.etree.parse( + file, parser=config.etree.XMLParser( huge_tree=True, recover=True, ), ) except TypeError: - element = config.etree.XML(string) + tree = config.etree.parse(file) + root = tree.getroot() if ns is None: - ns = cast(str, element.tag[:-3] if element.tag.endswith("kml") else "") + ns = cast(str, root.tag[:-3] if root.tag.endswith("kml") else "") name_spaces = name_spaces or {} name_spaces = {**config.NAME_SPACES, **name_spaces} return cls.class_from_element( ns=ns, name_spaces=name_spaces, strict=strict, - element=element, + element=root, ) diff --git a/tests/kml_test.py b/tests/kml_test.py index 1cc9ef51..42c06b28 100644 --- a/tests/kml_test.py +++ b/tests/kml_test.py @@ -15,14 +15,21 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """Test the kml class.""" +import io +import pathlib import pygeoif as geo from fastkml import features from fastkml import kml +from fastkml.containers import Document +from fastkml.features import Placemark from tests.base import Lxml from tests.base import StdLibrary +BASEDIR = pathlib.Path(__file__).parent +KMLFILEDIR = BASEDIR / "ogc_conformance" / "data" / "kml" + class TestStdLibrary(StdLibrary): """Test with the standard library.""" @@ -52,16 +59,92 @@ def test_kml(self) -> None: assert k.to_string() == k2.to_string() +class TestParseKML(StdLibrary): + def test_parse_kml(self) -> None: + empty_placemark = KMLFILEDIR / "emptyPlacemarkWithoutId.xml" + + doc = kml.KML.parse(empty_placemark) + + assert doc == kml.KML( + ns="{http://www.opengis.net/kml/2.2}", + features=[ + Document( + ns="{http://www.opengis.net/kml/2.2}", + id="doc-001", + target_id="", + name="Vestibulum eleifend lobortis lorem.", + features=[ + Placemark( + ns="{http://www.opengis.net/kml/2.2}", + ), + ], + schemata=[], + ), + ], + ) + + def test_parse_kml_filename(self) -> None: + empty_placemark = str(KMLFILEDIR / "emptyPlacemarkWithoutId.xml") + + doc = kml.KML.parse(empty_placemark) + + assert doc == kml.KML( + ns="{http://www.opengis.net/kml/2.2}", + features=[ + Document( + ns="{http://www.opengis.net/kml/2.2}", + id="doc-001", + target_id="", + name="Vestibulum eleifend lobortis lorem.", + features=[ + Placemark( + ns="{http://www.opengis.net/kml/2.2}", + ), + ], + schemata=[], + ), + ], + ) + + def test_parse_kml_fileobject(self) -> None: + empty_placemark = KMLFILEDIR / "emptyPlacemarkWithoutId.xml" + with empty_placemark.open() as f: + doc = kml.KML.parse(f) + + assert doc == kml.KML( + ns="{http://www.opengis.net/kml/2.2}", + features=[ + Document( + ns="{http://www.opengis.net/kml/2.2}", + id="doc-001", + target_id="", + name="Vestibulum eleifend lobortis lorem.", + features=[ + Placemark( + ns="{http://www.opengis.net/kml/2.2}", + ), + ], + schemata=[], + ), + ], + ) + + class TestLxml(Lxml, TestStdLibrary): """Test with lxml.""" + +class TestLxmlParseKML(Lxml, TestParseKML): + """Test with Lxml.""" + def test_from_string_with_unbound_prefix(self) -> None: - doc = """ - - - image.png - - """ - k = kml.KML.class_from_string(doc) + doc = io.StringIO( + '' + "" + "image.png" + "" + " ", + ) + k = kml.KML.parse(doc, ns="{http://www.opengis.net/kml/2.2}") assert len(k.features) == 1 assert isinstance(k.features[0], features.Placemark)