-
Notifications
You must be signed in to change notification settings - Fork 92
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
284 add from file or load method to kmlkml #338
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exception handling for |
||
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, | ||
) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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): | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Missing edge case for invalid KML file Consider adding a test case to handle invalid or malformed KML files to ensure the
Suggested change
|
||||||||||||||||||||
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 = """<kml xmlns="http://www.opengis.net/kml/2.2"> | ||||||||||||||||||||
<Placemark> | ||||||||||||||||||||
<ExtendedData> | ||||||||||||||||||||
<lc:attachment>image.png</lc:attachment> | ||||||||||||||||||||
</ExtendedData> | ||||||||||||||||||||
</Placemark> </kml>""" | ||||||||||||||||||||
k = kml.KML.class_from_string(doc) | ||||||||||||||||||||
doc = io.StringIO( | ||||||||||||||||||||
'<kml xmlns="http://www.opengis.net/kml/2.2">' | ||||||||||||||||||||
"<Placemark><ExtendedData>" | ||||||||||||||||||||
"<lc:attachment>image.png</lc:attachment>" | ||||||||||||||||||||
"</ExtendedData>" | ||||||||||||||||||||
"</Placemark> </kml>", | ||||||||||||||||||||
) | ||||||||||||||||||||
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (performance): Consider performance implications for large files
The method now parses the entire file before extracting the root element. For very large files, this could have memory usage implications. Consider adding a note about this in the docstring, or investigate if there's a more memory-efficient way to parse large KML files.