use self::builder::DictionaryBuilder;
use super::*;
use smallvec::SmallVec;
pub struct QuickFixReader<'a> {
node_with_header: roxmltree::Node<'a, 'a>,
node_with_trailer: roxmltree::Node<'a, 'a>,
node_with_components: roxmltree::Node<'a, 'a>,
node_with_messages: roxmltree::Node<'a, 'a>,
node_with_fields: roxmltree::Node<'a, 'a>,
builder: DictionaryBuilder,
}
impl<'a> QuickFixReader<'a> {
pub fn from_xml(xml_document: &'a roxmltree::Document<'a>) -> ParseResult<Dictionary> {
let mut reader = Self::empty(xml_document)?;
for child in reader.node_with_fields.children() {
if child.is_element() {
import_field(&mut reader.builder, child)?;
}
}
for child in reader.node_with_components.children() {
if child.is_element() {
let name = child
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string();
import_component(&mut reader.builder, child, &name)?;
}
}
for child in reader.node_with_messages.children() {
if child.is_element() {
import_message(&mut reader.builder, child)?;
}
}
import_component(
&mut reader.builder,
reader.node_with_header,
"StandardHeader",
)?;
import_component(
&mut reader.builder,
reader.node_with_trailer,
"StandardTrailer",
)?;
Ok(reader.builder.build())
}
fn empty(xml_document: &'a roxmltree::Document<'a>) -> ParseResult<Self> {
let root = xml_document.root_element();
let find_tagged_child = |tag: &str| {
root.children()
.find(|n| n.has_tag_name(tag))
.ok_or_else(|| ParseDictionaryError::InvalidData(format!("<{tag}> tag not found")))
};
let version_type = root
.attribute("type")
.ok_or(ParseDictionaryError::InvalidData(
"No version attribute.".to_string(),
))?;
let version_major = root
.attribute("major")
.ok_or(ParseDictionaryError::InvalidData(
"No major version attribute.".to_string(),
))?;
let version_minor = root
.attribute("minor")
.ok_or(ParseDictionaryError::InvalidData(
"No minor version attribute.".to_string(),
))?;
let version_sp = root.attribute("servicepack").unwrap_or("0");
let version = format!(
"{}.{}.{}{}",
version_type,
version_major,
version_minor,
if version_sp != "0" {
format!("-SP{version_sp}")
} else {
String::new()
}
);
Ok(QuickFixReader {
builder: DictionaryBuilder::new(Dictionary::new(version)),
node_with_header: find_tagged_child("header")?,
node_with_trailer: find_tagged_child("trailer")?,
node_with_messages: find_tagged_child("messages")?,
node_with_components: find_tagged_child("components")?,
node_with_fields: find_tagged_child("fields")?,
})
}
}
fn import_field(builder: &mut DictionaryBuilder, node: roxmltree::Node) -> ParseResult<()> {
if node.tag_name().name() != "field" {
return Err(ParseDictionaryError::InvalidFormat);
}
let data_type_name = import_datatype(builder, node);
let value_restrictions = value_restrictions_from_node(node, data_type_name.clone())?;
let name = node
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.into();
let tag = node
.attribute("number")
.ok_or(ParseDictionaryError::InvalidFormat)?
.parse()
.map_err(|_| ParseDictionaryError::InvalidFormat)?;
let field = FieldData {
name,
tag,
data_type_name,
associated_data_tag: None,
value_restrictions,
required: true,
abbr_name: None,
base_category_abbr_name: None,
base_category_id: None,
description: None,
};
builder.add_field(field);
Ok(())
}
fn import_message(dict: &mut DictionaryBuilder, node: roxmltree::Node) -> ParseResult<()> {
debug_assert_eq!(node.tag_name().name(), "message");
import_category(dict, node)?;
let mut layout_items = vec![];
for child in node.children() {
if child.is_element() {
layout_items.push(import_layout_item(dict, child)?);
}
}
let message = MessageData {
name: node
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.into(),
msg_type: node
.attribute("msgtype")
.ok_or(ParseDictionaryError::InvalidFormat)?
.into(),
component_id: 0,
category_name: "FIXME".into(),
section_id: String::new(),
layout_items,
abbr_name: None,
required: true,
elaboration: None,
description: String::new(),
};
dict.add_message(message);
Ok(())
}
fn import_component(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
name: &str,
) -> ParseResult<()> {
let mut layout_items = vec![];
for child in node.children() {
if child.is_element() {
layout_items.push(import_layout_item(builder, child)?);
}
}
let component = ComponentData {
id: 0,
component_type: FixmlComponentAttributes::Block {
is_implicit: false,
is_repeating: false,
is_optimized: false,
},
layout_items,
category_name: "".into(), name: name.into(),
abbr_name: None,
};
builder.add_component(component);
Ok(())
}
fn import_datatype(builder: &mut DictionaryBuilder, node: roxmltree::Node) -> SmartString {
debug_assert_eq!(node.tag_name().name(), "field");
let datatype = {
let quickfix_name = node.attribute("type").unwrap();
FixDatatype::from_quickfix_name(quickfix_name).unwrap()
};
let name = datatype.name();
if builder.dict().datatype_by_name(name).is_none() {
let dt = DatatypeData {
datatype,
description: String::new(),
examples: SmallVec::new(),
};
builder.add_datatype(dt);
}
name.into()
}
fn value_restrictions_from_node(
node: roxmltree::Node,
_datatype_name: SmartString,
) -> ParseResult<Option<SmallVec<[FieldEnumData; 16]>>> {
let mut values = SmallVec::new();
for child in node.children() {
if child.is_element() {
let variant = child
.attribute("enum")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string();
let description = child
.attribute("description")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string();
let enum_value = FieldEnumData {
value: variant,
description,
};
values.push(enum_value);
}
}
if values.is_empty() {
Ok(None)
} else {
Ok(Some(values))
}
}
fn import_layout_item(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
) -> ParseResult<LayoutItemData> {
debug_assert_ne!(builder.dict().fields().len(), 0);
let name = node
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?;
let required = node
.attribute("required")
.ok_or(ParseDictionaryError::InvalidFormat)?
== "Y";
let tag = node.tag_name().name();
let kind = match tag {
"field" => {
let field_tag = builder
.dict()
.field_by_name(name)
.ok_or(ParseDictionaryError::InvalidFormat)?
.tag()
.get();
LayoutItemKindData::Field { tag: field_tag }
}
"component" => {
import_component(builder, node, name)?;
LayoutItemKindData::Component { name: name.into() }
}
"group" => {
let len_field_tag = builder
.dict()
.field_by_name(name)
.ok_or(ParseDictionaryError::InvalidFormat)?
.tag()
.get();
let mut items = Vec::new();
for child in node.children().filter(|n| n.is_element()) {
items.push(import_layout_item(builder, child)?);
}
LayoutItemKindData::Group {
len_field_tag,
items,
}
}
_ => {
return Err(ParseDictionaryError::InvalidFormat);
}
};
let item = LayoutItemData { required, kind };
Ok(item)
}
fn import_category(builder: &mut DictionaryBuilder, node: roxmltree::Node) -> ParseResult<()> {
debug_assert_eq!(node.tag_name().name(), "message");
let name = node
.attribute("msgcat")
.ok_or(ParseDictionaryError::InvalidFormat)?;
if builder.dict().category_by_name(name).is_none() {
builder.add_category(CategoryData {
name: name.to_string(),
fixml_filename: String::new(),
});
}
Ok(())
}
type ParseError = ParseDictionaryError;
type ParseResult<T> = Result<T, ParseError>;
#[derive(Clone, Debug, thiserror::Error)]
pub enum ParseDictionaryError {
#[error("Invalid format.")]
InvalidFormat,
#[error("Invalid data: {0}")]
InvalidData(String),
}