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
JavaScript
/* 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": "