1use std::path::Path;
2
3use xot::Xot;
4
5use crate::namespace;
6
7pub fn assemble_combined(
17 file_paths: &[impl AsRef<Path>],
18) -> Result<(Xot, xot::Node), crate::Error> {
19 let mut xot = Xot::new();
20
21 let cmb_ns = xot.add_namespace(namespace::COMBINED);
23 let spec_name = xot.add_name_ns("spec", cmb_ns);
24 let root = xot.new_element(spec_name);
25
26 for (prefix, uri) in namespace::PREFIX_MAP {
27 let ns_id = xot.add_namespace(uri);
28 let prefix_id = xot.add_prefix(prefix);
29 xot.namespaces_mut(root).insert(prefix_id, ns_id);
30 }
31
32 for file_path in file_paths {
34 let content = std::fs::read_to_string(file_path.as_ref())?;
35 let doc = xot.parse(&content).map_err(xot::Error::from)?;
36 let file_root = xot.document_element(doc)?;
37
38 let children: Vec<_> = xot.children(file_root).collect();
40 for child in children {
41 xot.append(root, child)?;
42 }
43 }
44
45 Ok((xot, root))
46}
47
48pub fn assemble_combined_string(file_paths: &[impl AsRef<Path>]) -> Result<String, crate::Error> {
54 let (xot, root) = assemble_combined(file_paths)?;
55 Ok(xot.to_string(root).unwrap_or_default())
56}
57
58#[cfg(any())]
60mod _api {
61 use super::*;
62 pub fn assemble_combined(
63 file_paths: &[impl AsRef<Path>],
64 ) -> Result<(Xot, xot::Node), crate::Error>;
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use std::path::PathBuf;
71
72 fn spec_dir() -> PathBuf {
73 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
74 .join("../../clayers/clayers")
75 .canonicalize()
76 .expect("clayers/clayers/ not found")
77 }
78
79 fn spec_files() -> Vec<PathBuf> {
80 crate::discovery::discover_spec_files(&spec_dir().join("index.xml"))
81 .expect("discovery failed")
82 }
83
84 #[test]
85 fn assemble_shipped_spec_has_combined_root() {
86 let files = spec_files();
87 let (mut xot, root) = assemble_combined(&files).expect("assembly failed");
88
89 let cmb_ns = xot.add_namespace(namespace::COMBINED);
90 let spec_name = xot.add_name_ns("spec", cmb_ns);
91 assert!(xot.element(root).is_some_and(|e| e.name() == spec_name));
92 }
93
94 #[test]
95 fn combined_doc_has_elements_from_multiple_layers() {
96 let files = spec_files();
97 let (xot, root) = assemble_combined(&files).expect("assembly failed");
98 let xml = xot.to_string(root).unwrap_or_default();
99
100 assert!(xml.contains("urn:clayers:prose"), "missing prose namespace");
102 assert!(
103 xml.contains("urn:clayers:terminology"),
104 "missing terminology namespace"
105 );
106 assert!(
107 xml.contains("urn:clayers:relation"),
108 "missing relation namespace"
109 );
110 }
111
112 #[test]
113 fn combined_doc_preserves_ids() {
114 let files = spec_files();
115 let (xot, root) = assemble_combined(&files).expect("assembly failed");
116 let xml = xot.to_string(root).unwrap_or_default();
117
118 assert!(xml.contains("\"term-layer\""), "missing term-layer id");
120 assert!(
121 xml.contains("\"layered-architecture\""),
122 "missing layered-architecture id"
123 );
124 }
125
126 #[test]
127 fn assemble_single_file() {
128 let spec = spec_dir();
129 let overview = spec.join("overview.xml");
130 let binding = [&overview];
131 let (xot, root) = assemble_combined(&binding).expect("assembly failed");
132 let xml = xot.to_string(root).unwrap_or_default();
133 assert!(xml.contains("cmb:spec"), "missing combined root");
134 }
135}