diff --git a/.gitignore b/.gitignore index 3c5db48e..a9a456e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# apple +.DS_Store + +# vscode +.vscode/settings.json + # Logs logs *.log diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..aac62d55 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "biomejs.biome", + "svelte.svelte-vscode" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d047dad..0214cdfe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "editor.insertSpaces": false + "editor.tabSize": 2, // Set the number of spaces per tab + "editor.insertSpaces": false, // Use tabs for indentation + "editor.formatOnSave": true, // Automatically format the document on save + "editor.defaultFormatter": null, // Use the built-in formatter + // Svelte settings + "svelte.plugin.svelte.format.enable": true, // Enable Svelte formatting + "svelte.plugin.svelte.format.config.singleQuote": true, // Use single quotes } \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..073dd58c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders 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, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..44552ef0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# How to contribute + +A high-level overview of how the documentation is organized will help you know where to look for certain things: + +* [☑ How-Tos](#how-tos) guide you through the steps involved in addressing key problems and use-cases. They are more advanced than tutorials and assume some knowledge of how the plugins work. +* [👷 Guides](#guides) describe best practices and principles we follow. +* [🗃 References](#references) contain technical reference for APIs and other aspects of the plugins. They describe how it works and how to use it but assume that you have a basic understanding of key concepts. +* [👩‍🏫 Explanations](#explanations) about the architecture and general design of the project + +## ☑ How-Tos + +* [Setup Project](./doc//how-to/setup.md) + +## 👷 Guides + +* [Documentation Guidelines](./doc/guidelines/doc_guidelines.md) +* [Documentation Style Guide](./doc/guidelines/doc_styleguide.md) +* [Code Style guidelines](./doc/guidelines/code_style.md) +* [Development process](./doc/guidelines/dev_process.md) + +## 👩‍🏫 Explanations +* [1. Record architecture decisions](./doc/adr/0001-record-architecture-decisions.md) +* [2. Use Monorepo-Polypackage Setup](./doc/adr/0002-use-monorepo-polypackage-setup.md) +* [3. Release Process](./doc/adr/0003-release-process.md) + + +## 🗃 References + +* [Release Process](./doc/references/architecture/release-process.md) diff --git a/README.md b/README.md index 549d0aa3..ecb328f8 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,11 @@ This repository contains plugins for the [OpenSCD ↗](https://github.com/openscd/open-scd) project. -## Documentation -A high-level overview of how the documentation is organized will help you know where to look for certain things: +## Contribution -- [☑ How-Tos](#how-tos) guide you through the steps involved in addressing key problems and use-cases. They are more advanced than tutorials and assume some knowledge of how the plugins work. -- [👷 Guides](#guides) describe best practices and principles we follow. -- [🗃 References](#references) contain technical reference for APIs and other aspects of the plugins. They describe how it works and how to use it but assume that you have a basic understanding of key concepts. -- [👩‍🏫 Explanations](#explanations) about the architecture and general design of the project +Check out our [contributing guidelines](/CONTRIBUTING.md) for ways to contribute. -## ☑ How-Tos +## License -- [Setup Project](./doc//how-to/setup.md) - -## 👷 Guides - -- [Documentation Guidelines](./doc/guidelines/doc_guidelines.md) -- [Documentation Style Guide](./doc/guidelines/doc_styleguide.md) - -## 👩‍🏫 Explanations -- [1. Record architecture decisions](./doc/adr/0001-record-architecture-decisions.md) -- [2. Use Monorepo-Polypackage Setup](./doc/adr/0002-use-monorepo-polypackage-setup.md) -- [3. Release Process](./doc/adr/0003-release-process.md) - - -## 🗃 References - -- [Release Process](./doc/references/architecture/release-process.md) +Content is released under the [Apache License Version 2.0](/LICENSE). \ No newline at end of file diff --git a/Readme.md b/Readme.md index 549d0aa3..ecb328f8 100644 --- a/Readme.md +++ b/Readme.md @@ -2,30 +2,11 @@ This repository contains plugins for the [OpenSCD ↗](https://github.com/openscd/open-scd) project. -## Documentation -A high-level overview of how the documentation is organized will help you know where to look for certain things: +## Contribution -- [☑ How-Tos](#how-tos) guide you through the steps involved in addressing key problems and use-cases. They are more advanced than tutorials and assume some knowledge of how the plugins work. -- [👷 Guides](#guides) describe best practices and principles we follow. -- [🗃 References](#references) contain technical reference for APIs and other aspects of the plugins. They describe how it works and how to use it but assume that you have a basic understanding of key concepts. -- [👩‍🏫 Explanations](#explanations) about the architecture and general design of the project +Check out our [contributing guidelines](/CONTRIBUTING.md) for ways to contribute. -## ☑ How-Tos +## License -- [Setup Project](./doc//how-to/setup.md) - -## 👷 Guides - -- [Documentation Guidelines](./doc/guidelines/doc_guidelines.md) -- [Documentation Style Guide](./doc/guidelines/doc_styleguide.md) - -## 👩‍🏫 Explanations -- [1. Record architecture decisions](./doc/adr/0001-record-architecture-decisions.md) -- [2. Use Monorepo-Polypackage Setup](./doc/adr/0002-use-monorepo-polypackage-setup.md) -- [3. Release Process](./doc/adr/0003-release-process.md) - - -## 🗃 References - -- [Release Process](./doc/references/architecture/release-process.md) +Content is released under the [Apache License Version 2.0](/LICENSE). \ No newline at end of file diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..ca68e6d3 --- /dev/null +++ b/biome.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 4 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + }, + "ignore": [ + "**/*.cjs", + "**/dist", + "**/.DS_Store", + "**/node_modules", + "**/.svelte-kit", + "**/.env", + "**/.env.*", + "**/pnpm-lock.yaml", + "**/package-lock.json", + "**/yarn.lock" + ] + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingCommas": "none" + } + }, + "json": { + "formatter": { + "enabled": true + } + }, + "overrides": [ + { + "include": ["*.svelte"] + } + ] +} diff --git a/doc/adr/0004-structure-convention.md b/doc/adr/0004-structure-convention.md new file mode 100644 index 00000000..d0023ef9 --- /dev/null +++ b/doc/adr/0004-structure-convention.md @@ -0,0 +1,70 @@ +# 4. Structure convention + +Date: 2024-09-24 + +## Status + +Accepted + +## Context + +The core packages gains more and more logic, we need a way to have small piece of code that are easily maintainable. + +## Decision + +### Domain Driven +Code is split by domain. Each domain will have its own directory, and within each directory, the code will be further organized by functionality. This structure will help in maintaining a clean and manageable codebase. + +### Naming Convention + +To maintain consistency across the codebase, we will adopt a clear and concise naming convention. All files and directories will use `kebab-case` for their names. Except for main or index file, each file will be named using the format `name-of-file.name-of-folder.ts`, ensuring uniformity and readability. + +#### Examples + +- Directories: `data-type-templates` +- Files: `queries.data-type-templates.ts` + +It goes the same way for `service` or `utility` files : `service.data-type-templates.ts` + +### Type Declaration Files + +Type declaration files are special TypeScript files that end with the `.d.ts` extension. These files are used exclusively for declaring types and do not contain any executable code. By using `.d.ts` files, we ensure that type definitions are centralized, easily accessible, and separate from implementation code. These files should remains next to the domain, like the others. + +#### Examples + +- `types.data-type-templates.d.ts`: Contains type definitions related to data type templates. (inside the `data-type-templates` folder) +- `types.scd-queries.d.ts`: Contains type definitions related to SCD queries. (inside the `scd-queries` folder) + +This approach promotes better type management and reduces redundancy, contributing to a more organized and maintainable codebase. + + +#### Directory Structure + +The proposed directory structure is as follows: + +``` +/src + /domain1 + /featureA + /featureB + /domain2 + /featureC + /featureD +``` + +### Benefits + +- **Modularity**: Each domain and feature can be developed and tested independently. +- **Scalability**: New domains and features can be added without affecting existing code. +- **Maintainability**: Easier to locate and fix bugs or add new functionality. + +### Drawbacks + +- **Initial Setup**: Requires initial effort to set up the directory structure. +- **Learning Curve**: New developers may need time to understand the structure. + +## Consequences + +Adopting this structure will require refactoring existing code to fit into the new directories. However, the long-term benefits of a more organized and maintainable codebase outweigh the initial setup costs. + + diff --git a/doc/guidelines/code_style.md b/doc/guidelines/code_style.md index f1809513..43fd89f1 100644 --- a/doc/guidelines/code_style.md +++ b/doc/guidelines/code_style.md @@ -1,8 +1,16 @@ # Code Style +## Formatting & Linting -- do not use prettier for formatting -- use the [barrel pattern](./barrel-pattern.md) -- files in a module should not import from their own index file (except tests, demo pages) -- tests and demo pages (+page.svelte) should import from the module's index to ensure that the module's public API is used -- files outside of a module should always import from the module's index \ No newline at end of file +We use [BiomeJS](https://biomejs.dev/) at the root of the project, you can find a [vscode extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) to use it. + +## Programming + +* use the [barrel pattern](./barrel-pattern.md) +* files in a module should not import from their own index file (except tests, demo pages) +* tests and demo pages (+page.svelte) should import from the module's index to ensure that the module's public API is used +* files outside of a module should always import from the module's index + +## Commit convention + +We follow the rules of the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) \ No newline at end of file diff --git a/doc/guidelines/dev_process.md b/doc/guidelines/dev_process.md new file mode 100644 index 00000000..94c9125c --- /dev/null +++ b/doc/guidelines/dev_process.md @@ -0,0 +1,32 @@ +# Development process + +The [Backlog](https://github.com/orgs/sprinteins/projects/7/views/1) and current [Sprint Backlog](https://github.com/orgs/sprinteins/projects/7/) can be found on github, next to the [project repo](https://github.com/sprinteins/oscd-plugins). + +You need to be able to interact with Work Items : +* create, read, update, and delete issues +* add them to the project in order to make them appear in the backlog + +## Version control management practice + +We are following the [trunk-based development rules](https://trunkbaseddevelopment.com/) : one trunk (main), short life feature branches that should not live longer than a sprint. + +## Sprint Backlog + +* When you are assigned to an issue from the Sprint Backlog, please do the following: + * Create a new branch following this convention: + * `{type}/{scope}/{feature-name}` with : + * `type`: `fix` | `feat` + * `scope`: plugin name | `monorepo` + * `feature-name`: name of the feature (corresponding to the US) + * ie: `feat/type-designer/board-colums` + * Create a draft pull request + * Assign one or more reviewers + * Commit your changes + +* When the issue is mature enough, mark your pull request as ready. + +* Once the reviewer has validated the pull request, you are allowed to merge into main and delete the created branch. + +## Changelog + +Before each iteration, a changelog should list the last changes and the upcoming feature, as described by [keepachangelog](https://keepachangelog.com/en/1.1.0/) diff --git a/doc/how-to/setup.md b/doc/how-to/setup.md index a1caa46d..59b80aa7 100644 --- a/doc/how-to/setup.md +++ b/doc/how-to/setup.md @@ -5,10 +5,24 @@ ## Project dependencies -- NodeJS -- pnpm: `sudo corepack enable` +* Version control system [Git](https://git-scm.com) + * On MacOS: `brew install git` +* [NodeJS LTS](https://nodejs.org) (actual in use is 20.17.0) + * On MacOS: `brew install node@20` +* Package manager [pnpm](https://pnpm.io/) + * On MacOS: `brew install pnpm` + * Enable corepack afterwards : `sudo corepack enable` +* If you are using VSCode IDE: + * [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) ## Core's dependency -- chrome or chromium installed and available in terminal - `brew install chromium` \ No newline at end of file +* Chrome or [Chromium](https://www.chromium.org) installed and available in terminal + * On MacOS: `brew install chromium` + +## Miscellaneous + +* Team member of the github [repo](https://github.com/sprinteins/oscd-plugins) +* Team member of the github [TransnetBW Project](https://github.com/orgs/sprinteins/projects/7) +* Figma Read-Access +* Mural Read-Access diff --git a/package.json b/package.json index 04cd59b6..edadbeee 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,30 @@ { - "name": "@oscd-plugins/network", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --mode=DEV", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.json", - "prepare": "husky install" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^2.0.2", - "@tsconfig/svelte": "^3.0.0", - "husky": "^8.0.3", - "svelte": "^3.55.1", - "svelte-check": "^2.10.3", - "tslib": "^2.5.0", - "turbo": "^1.8.3", - "typescript": "^4.9.3", - "vite": "^4.1.0" - }, - "dependencies": {} -} \ No newline at end of file + "name": "@oscd-plugins/network", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --mode=DEV", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "//====== FORMAT & LINT ======//": "", + "biome:check": "biome check .", + "biome:fix": "biome check . --write", + "//====== PREPARE ======//": "", + "prepare": "husky install" + }, + "devDependencies": { + "@biomejs/biome": "1.9.2", + "@sveltejs/vite-plugin-svelte": "^2.0.2", + "@tsconfig/svelte": "^3.0.0", + "husky": "^8.0.3", + "svelte": "^3.55.1", + "svelte-check": "^2.10.3", + "tslib": "^2.5.0", + "turbo": "^1.8.3", + "typescript": "^4.9.3", + "vite": "^4.1.0" + }, + "packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c" +} diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js deleted file mode 100644 index d0143c26..00000000 --- a/packages/core/.eslintrc.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - }, - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - overrides: [], - ignorePatterns: [".eslintrc.js", "*.cjs", "dist"], - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - }, - rules: { - semi: ["error", "never"], - quotes: ["error", "double"], - indent: ["error", "tab"], - "no-tabs": "off", - "comma-dangle": ["error", "always-multiline"], - "key-spacing": ["error", { "align": "value" }], - }, -} diff --git a/packages/core/package.json b/packages/core/package.json index 4156adfc..d73f07e5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,48 +1,46 @@ { - "name": "@oscd-plugins/core", - "version": "1.0.0", - "description": "", - "main": "src/index.ts", - "types": "src/index.ts", - "//types": "dist/src/index.d.ts", - "scripts": { - "//build": "tsc --project .", - "esbuild": "esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node", - "esbuild:watch": "npm run esbuild -- --watch", - "build": "concurrently 'npm:esbuild' 'npm:types'", - "build:watch": "concurrently 'npm:esbuild:watch' 'npm:types:watch'", - "types": "tsc --project .", - "types:watch": "npm run types -- --watch", - "//build:watch": "npm run build -- --watch", - "dev": "npm run build:watch", - "test": "npm run test:watch --run", - "test:watch": "vitest", - "lint": "eslint .", - "fix": "npm run lint -- --fix --quiet" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@types/xml2js": "^0.4.11", - "@typescript-eslint/eslint-plugin": "^5.43.0", - "@vitest/browser": "^0.29.7", - "concurrently": "^7.6.0", - "eslint": "^8.0.1", - "eslint-config-standard-with-typescript": "^34.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0", - "eslint-plugin-promise": "^6.0.0", - "happy-dom": "^8.9.0", - "safaridriver": "^0.0.4", - "typescript": "4.9.4", - "vitest": "^0.29.7", - "webdriverio": "^8.6.7" - }, - "dependencies": { - "esbuild": "^0.17.12", - "fast-xml-parser": "^4.1.3", - "xml2js": "^0.4.23", - "zod": "^3.21.4" - } -} \ No newline at end of file + "name": "@oscd-plugins/core", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "types": "src/index.ts", + "//types": "dist/src/index.d.ts", + "scripts": { + "//build": "tsc --project .", + "esbuild": "esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm --platform=node", + "esbuild:watch": "npm run esbuild -- --watch", + "build": "concurrently 'npm:esbuild' 'npm:types'", + "build:watch": "concurrently 'npm:esbuild:watch' 'npm:types:watch'", + "types": "tsc --project .", + "types:watch": "npm run types -- --watch", + "//build:watch": "npm run build -- --watch", + "dev": "npm run build:watch", + "test": "npm run test:watch --run", + "test:watch": "vitest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/xml2js": "^0.4.11", + "@typescript-eslint/eslint-plugin": "^5.43.0", + "@vitest/browser": "^0.29.7", + "concurrently": "^7.6.0", + "eslint": "^8.0.1", + "eslint-config-standard-with-typescript": "^34.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "happy-dom": "^8.9.0", + "safaridriver": "^0.0.4", + "typescript": "4.9.4", + "vitest": "^0.29.7", + "webdriverio": "^8.6.7" + }, + "dependencies": { + "esbuild": "^0.17.12", + "fast-xml-parser": "^4.1.3", + "xml2js": "^0.4.23", + "zod": "^3.21.4" + } +} diff --git a/packages/core/src/constants/element.constant.ts b/packages/core/src/constants/element.constant.ts new file mode 100644 index 00000000..7d573308 --- /dev/null +++ b/packages/core/src/constants/element.constant.ts @@ -0,0 +1,7 @@ +export const ELEMENT_NAMES = { + substation: 'Substation', + voltageLevel: 'Voltage Level', + bay: 'Bay', + ied: 'IED', + lDevice: 'Logical Device' +} as const diff --git a/packages/core/src/constants/index.ts b/packages/core/src/constants/index.ts new file mode 100644 index 00000000..13d24fbd --- /dev/null +++ b/packages/core/src/constants/index.ts @@ -0,0 +1 @@ +export * from './element.constant' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c58a773d..071b35fc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,2 +1,12 @@ -export * from "./scd" -export * from "./use-cases" +export * from './use-cases' + +// SCD +export * from './scd-queries' +export * from './scd-xml' +export * from './scd-events-v1' + +// CONSTANTS +export * from './constants/element.constant' + +// UTILS +export type * from './utils' diff --git a/packages/core/src/network/index.ts b/packages/core/src/network/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/core/src/network/network.ts b/packages/core/src/network/network.ts deleted file mode 100644 index 2b0fe8b4..00000000 --- a/packages/core/src/network/network.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class Network { - -} \ No newline at end of file diff --git a/packages/core/src/scd-events-v1/index.ts b/packages/core/src/scd-events-v1/index.ts new file mode 100644 index 00000000..acce3731 --- /dev/null +++ b/packages/core/src/scd-events-v1/index.ts @@ -0,0 +1 @@ +export * from './scd-edit-events' diff --git a/packages/core/src/scd-events-v1/scd-edit-events.ts b/packages/core/src/scd-events-v1/scd-edit-events.ts new file mode 100644 index 00000000..eee90a8e --- /dev/null +++ b/packages/core/src/scd-events-v1/scd-edit-events.ts @@ -0,0 +1,183 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE DIRECTLY ! MAKE A PR TO MODIFY THE CORE LIBRARY. // +// AS FOR NOW THE OPENSCD EVENTS PACKAGE IS NOT PUBLISHED // +// WE USE A COPY OF ITS CORE EDITOR FUNCTIONALITIES - THIS IS THE V1 MARK AS DEPRECATED BUT STILL IN USE // +// SEE SOURCE https://github.com/openscd/open-scd/blob/main/packages/core/foundation/deprecated/editor.ts // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export type Initiator = 'user' | 'system' | 'undo' | 'redo' | string + +/** Inserts `new.element` to `new.parent` before `new.reference`. */ +export interface Create { + new: { parent: Node; element: Node; reference?: Node | null } + derived?: boolean + checkValidity?: () => boolean +} +/** Removes `old.element` from `old.parent` before `old.reference`. */ +export interface Delete { + old: { parent: Node; element: Node; reference?: Node | null } + derived?: boolean + checkValidity?: () => boolean +} +/** Reparents of `old.element` to `new.parent` before `new.reference`. */ +export interface Move { + old: { parent: Element; element: Element; reference?: Node | null } + new: { parent: Element; reference?: Node | null } + derived?: boolean + checkValidity?: () => boolean +} +/** Replaces `old.element` with `new.element`, keeping element children. */ +export interface Replace { + old: { element: Element } + new: { element: Element } + derived?: boolean + checkValidity?: () => boolean +} +/** Swaps `element`s `oldAttributes` with `newAttributes` */ +export interface Update { + element: Element + oldAttributes: Record + newAttributes: Record + derived?: boolean + checkValidity?: () => boolean +} + +export type SimpleAction = Update | Create | Replace | Delete | Move +export type ComplexAction = { + actions: SimpleAction[] + title: string + derived?: boolean +} +/** Represents an intended or committed change to some `Element`. */ +export type EditorAction = SimpleAction | ComplexAction + +export function isCreate(action: EditorAction): action is Create { + return ( + (action as Replace).old === undefined && + (action as Create).new?.parent !== undefined && + (action as Create).new?.element !== undefined + ) +} +export function isDelete(action: EditorAction): action is Delete { + return ( + (action as Delete).old?.parent !== undefined && + (action as Delete).old?.element !== undefined && + (action as Replace).new === undefined + ) +} +export function isMove(action: EditorAction): action is Move { + return ( + (action as Move).old?.parent !== undefined && + (action as Move).old?.element !== undefined && + (action as Move).new?.parent !== undefined && + (action as Replace).new?.element == undefined + ) +} +export function isReplace(action: EditorAction): action is Replace { + return ( + (action as Move).old?.parent === undefined && + (action as Replace).old?.element !== undefined && + (action as Move).new?.parent === undefined && + (action as Replace).new?.element !== undefined + ) +} +export function isUpdate(action: EditorAction): action is Update { + return ( + (action as Replace).old === undefined && + (action as Replace).new === undefined && + (action as Update).element !== undefined && + (action as Update).newAttributes !== undefined && + (action as Update).oldAttributes !== undefined + ) +} +export function isSimple(action: EditorAction): action is SimpleAction { + return !((action).actions instanceof Array) +} + +//** return `Update` action for `element` adding `oldAttributes` */ +export function createUpdateAction( + element: Element, + newAttributes: Record +): Update { + const oldAttributes: Record = {} + Array.from(element.attributes).forEach((attr) => { + oldAttributes[attr.name] = attr.value + }) + + return { element, oldAttributes, newAttributes } +} + +/** Throws an error bearing `message`, never returning. */ +export function unreachable(message: string): never { + throw new Error(message) +} + +/** @returns an [[`EditorAction`]] with opposite effect of `action`. */ +export function invert(action: EditorAction): EditorAction { + if (!isSimple(action)) { + const inverse: ComplexAction = { + title: action.title, + derived: action.derived, + actions: [] + } + action.actions.forEach((element) => + inverse.actions.unshift(invert(element)) + ) + return inverse + } + + const metaData = { + derived: action.derived, + checkValidity: action.checkValidity + } + if (isCreate(action)) return { old: action.new, ...metaData } + else if (isDelete(action)) return { new: action.old, ...metaData } + else if (isMove(action)) + return { + old: { + parent: action.new.parent, + element: action.old.element, + reference: action.new.reference + }, + new: { parent: action.old.parent, reference: action.old.reference }, + ...metaData + } + else if (isReplace(action)) + return { new: action.old, old: action.new, ...metaData } + else if (isUpdate(action)) + return { + element: action.element, + oldAttributes: action.newAttributes, + newAttributes: action.oldAttributes, + ...metaData + } + else return unreachable('Unknown EditorAction type in invert.') +} + +/** Represents some intended modification of a `Document` being edited. */ +export interface EditorActionDetail { + action: T + initiator?: Initiator +} +export type EditorActionEvent = CustomEvent< + EditorActionDetail +> + +export function newActionEvent( + action: T, + initiator: Initiator = 'user', + eventInitDict?: CustomEventInit>> +): EditorActionEvent { + return new CustomEvent>('editor-action', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { action, initiator, ...eventInitDict?.detail } + }) +} + +declare global { + interface ElementEventMap { + ['editor-action']: EditorActionEvent + } +} diff --git a/packages/core/src/scd-events/index.ts b/packages/core/src/scd-events/index.ts new file mode 100644 index 00000000..8119b3e7 --- /dev/null +++ b/packages/core/src/scd-events/index.ts @@ -0,0 +1 @@ +export * from "./scd-edit-events" \ No newline at end of file diff --git a/packages/core/src/scd-events/scd-edit-events.ts b/packages/core/src/scd-events/scd-edit-events.ts new file mode 100644 index 00000000..fbd95c0b --- /dev/null +++ b/packages/core/src/scd-events/scd-edit-events.ts @@ -0,0 +1,184 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE DIRECTLY ! MAKE A PR TO MODIFY THE CORE LIBRARY. // +// AS FOR NOW THE OPENSCD EVENTS PACKAGE IS NOT PUBLISHED // +// WE USE A COPY OF ITS CORE EDITOR FUNCTIONALITIES - THIS IS THE V1 MARK AS DEPRECATED BUT STILL IN USE // +// SEE SOURCE https://github.com/openscd/open-scd/blob/main/packages/core/foundation/deprecated/editor.ts // +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +export type Initiator = 'user' | 'system' | 'undo' | 'redo' | string; + +/** Inserts `new.element` to `new.parent` before `new.reference`. */ +export interface Create { + new: { parent: Node; element: Node; reference?: Node | null }; + derived?: boolean; + checkValidity?: () => boolean; +} +/** Removes `old.element` from `old.parent` before `old.reference`. */ +export interface Delete { + old: { parent: Node; element: Node; reference?: Node | null }; + derived?: boolean; + checkValidity?: () => boolean; +} +/** Reparents of `old.element` to `new.parent` before `new.reference`. */ +export interface Move { + old: { parent: Element; element: Element; reference?: Node | null }; + new: { parent: Element; reference?: Node | null }; + derived?: boolean; + checkValidity?: () => boolean; +} +/** Replaces `old.element` with `new.element`, keeping element children. */ +export interface Replace { + old: { element: Element }; + new: { element: Element }; + derived?: boolean; + checkValidity?: () => boolean; +} +/** Swaps `element`s `oldAttributes` with `newAttributes` */ +export interface Update { + element: Element; + oldAttributes: Record; + newAttributes: Record; + derived?: boolean; + checkValidity?: () => boolean; +} + +export type SimpleAction = Update | Create | Replace | Delete | Move; +export type ComplexAction = { + actions: SimpleAction[]; + title: string; + derived?: boolean; +}; +/** Represents an intended or committed change to some `Element`. */ +export type EditorAction = SimpleAction | ComplexAction; + +export function isCreate(action: EditorAction): action is Create { + return ( + (action as Replace).old === undefined && + (action as Create).new?.parent !== undefined && + (action as Create).new?.element !== undefined + ); +} +export function isDelete(action: EditorAction): action is Delete { + return ( + (action as Delete).old?.parent !== undefined && + (action as Delete).old?.element !== undefined && + (action as Replace).new === undefined + ); +} +export function isMove(action: EditorAction): action is Move { + return ( + (action as Move).old?.parent !== undefined && + (action as Move).old?.element !== undefined && + (action as Move).new?.parent !== undefined && + (action as Replace).new?.element == undefined + ); +} +export function isReplace(action: EditorAction): action is Replace { + return ( + (action as Move).old?.parent === undefined && + (action as Replace).old?.element !== undefined && + (action as Move).new?.parent === undefined && + (action as Replace).new?.element !== undefined + ); +} +export function isUpdate(action: EditorAction): action is Update { + return ( + (action as Replace).old === undefined && + (action as Replace).new === undefined && + (action as Update).element !== undefined && + (action as Update).newAttributes !== undefined && + (action as Update).oldAttributes !== undefined + ); +} +export function isSimple(action: EditorAction): action is SimpleAction { + return !((action).actions instanceof Array); +} + +//** return `Update` action for `element` adding `oldAttributes` */ +export function createUpdateAction( + element: Element, + newAttributes: Record +): Update { + const oldAttributes: Record = {}; + Array.from(element.attributes).forEach(attr => { + oldAttributes[attr.name] = attr.value; + }); + + return { element, oldAttributes, newAttributes }; +} + +/** Throws an error bearing `message`, never returning. */ +export function unreachable(message: string): never { + throw new Error(message); +} + +/** @returns an [[`EditorAction`]] with opposite effect of `action`. */ +export function invert(action: EditorAction): EditorAction { + if (!isSimple(action)) { + const inverse: ComplexAction = { + title: action.title, + derived: action.derived, + actions: [], + }; + action.actions.forEach(element => + inverse.actions.unshift(invert(element)) + ); + return inverse; + } + + const metaData = { + derived: action.derived, + checkValidity: action.checkValidity, + }; + if (isCreate(action)) return { old: action.new, ...metaData }; + else if (isDelete(action)) return { new: action.old, ...metaData }; + else if (isMove(action)) + return { + old: { + parent: action.new.parent, + element: action.old.element, + reference: action.new.reference, + }, + new: { parent: action.old.parent, reference: action.old.reference }, + ...metaData, + }; + else if (isReplace(action)) + return { new: action.old, old: action.new, ...metaData }; + else if (isUpdate(action)) + return { + element: action.element, + oldAttributes: action.newAttributes, + newAttributes: action.oldAttributes, + ...metaData, + }; + else return unreachable('Unknown EditorAction type in invert.'); +} + +/** Represents some intended modification of a `Document` being edited. */ +export interface EditorActionDetail { + action: T; + initiator?: Initiator; +} +export type EditorActionEvent = CustomEvent< + EditorActionDetail +>; + +export function newActionEvent( + action: T, + initiator: Initiator = 'user', + eventInitDict?: CustomEventInit>> +): EditorActionEvent { + return new CustomEvent>('editor-action', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { action, initiator, ...eventInitDict?.detail }, + }); +} + +declare global { + interface ElementEventMap { + ['editor-action']: EditorActionEvent; + } +} diff --git a/packages/core/src/scd-queries/data-type-templates/index.ts b/packages/core/src/scd-queries/data-type-templates/index.ts new file mode 100644 index 00000000..1002d50e --- /dev/null +++ b/packages/core/src/scd-queries/data-type-templates/index.ts @@ -0,0 +1,6 @@ +/// QUERIES +export * from './queries.data-type-templates' +// SERVICES +export * from './service.data-type-templates' +// TYPES +export type * from './types.data-type-templates' diff --git a/packages/core/src/scd-queries/data-type-templates/queries.data-type-templates.ts b/packages/core/src/scd-queries/data-type-templates/queries.data-type-templates.ts new file mode 100644 index 00000000..f34593b7 --- /dev/null +++ b/packages/core/src/scd-queries/data-type-templates/queries.data-type-templates.ts @@ -0,0 +1,89 @@ +import { SCDQueries } from '../scd-queries' +// CONSTANTS +import { ELEMENT_NAMES } from '../../constants/element.constant' +// TYPES +import type { + CommonOptions, + LDeviceElement, + BayElement, + IEDElement, + VoltageLevelElement, + SubstationElement +} from '../types.scd-queries' +import type { DataTypeTemplates } from './types.data-type-templates' + +export class DataTypeTemplatesQueries extends SCDQueries { + constructor(root: Element) { + super(root) + if (!ELEMENT_NAMES) { + throw new Error('ELEMENT_NAMES is not initialized') + } + } + + public static SelectorDataTypeTemplates = 'DataTypeTemplates' + public searchDataTypeTemplates(): DataTypeTemplates.RootElement | null { + return this.searchSingleElement( + DataTypeTemplatesQueries.SelectorDataTypeTemplates, + [] + ) + } + + public static SelectorLDeviceType = 'LDeviceType' + public searchLDeviceElements(options?: CommonOptions): LDeviceElement[] { + return this.searchElements( + DataTypeTemplatesQueries.SelectorLDeviceType, + ['id', 'desc', 'inst'], + options + ) + } + + public static SelectorBayType = 'BayType' + public searchBayElements(options?: CommonOptions): BayElement[] { + return this.searchElements( + DataTypeTemplatesQueries.SelectorBayType, + ['id', 'name', 'desc'], + options + ) + } + + public static SelectorIEDType = 'IEDType' + public searchIEDElements(options?: CommonOptions): IEDElement[] { + return this.searchElements( + DataTypeTemplatesQueries.SelectorIEDType, + [ + 'id', + 'name', + 'desc', + 'originalSclRevision', + 'originalSclVersion', + 'configVersion', + 'owner', + 'manufacturer', + 'type' + ], + options + ) + } + + public static SelectorVoltageLevelType = 'VoltageLevelType' + public searchVoltageLevelElements( + options?: CommonOptions + ): VoltageLevelElement[] { + return this.searchElements( + DataTypeTemplatesQueries.SelectorVoltageLevelType, + ['id', 'name', 'desc', 'nomFreq', 'numPhases'], + options + ) + } + + public static SelectorSubstationType = 'SubstationType' + public searchSubstationElements( + options?: CommonOptions + ): SubstationElement[] { + return this.searchElements( + DataTypeTemplatesQueries.SelectorSubstationType, + ['id', 'name', 'desc'], + options + ) + } +} diff --git a/packages/core/src/scd-queries/data-type-templates/service.data-type-templates.ts b/packages/core/src/scd-queries/data-type-templates/service.data-type-templates.ts new file mode 100644 index 00000000..5c51cd9a --- /dev/null +++ b/packages/core/src/scd-queries/data-type-templates/service.data-type-templates.ts @@ -0,0 +1,33 @@ +import { DataTypeTemplatesQueries } from './queries.data-type-templates' +// TYPES +import type { CommonOptions } from '../types.scd-queries' +import type { DataTypeTemplates } from './types.data-type-templates' + +export class DataTypeTemplatesService { + private dataTypeTemplatesQueries: DataTypeTemplatesQueries + + constructor(private readonly root: Element) { + this.dataTypeTemplatesQueries = new DataTypeTemplatesQueries(this.root) + } + + public findDataTypesElement(): DataTypeTemplates.RootElement | null { + return this.dataTypeTemplatesQueries.searchDataTypeTemplates() + } + + public findTypeDesignerElements( + options?: CommonOptions + ): DataTypeTemplates.SubElements { + return { + logicalDevices: + this.dataTypeTemplatesQueries.searchLDeviceElements(options), + bays: this.dataTypeTemplatesQueries.searchBayElements(options), + ieds: this.dataTypeTemplatesQueries.searchIEDElements(options), + voltageLevels: + this.dataTypeTemplatesQueries.searchVoltageLevelElements( + options + ), + substations: + this.dataTypeTemplatesQueries.searchSubstationElements(options) + } + } +} diff --git a/packages/core/src/scd-queries/data-type-templates/types.data-type-templates.d.ts b/packages/core/src/scd-queries/data-type-templates/types.data-type-templates.d.ts new file mode 100644 index 00000000..c04287f8 --- /dev/null +++ b/packages/core/src/scd-queries/data-type-templates/types.data-type-templates.d.ts @@ -0,0 +1,44 @@ +// TYPES +import type { + SCDBaseElement, + AllowedElements, + AllowedElementNames, + SubstationElement, + VoltageLevelElement, + BayElement, + IEDElement, + LDeviceElement +} from '../types.scd-queries' + +export namespace DataTypeTemplates { + type TreeOrder = + | `${number}` + | `${number}.${number}` + | `${number}.${number}.${number}` + + export type AllowedTag = + | 'SubstationType' + | 'VoltageLevelType' + | 'BayType' + | 'IEDType' + | 'LDeviceType' + + export type RootElement = SCDBaseElement + export type SubElements = { + substations: SubstationElement[] + voltageLevels: VoltageLevelElement[] + bays: BayElement[] + ieds: IEDElement[] + logicalDevices: LDeviceElement[] + } + + export type ElementsTreeStructure = Record< + AllowedElements, + { + elementName: AllowedElementNames + treeOrder: TreeOrder + tagName: DataTypeTemplates.Tag + children: DataTypeTemplates.Tag[] + } + > +} diff --git a/packages/core/src/scd-queries/index.ts b/packages/core/src/scd-queries/index.ts new file mode 100644 index 00000000..a6d954cd --- /dev/null +++ b/packages/core/src/scd-queries/index.ts @@ -0,0 +1,9 @@ +// SCD CORE +export * from './scd-query' +export * from './messages-types' + +// DOMAINS +export * from './data-type-templates' + +// TYPES +export type * from './types.scd-queries' diff --git a/packages/core/src/scd/messages-types.ts b/packages/core/src/scd-queries/messages-types.ts similarity index 52% rename from packages/core/src/scd/messages-types.ts rename to packages/core/src/scd-queries/messages-types.ts index d2e246cf..3457e313 100644 --- a/packages/core/src/scd/messages-types.ts +++ b/packages/core/src/scd-queries/messages-types.ts @@ -1,13 +1,13 @@ export const enum MessageType { - GOOSE = "GOOSE", - MMS = "MMS", - SampledValues = "SampledValues", - Unknown = "Unknown", + GOOSE = 'GOOSE', + MMS = 'MMS', + SampledValues = 'SampledValues', + Unknown = 'Unknown' } export const allMessageTypes = [ MessageType.GOOSE, MessageType.MMS, MessageType.SampledValues, - MessageType.Unknown, -] as const \ No newline at end of file + MessageType.Unknown +] as const diff --git a/packages/core/src/scd-queries/scd-queries.ts b/packages/core/src/scd-queries/scd-queries.ts new file mode 100644 index 00000000..a8e32d00 --- /dev/null +++ b/packages/core/src/scd-queries/scd-queries.ts @@ -0,0 +1,70 @@ +// TYPES +import type { + SCDBaseElement, + CommonOptions, + AttributeList, + Optional +} from './types.scd-queries' + +export class SCDQueries { + constructor(protected readonly root: Element) {} + + // + // Privates + // + + protected determineRoot(options?: CommonOptions): Element { + return options?.root || this.root + } + + protected searchElements( + selector: string, + attributeList: AttributeList[], + options?: CommonOptions + ): T[] { + const root = this.determineRoot(options) + const elements = Array.from(root.querySelectorAll(selector)) + const els = elements.map((el) => + this.createElement(el, attributeList) + ) + return els + } + + protected searchSingleElement( + selector: string, + attributeList: AttributeList[], + options?: CommonOptions + ): T | null { + const root = this.determineRoot(options) + const el = root.querySelector(selector) + + if (el === null) return null + + const els = this.createElement(el, attributeList) + return els + } + + protected searchElementParent( + element: Element, + parentSelector: string, + attributeList: AttributeList[] + ): Optional { + const parentEl = element.closest(parentSelector) + if (!parentEl) return + + return this.createElement(parentEl, attributeList) + } + + protected createElement( + el: Element, + attributeList: AttributeList[] + ): T { + const obj: { [key: string]: unknown } = { element: el } + for (const attr of attributeList) { + const key = attr as string + obj[key] = el.getAttribute(key) ?? '' + } + + return obj as T + } +} diff --git a/packages/core/src/scd-queries/scd-query.spec.ts b/packages/core/src/scd-queries/scd-query.spec.ts new file mode 100644 index 00000000..23b4341f --- /dev/null +++ b/packages/core/src/scd-queries/scd-query.spec.ts @@ -0,0 +1,55 @@ +import { expect, suite, test } from 'vitest' +import { SCDQueries } from './scd-query' +// TYPES +import type { GSEElement } from './scd-query' + +suite('SCD Queries', () => { + suite('GSE Queries', () => { + interface TestCase { + desc: string + xmlStr: string + expectedElements: Partial[] + } + + const featureTests: TestCase[] = [ + { + desc: 'first test', + xmlStr: '', + expectedElements: [ + { + ldInst: 'CircuitBreaker_CB1', + cbName: 'GCB' + } + ] + } + ] + + featureTests.forEach(testFeature) + + function testFeature(tc: TestCase) { + test(tc.desc, () => { + // + // Arrange + // + const parser = new DOMParser() + const doc = parser.parseFromString( + tc.xmlStr, + 'text/xml' + ) as unknown as Element + const scdQuery = new SCDQueries(doc) + + // + // Action + // + const gses = scdQuery.searchGSEs() + + // + // Assert + // + tc.expectedElements.forEach((el) => + gses.some((gse) => expect(gse).toMatchObject(el)) + ) + }) + } + }) // GSE Queries +}) // SCD Queries diff --git a/packages/core/src/scd-queries/scd-query.ts b/packages/core/src/scd-queries/scd-query.ts new file mode 100644 index 00000000..1a7c99d7 --- /dev/null +++ b/packages/core/src/scd-queries/scd-query.ts @@ -0,0 +1,717 @@ +////////////////////////////////////////// +// DO NOT ADD NEW CONTENT TO THIS FILE // +// PLEASE FOLLOW THE ADR 0004 // +// REGARDING CORE STRUCTURE CONVENTION // +////////////////////////////////////////// + +// TYPES +import type { + Optional, + AttributeList, + CommonOptions, + BayElement, + IEDElement, + LDeviceElement +} from './types.scd-queries' + +export class SCDQueries { + constructor(private readonly root: Element) {} + + public static SelectorGSE = 'GSE' + public searchGSEs(options?: CommonOptions): GSEElement[] { + return this.searchElement( + SCDQueries.SelectorGSE, + ['ldInst', 'cbName'], + options + ) + } + + public static SelectorIED = 'IED' + public static AttributeListIED: AttributeList[] = ['name'] + public searchIEDs(options?: CommonOptions): IEDElement[] { + return this.searchElement( + SCDQueries.SelectorIED, + SCDQueries.AttributeListIED, + options + ) + } + + public static SelectorLNode = 'LNode' + public static AttributeListLNode: AttributeList[] = [ + 'iedName', + 'ldInst', + 'lnClass', + 'lnInst', + 'lnType', + 'prefix' + ] + public searchLNodes(options?: CommonOptions): LNodeElement[] { + const selector = 'LNode' + return this.searchElement( + selector, + SCDQueries.AttributeListLNode, + options + ) + } + + public static SelectorBay = 'Bay' + public static AttributeListBay: AttributeList[] = ['name'] + public searchBays(options?: CommonOptions): BayElement[] { + return this.searchElement( + SCDQueries.SelectorBay, + SCDQueries.AttributeListBay, + options + ) + } + + public getBaysByIEDName(name: string): Set { + const root = this.determineRoot() + const selector = `SCL Substation VoltageLevel Bay LNode[iedName='${name}']` + // const selector = `SCL > Substation > VoltageLevel > Bay > LNode[iedName='${name}']` + + const lnodes = Array.from(root.querySelectorAll(selector)) + const connections = new Set() + + for (const lnode of lnodes) { + if (lnode != null) { + const bay = lnode.closest('Bay') + const bayName = bay?.getAttribute('name') + + if (bayName != null) connections.add(bayName) + } + } + + return connections + } + + public getConnectedAPByIEDName(name: string): Set { + const root = this.determineRoot() + const selector = `SCL > Communication > SubNetwork > ConnectedAP[iedName='${name}']` + + return new Set(root.querySelectorAll(selector)) + } + + public getSubnetworksByIEDName(name: string): Set { + const connectedAPs = this.getConnectedAPByIEDName(name) + const connections = new Set() + + for (const connectedAP of connectedAPs) { + if (connectedAP != null) { + const bay = connectedAP.closest('SubNetwork') + + if (bay != null) connections.add(bay) + } + } + + return connections + } + + public static SelectorGSEControl = 'GSEControl' + public searchGSEControls(options?: CommonOptions): GSEControlElement[] { + return this.searchElement( + SCDQueries.SelectorGSEControl, + ['name', 'datSet'], + options + ) + } + + public searchGSEControlByName( + cbName: string, + options?: CommonOptions + ): GSEControlElement { + return this.searchElement( + `${SCDQueries.SelectorGSEControl}[name='${cbName}']`, + ['name', 'datSet'], + options + )[0] + } + + public static SelectorInput = 'Inputs' + public searchInputs(options?: CommonOptions): InputElement[] { + return this.searchElement( + SCDQueries.SelectorInput, + [], + options + ) + } + + public static SelectorExtRef = 'ExtRef' + public static AttributeListExtRef: AttributeList[] = [ + 'iedName', + 'serviceType', + // "ldInst", + // "lnClass", + // "lnInst", + // "prefix", + // "doName", + // "daName", + // "srcLDInst", + // "srcPrefix", + 'srcCBName' + ] + public searchExtRef(options?: CommonOptions): InputExtRefElement[] { + return this.searchElement( + SCDQueries.SelectorExtRef, + SCDQueries.AttributeListExtRef, + options + ) + } + + public static SelectorDataSet = 'DataSet' + public searchDataSetByName( + name: string, + options?: CommonOptions + ): Optional { + const selector = `${SCDQueries.SelectorDataSet}[name="${name}"]` + const elements = this.searchElement( + selector, + ['name'], + options + ) + if (elements.length !== 1) { + console.log({ + level: 'error', + msg: 'we found not exactly one element', + length: elements.length + }) + return + } + return elements[0] + } + + // public searchSubNetworkByGOOSE( iedName: string, ldInst: string, gseControlName: string){ + + // } + + // We don't use the the standard functions because we look for parent elements + public static SelectorLD = 'LDevice' + public searchElementsLDParent(element: Element): Optional { + const el = element.closest(SCDQueries.SelectorLD) + if (!el) { + console.log({ + level: 'error', + msg: 'could not find LD parent', + element + }) + return + } + const ld = createElement(el, ['inst']) + return ld + } + + public static SelectorGSEElement = 'GSE' + public searchGSE( + ldInst: string, + cbName: string, + options?: CommonOptions + ): Optional { + const selector = `${SCDQueries.SelectorGSEElement}[ldInst='${ldInst}'][cbName='${cbName}']` + const elements = this.searchElement( + selector, + ['cbName', 'ldInst'], + options + ) + if (elements.length !== 1) { + console.error({ + level: 'error', + msg: 'we did not found exaclty one GSE element', + length: elements.length, + ldInst, + cbName, + selector, + root: this.root, + options + }) + return + } + + return elements[0] + } + + public static SelectorSubNetwork = 'SubNetwork' + public searchElementsParentSubnetwork( + element: Element + ): Optional { + const el = element.closest(SCDQueries.SelectorSubNetwork) + if (!el) { + console.error({ + level: 'error', + msg: 'could not find SubNetwork parent', + element + }) + return + } + + const subNetowk = createElement(el, ['name']) + return subNetowk + } + + public static SelectorDOType = 'DOType' + public searchDOTypes(options?: CommonOptions): DOTypeElement[] { + return this.searchElement( + SCDQueries.SelectorDOType, + ['id'], + options + ) + } + + public static SelectorDAType = 'DAType' + public searchDATypes(options?: CommonOptions): DATypeElement[] { + return this.searchElement( + SCDQueries.SelectorDAType, + ['id'], + options + ) + } + + public static SelectorEnumType = 'EnumType' + public searchEnumTypes(options?: CommonOptions): EnumTypeElement[] { + return this.searchElement( + SCDQueries.SelectorEnumType, + ['id'], + options + ) + } + + public static SelectorDO = 'DO' + public searchDOsByType(type: string, options?: CommonOptions): DOElement[] { + const selector = `${SCDQueries.SelectorDO}[type='${type}']` + return this.searchElement( + selector, + ['name', 'type'], + options + ) + } + + public static SelectorLNodeType = 'LNodeType' + public searchLNodeTypes(options?: CommonOptions): LNodeTypeElement[] { + return this.searchElement( + SCDQueries.SelectorLNodeType, + ['id', 'lnClass'], + options + ) + } + + public static SelectorReportControl = 'ReportControl' + public searchReportControls( + options?: CommonOptions + ): ReportControlElement[] { + return this.searchElement( + SCDQueries.SelectorReportControl, + ['rptID', 'name', 'datSet'], + options + ) + } + + public searchElementsParentIED(element: Element): Optional { + const parentSelector = SCDQueries.SelectorIED + const parentIED = this.searchElementParent( + element, + parentSelector, + SCDQueries.AttributeListIED + ) + + return parentIED + } + + public static SelectorClientLN = 'ClientLN' + public searcClientLNs(options?: CommonOptions): ClientLNElement[] { + return this.searchElement( + SCDQueries.SelectorClientLN, + ['iedName'], + options + ) + } + + public searchElementsByTypeAttr( + type: string, + options?: CommonOptions + ): SCDElement[] { + const selector = `[type='${type}']` + return this.searchElement(selector, [], options) + } + + public searchElementsByLnTypeAttr( + type: string, + options?: CommonOptions + ): SCDElement[] { + const selector = `[lnType='${type}']` + return this.searchElement(selector, [], options) + } + + public static SelectorConnectedAP = 'ConnectedAP' + public static AttributeListConnectedAP: AttributeList[] = + ['apName', 'iedName', 'redProt'] + public searchConnectedAPs(options?: CommonOptions): ConnectedAPElement[] { + const apElements = this.searchElement( + SCDQueries.SelectorConnectedAP, + SCDQueries.AttributeListConnectedAP, + options + ) + + return apElements + } + + public static SelectorIP = "Address > P[type='IP']" + public static AttributeListIP: AttributeList[] = [] + public searchConnectedAPIP( + options?: CommonOptions + ): ConnectedAPIPElement | undefined { + const ipElements = this.searchElement( + SCDQueries.SelectorIP, + SCDQueries.AttributeListIP, + options + ) + const ipElement = ipElements[0] + + return ipElement + } + + public static SelectorIPSubnet = "Address > P[type='IP-SUBNET']" + public static AttributeListIPSubnet: AttributeList[] = + [] + public searchConnectedAPIPSubnet( + options?: CommonOptions + ): ConnectedAPIPSubnetElement | undefined { + const ipSubnetElements = this.searchElement( + SCDQueries.SelectorIPSubnet, + SCDQueries.AttributeListIPSubnet, + options + ) + const ipSubnetElement = ipSubnetElements[0] + + return ipSubnetElement + } + + public static SelectorIPGateway = "Address > P[type='IP-GATEWAY']" + public static AttributeListIPGateway: AttributeList[] = + [] + public searchConnectedAPIPGateway( + options?: CommonOptions + ): ConnectedAPIPGatewayElement | undefined { + const ipGatewayElements = + this.searchElement( + SCDQueries.SelectorIPGateway, + SCDQueries.AttributeListIPGateway, + options + ) + const ipGatewayElement = ipGatewayElements[0] + + return ipGatewayElement + } + + public static SelectorCable = "PhysConn[type='Connection'] P[type='Cable']" + public static AttributeListCable: AttributeList[] = + [] + public searchConnectedAPCables( + options?: CommonOptions + ): ConnectedAPCableElement[] { + const cableElements = this.searchElement( + SCDQueries.SelectorCable, + SCDQueries.AttributeListCable, + options + ) + + return cableElements + } + + public static SelectorGooseAddress = 'GSE > Address' + public static AttributeListGooseAddress: AttributeList[] = + [] + public searchConnectedAPGooseAddresses( + options?: CommonOptions + ): ConnectedAPGooseAddressElement[] { + const gooseAddressElements = + this.searchElement( + SCDQueries.SelectorGooseAddress, + SCDQueries.AttributeListGooseAddress, + options + ) + + return gooseAddressElements + } + + public static SelectorSampledValuesAddress = 'SMV > Address' + public static AttributeListSampledValuesAddress: AttributeList[] = + [] + public searchConnectedAPSampledValuesAddresses( + options?: CommonOptions + ): ConnectedAPSampledValuesAddressElement[] { + const gooseAddressElements = + this.searchElement( + SCDQueries.SelectorSampledValuesAddress, + SCDQueries.AttributeListSampledValuesAddress, + options + ) + + return gooseAddressElements + } + + public static SelectorAddressVLanId = "P[type='VLAN-ID']" + public static AttributeListVLanId: AttributeList[] = + [] + public searchAddressVLanId( + options?: CommonOptions + ): AddressVLanIdElement | null { + const vLanIdElement = this.searchSingleElement( + SCDQueries.SelectorAddressVLanId, + SCDQueries.AttributeListVLanId, + options + ) + + return vLanIdElement + } + + public static SelectorAddressMacAddress = "P[type='MAC-Address']" + public static AttributeListMacAddress: AttributeList[] = + [] + public searchAddressMacAddress( + options?: CommonOptions + ): AddressMacAddressElement | null { + const macAddressElement = + this.searchSingleElement( + SCDQueries.SelectorAddressMacAddress, + SCDQueries.AttributeListMacAddress, + options + ) + + return macAddressElement + } + + public static SelectorPhysConnection = "PhysConn[type='Connection']" + public static AttributeListPhysConnection: AttributeList[] = + [] + public seachConnectedPhysConnections( + options?: CommonOptions + ): ConnectedAPPhyConnectionElement[] { + const physConnections = + this.searchElement( + SCDQueries.SelectorPhysConnection, + SCDQueries.AttributeListPhysConnection, + options + ) + + return physConnections + } + + public static SelectorPhysConnectionCable = "P[type='Cable']" + public seachPhysConnectionCable( + options?: CommonOptions + ): ConnectedAPCableElement | null { + return this.searchSingleElement( + SCDQueries.SelectorPhysConnectionCable, + SCDQueries.AttributeListCable, + options + ) + } + + public static SelectorPhysConnectionPort = "P[type='Port']" + public static AttributeListPort: AttributeList[] = + [] + public seachPhysConnectionPort( + options?: CommonOptions + ): ConnectedAPPortElement | null { + return this.searchSingleElement( + SCDQueries.SelectorPhysConnectionPort, + SCDQueries.AttributeListPort, + options + ) + } + public static SelectorPhysConnectionType = "P[type='Type']" + public static AttributeListType: AttributeList[] = + [] + public seachPhysConnectionType( + options?: CommonOptions + ): ConnectedAPPortElement | null { + return this.searchSingleElement( + SCDQueries.SelectorPhysConnectionType, + SCDQueries.AttributeListType, + options + ) + } + public static SelectorPhysConnectionPlug = "P[type='Plug']" + public static AttributeListPlug: AttributeList[] = + [] + public seachPhysConnectionPlug( + options?: CommonOptions + ): ConnectedAPPlugElement | null { + return this.searchSingleElement( + SCDQueries.SelectorPhysConnectionPlug, + SCDQueries.AttributeListPlug, + options + ) + } + + // + // Privates + // + private searchElement( + selector: string, + attributeList: AttributeList[], + options?: CommonOptions + ): T[] { + const root = this.determineRoot(options) + const elements = Array.from(root.querySelectorAll(selector)) + const els = elements.map((el) => createElement(el, attributeList)) + return els + } + + private determineRoot(options?: CommonOptions): Element { + if (!options?.root) { + return this.root + } + + return options.root + } + + private searchElementParent( + element: Element, + parentSelector: string, + attributeList: AttributeList[] + ): Optional { + const parentEl = element.closest(parentSelector) + if (!parentEl) { + return + } + + return createElement(parentEl, attributeList) + } + + private searchSingleElement( + selector: string, + attributeList: AttributeList[], + options?: CommonOptions + ): T | null { + const root = this.determineRoot(options) + const el = root.querySelector(selector) + + if (el === null) { + return null + } + + const els = createElement(el, attributeList) + return els + } +} + +// function createElement(el: Element, attributeList: (keyof T)[]): T{ +function createElement( + el: Element, + attributeList: AttributeList[] +): T { + const obj: { [key: string]: unknown } = { element: el } + for (const attr of attributeList) { + const key = attr as string + obj[key] = el.getAttribute(key) ?? '' + } + + return obj as T +} + +export type DOTypeElement = SCDElement & { + id: string + cdc: string +} + +export type DATypeElement = SCDElement & { + id: string +} +export type EnumTypeElement = SCDElement & { + id: string +} + +export type DOElement = SCDElement & { + name: string + type: string +} + +export type LNodeElement = SCDElement & { + iedName: string + ldInst: string + lnClass: string + lnInst: string + lnType: string + prefix: string +} + +export type LNodeTypeElement = SCDElement & { + id: string + lnClass: string +} + +export type SCDElement = { + element: Element +} + +export type IdentifiableElement = SCDElement & { + id: string +} + +export type GSEElement = SCDElement & { + ldInst: string + cbName: string +} + +export type GSEControlElement = SCDElement & { + name: string + datSet: string +} + +export type DataSetElement = SCDElement & { + name: string +} + +export type SubNetworkElement = SCDElement & { + name: string +} + +export type ReportControlElement = SCDElement & { + rptID: string + name: string + datSet: string +} + +export type ClientLNElement = SCDElement & { + iedName: string +} + +export type InputElement = SCDElement + +export type InputExtRefElement = SCDElement & { + iedName: string + serviceType: string + ldInst: string + lnClass: string + lnInst: string + prefix: string + doName: string + daName: string + srcLDInst: string + srcPrefix: string + srcCBName: string +} + +export type InputExtRefElementWithDatSet = InputExtRefElement & { + datSet: string | null +} + +// +export type ConnectedAPElement = SCDElement & { + apName: string + iedName: string + redProt: string +} + +export type ConnectedAPIPElement = SCDElement +export type ConnectedAPIPSubnetElement = SCDElement +export type ConnectedAPIPGatewayElement = SCDElement +export type ConnectedAPCableElement = SCDElement +export type ConnectedAPPortElement = SCDElement +export type ConnectedAPTypeElement = SCDElement +export type ConnectedAPPlugElement = SCDElement +export type ConnectedAPPhyConnectionElement = SCDElement +export type ConnectedAPGooseAddressElement = SCDElement +export type ConnectedAPSampledValuesAddressElement = SCDElement + +export type AddressVLanIdElement = SCDElement +export type AddressMacAddressElement = SCDElement diff --git a/packages/core/src/scd-queries/types.scd-queries.d.ts b/packages/core/src/scd-queries/types.scd-queries.d.ts new file mode 100644 index 00000000..9b68a8db --- /dev/null +++ b/packages/core/src/scd-queries/types.scd-queries.d.ts @@ -0,0 +1,64 @@ +// CONSTANTS +import type { ELEMENT_NAMES } from '../constants/element.constant' + +//====== SCD QUERIES + +export type SCDBaseElement = { + element: Element +} + +export type CommonOptions = { + root?: Element +} +export type Optional = T | undefined +export type AttributeList = Exclude< + keyof T, + keyof SCDBaseElement +> + +//====== SCD ELEMENTS +export type AllowedElements = keyof typeof ELEMENT_NAMES +export type AllowedElementNames = (typeof ELEMENT_NAMES)[AllowedElements] + +export type SubstationElementAttributes = { + id: string + desc: string + name: string +} +export type SubstationElement = SCDBaseElement & SubstationElementAttributes + +export type VoltageLevelElementAttributes = { + id: string + desc: string + name: string + nomFreq: string + numPhases: string +} +export type VoltageLevelElement = SCDBaseElement & VoltageLevelElementAttributes + +export type BayElementAttributes = { + id: string + desc: string + name: string +} +export type BayElement = SCDBaseElement & BayElementAttributes + +export type IEDElementAttributes = { + id: string + desc: string + name: string + originalSclRevision: string + originalSclVersion: string + owner: string + configVersion: string + manufacturer: string + type: string +} +export type IEDElement = SCDBaseElement & IEDElementAttributes + +export type LDeviceElementAttributes = { + id: string + desc: string + inst: string +} +export type LDeviceElement = SCDBaseElement & LDeviceElementAttributes diff --git a/packages/core/src/scd-xml/describers.ts b/packages/core/src/scd-xml/describers.ts new file mode 100644 index 00000000..a19ed279 --- /dev/null +++ b/packages/core/src/scd-xml/describers.ts @@ -0,0 +1,123 @@ +/** + * Source from: https://github.com/ca-d/oscd-template-generator/blob/main/generate-templates.js + */ + +function describeEnumType(element: Element) { + let _a, _b + const vals: { [key: string]: unknown } = {} + + for (const val of Array.from(element.children) + .filter((child) => child.tagName === 'EnumVal') + .sort((v1, v2) => { + let _a, _b + return ( + parseInt( + (_a = v1.getAttribute('ord')) !== null && _a !== void 0 + ? _a + : '', + 10 + ) - + parseInt( + (_b = v2.getAttribute('ord')) !== null && _b !== void 0 + ? _b + : '', + 10 + ) + ) + })) + vals[ + (_a = val.getAttribute('ord')) !== null && _a !== void 0 ? _a : '' + ] = (_b = val.textContent) !== null && _b !== void 0 ? _b : '' + + return { vals } +} + +function describeDAType(element: Element) { + let _a + const bdas: { [key: string]: unknown } = {} + for (const bda of Array.from(element.children) + .filter((child) => child.tagName === 'BDA') + .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { + const [bType, type, dchg, dupd, qchg] = [ + 'bType', + 'type', + 'dchg', + 'dupd', + 'qchg' + ].map((attr) => bda.getAttribute(attr)) + bdas[ + (_a = bda.getAttribute('name')) !== null && _a !== void 0 ? _a : '' + ] = { bType, type, dchg, dupd, qchg } + } + return { bdas } +} +function describeDOType(element: Element) { + const sdos: { [key: string]: unknown } = {} + for (const sdo of Array.from(element.children) + .filter((child) => child.tagName === 'SDO') + .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { + const [name, type, transient] = ['name', 'type', 'transient'].map( + (attr) => sdo.getAttribute(attr) + ) + sdos[name !== null && name !== void 0 ? name : ''] = { type, transient } + } + const das: { [key: string]: unknown } = {} + for (const da of Array.from(element.children) + .filter((child) => child.tagName === 'DA') + .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { + const [name, fc, bType, type, dchg, dupd, qchg] = [ + 'name', + 'fc', + 'bType', + 'type', + 'dchg', + 'dupd', + 'qchg' + ].map((attr) => da.getAttribute(attr)) + das[name !== null && name !== void 0 ? name : ''] = { + fc, + bType, + type, + dchg, + dupd, + qchg + } + } + return { + sdos, + das, + cdc: element.getAttribute('cdc') + } +} +function describeLNodeType(element: Element) { + const dos: { [key: string]: unknown } = {} + for (const doElement of Array.from(element.children) + .filter((child) => child.tagName === 'DO') + .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { + const [name, type, transient] = ['name', 'type', 'transient'].map( + (attr) => doElement.getAttribute(attr) + ) + dos[name !== null && name !== void 0 ? name : ''] = { type, transient } + } + return { + dos, + lnClass: element.getAttribute('lnClass') + } +} + +type DescriberFn = (element: Element) => any +const typeDescriptions: { [key: string]: DescriberFn } = { + EnumType: describeEnumType, + DAType: describeDAType, + DOType: describeDOType, + LNodeType: describeLNodeType +} +export function describeElement(element: Element) { + let _a, _b + return (_b = + (_a = typeDescriptions[element.tagName]) === null || _a === void 0 + ? void 0 + : _a.call(typeDescriptions, element)) !== null && _b !== void 0 + ? _b + : { xml: element.outerHTML } +} diff --git a/packages/core/src/scd-xml/foundation.ts b/packages/core/src/scd-xml/foundation.ts new file mode 100644 index 00000000..5a09c7f0 --- /dev/null +++ b/packages/core/src/scd-xml/foundation.ts @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE DIRECTLY ! MAKE A PR TO MODIFY THE CORE LIBRARY. // +// AS FOR NOW THE OPENSCD XML PACKAGE IS NOT PUBLISHED // +// WE USE A COPY OF ITS CORE XML FUNCTIONALITIES // +// SEE SOURCE https://github.com/openscd/open-scd/blob/main/packages/xml/src/foundation.ts // +//////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @since 0.0.1 + * + * @param Document - doc where the Element should be created + * @param string Tag - Tagname of the element + * @param Record - Attributes to be added to the created element + * + * @returns a new [[`tag`]] element owned by [[`doc`]]. + */ +export function createElement( + doc: Document, + tag: string, + attrs: Record +): Element { + const element = doc.createElementNS(doc.documentElement.namespaceURI, tag) + Object.entries(attrs) + .filter(([_, value]) => value !== null) + .forEach(([name, value]) => element.setAttribute(name, value!)) + return element +} + +/** + * @since 0.0.1 + * + * @param Element - element to be cloned + * @params Record - Attributes to be added + * + * @returns a clone of `element` with attributes set to values from `attrs`. + */ +export function cloneElement( + element: Element, + attrs: Record +): Element { + const newElement = element.cloneNode(false) + Object.entries(attrs).forEach(([name, value]) => { + if (value === null) newElement.removeAttribute(name) + else newElement.setAttribute(name, value) + }) + return newElement +} + +export function getChildElementsByTagName( + element: Element | null | undefined, + tag: string | null | undefined +): Element[] { + if (!element || !tag) return [] + return Array.from(element.children).filter( + (element) => element.tagName === tag + ) +} + +/** + * Format xml string in "pretty print" style and return as a string + * @param xml - xml document as a string + * @param tab - character to use as a tab + * @returns string with pretty print formatting + */ +export function formatXml(xml: string, tab?: string): string { + let formatted = '', + indent = '' + + if (!tab) tab = '\t' + xml.split(/>\s*\r\n' + if (node.match(/^]*[^/]$/)) indent += tab + }) + return formatted.substring(1, formatted.length - 3) +} + +export function getUniqueElementName( + parent: Element, + tagName: string, + iteration = 1 +): string { + const newName = 'new' + tagName + iteration + const child = parent.querySelector(`:scope > ${tagName}[name="${newName}"]`) + + if (!child) return newName + else return getUniqueElementName(parent, tagName, ++iteration) +} diff --git a/packages/core/src/xml/hash.spec.ts b/packages/core/src/scd-xml/hash.spec.ts similarity index 80% rename from packages/core/src/xml/hash.spec.ts rename to packages/core/src/scd-xml/hash.spec.ts index bc5b7158..4097e0fe 100644 --- a/packages/core/src/xml/hash.spec.ts +++ b/packages/core/src/scd-xml/hash.spec.ts @@ -1,26 +1,26 @@ -import { describe, expect, it } from "vitest" -import { hash } from "./hash" -import { describeElement } from "./describers" +import { describe, expect, it } from 'vitest' +import { hash } from './hash' +import { describeElement } from './describers' -describe("Hashing", () => { - describe("Different Element Types", () => { +describe('Hashing', () => { + describe('Different Element Types', () => { type XMLTypeDescription = { - tagName: string, - testCases: TestCase[], + tagName: string + testCases: TestCase[] } type TestCase = { - desc: string, - element1: string, - element2: string, - differentHash?: boolean, + desc: string + element1: string + element2: string + differentHash?: boolean } const xmlTypeDescriptions: XMLTypeDescription[] = [ { - tagName: "EnumType", + tagName: 'EnumType', testCases: [ { - desc: "same element", + desc: 'same element', element1: ` status-only @@ -32,10 +32,10 @@ describe("Hashing", () => { status-only direct-with-normal-security sbo-with-normal-security - `, + ` }, { - desc: "same children but different child order does not matter", + desc: 'same children but different child order does not matter', element1: ` status-only @@ -47,12 +47,12 @@ describe("Hashing", () => { status-only sbo-with-normal-security direct-with-normal-security - `, + ` }, { - desc: "different number of children produces different hash", + desc: 'different number of children produces different hash', differentHash: true, - element1: ` + element1: ` status-only_1 direct-with-normal-security @@ -63,12 +63,12 @@ describe("Hashing", () => { status-only_2 direct-with-normal-security sbo-with-normal-security - `, + ` }, { - desc: "same number but different children produces different hash", + desc: 'same number but different children produces different hash', differentHash: true, - element1: ` + element1: ` status-only_1 direct-with-normal-security @@ -79,15 +79,15 @@ describe("Hashing", () => { status-only_2 direct-with-normal-security sbo-with-normal-security - `, - }, - ], + ` + } + ] }, { - tagName: "DAType", + tagName: 'DAType', testCases: [ { - desc: "exact same element", + desc: 'exact same element', element1: ` @@ -99,10 +99,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same children, different order", + desc: 'same children, different order', element1: ` @@ -114,12 +114,12 @@ describe("Hashing", () => { - `, + ` }, { - desc: "different number of children produces different hash", + desc: 'different number of children produces different hash', differentHash: true, - element1: ` + element1: ` @@ -131,12 +131,12 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same number of children but different ones produces different hash", + desc: 'same number of children but different ones produces different hash', differentHash: true, - element1: ` + element1: ` @@ -147,10 +147,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "the order of BDAs' attributes does not matter", + desc: "the order of BDAs' attributes does not matter", element1: ` { dupd="5" type="3" /> - `, - }, - ], + ` + } + ] }, { - tagName: "DOType", + tagName: 'DOType', testCases: [ { - desc: "exact same element", + desc: 'exact same element', element1: ` @@ -192,10 +192,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same children, different order", + desc: 'same children, different order', element1: ` @@ -207,7 +207,7 @@ describe("Hashing", () => { - `, + ` }, // TODO: currently the element is ignored by the hash function // and therefore the hash of the following two elements is the same. @@ -229,9 +229,9 @@ describe("Hashing", () => { // `, // }, { - desc: "different number of children produces different hash", + desc: 'different number of children produces different hash', differentHash: true, - element1: ` + element1: ` @@ -243,12 +243,12 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same number of children but different ones produces different hash", + desc: 'same number of children but different ones produces different hash', differentHash: true, - element1: ` + element1: ` @@ -259,10 +259,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "the order of DAs' attributes does not matter", + desc: "the order of DAs' attributes does not matter", element1: ` { name="1" dchg="5" /> - `, - }, - ], + ` + } + ] }, { - tagName: "LNodeType", + tagName: 'LNodeType', testCases: [ { - desc: "exact same element", + desc: 'exact same element', element1: ` @@ -306,10 +306,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same children, different order", + desc: 'same children, different order', element1: ` @@ -321,12 +321,12 @@ describe("Hashing", () => { - `, + ` }, { - desc: "different number of children produces different hash", + desc: 'different number of children produces different hash', differentHash: true, - element1: ` + element1: ` @@ -338,12 +338,12 @@ describe("Hashing", () => { - `, + ` }, { - desc: "same number of children but different ones produces different hash", + desc: 'same number of children but different ones produces different hash', differentHash: true, - element1: ` + element1: ` @@ -354,10 +354,10 @@ describe("Hashing", () => { - `, + ` }, { - desc: "the order of DOs' attributes does not matter", + desc: "the order of DOs' attributes does not matter", element1: ` { transient="3" name="1" /> - `, - }, - ], - }, + ` + } + ] + } ] xmlTypeDescriptions.forEach(describeFeature) - function describeFeature(xmlDesc: XMLTypeDescription){ + function describeFeature(xmlDesc: XMLTypeDescription) { describe(xmlDesc.tagName, () => { xmlDesc.testCases.forEach(testFeature) }) @@ -392,25 +392,28 @@ describe("Hashing", () => { // Arrange // parse xml const parser = new DOMParser() - const xml1 = parser.parseFromString(tc.element1, "application/xml") - const xml2 = parser.parseFromString(tc.element2, "application/xml") - + const xml1 = parser.parseFromString( + tc.element1, + 'application/xml' + ) + const xml2 = parser.parseFromString( + tc.element2, + 'application/xml' + ) + // Act const desc1 = describeElement(xml1.documentElement) const desc2 = describeElement(xml2.documentElement) const hash1 = await hash(JSON.stringify(desc1)) const hash2 = await hash(JSON.stringify(desc2)) - + // Assert - if(tc.differentHash) { + if (tc.differentHash) { expect(hash1).not.toEqual(hash2) - }else{ - expect(hash1).toEqual(hash2) + } else { + expect(hash1).toEqual(hash2) } }) } - - }) // Different Element Types - -}) // Hashing \ No newline at end of file +}) // Hashing diff --git a/packages/core/src/xml/hash.ts b/packages/core/src/scd-xml/hash.ts similarity index 61% rename from packages/core/src/xml/hash.ts rename to packages/core/src/scd-xml/hash.ts index 002ac4cd..fa53406f 100644 --- a/packages/core/src/xml/hash.ts +++ b/packages/core/src/scd-xml/hash.ts @@ -1,15 +1,17 @@ -import { describeElement } from "./describers" +import { describeElement } from './describers' export function hashElement(el: Element): Promise { const desc = describeElement(el) - + return hash(JSON.stringify(desc)) } export async function hash(str: string): Promise { const buffer = new TextEncoder().encode(str) - const hashBuffer = await crypto.subtle.digest("SHA-256", buffer) + const hashBuffer = await crypto.subtle.digest('SHA-256', buffer) const hashArray = Array.from(new Uint8Array(hashBuffer)) - const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("") + const hashHex = hashArray + .map((b) => b.toString(16).padStart(2, '0')) + .join('') return hashHex -} \ No newline at end of file +} diff --git a/packages/core/src/scd-xml/index.ts b/packages/core/src/scd-xml/index.ts new file mode 100644 index 00000000..022fb452 --- /dev/null +++ b/packages/core/src/scd-xml/index.ts @@ -0,0 +1 @@ +export * from './foundation' diff --git a/packages/core/src/scd/event-edit.ts b/packages/core/src/scd/event-edit.ts deleted file mode 100644 index da0ea31c..00000000 --- a/packages/core/src/scd/event-edit.ts +++ /dev/null @@ -1,23 +0,0 @@ -// {parent: this.doc.documentElement, node: templates} - - - -// type DetailEdit = { -// parent: Element -// } -// function newEditEvent(edit) { -// return new CustomEvent("oscd-edit", { -// composed: true, -// bubbles: true, -// detail: edit, -// }) -// } - -// function newActionEvent(action, eventInitDict) { -// return new CustomEvent("editor-action", { -// bubbles: true, -// composed: true, -// ...eventInitDict, -// detail: {action, ...eventInitDict?.detail}, -// }) -// } \ No newline at end of file diff --git a/packages/core/src/scd/fcda.xml.ts b/packages/core/src/scd/fcda.xml.ts deleted file mode 100644 index e0c4f621..00000000 --- a/packages/core/src/scd/fcda.xml.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FCDA } from "./fcda" - -export const SelectorFCDA = "FCDA" - -export function elementToFCDA(el: Element): FCDA{ - return new FCDA({ - ldInst: el.getAttribute( "ldInst" ) ?? "", - prefix: el.getAttribute( "prefix" ) ?? "", - lnClass: el.getAttribute( "lnClass" ) ?? "", - lnInst: el.getAttribute( "lnInst" ) ?? "", - doName: el.getAttribute( "doName" ) ?? "", - daName: el.getAttribute( "daName" ) ?? "", - fc: el.getAttribute( "fc" ) ?? "", - }) -} \ No newline at end of file diff --git a/packages/core/src/scd/index.ts b/packages/core/src/scd/index.ts deleted file mode 100644 index a4db9c2d..00000000 --- a/packages/core/src/scd/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./scd-query" -export * from "./messages-types" \ No newline at end of file diff --git a/packages/core/src/scd/input-extref.spec.ts b/packages/core/src/scd/input-extref.spec.ts deleted file mode 100644 index d9a2b263..00000000 --- a/packages/core/src/scd/input-extref.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {expect, suite, test} from "vitest" -import { elementToInputExtRef, SelectorInputExtRef } from "./input-extref.xml" -import { InputExtRef } from "./input-extref" - -suite("InputExtRef", () => { - test("convert element to InputExtRef", () => { - // - // Arrange - // - const xmlStr = "" - const parser = new DOMParser() - const doc = parser.parseFromString(xmlStr, "text/xml") as unknown as Element - const el = doc.querySelector(SelectorInputExtRef) - if(el === null){ - throw new Error("msg='could not find Input ExtRef element'") - } - - const expectedInputExtRef = new InputExtRef({ - iedName: "IED1", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB1", - lnClass: "XCBR", - lnInst: "1", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - }) - - // - // Action - // - const actualInputExtRef = elementToInputExtRef(el) - - // - // Assert - // - expect(actualInputExtRef).toEqual(expectedInputExtRef) - }) -}) \ No newline at end of file diff --git a/packages/core/src/scd/input-extref.ts b/packages/core/src/scd/input-extref.ts deleted file mode 100644 index 5d5653b8..00000000 --- a/packages/core/src/scd/input-extref.ts +++ /dev/null @@ -1,24 +0,0 @@ -export class InputExtRef{ - - public readonly iedName: string = "" - public readonly serviceType: string = "" - public readonly ldInst: string = "" - public readonly lnClass: string = "" - public readonly lnInst: string = "" - public readonly prefix: string = "" - public readonly doName: string = "" - public readonly daName: string = "" - public readonly srcLNClass: string = "" - public readonly srcLDInst: string = "" - public readonly srcPrefix: string = "" - public readonly srcCBName: string = "" - - constructor(inputExtRef?: Partial){ - const newThis: InputExtRef = { - ...this, - ...inputExtRef, - } - Object.setPrototypeOf(newThis, InputExtRef.prototype) - return newThis - } -} \ No newline at end of file diff --git a/packages/core/src/scd/input-extref.xml.ts b/packages/core/src/scd/input-extref.xml.ts deleted file mode 100644 index 4f52067f..00000000 --- a/packages/core/src/scd/input-extref.xml.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { InputExtRef } from "./input-extref" - -export const SelectorInputExtRef = "ExtRef" - -export function elementToInputExtRef(el: Element): InputExtRef{ - return new InputExtRef({ - iedName: el.getAttribute("iedName") ?? "", - serviceType: el.getAttribute("serviceType") ?? "", - ldInst: el.getAttribute("ldInst") ?? "", - lnClass: el.getAttribute("lnClass") ?? "", - lnInst: el.getAttribute("lnInst") ?? "", - prefix: el.getAttribute("prefix") ?? "", - doName: el.getAttribute("doName") ?? "", - daName: el.getAttribute("daName") ?? "", - srcLNClass: el.getAttribute("srcLNClass") ?? "", - srcLDInst: el.getAttribute("srcLDInst") ?? "", - srcPrefix: el.getAttribute("srcPrefix") ?? "", - srcCBName: el.getAttribute("srcCBName") ?? "", - }) -} \ No newline at end of file diff --git a/packages/core/src/scd/input.spec.ts b/packages/core/src/scd/input.spec.ts deleted file mode 100644 index 623f6cb1..00000000 --- a/packages/core/src/scd/input.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { expect, suite, test } from "vitest" -import { Input } from "./input" -import { elementToInputs, SelectorInputs } from "./input.xml" -import { InputExtRef } from "./input-extref" - - -suite("Input", () => { - suite("convert element to Input", () => { - - interface TestCase { - desc: string - xmlStr: string - expectedInputs: Input[] - } - - const featureTests: TestCase[] = [ - { - desc: "one external reference", - xmlStr: ` - - `, - expectedInputs: [new Input({ - iedName: "IED1", - extRefs: [new InputExtRef({ - iedName: "IED1", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB1", - lnClass: "XCBR", - lnInst: "1", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - })], - })], - }, - { - desc: "one IED multiple ExtRefs", - xmlStr: ` - - - `, - expectedInputs: [new Input({ - iedName: "IED1", - extRefs: [ - new InputExtRef({ - iedName: "IED1", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB1", - lnClass: "XCBR", - lnInst: "1", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - }), - new InputExtRef({ - iedName: "IED1", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB2", - lnClass: "XCBR", - lnInst: "2", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - }), - ], - })], - }, - { - desc: "two IEDs", - xmlStr: ` - - - `, - expectedInputs: [ - new Input({ - iedName: "IED1", - extRefs: [new InputExtRef({ - iedName: "IED1", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB1", - lnClass: "XCBR", - lnInst: "1", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - })], - }), - new Input({ - iedName: "IED2", - extRefs: [new InputExtRef({ - iedName: "IED2", - serviceType: "GOOSE", - ldInst: "CircuitBreaker_CB1", - lnClass: "XCBR", - lnInst: "1", - prefix: "test_", - doName: "Pos", - daName: "stVal", - srcLDInst: "CircuitBreaker_CB1", - srcPrefix: "test01_", - srcLNClass: "LLN0", - srcCBName: "GCB", - })], - }), - ], - }, - ] - - featureTests.forEach(testFeature) - - function testFeature(tc: TestCase) { - test(tc.desc, () => { - - // - // Arrange - // - const parser = new DOMParser() - const doc = parser.parseFromString(tc.xmlStr, "text/xml") as unknown as Element - const el = doc.querySelector(SelectorInputs) - if(el === null){ - throw new Error("msg='could not find Input element'") - } - - // - // Action - // - const actualInput = elementToInputs(el) - - // - // Assert - // - expect(actualInput).toEqual(tc.expectedInputs) - - }) - } - - }) -}) \ No newline at end of file diff --git a/packages/core/src/scd/input.ts b/packages/core/src/scd/input.ts deleted file mode 100644 index a54d1ea1..00000000 --- a/packages/core/src/scd/input.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { InputExtRef } from "scd/input-extref" - -export class Input{ - - public readonly iedName: string = "" - public readonly extRefs: InputExtRef[] = [] - - constructor(input?: Partial){ - const newThis: Input = { - ...this, - ...input, - } - Object.setPrototypeOf(newThis, Input.prototype) - return newThis - } -} \ No newline at end of file diff --git a/packages/core/src/scd/input.xml.ts b/packages/core/src/scd/input.xml.ts deleted file mode 100644 index 238d2bce..00000000 --- a/packages/core/src/scd/input.xml.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Input } from "./input" -import { InputExtRef } from "./input-extref" -import { elementToInputExtRef, SelectorInputExtRef } from "./input-extref.xml" - -export const SelectorInputs = "Inputs" - -export function elementToInputs(el: Element): Input[]{ - const extRefsElements = Array.from( el?.querySelectorAll(SelectorInputExtRef) ?? [] ) - const extRefs = extRefsElements.map(elementToInputExtRef) - - const extRefBuckets: {[iedName: string]: InputExtRef[]} = {} - for(const extRef of extRefs){ - if(!extRefBuckets[extRef.iedName]){ - extRefBuckets[extRef.iedName] = [] - } - extRefBuckets[extRef.iedName].push(extRef) - } - const inputs = Object.keys(extRefBuckets).map( (iedName) => { - return new Input({ - iedName, - extRefs: extRefBuckets[iedName], - }) - }) - - return inputs -} \ No newline at end of file diff --git a/packages/core/src/scd/publisher.ts b/packages/core/src/scd/publisher.ts deleted file mode 100644 index e29a72f1..00000000 --- a/packages/core/src/scd/publisher.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class Publisher { - -} \ No newline at end of file diff --git a/packages/core/src/scd/scd-query.spec.ts b/packages/core/src/scd/scd-query.spec.ts deleted file mode 100644 index d1923408..00000000 --- a/packages/core/src/scd/scd-query.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect, suite, test } from "vitest" -import { GSEElement, SCDQueries } from "./scd-query" - -suite("SCD Queries", () => { - suite("GSE Queries", () => { - - interface TestCase { - desc: string - xmlStr: string - expectedElements: Partial[] - } - - const featureTests: TestCase[] = [ - { - desc: "first test", - xmlStr: "", - expectedElements: [ - { - ldInst: "CircuitBreaker_CB1", - cbName: "GCB", - }, - ], - }, - ] - - featureTests.forEach(testFeature) - - function testFeature(tc: TestCase) { - test(tc.desc, () => { - // - // Arrange - // - const parser = new DOMParser() - const doc = parser.parseFromString(tc.xmlStr, "text/xml") as unknown as Element - const scdQuery = new SCDQueries(doc) - - // - // Action - // - const gses = scdQuery.searchGSEs() - - // - // Assert - // - tc.expectedElements.forEach( el => gses.some( gse => expect(gse).toMatchObject(el)) ) - - }) - } - }) // GSE Queries - -}) // SCD Queries \ No newline at end of file diff --git a/packages/core/src/scd/scd-query.ts b/packages/core/src/scd/scd-query.ts deleted file mode 100644 index fb7a3ef4..00000000 --- a/packages/core/src/scd/scd-query.ts +++ /dev/null @@ -1,554 +0,0 @@ -export class SCDQueries { - constructor( - private readonly root: Element, - ){} - - - public static SelectorGSE = "GSE" - public searchGSEs(options?:CommonOptions): GSEElement[]{ - return this.searchElement(SCDQueries.SelectorGSE, ["ldInst", "cbName"], options) - } - - public static SelectorIED = "IED" - public static AttributeListIED: AttributeList[] = ["name"] - public searchIEDs(options?:CommonOptions): IEDElement[]{ - return this.searchElement(SCDQueries.SelectorIED, SCDQueries.AttributeListIED, options) - } - - - public static SelectorLNode = "LNode" - public static AttributeListLNode: AttributeList[] = [ - "iedName", - "ldInst", - "lnClass", - "lnInst", - "lnType", - "prefix", - ] - public searchLNodes(options?: CommonOptions): LNodeElement[] { - const selector = "LNode" - return this.searchElement(selector, SCDQueries.AttributeListLNode, options) - } - - public static SelectorBay = "Bay" - public static AttributeListBay: AttributeList[] = ["name"] - public searchBays(options?:CommonOptions): BayElement[]{ - return this.searchElement(SCDQueries.SelectorBay, SCDQueries.AttributeListBay, options) - } - - public getBaysByIEDName(name: string): Set { - const root = this.determineRoot() - const selector = `SCL Substation VoltageLevel Bay LNode[iedName='${name}']` - // const selector = `SCL > Substation > VoltageLevel > Bay > LNode[iedName='${name}']` - - const lnodes = Array.from(root.querySelectorAll(selector)) - const connections = new Set - - for (const lnode of lnodes) { - if (lnode != null){ - const bay = lnode.closest("Bay") - const bayName = bay?.getAttribute("name") - - if (bayName != null) - connections.add(bayName) - } - } - - return connections - } - - public getConnectedAPByIEDName(name: string): Set { - const root = this.determineRoot() - const selector = `SCL > Communication > SubNetwork > ConnectedAP[iedName='${name}']` - - return new Set(root.querySelectorAll(selector)) - } - - public getSubnetworksByIEDName(name: string): Set { - const connectedAPs = this.getConnectedAPByIEDName(name) - const connections = new Set - - for (const connectedAP of connectedAPs) { - if (connectedAP != null){ - const bay = connectedAP.closest("SubNetwork") - - if (bay != null) - connections.add(bay) - } - } - - return connections - } - - public static SelectorGSEControl = "GSEControl" - public searchGSEControls(options?:CommonOptions): GSEControlElement[] { - return this.searchElement( - SCDQueries.SelectorGSEControl, - ["name", "datSet"], - options, - ) - } - - public searchGSEControlByName(cbName: string, options?:CommonOptions): GSEControlElement { - return this.searchElement( - `${SCDQueries.SelectorGSEControl}[name='${cbName}']`, - ["name", "datSet"], - options, - )[0] - } - - public static SelectorInput = "Inputs" - public searchInputs(options?: CommonOptions): InputElement[] { - return this.searchElement(SCDQueries.SelectorInput,[],options) - } - - - - public static SelectorExtRef = "ExtRef" - public static AttributeListExtRef: AttributeList[] = [ - "iedName", - "serviceType", - // "ldInst", - // "lnClass", - // "lnInst", - // "prefix", - // "doName", - // "daName", - // "srcLDInst", - // "srcPrefix", - "srcCBName", - ] - public searchExtRef(options?: CommonOptions): InputExtRefElement[]{ - return this.searchElement( - SCDQueries.SelectorExtRef, - SCDQueries.AttributeListExtRef, - options, - ) - } - - - public static SelectorDataSet = "DataSet" - public searchDataSetByName(name:string, options?:CommonOptions): Optional{ - const selector = `${SCDQueries.SelectorDataSet}[name="${name}"]` - const elements = this.searchElement( - selector, - ["name"], - options, - ) - if(elements.length !== 1){ - console.log({level: "error", msg: "we found not exactly one element", length: elements.length}) - return - } - return elements[0] - } - - // public searchSubNetworkByGOOSE( iedName: string, ldInst: string, gseControlName: string){ - - // } - - // We don't use the the standard functions because we look for parent elements - public static SelectorLD = "LDevice" - public searchElementsLDParent(element: Element): Optional{ - const el = element.closest(SCDQueries.SelectorLD) - if(!el){ - console.log({level: "error", msg: "could not find LD parent", element}) - return - } - const ld = createElement(el,["inst"]) - return ld - } - - public static SelectorGSEElement = "GSE" - public searchGSE(ldInst:string, cbName:string, options?:CommonOptions): Optional{ - const selector = `${SCDQueries.SelectorGSEElement}[ldInst='${ldInst}'][cbName='${cbName}']` - const elements = this.searchElement(selector,["cbName","ldInst"], options) - if(elements.length !== 1){ - console.error({ - level: "error", - msg: "we did not found exaclty one GSE element", - length: elements.length, - ldInst, - cbName, - selector, - root: this.root, - options, - }) - return - } - - return elements[0] - } - - public static SelectorSubNetwork = "SubNetwork" - public searchElementsParentSubnetwork(element: Element): Optional{ - const el = element.closest(SCDQueries.SelectorSubNetwork) - if(!el){ - console.error({level: "error", msg: "could not find SubNetwork parent", element}) - return - } - - const subNetowk = createElement(el, ["name"]) - return subNetowk - } - - public static SelectorDOType = "DOType" - public searchDOTypes(options?:CommonOptions): DOTypeElement[]{ - return this.searchElement(SCDQueries.SelectorDOType, ["id"], options) - } - - public static SelectorDAType = "DAType" - public searchDATypes(options?:CommonOptions): DATypeElement[]{ - return this.searchElement(SCDQueries.SelectorDAType, ["id"], options) - } - - public static SelectorEnumType = "EnumType" - public searchEnumTypes(options?:CommonOptions): EnumTypeElement[]{ - return this.searchElement(SCDQueries.SelectorEnumType, ["id"], options) - } - - public static SelectorDO = "DO" - public searchDOsByType(type: string, options?:CommonOptions): DOElement[]{ - const selector = `${SCDQueries.SelectorDO}[type='${type}']` - return this.searchElement(selector, ["name", "type"], options) - } - - public static SelectorLNodeType = "LNodeType" - public searchLNodeTypes(options?:CommonOptions): LNodeTypeElement[]{ - return this.searchElement(SCDQueries.SelectorLNodeType, ["id", "lnClass"], options) - } - - public static SelectorReportControl = "ReportControl" - public searchReportControls(options?:CommonOptions): ReportControlElement[]{ - return this.searchElement(SCDQueries.SelectorReportControl, ["rptID", "name", "datSet"], options) - } - - public searchElementsParentIED(element: Element): Optional{ - const parentSelector = SCDQueries.SelectorIED - const parentIED = this.searchElementParent(element, parentSelector, SCDQueries.AttributeListIED) - - return parentIED - } - - public static SelectorClientLN = "ClientLN" - public searcClientLNs(options?:CommonOptions): ClientLNElement[]{ - return this.searchElement(SCDQueries.SelectorClientLN, ["iedName"], options) - } - - public searchElementsByTypeAttr(type: string, options?: CommonOptions): SCDElement[]{ - const selector = `[type='${type}']` - return this.searchElement(selector, [], options) - } - - public searchElementsByLnTypeAttr(type: string, options?: CommonOptions): SCDElement[]{ - const selector = `[lnType='${type}']` - return this.searchElement(selector, [], options) - } - - - public static SelectorConnectedAP = "ConnectedAP" - public static AttributeListConnectedAP: AttributeList[] = [ - "apName", - "iedName", - "redProt", - ] - public searchConnectedAPs(options?:CommonOptions): ConnectedAPElement[]{ - - const apElements = this.searchElement( - SCDQueries.SelectorConnectedAP, - SCDQueries.AttributeListConnectedAP, - options, - ) - - return apElements - } - - public static SelectorIP = "Address > P[type='IP']" - public static AttributeListIP: AttributeList[] = [] - public searchConnectedAPIP(options?:CommonOptions): ConnectedAPIPElement | undefined { - const ipElements = this.searchElement( - SCDQueries.SelectorIP, - SCDQueries.AttributeListIP, - options, - ) - const ipElement = ipElements[0] - - return ipElement - } - - public static SelectorIPSubnet = "Address > P[type='IP-SUBNET']" - public static AttributeListIPSubnet: AttributeList[] = [] - public searchConnectedAPIPSubnet(options?:CommonOptions): ConnectedAPIPSubnetElement | undefined { - const ipSubnetElements = this.searchElement( - SCDQueries.SelectorIPSubnet, - SCDQueries.AttributeListIPSubnet, - options, - ) - const ipSubnetElement = ipSubnetElements[0] - - return ipSubnetElement - } - - public static SelectorIPGateway = "Address > P[type='IP-GATEWAY']" - public static AttributeListIPGateway: AttributeList[] = [] - public searchConnectedAPIPGateway(options?:CommonOptions): ConnectedAPIPGatewayElement | undefined { - const ipGatewayElements = this.searchElement(SCDQueries.SelectorIPGateway, SCDQueries.AttributeListIPGateway, options) - const ipGatewayElement = ipGatewayElements[0] - - return ipGatewayElement - } - - public static SelectorCable = "PhysConn[type='Connection'] P[type='Cable']" - public static AttributeListCable: AttributeList[] = [] - public searchConnectedAPCables(options?:CommonOptions): ConnectedAPCableElement[]{ - const cableElements = this.searchElement(SCDQueries.SelectorCable, SCDQueries.AttributeListCable, options) - - return cableElements - } - - public static SelectorGooseAddress = "GSE > Address" - public static AttributeListGooseAddress: AttributeList[] = [] - public searchConnectedAPGooseAddresses(options?:CommonOptions): ConnectedAPGooseAddressElement[] { - const gooseAddressElements = this.searchElement(SCDQueries.SelectorGooseAddress, SCDQueries.AttributeListGooseAddress, options) - - return gooseAddressElements - } - - public static SelectorSampledValuesAddress = "SMV > Address" - public static AttributeListSampledValuesAddress: AttributeList[] = [] - public searchConnectedAPSampledValuesAddresses(options?:CommonOptions): ConnectedAPSampledValuesAddressElement[] { - const gooseAddressElements = this.searchElement(SCDQueries.SelectorSampledValuesAddress, SCDQueries.AttributeListSampledValuesAddress, options) - - return gooseAddressElements - } - - public static SelectorAddressVLanId = "P[type='VLAN-ID']" - public static AttributeListVLanId: AttributeList[] = [] - public searchAddressVLanId(options?:CommonOptions): AddressVLanIdElement | null { - const vLanIdElement = this.searchSingleElement(SCDQueries.SelectorAddressVLanId, SCDQueries.AttributeListVLanId, options) - - return vLanIdElement - } - - public static SelectorAddressMacAddress = "P[type='MAC-Address']" - public static AttributeListMacAddress: AttributeList[] = [] - public searchAddressMacAddress(options?:CommonOptions): AddressMacAddressElement | null { - const macAddressElement = this.searchSingleElement(SCDQueries.SelectorAddressMacAddress, SCDQueries.AttributeListMacAddress, options) - - return macAddressElement - } - - - public static SelectorPhysConnection = "PhysConn[type='Connection']" - public static AttributeListPhysConnection: AttributeList[] = [] - public seachConnectedPhysConnections(options?:CommonOptions): ConnectedAPPhyConnectionElement[] { - const physConnections = this.searchElement(SCDQueries.SelectorPhysConnection, SCDQueries.AttributeListPhysConnection, options) - - return physConnections - } - - public static SelectorPhysConnectionCable = "P[type='Cable']" - public seachPhysConnectionCable(options?:CommonOptions): ConnectedAPCableElement | null { - return this.searchSingleElement(SCDQueries.SelectorPhysConnectionCable, SCDQueries.AttributeListCable, options) - } - - public static SelectorPhysConnectionPort = "P[type='Port']" - public static AttributeListPort: AttributeList[] = [] - public seachPhysConnectionPort(options?:CommonOptions): ConnectedAPPortElement | null { - return this.searchSingleElement(SCDQueries.SelectorPhysConnectionPort, SCDQueries.AttributeListPort, options) - } - public static SelectorPhysConnectionType = "P[type='Type']" - public static AttributeListType: AttributeList[] = [] - public seachPhysConnectionType(options?:CommonOptions): ConnectedAPPortElement | null { - return this.searchSingleElement(SCDQueries.SelectorPhysConnectionType, SCDQueries.AttributeListType, options) - } - public static SelectorPhysConnectionPlug = "P[type='Plug']" - public static AttributeListPlug: AttributeList[] = [] - public seachPhysConnectionPlug(options?:CommonOptions): ConnectedAPPlugElement | null { - return this.searchSingleElement(SCDQueries.SelectorPhysConnectionPlug, SCDQueries.AttributeListPlug, options) - } - - - - - // - // Privates - // - private searchElement(selector: string, attributeList: AttributeList[], options?:CommonOptions): T[]{ - const root = this.determineRoot(options) - const elements = Array.from( root.querySelectorAll(selector) ) - const els = elements.map( el => createElement(el, attributeList) ) - return els - } - - private determineRoot(options?: CommonOptions): Element { - if(!options?.root){ - return this.root - } - - return options.root - } - - private searchElementParent( - element:Element, - parentSelector: string, - attributeList: AttributeList[], - ): Optional{ - const parentEl = element.closest(parentSelector) - if(!parentEl){ - return - } - - return createElement(parentEl, attributeList) - } - - private searchSingleElement(selector: string, attributeList: AttributeList[], options?:CommonOptions): T | null { - const root = this.determineRoot(options) - const el = root.querySelector(selector) - - if (el === null) { - return null - } - - const els = createElement(el, attributeList) - return els - } -} - - -// function createElement(el: Element, attributeList: (keyof T)[]): T{ -function createElement(el: Element, attributeList: AttributeList[]): T{ - const obj: {[key: string]: unknown} = { element: el } - for(const attr of attributeList){ - const key = attr as string - obj[key] = el.getAttribute(key) ?? "" - } - - return obj as T -} - -export type DOTypeElement = SCDElement & { - id: string - cdc: string -} - -export type DATypeElement = SCDElement & { - id: string -} -export type EnumTypeElement = SCDElement & { - id: string -} - -export type DOElement = SCDElement & { - name: string - type: string -} - -export type BayElement = SCDElement & { - name: string -} - -export type LNodeElement = SCDElement & { - iedName: string - ldInst: string - lnClass: string - lnInst: string - lnType: string - prefix: string -} - -export type LNodeTypeElement = SCDElement & { - id: string - lnClass: string -} - -export type CommonOptions = { - root?: Element -} - -export type SCDElement = { - element: Element -} - -export type IdentifiableElement = SCDElement & { - id: string -} - -export type GSEElement = SCDElement & { - ldInst: string - cbName: string -} - -export type IEDElement = SCDElement & { - name: string -} - -export type GSEControlElement = SCDElement & { - name: string - datSet: string -} - -export type DataSetElement = SCDElement & { - name: string -} - -export type LDeviceElement = SCDElement & { - inst: string -} - -export type SubNetworkElement = SCDElement & { - name: string -} - -export type ReportControlElement = SCDElement & { - rptID: string - name: string - datSet: string -} - -export type ClientLNElement = SCDElement & { - iedName: string -} - -export type InputElement = SCDElement - -export type InputExtRefElement = SCDElement & { - iedName: string, - serviceType: string, - ldInst: string, - lnClass: string, - lnInst: string, - prefix: string, - doName: string, - daName: string, - srcLDInst: string, - srcPrefix: string, - srcCBName: string, -} - -export type InputExtRefElementWithDatSet = InputExtRefElement & { - datSet: string | null -} - -// -export type ConnectedAPElement = SCDElement & { - apName: string - iedName: string - redProt: string -} - -export type ConnectedAPIPElement = SCDElement -export type ConnectedAPIPSubnetElement = SCDElement -export type ConnectedAPIPGatewayElement = SCDElement -export type ConnectedAPCableElement = SCDElement -export type ConnectedAPPortElement = SCDElement -export type ConnectedAPTypeElement = SCDElement -export type ConnectedAPPlugElement = SCDElement -export type ConnectedAPPhyConnectionElement = SCDElement -export type ConnectedAPGooseAddressElement = SCDElement -export type ConnectedAPSampledValuesAddressElement = SCDElement - -export type AddressVLanIdElement = SCDElement -export type AddressMacAddressElement = SCDElement - -export type Optional = T | undefined -export type AttributeList = Exclude \ No newline at end of file diff --git a/packages/core/src/use-cases/index.ts b/packages/core/src/use-cases/index.ts index cf415d00..d63dec48 100644 --- a/packages/core/src/use-cases/index.ts +++ b/packages/core/src/use-cases/index.ts @@ -1,3 +1,3 @@ -export * from "./uc-communication-information" -export * from "./uc-type-type-switcher" -export * from "./uc-network-information" +export * from './uc-communication-information' +export * from './uc-type-type-switcher' +export * from './uc-network-information' diff --git a/packages/core/src/use-cases/uc-communication-information.spec.ts b/packages/core/src/use-cases/uc-communication-information.spec.ts index 0d88050d..d42d396b 100644 --- a/packages/core/src/use-cases/uc-communication-information.spec.ts +++ b/packages/core/src/use-cases/uc-communication-information.spec.ts @@ -1,55 +1,56 @@ -import { expect, describe, it } from "vitest" -import { InputExtRefElement, SCDQueries } from "../scd/scd-query" -import { - IEDCommInfo, - UCCommunicationInformation, +import { expect, describe, it } from 'vitest' +import { InputExtRefElement, SCDQueries } from '../scd-queries/scd-query' +import { + IEDCommInfo, + UCCommunicationInformation, groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName, - ReceivedMessage, -} from "./uc-communication-information" -import { xmlStr } from "../../testfiles/simple_v5" + ReceivedMessage +} from './uc-communication-information' +import { xmlStr } from '../../testfiles/simple_v5' -describe("UCCommunicationInformation", () => { - it("IEDCommInfos", () => { - - // +describe('UCCommunicationInformation', () => { + it('IEDCommInfos', () => { + // // Arrange - // + // const parser = new DOMParser() - const doc = parser.parseFromString(xmlStr, "text/xml") as unknown as Element + const doc = parser.parseFromString( + xmlStr, + 'text/xml' + ) as unknown as Element const scdQueries = new SCDQueries(doc) const ucci = new UCCommunicationInformation(scdQueries) - // Note: use this if you want to debug the document in browser // globalThis.scd = doc // // Action - // + // const iedCommInfos: IEDCommInfo[] = ucci.IEDCommInfos() - - // + + // // Assert - // + // const expectedIEDCommInfos: IEDCommInfo[] = [ { - iedName: "IED2", + iedName: 'IED2', published: [ { - id: "IED2/CBSW/XSWI/SwitchGearBRCB_1", - name: "ReportCb", - targetIEDName: "IED1", - serviceType: "MMS", + id: 'IED2/CBSW/XSWI/SwitchGearBRCB_1', + name: 'ReportCb', + targetIEDName: 'IED1', + serviceType: 'MMS' }, { - id: "IED2/CBSW/XSWI/SwitchGearBRCB_2", - name: "ReportCb", - targetIEDName: "IED1", - serviceType: "MMS", - }, + id: 'IED2/CBSW/XSWI/SwitchGearBRCB_2', + name: 'ReportCb', + targetIEDName: 'IED1', + serviceType: 'MMS' + } ], - received: [], - }, + received: [] + } // { // iedName: "IED2", // published: [ @@ -57,131 +58,276 @@ describe("UCCommunicationInformation", () => { // ], // } ] - + // we don't check it yet - for(const iedCommInfo of iedCommInfos){ + for (const iedCommInfo of iedCommInfos) { iedCommInfo.received = [] } - expectedIEDCommInfos.forEach( expectedInfo => expect(iedCommInfos).toContainEqual(expectedInfo) ) + expectedIEDCommInfos.forEach((expectedInfo) => + expect(iedCommInfos).toContainEqual(expectedInfo) + ) }) - - describe("groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName", () => { + describe('groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName', () => { type TestCase = { - desc: string - elements: InputExtRefElement[] + desc: string + elements: InputExtRefElement[] expectedGroups: ReceivedMessage[] - } + } const testCases: TestCase[] = [ { - desc: "find a message", + desc: 'find a message', elements: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }) ], expectedGroups: [ - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), - ]}, - ], + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }) + ] + } + ] }, { - desc: "group data of the same message", + desc: 'group data of the same message', elements: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", daName: "q"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", daName: "stVal"}), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + daName: 'q' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + daName: 'stVal' + }) ], expectedGroups: [ - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", daName: "q"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", daName: "stVal"}), - ]}, - ], + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + daName: 'q' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + daName: 'stVal' + }) + ] + } + ] }, { - desc: "separate data of different messages", + desc: 'separate data of different messages', elements: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2"}), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2' + }) ], expectedGroups: [ - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1"}), - ]}, - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2"}), - ]}, - ], + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1' + }) + ] + }, + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2' + }) + ] + } + ] }, { - desc: "group the data of the same message and separate data of different messages", + desc: 'group the data of the same message and separate data of different messages', elements: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), - makeInputExtRefElement({iedName: "IED2", serviceType: "SMV", srcCBName: "MSVCB01"}), - makeInputExtRefElement({iedName: "IED2", serviceType: "SMV", srcCBName: "MSVCB01"}), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }), + makeInputExtRefElement({ + iedName: 'IED2', + serviceType: 'SMV', + srcCBName: 'MSVCB01' + }), + makeInputExtRefElement({ + iedName: 'IED2', + serviceType: 'SMV', + srcCBName: 'MSVCB01' + }) ], expectedGroups: [ - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB"}), - ]}, - {iedName: "IED2", serviceType: "SMV", srcCBName: "MSVCB01", data: [ - makeInputExtRefElement({iedName: "IED2", serviceType: "SMV", srcCBName: "MSVCB01"}), - makeInputExtRefElement({iedName: "IED2", serviceType: "SMV", srcCBName: "MSVCB01"}), - ]}, - ], + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB' + }) + ] + }, + { + iedName: 'IED2', + serviceType: 'SMV', + srcCBName: 'MSVCB01', + data: [ + makeInputExtRefElement({ + iedName: 'IED2', + serviceType: 'SMV', + srcCBName: 'MSVCB01' + }), + makeInputExtRefElement({ + iedName: 'IED2', + serviceType: 'SMV', + srcCBName: 'MSVCB01' + }) + ] + } + ] }, { - desc: "seperate data of different messages even if same service type", + desc: 'seperate data of different messages even if same service type', elements: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2"}), - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_3"}), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2' + }), + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_3' + }) ], expectedGroups: [ - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_1"}), - ]}, - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_2"}), - ]}, - {iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_3", data: [ - makeInputExtRefElement({iedName: "IED1", serviceType: "GOOSE", srcCBName: "GCB_3"}), - ]}, - ], - }, + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_1' + }) + ] + }, + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_2' + }) + ] + }, + { + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_3', + data: [ + makeInputExtRefElement({ + iedName: 'IED1', + serviceType: 'GOOSE', + srcCBName: 'GCB_3' + }) + ] + } + ] + } ] testCases.forEach(testFeature) - function testFeature(tc: TestCase){ + function testFeature(tc: TestCase) { it(tc.desc, () => { - const messages = groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName(tc.elements) + const messages = + groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName( + tc.elements + ) expect(messages).toEqual(tc.expectedGroups) }) } - }) - - }) - const defaultExtRef: InputExtRefElement = { - iedName: "string", - serviceType: "string", - ldInst: "string", - lnClass: "string", - lnInst: "string", - prefix: "string", - doName: "string", - daName: "string", - srcLDInst: "string", - srcPrefix: "string", - srcCBName: "string", - element: document.createElement("ExtRef"), + iedName: 'string', + serviceType: 'string', + ldInst: 'string', + lnClass: 'string', + lnInst: 'string', + prefix: 'string', + doName: 'string', + daName: 'string', + srcLDInst: 'string', + srcPrefix: 'string', + srcCBName: 'string', + element: document.createElement('ExtRef') +} +function makeInputExtRefElement(extRef: Partial) { + return { ...defaultExtRef, ...extRef } } -function makeInputExtRefElement( extRef: Partial){ - return {...defaultExtRef, ...extRef} -} \ No newline at end of file diff --git a/packages/core/src/use-cases/uc-communication-information.ts b/packages/core/src/use-cases/uc-communication-information.ts index 3103996e..2cb0fa51 100644 --- a/packages/core/src/use-cases/uc-communication-information.ts +++ b/packages/core/src/use-cases/uc-communication-information.ts @@ -1,25 +1,26 @@ -import { MessageType } from "../scd" -import type { IEDElement, InputExtRefElementWithDatSet, SCDQueries } from "../scd/scd-query" +import { MessageType } from '../scd-queries' +import type { + InputExtRefElementWithDatSet, + SCDQueries +} from '../scd-queries/scd-query' +// TYPES +import type { IEDElement } from '../scd-queries/types.scd-queries' -/** +/** * The name is temporary, rename it if you have a better one * UC = Use Case */ export class UCCommunicationInformation { - - constructor( - private readonly scdQueries: SCDQueries, - ){} + constructor(private readonly scdQueries: SCDQueries) {} public IEDCommInfos(): IEDCommInfo[] { - const ieds = this.scdQueries.searchIEDs() - const commInfos: IEDCommInfo[] = ieds.map(ied => { + const commInfos: IEDCommInfo[] = ieds.map((ied) => { return { - iedName: ied.name, + iedName: ied.name, // published: this.findPublishedMessages(ied), published: this.findPublishedMessages_V2(ied), - received: this.findReceivedMessages(ied, ieds), + received: this.findReceivedMessages(ied, ieds) } }) return commInfos @@ -31,12 +32,11 @@ export class UCCommunicationInformation { const baysWithIEDs = new Map() ieds.forEach((ied) => { const bayNames = this.scdQueries.getBaysByIEDName(ied.iedName) - + bayNames.forEach((bayName) => { let setList: IEDCommInfo[] | undefined = [] - if (!baysWithIEDs.has(bayName)) - baysWithIEDs.set(bayName, []) + if (!baysWithIEDs.has(bayName)) baysWithIEDs.set(bayName, []) setList = baysWithIEDs.get(bayName) setList?.push(ied) @@ -51,47 +51,45 @@ export class UCCommunicationInformation { const busesWithIEDs = new Map() ieds.forEach((ied) => { - const subnetworks = this.scdQueries.getSubnetworksByIEDName(ied.iedName) - + const subnetworks = this.scdQueries.getSubnetworksByIEDName( + ied.iedName + ) + subnetworks.forEach((subnetwork) => { let setList: IEDCommInfo[] | undefined = [] - const subnetworkName = subnetwork.getAttribute("name") + const subnetworkName = subnetwork.getAttribute('name') if (subnetworkName !== null) { - - if (!busesWithIEDs.has(subnetworkName)) + if (!busesWithIEDs.has(subnetworkName)) busesWithIEDs.set(subnetworkName, []) - + setList = busesWithIEDs.get(subnetworkName) setList?.push(ied) - } - }) }) return busesWithIEDs } - private findPublishedMessages_V2(ied: IEDElement): PublishedMessage_V2[]{ + private findPublishedMessages_V2(ied: IEDElement): PublishedMessage_V2[] { const messages: PublishedMessage_V2[] = [] - + const reportControlInfos = this.findPublishedReportControls(ied) - for(const info of reportControlInfos){ + for (const info of reportControlInfos) { messages.push({ - id: info.rptID, - name: info.name, + id: info.rptID, + name: info.name, targetIEDName: info.clientIEDName, - serviceType: MessageType.MMS, - serviceCbName: "MMS", - serviceDatSet: "not implemented yet", + serviceType: MessageType.MMS, + serviceCbName: 'MMS', + serviceDatSet: 'not implemented yet' }) } return messages } - /** * @@ -99,14 +97,14 @@ export class UCCommunicationInformation { * because GOOSE and Sampled Measure Values are always are inside an input * at the receiving IED and we use that to create the connections * This leads to that, that we ignore published messages that nobody receives - * + * * The current version takes only GOOSE messages into consideration * an therefore not compatible with Sampled Measured Values and MMS (ReportControls) * So we deprecate this function and rewrite it in a more general way - * + * * deprecated - * @param ied - * @returns + * @param ied + * @returns */ // private findPublishedMessages(ied: IEDElement): PublishedMessage[] { // const published = [] @@ -115,7 +113,7 @@ export class UCCommunicationInformation { // for (const gseControl of gseControls) { // const isGSEControlIrrelevant = gseControl.datSet === "" - // if (isGSEControlIrrelevant) { continue} + // if (isGSEControlIrrelevant) { continue} // const message: Partial = {} // message.gseControlName = gseControl.name @@ -153,60 +151,75 @@ export class UCCommunicationInformation { // return published // } - private findReceivedMessages(ied: IEDElement, allIeds: IEDElement[]): ReceivedMessage[] { + private findReceivedMessages( + ied: IEDElement, + allIeds: IEDElement[] + ): ReceivedMessage[] { const inputs = this.scdQueries.searchInputs({ root: ied.element }) - const extRefs = inputs.map(input => this.scdQueries.searchExtRef({ root: input.element })).flat() + const extRefs = inputs + .map((input) => + this.scdQueries.searchExtRef({ root: input.element }) + ) + .flat() const extRefsWithConnectionID = extRefs.map((el) => { const srcCBName = el.srcCBName const iedName = el.iedName - const parentIed = allIeds.find(i => i.name === iedName) + const parentIed = allIeds.find((i) => i.name === iedName) if (!parentIed) { const newInput: InputExtRefElementWithDatSet = { ...el, - datSet: null, + datSet: null } return newInput } - const connection = this.scdQueries.searchGSEControlByName(srcCBName, {root: parentIed?.element}) + const connection = this.scdQueries.searchGSEControlByName( + srcCBName, + { root: parentIed?.element } + ) const datSet = connection?.datSet const newInput: InputExtRefElementWithDatSet = { ...el, - datSet: datSet, + datSet: datSet } return newInput }) - const messages = groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName(extRefsWithConnectionID) + const messages = + groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName( + extRefsWithConnectionID + ) return messages } - private findPublishedReportControls(ied: IEDElement): ReportControlInfo[] { const controls: ReportControlInfo[] = [] - const reportControls = this.scdQueries.searchReportControls({root: ied.element}) - for(const reportControl of reportControls){ - const clientLNs = this.scdQueries.searcClientLNs({root: reportControl.element }) + const reportControls = this.scdQueries.searchReportControls({ + root: ied.element + }) + for (const reportControl of reportControls) { + const clientLNs = this.scdQueries.searcClientLNs({ + root: reportControl.element + }) - for(const clientLN of clientLNs){ + for (const clientLN of clientLNs) { controls.push({ clientIEDName: clientLN.iedName, - rptID: reportControl.rptID, - name: reportControl.name, + rptID: reportControl.rptID, + name: reportControl.name }) } } - + return controls } - } -export type MessageSourceMap = {[iedName: string]: ReceivedMessage[]} +export type MessageSourceMap = { [iedName: string]: ReceivedMessage[] } export type ReportControlInfo = { clientIEDName: string @@ -224,14 +237,14 @@ export type IEDCommInfo = { * @deprecated see `findPublishedMessages` */ export type PublishedMessage = { - dataSetName: string + dataSetName: string gseControlName: string - LDeviceInst: string + LDeviceInst: string subNetworkName: string } export type PublishedMessage_V2 = { - id: string, + id: string name: string targetIEDName: string serviceType: string @@ -240,40 +253,61 @@ export type PublishedMessage_V2 = { } export type ReceivedMessage = { - iedName: string // to show + iedName: string // to show serviceType: string // to filter - srcCBName: string // to show - datSet: string // to show - data: InputExtRefElementWithDatSet[] + srcCBName: string // to show + datSet: string // to show + data: InputExtRefElementWithDatSet[] } - -type TempKey = {iedName: string, serviceType: string, srcCBName: string, datSet: string} +type TempKey = { + iedName: string + serviceType: string + srcCBName: string + datSet: string +} export function groupInputExtRefElementsByIedNameServiceTypeAndSrcCBName( - elements: InputExtRefElementWithDatSet[], + elements: InputExtRefElementWithDatSet[] ): ReceivedMessage[] { - - const indexed: { [key: string]: {elements:InputExtRefElementWithDatSet[], key: TempKey} } = {} + const indexed: { + [key: string]: { + elements: InputExtRefElementWithDatSet[] + key: TempKey + } + } = {} for (const element of elements) { - if(element.iedName === ""){ continue } + if (element.iedName === '') { + continue + } const key = `${element.iedName}_${element.serviceType}_${element.srcCBName}_${element.datSet}` - const tempKey = {iedName: element.iedName, serviceType: element.serviceType, srcCBName: element.srcCBName, datSet: element.datSet} - + const tempKey = { + iedName: element.iedName, + serviceType: element.serviceType, + srcCBName: element.srcCBName, + datSet: element.datSet + } + if (!indexed[key]) { - indexed[key] = {elements: [], key: tempKey} + indexed[key] = { elements: [], key: tempKey } } - + indexed[key].elements.push(element) } - + const grouped: ReceivedMessage[] = [] - + for (const obj of Object.values(indexed)) { - const {iedName, serviceType, srcCBName, datSet} = obj.key - - grouped.push({ iedName, serviceType, srcCBName, datSet, data: obj.elements }) + const { iedName, serviceType, srcCBName, datSet } = obj.key + + grouped.push({ + iedName, + serviceType, + srcCBName, + datSet, + data: obj.elements + }) } return grouped -} \ No newline at end of file +} diff --git a/packages/core/src/use-cases/uc-network-information.ts b/packages/core/src/use-cases/uc-network-information.ts index 2a7ea017..f9666cb1 100644 --- a/packages/core/src/use-cases/uc-network-information.ts +++ b/packages/core/src/use-cases/uc-network-information.ts @@ -1,7 +1,6 @@ -import { SCDElement, SCDQueries } from "../scd" +import type { SCDElement, SCDQueries } from '../scd-queries' - -export type IEDNetworkInfoV3 = {iedName: string, networkInfo: Networking} +export type IEDNetworkInfoV3 = { iedName: string; networkInfo: Networking } // export type IEDNetworkInfoV3 = { // iedName: string, @@ -10,33 +9,32 @@ export type IEDNetworkInfoV3 = {iedName: string, networkInfo: Networking} // } export class UCNetworkInformation { - constructor( - private readonly scdQueries: SCDQueries, - ){} - - + constructor(private readonly scdQueries: SCDQueries) {} + public gatherNetworkings(): Networking[] { const connectedAPs = this.scdQueries.searchConnectedAPs() - + const networkings: Networking[] = [] - - for( const ap of connectedAPs ) { + + for (const ap of connectedAPs) { const address = this.extractAddressFromAP(ap.element) - const physConnections = this.extractPhysConnectionsFromAP(ap.element) - - for( const conn of physConnections ) { + const physConnections = this.extractPhysConnectionsFromAP( + ap.element + ) + + for (const conn of physConnections) { const networking: Networking = { - iedName: ap.iedName, - ipAddress: address.ip, - ipSubnet: address.ipSubnet, - ipGateway: address.ipGateway, - connectedAP: ap.apName, - plug: conn.plug, - type: conn.type, - cable: conn.cable, - port: conn.port, - connectedNetworking: undefined, - _physConnectionElement: conn.element, + iedName: ap.iedName, + ipAddress: address.ip, + ipSubnet: address.ipSubnet, + ipGateway: address.ipGateway, + connectedAP: ap.apName, + plug: conn.plug, + type: conn.type, + cable: conn.cable, + port: conn.port, + connectedNetworking: undefined, + _physConnectionElement: conn.element } networkings.push(networking) } @@ -47,153 +45,176 @@ export class UCNetworkInformation { // networkInfo: this.extractNetworkInfo(cap.element), // } } - - - // networkings.forEach( (networking) => addConnectedIedToPhysConn(networking, networkings) ) + + // networkings.forEach( (networking) => addConnectedIedToPhysConn(networking, networkings) ) this.enrichNetworkingWithConnectedIEDs(networkings) - - + return networkings } - - + private extractAddressFromAP(apElement: Element): Address { - - const ip = this.scdQueries.searchConnectedAPIP({root: apElement})?.element?.innerHTML ?? "" - const ipSubnet = this.scdQueries.searchConnectedAPIPSubnet({root: apElement})?.element?.innerHTML ?? "" - const ipGateway = this.scdQueries.searchConnectedAPIPGateway({root: apElement})?.element?.innerHTML ?? "" - + const ip = + this.scdQueries.searchConnectedAPIP({ root: apElement })?.element + ?.innerHTML ?? '' + const ipSubnet = + this.scdQueries.searchConnectedAPIPSubnet({ root: apElement }) + ?.element?.innerHTML ?? '' + const ipGateway = + this.scdQueries.searchConnectedAPIPGateway({ root: apElement }) + ?.element?.innerHTML ?? '' + return { - ip, - ipSubnet, - ipGateway, + ip, + ipSubnet, + ipGateway } satisfies Address } - + private extractPhysConnectionsFromAP(apElement: Element): PhysConnection[] { - const connectionElements = this.scdQueries.seachConnectedPhysConnections({root: apElement}) - const physConnections = connectionElements.map(physConnection => { - const cable = this.scdQueries.seachPhysConnectionCable({root: physConnection.element})?.element.innerHTML ?? "" - const port = this.scdQueries.seachPhysConnectionPort({root: physConnection.element})?.element.innerHTML ?? "" - const type = this.scdQueries.seachPhysConnectionType({root: physConnection.element})?.element.innerHTML ?? "" - const plug = this.scdQueries.seachPhysConnectionPlug({root: physConnection.element})?.element.innerHTML ?? "" - + const connectionElements = + this.scdQueries.seachConnectedPhysConnections({ root: apElement }) + const physConnections = connectionElements.map((physConnection) => { + const cable = + this.scdQueries.seachPhysConnectionCable({ + root: physConnection.element + })?.element.innerHTML ?? '' + const port = + this.scdQueries.seachPhysConnectionPort({ + root: physConnection.element + })?.element.innerHTML ?? '' + const type = + this.scdQueries.seachPhysConnectionType({ + root: physConnection.element + })?.element.innerHTML ?? '' + const plug = + this.scdQueries.seachPhysConnectionPlug({ + root: physConnection.element + })?.element.innerHTML ?? '' + return { cable, port, type, plug, - element: physConnection.element, + element: physConnection.element // node: physConnection, // connectedIed: undefined, } satisfies PhysConnection }) - + return physConnections } - - public extractPhysConnectionCable(physConnElement: Element): SCDElement | null { - const cableElement = this.scdQueries.seachPhysConnectionCable({root: physConnElement}) + + public extractPhysConnectionCable( + physConnElement: Element + ): SCDElement | null { + const cableElement = this.scdQueries.seachPhysConnectionCable({ + root: physConnElement + }) return cableElement } - + // private addConnectedIedToPhysConn(iedNetworkInfo: IEDNetworkInfoV3[]): void { - private enrichNetworkingWithConnectedIEDs(networkings:Networking[]): void { + private enrichNetworkingWithConnectedIEDs(networkings: Networking[]): void { for (const networking of networkings) { - - const connectedNetworking = this.findConnectedNetworkingByCableName(networking, networkings) - + const connectedNetworking = this.findConnectedNetworkingByCableName( + networking, + networkings + ) + if (connectedNetworking.length !== 1) { - console.log({"level": "debug", msg: "could not match networking exaclty, so no cable will be shown", ied: networking.iedName }) + console.log({ + level: 'debug', + msg: 'could not match networking exaclty, so no cable will be shown', + ied: networking.iedName + }) networking.connectedNetworking = null continue } - + networking.connectedNetworking = connectedNetworking[0] } } - - private findConnectedNetworkingByCableName(srcNetworking: Networking, networkings: Networking[]): Networking[] { - const connectedNetworking = networkings.filter( otherNetworking => { + + private findConnectedNetworkingByCableName( + srcNetworking: Networking, + networkings: Networking[] + ): Networking[] { + const connectedNetworking = networkings.filter((otherNetworking) => { const isSameNetworking = srcNetworking === otherNetworking - const hasSameCableName = srcNetworking.cable === otherNetworking.cable - + const hasSameCableName = + srcNetworking.cable === otherNetworking.cable + return !isSameNetworking && hasSameCableName }) - + return connectedNetworking } - + // private findConnectedIEDs(ied: IED): IED[] { - + // const connectedIEDs = this.ieds.filter( otherIED => { // if(ied === otherIED){ return false } - + // const connected = ied.networking.some(iedNetworking => { // return otherIED.networking.some(otherIEDNetworking => { // return iedNetworking.cable === otherIEDNetworking.cable // }) // }) - + // return connected // }) - + // return connectedIEDs // } - - - } - - + export interface Address { - ip: string - ipSubnet: string - ipGateway: string - } - + ip: string + ipSubnet: string + ipGateway: string +} + export type Networking = { - iedName: string - ipAddress: string - ipSubnet: string - ipGateway: string - connectedAP: string - plug: string - type: string - cable: string - port: string - connectedNetworking?: Networking | null - _physConnectionElement: Element // only use this if you know what you are doing - } - + iedName: string + ipAddress: string + ipSubnet: string + ipGateway: string + connectedAP: string + plug: string + type: string + cable: string + port: string + connectedNetworking?: Networking | null + _physConnectionElement: Element // only use this if you know what you are doing +} + export type PhysConnection = SCDElement & { - - type: string - plug: string - port: string - cable: string - // connectedIed?: string; - // node: ConnectedAPPhyConnectionElement; - } - + type: string + plug: string + port: string + cable: string + // connectedIed?: string; + // node: ConnectedAPPhyConnectionElement; +} + // export type VLanConnection = { // vLanId: string; // macAddress: string; // messageType: MessageType; // } - + // export interface SubnetworkConnection extends IPInfo { // subnetwork: string // } - + // export type IEDNetworkInfo = { // iedName: string // subneworkConnections: SubnetworkConnection[] // } - + // type Address = { // ip: string // ipSubnet: string // ipGateway: string // } - \ No newline at end of file diff --git a/packages/core/src/use-cases/uc-type-type-switcher.spec.ts b/packages/core/src/use-cases/uc-type-type-switcher.spec.ts index 30a0bc4c..636b7a0e 100644 --- a/packages/core/src/use-cases/uc-type-type-switcher.spec.ts +++ b/packages/core/src/use-cases/uc-type-type-switcher.spec.ts @@ -1,29 +1,31 @@ -import { describe, expect, it } from "vitest" -import { xmlStr } from "../../testfiles/simple_v5" -import { SCDQueries } from "../scd" -import { UCTypeTypeSwitcher } from "./uc-type-type-switcher" +import { describe, expect, it } from 'vitest' +import { xmlStr } from '../../testfiles/simple_v5' +import { SCDQueries } from '../scd-queries' +import { UCTypeTypeSwitcher } from './uc-type-type-switcher' -describe("UC: Type Type Switcher", () => { - it("finds duplicate Object Types", async () => { - // +describe('UC: Type Type Switcher', () => { + it('finds duplicate Object Types', async () => { + // // Arrange - // + // const parser = new DOMParser() - const doc = parser.parseFromString(xmlStr, "text/xml") as unknown as Element + const doc = parser.parseFromString( + xmlStr, + 'text/xml' + ) as unknown as Element const scdQueries = new SCDQueries(doc) const uc = new UCTypeTypeSwitcher(scdQueries) - // + // // Act - // + // const duplicateTypes = await uc.findDuplicateDataObjectTypes() - console.log({leve: "test", duplicateTypes}) + console.log({ leve: 'test', duplicateTypes }) - // + // // Assert - // + // expect(duplicateTypes.length).toEqual(1) expect(duplicateTypes[0].length).toEqual(3) - }) -}) \ No newline at end of file +}) diff --git a/packages/core/src/use-cases/uc-type-type-switcher.ts b/packages/core/src/use-cases/uc-type-type-switcher.ts index 465c4b3e..0aa20f10 100644 --- a/packages/core/src/use-cases/uc-type-type-switcher.ts +++ b/packages/core/src/use-cases/uc-type-type-switcher.ts @@ -1,67 +1,77 @@ -import type { SCDElement, IdentifiableElement } from "../scd/scd-query" -import type { SCDQueries } from "../scd/scd-query" -import { hashElement } from "../xml/hash" +import type { SCDElement, IdentifiableElement } from '../scd-queries/scd-query' +import type { SCDQueries } from '../scd-queries/scd-query' +import { hashElement } from '../scd-xml/hash' -/** +/** * The name is temporary, rename it if you have a better one * UC = Use Case */ // TODO: rename to UCTypeSwitcher export class UCTypeTypeSwitcher { - - constructor( - private readonly scdQueries: SCDQueries, - ){} + constructor(private readonly scdQueries: SCDQueries) {} public async findDuplicateDataObjectTypes(): Promise { - const duplicates = await this.findDuplicateTypes(this.scdQueries.searchDOTypes.bind(this.scdQueries)) + const duplicates = await this.findDuplicateTypes( + this.scdQueries.searchDOTypes.bind(this.scdQueries) + ) return duplicates } - public async findDuplicateDataAttributeTypes(): Promise{ - const duplicates = await this.findDuplicateTypes(this.scdQueries.searchDATypes.bind(this.scdQueries)) + public async findDuplicateDataAttributeTypes(): Promise { + const duplicates = await this.findDuplicateTypes( + this.scdQueries.searchDATypes.bind(this.scdQueries) + ) return duplicates } - public async findDuplicateLogicalNodeTypes(): Promise{ - const duplicates = await this.findDuplicateTypes(this.scdQueries.searchLNodeTypes.bind(this.scdQueries)) + public async findDuplicateLogicalNodeTypes(): Promise { + const duplicates = await this.findDuplicateTypes( + this.scdQueries.searchLNodeTypes.bind(this.scdQueries) + ) return duplicates } - public async findDuplicateEnumTypes(): Promise{ - const duplicates = await this.findDuplicateTypes(this.scdQueries.searchEnumTypes.bind(this.scdQueries)) + public async findDuplicateEnumTypes(): Promise { + const duplicates = await this.findDuplicateTypes( + this.scdQueries.searchEnumTypes.bind(this.scdQueries) + ) return duplicates } - - public async findDuplicateTypes(searchElements: () => IdentifiableElement[]): Promise{ + + public async findDuplicateTypes( + searchElements: () => IdentifiableElement[] + ): Promise { const types = searchElements() const hashedTypes: HashedElement[] = await Promise.all( - types.map(this.createHashedElement.bind(this)), + types.map(this.createHashedElement.bind(this)) ) const grouped = this.groupByHash(hashedTypes) - const duplicates = Object.values(grouped).filter(group => group.length > 1) + const duplicates = Object.values(grouped).filter( + (group) => group.length > 1 + ) return duplicates } - // TODO: this is a big perofrmance bottleneck // we should delay until the user selects elements from duplica types (2nd column) private findUserElements(elId: string): SCDElement[] { - const elements:SCDElement[] = [ + const elements: SCDElement[] = [ ...this.scdQueries.searchElementsByTypeAttr(elId), - ...this.scdQueries.searchElementsByLnTypeAttr(elId), + ...this.scdQueries.searchElementsByLnTypeAttr(elId) ] return elements } - private async createHashedElement(el: IdentifiableElement): Promise{ + private async createHashedElement( + el: IdentifiableElement + ): Promise { const hash = await hashElement(el.element) const usages = this.findUserElements(el.id) return { element: el, hash, - usages, + usages } } @@ -75,14 +85,12 @@ export class UCTypeTypeSwitcher { } return grouped } - - } export type HashedElement = { - element: IdentifiableElement, - hash: string, - usages: SCDElement[], + element: IdentifiableElement + hash: string + usages: SCDElement[] } export type HashedElementGroup = HashedElement[] // basically HashedElement[] @@ -93,4 +101,4 @@ export type HashedElementCollective = HashedElementGroup[] // basically HashedEl */ type GroupedHashedTypedElements = { [hash: string]: HashedElement[] -} \ No newline at end of file +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts new file mode 100644 index 00000000..b51ff121 --- /dev/null +++ b/packages/core/src/utils/index.ts @@ -0,0 +1 @@ +export type * from './utils' diff --git a/packages/core/src/utils/utils.d.ts b/packages/core/src/utils/utils.d.ts new file mode 100644 index 00000000..834a4ab8 --- /dev/null +++ b/packages/core/src/utils/utils.d.ts @@ -0,0 +1 @@ +export type ValueOf = T[keyof T] diff --git a/packages/core/src/xml/describers.ts b/packages/core/src/xml/describers.ts deleted file mode 100644 index 2f906bed..00000000 --- a/packages/core/src/xml/describers.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Source from: https://github.com/ca-d/oscd-template-generator/blob/main/generate-templates.js - */ - -function describeEnumType(element: Element) { - let _a, _b - const vals: {[key: string]: unknown} = {} - - for (const val of Array.from(element.children) - .filter(child => child.tagName === "EnumVal") - .sort((v1, v2) => { - let _a, _b - return parseInt((_a = v1.getAttribute("ord")) !== null && _a !== void 0 ? _a : "", 10) - - parseInt((_b = v2.getAttribute("ord")) !== null && _b !== void 0 ? _b : "", 10) - })) - vals[(_a = val.getAttribute("ord")) !== null && _a !== void 0 ? _a : ""] = (_b = val.textContent) !== null && _b !== void 0 ? _b : "" - - return { vals } -} - -function describeDAType(element: Element) { - let _a - const bdas: {[key: string]: unknown} = {} - for (const bda of Array.from(element.children) - .filter(child => child.tagName === "BDA") - .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { - const [bType, type, dchg, dupd, qchg] = [ - "bType", - "type", - "dchg", - "dupd", - "qchg", - ].map(attr => bda.getAttribute(attr)) - bdas[(_a = bda.getAttribute("name")) !== null && _a !== void 0 ? _a : ""] = { bType, type, dchg, dupd, qchg } - } - return { bdas } -} -function describeDOType(element: Element) { - const sdos: {[key: string]: unknown} = {} - for (const sdo of Array.from(element.children) - .filter(child => child.tagName === "SDO") - .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { - const [name, type, transient] = ["name", "type", "transient"].map(attr => sdo.getAttribute(attr)) - sdos[name !== null && name !== void 0 ? name : ""] = { type, transient } - } - const das: {[key: string]: unknown} = {} - for (const da of Array.from(element.children) - .filter(child => child.tagName === "DA") - .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { - const [name, fc, bType, type, dchg, dupd, qchg] = [ - "name", - "fc", - "bType", - "type", - "dchg", - "dupd", - "qchg", - ].map(attr => da.getAttribute(attr)) - das[name !== null && name !== void 0 ? name : ""] = { - fc, - bType, - type, - dchg, - dupd, - qchg, - } - } - return { - sdos, - das, - cdc: element.getAttribute("cdc"), - } -} -function describeLNodeType(element: Element){ - const dos: {[key: string]: unknown} = {} - for (const doElement of Array.from(element.children) - .filter(child => child.tagName === "DO") - .sort((c1, c2) => c1.outerHTML.localeCompare(c2.outerHTML))) { - const [name, type, transient] = ["name", "type", "transient"].map(attr => doElement.getAttribute(attr)) - dos[name !== null && name !== void 0 ? name : ""] = { type, transient } - } - return { - dos, - lnClass: element.getAttribute("lnClass"), - } -} - -type DescriberFn = (element: Element) => any -const typeDescriptions: {[key: string]: DescriberFn } = { - EnumType: describeEnumType, - DAType: describeDAType, - DOType: describeDOType, - LNodeType: describeLNodeType, -} -export function describeElement(element: Element) { - let _a, _b - return ((_b = (_a = typeDescriptions[element.tagName]) === null || _a === void 0 ? void 0 : _a.call(typeDescriptions, element)) !== null && _b !== void 0 ? _b : { xml: element.outerHTML }) -} diff --git a/packages/core/src/xml/index.ts b/packages/core/src/xml/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index dc3f8314..8396e629 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,111 +1,111 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */, + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "ESNext", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "ESNext" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + "baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */, + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "emitDeclarationOnly": true /* Only output d.ts files and not JavaScript files. */, + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + "removeComments": true /* Disable emitting comments. */, + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": ["./src"], - "exclude": ["./testfiles"] + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["./src"], + "exclude": ["./testfiles"] } diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 6998bceb..10355bc8 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -1,12 +1,11 @@ -import { defineConfig } from "vitest/config" - +import { defineConfig } from 'vitest/config' export default defineConfig({ test: { browser: { - enabled: true, + enabled: true, headless: false, - name: "chrome", - }, - }, -}) \ No newline at end of file + name: 'chrome' + } + } +}) diff --git a/packages/plugins/type-designer/.vscode/extensions.json b/packages/plugins/type-designer/.vscode/extensions.json deleted file mode 100644 index bdef8201..00000000 --- a/packages/plugins/type-designer/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["svelte.svelte-vscode"] -} diff --git a/packages/plugins/type-designer/index.html b/packages/plugins/type-designer/index.html deleted file mode 100644 index 8600ec0e..00000000 --- a/packages/plugins/type-designer/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - TypeDesigner - - -
- - - diff --git a/packages/plugins/type-designer/package.json b/packages/plugins/type-designer/package.json index 1c615ee4..9f44c9d3 100644 --- a/packages/plugins/type-designer/package.json +++ b/packages/plugins/type-designer/package.json @@ -1,31 +1,31 @@ { - "name": "@oscd-plugins/type-designer", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "_build:watch": "NODE_ENV=development vite build --watch", - "build:watch": "concurrently 'npm:_build:watch' 'npm:preview'", - "preview": "vite preview --port 55908", - "check": "svelte-check --tsconfig ./tsconfig.json", - "check:watch": "npm run check -- --watch", - "version": "echo $npm_package_version" - }, - "dependencies": { - "@oscd-plugins/uilib": "../../uilib", - "concurrently": "^7.6.0" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tsconfig/svelte": "^5.0.4", - "sass": "^1.61.0", - "svelte": "^4.2.18", - "svelte-check": "^3.8.5", - "tslib": "^2.6.3", - "typescript": "^5.5.3", - "vite": "^5.4.1", - "vite-plugin-css-injected-by-js": "^3.1.1" - } + "name": "@oscd-plugins/type-designer", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "_build:watch": "NODE_ENV=development vite build --watch", + "build:watch": "concurrently 'npm:_build:watch' 'npm:preview'", + "preview": "vite preview --port 55908", + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "npm run check -- --watch", + "version": "echo $npm_package_version" + }, + "dependencies": { + "@oscd-plugins/uilib": "workspace:^", + "concurrently": "^7.6.0" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.1.1", + "@tsconfig/svelte": "^5.0.4", + "sass": "^1.61.0", + "svelte": "^4.2.18", + "svelte-check": "^3.8.5", + "tslib": "^2.6.3", + "typescript": "^5.5.3", + "vite": "^5.4.1", + "vite-plugin-css-injected-by-js": "^3.1.1" + } } diff --git a/packages/plugins/type-designer/src/plugin.svelte b/packages/plugins/type-designer/src/plugin.svelte index b4546aa9..07e52e07 100644 --- a/packages/plugins/type-designer/src/plugin.svelte +++ b/packages/plugins/type-designer/src/plugin.svelte @@ -1,15 +1,14 @@ {#if doc} - + {/if} diff --git a/packages/plugins/type-designer/src/plugin.ts b/packages/plugins/type-designer/src/plugin.ts index 94519fa5..b10f2db8 100644 --- a/packages/plugins/type-designer/src/plugin.ts +++ b/packages/plugins/type-designer/src/plugin.ts @@ -1,30 +1,38 @@ import Plugin from './plugin.svelte' -import * as pkg from "../package.json"; +import * as pkg from '../package.json' export default class NewPlugin extends HTMLElement { - private plugin: Plugin - + connectedCallback() { - this.attachShadow({ mode: "open" }); + this.attachShadow({ mode: 'open' }) this.plugin = new Plugin({ target: this.shadowRoot, props: { doc: this._doc + // editCount: -1 } - }); + }) - const style = document.createElement("style"); - style.innerHTML = globalThis.pluginStyle[pkg.name]; - this.shadowRoot.appendChild(style); + const style = document.createElement('style') + style.innerHTML = globalThis.pluginStyle[pkg.name] + this.shadowRoot!.appendChild(style) } private _doc: XMLDocument - public set doc(newDoc: XMLDocument){ + public set doc(newDoc: XMLDocument) { this._doc = newDoc - if(!this.plugin) { return } + if (!this.plugin) { + return + } - this.plugin.$set({doc: newDoc}) + this.plugin.$set({ doc: newDoc }) } + // public set editCount(newCount: number) { + // if (!this.plugin) { + // return + // } + // this.plugin.$set({ editCount: newCount }) + // } } diff --git a/packages/plugins/type-designer/svelte.config.js b/packages/plugins/type-designer/svelte.config.js index b0683fd2..8ebf1fae 100644 --- a/packages/plugins/type-designer/svelte.config.js +++ b/packages/plugins/type-designer/svelte.config.js @@ -1,7 +1,7 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' export default { - // Consult https://svelte.dev/docs#compile-time-svelte-preprocess - // for more information about preprocessors - preprocess: vitePreprocess(), + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess() } diff --git a/packages/plugins/type-designer/tsconfig.json b/packages/plugins/type-designer/tsconfig.json index c4e1c5fe..8c35516f 100644 --- a/packages/plugins/type-designer/tsconfig.json +++ b/packages/plugins/type-designer/tsconfig.json @@ -1,20 +1,25 @@ { - "extends": "@tsconfig/svelte/tsconfig.json", - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "module": "ESNext", - "resolveJsonModule": true, - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable checkJs if you'd like to use dynamic types in JS. - * Note that setting allowJs false does not prevent the use - * of JS in `.svelte` files. - */ - "allowJs": true, - "checkJs": true, - "isolatedModules": true - }, - "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], - "references": [{ "path": "./tsconfig.node.json" }] + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.ts", + "src/**/*.js", + "src/**/*.svelte" + ], + "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/packages/plugins/type-designer/tsconfig.node.json b/packages/plugins/type-designer/tsconfig.node.json index 65dbdb96..73a2040d 100644 --- a/packages/plugins/type-designer/tsconfig.node.json +++ b/packages/plugins/type-designer/tsconfig.node.json @@ -1,8 +1,8 @@ { - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node" - }, - "include": ["vite.config.ts"] + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node" + }, + "include": ["vite.config.ts"] } diff --git a/packages/plugins/type-designer/vite.config.ts b/packages/plugins/type-designer/vite.config.ts index 85562a91..40286a25 100644 --- a/packages/plugins/type-designer/vite.config.ts +++ b/packages/plugins/type-designer/vite.config.ts @@ -9,21 +9,24 @@ export default defineConfig({ plugins: [ svelte(), cssInjectedByJsPlugin({ - styleId: process.env.npm_package_name, - injectCodeFunction: function injectCodeCustomRunTimeFunction(cssCode: string, options: any) { - if(!globalThis.pluginStyle){ - globalThis.pluginStyle = {} - } - globalThis.pluginStyle[options.styleId] = cssCode - } - }), + styleId: process.env.npm_package_name, + injectCodeFunction: function injectCodeCustomRunTimeFunction( + cssCode: string, + options: any + ) { + if (!globalThis.pluginStyle) { + globalThis.pluginStyle = {} + } + globalThis.pluginStyle[options.styleId] = cssCode + } + }) ], - build:{ - lib:{ - entry: "src/plugin.ts", + build: { + lib: { + entry: 'src/plugin.ts', formats: ['es'], - fileName: "index", + fileName: 'index' }, - sourcemap: isDevelopment ? "inline" : false, + sourcemap: isDevelopment ? 'inline' : false } }) diff --git a/packages/uilib/.eslintignore b/packages/uilib/.eslintignore deleted file mode 100644 index 38972655..00000000 --- a/packages/uilib/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/packages/uilib/.eslintrc.cjs b/packages/uilib/.eslintrc.cjs deleted file mode 100644 index 47800e3b..00000000 --- a/packages/uilib/.eslintrc.cjs +++ /dev/null @@ -1,38 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - plugins: ['svelte3', '@typescript-eslint'], - ignorePatterns: ['*.cjs'], - overrides: [ - { - files: ['*.svelte'], - processor: 'svelte3/svelte3', - rules: { - "missing-custom-element-compile-options": "off", - "comma-dangle": "off", - } - }, - ], - settings: { - 'svelte3/typescript': () => require('typescript'), - }, - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - }, - env: { - browser: true, - es2017: true, - node: true, - }, - rules: { - semi: ["error", "never"], - quotes: ["error", "double"], - "missing-custom-element-compile-options": "off", - indent: ["error", "tab"], - "no-tabs": "off", - "comma-dangle": ["error", "always-multiline"], - "key-spacing": ["error", { "align": "value" }], - } -}; diff --git a/packages/uilib/package.json b/packages/uilib/package.json index 39358d33..88aa49a0 100644 --- a/packages/uilib/package.json +++ b/packages/uilib/package.json @@ -14,14 +14,14 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test": "npm run test:watch -- --run", "test:watch": "vitest", - "coverage": "npm run test:watch -- --run --coverage", - "lint": "eslint .", - "fix": "eslint . --fix --quiet" + "coverage": "npm run test:watch -- --run --coverage" }, "dependencies": { "@material/typography": "^14.0.0", "@oscd-plugins/core": "../core", + "@smui-extra/accordion": "7.0.0-beta.8", "@smui/button": "7.0.0-beta.8", + "@smui/card": "7.0.0-beta.8", "@smui/checkbox": "7.0.0-beta.8", "@smui/chips": "7.0.0-beta.8", "@smui/common": "7.0.0-beta.8", @@ -31,7 +31,6 @@ "@smui/select": "7.0.0-beta.8", "@smui/snackbar": "7.0.0-beta.8", "@smui/textfield": "7.0.0-beta.8", - "@smui-extra/accordion": "7.0.0-beta.8", "@xyflow/svelte": "^0.0.33", "d3-path": "^3.1.0", "elkjs": "^0.8.2", @@ -64,4 +63,4 @@ "vitest": "^0.30.1" }, "type": "module" -} \ No newline at end of file +} diff --git a/packages/uilib/src/lib/plugins/type-designer/+page.svelte b/packages/uilib/src/lib/plugins/type-designer/+page.svelte index 621c785d..0f27c999 100644 --- a/packages/uilib/src/lib/plugins/type-designer/+page.svelte +++ b/packages/uilib/src/lib/plugins/type-designer/+page.svelte @@ -1,15 +1,12 @@ + + + + - - - - diff --git a/packages/uilib/src/lib/plugins/type-designer/Readme.md b/packages/uilib/src/lib/plugins/type-designer/Readme.md index a73e599e..1cd06194 100644 --- a/packages/uilib/src/lib/plugins/type-designer/Readme.md +++ b/packages/uilib/src/lib/plugins/type-designer/Readme.md @@ -2,6 +2,10 @@ This plugin serves as a collection of templates available for creating a substation schema. +## Tech overview + +[Diagram.drawio](./diagram.drawio) + ---- --[Naming](./naming.md) \ No newline at end of file +[Naming](./naming.md) \ No newline at end of file diff --git a/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/add-element-type.svelte b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/add-element-type.svelte new file mode 100644 index 00000000..5ef3a22c --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/add-element-type.svelte @@ -0,0 +1,142 @@ + + +
+ {#if showFields} +
+ {#each fieldsConfig[currentFieldsToShow] as field} + + + {/each} + +
+ {/if} + {#each buttons as { name, onClick }} + + {/each} +
+ + diff --git a/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/element-type-container.svelte b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/element-type-container.svelte new file mode 100644 index 00000000..d7907011 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/element-type-container.svelte @@ -0,0 +1,17 @@ +{#if $xmlDocument} + +{:else} +

No xml document loaded

+{/if} + + + + diff --git a/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/tool-bar.svelte b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/tool-bar.svelte new file mode 100644 index 00000000..5c030d97 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/components/elements-type-container/tool-bar.svelte @@ -0,0 +1,140 @@ +
+ {#if showFields} +
+ {#each fieldsConfig[currentFieldsToShow] as field} + + + {/each} + +
+ {/if} + {#each buttons as { name, onClick }} + + {/each} +
+ + + + diff --git a/packages/uilib/src/lib/plugins/type-designer/components/index.ts b/packages/uilib/src/lib/plugins/type-designer/components/index.ts new file mode 100644 index 00000000..6b1917b5 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/components/index.ts @@ -0,0 +1 @@ +export { default as ElementsTypeContainer } from './elements-type-container/element-type-container.svelte'; diff --git a/packages/uilib/src/lib/plugins/type-designer/docs/feat.drawio b/packages/uilib/src/lib/plugins/type-designer/docs/feat.drawio new file mode 100644 index 00000000..47d9a060 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/docs/feat.drawio @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/uilib/src/lib/plugins/type-designer/index.ts b/packages/uilib/src/lib/plugins/type-designer/index.ts index 657ae7f9..db9158b5 100644 --- a/packages/uilib/src/lib/plugins/type-designer/index.ts +++ b/packages/uilib/src/lib/plugins/type-designer/index.ts @@ -1 +1 @@ -export { default as TypeDesigner } from "./type-designer.svelte" +export { default as TypeDesignerPlugin } from './type-designer-plugin.svelte' diff --git a/packages/uilib/src/lib/plugins/type-designer/naming.md b/packages/uilib/src/lib/plugins/type-designer/naming.md deleted file mode 100644 index 1410e7dd..00000000 --- a/packages/uilib/src/lib/plugins/type-designer/naming.md +++ /dev/null @@ -1,7 +0,0 @@ -# Naming Variables and Functions - -## Business Terms - -### Reasoning - -## Naming Maps diff --git a/packages/uilib/src/lib/plugins/type-designer/stores/data-types-templates.store.ts b/packages/uilib/src/lib/plugins/type-designer/stores/data-types-templates.store.ts new file mode 100644 index 00000000..1eedc6fa --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/stores/data-types-templates.store.ts @@ -0,0 +1,42 @@ +import { writable, get } from 'svelte/store' +// CORE +import { DataTypeTemplatesService } from '@oscd-plugins/core' +// TYPES +import type { DataTypeTemplates } from '@oscd-plugins/core' + +//==== STATE +const rootElement = writable(null) +const subElements = writable() + +//==== PRIVATE ACTIONS + +function setRootElement(newXmlDocument: Element) { + const typeDesigner = new DataTypeTemplatesService(newXmlDocument) + const newRootElement = typeDesigner.findDataTypesElement() + + rootElement.set(newRootElement) +} + +function setSubElements(newXmlDocument: Element) { + const typeDesigner = new DataTypeTemplatesService(newXmlDocument) + + const newSubElements = typeDesigner.findTypeDesignerElements({ + root: get(rootElement)?.element + }) + + subElements.set(newSubElements) +} + +//==== INITIALIZATION +function init(newXmlDocument: Element) { + setRootElement(newXmlDocument) + setSubElements(newXmlDocument) +} + +export const dataTypeTemplatesStore = { + //state + dataTypeTemplatesRootElement: rootElement, + dataTypeTemplatesSubElements: subElements, + //actions + init +} diff --git a/packages/uilib/src/lib/plugins/type-designer/stores/element-type-container.store.ts b/packages/uilib/src/lib/plugins/type-designer/stores/element-type-container.store.ts new file mode 100644 index 00000000..8554bad3 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/stores/element-type-container.store.ts @@ -0,0 +1,14 @@ +import { writable } from 'svelte/store'; + +//==== STATE +const context = writable() + +function setContext(newContext: HTMLElement) { + context.set(newContext) +} +export const elementTypeContainerStore = { + //state + elementTypeContainerContext: context, + //actions + setContext +} \ No newline at end of file diff --git a/packages/uilib/src/lib/plugins/type-designer/stores/index.ts b/packages/uilib/src/lib/plugins/type-designer/stores/index.ts new file mode 100644 index 00000000..0f9612bc --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/stores/index.ts @@ -0,0 +1,2 @@ +export * from './xml-document.store' +export * from './data-types-templates.store' diff --git a/packages/uilib/src/lib/plugins/type-designer/stores/xml-document.store.ts b/packages/uilib/src/lib/plugins/type-designer/stores/xml-document.store.ts new file mode 100644 index 00000000..37218bd7 --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/stores/xml-document.store.ts @@ -0,0 +1,80 @@ +// SVELTE +import { writable, get } from 'svelte/store' +// OPENSCD +import { createElement, newActionEvent } from '@oscd-plugins/core' +// STORES +import { dataTypeTemplatesStore } from './index' +// TYPES +import type { DataTypeTemplates } from '@oscd-plugins/core' + +// STATE +const xmlDocument = writable() + +// ACTIONS +function init(newXMLDocument: XMLDocument) { + xmlDocument.set(newXMLDocument) +} + +function addElementToXmlDocument( + newElementTagName: DataTypeTemplates.AllowedTag, + elementAttributes: Record, + pluginHtmlContext: HTMLElement +) { + const { dataTypeTemplatesRootElement } = dataTypeTemplatesStore + + const newElement = createElement( + get(xmlDocument), + newElementTagName, + elementAttributes + ) + + const parent = + get(dataTypeTemplatesRootElement)?.element ?? + createDataTypeTemplateElement(pluginHtmlContext) + + return createAndDispatchActionEvent(parent, newElement, pluginHtmlContext) +} + +function createAndDispatchActionEvent( + parent: Element, + element: Element, + pluginHtmlContext: HTMLElement +) { + const event = newActionEvent({ + new: { + parent, + element + } + }) + + pluginHtmlContext.dispatchEvent(event) +} + +function createDataTypeTemplateElement( + pluginHtmlContext: HTMLElement +): Element { + const newDataTypeTemplatesElement = createElement( + get(xmlDocument), + 'DataTypeTemplates', + {} + ) + dataTypeTemplatesStore.dataTypeTemplatesRootElement.set({ + element: newDataTypeTemplatesElement + }) + + createAndDispatchActionEvent( + get(xmlDocument).documentElement, + newDataTypeTemplatesElement, + pluginHtmlContext + ) + + return newDataTypeTemplatesElement +} + +export const xmlDocumentStore = { + //state + xmlDocument, + //actions + init, + addElementToXmlDocument +} diff --git a/packages/uilib/src/lib/plugins/type-designer/type-designer-flow.drawio b/packages/uilib/src/lib/plugins/type-designer/type-designer-flow.drawio deleted file mode 100644 index e14131f9..00000000 --- a/packages/uilib/src/lib/plugins/type-designer/type-designer-flow.drawio +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/uilib/src/lib/plugins/type-designer/type-designer-plugin.svelte b/packages/uilib/src/lib/plugins/type-designer/type-designer-plugin.svelte new file mode 100644 index 00000000..81ff255c --- /dev/null +++ b/packages/uilib/src/lib/plugins/type-designer/type-designer-plugin.svelte @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/packages/uilib/src/lib/plugins/type-designer/type-designer.svelte b/packages/uilib/src/lib/plugins/type-designer/type-designer.svelte deleted file mode 100644 index e10b0177..00000000 --- a/packages/uilib/src/lib/plugins/type-designer/type-designer.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -

Type Designer - Hello

-
-
-
- - diff --git a/packages/uilib/vite.config.build.ts b/packages/uilib/vite.config.build.ts index 0d46f049..b7dd7a81 100644 --- a/packages/uilib/vite.config.build.ts +++ b/packages/uilib/vite.config.build.ts @@ -8,16 +8,15 @@ import { svelte } from "@sveltejs/vite-plugin-svelte" // https://vitejs.dev/config/ export default defineConfig({ plugins: [svelte({ - configFile: "svelte.config.build.js", + configFile: "svelte.config.build.js", compilerOptions: { customElement: true, }, })], build: { lib: { - entry: "src/lib/index.js", + entry: "src/lib/index.ts", formats: ["es"], }, }, }) - \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc2552e5..8e2cd1a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: 1.9.2 + version: 1.9.2 '@sveltejs/vite-plugin-svelte': specifier: ^2.0.2 version: 2.0.2(svelte@3.55.1)(vite@4.1.4(@types/node@18.11.18)(sass@1.62.0)) @@ -310,7 +313,7 @@ importers: packages/plugins/type-designer: dependencies: '@oscd-plugins/uilib': - specifier: ../../uilib + specifier: workspace:^ version: link:../../uilib concurrently: specifier: ^7.6.0 @@ -395,6 +398,9 @@ importers: '@smui/button': specifier: 7.0.0-beta.8 version: 7.0.0-beta.8(svelte@3.55.0)(typescript@5.2.2) + '@smui/card': + specifier: 7.0.0-beta.8 + version: 7.0.0-beta.8(svelte@3.55.0)(typescript@5.2.2) '@smui/checkbox': specifier: 7.0.0-beta.8 version: 7.0.0-beta.8(svelte@3.55.0)(typescript@5.2.2) @@ -473,7 +479,7 @@ importers: version: 6.11.0(eslint@8.31.0)(typescript@5.2.2) '@vitest/coverage-c8': specifier: ^0.30.1 - version: 0.30.1(vitest@0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2))) + version: 0.30.1(vitest@0.30.1(@vitest/ui@0.30.1)(jsdom@21.1.1)(sass@1.62.0)) '@vitest/ui': specifier: ^0.30.1 version: 0.30.1 @@ -491,7 +497,7 @@ importers: version: 3.55.0 svelte-check: specifier: ^3.0.1 - version: 3.0.4(postcss@8.4.45)(sass@1.62.0)(svelte@3.55.0) + version: 3.0.4(postcss@8.4.21)(sass@1.62.0)(svelte@3.55.0) tslib: specifier: ^2.4.1 version: 2.4.1 @@ -506,7 +512,7 @@ importers: version: 1.0.9 vitest: specifier: ^0.30.1 - version: 0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2)) + version: 0.30.1(@vitest/ui@0.30.1)(jsdom@21.1.1)(sass@1.62.0) packages: @@ -574,6 +580,59 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@biomejs/biome@1.9.2': + resolution: {integrity: sha512-4j2Gfwft8Jqp1X0qLYvK4TEy4xhTo4o6rlvJPsjPeEame8gsmbGQfOPBkw7ur+7/Z/f0HZmCZKqbMvR7vTXQYQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.2': + resolution: {integrity: sha512-rbs9uJHFmhqB3Td0Ro+1wmeZOHhAPTL3WHr8NtaVczUmDhXkRDWScaxicG9+vhSLj1iLrW47itiK6xiIJy6vaA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.2': + resolution: {integrity: sha512-BlfULKijNaMigQ9GH9fqJVt+3JTDOSiZeWOQtG/1S1sa8Lp046JHG3wRJVOvekTPL9q/CNFW1NVG8J0JN+L1OA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.2': + resolution: {integrity: sha512-ZATvbUWhNxegSALUnCKWqetTZqrK72r2RsFD19OK5jXDj/7o1hzI1KzDNG78LloZxftrwr3uI9SqCLh06shSZw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.2': + resolution: {integrity: sha512-T8TJuSxuBDeQCQzxZu2o3OU4eyLumTofhCxxFd3+aH2AEWVMnH7Z/c3QP1lHI5RRMBP9xIJeMORqDQ5j+gVZzw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.2': + resolution: {integrity: sha512-CjPM6jT1miV5pry9C7qv8YJk0FIZvZd86QRD3atvDgfgeh9WQU0k2Aoo0xUcPdTnoz0WNwRtDicHxwik63MmSg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.2': + resolution: {integrity: sha512-T0cPk3C3Jr2pVlsuQVTBqk2qPjTm8cYcTD9p/wmR9MeVqui1C/xTVfOIwd3miRODFMrJaVQ8MYSXnVIhV9jTjg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.2': + resolution: {integrity: sha512-2x7gSty75bNIeD23ZRPXyox6Z/V0M71ObeJtvQBhi1fgrvPdtkEuw7/0wEHg6buNCubzOFuN9WYJm6FKoUHfhg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.2': + resolution: {integrity: sha512-JC3XvdYcjmu1FmAehVwVV0SebLpeNTnO2ZaMdGCSOdS7f8O9Fq14T2P1gTG1Q29Q8Dt1S03hh0IdVpIZykOL8g==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -999,6 +1058,7 @@ packages: '@humanwhocodes/config-array@0.11.8': resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -1006,6 +1066,7 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} @@ -1208,6 +1269,7 @@ packages: '@playwright/test@1.31.1': resolution: {integrity: sha512-IsytVZ+0QLDh1Hj83XatGp/GsI1CDJWbyDaBGbainsh0p2zC7F4toUocqowmjS6sQff2NGT3D9WbDj/3K2CJiA==} engines: {node: '>=14'} + deprecated: Please update to the latest version of Playwright to test up-to-date browsers. hasBin: true '@polka/url@1.0.0-next.21': @@ -1807,6 +1869,7 @@ packages: '@vitest/coverage-c8@0.30.1': resolution: {integrity: sha512-/Wa3dtSuckpdngAmiCwowaEXXgJkqPrtfvrs9HTB9QoEfNbZWPu4E4cjEn4lJZb4qcGf4fxFtUA2f9DnDNAzBA==} + deprecated: v8 coverage is moved to @vitest/coverage-v8 package peerDependencies: vitest: '>=0.30.0 <1' @@ -1873,6 +1936,7 @@ packages: abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} @@ -2138,7 +2202,7 @@ packages: engines: {node: '>= 10'} concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} concordance@5.0.4: resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} @@ -2358,6 +2422,7 @@ packages: domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2568,6 +2633,7 @@ packages: eslint-config-standard-with-typescript@34.0.1: resolution: {integrity: sha512-J7WvZeLtd0Vr9F+v4dZbqJCLD16cbIy4U+alJMq4MiXdpipdBM3U5NkXaGUjePc4sb1ZE01U9g6VuTBpHHz1fg==} + deprecated: Please use eslint-config-love, instead. peerDependencies: '@typescript-eslint/eslint-plugin': ^5.43.0 eslint: ^8.0.1 @@ -2858,6 +2924,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@9.3.1: resolution: {integrity: sha512-qERvJb7IGsnkx6YYmaaGvDpf77c951hICMdWaFXyH3PlVob8sbPJJyJX0kWkiCWyXUzoy9UOTNjGg0RbD8bYIw==} @@ -3002,6 +3069,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3279,6 +3347,7 @@ packages: loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5 lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -3717,10 +3786,12 @@ packages: rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@4.4.0: @@ -4770,6 +4841,41 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@biomejs/biome@1.9.2': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.2 + '@biomejs/cli-darwin-x64': 1.9.2 + '@biomejs/cli-linux-arm64': 1.9.2 + '@biomejs/cli-linux-arm64-musl': 1.9.2 + '@biomejs/cli-linux-x64': 1.9.2 + '@biomejs/cli-linux-x64-musl': 1.9.2 + '@biomejs/cli-win32-arm64': 1.9.2 + '@biomejs/cli-win32-x64': 1.9.2 + + '@biomejs/cli-darwin-arm64@1.9.2': + optional: true + + '@biomejs/cli-darwin-x64@1.9.2': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.2': + optional: true + + '@biomejs/cli-linux-arm64@1.9.2': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.2': + optional: true + + '@biomejs/cli-linux-x64@1.9.2': + optional: true + + '@biomejs/cli-win32-arm64@1.9.2': + optional: true + + '@biomejs/cli-win32-x64@1.9.2': + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -6662,23 +6768,12 @@ snapshots: sirv: 2.0.2 vitest: 0.29.7(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@4.9.4)) - '@vitest/browser@0.29.7(vitest@0.30.1)': - dependencies: - '@vitest/runner': 0.29.7 - local-pkg: 0.4.2 - mlly: 1.2.0 - modern-node-polyfills: 0.1.0 - rollup-plugin-node-polyfills: 0.2.1 - sirv: 2.0.2 - vitest: 0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2)) - optional: true - - '@vitest/coverage-c8@0.30.1(vitest@0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2)))': + '@vitest/coverage-c8@0.30.1(vitest@0.30.1(@vitest/ui@0.30.1)(jsdom@21.1.1)(sass@1.62.0))': dependencies: c8: 7.13.0 picocolors: 1.0.0 std-env: 3.3.2 - vitest: 0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2)) + vitest: 0.30.1(@vitest/ui@0.30.1)(jsdom@21.1.1)(sass@1.62.0) '@vitest/expect@0.29.7': dependencies: @@ -7322,30 +7417,6 @@ snapshots: - typescript - utf-8-validate - devtools@8.6.6(typescript@5.2.2): - dependencies: - '@types/node': 18.11.18 - '@wdio/config': 8.6.6 - '@wdio/logger': 8.6.6 - '@wdio/protocols': 8.6.6 - '@wdio/types': 8.4.0 - '@wdio/utils': 8.6.6 - chrome-launcher: 0.15.1 - edge-paths: 3.0.5 - import-meta-resolve: 2.2.1 - puppeteer-core: 19.7.5(typescript@5.2.2) - query-selector-shadow-dom: 1.0.1 - ua-parser-js: 1.0.34 - uuid: 9.0.0 - which: 3.0.0 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - optional: true - diff@5.1.0: {} dir-glob@3.0.1: @@ -8739,28 +8810,6 @@ snapshots: - supports-color - utf-8-validate - puppeteer-core@19.7.5(typescript@5.2.2): - dependencies: - chromium-bidi: 0.4.5(devtools-protocol@0.0.1094867) - cross-fetch: 3.1.5 - debug: 4.3.4 - devtools-protocol: 0.0.1094867 - extract-zip: 2.0.1 - https-proxy-agent: 5.0.1 - proxy-from-env: 1.1.0 - rimraf: 4.4.0 - tar-fs: 2.1.1 - unbzip2-stream: 1.4.3 - ws: 8.12.1 - optionalDependencies: - typescript: 5.2.2 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - utf-8-validate - optional: true - query-selector-shadow-dom@1.0.1: {} querystringify@2.2.0: {} @@ -9147,7 +9196,7 @@ snapshots: - stylus - sugarss - svelte-check@3.0.4(postcss@8.4.45)(sass@1.62.0)(svelte@3.55.0): + svelte-check@3.0.4(postcss@8.4.21)(sass@1.62.0)(svelte@3.55.0): dependencies: '@jridgewell/trace-mapping': 0.3.17 chokidar: 3.5.3 @@ -9156,7 +9205,7 @@ snapshots: picocolors: 1.0.0 sade: 1.8.1 svelte: 3.55.0 - svelte-preprocess: 5.0.1(postcss@8.4.45)(sass@1.62.0)(svelte@3.55.0)(typescript@4.9.4) + svelte-preprocess: 5.0.1(postcss@8.4.21)(sass@1.62.0)(svelte@3.55.0)(typescript@4.9.4) typescript: 4.9.4 transitivePeerDependencies: - '@babel/core' @@ -9263,7 +9312,7 @@ snapshots: sass: 1.62.0 typescript: 4.9.4 - svelte-preprocess@5.0.1(postcss@8.4.45)(sass@1.62.0)(svelte@3.55.0)(typescript@4.9.4): + svelte-preprocess@5.0.1(postcss@8.4.21)(sass@1.62.0)(svelte@3.55.0)(typescript@4.9.4): dependencies: '@types/pug': 2.0.6 '@types/sass': 1.43.1 @@ -9273,7 +9322,7 @@ snapshots: strip-indent: 3.0.0 svelte: 3.55.0 optionalDependencies: - postcss: 8.4.45 + postcss: 8.4.21 sass: 1.62.0 typescript: 4.9.4 @@ -9660,7 +9709,7 @@ snapshots: - supports-color - terser - vitest@0.30.1(@vitest/browser@0.29.7)(@vitest/ui@0.30.1)(happy-dom@8.9.0)(jsdom@21.1.1)(safaridriver@0.0.4)(sass@1.62.0)(webdriverio@8.6.7(typescript@5.2.2)): + vitest@0.30.1(@vitest/ui@0.30.1)(jsdom@21.1.1)(sass@1.62.0): dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 @@ -9689,12 +9738,8 @@ snapshots: vite-node: 0.30.1(@types/node@18.11.18)(sass@1.62.0) why-is-node-running: 2.2.2 optionalDependencies: - '@vitest/browser': 0.29.7(vitest@0.30.1) '@vitest/ui': 0.30.1 - happy-dom: 8.9.0 jsdom: 21.1.1 - safaridriver: 0.0.4 - webdriverio: 8.6.7(typescript@5.2.2) transitivePeerDependencies: - less - sass @@ -9758,41 +9803,6 @@ snapshots: - typescript - utf-8-validate - webdriverio@8.6.7(typescript@5.2.2): - dependencies: - '@types/node': 18.11.18 - '@wdio/config': 8.6.6 - '@wdio/logger': 8.6.6 - '@wdio/protocols': 8.6.6 - '@wdio/repl': 8.6.6 - '@wdio/types': 8.4.0 - '@wdio/utils': 8.6.6 - archiver: 5.3.1 - aria-query: 5.1.3 - css-shorthand-properties: 1.1.1 - css-value: 0.0.1 - devtools: 8.6.6(typescript@5.2.2) - devtools-protocol: 0.0.1119014 - grapheme-splitter: 1.0.4 - import-meta-resolve: 2.2.1 - is-plain-obj: 4.1.0 - lodash.clonedeep: 4.5.0 - lodash.zip: 4.2.0 - minimatch: 7.4.2 - puppeteer-core: 19.7.5(typescript@5.2.2) - query-selector-shadow-dom: 1.0.1 - resq: 1.11.0 - rgb2hex: 0.2.5 - serialize-error: 8.1.0 - webdriver: 8.6.6 - transitivePeerDependencies: - - bufferutil - - encoding - - supports-color - - typescript - - utf-8-validate - optional: true - webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {}