Problem Overview
SiOME (Siemens OPC UA Modelling Editor) version 2.7.2 introduced a regression in the way it serializes the Value child element of OPC UA Variable nodes when a NodeSet2 namespace is exported. In the affected build, clearing an assigned value from a Variable node no longer causes the Value element to be omitted from the exported XML. Instead, the export emits a self-closing empty element, written as <Value/>, regardless of whether the original value was a String, Boolean, Int32, or LocalizedText.
This output violates the assumption made by several common OPC UA XML consumers — most notably the open62541 project's nodeset_compiler.py and the OPC Foundation .NET stack's Nodeset.Import function — that the Value element is either absent or contains a valid Variant body. Both consumers fail to deserialize the XML and surface error messages that do not obviously point at the empty element, so the failure presents as a generic build or import error rather than a schema defect.
Affected Versions and Environment
| SiOME Version | Behavior on cleared Variable | Status |
|---|---|---|
| 2.3.10 and earlier | Omits Value element when no value is set |
Reference behavior |
| 2.7.2 | Emits empty <Value/> self-closing tag |
Defective (current at time of report) |
SiOME is distributed as a standalone Java application used to design OPC UA information models and export them to NodeSet2 XML. The export workflow is identical in both versions: File → Export → Namespace, select the target namespace, save the resulting .xml or .xml2 file. No settings toggle exists in the editor to control whether the Value element is omitted on empty Variables, so the editor version is the only practical control surface.
Reproducing the Empty Value Tag Defect
- Launch SiOME 2.7.2 and open or create an OPC UA project.
- In the Information Model view, add a new Variable node of DataType
String. - Open the Attributes pane and assign any non-empty string to the
Valueattribute (for example,Hello). - Save the project, return to the Attributes pane, and delete the assigned string so the
Valueattribute is empty. - Select File → Export → Namespace and save the XML to disk.
- Open the exported file in a text editor and inspect the Variable node.
Reference output (SiOME 2.3.10):
<UAVariable NodeId="ns=1;s=MyStringVar" BrowseName="MyStringVar" DataType="String">
<DisplayName>MyStringVar</DisplayName>
</UAVariable>
Defective output (SiOME 2.7.2):
<UAVariable NodeId="ns=1;s=MyStringVar" BrowseName="MyStringVar" DataType="String">
<DisplayName>MyStringVar</DisplayName>
<Value/>
</UAVariable>
The <Value/> element appears even though no Variant body is encoded. The same behavior has been confirmed for Boolean, Int32, and LocalizedText DataTypes in addition to String. Arrays and structures are not affected by the regression as written, but the empty-body pattern is the same for any DataType whose default Variant body is empty.
Root Cause Analysis
NodeSet2 XML, as defined by the OPC UA specification (Part 6 — Mappings), allows a UAVariable to carry a Value child element that wraps a Variant encoded in XML. The XML schema permits the element to be present without a body, but the practical implementation in open62541's nodeset_compiler.py and in the OPC Foundation's .NET XML deserializer treats the element as a required wrapper around a Variant payload. An empty self-closing element passes XML schema validation but fails the deserializer's Variant read step, which expects a scalar, list, or array child element.
The behavioral change between SiOME 2.3.10 and 2.7.2 appears to originate in a refactor of the Java object that maps the in-memory Variable node to its XML representation. The legacy code path constructed the Value element only when an actual value existed; the current code path appears to always emit the element to preserve a stable attribute order, but fails to omit or default-fill it when the value is null. Because the change was not announced in the SiOME release notes, downstream consumers are exposed to the regression without warning.
Siemens has not published an official statement or changelog entry classifying the behavior as a bug at the time of writing. Treat the 2.3.10 output as the reference baseline for NodeSet2 export, and the 2.7.2 output as a regression pending a fix.
Impact: Parser and Importer Failures
The empty <Value/> element manifests as a hard error in two widely deployed consumers:
-
open62541 nodeset_compiler.py — The Python tool
tools/nodeset_compiler/nodeset_compiler.pyfrom the open62541 project reads NodeSet2 XML to generate C code and type definitions. Its Variant decoder expects a child payload inside theValueelement and raises aValueErrorwhen noListOfExtensionObject, scalar, or array child is found. The error is reported as "No Variant body found" and aborts the entire compilation, even if the rest of the NodeSet is valid. -
OPC Foundation .NET Nodeset.Import — The
Opc.Ua.Export.Nodesetnamespace in the official OPC UA .NET reference stack exposes a staticNodeset.Importmethod. The XML deserializer expects aValuechild of typeVariantand throws aServiceResultExceptionwith status codeBadDecodingErrorwhen the element is empty. The same defect is tracked upstream at dotnet/runtime#67622. -
Generic fast-xml-parser pipelines — Projects using fast-xml-parser to consume NodeSet2 output have a related issue tracked at fast-xml-parser#230: the default
tagValueProcessorskips empty nodes, so an emptyValueelement is silently dropped instead of converted to a null Variant. The behavior is less destructive but still breaks round-trip semantics — the value is not preserved through a parse-and-reserialize cycle.
Each of these failures cascades: a single empty <Value/> element in a multi-megabyte NodeSet file can block an entire build pipeline, an entire CI run, or an entire server bootstrap. The failure mode is also stealthy: the XML still validates against the NodeSet2 XSD, so a basic CI gate that relies on schema validation alone will not catch the defect.
Workarounds and Mitigations
Because SiOME does not expose a setting to control Value element emission, workarounds must be applied to the generated XML or to the build pipeline. Choose the option that best matches your team's ability to modify the editor, the XML, or the consumer.
-
Pin SiOME to 2.3.10. The most reliable mitigation is to retain the older editor version for projects whose consumers cannot tolerate empty
Valueelements. Lock the version in your asset repository and document the version in the project's README. New SiOME installs should be quarantined behind a CI gate that fails the build if the version differs from the pinned value without an explicit override in the change request. -
Post-process the exported XML. Add a build step that strips empty
<Value/>elements before downstream consumption. A portable approach uses lxml in Python:
from lxml import etree
NS = {"ua": "http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"}
def strip_empty_values(path_in: str, path_out: str) -> int:
tree = etree.parse(path_in)
root = tree.getroot()
removed = 0
for value in root.findall(".//ua:Value", NS):
if len(value) == 0 and (value.text is None or not value.text.strip()):
value.getparent().remove(value)
removed += 1
tree.write(path_out, xml_declaration=True, encoding="UTF-8", standalone=True)
return removed
if __name__ == "__main__":
import sys
n = strip_empty_values(sys.argv[1], sys.argv[2])
print(f"Stripped {n} empty <Value/> elements")
The same operation can be expressed in XSLT 1.0 for projects that prefer a declarative pipeline:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ua="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
exclude-result-prefixes="ua">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ua:Value[not(node()) and not(normalize-space(text()))]"/>
</xsl:stylesheet>
-
Patch the consumer. If you control the parser, extend the Variant decoder to treat an empty
Valueelement as a null Variant. This is typically a one-line guard: check for the absence of child elements and short-circuit the read path. For the OPC Foundation .NET stack, monitor dotnet/runtime#67622 for the official fix and align your local patch with the upstream resolution once it lands. -
Re-validate the model in SiOME before export. Before exporting, ensure that no Variable node has an empty
Valueattribute. Right-click each Variable, clear the attribute explicitly, save the project, and re-open it to confirm the cleared state. This eliminates the trigger condition but does not prevent re-introduction if a teammate re-assigns and then clears a value in a future revision. - Add a CI gate. Run a grep-based smoke test in CI to fail any build that contains the empty-element pattern. The check is intentionally trivial to read and can be embedded directly in a shell script, Makefile, or Jenkins pipeline step:
#!/usr/bin/env bash
set -euo pipefail
PATTERN='<Value/>'
for f in build/nodeset*.xml; do
if grep -nF "${PATTERN}" "${f}" >/dev/null 2>&1; then
echo "ERROR: ${f} contains empty ${PATTERN} elements (SiOME 2.7.2 regression)"
grep -nF "${PATTERN}" "${f}" | head -20
exit 1
fi
done
echo "OK: no empty <Value/> elements found in NodeSet2 exports"
Downstream Tool Compatibility Matrix
| Consumer | Tolerates empty <Value/>? |
Workaround available? | Notes |
|---|---|---|---|
open62541 nodeset_compiler.py
|
No | XML post-process | Hard fail with "No Variant body found" |
OPC Foundation .NET Nodeset.Import
|
No | XML post-process or pin SiOME 2.3.10 |
BadDecodingError; tracked at dotnet/runtime#67622
|
| fast-xml-parser default pipeline | Partial | Custom tagValueProcessor
|
Empty element dropped silently — round-trip broken (issue #230) |
| UA Java stack (Eclipse Milo) NodeSet loader | Partial | XML post-process recommended | Reads empty Value as null Variant in newer versions; verify against your build |
| Siemens S7 / TIA Portal OPC UA server import | Unknown — vendor test required | Strip empty values before import | Use the SiOME/STEP 7 integration described in the Siemens OPC UA SetDataValue documentation |
| Node-OPCUA (Node.js) | No | XML post-process | Same Xml2Json Variant decoding path |
Even consumers that technically tolerate an empty element are at risk of semantic drift: an empty Value for a String Variable is not the same as a null Variant in every OPC UA server's Read service implementation. Pin the producer version and the consumer version together to keep behavior stable across releases.
Reporting the Issue to Siemens Support
SiOME does not appear in the Siemens Support entry page as a first-class product, which complicates ticket routing. The official escalation channels are:
- The general Siemens Industry Online Support feedback form referenced from support entry 109755133. Select the product family that owns SiOME and attach the NodeSet2 export with a minimal reproducer.
- Your regional Siemens representative, with a request to route the ticket to the OPC UA tooling team. Provide the SiOME build number from Help → About, the Java runtime version, and the full export diff between 2.3.10 and 2.7.2.
- The OPC Foundation Companion Specification working group, if the defect is reproduced inside a published companion specification. This routes the issue to the team that maintains the reference NodeSet2 schema and the .NET deserializer.
When filing, include the two XML fragments shown in the reproduction section above and the exact open62541 and .NET error stack traces. Mention dotnet/runtime#67622 and fast-xml-parser#230 as parallel upstream issues so the Siemens support engineer can correlate the symptoms.
OPC UA Specification Context for Variable Value Element
The OPC UA specification (Part 6 — Mappings) defines the UAVariable type and allows a Value attribute of type Variant. The corresponding NodeSet2 XML schema declares the element as optional with a minimum occurrence of 0 and a maximum of 1. A self-closing <Value/> element is technically schema-valid in W3C XML Schema terms because the element's type permits an empty content model after defaulting and nillability rules are applied.
The practical interoperability gap is therefore at the deserializer level, not the schema level. Consumers must decide whether to treat an empty body as a null Variant, an unset value, or an error. The OPC Foundation .NET reference stack chose error; open62541 chose error; Eclipse Milo's behavior depends on the build. There is no normative statement in the specification that resolves the ambiguity, which is why this defect slips past formal validation and only surfaces in production. When designing your own NodeSet consumers, document explicitly which interpretation your code applies. A short comment in the Variant decoder — "treat empty Value as null Variant, raise on malformed body otherwise" — saves the next engineer hours of debugging.
Verification and Regression Testing
Add the following checks to your NodeSet2 export pipeline to catch this defect and similar future regressions at the source rather than at the consumer:
-
Schema validation. Validate the exported XML against the NodeSet2 XSD bundled with the OPC UA reference stack. This catches structural errors but, as noted, will not catch the empty
Valuedefect. - Round-trip test. Load the exported XML back into SiOME and re-export. The two files must be byte-identical apart from whitespace. Any divergence indicates a serialization regression.
-
Consumer smoke test. Run the exported XML through your downstream consumer's import path. For
open62541, that ispython nodeset_compiler.py --types nodeset.xml. For the .NET stack, that isNodeset.Import(nodesetPath). Both must complete without exception. -
CI gate. Add the
grep -L "<Value/>" build/nodeset.xmlcheck to the build script. The build fails if the pattern is found, and the failure message includes the file path and line number for fast debugging. -
Version pin. Capture the SiOME version in
build.propertiesor equivalent. Reject builds where the version differs from the pinned value without an explicit override in the change request.
Troubleshooting Matrix
| Symptom | Likely Cause | Action |
|---|---|---|
| Build fails immediately after SiOME upgrade with no other change | Empty <Value/> in NodeSet XML |
Run XML post-processor or pin SiOME 2.3.10 |
.NET Nodeset.Import throws BadDecodingError on previously valid file |
Empty Value element |
Strip empty values before import; track dotnet/runtime#67622 |
open62541 nodeset_compiler reports "No Variant body found"
|
Empty Value element |
Post-process XML or pin SiOME 2.3.10 |
| OPC UA server starts but reads empty strings where a value was expected | Round-trip mismatch from fast-xml-parser skipping empty nodes |
Custom tagValueProcessor that returns null for empty elements (issue #230) |
| Schema validation passes but consumer fails | Schema-tolerant but deserializer-strict | Add a CI gate that greps for the empty Value pattern |
| Identical XML produces different results on different consumers | Vendor-specific Variant decoding rules | Document the expected interpretation per consumer; align producer and consumer versions |
FAQ
Which SiOME version first introduced the empty <Value/> export defect?
The defect first appears in SiOME 2.7.2. SiOME 2.3.10 and earlier versions omit the Value element entirely when a Variable's value is cleared, which is the behavior that open62541's nodeset_compiler.py and the OPC Foundation .NET Nodeset.Import expect.
Why does the empty <Value/> tag break open62541's nodeset_compiler?
nodeset_compiler.py reads the Value child as a Variant and requires a scalar, list, or array body. An empty self-closing element passes XML schema parsing but raises a ValueError ("No Variant body found") during Variant decoding, which aborts the entire compilation.
How do I report this defect to Siemens if SiOME is not in the support portal?
Use the general Siemens Industry Online Support feedback form linked from entry 109755133, or escalate through your local Siemens representative. Attach the SiOME build number from Help → About, the two XML fragments (2.3.10 vs 2.7.2), and the consumer stack traces.
Is the empty <Value/> element technically valid against the OPC UA NodeSet2 schema?
Yes — the W3C XML Schema permits an empty body for the optional Value element. The interoperability failure is at the deserializer level, not the schema level. Both the OPC Foundation .NET stack and open62541 chose to treat empty bodies as errors; the OPC UA specification does not mandate either behavior.
What is the fastest workaround when I cannot change the consumer code?
Post-process the exported XML with the lxml snippet or the XSLT 1.0 stylesheet shown in the Workarounds and Mitigations section to strip empty <Value/> elements before passing the file to the consumer. Add a grep-based CI gate to prevent re-introduction.