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)