diff --git a/describe/ControlWithIEDName.spec.ts b/describe/ControlWithIEDName.spec.ts
new file mode 100644
index 00000000..5cda0597
--- /dev/null
+++ b/describe/ControlWithIEDName.spec.ts
@@ -0,0 +1,80 @@
+import { expect } from "chai";
+
+import { describeControlWithIEDName } from "./ControlWithIEDName.js";
+
+const scl = new DOMParser().parseFromString(
+ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IED3
+ IED1
+ IED2
+ IED1
+
+
+ IED1
+ IED2
+ IED3
+ IED1
+
+
+
+
+
+
+
+
+
+ `,
+ "application/xml"
+);
+
+const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
+const equalGSEControl = scl.querySelector('*[datSet="equalDataSet"]')!;
+const diffGSEControl = scl.querySelector('*[datSet="diffDataSet"]')!;
+const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
+const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
+
+describe("Description for SCL schema type tControlWithIEDName", () => {
+ it("returns undefined when referenced DataSet is undefined", () =>
+ expect(describeControlWithIEDName(invalidDataSet)).to.be.undefined);
+
+ it("returns undefined with missing referenced DataSet", () =>
+ expect(describeControlWithIEDName(invalidReference)).to.be.undefined);
+
+ it("returns same description with semantically equal ControlWithIEDName's", () =>
+ expect(JSON.stringify(describeControlWithIEDName(baseGSEControl))).to.equal(
+ JSON.stringify(describeControlWithIEDName(equalGSEControl))
+ ));
+
+ it("returns different description with unequal ControlWithIEDName elements", () =>
+ expect(
+ JSON.stringify(describeControlWithIEDName(baseGSEControl))
+ ).to.not.equal(JSON.stringify(describeControlWithIEDName(diffGSEControl))));
+});
diff --git a/describe/ControlWithIEDName.ts b/describe/ControlWithIEDName.ts
new file mode 100644
index 00000000..b5d4368d
--- /dev/null
+++ b/describe/ControlWithIEDName.ts
@@ -0,0 +1,80 @@
+import { ControlDescription, describeControl } from "./Control.js";
+
+function compareIEDNameDescription(a: IEDName, b: IEDName): number {
+ const stringifiedA = JSON.stringify(a);
+ const stringifiedB = JSON.stringify(b);
+
+ if (stringifiedA < stringifiedB) return -1;
+ if (stringifiedA > stringifiedB) return 1;
+ return 0;
+}
+
+type IEDName = {
+ /** IEDName attribute apRef*/
+ apRef?: string;
+ /** IEDName attribute ldInst*/
+ ldInst?: string;
+ /** IEDName attribute prefix*/
+ prefix?: string;
+ /** IEDName attribute lnClass*/
+ lnClass?: string;
+ /** IEDName attribute lnInst*/
+ lnInst?: string;
+ /** IEDName child text content */
+ val?: string;
+};
+
+function describeIEDName(element: Element): IEDName {
+ const iedName: IEDName = {};
+
+ const [apRef, ldInst, prefix, lnClass, lnInst] = [
+ "apRef",
+ "ldInst",
+ "prefix",
+ "lnClass",
+ "lnInst",
+ ].map((attr) => element.getAttribute(attr));
+
+ const val = element.textContent;
+
+ if (apRef) iedName.apRef = apRef;
+ if (ldInst) iedName.ldInst = ldInst;
+ if (prefix) iedName.prefix = prefix;
+ if (lnClass) iedName.lnClass = lnClass;
+ if (lnInst) iedName.lnInst = lnInst;
+ if (val) iedName.val = val;
+
+ return iedName;
+}
+
+export interface ControlWithIEDNameDescription extends ControlDescription {
+ /** ControlWithIEDName children IEDName */
+ iedNames: IEDName[];
+ /** ControlWithIEDName attribute confRev defaulted to 0 */
+ confRev?: number;
+}
+
+export function describeControlWithIEDName(
+ element: Element
+): ControlWithIEDNameDescription | undefined {
+ const controlDescription = describeControl(element);
+ if (!controlDescription) return;
+
+ const controlWithIEDNameDescription: ControlWithIEDNameDescription = {
+ ...controlDescription,
+ iedNames: [],
+ };
+
+ controlWithIEDNameDescription.iedNames.push(
+ ...Array.from(element.children)
+ .filter((child) => child.tagName === "IEDName")
+ .map((iedName) => describeIEDName(iedName))
+ .sort(compareIEDNameDescription)
+ );
+
+ const confRev = element.getAttribute("confRev");
+ if (confRev && !isNaN(parseInt(confRev, 10)))
+ controlWithIEDNameDescription.confRev = parseInt(confRev, 10);
+
+ return controlWithIEDNameDescription;
+}
diff --git a/describe/GSEControl.spec.ts b/describe/GSEControl.spec.ts
new file mode 100644
index 00000000..980eb8c0
--- /dev/null
+++ b/describe/GSEControl.spec.ts
@@ -0,0 +1,82 @@
+import { expect } from "chai";
+
+import { describeGSEControl } from "./GSEControl.js";
+
+const scl = new DOMParser().parseFromString(
+ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IED3
+ IED1
+ IED2
+ IED1
+ R-GOOSE
+
+
+ IED1
+ IED2
+ IED3
+ IED1
+ R-GOOSE
+
+
+
+
+
+
+
+
+
+ `,
+ "application/xml"
+);
+
+const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
+const equalGSEControl = scl.querySelector('*[datSet="equalDataSet"]')!;
+const diffGSEControl = scl.querySelector('*[datSet="diffDataSet"]')!;
+const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
+const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
+
+describe("Description for SCL schema type tControlWithIEDName", () => {
+ it("returns undefined when referenced DataSet is undefined", () =>
+ expect(describeGSEControl(invalidDataSet)).to.be.undefined);
+
+ it("returns undefined with missing referenced DataSet", () =>
+ expect(describeGSEControl(invalidReference)).to.be.undefined);
+
+ it("returns same description with semantically equal GSEControl's", () =>
+ expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.equal(
+ JSON.stringify(describeGSEControl(equalGSEControl))
+ ));
+
+ it("returns different description with unequal GSEControl elements", () =>
+ expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.not.equal(
+ JSON.stringify(describeGSEControl(diffGSEControl))
+ ));
+});
diff --git a/describe/GSEControl.ts b/describe/GSEControl.ts
new file mode 100644
index 00000000..957dee31
--- /dev/null
+++ b/describe/GSEControl.ts
@@ -0,0 +1,44 @@
+import {
+ ControlWithIEDNameDescription,
+ describeControlWithIEDName,
+} from "./ControlWithIEDName.js";
+
+export interface GSEControlDescription extends ControlWithIEDNameDescription {
+ /** GSEControl attribute type defaulted to "GOOSE" */
+ type: "GOOSE" | "GSSE";
+ /** GSEControl attribute appId */
+ appID: string;
+ /** GSEControl attribute fixedOffs defaulted to false */
+ fixedOffs: boolean;
+ /** GSEControl attribute securityEnable defaulted to "None" */
+ securityEnable: "None" | "Signature" | "SignatureAndEncryption";
+ /**GSEControl child Protocol*/
+ protocol?: { mustUnderstand: true; val: "R-GOOSE" };
+}
+
+export function describeGSEControl(
+ element: Element
+): GSEControlDescription | undefined {
+ const controlWithTriggerOptDesc = describeControlWithIEDName(element);
+ if (!controlWithTriggerOptDesc) return;
+
+ const gseControlDescription: GSEControlDescription = {
+ ...controlWithTriggerOptDesc,
+ type: element.getAttribute("type") === "GSSE" ? "GSSE" : "GOOSE",
+ appID: element.getAttribute("appID") ?? "",
+ fixedOffs: element.getAttribute("fixedOffs") === "true" ? true : false,
+ securityEnable: element.getAttribute("securityEnable")
+ ? (element.getAttribute("securityEnable") as
+ | "Signature"
+ | "SignatureAndEncryption")
+ : "None",
+ };
+
+ const protocol = Array.from(element.children).find(
+ (child) => child.tagName === "Protocol"
+ );
+ if (protocol)
+ gseControlDescription.protocol = { mustUnderstand: true, val: "R-GOOSE" };
+
+ return gseControlDescription;
+}