Files
UA-.NETStandard/tests/Opc.Ua.SourceGeneration.Tests/PlaceholderReferenceGeneratorTests.cs
Marc Schier d764161a53 Extract core/types fixes for schemas, replay, and source generation (#4801)
# Description

Extract the reusable core/types layer from #4462 without including
UaLens or the other planned extraction layers.

- Preserve null versus empty in `ByteString.Copy`, and provide
older-framework path, random-byte, and SHA-256 convenience APIs.
- Encode abstract numeric structure fields (`Number`, `Integer`, and
`UInteger`) as Variants in JSON, XSD, and BSD schemas, with regression
coverage and schema documentation.
- Add opt-in complete-file validation for finalized PCAP replay while
retaining the existing tolerant reader default.
- Keep placeholder references on generated type templates, not runtime
instances, while preserving ordinary external references.

**Initial extraction:** commit
`8881e096fb5e4eaa36f3c13f8caa57733373bd19` preserves the assigned
changes from source `390c4a7a51b71da40caec2d22ef74518bbe670ce`, based on
pinned `master` commit `5b03e9afa492cdb36049ee52ae844f630f89d8db`: 20
exact full-file extractions and only the generic replay paragraph in
`docs/Diagnostics.md`. The UaLens capture-window paragraph remains with
the source PR. No UaLens projects, UI, native-rendering paths,
dependencies, or workflows are included.

**Review follow-up:** `814c0f2dc6056ea5eb898611565b3e96cfa65104` fixes
direct-placeholder references that bypassed filtering through
`TargetPath` and emitted an undefined `forInstance` guard in
instance-only factories. Filtering now follows target resolution.
Compilation/reference-shape regressions cover ObjectType/VariableType,
both placeholder rules, and direct-child/external targets;
`docs/NodeManagers.md` documents the behavior. The review thread was
resolved after publication and verification.

**CI follow-up:** `93ec3928bd5a9cb2ea86110085e79131db17d443` addresses
both causes found in [CI run
38700770996](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38700770996):

- `MODELGEN012` treated an identical payload-less transitive dependency
declaration as a competing model producer after the SDK Programs model
appeared in newer `master`. Matching closure rows no longer emit that
diagnostic. Producer ordering is unchanged; actual producers, differing
declarations, and malformed payload handling retain their existing
diagnostics. New regression cases exercise both reference orders, and
`docs/ModelDependencies.md` records the distinction.
- The queue-fairness test sampled OS scheduling and failed on macOS.
This reuses the exact one-file correction from
`84d2c34d3902bfe700a87f6c30708b435500878f` in #4765: hold the existing
heavy-owner publication hook, prove concurrent work-conserving progress,
then assert exact recovered 1:3 service, all 960 unique requests, and
complete queue/token/owner/lease cleanup. No production scheduler or
fairness bound was changed.

The PR now changes 26 paths. Its pinned extraction ancestry is
preserved: no rebase, force push, or merge of newer `master` was
performed. This remains the bottom of native stack 4807, ordered 4801 ->
4802 -> 4803 -> 4804 -> 4806.

### Current CI-repair validation

Windows, existing .NET SDK **10.0.401**, **Release**, with analyzers
enabled. CI tested merge `9a2d252793da12aeb99ed379dfc9b03e89c912b7`
(master `d40843c23a5fe8116ffe380cf937d88759f5218b` plus PR head
`814c0f2dc6056ea5eb898611565b3e96cfa65104`), which includes newer tests
absent from the pinned extraction branch. That exact merge was
reproduced in a disposable detached worktree, then the proposed patch
was applied. Git patch-id verified that the tested patch and committed
branch patch were identical
(`b7ed3ba2b61d1862825dd33d93c50c64d3a759ea`).

Before the fix, all four failing client-accessor cases reproduced
`MODELGEN012`, and both new matching-reexport cases failed. After the
fix:

| Context / scope | Framework | Passed | Failed | Skipped |
| --- | --- | ---: | ---: | ---: |
| Exact CI merge + patch: client-accessor and dependency fixtures |
net10.0 | 32 | 0 | 0 |
| Same fixtures with CI coverage instrumentation | net10.0 | 32 | 0 | 0
|
| Exact CI merge + patch: entire SourceGeneration.Tests project |
net10.0 | 295 | 0 | 0 |
| Exact CI merge + patch: entire SourceGeneration.Tests project | net48
| 295 | 0 | 0 |
| Preserved PR branch: dependency and placeholder fixtures | net10.0 |
30 | 0 | 0 |
| Preserved PR branch: dependency and placeholder fixtures | net48 | 30
| 0 | 0 |
| Preserved PR branch: entire FairRequestQueueTests fixture, with
coverage | net10.0 | 93 | 0 | 0 |
| Preserved PR branch: entire FairRequestQueueTests fixture | net48 | 93
| 0 | 0 |
| Exact CI merge + patch: entire FairRequestQueueTests fixture, with
coverage | net10.0 | 93 | 0 | 0 |

All nine commands exited **0**. The repository TRX verdict helpers
verified **993 passing test executions, 0 failures, and 0 fixture
setup/teardown failures** across these overlapping runs. No
build/analyzer warnings were reported. Normal restore resolved missing
assets/target entries in the new checkout and when switching TFMs; no
dependency declaration or SDK pin was changed.

Exact successful test argument sets follow. Variables abbreviate the
executable, project paths, and results directory only; output was also
captured to separate local logs.

```powershell
$env:DOTNET_ROOT = 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk'
$env:PATH = "$env:DOTNET_ROOT;$env:PATH"
$dotnet = "$env:DOTNET_ROOT\dotnet.exe"
$generator = 'tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj'
$coreTests = 'tests\Opc.Ua.Core.Tests\Opc.Ua.Core.Tests.csproj'
$results = 'C:\Users\mschier\.copilot\session-state\8f497e89-7fc2-4f48-880b-4803d97c1c12\files\ci-validation'

# In the disposable exact-CI-merge checkout, with the identical proposed patch applied:
Set-Location 'D:\Temp\ua-ci4801-8f497e89'

# Passed: 32, Failed: 0, Skipped: 0
& $dotnet test $generator --no-restore --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true --filter 'FullyQualifiedName~ClientAccessorGeneratorTests|FullyQualifiedName~ModelDependencyScannerTests' --logger 'trx;LogFileName=ci-metadata-green-net10.trx' --results-directory $results --verbosity minimal

# Passed: 295, Failed: 0, Skipped: 0; reuses the analyzer-enabled build above
& $dotnet test $generator --no-build --no-restore --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 --logger 'trx;LogFileName=ci-sourcegeneration-merge-net10.trx' --results-directory $results --verbosity quiet

# Passed: 295, Failed: 0, Skipped: 0
& $dotnet test $generator --configuration Release --framework net48 -p:CustomTestTarget=net48 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true --logger 'trx;LogFileName=ci-sourcegeneration-merge-net48.trx' --results-directory $results --verbosity minimal

# Passed: 93, Failed: 0, Skipped: 0
& $dotnet test $coreTests --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true -p:CollectCoverage=true --collect 'XPlat Code Coverage' --settings 'tests\coverlet.runsettings.xml' --filter 'FullyQualifiedName~FairRequestQueueTests' --logger 'trx;LogFileName=ci-fairqueue-merge-net10.trx' --results-directory $results --verbosity minimal

# Passed: 32, Failed: 0, Skipped: 0
& $dotnet test $generator --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true -p:CollectCoverage=true --collect 'XPlat Code Coverage' --settings 'tests\coverlet.runsettings.xml' --filter 'FullyQualifiedName~ClientAccessorGeneratorTests|FullyQualifiedName~ModelDependencyScannerTests' --logger 'trx;LogFileName=ci-metadata-coverage-net10.trx' --results-directory $results --verbosity minimal

# In the preserved PR branch worktree:
Set-Location 'D:\git\copilot-worktrees\UA-wot-d3-final\marcschier-congenial-umbrella'

# Passed: 30, Failed: 0, Skipped: 0
& $dotnet test $generator --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true --filter 'FullyQualifiedName~ModelDependencyScannerTests|FullyQualifiedName~PlaceholderReferenceGeneratorTests' --logger 'trx;LogFileName=ci-sourcegeneration-branch-net10.trx' --results-directory $results --verbosity minimal

# Passed: 93, Failed: 0, Skipped: 0
& $dotnet test $coreTests --no-restore --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true -p:CollectCoverage=true --collect 'XPlat Code Coverage' --settings 'tests\coverlet.runsettings.xml' --filter 'FullyQualifiedName~FairRequestQueueTests' --logger 'trx;LogFileName=ci-fairqueue-branch-net10.trx' --results-directory $results --verbosity minimal

# Passed: 93, Failed: 0, Skipped: 0
& $dotnet test $coreTests --configuration Release --framework net48 -p:CustomTestTarget=net48 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true --filter 'FullyQualifiedName~FairRequestQueueTests' --logger 'trx;LogFileName=ci-fairqueue-branch-net48.trx' --results-directory $results --verbosity minimal

# Passed: 30, Failed: 0, Skipped: 0
& $dotnet test $generator --configuration Release --framework net48 -p:CustomTestTarget=net48 -p:RunAnalyzers=true -p:RunAnalyzersDuringBuild=true --filter 'FullyQualifiedName~ModelDependencyScannerTests|FullyQualifiedName~PlaceholderReferenceGeneratorTests' --logger 'trx;LogFileName=ci-sourcegeneration-branch-net48.trx' --results-directory $results --verbosity minimal
```

`git diff --cached --check` passed. The queue-test blob was verified
byte-for-byte against the existing published correction. The upstream
branch was verified as `93ec3928bd5a9cb2ea86110085e79131db17d443` after
push.

**Remote CI is pending, not claimed green:** [new CI run
38712127138](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38712127138).
Cross-platform confirmation, CodeQL, coverage gates, and the complete
`UA.slnx` suites on this new head are not established by these local
checks. The source PR's checks are not claimed as evidence for this
branch.

### Earlier validation

The original extraction had 1,407 passing scoped test executions across
net10.0/net48. The placeholder-review follow-up had six passing focused
cases and 269 passing tests in the full source-generator project on each
framework, with analyzer-enabled builds. Those are historical results
for their respective commits, not fresh full-suite evidence for the
CI-repair head. Earlier command details remain in this PR's edit
history.

## Related Issues

- Related to #4461 (existing tracking issue; not auto-closing).
- Extracted from #4462 (source PR; remains open and is not
auto-closing).
- Reuses the bounded queue-test correction from #4765; does not
incorporate that PR's other changes.

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

