///|
priv struct GraphFacts {
graph : PackageGraph
part_index : Map[String, Int]
node_index : Map[String, Int]
local_relationships : Map[String, Map[String, Int]]
}
///|
fn graph_relationship(
facts : GraphFacts,
source : String,
id : String,
) -> Relationship? {
match facts.local_relationships.get(source) {
Some(ids) => ids.get(id).map(fn(i) { facts.graph.relationships[i] })
None => None
}
}
///|
fn graph_incoming(facts : GraphFacts, target : String) -> Array[Relationship] {
facts.graph.nodes[facts.node_index[target]].inbound.map(fn(i) {
facts.graph.relationships[i]
})
}
///|
fn build_graph(
parts : Array[PartRecord],
relationships : Array[Relationship],
xml_nodes : Map[String, Array[Node]],
) -> GraphFacts raise {
let part_index : Map[String, Int] = Map([])
let node_index : Map[String, Int] = Map([("/", 0)])
let names : Array[String] = ["/"]
let inbound : Array[Array[Int]] = [[]]
let outbound : Array[Array[Int]] = [[]]
let source_refs : Array[Array[Int]] = [[]]
let type_parts : Map[String, Array[String]] = Map([])
for i, part in parts {
guard !part_index.contains(part.name) else {
raise Invalid("duplicate graph part")
}
part_index[part.name] = i
node_index[part.name] = names.length()
names.push(part.name)
inbound.push([])
outbound.push([])
source_refs.push([])
match type_parts.get(part.content_type) {
Some(names) => names.push(part.name)
None => type_parts[part.content_type] = [part.name]
}
}
let local_relationships : Map[String, Map[String, Int]] = Map([])
let implicit : Array[ImplicitReference] = []
let gaps : Array[String] = []
for i, r in relationships {
guard node_index.contains(r.source) else {
raise Invalid("relationship source missing")
}
let ids = match local_relationships.get(r.source) {
Some(ids) => ids
None => {
let ids : Map[String, Int] = Map([])
local_relationships[r.source] = ids
ids
}
}
guard !ids.contains(r.id) else {
raise Invalid("duplicate local graph relationship ID")
}
ids[r.id] = i
outbound[node_index[r.source]].push(i)
if !r.external {
guard node_index.contains(r.resolved) && r.resolved != "/" else {
raise Invalid("internal graph target missing")
}
inbound[node_index[r.resolved]].push(i)
}
let implicit_semantics = if r.source == "/" &&
(
r.kind == office_rel + "officeDocument" ||
r.kind == office_rel + "extended-properties" ||
r.kind ==
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
) {
Some("package metadata relationship")
} else if part_index.get(r.source) is Some(source_part) &&
(
parts[source_part].content_type == xlsm_ct ||
parts[source_part].content_type == xlsx_ct ||
parts[source_part].content_type == docm_ct ||
parts[source_part].content_type == docx_ct
) &&
(
r.kind == vba_rel ||
r.kind == office_rel + "styles" ||
r.kind == office_rel + "theme" ||
r.kind == office_rel + "sharedStrings" ||
r.kind == office_rel + "settings" ||
r.kind == office_rel + "fontTable" ||
r.kind == office_rel + "webSettings"
) {
Some(
"main-document implicit relationship; retained indices are not garbage-collected",
)
} else if r.kind == word_vba_data_rel &&
part_index.get(r.source) is Some(source_part) &&
parts[source_part].content_type == vba_ct {
Some("Word VBA supporting metadata owned by its VBA project")
} else {
None
}
if implicit_semantics is Some(semantics) {
implicit.push({ source: r.source, relationship_index: i, semantics, })
} else if r.kind != office_rel + "worksheet" {
gaps.push("unsupported relationship semantics: " + r.source + "#" + r.id)
}
}
let references : Array[XmlReference] = []
let relationship_ns = office_rel[0:office_rel.length() - 1].to_owned()
let known_namespaces = [
ct_ns, rel_ns, spreadsheet_ns, drawing_ns, word_ns, word_math_ns, word_office_ns,
word_macro_ns, "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
"http://purl.org/dc/elements/1.1/", "http://purl.org/dc/terms/", "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
]
for part in parts {
if xml_nodes.get(part.name) is Some(nodes) {
let mut has_opaque_content = false
for ordinal, node in nodes {
let ns = node.element.name.namespace_uri.unwrap_or("")
let local_name = node.element.name.local_name
if ns == "http://schemas.openxmlformats.org/markup-compatibility/2006" ||
ns == "urn:schemas-microsoft-com:vml" ||
!known_namespaces.contains(ns) ||
local_name == "ext" ||
local_name == "extLst" {
has_opaque_content = true
}
for a in node.element.attributes {
let attribute_ns = a.name.namespace_uri.unwrap_or("")
let inert_shape_attribute = part.content_type ==
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" &&
ns == word_office_ns &&
["shapedefaults", "shapelayout", "idmap"].contains(local_name) &&
attribute_ns == "urn:schemas-microsoft-com:vml" &&
a.name.local_name == "ext" &&
a.value == "edit"
if !inert_shape_attribute &&
attribute_ns != "" &&
attribute_ns != relationship_ns &&
attribute_ns != word_ns &&
attribute_ns != word_math_ns &&
attribute_ns != word_macro_ns &&
attribute_ns != "http://www.w3.org/XML/1998/namespace" &&
attribute_ns != "http://www.w3.org/2001/XMLSchema-instance" {
has_opaque_content = true
}
if a.name.namespace_uri == Some(relationship_ns) &&
["id", "embed", "link"].contains(a.name.local_name) {
guard references.length() < 32768 else {
raise Incomplete("XML relationship reference limit")
}
guard local_relationships.get(part.name) is Some(ids) &&
ids.get(a.value) is Some(index) else {
raise Invalid("dangling source-local XML relationship reference")
}
source_refs[node_index[part.name]].push(references.length())
references.push({
source: part.name,
element_index: ordinal,
depth: node.depth,
element_namespace: ns,
element_name: local_name,
attribute_name: a.name.local_name,
relationship_id: a.value,
relationship_index: index,
})
}
}
}
if has_opaque_content {
gaps.push(
"opaque XML namespace/extension (including AlternateContent or VML): " +
part.name,
)
}
}
}
// Iterative traversal tolerates cycles and visits each internal edge once.
let seen : Map[String, Bool] = Map([("/", true)])
let queue : Array[String] = ["/"]
let mut cursor = 0
while cursor < queue.length() {
let source = queue[cursor]
cursor += 1
for i in outbound[node_index[source]] {
let edge = relationships[i]
if !edge.external && !seen.contains(edge.resolved) {
seen[edge.resolved] = true
queue.push(edge.resolved)
}
}
}
// Metadata belongs to the package or its reachable source, not to a GC root.
for part in parts {
if part.name == "[Content_Types].xml" {
seen[part.name] = true
} else if part.name.has_suffix(".rels") {
let owner = relationship_source(part.name)
if seen.contains(owner) {
seen[part.name] = true
}
}
}
let reachable : Array[String] = []
let orphans : Array[String] = []
let graph_nodes : Array[GraphNode] = []
for i, name in names {
let reached = seen.contains(name)
if reached {
reachable.push(name)
} else if !name.has_suffix(".rels") {
orphans.push(name)
}
graph_nodes.push({
part: name,
inbound: inbound[i],
outbound: outbound[i],
xml_references: source_refs[i],
reachable: reached,
})
}
reachable.sort()
orphans.sort()
gaps.sort()
let types = type_parts.keys().collect()
types.sort()
let content_types = types.map(fn(t) {
let names = type_parts[t]
names.sort()
ContentTypeIndex::{ content_type: t, parts: names, }
})
{
graph: {
schema: "partsieve.graph.v1",
root: "/",
parts,
relationships,
xml_references: references,
implicit_references: implicit,
nodes: graph_nodes,
content_types,
reachable,
orphan_candidates: orphans,
coverage_gaps: gaps,
},
part_index,
node_index,
local_relationships,
}
}
///|
// Structural inspection may report gaps. It does not authorize a rebuild.
pub fn inspect_graph(
input : Bytes,
limits? : Limits = Limits::default(),
) -> PackageGraph raise {
load_package(input, limits, enforce_profile=false).graph.graph
}