Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { updateBay } from "./tBay/updateBay.js";
export { updateVoltageLevel } from "./tVoltageLevel/updateVoltageLevel.js";
export { updateSubstation } from "./tSubstation/updateSubstation.js";
export { removeProcessElement } from "./tSubstation/removeProcessElement.js";
export { updateLnType } from "./tSubstation/updateLnType.js";

export { InsertIedOptions, insertIed } from "./tIED/insertIED.js";
export { updateIED } from "./tIED/updateIED.js";
Expand Down Expand Up @@ -71,8 +72,7 @@ export {
export { sourceControlBlock } from "./tExtRef/sourceControlBlock.js";
export { isSubscribed } from "./tExtRef/isSubscribed.js";

export { importLNodeType } from "./tDataTypeTemplates/importLNodeType.js";
export { lNodeTypeToSelection } from "./tDataTypeTemplates/lNodeTypeToSelection.js";


export {
LNodeDescription,
Expand All @@ -82,7 +82,10 @@ export {

export { insertSelectedLNodeType } from "./tDataTypeTemplates/insertSelectedLNodeType.js";

export {removeDataType, RemoveDataTypeOptions} from "./tDataTypeTemplates/removeDataType.js"
export { removeDataType, RemoveDataTypeOptions } from "./tDataTypeTemplates/removeDataType.js"
export { importLNodeType } from "./tDataTypeTemplates/importLNodeType.js";
export { updateLNodeType } from "./tDataTypeTemplates/updateLNodeType.js";
export { lNodeTypeToSelection } from "./tDataTypeTemplates/lNodeTypeToSelection.js";

export {
Supervision,
Expand Down
142 changes: 142 additions & 0 deletions tDataTypeTemplates/foundation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@


function describeEnumType(element: Element): { vals: Record<string, string> } {
const vals: Record<string, string> = {};

const sortedEnumVals = Array.from(element.children)
.filter((child) => child.tagName === "EnumVal")
.sort(
(v1, v2) =>
parseInt(v1.getAttribute("ord")!, 10) -
parseInt(v2.getAttribute("ord")!, 10),
);
for (const val of sortedEnumVals)
vals[val.getAttribute("ord")!] = val.textContent ?? "";

return { vals };
}

function describeDAType(element: Element): {
bdas: Record<string, Record<string, string | null>>;
} {
const bdas: Record<string, Record<string, string | null>> = {};
for (const bda of Array.from(element.children)
.filter((child) => child.tagName === "BDA")
.sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) {
const [bType, type, dchg, dupd, qchg] = [
"bType",
"type",
"dchg",
"dupd",
"qchg",
].map((attr) => bda.getAttribute(attr));
bdas[bda.getAttribute("name")!] = { bType, type, dchg, dupd, qchg };
}
return { bdas };
}

function describeDOType(element: Element) {
const sdos: Record<string, Record<string, string | null>> = {};
for (const sdo of Array.from(element.children)
.filter((child) => child.tagName === "SDO")
.sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) {
const [name, type, transient] = ["name", "type", "transient"].map((attr) =>
sdo.getAttribute(attr),
);
sdos[name!] = { type, transient };
}
const das: Record<string, Record<string, string | null>> = {};
for (const da of Array.from(element.children)
.filter((child) => child.tagName === "DA")
.sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) {
const [name, fc, bType, type, dchg, dupd, qchg] = [
"name",
"fc",
"bType",
"type",
"dchg",
"dupd",
"qchg",
].map((attr) => da.getAttribute(attr));
das[name!] = {
fc,
bType,
type,
dchg,
dupd,
qchg,
};
}
return {
sdos,
das,
cdc: element.getAttribute("cdc"),
};
}

function describeLNodeType(element: Element) {
const dos: Record<string, Record<string, string | null>> = {};
for (const doElement of Array.from(element.children)
.filter((child) => child.tagName === "DO")
.sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) {
const [name, type, transient] = ["name", "type", "transient"].map((attr) =>
doElement.getAttribute(attr),
);
dos[name!] = { type, transient };
}
return {
dos,
lnClass: element.getAttribute("lnClass"),
};
}

const typeDescriptions = {
EnumType: describeEnumType,
DAType: describeDAType,
DOType: describeDOType,
LNodeType: describeLNodeType,
} as Partial<Record<string, (e: Element) => object>>;

function describeElement(element: Element): object {
const describe = typeDescriptions[element.tagName]!;

return describe(element);
}

export function hashElement(element: Element): string {
/** A direct copy from www.github.com/openscd/open-scd-core/foundation/cyrb64.ts */

/**
* Hashes `str` using the cyrb64 variant of
* https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
* @returns digest - a rather insecure hash, very quickly
*/
function cyrb64(str: string): string {
/* eslint-disable no-bitwise */
let h1 = 0xdeadbeef;
let h2 = 0x41c6ce57;
/* eslint-disable-next-line no-plusplus */
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 =
Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 =
Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return (
(h2 >>> 0).toString(16).padStart(8, "0") +
(h1 >>> 0).toString(16).padStart(8, "0")
);
/* eslint-enable no-bitwise */
}

return cyrb64(JSON.stringify(describeElement(element)));
}

export function isEqualNode(ours: Element, theirs: Element): boolean {
return JSON.stringify(describeElement(ours)) === JSON.stringify(describeElement(theirs));
}
36 changes: 32 additions & 4 deletions tDataTypeTemplates/importLNodeType.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { expect } from "chai";

import { Insert, isInsert, isRemove } from "../foundation/utils.js";

import { importLNodeType } from "./importLNodeType.js";
import {
baseDataTypes,
emptyBayTemplate,
competeBayTemplate,
invalidBayTemplate,
hardUpdate,
} from "./importLNodeType.testfiles.js";

import { findElement } from "../foundation/helpers.test.js";
Expand All @@ -21,6 +24,10 @@ const mmxuLNodeType = findElement(
baseDataTypes,
'LNodeType[id="Dummy.MMXU"]'
) as Element;
const tctrHardUpdate = findElement(
hardUpdate,
'LNodeType[id="Dummy.TCTR"]'
) as Element;

describe("Function to import LNodeType with its sub data", () => {
it("is returning an empty string on invalid SCL files", () => {
Expand All @@ -30,14 +37,14 @@ describe("Function to import LNodeType with its sub data", () => {
});

it("is inserting the LNodeType element itself when missing", () => {
const edits = importLNodeType(tctrLNodeType, emptyTemplate);
const edits = importLNodeType(tctrLNodeType, emptyTemplate) as Insert[];

expect(edits.length).to.equal(6);
expect((edits[1].node as Element).tagName).to.equal(tctrLNodeType.tagName);
});

it("is inserting DataTypeTemplate element when missing", () => {
const edits = importLNodeType(tctrLNodeType, emptyTemplate);
const edits = importLNodeType(tctrLNodeType, emptyTemplate) as Insert[];

expect(edits.length).to.equal(6);
expect((edits[0].node as Element).tagName).to.equal("DataTypeTemplates");
Expand All @@ -50,14 +57,35 @@ describe("Function to import LNodeType with its sub data", () => {
});

it("is checking for duplicate data types", () => {
const edits = importLNodeType(tctrLNodeType, completeTemplate);
const edits = importLNodeType(tctrLNodeType, completeTemplate) as Insert[];

expect(edits.length).to.equal(0);
});

it("does not cut out data type from the base project", () => {
const edits = importLNodeType(mmxuLNodeType, emptyTemplate);
const edits = importLNodeType(mmxuLNodeType, emptyTemplate) as Insert[];

edits.forEach((edit) => expect(edit.node.isConnected).to.be.false);
});

it("insert when not duplicate", () => {
const edits = importLNodeType(tctrHardUpdate, completeTemplate) as Insert[];

expect(edits.length).to.equal(1);
});

it("allows to overwrite existing LNodeType", () => {
const edits1 = importLNodeType(tctrHardUpdate, completeTemplate, { overwrite: true }) as Insert[];

expect(edits1.length).to.equal(2);

expect(edits1[0]).to.satisfies(isInsert);
expect(edits1[1]).to.satisfies(isRemove);

const edits2 = importLNodeType(tctrHardUpdate, completeTemplate, { overwrite: false }) as Insert[];

expect(edits2.length).to.equal(1);

expect(edits2[0]).to.satisfies(isInsert);
});
});
42 changes: 42 additions & 0 deletions tDataTypeTemplates/importLNodeType.testfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const competeBayTemplate = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL"
<DAType id="someAnalogueValueINT32">
<BDA name="i" bType="INT32" />
</DAType>
<DAType id="someAnalogueValueFLOAT32">
<BDA name="i" bType="INT32" />
</DAType>
<EnumType id="BehaviourModeKind">
<EnumVal ord="1">on</EnumVal>
<EnumVal ord="2">blocked</EnumVal>
Expand All @@ -44,6 +47,45 @@ export const competeBayTemplate = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL"
</SCL>`;
export const invalidBayTemplate = `<SomeInvalidSCL></SomeInvalidSCL>`;

export const hardUpdate = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL" xmlns:esld="https://transpower.co.nz/SCL/SSD/SLD/v0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2007" revision="B" release="4">
<Header id="BayTemplate"/>
<Substation name="S1">
<VoltageLevel name="V1" desc="" nomFreq="50" numPhases="3">
<Voltage unit="V" multiplier="k">110</Voltage>
<Bay name="B1" desc=""/>
</VoltageLevel>
</Substation>
<DataTypeTemplates>
<LNodeType lnClass="TCTR" id="Dummy.TCTR" desc="Current Transformer">
<DO name="Beh" desc="ENS_Beh" type="OpenSCD_ENS_Beh"/>
<DO name="AmpSv" type="Dummy.SAV"/>
<DO name="MyBeh" type="OpenSCD_ENS_Beh" />
</LNodeType>
<DOType cdc="ENS" id="OpenSCD_ENS_Beh">
<DA name="stVal" bType="Enum" dchg="true" fc="ST" type="BehaviourModeKind"/>
<DA name="q" bType="Quality" qchg="true" fc="ST"/>
<DA name="t" bType="Timestamp" fc="ST"/>
</DOType>
<DOType cdc="SAV" id="Dummy.SAV" desc="Sampled value">
<DA fc="MX" name="instMag" bType="Struct" type="someAnalogueValueINT32"/>
<DA fc="MX" qchg="true" name="q" bType="Quality"/>
</DOType>
<DAType id="someAnalogueValueINT32">
<BDA name="i" bType="INT32" />
</DAType>
<DAType id="someAnalogueValueFLOAT32">
<BDA name="i" bType="INT32" />
</DAType>
<EnumType id="BehaviourModeKind">
<EnumVal ord="1">on</EnumVal>
<EnumVal ord="2">blocked</EnumVal>
<EnumVal ord="3">test</EnumVal>
<EnumVal ord="4">test/blocked</EnumVal>
<EnumVal ord="5">off</EnumVal>
</EnumType>
</DataTypeTemplates>
</SCL>`;

export const baseDataTypes = `
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B" release="4">
<Header id="BaseDataTypes"/>
Expand Down
24 changes: 19 additions & 5 deletions tDataTypeTemplates/importLNodeType.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Insert, createElement } from "../foundation/utils.js";
import { Edit, Insert, createElement } from "../foundation/utils.js";

import { getReference } from "../tBaseElement/getReference.js";
import { isEqualNode } from "./foundation.js";

type ImportLNodeTypeOptions = {
overwrite?: boolean;
}

function removeDuplicates(inserts: Insert[]): Insert[] {
const uniqueInserts: Insert[] = [];
Expand All @@ -25,7 +30,7 @@ function insertDataType(
const existingDataType = targetDataTypeTemplate.querySelector(
`${dataType.tagName}[id="${dataType.getAttribute("id")}"] `
);
if (existingDataType && dataType.isEqualNode(existingDataType)) return;
if (existingDataType && isEqualNode(dataType, existingDataType)) return;

const node = dataType.cloneNode(true);
// const node = dataType;
Expand Down Expand Up @@ -112,8 +117,9 @@ function getDoTypes(parent: Element): Element[] {
*/
export function importLNodeType(
lNodeType: Element,
targetDoc: XMLDocument
): Insert[] {
targetDoc: XMLDocument,
option: ImportLNodeTypeOptions = {}
): Edit[] {
const doc = lNodeType.ownerDocument;
const targetScl = targetDoc.querySelector("SCL");

Expand All @@ -134,8 +140,16 @@ export function importLNodeType(
.filter((enumType) => !!enumType) as Element[]
);

return insertDataTypes(
const inserts = insertDataTypes(
[lNodeType, ...doTypes, ...daTypes, ...enumTypes],
targetScl
);
if (option.overwrite === undefined || option.overwrite === false) return inserts;

const duplicatedLNodeType = targetScl.querySelector(
`:root > DataTypeTemplates > LNodeType[id="${lNodeType.getAttribute("id")}"]`
);
if (!duplicatedLNodeType) return inserts;

return [...inserts, { node: duplicatedLNodeType }]
}
Loading