From 4e6f4d2021c601a805fb0b7720c5de93a28cecd8 Mon Sep 17 00:00:00 2001
From: Timothy Nunnink <46979634+tnunnink@users.noreply.github.com>
Date: Tue, 21 Nov 2023 20:50:56 -0600
Subject: [PATCH] FBD refinement. L5X API refinement. Documentations. Bug
fixes. Other development
---
src/.idea/.idea.L5Sharp/.idea/workspace.xml | 190 +-
src/L5Sharp.sln.DotSettings | 1 +
src/L5Sharp/Common/CrossReference.cs | 162 +-
src/L5Sharp/Common/TagName.cs | 4 +-
src/L5Sharp/Components/DataType.cs | 2 +-
src/L5Sharp/Components/Tag.cs | 7 +-
src/L5Sharp/Elements/AOI.cs | 25 +-
src/L5Sharp/Elements/Block.cs | 84 +-
src/L5Sharp/Elements/DataTypeMember.cs | 10 +-
src/L5Sharp/Elements/Diagram.cs | 124 +-
src/L5Sharp/Elements/DiagramBlock.cs | 57 +-
src/L5Sharp/Elements/DiagramConnector.cs | 75 +-
src/L5Sharp/Elements/Function.cs | 28 +-
src/L5Sharp/Elements/FunctionBlock.cs | 32 +-
src/L5Sharp/Elements/ICON.cs | 20 +
src/L5Sharp/Elements/IREF.cs | 8 +-
src/L5Sharp/Elements/JSR.cs | 6 +
src/L5Sharp/Elements/Line.cs | 4 +-
src/L5Sharp/Elements/OCON.cs | 20 +
src/L5Sharp/Elements/OREF.cs | 8 +-
src/L5Sharp/Elements/RET.cs | 73 +
src/L5Sharp/Elements/Rung.cs | 81 +-
src/L5Sharp/Elements/SBR.cs | 27 +-
src/L5Sharp/Elements/Sheet.cs | 5 +-
src/L5Sharp/Elements/Wire.cs | 22 +-
src/L5Sharp/Enums/Radix.cs | 3 +-
src/L5Sharp/Enums/Scope.cs | 34 +-
src/L5Sharp/ILogixReferencable.cs | 3 +-
src/L5Sharp/L5X.cs | 1119 +--
src/L5Sharp/Logix.cs | 14 +
src/L5Sharp/LogixCode.cs | 7 +-
src/L5Sharp/LogixComponent.cs | 2 +-
src/L5Sharp/LogixElement.cs | 50 +-
src/L5Sharp/LogixIndex.cs | 389 +
src/L5Sharp/Types/StringType.cs | 11 +-
src/L5Sharp/Utilities/L5XTypeAttribute.cs | 2 +-
tests/L5Sharp.Samples/L5Sharp.Samples.csproj | 6 +-
tests/L5Sharp.Samples/Routines/FBD.L5X | 69 +-
tests/L5Sharp.Samples/Test.L5X | 9312 ------------------
tests/L5Sharp.Samples/Test.xml | 742 +-
tests/L5Sharp.Tests/Elements/BlockTests.cs | 16 +-
tests/L5Sharp.Tests/Elements/IREFTests.cs | 2 +-
tests/L5Sharp.Tests/Elements/WireTests.cs | 85 +
tests/L5Sharp.Tests/Examples.cs | 12 +-
tests/L5Sharp.Tests/L5XBasicTests.cs | 84 +-
tests/L5Sharp.Tests/L5XDataTypeTests.cs | 4 +-
tests/L5Sharp.Tests/L5XReferenceTests.cs | 10 +-
tests/L5Sharp.Tests/L5XTagTests.cs | 2 +-
tests/L5Sharp.Tests/L5XTemplateTests.cs | 4 +-
tests/L5Sharp.Tests/ProofTesting.cs | 23 +-
tests/L5Sharp.Tests/TagPerformanceTests.cs | 2 +-
51 files changed, 2586 insertions(+), 10496 deletions(-)
create mode 100644 src/L5Sharp/Elements/RET.cs
create mode 100644 src/L5Sharp/Logix.cs
create mode 100644 src/L5Sharp/LogixIndex.cs
delete mode 100644 tests/L5Sharp.Samples/Test.L5X
create mode 100644 tests/L5Sharp.Tests/Elements/WireTests.cs
diff --git a/src/.idea/.idea.L5Sharp/.idea/workspace.xml b/src/.idea/.idea.L5Sharp/.idea/workspace.xml
index 00c7a035..6ef888c3 100644
--- a/src/.idea/.idea.L5Sharp/.idea/workspace.xml
+++ b/src/.idea/.idea.L5Sharp/.idea/workspace.xml
@@ -9,55 +9,55 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
-
-
-
+
+
-
-
-
+
+
+
+
+
+
-
-
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -100,9 +100,8 @@
-
+
-
@@ -124,56 +123,37 @@
- {
- "keyToString": {
- "Notification.DisplayName-DoNotAsk-Plugin Error": "Plugins failed to load",
- "Notification.DoNotAsk-Plugin Error": "true",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "TODO_SCOPE": "All Places",
- "WebServerToolWindowFactoryState": "false",
- "git-widget-placeholder": "main",
- "ignore.virus.scanning.warn.message": "true",
- "last_opened_file_path": "C:/Users/tnunnink/Documents/GitHub/L5Sharp/src/L5Sharp.sln",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "RiderCSharpLiveTemplatesSettingsId",
- "vue.rearranger.settings.migration": "true"
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+}]]>C:\Users\tnunnink\AppData\Roaming\Subversion
@@ -419,6 +399,10 @@
+
+
+
+ 1677621948966
@@ -843,22 +827,42 @@
file://$PROJECT_DIR$/../tests/L5Sharp.Tests/ProofTesting.cs
- 45
-
-
+ 34
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/L5Sharp/LogixIndex.cs
+ 32
+
+
+
+
+
+
+
+
+
- file://$PROJECT_DIR$/L5Sharp/Elements/DiagramBlock.cs
- 23
-
+ file://$PROJECT_DIR$/L5Sharp/L5X.cs
+ 251
+
-
+
-
+
-
+
@@ -872,7 +876,6 @@
-
@@ -898,6 +901,7 @@
+
diff --git a/src/L5Sharp.sln.DotSettings b/src/L5Sharp.sln.DotSettings
index d11a31b5..17ba1fc7 100644
--- a/src/L5Sharp.sln.DotSettings
+++ b/src/L5Sharp.sln.DotSettings
@@ -41,6 +41,7 @@
PIDLOGREAL
+ RETRLLRPISBR
diff --git a/src/L5Sharp/Common/CrossReference.cs b/src/L5Sharp/Common/CrossReference.cs
index 4f8b3425..2d87fd2c 100644
--- a/src/L5Sharp/Common/CrossReference.cs
+++ b/src/L5Sharp/Common/CrossReference.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Xml.Linq;
using JetBrains.Annotations;
-using L5Sharp.Elements;
using L5Sharp.Enums;
using L5Sharp.Utilities;
@@ -10,7 +9,9 @@ namespace L5Sharp.Common;
///
/// Represents a reference to a component within a Logix project. This could be a code reference or a reference
-/// from another component. This class is meant to provide a uniform set of information for all types of references.
+/// from another component. This class is meant to provide a uniform set of information for all types of references,
+/// however, code references have some additional information that is useful for further identifying the target or
+/// location of the reference.
///
[PublicAPI]
public class CrossReference
@@ -18,63 +19,90 @@ public class CrossReference
private readonly XElement _element;
///
- /// Creates a new with a referencing element, component name and type.
+ /// Creates a new with a referencing element, component name and type, and optional instruction data.
///
/// The referencing object.
- ///
- ///
- /// Any provided parameter is null.
- public CrossReference(XElement element, string type, string name)
+ /// The type of the component that is being referenced.
+ /// The name of the component that is being referenced.
+ /// The optional instruction name/key for the reference.
+ /// This is intended for code references as opposed to component references. Will be null if not applicable.
+ /// element, type, or name is null.
+ public CrossReference(XElement element, string type, string reference, Instruction? instruction = null)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be null or empty.", nameof(name));
- if (string.IsNullOrEmpty(type)) throw new ArgumentException("Type cannot be null or empty.", nameof(type));
- ComponentType = type;
- ComponentName = name;
_element = element ?? throw new ArgumentNullException(nameof(element));
+
+ if (string.IsNullOrEmpty(type))
+ throw new ArgumentException("Type cannot be null or empty.", nameof(type));
+ if (string.IsNullOrEmpty(reference))
+ throw new ArgumentException("Name cannot be null or empty.", nameof(reference));
+
+ Type = type;
+ Reference = reference;
+ Instruction = instruction;
}
///
/// The corresponding of the reference, indicating both the component type and name
/// this element is in reference to.
///
- public ComponentKey ComponentKey => new(ComponentType, ComponentName);
+ public ComponentKey Key => new(Type, Reference);
///
- /// The type of the component the element is in reference to.
+ /// The type of the component the element references.
///
/// A indicating the type of the component.
- public string ComponentType { get; }
+ public string Type { get; }
///
- /// The name of the component the element is in reference to.
+ /// The name of the component or element that is referenced.
///
/// A indicating the name of the component.
- public string ComponentName { get; }
+ public string Reference { get; }
///
- /// The referencing object
+ /// The that is contains the reference to the component.
///
- public LogixElement Reference => GetReference();
+ /// The object that contains the component reference. This may be another
+ /// Component, a Code instance, or even a single DiagramBlock object.
+ public LogixElement Element => _element.Deserialize();
///
- /// The location of the reference if this is a reference type.
+ /// The type of the LogixElement that contains the reference to the component.
///
- /// A containing the Rung, Line, or DiagramBlock location for
- /// the reference. If this reference has no relevant location, an empty string is returned.
- ///
- public string ReferenceId => GetIdentifier() ?? string.Empty;
+ /// A representing the name of the element type.
+ /// This helps further identify the reference element relative to other references.
+ public string ElementType => _element.Name.LocalName;
///
- /// The type of the element referencing the component for this reference.
+ /// A unique identifier of the LogixElement that contains the reference to the component.
///
- public string ReferenceType => GetReference().L5XType;
+ /// A containing the name or number identifying the reference element.
+ ///
+ /// This will ultimately be either the name of the referencing component (for Tag references),
+ /// the number of the referencing rung or line of logic (for RLL and ST code), or the ID of the referencing
+ /// diagram block (for FBD/SFC code). This helps further identify the reference element relative to other references.
+ ///
+ public string ElementId => _element.Attribute(L5XName.Name) is not null ? _element.Attribute(L5XName.Name)!.Value
+ : _element.Attribute(L5XName.Number) is not null ? _element.Attribute(L5XName.Number)!.Value
+ : _element.Attribute(L5XName.ID) is not null ? _element.Attribute(L5XName.ID)!.Value
+ : string.Empty;
+
+ ///
+ /// The name of the Task that the reference is contained within if applicable.
+ ///
+ /// A representing the containing task if found; Otherwise, an empty string.
+ ///
+ /// This could potentially be helpful for analyzing references to tags that are used across multiple
+ /// Task components.
+ ///
+ public string Task => Scope.Task(_element);
///
/// The type that the reference is contained within.
///
/// A indicating scope of the reference.
- public Scope Scope => Scope.ScopeType(_element);
-
+ public Scope Scope => Scope.Type(_element);
+
///
/// The name of the scoped program, instruction, or controller that the reference is contained within.
///
@@ -82,70 +110,50 @@ public CrossReference(XElement element, string type, string name)
/// A representing the name of program, controller, or instruction the reference
/// is contained within.
///
- public string Container => Scope.ScopeName(_element);
-
+ public string Container => Scope.Container(_element);
+
///
/// The name of the Routine that the reference is contained within, it is a
/// type element.
///
/// A representing the containing routine if found; Otherwise, an empty string.
- public string RoutineName => _element.Ancestors(L5XName.Routine).FirstOrDefault()?.LogixName() ?? string.Empty;
-
- ///
- /// The name of the Task that the reference is contained within if applicable.
- ///
- /// A representing the containing task if found; Otherwise, an empty string.
- public string TaskName => GetTaskName() ?? string.Empty;
+ public string Routine => _element.Ancestors(L5XName.Routine).FirstOrDefault()?.LogixName() ?? string.Empty;
///
- /// Determines if this is the same as another .
+ /// The instruction object containing the reference to the component if this reference is a logic or code reference.
///
- /// The other logix reference to compare to.
- /// true if the provided reference has the same scope, routine, location, and reference type,
- /// indicating that they are referring to the same code or component element in the L5X structure;
- /// Otherwise, false.
- ///
- public bool IsSame(CrossReference other)
+ /// If the reference is a code reference, then the object the reference
+ /// was found; Otherwise, null.
+ ///
+ /// The helps further identify where in the logix element the reference is located. Having the associated
+ /// instruction can help for searching or filtering references and finding other references sharing the common
+ /// instruction.
+ ///
+ public Instruction? Instruction { get; }
+
+ ///
+ public override bool Equals(object? obj)
{
- return ComponentKey == other.ComponentKey &&
+ if (ReferenceEquals(this, obj))
+ return true;
+
+ if (obj is not CrossReference other)
+ return false;
+
+ return Key == other.Key &&
Scope == other.Scope &&
Container.IsEquivalent(other.Container) &&
- RoutineName.IsEquivalent(other.RoutineName) &&
- ReferenceId.IsEquivalent(other.ReferenceId) &&
- ReferenceType.IsEquivalent(other.ReferenceType);
+ Routine.IsEquivalent(other.Routine) &&
+ ElementId.IsEquivalent(other.ElementId) &&
+ ElementType.IsEquivalent(other.ElementType);
}
- ///
- /// Determines the string that identifies this reference element within the context or scope of the L5X.
- ///
- private string? GetIdentifier()
+ ///
+ public override int GetHashCode()
{
- return GetReference() switch
- {
- LogixComponent component => component.Name,
- LogixCode code => code.Identifier,
- DiagramBlock diagram => $"{diagram.Location} ",
- _ => null
- };
+ return HashCode.Combine(Key, Scope, Container, Routine, ElementId, ElementType);
}
- ///
- /// Deserialized the element into the appropriate type.
- ///
- private LogixElement GetReference() => _element.Deserialize();
-
- ///
- /// Uses the underlying element hierarchy to determine the name of the Task that the reference is contained.
- /// This would only apply to code or scoped tag reference types. Controller scoped items are not contained
- /// within a task
- ///
- private string? GetTaskName()
- {
- return _element
- .Ancestors(L5XName.RSLogix5000Content)
- .Descendants(L5XName.Task)
- .FirstOrDefault(e => e.Descendants(L5XName.ScheduledProgram)
- .Any(p => p.Attribute(L5XName.Name)?.Value == Container))
- ?.LogixName();
- }
+ ///
+ public override string ToString() => Key.ToString();
}
\ No newline at end of file
diff --git a/src/L5Sharp/Common/TagName.cs b/src/L5Sharp/Common/TagName.cs
index a5f4148a..ee23e34d 100644
--- a/src/L5Sharp/Common/TagName.cs
+++ b/src/L5Sharp/Common/TagName.cs
@@ -53,7 +53,7 @@ public TagName(string tagName)
///
///
/// The root portion of a given tag name is simply the beginning part of the tag name up to the first
- /// member separator character ('.' or '['). For Module defined tags, this includes the colon separator.
+ /// member separator character '.' or '['. For Module defined tags, this includes the colon separator.
///
///
/// This value can be swapped out easily using to return a new with the
@@ -222,7 +222,7 @@ public bool Contains(TagName tagName)
/// A tag name object to compare.
/// The equality comparer to use for comparison.
/// true if the tag name are equal according too the provided comparer; otherwise, false.
- /// Use the prebuilt class for several predefined comparers.
+ /// Use the prebuilt class for several predefined comparer objects.
public static bool Equals(TagName first, TagName second, IEqualityComparer comparer) =>
comparer.Equals(first, second);
diff --git a/src/L5Sharp/Components/DataType.cs b/src/L5Sharp/Components/DataType.cs
index 987dbacc..0c396efb 100644
--- a/src/L5Sharp/Components/DataType.cs
+++ b/src/L5Sharp/Components/DataType.cs
@@ -109,7 +109,7 @@ public override IEnumerable Dependencies()
foreach (var member in Members)
{
- var dataType = L5X.FindComponent(member.DataType);
+ var dataType = L5X.Find(member.DataType);
if (dataType is null) continue;
dependencies.Add(dataType);
dependencies.AddRange(dataType.Dependencies());
diff --git a/src/L5Sharp/Components/Tag.cs b/src/L5Sharp/Components/Tag.cs
index 84e51753..b307644e 100644
--- a/src/L5Sharp/Components/Tag.cs
+++ b/src/L5Sharp/Components/Tag.cs
@@ -256,6 +256,11 @@ public string? Unit
///
public Tag Root { get; }
+ ///
+ ///
+ ///
+ public Tag? Alias => AliasFor is not null ? L5X?.Find(AliasFor) : default;
+
///
/// The full tag name path of the .
///
@@ -288,7 +293,7 @@ public Tag this[TagName tagName]
get
{
if (tagName is null) throw new ArgumentNullException(nameof(tagName));
- if (tagName.IsEmpty) throw new ArgumentException("Can not retrieve member from empty tag name.");
+ if (tagName.IsEmpty) return this;
var member = Value.Member(tagName.Root);
if (member is null)
diff --git a/src/L5Sharp/Elements/AOI.cs b/src/L5Sharp/Elements/AOI.cs
index 0e7bb330..ca0cd2e6 100644
--- a/src/L5Sharp/Elements/AOI.cs
+++ b/src/L5Sharp/Elements/AOI.cs
@@ -47,12 +47,12 @@ public string? Name
}
///
- /// The backing tag name for the AoiBlock instance.
+ /// The backing tag name for the AOI block element.
///
/// A containing the tag name if it exists; Otherwise, null.
- public string? Operand
+ public TagName? Operand
{
- get => GetValue();
+ get => GetValue();
set => SetValue(value);
}
@@ -62,7 +62,13 @@ public string? Operand
/// A containing the names of the pins if found. If not found then an
/// empty collection.
/// To update the property, you must assign a new collection of pin names.
- public IEnumerable VisiblePins => throw new NotImplementedException();
+ public Params? VisiblePins
+ {
+ get => Element.Attribute(L5XName.VisiblePins) is not null
+ ? new Params(Element.Attribute(L5XName.VisiblePins)!)
+ : default;
+ set => SetValue(value is not null ? string.Join(" ", value) : null);
+ }
///
/// The collection of input/output parameters for the AoiBlock instance.
@@ -88,7 +94,7 @@ public IEnumerable> Parameters
///
public override IEnumerable References()
{
- if (Operand is not null && Operand.IsTag())
+ if (Operand is not null)
yield return new CrossReference(Element, Operand, L5XName.Tag);
if (Name is not null)
@@ -98,4 +104,13 @@ public override IEnumerable References()
if (parameter.Value.IsTag())
yield return new CrossReference(Element, parameter.Value, L5XName.Tag);
}
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ //todo we need to think about how could this be an invalid call (i.e. why check pins)
+ yield return Operand is not null && endpoint.Value is not null && VisiblePins?.Contains(endpoint.Value) == true
+ ? TagName.Concat(Operand, endpoint.Value)
+ : Argument.Empty;
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Block.cs b/src/L5Sharp/Elements/Block.cs
index 4bfc21d6..dafb8ede 100644
--- a/src/L5Sharp/Elements/Block.cs
+++ b/src/L5Sharp/Elements/Block.cs
@@ -69,7 +69,12 @@ public bool? HideDesc
get => GetValue();
set => SetValue(value);
}
-
+
+ ///
+ /// The collection of input parameters to the routine being called by the FunctionBlock element.
+ ///
+ /// A object wrapping the underlying attribute containing the In parameters
+ /// if exists; Otherwise, null.
///
/// A collection of pin names that are visible for the Block element.
///
@@ -82,11 +87,12 @@ public bool? HideDesc
/// diagram types Block and AddOnInstruction. Invalid configuration of the element may result in a
/// failure to import.
///
- public IEnumerable VisiblePins
+ public Params? VisiblePins
{
- get => Element.Attribute(L5XName.VisiblePins)?.Value.Split(" ").Select(a => new TagName(a)) ??
- Enumerable.Empty();
- set => Element.SetAttributeValue(L5XName.VisiblePins, string.Join(" ", value));
+ get => Element.Attribute(L5XName.VisiblePins) is not null
+ ? new Params(Element.Attribute(L5XName.VisiblePins)!)
+ : default;
+ set => SetValue(value is not null ? string.Join(" ", value) : null);
}
///
@@ -95,65 +101,31 @@ public IEnumerable VisiblePins
/// A containing values.
/// This is a helper to get the tag names concatenated with the root
/// tag name of the Block.
- public IEnumerable TagNames => VisiblePins.Select(p => TagName.Concat(Operand ?? TagName.Empty, p));
+ public IEnumerable TagNames =>
+ VisiblePins?.Select(p => TagName.Concat(Operand ?? TagName.Empty, p)) ?? Enumerable.Empty();
- ///
- /// Adds the provided pin tag name to the collection of .
- ///
- /// The of the pin to add.
- /// This method assists with updating the collection of pins for the underlying Block element
- /// without having to manually reassign the entire collection property.
- public void AddPin(TagName tagName)
+ ///
+ public override IEnumerable References()
{
- var pins = VisiblePins.ToList();
- pins.Add(tagName);
- VisiblePins = pins;
- }
+ if (Operand is null) return base.References();
- ///
- /// Adds the provided pin tag name collection to the collection of .
- ///
- /// The collection pins to add.
- /// This method assists with updating the collection of pins for the underlying Block element
- /// without having to manually reassign the entire collection property.
- public void AddPins(IEnumerable tagNames)
- {
- var pins = VisiblePins.ToList();
- pins.AddRange(tagNames);
- VisiblePins = pins;
- }
- ///
- /// Clears the collection of for the element.
- ///
- /// This method assists with updating the collection of pins for the underlying Block element
- /// without having to manually reassign the entire collection property.
- public void ClearPins()
- {
- Element.SetAttributeValue(L5XName.VisiblePins, string.Empty);
- }
+ foreach (var pin in VisiblePins)
+ {
+ }
- ///
- /// Removes the specified pin tag name from the collection of .
- ///
- /// The of the pin to remove.
- /// This method assists with updating the collection of pins for the underlying Block element
- /// without having to manually reassign the entire collection property.
- public void RemovePin(TagName tagName)
- {
- var pins = VisiblePins.ToList();
- pins.Remove(tagName);
- VisiblePins = pins;
+ var references = new List { new(Element, L5XName.Tag, Operand) };
+ references.AddRange(TagNames.Select(t => new CrossReference(Element, L5XName.Tag, t)));
+
+ return references;
}
///
- public override IEnumerable References()
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
{
- if (Operand is null) return base.References();
-
- var references = new List { new(Element, Operand, L5XName.Tag) };
- references.AddRange(TagNames.Select(t => new CrossReference(Element, t, L5XName.Tag)));
-
- return references;
+ //todo we need to think about how could this be an invalid call (i.e. why check pins)
+ yield return Operand is not null && endpoint.Value is not null && VisiblePins?.Contains(endpoint.Value) == true
+ ? TagName.Concat(Operand, endpoint.Value)
+ : Argument.Empty;
}
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/DataTypeMember.cs b/src/L5Sharp/Elements/DataTypeMember.cs
index ecaf3334..2a963433 100644
--- a/src/L5Sharp/Elements/DataTypeMember.cs
+++ b/src/L5Sharp/Elements/DataTypeMember.cs
@@ -1,7 +1,5 @@
using System;
-using System.Linq;
using System.Xml.Linq;
-using System.Xml.Serialization;
using L5Sharp.Common;
using L5Sharp.Components;
using L5Sharp.Enums;
@@ -159,16 +157,10 @@ public int? BitNumber
set => SetValue(value);
}
- //Extensions represent properties or methods of a logix element that are not inherent in the underlying XML,
- //but ones that add value for ease of navigation or retrieval of information.
- #region Extensions
-
///
/// Gets the parent component that this Member is contained by. If this
/// Member is not contained or attached to a L5X document, this returns null.
///
/// A representing the parent type for this Member.
- public DataType? Parent => GetParent();
-
- #endregion
+ public DataType? Parent => GetAncestor();
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Diagram.cs b/src/L5Sharp/Elements/Diagram.cs
index 291b63e2..0131e382 100644
--- a/src/L5Sharp/Elements/Diagram.cs
+++ b/src/L5Sharp/Elements/Diagram.cs
@@ -43,17 +43,23 @@ protected Diagram(XElement element) : base(element)
}
///
- /// Gets a with the specified block id.
+ /// Gets or sets a single with the specified block id.
///
/// The Id of the block to get or set.
- ///
- /// This will
+ /// No element has the specified id.
+ /// -or- More than one element has the specified id.
+ /// -or- The source sequence is empty.
+ /// This will find the block in the Diagram with the specified ID attribute value. Internally this is
+ /// calling Single so if the element does not exists or there are more than one of the same ID then this will
+ /// throw an exception.
public TBlock this[uint id]
{
get => Element.Elements()
.Single(e => e.Attribute(L5XName.ID)?.Value.Parse() == id)
.Deserialize();
- set => Element.Elements().Single(e => e.Get(L5XName.ID).Parse() == id).ReplaceWith(value);
+ set => Element.Elements()
+ .Single(e => e.Attribute(L5XName.ID)?.Value.Parse() == id)
+ .ReplaceWith(value);
}
///
@@ -64,7 +70,7 @@ public TBlock this[uint id]
///
/// This will update the block ID to the next available ID if the ID is already used.
/// This will preserve the uniqueness of the ID's and prevent import errors. This will also perform a sort of the
- /// current underlying diagram elements to ensure the order of the element is maintained. This also is required to
+ /// current underlying diagram elements to ensure the order of the element is maintained, which is also required to
/// prevent import errors.
///
public uint Add(TBlock block)
@@ -217,6 +223,20 @@ public IEnumerable Connectors()
return Elements().Where(e => e is TConnector).Cast();
}
+ ///
+ /// Gets all elements in the .
+ ///
+ ///
+ /// An of the diagram specific type objects if any.
+ /// If none, an empty collection.
+ ///
+ public IEnumerable Connectors(TBlock block)
+ {
+ var inputs = Connectors().Where(c => c.IsConnectedTo(block));
+ var outputs = Connectors().Where(c => c.IsConnectedFrom(block));
+ return inputs.Concat(outputs);
+ }
+
///
/// Finds all other elements that are connected via a to
/// the provided block.
@@ -231,8 +251,65 @@ public IEnumerable Connectors()
///
public IEnumerable Connections(TBlock block)
{
- var connections = Connectors().Select(c => c.Connected(block)).Where(id => id.HasValue).ToHashSet();
- return Blocks().Where(b => connections.Contains(b.ID));
+ var endpoints = Connectors(block).Select(c => c.Endpoint(block).Key).ToHashSet();
+ return Blocks().Where(b => endpoints.Contains(b.ID));
+ }
+
+ ///
+ /// Completely removes the block and all associated connectors from the Diagram.
+ ///
+ /// The to delete.
+ /// This is effectively a combination of and
+ /// for a more concise way of scrubbing a block from the diagram.
+ public void Delete(TBlock block)
+ {
+ Element.Elements()
+ .Where(e => e.Attribute(L5XName.ID)?.Value.Parse() == block.ID ||
+ e.Attribute(L5XName.FromID)?.Value.Parse() == block.ID ||
+ e.Attribute(L5XName.ToID)?.Value.Parse() == block.ID)
+ .Remove();
+ }
+
+ ///
+ /// Removes all elements from the diagram with connections to the specified block ID.
+ ///
+ /// The block to disconnect from the diagram.
+ public void Disconnect(TBlock block)
+ {
+ Element.Elements()
+ .Where(e => e.Attribute(L5XName.FromID)?.Value.Parse() == block.ID ||
+ e.Attribute(L5XName.ToID)?.Value.Parse() == block.ID)
+ .Remove();
+ }
+
+ ///
+ /// Removes all elements from the diagram with connections to the specified block ID.
+ ///
+ ///
+ public void Disconnect(uint id)
+ {
+ Element.Elements()
+ .Where(e => e.Attribute(L5XName.FromID)?.Value.Parse() == id ||
+ e.Attribute(L5XName.ToID)?.Value.Parse() == id)
+ .Remove();
+ }
+
+ ///
+ /// Removes all elements from the diagram with the from/to ID pair.
+ ///
+ /// The ID of the source block for the connector.
+ /// The ID of the destination block for the connector.
+ ///
+ /// This will remove any connector with the from/to ID pair, which could be multiple connectors. This is
+ /// because there is no ID to uniquely identify a generic . If no connectors are
+ /// found with provided ID pair, then will return.
+ ///
+ public void Disconnect(uint from, uint to)
+ {
+ Element.Elements(typeof(TConnector).L5XType())
+ .Where(e => e.Attribute(L5XName.FromID)?.Value.Parse() == from ||
+ e.Attribute(L5XName.FromID)?.Value.Parse() == to)
+ .Remove();
}
///
@@ -258,6 +335,37 @@ public override IEnumerable References()
return Blocks().Where(b => b is ILogixReferencable).Cast().SelectMany(r => r.References());
}
+ ///
+ /// Removes the provided from the the Diagram collection.
+ ///
+ /// The to remove.
+ ///
+ /// This will remove single block with the Id of the provided block if it is found.
+ /// If not found, the will return without removing anything.
+ ///
+ public void Remove(TBlock block)
+ {
+ Element.Elements().SingleOrDefault(e => e.Attribute(L5XName.ID)?.Value.Parse() == block.ID)?.Remove();
+ }
+
+ ///
+ /// Removes the provided from the the Diagram collection.
+ ///
+ /// The to remove.
+ ///
+ /// This will remove any connector having the same from/to ID pair as the provided connector IDs if found.
+ /// If not found, the will return without removing anything.
+ ///
+ public void Remove(TConnector connector)
+ {
+ Element.Elements(typeof(TConnector).L5XType()).SingleOrDefault(e =>
+ e.Attribute(L5XName.FromID)?.Value.Parse() == connector.FromID &&
+ e.Attribute(L5XName.ToID)?.Value.Parse() == connector.ToID)
+ ?.Remove();
+ }
+
+ #region Internal
+
///
/// Joins the defined ordered set of element names with the child elements of the diagram, and replaces all current
/// nodes with the same set of order nodes. This maintains the order of the diagram elements base on the derived
@@ -295,4 +403,6 @@ private bool IsUsed(uint id)
{
return Element.Elements().Any(e => e.Attribute(L5XName.ID)?.Value.Parse() == id);
}
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/DiagramBlock.cs b/src/L5Sharp/Elements/DiagramBlock.cs
index fa05934a..84b35e09 100644
--- a/src/L5Sharp/Elements/DiagramBlock.cs
+++ b/src/L5Sharp/Elements/DiagramBlock.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Xml.Linq;
using L5Sharp.Common;
-using L5Sharp.Enums;
using L5Sharp.Utilities;
namespace L5Sharp.Elements;
@@ -11,8 +10,7 @@ namespace L5Sharp.Elements;
///
/// A base class for all FBD/SFC routine elements within a containing Diagram. This base class simply
/// contains some of the common properties and functions that all FBD/SFC elements share,
-/// such as X and Y coordinates, and ID. We have also added a and
-/// property for determining the type and location (e.g. A1) of a given block.
+/// such as X and Y coordinates, and ID.
///
public abstract class DiagramBlock : LogixElement, ILogixReferencable
{
@@ -41,8 +39,8 @@ protected DiagramBlock(XElement element) : base(element)
/// A zero based representing the block id.
public uint ID
{
- get => GetValue();
- set => SetValue(value);
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
}
///
@@ -54,8 +52,8 @@ public uint ID
///
public uint X
{
- get => GetValue();
- set => SetValue(value);
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
}
///
@@ -67,8 +65,8 @@ public uint X
///
public uint Y
{
- get => GetValue();
- set => SetValue(value);
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
}
///
@@ -88,6 +86,47 @@ public uint Y
///
public virtual string Location => Cell;
+ ///
+ /// Updates the X and Y coordinates of the to the specified cell location.
+ ///
+ /// The alpha-numeric cell location to move the block to.
+ /// cell is null, empty, not two characters, does not start with a letter,
+ /// or does not end with a digit.
+ public void MoveTo(string cell)
+ {
+ if (string.IsNullOrEmpty(cell))
+ throw new ArgumentException("Can not perform function with null or empty cell location.");
+
+ if (cell.Length != 2)
+ throw new ArgumentException(
+ $"Cell {cell} is not a valid length argument. Must be 2 character cell location.");
+
+ if (!char.IsLetter(cell[0]))
+ throw new ArgumentException($"Cell {cell} must start with a valid letter character");
+
+ if (!char.IsDigit(cell[1]))
+ throw new ArgumentException($"Cell {cell} must end with a valid number character");
+
+ X = (uint)(cell.ToUpper()[0] - 'A') * 200;
+ Y = (uint)cell[1] * 200;
+ }
+
///
public virtual IEnumerable References() => Enumerable.Empty();
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj)) return true;
+
+ return obj switch
+ {
+ ValueType value => Equals(ID, value),
+ DiagramBlock block => Equals(ID, block.ID),
+ _ => false
+ };
+ }
+
+ ///
+ public override int GetHashCode() => ID.GetHashCode();
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/DiagramConnector.cs b/src/L5Sharp/Elements/DiagramConnector.cs
index 3b57084d..22ac34f1 100644
--- a/src/L5Sharp/Elements/DiagramConnector.cs
+++ b/src/L5Sharp/Elements/DiagramConnector.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Xml.Linq;
namespace L5Sharp.Elements;
@@ -15,6 +16,8 @@ public abstract class DiagramConnector : LogixElement
///
protected DiagramConnector()
{
+ FromID = 0;
+ ToID = 0;
}
///
@@ -31,8 +34,8 @@ protected DiagramConnector(XElement element) : base(element)
///
public uint FromID
{
- get => GetValue();
- set => SetValue(value);
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
}
///
@@ -40,35 +43,67 @@ public uint FromID
///
public uint ToID
{
- get => GetValue();
- set => SetValue(value);
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
}
-
+
///
- /// Determines if this connector connects either to or from the provided DiagramBlock.
+ /// Returns the connecting endpoint of this Connector element, which is a
+ /// where the key/value is the ID and param of the block element opposite the provided block element.
///
- /// The block to determine the connection to/from.
- /// true if this wire has a ToId or FromId connecting the provided block; Otherwise, false.
- public bool IsConnected(DiagramBlock block) => ToID == block.ID || FromID == block.ID;
-
+ /// The block element for which to find the connected endpoint.
+ ///
+ /// A containing the opposite block's ID and param the connector
+ /// is connected to.
+ ///
+ ///
+ /// This makes it easier to find which block id and parameter the connector is attached to. Note that
+ /// for a generic the parameter will always be null since it does not
+ /// define a To/From parameter. The Wire connector will override this implementation to return it's
+ /// associated param name.
+ ///
+ public KeyValuePair Endpoint(DiagramBlock block) => Endpoint(block.ID);
+
///
- /// Determines if this wire connects to the provided DiagramBlock.
+ /// Returns the connecting endpoint of this Connector element, which is a
+ /// where the key/value is the ID/Param of the block element opposite the provided block element.
///
- /// The block to determine the connection to.
- /// ture if this wire has a ToID connecting the provided block; Otherwise, false.
- public bool ConnectsTo(DiagramBlock block) => ToID == block.ID;
+ /// The ID of the block element for which to find the connected endpoint.
+ ///
+ /// A containing the opposite block's ID and Param the connector
+ /// is connected to.
+ ///
+ ///
+ /// This makes it easier to find which block id and parameter the connector is attached to. Note that
+ /// for a generic the parameter will always be null since it does not
+ /// define a To/From parameter. The Wire connector will override this implementation to return it's
+ /// associated param name.
+ ///
+ public virtual KeyValuePair Endpoint(uint id)
+ {
+ return FromID == id ? new KeyValuePair(ToID, default)
+ : ToID == id ? new KeyValuePair(FromID, default)
+ : throw new ArgumentException($"The connector does not have a to/from id matching the id '{id}'");
+ }
///
- /// Determines if this wire has a connection from the provided DiagramBlock.
+ /// Determines if this Connector has a connection or endpoint to the provided Block.
///
- /// The block to determine the connection to.
- /// ture if this wire has a ToID connecting the provided block; Otherwise, false.
- public bool ConnectsFrom(DiagramBlock block) => FromID == block.ID;
-
+ ///
+ ///
+ public bool IsConnected(DiagramBlock block) => block.ID == FromID || block.ID == ToID;
+
+ ///
+ /// Determines if this Connector has a connection or endpoint to the provided Block.
+ ///
+ ///
+ ///
+ public bool IsConnectedTo(DiagramBlock block) => block.ID == ToID;
+
///
///
///
///
///
- public uint? Connected(DiagramBlock block) => ToID == block.ID ? FromID : FromID == block.ID ? ToID : default;
+ public bool IsConnectedFrom(DiagramBlock block) => block.ID == FromID;
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Function.cs b/src/L5Sharp/Elements/Function.cs
index 14575730..fc50a060 100644
--- a/src/L5Sharp/Elements/Function.cs
+++ b/src/L5Sharp/Elements/Function.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections.Generic;
using System.Xml.Linq;
+using L5Sharp.Common;
using L5Sharp.Utilities;
namespace L5Sharp.Elements;
@@ -20,17 +22,26 @@ namespace L5Sharp.Elements;
/// `Logix 5000 Controllers Import/Export` for more information.
///
[L5XType(L5XName.Function, L5XName.Sheet)]
-public class Function : FunctionBlock
+public sealed class Function : FunctionBlock
{
///
- /// Creates a new with default values.
+ /// Creates a new with default values.
///
public Function()
{
}
///
- /// Creates a new initialized with the provided .
+ /// Creates a new with the provided type name.
+ ///
+ ///
+ public Function(string type)
+ {
+ Type = type;
+ }
+
+ ///
+ /// Creates a new initialized with the provided .
///
/// The to initialize the type with.
/// element is null.
@@ -47,4 +58,15 @@ public string? Type
get => GetValue();
set => SetValue(value);
}
+
+ ///
+ ///
+ ///
+ public static Function BAND => new($"{nameof(BAND)}_F");
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return Argument.Unknown;
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/FunctionBlock.cs b/src/L5Sharp/Elements/FunctionBlock.cs
index d8522124..0c1d06a1 100644
--- a/src/L5Sharp/Elements/FunctionBlock.cs
+++ b/src/L5Sharp/Elements/FunctionBlock.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
+using L5Sharp.Common;
namespace L5Sharp.Elements;
@@ -40,12 +41,33 @@ protected FunctionBlock(XElement element) : base(element)
/// The parent element that this FunctionBlock is contained within.
///
public Sheet? Sheet => Element.Parent is not null ? new Sheet(Element.Parent) : default;
+
+ ///
+ ///
+ ///
+ ///
+ public IEnumerable Arguments()
+ {
+ var arguments = new List();
+
+ var wires = Sheet?.Connectors(this) ?? Enumerable.Empty();
+
+ foreach (var wire in wires)
+ {
+ var endpoint = wire.Endpoint(this);
+ var block = Sheet?.Block(endpoint.Key);
+ var args = block?.GetArguments(endpoint);
+ if (args is null) continue;
+ arguments.AddRange(args);
+ }
+
+ return arguments;
+ }
///
- /// Finds all other elements that have connections to this block element.
+ ///
///
- /// An containing connected element objects.
- /// This relies on the parent diagram element to find other connecting blocks.
- /// If this block element is not attached to a Sheet then it will return and empty collection.
- public IEnumerable Connections() => Sheet?.Connections(this) ?? Enumerable.Empty();
+ ///
+ ///
+ protected abstract IEnumerable GetArguments(KeyValuePair endpoint);
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/ICON.cs b/src/L5Sharp/Elements/ICON.cs
index a3308658..61fff24d 100644
--- a/src/L5Sharp/Elements/ICON.cs
+++ b/src/L5Sharp/Elements/ICON.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Xml.Linq;
+using L5Sharp.Common;
using L5Sharp.Enums;
using L5Sharp.Utilities;
@@ -55,4 +58,21 @@ public string? Name
get => GetValue();
set => SetValue(value);
}
+
+ ///
+ /// The pair block that represents the other end of this connection.
+ ///
+ /// A block element with the same connector name.
+ public OCON? Pair => Sheet?.Blocks().FirstOrDefault(b => b.Name == Name);
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ if (endpoint.Key != ID || Pair is null)
+ {
+ return Enumerable.Empty();
+ }
+
+ return Pair.Arguments();
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/IREF.cs b/src/L5Sharp/Elements/IREF.cs
index 73598db6..a6edfe0b 100644
--- a/src/L5Sharp/Elements/IREF.cs
+++ b/src/L5Sharp/Elements/IREF.cs
@@ -48,7 +48,7 @@ public IREF(XElement element) : base(element)
}
///
- /// The tag name or immediate operand value for the reference element.
+ /// The tag name or immediate value for the reference element.
///
/// A containing the reference if it exists; Otherwise, null.
public Argument? Operand
@@ -75,4 +75,10 @@ public override IEnumerable References()
yield return new CrossReference(Element, L5XName.Tag, Operand.ToString());
}
}
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return Operand ?? Argument.Unknown;
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/JSR.cs b/src/L5Sharp/Elements/JSR.cs
index 7b2129dd..94f90a82 100644
--- a/src/L5Sharp/Elements/JSR.cs
+++ b/src/L5Sharp/Elements/JSR.cs
@@ -81,4 +81,10 @@ public override IEnumerable References()
foreach (var parameter in Ret)
yield return new CrossReference(Element, L5XName.Tag, parameter);
}
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return endpoint.Value is not null ? new TagName(endpoint.Value) : Argument.Empty;
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Line.cs b/src/L5Sharp/Elements/Line.cs
index 10b891ab..34c5cb06 100644
--- a/src/L5Sharp/Elements/Line.cs
+++ b/src/L5Sharp/Elements/Line.cs
@@ -50,10 +50,10 @@ public override IEnumerable References()
var references = new List();
references.AddRange(Text.Tags()
- .Select(name => new CrossReference(Element, name, L5XName.Tag)));
+ .Select(name => new CrossReference(Element, L5XName.Tag, name)));
references.AddRange(Text.Instructions()
- .Select(instruction => new CrossReference(Element, instruction.Key, L5XName.AddOnInstructionDefinition)));
+ .Select(instruction => new CrossReference(Element, L5XName.AddOnInstructionDefinition, instruction.Key)));
//todo routines? Have to look for JSR and SBR, RET
//todo modules? Have to look for tag names with ':'
diff --git a/src/L5Sharp/Elements/OCON.cs b/src/L5Sharp/Elements/OCON.cs
index ccca23c2..9446b4d0 100644
--- a/src/L5Sharp/Elements/OCON.cs
+++ b/src/L5Sharp/Elements/OCON.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Xml.Linq;
+using L5Sharp.Common;
using L5Sharp.Utilities;
namespace L5Sharp.Elements;
@@ -51,4 +54,21 @@ public string? Name
get => GetValue();
set => SetValue(value);
}
+
+ ///
+ /// The pair block that represents the other end of this connection.
+ ///
+ /// A block element with the same connector name.
+ public ICON? Pair => Sheet?.Blocks().FirstOrDefault(b => b.Name == Name);
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ if (endpoint.Key != ID || Pair is null)
+ {
+ return Enumerable.Empty();
+ }
+
+ return Pair.Arguments();
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/OREF.cs b/src/L5Sharp/Elements/OREF.cs
index 597d7a6b..ef56dff4 100644
--- a/src/L5Sharp/Elements/OREF.cs
+++ b/src/L5Sharp/Elements/OREF.cs
@@ -72,7 +72,13 @@ public override IEnumerable References()
{
if (Operand is not null && Operand.IsTag)
{
- yield return new CrossReference(Element, L5XName.Tag, Operand.ToString());
+ yield return new CrossReference(Element, L5XName.Tag, Operand.ToString(), new Instruction(nameof(IREF), Operand));
}
}
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return Operand ?? Argument.Unknown;
+ }
}
diff --git a/src/L5Sharp/Elements/RET.cs b/src/L5Sharp/Elements/RET.cs
new file mode 100644
index 00000000..1d61068f
--- /dev/null
+++ b/src/L5Sharp/Elements/RET.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Linq;
+using L5Sharp.Common;
+using L5Sharp.Utilities;
+
+namespace L5Sharp.Elements;
+
+///
+/// A DiagramBlock type that defines the properties for a call to a Routine.
+///
+///
+[L5XType(L5XName.RET, L5XName.Sheet)]
+public class RET : FunctionBlock
+{
+ ///
+ /// Creates a new with default values.
+ ///
+ public RET()
+ {
+ }
+
+ ///
+ /// Creates a new initialized with the provided .
+ ///
+ /// The to initialize the type with.
+ /// element is null.
+ public RET(XElement element) : base(element)
+ {
+ }
+
+ ///
+ /// The name of the routine to call for the element.
+ ///
+ /// A containing the name of the routine if found; Otherwise, null.
+ public string? Routine
+ {
+ get => GetValue();
+ set => SetValue(value);
+ }
+
+ ///
+ /// The collection of input parameters to the routine being called by the FunctionBlock element.
+ ///
+ /// A object wrapping the underlying attribute containing the In parameters
+ /// if exists; Otherwise, null.
+ public Params? Ret
+ {
+ get => Element.Attribute(L5XName.In) is not null ? new Params(Element.Attribute(L5XName.In)!) : default;
+ set => SetValue(value is not null ? string.Join(" ", value) : null);
+ }
+
+ ///
+ public override IEnumerable References()
+ {
+ if (Routine is not null)
+ yield return new CrossReference(Element, L5XName.Routine, Routine);
+
+ if (Ret is null) yield break;
+
+ foreach (var parameter in Ret)
+ yield return new CrossReference(Element, L5XName.Tag, parameter);
+ }
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return endpoint.Value is not null ? new TagName(endpoint.Value) : Argument.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Rung.cs b/src/L5Sharp/Elements/Rung.cs
index 1aec6c0e..1644dfbb 100644
--- a/src/L5Sharp/Elements/Rung.cs
+++ b/src/L5Sharp/Elements/Rung.cs
@@ -75,6 +75,39 @@ public string? Comment
get => GetProperty();
set => SetProperty(value);
}
+
+ ///
+ /// Returns a flat list of representing all base and nested AoiBlock logic in the
+ /// collection of objects.
+ ///
+ /// A containing all the , including nested instruction
+ /// text, found in the rung collection.
+ ///
+ ///
+ /// This extension was specifically created to assist in getting a flat list of logic, including
+ /// nested AoiBlock logic, for specialized querying purposes, such as finding tag references within nested logic.
+ /// This method will replace the instruction logic parameters with the neutral text operands of the instruction signature,
+ /// so to get the effective flattened list of executing code.
+ ///
+ public IEnumerable Flatten()
+ {
+ if (L5X is null)
+ throw new InvalidOperationException("Can not flatten rungs that are not attached to a L5X content file.");
+
+ var code = new List();
+
+ var references = L5X.Instructions
+ .Select(i => new { Instruction = i, Instances = Text.Instructions(i.Name) })
+ .ToList();
+
+ foreach (var reference in references.Where(r => r.Instances.Any()))
+ {
+ var logic = reference.Instances.SelectMany(i => reference.Instruction.LogicFor(i));
+ code.AddRange(logic);
+ }
+
+ return code;
+ }
///
public override IEnumerable References()
@@ -86,21 +119,24 @@ public override IEnumerable References()
if (instruction.CallsRoutine)
{
var routine = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty;
- references.Add(new CrossReference(Element, L5XName.Routine, routine));
+ references.Add(new CrossReference(Element, L5XName.Routine, routine, instruction));
+
var parameters = instruction.Arguments.Skip(1).Where(a => a.IsTag).Select(t => t.ToString());
- references.AddRange(parameters.Select(p => new CrossReference(Element, L5XName.Tag, p)));
+ references.AddRange(parameters.Select(p => new CrossReference(Element, L5XName.Tag, p, instruction)));
continue;
}
if (instruction.CallsTask)
{
var task = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty;
- references.Add(new CrossReference(Element, L5XName.Task, task));
+ references.Add(new CrossReference(Element, L5XName.Task, task, instruction));
continue;
}
+
+ //todo other instructions like GSV SSV
references.AddRange(instruction.Text.Tags()
- .Select(t => new CrossReference(Element, L5XName.Tag, t.ToString())));
+ .Select(t => new CrossReference(Element, L5XName.Tag, t.ToString(), instruction)));
}
return references;
@@ -122,43 +158,6 @@ public override IEnumerable References()
/// The NeutralText to convert.
/// A instance representing the contents of the NeutralText.
public static implicit operator Rung(NeutralText text) => new(text);
-
- #region Extensions
-
- ///
- /// Returns a flat list of representing all base and nested AoiBlock logic in the
- /// collection of objects.
- ///
- /// A containing all the , including nested instruction
- /// text, found in the rung collection.
- ///
- ///
- /// This extension was specifically created to assist in getting a flat list of logic, including
- /// nested AoiBlock logic, for specialized querying purposes, such as finding tag references within nested logic.
- /// This method will replace the instruction logic parameters with the neutral text operands of the instruction signature,
- /// so to get the effective flattened list of executing code.
- ///
- public IEnumerable Flatten()
- {
- if (L5X is null)
- throw new InvalidOperationException("Can not flatten rungs that are not attached to a L5X content file.");
-
- var code = new List();
-
- var references = L5X.Instructions
- .Select(i => new { Instruction = i, Instances = Text.Instructions(i.Name) })
- .ToList();
-
- foreach (var reference in references.Where(r => r.Instances.Any()))
- {
- var logic = reference.Instances.SelectMany(i => reference.Instruction.LogicFor(i));
- code.AddRange(logic);
- }
-
- return code;
- }
-
- #endregion
}
///
diff --git a/src/L5Sharp/Elements/SBR.cs b/src/L5Sharp/Elements/SBR.cs
index f81db800..1d350924 100644
--- a/src/L5Sharp/Elements/SBR.cs
+++ b/src/L5Sharp/Elements/SBR.cs
@@ -14,7 +14,7 @@ namespace L5Sharp.Elements;
/// `Logix 5000 Controllers Import/Export` for more information.
///
[L5XType(L5XName.SBR, L5XName.Sheet)]
-public class SBR : DiagramBlock
+public class SBR : FunctionBlock
{
///
/// Creates a new with default values.
@@ -43,20 +43,31 @@ public string? Routine
}
///
- /// A collection of input parameter names for the JSR.
+ /// The collection of input parameters to the routine being called by the FunctionBlock element.
///
- /// A containing the names of the parameters if found. If not found then an
- /// empty collection.
- /// To update the property, you must assign a new collection of names.
- public IEnumerable In => throw new NotImplementedException();
+ /// A object wrapping the underlying attribute containing the In parameters
+ /// if exists; Otherwise, null.
+ public Params? In
+ {
+ get => Element.Attribute(L5XName.In) is not null ? new Params(Element.Attribute(L5XName.In)!) : default;
+ set => SetValue(value is not null ? string.Join(" ", value) : null);
+ }
///
public override IEnumerable References()
{
if (Routine is not null)
- yield return new CrossReference(Element, Routine, L5XName.Routine);
+ yield return new CrossReference(Element, L5XName.Routine, Routine);
+ if (In is null) yield break;
+
foreach (var parameter in In)
- yield return new CrossReference(Element, parameter, L5XName.Tag);
+ yield return new CrossReference(Element, L5XName.Tag, parameter);
+ }
+
+ ///
+ protected override IEnumerable GetArguments(KeyValuePair endpoint)
+ {
+ yield return endpoint.Value is not null ? new TagName(endpoint.Value) : Argument.Empty;
}
}
\ No newline at end of file
diff --git a/src/L5Sharp/Elements/Sheet.cs b/src/L5Sharp/Elements/Sheet.cs
index 136aa05a..d77e6e1c 100644
--- a/src/L5Sharp/Elements/Sheet.cs
+++ b/src/L5Sharp/Elements/Sheet.cs
@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
+using L5Sharp.Common;
using L5Sharp.Utilities;
namespace L5Sharp.Elements;
///
-/// A Logix Sheet block containing the properties for a L5X Sheet block.
+/// A Logix Sheet block containing the properties for a L5X Sheet or Function Block Diagram (FBD).
///
///
/// A Sheet implements and is the type of content that FBD routines contain.
///
-/// Observe these guidelines when defining a controller:
+/// Observe these guidelines when defining a sheet:
/// • The sheets in the routine appear in order in the export file.
/// Each sheet section contains all the drawing elements and wires for that sheet.
/// • On import, sheet numbers are assigned based on order in the file, not on the number attribute on the sheet.
diff --git a/src/L5Sharp/Elements/Wire.cs b/src/L5Sharp/Elements/Wire.cs
index b5ee9fdd..b1383e17 100644
--- a/src/L5Sharp/Elements/Wire.cs
+++ b/src/L5Sharp/Elements/Wire.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Xml.Linq;
using L5Sharp.Utilities;
@@ -54,15 +55,20 @@ public string? ToParam
}
///
- /// Determines if this connector connects either to or from the provided DiagramBlock.
+ /// The parent element that this FunctionBlock is contained within.
///
- /// The id of the source block to find the connection to.
- /// The parameter name of the source block to find the connection to.
- /// true if this wire has a ToId or FromId connecting the provided block; Otherwise, false.
- public Tuple? Connection(uint id, string param)
+ public Sheet? Sheet => Element.Parent is not null ? new Sheet(Element.Parent) : default;
+
+ ///
+ ///
+ /// This makes it easier to find which block id and parameter the connector is attached to. This class is overriding
+ /// the default implementation to return the local or depending on the
+ /// associated to/from ID of the endpoint.
+ ///
+ public override KeyValuePair Endpoint(uint id)
{
- return ToID == id && ToParam?.IsEquivalent(param) == true ? new Tuple(FromID, FromParam)
- : FromID == id && FromParam?.IsEquivalent(param) == true ? new Tuple(ToID, ToParam)
- : default;
+ return FromID == id ? new KeyValuePair(ToID, ToParam)
+ : ToID == id ? new KeyValuePair(FromID, FromParam)
+ : throw new ArgumentException($"The connector does not have a to/from id matching the id '{id}'");
}
}
\ No newline at end of file
diff --git a/src/L5Sharp/Enums/Radix.cs b/src/L5Sharp/Enums/Radix.cs
index f85f9284..6b536312 100644
--- a/src/L5Sharp/Enums/Radix.cs
+++ b/src/L5Sharp/Enums/Radix.cs
@@ -233,8 +233,7 @@ private static AtomicType ToAtomic(string value, int charsPerByte, int baseNumbe
> 2 and <= 4 => new DINT(Convert.ToInt32(value, baseNumber)),
> 4 and <= 8 => new LINT(Convert.ToInt64(value, baseNumber)),
_ => throw new ArgumentOutOfRangeException(nameof(byteLength),
- $"The provided value byte length '{byteLength}' is out of range for atomic conversion. " +
- "Must be between 0 and 8 bytes.")
+ $"The provided value '{value}' is out of range for atomic conversion. Must be between 0 and 8 bytes.")
};
}
diff --git a/src/L5Sharp/Enums/Scope.cs b/src/L5Sharp/Enums/Scope.cs
index 395ca687..b79a878c 100644
--- a/src/L5Sharp/Enums/Scope.cs
+++ b/src/L5Sharp/Enums/Scope.cs
@@ -22,7 +22,7 @@ private Scope(string name, string value) : base(name, value)
/// if the element has a AddOnInstructionDefinition element ancestor,
/// otherwise.
///
- public static Scope ScopeType(XElement element)
+ public static Scope Type(XElement element)
{
var ancestor = FindContainer(element)?.Name.LocalName;
@@ -42,7 +42,16 @@ public static Scope ScopeType(XElement element)
/// The element for which to find the container name of.
/// A representing the name of the containing program, instruction, or controller
/// if found; Otherwise, an empty string.
- public static string ScopeName(XElement element) => FindContainer(element)?.LogixName() ?? string.Empty;
+ public static string Container(XElement element) => FindContainer(element)?.LogixName() ?? string.Empty;
+
+ ///
+ /// Finds the container element in the ancestral chain and returns the logix name of the element. This will be either
+ /// the name of the program container or the name of the controller, depending on the scope of the element.
+ ///
+ /// The element for which to find the container name of.
+ /// A representing the name of the containing program, instruction, or controller
+ /// if found; Otherwise, an empty string.
+ public static string Task(XElement element) => FindTask(element)?.LogixName() ?? string.Empty;
///
/// Represents a Null value.
@@ -64,14 +73,29 @@ public static Scope ScopeType(XElement element)
/// Represents a Program value.
///
public static readonly Scope Instruction = new(nameof(Instruction), "InstructionScope");
-
+
///
/// Finds the first ancestor element that is either a Program, Controller, or
/// AddOnInstructionDefinition element.
///
/// The to examine.
/// The first matching ancestor if found or null.
- private static XElement? FindContainer(XNode node) =>
- node.Ancestors().FirstOrDefault(e => e.Name.LocalName
+ private static XElement? FindContainer(XNode node)
+ {
+ return node.Ancestors().FirstOrDefault(e => e.Name.LocalName
is L5XName.Program or L5XName.Controller or L5XName.AddOnInstructionDefinition);
+ }
+
+ ///
+ /// Finds the first Task element in the L5X document with the provided node's container name.
+ ///
+ /// The to examine.
+ /// The first matching ancestor if found or null.
+ private static XElement? FindTask(XNode node)
+ {
+ var container = FindContainer(node)?.LogixName() ?? string.Empty;
+
+ return node.Ancestors(L5XName.RSLogix5000Content).Descendants(L5XName.Task)
+ .FirstOrDefault(e => e.Descendants(L5XName.ScheduledProgram).Any(p => p.LogixName() == container));
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/ILogixReferencable.cs b/src/L5Sharp/ILogixReferencable.cs
index 88a525d6..26dedf75 100644
--- a/src/L5Sharp/ILogixReferencable.cs
+++ b/src/L5Sharp/ILogixReferencable.cs
@@ -4,7 +4,8 @@
namespace L5Sharp;
///
-/// Provides a common interface for Logix elements that can be referenced by other elements.
+/// Provides a common interface for Logix elements that can be referenced by or refer to other elements. Implementing
+/// this interface is signing the class up to determine it's references.
///
public interface ILogixReferencable
{
diff --git a/src/L5Sharp/L5X.cs b/src/L5Sharp/L5X.cs
index 77ee0096..2b1f088c 100644
--- a/src/L5Sharp/L5X.cs
+++ b/src/L5Sharp/L5X.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using System.Xml.Linq;
using JetBrains.Annotations;
using L5Sharp.Common;
@@ -8,6 +11,9 @@
using L5Sharp.Elements;
using L5Sharp.Enums;
using L5Sharp.Utilities;
+using Task = L5Sharp.Components.Task;
+using task = System.Threading.Tasks.Task;
+
namespace L5Sharp;
@@ -17,6 +23,7 @@ namespace L5Sharp;
///
///
///
+[PublicAPI]
public class L5X : ILogixSerializable
{
///
@@ -32,12 +39,12 @@ public class L5X : ILogixSerializable
///
/// An index of all logix components in the L5X file for fast lookups.
///
- private readonly Dictionary> _componentIndex = new();
+ private LogixIndex? _index;
///
- /// An index of all references to a logix component in the L5X file for fast lookups.
+ /// An index of all logix components in the L5X file for fast lookups.
///
- private readonly Dictionary> _referenceIndex = new();
+ private LogixIndex Index => _index ??= new LogixIndex(GetController());
///
/// The list of top level component containers for a L5X content or controller element in order of which
@@ -63,6 +70,12 @@ public class L5X : ILogixSerializable
/// L5X file.
/// content is null.
/// content name is not expected RSLogix5000Content.
+ ///
+ /// Although you create the L5X instance by providing a valid XML element, it is typically easier or more
+ /// typical to use the static factory methods to load a file from disc or
+ /// to generate a new default instance. Also note that each individual component can be exported to generate a new
+ /// L5X content file.
+ ///
public L5X(XElement content)
{
if (content is null)
@@ -78,67 +91,12 @@ public L5X(XElement content)
//files so that we won't have issues getting top level containers. When saving we can remove unused containers.
NormalizeContent();
- //Index all components for quick lookup from child elements or from top level L5X.
- IndexComponents();
- IndexReferences();
-
- //Detect changes to keep index up to date.
- _content.Changing += OnContentChanging;
- _content.Changed += OnContentChanged;
-
//This stores L5X object as in-memory object for the root XElement,
//allowing child elements to retrieve the object locally without creating a new instance (and reindexing of content).
//This allows them to reference to root L5X for cross referencing or other operations.
_content.AddAnnotation(this);
}
- ///
- /// Creates a new by loading the contents of the provide file name.
- ///
- /// The full path, including file name, to the L5X file to load.
- /// A new containing the contents of the specified file.
- /// The string is null or empty.
- ///
- /// This factory method uses the to load the contents of the XML file into
- /// memory. This means that this method is subject to the same exceptions that could be generated by loading the
- /// XElement.
- ///
- public static L5X Load(string fileName) => new(XElement.Load(fileName));
-
- ///
- /// Creates a new file with the standard root content and controller elements, and configures them
- /// with the provided controller name, processor, and revision.
- ///
- /// The name of the controller.
- /// The processor catalog number.
- /// The optional software revision of the processor.
- /// A new default with the specified controller properties.
- public static L5X New(string name, string processor, Revision? revision) =>
- new(NewContent(name, nameof(Controller), revision));
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static L5X New(TComponent component, Revision? revision = null)
- where TComponent : LogixComponent => new(NewContent(component.Name, typeof(TComponent).L5XType(), revision));
-
- ///
- /// Creates a new with the provided L5X string content.
- ///
- /// The string that contains the L5X content to parse.
- /// A new containing the contents of the specified string.
- /// The string is null or empty.
- ///
- /// This factory method uses the to load the contents of the XML file into
- /// memory. This means that this method is subject to the same exceptions that could be generated by parsing the
- /// XElement.
- ///
- public static L5X Parse(string text) => new(XElement.Parse(text));
-
///
/// The representing the L5X content export information.
///
@@ -157,21 +115,18 @@ public static L5X New(TComponent component, Revision? revision = nul
/// The container collection of components found in the L5X file.
///
/// A of components.
- [PublicAPI]
public LogixContainer DataTypes => new(GetContainer(L5XName.DataTypes));
///
/// Gets the collection of components found in the L5X file.
///
/// A of components.
- [PublicAPI]
public LogixContainer Instructions => new(GetContainer(L5XName.AddOnInstructionDefinitions));
///
/// Gets the collection of components found in the L5X file.
///
/// A of components.
- [PublicAPI]
public LogixContainer Modules => new(GetContainer(L5XName.Modules));
///
@@ -213,35 +168,70 @@ public static L5X New(TComponent component, Revision? revision = nul
public LogixContainer WatchLists => new(GetContainer(L5XName.QuickWatchLists));
///
- /// Adds the given logix component to the first found container within the L5X tree.
+ /// Creates a new by loading the contents of the provide file name.
///
- /// The component to add to the L5X.
- /// The type of component to add to the L5X.
- /// No container was found in the L5X tree for the specified type.
+ /// The full path, including file name, to the L5X file to load.
+ /// A new containing the contents of the specified file.
+ /// The string is null or empty.
///
- /// This provides a more dynamic way to add content to an L5X file. Note that this only adds to the first
- /// container found of the specific type. If you are adding scoped components such as Tag or Routine
- /// you should be doing so in the context of a Program component.
+ /// This factory method uses the to load the contents of the XML file into
+ /// memory. This means that this method is subject to the same exceptions that could be generated by loading the
+ /// XElement.
///
- public void Add(TComponent component) where TComponent : LogixComponent
+ public static L5X Load(string fileName) => new(XElement.Load(fileName));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task LoadAsync(string fileName, CancellationToken token)
{
- var containerType = typeof(TComponent).L5XContainer();
- var container = GetContainer(containerType);
- container.Add(component.Serialize());
+ await using var stream = new FileStream(fileName, FileMode.Open);
+ var element = await XElement.LoadAsync(stream, LoadOptions.SetLineInfo, token);
+ var l5X = await task.Run(() => new L5X(element), token);
+ return l5X;
}
///
- /// Gets the number of elements of the specified type in the L5X.
+ /// Creates a new file with the standard root content and controller elements, and configures them
+ /// with the provided controller name, processor, and revision.
///
- /// The logix element type to get the count for.
- /// A representing the number of elements of the specified type.
- public int Count() where TElement : LogixElement =>
- _content.Descendants(typeof(TElement).L5XType()).Count();
+ /// The name of the controller.
+ /// The processor catalog number.
+ /// The optional software revision of the processor.
+ /// A new default with the specified controller properties.
+ public static L5X New(string name, string processor, Revision? revision) =>
+ new(NewContent(name, nameof(Controller), revision));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static L5X New(TComponent component, Revision? revision = null)
+ where TComponent : LogixComponent => new(NewContent(component.Name, typeof(TComponent).L5XType(), revision));
+
+ ///
+ /// Creates a new with the provided L5X string content.
+ ///
+ /// The string that contains the L5X content to parse.
+ /// A new containing the contents of the specified string.
+ /// The string is null or empty.
+ ///
+ /// This factory method uses the to load the contents of the XML file into
+ /// memory. This means that this method is subject to the same exceptions that could be generated by parsing the
+ /// XElement.
+ ///
+ public static L5X Parse(string text) => new(XElement.Parse(text));
///
/// Finds element across the entire L5X with the provided type as a flat collection of object.
///
- /// The type name or element name to retrieve.
+ /// The type name or element name to retrieve.
/// A containing all found object with the provided type name.
/// type is null.
///
@@ -258,12 +248,12 @@ public int Count() where TElement : LogixElement =>
/// For example for controller scoped tag components only.
///
///
- public IEnumerable Find(string typeName)
+ public IEnumerable Query(string type)
{
- if (string.IsNullOrEmpty(typeName))
- throw new ArgumentNullException(nameof(typeName), "Type is required to retrieve elements from the L5X");
+ if (string.IsNullOrEmpty(type))
+ throw new ArgumentNullException(nameof(type), "Type is required to retrieve elements from the L5X");
- return _content.Descendants(typeName).Select(e => e.Deserialize());
+ return _content.Descendants(type).Select(e => e.Deserialize());
}
///
@@ -286,7 +276,7 @@ public IEnumerable Find(string typeName)
/// For example for controller scoped tag components only.
///
///
- public IEnumerable Find(Type type)
+ public IEnumerable Query(Type type)
{
if (type is null)
throw new ArgumentNullException(nameof(type), "Type is required to retrieve elements from the L5X");
@@ -306,10 +296,9 @@ public IEnumerable Find(Type type)
///
/// This methods provides a flexible and simple way to query the entire L5X for a specific type. Since
/// it returns an , you can make use of LINQ and the strongly typed objects to build
- /// more complex queries. This method does not make use of any optimized searching, so if you want to find items quickly,
- /// see or and .
+ /// more complex queries.
///
- public IEnumerable Find() where TElement : LogixElement
+ public IEnumerable Query() where TElement : LogixElement
{
var typeNames = typeof(TElement).L5XTypes().ToList();
@@ -319,288 +308,553 @@ public IEnumerable Find() where TElement : LogixElement
}
///
- /// Finds the first component with the specified name using the internal component index.
+ /// Adds the given logix component to the first found container within the L5X tree.
///
- ///
- ///
- /// The first found with the specified component name; If none exist, then null.
- ///
+ /// The component to add to the L5X.
+ /// The type of component to add to the L5X.
+ ///
+ /// This provides a more dynamic way to add content to an L5X file. Note that this only adds to the first
+ /// container found of the specific type. If you are adding scoped components such as Tag or Routine
+ /// you should be doing so in the context of a Program component.
+ ///
+ public void Add(TComponent component) where TComponent : LogixComponent
+ {
+ var containerType = typeof(TComponent).L5XContainer();
+ var container = GetContainer(containerType);
+ container.Add(component.Serialize());
+ }
+
+ ///
+ /// Adds the given logix component to the first found container within the L5X tree.
+ ///
+ /// The component to add to the L5X.
+ ///
+ /// The type of component to add to the L5X.
+ /// No container was found in the L5X tree for the specified type.
+ ///
+ /// This provides a more dynamic way to add content to an L5X file. Note that this only adds to the first
+ /// container found of the specific type. If you are adding scoped components such as Tag or Routine
+ /// you should be doing so in the context of a Program component.
+ ///
+ public void AddTo(TComponent component, string container) where TComponent : LogixComponent
+ {
+ var type = typeof(TComponent).L5XContainer();
+ var target = _content.Descendants(type).FirstOrDefault(e => Scope.Container(e) == container);
+ if (target is null)
+ throw new InvalidOperationException($"Not container with name '{container}' was found in the L5X.");
+ target.Add(component.Serialize());
+ }
+
+ ///
+ /// Retrieves all components with the specified key.
+ ///
+ /// The to search for.
+ /// An of having the specified type/name
+ /// composite key.
+ ///
+ ///
+ /// Note that this method returns all components with the type/name pair. This is typically a single component,
+ /// but types like Tag can have same name across different scopes, so it may be multiple different objects.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
+ ///
+ public IEnumerable All(ComponentKey key)
+ {
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Select(c => c.Value.Deserialize())
+ : Enumerable.Empty();
+ }
+
+ ///
+ /// Retrieves all components of type with the specified name.
+ ///
+ /// The name of the components to retrieve.
+ /// The type of components to retrieve.
+ /// An of having the specified type/name
+ /// composite key.
/// name is null or empty.
///
///
- /// Since components have unique names, we can find and index them for fast lookup when needed. There may be more
- /// than one component with the same name in the L5X file (Tags, Routines). This method just returns the first one
- /// found.
+ /// Note that this method returns all components with the type/name pair. This is typically a single component,
+ /// but types like Tag or Routine can have same name across different scopes, so it may be multiple different objects.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
///
///
- public LogixComponent? FindComponent(string name)
+ ///
+ public IEnumerable All(string name) where TComponent : LogixComponent
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name can not be null or empty.", nameof(name));
- var components = ComponentType.All().Select(c => c.Name).ToList();
+ var key = new ComponentKey(typeof(TComponent).L5XType(), name);
- return _content.Descendants()
- .FirstOrDefault(e => components.Any(n => n == e.L5XType() && e.LogixName() == name))
- ?.Deserialize();
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Select(c => c.Value.Deserialize())
+ : Enumerable.Empty();
}
///
- /// Finds the first component with the using the internal component index.
+ /// Retrieves all tags with the given tag name across the entire L5X file.
///
- ///
- ///
- /// The first found with the specified component key; If none exist, then null.
+ /// The name of the tag to search for.
+ /// An of objects that match the given tag name.
+ /// Note that this could be multiple tags from different containers or scopes.
///
+ /// tagName parameter is null.
///
///
- /// Since components have unique names, we can find and index them for fast lookup when needed. There may be more
- /// than one component with the same name in the L5X file (Tags, Routines). This method just returns the first one
- /// found.
+ /// The provided tag name can be a top level tag name or a nested (dot-down) tag name path. This method will
+ /// find the root tag and then reach down the tag hierarchy as specified. Note that this method can return
+ /// multiple tags if the specified tag name exists in different scopes.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
///
///
- public LogixComponent? FindComponent(ComponentKey key)
+ ///
+ public IEnumerable All(TagName tagName)
{
- return _componentIndex.TryGetValue(key, out var components)
- ? components.Values.FirstOrDefault()?.Deserialize()
+ if (tagName is null) throw new ArgumentNullException(nameof(tagName));
+
+ var key = new ComponentKey(nameof(Tag), tagName.Root);
+
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.Select(t => new Tag(t).Member(tagName.Path)).Where(t => t is not null).Cast()
+ : Enumerable.Empty();
+ }
+
+ ///
+ /// Checks if a component object with the specified type and name composite key exists in the L5X file.
+ ///
+ /// The to check.
+ /// true if the component key exists in the L5X; otherwise, false.
+ /// This
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
+ public bool Contains(ComponentKey key) => Index.Components.ContainsKey(key);
+
+ ///
+ /// Checks if a component object with the specified type and name composite key exists in the L5X file.
+ ///
+ /// The name of the component to check for existence.
+ /// The type of component to check for existence.
+ /// true if the component key exists in the L5X; otherwise, false.
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
+ public bool Contains(string name) =>
+ Index.Components.ContainsKey(new ComponentKey(typeof(TComponent).L5XType(), name));
+
+ ///
+ /// Returns the number of elements of the specified type in the L5X.
+ ///
+ /// The logix element type to get the count for.
+ /// A representing the number of elements of the specified type.
+ public int Count() where TElement : LogixElement =>
+ _content.Descendants(typeof(TElement).L5XType()).Count();
+
+ ///
+ /// Finds the first component in the L5X file having the provided composite type/name key.
+ ///
+ /// The to search for.
+ /// The found if it exists, otherwise returns null.
+ ///
+ ///
+ /// If there are multiple different scoped components with the same component key, this method will return the first
+ /// found object. For types like Tag, this will be the controller scoped instance (as it is indexed first).
+ /// For non-scoped components, there should be only one component anyway.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
+ ///
+ ///
+ public LogixComponent? Find(ComponentKey key)
+ {
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.First()?.Deserialize()
: default;
}
///
- /// Finds the first component with the specified name and generic type parameter using the internal component index.
+ /// Finds a component in the L5X file having the provided composite type/name key and container name.
+ ///
+ /// The to search for.
+ /// The name of the container (controller, program, instruction) in which the
+ /// component should be found.
+ /// The found if it exists, otherwise returns null.
+ /// container is null or empty.
+ ///
+ ///
+ /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of
+ /// just getting the first found component object. Container represents the name of the controller, program, or
+ /// instruction the scoped component is/should be contained in. If no component is found in that scope,
+ /// this method returns null.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
+ ///
+ public LogixComponent? Find(ComponentKey key, string container)
+ {
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
+
+ if (Index.Components.TryGetValue(key, out var components)
+ && components.TryGetValue(container, out var component))
+ return component.Deserialize();
+
+ return default;
+ }
+
+ ///
+ /// Finds the first component in the L5X file having the provided type and name.
///
/// The name of the component to find.
/// The type of component to find.
- ///
- /// The first found with the specified component name and type; If none exist, then null.
- ///
+ /// The found if it exists, otherwise returns null.
/// name is null or empty.
///
///
- /// Since components have unique names, we can find and index them for fast lookup when needed. There may be more
- /// than one component with the same name in the L5X file (Tags, Routines). This method just returns the first one
- /// found.
+ /// If there are multiple different scoped components with the same type and name, this method will return the first
+ /// found object. For types like Tag, this will be the controller scoped instance (as it is indexed first).
+ /// For non-scoped components, there should be only one component anyway.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
///
///
- ///
- public TComponent? FindComponent(string name) where TComponent : LogixComponent
+ public TComponent? Find(string name) where TComponent : LogixComponent
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("Name can not be null or empty.", nameof(name));
var key = new ComponentKey(typeof(TComponent).L5XType(), name);
- return _componentIndex.TryGetValue(key, out var components)
- ? (TComponent?)components.Values.FirstOrDefault()?.Deserialize()
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.First()?.Deserialize()
: default;
}
///
- /// Finds a component with the specified component and scope name using the internal component index.
+ /// Finds a component in the L5X file having the provided composite type/name key and container name.
///
/// The name of the component to find.
- /// The name of the program in which to search for the component.
- /// This really only applies to tags and routines since they are program scoped components.
+ /// The name of the container (controller, program, instruction) in which the
+ /// component should be found.
/// The type of component to find.
- ///
- /// A single with the specified component name and scope if found; Otherwise, null.
- ///
- /// name or scope is null or empty.
+ /// The found if it exists, otherwise returns null.
+ /// name or container is null or empty.
///
- /// Since components have unique names, we can find and index them for fast lookup when needed. There may be more
- /// than one component with the same name inf the L5X file (Tags, Routines). This method
- /// returns the single component within the specified scope.
+ ///
+ /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of
+ /// just getting the first found component object. Container represents the name of the controller, program, or
+ /// instruction the scoped component is/should be contained in. If no component is found in that scope,
+ /// this method returns null.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
///
- public TComponent? FindComponent(string name, string scope) where TComponent : LogixComponent
+ public TComponent? Find(string name, string container) where TComponent : LogixComponent
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
- if (string.IsNullOrEmpty(scope)) throw new ArgumentException("Scope can not be null or empty.", nameof(scope));
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
var key = new ComponentKey(typeof(TComponent).L5XType(), name);
- if (_componentIndex.TryGetValue(key, out var components) && components.TryGetValue(scope, out var element))
- element.Deserialize();
+ if (Index.Components.TryGetValue(key, out var components)
+ && components.TryGetValue(container, out var component))
+ return component.Deserialize();
return default;
}
///
- /// Finds all component with the specified name using the internal component index.
+ /// Finds the first in the L5X file having the specified tag name.
///
- /// The name of the component to find.
- /// The type of component to find.
- ///
- /// A collection of with the specified component name if any are found.
- /// If none are found, an empty collection.
- ///
- /// name is null or empty.
+ /// The of the tag to find.
+ /// The found if it exists, otherwise returns null.
+ /// tagName is null.
///
///
- /// Since components have unique names, we can find and index them for fast lookup when needed. There may be more
- /// than one component with the same name inf the L5X file (Tags, Routines). This method
- /// returns all components of the specified type and name found in the L5X file.
+ /// This method is similar to the other Find methods with the slight difference that is will also return the
+ /// nested tag member based on the provided tagName parameter. The tag name can represent a nested or dot-down
+ /// member path to a child tag of a given base tag. So ultimately this is a slightly more concise call for a Tag
+ /// type component specifically. Note that his will return the first found tag.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
///
///
- public IEnumerable FindComponents(string name) where TComponent : LogixComponent
+ ///
+ ///
+ public Tag? Find(TagName tagName)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (tagName is null) throw new ArgumentNullException(nameof(tagName));
- var key = new ComponentKey(typeof(TComponent).L5XType(), name);
+ var key = new ComponentKey(nameof(Tag), tagName.Root);
- return _componentIndex.TryGetValue(key, out var components)
- ? components.Values.Select(e => e.Deserialize())
- : Enumerable.Empty();
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.First().Deserialize().Member(tagName.Path)
+ : default;
}
///
- /// Finds all tags with the specified name in the L5X using internal component index.
+ /// Finds a single in the L5X file having the specified tag and container name.
///
- /// The of the tag to find in the L5X file.
- /// The optional scoped container of the tag to find. If not provided, this will return
- /// the first found object with the provided tag name.
- /// A with the specified tag name and scope if found; Otherwise, null.
- /// tagName is null.
+ /// The of the tag to find.
+ /// The name of the container (controller, program, instruction) in which the tag
+ /// should be found.
+ /// The found if it exists, otherwise returns null.
+ /// tagName is null -or- container is null or empty.
///
- /// The point of this method is to provide an optimized way to retrieve a tag with a specific name in the L5X without
- /// having to iterate all tags in the file, which can be a lot if you consider nested tag members.
- /// This method uses the underlying component index to search for tags.
- /// By default this will return the first found tag with the specified name. You can also specify a scope
- /// name to find a tag within a specific program.
+ ///
+ /// This method is similar to the other Find methods with the slight difference that is will also return the
+ /// nested tag member based on the provided tagName parameter. The tag name can represent a nested or dot-down
+ /// member path to a child tag of a given base tag. So ultimately this is a slightly more concise call for a Tag
+ /// type component specifically.
+ ///
+ ///
+ /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of
+ /// just getting the first found component object. Container represents the name of the controller, program, or
+ /// instruction the scoped component is/should be contained in. If no component is found in that scope,
+ /// this method returns null.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
///
- public Tag? FindTag(TagName tagName, string? container = null)
+ ///
+ ///
+ public Tag? Find(TagName tagName, string container)
{
if (tagName is null) throw new ArgumentNullException(nameof(tagName));
- var key = new ComponentKey(typeof(Tag).L5XType(), tagName.Root);
-
- if (!_componentIndex.TryGetValue(key, out var components))
- return default;
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
- if (container is not null)
- return components.TryGetValue(container, out var element) ? new Tag(element).Member(tagName.Path) : default;
+ var key = new ComponentKey(nameof(Tag), tagName.Root);
- return components.Values.FirstOrDefault()?.Deserialize().Member(tagName.Path);
+ return Index.Components.TryGetValue(key, out var components) && components.TryGetValue(container, out var tag)
+ ? tag.Deserialize().Member(tagName.Path)
+ : default;
}
///
- /// Finds all tags with the specified name in the L5X using internal component index.
+ /// Gets the first component in the L5X file having the provided composite type/name key.
///
- /// The of the tag to find in the L5X file.
- /// A with the specified tag name if found; Otherwise, null.
- /// tagName is null.
+ /// The to search for.
+ /// The with the specified key.
+ /// No component is found with the provided composite key.
///
- /// The point of this method is to provide an optimized way to retrieve a tag with a specific name in the L5X without
- /// having to iterate all tags in the file. This method uses the underlying component index to search for tags.
+ ///
+ /// If there are multiple different scoped components with the same component key, this method will return the first
+ /// found object. For types like Tag, this will be the controller scoped instance (as it is indexed first).
+ /// For non-scoped components, there should be only one component anyway.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
///
- public IEnumerable FindTags(TagName tagName)
+ ///
+ public LogixComponent Get(ComponentKey key)
{
- if (tagName is null) throw new ArgumentNullException(nameof(tagName));
-
- var key = new ComponentKey(typeof(Tag).L5XType(), tagName.Root);
-
- return _componentIndex.TryGetValue(key, out var components)
- ? components.Values.Select(t => new Tag(t).Member(tagName.Path)).Where(t => t is not null).Cast()
- : Enumerable.Empty();
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.Single().Deserialize()
+ : throw new KeyNotFoundException($"Component not found in L5X: {key}");
}
///
- /// Returns all known references to the provided instance.
+ /// Gets a component in the L5X file having the provided composite type/name key and container name.
///
- /// The component to find references to.
- /// A containing objects with the data
- /// pertaining to the element referencing the provided logix component.
- /// component is null.
+ /// The to search for.
+ /// The name of the container (controller, program, instruction) in which the
+ /// component should be found.
+ /// The with the specified key.
+ /// container is null or empty.
+ /// No component is found with the provided composite key.
///
- /// This method calls the internal reference index which is generated upon creation of the L5X.
- /// This allows references to be located quickly without having to iterate all elements in the L5X.
+ ///
+ /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of
+ /// just getting the first found component object. Container represents the name of the controller, program, or
+ /// instruction the scoped component is/should be contained in. If no component is found in that scope,
+ /// this method returns null.
+ ///
+ ///
+ /// This method makes use of the internal component index to find a components efficiently.
+ /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly.
+ /// This is needed for operations that might be more computationally complex, such as iterating many components
+ /// and finding references or dependents.
+ ///
///
- public IEnumerable FindReferences(LogixComponent component)
+ public LogixComponent Get(ComponentKey key, string container)
{
- if (component is null) throw new ArgumentNullException(nameof(component));
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
- return _referenceIndex.TryGetValue(component.Key, out var references)
- ? references
- : Enumerable.Empty();
+ return Index.Components.TryGetValue(key, out var scoped) && scoped.TryGetValue(container, out var component)
+ ? component.Deserialize()
+ : throw new KeyNotFoundException($"Component not found in L5X: {key}");
}
///
- /// Returns all known references to the specified component type and name.
+ ///
///
- /// The component name to find references to.
- /// The component type to find references for.
- /// A containing objects with the data
- /// pertaining to the element referencing the provided logix component.
- /// name is null or empty.
- ///
- /// This method calls the internal reference index which is generated upon creation of the L5X.
- /// This allows references to be located quickly without having to iterate all elements in the L5X.
- ///
- public IEnumerable FindReferences(string name)
- where TComponent : LogixComponent
+ ///
+ ///
+ ///
+ ///
+ ///
+ public TComponent Get(string name) where TComponent : LogixComponent
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("Name can not be null or empty.", nameof(name));
+
var key = new ComponentKey(typeof(TComponent).L5XType(), name);
- return _referenceIndex.TryGetValue(key, out var references) ? references : Enumerable.Empty();
+
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.First().Deserialize()
+ : throw new KeyNotFoundException($"Component not found in L5X: {key}");
}
///
- /// Gets a component with the specified name using the internal component index.
+ /// Gets the component of type TComponent from the specified container in the L
+ /// 5X index.
///
- /// The name of the component to get.
- /// The type of component to get.
- /// A single with the specified component name.
- /// name is null or empty.
- /// A component with name was not found in the L5X.
- ///
- /// Since components have unique names, we can find and index them for fast lookup when needed. This might
- /// be helpful for certain functions that need to repeatedly find references to other components to perform
- /// certain tasks. Note that the only difference between this method and
- /// is that this method will throw an exception if the component is not found. Therefore, it also returns a
- /// non-nullable reference type so that the caller can be sure that the component was found.
- ///
- public TComponent GetComponent(string name) where TComponent : LogixComponent
+ /// The type of the component to retrieve.
+ /// The name of the component.
+ /// The container of the component.
+ /// The component of type TComponent.
+ /// Thrown when the name or container is null or empty.
+ /// Thrown when the component is not found in the L5X index.
+ public TComponent Get(string name, string container) where TComponent : LogixComponent
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
var key = new ComponentKey(typeof(TComponent).L5XType(), name);
- if (!_componentIndex.TryGetValue(key, out var components))
- throw new KeyNotFoundException($"FindComponent not found in L5X: {key}");
+ return Index.Components.TryGetValue(key, out var scoped) && scoped.TryGetValue(container, out var component)
+ ? component.Deserialize()
+ : throw new KeyNotFoundException($"Component not found in L5X: {key}");
+ }
- var component = components.Values.SingleOrDefault();
+ public Tag Get(TagName tagName)
+ {
+ if (tagName is null) throw new ArgumentNullException(nameof(tagName));
- return component is not null
- ? component.Deserialize()
- : throw new KeyNotFoundException($"FindComponent not found in L5X: {key}");
+ var key = new ComponentKey(nameof(Tag), tagName.Root);
+
+ return Index.Components.TryGetValue(key, out var components)
+ ? components.Values.First().Deserialize()[tagName.Path]
+ : throw new KeyNotFoundException($"Tag not found in L5X: {tagName}");
}
- ///
- /// Gets a component with the specified name and optional scope name using the internal component index.
- ///
- /// The name of the component to get.
- /// The name of the program in which to search for the component.
- /// This really only applies to tags and routines since they are scoped components.
- /// The type of component to find.
- /// A single with the specified component name.
- /// name or scope is null or empty.
- /// A component with name was not found in the L5X.
- ///
- /// Since components have unique names, we can find and index them for fast lookup when needed. This might
- /// be helpful for certain functions that need to repeatedly find references to other components to perform
- /// certain tasks. Note that the only difference between this method and
- /// is that this method will throw an exception if the component is not found. Therefore, it also returns a
- /// non-nullable reference type so that the caller can be sure that the component was found.
- ///
- public TComponent GetComponent(string name, string scope) where TComponent : LogixComponent
+ public Tag Get(TagName tagName, string container)
{
- if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
- if (string.IsNullOrEmpty(scope)) throw new ArgumentException("Scope can not be null or empty.", nameof(scope));
+ if (tagName is null) throw new ArgumentNullException(nameof(tagName));
+
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
+
+ var key = new ComponentKey(nameof(Tag), tagName.Root);
+
+ return Index.Components.TryGetValue(key, out var components) && components.TryGetValue(container, out var tag)
+ ? tag.Deserialize()[tagName.Path]
+ : throw new KeyNotFoundException($"Tag not found in L5X: {tagName}");
+ }
+
+ public void Remove(ComponentKey key)
+ {
+ if (!Index.Components.TryGetValue(key, out var components)) return;
+ foreach (var component in components)
+ component.Value.Remove();
+ }
+
+ public void Remove(ComponentKey key, string container)
+ {
+ if (string.IsNullOrEmpty(container))
+ throw new ArgumentException("Container can not be null or empty.", nameof(container));
+
+ if (Index.Components.TryGetValue(key, out var components) &&
+ components.TryGetValue(container, out var element))
+ {
+ element.Remove();
+ }
+ }
+
+ public void Remove(string name) where TComponent : LogixComponent
+ {
+ if (string.IsNullOrEmpty(name))
+ throw new ArgumentException("Name can not be null or empty.", nameof(name));
var key = new ComponentKey(typeof(TComponent).L5XType(), name);
+
+ if (!Index.Components.TryGetValue(key, out var components)) return;
+ foreach (var component in components)
+ component.Value.Remove();
+ }
- if (_componentIndex.TryGetValue(key, out var components) && components.TryGetValue(scope, out var element))
- element.Deserialize();
+ public IEnumerable ReferencesTo(LogixComponent component)
+ {
+ if (component is null) throw new ArgumentNullException(nameof(component));
+
+ return Index.References.TryGetValue(component.Key, out var references)
+ ? references
+ : Enumerable.Empty();
+ }
- throw new KeyNotFoundException($"FindComponent not found in L5X: {key}");
+ public IEnumerable ReferencesTo(string name) where TComponent : LogixComponent
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty.", nameof(name));
+ var key = new ComponentKey(typeof(TComponent).L5XType(), name);
+ return Index.References.TryGetValue(key, out var references) ? references : Enumerable.Empty();
}
///
@@ -643,54 +897,6 @@ public void Import(L5X content, bool overwrite = true)
#region Internal
- ///
- /// Handles adding a component to the index. This requires the element to be attached to the L5X tree
- /// for it to determine the scope of the element. This will also throw a
- /// if the component already exists in the index.
- ///
- private void AddComponent(XElement element)
- {
- var key = new ComponentKey(element.Name.LocalName, element.LogixName());
- var scope = Scope.ScopeName(element);
-
- if (_componentIndex.TryAdd(key, new Dictionary { { scope, element } })) return;
- if (!_componentIndex[key].TryAdd(scope, element))
- throw new ArgumentException($"The provided component '{key}' already exists in {scope}.");
- }
-
- ///
- /// Handles adding the provided reference to the index. If no reference exists for the provided key, a new
- /// entry is created, otherwise the reference is added to the existing collection.
- ///
- private void AddReference(CrossReference reference)
- {
- if (!_referenceIndex.TryAdd(reference.ComponentKey, new List { reference }))
- _referenceIndex[reference.ComponentKey].Add(reference);
- }
-
- ///
- /// Adds the provided collection of references. This is a convenience method to add multiple references.
- ///
- private void AddReferences(IEnumerable references)
- {
- foreach (var reference in references)
- {
- AddReference(reference);
- }
- }
-
- ///
- /// Handles adding all references to the index that are associated with the provided element.
- /// This is a convenience method since we need to parse the element as an to
- /// obtain the references to add.
- ///
- private void AddReferences(XElement element)
- {
- if (element.Deserialize() is not ILogixReferencable referencable) return;
- var references = referencable.References().ToList();
- AddReferences(references);
- }
-
///
/// Gets a top level container element from the root controller element of the L5X.
///
@@ -717,195 +923,6 @@ private XElement GetController() =>
///
private string GetControllerName() => GetController().LogixName();
- ///
- /// Finds all logix component elements and indexes them into a local dictionary for fast lookups.
- ///
- private void IndexComponents()
- {
- IndexControllerScopedComponents();
- IndexProgramScopedComponents();
- IndexModuleDefinedTagComponents();
- }
-
- ///
- /// Finds all controller scoped top level components to index. This includes all components except for program tags,
- /// routines, and module defined IO tags, which are handles separately.
- ///
- private void IndexControllerScopedComponents()
- {
- //The scope for all controller scoped components will be the name of the controller.
- var scope = GetControllerName();
-
- //Only consider component elements with a valid name attribute. Some components don't have and name and we
- //can't possibly index them.
- var components = GetContainers().SelectMany(c =>
- c.Elements().Where(e => e.Attribute(L5XName.Name) is not null));
-
- foreach (var component in components)
- {
- var key = new ComponentKey(component.Name.LocalName, component.LogixName());
- if (!_componentIndex.TryAdd(key, new Dictionary { { scope, component } }))
- _componentIndex[key].TryAdd(scope, component);
- //todo what about collisions?
- }
- }
-
- ///
- /// Handles iterating each program component element in the L5X and indexed each tag and routine
- /// with the correct scope.
- ///
- private void IndexProgramScopedComponents()
- {
- var programs = GetContainer(L5XName.Programs).Elements();
-
- foreach (var program in programs)
- {
- var scope = program.LogixName();
-
- foreach (var component in program.Descendants()
- .Where(d => d.Name.LocalName is L5XName.Tag or L5XName.Routine))
- {
- var key = new ComponentKey(component.Name.LocalName, component.LogixName());
- if (!_componentIndex.TryAdd(key, new Dictionary { { scope, component } }))
- _componentIndex[key].TryAdd(scope, component);
- //todo what about collisions?
- }
- }
- }
-
- ///
- /// Handles iterating each module defined tag component element in the L5X and indexes each tag.
- ///
- private void IndexModuleDefinedTagComponents()
- {
- var scope = GetControllerName();
-
- foreach (var component in GetContainer(L5XName.Modules).Descendants().Where(e =>
- e.L5XType() is L5XName.ConfigTag or L5XName.InputTag or L5XName.OutputTag))
- {
- var key = new ComponentKey(L5XName.Tag, component.ModuleTagName());
- if (!_componentIndex.TryAdd(key, new Dictionary { { scope, component } }))
- _componentIndex[key].TryAdd(scope, component);
- //todo what about collisions?
- }
- }
-
- ///
- /// Finds all logix reference elements and indexes them into a local dictionary for fast lookups.
- ///
- private void IndexReferences()
- {
- IndexDataTypeReferences();
- IndexCodeReferences();
- }
-
- ///
- /// Finds all elements with a data type attribute and indexes them into a local reference index for fast lookup.
- /// This will include technically any type, predefined, atomic, user defined, or add on instruction.
- ///
- private void IndexDataTypeReferences()
- {
- var targets = _content.Descendants().Where(d => d.Attribute(L5XName.DataType) is not null);
- foreach (var target in targets)
- {
- var componentName = target.Attribute(L5XName.DataType)!.Value;
- var reference = new CrossReference(target, componentName, L5XName.DataType);
- if (!_referenceIndex.TryAdd(reference.ComponentKey, new List { reference }))
- _referenceIndex[reference.ComponentKey].Add(reference);
- }
- }
-
- ///
- /// Finds all routine content elements, iterates each "code" element, and delegates the retrieval or references to the
- /// materialized logix element object. Then adds each set of references to the reference index for fast lookup.
- ///
- private void IndexCodeReferences()
- {
- var contentTypes = RoutineType.All().Select(r => r.ContentName).ToList();
-
- var targets = _content.Descendants().Where(e => contentTypes.Contains(e.Name.LocalName));
-
- foreach (var target in targets)
- {
- switch (target.Name.LocalName)
- {
- case L5XName.RLLContent:
- AddReferences(target.Descendants(L5XName.Rung)
- .SelectMany(x => new Rung(x).References()));
- break;
- case L5XName.STContent:
- AddReferences(target.Descendants(L5XName.Line)
- .SelectMany(x => new Line(x).References()));
- break;
- case L5XName.FBDContent:
- AddReferences(target.Descendants(L5XName.Sheet)
- .SelectMany(x => new Sheet(x).References()));
- break;
- case L5XName.SFCContent:
- AddReferences(new Chart(target).References());
- break;
- }
- }
- }
-
- ///
- /// Determines if the provided object is a component element, for which we need to reindex the component.
- ///
- private static bool IsComponentElement(object sender)
- {
- if (sender is not XElement element) return false;
- var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
- return componentTypes.Contains(element.Name.LocalName);
- }
-
- ///
- /// Determines if the provided object is a attribute or property is a component name, for which we need to
- /// reindex the component.
- ///
- private static bool IsNameProperty(object sender)
- {
- if (sender is not XAttribute attribute) return false;
- if (attribute.Name.LocalName is not L5XName.Name) return false;
- if (attribute.Parent is null) return false;
- var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
- return componentTypes.Contains(attribute.Parent.Name.LocalName);
- }
-
- ///
- /// Determines if the provided object is a attribute or property is a data type reference, for which we need to
- /// reindex references.
- ///
- private static bool IsDataTypeProperty(object sender)
- {
- if (sender is not XAttribute attribute) return false;
- if (attribute.Name.LocalName is not L5XName.DataType) return false;
- if (attribute.Parent is null) return false;
- var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
- return componentTypes.Contains(attribute.Parent.Name.LocalName);
- }
-
- ///
- /// Determines if the provided object is a a logix code element, for which we need to reindex references.
- ///
- private static bool IsCodeElement(object sender)
- {
- if (sender is not XElement element) return false;
- var contentTypes = RoutineType.All().Select(r => r.ContentName).ToList();
- return element.Ancestors().Any(a => contentTypes.Contains(a.Name.LocalName));
- }
-
- ///
- /// Determines if the provided object is a attribute or property of a logix code element, for which we need to
- /// reindex references.
- ///
- private static bool IsCodeProperty(object sender)
- {
- if (sender is not XAttribute attribute) return false;
- if (attribute.Name.LocalName is not
- (L5XName.Operand or L5XName.Argument or L5XName.Routine or L5XName.Type or L5XName.Name)) return false;
- return attribute.Parent is not null && IsCodeElement(attribute.Parent);
- }
-
///
/// Merges all top level containers and their immediate child elements between the current L5X content and the
/// provided L5X content. Will overwrite if specified.
@@ -986,122 +1003,6 @@ where existing is null
}
}
- ///
- /// Triggered when any content of the L5X is about to change. We need to know if the object changing is an element
- /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions.
- /// Prior to the object changing and if the change action is a remove or value change, we need to remove the applicable
- /// components or references from the index. This is because after the object has changed we no longer have access
- /// to the previous state.
- ///
- private void OnContentChanging(object sender, XObjectChangeEventArgs e)
- {
- if (e.ObjectChange is XObjectChange.Remove)
- {
- if (IsComponentElement(sender)) RemoveComponent((XElement)sender);
- if (IsCodeElement(sender)) RemoveReferences((XElement)sender);
- }
-
- if (e.ObjectChange is not XObjectChange.Value) return;
-
- if (IsNameProperty(sender)) RemoveComponent(((XAttribute)sender).Parent!);
- if (IsDataTypeProperty(sender))
- {
- var attribute = (XAttribute)sender;
- var reference = new CrossReference(attribute.Parent!, L5XName.DataType, attribute.Value);
- RemoveReference(reference);
- }
-
- if (IsCodeProperty(sender)) RemoveReferences(((XAttribute)sender).Parent!);
- }
-
- ///
- /// Triggered when any content of the L5X has changed. We need to know if the object that changed is an element
- /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions.
- /// Once the object has changed, the sender will hold the new state. If the change action is an add or value change,
- /// and the element or property value is one that would refer to an indexed object, we will update the state of the index
- /// to ensure consistency.
- ///
- private void OnContentChanged(object sender, XObjectChangeEventArgs e)
- {
- if (e.ObjectChange is XObjectChange.Add)
- {
- if (IsComponentElement(sender)) AddComponent((XElement)sender);
- if (IsCodeElement(sender)) AddReferences((XElement)sender);
- }
-
- if (e.ObjectChange is not XObjectChange.Value) return;
-
- if (IsNameProperty(sender)) AddComponent(((XAttribute)sender).Parent!);
- if (IsDataTypeProperty(sender))
- {
- var attribute = (XAttribute)sender;
- var reference = new CrossReference(attribute.Parent!, attribute.Value, L5XName.DataType);
- AddReference(reference);
- }
-
- if (IsCodeProperty(sender)) AddReferences(((XAttribute)sender).Parent!);
- }
-
- ///
- /// Handles removing a component matching the current attached element from the index. This requires the element
- /// to be attached to the L5X tree for it to determine the scope of the element.
- ///
- private void RemoveComponent(XElement element)
- {
- var key = new ComponentKey(element.Name.LocalName, element.LogixName());
- var scope = Scope.ScopeName(element);
-
- if (!_componentIndex.TryGetValue(key, out var components)) return;
-
- if (components.Count == 1)
- {
- _componentIndex.Remove(key);
- return;
- }
-
- components.Remove(scope);
- }
-
- ///
- /// Handles removing the provided reference from the index. If only a single reference exists for the provided key,
- /// the entire reference is removed, otherwise we iterate the references and remove all that match the provided
- /// reference's location and type.
- ///
- private void RemoveReference(CrossReference reference)
- {
- if (!_referenceIndex.TryGetValue(reference.ComponentKey, out var results)) return;
- if (results.Count == 1)
- {
- _referenceIndex.Remove(reference.ComponentKey);
- return;
- }
-
- results.RemoveAll(r => r.IsSame(reference));
- }
-
- ///
- /// Removes the provided collection of references. This is a convenience method to remove multiple references.
- ///
- private void RemoveReferences(IEnumerable references)
- {
- foreach (var reference in references)
- {
- RemoveReference(reference);
- }
- }
-
- ///
- /// Handles removing all references from the index that are associated with the provided element.
- /// This is a convenience method since we need to parse the element as an to
- /// obtain the references to remove.
- ///
- private void RemoveReferences(XElement element)
- {
- if (LogixSerializer.Deserialize(element) is not ILogixReferencable referencable) return;
- var references = referencable.References().ToList();
- RemoveReferences(references);
- }
-
///
/// Create document, adds default declaration, and saves the current L5X content to the specified file name.
///
diff --git a/src/L5Sharp/Logix.cs b/src/L5Sharp/Logix.cs
new file mode 100644
index 00000000..15829db1
--- /dev/null
+++ b/src/L5Sharp/Logix.cs
@@ -0,0 +1,14 @@
+namespace L5Sharp;
+
+///
+///
+///
+public static class Logix
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static L5X Load(string fileName) => L5X.Load(fileName);
+}
\ No newline at end of file
diff --git a/src/L5Sharp/LogixCode.cs b/src/L5Sharp/LogixCode.cs
index 40df8882..b839fea7 100644
--- a/src/L5Sharp/LogixCode.cs
+++ b/src/L5Sharp/LogixCode.cs
@@ -15,7 +15,8 @@ namespace L5Sharp;
///
/// This class is meant to specify a common set of properties and functions that all code elements, regardless
/// of programming language type (RLL, ST, FBD, SFC) should provide so that we can retrieve information about the contents
-/// and location of the code within the L5X file.
+/// and location of the code within the L5X file. It is also here to help constrain the type of element that the caller
+/// can query from the content of a given routine type.
///
///
/// This class overrides the default equality implementation to determine code equality by it's location within the L5X tree.
@@ -47,7 +48,7 @@ protected LogixCode(XElement element) : base(element)
/// A representing the zero-based order.
/// Logix ignores the these number identifiers upon importing, and only considers the order within the
/// containing Routine. This makes the property somewhat useless, but is here all the same as it is
- /// inherent to the underlying XMl.
+ /// inherent to the underlying XMl, and can help identify code elements from deserialized L5X documents.
public virtual int Number
{
get => GetValue();
@@ -57,7 +58,7 @@ public virtual int Number
///
///
///
- public virtual string Identifier => $"{L5XType} {Number}".Trim();
+ public string Location => $"{L5XType} {Number}".Trim();
///
/// The the parent component for the current LogixCode element.
diff --git a/src/L5Sharp/LogixComponent.cs b/src/L5Sharp/LogixComponent.cs
index cf282979..d437f22f 100644
--- a/src/L5Sharp/LogixComponent.cs
+++ b/src/L5Sharp/LogixComponent.cs
@@ -136,7 +136,7 @@ public L5X Export(Revision? softwareRevision = null)
/// at least one property value referencing this component's name.
///
public IEnumerable References() =>
- L5X is not null ? L5X.FindReferences(this) : Enumerable.Empty();
+ L5X is not null ? L5X.ReferencesTo(this) : Enumerable.Empty();
///
/// This override returns the component name of the type.
diff --git a/src/L5Sharp/LogixElement.cs b/src/L5Sharp/LogixElement.cs
index 213c5d28..524389a3 100644
--- a/src/L5Sharp/LogixElement.cs
+++ b/src/L5Sharp/LogixElement.cs
@@ -80,26 +80,6 @@ protected LogixElement(XElement element)
///
public string L5XType => Element.Name.LocalName;
- ///
- /// The name of the ancestral LogixElement, indicating the program, instruction, or controller for which
- /// it is contained.
- ///
- ///
- /// A representing the name of the program, instruction, or controller in which this component
- /// is contained. If the component has scope, then an string.
- ///
- ///
- ///
- /// This value is retrieved from the ancestors of the underlying element. If no ancestors exists, meaning this
- /// element is not attached to a L5X tree, then this returns an empty string.
- ///
- ///
- /// This property is not inherent in the underlying XML (not serialized), but one that adds a lot of
- /// value as it helps uniquely identify elements within the L5X file, especially scoped components with same name.
- ///
- ///
- public string Container => Scope.ScopeName(Element);
-
///
/// The scope of the LogixElement, indicating whether it is a globally scoped controller element,
/// a locally scoped program or instruction element, or neither (not attached to L5X tree).
@@ -119,7 +99,27 @@ protected LogixElement(XElement element)
/// Tag and Routine components.
///
///
- public Scope Scope => Scope.ScopeType(Element);
+ public Scope Scope => Scope.Type(Element);
+
+ ///
+ /// The name of the ancestral LogixElement, indicating the program, instruction, or controller for which
+ /// it is contained.
+ ///
+ ///
+ /// A representing the name of the program, instruction, or controller in which this component
+ /// is contained. If the component has scope, then an string.
+ ///
+ ///
+ ///
+ /// This value is retrieved from the ancestors of the underlying element. If no ancestors exists, meaning this
+ /// element is not attached to a L5X tree, then this returns an empty string.
+ ///
+ ///
+ /// This property is not inherent in the underlying XML (not serialized), but one that adds a lot of
+ /// value as it helps uniquely identify elements within the L5X file, especially scoped components with same name.
+ ///
+ ///
+ public string Container => Scope.Container(Element);
///
/// Adds a new element of the same type directly after this element in the L5X document.
@@ -479,7 +479,7 @@ protected LogixContainer GetContainer([CallerMemberName] string?
/// Gets the first parent element of the current underlying element object with the specified name, and returns the
/// a new deserialized instance of the parent type if found. If not found, returns null.
///
- /// The name of the parent element to return. If not provided will use the default configured
+ /// The name of the parent element to return. If not provided will use the default configured
/// L5XType for the specified element type.
/// The element type of the parent to return.
/// A representing the specified parent element if found;
@@ -489,10 +489,10 @@ protected LogixContainer GetContainer([CallerMemberName] string?
/// to a L5X document then this will return null. Note that we only get parent but don't set it. A parent is
/// defined by adding a given logix element to the corresponding parent logix container.
///
- protected TElement? GetParent(string? parent = null) where TElement : LogixElement
+ protected TElement? GetAncestor(string? ancestor = null) where TElement : LogixElement
{
- parent ??= typeof(TElement).L5XType();
- return Element.Ancestors(parent).FirstOrDefault()?.Deserialize();
+ ancestor ??= typeof(TElement).L5XType();
+ return Element.Ancestors(ancestor).FirstOrDefault()?.Deserialize();
}
///
diff --git a/src/L5Sharp/LogixIndex.cs b/src/L5Sharp/LogixIndex.cs
new file mode 100644
index 00000000..02948daf
--- /dev/null
+++ b/src/L5Sharp/LogixIndex.cs
@@ -0,0 +1,389 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using L5Sharp.Common;
+using L5Sharp.Elements;
+using L5Sharp.Enums;
+using L5Sharp.Utilities;
+
+namespace L5Sharp;
+
+///
+/// The internal implementation that indexes components and references and stores them in the local dictionaries.
+/// This class is then used by to find components, tags, and references quickly.
+///
+internal class LogixIndex
+{
+ ///
+ /// The root controller element of the project.
+ ///
+ private readonly XElement _content;
+
+ ///
+ /// An index of all logix components in the L5X file for fast lookups.
+ ///
+ public readonly Dictionary> Components = new();
+
+ ///
+ /// An index of all references to a logix component in the L5X file for fast lookups.
+ ///
+ public readonly Dictionary> References = new();
+
+ public LogixIndex(XElement content)
+ {
+ _content = content;
+
+ //Initialize the dictionaries.
+ IndexComponents();
+ IndexReferences();
+
+ //Detect changes to keep index up to date.
+ _content.Changing += OnContentChanging;
+ _content.Changed += OnContentChanged;
+ }
+
+ ///
+ /// Handles adding a component to the index. This requires the element to be attached to the L5X tree
+ /// for it to determine the scope of the element.
+ ///
+ private void AddComponent(XElement element)
+ {
+ var key = new ComponentKey(element.L5XType(), element.LogixName());
+ var container = Scope.Container(element);
+
+ if (Components.TryAdd(key, new Dictionary { { container, element } })) return;
+ Components[key].TryAdd(container, element);
+ }
+
+ ///
+ /// Handles adding the provided reference to the index. If no reference exists for the provided key, a new
+ /// entry is created, otherwise the reference is added to the existing collection.
+ ///
+ private void AddReference(CrossReference reference)
+ {
+ if (!References.TryAdd(reference.Key, new List { reference }))
+ References[reference.Key].Add(reference);
+ }
+
+ ///
+ /// Adds the provided collection of references. This is a convenience method to add multiple references.
+ ///
+ private void AddReferences(IEnumerable references)
+ {
+ foreach (var reference in references)
+ {
+ AddReference(reference);
+ }
+ }
+
+ ///
+ /// Handles adding all references to the index that are associated with the provided element.
+ /// This is a convenience method since we need to parse the element as an to
+ /// obtain the references to add.
+ ///
+ private void AddReferences(XElement element)
+ {
+ if (element.Deserialize() is not ILogixReferencable referencable) return;
+ var references = referencable.References().ToList();
+ AddReferences(references);
+ }
+
+ ///
+ /// Triggered when any content of the L5X is about to change. We need to know if the object changing is an element
+ /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions.
+ /// Prior to the object changing and if the change action is a remove or value change, we need to remove the applicable
+ /// components or references from the index. This is because after the object has changed we no longer have access
+ /// to the previous state.
+ ///
+ private void OnContentChanging(object sender, XObjectChangeEventArgs e)
+ {
+ if (e.ObjectChange is XObjectChange.Remove)
+ {
+ if (IsComponentElement(sender)) RemoveComponent((XElement)sender);
+ if (IsCodeElement(sender)) RemoveReferences((XElement)sender);
+ }
+
+ if (e.ObjectChange is not XObjectChange.Value) return;
+
+ if (IsNameProperty(sender)) RemoveComponent(((XAttribute)sender).Parent!);
+ if (IsDataTypeProperty(sender))
+ {
+ var attribute = (XAttribute)sender;
+ var reference = new CrossReference(attribute.Parent!, L5XName.DataType, attribute.Value);
+ RemoveReference(reference);
+ }
+
+ if (IsCodeProperty(sender)) RemoveReferences(((XAttribute)sender).Parent!);
+ }
+
+ ///
+ /// Triggered when any content of the L5X has changed. We need to know if the object that changed is an element
+ /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions.
+ /// Once the object has changed, the sender will hold the new state. If the change action is an add or value change,
+ /// and the element or property value is one that would refer to an indexed object, we will update the state of the index
+ /// to ensure consistency.
+ ///
+ private void OnContentChanged(object sender, XObjectChangeEventArgs e)
+ {
+ if (e.ObjectChange is XObjectChange.Add)
+ {
+ if (IsComponentElement(sender)) AddComponent((XElement)sender);
+ if (IsCodeElement(sender)) AddReferences((XElement)sender);
+ }
+
+ if (e.ObjectChange is not XObjectChange.Value) return;
+
+ if (IsNameProperty(sender)) AddComponent(((XAttribute)sender).Parent!);
+ if (IsDataTypeProperty(sender))
+ {
+ var attribute = (XAttribute)sender;
+ var reference = new CrossReference(attribute.Parent!, L5XName.DataType, attribute.Value);
+ AddReference(reference);
+ }
+
+ if (IsCodeProperty(sender)) AddReferences(((XAttribute)sender).Parent!);
+ }
+
+ ///
+ /// Finds all logix component elements and indexes them into a local dictionary for fast lookups.
+ ///
+ private void IndexComponents()
+ {
+ IndexControllerScopedComponents();
+ IndexProgramScopedComponents();
+ IndexModuleDefinedTagComponents();
+ }
+
+ ///
+ /// Finds all controller scoped top level components to index. This includes all components except for program tags,
+ /// routines, and module defined IO tags, which are handled separately in the following methods.
+ ///
+ private void IndexControllerScopedComponents()
+ {
+ //The scope for all controller scoped components will be the name of the controller.
+ var scope = _content.LogixName();
+
+ //Only consider component elements with a valid name attribute. Some components don't have and name and we
+ //can't possibly index them.
+ var components = _content.Elements()
+ .SelectMany(c => c.Elements().Where(e => e.Attribute(L5XName.Name) is not null));
+
+ foreach (var component in components)
+ {
+ var key = new ComponentKey(component.L5XType(), component.LogixName());
+ if (!Components.TryAdd(key, new Dictionary { { scope, component } }))
+ Components[key].TryAdd(scope, component);
+ }
+ }
+
+ ///
+ /// Handles iterating each program component element in the L5X and indexes each tag and routine
+ /// with the corresponding program scope.
+ ///
+ private void IndexProgramScopedComponents()
+ {
+ var programs = _content.Descendants(L5XName.Programs).Elements();
+
+ foreach (var program in programs)
+ {
+ var scope = program.LogixName();
+
+ foreach (var component in program.Descendants()
+ .Where(d => d.Name.LocalName is L5XName.Tag or L5XName.Routine))
+ {
+ var key = new ComponentKey(component.L5XType(), component.LogixName());
+ if (!Components.TryAdd(key, new Dictionary { { scope, component } }))
+ Components[key].TryAdd(scope, component);
+ }
+ }
+ }
+
+ ///
+ /// Handles iterating each module defined tag component element in the L5X and indexes each tag.
+ ///
+ private void IndexModuleDefinedTagComponents()
+ {
+ var scope = _content.LogixName();
+
+ foreach (var component in _content.Descendants(L5XName.Modules).Descendants().Where(e =>
+ e.L5XType() is L5XName.ConfigTag or L5XName.InputTag or L5XName.OutputTag))
+ {
+ var key = new ComponentKey(L5XName.Tag, component.ModuleTagName());
+ if (!Components.TryAdd(key, new Dictionary { { scope, component } }))
+ Components[key].TryAdd(scope, component);
+ }
+ }
+
+ ///
+ /// Finds all logix reference elements and indexes them into a local dictionary for fast lookups.
+ ///
+ private void IndexReferences()
+ {
+ IndexDataTypeReferences();
+ IndexCodeReferences();
+ }
+
+ ///
+ /// Finds all elements with a data type attribute and indexes them into a local reference index for fast lookup.
+ /// This will include technically any type, predefined, atomic, user defined, or add on instruction.
+ ///
+ private void IndexDataTypeReferences()
+ {
+ var targets = _content.Descendants().Where(d => d.Attribute(L5XName.DataType) is not null);
+ foreach (var target in targets)
+ {
+ var componentName = target.Attribute(L5XName.DataType)!.Value;
+ var reference = new CrossReference(target, L5XName.DataType, componentName);
+ if (!References.TryAdd(reference.Key, new List { reference }))
+ References[reference.Key].Add(reference);
+ }
+ }
+
+ ///
+ /// Finds all routine content elements, iterates each "code" element, and delegates the retrieval or references to the
+ /// materialized logix element object. Then adds each set of references to the reference index for fast lookup.
+ ///
+ private void IndexCodeReferences()
+ {
+ var contentTypes = RoutineType.All().Select(r => r.ContentName).ToList();
+
+ var targets = _content.Descendants().Where(e => contentTypes.Contains(e.Name.LocalName));
+
+ foreach (var target in targets)
+ {
+ switch (target.Name.LocalName)
+ {
+ case L5XName.RLLContent:
+ AddReferences(target.Descendants(L5XName.Rung).SelectMany(x => new Rung(x).References()));
+ break;
+ case L5XName.STContent:
+ AddReferences(target.Descendants(L5XName.Line).SelectMany(x => new Line(x).References()));
+ break;
+ case L5XName.FBDContent:
+ AddReferences(target.Descendants(L5XName.Sheet).SelectMany(x => new Sheet(x).References()));
+ break;
+ case L5XName.SFCContent:
+ AddReferences(new Chart(target).References());
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Determines if the provided object is a component element, for which we need to reindex the component.
+ ///
+ private static bool IsComponentElement(object sender)
+ {
+ if (sender is not XElement element) return false;
+ var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
+ return componentTypes.Contains(element.Name.LocalName);
+ }
+
+ ///
+ /// Determines if the provided object is a attribute or property is a component name, for which we need to
+ /// reindex the component.
+ ///
+ private static bool IsNameProperty(object sender)
+ {
+ if (sender is not XAttribute attribute) return false;
+ if (attribute.Name.LocalName is not L5XName.Name) return false;
+ if (attribute.Parent is null) return false;
+ var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
+ return componentTypes.Contains(attribute.Parent.Name.LocalName);
+ }
+
+ ///
+ /// Determines if the provided object is a attribute or property is a data type reference, for which we need to
+ /// reindex references.
+ ///
+ private static bool IsDataTypeProperty(object sender)
+ {
+ if (sender is not XAttribute attribute) return false;
+ if (attribute.Name.LocalName is not L5XName.DataType) return false;
+ if (attribute.Parent is null) return false;
+ var componentTypes = ComponentType.All().Select(c => c.Value).ToList();
+ return componentTypes.Contains(attribute.Parent.Name.LocalName);
+ }
+
+ ///
+ /// Determines if the provided object is a a logix code element, for which we need to reindex references.
+ ///
+ private static bool IsCodeElement(object sender)
+ {
+ if (sender is not XElement element) return false;
+ var contentTypes = RoutineType.All().Select(r => r.ContentName).ToList();
+ return element.Ancestors().Any(a => contentTypes.Contains(a.Name.LocalName));
+ }
+
+ ///
+ /// Determines if the provided object is a attribute or property of a logix code element, for which we need to
+ /// reindex references.
+ ///
+ private static bool IsCodeProperty(object sender)
+ {
+ if (sender is not XAttribute attribute) return false;
+ if (attribute.Name.LocalName is not
+ (L5XName.Operand or L5XName.Argument or L5XName.Routine or L5XName.Type or L5XName.Name)) return false;
+ return attribute.Parent is not null && IsCodeElement(attribute.Parent);
+ }
+
+ ///
+ /// Handles removing a component matching the current attached element from the index. This requires the element
+ /// to be attached to the L5X tree for it to determine the scope of the element.
+ ///
+ private void RemoveComponent(XElement element)
+ {
+ var key = new ComponentKey(element.Name.LocalName, element.LogixName());
+ var scope = Scope.Container(element);
+
+ if (!Components.TryGetValue(key, out var components)) return;
+
+ if (components.Count == 1)
+ {
+ Components.Remove(key);
+ return;
+ }
+
+ components.Remove(scope);
+ }
+
+ ///
+ /// Handles removing the provided reference from the index. If only a single reference exists for the provided key,
+ /// the entire reference is removed, otherwise we iterate the references and remove all that match the provided
+ /// reference's location and type.
+ ///
+ private void RemoveReference(CrossReference reference)
+ {
+ if (!References.TryGetValue(reference.Key, out var results)) return;
+ if (results.Count == 1)
+ {
+ References.Remove(reference.Key);
+ return;
+ }
+
+ results.RemoveAll(r => r.Equals(reference));
+ }
+
+ ///
+ /// Removes the provided collection of references. This is a convenience method to remove multiple references.
+ ///
+ private void RemoveReferences(IEnumerable references)
+ {
+ foreach (var reference in references)
+ {
+ RemoveReference(reference);
+ }
+ }
+
+ ///
+ /// Handles removing all references from the index that are associated with the provided element.
+ /// This is a convenience method since we need to parse the element as an to
+ /// obtain the references to remove.
+ ///
+ private void RemoveReferences(XElement element)
+ {
+ if (element.Deserialize() is not ILogixReferencable referencable) return;
+ var references = referencable.References().ToList();
+ RemoveReferences(references);
+ }
+}
\ No newline at end of file
diff --git a/src/L5Sharp/Types/StringType.cs b/src/L5Sharp/Types/StringType.cs
index 58f90838..0cf6631b 100644
--- a/src/L5Sharp/Types/StringType.cs
+++ b/src/L5Sharp/Types/StringType.cs
@@ -276,12 +276,19 @@ private static ArrayType GenerateData(IReadOnlyList array, ushort le
}
///
- /// Converts the provided string value to a SINT array. Handles empty or null string. SINT array can not be empty.
+ /// Converts the provided string value to a SINT array. Handles empty or null string. SINT array can not be empty
+ /// to invalid initialization of the ArrayType object.
///
private static SINT[] ToArray(string value)
{
- if (string.IsNullOrEmpty(value)) return new SINT[] { new(Radix.Ascii) };
+ //If we get a null or empty string then we need to return a single element array to avoid exceptions from array type.
+ if (string.IsNullOrEmpty(value) || value.All(c => c == '\''))
+ return new SINT[] { new(Radix.Ascii) };
+
+ //Logix encloses strings in single quotes so we need toe remove those if the are present.
value = value.TrimStart('\'').TrimEnd('\'');
+
+ //Breaks apart the string into single ASCII characters to be parsed.
var matches = Regex.Matches(value, LogixAsciiPattern);
return matches.Select(m =>
{
diff --git a/src/L5Sharp/Utilities/L5XTypeAttribute.cs b/src/L5Sharp/Utilities/L5XTypeAttribute.cs
index 54121605..c8c09f0d 100644
--- a/src/L5Sharp/Utilities/L5XTypeAttribute.cs
+++ b/src/L5Sharp/Utilities/L5XTypeAttribute.cs
@@ -3,7 +3,7 @@
namespace L5Sharp.Utilities;
///
-/// A custom attribute that defines the L5X type name and container name for a logic type class in order to match XMl
+/// A custom attribute that defines the L5X type name and container name for a logic type class in order to match XML
/// elements to a given class. This attribute is mostly needed for class types whose names do not match the L5X element type
/// found in the L5X, or for classes that support multiple L5X element types.
///
diff --git a/tests/L5Sharp.Samples/L5Sharp.Samples.csproj b/tests/L5Sharp.Samples/L5Sharp.Samples.csproj
index 589a16aa..938fd413 100644
--- a/tests/L5Sharp.Samples/L5Sharp.Samples.csproj
+++ b/tests/L5Sharp.Samples/L5Sharp.Samples.csproj
@@ -104,12 +104,12 @@
PreserveNewest
-
- Always
- PreserveNewest
+
+ Always
+
diff --git a/tests/L5Sharp.Samples/Routines/FBD.L5X b/tests/L5Sharp.Samples/Routines/FBD.L5X
index f2f3f24d..a9c0e526 100644
--- a/tests/L5Sharp.Samples/Routines/FBD.L5X
+++ b/tests/L5Sharp.Samples/Routines/FBD.L5X
@@ -1,5 +1,5 @@
-
+
@@ -294,6 +294,14 @@
+
+
+
+
+
+
+
+
@@ -580,47 +588,54 @@
-
+
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
diff --git a/tests/L5Sharp.Samples/Test.L5X b/tests/L5Sharp.Samples/Test.L5X
deleted file mode 100644
index e8ed672b..00000000
--- a/tests/L5Sharp.Samples/Test.L5X
+++ /dev/null
@@ -1,9312 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-EN2T4325481
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-EN2T4456551
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621451734-AENT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621471734-IV8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-7001732E-IB16M12DR
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-300Prosoft TechnologyMVI56E-MCM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-RIO101
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-400Spectrum Controls, Inc.1756sc-CTR8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-CNB131177
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1001756-L83E
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-EN2T4456551
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621511756-IB16
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2251756-IF8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621511756-IB16
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley5094-AEN2TR4325281
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley5094-IF8101
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1105094-OB16
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1015094-IB16
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-70015094-IB32
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley5094-OF8301
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley5094-IY8201
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-1005094-HSC
-
-
-
-
-
-
-
-
-
-
-
-1001756-L83E
-
-
-
-
-
-
-
-
-1311771783-ETAP
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-44565511794-AENT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621571794-IB16
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2621571794-IB16
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Rockwell Automation/Allen-Bradley1756-EN2T4325481
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1.0)[TON(TimerArray[0],?,?) ,OTU(TestComplexTag.SimpleMember.BoolMember) ];]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-208 207 17 224 161 177 26 225 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 62 0 3 0 254 255 9 0 6 0 0 0 0 0 0 0
- 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 16 0 0 2 0 0 0 1 0 0 0 254 255 255 255 0 0 0 0 0 0 0 0
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 253 255 255 255 254 255 255 255
- 254 255 255 255 4 0 0 0 5 0 0 0 254 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 82 0 111 0 111 0 116 0 32 0 69 0 110 0 116 0
- 114 0 121 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 22 0 5 0 255 255 255 255 255 255 255 255 1 0 0 0 14 52 81 191 9 48 205 65 159 190 222 136 174 167 50 40
- 0 0 0 0 0 0 0 0 0 0 0 0 224 29 228 49 217 165 217 1 3 0 0 0 64 4 0 0 0 0 0 0 67 0 111 0 110 0 116 0
- 101 0 110 0 116 0 115 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18 0 2 1 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 17 4 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255 255 255
- 255 255 255 255 255 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 2 0 0 0 3 0 0 0 4 0 0 0 5 0 0 0 6 0 0 0
- 7 0 0 0 8 0 0 0 9 0 0 0 10 0 0 0 11 0 0 0 12 0 0 0 13 0 0 0 14 0 0 0 15 0 0 0 16 0 0 0
- 254 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
- 255 255 255 255 255 255 255 255 4 0 0 0 131 157 0 0 202 79 0 0 0 0 0 0 13 0 0 0 255 254 255 4 84 0 101 0 115 0 116 0
- 0 0 0 0 0 0 0 0 255 255 255 255 200 0 0 0 255 157 3 0 255 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 254 255
- 0 255 254 255 0 1 96 234 0 0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0 0 0 166 3 0 0 2 0 255 255 1 0 13 0 67
- 86 105 101 119 76 105 110 101 73 110 102 111 3 0 0 0 1 0 0 0 0 0 0 0 0 0 200 66 255 254 255 0 255 254 255 0 0 0 0 0
- 0 0 0 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0 255 254 255 0 255 254 255 5 95 0 84 0 101 0 115 0 116 0 32 1
- 0 0 0 255 254 255 0 255 254 255 0 1 128 3 0 0 0 1 0 0 0 0 0 0 0 0 0 200 66 255 254 255 0 255 254 255 0 0 0 0
- 0 0 0 0 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0 255 254 255 0 255 254 255 8 65 0 115 0 99 0 105 0 105 0 84
- 0 97 0 103 0 32 1 0 0 0 255 254 255 0 255 254 255 0 9 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0
- 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0
- 0 0 2 0 0 0 255 0 0 255 0 0 0 0 0 0 0 0 0 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 200 66
- 0 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 2 0 1 0 0 0 1 0 0 0 2 0 0 0 0 0
- 0 0 0 0 2 0 1 0 0 0 1 0 0 0 2 0 0 0 0 0 0 0 0 0 255 255 255 255 0 0 0 0 44 0 0 0 0 0 0 0
- 0 0 0 0 12 0 0 0 0 0 0 0 0 0 136 4 28 51 157 91 0 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0 128 128 128 0
- 128 128 128 0 255 239 255 254 255 191 255 247 175 154 149 100 1 0 0 0 152 3 0 0 177 154 149 100 1 0 0 0 152 3 0 0 255 254 255 0
- 1 0 0 0 1 0 0 0 2 0 0 0 0 0 0 0 0 0 2 0 0 0 40 0 0 0 50 0 0 0 0 0 0 0 0 0 0 0 92 0
- 0 0 245 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 254
- 255 5 65 0 114 0 105 0 97 0 108 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0 255 254 255 0 255 254 255 0 32 1 0 0 0 255 254 255 0 255 254 255
- 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0 255 254 255 0 255 254 255 0 32 1 0 0 0 255 254 255 0 255 254 255 0 0 0
- 0 0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 2 0 0 0 0 0 0 0 0 0
- 2 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 255 255 1 0 9 0 67 76 105 110 101 73 110 102 111 3 0 0 0 1 0 0 0 0
- 0 0 0 0 0 200 66 255 254 255 0 255 254 255 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0 255
- 254 255 0 255 254 255 5 95 0 84 0 101 0 115 0 116 0 32 1 0 0 0 255 254 255 0 255 254 255 0 4 128 3 0 0 0 1 0 0 0
- 0 0 0 0 0 0 200 66 255 254 255 0 255 254 255 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 255 254 255 0 255 254 255 0
- 255 254 255 0 255 254 255 8 65 0 115 0 99 0 105 0 105 0 84 0 97 0 103 0 32 1 0 0 0 255 254 255 0 255 254 255 0 0 0 0
- 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 247 51 3 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/L5Sharp.Samples/Test.xml b/tests/L5Sharp.Samples/Test.xml
index cc55ceed..5d0bf556 100644
--- a/tests/L5Sharp.Samples/Test.xml
+++ b/tests/L5Sharp.Samples/Test.xml
@@ -1,9 +1,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7447,6 +7472,14 @@ Dint Array]]>
+
+
+
+
+
+
+
+
@@ -7538,6 +7571,14 @@ Dint Array]]>
+
+
+
+
+
+
+
+
@@ -7812,11 +7853,11 @@ Dint Array]]>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
@@ -9183,6 +9248,12 @@ Dint Array]]>
+
+
+
+
+
+
@@ -9221,6 +9292,61 @@ Dint Array]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9239,6 +9365,179 @@ Dint Array]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9257,35 +9556,22 @@ Dint Array]]>
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
@@ -9313,7 +9599,7 @@ Dint Array]]>
-
@@ -9341,7 +9627,7 @@ Dint Array]]>
-
@@ -9369,7 +9655,7 @@ Dint Array]]>
-
@@ -9432,6 +9718,67 @@ Dint Array]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9459,25 +9806,76 @@ Dint Array]]>
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9533,12 +9931,27 @@ Dint Array]]>
1.0)[TON(TimerArray[0],?,?) ,OTU(TestComplexTag.SimpleMember.BoolMember) ];]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
@@ -9581,28 +10000,30 @@ Dint Array]]>
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -9614,6 +10035,54 @@ Dint Array]]>
+ = 1000 then]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = 100 then]]>
+
+
+
+
+
+
+
+
+
+
+
+ = 100 then TONR(TimerResetTag);]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9626,6 +10095,136 @@ Dint Array]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -9918,6 +10517,7 @@ Dint Array]]>
+
diff --git a/tests/L5Sharp.Tests/Elements/BlockTests.cs b/tests/L5Sharp.Tests/Elements/BlockTests.cs
index 47ecd30a..86d3bb9c 100644
--- a/tests/L5Sharp.Tests/Elements/BlockTests.cs
+++ b/tests/L5Sharp.Tests/Elements/BlockTests.cs
@@ -49,10 +49,10 @@ public void New_Element_ShouldNotBeNull()
[Test]
public void New_Overriden_ShouldBeExpected()
{
- var block = new Block()
+ var block = new Block
{
ID = 1, X = 100, Y = 100, Operand = "TestBlock", Type = "SCL",
- VisiblePins = new List()
+ VisiblePins = new Params
{
"Source", "Destination"
}
@@ -71,7 +71,7 @@ public Task New_Overriden_ShouldBeVerified()
var block = new Block
{
ID = 1, X = 100, Y = 100, Operand = "TestBlock", Type = "SCL",
- VisiblePins = new List
+ VisiblePins = new Params
{
"Source", "Destination"
}
@@ -85,12 +85,12 @@ public void AddPin_ValidTagName_ShouldHaveExpectedCount()
{
var block = new Block();
- block.AddPin("MyPinName");
+ block.VisiblePins?.Add("MyPinName");
block.VisiblePins.Should().HaveCount(1);
}
- [Test]
+ /*[Test]
public void AddPins_ValidCollection_ShouldHaveExpectedCount()
{
var block = new Block();
@@ -99,10 +99,10 @@ public void AddPins_ValidCollection_ShouldHaveExpectedCount()
"Pin1", "Pin2", "Pin3"
};
- block.AddPins(pins);
+ block.VisiblePins?.AddRange(pins);
block.VisiblePins.Should().HaveCount(3);
- }
+ }*/
[Test]
public void References_WhenCalled_ShouldHaveExpectedCount()
@@ -111,7 +111,7 @@ public void References_WhenCalled_ShouldHaveExpectedCount()
{
ID = 1, X = 100, Y = 100,
Operand = "MyTagName", Type = "SCL",
- VisiblePins = new List { "Source", "Destination" }
+ VisiblePins = new Params { "Source", "Destination" }
};
var references = block.References().ToList();
diff --git a/tests/L5Sharp.Tests/Elements/IREFTests.cs b/tests/L5Sharp.Tests/Elements/IREFTests.cs
index 4c9116a7..a594d2d6 100644
--- a/tests/L5Sharp.Tests/Elements/IREFTests.cs
+++ b/tests/L5Sharp.Tests/Elements/IREFTests.cs
@@ -127,6 +127,6 @@ public void References_WhenCalled_ShouldReturnExpected()
var references = element.References().ToList();
references.Should().HaveCount(1);
- references[0].ComponentKey.Should().Be(new ComponentKey("Tag", "TestTag"));
+ references[0].Key.Should().Be(new ComponentKey("Tag", "TestTag"));
}
}
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/Elements/WireTests.cs b/tests/L5Sharp.Tests/Elements/WireTests.cs
new file mode 100644
index 00000000..787b3caf
--- /dev/null
+++ b/tests/L5Sharp.Tests/Elements/WireTests.cs
@@ -0,0 +1,85 @@
+using System.Xml.Linq;
+using FluentAssertions;
+using L5Sharp.Elements;
+using L5Sharp.Samples;
+using L5Sharp.Utilities;
+
+namespace L5Sharp.Tests.Elements;
+
+[TestFixture]
+public class WireTests
+{
+ [Test]
+ public void New_Default_ShouldNotBeNull()
+ {
+ var wire = new Wire();
+
+ wire.Should().NotBeNull();
+ }
+
+ [Test]
+ public void New_Default_ShouldHaveExpectedValues()
+ {
+ var wire = new Wire();
+
+ wire.FromID.Should().Be(0);
+ wire.ToID.Should().Be(0);
+ wire.FromParam.Should().BeNull();
+ wire.ToParam.Should().BeNull();
+ }
+
+ [Test]
+ public void New_ValidElement_ShouldNotBeNull()
+ {
+ var wire = new Wire(new XElement(L5XName.Wire));
+
+ wire.Should().NotBeNull();
+ }
+
+ [Test]
+ public void New_NullElement_ShouldThrowArgumentNullException()
+ {
+ FluentActions.Invoking(() => new Wire(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void New_Overloaded_ShouldHaveExpectedValues()
+ {
+ var wire = new Wire
+ {
+ FromID = 1,
+ ToID = 2,
+ FromParam = "In",
+ ToParam = "Out"
+ };
+
+ wire.FromID.Should().Be(1);
+ wire.ToID.Should().Be(2);
+ wire.FromParam.Should().Be("In");
+ wire.ToParam.Should().Be("Out");
+ }
+
+ [Test]
+ public void Endpoints_FirstWire_ShouldNotBeEmpty()
+ {
+ var content = Logix.Load(Known.Test);
+ var sheet = content.Query().First();
+ var wire = sheet.Connectors().First();
+
+ var endpoint = wire.Endpoint(0);
+
+ endpoint.Should().NotBeNull();
+ }
+
+ [Test]
+ public void Endpoints_WireToConnector_ShouldNotBeEmpty()
+ {
+ var content = Logix.Load(Known.Test);
+ var sheet = content.Query().First();
+ var wire = sheet.Connectors().First(w => w.ToID == 6);
+
+ var endpoint = wire.Endpoint(9);
+
+ endpoint.Should().NotBeNull();
+ }
+}
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/Examples.cs b/tests/L5Sharp.Tests/Examples.cs
index a2e11dfa..dcbee4de 100644
--- a/tests/L5Sharp.Tests/Examples.cs
+++ b/tests/L5Sharp.Tests/Examples.cs
@@ -16,9 +16,9 @@ public class Examples
[Test]
public void SampleQuery001()
{
- var content = L5X.Load(Known.Test);
+ var content = Logix.Load(Known.Test);
- var results = content.Find()
+ var results = content.Query()
.SelectMany(t => t.Members())
.Where(t => t.DataType == "TIMER")
.Select(t => new {t.TagName, t.Description, Preset = t.Value.As().PRE})
@@ -33,7 +33,7 @@ public void SampleQuery002()
{
var content = L5X.Load(Known.Test);
- var results = content.Find()
+ var results = content.Query()
.SelectMany(t => t.Members())
.Where(t => t.DataType == "SimpleType")
.OrderBy(v => v.TagName)
@@ -47,7 +47,7 @@ public void SampleQuery003()
{
var content = L5X.Load(Known.Test);
- var results = content.Find().Where(t => t.Scope == Scope.Program && t.DataType == "DINT");
+ var results = content.Query().Where(t => t.Scope == Scope.Program && t.DataType == "DINT");
results.Should().NotBeEmpty();
}
@@ -119,7 +119,7 @@ public void QueryAllRungsAndSelectDistinctTagNamesFromTheNeutralTextValue()
{
var content = L5X.Load(Known.Test);
- var results = content.Find().SelectMany(t => t.Text.Tags()).Distinct().ToList();
+ var results = content.Query().SelectMany(t => t.Text.Tags()).Distinct().ToList();
results.Should().NotBeEmpty();
}
@@ -129,7 +129,7 @@ public void QueryAllRungsAndGetTagsInMovInstruction()
{
var content = L5X.Load(Known.Test);
- var results = content.Find()
+ var results = content.Query()
.SelectMany(r => r.Text.TagsIn("MOV"))
.ToList();
diff --git a/tests/L5Sharp.Tests/L5XBasicTests.cs b/tests/L5Sharp.Tests/L5XBasicTests.cs
index a9b08f2b..f13ba479 100644
--- a/tests/L5Sharp.Tests/L5XBasicTests.cs
+++ b/tests/L5Sharp.Tests/L5XBasicTests.cs
@@ -39,14 +39,6 @@ public void New_Template_ShouldNotBeNull()
l5X.Should().NotBeNull();
}
- [Test]
- public void LoadL5XFileUsingTheL5XLoadMethod()
- {
- var content = L5X.Load(Known.Test);
-
- content.Should().NotBeNull();
- }
-
[Test]
public void Info_ValidContent_ShouldHaveExpectedValues()
{
@@ -61,66 +53,43 @@ public void Info_ValidContent_ShouldHaveExpectedValues()
content.Info.Owner.Should().Be("tnunnink, EN Engineering");
content.Info.ExportDate.Should().NotBeNull();
}
-
- [Test]
- public void Add_ValidComponent_ShouldHaveExpectedCount()
- {
- var content = L5X.Load(Known.Test);
- var count = content.DataTypes.Count();
- var dataType = new DataType {Name = "TestAdd"};
-
- content.Add(dataType);
-
- content.DataTypes.Count().Should().Be(count + 1);
- }
[Test]
- public Task Add_ValidComponent_ShouldBeVerified()
- {
- var content = L5X.Load(Known.Test);
- var dataType = new DataType {Name = "TestAdd"};
-
- content.Add(dataType);
-
- return Verify(content.DataTypes.Serialize().ToString());
- }
-
- [Test]
- public void Find_TypeNameOverload_ShouldNotBeEmpty()
+ public void Query_TypeNameOverload_ShouldNotBeEmpty()
{
var content = L5X.Load(Known.Test);
- var tags = content.Find(nameof(Tag)).ToList();
+ var tags = content.Query(nameof(Tag)).ToList();
tags.Should().NotBeEmpty();
}
[Test]
- public void Find_TypeOverload_ShouldNotBeEmpty()
+ public void Query_TypeOverload_ShouldNotBeEmpty()
{
var content = L5X.Load(Known.Test);
- var tags = content.Find(typeof(Tag)).ToList();
+ var tags = content.Query(typeof(Tag)).ToList();
tags.Should().NotBeEmpty();
}
[Test]
- public void Find_ContainsElement_ShouldNotBeEmpty()
+ public void Query_ContainsElement_ShouldNotBeEmpty()
{
var content = L5X.Load(Known.Test);
- var results = content.Find().ToList();
+ var results = content.Query().ToList();
results.Should().NotBeEmpty();
}
[Test]
- public void Find_NoElement_ShouldBeEmpty()
+ public void Query_NoElement_ShouldBeEmpty()
{
var content = L5X.Load(Known.Empty);
- var results = content.Find().ToList();
+ var results = content.Query().ToList();
results.Should().BeEmpty();
}
@@ -128,34 +97,57 @@ public void Find_NoElement_ShouldBeEmpty()
[Test]
public void Find_ValidComponent_ShouldNotBeNull()
{
- var content = L5X.Load(Known.Test);
+ var content = Logix.Load(Known.Test);
- var component = content.FindComponent(Known.DataType);
+ var component = content.Find(Known.DataType);
component.Should().NotBeNull();
}
[Test]
- public void FindTag_ValidComponent_ShouldNotBeNull()
+ public void Find_ValidTagName_ShouldNotBeNull()
{
var content = L5X.Load(Known.Test);
- var tag = content.FindTag(Known.Tag);
+ var tag = content.Find(Known.Tag);
tag.Should().NotBeNull();
}
[Test]
- public void FindReferences_ValidComponent_ShouldHaveExpectedCount()
+ public void ReferencesTo_ValidComponent_ShouldHaveExpectedCount()
{
var content = L5X.Load(Known.Test);
- var tag = content.FindTag(Known.Tag);
+ var tag = content.Get(Known.Tag);
- var references = content.FindReferences(tag).ToList();
+ var references = content.ReferencesTo(tag).ToList();
references.Should().NotBeEmpty();
}
+ [Test]
+ public void Add_ValidComponent_ShouldHaveExpectedCount()
+ {
+ var content = L5X.Load(Known.Test);
+ var count = content.DataTypes.Count();
+ var dataType = new DataType {Name = "TestAdd"};
+
+ content.Add(dataType);
+
+ content.DataTypes.Count().Should().Be(count + 1);
+ }
+
+ [Test]
+ public Task Add_ValidComponent_ShouldBeVerified()
+ {
+ var content = L5X.Load(Known.Test);
+ var dataType = new DataType {Name = "TestAdd"};
+
+ content.Add(dataType);
+
+ return Verify(content.DataTypes.Serialize().ToString());
+ }
+
[Test]
public void Serialize_WhenCalled_ShouldNotBeNull()
{
diff --git a/tests/L5Sharp.Tests/L5XDataTypeTests.cs b/tests/L5Sharp.Tests/L5XDataTypeTests.cs
index 4c3ae9c7..8a0d56f0 100644
--- a/tests/L5Sharp.Tests/L5XDataTypeTests.cs
+++ b/tests/L5Sharp.Tests/L5XDataTypeTests.cs
@@ -306,7 +306,7 @@ public void SetName_ValidName_ShouldUpdateIndex()
component.Name = "NewType";
- var result = content.GetComponent("NewType");
+ var result = content.Find("NewType");
result.Should().NotBeNull();
}
@@ -314,7 +314,7 @@ public void SetName_ValidName_ShouldUpdateIndex()
public void Dependencies_AttachedHasDependencies_ShouldNotBeEmpty()
{
var file = L5X.Load(Known.Test);
- var dataType = file.GetComponent("ComplexType")!;
+ var dataType = file.Get("ComplexType")!;
var dependencies = dataType.Dependencies().ToList();
diff --git a/tests/L5Sharp.Tests/L5XReferenceTests.cs b/tests/L5Sharp.Tests/L5XReferenceTests.cs
index abf021bc..c677431b 100644
--- a/tests/L5Sharp.Tests/L5XReferenceTests.cs
+++ b/tests/L5Sharp.Tests/L5XReferenceTests.cs
@@ -13,7 +13,7 @@ public void FindReferences_ComponentWithKnownReference_ShouldNotBeEmpty()
{
var content = L5X.Load(Known.Test);
- var references = content.FindReferences("TestSimpleTag").ToList();
+ var references = content.ReferencesTo("TestSimpleTag").ToList();
references.Should().NotBeEmpty();
}
@@ -22,18 +22,18 @@ public void FindReferences_ComponentWithKnownReference_ShouldNotBeEmpty()
public void UpdatingTextForKnownRungWithTagReferenceShouldUpdateAndReturnEmptyReferences()
{
var content = L5X.Load(Known.Test);
- var initialReferences = content.FindReferences("TestSimpleTag").ToList();
+ var initialReferences = content.ReferencesTo("TestSimpleTag").ToList();
initialReferences.Should().NotBeEmpty();
var rung = initialReferences.First(r =>
- r.Container == "MainProgram" && r.RoutineName == "Main" && r.ReferenceId == "Rung 2")
- .Reference as Rung;
+ r.Container == "MainProgram" && r.Routine == "Main" && r.ElementId == "2")
+ .Element as Rung;
rung.Should().NotBeNull();
//once we update the text we have replace the references to it.
//It should update the index internally an recalling FindReferences should return an empty collection.
rung!.Text = "This is me fucking with the text.";
- var finalReferences = content.FindReferences("TestSimpleTag").ToList();
+ var finalReferences = content.ReferencesTo("TestSimpleTag").ToList();
finalReferences.Should().BeEmpty();
}
}
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/L5XTagTests.cs b/tests/L5Sharp.Tests/L5XTagTests.cs
index f60f433f..a75857f9 100644
--- a/tests/L5Sharp.Tests/L5XTagTests.cs
+++ b/tests/L5Sharp.Tests/L5XTagTests.cs
@@ -54,7 +54,7 @@ public void References_AgainstKnownTest_ShouldNotBeEmpty()
public void References_AgainstAllTags_ShouldNotBeEmpty()
{
var content = L5X.Load(Known.Example);
- var tags = content.Find().SelectMany(t => t.Members()).ToList();
+ var tags = content.Query().SelectMany(t => t.Members()).ToList();
var references = tags.Select(t => new {t.TagName, Refernces = t.References()}).ToList();
diff --git a/tests/L5Sharp.Tests/L5XTemplateTests.cs b/tests/L5Sharp.Tests/L5XTemplateTests.cs
index 490b7064..fb91cf94 100644
--- a/tests/L5Sharp.Tests/L5XTemplateTests.cs
+++ b/tests/L5Sharp.Tests/L5XTemplateTests.cs
@@ -55,7 +55,7 @@ public void TagsAll_WhenEnumerated_ShouldWork()
var stopwatch = new Stopwatch();
stopwatch.Start();
- var components = content.Find().SelectMany(t => t.Members()).ToList();
+ var components = content.Query().SelectMany(t => t.Members()).ToList();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
@@ -89,7 +89,7 @@ public void Find_Tags_ShouldNotBeEmpty()
var stopwatch = new Stopwatch();
stopwatch.Start();
- var components = content.Find(typeof(Tag)).Cast().SelectMany(t => t.Members()).ToList();
+ var components = content.Query(typeof(Tag)).Cast().SelectMany(t => t.Members()).ToList();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
diff --git a/tests/L5Sharp.Tests/ProofTesting.cs b/tests/L5Sharp.Tests/ProofTesting.cs
index 623d97a9..46c34ad4 100644
--- a/tests/L5Sharp.Tests/ProofTesting.cs
+++ b/tests/L5Sharp.Tests/ProofTesting.cs
@@ -24,16 +24,15 @@ public void HowLongDoesThisShitTake()
}
[Test]
- [TestCase(0)]
- [TestCase(150)]
- [TestCase(200)]
- [TestCase(250)]
- [TestCase(399)]
- [TestCase(400)]
- public void Scratch(int input)
+ public void Scratch()
{
- var character = (char)(input / 200 + 'A');
- Console.WriteLine(character);
+ var content = Logix.Load(Known.Test);
+ var sheet = content.Query().First();
+ var block = sheet[9];
+
+ var arguments = block.Arguments();
+
+ arguments.Should().NotBeEmpty();
}
[Test]
@@ -41,8 +40,10 @@ public void Query()
{
var content = L5X.Load(Known.Example);
- var rungs = content.Find().ToList();
+ var test = content.Query().ToList().First();
+
+ var parent = test.Parent;
- rungs.Should().NotBeEmpty();
+ parent.Should().NotBeNull();
}
}
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/TagPerformanceTests.cs b/tests/L5Sharp.Tests/TagPerformanceTests.cs
index ee253b99..9354ce4c 100644
--- a/tests/L5Sharp.Tests/TagPerformanceTests.cs
+++ b/tests/L5Sharp.Tests/TagPerformanceTests.cs
@@ -18,7 +18,7 @@ public void GetTagsToLookup_FromTestFile_MeasurePerformance()
var stopwatch = new Stopwatch();
stopwatch.Start();
- var tags = content.Find().SelectMany(t => t.Members()).Select(t => t.TagName.ToString()).ToList();
+ var tags = content.Query().SelectMany(t => t.Members()).Select(t => t.TagName.ToString()).ToList();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);