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 serde::{Deserialize, Serialize};
19use thiserror::Error;
20
21use parsers::{
22 parse_boolean, parse_category, parse_category_empty, parse_command, parse_command_empty,
23 parse_converter, parse_enum, parse_float, parse_int_converter, parse_integer, parse_string,
24 parse_struct_reg, parse_swissknife,
25};
26use util::{attribute_value, skip_element};
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub enum EnumValueSrc {
31 Literal(i64),
33 FromNode(String),
35}
36
37#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
49pub struct PredicateRefs {
50 #[serde(
52 default,
53 skip_serializing_if = "Option::is_none",
54 rename = "pIsImplemented"
55 )]
56 pub p_is_implemented: Option<String>,
57 #[serde(
59 default,
60 skip_serializing_if = "Option::is_none",
61 rename = "pIsAvailable"
62 )]
63 pub p_is_available: Option<String>,
64 #[serde(default, skip_serializing_if = "Option::is_none", rename = "pIsLocked")]
66 pub p_is_locked: Option<String>,
67}
68
69impl PredicateRefs {
70 pub fn references(&self) -> impl Iterator<Item = &str> {
72 [
73 self.p_is_implemented.as_deref(),
74 self.p_is_available.as_deref(),
75 self.p_is_locked.as_deref(),
76 ]
77 .into_iter()
78 .flatten()
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.p_is_implemented.is_none()
84 && self.p_is_available.is_none()
85 && self.p_is_locked.is_none()
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct EnumEntryDecl {
92 pub name: String,
94 pub value: EnumValueSrc,
96 pub display_name: Option<String>,
98 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
100 pub predicates: PredicateRefs,
101}
102
103#[derive(Debug, Error)]
104#[non_exhaustive]
105pub enum XmlError {
106 #[error("xml: {0}")]
107 Xml(String),
108 #[error("invalid descriptor: {0}")]
109 Invalid(String),
110 #[error("transport: {0}")]
111 Transport(String),
112 #[error("unsupported URL: {0}")]
113 Unsupported(String),
114}
115
116#[derive(
121 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
122)]
123#[non_exhaustive]
124pub enum Visibility {
125 #[default]
127 Beginner,
128 Expert,
130 Guru,
132 Invisible,
134}
135
136impl Visibility {
137 pub(crate) fn parse(s: &str) -> Option<Self> {
138 match s.trim() {
139 "Beginner" => Some(Self::Beginner),
140 "Expert" => Some(Self::Expert),
141 "Guru" => Some(Self::Guru),
142 "Invisible" => Some(Self::Invisible),
143 _ => None,
144 }
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
150#[non_exhaustive]
151pub enum Representation {
152 Linear,
153 Logarithmic,
154 Boolean,
155 PureNumber,
156 HexNumber,
157 IPV4Address,
159 MACAddress,
161}
162
163impl Representation {
164 pub(crate) fn parse(s: &str) -> Option<Self> {
165 match s.trim() {
166 "Linear" => Some(Self::Linear),
167 "Logarithmic" => Some(Self::Logarithmic),
168 "Boolean" => Some(Self::Boolean),
169 "PureNumber" => Some(Self::PureNumber),
170 "HexNumber" => Some(Self::HexNumber),
171 "IPV4Address" => Some(Self::IPV4Address),
172 "MACAddress" => Some(Self::MACAddress),
173 _ => None,
174 }
175 }
176}
177
178#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
180pub struct NodeMeta {
181 pub visibility: Visibility,
183 pub description: Option<String>,
185 pub tooltip: Option<String>,
187 pub display_name: Option<String>,
189 pub representation: Option<Representation>,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
195pub enum AccessMode {
196 RO,
198 WO,
200 RW,
202}
203
204impl AccessMode {
205 pub(crate) fn parse(value: &str) -> Result<Self, XmlError> {
206 match value.trim().to_ascii_uppercase().as_str() {
207 "RO" => Ok(AccessMode::RO),
208 "WO" => Ok(AccessMode::WO),
209 "RW" => Ok(AccessMode::RW),
210 other => Err(XmlError::Invalid(format!("unknown access mode: {other}"))),
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
217pub enum Addressing {
218 Fixed { address: u64, len: u32 },
220 BySelector {
222 selector: String,
224 map: Vec<(String, (u64, u32))>,
226 },
227 Indirect {
229 p_address_node: String,
231 len: u32,
233 },
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238pub enum ByteOrder {
239 Little,
241 Big,
243}
244
245impl ByteOrder {
246 pub(crate) fn parse(tag: &str) -> Option<Self> {
247 match tag.trim().to_ascii_lowercase().as_str() {
248 "littleendian" => Some(ByteOrder::Little),
249 "bigendian" => Some(ByteOrder::Big),
250 _ => None,
251 }
252 }
253}
254
255fn default_big_endian() -> ByteOrder {
256 ByteOrder::Big
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261pub struct BitField {
262 pub bit_offset: u16,
264 pub bit_length: u16,
266 pub byte_order: ByteOrder,
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
284pub enum FloatEncoding {
285 Ieee754,
287 #[default]
290 ScaledInteger,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
295pub enum SkOutput {
296 Integer,
299 #[default]
302 Float,
303}
304
305impl SkOutput {
306 pub(crate) fn parse(tag: &str) -> Option<Self> {
307 match tag.trim().to_ascii_lowercase().as_str() {
308 "integer" => Some(SkOutput::Integer),
309 "float" => Some(SkOutput::Float),
310 _ => None,
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct SwissKnifeDecl {
318 pub name: String,
320 pub meta: NodeMeta,
322 pub expr: String,
324 pub variables: Vec<(String, String)>,
326 pub output: SkOutput,
328 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
330 pub predicates: PredicateRefs,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct ConverterDecl {
339 pub name: String,
341 pub meta: NodeMeta,
343 pub p_value: String,
345 pub formula_to: String,
347 pub formula_from: String,
349 pub variables_to: Vec<(String, String)>,
351 pub variables_from: Vec<(String, String)>,
353 pub unit: Option<String>,
355 pub output: SkOutput,
357 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
359 pub predicates: PredicateRefs,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct IntConverterDecl {
365 pub name: String,
367 pub meta: NodeMeta,
369 pub p_value: String,
371 pub formula_to: String,
373 pub formula_from: String,
375 pub variables_to: Vec<(String, String)>,
377 pub variables_from: Vec<(String, String)>,
379 pub unit: Option<String>,
381 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
383 pub predicates: PredicateRefs,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct StringDecl {
389 pub name: String,
391 pub meta: NodeMeta,
393 pub addressing: Addressing,
395 pub access: AccessMode,
397 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
399 pub predicates: PredicateRefs,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub enum NodeDecl {
405 Integer {
407 name: String,
409 meta: NodeMeta,
411 addressing: Option<Addressing>,
413 len: u32,
415 access: AccessMode,
417 min: i64,
419 max: i64,
421 inc: Option<i64>,
423 unit: Option<String>,
425 bitfield: Option<BitField>,
427 selectors: Vec<String>,
429 selected_if: Vec<(String, Vec<String>)>,
431 pvalue: Option<String>,
433 p_max: Option<String>,
435 p_min: Option<String>,
437 value: Option<i64>,
439 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
441 predicates: PredicateRefs,
442 },
443 Float {
446 name: String,
447 meta: NodeMeta,
448 addressing: Option<Addressing>,
450 access: AccessMode,
451 min: f64,
452 max: f64,
453 unit: Option<String>,
454 scale: Option<(i64, i64)>,
456 offset: Option<f64>,
458 selectors: Vec<String>,
459 selected_if: Vec<(String, Vec<String>)>,
460 pvalue: Option<String>,
462 #[serde(default)]
466 encoding: FloatEncoding,
467 #[serde(default = "default_big_endian")]
470 byte_order: ByteOrder,
471 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
473 predicates: PredicateRefs,
474 },
475 Enum {
477 name: String,
478 meta: NodeMeta,
479 addressing: Option<Addressing>,
481 access: AccessMode,
482 entries: Vec<EnumEntryDecl>,
483 default: Option<String>,
484 selectors: Vec<String>,
485 selected_if: Vec<(String, Vec<String>)>,
486 pvalue: Option<String>,
488 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
490 predicates: PredicateRefs,
491 },
492 Boolean {
494 name: String,
495 meta: NodeMeta,
496 addressing: Option<Addressing>,
498 len: u32,
499 access: AccessMode,
500 bitfield: Option<BitField>,
501 selectors: Vec<String>,
502 selected_if: Vec<(String, Vec<String>)>,
503 pvalue: Option<String>,
505 on_value: Option<i64>,
507 off_value: Option<i64>,
509 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
511 predicates: PredicateRefs,
512 },
513 Command {
515 name: String,
516 meta: NodeMeta,
517 address: Option<u64>,
519 len: u32,
520 pvalue: Option<String>,
522 command_value: Option<i64>,
524 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
526 predicates: PredicateRefs,
527 },
528 Category {
530 name: String,
531 meta: NodeMeta,
532 children: Vec<String>,
533 #[serde(default, skip_serializing_if = "PredicateRefs::is_empty")]
535 predicates: PredicateRefs,
536 },
537 SwissKnife(SwissKnifeDecl),
539 Converter(ConverterDecl),
541 IntConverter(IntConverterDecl),
543 String(StringDecl),
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct XmlModel {
550 pub version: String,
552 pub nodes: Vec<NodeDecl>,
554}
555
556#[derive(Debug, Clone, PartialEq, Eq)]
558pub struct MinimalXmlInfo {
559 pub schema_version: Option<String>,
560 pub top_level_features: Vec<String>,
561}
562
563pub fn parse_into_minimal_nodes(xml: &str) -> Result<MinimalXmlInfo, XmlError> {
565 let mut reader = Reader::from_str(xml);
566 reader.trim_text(true);
567 let mut buf = Vec::new();
568 let mut depth = 0usize;
569 let mut schema_version: Option<String> = None;
570 let mut top_level_features = Vec::new();
571
572 loop {
573 match reader.read_event_into(&mut buf) {
574 Ok(Event::Start(e)) => {
575 depth += 1;
576 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
577 }
578 Ok(Event::Empty(e)) => {
579 depth += 1;
580 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
581 if depth > 0 {
582 depth = depth.saturating_sub(1);
583 }
584 }
585 Ok(Event::End(_)) => {
586 if depth > 0 {
587 depth = depth.saturating_sub(1);
588 }
589 }
590 Ok(Event::Eof) => break,
591 Err(err) => return Err(XmlError::Xml(err.to_string())),
592 _ => {}
593 }
594 buf.clear();
595 }
596
597 Ok(MinimalXmlInfo {
598 schema_version,
599 top_level_features,
600 })
601}
602
603pub fn parse(xml: &str) -> Result<XmlModel, XmlError> {
609 let mut reader = Reader::from_str(xml);
610 reader.trim_text(true);
611 let mut buf = Vec::new();
612 let mut version = String::from("0.0.0");
613 let mut nodes = Vec::new();
614
615 loop {
616 match reader.read_event_into(&mut buf) {
617 Ok(Event::Start(ref e)) => match e.name().as_ref() {
618 b"RegisterDescription" => {
619 version = schema_version_from(e)?;
620 }
621 b"Integer" | b"IntReg" | b"MaskedIntReg" => {
622 let node = parse_integer(&mut reader, e.clone())?;
623 nodes.push(node);
624 }
625 b"IntSwissKnife" => {
626 let node = parse_swissknife(&mut reader, e.clone())?;
627 nodes.push(node);
628 }
629 b"Float" | b"FloatReg" => {
630 let node = parse_float(&mut reader, e.clone())?;
631 nodes.push(node);
632 }
633 b"Enumeration" => {
634 let node = parse_enum(&mut reader, e.clone())?;
635 nodes.push(node);
636 }
637 b"Boolean" => {
638 let node = parse_boolean(&mut reader, e.clone())?;
639 nodes.push(node);
640 }
641 b"Command" => {
642 let node = parse_command(&mut reader, e.clone())?;
643 nodes.push(node);
644 }
645 b"Category" => {
646 let node = parse_category(&mut reader, e.clone())?;
647 nodes.push(node);
648 }
649 b"SwissKnife" => {
650 let node = parse_swissknife(&mut reader, e.clone())?;
651 nodes.push(node);
652 }
653 b"Converter" => {
654 let node = parse_converter(&mut reader, e.clone())?;
655 nodes.push(node);
656 }
657 b"IntConverter" => {
658 let node = parse_int_converter(&mut reader, e.clone())?;
659 nodes.push(node);
660 }
661 b"StringReg" | b"String" => {
662 let node = parse_string(&mut reader, e.clone())?;
663 nodes.push(node);
664 }
665 b"StructReg" => {
666 let entries = parse_struct_reg(&mut reader, e.clone())?;
667 nodes.extend(entries);
668 }
669 b"Group" => {
670 }
673 b"Port" => {
674 skip_element(&mut reader, e.name().as_ref())?;
676 }
677 _ => {
678 skip_element(&mut reader, e.name().as_ref())?;
679 }
680 },
681 Ok(Event::Empty(ref e)) => match e.name().as_ref() {
682 b"RegisterDescription" => {
683 version = schema_version_from(e)?;
684 }
685 b"Command" => {
686 let node = parse_command_empty(e)?;
687 nodes.push(node);
688 }
689 b"Category" => {
690 let node = parse_category_empty(e)?;
691 nodes.push(node);
692 }
693 _ => {}
694 },
695 Ok(Event::Eof) => break,
696 Err(err) => return Err(XmlError::Xml(err.to_string())),
697 _ => {}
698 }
699 buf.clear();
700 }
701
702 Ok(XmlModel { version, nodes })
703}
704
705fn schema_version_from(event: &BytesStart<'_>) -> Result<String, XmlError> {
706 let major = attribute_value(event, b"SchemaMajorVersion")?;
707 let minor = attribute_value(event, b"SchemaMinorVersion")?;
708 let sub = attribute_value(event, b"SchemaSubMinorVersion")?;
709 let major = major.unwrap_or_else(|| "0".to_string());
710 let minor = minor.unwrap_or_else(|| "0".to_string());
711 let sub = sub.unwrap_or_else(|| "0".to_string());
712 Ok(format!("{major}.{minor}.{sub}"))
713}
714
715fn handle_start(
716 event: &BytesStart<'_>,
717 depth: usize,
718 schema_version: &mut Option<String>,
719 top_level: &mut Vec<String>,
720) -> Result<(), XmlError> {
721 if depth == 1 && schema_version.is_none() {
722 *schema_version = extract_schema_version(event);
723 } else if depth == 2 {
724 if let Some(name) = attribute_value(event, b"Name")? {
725 top_level.push(name);
726 } else {
727 top_level.push(String::from_utf8_lossy(event.name().as_ref()).to_string());
728 }
729 }
730 Ok(())
731}
732
733fn extract_schema_version(event: &BytesStart<'_>) -> Option<String> {
734 let major = attribute_value(event, b"SchemaMajorVersion").ok().flatten();
735 let minor = attribute_value(event, b"SchemaMinorVersion").ok().flatten();
736 let sub = attribute_value(event, b"SchemaSubMinorVersion")
737 .ok()
738 .flatten();
739 if major.is_none() && minor.is_none() && sub.is_none() {
740 None
741 } else {
742 let major = major.unwrap_or_else(|| "0".to_string());
743 let minor = minor.unwrap_or_else(|| "0".to_string());
744 let sub = sub.unwrap_or_else(|| "0".to_string());
745 Some(format!("{major}.{minor}.{sub}"))
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 const FIXTURE: &str = r#"
754 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
755 <Category Name="Root">
756 <pFeature>Gain</pFeature>
757 <pFeature>GainSelector</pFeature>
758 </Category>
759 <Integer Name="Width">
760 <Address>0x0000_0100</Address>
761 <Length>4</Length>
762 <AccessMode>RW</AccessMode>
763 <Min>16</Min>
764 <Max>4096</Max>
765 <Inc>2</Inc>
766 </Integer>
767 <Float Name="ExposureTime">
768 <Address>0x0000_0200</Address>
769 <Length>4</Length>
770 <AccessMode>RW</AccessMode>
771 <Min>10.0</Min>
772 <Max>200000.0</Max>
773 <Scale>1/1000</Scale>
774 <Offset>0.0</Offset>
775 </Float>
776 <Enumeration Name="GainSelector">
777 <Address>0x0000_0300</Address>
778 <Length>2</Length>
779 <AccessMode>RW</AccessMode>
780 <EnumEntry Name="AnalogAll" Value="0" />
781 <EnumEntry Name="DigitalAll" Value="1" />
782 </Enumeration>
783 <Integer Name="Gain">
784 <Address>0x0000_0304</Address>
785 <Length>2</Length>
786 <AccessMode>RW</AccessMode>
787 <Min>0</Min>
788 <Max>48</Max>
789 <pSelected>GainSelector</pSelected>
790 <Selected>AnalogAll</Selected>
791 </Integer>
792 <Boolean Name="GammaEnable">
793 <Address>0x0000_0400</Address>
794 <Length>1</Length>
795 <AccessMode>RW</AccessMode>
796 </Boolean>
797 <Command Name="AcquisitionStart">
798 <Address>0x0000_0500</Address>
799 <Length>4</Length>
800 </Command>
801 </RegisterDescription>
802 "#;
803
804 #[test]
805 fn parse_minimal_xml() {
806 let info = parse_into_minimal_nodes(FIXTURE).expect("parse xml");
807 assert_eq!(info.schema_version.as_deref(), Some("1.2.3"));
808 assert_eq!(info.top_level_features.len(), 7);
809 assert_eq!(info.top_level_features[0], "Root");
810 }
811
812 #[test]
813 fn parse_fixture_model() {
814 let model = parse(FIXTURE).expect("parse fixture");
815 assert_eq!(model.version, "1.2.3");
816 assert_eq!(model.nodes.len(), 7);
817 match &model.nodes[0] {
818 NodeDecl::Category { name, children, .. } => {
819 assert_eq!(name, "Root");
820 assert_eq!(
821 children,
822 &vec!["Gain".to_string(), "GainSelector".to_string()]
823 );
824 }
825 other => panic!("unexpected node: {other:?}"),
826 }
827 match &model.nodes[1] {
828 NodeDecl::Integer {
829 name,
830 min,
831 max,
832 inc,
833 ..
834 } => {
835 assert_eq!(name, "Width");
836 assert_eq!(*min, 16);
837 assert_eq!(*max, 4096);
838 assert_eq!(*inc, Some(2));
839 }
840 other => panic!("unexpected node: {other:?}"),
841 }
842 match &model.nodes[2] {
843 NodeDecl::Float {
844 name,
845 scale,
846 offset,
847 ..
848 } => {
849 assert_eq!(name, "ExposureTime");
850 assert_eq!(*scale, Some((1, 1000)));
851 assert_eq!(*offset, Some(0.0));
852 }
853 other => panic!("unexpected node: {other:?}"),
854 }
855 match &model.nodes[3] {
856 NodeDecl::Enum { name, entries, .. } => {
857 assert_eq!(name, "GainSelector");
858 assert_eq!(entries.len(), 2);
859 assert!(matches!(entries[0].value, EnumValueSrc::Literal(0)));
860 assert!(matches!(entries[1].value, EnumValueSrc::Literal(1)));
861 }
862 other => panic!("unexpected node: {other:?}"),
863 }
864 match &model.nodes[4] {
865 NodeDecl::Integer {
866 name, selected_if, ..
867 } => {
868 assert_eq!(name, "Gain");
869 assert_eq!(selected_if.len(), 1);
870 assert_eq!(selected_if[0].0, "GainSelector");
871 assert_eq!(selected_if[0].1, vec!["AnalogAll".to_string()]);
872 }
873 other => panic!("unexpected node: {other:?}"),
874 }
875 }
876
877 #[test]
878 fn parse_swissknife_node() {
879 const XML: &str = r#"
880 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
881 <Integer Name="GainRaw">
882 <Address>0x3000</Address>
883 <Length>4</Length>
884 <AccessMode>RW</AccessMode>
885 <Min>0</Min>
886 <Max>1000</Max>
887 </Integer>
888 <Float Name="Offset">
889 <Address>0x3008</Address>
890 <Length>4</Length>
891 <AccessMode>RW</AccessMode>
892 <Min>-100.0</Min>
893 <Max>100.0</Max>
894 </Float>
895 <SwissKnife Name="ComputedGain">
896 <Expression>(GainRaw * 0.5) + Offset</Expression>
897 <pVariable Name="GainRaw">GainRaw</pVariable>
898 <pVariable Name="Offset">Offset</pVariable>
899 <Output>Float</Output>
900 </SwissKnife>
901 </RegisterDescription>
902 "#;
903
904 let model = parse(XML).expect("parse swissknife xml");
905 assert_eq!(model.nodes.len(), 3);
906 let swiss = model
907 .nodes
908 .iter()
909 .find_map(|decl| match decl {
910 NodeDecl::SwissKnife(node) => Some(node),
911 _ => None,
912 })
913 .expect("swissknife present");
914 assert_eq!(swiss.name, "ComputedGain");
915 assert_eq!(swiss.expr, "(GainRaw * 0.5) + Offset");
916 assert_eq!(swiss.output, SkOutput::Float);
917 assert_eq!(swiss.variables.len(), 2);
918 assert_eq!(
919 swiss.variables[0],
920 ("GainRaw".to_string(), "GainRaw".to_string())
921 );
922 assert_eq!(
923 swiss.variables[1],
924 ("Offset".to_string(), "Offset".to_string())
925 );
926 }
927
928 #[test]
929 fn parse_int_swissknife_with_hex_and_ampersand() {
930 const XML: &str = r#"
932 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
933 <IntSwissKnife Name="PayloadSize">
934 <pVariable Name="W">Width</pVariable>
935 <pVariable Name="H">Height</pVariable>
936 <pVariable Name="PF">PixelFormat</pVariable>
937 <Formula>W * H * ((PF>>16)&0xFF) / 8</Formula>
938 </IntSwissKnife>
939 </RegisterDescription>
940 "#;
941
942 let model = parse(XML).expect("parse intswissknife");
943 assert_eq!(model.nodes.len(), 1);
944 let swiss = model
945 .nodes
946 .iter()
947 .find_map(|decl| match decl {
948 NodeDecl::SwissKnife(node) => Some(node),
949 _ => None,
950 })
951 .expect("swissknife present");
952 assert_eq!(swiss.name, "PayloadSize");
953 assert!(
955 swiss.expr.contains('&'),
956 "expression should contain decoded '&': {}",
957 swiss.expr
958 );
959 assert!(
960 swiss.expr.contains("0xFF"),
961 "expression should contain hex literal: {}",
962 swiss.expr
963 );
964 }
965
966 #[test]
967 fn parse_enum_entry_with_pvalue() {
968 const XML: &str = r#"
969 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
970 <Enumeration Name="Mode">
971 <Address>0x0000_4000</Address>
972 <Length>4</Length>
973 <AccessMode>RW</AccessMode>
974 <EnumEntry Name="Fixed10">
975 <Value>10</Value>
976 </EnumEntry>
977 <EnumEntry Name="DynFromReg">
978 <pValue>RegModeVal</pValue>
979 </EnumEntry>
980 </Enumeration>
981 <Integer Name="RegModeVal">
982 <Address>0x0000_4100</Address>
983 <Length>4</Length>
984 <AccessMode>RW</AccessMode>
985 <Min>0</Min>
986 <Max>65535</Max>
987 </Integer>
988 </RegisterDescription>
989 "#;
990
991 let model = parse(XML).expect("parse enum pvalue");
992 assert_eq!(model.nodes.len(), 2);
993 match &model.nodes[0] {
994 NodeDecl::Enum { entries, .. } => {
995 assert_eq!(entries.len(), 2);
996 assert!(matches!(entries[0].value, EnumValueSrc::Literal(10)));
997 match &entries[1].value {
998 EnumValueSrc::FromNode(node) => assert_eq!(node, "RegModeVal"),
999 other => panic!("unexpected entry value: {other:?}"),
1000 }
1001 }
1002 other => panic!("unexpected node: {other:?}"),
1003 }
1004 }
1005
1006 #[test]
1007 fn parse_indirect_addressing() {
1008 const XML: &str = r#"
1009 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1010 <Integer Name="RegAddr">
1011 <Address>0x2000</Address>
1012 <Length>4</Length>
1013 <AccessMode>RW</AccessMode>
1014 <Min>0</Min>
1015 <Max>65535</Max>
1016 </Integer>
1017 <Integer Name="Gain" Address="0xFFFF">
1018 <pAddress>RegAddr</pAddress>
1019 <Length>4</Length>
1020 <AccessMode>RW</AccessMode>
1021 <Min>0</Min>
1022 <Max>255</Max>
1023 </Integer>
1024 </RegisterDescription>
1025 "#;
1026
1027 let model = parse(XML).expect("parse indirect xml");
1028 assert_eq!(model.nodes.len(), 2);
1029 match &model.nodes[0] {
1030 NodeDecl::Integer {
1031 name, addressing, ..
1032 } => {
1033 assert_eq!(name, "RegAddr");
1034 assert!(
1035 matches!(addressing, Some(Addressing::Fixed { address, len }) if *address == 0x2000 && *len == 4)
1036 );
1037 }
1038 other => panic!("unexpected node: {other:?}"),
1039 }
1040 match &model.nodes[1] {
1041 NodeDecl::Integer {
1042 name, addressing, ..
1043 } => {
1044 assert_eq!(name, "Gain");
1045 match addressing {
1046 Some(Addressing::Indirect {
1047 p_address_node,
1048 len,
1049 }) => {
1050 assert_eq!(p_address_node, "RegAddr");
1051 assert_eq!(*len, 4);
1052 }
1053 other => panic!("expected indirect addressing, got {other:?}"),
1054 }
1055 }
1056 other => panic!("unexpected node: {other:?}"),
1057 }
1058 }
1059
1060 #[test]
1061 fn parse_indirect_float_is_scaled_integer() {
1062 const XML: &str = r#"
1068 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1069 <Integer Name="RegAddr">
1070 <Address>0x2000</Address>
1071 <Length>4</Length>
1072 <AccessMode>RW</AccessMode>
1073 </Integer>
1074 <Float Name="Exposure">
1075 <pAddress>RegAddr</pAddress>
1076 <Length>4</Length>
1077 <AccessMode>RW</AccessMode>
1078 </Float>
1079 </RegisterDescription>
1080 "#;
1081
1082 let model = parse(XML).expect("parse indirect float");
1083 let float = model
1084 .nodes
1085 .iter()
1086 .find(|n| matches!(n, NodeDecl::Float { name, .. } if name == "Exposure"))
1087 .expect("Exposure node");
1088 match float {
1089 NodeDecl::Float {
1090 encoding,
1091 addressing,
1092 ..
1093 } => {
1094 assert!(
1095 matches!(addressing, Some(Addressing::Indirect { .. })),
1096 "expected indirect addressing"
1097 );
1098 assert_eq!(*encoding, FloatEncoding::ScaledInteger);
1099 }
1100 _ => unreachable!(),
1101 }
1102 }
1103
1104 #[test]
1105 fn parse_integer_bitfield_big_endian() {
1106 const XML: &str = r#"
1107 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1108 <Integer Name="Packed">
1109 <Address>0x1000</Address>
1110 <Length>4</Length>
1111 <AccessMode>RW</AccessMode>
1112 <Min>0</Min>
1113 <Max>65535</Max>
1114 <Lsb>8</Lsb>
1115 <Msb>15</Msb>
1116 <Endianness>BigEndian</Endianness>
1117 </Integer>
1118 </RegisterDescription>
1119 "#;
1120
1121 let model = parse(XML).expect("parse big-endian bitfield");
1122 assert_eq!(model.nodes.len(), 1);
1123 match &model.nodes[0] {
1124 NodeDecl::Integer { len, bitfield, .. } => {
1125 assert_eq!(*len, 4);
1126 let field = bitfield.as_ref().expect("bitfield present");
1127 assert_eq!(field.byte_order, ByteOrder::Big);
1128 assert_eq!(field.bit_length, 8);
1129 assert_eq!(field.bit_offset, 16);
1130 }
1131 other => panic!("unexpected node: {other:?}"),
1132 }
1133 }
1134
1135 #[test]
1136 fn parse_boolean_bitfield_default_length() {
1137 const XML: &str = r#"
1138 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1139 <Boolean Name="Flag">
1140 <Address>0x2000</Address>
1141 <Length>1</Length>
1142 <AccessMode>RW</AccessMode>
1143 <Bit>3</Bit>
1144 </Boolean>
1145 </RegisterDescription>
1146 "#;
1147
1148 let model = parse(XML).expect("parse boolean bitfield");
1149 assert_eq!(model.nodes.len(), 1);
1150 match &model.nodes[0] {
1151 NodeDecl::Boolean { len, bitfield, .. } => {
1152 assert_eq!(*len, 1);
1153 let bf = bitfield.as_ref().expect("bitfield present");
1154 assert_eq!(bf.byte_order, ByteOrder::Little);
1155 assert_eq!(bf.bit_length, 1);
1156 assert_eq!(bf.bit_offset, 3);
1157 }
1158 other => panic!("unexpected node: {other:?}"),
1159 }
1160 }
1161
1162 #[test]
1163 fn parse_integer_bitfield_mask() {
1164 const XML: &str = r#"
1165 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1166 <Integer Name="Masked">
1167 <Address>0x3000</Address>
1168 <Length>4</Length>
1169 <AccessMode>RW</AccessMode>
1170 <Min>0</Min>
1171 <Max>65535</Max>
1172 <Mask>0x0000FF00</Mask>
1173 </Integer>
1174 </RegisterDescription>
1175 "#;
1176
1177 let model = parse(XML).expect("parse mask bitfield");
1178 assert_eq!(model.nodes.len(), 1);
1179 match &model.nodes[0] {
1180 NodeDecl::Integer { bitfield, .. } => {
1181 let field = bitfield.as_ref().expect("bitfield present");
1182 assert_eq!(field.byte_order, ByteOrder::Little);
1183 assert_eq!(field.bit_length, 8);
1184 assert_eq!(field.bit_offset, 8);
1185 }
1186 other => panic!("unexpected node: {other:?}"),
1187 }
1188 }
1189
1190 #[test]
1191 fn parse_node_metadata() {
1192 const XML: &str = r#"
1193 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1194 <Integer Name="Width">
1195 <Address>0x100</Address>
1196 <Length>4</Length>
1197 <AccessMode>RW</AccessMode>
1198 <Min>16</Min>
1199 <Max>4096</Max>
1200 <Visibility>Expert</Visibility>
1201 <Description>Image width in pixels.</Description>
1202 <ToolTip>Width of the acquired image</ToolTip>
1203 <DisplayName>Image Width</DisplayName>
1204 <Representation>Linear</Representation>
1205 </Integer>
1206 <Float Name="Gain">
1207 <Address>0x200</Address>
1208 <Length>4</Length>
1209 <AccessMode>RW</AccessMode>
1210 <Min>0.0</Min>
1211 <Max>48.0</Max>
1212 <Unit>dB</Unit>
1213 <Visibility>Beginner</Visibility>
1214 <Representation>Logarithmic</Representation>
1215 </Float>
1216 <Category Name="Root">
1217 <Visibility>Guru</Visibility>
1218 <Description>Top-level category</Description>
1219 <pFeature>Width</pFeature>
1220 <pFeature>Gain</pFeature>
1221 </Category>
1222 <Enumeration Name="PixelFormat">
1223 <Address>0x300</Address>
1224 <Length>4</Length>
1225 <AccessMode>RW</AccessMode>
1226 <Visibility>Beginner</Visibility>
1227 <ToolTip>Pixel format selector</ToolTip>
1228 <EnumEntry Name="Mono8" Value="0" />
1229 </Enumeration>
1230 </RegisterDescription>
1231 "#;
1232
1233 let model = parse(XML).expect("parse metadata xml");
1234 assert_eq!(model.nodes.len(), 4);
1235
1236 match &model.nodes[0] {
1238 NodeDecl::Integer { name, meta, .. } => {
1239 assert_eq!(name, "Width");
1240 assert_eq!(meta.visibility, Visibility::Expert);
1241 assert_eq!(meta.description.as_deref(), Some("Image width in pixels."));
1242 assert_eq!(meta.tooltip.as_deref(), Some("Width of the acquired image"));
1243 assert_eq!(meta.display_name.as_deref(), Some("Image Width"));
1244 assert_eq!(meta.representation, Some(Representation::Linear));
1245 }
1246 other => panic!("unexpected node: {other:?}"),
1247 }
1248
1249 match &model.nodes[1] {
1251 NodeDecl::Float { name, meta, .. } => {
1252 assert_eq!(name, "Gain");
1253 assert_eq!(meta.visibility, Visibility::Beginner);
1254 assert_eq!(meta.representation, Some(Representation::Logarithmic));
1255 assert!(meta.description.is_none());
1256 }
1257 other => panic!("unexpected node: {other:?}"),
1258 }
1259
1260 match &model.nodes[2] {
1262 NodeDecl::Category { name, meta, .. } => {
1263 assert_eq!(name, "Root");
1264 assert_eq!(meta.visibility, Visibility::Guru);
1265 assert_eq!(meta.description.as_deref(), Some("Top-level category"));
1266 }
1267 other => panic!("unexpected node: {other:?}"),
1268 }
1269
1270 match &model.nodes[3] {
1272 NodeDecl::Enum { name, meta, .. } => {
1273 assert_eq!(name, "PixelFormat");
1274 assert_eq!(meta.visibility, Visibility::Beginner);
1275 assert_eq!(meta.tooltip.as_deref(), Some("Pixel format selector"));
1276 }
1277 other => panic!("unexpected node: {other:?}"),
1278 }
1279 }
1280}