diff --git a/describe/Inputs.spec.ts b/describe/Inputs.spec.ts
new file mode 100644
index 00000000..7c37965e
--- /dev/null
+++ b/describe/Inputs.spec.ts
@@ -0,0 +1,57 @@
+import { expect } from "chai";
+
+import { describeInputs } from "./Inputs.js";
+
+const scl = new DOMParser().parseFromString(
+ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ "application/xml",
+);
+
+const baseInputs = scl.querySelector("LN0 > Inputs")!;
+const equalInputs = scl.querySelector('LN[lnClass="MMXU"][inst="1"] > Inputs')!;
+const diffInputs = scl.querySelector('LN[lnClass="MMXU"][inst="2"] > Inputs')!;
+
+describe("Description for SCL schema element Inputs", () => {
+ it("returns same description with semantically equal Inputs", () =>
+ expect(JSON.stringify(describeInputs(baseInputs))).to.equal(
+ JSON.stringify(describeInputs(equalInputs)),
+ ));
+
+ it("returns same description with semantically different Inputs", () =>
+ expect(JSON.stringify(describeInputs(baseInputs))).to.not.equal(
+ JSON.stringify(describeInputs(diffInputs)),
+ ));
+});
diff --git a/describe/Inputs.ts b/describe/Inputs.ts
new file mode 100644
index 00000000..0770e09c
--- /dev/null
+++ b/describe/Inputs.ts
@@ -0,0 +1,140 @@
+import { NamingDescription, describeNaming } from "./Naming.js";
+
+function compareExtRefDescription(
+ a: ExtRefDescription,
+ b: ExtRefDescription,
+): number {
+ const stringifiedA = JSON.stringify(a);
+ const stringifiedB = JSON.stringify(b);
+
+ if (stringifiedA < stringifiedB) return -1;
+ if (stringifiedA > stringifiedB) return 1;
+ return 0;
+}
+
+interface ExtRefDescription {
+ /** Source IED name attribute */
+ iedName?: string;
+ /** Source LDevice inst attribute */
+ ldInst?: string;
+ /** Source AnyLn prefix attribute */
+ prefix?: string;
+ /** Source AnyLn lnClass attribute */
+ lnClass?: string;
+ /** Source AnyLn lnInst attribute */
+ lnInst?: string;
+ /** Source data object(s) */
+ doName?: string;
+ /** Source data attributes(s) */
+ daName?: string;
+ /** ExtRef attribute instAddr attribute */
+ intAddr?: string;
+ /** Source control block name attribute */
+ srcCBName?: string;
+ /** Source control block parent LDevice inst attribute */
+ srcLDInst?: string;
+ /** Source control block parent AnyLn prefix attribute */
+ srcPrefix?: string;
+ /** Source control block parent AnyLn lnInst attribute */
+ srcLNClass?: string;
+ /** Source control block parent AnyLn inst attribute */
+ srcLNInst?: string;
+ /** Source control block type */
+ serviceType?: "GOOSE" | "Report" | "SMV" | "Poll";
+ /** Restriction logical node class */
+ pLN?: string;
+ /** Restriction data object name(s) */
+ pDO?: string;
+ /** Restriction data attribute name(s) */
+ pDA?: string;
+ /** Restriction control block type */
+ pServT?: "GOOSE" | "Report" | "SMV" | "Poll";
+}
+
+function describeExtRef(element: Element): ExtRefDescription {
+ const extRefDesc: ExtRefDescription = {};
+
+ const [
+ iedName,
+ ldInst,
+ prefix,
+ lnClass,
+ lnInst,
+ doName,
+ daName,
+ intAddr,
+ srcCBName,
+ srcLDInst,
+ srcPrefix,
+ srcLNClass,
+ srcLNInst,
+ serviceType,
+ pLN,
+ pDO,
+ pDA,
+ pServT,
+ ] = [
+ "iedName",
+ "ldInst",
+ "prefix",
+ "lnClass",
+ "lnInst",
+ "doName",
+ "daName",
+ "intAddr",
+ "srcCBName",
+ "srcLDInst",
+ "srcPrefix",
+ "srcLNClass",
+ "srcLNInst",
+ "serviceType",
+ "pLN",
+ "pDO",
+ "pDA",
+ "pServT",
+ ].map((attr) => element.getAttribute(attr));
+
+ if (iedName) extRefDesc.iedName = iedName;
+ if (ldInst) extRefDesc.ldInst = ldInst;
+ if (prefix) extRefDesc.prefix = prefix;
+ if (lnClass) extRefDesc.lnClass = lnClass;
+ if (lnInst) extRefDesc.lnInst = lnInst;
+ if (doName) extRefDesc.doName = doName;
+ if (daName) extRefDesc.daName = daName;
+ if (lnInst) extRefDesc.lnInst = lnInst;
+ if (intAddr) extRefDesc.intAddr = intAddr;
+ if (srcCBName) extRefDesc.srcCBName = srcCBName;
+ if (srcLDInst) extRefDesc.srcLDInst = srcLDInst;
+ if (srcPrefix) extRefDesc.srcPrefix = srcPrefix;
+ if (srcLNClass) extRefDesc.srcLNClass = srcLNClass;
+ if (srcLNInst) extRefDesc.srcLNInst = srcLNInst;
+ if (serviceType && ["Report", "SMV", "GOOSE", "Poll"].includes(serviceType))
+ extRefDesc.serviceType = serviceType as "Report" | "SMV" | "GOOSE" | "Poll";
+ if (pLN) extRefDesc.pLN = pLN;
+ if (pDO) extRefDesc.pDO = pDO;
+ if (pDA) extRefDesc.pDA = pDA;
+ if (pServT && ["Report", "SMV", "GOOSE", "Poll"].includes(pServT))
+ extRefDesc.pServT = pServT as "Report" | "SMV" | "GOOSE" | "Poll";
+
+ return extRefDesc;
+}
+
+export interface InputsDescription extends NamingDescription {
+ extRefs: ExtRefDescription[];
+}
+
+export function describeInputs(element: Element): InputsDescription {
+ const inputsDesc: InputsDescription = {
+ ...describeNaming(element),
+ extRefs: [],
+ };
+
+ inputsDesc.extRefs.push(
+ ...Array.from(element.children)
+ .filter((child) => child.tagName === "ExtRef")
+ .map((extRef) => describeExtRef(extRef))
+ .sort(compareExtRefDescription),
+ );
+
+ return inputsDesc;
+}
diff --git a/describe/LN.spec.ts b/describe/LN.spec.ts
index 7aafb19c..9bb8fd8b 100644
--- a/describe/LN.spec.ts
+++ b/describe/LN.spec.ts
@@ -14,6 +14,9 @@ const scl = new DOMParser().parseFromString(
on
+
+
+
@@ -51,7 +54,11 @@ const scl = new DOMParser().parseFromString(
-
+
+
+
+
+
@@ -72,6 +79,9 @@ const scl = new DOMParser().parseFromString(
test
+
+
+
diff --git a/describe/LN.ts b/describe/LN.ts
index 459e7bf6..acdb4c25 100644
--- a/describe/LN.ts
+++ b/describe/LN.ts
@@ -10,6 +10,7 @@ import {
} from "./LNodeType.js";
import { LogControlDescription, describeLogControl } from "./LogControl.js";
import { NamingDescription, describeNaming } from "./Naming.js";
+import { InputsDescription, describeInputs } from "./Inputs.js";
import {
ReportControlDescription,
describeReportControl,
@@ -19,6 +20,7 @@ import { describeVal, compareBySGroup } from "./Val.js";
export interface LNDescription extends NamingDescription {
reports: Record;
logControls: Record;
+ inputs?: InputsDescription;
lnType: LNodeTypeDescription;
}
@@ -154,10 +156,17 @@ export function LN(element: Element): LNDescription | undefined {
const lnType = updateValues(lNodeTypeDescriptions, instanceValues(element));
- return {
+ const lnDescription: LNDescription = {
...describeNaming(element),
- lnType,
reports: reportControls(element),
logControls: logControls(element),
+ lnType,
};
+
+ const inputs = Array.from(element.children).find(
+ (child) => child.tagName === "Inputs",
+ );
+ if (inputs) lnDescription.inputs = describeInputs(inputs);
+
+ return lnDescription;
}