use crate::ids::{DocumentId, NameId};
use crate::namespace::context::NamespaceContextSnapshot;
use crate::parser::location::{SourceRef, SourceSpan};
#[derive(Debug, Clone)]
pub struct XmlFragment {
pub doc_id: DocumentId,
pub span: SourceSpan,
}
impl XmlFragment {
pub fn new(doc_id: DocumentId, span: SourceSpan) -> Self {
Self { doc_id, span }
}
pub fn byte_range(&self) -> std::ops::Range<usize> {
self.span.start..self.span.end
}
}
#[derive(Debug, Clone)]
pub struct ForeignAttribute {
pub namespace: Option<NameId>,
pub local_name: NameId,
pub prefix: Option<NameId>,
pub value: String,
pub source: Option<SourceRef>,
}
impl ForeignAttribute {
pub fn new(namespace: Option<NameId>, local_name: NameId, value: String) -> Self {
Self {
namespace,
local_name,
prefix: None,
value,
source: None,
}
}
pub fn is_in_namespace(&self, ns: Option<NameId>) -> bool {
self.namespace == ns
}
}
#[derive(Debug, Clone)]
pub enum AnnotationItem {
AppInfo(AppInfoElement),
Documentation(DocumentationElement),
}
#[derive(Debug, Clone)]
pub struct AppInfoElement {
pub source: Option<String>,
pub attributes: Vec<ForeignAttribute>,
pub namespaces: NamespaceContextSnapshot,
pub content: XmlFragment,
pub source_ref: Option<SourceRef>,
}
impl AppInfoElement {
pub fn new(content: XmlFragment, namespaces: NamespaceContextSnapshot) -> Self {
Self {
source: None,
attributes: Vec::new(),
namespaces,
content,
source_ref: None,
}
}
}
#[derive(Debug, Clone)]
pub struct DocumentationElement {
pub source: Option<String>,
pub lang: Option<String>,
pub attributes: Vec<ForeignAttribute>,
pub namespaces: NamespaceContextSnapshot,
pub content: XmlFragment,
pub source_ref: Option<SourceRef>,
}
impl DocumentationElement {
pub fn new(content: XmlFragment, namespaces: NamespaceContextSnapshot) -> Self {
Self {
source: None,
lang: None,
attributes: Vec::new(),
namespaces,
content,
source_ref: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Annotation {
pub id: Option<String>,
pub attributes: Vec<ForeignAttribute>,
pub items: Vec<AnnotationItem>,
pub source: Option<SourceRef>,
}
impl Annotation {
pub fn new() -> Self {
Self {
id: None,
attributes: Vec::new(),
items: Vec::new(),
source: None,
}
}
pub fn is_empty(&self) -> bool {
self.items.is_empty() && self.attributes.is_empty()
}
pub fn add_appinfo(&mut self, appinfo: AppInfoElement) {
self.items.push(AnnotationItem::AppInfo(appinfo));
}
pub fn add_documentation(&mut self, doc: DocumentationElement) {
self.items.push(AnnotationItem::Documentation(doc));
}
pub fn appinfos(&self) -> impl Iterator<Item = &AppInfoElement> {
self.items.iter().filter_map(|item| match item {
AnnotationItem::AppInfo(a) => Some(a),
_ => None,
})
}
pub fn documentations(&self) -> impl Iterator<Item = &DocumentationElement> {
self.items.iter().filter_map(|item| match item {
AnnotationItem::Documentation(d) => Some(d),
_ => None,
})
}
pub fn documentation_for_lang(&self, lang: &str) -> Option<&DocumentationElement> {
self.documentations()
.find(|d| d.lang.as_ref().is_some_and(|l| l == lang))
}
pub fn add_foreign_attribute(&mut self, attr: ForeignAttribute) {
self.attributes.push(attr);
}
}
impl Default for Annotation {
fn default() -> Self {
Self::new()
}
}
pub fn create_implicit_annotation(
attrs: Vec<ForeignAttribute>,
source: Option<SourceRef>,
) -> Annotation {
Annotation {
id: None,
attributes: attrs,
items: Vec::new(),
source,
}
}
pub fn merge_foreign_attributes(
annotation: Option<Annotation>,
foreign_attrs: Vec<ForeignAttribute>,
source: Option<SourceRef>,
) -> Option<Annotation> {
if foreign_attrs.is_empty() {
return annotation;
}
match annotation {
Some(mut ann) => {
ann.attributes.extend(foreign_attrs);
Some(ann)
}
None => Some(create_implicit_annotation(foreign_attrs, source)),
}
}
pub fn is_foreign_attribute(namespace: Option<NameId>, xsd_ns: NameId, xsi_ns: NameId) -> bool {
match namespace {
None => false, Some(ns) => ns != xsd_ns && ns != xsi_ns,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xml_fragment() {
let fragment = XmlFragment::new(0, SourceSpan { start: 10, end: 50 });
assert_eq!(fragment.byte_range(), 10..50);
}
#[test]
fn test_foreign_attribute() {
let attr = ForeignAttribute::new(Some(NameId(1)), NameId(2), "value".to_string());
assert!(attr.is_in_namespace(Some(NameId(1))));
assert!(!attr.is_in_namespace(None));
}
#[test]
fn test_annotation_empty() {
let ann = Annotation::new();
assert!(ann.is_empty());
}
#[test]
fn test_annotation_with_items() {
let mut ann = Annotation::new();
let content = XmlFragment::new(0, SourceSpan { start: 0, end: 10 });
let namespaces = NamespaceContextSnapshot {
default_ns: None,
bindings: vec![],
};
ann.add_appinfo(AppInfoElement::new(content.clone(), namespaces.clone()));
ann.add_documentation(DocumentationElement::new(content, namespaces));
assert!(!ann.is_empty());
assert_eq!(ann.appinfos().count(), 1);
assert_eq!(ann.documentations().count(), 1);
}
#[test]
fn test_documentation_by_lang() {
let mut ann = Annotation::new();
let content = XmlFragment::new(0, SourceSpan { start: 0, end: 10 });
let namespaces = NamespaceContextSnapshot {
default_ns: None,
bindings: vec![],
};
let mut doc_en = DocumentationElement::new(content.clone(), namespaces.clone());
doc_en.lang = Some("en".to_string());
let mut doc_fr = DocumentationElement::new(content, namespaces);
doc_fr.lang = Some("fr".to_string());
ann.add_documentation(doc_en);
ann.add_documentation(doc_fr);
assert!(ann.documentation_for_lang("en").is_some());
assert!(ann.documentation_for_lang("fr").is_some());
assert!(ann.documentation_for_lang("de").is_none());
}
#[test]
fn test_implicit_annotation() {
let attrs = vec![ForeignAttribute::new(
Some(NameId(1)),
NameId(2),
"value".to_string(),
)];
let ann = create_implicit_annotation(attrs, None);
assert!(!ann.is_empty());
assert_eq!(ann.attributes.len(), 1);
}
#[test]
fn test_is_foreign_attribute() {
let xsd_ns = NameId(1);
let xsi_ns = NameId(2);
let other_ns = NameId(3);
assert!(!is_foreign_attribute(None, xsd_ns, xsi_ns));
assert!(!is_foreign_attribute(Some(xsd_ns), xsd_ns, xsi_ns));
assert!(!is_foreign_attribute(Some(xsi_ns), xsd_ns, xsi_ns));
assert!(is_foreign_attribute(Some(other_ns), xsd_ns, xsi_ns));
}
}