1use crate::ids::{DocumentId, NameId};
9use crate::namespace::context::NamespaceContextSnapshot;
10use crate::parser::location::{SourceRef, SourceSpan};
11
12#[derive(Debug, Clone)]
17pub struct XmlFragment {
18 pub doc_id: DocumentId,
20
21 pub span: SourceSpan,
23}
24
25impl XmlFragment {
26 pub fn new(doc_id: DocumentId, span: SourceSpan) -> Self {
28 Self { doc_id, span }
29 }
30
31 pub fn byte_range(&self) -> std::ops::Range<usize> {
33 self.span.start..self.span.end
34 }
35}
36
37#[derive(Debug, Clone)]
42pub struct ForeignAttribute {
43 pub namespace: Option<NameId>,
45 pub local_name: NameId,
46 pub prefix: Option<NameId>,
47
48 pub value: String,
50
51 pub source: Option<SourceRef>,
53}
54
55impl ForeignAttribute {
56 pub fn new(namespace: Option<NameId>, local_name: NameId, value: String) -> Self {
58 Self {
59 namespace,
60 local_name,
61 prefix: None,
62 value,
63 source: None,
64 }
65 }
66
67 pub fn is_in_namespace(&self, ns: Option<NameId>) -> bool {
69 self.namespace == ns
70 }
71}
72
73#[derive(Debug, Clone)]
75pub enum AnnotationItem {
76 AppInfo(AppInfoElement),
78 Documentation(DocumentationElement),
80}
81
82#[derive(Debug, Clone)]
86pub struct AppInfoElement {
87 pub source: Option<String>,
89
90 pub attributes: Vec<ForeignAttribute>,
92
93 pub namespaces: NamespaceContextSnapshot,
95
96 pub content: XmlFragment,
98
99 pub source_ref: Option<SourceRef>,
101}
102
103impl AppInfoElement {
104 pub fn new(content: XmlFragment, namespaces: NamespaceContextSnapshot) -> Self {
106 Self {
107 source: None,
108 attributes: Vec::new(),
109 namespaces,
110 content,
111 source_ref: None,
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
120pub struct DocumentationElement {
121 pub source: Option<String>,
123
124 pub lang: Option<String>,
126
127 pub attributes: Vec<ForeignAttribute>,
129
130 pub namespaces: NamespaceContextSnapshot,
132
133 pub content: XmlFragment,
135
136 pub source_ref: Option<SourceRef>,
138}
139
140impl DocumentationElement {
141 pub fn new(content: XmlFragment, namespaces: NamespaceContextSnapshot) -> Self {
143 Self {
144 source: None,
145 lang: None,
146 attributes: Vec::new(),
147 namespaces,
148 content,
149 source_ref: None,
150 }
151 }
152}
153
154#[derive(Debug, Clone)]
160pub struct Annotation {
161 pub id: Option<String>,
163
164 pub attributes: Vec<ForeignAttribute>,
166
167 pub items: Vec<AnnotationItem>,
169
170 pub source: Option<SourceRef>,
172}
173
174impl Annotation {
175 pub fn new() -> Self {
177 Self {
178 id: None,
179 attributes: Vec::new(),
180 items: Vec::new(),
181 source: None,
182 }
183 }
184
185 pub fn is_empty(&self) -> bool {
187 self.items.is_empty() && self.attributes.is_empty()
188 }
189
190 pub fn add_appinfo(&mut self, appinfo: AppInfoElement) {
192 self.items.push(AnnotationItem::AppInfo(appinfo));
193 }
194
195 pub fn add_documentation(&mut self, doc: DocumentationElement) {
197 self.items.push(AnnotationItem::Documentation(doc));
198 }
199
200 pub fn appinfos(&self) -> impl Iterator<Item = &AppInfoElement> {
202 self.items.iter().filter_map(|item| match item {
203 AnnotationItem::AppInfo(a) => Some(a),
204 _ => None,
205 })
206 }
207
208 pub fn documentations(&self) -> impl Iterator<Item = &DocumentationElement> {
210 self.items.iter().filter_map(|item| match item {
211 AnnotationItem::Documentation(d) => Some(d),
212 _ => None,
213 })
214 }
215
216 pub fn documentation_for_lang(&self, lang: &str) -> Option<&DocumentationElement> {
218 self.documentations()
219 .find(|d| d.lang.as_ref().is_some_and(|l| l == lang))
220 }
221
222 pub fn add_foreign_attribute(&mut self, attr: ForeignAttribute) {
224 self.attributes.push(attr);
225 }
226}
227
228impl Default for Annotation {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234pub fn create_implicit_annotation(
239 attrs: Vec<ForeignAttribute>,
240 source: Option<SourceRef>,
241) -> Annotation {
242 Annotation {
243 id: None,
244 attributes: attrs,
245 items: Vec::new(),
246 source,
247 }
248}
249
250pub fn merge_foreign_attributes(
256 annotation: Option<Annotation>,
257 foreign_attrs: Vec<ForeignAttribute>,
258 source: Option<SourceRef>,
259) -> Option<Annotation> {
260 if foreign_attrs.is_empty() {
261 return annotation;
262 }
263 match annotation {
264 Some(mut ann) => {
265 ann.attributes.extend(foreign_attrs);
266 Some(ann)
267 }
268 None => Some(create_implicit_annotation(foreign_attrs, source)),
269 }
270}
271
272pub fn is_foreign_attribute(namespace: Option<NameId>, xsd_ns: NameId, xsi_ns: NameId) -> bool {
279 match namespace {
280 None => false, Some(ns) => ns != xsd_ns && ns != xsi_ns,
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_xml_fragment() {
291 let fragment = XmlFragment::new(0, SourceSpan { start: 10, end: 50 });
292 assert_eq!(fragment.byte_range(), 10..50);
293 }
294
295 #[test]
296 fn test_foreign_attribute() {
297 let attr = ForeignAttribute::new(Some(NameId(1)), NameId(2), "value".to_string());
298 assert!(attr.is_in_namespace(Some(NameId(1))));
299 assert!(!attr.is_in_namespace(None));
300 }
301
302 #[test]
303 fn test_annotation_empty() {
304 let ann = Annotation::new();
305 assert!(ann.is_empty());
306 }
307
308 #[test]
309 fn test_annotation_with_items() {
310 let mut ann = Annotation::new();
311
312 let content = XmlFragment::new(0, SourceSpan { start: 0, end: 10 });
313 let namespaces = NamespaceContextSnapshot {
314 default_ns: None,
315 bindings: vec![],
316 };
317
318 ann.add_appinfo(AppInfoElement::new(content.clone(), namespaces.clone()));
319 ann.add_documentation(DocumentationElement::new(content, namespaces));
320
321 assert!(!ann.is_empty());
322 assert_eq!(ann.appinfos().count(), 1);
323 assert_eq!(ann.documentations().count(), 1);
324 }
325
326 #[test]
327 fn test_documentation_by_lang() {
328 let mut ann = Annotation::new();
329
330 let content = XmlFragment::new(0, SourceSpan { start: 0, end: 10 });
331 let namespaces = NamespaceContextSnapshot {
332 default_ns: None,
333 bindings: vec![],
334 };
335
336 let mut doc_en = DocumentationElement::new(content.clone(), namespaces.clone());
337 doc_en.lang = Some("en".to_string());
338
339 let mut doc_fr = DocumentationElement::new(content, namespaces);
340 doc_fr.lang = Some("fr".to_string());
341
342 ann.add_documentation(doc_en);
343 ann.add_documentation(doc_fr);
344
345 assert!(ann.documentation_for_lang("en").is_some());
346 assert!(ann.documentation_for_lang("fr").is_some());
347 assert!(ann.documentation_for_lang("de").is_none());
348 }
349
350 #[test]
351 fn test_implicit_annotation() {
352 let attrs = vec![ForeignAttribute::new(
353 Some(NameId(1)),
354 NameId(2),
355 "value".to_string(),
356 )];
357
358 let ann = create_implicit_annotation(attrs, None);
359 assert!(!ann.is_empty());
360 assert_eq!(ann.attributes.len(), 1);
361 }
362
363 #[test]
364 fn test_is_foreign_attribute() {
365 let xsd_ns = NameId(1);
366 let xsi_ns = NameId(2);
367 let other_ns = NameId(3);
368
369 assert!(!is_foreign_attribute(None, xsd_ns, xsi_ns));
371
372 assert!(!is_foreign_attribute(Some(xsd_ns), xsd_ns, xsi_ns));
374
375 assert!(!is_foreign_attribute(Some(xsi_ns), xsd_ns, xsi_ns));
377
378 assert!(is_foreign_attribute(Some(other_ns), xsd_ns, xsi_ns));
380 }
381}