/// A 'recursive descent' parser for Doxygen XML files mod builders; use xml::{ attribute::OwnedAttribute, reader::{EventReader, ParserConfig, XmlEvent}, }; use std::collections::HashMap; use std::io::Read; // === Types === #[derive(Debug, Clone)] pub(crate) enum DoxygenType { NameSpace(NameSpace), Class(Class), Unhandled, } #[derive(Debug, Clone)] pub(crate) struct NameSpace { pub(crate) name: String, pub(crate) members: Vec, pub(crate) brief_description: Description, pub(crate) detailed_description: Description, } #[derive(Debug, Clone)] pub(crate) struct FunctionArgument { pub(crate) name: String, pub(crate) r#type: String, pub(crate) default_value: Option, } #[derive(Debug, Clone)] pub(crate) enum Member { Variable { name: String, r#type: String, }, // What /* Enum { } */ Function { name: String, args: Vec, return_type: String, }, Unhandled, } #[derive(Debug, Clone)] pub(crate) struct Class { pub(crate) name: String, pub(crate) inners: Vec<(String, String)>, pub(crate) sections: HashMap>, pub(crate) brief_description: Description, pub(crate) detailed_description: Description, } // === Description === #[derive(Debug, Clone)] pub(crate) struct Description { pub(crate) inner: Vec, } impl Description { fn new() -> Self { Self { inner: Vec::new() } } } #[derive(Debug, Clone)] pub(crate) enum DescriptionNode { Text(String), Title(String), Para(Paragraph), Sect1(String), Internal(String), } #[derive(Debug, Clone)] pub(crate) struct Paragraph { pub(crate) inner: Vec, } #[derive(Debug, Clone)] pub(crate) enum ParagraphNode { Text(String), Ref(Ref), } #[derive(Debug, Clone)] pub(crate) struct Ref { pub(crate) id: String, pub(crate) kind: RefKind, pub(crate) text: String, } #[derive(Debug, Clone)] pub(crate) enum RefKind { Compound, Member, } #[derive(Debug, Clone)] enum DoxygenTypeKind { NameSpace, Unhandled, } trait TypeBuilder { //const KIND: DoxygenTypeKind; fn build(self: Box) -> DoxygenType; fn name(&mut self, value: String); fn brief_description(&mut self, description: Description); fn detailed_description(&mut self, description: Description); fn add_inner(&mut self, name: String, kind: String); fn add_member(&mut self, section: String, member: Member); } trait FromXML { fn from_xml(attributes: Vec, event_reader: &mut EventReader) -> Self; } impl FromXML for Description { fn from_xml( _attributes: Vec, mut event_reader: &mut EventReader, ) -> Self { let mut inner = Vec::new(); while let Ok(event) = event_reader.next() { match event { XmlEvent::Characters(text) => inner.push(DescriptionNode::Text(text)), XmlEvent::StartElement { name, attributes, .. } => inner.push(match name.local_name.as_str() { "para" => { DescriptionNode::Para(Paragraph::from_xml(attributes, &mut event_reader)) } "title" => DescriptionNode::Title(event_simple(&mut event_reader)), "sect1" => DescriptionNode::Sect1(event_simple(&mut event_reader)), "internal" => DescriptionNode::Internal(event_simple(&mut event_reader)), other => { warn!("Description element not supported: {}", other); continue; } }), XmlEvent::EndElement { .. } => break, other => { warn!("Description event not supported: {:?}", other); } } } Self { inner } } } impl FromXML for Paragraph { fn from_xml( _attributes: Vec, mut event_reader: &mut EventReader, ) -> Self { let mut inner = Vec::new(); while let Ok(event) = event_reader.next() { match event { XmlEvent::Characters(text) => inner.push(ParagraphNode::Text(text)), XmlEvent::StartElement { name, attributes, .. } => inner.push(match name.local_name.as_str() { "ref" => ParagraphNode::Ref(Ref::from_xml(attributes, &mut event_reader)), other => { warn!("Paragraph element not supported: {}", other); continue; } }), XmlEvent::EndElement { .. } => break, other => { warn!("Paragraph event not supported: {:?}", other); } } } Self { inner } } } impl FromXML for Ref { fn from_xml( attributes: Vec, mut event_reader: &mut EventReader, ) -> Self { let mut id = None; let mut kind = None; for attr in attributes.into_iter() { match attr.name.local_name.as_str() { "refid" => id = Some(attr.value), "kindref" => { kind = Some(match attr.value.as_str() { "compound" => RefKind::Compound, "member" => RefKind::Member, other => { warn!("Ref kind not supported: {}", other); RefKind::Compound } }); } other => { warn!("Ref element not supported: {}", other); } } } let text = event_simple(&mut event_reader); Ref { id: id.unwrap(), kind: kind.unwrap(), text, } } } /// Parse a type from a XML Doxygen file pub(crate) fn parse_type(reader: impl Read) -> Vec { let mut event_reader = EventReader::new(reader); match event_reader.next().unwrap() { XmlEvent::StartDocument { .. } => (), _ => panic!("XML file does not begin with document"), } match event_reader.next().unwrap() { XmlEvent::StartElement { name, .. } => { if name.local_name != "doxygen" { panic!("XML file does not start with a 'doxygen' element"); } } _ => panic!("XML file does not start with a 'doxygen' element"), } let mut result = Vec::new(); while let Ok(event) = event_reader.next() { match dbg!(event) { XmlEvent::StartElement { name, attributes, .. } => { if name.local_name != "compounddef" { panic!("'doxygen' is not a sequence of 'compounddef' elements"); } result.push(event_compounddef(attributes, &mut event_reader)); } XmlEvent::Whitespace(_) => (), XmlEvent::EndElement { .. } => break, _ => panic!("'doxygen' is not a sequence of 'compounddef' elements"), } } // Unnecessarily strict? match event_reader.next().unwrap() { XmlEvent::EndDocument => (), _ => panic!("XML file does not end after 'doxygen' element"), } dbg!(result) } fn event_compounddef( attributes: Vec, mut event_reader: &mut EventReader, ) -> DoxygenType { let kind = attributes .into_iter() .find_map(|attr| { if attr.name.local_name == "kind" { Some(attr.value) } else { None } }) .unwrap(); let mut builder = builders::builder_for(kind); while let Ok(event) = event_reader.next() { match dbg!(event) { XmlEvent::StartElement { name, attributes, .. } => match name.local_name.as_str() { "compoundname" => { let name = event_simple(&mut event_reader); builder.name(name); } "briefdescription" => { let brief_description = Description::from_xml(attributes, &mut event_reader); builder.brief_description(brief_description); } "detaileddescription" => { let detailed_description = Description::from_xml(attributes, &mut event_reader); builder.detailed_description(detailed_description); } "location" => { event_simple(&mut event_reader); debug!("Do something?"); } "sectiondef" => { event_section(&mut builder, &mut event_reader); } other_name if is_inner_type(other_name) => { let inner = event_simple(&mut event_reader); builder.add_inner(inner, other_name.to_string()); } _other_name => { event_ignore(&mut event_reader); } }, XmlEvent::Whitespace(_) => (), XmlEvent::EndElement { .. } => break, _ => panic!("Unhandled XML event"), } } builder.build() } /// Returns true if the given XML Element is a reference to another inner type. /// /// Corresponds to the "refType" XSD type in `compound.xsd` fn is_inner_type(element_name: &str) -> bool { match element_name { "innerdir" | "innerfile" | "innerclass" | "innernamespace" | "innerpage" | "innergroup" => { true } _ => false, } } /// Get the text inside a simple, non recursive XML tag fn event_simple(event_reader: &mut EventReader) -> String { let result; match dbg!(event_reader.next().unwrap()) { XmlEvent::Characters(tmp_result) => { result = tmp_result; } XmlEvent::EndElement { .. } => return "".to_string(), _ => panic!("Simple XML event is not so simple"), } match dbg!(event_reader.next().unwrap()) { XmlEvent::EndElement { .. } => (), _ => panic!("Simple XML event is not so simple"), } result } fn event_ignore(mut event_reader: &mut EventReader) { loop { let event = event_reader.next().unwrap(); match dbg!(event) { XmlEvent::StartElement { .. } => event_ignore(&mut event_reader), XmlEvent::EndElement { .. } => break, _ => (), } } } fn event_section( mut builder: &mut Box, mut event_reader: &mut EventReader, ) { loop { let event = event_reader.next().unwrap(); match dbg!(event) { XmlEvent::StartElement { name, attributes, .. } => match name.local_name.as_str() { "memberdef" => { let member = event_member(attributes, &mut event_reader); builder.add_member("bla".to_owned(), member); } _ => panic!("Unhandled thing"), }, XmlEvent::Whitespace(_) => (), XmlEvent::EndElement { .. } => break, _ => panic!("Unknown section XML event"), } } } fn event_member( attributes: Vec, mut event_reader: &mut EventReader, ) -> Member { let kind = attributes .into_iter() .find_map(|attr| { if attr.name.local_name == "kind" { Some(attr.value) } else { None } }) .unwrap(); match kind.as_str() { "variable" => event_member_variable(&mut event_reader), _ => { event_ignore(&mut event_reader); Member::Unhandled } } } fn event_member_variable(mut event_reader: &mut EventReader) -> Member { let mut member_name = None; let mut r#type = None; loop { let event = event_reader.next().unwrap(); match dbg!(event) { XmlEvent::StartElement { name, .. } => match name.local_name.as_str() { "name" => member_name = Some(event_simple(&mut event_reader)), "definition" => r#type = Some(event_simple(&mut event_reader)), _ => event_ignore(&mut event_reader), }, XmlEvent::Whitespace(_) => (), XmlEvent::EndElement { .. } => break, _ => panic!("Unknown member XML event"), } } Member::Variable { name: member_name.unwrap(), r#type: r#type.unwrap(), } }