georust/kml

Nested folders

Closed this issue · 3 comments

Hi! I've been trying out the kml crate and am wondering what the intended behavior is when parsing Folders that are siblings. I've found that Folders that are siblings in the kml are nested after being parsed by the crate.

Example:

use kml::Kml;

fn main() {
    let kml_str = r#"
    <?xml version="1.0" encoding="UTF-8"?>
    <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
    <Document>
    <Folder>
        <name>Folder 1</name>
        <Placemark>
            <LineString>
                <tessellate>1</tessellate>
                <coordinates>
                    0,0
                </coordinates>
            </LineString>
        </Placemark>
    </Folder>

    <Folder>
        <name>Folder 2</name>
        <Placemark>
            <LineString>
                <tessellate>1</tessellate>
                <coordinates>
                    0,0
                </coordinates>
            </LineString>
        </Placemark>
    </Folder>
    </Document>
    </kml>
    "#;

    let kml: Kml = kml_str.parse().unwrap();
    println!("{:#?}", kml);

    match &kml {
        Kml::KmlDocument(k) => match &k.elements[0] {
            Kml::Document { attrs: _, elements } => {
                let top_level_folder_count = elements
                    .iter()
                    .filter(|element| {
                        matches!(
                            element,
                            Kml::Folder {
                                attrs: _,
                                elements: _
                            }
                        )
                    })
                    .count();
                println!("number of top level folders: {}", top_level_folder_count);
            }
            _ => unimplemented!(),
        },
        _ => unimplemented!(),
    }
}

produces the output (trimmed to just the folders):

Folder {
    attrs: {},
    elements: [
        Element(
            Element {
                name: "name",
                attrs: {},
                content: Some(
                    "Folder 1",
                ),
                children: [],
            },
        ),
        Placemark(
            Placemark {
                name: None,
                description: None,
                geometry: Some(
                    LineString(
                        LineString {
                            coords: [
                                Coord {
                                    x: 0.0,
                                    y: 0.0,
                                    z: None,
                                },
                            ],
                            extrude: false,
                            tessellate: true,
                            altitude_mode: ClampToGround,
                            attrs: {},
                        },
                    ),
                ),
                attrs: {},
                children: [],
            },
        ),
        Folder { // <--- the second folder is a child of the first folder
            attrs: {},
            elements: [
                Element(
                    Element {
                        name: "name",
                        attrs: {},
                        content: Some(
                            "Folder 2",
                        ),
                        children: [],
                    },
                ),
                Placemark(
                    Placemark {
                        name: None,
                        description: None,
                        geometry: Some(
                            LineString(
                                LineString {
                                    coords: [
                                        Coord {
                                            x: 0.0,
                                            y: 0.0,
                                            z: None,
                                        },
                                    ],
                                    extrude: false,
                                    tessellate: true,
                                    altitude_mode: ClampToGround,
                                    attrs: {},
                                },
                            ),
                        ),
                        attrs: {},
                        children: [],
                    },
                ),
            ],
        },
    ],
}
number of top level folders: 1

Is this the intended representation? Unfortunately I don't know much about parsers, so wasn't able to look in to the source further.

It also makes me wonder if something like this shouldn't work, when it currently does:

use kml::Kml;

fn main() {
    let kml_str = r#"
    <Folder>
        <name>Folder 1</name>
        <Placemark>
            <LineString>
                <tessellate>1</tessellate>
                <coordinates>
                    0,0
                </coordinates>
            </LineString>
        </Placemark>
    </Folder>

    <Folder>
        <name>Folder 2</name>
        <Placemark>
            <LineString>
                <tessellate>1</tessellate>
                <coordinates>
                    0,0
                </coordinates>
            </LineString>
        </Placemark>
    </Folder>
    "#;

    let kml: Kml = kml_str.parse().unwrap();
    println!("{:#?}", kml); // outputs the same nested folder as above
}

Should it fail to parse this since it doesn't have a parent container around the two folders?

Thank you!

Thanks so much for the detailed issue! This is definitely a bug and not intended behavior, and the specific examples were really helpful for debugging. This should be fixed in #19

Should it fail to parse this since it doesn't have a parent container around the two folders?

That example reproduces the bug you mentioned, but we aren't requiring KML documents to have the top level kml tag so that we can use the Kml enum to parse snippets as well as full docs. I based this on georust/geojson and it makes parsing a bit easier

@nocduro thanks again for reporting this! The new release should include a fix

Much appreciated! The new version is working great