UNPKG

nodalis-compiler

Version:

Compiles IEC-61131-3/10 languages into code that can be used as a PLC on multiple platforms.

1,484 lines (1,389 loc) 142 kB
/* eslint-disable curly */ /* eslint-disable eqeqeq */ // Copyright [2025] Nathan Skipper // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @description IEC Project Parser * @author Nathan Skipper, MTI * @version 1.0.2 * @copyright Apache 2.0 */ import {DOMParser} from "xmldom"; /** * Tests whether an object value is null or undefined. * @param {Object} obj * @returns Returns True of the object is valid, or false if it is null or undefined. */ function isValid(obj){ return obj !== null && typeof obj !== "undefined"; } /** * Determines whether the value is a primitive type. * @param {*} val The value to evaluate. * @returns {boolean} Returns true if the value is a primitive type. */ function isPrimitive(val) { return ( val === null || typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || typeof val === 'undefined' ); } /** * Determines whether the value is an array. * @param {*} val The value to evaluate. * @returns {boolean} returns true if the value is an array. */ function isArray(val) { return Array.isArray(val); } function forEachElem(array, action){ for(var x = 0; x < array.length; x++){ action(array[x]); } } /** * Evaluates whether a value is an object. * @param {*} val The value to evaluate. * @returns {boolean} returns true if the value is an object. */ function isObject(val) { return typeof val === 'object' && val !== null && !isArray(val); } /** * Creates a new SVG element based on the SVG provided. * @param {string} htmlString The svg code to use in generating a new element. Note: the svg code must be wrapped in a containing element, such as a "g". * @param {boolean} includeSVG Indicates whether to include a containing SVG around the created element. * @returns {SVGElement} Returns a new SVG element. */ function createElementFromSVG(htmlString, includeSVG = false) { const doc = document.createElementNS("http://www.w3.org/2000/svg", "svg"); doc.innerHTML = htmlString.trim(); return includeSVG ? doc.cloneNode(true) : doc.firstChild.cloneNode(true); } /** * Provides a new GUID. * @returns {string} Returns a GUID string. */ function generateGUID() { return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); // y is 8, 9, A, or B return v.toString(16); }); } /** * Creates a timestamp string of the current date and time. * @returns {string} Returns a string of the current date and time in yyyyMMddHHmmss format. */ function formatTimestamp() { const now = new Date(Date.now()); const yyyy = now.getFullYear(); const MM = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based const dd = String(now.getDate()).padStart(2, '0'); const HH = String(now.getHours()).padStart(2, '0'); const mm = String(now.getMinutes()).padStart(2, '0'); const ss = String(now.getSeconds()).padStart(2, '0'); return `${yyyy}${MM}${dd}${HH}${mm}${ss}`; } /** * An abstract class which will provide definition for serializing any inheriting class * into json or from json. The inheriting class must extend the "TypeMap" property defined * by this class to provide types for the properties of the class. Primitive types must be * assigned default values instead of the class name of the type. For example, a property * that has is a number should be "IntProperty": 0. */ export class Serializable{ constructor(){ this.TypeMap = {}; } /** * Converts this object to a json string based on the defined TypeMap. * @returns {string} returns the json string representing this object. */ toJSON() { const obj = {}; for (const key in this.TypeMap) { if(this.hasOwnProperty(key)){ var value = this[key]; if (isPrimitive(value)) { obj[key] = value; } else if (isArray(value)) { obj[key] = value.map(item => isPrimitive(item) ? item : item?.toJSON?.() ?? item ); } else if (isObject(value)) { obj[key] = value?.toJSON?.() ?? value; } else { obj[key] = value; // fallback } } } return JSON.stringify(obj); } /** * Populates the object with the values of the json string, based on the TypeMap definition. * @param {string} json The json string from which to populate the properties of the object. * @param {Object?} parent The parent to add to this object, if it has a Parent property. * @returns {Object} Returns the object after populating its properties. */ fromJSON(json, parent){ var jsonObj = JSON.parse(json); for (const [key, value] of Object.entries(jsonObj)) { const Type = this.TypeMap[key]; if (isValid(value)) { if (Array.isArray(value)) { this[key] = value.map(item => { if (isPrimitive(item)) { return item; } else if (Type && typeof Type === 'function') { const instance = new Type(); return instance.fromJSON(item, this); } else { return item; } }); } else if (isPrimitive(value)) { this[key] = value; } else if (Type && typeof Type === 'function') { const instance = new Type(); this[key] = instance.fromJSON(value, this); } else { this[key] = value; } } } if(this.hasOwnProperty("Parent")){ this.Parent = parent; } return this; } } /** * The Project class represents the XML data for a Project element in the IEC file. */ export class Project extends Serializable { /** * Constructs a new Project object based on the child elements. * @param {FileHeader?} fileHeader Can be undefined, but if provided, sets the FileHeader property to what is provided. * @param {ContentHeader?} contentHeader Can be undefined, but if provided, sets the ContentHeader property to what is provided. * @param {Types?} types Can be undefined, but if provided, sets the Types property. * @param {Instances?} instances Not used currently. */ constructor(fileHeader, contentHeader, types, instances, mappingTable) { super(); this.TypeMap = { "FileHeader": FileHeader, "ContentHeader": ContentHeader, "Types": Types, "Instances": Instances, "MappingTable": MappingTable }; /**@type {FileHeader} */ this.FileHeader = new FileHeader(); /**@type {ContentHeader} */ this.ContentHeader = new ContentHeader(); /**@type {Types} */ this.Types = new Types(); /**@type {Instances} */ this.Instances = new Instances(); this.MappingTable = new MappingTable(); if(isValid(fileHeader)) {this.FileHeader = fileHeader;} if(isValid(contentHeader)) {this.ContentHeader = contentHeader;} if(isValid(types)) {this.Types = types;} if(isValid(instances)) this.Instances = instances; if(isValid(mappingTable)) this.MappingTable = mappingTable; } /** * Parses a string of xml representing the complete project file and sets the properties of a new Project object based on it. * @param {String} xml A string containing the xml to parse. * @returns A new Project object. */ static fromXML(xml) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xml, "text/xml"); const proj = new Project( FileHeader.fromXML(xmlDoc.getElementsByTagName("FileHeader")[0]), ContentHeader.fromXML(xmlDoc.getElementsByTagName("ContentHeader")[0]), null, null ); proj.Types = Types.fromXML(xmlDoc.getElementsByTagName("Types")[0], proj); proj.Instances = Instances.fromXML(xmlDoc.getElementsByTagName("Instances")[0], proj); proj.MappingTable = MappingTable.fromXML(xmlDoc.getElementsByTagName("MappingTable")[0], proj); if(!isValid(proj.Types)) proj.Types = new Types(null, proj); if(!isValid(proj.Instances)) proj.Instances = new Instances(null, proj); if(!isValid(proj.MappingTable)) proj.MappingTable = new MappingTable(); return proj; } /** * Formats the object as an XML string. * @returns A string representation of the Project object. */ toXML() { return `<?xml version="1.0" encoding="utf-8"?> <Project xmlns="www.iec.ch/public/TC65SC65BWG7TF10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="www.iec.ch/public/TC65SC65BWG7TF10 IEC61131_10_Ed1_0.xsd" schemaVersion="1.0"> ${this.FileHeader.toXML()} ${this.ContentHeader.toXML()} ${this.Types.toXML()} ${this.Instances.toXML()} ${this.MappingTable.toXML()} </Project>`; } getAllPrograms(){ var programs = []; try{ forEachElem(this.Types.GlobalNamespace.NamespaceDecl.Programs, (p) => { programs.push(p); } ); } catch(e){ console.error(e); } return programs; } getAllFunctionBlocks(){ var fbs = []; try{ forEachElem(this.Types.GlobalNamespace.NamespaceDecl.FunctionBlocks, (p) => { fbs.push(p); } ); } catch(e){ console.error(e); } return fbs; } /** * Finds the containing Project for the child. * @param {FbdObject|LdObject|Rung|Network|BodyContent|MainBody|Program|FunctionBlock|NamespaceDecl|GlobalNamespace|Types} child The child object from which to find the containing Project. * @returns Returns the Project for the child, or null if not found. */ static getProject(child){ return Project.getParentOfType(child, Project); } /** * Finds the containing parent for the child that matches the specified type. * @param {FbdObject|LdObject|Rung|Network|BodyContent|MainBody|Program|FunctionBlock|NamespaceDecl|GlobalNamespace|Types} child The child object from which to find the containing Project. * @param {class} type The class type to search for. * @returns Returns the parent object of the specified type for the child, or null if not found. */ static getParentOfType(child, type){ var ret = null; try{ ret = child; while(isValid(ret.Parent) && !(ret instanceof type)){ ret = ret.Parent; } } catch(e){ console.error(e); } return ret; } } /** * Represents the FileHeader element of an IEC file. */ export class FileHeader extends Serializable { /** * Instantiates a new FileHeader object based on provided information. * @param {string?} companyName Can be undefined, but if provided, sets the companyName property. * @param {string?} companyURL Can be undefined, but if provided, sets the companyURL property. * @param {string?} productName Can be undefined, but if provided, sets the productName property. * @param {string?} productVersion Can be undefined, but if provided, sets the productVersion property. * @param {strin?} productRelease Can be undefined, but if provided, sets the productRelease property. */ constructor(companyName, companyURL, productName, productVersion, productRelease) { super(); this.TypeMap = { "companyName": "", "companyURL": "", "productName": "", "productVersion": "", "productRelease": "" }; this.companyName = ""; this.companyURL = ""; this.productName = ""; this.productVersion = ""; this.productRelease = ""; if(isValid(companyName)) {this.companyName = companyName;} if(isValid(companyURL)) {this.companyURL = companyURL;} if(isValid(productName)) {this.productName = productName;} if(isValid(productVersion)) {this.productVersion = productVersion;} if(isValid(productRelease)) {this.productRelease = productRelease;} } /** * Creates a new FileHeader object based on an Element object representing the XML. * @param {Element} xml The xml element to parse. * @returns Returns a new FileHeader object. */ static fromXML(xml) { if(!isValid(xml)) {return null;} return new FileHeader( xml.getAttribute("companyName"), xml.getAttribute("companyURL"), xml.getAttribute("productName"), xml.getAttribute("productVersion"), xml.getAttribute("productRelease") ); } /** * * @returns Returns a string of XML representing the object. */ toXML() { return `<FileHeader companyName="${this.companyName}" companyURL="${this.companyURL}" productName="${this.productName}" productVersion="${this.productVersion}" productRelease="${this.productRelease}"/>`; } } /** * Represents the ContentHeader element in an IEC project file. */ export class ContentHeader extends Serializable { /** * Instantiates a new ContentHeader based on properties provided. * @param {string?} name Can be null or undefined. * @param {string?} version Can be null or undefined. * @param {string?} creationDateTime Can be null or undefined. * @param {string?} modificationDateTime Can be null or undefined. * @param {string?} organization Can be null or undefined. * @param {string?} author Can be null or undefined. * @param {string?} language Can be null or undefined. */ constructor(name, version, creationDateTime, modificationDateTime, organization, author, language) { super(); this.TypeMap = { "name": "", "version": "", "creationDateTime": "", "modificationDateTime": "", "organization": "", "author": "", "language": "" }; this.Name = "New"; this.version = "1.0"; this.creationDateTime = Date.now().toString(); this.modificationDateTime = Date.now().toString(); this.organization = ""; this.author = ""; this.language = "En"; if(isValid(name)) {this.Name = name;} if(isValid(version)) {this.version = version;} if(isValid(creationDateTime)) {this.creationDateTime = creationDateTime;} if(isValid(modificationDateTime)) {this.modificationDateTime = modificationDateTime;} if(isValid(organization)) {this.organization = organization;} if(isValid(author)) {this.author = author;} if(isValid(language)) {this.language = language;} } /** * Creates a new ContentHeader object based on an XML element object. * @param {Element} xml An Element object representing the XML. * @returns A new ContentHeader object. */ static fromXML(xml) { if(!isValid(xml)) {return null;} return new ContentHeader( xml.getAttribute("name"), xml.getAttribute("version"), xml.getAttribute("creationDateTime"), xml.getAttribute("modificationDateTime"), xml.getAttribute("organization"), xml.getAttribute("author"), xml.getAttribute("language")); } /** * * @returns Returns an string of xml representing the object. */ toXML() { return `<ContentHeader name="${this.Name}" version="${this.version}" creationDateTime="${this.creationDateTime}" modificationDateTime="${this.modificationDateTime}" organization="${this.organization}" author="${this.author}" language="${this.language}"> </ContentHeader>`; } } /** * Defines the coordinate info for the IEC file. This provides scaling for the diagram types. */ export class CoordinateInfo extends Serializable{ /** * * @param {Scaling?} FbdScaling * @param {Scaling?} LdScaling * @param {Scaling?} SfcScaling */ constructor(FbdScaling, LdScaling, SfcScaling) { super(); this.TypeMap = { "FbdScaling": Scaling, "LdScaling": Scaling, "SfcScaling": Scaling }; this.FbdScaling = new Scaling(1, 1); this.LdScaling = new Scaling(1, 1); this.SfcScaling = new Scaling(1, 1); if(isValid(FbdScaling)) this.FbdScaling = FbdScaling; if(isValid(LdScaling)) this.LdScaling = LdScaling; if(isValid(SfcScaling)) this.SfcScaling = SfcScaling; } /** * Creates a new CoordinateInfo object based on an XML element. * @param {Element} xml The XML element from which to get the object's properties. * @returns {CoordinateInfo} Returns a new object based on the XML. */ static fromXML(xml) { if(!isValid(xml)) {return null;} return new CoordinateInfo( Scaling.fromXML(xml.getElementsByTagName("FbdScaling")[0]), Scaling.fromXML(xml.getElementsByTagName("LdScaling")[0]), Scaling.fromXML(xml.getElementsByTagName("SfcScaling")[0]) ); } /** * Creates an xml string representing the object. * @returns {string} Returns the XML string representing the object. */ toXML() { return `<CoordinateInfo> ${this.FbdScaling.toXML()} ${this.LdScaling.toXML()} ${this.SfcScaling.toXML()} </CoordinateInfo>`; } } /** * Implements the scaling for an IEC file. */ export class Scaling extends Serializable{ /** * Constructs a new Scaling object. * @param {string?} x The x scale factor * @param {string?} y The y scale factor. */ constructor(x, y) { super(); this.TypeMap = { "x": "", "y": "" }; this.x = "1"; this.y = "1"; if(isValid(x)) this.x = x; if(isValid(y)) this.y = y; } /** * Creates a new scaling object based on the xml element. * @param {Element} xml The XML element from which to create the object. * @returns {Scaling} A new scaling object. */ static fromXML(xml) { if(!isValid(xml)) {return null;} return new Scaling( xml.getAttribute("x"), xml.getAttribute("y") ); } /** * Creates an xml string representing the object. * @returns {string} An xml string representing the object. */ toXML() { return `<Scaling x="${this.x}" y="${this.y}"/>`; } } /** * Represents the Types element in an IEC project file. */ export class Types extends Serializable{ /** * Instantiates a new Types object. * @param {GlobalNamespace?} globalNamespace Can be null or undefined. * @param {Project?} parent The containing project. */ constructor(globalNamespace, parent) { super(); this.TypeMap = { "GlobalNameSpace": GlobalNamespace }; this.GlobalNamespace = new GlobalNamespace(); this.Parent = null; if(isValid(globalNamespace)) {this.GlobalNamespace = globalNamespace;} if(isValid(parent)) this.Parent = parent; } /** * Creates a new Types object based on an XML element. * @param {Element} xml An element object representing the XML for the types. * @param {Project} parent The containing project. * @returns Returns a new Types object. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} const type = new Types(null, parent); const gns = GlobalNamespace.fromXML(xml.getElementsByTagName("GlobalNamespace")[0], type); type.GlobalNamespace = gns; return type; } /** * * @returns Returns a string representing the xml of this object. */ toXML() { return `<Types> ${this.GlobalNamespace.toXML()} </Types>`; } toST(){ return this.GlobalNamespace.toST(); } } /** * Represents the GlobalNamespace element of the IEC project file. */ export class GlobalNamespace extends Serializable { /** * Constructs a new GlobalNamespace object. * @param {NamespaceDecl?} namespaceDecl Can be null or undefined. * @param {Types?} parent The containing object. */ constructor(namespaceDecl, parent) { super(); this.TypeMap = { "NamespaceDecl": NamespaceDecl }; /**@type {NamespaceDecl} */ this.NamespaceDecl = new NamespaceDecl(); this.Parent = null; if(isValid(namespaceDecl)) {this.NamespaceDecl = namespaceDecl;} if(isValid(parent)) this.Parent = parent; } /** * Creates a new GlobalNamespace object based on an XML element. * @param {Element} xml An xml Element object from which to create the GlobalNamespace. * @returns Returns a new GlobalNamespace object. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} const gns = new GlobalNamespace(null , parent); gns.NamespaceDecl = NamespaceDecl.fromXML(xml.getElementsByTagName("NamespaceDecl")[0], gns); return gns; } /** * * @returns Returns a string of xml representing the object. */ toXML() { return `<GlobalNamespace> ${this.NamespaceDecl.toXML()} </GlobalNamespace>`; } toST(){ return this.NamespaceDecl.toST(); } } /** * Represents the NamespaceDecl element of an IEC project file. */ export class NamespaceDecl extends Serializable{ /** * Instantiates a new NamespaceDecl object. * @param {string?} name Can be null or undefined. * @param {GlobalNamespace?} parent The containing object. */ constructor(name, parent) { super(); this.TypeMap = { "name": "", "Programs": [], "FunctionBlocks": [] }; /** * @type {string} */ this.Name = "Default"; /** * @type {Program[]} */ this.Programs = []; /** * @type {FunctionBlock[]} */ this.FunctionBlocks = []; /** * @type {GlobalNamespace} */ this.Parent = null; if(isValid(name)) {this.Name = name;} if(isValid(parent)) this.Parent = parent; } /** * Creates a new NamespaceDecl object from the given xml element. * @param {Element} xml The XML element to parse. * @param {GlobalNamespace} parent The containing object. * @returns instantiated NamespaceDecl object based on xml. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} var ns = new NamespaceDecl( xml.getAttribute("name"), parent ); var xmlprogs = xml.getElementsByTagName("Program"); var xmlfb = xml.getElementsByTagName("FunctionBlock"); if(!isValid(ns.Programs)) {ns.Programs = [];} if(!isValid(ns.FunctionBlocks)) {ns.FunctionBlocks = [];} forEachElem(xmlprogs, (prog) => { ns.Programs.push(Program.fromXML(prog, ns)); }); forEachElem(xmlfb, (fb) => { ns.FunctionBlocks.push(FunctionBlock.fromXML(fb, ns)); }); return ns; } /** * * @returns Returns a string of xml representing the object. */ toXML() { var progs = ""; var fbs = ""; forEachElem(this.Programs, (elem) => { progs += elem.toXML(); }); forEachElem(this.FunctionBlocks, (elem) => { fbs += elem.toXML(); }); return `<NamespaceDecl name="${this.Name}"> ${progs} ${fbs} </NamespaceDecl>`; } /** * Converts all elements of this namespace to structured text. * @returns {string} A string representing the structured text. */ toST(){ var blocks = ""; var programs = ""; forEachElem(this.FunctionBlocks, (f) => blocks += f.toST() + "\n"); forEachElem(this.Programs, (p) => programs += p.toST() + "\n"); return `${blocks} ${programs}`; } /** * Adds options to a given element for all variable types defined within this namespace, or as a standard function block. * @param {HTMLElement} elem The element to add the options to. */ addVariableTypes(elem){ try{ forEachElem(FbdObject.StandardBlocks, sb => { var opt = document.createElement("option"); opt.value = sb.TypeName; opt.textContent = sb.TypeName; elem.appendChild(opt); }); forEachElem(this.FunctionBlocks, sb => { var opt = document.createElement("option"); opt.value = sb.Name; opt.textContent = sb.Name; elem.appendChild(opt); }); } catch(e){ console.error(e); } } } /** * Represents the instances list for the IEC file. */ export class Instances extends Serializable { /** * Constructs a new Instances object. * @param {Configuration[]?} configurations The configurations for the instances. * @param {Project?} parent The parent that owns this object. */ constructor(configurations, parent) { super(); this.TypeMap = { "Configurations": [] }; this.Configurations = []; this.Parent = null; if(isValid(configurations)) this.Configurations = configurations; if(isValid(parent)) this.Parent = parent; } /** * Creates an object from an xml element. * @param {Element} xml The xml element from which to create the object. * @param {Project?} parent The parent that owns this object. * @returns {Instances} A new object. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} var obj = new Instances(); if(isValid(parent)) obj.Parent = parent; var configs = xml.getElementsByTagName("Configuration"); forEachElem(configs, (c) => { obj.Configurations.push(Configuration.fromXML(xml, obj)); }); return obj; } /** * Creates an xml string representing the object. * @returns {string} An xml string representing the object. */ toXML() { var configs = ""; forEachElem(this.Configurations, (c) => { configs += c.toXML() + "\n"; }); return `<Instances> ${configs} </Instances>`; } } /** * A configuration for the IEC file. */ export class Configuration extends Serializable{ /** * Constructs a new configuration object. * @param {string?} name The name of the configuration. * @param {Resource[]?} resources An array of resources. * @param {Instances?} parent The parent that owns this object. */ constructor(name, resources, parent) { super(); this.TypeMap = { "Name": "", "Resources": [] }; this.Parent = null; this.Resources = []; this.Name = "Main"; if(isValid(parent)) this.Parent = parent; if(isValid(name)) this.Name = name; if(isValid(resources)) this.Resources = resources; } /** * Creates a new Configuration object based on the XML element. * @param {Element} xml The xml element to create the object from. * @param {Instances} parent The parent that owns this object. * @returns {Configuration} Returns the configuration object. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} var obj = new Configuration(xml.getAttribute("name"),null, parent); forEachElem(xml.getElementsByTagName("Resource"), (r) => { obj.Resources.push(Resource.fromXML(r, obj)); }); return obj; } /** * Creates an xml string representing the object. * @returns {string} An xml string representing the object. */ toXML() { var resources = ""; forEachElem(this.Resources, (r) => { resources += r.toXML() + "\n"; }); return `<Configuration name="${this.Name}"> ${resources} </Configuration>`; } } /** * Represents a Resource element in an IEC file. */ export class Resource extends Serializable{ /** * Constructs a new resource object. * @param {string?} name The name of the resource. * @param {string?} resourceTypeName The type of the resource. * @param {GlobalVars?} globalVars A GlobalVars objects. * @param {Task[]?} tasks An array of tasks. * @param {ProgramInstance[]?} programInstances An array of program instances. * @param {Configuration?} parent The parent that owns this object. */ constructor(name, resourceTypeName, globalVars, tasks, programInstances, parent) { super(); this.TypeMap = { "Name": "", "ResourceTypeName": "", "GlobalVars": GlobalVars, "Tasks": [], "ProgramInstances": [] }; this.Name = "Main"; this.ResourceTypeName = ""; this.GlobalVars = new GlobalVars(null, this); this.Tasks = []; this.ProgramInstances = []; this.Parent = null; if(isValid(name)) this.Name = name; if(isValid(resourceTypeName)) this.ResourceTypeName = resourceTypeName; if(isValid(globalVars)) this.GlobalVars = globalVars; if(isValid(tasks)) this.Tasks = tasks; if(isValid(programInstances)) this.ProgramInstances = programInstances; if(isValid(parent)) this.Parent = parent; } /** * Creates a new object based on the xml element. * @param {Element} xml The xml element to create the object from. * @param {Configuration?} parent The parent that will own the object. * @returns {Resource} Returns a new resource object based on the Xml. */ static fromXML(xml, parent) { if(!isValid(xml)) {return null;} var obj = new Resource(xml.getAttribute("name"), xml.getAttribute("resourceTypeName"), null, null, null, parent); obj.GlobalVars = GlobalVars.fromXML(xml.getElementsByTagName("GlobalVars")[0]); forEachElem(xml.getElementsByTagName("Task"), (t) => { obj.Tasks.push(Task.fromXML(t, obj)); }); forEachElem(xml.getElementsByTagName("ProgramInstance"), (p) => { obj.ProgramInstances.push(ProgramInstance.fromXML(p, obj)); }); return obj; } /** * Creates an xml string representing the object. * @returns {string} Returns an xml string of the object. */ toXML() { var tasks = ""; var progs = ""; forEachElem(this.Tasks, (t) => { tasks += t.toXML() + "\n"; }); forEachElem(this.ProgramInstances, (p) => { progs += p.toXML() + "\n"; }); return `<Resource name="${this.Name}" resourceTypeName="${this.ResourceTypeName}"> ${this.GlobalVars.toXML()} ${tasks} ${progs} </Resource>`; } /** * Converts the resource's programming to Structured Text. * @returns {string} Returns a string representing the structured text. */ toST(){ var tasks = ""; var progs = ""; var vars = ""; var included = ""; var map = this.Parent.Parent.Parent.MappingTable.toST(this.Name); var programs = this.Parent.Parent.Parent.getAllPrograms(); var fbs = this.Parent.Parent.Parent.getAllFunctionBlocks(); forEachElem(fbs, fb => included += fb.toST() + "\n"); forEachElem(this.Tasks, t => tasks += t.toST()); forEachElem(this.ProgramInstances, /** * * @param {ProgramInstance} p */ p => { progs += p.toST(); var incProg = programs.find(pr => pr.Name === p.TypeName); if(isValid(incProg)){ included += incProg.toST() + "\n"; } }); forEachElem(this.GlobalVars.Variables, /** * * @param {Variable} v */ (v) => { let addr = null; if(isValid(v.Address)){ addr = "%" + v.Address.Location + v.Address.Size + v.Address.Address; } vars += `${v.Name} ${isValid(addr) ? " AT " + addr : ""} : ${v.Type.TypeName};${isValid(addr) ? `\n//Global={"Name":"${v.Name}", "Address":"${addr}"}` : ""}\n`; } ); var res = ` ${map} ${tasks} ${progs} VAR_GLOBAL ${vars} END_VAR ${included}`; return res; } } /** * Represents a GlobalVars element in the IEC file. */ export class GlobalVars extends Serializable{ /** * Constructs a new GlobalVars object. * @param {Variable[]?} variables An array of variables. * @param {Resource?} parent The parent that owns this object. */ constructor(variables, parent){ super(); this.TypeMap = { "Variables": [] }; this.Variables = []; this.Parent = null; if(isValid(variables)) this.Variables = variables; if(isValid(parent)) this.Parent = parent; } /** * Creates a new object based on the xml element. * @param {Element} xml The xml element from which to create the object. * @param {Resource?} parent The parent that owns the object. * @returns {GlobalVars?} Returns a new GlobalVars object from the xml. */ static fromXML(xml, parent){ if(!isValid(xml)) return null; var obj = new GlobalVars(null, parent); forEachElem(xml.getElementsByTagName("Variable"), v => { obj.Variables.push(Variable.fromXML(v)); }); return obj; } /** * Creates an xml string representation of the object. * @returns {string} Returns an xml string of the object. */ toXML(){ var v = ""; forEachElem(this.Variables, va => { v += va.toXML() + "\n"; }); return `<GlobalVars> ${v} </GlobalVars>`; } } /** * Represents a task in the IEC file. */ export class Task extends Serializable{ /** * Constructs a new Task object. * @param {string?} type The type of the task. * @param {string?} name The name of the task. * @param {string?} interval The interval at which the task runs. * @param {string?} priority The priority of the task. * @param {Configuration?} parent The parent that owns the task. */ constructor(type, name, interval, priority, parent){ super(); this.TypeMap = { "Type": "", "Name": "", "Interval": "", "Priority": "" }; this.Type = ""; this.Name = ""; this.Interval = "1000"; this.Priority = "1"; this.Parent = null; if(isValid(type)) this.Type = type; if(isValid(name)) this.Name = name; if(isValid(interval)) this.Interval = interval; if(isValid(priority)) this.Priority = priority; if(isValid(parent)) this.Parent = parent; } /** * Creates a new task object from xml. * @param {Element} xml The xml element from which to create the object. * @param {Configuration?} parent The parent that owns the object. * @returns {Task} Returns a new task object. */ static fromXML(xml, parent){ if(!isValid(xml)) return null; return new Task(xml.getAttribute("xsi:type"), xml.getAttribute("name"), xml.getAttribute("interval"), xml.getAttribute("priority"), parent); } /** * Creates a xml string representation of the object. * @returns {string} Returns an xml string of the object. */ toXML(){ return `<Task xsi:type="${this.Type}" name="${this.Name}" interval="${this.Interval}" priority="${this.Priority}"/>`; } /** * Converts the task information to a structured text comment containing JSON that can be consumed by a compiler * for creating platform specific tasks. * @returns {string} A string representing the task as structured text. */ toST(){ return `//Task={"Name":"${this.Name}", "Interval":"${this.Interval}", "Priority":"${this.Priority}"}\n`; } } /** * Represents a ProgramInstance in the IEC file. */ export class ProgramInstance extends Serializable{ /** * Constructs a new ProgramInstance object. * @param {string?} name The name of the instance. * @param {string?} typeName The type name for the instance. * @param {string?} associatedTaskName The task associated with this instance. * @param {Configuration?} parent The parent that owns this object. */ constructor(name, typeName, associatedTaskName, parent){ super(); this.TypeMap = { "Name": "", "TypeName": "", "AssociatedTaskName": "" }; this.TypeName = ""; this.Name = ""; this.AssociatedTaskName = ""; this.Parent = null; if(isValid(typeName)) this.TypeName = typeName; if(isValid(name)) this.Name = name; if(isValid(associatedTaskName)) this.AssociatedTaskName = associatedTaskName; if(isValid(parent)) this.Parent = parent; } /** * Creates a new ProgramInstance object from the xml element. * @param {Element} xml The xml element from which to create the object. * @param {Configuration?} parent The parent that owns the objec.t * @returns {ProgramInstance?} Returns a new object. */ static fromXML(xml, parent){ if(!isValid(xml)) return null; return new ProgramInstance(xml.getAttribute("name"), xml.getAttribute("typeName"), xml.getAttribute("associatedTaskName"), parent); } /** * * @returns {string} An xml string representing the object. */ toXML(){ return `<ProgramInstance typeName="${this.TypeName}" name="${this.Name}" associatedTaskName="${this.AssociatedTaskName}"/>`; } /** * Creates a structured text comment containing JSON that represents the properties of the instance, which can be consumed by * compilers to set up platform-specific instances. * @returns {string} Returns a string representing the structured Text. */ toST(){ return `//Instance={"TypeName":"${this.TypeName}", "Name":"${this.Name}", "AssociatedTaskName":"${this.AssociatedTaskName}"}\n`; } } /** * Represents the Program element of the IEC project file. */ export class Program extends Serializable { /** * * @param {string?} name Can be null or undefined. * @param {ExternalVars?} externalVars can be null or undefined. * @param {Vars?} vars can be null or undefined. * @param {MainBody?} mainBody Can be null or undefined. * @param {NamespaceDecl?} parent The containing namespace. */ constructor(name, externalVars, vars, mainBody, parent) { super(); this.TypeMap = { "Name": "", "ExternalVars": ExternalVars, "Vars": Vars, "MainBody": MainBody, }; this.Name = ""; this.ExternalVars = new ExternalVars(); this.Vars = new Vars(); this.MainBody = new MainBody(); this.Parent = null; if(isValid(name)) this.Name = name; if(isValid(externalVars)) this.ExternalVars = externalVars; if(isValid(vars)) this.Vars = vars; if(isValid(mainBody)) this.MainBody = mainBody; if(isValid(parent)) this.Parent = parent; } /** * Creates a new Program object based on an XML element. * @param {Element} xml The XML element to create from. * @param {NamespaceDecl} parent The containing namespace. * @returns A new Program object. */ static fromXML(xml, parent) { if(!isValid(xml)) return null; const prog = new Program( xml.getAttribute("name"), ExternalVars.fromXML(xml.getElementsByTagName("ExternalVars")[0]), Vars.fromXML(xml.getElementsByTagName("Vars")[0]), null, parent ); prog.MainBody = MainBody.fromXML(xml.getElementsByTagName("MainBody")[0], prog); return prog; } /** * * @returns Returns an xml string representing the object. */ toXML() { return `<Program name="${this.Name}"> ${this.ExternalVars.toXML()} ${this.Vars.toXML()} ${this.MainBody.toXML()} </Program>`; } /** * Compiles the program as structured text. NOTE: external variables are ignored and should be * defined in the resource section of the project as global variables. * @returns {string} Returns a string representing the structured text of the program. */ toST(){ var st = ""; try{ var decl = ""; forEachElem(this.Vars.Variables, /** * * @param {Variable} v */ (v) => { decl += `${v.Name} ${isValid(v.Address) ? "AT %" + v.Address.Location + v.Address.Size + (v.Address.Address.length > 0 ? "." + v.Address.Address : "") : ""} : ${v.Type.TypeName};\n`; } ); st = `PROGRAM ${this.Name} VAR ${decl} END_VAR ${this.MainBody.toST()} END_PROGRAM`; } catch(e){ console.error(e); } return st; } } /** * Represents a MainBody element in the IEC project file. */ export class MainBody extends Serializable{ /** * Constructs a new MainBody object. * @param {BodyContent?} bodyContent Can be null or undefined. * @param {Program|FunctionBlock?} parent The containing object for this one. */ constructor(bodyContent, parent) { super(); this.TypeMap = { "BodyContent": BodyContent }; this.BodyContent = new BodyContent(); this.Parent = null; if(isValid(bodyContent)) this.BodyContent = bodyContent; if(isValid(parent)) this.Parent = parent; } /** * Creates a new MainBody object from an xml element. * @param {Element} xml The XML element object from which to create an object. * @param {Program|FunctionBlock?} parent The containing object for this one. * @returns {MainBody} A new MainBody object. */ static fromXML(xml, parent) { if(!isValid(xml)) return null; const body = new MainBody( null , parent); body.BodyContent = BodyContent.fromXML(xml.getElementsByTagName("BodyContent")[0], body); return body; } /** * * @returns {string} Returns a string of xml representing htis object. */ toXML() { return `<MainBody> ${this.BodyContent.toXML()} </MainBody>`; } /** * Converts the content of this body to structured text. * @returns {string} Returns a string representing the structured text. */ toST(){ return this.BodyContent.toST(); } } /** * Represents a BodyContent element in an IEC project file. */ export class BodyContent extends Serializable { /** * Constructs a new BodyContent object * @param {string?} type The type of body content. Default: ST * @param {ST?} st An ST object, can be null or undefined. * @param {Rung[]?} rungs An array of Rung objects. Can be null or undefined. * @param {Network[]?} networks An array of network objects. Can be null or undefined. * @param {MainBody?} parent The Mainbody object that contains this content */ constructor(type, st, rungs, networks, parent) { super(); this.TypeMap = { "Type": "", "ST": ST, "Rungs": [], "Networks": [] }; this.Type = "ST"; if(isValid(type)) this.Type = type; this.ST = null; this.Rungs = []; this.Networks = []; this.Parent = null; if(type == "ST"){ this.ST = new ST(); if(isValid(st)) this.ST = st; } else if(type == "LD"){ if(isValid(rungs)) this.Rungs = rungs; } else if(type == "FBD"){ if(isValid(networks)) this.Networks = networks; } if(isValid(parent)) this.Parent = parent; } /** * Creates a new BodyContent object based on an XML element. * @param {Element} xml An XML element object. * @param {MainBody?} parent The parent of this object. * @returns {BodyContent} Returns a new BodyContent object. */ static fromXML(xml, parent) { if(!isValid(xml)) return null; var b = new BodyContent( xml.getAttribute("xsi:type"), null, null, null, parent); var xmlrungs = xml.getElementsByTagName("Rung"); var xmlnet = xml.getElementsByTagName("Network"); var xmlst = xml.getElementsByTagName("ST")[0]; if(b.Type == "ST"){ b.ST = ST.fromXML(xmlst); if(!isValid(b.ST)){ b.ST = new ST(); } } else if(b.Type == "LD"){ forEachElem(xmlrungs, (elem) => { b.Rungs.push(Rung.fromXML(elem, b)); }); } else if(b.Type == "FBD"){ forEachElem(xmlnet, (elem) => { b.Networks.push(Network.fromXML(elem, b)); }); } return b; } /** * * @returns Returns an xml string representing the object. */ toXML() { var subxml = ""; if(this.Type == "ST"){ if(isValid(this.ST)){ subxml = this.ST.toXML(); } } else if(this.Type == "LD"){ if(isValid(this.Rungs)){ forEachElem(this.Rungs, (rung) => { subxml += rung.toXML() + "\n"; }); } } else if(this.Type == "FBD"){ if(isValid(this.Networks)){ forEachElem(this.Networks, (net) => { subxml += net.toXML() + "\n"; }); } } return `<BodyContent xsi:type="${this.Type}"> ${subxml} </BodyContent>`; } /** * Finds an object in the rungs based on the ID value. * @param {string} id The ID value for the object. * @returns {FbdObject|LdObject?} Returns the object matching the ID, or null if no match. */ findObject(id){ var ret = null; try{ forEachElem(this.Rungs, (r) => { if(isValid(ret)){ return; } ret = r.findObject(id); }); } catch(e){ console.error(e); } return ret; } /** * Converts the content to structured text. * @returns {string} Returns a string representing the structured text. */ toST(){ var st = ""; try{ if(this.Type ==="LD"){ this.Rungs.sort((r1, r2) => parseInt(r1.EvaluationOrder) - parseInt(r2.EvaluationOrder)); forEachElem(this.Rungs, r => { st += r.toST(); }); } else if(this.ST !== null){ st = this.ST.Content.trim(); } } catch(e){ console.error(e); } return st; } } /** * Represents a Structured Text (ST) element in the IEC project file. */ export class ST extends Serializable{ /** * Constructs a new ST object with content. * @param {string?} content The content of the code, or null or undefined. */ constructor(content) { super(); this.TypeMap = { "Content": "