Skip to main content

opcua_types/xml/
mod.rs

1//! Enabled with the "xml" feature.
2//!
3//! Core utilities for working with decoding OPC UA types from NodeSet2 XML files.
4
5mod builtins;
6mod encoding;
7
8pub use crate::{Context, EncodingResult, Error};
9pub use encoding::{
10    from_bytes, to_bytes, to_string, XmlDecodable, XmlEncodable, XmlReadExt, XmlType, XmlWriteExt,
11};
12pub use opcua_xml::{XmlStreamReader, XmlStreamWriter};
13
14use std::{
15    io::{Cursor, Read},
16    str::{from_utf8, FromStr},
17};
18
19pub use opcua_xml::schema::opc_ua_types::XmlElement;
20use tracing::warn;
21
22use crate::{
23    Array, ByteString, ExpandedNodeId, ExtensionObject, LocalizedText, NodeId, QualifiedName,
24    StatusCode, UninitializedIndex, Variant, VariantScalarTypeId,
25};
26
27impl From<UninitializedIndex> for Error {
28    fn from(value: UninitializedIndex) -> Self {
29        Self::decoding(format!("Uninitialized index {}", value.0))
30    }
31}
32
33fn mk_node_id(node_id: &opc_ua_types::NodeId, ctx: &Context<'_>) -> Result<NodeId, Error> {
34    let Some(idf) = &node_id.identifier else {
35        return Ok(NodeId::null());
36    };
37    let Ok(mut parsed) = NodeId::from_str(idf) else {
38        return Err(Error::decoding(format!("Invalid node ID: {idf}")));
39    };
40    parsed.namespace = ctx.resolve_namespace_index(parsed.namespace)?;
41    Ok(parsed)
42}
43
44use opcua_xml::{
45    events::Event,
46    schema::opc_ua_types::{self, Variant as XmlVariant},
47};
48
49/// Enter the first tag in the stream, returning `true` if a start tag was found.
50pub(crate) fn enter_first_tag(
51    stream: &mut XmlStreamReader<&mut dyn Read>,
52) -> EncodingResult<Option<String>> {
53    loop {
54        match stream.next_event()? {
55            Event::Start(s) => {
56                let local_name = s.local_name();
57                let name = from_utf8(local_name.as_ref())?;
58                return Ok(Some(name.to_owned()));
59            }
60            Event::End(_) | Event::Eof | Event::Empty(_) => return Ok(None),
61            _ => (),
62        }
63    }
64}
65
66fn mk_extension_object(
67    val: &opc_ua_types::ExtensionObject,
68    ctx: &Context<'_>,
69) -> EncodingResult<ExtensionObject> {
70    let Some(body) = &val.body else {
71        return Ok(ExtensionObject::null());
72    };
73    let Some(data) = &body.raw else {
74        return Ok(ExtensionObject::null());
75    };
76    let Some(type_id) = &val.type_id else {
77        return Err(Error::decoding("Extension object missing type ID"));
78    };
79    let node_id = mk_node_id(type_id, ctx)?;
80    let mut cursor = Cursor::new(data.as_bytes());
81    let mut stream = XmlStreamReader::new(&mut cursor as &mut dyn Read);
82    // Read the entry tag, as this is how extension objects are parsed
83    if let Some(name) = enter_first_tag(&mut stream)? {
84        ctx.load_from_xml(&node_id, &mut stream, &name)
85    } else {
86        Ok(ExtensionObject::null())
87    }
88}
89
90impl Variant {
91    /// Create a Variant value from a NodeSet2 variant object.
92    /// Note that this is different from the `FromXml` implementation of `Variant`,
93    /// which accepts an untyped XML node.
94    pub fn from_nodeset(val: &XmlVariant, ctx: &Context<'_>) -> EncodingResult<Variant> {
95        Ok(match val {
96            XmlVariant::Boolean(v) => (*v).into(),
97            XmlVariant::ListOfBoolean(v) => v.into(),
98            XmlVariant::SByte(v) => (*v).into(),
99            XmlVariant::ListOfSByte(v) => v.into(),
100            XmlVariant::Byte(v) => (*v).into(),
101            XmlVariant::ListOfByte(v) => v.into(),
102            XmlVariant::Int16(v) => (*v).into(),
103            XmlVariant::ListOfInt16(v) => v.into(),
104            XmlVariant::UInt16(v) => (*v).into(),
105            XmlVariant::ListOfUInt16(v) => v.into(),
106            XmlVariant::Int32(v) => (*v).into(),
107            XmlVariant::ListOfInt32(v) => v.into(),
108            XmlVariant::UInt32(v) => (*v).into(),
109            XmlVariant::ListOfUInt32(v) => v.into(),
110            XmlVariant::Int64(v) => (*v).into(),
111            XmlVariant::ListOfInt64(v) => v.into(),
112            XmlVariant::UInt64(v) => (*v).into(),
113            XmlVariant::ListOfUInt64(v) => v.into(),
114            XmlVariant::Float(v) => (*v).into(),
115            XmlVariant::ListOfFloat(v) => v.into(),
116            XmlVariant::Double(v) => (*v).into(),
117            XmlVariant::ListOfDouble(v) => v.into(),
118            XmlVariant::String(v) => v.clone().into(),
119            XmlVariant::ListOfString(v) => v.into(),
120            XmlVariant::DateTime(v) => (*v).into(),
121            XmlVariant::ListOfDateTime(v) => v.into(),
122            XmlVariant::Guid(v) => (*v).into(),
123            XmlVariant::ListOfGuid(v) => v.into(),
124            XmlVariant::ByteString(b) => ByteString::from_base64(b.trim())
125                .unwrap_or_else(|| {
126                    warn!("Invalid byte string: {b}");
127                    ByteString::null()
128                })
129                .into(),
130            XmlVariant::ListOfByteString(v) => v
131                .iter()
132                .map(|b| {
133                    ByteString::from_base64(b.trim()).unwrap_or_else(|| {
134                        warn!("Invalid byte string: {b}");
135                        ByteString::null()
136                    })
137                })
138                .collect::<Vec<_>>()
139                .into(),
140            XmlVariant::XmlElement(vec) => Variant::XmlElement(
141                vec.iter()
142                    .map(|v| v.to_string().trim().to_owned())
143                    .collect::<String>()
144                    .into(),
145            ),
146            XmlVariant::ListOfXmlElement(vec) => Variant::Array(Box::new(Array {
147                value_type: VariantScalarTypeId::XmlElement,
148                values: vec
149                    .iter()
150                    .map(|v| {
151                        Variant::XmlElement(
152                            v.iter()
153                                .map(|vv| vv.to_string().trim().to_string())
154                                .collect::<String>()
155                                .into(),
156                        )
157                    })
158                    .collect(),
159                dimensions: None,
160            })),
161            XmlVariant::QualifiedName(q) => QualifiedName::new(
162                ctx.resolve_namespace_index(q.namespace_index.unwrap_or(0))?,
163                q.name.as_deref().unwrap_or("").trim(),
164            )
165            .into(),
166            XmlVariant::ListOfQualifiedName(v) => v
167                .iter()
168                .map(|q| {
169                    Ok(QualifiedName::new(
170                        ctx.resolve_namespace_index(q.namespace_index.unwrap_or(0))?,
171                        q.name.as_deref().unwrap_or("").trim(),
172                    ))
173                })
174                .collect::<Result<Vec<QualifiedName>, Error>>()?
175                .into(),
176            XmlVariant::LocalizedText(l) => LocalizedText::new(
177                l.locale.as_deref().unwrap_or("").trim(),
178                l.text.as_deref().unwrap_or("").trim(),
179            )
180            .into(),
181            XmlVariant::ListOfLocalizedText(v) => v
182                .iter()
183                .map(|l| {
184                    LocalizedText::new(
185                        l.locale.as_deref().unwrap_or("").trim(),
186                        l.text.as_deref().unwrap_or("").trim(),
187                    )
188                })
189                .collect::<Vec<_>>()
190                .into(),
191            XmlVariant::NodeId(node_id) => mk_node_id(node_id, ctx)?.into(),
192            XmlVariant::ListOfNodeId(v) => v
193                .iter()
194                .map(|node_id| mk_node_id(node_id, ctx))
195                .collect::<Result<Vec<_>, _>>()?
196                .into(),
197            XmlVariant::ExpandedNodeId(node_id) => {
198                ExpandedNodeId::new(mk_node_id(node_id, ctx)?).into()
199            }
200            XmlVariant::ListOfExpandedNodeId(v) => v
201                .iter()
202                .map(|node_id| Ok(ExpandedNodeId::new(mk_node_id(node_id, ctx)?)))
203                .collect::<Result<Vec<_>, Error>>()?
204                .into(),
205            XmlVariant::ExtensionObject(val) => mk_extension_object(val, ctx)?.into(),
206            XmlVariant::ListOfExtensionObject(v) => v
207                .iter()
208                .map(|val| mk_extension_object(val, ctx))
209                .collect::<Result<Vec<_>, _>>()?
210                .into(),
211            XmlVariant::Variant(variant) => {
212                let inner = Variant::from_nodeset(variant, ctx)?;
213                Variant::Variant(Box::new(inner))
214            }
215            XmlVariant::ListOfVariant(vec) => Variant::Array(Box::new(Array {
216                value_type: VariantScalarTypeId::Variant,
217                values: vec
218                    .iter()
219                    .map(|v| Ok(Variant::Variant(Box::new(Variant::from_nodeset(v, ctx)?))))
220                    .collect::<Result<Vec<_>, Error>>()?,
221                dimensions: None,
222            })),
223            XmlVariant::StatusCode(status_code) => StatusCode::from(status_code.code).into(),
224            XmlVariant::ListOfStatusCode(vec) => vec
225                .iter()
226                .map(|v| StatusCode::from(v.code))
227                .collect::<Vec<_>>()
228                .into(),
229        })
230    }
231}