Auto: abcip-2.2 — L5X (XML) parser + ingest
Adds Import/L5xParser.cs that consumes Studio 5000 L5X (XML) controller exports via System.Xml.XPath and produces the same L5kDocument bundle as L5kParser, so L5kIngest handles both formats interchangeably. - Controller-scope and program-scope <Tag> elements with Name, DataType, TagType, ExternalAccess, AliasFor, and <Description> child. - <DataType>/<Members>/<Member> with Hidden BOOL-host (ZZZZZZZZZZ*) skip. - AddOnInstructionDefinitions surfaced as L5kDataType entries so AOI-typed tags pick up a member layout the same way UDT-typed tags do; hidden EnableIn/EnableOut parameters skipped. Full directional Input/Output/InOut modelling stays deferred to PR 2.6. AbCipDriverOptions gains parallel L5xImports collection (mirrors L5kImports field-for-field). InitializeAsync funnels both through one shared MergeImport helper that differs only in the parser delegate. Tests: 8 L5X fixtures cover controller- and program-scope tags, alias skip, UDT layout fan-out, AOI-typed tag, ZZZZZZZZZZ host skip, hidden AOI param skip, missing-ExternalAccess default, and an empty-controller no-throw. Closes #230
This commit is contained in:
@@ -130,28 +130,27 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
var allTags = new List<AbCipTagDefinition>(_options.Tags);
|
||||
foreach (var import in _options.L5kImports)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(import.DeviceHostAddress))
|
||||
throw new InvalidOperationException(
|
||||
"AbCip L5K import is missing DeviceHostAddress — every imported tag needs a target device.");
|
||||
IL5kSource? src = null;
|
||||
if (!string.IsNullOrEmpty(import.FilePath))
|
||||
src = new FileL5kSource(import.FilePath);
|
||||
else if (!string.IsNullOrEmpty(import.InlineText))
|
||||
src = new StringL5kSource(import.InlineText);
|
||||
if (src is null) continue;
|
||||
var doc = L5kParser.Parse(src);
|
||||
var ingest = new L5kIngest
|
||||
{
|
||||
DefaultDeviceHostAddress = import.DeviceHostAddress,
|
||||
NamePrefix = import.NamePrefix,
|
||||
};
|
||||
var result = ingest.Ingest(doc);
|
||||
foreach (var importedTag in result.Tags)
|
||||
{
|
||||
if (declaredNames.Contains(importedTag.Name)) continue;
|
||||
allTags.Add(importedTag);
|
||||
declaredNames.Add(importedTag.Name);
|
||||
}
|
||||
MergeImport(
|
||||
deviceHost: import.DeviceHostAddress,
|
||||
filePath: import.FilePath,
|
||||
inlineText: import.InlineText,
|
||||
namePrefix: import.NamePrefix,
|
||||
parse: L5kParser.Parse,
|
||||
formatLabel: "L5K",
|
||||
declaredNames: declaredNames,
|
||||
allTags: allTags);
|
||||
}
|
||||
foreach (var import in _options.L5xImports)
|
||||
{
|
||||
MergeImport(
|
||||
deviceHost: import.DeviceHostAddress,
|
||||
filePath: import.FilePath,
|
||||
inlineText: import.InlineText,
|
||||
namePrefix: import.NamePrefix,
|
||||
parse: L5xParser.Parse,
|
||||
formatLabel: "L5X",
|
||||
declaredNames: declaredNames,
|
||||
allTags: allTags);
|
||||
}
|
||||
|
||||
foreach (var tag in allTags)
|
||||
@@ -194,6 +193,47 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared L5K / L5X import path — keeps source-format selection (parser delegate) the
|
||||
/// only behavioural axis between the two formats. Adds the parser's tags to
|
||||
/// <paramref name="allTags"/> while skipping any name already covered by an earlier
|
||||
/// declaration or import (declared > L5K > L5X precedence falls out from call order).
|
||||
/// </summary>
|
||||
private static void MergeImport(
|
||||
string deviceHost,
|
||||
string? filePath,
|
||||
string? inlineText,
|
||||
string namePrefix,
|
||||
Func<IL5kSource, L5kDocument> parse,
|
||||
string formatLabel,
|
||||
HashSet<string> declaredNames,
|
||||
List<AbCipTagDefinition> allTags)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceHost))
|
||||
throw new InvalidOperationException(
|
||||
$"AbCip {formatLabel} import is missing DeviceHostAddress — every imported tag needs a target device.");
|
||||
IL5kSource? src = null;
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
src = new FileL5kSource(filePath);
|
||||
else if (!string.IsNullOrEmpty(inlineText))
|
||||
src = new StringL5kSource(inlineText);
|
||||
if (src is null) return;
|
||||
|
||||
var doc = parse(src);
|
||||
var ingest = new L5kIngest
|
||||
{
|
||||
DefaultDeviceHostAddress = deviceHost,
|
||||
NamePrefix = namePrefix,
|
||||
};
|
||||
var result = ingest.Ingest(doc);
|
||||
foreach (var importedTag in result.Tags)
|
||||
{
|
||||
if (declaredNames.Contains(importedTag.Name)) continue;
|
||||
allTags.Add(importedTag);
|
||||
declaredNames.Add(importedTag.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
await ShutdownAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user