opcua_xml/schema/
opc_binary_schema.rs

1//! This module contains an implementation of the OPCBinarySchema.xsd XML schema,
2//! for use with code generation.
3//! Attributes such as `any` or `anyAttribute` are not added.
4
5use roxmltree::{Document, Node};
6
7use crate::{
8    error::XmlError,
9    ext::{children_with_name, first_child_with_name_opt, int_attr, uint_attr, NodeExt},
10    XmlLoad,
11};
12
13#[derive(Debug)]
14/// Documentation object.
15pub struct Documentation {
16    /// Documentation node content.
17    pub contents: Option<String>,
18}
19
20impl<'input> XmlLoad<'input> for Documentation {
21    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
22        Ok(Self {
23            contents: node.text().map(|a| a.to_owned()),
24        })
25    }
26}
27
28#[derive(Debug)]
29/// Byte order for a value.
30pub enum ByteOrder {
31    /// Big endian.
32    BigEndian,
33    /// Little endian.
34    LittleEndian,
35}
36
37impl ByteOrder {
38    pub(crate) fn from_node(node: &Node<'_, '_>, attr: &str) -> Result<Option<Self>, XmlError> {
39        Ok(match node.attribute(attr) {
40            Some("LittleEndian") => Some(ByteOrder::LittleEndian),
41            Some("BigEndian") => Some(ByteOrder::BigEndian),
42            Some(r) => {
43                return Err(XmlError::other(
44                    node,
45                    &format!("Expected LittleEndian or BigEndian for {attr}, got {r}"),
46                ))
47            }
48            None => None,
49        })
50    }
51}
52
53#[derive(Debug)]
54/// Description of a type in an OPC-UA binary schema.
55pub struct TypeDescription {
56    /// Documentation object.
57    pub documentation: Option<Documentation>,
58    /// Type name.
59    pub name: String,
60    /// Default byte order.
61    pub default_byte_order: Option<ByteOrder>,
62}
63
64impl<'input> XmlLoad<'input> for TypeDescription {
65    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
66        Ok(Self {
67            documentation: first_child_with_name_opt(node, "Documentation")?,
68            name: node.try_attribute("Name")?.to_owned(),
69            default_byte_order: ByteOrder::from_node(node, "DefaultByteOrder")?,
70        })
71    }
72}
73
74#[derive(Debug)]
75/// Opaque type, these are stored as some other primitive type.
76pub struct OpaqueType {
77    /// Type description.
78    pub description: TypeDescription,
79    /// Fixed length in bits. Can be left out if the type has dynamic length.
80    pub length_in_bits: Option<i64>,
81    /// Whether the byte order is significant.
82    pub byte_order_significant: bool,
83}
84
85impl<'input> XmlLoad<'input> for OpaqueType {
86    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
87        Ok(Self {
88            description: TypeDescription::load(node)?,
89            length_in_bits: int_attr(node, "LengthInBits")?,
90            byte_order_significant: node.attribute("ByteOrderSignificant") == Some("true"),
91        })
92    }
93}
94
95#[derive(Debug)]
96/// Description of an enum value.
97pub struct EnumeratedValue {
98    /// Value documentation.
99    pub documentation: Option<Documentation>,
100    /// Enum value name.
101    pub name: Option<String>,
102    /// Numeric value.
103    pub value: Option<i64>,
104}
105impl<'input> XmlLoad<'input> for EnumeratedValue {
106    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
107        Ok(Self {
108            documentation: first_child_with_name_opt(node, "Documentation")?,
109            name: node.attribute("Name").map(|n| n.to_owned()),
110            value: int_attr(node, "Value")?,
111        })
112    }
113}
114
115#[derive(Debug)]
116/// Description of an enumerated type.
117pub struct EnumeratedType {
118    /// Base opaque type.
119    pub opaque: OpaqueType,
120    /// Possible enum variants.
121    pub variants: Vec<EnumeratedValue>,
122    /// Whether this is an option set, i.e. it can have multiple values at the same time.
123    pub is_option_set: bool,
124}
125
126impl<'input> XmlLoad<'input> for EnumeratedType {
127    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
128        Ok(Self {
129            opaque: OpaqueType::load(node)?,
130            variants: children_with_name(node, "EnumeratedValue")?,
131            is_option_set: node.attribute("IsOptionSet") == Some("true"),
132        })
133    }
134}
135
136#[derive(Debug)]
137/// Switch operand.
138pub enum SwitchOperand {
139    /// Equality operator.
140    Equals,
141    /// Greater than operator.
142    GreaterThan,
143    /// Less than operator.
144    LessThan,
145    /// Greater than or equal to operator.
146    GreaterThanOrEqual,
147    /// Less than or eqaul to operator.
148    LessThanOrEqual,
149    /// Not equal operator.
150    NotEqual,
151}
152
153impl SwitchOperand {
154    pub(crate) fn from_node(node: &Node<'_, '_>, attr: &str) -> Result<Option<Self>, XmlError> {
155        Ok(match node.attribute(attr) {
156            Some("Equals") => Some(SwitchOperand::Equals),
157            Some("GreaterThan") => Some(SwitchOperand::GreaterThan),
158            Some("LessThan") => Some(SwitchOperand::LessThan),
159            Some("GreaterThanOrEqual") => Some(SwitchOperand::GreaterThanOrEqual),
160            Some("LessThanOrEqual") => Some(SwitchOperand::LessThanOrEqual),
161            Some("NotEqual") => Some(SwitchOperand::NotEqual),
162            Some(r) => {
163                return Err(XmlError::other(
164                    node,
165                    &format!("Unexpected value for {attr}: {r}"),
166                ))
167            }
168            _ => None,
169        })
170    }
171}
172
173#[derive(Debug)]
174/// Type of a struct field.
175pub struct FieldType {
176    /// Field documentation.
177    pub documentation: Option<Documentation>,
178    /// Field name.
179    pub name: String,
180    /// Name of this fields type.
181    pub type_name: Option<String>,
182    /// Fixed field length.
183    pub length: Option<u64>,
184    /// Name of field storing this fields length.
185    pub length_field: Option<String>,
186    /// Whether the length is in bytes or number of elements.
187    pub is_length_in_bytes: bool,
188    /// Field to switch on.
189    pub switch_field: Option<String>,
190    /// Value to compare to.
191    pub switch_value: Option<u64>,
192    /// Switch operand.
193    pub switch_operand: Option<SwitchOperand>,
194    /// Field terminator.
195    pub terminator: Option<String>,
196}
197
198impl<'input> XmlLoad<'input> for FieldType {
199    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
200        Ok(Self {
201            documentation: first_child_with_name_opt(node, "Documentation")?,
202            name: node.try_attribute("Name")?.to_owned(),
203            type_name: node.attribute("TypeName").map(|a| a.to_owned()),
204            length: uint_attr(node, "Length")?,
205            length_field: node.attribute("LengthField").map(|a| a.to_owned()),
206            is_length_in_bytes: node.attribute("IsLengthInBytes") == Some("true"),
207            switch_field: node.attribute("SwitchField").map(|a| a.to_owned()),
208            switch_value: uint_attr(node, "SwitchValue")?,
209            switch_operand: SwitchOperand::from_node(node, "SwitchOperand")?,
210            terminator: node.attribute("Terminator").map(|a| a.to_owned()),
211        })
212    }
213}
214
215#[derive(Debug)]
216/// Description of a structured type.
217pub struct StructuredType {
218    /// Type description.
219    pub description: TypeDescription,
220    /// List of fields, the order is significant.
221    pub fields: Vec<FieldType>,
222    /// Base type.
223    pub base_type: Option<String>,
224}
225
226impl<'input> XmlLoad<'input> for StructuredType {
227    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
228        Ok(Self {
229            description: TypeDescription::load(node)?,
230            fields: children_with_name(node, "Field")?,
231            base_type: node.attribute("BaseType").map(|t| t.to_owned()),
232        })
233    }
234}
235
236#[derive(Debug)]
237/// Import types from some other schema.
238pub struct ImportDirective {
239    /// Namespace to import.
240    pub namespace: Option<String>,
241    /// Location of import.
242    pub location: Option<String>,
243}
244
245impl<'input> XmlLoad<'input> for ImportDirective {
246    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
247        Ok(Self {
248            namespace: node.attribute("Namespace").map(|a| a.to_owned()),
249            location: node.attribute("Location").map(|a| a.to_owned()),
250        })
251    }
252}
253
254#[derive(Debug)]
255/// Item in the outer type dictionary.
256pub enum TypeDictionaryItem {
257    /// An opaque type represented via some primitive type.
258    Opaque(OpaqueType),
259    /// An enum.
260    Enumerated(EnumeratedType),
261    /// A structured type.
262    Structured(StructuredType),
263}
264
265#[derive(Debug)]
266/// The outer type dictionary containing the types in an OPC UA BSD file.
267pub struct TypeDictionary {
268    /// Type dictionary documentation.
269    pub documentation: Option<Documentation>,
270    /// List of imports.
271    pub imports: Vec<ImportDirective>,
272    /// List of types defined in this schema.
273    pub elements: Vec<TypeDictionaryItem>,
274    /// Target OPC-UA namespace, required.
275    pub target_namespace: String,
276    /// Default byte order for types in this schema.
277    pub default_byte_order: Option<ByteOrder>,
278}
279
280impl<'input> XmlLoad<'input> for TypeDictionary {
281    fn load(node: &Node<'_, 'input>) -> Result<Self, XmlError> {
282        Ok(Self {
283            documentation: first_child_with_name_opt(node, "Documentation")?,
284            imports: children_with_name(node, "Import")?,
285            elements: node
286                .children()
287                .filter_map(|e| match e.tag_name().name() {
288                    "OpaqueType" => Some(OpaqueType::load(&e).map(TypeDictionaryItem::Opaque)),
289                    "EnumeratedType" => {
290                        Some(EnumeratedType::load(&e).map(TypeDictionaryItem::Enumerated))
291                    }
292                    "StructuredType" => {
293                        Some(StructuredType::load(&e).map(TypeDictionaryItem::Structured))
294                    }
295                    _ => None,
296                })
297                .collect::<Result<Vec<_>, _>>()?,
298            target_namespace: node.try_attribute("TargetNamespace")?.to_owned(),
299            default_byte_order: ByteOrder::from_node(node, "DefaultByteOrder")?,
300        })
301    }
302}
303
304/// Load an OPC-UA BSD file from a string, `document` is the content of an OPC-UA BSD file.
305pub fn load_bsd_file(document: &str) -> Result<TypeDictionary, XmlError> {
306    let document = Document::parse(document).map_err(|e| XmlError {
307        span: 0..1,
308        error: crate::error::XmlErrorInner::Xml(e),
309    })?;
310    TypeDictionary::load(&document.root().first_child_with_name("TypeDictionary")?)
311}