def _save_graph_xml(
    G: nx.MultiDiGraph,
    filepath: str | Path | None,
    way_tag_aggs: dict[str, Any] | None,
    encoding: str = "utf-8",
) -> None:
    """
    Save graph to disk as an OSM XML file.

    Parameters
    ----------
    G
        Unsimplified, unprojected graph to save as an OSM XML file.
    filepath
        Path to the saved file including extension. If None, use default
        `settings.data_folder/graph.osm`.
    way_tag_aggs
        Keys are OSM way tag keys and values are aggregation functions
        (anything accepted as an argument by `pandas.agg`). Allows user to
        aggregate graph edge attribute values into single OSM way values. If
        None, or if some tag's key does not exist in the dict, the way
        attribute will be assigned the value of the first edge of the way.
    encoding
        The character encoding of the saved OSM XML file.
    """
    # default "oneway" value used to fill this tag where missing
    ONEWAY = False

    # round lat/lon coordinates to 7 decimals (approx 5 to 10 mm resolution)
    PRECISION = 7

    # warn user if ox.settings.all_oneway is not currently True (but maybe it
    # was when they created the graph)
    if not settings.all_oneway:
        msg = "Make sure graph was created with `ox.settings.all_oneway=True` to save as OSM XML."
        warn(msg, category=UserWarning, stacklevel=2)

    # warn user if graph is projected
    if projection.is_projected(G.graph["crs"]):
        msg = (
            "Graph should be unprojected to save as OSM XML: the existing "
            "projected x-y coordinates will be saved as lat-lon node attributes. "
            "Project your graph back to lat-lon to avoid this."
        )
        warn(msg, category=UserWarning, stacklevel=2)

    # raise error if graph has been simplified
    if G.graph.get("simplified", False):
        msg = "Graph must be unsimplified to save as OSM XML."
        raise GraphSimplificationError(msg)

    # set default filepath if None was provided
    filepath = Path(settings.data_folder) / "graph.osm" if filepath is None else Path(filepath)
    filepath.parent.mkdir(parents=True, exist_ok=True)

    # convert graph to node/edge gdfs and create dict of spatial bounds
    gdf_nodes, gdf_edges = convert.graph_to_gdfs(G, fill_edge_geometry=False)
    coords = [str(round(c, PRECISION)) for c in gdf_nodes.union_all().bounds]
    bounds = dict(zip(["minlon", "minlat", "maxlon", "maxlat"], coords))

    # add default values (if missing) for standard attrs
    for gdf in (gdf_nodes, gdf_edges):
        for col, value in ATTR_DEFAULTS.items():
            if col not in gdf.columns:
                gdf[col] = value
            else:
                gdf[col] = gdf[col].fillna(value)

    # transform nodes gdf to meet OSM XML spec
    # 1) reset index (osmid) then rename osmid, x, and y columns
    # 2) round lat/lon coordinates
    # 3) drop unnecessary geometry column
    gdf_nodes = gdf_nodes.reset_index().rename(columns={"osmid": "id", "x": "lon", "y": "lat"})
    gdf_nodes[["lon", "lat"]] = gdf_nodes[["lon", "lat"]].round(PRECISION)
    gdf_nodes = gdf_nodes.drop(columns=["geometry"])

    # transform edges gdf to meet OSM XML spec
    # 1) fill and convert oneway bools to strings
    # 2) rename osmid column (but keep (u, v, k) index for processing)
    # 3) drop unnecessary geometry column
    if "oneway" in gdf_edges.columns:
        gdf_edges["oneway"] = gdf_edges["oneway"].fillna(ONEWAY).replace({True: "yes", False: "no"})
    gdf_edges = gdf_edges.rename(columns={"osmid": "id"}).drop(columns=["geometry"])

    # create parent XML element then add bounds, nodes, ways as subelements
    element = Element("osm", attrib=ROOT_ATTR_DEFAULTS)
    _ = SubElement(element, "bounds", attrib=bounds)
    _add_nodes_xml(element, gdf_nodes)
    _add_ways_xml(element, gdf_edges, way_tag_aggs)

    # write to disk
    ElementTree(element).write(filepath, encoding=encoding, xml_declaration=True)
    msg = f"Saved graph as OSM XML file at {str(filepath)!r}"
    utils.log(msg, level=lg.INFO)

    Save graph to disk as an OSM XML file.

    Parameters
    ----------
    G
        Unsimplified, unprojected graph to save as an OSM XML file.
    filepath
        Path to the saved file including extension. If None, use default
        `settings.data_folder/graph.osm`.
    way_tag_aggs
        Keys are OSM way tag keys and values are aggregation functions
        (anything accepted as an argument by `pandas.agg`). Allows user to
        aggregate graph edge attribute values into single OSM way values. If
        None, or if some tag's key does not exist in the dict, the way
        attribute will be assigned the value of the first edge of the way.
    encoding
        The character encoding of the saved OSM XML file.
    
osmnx/_osm_xml.py