├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── manuals
└── L5X.pdf
├── package-lock.json
├── package.json
└── src
├── document
├── __snapshots__
│ └── document.spec.js.snap
├── __test-data__
│ └── datatype.L5X
├── document.spec.js
└── index.js
├── element
├── __snapshots__
│ └── element.spec.js.snap
├── element.spec.js
└── index.js
├── index.js
├── member
├── __snapshots__
│ └── member.spec.js.snap
├── index.js
└── member.spec.js
├── program
├── __snapshots__
│ └── program.spec.js.snap
├── index.js
└── program.spec.js
├── routine
├── __snapshots__
│ └── routine.spec.js.snap
├── index.js
└── routine.spec.js
├── rung
├── __snapshots__
│ └── rung.spec.js.snap
├── index.js
└── rung.spec.js
├── tag
├── __snapshots__
│ └── tag.spec.js.snap
├── index.js
└── tag.spec.js
└── utilities
├── index.js
└── utilities.spec.js
/.eslintignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules/**
3 | coverage/**
4 | scripts/**
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | "jest/globals": true,
4 | es6: true,
5 | node: true
6 | },
7 | extends: ["eslint:recommended"],
8 | parserOptions: {
9 | ecmaVersion: 8,
10 | sourceType: "module",
11 | ecmaFeatures: {
12 | jsx: true,
13 | experimentalObjectRestSpread: true
14 | }
15 | },
16 | rules: {
17 | indent: ["error", 4],
18 | "no-console": 0,
19 | quotes: ["error", "double"],
20 | semi: ["error", "always"],
21 | "jest/no-disabled-tests": "warn",
22 | "jest/no-focused-tests": "error",
23 | "jest/no-identical-title": "error",
24 | "jest/prefer-to-have-length": "warn",
25 | "jest/valid-expect": "error"
26 | },
27 | plugins: ["jest"]
28 | };
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
3 | /sample/
4 |
5 | /scripts/
6 |
7 | /assets/
8 |
9 | /coverage/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | **/*.spec.js
2 | /coverage
3 | /manuals
4 | /scripts
5 | /sample
6 | /assets
7 |
8 | **/__snapshots__
9 | **/__test-data__
10 | CONTRIBUTE.md
11 | CODE_OF_CONDUCT.md
12 | ISSUE_TEMPLATE.md
13 | PULL_REQUEST_TEMPLATE.md
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project Owner at cmseaton42@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
3 | This is the **CONTRIBUTE** section of our project. Great to have you here. Here are a few ways you can help make this project better!
4 |
5 | ## Team Members
6 |
7 | *New Team Members Welcome...*
8 |
9 | * **Canaan Seaton** - *Owner* - [GitHub Profile](https://github.com/cmseaton42) - [Personal Website](http://www.canaanseaton.com/)
10 |
11 | ## Support
12 |
13 | How can I help?
14 |
15 | - Simply use the module. The more people who use it, the better it will become.
16 | - Get Involved (see below for more details)
17 | - A freshly dropped star on the repo is always appreciated ;)
18 |
19 | ## Learn & Listen
20 |
21 | This section includes ways to get started with your open source project. Include links to documentation and to different communication channels:
22 |
23 | * How to Open Source - See this excellent [article](https://opensource.guide/how-to-contribute/)
24 | * To Learn about CIP and Ethernet/IP - See the [Manuals](https://github.com/cmseaton42/L5XJS/tree/master/manuals) folder
25 | * Ask - See the project [Gitter](https://gitter.im/L5XJS/Lobby) community
26 | * Blog Posts - Coming Soon....
27 |
28 | ## Get Involved
29 |
30 | So you say you wanna help? Here's how! 🎉 👍
31 |
32 | **Submitting Issues**
33 |
34 | Contributing to the Code is great but feedback on the project is just as important.
35 |
36 | 1. Research to make sure the issue isn't already being tracked
37 | 2. Submit your issue via the project [issue tracker](https://github.com/cmseaton42/L5XJS/issues)
38 | - Be Clear
39 | - Be Thorough (too much information >>> not enough information)
40 | - Include Pictures (Screenshots of Errors, gifs, etc)
41 | - Include *Node Version* -> `node --version`
42 | - Include *package version* -> `npm list`
43 |
44 | **Protip:** Use the project [Issue Template](https://github.com/cmseaton42/L5XJS/blob/master/ISSUE_TEMPLATE.md) as a starting point
45 |
46 | **Feature Requests**
47 |
48 | Have you ever been working with a project and thought *Man, I wish it just did this out of the box*?
49 |
50 | 1. Submit your feature request to the [issue tracker](https://github.com/cmseaton42/L5XJS/issues) **Be Sure to Mark your Issue as a Feature Request in the title (eg `[FEATURE REQUEST] Some Awesome Idea`)
51 | - Be Clear
52 | - Be Thorough (too much information >>> not enough information)
53 | - Don't submit it and *forget* it, be prepared to answer some follow up questions
54 |
55 | **Protip:** Use the project [Issue Template](https://github.com/cmseaton42/L5XJS/blob/master/ISSUE_TEMPLATE.md) as a starting point
56 |
57 | **Contributing Code**
58 |
59 | 1. Open an issue if one doesn't already partain to your contribution
60 | 2. Discuss the changes you want to make in the issue tracker to see if someone is already working on it or if there is a reason it shouldn't be added
61 | 1. Assumming the outcome of the above discussion good to go, fork the Repo
62 | 2. Clone your forked copy to your local machine
63 | 3. Add the host repo as upstream -> `git remote add upstream https://github.com/cmseaton42/L5XJS.git`
64 | 3. Get acclamated with the environment (ask questions if necessary)
65 | 5. Make a new branch for your changes
66 | 6. Start *hacking* away at some crispy new code
67 | 7. Make sure to add tests
68 | 8. Submit you're PR to the [Primary](https://github.com/cmseaton42/L5XJS) repo
69 | - **Protip:** Use the [Pull Request Template](https://github.com/cmseaton42/L5XJS/blob/master/PULL_REQUEST_TEMPLATE.md) as a starting point
70 | 9. Wait for feedback
71 | 10. Make changes if necessary and re-submit
72 | 11. Boom, PR accepted 👍 💯 🎉
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Current Behavior
4 |
5 |
6 |
7 | ## Expected Behavior
8 |
9 |
10 |
11 | ## Possible Solution (Optional)
12 |
13 |
14 |
15 | ## Context
16 |
17 |
18 |
19 | ## Steps to Reproduce (for bugs only)
20 |
21 |
22 | 1.
23 | 2.
24 | 3.
25 | 4.
26 |
27 | ## Your Environment
28 |
29 | * Package version (Use `npm list` - e.g. *1.0.6*):
30 | * Node Version (Use `node --version` - e.g. *9.8.0*):
31 | * Operating System and version:
32 | * Controller Type (eg 1756-L83E/B):
33 | * Controller Firmware (eg 30.11):
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Description, Motivation, and Context
4 |
5 |
6 |
7 | ## How Has This Been Tested?
8 |
9 |
10 |
11 |
12 | ## Screenshots (if appropriate):
13 |
14 | ## Types of changes
15 |
16 | - [ ] Bug fix (non-breaking change which fixes an issue)
17 | - [ ] New feature (non-breaking change which adds functionality)
18 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
19 |
20 | ## Checklist:
21 |
22 |
23 | - [ ] My code follows the code style of this project.
24 | - [ ] My change requires a change to the documentation.
25 | - [ ] I have updated the documentation accordingly.
26 | - [ ] I have read the **CONTRIBUTING** document.
27 | - [ ] I have added tests to cover my changes.
28 | - [ ] All new and existing tests passed.
29 | - [ ] This is a work in progress, and I want some feedback (If yes, please mark it in the title -> e.g. `[WIP] Some awesome PR title`)
30 |
31 | ## Related Issue
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 | # L5XJS
16 |
17 | A simple API designed to build and parse Allen Bradley *.L5X* files in order to empower users to automate PLC code generation.
18 |
19 | ## Prerequisites
20 |
21 | latest version of [NodeJS](https://nodejs.org/en/)
22 |
23 | ## Getting Started
24 |
25 | Install with npm
26 |
27 | ```
28 | npm install l5x-js --save
29 | ```
30 | ## The API
31 |
32 | How the heck does this thing work anyway? Great question!
33 |
34 |
35 |
36 | ## Built With
37 |
38 | * [NodeJS](https://nodejs.org/en/) - The Engine
39 | * [javascript - ES2017](https://maven.apache.org/) - The Language
40 |
41 | ## Contributers
42 |
43 | * **Canaan Seaton** - *Owner* - [GitHub Profile](https://github.com/cmseaton42) - [Personal Website](http://www.canaanseaton.com/)
44 |
45 | Wanna *become* a contributor? [Here's](https://github.com/cmseaton42/L5XJS/blob/master/CONTRIBUTING.md) how!
46 |
47 | ## License
48 |
49 | This project is licensed under the MIT License - see the [LICENCE](https://github.com/cmseaton42/L5XJS/blob/master/LICENSE) file for details
50 |
--------------------------------------------------------------------------------
/manuals/L5X.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmseaton42/L5XJS/7a81754fc7dfa6a090dc1703ef0d2f2e6d7b9456/manuals/L5X.pdf
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "l5x-js",
3 | "version": "1.0.0-beta.0",
4 | "description": "A tool to help with parsing and generating Allen Bradley L5X files.",
5 | "main": "./src/index.js",
6 | "scripts": {
7 | "test": "npm run lint && jest && npm run test:coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
8 | "test:local": "npm run lint && jest && npm run test:coverage",
9 | "test:watch": "jest --watch",
10 | "test:coverage": "jest --coverage",
11 | "test:detailed": "jest --verbose",
12 | "test:update": "jest -u",
13 | "lint": "./node_modules/.bin/eslint . --ext .js",
14 | "lint:fix": "npm run lint -- --fix"
15 | },
16 | "keywords": [
17 | "Allen",
18 | "Bradley",
19 | "Node",
20 | "javascript",
21 | "Allen-Bradley",
22 | "L5X",
23 | "L5K",
24 | "Generator",
25 | "Parser"
26 | ],
27 | "author": "Canaan Seaton",
28 | "license": "ISC",
29 | "dependencies": {
30 | "clone": "^1.0.4",
31 | "xml-js": "^1.6.2"
32 | },
33 | "devDependencies": {
34 | "coveralls": "^3.0.0",
35 | "eslint": "^4.19.1",
36 | "eslint-plugin-jest": "^21.15.0",
37 | "jest": "^22.4.3"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/document/__test-data__/datatype.L5X:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/document/document.spec.js:
--------------------------------------------------------------------------------
1 | const Document = require("./index");
2 | const Tag = require("../tag");
3 | const Program = require("../program");
4 | const Routine = require("../routine");
5 | const fs = require("fs");
6 | const path = require("path");
7 |
8 | describe("Document Class", () => {
9 | describe("New Instance", () => {
10 | it("Accepts Proper Input", () => {
11 | const fn = arg => () => new Document(arg);
12 |
13 | expect(fn("./__test-data__/datatype.L5X")).toThrow();
14 | expect(fn(57)).toThrow();
15 | expect(fn(path.resolve(__dirname, "./__test-data__/datatype.L5X"))).not.toThrow();
16 | expect(fn()).not.toThrow();
17 | });
18 |
19 | it("Initializes with Desired Document Model", () => {
20 | const doc1 = new Document();
21 | const doc2 = new Document(path.resolve(__dirname, "./__test-data__/datatype.L5X"));
22 |
23 | expect(doc1._dom).toMatchSnapshot();
24 | expect(doc2._dom).toMatchSnapshot();
25 | });
26 | });
27 |
28 | describe("Static Methods", () => {
29 | test("isDocument: Returns Appropriate Judgement", () => {
30 | const doc = new Document();
31 | const fake = {};
32 |
33 | expect(Document.isDocument(doc)).toBeTruthy();
34 | expect(Document.isDocument(fake)).toBeFalsy();
35 | });
36 | });
37 |
38 | describe("Methods", () => {
39 | let docFull;
40 | let docEmpty;
41 |
42 | beforeEach(() => {
43 | docFull = new Document(path.resolve(__dirname, "./__test-data__/datatype.L5X"));
44 | docEmpty = new Document();
45 | });
46 |
47 | test("addTag: Throws on Invalid Input", () => {
48 | const fn = tag => () => docFull.addTag(tag);
49 |
50 | expect(fn(new Program("notATag"))).toThrow();
51 | expect(fn("notATag")).toThrow();
52 | });
53 |
54 | test("addTag: Adds Tag to Document Instance", () => {
55 | docFull.addTag(new Tag("tag1", "BOOL"));
56 | docEmpty.addTag(new Tag("tag1", "BOOL"));
57 | expect(docFull).toMatchSnapshot();
58 | expect(docEmpty).toMatchSnapshot();
59 |
60 | docFull.addTag(new Tag("tag2", "DINT"));
61 | docEmpty.addTag(new Tag("tag2", "DINT"));
62 | expect(docFull).toMatchSnapshot();
63 | expect(docEmpty).toMatchSnapshot();
64 |
65 | docEmpty.addTag(new Tag("tag3", "SINT"));
66 | expect(docEmpty).toMatchSnapshot();
67 |
68 | const tag4 = new Tag("tag4", "REAL");
69 | docEmpty.addTag(tag4);
70 | expect(docEmpty).toMatchSnapshot();
71 |
72 | tag4.dom.attributes.Name = "not_tag4Anymore";
73 | expect(docEmpty).toMatchSnapshot();
74 | });
75 |
76 | test("addProgam: Throws on Invalid Input", () => {
77 | const fn = prog => () => docFull.addProgram(prog);
78 |
79 | expect(fn(new Program("notATag"))).not.toThrow();
80 | expect(fn(new Tag("ATag", "DINT"))).toThrow();
81 | expect(fn("notATag")).toThrow();
82 | });
83 |
84 | test("addProgram: Adds Program to Document Instance", () => {
85 | docFull.addProgram(new Program("aProgram"));
86 | docFull.addProgram(new Program("bProgram"));
87 | docFull.addProgram(new Program("cProgram"));
88 |
89 | docEmpty.addProgram(new Program("aProgram"));
90 | docEmpty.addProgram(new Program("bProgram"));
91 | docEmpty.addProgram(new Program("cProgram"));
92 |
93 | expect(docFull).toMatchSnapshot();
94 | expect(docEmpty).toMatchSnapshot();
95 |
96 | const prog = new Program("dProgram");
97 | docEmpty.addProgram(prog);
98 | expect(docEmpty).toMatchSnapshot();
99 |
100 | prog.addRoutine(new Routine("aRoutine"));
101 | expect(docEmpty).toMatchSnapshot();
102 | });
103 |
104 | test("findTag: Throws on Invalid Input", () => {
105 | const fn = name => () => docEmpty.findTag(name);
106 |
107 | expect(fn(new Program("notATag"))).toThrow();
108 | expect(fn(12)).toThrow();
109 | expect(fn("aTag")).not.toThrow();
110 | });
111 |
112 | test("findTag: Finds Target Tag", () => {
113 | docEmpty.addTag(new Tag("aTag", "DINT"));
114 | docEmpty.addTag(new Tag("bTag", "SINT"));
115 |
116 | const tag = new Tag("cTag", "BOOL");
117 | docEmpty.addTag(tag);
118 |
119 | expect(docEmpty.findTag("aTag")).not.toBeNull();
120 | expect(docEmpty.findTag("aTag")).toMatchSnapshot();
121 | expect(docEmpty.findTag("bTag")).not.toBeNull();
122 | expect(docEmpty.findTag("bTag")).toMatchSnapshot();
123 | expect(docEmpty.findTag("cTag")).not.toBeNull();
124 | expect(docEmpty.findTag("cTag")).toMatchSnapshot();
125 | expect(docEmpty.findTag("dTag")).toBeNull();
126 |
127 | docEmpty.findTag("cTag").dom.attributes.Name = "eTag";
128 | expect(docEmpty).toMatchSnapshot();
129 | expect(tag).toMatchSnapshot();
130 | });
131 |
132 | test("findProgram: Throws on Invalid Input", () => {
133 | const fn = name => () => docEmpty.findProgram(name);
134 |
135 | expect(fn(new Program("notATag"))).toThrow();
136 | expect(fn(12)).toThrow();
137 | expect(fn("aProgram")).not.toThrow();
138 | });
139 |
140 | test("findProgram: Finds Target Program", () => {
141 | docEmpty.addProgram(new Program("aProgram"));
142 | docEmpty.addProgram(new Program("bProgram"));
143 |
144 | const prog = new Program("cProgram");
145 | docEmpty.addProgram(prog);
146 |
147 | expect(docEmpty.findProgram("aProgram")).not.toBeNull();
148 | expect(docEmpty.findProgram("aProgram")).toMatchSnapshot();
149 | expect(docEmpty.findProgram("bProgram")).not.toBeNull();
150 | expect(docEmpty.findProgram("bProgram")).toMatchSnapshot();
151 | expect(docEmpty.findProgram("cProgram")).not.toBeNull();
152 | expect(docEmpty.findProgram("cProgram")).toMatchSnapshot();
153 | expect(docEmpty.findProgram("dProgram")).toBeNull();
154 |
155 | docEmpty.findProgram("cProgram").dom.attributes.Name = "eProgram";
156 | expect(docEmpty).toMatchSnapshot();
157 | expect(prog).toMatchSnapshot();
158 | });
159 |
160 | test("setTarget: Throws on Invalid Inputs", () => {
161 | const fn = target => () => docEmpty.setTarget(target);
162 |
163 | expect(fn()).toThrow();
164 | expect(fn(12)).toThrow();
165 | expect(fn(new Program("aProgram"))).not.toThrow();
166 | expect(fn(new Tag("aTag", "DINT"))).toThrow();
167 | });
168 |
169 | test("setTarget: Sets Target Successfully", () => {
170 | const progA = new Program("aProgram");
171 | const progB = new Program("bProgram");
172 | const routA = new Routine("aRoutine");
173 | const routB = new Routine("bRoutine");
174 |
175 | docEmpty.addProgram(progA);
176 | progA.addRoutine(routA);
177 |
178 | docEmpty.addProgram(progB);
179 | progB.addRoutine(routB);
180 |
181 | docEmpty.setTarget(progA);
182 | expect(docEmpty).toMatchSnapshot();
183 |
184 | docEmpty.setTarget(progB);
185 | expect(docEmpty).toMatchSnapshot();
186 |
187 | docEmpty.setTarget(routA);
188 | expect(docEmpty).toMatchSnapshot();
189 |
190 | docEmpty.setTarget(routB);
191 | expect(docEmpty).toMatchSnapshot();
192 | });
193 |
194 | test("export: Exported Data is Correct", () => {
195 | docFull.export(path.resolve(__dirname, "./__test-data__/generated_datatype.L5X"));
196 |
197 | let original = fs.readFileSync(
198 | path.resolve(__dirname, "./__test-data__/datatype.L5X"),
199 | "utf8"
200 | );
201 |
202 | let generated = fs.readFileSync(
203 | path.resolve(__dirname, "./__test-data__/generated_datatype.L5X"),
204 | "utf8"
205 | );
206 |
207 | original = original.replace(/\s/g, "");
208 | generated = generated.replace(/\s/g, "");
209 |
210 | expect(original === generated).toBeTruthy();
211 |
212 | fs.unlinkSync(path.resolve(__dirname, "./__test-data__/generated_datatype.L5X"));
213 | });
214 |
215 | test("_orderify: Results in the Correct Order", () => {
216 | docEmpty
217 | .findOne("Controller")
218 | .dom.elements.push({ type: "element", name: "Tags", elements: [] });
219 |
220 | docEmpty.findOne("Controller").dom.elements.push({
221 | type: "element",
222 | name: "Trends",
223 | elements: []
224 | });
225 |
226 | docEmpty.findOne("Controller").dom.elements.push({
227 | type: "element",
228 | name: "DataTypes",
229 | elements: []
230 | });
231 |
232 | docEmpty.findOne("Controller").dom.elements.push({
233 | type: "element",
234 | name: "Description",
235 | elements: []
236 | });
237 |
238 | docEmpty._orderify();
239 | expect(docEmpty).toMatchSnapshot();
240 | });
241 | });
242 | });
243 |
--------------------------------------------------------------------------------
/src/document/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const Element = require("../element");
3 | const Tag = require("../tag");
4 | const Program = require("../program");
5 | const { resolve } = require("path");
6 | const { hash } = require("../utilities");
7 | const { xml2js } = require("xml-js");
8 |
9 | const DOCUMENT_ID = hash("DOCUMENT_ID_STRING");
10 |
11 | class Document extends Element {
12 | constructor(filepath = null) {
13 | if (filepath && typeof filepath !== "string")
14 | throw new Error(`Document expected to receive filepath of type instead got <${typeof filepath}>`);
15 |
16 | // Call parent constructor
17 | if (filepath) {
18 | // Start from existing L5X file
19 | super(xml2js(fs.readFileSync(resolve(filepath)), "utf8"));
20 | } else {
21 | // No filepath or tree given, initialize new document
22 | super({
23 | declaration: {
24 | attributes: { version: "1.0", encoding: "UTF-8", standalone: "yes" }
25 | },
26 | elements: [
27 | {
28 | type: "element",
29 | name: "RSLogix5000Content",
30 | attributes: { SchemaRevision: "1.0", ContainsContext: true },
31 | elements: [
32 | {
33 | type: "element",
34 | name: "Controller",
35 | attributes: { Use: "Context" },
36 | elements: []
37 | }
38 | ]
39 | }
40 | ]
41 | });
42 | }
43 |
44 | this.class_id = DOCUMENT_ID;
45 | }
46 |
47 | /**
48 | * Adds tag to controller instance
49 | *
50 | * @param {Tag} tag
51 | * @memberof Program
52 | */
53 | addTag(tag) {
54 | if (!Tag.isTag(tag)) throw new Error(`addTag expected tag of type instead got <${typeof tag}>`);
55 |
56 | const controller = this.findOne("Controller");
57 | const tags = controller.findOne("Tags", null, ["Programs"]);
58 |
59 | /* eslint-disable indent */
60 | if (tags) tags.append(tag);
61 | else
62 | controller.append(
63 | new Element({
64 | type: "element",
65 | name: "Tags",
66 | elements: []
67 | }).append(tag)
68 | );
69 | /* eslint-enable indent */
70 |
71 | this._orderify();
72 | return this;
73 | }
74 |
75 | /**
76 | * Adds program to controller instance
77 | *
78 | * @param {Program} prog
79 | * @memberof Program
80 | */
81 | addProgram(prog) {
82 | if (!Program.isProgram(prog))
83 | throw new Error(`addProgram expected prog of type instead got <${typeof prog}>`);
84 |
85 | const controller = this.findOne("Controller");
86 | const programs = controller.findOne("Programs");
87 |
88 | /* eslint-disable indent */
89 | if (programs) programs.append(prog);
90 | else
91 | controller.append(
92 | new Element({
93 | type: "element",
94 | name: "Programs",
95 | elements: []
96 | }).append(prog)
97 | );
98 | /* eslint-enable indent */
99 |
100 | this._orderify();
101 | return this;
102 | }
103 |
104 | /**
105 | * Finds tag in the controller instance that matches the name
106 | *
107 | * @param {string} name
108 | * @returns {Tag|null}
109 | * @memberof Program
110 | */
111 | findTag(name) {
112 | if (typeof name !== "string")
113 | throw new Error(`findTag expected name of type instead got <${typeof name}>`);
114 |
115 | const found = this.findOne("Tag", { Name: name }, ["Programs"]);
116 | if (!found) return null;
117 |
118 | const tag = new Tag(name, found.dom.attributes.DataType);
119 | tag._dom = found.dom;
120 |
121 | return tag;
122 | }
123 |
124 | /**
125 | * Finds program in the controller instance that matches the name
126 | *
127 | * @param {string} name
128 | * @returns {Program|null}
129 | * @memberof Program
130 | */
131 | findProgram(name) {
132 | if (typeof name !== "string")
133 | throw new Error(`findProgram expected name of type instead got <${typeof name}>`);
134 |
135 | const found = this.findOne("Program", { Name: name });
136 | if (!found) return null;
137 |
138 | const prog = new Program(name);
139 | prog._dom = found.dom;
140 |
141 | return prog;
142 | }
143 |
144 | /**
145 | * Sets passed element as document target element
146 | * --> Only if a valid element subtype
147 | *
148 | * @param {Element} target
149 | * @memberof Document
150 | */
151 | setTarget(target) {
152 | if (!Element.isElement(target))
153 | throw new Error(`setTarget expected to receive target of type instead got <${typeof target}>`);
154 |
155 | const prog = this.findOne("Program", { Use: "Target" });
156 | if (prog) prog.dom.attributes.Use = "Context";
157 |
158 | const rout = this.findOne("Routine", { Use: "Target" });
159 | if (rout) rout.dom.attributes.Use = "Context";
160 |
161 | this.dom.elements[0].attributes.TargetName = target.dom.attributes.Name;
162 | target.dom.attributes.Use = "Target";
163 |
164 | let routines = null;
165 |
166 | /* eslint-disable indent */
167 | switch (target.dom.name) {
168 | case "Program":
169 | this.dom.elements[0].attributes.TargetType = "Program";
170 | routines = target.find("Routine");
171 |
172 | if (routines) {
173 | for (const routine of routines) delete routine.dom.attributes.Use;
174 | }
175 |
176 | break;
177 | case "Routine":
178 | this.dom.elements[0].attributes.TargetType = "Routine";
179 | break;
180 | default:
181 | throw new Error(
182 | `setTarget could not set passed element as Target - Unrecognized Element Type <${target.name}>`
183 | );
184 | }
185 | /* eslint-enable indent */
186 | }
187 |
188 | /**
189 | * Puts the programs children in the correct order
190 | *
191 | * @private
192 | * @memberof Program
193 | */
194 | _orderify() {
195 | const order = {
196 | Description: 1,
197 | RedundancyInfo: 2,
198 | Security: 3,
199 | SafetyInfo: 4,
200 | DataTypes: 5,
201 | Modules: 6,
202 | AddOnInstructionDefinitions: 7,
203 | Tags: 8,
204 | Programs: 9,
205 | Tasks: 10,
206 | ParameterConnections: 11,
207 | CommPorts: 12,
208 | CST: 13,
209 | WallClockTime: 14,
210 | Trends: 15,
211 | QuickWatcherLists: 16,
212 | InternetProtocol: 17,
213 | EthernetPorts: 18,
214 | EthernetNetwork: 19
215 | };
216 |
217 | this.findOne("Controller").dom.elements.sort((a, b) => {
218 | return order[a.name] - order[b.name];
219 | });
220 | }
221 |
222 | /**
223 | * Exports document to provided filepath
224 | *
225 | * @param {string} filepath
226 | * @memberof Document
227 | */
228 | export(filepath) {
229 | fs.writeFileSync(resolve(filepath), this.toString(), "utf8");
230 | }
231 |
232 | /**
233 | * Check if passed object is a document
234 | *
235 | * @static
236 | * @param {object} doc - object to test
237 | * @returns {boolean}
238 | * @memberof Document
239 | */
240 | static isDocument(doc) {
241 | return doc.class_id && doc.class_id === DOCUMENT_ID;
242 | }
243 | }
244 |
245 | module.exports = Document;
246 |
--------------------------------------------------------------------------------
/src/element/__snapshots__/element.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Element Class Instance Methods append: Appends New Element 1`] = `
4 | Element {
5 | "_dom": Object {
6 | "elements": Array [
7 | Object {
8 | "elements": Array [
9 | Object {
10 | "elements": Array [
11 | Object {
12 | "attributes": Object {
13 | "Name": "TagName",
14 | },
15 | "elements": Array [],
16 | "name": "Tag",
17 | "type": "element",
18 | },
19 | ],
20 | "name": "Tags",
21 | "type": "element",
22 | },
23 | Object {
24 | "elements": Array [
25 | Object {
26 | "elements": Array [
27 | Object {
28 | "elements": Array [
29 | Object {
30 | "attributes": Object {
31 | "Name": "AnotherTagName",
32 | },
33 | "elements": Array [],
34 | "name": "Tag",
35 | "type": "element",
36 | },
37 | ],
38 | "name": "Tags",
39 | "type": "element",
40 | },
41 | ],
42 | "name": "Program",
43 | "type": "element",
44 | },
45 | ],
46 | "name": "Programs",
47 | "type": "element",
48 | },
49 | Object {
50 | "name": "Tags",
51 | "type": "element",
52 | },
53 | ],
54 | "name": "Controller",
55 | "type": "element",
56 | },
57 | ],
58 | "name": "root",
59 | "type": "element",
60 | },
61 | "elem_id": "33cb4159377b63ce9db11667160e39df",
62 | }
63 | `;
64 |
65 | exports[`Element Class Instance Methods append: Appends New Element 2`] = `
66 | Element {
67 | "_dom": Object {
68 | "elements": Array [
69 | Object {
70 | "elements": Array [
71 | Object {
72 | "elements": Array [
73 | Object {
74 | "attributes": Object {
75 | "Name": "TagName",
76 | },
77 | "elements": Array [],
78 | "name": "Tag",
79 | "type": "element",
80 | },
81 | ],
82 | "name": "Tags",
83 | "type": "element",
84 | },
85 | Object {
86 | "elements": Array [
87 | Object {
88 | "elements": Array [
89 | Object {
90 | "elements": Array [
91 | Object {
92 | "attributes": Object {
93 | "Name": "AnotherTagName",
94 | },
95 | "elements": Array [],
96 | "name": "Tag",
97 | "type": "element",
98 | },
99 | ],
100 | "name": "Tags",
101 | "type": "element",
102 | },
103 | ],
104 | "name": "Program",
105 | "type": "element",
106 | },
107 | ],
108 | "name": "Programs",
109 | "type": "element",
110 | },
111 | Object {
112 | "elements": Array [
113 | Object {
114 | "attributes": Object {
115 | "Name": "SomeTag",
116 | "TagType": "Base",
117 | },
118 | "name": "Tag",
119 | "type": "element",
120 | },
121 | ],
122 | "name": "Tags",
123 | "type": "element",
124 | },
125 | ],
126 | "name": "Controller",
127 | "type": "element",
128 | },
129 | ],
130 | "name": "root",
131 | "type": "element",
132 | },
133 | "elem_id": "33cb4159377b63ce9db11667160e39df",
134 | }
135 | `;
136 |
137 | exports[`Element Class Instance Methods find: Finds Document Elements and Returns New Document 1`] = `
138 | Array [
139 | Element {
140 | "_dom": Object {
141 | "elements": Array [
142 | Object {
143 | "elements": Array [
144 | Object {
145 | "attributes": Object {
146 | "Name": "TagName",
147 | },
148 | "elements": Array [],
149 | "name": "Tag",
150 | "type": "element",
151 | },
152 | ],
153 | "name": "Tags",
154 | "type": "element",
155 | },
156 | Object {
157 | "elements": Array [
158 | Object {
159 | "elements": Array [
160 | Object {
161 | "elements": Array [
162 | Object {
163 | "attributes": Object {
164 | "Name": "AnotherTagName",
165 | },
166 | "elements": Array [],
167 | "name": "Tag",
168 | "type": "element",
169 | },
170 | ],
171 | "name": "Tags",
172 | "type": "element",
173 | },
174 | ],
175 | "name": "Program",
176 | "type": "element",
177 | },
178 | ],
179 | "name": "Programs",
180 | "type": "element",
181 | },
182 | ],
183 | "name": "Controller",
184 | "type": "element",
185 | },
186 | "elem_id": "33cb4159377b63ce9db11667160e39df",
187 | },
188 | ]
189 | `;
190 |
191 | exports[`Element Class Instance Methods find: Finds Document Elements and Returns New Document 2`] = `
192 | Array [
193 | Element {
194 | "_dom": Object {
195 | "attributes": Object {
196 | "Name": "TagName",
197 | },
198 | "elements": Array [],
199 | "name": "Tag",
200 | "type": "element",
201 | },
202 | "elem_id": "33cb4159377b63ce9db11667160e39df",
203 | },
204 | ]
205 | `;
206 |
207 | exports[`Element Class Instance Methods find: Finds Document Elements and Returns New Document 3`] = `
208 | Array [
209 | Element {
210 | "_dom": Object {
211 | "attributes": Object {
212 | "Name": "TagName",
213 | },
214 | "elements": Array [],
215 | "name": "Tag",
216 | "type": "element",
217 | },
218 | "elem_id": "33cb4159377b63ce9db11667160e39df",
219 | },
220 | Element {
221 | "_dom": Object {
222 | "attributes": Object {
223 | "Name": "AnotherTagName",
224 | },
225 | "elements": Array [],
226 | "name": "Tag",
227 | "type": "element",
228 | },
229 | "elem_id": "33cb4159377b63ce9db11667160e39df",
230 | },
231 | ]
232 | `;
233 |
234 | exports[`Element Class Instance Methods find: Finds Document Elements and Returns New Document 4`] = `
235 | Array [
236 | Element {
237 | "_dom": Object {
238 | "attributes": Object {
239 | "Name": "TagName",
240 | },
241 | "elements": Array [],
242 | "name": "Tag",
243 | "type": "element",
244 | },
245 | "elem_id": "33cb4159377b63ce9db11667160e39df",
246 | },
247 | ]
248 | `;
249 |
250 | exports[`Element Class Instance Methods find: Finds Document Elements and Returns New Document 5`] = `
251 | Array [
252 | Element {
253 | "_dom": Object {
254 | "attributes": Object {
255 | "Name": "AnotherTagName",
256 | },
257 | "elements": Array [],
258 | "name": "Tag",
259 | "type": "element",
260 | },
261 | "elem_id": "33cb4159377b63ce9db11667160e39df",
262 | },
263 | ]
264 | `;
265 |
266 | exports[`Element Class Instance Methods findOne: Finds First Document Element 1`] = `
267 | Element {
268 | "_dom": Object {
269 | "elements": Array [
270 | Object {
271 | "elements": Array [
272 | Object {
273 | "attributes": Object {
274 | "Name": "TagName",
275 | },
276 | "elements": Array [],
277 | "name": "Tag",
278 | "type": "element",
279 | },
280 | ],
281 | "name": "Tags",
282 | "type": "element",
283 | },
284 | Object {
285 | "elements": Array [
286 | Object {
287 | "elements": Array [
288 | Object {
289 | "elements": Array [
290 | Object {
291 | "attributes": Object {
292 | "Name": "AnotherTagName",
293 | },
294 | "elements": Array [],
295 | "name": "Tag",
296 | "type": "element",
297 | },
298 | ],
299 | "name": "Tags",
300 | "type": "element",
301 | },
302 | ],
303 | "name": "Program",
304 | "type": "element",
305 | },
306 | ],
307 | "name": "Programs",
308 | "type": "element",
309 | },
310 | ],
311 | "name": "Controller",
312 | "type": "element",
313 | },
314 | "elem_id": "33cb4159377b63ce9db11667160e39df",
315 | }
316 | `;
317 |
318 | exports[`Element Class Instance Methods findOne: Finds First Document Element 2`] = `
319 | Element {
320 | "_dom": Object {
321 | "attributes": Object {
322 | "Name": "AnotherTagName",
323 | },
324 | "elements": Array [],
325 | "name": "Tag",
326 | "type": "element",
327 | },
328 | "elem_id": "33cb4159377b63ce9db11667160e39df",
329 | }
330 | `;
331 |
332 | exports[`Element Class Instance Methods findOne: Finds First Document Element 3`] = `
333 | Element {
334 | "_dom": Object {
335 | "attributes": Object {
336 | "Name": "TagName",
337 | },
338 | "elements": Array [],
339 | "name": "Tag",
340 | "type": "element",
341 | },
342 | "elem_id": "33cb4159377b63ce9db11667160e39df",
343 | }
344 | `;
345 |
346 | exports[`Element Class Instance Methods findOne: Finds First Document Element 4`] = `
347 | Element {
348 | "_dom": Object {
349 | "attributes": Object {
350 | "Name": "TagName",
351 | },
352 | "elements": Array [],
353 | "name": "Tag",
354 | "type": "element",
355 | },
356 | "elem_id": "33cb4159377b63ce9db11667160e39df",
357 | }
358 | `;
359 |
360 | exports[`Element Class Instance Methods get dom: Gets Dom Reference 1`] = `
361 | Object {
362 | "elements": Array [
363 | Object {
364 | "elements": Array [
365 | Object {
366 | "elements": Array [
367 | Object {
368 | "attributes": Object {
369 | "Name": "TagName",
370 | },
371 | "elements": Array [],
372 | "name": "Tag",
373 | "type": "element",
374 | },
375 | ],
376 | "name": "Tags",
377 | "type": "element",
378 | },
379 | Object {
380 | "elements": Array [
381 | Object {
382 | "elements": Array [
383 | Object {
384 | "elements": Array [
385 | Object {
386 | "attributes": Object {
387 | "Name": "AnotherTagName",
388 | },
389 | "elements": Array [],
390 | "name": "Tag",
391 | "type": "element",
392 | },
393 | ],
394 | "name": "Tags",
395 | "type": "element",
396 | },
397 | ],
398 | "name": "Program",
399 | "type": "element",
400 | },
401 | ],
402 | "name": "Programs",
403 | "type": "element",
404 | },
405 | ],
406 | "name": "Controller",
407 | "type": "element",
408 | },
409 | ],
410 | "name": "root",
411 | "type": "element",
412 | }
413 | `;
414 |
415 | exports[`Element Class Instance Methods toString: Generates Appropriate Output 1`] = `" "`;
416 |
417 | exports[`Element Class New Instance Initializes with Desired Document Model 1`] = `
418 | Element {
419 | "_dom": Object {
420 | "attr": "value",
421 | },
422 | "elem_id": "33cb4159377b63ce9db11667160e39df",
423 | }
424 | `;
425 |
--------------------------------------------------------------------------------
/src/element/element.spec.js:
--------------------------------------------------------------------------------
1 | const Element = require("./index");
2 |
3 | describe("Element Class", () => {
4 | describe("New Instance", () => {
5 | it("Accepts Proper Input", () => {
6 | const fn = arg => () => new Element(arg);
7 |
8 | expect(fn("./__test-data__/datatype.L5X")).toThrow();
9 | expect(fn(57)).toThrow();
10 | expect(
11 | fn({
12 | type: "element",
13 | name: "Element",
14 | elements: []
15 | })
16 | ).not.toThrow();
17 | });
18 |
19 | it("Initializes with Desired Document Model", () => {
20 | const doc = new Element({
21 | attr: "value"
22 | });
23 |
24 | expect(doc).toMatchSnapshot();
25 | });
26 | });
27 |
28 | describe("Static Methods", () => {
29 | test("isDocument: Returns Appropriate Judgement", () => {
30 | const doc = new Element({
31 | type: "element",
32 | name: "elem",
33 | elements: []
34 | });
35 |
36 | const fake = {};
37 |
38 | expect(doc).toBeTruthy();
39 | expect(Element.isElement(fake)).toBeFalsy();
40 | });
41 | });
42 |
43 | describe("Instance Methods", () => {
44 | let doc;
45 |
46 | beforeEach(() => {
47 | doc = new Element({
48 | type: "element",
49 | name: "root",
50 | elements: [
51 | {
52 | type: "element",
53 | name: "Controller",
54 | elements: [
55 | {
56 | type: "element",
57 | name: "Tags",
58 | elements: [
59 | {
60 | type: "element",
61 | name: "Tag",
62 | attributes: {
63 | Name: "TagName"
64 | },
65 | elements: []
66 | }
67 | ]
68 | },
69 | {
70 | type: "element",
71 | name: "Programs",
72 | elements: [
73 | {
74 | type: "element",
75 | name: "Program",
76 | elements: [
77 | {
78 | type: "element",
79 | name: "Tags",
80 | elements: [
81 | {
82 | type: "element",
83 | name: "Tag",
84 | attributes: {
85 | Name: "AnotherTagName"
86 | },
87 | elements: []
88 | }
89 | ]
90 | }
91 | ]
92 | }
93 | ]
94 | }
95 | ]
96 | }
97 | ]
98 | });
99 | });
100 |
101 | test("get dom: Gets Dom Reference", () => {
102 | expect(doc.dom).toMatchSnapshot();
103 | });
104 |
105 | test("findOne: Finds First Document Element", () => {
106 | expect(doc.findOne("nothing")).toBeNull();
107 | expect(doc.findOne("Controller")).not.toBeNull();
108 | expect(doc.findOne("Controller")).toMatchSnapshot();
109 | expect(doc.findOne("Member", { Name: "Push" })).toBeNull();
110 | expect(doc.findOne("Tag", { Name: "AnotherTagName" })).toMatchSnapshot();
111 | expect(doc.findOne("Tag", { Name: "AnotherTagName" }, ["Tags"])).toBeNull();
112 | expect(doc.findOne("Tag")).toMatchSnapshot();
113 | expect(doc.findOne("Controller").findOne("Tag")).toMatchSnapshot();
114 | expect(doc.findOne("Controller").findOne("Member", { name: "Push" })).toBeNull();
115 | });
116 |
117 | test("findOne: Rejects Invalid Inputs", () => {
118 | const fn = (type, attr, ignore = [], tree = null) => () =>
119 | doc.findOne(type, attr, ignore, tree);
120 |
121 | expect(fn(12, null, [], null)).toThrow();
122 | expect(fn("Controller", null, [], null)).not.toThrow();
123 | expect(fn("Controller", 12, null)).toThrow();
124 | expect(fn("Tag", { Name: "TagName" }, [], null)).not.toThrow();
125 | expect(fn("Tag", { Name: "TagName" }, [], "string")).toThrow();
126 | expect(fn("Tag", { Name: "TagName" }, 12)).toThrow();
127 | expect(fn("Tag", { Name: "TagName" }, [], {})).not.toThrow();
128 | expect(fn("Tag", { Name: "TagName" }, "throw")).toThrow();
129 | });
130 |
131 | test("find: Finds Document Elements and Returns New Document", () => {
132 | expect(doc.find("nothing")).toBeNull();
133 |
134 | expect(doc.find("Controller")).not.toBeNull();
135 | expect(doc.find("Controller")).toMatchSnapshot();
136 |
137 | expect(doc.find("Tag", { Name: "TagName" })).not.toBeNull();
138 | expect(doc.find("Member")).toBeNull();
139 |
140 | expect(doc.find("Tag", { Name: "TagName" })).toMatchSnapshot();
141 | expect(doc.find("Tag")).toMatchSnapshot();
142 |
143 | expect(doc.find("Tags")[0].find("Tag")).toMatchSnapshot();
144 | expect(doc.find("Tags")[1].find("Tag")).toMatchSnapshot();
145 |
146 | expect(doc.find("Tag", {}, ["Tags"])).toBeNull();
147 | });
148 |
149 | test("find: Rejects Invalid Inputs", () => {
150 | const fn = (type, attr, ignore = [], tree) => () => doc.find(type, attr, ignore, tree);
151 |
152 | expect(fn(12, null, null)).toThrow();
153 | expect(fn("Controller", null)).not.toThrow();
154 | expect(fn("Controller", 12, null)).toThrow();
155 | expect(fn("Tag", { Name: "TagName" })).not.toThrow();
156 | expect(fn("Tag", { Name: "TagName" }, 12)).toThrow();
157 | expect(fn("Tag", { Name: "TagName" }, [], {})).not.toThrow();
158 | expect(fn("Tag", { Name: "TagName" }, {}, "Program")).toThrow();
159 | expect(fn("Member", { Name: "another" }, ["DataTypes"], {})).not.toThrow();
160 | });
161 |
162 | test("append: Throws on Invalid Inputs", () => {
163 | const fn = arg => () => doc.append(arg);
164 |
165 | expect(fn({ key: "value" })).toThrow();
166 | expect(fn(doc)).not.toThrow();
167 | });
168 |
169 | test("append: Appends New Element", () => {
170 | const tags = new Element({
171 | type: "element",
172 | name: "Tags"
173 | });
174 |
175 | doc.findOne("Controller").append(tags);
176 | expect(doc).toMatchSnapshot();
177 |
178 | tags.append(
179 | new Element({
180 | type: "element",
181 | name: "Tag",
182 | attributes: {
183 | Name: "SomeTag",
184 | TagType: "Base"
185 | }
186 | })
187 | );
188 | expect(doc).toMatchSnapshot();
189 | });
190 |
191 | test("toString: Generates Appropriate Output", () => {
192 | expect(doc.toString()).toMatchSnapshot();
193 | });
194 | });
195 | });
196 |
--------------------------------------------------------------------------------
/src/element/index.js:
--------------------------------------------------------------------------------
1 | const { hash } = require("../utilities");
2 | const { js2xml } = require("xml-js");
3 |
4 | const ELEMENT_ID = hash("ELEMENT_ID_STRING");
5 |
6 | class Element {
7 | constructor(document_tree) {
8 | this.elem_id = ELEMENT_ID;
9 |
10 | if (typeof document_tree !== "object")
11 | throw new Error(
12 | `Element must be initialized with a document_tree of type instead got ${typeof document_tree}`
13 | );
14 |
15 | // Initialize Base L5X Dom Object
16 | // --> This Object is in the form that the
17 | // --> package 'xml-js' will understand
18 | // No filepath or tree given, initialize new document
19 | this._dom = document_tree;
20 | }
21 |
22 | /**
23 | * Returns a mutable dom reference to be manipulated by user
24 | *
25 | * @readonly
26 | * @memberof Document
27 | */
28 | get dom() {
29 | return this._dom;
30 | }
31 |
32 | /**
33 | * Finds the first desired element within the Document
34 | *
35 | * @param {string} type - Type of element to look for (e.g. Controller, Datatype, etc)
36 | * @param {object} [attr=null] - Object of attributes to match in element
37 | * @returns {object|null} found documents
38 | * @param {object} [search_tree=null] - Tree to search on
39 | * @memberof Document
40 | */
41 | findOne(type, attr = null, ignore = [], search_tree = null) {
42 | // Validate Inputs
43 | if (typeof type !== "string")
44 | throw new Error(
45 | `Find expected a type name of type instead got type <${typeof type}>`
46 | );
47 |
48 | if (attr && typeof attr !== "object")
49 | throw new Error(
50 | `Find expected a attr of type or null instead got <${typeof attr}>`
51 | );
52 |
53 | if (search_tree && typeof search_tree !== "object")
54 | throw new Error(
55 | `Find expected a search tree of type or null instead got <${typeof search_tree}>`
56 | );
57 |
58 | if (!Array.isArray(ignore))
59 | throw new Error(
60 | `Find expected a ignore of type instead got <${typeof ignore}>`
61 | );
62 |
63 | // If no tree is passed then search from document root
64 | let tree = search_tree ? search_tree : this._dom;
65 |
66 | // Nothing left to search, return null
67 | if (!tree.elements || !Array.isArray(tree.elements) || tree.length === 0) return null;
68 |
69 | // Search elements
70 | for (let elem of tree.elements) {
71 | if (!ignore.includes(elem.name)) {
72 | // Check if this is the desired element
73 | if (elem.name === type) {
74 | // Check if attributes match
75 | if (attr) {
76 | let attrMatch = true;
77 | for (let key of Object.keys(attr)) {
78 | if (!elem.attributes[key] || elem.attributes[key] !== attr[key])
79 | attrMatch = false;
80 | }
81 |
82 | // Attributes match so return subtree
83 | if (attrMatch) return new Element(elem);
84 | } else {
85 | // No attributes need to match
86 | return new Element(elem);
87 | }
88 | }
89 | // Search branches for desired element
90 | const found = this.findOne(type, attr, ignore, elem);
91 |
92 | if (found) return found;
93 | }
94 | }
95 |
96 | // Nothing found, return null
97 | return null;
98 | }
99 |
100 | /**
101 | * Finds desired element within the Document
102 | *
103 | * @param {string} type - Type of element to look for (e.g. Controller, Datatype, etc)
104 | * @param {object} [attr=null] - Object of attributes to match in element
105 | * @param {Array} [ignore=null] - Names to Ignore
106 | * @param {object} [search_tree=null] - Tree to search on
107 | * @returns {Array|null} found documents
108 | * @memberof Document
109 | */
110 | find(type, attr = null, ignore = [], search_tree = null) {
111 | // Validate Inputs
112 | if (typeof type !== "string")
113 | throw new Error(
114 | `Find expected a type name of type or null instead got type <${typeof type}>`
115 | );
116 |
117 | if (attr && typeof attr !== "object")
118 | throw new Error(
119 | `Find expected a attr of type or null instead got <${typeof attr}>`
120 | );
121 |
122 | if (search_tree && typeof search_tree !== "object")
123 | throw new Error(
124 | `Find expected a search tree of type or null instead got <${typeof search_tree}>`
125 | );
126 |
127 | if (!Array.isArray(ignore))
128 | throw new Error(
129 | `Find expected a ignore of type instead got <${typeof ignore}>`
130 | );
131 |
132 | let returnArray = [];
133 |
134 | // Search function definiton
135 | const _find = (type, attr, search_tree) => {
136 | // Nothing left to search, return null
137 | if (
138 | !search_tree.elements ||
139 | !Array.isArray(search_tree.elements) ||
140 | search_tree.elements.length === 0
141 | )
142 | return null;
143 |
144 | // Search elements
145 | for (let elem of search_tree.elements) {
146 | // explore tree if not in ignores
147 | if (!ignore.includes(elem.name)) {
148 | // Check if this is the desired element
149 | if (elem.name === type) {
150 | // Check if attributes match
151 | if (attr) {
152 | let attrMatch = true;
153 | for (let key of Object.keys(attr)) {
154 | if (!elem.attributes[key] || elem.attributes[key] !== attr[key])
155 | attrMatch = false;
156 | }
157 |
158 | // Attributes match so return subtree
159 | if (attrMatch) {
160 | returnArray.push(new Element(elem));
161 | }
162 | } else {
163 | // No attributes need to match
164 | returnArray.push(new Element(elem));
165 | }
166 | } else {
167 | _find(type, attr, elem);
168 | }
169 | }
170 | }
171 | };
172 |
173 | // If no tree is passed then search from document root
174 | let tree = search_tree ? search_tree : this._dom;
175 |
176 | _find(type, attr, tree);
177 |
178 | return returnArray.length > 0 ? returnArray : null;
179 | }
180 |
181 | /**
182 | * Appends element to elem._dom.elements
183 | *
184 | * @param {Document} elem
185 | * @returns {Document} - this (for chaining)
186 | * @memberof Document
187 | */
188 | append(elem) {
189 | if (!Element.isElement(elem))
190 | throw new Error(
191 | `Append expected to receive doc of type instead got type <${typeof doc}>`
192 | );
193 |
194 | if (this.dom.elements && Array.isArray(this.dom.elements))
195 | this.dom.elements.push(elem.dom);
196 | else this.dom.elements = [elem.dom];
197 |
198 | return this;
199 | }
200 |
201 | /**
202 | * Returns a string representation of the current document
203 | *
204 | * @returns {string}
205 | * @memberof Document
206 | */
207 | toString() {
208 | return js2xml(this._dom);
209 | }
210 |
211 | /**
212 | * Check if passed object is a document
213 | *
214 | * @static
215 | * @param {object} doc - object to test
216 | * @returns {boolean}
217 | * @memberof Document
218 | */
219 | static isElement(doc) {
220 | return doc.elem_id && doc.elem_id === ELEMENT_ID;
221 | }
222 | }
223 |
224 | module.exports = Element;
225 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const Document = require("./document");
2 | const Tag = require("./tag");
3 | const Program = require("./program");
4 | const Routine = require("./routine");
5 | const Rung = require("./rung");
6 | const Util = require("./utilities");
7 |
8 | module.exports = { Document, Tag, Program, Routine, Rung, Util };
--------------------------------------------------------------------------------
/src/member/__snapshots__/member.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Member Class New Instance Initializes with Desired Document Model 1`] = `
4 | Member {
5 | "_dom": Object {
6 | "attributes": Object {
7 | "DataType": "DINT",
8 | "Hidden": false,
9 | "Name": "tagName",
10 | "Radix": "decimal",
11 | },
12 | "elements": Array [],
13 | "name": "Member",
14 | "type": "element",
15 | },
16 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
17 | "elem_id": "33cb4159377b63ce9db11667160e39df",
18 | }
19 | `;
20 |
21 | exports[`Member Class New Instance Initializes with Desired Document Model 2`] = `
22 | Member {
23 | "_dom": Object {
24 | "attributes": Object {
25 | "BitNumber": 0,
26 | "DataType": "BIT",
27 | "Hidden": false,
28 | "Name": "tagName",
29 | "Radix": "binary",
30 | "Target": "aTarget",
31 | },
32 | "elements": Array [],
33 | "name": "Member",
34 | "type": "element",
35 | },
36 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
37 | "elem_id": "33cb4159377b63ce9db11667160e39df",
38 | }
39 | `;
40 |
41 | exports[`Member Class New Instance Initializes with Desired Document Model 3`] = `
42 | Member {
43 | "_dom": Object {
44 | "attributes": Object {
45 | "BitNumber": 0,
46 | "DataType": "BIT",
47 | "Hidden": false,
48 | "Name": "tagName",
49 | "Radix": "binary",
50 | "Target": "aTarget",
51 | },
52 | "elements": Array [],
53 | "name": "Member",
54 | "type": "element",
55 | },
56 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
57 | "elem_id": "33cb4159377b63ce9db11667160e39df",
58 | }
59 | `;
60 |
61 | exports[`Member Class New Instance Initializes with Desired Document Model 4`] = `
62 | Member {
63 | "_dom": Object {
64 | "attributes": Object {
65 | "DataType": "DINT",
66 | "Hidden": false,
67 | "Name": "tagName",
68 | "Radix": "decimal",
69 | },
70 | "elements": Array [
71 | Object {
72 | "elements": Array [
73 | Object {
74 | "cdata": "A Description",
75 | "type": "cdata",
76 | },
77 | ],
78 | "name": "Description",
79 | "type": "element",
80 | },
81 | ],
82 | "name": "Member",
83 | "type": "element",
84 | },
85 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
86 | "elem_id": "33cb4159377b63ce9db11667160e39df",
87 | }
88 | `;
89 |
90 | exports[`Member Class New Instance Initializes with Desired Document Model 5`] = `
91 | Member {
92 | "_dom": Object {
93 | "attributes": Object {
94 | "DataType": "DINT",
95 | "Hidden": true,
96 | "Name": "tagName",
97 | "Radix": "decimal",
98 | },
99 | "elements": Array [
100 | Object {
101 | "elements": Array [
102 | Object {
103 | "cdata": "A Description",
104 | "type": "cdata",
105 | },
106 | ],
107 | "name": "Description",
108 | "type": "element",
109 | },
110 | ],
111 | "name": "Member",
112 | "type": "element",
113 | },
114 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
115 | "elem_id": "33cb4159377b63ce9db11667160e39df",
116 | }
117 | `;
118 |
119 | exports[`Member Class New Instance Initializes with Desired Document Model 6`] = `
120 | Member {
121 | "_dom": Object {
122 | "attributes": Object {
123 | "DataType": "DINT",
124 | "Hidden": false,
125 | "Name": "tagName",
126 | "Radix": "decimal",
127 | },
128 | "elements": Array [
129 | Object {
130 | "elements": Array [
131 | Object {
132 | "cdata": "A Description",
133 | "type": "cdata",
134 | },
135 | ],
136 | "name": "Description",
137 | "type": "element",
138 | },
139 | ],
140 | "name": "Member",
141 | "type": "element",
142 | },
143 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
144 | "elem_id": "33cb4159377b63ce9db11667160e39df",
145 | }
146 | `;
147 |
148 | exports[`Member Class New Instance Initializes with Desired Document Model 7`] = `
149 | Member {
150 | "_dom": Object {
151 | "attributes": Object {
152 | "DataType": "DINT",
153 | "Hidden": false,
154 | "Name": "tagName",
155 | "Radix": "decimal",
156 | },
157 | "elements": Array [
158 | Object {
159 | "elements": Array [
160 | Object {
161 | "cdata": "A Description",
162 | "type": "cdata",
163 | },
164 | ],
165 | "name": "Description",
166 | "type": "element",
167 | },
168 | ],
169 | "name": "Member",
170 | "type": "element",
171 | },
172 | "class_id": "cdb6dcc14d327d3c45e6e77e6648d07e",
173 | "elem_id": "33cb4159377b63ce9db11667160e39df",
174 | }
175 | `;
176 |
--------------------------------------------------------------------------------
/src/member/index.js:
--------------------------------------------------------------------------------
1 | const Element = require("../element");
2 | const { hash } = require("../utilities");
3 |
4 | const MEMBER_ID = hash("MEMBER_ID_STRING");
5 |
6 | class Member extends Element {
7 | constructor(
8 | name,
9 | datatype,
10 | description = null,
11 | hidden = false,
12 | target = null,
13 | bit_number = null
14 | ) {
15 | if (typeof name !== "string")
16 | throw new Error(
17 | `Member constructor expected name of type instead got <${typeof name}>`
18 | );
19 |
20 | if (typeof datatype !== "string")
21 | throw new Error(
22 | `Member constructor expected datatype of type instead got <${typeof datatype}>`
23 | );
24 |
25 | if (description && typeof description !== "string")
26 | throw new Error(
27 | `Member constructor expected description of type instead got <${typeof description}>`
28 | );
29 |
30 | if (hidden && typeof hidden !== "boolean")
31 | throw new Error(
32 | `Member constructor expected hidden of type instead got <${typeof hidden}>`
33 | );
34 |
35 | if (target && typeof target !== "string")
36 | throw new Error(
37 | `Member constructor expected target of type instead got <${typeof target}>`
38 | );
39 |
40 | if (bit_number && !target)
41 | throw new Error("bit_number can only be defined with valid target name");
42 |
43 | if (bit_number && typeof bit_number !== "number")
44 | throw new Error(
45 | `Member constructor expected bit_number of type instead got <${typeof bit_number}>`
46 | );
47 |
48 | let attributes = {
49 | Name: name,
50 | DataType: datatype === "BOOL" ? "BIT" : datatype,
51 | Hidden: hidden,
52 | Radix: datatype === "BOOL" || datatype === "BIT" ? "binary" : "decimal"
53 | };
54 |
55 | if ((datatype === "BOOL" || datatype === "BIT") && target) {
56 | attributes.Target = target;
57 | attributes.BitNumber = bit_number;
58 | }
59 |
60 | let elements = [];
61 |
62 | /* eslint-disable indent */
63 | if (description)
64 | elements.push({
65 | type: "element",
66 | name: "Description",
67 | elements: [
68 | {
69 | type: "cdata",
70 | cdata: description
71 | }
72 | ]
73 | });
74 | /* eslint-enable indent */
75 |
76 | // Call parent class constructor
77 | super({
78 | type: "element",
79 | name: "Member",
80 | attributes,
81 | elements
82 | });
83 |
84 | this.class_id = MEMBER_ID;
85 | }
86 |
87 | /**
88 | * Checks if an incoming object is a member instance
89 | *
90 | * @static
91 | * @param {Member} member
92 | * @returns {boolean}
93 | * @memberof Tag
94 | */
95 | static isMember(member) {
96 | return member.class_id && member.class_id === MEMBER_ID;
97 | }
98 | }
99 |
100 | module.exports = Member;
101 |
--------------------------------------------------------------------------------
/src/member/member.spec.js:
--------------------------------------------------------------------------------
1 | const Member = require("./index");
2 |
3 | describe("Member Class", () => {
4 | describe("New Instance", () => {
5 | it("Accepts Proper Input", () => {
6 | const fn = (
7 | name,
8 | datatype,
9 | desc = null,
10 | hidden = false,
11 | target = null,
12 | bit = null
13 | ) => () => new Member(name, datatype, desc, hidden, target, bit);
14 |
15 | expect(fn("tagName")).toThrow();
16 | expect(fn(null, "DINT")).toThrow();
17 | expect(fn("tagName", "DINT")).not.toThrow();
18 | expect(fn("tagName", "DINT", 12)).toThrow();
19 | expect(fn("tagName", "DINT", "A Description")).not.toThrow();
20 | expect(fn("tagName", "DINT", "A Description", 12)).toThrow();
21 | expect(fn("tagName", "DINT", "A Description", true)).not.toThrow();
22 | expect(fn("tagName", "DINT", "A Description", true, 12)).toThrow();
23 | expect(fn("tagName", "DINT", "A Description", true, "aTarget")).not.toThrow();
24 | expect(
25 | fn("tagName", "DINT", "A Description", true, "aTarget", "notANumber")
26 | ).toThrow();
27 | expect(fn("tagName", "DINT", "A Description", true, null, 12)).toThrow();
28 | expect(
29 | fn("tagName", "DINT", "A Description", true, "aTarget", 12)
30 | ).not.toThrow();
31 | });
32 |
33 | it("Initializes with Desired Document Model", () => {
34 | let mem = new Member("tagName", "DINT");
35 | expect(mem).toMatchSnapshot();
36 |
37 | mem = new Member("tagName", "BOOL", null, false, "aTarget", 0);
38 | expect(mem).toMatchSnapshot();
39 |
40 | mem = new Member("tagName", "BIT", null, false, "aTarget", 0);
41 | expect(mem).toMatchSnapshot();
42 |
43 | mem = new Member("tagName", "DINT", "A Description");
44 | expect(mem).toMatchSnapshot();
45 |
46 | mem = new Member("tagName", "DINT", "A Description", true);
47 | expect(mem).toMatchSnapshot();
48 |
49 | mem = new Member("tagName", "DINT", "A Description", false, "ATarget");
50 | expect(mem).toMatchSnapshot();
51 |
52 | mem = new Member("tagName", "DINT", "A Description", false, "ATarget", 12);
53 | expect(mem).toMatchSnapshot();
54 | });
55 | });
56 |
57 | describe("Static Methods", () => {
58 | test("isMember: Returns Appropriate Judgement", () => {
59 | let mem = new Member("tagName", "DINT");
60 | expect(Member.isMember(mem)).toBeTruthy();
61 |
62 | mem = { notATag: 12 };
63 | expect(Member.isMember(mem)).toBeFalsy();
64 |
65 | mem = 12;
66 | expect(Member.isMember(mem)).toBeFalsy();
67 | });
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/src/program/__snapshots__/program.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Program Class Methods _orderify: Results in the Correct Order 1`] = `
4 | Program {
5 | "_dom": Object {
6 | "attributes": Object {
7 | "Class": "Standard",
8 | "Name": "testProg",
9 | "Type": "Normal",
10 | "Use": "Context",
11 | },
12 | "elements": Array [
13 | Object {
14 | "elements": Array [],
15 | "name": "Description",
16 | "type": "element",
17 | },
18 | Object {
19 | "elements": Array [],
20 | "name": "Tags",
21 | "type": "element",
22 | },
23 | Object {
24 | "elements": Array [],
25 | "name": "Routines",
26 | "type": "element",
27 | },
28 | ],
29 | "name": "Program",
30 | "type": "element",
31 | },
32 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
33 | "elem_id": "33cb4159377b63ce9db11667160e39df",
34 | }
35 | `;
36 |
37 | exports[`Program Class Methods addRoutine: Adds Routine Program Instance 1`] = `
38 | Program {
39 | "_dom": Object {
40 | "attributes": Object {
41 | "Class": "Standard",
42 | "Name": "testProg",
43 | "Type": "Normal",
44 | "Use": "Context",
45 | },
46 | "elements": Array [
47 | Object {
48 | "elements": Array [
49 | Object {
50 | "attributes": Object {
51 | "Name": "aRoutine",
52 | "Type": "RLL",
53 | "Use": "Context",
54 | },
55 | "elements": Array [
56 | Object {
57 | "elements": Array [],
58 | "name": "RLLContent",
59 | "type": "element",
60 | },
61 | ],
62 | "name": "Routine",
63 | "type": "element",
64 | },
65 | ],
66 | "name": "Routines",
67 | "type": "element",
68 | },
69 | ],
70 | "name": "Program",
71 | "type": "element",
72 | },
73 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
74 | "elem_id": "33cb4159377b63ce9db11667160e39df",
75 | }
76 | `;
77 |
78 | exports[`Program Class Methods addRoutine: Adds Routine Program Instance 2`] = `
79 | Program {
80 | "_dom": Object {
81 | "attributes": Object {
82 | "Class": "Standard",
83 | "Name": "testProg",
84 | "Type": "Normal",
85 | "Use": "Context",
86 | },
87 | "elements": Array [
88 | Object {
89 | "elements": Array [
90 | Object {
91 | "attributes": Object {
92 | "Name": "aRoutine",
93 | "Type": "RLL",
94 | "Use": "Context",
95 | },
96 | "elements": Array [
97 | Object {
98 | "elements": Array [],
99 | "name": "RLLContent",
100 | "type": "element",
101 | },
102 | ],
103 | "name": "Routine",
104 | "type": "element",
105 | },
106 | Object {
107 | "attributes": Object {
108 | "Name": "bRoutine",
109 | "Type": "RLL",
110 | "Use": "Context",
111 | },
112 | "elements": Array [
113 | Object {
114 | "elements": Array [],
115 | "name": "RLLContent",
116 | "type": "element",
117 | },
118 | ],
119 | "name": "Routine",
120 | "type": "element",
121 | },
122 | ],
123 | "name": "Routines",
124 | "type": "element",
125 | },
126 | ],
127 | "name": "Program",
128 | "type": "element",
129 | },
130 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
131 | "elem_id": "33cb4159377b63ce9db11667160e39df",
132 | }
133 | `;
134 |
135 | exports[`Program Class Methods addRoutine: Adds Routine Program Instance 3`] = `
136 | Program {
137 | "_dom": Object {
138 | "attributes": Object {
139 | "Class": "Standard",
140 | "Name": "testProg",
141 | "Type": "Normal",
142 | "Use": "Context",
143 | },
144 | "elements": Array [
145 | Object {
146 | "elements": Array [
147 | Object {
148 | "attributes": Object {
149 | "Name": "aRoutine",
150 | "Type": "RLL",
151 | "Use": "Context",
152 | },
153 | "elements": Array [
154 | Object {
155 | "elements": Array [],
156 | "name": "RLLContent",
157 | "type": "element",
158 | },
159 | ],
160 | "name": "Routine",
161 | "type": "element",
162 | },
163 | Object {
164 | "attributes": Object {
165 | "Name": "bRoutine",
166 | "Type": "RLL",
167 | "Use": "Context",
168 | },
169 | "elements": Array [
170 | Object {
171 | "elements": Array [],
172 | "name": "RLLContent",
173 | "type": "element",
174 | },
175 | ],
176 | "name": "Routine",
177 | "type": "element",
178 | },
179 | Object {
180 | "attributes": Object {
181 | "Name": "cRoutine",
182 | "Type": "RLL",
183 | "Use": "Context",
184 | },
185 | "elements": Array [
186 | Object {
187 | "elements": Array [],
188 | "name": "RLLContent",
189 | "type": "element",
190 | },
191 | ],
192 | "name": "Routine",
193 | "type": "element",
194 | },
195 | ],
196 | "name": "Routines",
197 | "type": "element",
198 | },
199 | ],
200 | "name": "Program",
201 | "type": "element",
202 | },
203 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
204 | "elem_id": "33cb4159377b63ce9db11667160e39df",
205 | }
206 | `;
207 |
208 | exports[`Program Class Methods addRoutine: Adds Routine Program Instance 4`] = `
209 | Program {
210 | "_dom": Object {
211 | "attributes": Object {
212 | "Class": "Standard",
213 | "Name": "testProg",
214 | "Type": "Normal",
215 | "Use": "Context",
216 | },
217 | "elements": Array [
218 | Object {
219 | "elements": Array [
220 | Object {
221 | "attributes": Object {
222 | "Name": "aRoutine",
223 | "Type": "RLL",
224 | "Use": "Context",
225 | },
226 | "elements": Array [
227 | Object {
228 | "elements": Array [],
229 | "name": "RLLContent",
230 | "type": "element",
231 | },
232 | ],
233 | "name": "Routine",
234 | "type": "element",
235 | },
236 | Object {
237 | "attributes": Object {
238 | "Name": "bRoutine",
239 | "Type": "RLL",
240 | "Use": "Context",
241 | },
242 | "elements": Array [
243 | Object {
244 | "elements": Array [],
245 | "name": "RLLContent",
246 | "type": "element",
247 | },
248 | ],
249 | "name": "Routine",
250 | "type": "element",
251 | },
252 | Object {
253 | "attributes": Object {
254 | "Name": "cRoutine",
255 | "Type": "RLL",
256 | "Use": "Context",
257 | },
258 | "elements": Array [
259 | Object {
260 | "elements": Array [
261 | Object {
262 | "attributes": Object {
263 | "Number": 0,
264 | "Type": "N",
265 | },
266 | "elements": Array [
267 | Object {
268 | "elements": Array [
269 | Object {
270 | "cdata": "XIC(someTag)NOP();",
271 | "type": "cdata",
272 | },
273 | ],
274 | "name": "Text",
275 | "type": "element",
276 | },
277 | ],
278 | "name": "Rung",
279 | "type": "element",
280 | },
281 | ],
282 | "name": "RLLContent",
283 | "type": "element",
284 | },
285 | ],
286 | "name": "Routine",
287 | "type": "element",
288 | },
289 | ],
290 | "name": "Routines",
291 | "type": "element",
292 | },
293 | ],
294 | "name": "Program",
295 | "type": "element",
296 | },
297 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
298 | "elem_id": "33cb4159377b63ce9db11667160e39df",
299 | }
300 | `;
301 |
302 | exports[`Program Class Methods addTag: Adds Tag Program Instance 1`] = `
303 | Program {
304 | "_dom": Object {
305 | "attributes": Object {
306 | "Class": "Standard",
307 | "Name": "testProg",
308 | "Type": "Normal",
309 | "Use": "Context",
310 | },
311 | "elements": Array [
312 | Object {
313 | "elements": Array [
314 | Object {
315 | "attributes": Object {
316 | "Class": "Standard",
317 | "DataType": "BOOL",
318 | "Name": "tag1",
319 | "TagType": "Base",
320 | },
321 | "elements": Array [],
322 | "name": "Tag",
323 | "type": "element",
324 | },
325 | ],
326 | "name": "Tags",
327 | "type": "element",
328 | },
329 | ],
330 | "name": "Program",
331 | "type": "element",
332 | },
333 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
334 | "elem_id": "33cb4159377b63ce9db11667160e39df",
335 | }
336 | `;
337 |
338 | exports[`Program Class Methods addTag: Adds Tag Program Instance 2`] = `
339 | Program {
340 | "_dom": Object {
341 | "attributes": Object {
342 | "Class": "Standard",
343 | "Name": "testProg",
344 | "Type": "Normal",
345 | "Use": "Context",
346 | },
347 | "elements": Array [
348 | Object {
349 | "elements": Array [
350 | Object {
351 | "attributes": Object {
352 | "Class": "Standard",
353 | "DataType": "BOOL",
354 | "Name": "tag1",
355 | "TagType": "Base",
356 | },
357 | "elements": Array [],
358 | "name": "Tag",
359 | "type": "element",
360 | },
361 | Object {
362 | "attributes": Object {
363 | "Class": "Standard",
364 | "DataType": "DINT",
365 | "Name": "tag2",
366 | "TagType": "Base",
367 | },
368 | "elements": Array [],
369 | "name": "Tag",
370 | "type": "element",
371 | },
372 | ],
373 | "name": "Tags",
374 | "type": "element",
375 | },
376 | ],
377 | "name": "Program",
378 | "type": "element",
379 | },
380 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
381 | "elem_id": "33cb4159377b63ce9db11667160e39df",
382 | }
383 | `;
384 |
385 | exports[`Program Class Methods addTag: Adds Tag Program Instance 3`] = `
386 | Program {
387 | "_dom": Object {
388 | "attributes": Object {
389 | "Class": "Standard",
390 | "Name": "testProg",
391 | "Type": "Normal",
392 | "Use": "Context",
393 | },
394 | "elements": Array [
395 | Object {
396 | "elements": Array [
397 | Object {
398 | "cdata": "A Cool Program Description",
399 | "type": "cdata",
400 | },
401 | ],
402 | "name": "Description",
403 | "type": "element",
404 | },
405 | Object {
406 | "elements": Array [
407 | Object {
408 | "attributes": Object {
409 | "Class": "Standard",
410 | "DataType": "BOOL",
411 | "Name": "tag1",
412 | "TagType": "Base",
413 | },
414 | "elements": Array [],
415 | "name": "Tag",
416 | "type": "element",
417 | },
418 | Object {
419 | "attributes": Object {
420 | "Class": "Standard",
421 | "DataType": "DINT",
422 | "Name": "tag2",
423 | "TagType": "Base",
424 | },
425 | "elements": Array [],
426 | "name": "Tag",
427 | "type": "element",
428 | },
429 | Object {
430 | "attributes": Object {
431 | "Class": "Standard",
432 | "DataType": "SINT",
433 | "Name": "tag3",
434 | "TagType": "Base",
435 | },
436 | "elements": Array [],
437 | "name": "Tag",
438 | "type": "element",
439 | },
440 | ],
441 | "name": "Tags",
442 | "type": "element",
443 | },
444 | ],
445 | "name": "Program",
446 | "type": "element",
447 | },
448 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
449 | "elem_id": "33cb4159377b63ce9db11667160e39df",
450 | }
451 | `;
452 |
453 | exports[`Program Class Methods addTag: Adds Tag Program Instance 4`] = `
454 | Program {
455 | "_dom": Object {
456 | "attributes": Object {
457 | "Class": "Standard",
458 | "Name": "testProg",
459 | "Type": "Normal",
460 | "Use": "Context",
461 | },
462 | "elements": Array [
463 | Object {
464 | "elements": Array [
465 | Object {
466 | "cdata": "A Cool Program Description",
467 | "type": "cdata",
468 | },
469 | ],
470 | "name": "Description",
471 | "type": "element",
472 | },
473 | Object {
474 | "elements": Array [
475 | Object {
476 | "attributes": Object {
477 | "Class": "Standard",
478 | "DataType": "BOOL",
479 | "Name": "tag1",
480 | "TagType": "Base",
481 | },
482 | "elements": Array [],
483 | "name": "Tag",
484 | "type": "element",
485 | },
486 | Object {
487 | "attributes": Object {
488 | "Class": "Standard",
489 | "DataType": "DINT",
490 | "Name": "tag2",
491 | "TagType": "Base",
492 | },
493 | "elements": Array [],
494 | "name": "Tag",
495 | "type": "element",
496 | },
497 | Object {
498 | "attributes": Object {
499 | "Class": "Standard",
500 | "DataType": "SINT",
501 | "Name": "tag3",
502 | "TagType": "Base",
503 | },
504 | "elements": Array [],
505 | "name": "Tag",
506 | "type": "element",
507 | },
508 | Object {
509 | "attributes": Object {
510 | "Class": "Standard",
511 | "DataType": "REAL",
512 | "Name": "tag4",
513 | "TagType": "Base",
514 | },
515 | "elements": Array [],
516 | "name": "Tag",
517 | "type": "element",
518 | },
519 | ],
520 | "name": "Tags",
521 | "type": "element",
522 | },
523 | ],
524 | "name": "Program",
525 | "type": "element",
526 | },
527 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
528 | "elem_id": "33cb4159377b63ce9db11667160e39df",
529 | }
530 | `;
531 |
532 | exports[`Program Class Methods addTag: Adds Tag Program Instance 5`] = `
533 | Program {
534 | "_dom": Object {
535 | "attributes": Object {
536 | "Class": "Standard",
537 | "Name": "testProg",
538 | "Type": "Normal",
539 | "Use": "Context",
540 | },
541 | "elements": Array [
542 | Object {
543 | "elements": Array [
544 | Object {
545 | "cdata": "A Cool Program Description",
546 | "type": "cdata",
547 | },
548 | ],
549 | "name": "Description",
550 | "type": "element",
551 | },
552 | Object {
553 | "elements": Array [
554 | Object {
555 | "attributes": Object {
556 | "Class": "Standard",
557 | "DataType": "BOOL",
558 | "Name": "tag1",
559 | "TagType": "Base",
560 | },
561 | "elements": Array [],
562 | "name": "Tag",
563 | "type": "element",
564 | },
565 | Object {
566 | "attributes": Object {
567 | "Class": "Standard",
568 | "DataType": "DINT",
569 | "Name": "tag2",
570 | "TagType": "Base",
571 | },
572 | "elements": Array [],
573 | "name": "Tag",
574 | "type": "element",
575 | },
576 | Object {
577 | "attributes": Object {
578 | "Class": "Standard",
579 | "DataType": "SINT",
580 | "Name": "tag3",
581 | "TagType": "Base",
582 | },
583 | "elements": Array [],
584 | "name": "Tag",
585 | "type": "element",
586 | },
587 | Object {
588 | "attributes": Object {
589 | "Class": "Standard",
590 | "DataType": "REAL",
591 | "Name": "not_tag4Anymore",
592 | "TagType": "Base",
593 | },
594 | "elements": Array [],
595 | "name": "Tag",
596 | "type": "element",
597 | },
598 | ],
599 | "name": "Tags",
600 | "type": "element",
601 | },
602 | ],
603 | "name": "Program",
604 | "type": "element",
605 | },
606 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
607 | "elem_id": "33cb4159377b63ce9db11667160e39df",
608 | }
609 | `;
610 |
611 | exports[`Program Class Methods findRoutine: Finds Target Routine 1`] = `
612 | Routine {
613 | "_dom": Object {
614 | "attributes": Object {
615 | "Name": "aRoutine",
616 | "Type": "RLL",
617 | "Use": "Context",
618 | },
619 | "elements": Array [
620 | Object {
621 | "elements": Array [],
622 | "name": "RLLContent",
623 | "type": "element",
624 | },
625 | ],
626 | "name": "Routine",
627 | "type": "element",
628 | },
629 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
630 | "elem_id": "33cb4159377b63ce9db11667160e39df",
631 | }
632 | `;
633 |
634 | exports[`Program Class Methods findRoutine: Finds Target Routine 2`] = `
635 | Routine {
636 | "_dom": Object {
637 | "attributes": Object {
638 | "Name": "bRoutine",
639 | "Type": "RLL",
640 | "Use": "Context",
641 | },
642 | "elements": Array [
643 | Object {
644 | "elements": Array [],
645 | "name": "RLLContent",
646 | "type": "element",
647 | },
648 | ],
649 | "name": "Routine",
650 | "type": "element",
651 | },
652 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
653 | "elem_id": "33cb4159377b63ce9db11667160e39df",
654 | }
655 | `;
656 |
657 | exports[`Program Class Methods findRoutine: Finds Target Routine 3`] = `
658 | Routine {
659 | "_dom": Object {
660 | "attributes": Object {
661 | "Name": "cRoutine",
662 | "Type": "RLL",
663 | "Use": "Context",
664 | },
665 | "elements": Array [
666 | Object {
667 | "elements": Array [],
668 | "name": "RLLContent",
669 | "type": "element",
670 | },
671 | ],
672 | "name": "Routine",
673 | "type": "element",
674 | },
675 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
676 | "elem_id": "33cb4159377b63ce9db11667160e39df",
677 | }
678 | `;
679 |
680 | exports[`Program Class Methods findRoutine: Finds Target Routine 4`] = `
681 | Routine {
682 | "_dom": Object {
683 | "attributes": Object {
684 | "Name": "cRoutine",
685 | "Type": "RLL",
686 | "Use": "Context",
687 | },
688 | "elements": Array [
689 | Object {
690 | "elements": Array [
691 | Object {
692 | "attributes": Object {
693 | "Number": 0,
694 | "Type": "N",
695 | },
696 | "elements": Array [
697 | Object {
698 | "elements": Array [
699 | Object {
700 | "cdata": "XIC(someTag)NOP();",
701 | "type": "cdata",
702 | },
703 | ],
704 | "name": "Text",
705 | "type": "element",
706 | },
707 | ],
708 | "name": "Rung",
709 | "type": "element",
710 | },
711 | ],
712 | "name": "RLLContent",
713 | "type": "element",
714 | },
715 | ],
716 | "name": "Routine",
717 | "type": "element",
718 | },
719 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
720 | "elem_id": "33cb4159377b63ce9db11667160e39df",
721 | }
722 | `;
723 |
724 | exports[`Program Class Methods findRoutine: Finds Target Routine 5`] = `
725 | Program {
726 | "_dom": Object {
727 | "attributes": Object {
728 | "Class": "Standard",
729 | "Name": "testProg",
730 | "Type": "Normal",
731 | "Use": "Context",
732 | },
733 | "elements": Array [
734 | Object {
735 | "elements": Array [
736 | Object {
737 | "attributes": Object {
738 | "Name": "aRoutine",
739 | "Type": "RLL",
740 | "Use": "Context",
741 | },
742 | "elements": Array [
743 | Object {
744 | "elements": Array [],
745 | "name": "RLLContent",
746 | "type": "element",
747 | },
748 | ],
749 | "name": "Routine",
750 | "type": "element",
751 | },
752 | Object {
753 | "attributes": Object {
754 | "Name": "bRoutine",
755 | "Type": "RLL",
756 | "Use": "Context",
757 | },
758 | "elements": Array [
759 | Object {
760 | "elements": Array [],
761 | "name": "RLLContent",
762 | "type": "element",
763 | },
764 | ],
765 | "name": "Routine",
766 | "type": "element",
767 | },
768 | Object {
769 | "attributes": Object {
770 | "Name": "cRoutine",
771 | "Type": "RLL",
772 | "Use": "Context",
773 | },
774 | "elements": Array [
775 | Object {
776 | "elements": Array [
777 | Object {
778 | "attributes": Object {
779 | "Number": 0,
780 | "Type": "N",
781 | },
782 | "elements": Array [
783 | Object {
784 | "elements": Array [
785 | Object {
786 | "cdata": "XIC(someTag)NOP();",
787 | "type": "cdata",
788 | },
789 | ],
790 | "name": "Text",
791 | "type": "element",
792 | },
793 | ],
794 | "name": "Rung",
795 | "type": "element",
796 | },
797 | ],
798 | "name": "RLLContent",
799 | "type": "element",
800 | },
801 | ],
802 | "name": "Routine",
803 | "type": "element",
804 | },
805 | ],
806 | "name": "Routines",
807 | "type": "element",
808 | },
809 | ],
810 | "name": "Program",
811 | "type": "element",
812 | },
813 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
814 | "elem_id": "33cb4159377b63ce9db11667160e39df",
815 | }
816 | `;
817 |
818 | exports[`Program Class Methods findRoutine: Finds Target Routine 6`] = `
819 | Program {
820 | "_dom": Object {
821 | "attributes": Object {
822 | "Class": "Standard",
823 | "Name": "testProg",
824 | "Type": "Normal",
825 | "Use": "Context",
826 | },
827 | "elements": Array [
828 | Object {
829 | "elements": Array [
830 | Object {
831 | "attributes": Object {
832 | "Name": "aRoutine",
833 | "Type": "RLL",
834 | "Use": "Context",
835 | },
836 | "elements": Array [
837 | Object {
838 | "elements": Array [
839 | Object {
840 | "attributes": Object {
841 | "Number": 0,
842 | "Type": "N",
843 | },
844 | "elements": Array [
845 | Object {
846 | "elements": Array [
847 | Object {
848 | "cdata": "XIC(anotherTag)NOP();",
849 | "type": "cdata",
850 | },
851 | ],
852 | "name": "Text",
853 | "type": "element",
854 | },
855 | ],
856 | "name": "Rung",
857 | "type": "element",
858 | },
859 | ],
860 | "name": "RLLContent",
861 | "type": "element",
862 | },
863 | ],
864 | "name": "Routine",
865 | "type": "element",
866 | },
867 | Object {
868 | "attributes": Object {
869 | "Name": "bRoutine",
870 | "Type": "RLL",
871 | "Use": "Context",
872 | },
873 | "elements": Array [
874 | Object {
875 | "elements": Array [],
876 | "name": "RLLContent",
877 | "type": "element",
878 | },
879 | ],
880 | "name": "Routine",
881 | "type": "element",
882 | },
883 | Object {
884 | "attributes": Object {
885 | "Name": "cRoutine",
886 | "Type": "RLL",
887 | "Use": "Context",
888 | },
889 | "elements": Array [
890 | Object {
891 | "elements": Array [
892 | Object {
893 | "attributes": Object {
894 | "Number": 0,
895 | "Type": "N",
896 | },
897 | "elements": Array [
898 | Object {
899 | "elements": Array [
900 | Object {
901 | "cdata": "XIC(someTag)NOP();",
902 | "type": "cdata",
903 | },
904 | ],
905 | "name": "Text",
906 | "type": "element",
907 | },
908 | ],
909 | "name": "Rung",
910 | "type": "element",
911 | },
912 | ],
913 | "name": "RLLContent",
914 | "type": "element",
915 | },
916 | ],
917 | "name": "Routine",
918 | "type": "element",
919 | },
920 | ],
921 | "name": "Routines",
922 | "type": "element",
923 | },
924 | ],
925 | "name": "Program",
926 | "type": "element",
927 | },
928 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
929 | "elem_id": "33cb4159377b63ce9db11667160e39df",
930 | }
931 | `;
932 |
933 | exports[`Program Class Methods findTag: Finds Target Tag 1`] = `
934 | Tag {
935 | "_dom": Object {
936 | "attributes": Object {
937 | "Class": "Standard",
938 | "DataType": "DINT",
939 | "Name": "aTag",
940 | "TagType": "Base",
941 | },
942 | "elements": Array [],
943 | "name": "Tag",
944 | "type": "element",
945 | },
946 | "class_id": "202108c6004705df7b5fd0add930619f",
947 | "elem_id": "33cb4159377b63ce9db11667160e39df",
948 | }
949 | `;
950 |
951 | exports[`Program Class Methods findTag: Finds Target Tag 2`] = `
952 | Tag {
953 | "_dom": Object {
954 | "attributes": Object {
955 | "Class": "Standard",
956 | "DataType": "SINT",
957 | "Name": "bTag",
958 | "TagType": "Base",
959 | },
960 | "elements": Array [],
961 | "name": "Tag",
962 | "type": "element",
963 | },
964 | "class_id": "202108c6004705df7b5fd0add930619f",
965 | "elem_id": "33cb4159377b63ce9db11667160e39df",
966 | }
967 | `;
968 |
969 | exports[`Program Class Methods findTag: Finds Target Tag 3`] = `
970 | Tag {
971 | "_dom": Object {
972 | "attributes": Object {
973 | "Class": "Standard",
974 | "DataType": "BOOL",
975 | "Name": "cTag",
976 | "TagType": "Base",
977 | },
978 | "elements": Array [],
979 | "name": "Tag",
980 | "type": "element",
981 | },
982 | "class_id": "202108c6004705df7b5fd0add930619f",
983 | "elem_id": "33cb4159377b63ce9db11667160e39df",
984 | }
985 | `;
986 |
987 | exports[`Program Class Methods findTag: Finds Target Tag 4`] = `
988 | Program {
989 | "_dom": Object {
990 | "attributes": Object {
991 | "Class": "Standard",
992 | "Name": "testProg",
993 | "Type": "Normal",
994 | "Use": "Context",
995 | },
996 | "elements": Array [
997 | Object {
998 | "elements": Array [
999 | Object {
1000 | "attributes": Object {
1001 | "Class": "Standard",
1002 | "DataType": "DINT",
1003 | "Name": "aTag",
1004 | "TagType": "Base",
1005 | },
1006 | "elements": Array [],
1007 | "name": "Tag",
1008 | "type": "element",
1009 | },
1010 | Object {
1011 | "attributes": Object {
1012 | "Class": "Standard",
1013 | "DataType": "SINT",
1014 | "Name": "bTag",
1015 | "TagType": "Base",
1016 | },
1017 | "elements": Array [],
1018 | "name": "Tag",
1019 | "type": "element",
1020 | },
1021 | Object {
1022 | "attributes": Object {
1023 | "Class": "Standard",
1024 | "DataType": "BOOL",
1025 | "Name": "eTag",
1026 | "TagType": "Base",
1027 | },
1028 | "elements": Array [],
1029 | "name": "Tag",
1030 | "type": "element",
1031 | },
1032 | ],
1033 | "name": "Tags",
1034 | "type": "element",
1035 | },
1036 | ],
1037 | "name": "Program",
1038 | "type": "element",
1039 | },
1040 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
1041 | "elem_id": "33cb4159377b63ce9db11667160e39df",
1042 | }
1043 | `;
1044 |
1045 | exports[`Program Class Methods findTag: Finds Target Tag 5`] = `
1046 | Tag {
1047 | "_dom": Object {
1048 | "attributes": Object {
1049 | "Class": "Standard",
1050 | "DataType": "BOOL",
1051 | "Name": "eTag",
1052 | "TagType": "Base",
1053 | },
1054 | "elements": Array [],
1055 | "name": "Tag",
1056 | "type": "element",
1057 | },
1058 | "class_id": "202108c6004705df7b5fd0add930619f",
1059 | "elem_id": "33cb4159377b63ce9db11667160e39df",
1060 | }
1061 | `;
1062 |
1063 | exports[`Program Class New Instance Initializes with Desired Document Model 1`] = `
1064 | Program {
1065 | "_dom": Object {
1066 | "attributes": Object {
1067 | "Class": "Standard",
1068 | "Name": "ProgramName",
1069 | "Type": "Normal",
1070 | "Use": "Context",
1071 | },
1072 | "elements": Array [],
1073 | "name": "Program",
1074 | "type": "element",
1075 | },
1076 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
1077 | "elem_id": "33cb4159377b63ce9db11667160e39df",
1078 | }
1079 | `;
1080 |
1081 | exports[`Program Class New Instance Initializes with Desired Document Model 2`] = `
1082 | Program {
1083 | "_dom": Object {
1084 | "attributes": Object {
1085 | "Class": "Standard",
1086 | "Name": "ProgramName",
1087 | "Type": "Normal",
1088 | "Use": "Context",
1089 | },
1090 | "elements": Array [
1091 | Object {
1092 | "elements": Array [
1093 | Object {
1094 | "cdata": "A Description",
1095 | "type": "cdata",
1096 | },
1097 | ],
1098 | "name": "Description",
1099 | "type": "element",
1100 | },
1101 | ],
1102 | "name": "Program",
1103 | "type": "element",
1104 | },
1105 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
1106 | "elem_id": "33cb4159377b63ce9db11667160e39df",
1107 | }
1108 | `;
1109 |
1110 | exports[`Program Class New Instance Initializes with Desired Document Model 3`] = `
1111 | Program {
1112 | "_dom": Object {
1113 | "attributes": Object {
1114 | "Class": "Safety",
1115 | "Name": "ProgramName",
1116 | "Type": "Normal",
1117 | "Use": "Context",
1118 | },
1119 | "elements": Array [
1120 | Object {
1121 | "elements": Array [
1122 | Object {
1123 | "cdata": "A Description",
1124 | "type": "cdata",
1125 | },
1126 | ],
1127 | "name": "Description",
1128 | "type": "element",
1129 | },
1130 | ],
1131 | "name": "Program",
1132 | "type": "element",
1133 | },
1134 | "class_id": "bacac87817ce803505fcdfaf4360dc50",
1135 | "elem_id": "33cb4159377b63ce9db11667160e39df",
1136 | }
1137 | `;
1138 |
--------------------------------------------------------------------------------
/src/program/index.js:
--------------------------------------------------------------------------------
1 | const Element = require("../element");
2 | const Tag = require("../tag");
3 | const Routine = require("../routine");
4 | const { hash } = require("../utilities");
5 |
6 | const PROGRAM_ID = hash("PROGRAM_ID_STRING");
7 |
8 | class Program extends Element {
9 | constructor(name, description = null, safety = false) {
10 | if (typeof name !== "string")
11 | throw new Error(
12 | `Program constructor expected name of type instead got <${typeof name}>`
13 | );
14 |
15 | if (description && typeof description !== "string")
16 | throw new Error(
17 | `Program constructor expected description of type instead got <${typeof description}>`
18 | );
19 |
20 | if (safety && typeof safety !== "boolean")
21 | throw new Error(
22 | `Program constructor expected safety of type instead got <${typeof safety}>`
23 | );
24 |
25 | let attributes = {
26 | Name: name,
27 | Type: "Normal",
28 | Class: safety ? "Safety" : "Standard",
29 | Use: "Context"
30 | };
31 |
32 | let elements = [];
33 |
34 | /* eslint-disable indent */
35 | if (description)
36 | elements.push({
37 | type: "element",
38 | name: "Description",
39 | elements: [
40 | {
41 | type: "cdata",
42 | cdata: description
43 | }
44 | ]
45 | });
46 | /* eslint-enable indent */
47 |
48 | // Call parent class constructor
49 | super({
50 | type: "element",
51 | name: "Program",
52 | attributes,
53 | elements
54 | });
55 |
56 | this.class_id = PROGRAM_ID;
57 | }
58 |
59 | /**
60 | * Adds tag to program instance
61 | *
62 | * @param {Tag} tag
63 | * @memberof Program
64 | */
65 | addTag(tag) {
66 | if (!Tag.isTag(tag))
67 | throw new Error(`addTag expected tag of type instead got <${typeof tag}>`);
68 |
69 | const tags = this.findOne("Tags");
70 |
71 | /* eslint-disable indent */
72 | if (tags) tags.append(tag);
73 | else
74 | this.append(
75 | new Element({
76 | type: "element",
77 | name: "Tags",
78 | elements: []
79 | }).append(tag)
80 | );
81 | /* eslint-enable indent */
82 |
83 | this._orderify();
84 | return this;
85 | }
86 |
87 | /**
88 | * Adds routine to program instance
89 | *
90 | * @param {any} routine
91 | * @memberof Program
92 | */
93 | addRoutine(routine) {
94 | if (!Routine.isRoutine(routine))
95 | throw new Error(
96 | `addRoutine expected routine of type instead got <${typeof routine}>`
97 | );
98 |
99 | const routines = this.findOne("Routines");
100 |
101 | /* eslint-disable indent */
102 | if (routines) routines.append(routine);
103 | else
104 | this.append(
105 | new Element({
106 | type: "element",
107 | name: "Routines",
108 | elements: []
109 | }).append(routine)
110 | );
111 | /* eslint-enable indent */
112 |
113 | this._orderify();
114 | return this;
115 | }
116 |
117 | /**
118 | * Finds tag in the program instance that matches the name
119 | *
120 | * @param {string} name
121 | * @returns {Tag|null}
122 | * @memberof Program
123 | */
124 | findTag(name) {
125 | if (typeof name !== "string")
126 | throw new Error(
127 | `findTag expected name of type instead got <${typeof name}>`
128 | );
129 |
130 | const found = this.findOne("Tag", { Name: name });
131 | if (!found) return null;
132 |
133 | const tag = new Tag(name, found.dom.attributes.DataType);
134 | tag._dom = found.dom;
135 |
136 | return tag;
137 | }
138 |
139 | /**
140 | * Finds routine in the program instance that matches the name
141 | *
142 | * @param {string} name
143 | * @returns {Routine|null}
144 | * @memberof Program
145 | */
146 | findRoutine(name) {
147 | if (typeof name !== "string")
148 | throw new Error(
149 | `findRoutine expected name of type instead got <${typeof name}>`
150 | );
151 |
152 | const found = this.findOne("Routine", { Name: name });
153 | if (!found) return null;
154 |
155 | const routine = new Routine(name);
156 | routine._dom = found.dom;
157 |
158 | return routine;
159 | }
160 |
161 | /**
162 | * Puts the programs children in the correct order
163 | *
164 | * @private
165 | * @memberof Program
166 | */
167 | _orderify() {
168 | const order = {
169 | Description: 1,
170 | Tags: 2,
171 | Routines: 3,
172 | ChildPrograms: 4
173 | };
174 |
175 | this.dom.elements.sort((a, b) => {
176 | return order[a.name] - order[b.name];
177 | });
178 | }
179 |
180 | /**
181 | * Checks if an incoming object is a program instance
182 | *
183 | * @static
184 | * @param {any} program
185 | * @returns {boolean}
186 | * @memberof Tag
187 | */
188 | static isProgram(program) {
189 | return program.class_id && program.class_id === PROGRAM_ID;
190 | }
191 | }
192 |
193 | module.exports = Program;
194 |
--------------------------------------------------------------------------------
/src/program/program.spec.js:
--------------------------------------------------------------------------------
1 | const Program = require("./index");
2 | const Tag = require("../tag");
3 | const Routine = require("../routine");
4 | const Rung = require("../rung");
5 |
6 | describe("Program Class", () => {
7 | describe("New Instance", () => {
8 | it("Accepts Proper Input", () => {
9 | const fn = (name, desc = null, safety = false) => () => new Program(name, desc, safety);
10 |
11 | expect(fn("ProgramName")).not.toThrow();
12 | expect(fn(null, "A Program Description")).toThrow();
13 | expect(fn("ProgramName", 12)).toThrow();
14 | expect(fn("ProgramName", "A Program Description")).not.toThrow();
15 | expect(fn("ProgramName", "A Program Description", 12)).toThrow();
16 | expect(fn("ProgramName", "A Program Description", true)).not.toThrow();
17 | });
18 |
19 | it("Initializes with Desired Document Model", () => {
20 | let program = new Program("ProgramName");
21 | expect(program).toMatchSnapshot();
22 |
23 | program = new Program("ProgramName", "A Description");
24 | expect(program).toMatchSnapshot();
25 |
26 | program = new Program("ProgramName", "A Description", true);
27 | expect(program).toMatchSnapshot();
28 | });
29 | });
30 |
31 | describe("Methods", () => {
32 | let prog;
33 |
34 | beforeEach(() => {
35 | prog = new Program("testProg");
36 | });
37 |
38 | test("_orderify: Results in the Correct Order", () => {
39 | prog.dom.elements.push({
40 | type: "element",
41 | name: "Tags",
42 | elements: []
43 | });
44 |
45 | prog.dom.elements.push({
46 | type: "element",
47 | name: "Routines",
48 | elements: []
49 | });
50 |
51 | prog.dom.elements.push({
52 | type: "element",
53 | name: "Description",
54 | elements: []
55 | });
56 |
57 | prog._orderify();
58 |
59 | expect(prog).toMatchSnapshot();
60 | });
61 |
62 | test("addTag: Throws on Invalid Input", () => {
63 | const fn = tag => () => prog.addTag(tag);
64 |
65 | expect(fn(new Program("notATag"))).toThrow();
66 | expect(fn("notATag")).toThrow();
67 | });
68 |
69 | test("addTag: Adds Tag Program Instance", () => {
70 | prog.addTag(new Tag("tag1", "BOOL"));
71 | expect(prog).toMatchSnapshot();
72 |
73 | prog.addTag(new Tag("tag2", "DINT"));
74 | expect(prog).toMatchSnapshot();
75 |
76 | prog.dom.elements.push({
77 | type: "element",
78 | name: "Description",
79 | elements: [
80 | {
81 | type: "cdata",
82 | cdata: "A Cool Program Description"
83 | }
84 | ]
85 | });
86 |
87 | prog.addTag(new Tag("tag3", "SINT"));
88 | expect(prog).toMatchSnapshot();
89 |
90 | const tag4 = new Tag("tag4", "REAL");
91 | prog.addTag(tag4);
92 | expect(prog).toMatchSnapshot();
93 |
94 | tag4.dom.attributes.Name = "not_tag4Anymore";
95 | expect(prog).toMatchSnapshot();
96 | });
97 |
98 | test("addRoutine: Throws on Invalid Input", () => {
99 | const fn = rout => () => prog.addRoutine(rout);
100 |
101 | expect(fn(new Program("notATag"))).toThrow();
102 | expect(fn("notATag")).toThrow();
103 | });
104 |
105 | test("addRoutine: Adds Routine Program Instance", () => {
106 | prog.addRoutine(new Routine("aRoutine"));
107 | expect(prog).toMatchSnapshot();
108 |
109 | prog.addRoutine(new Routine("bRoutine"));
110 | expect(prog).toMatchSnapshot();
111 |
112 | const rout = new Routine("cRoutine");
113 | prog.addRoutine(rout);
114 | expect(prog).toMatchSnapshot();
115 |
116 | rout.addRung(new Rung("XIC(someTag)NOP();"));
117 | expect(prog).toMatchSnapshot();
118 | });
119 |
120 | test("findTag: Throws on Invalid Input", () => {
121 | const fn = name => () => prog.findTag(name);
122 |
123 | expect(fn(new Program("notATag"))).toThrow();
124 | expect(fn(12)).toThrow();
125 | expect(fn("aTag")).not.toThrow();
126 | });
127 |
128 | test("findTag: Finds Target Tag", () => {
129 | prog.addTag(new Tag("aTag", "DINT"));
130 | prog.addTag(new Tag("bTag", "SINT"));
131 |
132 | const tag = new Tag("cTag", "BOOL");
133 | prog.addTag(tag);
134 |
135 | expect(prog.findTag("aTag")).not.toBeNull();
136 | expect(prog.findTag("aTag")).toMatchSnapshot();
137 | expect(prog.findTag("bTag")).not.toBeNull();
138 | expect(prog.findTag("bTag")).toMatchSnapshot();
139 | expect(prog.findTag("cTag")).not.toBeNull();
140 | expect(prog.findTag("cTag")).toMatchSnapshot();
141 | expect(prog.findTag("dTag")).toBeNull();
142 |
143 | prog.findTag("cTag").dom.attributes.Name = "eTag";
144 | expect(prog).toMatchSnapshot();
145 | expect(tag).toMatchSnapshot();
146 | });
147 |
148 | test("findRoutine: Throws on Invalid Input", () => {
149 | const fn = name => () => prog.findRoutine(name);
150 |
151 | expect(fn(new Program("notATag"))).toThrow();
152 | expect(fn(12)).toThrow();
153 | expect(fn("aRoutine")).not.toThrow();
154 | });
155 |
156 | test("findRoutine: Finds Target Routine", () => {
157 | prog.addRoutine(new Routine("aRoutine"));
158 | prog.addRoutine(new Routine("bRoutine"));
159 |
160 | const rout = new Routine("cRoutine");
161 | prog.addRoutine(rout);
162 |
163 | expect(prog.findRoutine("aRoutine")).not.toBeNull();
164 | expect(prog.findRoutine("aRoutine")).toMatchSnapshot();
165 | expect(prog.findRoutine("bRoutine")).not.toBeNull();
166 | expect(prog.findRoutine("bRoutine")).toMatchSnapshot();
167 | expect(prog.findRoutine("cRoutine")).not.toBeNull();
168 | expect(prog.findRoutine("cRoutine")).toMatchSnapshot();
169 | expect(prog.findRoutine("dRoutine")).toBeNull();
170 |
171 | rout.addRung(new Rung("XIC(someTag)NOP();"));
172 | expect(prog.findRoutine("cRoutine")).not.toBeNull();
173 | expect(prog.findRoutine("cRoutine")).toMatchSnapshot();
174 | expect(prog).toMatchSnapshot();
175 |
176 | prog.findRoutine("aRoutine").addRung(new Rung("XIC(anotherTag)NOP();"));
177 | expect(prog).toMatchSnapshot();
178 | });
179 | });
180 |
181 | describe("Static Methods", () => {
182 | test("isProgram: Returns Appropriate Judgement", () => {
183 | let program = new Program("ProgramName", "A Description");
184 | expect(Program.isProgram(program)).toBeTruthy();
185 |
186 | program = { notAProgram: 12 };
187 | expect(Program.isProgram(program)).toBeFalsy();
188 |
189 | program = 12;
190 | expect(Program.isProgram(program)).toBeFalsy();
191 | });
192 | });
193 | });
194 |
--------------------------------------------------------------------------------
/src/routine/__snapshots__/routine.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Routine Class Methods addRung: Produces Desired Doc Model 1`] = `
4 | Routine {
5 | "_dom": Object {
6 | "attributes": Object {
7 | "Name": "IamRoutine",
8 | "Type": "RLL",
9 | "Use": "Context",
10 | },
11 | "elements": Array [
12 | Object {
13 | "elements": Array [
14 | Object {
15 | "attributes": Object {
16 | "Number": 0,
17 | "Type": "N",
18 | },
19 | "elements": Array [
20 | Object {
21 | "elements": Array [
22 | Object {
23 | "cdata": "XIC(someTag)NOP();",
24 | "type": "cdata",
25 | },
26 | ],
27 | "name": "Text",
28 | "type": "element",
29 | },
30 | ],
31 | "name": "Rung",
32 | "type": "element",
33 | },
34 | ],
35 | "name": "RLLContent",
36 | "type": "element",
37 | },
38 | ],
39 | "name": "Routine",
40 | "type": "element",
41 | },
42 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
43 | "elem_id": "33cb4159377b63ce9db11667160e39df",
44 | }
45 | `;
46 |
47 | exports[`Routine Class Methods addRung: Produces Desired Doc Model 2`] = `
48 | Routine {
49 | "_dom": Object {
50 | "attributes": Object {
51 | "Name": "IamRoutine",
52 | "Type": "RLL",
53 | "Use": "Context",
54 | },
55 | "elements": Array [
56 | Object {
57 | "elements": Array [
58 | Object {
59 | "attributes": Object {
60 | "Number": 1,
61 | "Type": "N",
62 | },
63 | "elements": Array [
64 | Object {
65 | "elements": Array [
66 | Object {
67 | "cdata": "XIC(someTag)NOP();",
68 | "type": "cdata",
69 | },
70 | ],
71 | "name": "Text",
72 | "type": "element",
73 | },
74 | ],
75 | "name": "Rung",
76 | "type": "element",
77 | },
78 | ],
79 | "name": "RLLContent",
80 | "type": "element",
81 | },
82 | ],
83 | "name": "Routine",
84 | "type": "element",
85 | },
86 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
87 | "elem_id": "33cb4159377b63ce9db11667160e39df",
88 | }
89 | `;
90 |
91 | exports[`Routine Class Methods addRung: Produces Desired Doc Model 3`] = `
92 | Routine {
93 | "_dom": Object {
94 | "attributes": Object {
95 | "Name": "IamRoutine",
96 | "Type": "RLL",
97 | "Use": "Context",
98 | },
99 | "elements": Array [
100 | Object {
101 | "elements": Array [
102 | Object {
103 | "attributes": Object {
104 | "Number": 0,
105 | "Type": "N",
106 | },
107 | "elements": Array [
108 | Object {
109 | "elements": Array [
110 | Object {
111 | "cdata": "XIC(someTag)NOP();",
112 | "type": "cdata",
113 | },
114 | ],
115 | "name": "Text",
116 | "type": "element",
117 | },
118 | ],
119 | "name": "Rung",
120 | "type": "element",
121 | },
122 | Object {
123 | "attributes": Object {
124 | "Number": 1,
125 | "Type": "N",
126 | },
127 | "elements": Array [
128 | Object {
129 | "elements": Array [
130 | Object {
131 | "cdata": "XIC(anotherTag)NOP();",
132 | "type": "cdata",
133 | },
134 | ],
135 | "name": "Text",
136 | "type": "element",
137 | },
138 | ],
139 | "name": "Rung",
140 | "type": "element",
141 | },
142 | ],
143 | "name": "RLLContent",
144 | "type": "element",
145 | },
146 | ],
147 | "name": "Routine",
148 | "type": "element",
149 | },
150 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
151 | "elem_id": "33cb4159377b63ce9db11667160e39df",
152 | }
153 | `;
154 |
155 | exports[`Routine Class New Instance Initializes with Desired Document Model 1`] = `
156 | Routine {
157 | "_dom": Object {
158 | "attributes": Object {
159 | "Name": "ARoutine",
160 | "Type": "RLL",
161 | "Use": "Context",
162 | },
163 | "elements": Array [
164 | Object {
165 | "elements": Array [],
166 | "name": "RLLContent",
167 | "type": "element",
168 | },
169 | ],
170 | "name": "Routine",
171 | "type": "element",
172 | },
173 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
174 | "elem_id": "33cb4159377b63ce9db11667160e39df",
175 | }
176 | `;
177 |
178 | exports[`Routine Class New Instance Initializes with Desired Document Model 2`] = `
179 | Routine {
180 | "_dom": Object {
181 | "attributes": Object {
182 | "Name": "ARoutine",
183 | "Type": "RLL",
184 | "Use": "Context",
185 | },
186 | "elements": Array [
187 | Object {
188 | "elements": Array [
189 | Object {
190 | "cdata": "I am a description",
191 | "type": "cdata",
192 | },
193 | ],
194 | "name": "Description",
195 | "type": "element",
196 | },
197 | Object {
198 | "elements": Array [],
199 | "name": "RLLContent",
200 | "type": "element",
201 | },
202 | ],
203 | "name": "Routine",
204 | "type": "element",
205 | },
206 | "class_id": "262fbaac4bd096afccf0fbf1fc161c89",
207 | "elem_id": "33cb4159377b63ce9db11667160e39df",
208 | }
209 | `;
210 |
--------------------------------------------------------------------------------
/src/routine/index.js:
--------------------------------------------------------------------------------
1 | const Element = require("../element");
2 | const Rung = require("../rung");
3 | const { hash } = require("../utilities");
4 |
5 | const ROUTINE_ID = hash("ROUTINE_ID_STRING");
6 |
7 | class Routine extends Element {
8 | constructor(name, description = null) {
9 | if (typeof name !== "string")
10 | throw new Error(
11 | `Routine constructor expected name of type instead got <${typeof name}>`
12 | );
13 |
14 | if (description && typeof description !== "string")
15 | throw new Error(
16 | `Routine constructor expected description of type instead got <${typeof description}>`
17 | );
18 |
19 | let attributes = {
20 | Name: name,
21 | Use: "Context",
22 | Type: "RLL" // Specify Ladder Logic
23 | };
24 |
25 | let elements = [];
26 |
27 | /* eslint-disable indent */
28 | if (description)
29 | elements.push({
30 | type: "element",
31 | name: "Description",
32 | elements: [
33 | {
34 | type: "cdata",
35 | cdata: description
36 | }
37 | ]
38 | });
39 |
40 | elements.push({
41 | type: "element",
42 | name: "RLLContent", // Routine content
43 | elements: []
44 | });
45 | /* eslint-enable indent */
46 |
47 | // Call parent class constructor
48 | super({
49 | type: "element",
50 | name: "Routine",
51 | attributes,
52 | elements
53 | });
54 |
55 | this.class_id = ROUTINE_ID;
56 | }
57 |
58 | /**
59 | * Adds a rung to the ladder content
60 | *
61 | * @param {Rung} rung
62 | * @memberof Routine
63 | */
64 | addRung(rung) {
65 | if (!Rung.isRung(rung))
66 | throw new Error(`addRung expected rung of type instead got <${typeof rung}>`);
67 |
68 | const rllContent = this.findOne("RLLContent");
69 | const rungNum = rllContent.dom.elements.length;
70 |
71 | rung.dom.attributes.Number = rungNum;
72 |
73 | rllContent.append(rung);
74 | return this;
75 | }
76 |
77 | /**
78 | * Checks if an incoming object is a Routine instance
79 | *
80 | * @static
81 | * @param {any} routine
82 | * @returns {boolean}
83 | * @memberof Tag
84 | */
85 | static isRoutine(routine) {
86 | return routine.class_id && routine.class_id === ROUTINE_ID;
87 | }
88 | }
89 |
90 | module.exports = Routine;
91 |
--------------------------------------------------------------------------------
/src/routine/routine.spec.js:
--------------------------------------------------------------------------------
1 | const Routine = require("./index");
2 | const Element = require("../element");
3 | const Rung = require("../rung");
4 |
5 | describe("Routine Class", () => {
6 | describe("New Instance", () => {
7 | it("Accepts Proper Input", () => {
8 | const fn = (name, desc = null) => () => new Routine(name, desc);
9 |
10 | expect(fn()).toThrow();
11 | expect(fn(12)).toThrow();
12 | expect(fn("IamRoutine")).not.toThrow();
13 | expect(fn("IamRoutine", 12)).toThrow();
14 | expect(fn("IamRoutine", "I am a decription")).not.toThrow();
15 | });
16 |
17 | it("Initializes with Desired Document Model", () => {
18 | let routine = new Routine("ARoutine");
19 | expect(routine).toMatchSnapshot();
20 |
21 | routine = new Routine("ARoutine", "I am a description");
22 | expect(routine).toMatchSnapshot();
23 | });
24 | });
25 |
26 | describe("Methods", () => {
27 | let rout;
28 |
29 | beforeEach(() => {
30 | rout = new Routine("IamRoutine");
31 | });
32 |
33 | test("addRung: Throws on Invalid Input", () => {
34 | const fn = rung => () => rout.addRung(rung);
35 |
36 | expect(fn(new Rung("XIC(someTag)NOP();"))).not.toThrow();
37 | expect(fn(new Element({ type: "element", name: "el" }))).toThrow();
38 | });
39 |
40 | test("addRung: Produces Desired Doc Model", () => {
41 | const r1 = new Rung("XIC(someTag)NOP();");
42 | rout.addRung(r1);
43 | expect(rout).toMatchSnapshot();
44 |
45 | r1.dom.attributes.Number = 1;
46 | expect(rout).toMatchSnapshot();
47 |
48 | r1.dom.attributes.Number = 0;
49 | rout.addRung(new Rung("XIC(anotherTag)NOP();"));
50 | expect(rout).toMatchSnapshot();
51 | });
52 | });
53 |
54 | describe("Static Methods", () => {
55 | test("isRoutine: Returns Appropriate Judgement", () => {
56 | let routine = new Routine("ARoutine");
57 | expect(Routine.isRoutine(routine)).toBeTruthy();
58 |
59 | routine = { notARoutine: 12 };
60 | expect(Routine.isRoutine(routine)).toBeFalsy();
61 |
62 | routine = 12;
63 | expect(Routine.isRoutine(routine)).toBeFalsy();
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/src/rung/__snapshots__/rung.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Rung Class New Instance Initializes with Desired Document Model 1`] = `
4 | Rung {
5 | "_dom": Object {
6 | "attributes": Object {
7 | "Number": 12,
8 | "Type": "N",
9 | },
10 | "elements": Array [
11 | Object {
12 | "elements": Array [
13 | Object {
14 | "cdata": "XIC(someTag)NOP();",
15 | "type": "cdata",
16 | },
17 | ],
18 | "name": "Text",
19 | "type": "element",
20 | },
21 | ],
22 | "name": "Rung",
23 | "type": "element",
24 | },
25 | "class_id": "b49e5b79e8662ca3b6e884d3057719d0",
26 | "elem_id": "33cb4159377b63ce9db11667160e39df",
27 | }
28 | `;
29 |
30 | exports[`Rung Class New Instance Initializes with Desired Document Model 2`] = `
31 | Rung {
32 | "_dom": Object {
33 | "attributes": Object {
34 | "Number": 12,
35 | "Type": "N",
36 | },
37 | "elements": Array [
38 | Object {
39 | "elements": Array [
40 | Object {
41 | "cdata": "I am a comment",
42 | "type": "cdata",
43 | },
44 | ],
45 | "name": "Comment",
46 | "type": "element",
47 | },
48 | Object {
49 | "elements": Array [
50 | Object {
51 | "cdata": "XIC(someTag)NOP();",
52 | "type": "cdata",
53 | },
54 | ],
55 | "name": "Text",
56 | "type": "element",
57 | },
58 | ],
59 | "name": "Rung",
60 | "type": "element",
61 | },
62 | "class_id": "b49e5b79e8662ca3b6e884d3057719d0",
63 | "elem_id": "33cb4159377b63ce9db11667160e39df",
64 | }
65 | `;
66 |
--------------------------------------------------------------------------------
/src/rung/index.js:
--------------------------------------------------------------------------------
1 | const Element = require("../element");
2 | const { hash } = require("../utilities");
3 |
4 | const RUNG_ID = hash("RUNG_ID_STRING");
5 |
6 | class Rung extends Element {
7 | constructor(logic, num = 0, comment = null) {
8 | if (typeof logic !== "string")
9 | throw new Error(
10 | `Rung constructor expected logic of type instead got <${typeof logic}>`
11 | );
12 |
13 | if (typeof num !== "number")
14 | throw new Error(
15 | `Rung constructor expected num of type instead got <${typeof num}>`
16 | );
17 |
18 | if (comment && typeof comment !== "string")
19 | throw new Error(
20 | `Rung constructor expected comment of type instead got <${typeof comment}>`
21 | );
22 |
23 | let attributes = {
24 | Number: num,
25 | Type: "N"
26 | };
27 |
28 | let elements = [];
29 |
30 | /* eslint-disable indent */
31 | if (comment)
32 | elements.push({
33 | type: "element",
34 | name: "Comment",
35 | elements: [
36 | {
37 | type: "cdata",
38 | cdata: comment
39 | }
40 | ]
41 | });
42 |
43 | elements.push({
44 | type: "element",
45 | name: "Text", // rung content
46 | elements: [
47 | {
48 | type: "cdata",
49 | cdata: logic
50 | }
51 | ]
52 | });
53 | /* eslint-enable indent */
54 |
55 | // Call parent class constructor
56 | super({
57 | type: "element",
58 | name: "Rung",
59 | attributes,
60 | elements
61 | });
62 |
63 | this.class_id = RUNG_ID;
64 | }
65 |
66 | /**
67 | * Checks if an incoming object is a rung instance
68 | *
69 | * @static
70 | * @param {any} rung
71 | * @returns {boolean}
72 | * @memberof Tag
73 | */
74 | static isRung(rung) {
75 | return rung.class_id && rung.class_id === RUNG_ID;
76 | }
77 | }
78 |
79 | module.exports = Rung;
80 |
--------------------------------------------------------------------------------
/src/rung/rung.spec.js:
--------------------------------------------------------------------------------
1 | const Rung = require("./index");
2 | const Element = require("../element");
3 |
4 | describe("Rung Class", () => {
5 | describe("New Instance", () => {
6 | it("Accepts Proper Input", () => {
7 | const fn = (logic, num, comment = null) => () => new Rung(logic, num, comment);
8 |
9 | expect(fn("XIC(someTag)NOP();")).not.toThrow();
10 | expect(fn("XIC(someTag)NOP();", "notANum")).toThrow();
11 | expect(fn(null, 12)).toThrow();
12 | expect(fn("XIC(someTag)NOP();", 12)).not.toThrow();
13 | expect(fn("XIC(someTag)NOP();", 12, 12)).toThrow();
14 | expect(fn("XIC(someTag)NOP();", 12, "I am a Comment")).not.toThrow();
15 | });
16 |
17 | it("Initializes with Desired Document Model", () => {
18 | let rung = new Rung("XIC(someTag)NOP();", 12);
19 | expect(rung).toMatchSnapshot();
20 |
21 | rung = new Rung("XIC(someTag)NOP();", 12, "I am a comment");
22 | expect(rung).toMatchSnapshot();
23 | });
24 | });
25 |
26 | describe("Static Methods", () => {
27 | test("isRung: Returns Appropriate Judgement", () => {
28 | let rung = new Rung("XIC(someTag)NOP();");
29 | expect(Rung.isRung(rung)).toBeTruthy();
30 |
31 | rung = { notARung: 12 };
32 | expect(Rung.isRung(rung)).toBeFalsy();
33 |
34 | rung = 12;
35 | expect(Rung.isRung(rung)).toBeFalsy();
36 |
37 | rung = new Element({ type: "element", name: "el" });
38 | expect(Rung.isRung(rung)).toBeFalsy();
39 | });
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/tag/__snapshots__/tag.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Tag Class New Instance Initializes with Desired Document Model 1`] = `
4 | Tag {
5 | "_dom": Object {
6 | "attributes": Object {
7 | "Class": "Standard",
8 | "DataType": "DINT",
9 | "Name": "tagName",
10 | "TagType": "Base",
11 | },
12 | "elements": Array [],
13 | "name": "Tag",
14 | "type": "element",
15 | },
16 | "class_id": "202108c6004705df7b5fd0add930619f",
17 | "elem_id": "33cb4159377b63ce9db11667160e39df",
18 | }
19 | `;
20 |
21 | exports[`Tag Class New Instance Initializes with Desired Document Model 2`] = `
22 | Tag {
23 | "_dom": Object {
24 | "attributes": Object {
25 | "Class": "Standard",
26 | "DataType": "DINT",
27 | "Name": "tagName",
28 | "TagType": "Base",
29 | },
30 | "elements": Array [
31 | Object {
32 | "elements": Array [
33 | Object {
34 | "cdata": "A Description",
35 | "type": "cdata",
36 | },
37 | ],
38 | "name": "Description",
39 | "type": "element",
40 | },
41 | ],
42 | "name": "Tag",
43 | "type": "element",
44 | },
45 | "class_id": "202108c6004705df7b5fd0add930619f",
46 | "elem_id": "33cb4159377b63ce9db11667160e39df",
47 | }
48 | `;
49 |
50 | exports[`Tag Class New Instance Initializes with Desired Document Model 3`] = `
51 | Tag {
52 | "_dom": Object {
53 | "attributes": Object {
54 | "AliasFor": "anotherTag",
55 | "Class": "Standard",
56 | "DataType": "DINT",
57 | "Name": "tagName",
58 | "TagType": "Alias",
59 | },
60 | "elements": Array [
61 | Object {
62 | "elements": Array [
63 | Object {
64 | "cdata": "A Description",
65 | "type": "cdata",
66 | },
67 | ],
68 | "name": "Description",
69 | "type": "element",
70 | },
71 | ],
72 | "name": "Tag",
73 | "type": "element",
74 | },
75 | "class_id": "202108c6004705df7b5fd0add930619f",
76 | "elem_id": "33cb4159377b63ce9db11667160e39df",
77 | }
78 | `;
79 |
80 | exports[`Tag Class New Instance Initializes with Desired Document Model 4`] = `
81 | Tag {
82 | "_dom": Object {
83 | "attributes": Object {
84 | "AliasFor": "anotherTag",
85 | "Class": "Safety",
86 | "DataType": "DINT",
87 | "Name": "tagName",
88 | "TagType": "Alias",
89 | },
90 | "elements": Array [
91 | Object {
92 | "elements": Array [
93 | Object {
94 | "cdata": "A Description",
95 | "type": "cdata",
96 | },
97 | ],
98 | "name": "Description",
99 | "type": "element",
100 | },
101 | ],
102 | "name": "Tag",
103 | "type": "element",
104 | },
105 | "class_id": "202108c6004705df7b5fd0add930619f",
106 | "elem_id": "33cb4159377b63ce9db11667160e39df",
107 | }
108 | `;
109 |
110 | exports[`Tag Class New Instance Initializes with Desired Document Model 5`] = `
111 | Tag {
112 | "_dom": Object {
113 | "attributes": Object {
114 | "AliasFor": "anotherTag",
115 | "Class": "Safety",
116 | "DataType": "DINT",
117 | "Dimensions": "[12]",
118 | "Name": "tagName",
119 | "TagType": "Alias",
120 | },
121 | "elements": Array [
122 | Object {
123 | "elements": Array [
124 | Object {
125 | "cdata": "A Description",
126 | "type": "cdata",
127 | },
128 | ],
129 | "name": "Description",
130 | "type": "element",
131 | },
132 | ],
133 | "name": "Tag",
134 | "type": "element",
135 | },
136 | "class_id": "202108c6004705df7b5fd0add930619f",
137 | "elem_id": "33cb4159377b63ce9db11667160e39df",
138 | }
139 | `;
140 |
--------------------------------------------------------------------------------
/src/tag/index.js:
--------------------------------------------------------------------------------
1 | const Element = require("../element");
2 | const { hash } = require("../utilities");
3 |
4 | const TAG_ID = hash("TAG_ID_STRING");
5 |
6 | class Tag extends Element {
7 | constructor(
8 | name,
9 | datatype,
10 | description = null,
11 | alias = null,
12 | safety = false,
13 | dimension = null
14 | ) {
15 | if (typeof name !== "string")
16 | throw new Error(
17 | `Tag constructor expected name of type instead got <${typeof name}>`
18 | );
19 |
20 | if (typeof datatype !== "string")
21 | throw new Error(
22 | `Tag constructor expected datatype of type instead got <${typeof datatype}>`
23 | );
24 |
25 | if (description && typeof description !== "string")
26 | throw new Error(
27 | `Tag constructor expected description of type instead got <${typeof description}>`
28 | );
29 |
30 | if (alias && typeof alias !== "string")
31 | throw new Error(
32 | `Tag constructor expected alias of type instead got <${typeof alias}>`
33 | );
34 |
35 | if (safety && typeof safety !== "boolean")
36 | throw new Error(
37 | `Tag constructor expected safety of type instead got <${typeof safety}>`
38 | );
39 |
40 | if (dimension && typeof dimension !== "string")
41 | throw new Error(
42 | `Tag constructor expected dimension of type instead got <${typeof dimension}>`
43 | );
44 |
45 | let attributes = {
46 | Name: name,
47 | DataType: datatype,
48 | TagType: alias ? "Alias" : "Base",
49 | Class: safety ? "Safety" : "Standard"
50 | };
51 |
52 | if (alias) attributes.AliasFor = alias;
53 | if (dimension) attributes.Dimensions = dimension;
54 |
55 | let elements = [];
56 |
57 | /* eslint-disable indent */
58 | if (description)
59 | elements.push({
60 | type: "element",
61 | name: "Description",
62 | elements: [
63 | {
64 | type: "cdata",
65 | cdata: description
66 | }
67 | ]
68 | });
69 | /* eslint-enable indent */
70 |
71 | // Call parent class constructor
72 | super({
73 | type: "element",
74 | name: "Tag",
75 | attributes,
76 | elements
77 | });
78 |
79 | this.class_id = TAG_ID;
80 | }
81 |
82 | /**
83 | * Checks if an incoming object is a tag instance
84 | *
85 | * @static
86 | * @param {any} tag
87 | * @returns {boolean}
88 | * @memberof Tag
89 | */
90 | static isTag(tag) {
91 | return tag.class_id && tag.class_id === TAG_ID;
92 | }
93 | }
94 |
95 | module.exports = Tag;
96 |
--------------------------------------------------------------------------------
/src/tag/tag.spec.js:
--------------------------------------------------------------------------------
1 | const Tag = require("./index");
2 |
3 | describe("Tag Class", () => {
4 | describe("New Instance", () => {
5 | it("Accepts Proper Input", () => {
6 | const fn = (
7 | name,
8 | datatype,
9 | desc = null,
10 | alias = null,
11 | safety = false,
12 | dim = null
13 | ) => () => new Tag(name, datatype, desc, alias, safety, dim);
14 |
15 | expect(fn("tagName")).toThrow();
16 | expect(fn(null, "DINT")).toThrow();
17 | expect(fn("tagName", "DINT")).not.toThrow();
18 | expect(fn("tagName", "DINT", 12)).toThrow();
19 | expect(fn("tagName", "DINT", "A Description")).not.toThrow();
20 | expect(fn("tagName", "DINT", "A Description", 12)).toThrow();
21 | expect(fn("tagName", "DINT", "A Description", "anotherTag")).not.toThrow();
22 | expect(fn("tagName", "DINT", "A Description", "anotherTag", 12)).toThrow();
23 | expect(fn("tagName", "DINT", "A Description", "anotherTag", true)).not.toThrow();
24 | expect(fn("tagName", "DINT", "A Description", "anotherTag", true, 12)).toThrow();
25 | expect(
26 | fn("tagName", "DINT", "A Description", "anotherTag", true, "[10]")
27 | ).not.toThrow();
28 | });
29 |
30 | it("Initializes with Desired Document Model", () => {
31 | let tag = new Tag("tagName", "DINT");
32 | expect(tag).toMatchSnapshot();
33 |
34 | tag = new Tag("tagName", "DINT", "A Description");
35 | expect(tag).toMatchSnapshot();
36 |
37 | tag = new Tag("tagName", "DINT", "A Description", "anotherTag");
38 | expect(tag).toMatchSnapshot();
39 |
40 | tag = new Tag("tagName", "DINT", "A Description", "anotherTag", true);
41 | expect(tag).toMatchSnapshot();
42 |
43 | tag = new Tag("tagName", "DINT", "A Description", "anotherTag", true, "[12]");
44 | expect(tag).toMatchSnapshot();
45 | });
46 | });
47 |
48 | describe("Static Methods", () => {
49 | test("isTag: Returns Appropriate Judgement", () => {
50 | let tag = new Tag("tagName", "DINT");
51 | expect(Tag.isTag(tag)).toBeTruthy();
52 |
53 | tag = { notATag: 12 };
54 | expect(Tag.isTag(tag)).toBeFalsy();
55 |
56 | tag = 12;
57 | expect(Tag.isTag(tag)).toBeFalsy();
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/utilities/index.js:
--------------------------------------------------------------------------------
1 | const crypto = require("crypto");
2 |
3 | /**
4 | * Generates Unique ID for Each Instance
5 | * based on the Generated EPATH
6 | *
7 | * @param {buffer} input - EPATH of Tag
8 | * @returns {string} hash
9 | */
10 | const hash = input => {
11 | return crypto
12 | .createHash("md5")
13 | .update(input)
14 | .digest("hex");
15 | };
16 |
17 | module.exports = { hash };
18 |
--------------------------------------------------------------------------------
/src/utilities/utilities.spec.js:
--------------------------------------------------------------------------------
1 | const { hash } = require("./index");
2 |
3 | describe("Utilities", () => {
4 | test("hash: Produces Correct Output", () => {
5 | expect(hash("hello world") === hash("hello world")).toBeTruthy();
6 | expect(hash("I am a document") === hash("I am a document")).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------