This began as an exact extraction of existing behavior. The subsequent
commits address the reported placeholder-generation defect and observed
CI failures; they are not a new design or a claim of design sign-off.

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.
2026-10-15 12:21:46 +02:00

235 lines
13 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NUnit.Framework;
namespace Opc.Ua.SourceGeneration
{
/// <summary>
/// Verifies placeholder references in generated type and instance factories.
/// </summary>
[TestFixture]
[Category("SourceGeneration")]
public sealed class PlaceholderReferenceGeneratorTests
{
/// <summary>
/// Keeps nested placeholder references on templates without exposing them on runtime instances.
/// </summary>
[TestCase("MandatoryPlaceholder")]
[TestCase("OptionalPlaceholder")]
public void PlaceholderReferencesAreRetainedOnlyForTheTypeTemplate(string rule)
{
GeneratorDriver driver = GenerateModel(
sModel.Replace("PLACEHOLDER_RULE", rule, StringComparison.Ordinal), out Compilation output);
InvocationExpressionSyntax[] references = GetGeneratedReferenceCalls(driver);
InvocationExpressionSyntax[] placeholders = [.. references.Where(call => IsForwardReference(call, "47"))];
Assert.That(placeholders, Has.Length.EqualTo(1),
"The type template must retain the placeholder's forward HasComponent reference.");
foreach (InvocationExpressionSyntax reference in placeholders)
{
Assert.That(reference.Ancestors().OfType<IfStatementSyntax>()
.Any(statement => statement.Condition.ToString() == "!forInstance"), Is.True,
"A runtime instance must not link to an uninstantiated type declaration: " + reference);
}
InvocationExpressionSyntax[] ordinary = [.. references.Where(call => IsForwardReference(call, "35"))];
Assert.That(ordinary, Has.Length.EqualTo(1));
foreach (InvocationExpressionSyntax reference in ordinary)
{
Assert.That(reference.Ancestors().OfType<IfStatementSyntax>()
.Any(statement => statement.Condition.ToString() == "!forInstance"), Is.False,
"Ordinary external references must remain available on runtime instances.");
}
output.GetDiagnostics().Check(TestContext.Out, out int errors, out _);
Assert.That(errors, Is.Zero);
}
/// <summary>
/// Keeps direct and external placeholder references only in guarded, compilable type factories.
/// </summary>
[Test]
public void DirectPlaceholderReferencesCompileForTypeAndInstanceFactories(
[Values("MandatoryPlaceholder", "OptionalPlaceholder")] string rule,
[Values("ObjectType", "VariableType")] string rootType)
{
string baseType = rootType == "ObjectType" ? "BaseObjectType" : "BaseDataVariableType";
string model = sDirectChildModel
.Replace("ROOT_TYPE", rootType, StringComparison.Ordinal)
.Replace("BASE_TYPE", baseType, StringComparison.Ordinal)
.Replace("PLACEHOLDER_RULE", rule, StringComparison.Ordinal);
GeneratorDriver driver = GenerateModel(model, out Compilation output);
output.GetDiagnostics().Check(TestContext.Out, out int errors, out _);
Assert.That(errors, Is.Zero);
InvocationExpressionSyntax[] references = GetGeneratedReferenceCalls(driver);
InvocationExpressionSyntax[] placeholders = [.. references.Where(call => IsForwardReference(call, "47"))];
Assert.That(placeholders, Has.Length.EqualTo(2),
"The type template must retain both direct-child and external placeholder references.");
foreach (InvocationExpressionSyntax reference in placeholders)
{
MethodDeclarationSyntax factory = reference.Ancestors().OfType<MethodDeclarationSyntax>().Single();
Assert.That(factory.Identifier.ValueText, Is.EqualTo("CreateDeviceType"),
"Instance-only factories must omit every placeholder reference.");
Assert.That(factory.ParameterList.Parameters.Any(
parameter => parameter.Identifier.ValueText == "forInstance"), Is.True);
Assert.That(reference.Ancestors().OfType<IfStatementSyntax>()
.Any(statement => statement.Condition.ToString() == "!forInstance"), Is.True);
}
InvocationExpressionSyntax[] ordinary = [.. references.Where(call => IsForwardReference(call, "35"))];
Assert.That(ordinary.Select(reference =>
reference.Ancestors().OfType<MethodDeclarationSyntax>().Single().Identifier.ValueText),
Is.EquivalentTo(sFactoryNames));
foreach (InvocationExpressionSyntax reference in ordinary)
{
Assert.That(reference.Ancestors().OfType<IfStatementSyntax>()
.Any(statement => statement.Condition.ToString() == "!forInstance"), Is.False,
"Ordinary external references must remain unconditional in both factory contexts.");
}
}
private static GeneratorDriver GenerateModel(string model, out Compilation output)
{
CSharpCompilation compilation = OptimizationLevel.Release.CreateCompilation()
.AddCode(new Dictionary<string, string>().WithOpcUaGeneratedStack(), LanguageVersion.CSharp11);
var options = new AnalyzerOptionsProvider(new Dictionary<string, string>
{
["build_property.ModelSourceGeneratorOmitFluentApi"] = "true",
["build_property.ModelSourceGeneratorOmitEventRecords"] = "true"
});
GeneratorDriver driver = CSharpGeneratorDriver.Create(new ModelSourceGenerator())
.WithUpdatedParseOptions(new CSharpParseOptions(LanguageVersion.CSharp11))
.AddAdditionalTexts([EmbeddedText.Create("Placeholders.xml", model)])
.WithUpdatedAnalyzerConfigOptions(options);
driver = driver.RunGeneratorsAndUpdateCompilation(
compilation, out output, out ImmutableArray<Diagnostic> diagnostics);
Assert.That(diagnostics, Is.Empty, string.Join("\n", diagnostics));
return driver;
}
private static InvocationExpressionSyntax[] GetGeneratedReferenceCalls(GeneratorDriver driver)
{
return [.. driver.GetRunResult().Results[0].GeneratedSources
.SelectMany(source => source.SyntaxTree.GetRoot().DescendantNodes()
.OfType<InvocationExpressionSyntax>())
.Where(call => call.Expression.ToString() == "state.AddReference")];
}
private static bool IsForwardReference(InvocationExpressionSyntax call, string referenceType)
{
return call.ArgumentList.Arguments[1].Expression.IsKind(SyntaxKind.FalseLiteralExpression) &&
call.ArgumentList.Arguments[0].Expression.DescendantNodes().OfType<LiteralExpressionSyntax>()
.Any(literal => literal.Token.ValueText == referenceType);
}
private static readonly string[] sFactoryNames = ["CreateDeviceType", "CreateInstanceOfDeviceType"];
private const string sDirectChildModel = """
<?xml version="1.0" encoding="utf-8" ?>
<opc:ModelDesign xmlns:opc="http://opcfoundation.org/UA/ModelDesign.xsd"
xmlns:ua="http://opcfoundation.org/UA/" xmlns="http://test.org/UA/PlaceholderReferences/"
TargetNamespace="http://test.org/UA/PlaceholderReferences/">
<opc:Namespaces>
<opc:Namespace XmlNamespace="http://opcfoundation.org/UA/2008/02/Types.xsd"
Name="OpcUa" Prefix="Opc.Ua">http://opcfoundation.org/UA/</opc:Namespace>
<opc:Namespace Name="PlaceholderReferences"
Prefix="Test.PlaceholderReferences">http://test.org/UA/PlaceholderReferences/</opc:Namespace>
</opc:Namespaces>
<opc:ROOT_TYPE SymbolicName="DeviceType" BaseType="ua:BASE_TYPE">
<opc:Children>
<opc:Variable SymbolicName="Value_Placeholder" TypeDefinition="ua:BaseDataVariableType"
DataType="ua:String" ModellingRule="PLACEHOLDER_RULE" />
</opc:Children>
<opc:References>
<opc:Reference>
<opc:ReferenceType>ua:HasComponent</opc:ReferenceType>
<opc:TargetId>DeviceType_Value_Placeholder</opc:TargetId>
</opc:Reference>
<opc:Reference>
<opc:ReferenceType>ua:HasComponent</opc:ReferenceType>
<opc:TargetId>ExternalType_Value_Placeholder</opc:TargetId>
</opc:Reference>
<opc:Reference>
<opc:ReferenceType>ua:Organizes</opc:ReferenceType>
<opc:TargetId>ua:ObjectsFolder</opc:TargetId>
</opc:Reference>
</opc:References>
</opc:ROOT_TYPE>
<opc:ObjectType SymbolicName="ExternalType" BaseType="ua:BaseObjectType">
<opc:Children>
<opc:Variable SymbolicName="Value_Placeholder" TypeDefinition="ua:BaseDataVariableType"
DataType="ua:String" ModellingRule="PLACEHOLDER_RULE" />
</opc:Children>
</opc:ObjectType>
</opc:ModelDesign>
""";
private const string sModel = """
<?xml version="1.0" encoding="utf-8" ?>
<opc:ModelDesign xmlns:opc="http://opcfoundation.org/UA/ModelDesign.xsd"
xmlns:ua="http://opcfoundation.org/UA/" xmlns="http://test.org/UA/PlaceholderReferences/"
TargetNamespace="http://test.org/UA/PlaceholderReferences/">
<opc:Namespaces>
<opc:Namespace XmlNamespace="http://opcfoundation.org/UA/2008/02/Types.xsd"
Name="OpcUa" Prefix="Opc.Ua">http://opcfoundation.org/UA/</opc:Namespace>
<opc:Namespace Name="PlaceholderReferences"
Prefix="Test.PlaceholderReferences">http://test.org/UA/PlaceholderReferences/</opc:Namespace>
</opc:Namespaces>
<opc:ObjectType SymbolicName="DeviceType" BaseType="ua:BaseObjectType">
<opc:Children>
<opc:Object SymbolicName="Endpoints" TypeDefinition="ua:FolderType" ModellingRule="Mandatory">
<opc:Children>
<opc:Object SymbolicName="Endpoint_Placeholder" TypeDefinition="ua:BaseObjectType"
ModellingRule="PLACEHOLDER_RULE" />
</opc:Children>
<opc:References>
<opc:Reference>
<opc:ReferenceType>ua:HasComponent</opc:ReferenceType>
<opc:TargetId>DeviceType_Endpoints_Endpoint_Placeholder</opc:TargetId>
</opc:Reference>
<opc:Reference>
<opc:ReferenceType>ua:Organizes</opc:ReferenceType>
<opc:TargetId>ua:ObjectsFolder</opc:TargetId>
</opc:Reference>
</opc:References>
</opc:Object>
</opc:Children>
</opc:ObjectType>
</opc:ModelDesign>
""";
}
}