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:
Joseph Doherty
2026-04-25 18:10:53 -04:00
parent 2731318c81
commit cfcaf5c1d3
4 changed files with 529 additions and 22 deletions

View File

@@ -32,6 +32,16 @@ public sealed class AbCipDriverOptions
/// </summary>
public IReadOnlyList<AbCipL5kImportOptions> L5kImports { get; init; } = [];
/// <summary>
/// L5X (Studio 5000 XML controller export) imports merged into <see cref="Tags"/> at
/// <c>InitializeAsync</c>. Same shape and merge semantics as <see cref="L5kImports"/> —
/// the entries differ only in source format. Pre-declared <see cref="Tags"/> entries win
/// on <c>Name</c> conflicts; entries already produced by <see cref="L5kImports"/> also win
/// so an L5X re-export of the same controller doesn't double-emit. See
/// <see cref="Import.L5xParser"/> for the format-specific mechanics.
/// </summary>
public IReadOnlyList<AbCipL5xImportOptions> L5xImports { get; init; } = [];
/// <summary>Per-device probe settings. Falls back to defaults when omitted.</summary>
public AbCipProbeOptions Probe { get; init; } = new();
@@ -150,6 +160,22 @@ public sealed record AbCipL5kImportOptions(
string? InlineText = null,
string NamePrefix = "");
/// <summary>
/// One L5X-import entry. Mirrors <see cref="AbCipL5kImportOptions"/> field-for-field — the
/// two are kept as distinct types so configuration JSON makes the source format explicit
/// (an L5X file under an <c>L5kImports</c> entry would parse-fail confusingly otherwise).
/// </summary>
/// <param name="DeviceHostAddress">Target device <c>HostAddress</c> tags from this file are bound to.</param>
/// <param name="FilePath">On-disk path to a <c>*.L5X</c> XML export. Loaded eagerly at InitializeAsync.</param>
/// <param name="InlineText">Pre-loaded L5X body — used by tests + Admin UI uploads.</param>
/// <param name="NamePrefix">Optional prefix prepended to imported tag names to avoid collisions
/// when ingesting multiple files into one driver instance.</param>
public sealed record AbCipL5xImportOptions(
string DeviceHostAddress,
string? FilePath = null,
string? InlineText = null,
string NamePrefix = "");
/// <summary>Which AB PLC family the device is — selects the profile applied to connection params.</summary>
public enum AbCipPlcFamily
{