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, PartialEq, Eq, Serialize, Deserialize)]
39pub struct EnumEntryDecl {
40 pub name: String,
42 pub value: EnumValueSrc,
44 pub display_name: Option<String>,
46}
47
48#[derive(Debug, Error)]
49#[non_exhaustive]
50pub enum XmlError {
51 #[error("xml: {0}")]
52 Xml(String),
53 #[error("invalid descriptor: {0}")]
54 Invalid(String),
55 #[error("transport: {0}")]
56 Transport(String),
57 #[error("unsupported URL: {0}")]
58 Unsupported(String),
59}
60
61#[derive(
66 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
67)]
68#[non_exhaustive]
69pub enum Visibility {
70 #[default]
72 Beginner,
73 Expert,
75 Guru,
77 Invisible,
79}
80
81impl Visibility {
82 pub(crate) fn parse(s: &str) -> Option<Self> {
83 match s.trim() {
84 "Beginner" => Some(Self::Beginner),
85 "Expert" => Some(Self::Expert),
86 "Guru" => Some(Self::Guru),
87 "Invisible" => Some(Self::Invisible),
88 _ => None,
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
95#[non_exhaustive]
96pub enum Representation {
97 Linear,
98 Logarithmic,
99 Boolean,
100 PureNumber,
101 HexNumber,
102 IPV4Address,
104 MACAddress,
106}
107
108impl Representation {
109 pub(crate) fn parse(s: &str) -> Option<Self> {
110 match s.trim() {
111 "Linear" => Some(Self::Linear),
112 "Logarithmic" => Some(Self::Logarithmic),
113 "Boolean" => Some(Self::Boolean),
114 "PureNumber" => Some(Self::PureNumber),
115 "HexNumber" => Some(Self::HexNumber),
116 "IPV4Address" => Some(Self::IPV4Address),
117 "MACAddress" => Some(Self::MACAddress),
118 _ => None,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
125pub struct NodeMeta {
126 pub visibility: Visibility,
128 pub description: Option<String>,
130 pub tooltip: Option<String>,
132 pub display_name: Option<String>,
134 pub representation: Option<Representation>,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
140pub enum AccessMode {
141 RO,
143 WO,
145 RW,
147}
148
149impl AccessMode {
150 pub(crate) fn parse(value: &str) -> Result<Self, XmlError> {
151 match value.trim().to_ascii_uppercase().as_str() {
152 "RO" => Ok(AccessMode::RO),
153 "WO" => Ok(AccessMode::WO),
154 "RW" => Ok(AccessMode::RW),
155 other => Err(XmlError::Invalid(format!("unknown access mode: {other}"))),
156 }
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub enum Addressing {
163 Fixed { address: u64, len: u32 },
165 BySelector {
167 selector: String,
169 map: Vec<(String, (u64, u32))>,
171 },
172 Indirect {
174 p_address_node: String,
176 len: u32,
178 },
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183pub enum ByteOrder {
184 Little,
186 Big,
188}
189
190impl ByteOrder {
191 pub(crate) fn parse(tag: &str) -> Option<Self> {
192 match tag.trim().to_ascii_lowercase().as_str() {
193 "littleendian" => Some(ByteOrder::Little),
194 "bigendian" => Some(ByteOrder::Big),
195 _ => None,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
202pub struct BitField {
203 pub bit_offset: u16,
205 pub bit_length: u16,
207 pub byte_order: ByteOrder,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
213pub enum SkOutput {
214 Integer,
217 #[default]
220 Float,
221}
222
223impl SkOutput {
224 pub(crate) fn parse(tag: &str) -> Option<Self> {
225 match tag.trim().to_ascii_lowercase().as_str() {
226 "integer" => Some(SkOutput::Integer),
227 "float" => Some(SkOutput::Float),
228 _ => None,
229 }
230 }
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct SwissKnifeDecl {
236 pub name: String,
238 pub meta: NodeMeta,
240 pub expr: String,
242 pub variables: Vec<(String, String)>,
244 pub output: SkOutput,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct ConverterDecl {
254 pub name: String,
256 pub meta: NodeMeta,
258 pub p_value: String,
260 pub formula_to: String,
262 pub formula_from: String,
264 pub variables_to: Vec<(String, String)>,
266 pub variables_from: Vec<(String, String)>,
268 pub unit: Option<String>,
270 pub output: SkOutput,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct IntConverterDecl {
277 pub name: String,
279 pub meta: NodeMeta,
281 pub p_value: String,
283 pub formula_to: String,
285 pub formula_from: String,
287 pub variables_to: Vec<(String, String)>,
289 pub variables_from: Vec<(String, String)>,
291 pub unit: Option<String>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct StringDecl {
298 pub name: String,
300 pub meta: NodeMeta,
302 pub addressing: Addressing,
304 pub access: AccessMode,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub enum NodeDecl {
311 Integer {
313 name: String,
315 meta: NodeMeta,
317 addressing: Option<Addressing>,
319 len: u32,
321 access: AccessMode,
323 min: i64,
325 max: i64,
327 inc: Option<i64>,
329 unit: Option<String>,
331 bitfield: Option<BitField>,
333 selectors: Vec<String>,
335 selected_if: Vec<(String, Vec<String>)>,
337 pvalue: Option<String>,
339 p_max: Option<String>,
341 p_min: Option<String>,
343 value: Option<i64>,
345 },
346 Float {
349 name: String,
350 meta: NodeMeta,
351 addressing: Option<Addressing>,
353 access: AccessMode,
354 min: f64,
355 max: f64,
356 unit: Option<String>,
357 scale: Option<(i64, i64)>,
359 offset: Option<f64>,
361 selectors: Vec<String>,
362 selected_if: Vec<(String, Vec<String>)>,
363 pvalue: Option<String>,
365 },
366 Enum {
368 name: String,
369 meta: NodeMeta,
370 addressing: Option<Addressing>,
372 access: AccessMode,
373 entries: Vec<EnumEntryDecl>,
374 default: Option<String>,
375 selectors: Vec<String>,
376 selected_if: Vec<(String, Vec<String>)>,
377 pvalue: Option<String>,
379 },
380 Boolean {
382 name: String,
383 meta: NodeMeta,
384 addressing: Option<Addressing>,
386 len: u32,
387 access: AccessMode,
388 bitfield: Option<BitField>,
389 selectors: Vec<String>,
390 selected_if: Vec<(String, Vec<String>)>,
391 pvalue: Option<String>,
393 on_value: Option<i64>,
395 off_value: Option<i64>,
397 },
398 Command {
400 name: String,
401 meta: NodeMeta,
402 address: Option<u64>,
404 len: u32,
405 pvalue: Option<String>,
407 command_value: Option<i64>,
409 },
410 Category {
412 name: String,
413 meta: NodeMeta,
414 children: Vec<String>,
415 },
416 SwissKnife(SwissKnifeDecl),
418 Converter(ConverterDecl),
420 IntConverter(IntConverterDecl),
422 String(StringDecl),
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct XmlModel {
429 pub version: String,
431 pub nodes: Vec<NodeDecl>,
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
437pub struct MinimalXmlInfo {
438 pub schema_version: Option<String>,
439 pub top_level_features: Vec<String>,
440}
441
442pub fn parse_into_minimal_nodes(xml: &str) -> Result<MinimalXmlInfo, XmlError> {
444 let mut reader = Reader::from_str(xml);
445 reader.trim_text(true);
446 let mut buf = Vec::new();
447 let mut depth = 0usize;
448 let mut schema_version: Option<String> = None;
449 let mut top_level_features = Vec::new();
450
451 loop {
452 match reader.read_event_into(&mut buf) {
453 Ok(Event::Start(e)) => {
454 depth += 1;
455 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
456 }
457 Ok(Event::Empty(e)) => {
458 depth += 1;
459 handle_start(&e, depth, &mut schema_version, &mut top_level_features)?;
460 if depth > 0 {
461 depth = depth.saturating_sub(1);
462 }
463 }
464 Ok(Event::End(_)) => {
465 if depth > 0 {
466 depth = depth.saturating_sub(1);
467 }
468 }
469 Ok(Event::Eof) => break,
470 Err(err) => return Err(XmlError::Xml(err.to_string())),
471 _ => {}
472 }
473 buf.clear();
474 }
475
476 Ok(MinimalXmlInfo {
477 schema_version,
478 top_level_features,
479 })
480}
481
482pub fn parse(xml: &str) -> Result<XmlModel, XmlError> {
488 let mut reader = Reader::from_str(xml);
489 reader.trim_text(true);
490 let mut buf = Vec::new();
491 let mut version = String::from("0.0.0");
492 let mut nodes = Vec::new();
493
494 loop {
495 match reader.read_event_into(&mut buf) {
496 Ok(Event::Start(ref e)) => match e.name().as_ref() {
497 b"RegisterDescription" => {
498 version = schema_version_from(e)?;
499 }
500 b"Integer" | b"IntReg" | b"MaskedIntReg" => {
501 let node = parse_integer(&mut reader, e.clone())?;
502 nodes.push(node);
503 }
504 b"IntSwissKnife" => {
505 let node = parse_swissknife(&mut reader, e.clone())?;
506 nodes.push(node);
507 }
508 b"Float" | b"FloatReg" => {
509 let node = parse_float(&mut reader, e.clone())?;
510 nodes.push(node);
511 }
512 b"Enumeration" => {
513 let node = parse_enum(&mut reader, e.clone())?;
514 nodes.push(node);
515 }
516 b"Boolean" => {
517 let node = parse_boolean(&mut reader, e.clone())?;
518 nodes.push(node);
519 }
520 b"Command" => {
521 let node = parse_command(&mut reader, e.clone())?;
522 nodes.push(node);
523 }
524 b"Category" => {
525 let node = parse_category(&mut reader, e.clone())?;
526 nodes.push(node);
527 }
528 b"SwissKnife" => {
529 let node = parse_swissknife(&mut reader, e.clone())?;
530 nodes.push(node);
531 }
532 b"Converter" => {
533 let node = parse_converter(&mut reader, e.clone())?;
534 nodes.push(node);
535 }
536 b"IntConverter" => {
537 let node = parse_int_converter(&mut reader, e.clone())?;
538 nodes.push(node);
539 }
540 b"StringReg" | b"String" => {
541 let node = parse_string(&mut reader, e.clone())?;
542 nodes.push(node);
543 }
544 b"StructReg" => {
545 let entries = parse_struct_reg(&mut reader, e.clone())?;
546 nodes.extend(entries);
547 }
548 b"Group" => {
549 }
552 b"Port" => {
553 skip_element(&mut reader, e.name().as_ref())?;
555 }
556 _ => {
557 skip_element(&mut reader, e.name().as_ref())?;
558 }
559 },
560 Ok(Event::Empty(ref e)) => match e.name().as_ref() {
561 b"RegisterDescription" => {
562 version = schema_version_from(e)?;
563 }
564 b"Command" => {
565 let node = parse_command_empty(e)?;
566 nodes.push(node);
567 }
568 b"Category" => {
569 let node = parse_category_empty(e)?;
570 nodes.push(node);
571 }
572 _ => {}
573 },
574 Ok(Event::Eof) => break,
575 Err(err) => return Err(XmlError::Xml(err.to_string())),
576 _ => {}
577 }
578 buf.clear();
579 }
580
581 Ok(XmlModel { version, nodes })
582}
583
584fn schema_version_from(event: &BytesStart<'_>) -> Result<String, XmlError> {
585 let major = attribute_value(event, b"SchemaMajorVersion")?;
586 let minor = attribute_value(event, b"SchemaMinorVersion")?;
587 let sub = attribute_value(event, b"SchemaSubMinorVersion")?;
588 let major = major.unwrap_or_else(|| "0".to_string());
589 let minor = minor.unwrap_or_else(|| "0".to_string());
590 let sub = sub.unwrap_or_else(|| "0".to_string());
591 Ok(format!("{major}.{minor}.{sub}"))
592}
593
594fn handle_start(
595 event: &BytesStart<'_>,
596 depth: usize,
597 schema_version: &mut Option<String>,
598 top_level: &mut Vec<String>,
599) -> Result<(), XmlError> {
600 if depth == 1 && schema_version.is_none() {
601 *schema_version = extract_schema_version(event);
602 } else if depth == 2 {
603 if let Some(name) = attribute_value(event, b"Name")? {
604 top_level.push(name);
605 } else {
606 top_level.push(String::from_utf8_lossy(event.name().as_ref()).to_string());
607 }
608 }
609 Ok(())
610}
611
612fn extract_schema_version(event: &BytesStart<'_>) -> Option<String> {
613 let major = attribute_value(event, b"SchemaMajorVersion").ok().flatten();
614 let minor = attribute_value(event, b"SchemaMinorVersion").ok().flatten();
615 let sub = attribute_value(event, b"SchemaSubMinorVersion")
616 .ok()
617 .flatten();
618 if major.is_none() && minor.is_none() && sub.is_none() {
619 None
620 } else {
621 let major = major.unwrap_or_else(|| "0".to_string());
622 let minor = minor.unwrap_or_else(|| "0".to_string());
623 let sub = sub.unwrap_or_else(|| "0".to_string());
624 Some(format!("{major}.{minor}.{sub}"))
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 const FIXTURE: &str = r#"
633 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="2" SchemaSubMinorVersion="3">
634 <Category Name="Root">
635 <pFeature>Gain</pFeature>
636 <pFeature>GainSelector</pFeature>
637 </Category>
638 <Integer Name="Width">
639 <Address>0x0000_0100</Address>
640 <Length>4</Length>
641 <AccessMode>RW</AccessMode>
642 <Min>16</Min>
643 <Max>4096</Max>
644 <Inc>2</Inc>
645 </Integer>
646 <Float Name="ExposureTime">
647 <Address>0x0000_0200</Address>
648 <Length>4</Length>
649 <AccessMode>RW</AccessMode>
650 <Min>10.0</Min>
651 <Max>200000.0</Max>
652 <Scale>1/1000</Scale>
653 <Offset>0.0</Offset>
654 </Float>
655 <Enumeration Name="GainSelector">
656 <Address>0x0000_0300</Address>
657 <Length>2</Length>
658 <AccessMode>RW</AccessMode>
659 <EnumEntry Name="AnalogAll" Value="0" />
660 <EnumEntry Name="DigitalAll" Value="1" />
661 </Enumeration>
662 <Integer Name="Gain">
663 <Address>0x0000_0304</Address>
664 <Length>2</Length>
665 <AccessMode>RW</AccessMode>
666 <Min>0</Min>
667 <Max>48</Max>
668 <pSelected>GainSelector</pSelected>
669 <Selected>AnalogAll</Selected>
670 </Integer>
671 <Boolean Name="GammaEnable">
672 <Address>0x0000_0400</Address>
673 <Length>1</Length>
674 <AccessMode>RW</AccessMode>
675 </Boolean>
676 <Command Name="AcquisitionStart">
677 <Address>0x0000_0500</Address>
678 <Length>4</Length>
679 </Command>
680 </RegisterDescription>
681 "#;
682
683 #[test]
684 fn parse_minimal_xml() {
685 let info = parse_into_minimal_nodes(FIXTURE).expect("parse xml");
686 assert_eq!(info.schema_version.as_deref(), Some("1.2.3"));
687 assert_eq!(info.top_level_features.len(), 7);
688 assert_eq!(info.top_level_features[0], "Root");
689 }
690
691 #[test]
692 fn parse_fixture_model() {
693 let model = parse(FIXTURE).expect("parse fixture");
694 assert_eq!(model.version, "1.2.3");
695 assert_eq!(model.nodes.len(), 7);
696 match &model.nodes[0] {
697 NodeDecl::Category { name, children, .. } => {
698 assert_eq!(name, "Root");
699 assert_eq!(
700 children,
701 &vec!["Gain".to_string(), "GainSelector".to_string()]
702 );
703 }
704 other => panic!("unexpected node: {other:?}"),
705 }
706 match &model.nodes[1] {
707 NodeDecl::Integer {
708 name,
709 min,
710 max,
711 inc,
712 ..
713 } => {
714 assert_eq!(name, "Width");
715 assert_eq!(*min, 16);
716 assert_eq!(*max, 4096);
717 assert_eq!(*inc, Some(2));
718 }
719 other => panic!("unexpected node: {other:?}"),
720 }
721 match &model.nodes[2] {
722 NodeDecl::Float {
723 name,
724 scale,
725 offset,
726 ..
727 } => {
728 assert_eq!(name, "ExposureTime");
729 assert_eq!(*scale, Some((1, 1000)));
730 assert_eq!(*offset, Some(0.0));
731 }
732 other => panic!("unexpected node: {other:?}"),
733 }
734 match &model.nodes[3] {
735 NodeDecl::Enum { name, entries, .. } => {
736 assert_eq!(name, "GainSelector");
737 assert_eq!(entries.len(), 2);
738 assert!(matches!(entries[0].value, EnumValueSrc::Literal(0)));
739 assert!(matches!(entries[1].value, EnumValueSrc::Literal(1)));
740 }
741 other => panic!("unexpected node: {other:?}"),
742 }
743 match &model.nodes[4] {
744 NodeDecl::Integer {
745 name, selected_if, ..
746 } => {
747 assert_eq!(name, "Gain");
748 assert_eq!(selected_if.len(), 1);
749 assert_eq!(selected_if[0].0, "GainSelector");
750 assert_eq!(selected_if[0].1, vec!["AnalogAll".to_string()]);
751 }
752 other => panic!("unexpected node: {other:?}"),
753 }
754 }
755
756 #[test]
757 fn parse_swissknife_node() {
758 const XML: &str = r#"
759 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
760 <Integer Name="GainRaw">
761 <Address>0x3000</Address>
762 <Length>4</Length>
763 <AccessMode>RW</AccessMode>
764 <Min>0</Min>
765 <Max>1000</Max>
766 </Integer>
767 <Float Name="Offset">
768 <Address>0x3008</Address>
769 <Length>4</Length>
770 <AccessMode>RW</AccessMode>
771 <Min>-100.0</Min>
772 <Max>100.0</Max>
773 </Float>
774 <SwissKnife Name="ComputedGain">
775 <Expression>(GainRaw * 0.5) + Offset</Expression>
776 <pVariable Name="GainRaw">GainRaw</pVariable>
777 <pVariable Name="Offset">Offset</pVariable>
778 <Output>Float</Output>
779 </SwissKnife>
780 </RegisterDescription>
781 "#;
782
783 let model = parse(XML).expect("parse swissknife xml");
784 assert_eq!(model.nodes.len(), 3);
785 let swiss = model
786 .nodes
787 .iter()
788 .find_map(|decl| match decl {
789 NodeDecl::SwissKnife(node) => Some(node),
790 _ => None,
791 })
792 .expect("swissknife present");
793 assert_eq!(swiss.name, "ComputedGain");
794 assert_eq!(swiss.expr, "(GainRaw * 0.5) + Offset");
795 assert_eq!(swiss.output, SkOutput::Float);
796 assert_eq!(swiss.variables.len(), 2);
797 assert_eq!(
798 swiss.variables[0],
799 ("GainRaw".to_string(), "GainRaw".to_string())
800 );
801 assert_eq!(
802 swiss.variables[1],
803 ("Offset".to_string(), "Offset".to_string())
804 );
805 }
806
807 #[test]
808 fn parse_int_swissknife_with_hex_and_ampersand() {
809 const XML: &str = r#"
811 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
812 <IntSwissKnife Name="PayloadSize">
813 <pVariable Name="W">Width</pVariable>
814 <pVariable Name="H">Height</pVariable>
815 <pVariable Name="PF">PixelFormat</pVariable>
816 <Formula>W * H * ((PF>>16)&0xFF) / 8</Formula>
817 </IntSwissKnife>
818 </RegisterDescription>
819 "#;
820
821 let model = parse(XML).expect("parse intswissknife");
822 assert_eq!(model.nodes.len(), 1);
823 let swiss = model
824 .nodes
825 .iter()
826 .find_map(|decl| match decl {
827 NodeDecl::SwissKnife(node) => Some(node),
828 _ => None,
829 })
830 .expect("swissknife present");
831 assert_eq!(swiss.name, "PayloadSize");
832 assert!(
834 swiss.expr.contains('&'),
835 "expression should contain decoded '&': {}",
836 swiss.expr
837 );
838 assert!(
839 swiss.expr.contains("0xFF"),
840 "expression should contain hex literal: {}",
841 swiss.expr
842 );
843 }
844
845 #[test]
846 fn parse_enum_entry_with_pvalue() {
847 const XML: &str = r#"
848 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
849 <Enumeration Name="Mode">
850 <Address>0x0000_4000</Address>
851 <Length>4</Length>
852 <AccessMode>RW</AccessMode>
853 <EnumEntry Name="Fixed10">
854 <Value>10</Value>
855 </EnumEntry>
856 <EnumEntry Name="DynFromReg">
857 <pValue>RegModeVal</pValue>
858 </EnumEntry>
859 </Enumeration>
860 <Integer Name="RegModeVal">
861 <Address>0x0000_4100</Address>
862 <Length>4</Length>
863 <AccessMode>RW</AccessMode>
864 <Min>0</Min>
865 <Max>65535</Max>
866 </Integer>
867 </RegisterDescription>
868 "#;
869
870 let model = parse(XML).expect("parse enum pvalue");
871 assert_eq!(model.nodes.len(), 2);
872 match &model.nodes[0] {
873 NodeDecl::Enum { entries, .. } => {
874 assert_eq!(entries.len(), 2);
875 assert!(matches!(entries[0].value, EnumValueSrc::Literal(10)));
876 match &entries[1].value {
877 EnumValueSrc::FromNode(node) => assert_eq!(node, "RegModeVal"),
878 other => panic!("unexpected entry value: {other:?}"),
879 }
880 }
881 other => panic!("unexpected node: {other:?}"),
882 }
883 }
884
885 #[test]
886 fn parse_indirect_addressing() {
887 const XML: &str = r#"
888 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
889 <Integer Name="RegAddr">
890 <Address>0x2000</Address>
891 <Length>4</Length>
892 <AccessMode>RW</AccessMode>
893 <Min>0</Min>
894 <Max>65535</Max>
895 </Integer>
896 <Integer Name="Gain" Address="0xFFFF">
897 <pAddress>RegAddr</pAddress>
898 <Length>4</Length>
899 <AccessMode>RW</AccessMode>
900 <Min>0</Min>
901 <Max>255</Max>
902 </Integer>
903 </RegisterDescription>
904 "#;
905
906 let model = parse(XML).expect("parse indirect xml");
907 assert_eq!(model.nodes.len(), 2);
908 match &model.nodes[0] {
909 NodeDecl::Integer {
910 name, addressing, ..
911 } => {
912 assert_eq!(name, "RegAddr");
913 assert!(
914 matches!(addressing, Some(Addressing::Fixed { address, len }) if *address == 0x2000 && *len == 4)
915 );
916 }
917 other => panic!("unexpected node: {other:?}"),
918 }
919 match &model.nodes[1] {
920 NodeDecl::Integer {
921 name, addressing, ..
922 } => {
923 assert_eq!(name, "Gain");
924 match addressing {
925 Some(Addressing::Indirect {
926 p_address_node,
927 len,
928 }) => {
929 assert_eq!(p_address_node, "RegAddr");
930 assert_eq!(*len, 4);
931 }
932 other => panic!("expected indirect addressing, got {other:?}"),
933 }
934 }
935 other => panic!("unexpected node: {other:?}"),
936 }
937 }
938
939 #[test]
940 fn parse_integer_bitfield_big_endian() {
941 const XML: &str = r#"
942 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
943 <Integer Name="Packed">
944 <Address>0x1000</Address>
945 <Length>4</Length>
946 <AccessMode>RW</AccessMode>
947 <Min>0</Min>
948 <Max>65535</Max>
949 <Lsb>8</Lsb>
950 <Msb>15</Msb>
951 <Endianness>BigEndian</Endianness>
952 </Integer>
953 </RegisterDescription>
954 "#;
955
956 let model = parse(XML).expect("parse big-endian bitfield");
957 assert_eq!(model.nodes.len(), 1);
958 match &model.nodes[0] {
959 NodeDecl::Integer { len, bitfield, .. } => {
960 assert_eq!(*len, 4);
961 let field = bitfield.as_ref().expect("bitfield present");
962 assert_eq!(field.byte_order, ByteOrder::Big);
963 assert_eq!(field.bit_length, 8);
964 assert_eq!(field.bit_offset, 16);
965 }
966 other => panic!("unexpected node: {other:?}"),
967 }
968 }
969
970 #[test]
971 fn parse_boolean_bitfield_default_length() {
972 const XML: &str = r#"
973 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
974 <Boolean Name="Flag">
975 <Address>0x2000</Address>
976 <Length>1</Length>
977 <AccessMode>RW</AccessMode>
978 <Bit>3</Bit>
979 </Boolean>
980 </RegisterDescription>
981 "#;
982
983 let model = parse(XML).expect("parse boolean bitfield");
984 assert_eq!(model.nodes.len(), 1);
985 match &model.nodes[0] {
986 NodeDecl::Boolean { len, bitfield, .. } => {
987 assert_eq!(*len, 1);
988 let bf = bitfield.as_ref().expect("bitfield present");
989 assert_eq!(bf.byte_order, ByteOrder::Little);
990 assert_eq!(bf.bit_length, 1);
991 assert_eq!(bf.bit_offset, 3);
992 }
993 other => panic!("unexpected node: {other:?}"),
994 }
995 }
996
997 #[test]
998 fn parse_integer_bitfield_mask() {
999 const XML: &str = r#"
1000 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1001 <Integer Name="Masked">
1002 <Address>0x3000</Address>
1003 <Length>4</Length>
1004 <AccessMode>RW</AccessMode>
1005 <Min>0</Min>
1006 <Max>65535</Max>
1007 <Mask>0x0000FF00</Mask>
1008 </Integer>
1009 </RegisterDescription>
1010 "#;
1011
1012 let model = parse(XML).expect("parse mask bitfield");
1013 assert_eq!(model.nodes.len(), 1);
1014 match &model.nodes[0] {
1015 NodeDecl::Integer { bitfield, .. } => {
1016 let field = bitfield.as_ref().expect("bitfield present");
1017 assert_eq!(field.byte_order, ByteOrder::Little);
1018 assert_eq!(field.bit_length, 8);
1019 assert_eq!(field.bit_offset, 8);
1020 }
1021 other => panic!("unexpected node: {other:?}"),
1022 }
1023 }
1024
1025 #[test]
1026 fn parse_node_metadata() {
1027 const XML: &str = r#"
1028 <RegisterDescription SchemaMajorVersion="1" SchemaMinorVersion="0" SchemaSubMinorVersion="0">
1029 <Integer Name="Width">
1030 <Address>0x100</Address>
1031 <Length>4</Length>
1032 <AccessMode>RW</AccessMode>
1033 <Min>16</Min>
1034 <Max>4096</Max>
1035 <Visibility>Expert</Visibility>
1036 <Description>Image width in pixels.</Description>
1037 <ToolTip>Width of the acquired image</ToolTip>
1038 <DisplayName>Image Width</DisplayName>
1039 <Representation>Linear</Representation>
1040 </Integer>
1041 <Float Name="Gain">
1042 <Address>0x200</Address>
1043 <Length>4</Length>
1044 <AccessMode>RW</AccessMode>
1045 <Min>0.0</Min>
1046 <Max>48.0</Max>
1047 <Unit>dB</Unit>
1048 <Visibility>Beginner</Visibility>
1049 <Representation>Logarithmic</Representation>
1050 </Float>
1051 <Category Name="Root">
1052 <Visibility>Guru</Visibility>
1053 <Description>Top-level category</Description>
1054 <pFeature>Width</pFeature>
1055 <pFeature>Gain</pFeature>
1056 </Category>
1057 <Enumeration Name="PixelFormat">
1058 <Address>0x300</Address>
1059 <Length>4</Length>
1060 <AccessMode>RW</AccessMode>
1061 <Visibility>Beginner</Visibility>
1062 <ToolTip>Pixel format selector</ToolTip>
1063 <EnumEntry Name="Mono8" Value="0" />
1064 </Enumeration>
1065 </RegisterDescription>
1066 "#;
1067
1068 let model = parse(XML).expect("parse metadata xml");
1069 assert_eq!(model.nodes.len(), 4);
1070
1071 match &model.nodes[0] {
1073 NodeDecl::Integer { name, meta, .. } => {
1074 assert_eq!(name, "Width");
1075 assert_eq!(meta.visibility, Visibility::Expert);
1076 assert_eq!(meta.description.as_deref(), Some("Image width in pixels."));
1077 assert_eq!(meta.tooltip.as_deref(), Some("Width of the acquired image"));
1078 assert_eq!(meta.display_name.as_deref(), Some("Image Width"));
1079 assert_eq!(meta.representation, Some(Representation::Linear));
1080 }
1081 other => panic!("unexpected node: {other:?}"),
1082 }
1083
1084 match &model.nodes[1] {
1086 NodeDecl::Float { name, meta, .. } => {
1087 assert_eq!(name, "Gain");
1088 assert_eq!(meta.visibility, Visibility::Beginner);
1089 assert_eq!(meta.representation, Some(Representation::Logarithmic));
1090 assert!(meta.description.is_none());
1091 }
1092 other => panic!("unexpected node: {other:?}"),
1093 }
1094
1095 match &model.nodes[2] {
1097 NodeDecl::Category { name, meta, .. } => {
1098 assert_eq!(name, "Root");
1099 assert_eq!(meta.visibility, Visibility::Guru);
1100 assert_eq!(meta.description.as_deref(), Some("Top-level category"));
1101 }
1102 other => panic!("unexpected node: {other:?}"),
1103 }
1104
1105 match &model.nodes[3] {
1107 NodeDecl::Enum { name, meta, .. } => {
1108 assert_eq!(name, "PixelFormat");
1109 assert_eq!(meta.visibility, Visibility::Beginner);
1110 assert_eq!(meta.tooltip.as_deref(), Some("Pixel format selector"));
1111 }
1112 other => panic!("unexpected node: {other:?}"),
1113 }
1114 }
1115}