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