1#![cfg_attr(docsrs, feature(doc_cfg))]
2mod builders;
8#[cfg(feature = "fetch")]
9mod fetch;
10mod parsers;
11mod util;
12
13#[cfg(feature = "fetch")]
14pub use fetch::fetch_and_load_xml;
15
16use quick_xml::Reader;
17use quick_xml::events::{BytesStart, Event};
18use quick_xml::name::QName;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21
22use parsers::{
23 parse_boolean, parse_category, parse_category_empty, parse_command, parse_command_empty,
24 parse_converter, parse_enum, parse_float, parse_int_converter, parse_integer, parse_register,
25 parse_string, parse_struct_reg, parse_swissknife,
26};
27use util::{attribute_value, skip_element};
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum EnumValueSrc {
32 Literal(i64),
34 FromNode(String),
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
50pub struct PredicateRefs {
51 #[serde(
53 default,
54 skip_serializing_if = "Option::is_none",
55 rename = "pIsImplemented"
56 )]
57 pub p_is_implemented: Option<String>,
58 #[serde(
60 default,
61 skip_serializing_if = "Option::is_none",
62 rename = "pIsAvailable"
63 )]
64 pub p_is_available: Option<String>,
65 #[serde(default, skip_serializing_if = "Option::is_none", rename = "pIsLocked")]
67 pub p_is_locked: Option<String>,
68}
69
70impl PredicateRefs {
71 pub fn references(&self) -> impl Iterator<Item = &str> {
73 [
74 self.p_is_implemented.as_deref(),
75 self.p_is_available.as_deref(),
76 self.p_is_locked.as_deref(),
77 ]
78 .into_iter()
79 .flatten()
80 }
81
82 pub fn is_empty(&self) -> bool {
84 self.p_is_implemented.is_none()
85 && self.p_is_available.is_none()
86 && self.p_is_locked.is_none()
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct EnumEntryDecl {
93 pub name: String,
95 pub value: EnumValueSrc,
97 pub display_name: Option<String>,
99 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
101 pub predicates: PredicateRefs,
102}
103
104#[derive(Debug, Error)]
105#[non_exhaustive]
106pub enum XmlError {
107 #[error("xml: {0}")]
108 Xml(String),
109 #[error("invalid descriptor: {0}")]
110 Invalid(String),
111 #[error("transport: {0}")]
112 Transport(String),
113 #[error("unsupported URL: {0}")]
114 Unsupported(String),
115}
116
117#[derive(
122 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
123)]
124#[non_exhaustive]
125pub enum Visibility {
126 #[default]
128 Beginner,
129 Expert,
131 Guru,
133 Invisible,
135}
136
137impl Visibility {
138 pub(crate) fn parse(s: &str) -> Option<Self> {
139 match s.trim() {
140 "Beginner" => Some(Self::Beginner),
141 "Expert" => Some(Self::Expert),
142 "Guru" => Some(Self::Guru),
143 "Invisible" => Some(Self::Invisible),
144 _ => None,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
151#[non_exhaustive]
152pub enum Representation {
153 Linear,
154 Logarithmic,
155 Boolean,
156 PureNumber,
157 HexNumber,
158 IPV4Address,
160 MACAddress,
162}
163
164impl Representation {
165 pub(crate) fn parse(s: &str) -> Option<Self> {
166 match s.trim() {
167 "Linear" => Some(Self::Linear),
168 "Logarithmic" => Some(Self::Logarithmic),
169 "Boolean" => Some(Self::Boolean),
170 "PureNumber" => Some(Self::PureNumber),
171 "HexNumber" => Some(Self::HexNumber),
172 "IPV4Address" => Some(Self::IPV4Address),
173 "MACAddress" => Some(Self::MACAddress),
174 _ => None,
175 }
176 }
177}
178
179#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
181pub struct NodeMeta {
182 pub visibility: Visibility,
184 pub description: Option<String>,
186 pub tooltip: Option<String>,
188 pub display_name: Option<String>,
190 pub representation: Option<Representation>,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196pub enum AccessMode {
197 RO,
199 WO,
201 RW,
203}
204
205impl AccessMode {
206 pub(crate) fn parse(value: &str) -> Result<Self, XmlError> {
215 let trimmed = value.trim();
216 match trimmed.to_ascii_uppercase().as_str() {
217 "RO" | "R" => Ok(AccessMode::RO),
218 "WO" | "W" => Ok(AccessMode::WO),
219 "RW" | "WR" => Ok(AccessMode::RW),
220 other => {
221 tracing::warn!(
222 access_mode = other,
223 "unknown <AccessMode> value, assuming RW"
224 );
225 Ok(AccessMode::RW)
226 }
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub enum Addressing {
234 Sum {
251 terms: Vec<AddressTerm>,
253 len: u32,
255 },
256 BySelector {
261 selector: String,
263 map: Vec<(String, (u64, u32))>,
265 },
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270pub enum AddressTerm {
271 Fixed(u64),
273 Node(String),
275 Index {
277 node: String,
279 offset: IndexOffset,
281 },
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
286pub enum IndexOffset {
287 Fixed(u64),
289 Node(String),
291 Length,
293}
294
295impl Addressing {
296 pub fn fixed(address: u64, len: u32) -> Self {
298 Addressing::Sum {
299 terms: vec![AddressTerm::Fixed(address)],
300 len,
301 }
302 }
303
304 pub fn byte_len(&self) -> Option<u32> {
306 match self {
307 Addressing::Sum { len, .. } => Some(*len),
308 Addressing::BySelector { .. } => None,
309 }
310 }
311
312 pub fn referenced_nodes(&self) -> Vec<&str> {
317 match self {
318 Addressing::Sum { terms, .. } => terms
319 .iter()
320 .flat_map(|term| match term {
321 AddressTerm::Fixed(_) => Vec::new(),
322 AddressTerm::Node(node) => vec![node.as_str()],
323 AddressTerm::Index { node, offset } => match offset {
324 IndexOffset::Node(stride) => vec![node.as_str(), stride.as_str()],
325 _ => vec![node.as_str()],
326 },
327 })
328 .collect(),
329 Addressing::BySelector { selector, .. } => vec![selector.as_str()],
330 }
331 }
332}
333
334#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
342pub enum Sign {
343 #[default]
345 Unsigned,
346 Signed,
348}
349
350impl Sign {
351 pub(crate) fn parse(tag: &str) -> Option<Self> {
352 match tag.trim().to_ascii_lowercase().as_str() {
353 "signed" => Some(Sign::Signed),
354 "unsigned" => Some(Sign::Unsigned),
355 _ => None,
356 }
357 }
358
359 pub fn is_signed(self) -> bool {
361 matches!(self, Sign::Signed)
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367pub enum ByteOrder {
368 Little,
370 Big,
372}
373
374impl ByteOrder {
375 pub(crate) fn parse(tag: &str) -> Option<Self> {
376 match tag.trim().to_ascii_lowercase().as_str() {
377 "littleendian" => Some(ByteOrder::Little),
378 "bigendian" => Some(ByteOrder::Big),
379 _ => None,
380 }
381 }
382}
383
384fn default_big_endian() -> ByteOrder {
385 ByteOrder::Big
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
390pub struct BitField {
391 pub bit_offset: u16,
393 pub bit_length: u16,
395 pub byte_order: ByteOrder,
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
413pub enum FloatEncoding {
414 Ieee754,
416 #[default]
419 ScaledInteger,
420}
421
422#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
424pub enum SkOutput {
425 Integer,
428 #[default]
431 Float,
432}
433
434impl SkOutput {
435 pub(crate) fn parse(tag: &str) -> Option<Self> {
436 match tag.trim().to_ascii_lowercase().as_str() {
437 "integer" => Some(SkOutput::Integer),
438 "float" => Some(SkOutput::Float),
439 _ => None,
440 }
441 }
442}
443#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
458pub struct FormulaBindings {
459 #[serde(default, skip_serializing_if = "Vec::is_empty")]
461 pub constants: Vec<(String, String)>,
462 #[serde(default, skip_serializing_if = "Vec::is_empty")]
464 pub expressions: Vec<(String, String)>,
465}
466
467impl FormulaBindings {
468 pub fn is_empty(&self) -> bool {
470 self.constants.is_empty() && self.expressions.is_empty()
471 }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct SwissKnifeDecl {
477 pub name: String,
479 pub meta: NodeMeta,
481 pub expr: String,
483 pub variables: Vec<(String, String)>,
485 pub output: SkOutput,
487 #[serde(default, skip_serializing_if = "FormulaBindings::is_empty")]
489 pub bindings: FormulaBindings,
490 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
492 pub predicates: PredicateRefs,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct ConverterDecl {
501 pub name: String,
503 pub meta: NodeMeta,
505 pub p_value: String,
507 pub formula_to: String,
511 pub formula_from: String,
514 pub variables_to: Vec<(String, String)>,
516 pub variables_from: Vec<(String, String)>,
518 #[serde(default, skip_serializing_if = "FormulaBindings::is_empty")]
520 pub bindings: FormulaBindings,
521 pub unit: Option<String>,
523 pub output: SkOutput,
525 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
527 pub predicates: PredicateRefs,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
532pub struct IntConverterDecl {
533 pub name: String,
535 pub meta: NodeMeta,
537 pub p_value: String,
539 pub formula_to: String,
543 pub formula_from: String,
546 pub variables_to: Vec<(String, String)>,
548 pub variables_from: Vec<(String, String)>,
550 #[serde(default, skip_serializing_if = "FormulaBindings::is_empty")]
552 pub bindings: FormulaBindings,
553 pub unit: Option<String>,
555 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
557 pub predicates: PredicateRefs,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
562pub struct StringDecl {
563 pub name: String,
565 pub meta: NodeMeta,
567 pub addressing: Addressing,
569 pub access: AccessMode,
571 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
573 pub predicates: PredicateRefs,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct RegisterDecl {
587 pub name: String,
589 pub meta: NodeMeta,
591 pub addressing: Addressing,
593 pub access: AccessMode,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
599 pub port: Option<String>,
600 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
602 pub predicates: PredicateRefs,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
607#[non_exhaustive]
608pub enum NodeDecl {
609 Integer {
611 name: String,
613 meta: NodeMeta,
615 addressing: Option<Addressing>,
617 len: u32,
619 access: AccessMode,
621 min: i64,
623 max: i64,
625 inc: Option<i64>,
627 unit: Option<String>,
629 bitfield: Option<BitField>,
631 #[serde(default)]
633 sign: Sign,
634 #[serde(default = "default_big_endian")]
638 byte_order: ByteOrder,
639 selectors: Vec<String>,
641 selected_if: Vec<(String, Vec<String>)>,
643 pvalue: Option<String>,
645 p_max: Option<String>,
647 p_min: Option<String>,
649 value: Option<i64>,
651 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
653 predicates: PredicateRefs,
654 },
655 Float {
658 name: String,
659 meta: NodeMeta,
660 addressing: Option<Addressing>,
662 access: AccessMode,
663 min: f64,
664 max: f64,
665 unit: Option<String>,
666 scale: Option<(i64, i64)>,
668 offset: Option<f64>,
670 selectors: Vec<String>,
671 selected_if: Vec<(String, Vec<String>)>,
672 pvalue: Option<String>,
674 #[serde(default)]
678 encoding: FloatEncoding,
679 #[serde(default = "default_big_endian")]
682 byte_order: ByteOrder,
683 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
685 predicates: PredicateRefs,
686 },
687 Enum {
689 name: String,
690 meta: NodeMeta,
691 addressing: Option<Addressing>,
693 access: AccessMode,
694 entries: Vec<EnumEntryDecl>,
695 default: Option<String>,
696 selectors: Vec<String>,
697 selected_if: Vec<(String, Vec<String>)>,
698 pvalue: Option<String>,
700 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
702 predicates: PredicateRefs,
703 },
704 Boolean {
706 name: String,
707 meta: NodeMeta,
708 addressing: Option<Addressing>,
710 len: u32,
711 access: AccessMode,
712 bitfield: Option<BitField>,
713 selectors: Vec<String>,
714 selected_if: Vec<(String, Vec<String>)>,
715 pvalue: Option<String>,
717 on_value: Option<i64>,
719 off_value: Option<i64>,
721 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
723 predicates: PredicateRefs,
724 },
725 Command {
727 name: String,
728 meta: NodeMeta,
729 address: Option<u64>,
731 len: u32,
732 pvalue: Option<String>,
734 command_value: Option<i64>,
736 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
738 predicates: PredicateRefs,
739 },
740 Category {
742 name: String,
743 meta: NodeMeta,
744 children: Vec<String>,
745 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
747 predicates: PredicateRefs,
748 },
749 SwissKnife(SwissKnifeDecl),
751 Converter(ConverterDecl),
753 IntConverter(IntConverterDecl),
755 String(StringDecl),
757 Register(RegisterDecl),
759}
760
761impl NodeDecl {
762 pub fn name(&self) -> &str {
764 match self {
765 NodeDecl::Integer { name, .. }
766 | NodeDecl::Float { name, .. }
767 | NodeDecl::Enum { name, .. }
768 | NodeDecl::Boolean { name, .. }
769 | NodeDecl::Command { name, .. }
770 | NodeDecl::Category { name, .. } => name,
771 NodeDecl::SwissKnife(decl) => &decl.name,
772 NodeDecl::Converter(decl) => &decl.name,
773 NodeDecl::IntConverter(decl) => &decl.name,
774 NodeDecl::String(decl) => &decl.name,
775 NodeDecl::Register(decl) => &decl.name,
776 }
777 }
778
779 pub fn kind(&self) -> &'static str {
786 match self {
787 NodeDecl::Integer { .. } => "Integer",
788 NodeDecl::Float { .. } => "Float",
789 NodeDecl::Enum { .. } => "Enum",
790 NodeDecl::Boolean { .. } => "Boolean",
791 NodeDecl::Command { .. } => "Command",
792 NodeDecl::Category { .. } => "Category",
793 NodeDecl::SwissKnife(_) => "SwissKnife",
794 NodeDecl::Converter(_) => "Converter",
795 NodeDecl::IntConverter(_) => "IntConverter",
796 NodeDecl::String(_) => "String",
797 NodeDecl::Register(_) => "Register",
798 }
799 }
800}
801
802#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
809pub struct SkippedNode {
810 pub tag: String,
812 pub name: Option<String>,
814 pub error: String,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize)]
820pub struct XmlModel {
821 pub version: String,
823 pub nodes: Vec<NodeDecl>,
825 #[serde(default, skip_serializing_if = "Vec::is_empty")]
828 pub skipped: Vec<SkippedNode>,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq)]
833pub struct MinimalXmlInfo {
834 pub schema_version: Option<String>,
835 pub top_level_features: Vec<String>,
836}
837
838fn strip_bom(xml: &str) -> &str {
849 xml.strip_prefix('\u{feff}').unwrap_or(xml)
850}
851
852pub fn parse_into_minimal_nodes(xml: &str) -> Result<MinimalXmlInfo, XmlError> {
854 let xml = strip_bom(xml);
855 let mut reader = Reader::from_str(xml);
856 reader.config_mut().trim_text(true);
857 reader.config_mut().allow_dangling_amp = true;
860 let mut buf = Vec::new();
861 let mut depth = 0usize;
862 let mut schema_version: Option<String> = None;
863 let mut top_level_features = Vec::new();
864
865 loop {
866 match reader.read_event_into(&mut buf) {
867 Ok(Event::Start(e)) => {
868 depth += 1;
869 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
870 }
871 Ok(Event::Empty(e)) => {
872 depth += 1;
873 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
874 depth = depth.saturating_sub(1);
875 }
876 Ok(Event::End(_)) => {
877 depth = depth.saturating_sub(1);
878 }
879 Ok(Event::Eof) => break,
880 Err(err) => return Err(XmlError::Xml(err.to_string())),
881 _ => {}
882 }
883 buf.clear();
884 }
885
886 Ok(MinimalXmlInfo {
887 schema_version,
888 top_level_features,
889 })
890}
891
892fn is_node_tag(tag: &str) -> bool {
894 matches!(
895 tag,
896 "Integer"
897 | "IntReg"
898 | "MaskedIntReg"
899 | "IntSwissKnife"
900 | "SwissKnife"
901 | "Float"
902 | "FloatReg"
903 | "Enumeration"
904 | "Boolean"
905 | "Command"
906 | "Category"
907 | "Converter"
908 | "IntConverter"
909 | "Register"
910 | "StringReg"
911 | "String"
912 | "StructReg"
913 )
914}
915
916fn unknown_node(tag: &str, start: &BytesStart<'_>) -> Result<Option<SkippedNode>, XmlError> {
926 let Some(name) = attribute_value(start, "Name")? else {
927 return Ok(None);
928 };
929 let tag = tag.to_owned();
930 tracing::warn!(
931 tag = %tag,
932 node = %name,
933 "skipping node of an unsupported type"
934 );
935 Ok(Some(SkippedNode {
936 error: format!("unsupported node type <{tag}>"),
937 tag,
938 name: Some(name),
939 }))
940}
941
942fn parse_node(
945 reader: &mut Reader<&[u8]>,
946 start: BytesStart<'_>,
947) -> Result<Vec<NodeDecl>, XmlError> {
948 let tag = start.name().as_ref().to_string();
949 let node = match tag.as_str() {
950 "Integer" | "IntReg" | "MaskedIntReg" => parse_integer(reader, start)?,
951 "IntSwissKnife" | "SwissKnife" => parse_swissknife(reader, start)?,
952 "Float" | "FloatReg" => parse_float(reader, start)?,
953 "Enumeration" => parse_enum(reader, start)?,
954 "Boolean" => parse_boolean(reader, start)?,
955 "Command" => parse_command(reader, start)?,
956 "Category" => parse_category(reader, start)?,
957 "Converter" => parse_converter(reader, start)?,
958 "IntConverter" => parse_int_converter(reader, start)?,
959 "Register" => parse_register(reader, start)?,
960 "StringReg" | "String" => parse_string(reader, start)?,
961 "StructReg" => return parse_struct_reg(reader, start),
962 other => {
963 return Err(XmlError::Invalid(format!("not a node element: {other}")));
964 }
965 };
966 Ok(vec![node])
967}
968
969fn parse_isolated_node(element: &str) -> Result<Vec<NodeDecl>, XmlError> {
975 let mut reader = Reader::from_str(element);
976 reader.config_mut().trim_text(true);
977 reader.config_mut().allow_dangling_amp = true;
978 let mut buf = Vec::new();
979 loop {
980 match reader.read_event_into(&mut buf) {
981 Ok(Event::Start(e)) => {
982 let start = e.into_owned();
983 return parse_node(&mut reader, start);
984 }
985 Ok(Event::Eof) => {
986 return Err(XmlError::Invalid("node element is empty".into()));
987 }
988 Err(err) => return Err(XmlError::Xml(err.to_string())),
989 _ => {}
991 }
992 }
993}
994
995pub fn parse(xml: &str) -> Result<XmlModel, XmlError> {
1006 let xml = strip_bom(xml);
1007 let mut reader = Reader::from_str(xml);
1008 reader.config_mut().trim_text(true);
1009 reader.config_mut().allow_dangling_amp = true;
1012 let mut buf = Vec::new();
1013 let mut version = String::from("0.0.0");
1014 let mut nodes = Vec::new();
1015 let mut skipped = Vec::new();
1016
1017 loop {
1018 let element_start = reader.buffer_position() as usize;
1021 match reader.read_event_into(&mut buf) {
1022 Ok(Event::Start(ref e)) => match e.name().as_ref() {
1023 "RegisterDescription" => {
1024 version = schema_version_from(e)?;
1025 }
1026 "Group" => {
1027 }
1030 "Port" => {
1031 skip_element(&mut reader, e.name().as_ref())?;
1033 }
1034 tag if is_node_tag(tag) => {
1035 let tag = tag.to_owned();
1036 let name = attribute_value(e, "Name")?;
1037 reader
1040 .read_to_end(QName(&tag))
1041 .map_err(|err| XmlError::Xml(err.to_string()))?;
1042 let element = xml
1049 .get(element_start..reader.buffer_position() as usize)
1050 .ok_or_else(|| {
1051 XmlError::Invalid(format!(
1052 "reader offset {element_start}..{} is not a character \
1053 boundary in the document",
1054 reader.buffer_position()
1055 ))
1056 });
1057 match element.and_then(parse_isolated_node) {
1058 Ok(parsed) => nodes.extend(parsed),
1059 Err(err) => {
1060 tracing::warn!(
1061 tag = %tag,
1062 node = name.as_deref().unwrap_or("<unnamed>"),
1063 error = %err,
1064 "skipping unparsable node"
1065 );
1066 skipped.push(SkippedNode {
1067 tag,
1068 name,
1069 error: err.to_string(),
1070 });
1071 }
1072 }
1073 }
1074 tag => {
1075 let unknown = unknown_node(tag, e)?;
1076 skip_element(&mut reader, e.name().as_ref())?;
1077 if let Some(record) = unknown {
1078 skipped.push(record);
1079 }
1080 }
1081 },
1082 Ok(Event::Empty(ref e)) => match e.name().as_ref() {
1083 "RegisterDescription" => {
1084 version = schema_version_from(e)?;
1085 }
1086 "Command" => {
1087 let node = parse_command_empty(e)?;
1088 nodes.push(node);
1089 }
1090 "Category" => {
1091 let node = parse_category_empty(e)?;
1092 nodes.push(node);
1093 }
1094 "Port" => {}
1097 tag => {
1098 if let Some(record) = unknown_node(tag, e)? {
1099 skipped.push(record);
1100 }
1101 }
1102 },
1103 Ok(Event::Eof) => break,
1104 Err(err) => return Err(XmlError::Xml(err.to_string())),
1105 _ => {}
1106 }
1107 buf.clear();
1108 }
1109
1110 if !skipped.is_empty() {
1111 tracing::warn!(
1112 count = skipped.len(),
1113 total = nodes.len() + skipped.len(),
1114 "some GenApi nodes could not be parsed and were skipped"
1115 );
1116 }
1117
1118 Ok(XmlModel {
1119 version,
1120 nodes,
1121 skipped,
1122 })
1123}
1124
1125fn schema_version_from(event: &BytesStart<'_>) -> Result<String, XmlError> {
1126 let major = attribute_value(event, "SchemaMajorVersion")?;
1127 let minor = attribute_value(event, "SchemaMinorVersion")?;
1128 let sub = attribute_value(event, "SchemaSubMinorVersion")?;
1129 let major = major.unwrap_or_else(|| "0".to_string());
1130 let minor = minor.unwrap_or_else(|| "0".to_string());
1131 let sub = sub.unwrap_or_else(|| "0".to_string());
1132 Ok(format!("{major}.{minor}.{sub}"))
1133}
1134
1135fn handle_start(
1136 event: &BytesStart<'_>,
1137 depth: usize,
1138 schema_version: &mut Option<String>,
1139 top_level: &mut Vec<String>,
1140) -> Result<(), XmlError> {
1141 if depth == 1 && schema_version.is_none() {
1142 *schema_version = extract_schema_version(event);
1143 } else if depth == 2 {
1144 if let Some(name) = attribute_value(event, "Name")? {
1145 top_level.push(name);
1146 } else {
1147 top_level.push(event.name().as_ref().to_string());
1148 }
1149 }
1150 Ok(())
1151}
1152
1153fn extract_schema_version(event: &BytesStart<'_>) -> Option<String> {
1154 let major = attribute_value(event, "SchemaMajorVersion").ok().flatten();
1155 let minor = attribute_value(event, "SchemaMinorVersion").ok().flatten();
1156 let sub = attribute_value(event, "SchemaSubMinorVersion")
1157 .ok()
1158 .flatten();
1159 if major.is_none() && minor.is_none() && sub.is_none() {
1160 None
1161 } else {
1162 let major = major.unwrap_or_else(|| "0".to_string());
1163 let minor = minor.unwrap_or_else(|| "0".to_string());
1164 let sub = sub.unwrap_or_else(|| "0".to_string());
1165 Some(format!("{major}.{minor}.{sub}"))
1166 }
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171 use super::*;
1172
1173 const FIXTURE: &str = r#"
1174 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
1175 <Category Name="Root">
1176 <pFeature>Gain</pFeature>
1177 <pFeature>GainSelector</pFeature>
1178 </Category>
1179 <Integer Name="Width">
1180 <Address>0x0000_0100</Address>
1181 <Length>4</Length>
1182 <AccessMode>RW</AccessMode>
1183 <Min>16</Min>
1184 <Max>4096</Max>
1185 <Inc>2</Inc>
1186 </Integer>
1187 <Float Name="ExposureTime">
1188 <Address>0x0000_0200</Address>
1189 <Length>4</Length>
1190 <AccessMode>RW</AccessMode>
1191 <Min>10.0</Min>
1192 <Max>200000.0</Max>
1193 <Scale>1/1000</Scale>
1194 <Offset>0.0</Offset>
1195 </Float>
1196 <Enumeration Name="GainSelector">
1197 <Address>0x0000_0300</Address>
1198 <Length>2</Length>
1199 <AccessMode>RW</AccessMode>
1200 <EnumEntry Name="AnalogAll" Value="0" />
1201 <EnumEntry Name="DigitalAll" Value="1" />
1202 </Enumeration>
1203 <Integer Name="Gain">
1204 <Address>0x0000_0304</Address>
1205 <Length>2</Length>
1206 <AccessMode>RW</AccessMode>
1207 <Min>0</Min>
1208 <Max>48</Max>
1209 <pSelected>GainSelector</pSelected>
1210 <Selected>AnalogAll</Selected>
1211 </Integer>
1212 <Boolean Name="GammaEnable">
1213 <Address>0x0000_0400</Address>
1214 <Length>1</Length>
1215 <AccessMode>RW</AccessMode>
1216 </Boolean>
1217 <Command Name="AcquisitionStart">
1218 <Address>0x0000_0500</Address>
1219 <Length>4</Length>
1220 </Command>
1221 </RegisterDescription>
1222 "#;
1223
1224 #[test]
1225 fn parse_minimal_xml() {
1226 let info = parse_into_minimal_nodes(FIXTURE).expect("parse xml");
1227 assert_eq!(info.schema_version.as_deref(), Some("1.2.3"));
1228 assert_eq!(info.top_level_features.len(), 7);
1229 assert_eq!(info.top_level_features[0], "Root");
1230 }
1231
1232 #[test]
1233 fn parse_fixture_model() {
1234 let model = parse(FIXTURE).expect("parse fixture");
1235 assert_eq!(model.version, "1.2.3");
1236 assert_eq!(model.nodes.len(), 7);
1237 match &model.nodes[0] {
1238 NodeDecl::Category { name, children, .. } => {
1239 assert_eq!(name, "Root");
1240 assert_eq!(
1241 children,
1242 &vec!["Gain".to_string(), "GainSelector".to_string()]
1243 );
1244 }
1245 other => panic!("unexpected node: {other:?}"),
1246 }
1247 match &model.nodes[1] {
1248 NodeDecl::Integer {
1249 name,
1250 min,
1251 max,
1252 inc,
1253 ..
1254 } => {
1255 assert_eq!(name, "Width");
1256 assert_eq!(*min, 16);
1257 assert_eq!(*max, 4096);
1258 assert_eq!(*inc, Some(2));
1259 }
1260 other => panic!("unexpected node: {other:?}"),
1261 }
1262 match &model.nodes[2] {
1263 NodeDecl::Float {
1264 name,
1265 scale,
1266 offset,
1267 ..
1268 } => {
1269 assert_eq!(name, "ExposureTime");
1270 assert_eq!(*scale, Some((1, 1000)));
1271 assert_eq!(*offset, Some(0.0));
1272 }
1273 other => panic!("unexpected node: {other:?}"),
1274 }
1275 match &model.nodes[3] {
1276 NodeDecl::Enum { name, entries, .. } => {
1277 assert_eq!(name, "GainSelector");
1278 assert_eq!(entries.len(), 2);
1279 assert!(matches!(entries[0].value, EnumValueSrc::Literal(0)));
1280 assert!(matches!(entries[1].value, EnumValueSrc::Literal(1)));
1281 }
1282 other => panic!("unexpected node: {other:?}"),
1283 }
1284 match &model.nodes[4] {
1285 NodeDecl::Integer {
1286 name, selected_if, ..
1287 } => {
1288 assert_eq!(name, "Gain");
1289 assert_eq!(selected_if.len(), 1);
1290 assert_eq!(selected_if[0].0, "GainSelector");
1291 assert_eq!(selected_if[0].1, vec!["AnalogAll".to_string()]);
1292 }
1293 other => panic!("unexpected node: {other:?}"),
1294 }
1295 }
1296
1297 #[test]
1298 fn parse_swissknife_node() {
1299 const XML: &str = r#"
1300 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1301 <Integer Name="GainRaw">
1302 <Address>0x3000</Address>
1303 <Length>4</Length>
1304 <AccessMode>RW</AccessMode>
1305 <Min>0</Min>
1306 <Max>1000</Max>
1307 </Integer>
1308 <Float Name="Offset">
1309 <Address>0x3008</Address>
1310 <Length>4</Length>
1311 <AccessMode>RW</AccessMode>
1312 <Min>-100.0</Min>
1313 <Max>100.0</Max>
1314 </Float>
1315 <SwissKnife Name="ComputedGain">
1316 <Expression>(GainRaw * 0.5) + Offset</Expression>
1317 <pVariable Name="GainRaw">GainRaw</pVariable>
1318 <pVariable Name="Offset">Offset</pVariable>
1319 <Output>Float</Output>
1320 </SwissKnife>
1321 </RegisterDescription>
1322 "#;
1323
1324 let model = parse(XML).expect("parse swissknife xml");
1325 assert_eq!(model.nodes.len(), 3);
1326 let swiss = model
1327 .nodes
1328 .iter()
1329 .find_map(|decl| match decl {
1330 NodeDecl::SwissKnife(node) => Some(node),
1331 _ => None,
1332 })
1333 .expect("swissknife present");
1334 assert_eq!(swiss.name, "ComputedGain");
1335 assert_eq!(swiss.expr, "(GainRaw * 0.5) + Offset");
1336 assert_eq!(swiss.output, SkOutput::Float);
1337 assert_eq!(swiss.variables.len(), 2);
1338 assert_eq!(
1339 swiss.variables[0],
1340 ("GainRaw".to_string(), "GainRaw".to_string())
1341 );
1342 assert_eq!(
1343 swiss.variables[1],
1344 ("Offset".to_string(), "Offset".to_string())
1345 );
1346 }
1347
1348 #[test]
1349 fn parse_int_swissknife_with_hex_and_ampersand() {
1350 const XML: &str = r#"
1352 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1353 <IntSwissKnife Name="PayloadSize">
1354 <pVariable Name="W">Width</pVariable>
1355 <pVariable Name="H">Height</pVariable>
1356 <pVariable Name="PF">PixelFormat</pVariable>
1357 <Formula>W * H * ((PF>>16)&0xFF) / 8</Formula>
1358 </IntSwissKnife>
1359 </RegisterDescription>
1360 "#;
1361
1362 let model = parse(XML).expect("parse intswissknife");
1363 assert_eq!(model.nodes.len(), 1);
1364 let swiss = model
1365 .nodes
1366 .iter()
1367 .find_map(|decl| match decl {
1368 NodeDecl::SwissKnife(node) => Some(node),
1369 _ => None,
1370 })
1371 .expect("swissknife present");
1372 assert_eq!(swiss.name, "PayloadSize");
1373 assert!(
1375 swiss.expr.contains('&'),
1376 "expression should contain decoded '&': {}",
1377 swiss.expr
1378 );
1379 assert!(
1380 swiss.expr.contains("0xFF"),
1381 "expression should contain hex literal: {}",
1382 swiss.expr
1383 );
1384 }
1385
1386 #[test]
1387 fn parse_enum_entry_with_pvalue() {
1388 const XML: &str = r#"
1389 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1390 <Enumeration Name="Mode">
1391 <Address>0x0000_4000</Address>
1392 <Length>4</Length>
1393 <AccessMode>RW</AccessMode>
1394 <EnumEntry Name="Fixed10">
1395 <Value>10</Value>
1396 </EnumEntry>
1397 <EnumEntry Name="DynFromReg">
1398 <pValue>RegModeVal</pValue>
1399 </EnumEntry>
1400 </Enumeration>
1401 <Integer Name="RegModeVal">
1402 <Address>0x0000_4100</Address>
1403 <Length>4</Length>
1404 <AccessMode>RW</AccessMode>
1405 <Min>0</Min>
1406 <Max>65535</Max>
1407 </Integer>
1408 </RegisterDescription>
1409 "#;
1410
1411 let model = parse(XML).expect("parse enum pvalue");
1412 assert_eq!(model.nodes.len(), 2);
1413 match &model.nodes[0] {
1414 NodeDecl::Enum { entries, .. } => {
1415 assert_eq!(entries.len(), 2);
1416 assert!(matches!(entries[0].value, EnumValueSrc::Literal(10)));
1417 match &entries[1].value {
1418 EnumValueSrc::FromNode(node) => assert_eq!(node, "RegModeVal"),
1419 other => panic!("unexpected entry value: {other:?}"),
1420 }
1421 }
1422 other => panic!("unexpected node: {other:?}"),
1423 }
1424 }
1425
1426 #[test]
1433 fn address_terms_are_summed() {
1434 const XML: &str = r#"
1435 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1436 <Integer Name="RegAddr">
1437 <Address>0x2000</Address>
1438 <Length>4</Length>
1439 <AccessMode>RW</AccessMode>
1440 <Min>0</Min>
1441 <Max>65535</Max>
1442 </Integer>
1443 <Integer Name="Gain">
1444 <pAddress>RegAddr</pAddress>
1445 <Address>0x00000008</Address>
1446 <Length>4</Length>
1447 <AccessMode>RW</AccessMode>
1448 <Min>0</Min>
1449 <Max>255</Max>
1450 </Integer>
1451 </RegisterDescription>
1452 "#;
1453
1454 let model = parse(XML).expect("parse indirect xml");
1455 assert_eq!(model.nodes.len(), 2);
1456 match &model.nodes[0] {
1457 NodeDecl::Integer {
1458 name, addressing, ..
1459 } => {
1460 assert_eq!(name, "RegAddr");
1461 assert_eq!(addressing.as_ref(), Some(&Addressing::fixed(0x2000, 4)));
1462 }
1463 other => panic!("unexpected node: {other:?}"),
1464 }
1465 match &model.nodes[1] {
1466 NodeDecl::Integer {
1467 name, addressing, ..
1468 } => {
1469 assert_eq!(name, "Gain");
1470 match addressing {
1471 Some(Addressing::Sum { terms, len }) => {
1472 assert_eq!(
1473 terms,
1474 &[AddressTerm::Node("RegAddr".into()), AddressTerm::Fixed(0x8),]
1475 );
1476 assert_eq!(*len, 4);
1477 }
1478 other => panic!("expected summed addressing, got {other:?}"),
1479 }
1480 }
1481 other => panic!("unexpected node: {other:?}"),
1482 }
1483 }
1484
1485 #[test]
1487 fn parse_p_index_term() {
1488 const XML: &str = r#"
1489 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1490 <MaskedIntReg Name="RegTriggerInqDelay">
1491 <Address>0x13400</Address>
1492 <pIndex Offset="64">IntTriggerSelector</pIndex>
1493 <Length>4</Length>
1494 <AccessMode>RO</AccessMode>
1495 <Bit>30</Bit>
1496 </MaskedIntReg>
1497 </RegisterDescription>
1498 "#;
1499
1500 let model = parse(XML).expect("parse pIndex xml");
1501 match &model.nodes[0] {
1502 NodeDecl::Integer { addressing, .. } => match addressing {
1503 Some(Addressing::Sum { terms, .. }) => assert_eq!(
1504 terms,
1505 &[
1506 AddressTerm::Fixed(0x13400),
1507 AddressTerm::Index {
1508 node: "IntTriggerSelector".into(),
1509 offset: IndexOffset::Fixed(64),
1510 },
1511 ]
1512 ),
1513 other => panic!("expected summed addressing, got {other:?}"),
1514 },
1515 other => panic!("unexpected node: {other:?}"),
1516 }
1517 }
1518
1519 #[test]
1525 fn struct_reg_inherits_all_address_terms() {
1526 const XML: &str = r#"
1527 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1528 <StructReg Comment="Gain Inquiry Register">
1529 <Address>0x520</Address>
1530 <pAddress>CamRegBaseAddress</pAddress>
1531 <Length>4</Length>
1532 <AccessMode>RO</AccessMode>
1533 <Endianess>BigEndian</Endianess>
1534 <StructEntry Name="GainPresInq_Bit"><Bit>0</Bit></StructEntry>
1535 <StructEntry Name="GainAutoInq_Bit"><Bit>6</Bit></StructEntry>
1536 </StructReg>
1537 </RegisterDescription>
1538 "#;
1539
1540 let model = parse(XML).expect("parse struct reg");
1541 assert_eq!(model.nodes.len(), 2);
1542 for node in &model.nodes {
1543 match node {
1544 NodeDecl::Integer { addressing, .. } => match addressing {
1545 Some(Addressing::Sum { terms, len }) => {
1546 assert_eq!(
1547 terms,
1548 &[
1549 AddressTerm::Fixed(0x520),
1550 AddressTerm::Node("CamRegBaseAddress".into()),
1551 ]
1552 );
1553 assert_eq!(*len, 4);
1554 }
1555 other => panic!("expected summed addressing, got {other:?}"),
1556 },
1557 other => panic!("unexpected node: {other:?}"),
1558 }
1559 }
1560 }
1561
1562 #[test]
1565 fn struct_reg_without_address_is_skipped() {
1566 const XML: &str = r#"
1567 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1568 <StructReg>
1569 <Length>4</Length>
1570 <AccessMode>RO</AccessMode>
1571 <StructEntry Name="SomeInq_Bit"><Bit>0</Bit></StructEntry>
1572 </StructReg>
1573 </RegisterDescription>
1574 "#;
1575
1576 let model = parse(XML).expect("document still parses");
1577 assert!(model.nodes.is_empty());
1578 assert_eq!(model.skipped.len(), 1);
1579 assert!(model.skipped[0].error.contains("Address"));
1580 }
1581
1582 #[test]
1583 fn parse_indirect_float_is_scaled_integer() {
1584 const XML: &str = r#"
1590 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1591 <Integer Name="RegAddr">
1592 <Address>0x2000</Address>
1593 <Length>4</Length>
1594 <AccessMode>RW</AccessMode>
1595 </Integer>
1596 <Float Name="Exposure">
1597 <pAddress>RegAddr</pAddress>
1598 <Length>4</Length>
1599 <AccessMode>RW</AccessMode>
1600 </Float>
1601 </RegisterDescription>
1602 "#;
1603
1604 let model = parse(XML).expect("parse indirect float");
1605 let float = model
1606 .nodes
1607 .iter()
1608 .find(|n| matches!(n, NodeDecl::Float { name, .. } if name == "Exposure"))
1609 .expect("Exposure node");
1610 match float {
1611 NodeDecl::Float {
1612 encoding,
1613 addressing,
1614 ..
1615 } => {
1616 assert!(
1617 matches!(
1618 addressing,
1619 Some(Addressing::Sum { terms, .. })
1620 if terms.iter().any(|t| matches!(t, AddressTerm::Node(_)))
1621 ),
1622 "expected a pAddress term"
1623 );
1624 assert_eq!(*encoding, FloatEncoding::ScaledInteger);
1625 }
1626 _ => unreachable!(),
1627 }
1628 }
1629
1630 #[test]
1646 fn parse_integer_bitfield_big_endian() {
1647 const XML: &str = r#"
1648 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1649 <Integer Name="RegSCPSPacketSize">
1650 <Address>0xD04</Address>
1651 <Length>4</Length>
1652 <AccessMode>RW</AccessMode>
1653 <LSB>31</LSB>
1654 <MSB>16</MSB>
1655 <Endianess>BigEndian</Endianess>
1656 </Integer>
1657 </RegisterDescription>
1658 "#;
1659
1660 let model = parse(XML).expect("parse big-endian bitfield");
1661 assert_eq!(model.nodes.len(), 1);
1662 match &model.nodes[0] {
1663 NodeDecl::Integer { len, bitfield, .. } => {
1664 assert_eq!(*len, 4);
1665 let field = bitfield.as_ref().expect("bitfield present");
1666 assert_eq!(field.byte_order, ByteOrder::Big);
1667 assert_eq!(field.bit_length, 16);
1668 assert_eq!(field.bit_offset, 16);
1669 }
1670 other => panic!("unexpected node: {other:?}"),
1671 }
1672 }
1673
1674 #[test]
1681 fn parse_big_endian_single_bit_is_counted_from_the_msb() {
1682 const XML: &str = r#"
1683 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1684 <Integer Name="ExposureTime_Imp">
1685 <Address>0x000C1000</Address>
1686 <Length>4</Length>
1687 <AccessMode>RO</AccessMode>
1688 <Bit>0</Bit>
1689 <Endianess>BigEndian</Endianess>
1690 </Integer>
1691 </RegisterDescription>
1692 "#;
1693
1694 let model = parse(XML).expect("parse big-endian single bit");
1695 match &model.nodes[0] {
1696 NodeDecl::Integer { bitfield, .. } => {
1697 let field = bitfield.as_ref().expect("bitfield present");
1698 assert_eq!(field.bit_length, 1);
1699 assert_eq!(field.bit_offset, 0);
1702 }
1703 other => panic!("unexpected node: {other:?}"),
1704 }
1705 }
1706
1707 #[test]
1717 fn both_spellings_of_lsb_and_msb_are_accepted() {
1718 fn bitfield_of(lsb_tag: &str, msb_tag: &str) -> BitField {
1719 let xml = format!(
1720 r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">
1721 <MaskedIntReg Name="Field">
1722 <Address>0xD04</Address><Length>4</Length><AccessMode>RW</AccessMode>
1723 <{lsb_tag}>31</{lsb_tag}><{msb_tag}>16</{msb_tag}>
1724 <Endianess>BigEndian</Endianess>
1725 </MaskedIntReg>
1726 </RegisterDescription>"#
1727 );
1728 let model = parse(&xml).expect("parse");
1729 match &model.nodes[0] {
1730 NodeDecl::Integer { bitfield, .. } => *bitfield.as_ref().expect("bitfield present"),
1731 other => panic!("unexpected node: {other:?}"),
1732 }
1733 }
1734
1735 let schema = bitfield_of("LSB", "MSB");
1736 let mixed = bitfield_of("Lsb", "Msb");
1737 assert_eq!(schema.bit_offset, 16);
1738 assert_eq!(schema.bit_length, 16);
1739 assert_eq!(schema, mixed);
1740 }
1741
1742 #[test]
1755 fn boolean_accepts_both_spellings_of_lsb_and_msb() {
1756 fn flag_bitfield(lsb_tag: &str, msb_tag: &str) -> BitField {
1757 let xml = format!(
1758 r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">
1759 <Boolean Name="Flag">
1760 <Address>0x2000</Address><Length>4</Length><AccessMode>RW</AccessMode>
1761 <{lsb_tag}>31</{lsb_tag}><{msb_tag}>31</{msb_tag}>
1762 <Endianess>BigEndian</Endianess>
1763 </Boolean>
1764 </RegisterDescription>"#
1765 );
1766 let model = parse(&xml).expect("parse");
1767 assert!(model.skipped.is_empty(), "skipped: {:?}", model.skipped);
1768 match &model.nodes[0] {
1769 NodeDecl::Boolean { bitfield, .. } => *bitfield.as_ref().expect("bitfield present"),
1770 other => panic!("unexpected node: {other:?}"),
1771 }
1772 }
1773
1774 let schema = flag_bitfield("LSB", "MSB");
1775 let mixed = flag_bitfield("Lsb", "Msb");
1776 assert_eq!(schema.bit_offset, 31);
1779 assert_eq!(schema.bit_length, 1);
1780 assert_eq!(schema, mixed);
1781 }
1782
1783 #[test]
1790 fn big_endian_mask_stays_lsb_relative() {
1791 const XML: &str = r#"
1792 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1793 <Integer Name="Masked">
1794 <Address>0x3000</Address>
1795 <Length>4</Length>
1796 <AccessMode>RW</AccessMode>
1797 <Mask>0x0000FF00</Mask>
1798 <Endianess>BigEndian</Endianess>
1799 </Integer>
1800 </RegisterDescription>
1801 "#;
1802
1803 let model = parse(XML).expect("parse big-endian mask");
1804 match &model.nodes[0] {
1805 NodeDecl::Integer { bitfield, .. } => {
1806 let field = bitfield.as_ref().expect("bitfield present");
1807 assert_eq!(field.byte_order, ByteOrder::Big);
1808 assert_eq!(field.bit_length, 8);
1809 assert_eq!(field.bit_offset, 16);
1811 }
1812 other => panic!("unexpected node: {other:?}"),
1813 }
1814 }
1815 #[test]
1816 fn parse_boolean_bitfield_default_length() {
1817 const XML: &str = r#"
1818 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1819 <Boolean Name="Flag">
1820 <Address>0x2000</Address>
1821 <Length>1</Length>
1822 <AccessMode>RW</AccessMode>
1823 <Bit>3</Bit>
1824 </Boolean>
1825 </RegisterDescription>
1826 "#;
1827
1828 let model = parse(XML).expect("parse boolean bitfield");
1829 assert_eq!(model.nodes.len(), 1);
1830 match &model.nodes[0] {
1831 NodeDecl::Boolean { len, bitfield, .. } => {
1832 assert_eq!(*len, 1);
1833 let bf = bitfield.as_ref().expect("bitfield present");
1834 assert_eq!(bf.byte_order, ByteOrder::Little);
1835 assert_eq!(bf.bit_length, 1);
1836 assert_eq!(bf.bit_offset, 3);
1837 }
1838 other => panic!("unexpected node: {other:?}"),
1839 }
1840 }
1841
1842 #[test]
1848 fn plain_integer_records_its_declared_byte_order() {
1849 for tag in ["Endianess", "Endianness", "ByteOrder"] {
1850 let xml = format!(
1851 r#"
1852 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1853 <IntReg Name="Reg">
1854 <Address>0x3000</Address>
1855 <Length>4</Length>
1856 <AccessMode>RO</AccessMode>
1857 <{tag}>LittleEndian</{tag}>
1858 </IntReg>
1859 </RegisterDescription>
1860 "#
1861 );
1862
1863 let model = parse(&xml).unwrap_or_else(|err| panic!("parse <{tag}>: {err}"));
1864 match &model.nodes[0] {
1865 NodeDecl::Integer {
1866 byte_order,
1867 bitfield,
1868 ..
1869 } => {
1870 assert_eq!(*byte_order, ByteOrder::Little, "<{tag}>");
1871 assert!(bitfield.is_none(), "<{tag}>");
1874 }
1875 other => panic!("unexpected node: {other:?}"),
1876 }
1877 }
1878 }
1879
1880 #[test]
1883 fn plain_integer_without_endianness_defaults_to_big() {
1884 const XML: &str = r#"
1885 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1886 <IntReg Name="Reg">
1887 <Address>0x3000</Address>
1888 <Length>4</Length>
1889 <AccessMode>RO</AccessMode>
1890 </IntReg>
1891 </RegisterDescription>
1892 "#;
1893
1894 let model = parse(XML).expect("parse");
1895 match &model.nodes[0] {
1896 NodeDecl::Integer { byte_order, .. } => assert_eq!(*byte_order, ByteOrder::Big),
1897 other => panic!("unexpected node: {other:?}"),
1898 }
1899 }
1900
1901 #[test]
1906 fn masked_integer_keeps_the_bitfield_byte_order_path() {
1907 const XML: &str = r#"
1908 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1909 <IntReg Name="Reg">
1910 <Address>0x3000</Address>
1911 <Length>4</Length>
1912 <AccessMode>RO</AccessMode>
1913 <Endianess>LittleEndian</Endianess>
1914 <LSB>15</LSB>
1915 <MSB>0</MSB>
1916 </IntReg>
1917 </RegisterDescription>
1918 "#;
1919
1920 let model = parse(XML).expect("parse");
1921 match &model.nodes[0] {
1922 NodeDecl::Integer {
1923 byte_order,
1924 bitfield,
1925 ..
1926 } => {
1927 assert_eq!(*byte_order, ByteOrder::Little);
1928 let field = bitfield.as_ref().expect("bitfield present");
1929 assert_eq!(field.byte_order, ByteOrder::Little);
1930 }
1931 other => panic!("unexpected node: {other:?}"),
1932 }
1933 }
1934
1935 #[test]
1936 fn parse_integer_bitfield_mask() {
1937 const XML: &str = r#"
1938 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1939 <Integer Name="Masked">
1940 <Address>0x3000</Address>
1941 <Length>4</Length>
1942 <AccessMode>RW</AccessMode>
1943 <Min>0</Min>
1944 <Max>65535</Max>
1945 <Mask>0x0000FF00</Mask>
1946 </Integer>
1947 </RegisterDescription>
1948 "#;
1949
1950 let model = parse(XML).expect("parse mask bitfield");
1951 assert_eq!(model.nodes.len(), 1);
1952 match &model.nodes[0] {
1953 NodeDecl::Integer { bitfield, .. } => {
1954 let field = bitfield.as_ref().expect("bitfield present");
1955 assert_eq!(field.byte_order, ByteOrder::Little);
1956 assert_eq!(field.bit_length, 8);
1957 assert_eq!(field.bit_offset, 8);
1958 }
1959 other => panic!("unexpected node: {other:?}"),
1960 }
1961 }
1962
1963 #[test]
1964 fn parse_node_metadata() {
1965 const XML: &str = r#"
1966 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1967 <Integer Name="Width">
1968 <Address>0x100</Address>
1969 <Length>4</Length>
1970 <AccessMode>RW</AccessMode>
1971 <Min>16</Min>
1972 <Max>4096</Max>
1973 <Visibility>Expert</Visibility>
1974 <Description>Image width in pixels.</Description>
1975 <ToolTip>Width of the acquired image</ToolTip>
1976 <DisplayName>Image Width</DisplayName>
1977 <Representation>Linear</Representation>
1978 </Integer>
1979 <Float Name="Gain">
1980 <Address>0x200</Address>
1981 <Length>4</Length>
1982 <AccessMode>RW</AccessMode>
1983 <Min>0.0</Min>
1984 <Max>48.0</Max>
1985 <Unit>dB</Unit>
1986 <Visibility>Beginner</Visibility>
1987 <Representation>Logarithmic</Representation>
1988 </Float>
1989 <Category Name="Root">
1990 <Visibility>Guru</Visibility>
1991 <Description>Top-level category</Description>
1992 <pFeature>Width</pFeature>
1993 <pFeature>Gain</pFeature>
1994 </Category>
1995 <Enumeration Name="PixelFormat">
1996 <Address>0x300</Address>
1997 <Length>4</Length>
1998 <AccessMode>RW</AccessMode>
1999 <Visibility>Beginner</Visibility>
2000 <ToolTip>Pixel format selector</ToolTip>
2001 <EnumEntry Name="Mono8" Value="0" />
2002 </Enumeration>
2003 </RegisterDescription>
2004 "#;
2005
2006 let model = parse(XML).expect("parse metadata xml");
2007 assert_eq!(model.nodes.len(), 4);
2008
2009 match &model.nodes[0] {
2011 NodeDecl::Integer { name, meta, .. } => {
2012 assert_eq!(name, "Width");
2013 assert_eq!(meta.visibility, Visibility::Expert);
2014 assert_eq!(meta.description.as_deref(), Some("Image width in pixels."));
2015 assert_eq!(meta.tooltip.as_deref(), Some("Width of the acquired image"));
2016 assert_eq!(meta.display_name.as_deref(), Some("Image Width"));
2017 assert_eq!(meta.representation, Some(Representation::Linear));
2018 }
2019 other => panic!("unexpected node: {other:?}"),
2020 }
2021
2022 match &model.nodes[1] {
2024 NodeDecl::Float { name, meta, .. } => {
2025 assert_eq!(name, "Gain");
2026 assert_eq!(meta.visibility, Visibility::Beginner);
2027 assert_eq!(meta.representation, Some(Representation::Logarithmic));
2028 assert!(meta.description.is_none());
2029 }
2030 other => panic!("unexpected node: {other:?}"),
2031 }
2032
2033 match &model.nodes[2] {
2035 NodeDecl::Category { name, meta, .. } => {
2036 assert_eq!(name, "Root");
2037 assert_eq!(meta.visibility, Visibility::Guru);
2038 assert_eq!(meta.description.as_deref(), Some("Top-level category"));
2039 }
2040 other => panic!("unexpected node: {other:?}"),
2041 }
2042
2043 match &model.nodes[3] {
2045 NodeDecl::Enum { name, meta, .. } => {
2046 assert_eq!(name, "PixelFormat");
2047 assert_eq!(meta.visibility, Visibility::Beginner);
2048 assert_eq!(meta.tooltip.as_deref(), Some("Pixel format selector"));
2049 }
2050 other => panic!("unexpected node: {other:?}"),
2051 }
2052 }
2053
2054 fn parse_fragment(node: &str) -> XmlModel {
2056 let xml = format!(
2057 r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="0">{node}</RegisterDescription>"#
2058 );
2059 parse(&xml).expect("parse fragment")
2060 }
2061
2062 fn only_meta(model: &XmlModel) -> &NodeMeta {
2064 match model.nodes.first().expect("one node") {
2065 NodeDecl::Integer { meta, .. } => meta,
2066 other => panic!("unexpected node: {other:?}"),
2067 }
2068 }
2069
2070 #[test]
2080 fn byte_order_mark_does_not_shift_node_slices() {
2081 let body = r#"<Integer Name="Width"><Address>0x100</Address><Length>4</Length></Integer><Integer Name="Height"><Address>0x104</Address><Length>4</Length></Integer>"#;
2082 let doc = format!(
2083 r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1">{body}</RegisterDescription>"#
2084 );
2085 let with_bom = format!("\u{feff}{doc}");
2086
2087 let plain = parse(&doc).expect("parse without BOM");
2088 let bom = parse(&with_bom).expect("parse with BOM");
2089
2090 assert!(
2091 bom.skipped.is_empty(),
2092 "BOM document skipped nodes: {:?}",
2093 bom.skipped
2094 );
2095 assert_eq!(bom.nodes.len(), plain.nodes.len());
2096 assert_eq!(bom.version, plain.version);
2097 }
2098
2099 #[test]
2104 fn byte_order_mark_does_not_disturb_the_minimal_scan() {
2105 let doc = r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1"><Category Name="Root"><pFeature>Width</pFeature></Category></RegisterDescription>"#;
2106 let with_bom = format!("\u{feff}{doc}");
2107
2108 let info = parse_into_minimal_nodes(&with_bom).expect("minimal scan with BOM");
2109 assert_eq!(info.schema_version.as_deref(), Some("1.1.1"));
2110 assert_eq!(info.top_level_features, vec!["Root".to_string()]);
2111 }
2112
2113 #[test]
2123 fn byte_order_mark_before_multibyte_text_keeps_the_text_intact() {
2124 let doc = r#"<RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="1" SchemaSubMinorVersion="1"><Integer Name="Gain"><Address>0x100</Address><Length>4</Length><ToolTip>Verstärkung in dB — Meßwert</ToolTip></Integer></RegisterDescription>"#;
2125 let with_bom = format!("\u{feff}{doc}");
2126
2127 let model = parse(&with_bom).expect("parse with BOM and multi-byte text");
2128 assert!(model.skipped.is_empty(), "skipped: {:?}", model.skipped);
2129 assert_eq!(
2130 only_meta(&model).tooltip.as_deref(),
2131 Some("Verstärkung in dB — Meßwert")
2132 );
2133 }
2134
2135 #[test]
2139 fn cdata_text_is_taken_literally() {
2140 let model = parse_fragment(
2141 r#"<Integer Name="Gain">
2142 <Address>0x100</Address>
2143 <ToolTip><![CDATA[Gain in dB & raw units, 0 < x < 10]]></ToolTip>
2144 </Integer>"#,
2145 );
2146 assert_eq!(
2147 only_meta(&model).tooltip.as_deref(),
2148 Some("Gain in dB & raw units, 0 < x < 10")
2149 );
2150 }
2151
2152 #[test]
2155 fn dangling_ampersand_is_kept_verbatim() {
2156 let model = parse_fragment(
2157 r#"<Integer Name="Gain">
2158 <Address>0x100</Address>
2159 <ToolTip>Exposure & gain</ToolTip>
2160 </Integer>"#,
2161 );
2162 assert_eq!(
2163 only_meta(&model).tooltip.as_deref(),
2164 Some("Exposure & gain")
2165 );
2166 }
2167
2168 #[test]
2171 fn comments_inside_text_elements_are_dropped() {
2172 let model = parse_fragment(
2173 r#"<Integer Name="Gain">
2174 <Address>0x100</Address>
2175 <Description>Analog <!-- R&D note --> gain</Description>
2176 </Integer>"#,
2177 );
2178 assert_eq!(
2179 only_meta(&model).description.as_deref(),
2180 Some("Analog gain")
2181 );
2182 }
2183
2184 #[test]
2187 fn entity_references_resolve_and_preserve_spacing() {
2188 let model = parse_fragment(
2189 r#"<Integer Name="Gain">
2190 <Address>0x100</Address>
2191 <ToolTip>A & B < C > D "E" 'F'</ToolTip>
2192 </Integer>"#,
2193 );
2194 assert_eq!(
2195 only_meta(&model).tooltip.as_deref(),
2196 Some(r#"A & B < C > D "E" 'F'"#)
2197 );
2198 }
2199
2200 #[test]
2202 fn character_references_resolve() {
2203 let model = parse_fragment(
2204 r#"<Integer Name="Gain">
2205 <Address>0x100</Address>
2206 <ToolTip>— dash —</ToolTip>
2207 </Integer>"#,
2208 );
2209 assert_eq!(only_meta(&model).tooltip.as_deref(), Some("— dash —"));
2210 }
2211
2212 #[test]
2215 fn unknown_entity_is_kept_as_written() {
2216 let model = parse_fragment(
2217 r#"<Integer Name="Gain">
2218 <Address>0x100</Address>
2219 <ToolTip>copyright © vendor</ToolTip>
2220 </Integer>"#,
2221 );
2222 assert_eq!(
2223 only_meta(&model).tooltip.as_deref(),
2224 Some("copyright © vendor")
2225 );
2226 }
2227
2228 #[test]
2231 fn swissknife_formula_entities_resolve_to_operators() {
2232 let model = parse_fragment(
2233 r#"<IntSwissKnife Name="GainMask">
2234 <pVariable Name="RAW">Gain</pVariable>
2235 <Formula>(RAW & 0xFF) < 16</Formula>
2236 </IntSwissKnife>"#,
2237 );
2238 match model.nodes.first().expect("one node") {
2239 NodeDecl::SwissKnife(node) => assert_eq!(node.expr, "(RAW & 0xFF) < 16"),
2240 other => panic!("unexpected node: {other:?}"),
2241 }
2242 }
2243
2244 #[test]
2248 fn swissknife_without_variables_is_accepted() {
2249 let model = parse_fragment(
2250 r#"<IntSwissKnife Name="PixelDynamicRangeMin_Value">
2251 <Formula>0x1234</Formula>
2252 </IntSwissKnife>"#,
2253 );
2254 match model.nodes.first().expect("one node") {
2255 NodeDecl::SwissKnife(node) => {
2256 assert_eq!(node.expr, "0x1234");
2257 assert!(node.variables.is_empty());
2258 }
2259 other => panic!("unexpected node: {other:?}"),
2260 }
2261 }
2262
2263 #[test]
2266 fn access_mode_accepts_aliases_and_defaults_unknown_to_rw() {
2267 assert_eq!(AccessMode::parse("R").unwrap(), AccessMode::RO);
2268 assert_eq!(AccessMode::parse("W").unwrap(), AccessMode::WO);
2269 assert_eq!(AccessMode::parse("WR").unwrap(), AccessMode::RW);
2270 assert_eq!(AccessMode::parse(" ro ").unwrap(), AccessMode::RO);
2271 assert_eq!(AccessMode::parse("Bogus").unwrap(), AccessMode::RW);
2272 }
2273
2274 #[test]
2277 fn unparsable_node_is_skipped_not_fatal() {
2278 let model = parse_fragment(
2279 r#"<Integer Name="Before">
2280 <Address>0x100</Address>
2281 <Length>4</Length>
2282 </Integer>
2283 <Integer Name="Broken">
2284 <Address>0xZZZZ</Address>
2285 <Length>4</Length>
2286 </Integer>
2287 <Integer Name="After">
2288 <Address>0x200</Address>
2289 <Length>4</Length>
2290 </Integer>"#,
2291 );
2292
2293 let names: Vec<&str> = model
2294 .nodes
2295 .iter()
2296 .map(|node| match node {
2297 NodeDecl::Integer { name, .. } => name.as_str(),
2298 other => panic!("unexpected node: {other:?}"),
2299 })
2300 .collect();
2301 assert_eq!(names, ["Before", "After"]);
2302
2303 assert_eq!(model.skipped.len(), 1);
2304 let skipped = &model.skipped[0];
2305 assert_eq!(skipped.tag, "Integer");
2306 assert_eq!(skipped.name.as_deref(), Some("Broken"));
2307 assert!(
2308 skipped.error.contains("invalid hex"),
2309 "unexpected error: {}",
2310 skipped.error
2311 );
2312 }
2313
2314 #[test]
2318 fn node_missing_required_name_is_skipped() {
2319 let model = parse_fragment(
2320 r#"<Integer>
2321 <Address>0x100</Address>
2322 </Integer>
2323 <Integer Name="Good">
2324 <Address>0x200</Address>
2325 <Length>4</Length>
2326 </Integer>"#,
2327 );
2328 assert_eq!(model.nodes.len(), 1);
2329 assert_eq!(model.skipped.len(), 1);
2330 assert_eq!(model.skipped[0].name, None);
2331 }
2332
2333 #[test]
2335 fn clean_document_skips_nothing() {
2336 let model = parse(FIXTURE).expect("parse fixture");
2337 assert!(model.skipped.is_empty());
2338 }
2339}