///|
// Topology-derived hints cannot safely describe a filtered graph. Keep extension
// attributes, even when their local names resemble GraphML's unqualified hints.
fn subgraph_extra(
attributes : Array[XmlAttributeValue],
) -> Array[XmlAttributeValue] {
attributes.filter(fn(attribute) {
attribute.namespace_uri is Some(_) || !attribute.name.has_prefix("parse.")
})
}
///|
/// Copy a node-induced subgraph, retaining source order and all edges whose two
/// endpoints are selected. Unknown or repeated node IDs are errors, not typos
/// silently ignored. Keys, defaults and graph/element XML metadata are retained;
/// topology-derived parse hints are removed. Document-level metadata is outside
/// a FlatGraph view. The resulting Document is independently owned and validated.
pub fn FlatGraph::induced_subgraph(
self : FlatGraph,
node_ids : Array[String],
limits~ : Limits = Limits::default(),
) -> Document raise GraphError {
limits.validate()
check_bound("nodes", node_ids.length(), limits.nodes)
let selected : Map[String, Bool] = Map([])
for id in node_ids {
require(
!selected.contains(id),
"subgraph.nodes",
"duplicate selection \{id}",
)
selected[id] = true
}
let nodes : Array[Node] = []
for node in self.graph.nodes {
if selected.contains(node.id) {
nodes.push({ ..node, extra: subgraph_extra(node.extra), })
}
}
require(
nodes.length() == selected.length(),
"subgraph.nodes",
"selection contains an unknown node ID",
)
let edges : Array[Edge] = []
for edge in self.graph.edges {
if selected.contains(edge.source) && selected.contains(edge.target) {
// Check before growing the retained edge array.
check_bound("edges", edges.length() + 1, limits.edges)
edges.push(edge)
}
}
let graph = {
..self.graph,
nodes,
edges,
extra: subgraph_extra(self.graph.extra),
}
Document::new(self.keys, [graph], limits~)
}