Skip to content

feat(plcopen): PLCopen XML import parser + import/export UI (NODE-111, NODE-112) - #936

Merged
dcoutinho1328 merged 6 commits into
developmentfrom
feature/plcopen-import-export
Jul 21, 2026
Merged

feat(plcopen): PLCopen XML import parser + import/export UI (NODE-111, NODE-112)#936
dcoutinho1328 merged 6 commits into
developmentfrom
feature/plcopen-import-export

Conversation

@dcoutinho1328

@dcoutinho1328 dcoutinho1328 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Editor-side half of NODE-95 (Weidmüller: PLCopen import/export). autonomy-node's backend already owns PLCopen export and raw-import storage (a .xml upload is stored verbatim, flagged pending conversion); this PR adds the actual XML↔project conversion, which belongs here since this app owns the native project model.

  • Ports a PLCopen XML→project parser (textual POUs, dataTypes, task/instance config, FBD and LD graphical bodies) into the shared src/frontend/utils/PLC/xml-parser/ tree, mirroring the existing xml-generator/ (export) layout. The parser was originally prototyped against autonomy-node's own domain model, proven there, then ported to this repo's native xyflow-based types (see NODE-111 for the reference implementation history).
  • Wires the previously-disabled "Export as PLCopen XML" menu stub and adds a new "Import PLCopen XML" item (with a confirm-overwrite modal, since import replaces the whole open project), backed by two new ProjectPort methods.
  • Editor adapter: implements the two new port methods via Electron IPC (native open/save file dialogs filtered to .xml).
  • Fixes a pre-existing bug in the ladder export path where an unnamed variable node was dropped from generated XML while a <connection> elsewhere still referenced it (dangling reference).

Affected Surfaces

  • Shared (byte-identical): frontend/utils/PLC/xml-parser/ (new), frontend/utils/PLC/xml-generator/old-editor/language/ladder-xml.ts (bug fix), frontend/services/{export,import}-actions.ts (new), frontend/components/_organisms/modals/confirm-plcopen-import-modal.tsx (new), frontend/components/_molecules/menu-bar/menus/file.tsx, middleware/shared/ports/{project-port,platform-capabilities}.ts, __architecture__/validate.ts (new KNOWN_EXCEPTIONS entry)
  • Editor-specific: backend/editor/utils/path-picker.ts, main/modules/ipc/{main,renderer}.ts, middleware/adapters/editor/project-adapter.ts, jest.config.json (excludes 3 shared test files that hit a pre-existing Jest/vi-shim relative-mock-path limitation, same workaround already applied to notify-no-write-permission.test.ts)
  • Web-specific: none (see matching web PR)

Validation

  • Architecture validation passes (npx tsx src/__architecture__/validate.ts)
  • npm run test — all suites pass (coverage-threshold shortfalls in backend/shared/middleware/adapters/editor are pre-existing on development, confirmed via side-by-side run)
  • npm run lint — 0 errors
  • Byte-identical surfaces pass (compare_repos.py — 1168 files, all identical)

Cross-repo

Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added PLCopen XML export/import to the File menu with capability-based visibility.
    • Implemented PLCopen XML import/export end-to-end, including XML generation/parsing, save/open dialogs, and IPC-backed file handling.
    • Added a confirmation modal for PLCopen imports that warns about overwriting the open project.
  • Bug Fixes
    • Improved ladder XML import to keep variable nodes with empty names so related connections remain intact.
  • Tests
    • Added/expanded automated coverage for PLCopen import/export actions, modal behavior, and PLCopen XML parsing/generation.

…UI (NODE-111, NODE-112)

- Port the PLCopen XML->project parser (textual POUs, dataTypes, task/instance
  config, FBD and LD graphical bodies) from autonomy-node's git history into
  the shared frontend/utils/PLC/xml-parser/ tree, retargeted to this app's
  native xyflow-based project model.
- Wire "Export as PLCopen XML" (previously a disabled stub) and a new
  "Import PLCopen XML" menu item, backed by new ProjectPort methods
  (pickPlcopenImportFile, exportPlcopenFile) and a confirm-overwrite modal.
- Editor adapter: implement the two new port methods via Electron IPC
  (native open/save file dialogs filtered to .xml).
- Fix a pre-existing export bug where an unnamed ladder variable node was
  dropped from generated XML while a <connection> still referenced it.
- Exclude three new shared test files from this repo's Jest run
  (testPathIgnorePatterns) that rely on vi.mock with a relative module path,
  a pre-existing Jest/vi-shim limitation already worked around for
  notify-no-write-permission.test.ts; these run correctly under web's Vitest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 70d12379-0090-46e4-b0e6-dd0b7399f468

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef0feb and 627e5dc.

📒 Files selected for processing (9)
  • src/backend/editor/utils/path-picker.ts
  • src/frontend/components/_organisms/modals/__tests__/confirm-plcopen-import-modal.test.tsx
  • src/frontend/services/__tests__/import-actions.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/instances-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/parse-plcopen-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/language/__tests__/ladder-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/language/ladder-xml.ts
  • src/frontend/utils/PLC/xml-parser/variable-xml.ts
  • src/frontend/utils/__tests__/iec-types-registry.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/frontend/utils/PLC/xml-parser/variable-xml.ts
  • src/frontend/utils/tests/iec-types-registry.test.ts
  • src/frontend/components/_organisms/modals/tests/confirm-plcopen-import-modal.test.tsx
  • src/backend/editor/utils/path-picker.ts
  • src/frontend/utils/PLC/xml-parser/tests/instances-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/tests/parse-plcopen-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/language/ladder-xml.ts
  • src/frontend/services/tests/import-actions.test.ts
  • src/frontend/utils/PLC/xml-parser/language/tests/ladder-xml.test.ts

Walkthrough

PLCopen XML import and export are added across parsing, project-state conversion, filesystem dialogs, IPC bridges, frontend services, menus, and confirmation UI. LD/FBD graph parsing, round-trip coverage, and handling for unsupported or dangling XML connections are included.

Changes

PLCopen XML workflow

Layer / File(s) Summary
PLCopen XML parsing contracts
src/frontend/utils/PLC/xml-parser/*, src/frontend/utils/iec-types-registry.ts
Adds XML normalization and parsers for documents, types, variables, data types, POUs, configurations, and project metadata.
LD and FBD graph reconstruction
src/frontend/utils/PLC/xml-parser/language/*
Reconstructs ladder and FBD nodes, handles, edges, rungs, geometry, comments, and warnings.
Generator reference preservation
src/frontend/utils/PLC/xml-generator/old-editor/language/*
Serializes ladder variable nodes even when their names are empty and updates the corresponding test.
PLCopen file transport and ports
src/backend/editor/utils/*, src/main/modules/ipc/*, src/middleware/adapters/editor/*, src/middleware/shared/ports/*
Adds XML file picking and saving through filesystem helpers, IPC channels, renderer bridges, adapters, capabilities, and project-port methods.
Frontend import and export workflow
src/frontend/services/*, src/frontend/components/_molecules/menu-bar/menus/file.tsx, src/frontend/components/_organisms/modals/*, src/frontend/components/_templates/app-layout.tsx
Converts project state to PLCopen XML, imports parsed XML into the open project, adds menu actions, and adds overwrite confirmation UI.
Review and test configuration
jest.config.json, src/__architecture__/validate.ts
Expands Jest test exclusions and records the architecture exception required by the export service.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FileMenu
  participant FrontendService
  participant ProjectPort
  participant IPC
  participant FilePicker
  User->>FileMenu: choose PLCopen import or export
  FileMenu->>FrontendService: execute import or export action
  FrontendService->>ProjectPort: request XML file operation
  ProjectPort->>IPC: invoke PLCopen channel
  IPC->>FilePicker: read or write XML
  FilePicker-->>IPC: return operation result
  IPC-->>ProjectPort: return content or error
  ProjectPort-->>FrontendService: complete workflow
Loading

Possibly related PRs

Suggested labels: feature, bug

Suggested reviewers: thiagoralves, joaogsp, vmleroy

Poem

I’m a rabbit with XML in my den,
Parsing each rung and exporting again.
Handles hop, wires gleam,
Imports flow like a dream,
While menus say, “PLCopen!”—amen!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative but does not follow the repository template and omits required references and DOD checklist sections. Rewrite it using the template sections: pull request info, references, change description, and the full DOD checklist with the required items.
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: PLCopen XML import/export support plus parser work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/plcopen-import-export

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/frontend/locales/en/menu.json (1)

25-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Normalize the capitalization of PLCopen.

For consistency with the new Import PLCopen XML string, you might want to consider updating the export string above it from PLCOpen to PLCopen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/locales/en/menu.json` at line 25, Update the adjacent export
menu translation to use “PLCopen” instead of “PLCOpen,” matching the
capitalization of the importFromPLCOpenXml label and the requested naming
convention.
src/frontend/utils/PLC/xml-parser/language/__tests__/ladder-xml.test.ts (1)

185-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for @formalParameter-less and empty-string connection cases.

Every connection fixture in this file sets a non-empty @formalParameter (Lines 30, 42, 53, 212, 263, 294). Two documented branches in parseConnectionXml (ladder-xml.ts L64-74) and its consumer fallback (ladder-xml.ts L519) are never exercised:

  1. A connection with no @formalParameter attribute at all (the "block input wired directly to an <inVariable>" case the code comments describe).
  2. A connection with @formalParameter="" referencing an unnamed function's return pin.

As per coding guidelines, "Code in src/frontend/utils/ must maintain 100% test coverage when new code is added."

✅ Suggested additional test cases
it('defaults the source handle to "output" when a connection omits `@formalParameter`', () => {
  const { body, warnings } = parseLadderXml('p', {
    inVariable: [
      {
        '`@localId`': '1',
        '`@width`': '80',
        '`@height`': '30',
        position: { '`@x`': '0', '`@y`': '0' },
        connectionPointOut: { relPosition: { '`@x`': '80', '`@y`': '15' } },
        expression: 'LIT',
      },
    ],
    block: [
      {
        '`@localId`': '2',
        '`@typeName`': 'ADD',
        '`@executionOrderId`': '0',
        '`@width`': '100',
        '`@height`': '60',
        position: { '`@x`': '100', '`@y`': '0' },
        inputVariables: {
          variable: [
            { '`@formalParameter`': 'IN1', connectionPointIn: { relPosition: { '`@x`': '0', '`@y`': '10' }, connection: [{ '`@refLocalId`': '1' }] } },
          ],
        },
        outputVariables: '',
      },
    ],
  })
  expect(warnings).toEqual([])
  expect(body.rungs[0].edges).toContainEqual(expect.objectContaining({ sourceHandle: 'output', targetHandle: 'IN1' }))
})

it("resolves a connection referencing an unnamed function's return pin (`@formalParameter`=\"\")", () => {
  const { body } = parseLadderXml('p', {
    block: [
      { '`@localId`': '1', '`@typeName`': 'ADD', '`@executionOrderId`': '0', '`@width`': '100', '`@height`': '60', position: { '`@x`': '0', '`@y`': '0' }, inputVariables: '', outputVariables: { variable: [{ '`@formalParameter`': '', connectionPointOut: { relPosition: { '`@x`': '100', '`@y`': '10' } } }] } },
    ],
    coil: [
      { '`@localId`': '2', '`@width`': '40', '`@height`': '40', position: { '`@x`': '150', '`@y`': '0' }, connectionPointIn: { relPosition: { '`@x`': '0', '`@y`': '20' }, connection: [{ '`@refLocalId`': '1', '`@formalParameter`': '' }] }, connectionPointOut: { relPosition: { '`@x`': '40', '`@y`': '20' } }, variable: ['Y'] },
    ],
  })
  expect(body.rungs[0].edges).toContainEqual(expect.objectContaining({ sourceHandle: 'OUT', target: 'COIL-2' }))
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/utils/PLC/xml-parser/language/__tests__/ladder-xml.test.ts`
around lines 185 - 227, Add tests covering both untested parseConnectionXml
branches: a connection without `@formalParameter` from an inVariable to a block
input must produce sourceHandle "output", and a connection with
`@formalParameter`="" from an unnamed block output to a coil must resolve
sourceHandle "OUT". Keep the assertions for empty warnings where applicable and
verify the expected target handles or edge endpoints.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jest.config.json`:
- Around line 18-24: Remove the newly added test paths from Jest’s
testPathIgnorePatterns so they are discovered and executed by the test suite. If
any cannot run because of environment or mocking issues, update the
corresponding test files to explicitly skip them rather than excluding them in
jest.config.json.

In `@src/frontend/services/export-actions.ts`:
- Around line 17-22: Reorder the imports in export-actions.ts to match the
repository’s lint rules, preserving the existing import grouping and symbols;
run the lint autofix or formatter to confirm the exact order.

In `@src/middleware/shared/ports/platform-capabilities.ts`:
- Around line 154-157: Keep hasProjectExport and hasProjectImport set to false
in WEB_CAPABILITIES until the browser-based port adapters are implemented,
preventing these actions from being exposed in the web UI prematurely.

---

Nitpick comments:
In `@src/frontend/locales/en/menu.json`:
- Line 25: Update the adjacent export menu translation to use “PLCopen” instead
of “PLCOpen,” matching the capitalization of the importFromPLCOpenXml label and
the requested naming convention.

In `@src/frontend/utils/PLC/xml-parser/language/__tests__/ladder-xml.test.ts`:
- Around line 185-227: Add tests covering both untested parseConnectionXml
branches: a connection without `@formalParameter` from an inVariable to a block
input must produce sourceHandle "output", and a connection with
`@formalParameter`="" from an unnamed block output to a coil must resolve
sourceHandle "OUT". Keep the assertions for empty warnings where applicable and
verify the expected target handles or edge endpoints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 819dda0f-411a-410c-981d-efcecafa6d20

📥 Commits

Reviewing files that changed from the base of the PR and between fda6964 and b4c8ca5.

📒 Files selected for processing (47)
  • jest.config.json
  • src/__architecture__/validate.ts
  • src/backend/editor/utils/__tests__/path-picker.test.ts
  • src/backend/editor/utils/path-picker.ts
  • src/frontend/components/_molecules/menu-bar/menus/file.tsx
  • src/frontend/components/_organisms/modals/__tests__/confirm-plcopen-import-modal.test.tsx
  • src/frontend/components/_organisms/modals/confirm-plcopen-import-modal.tsx
  • src/frontend/components/_templates/app-layout.tsx
  • src/frontend/locales/en/menu.json
  • src/frontend/services/__tests__/export-actions.test.ts
  • src/frontend/services/__tests__/import-actions.test.ts
  • src/frontend/services/export-actions.ts
  • src/frontend/services/import-actions.ts
  • src/frontend/store/slices/modal/types.ts
  • src/frontend/utils/PLC/build-plcopen-project-response.ts
  • src/frontend/utils/PLC/xml-generator/old-editor/language/__tests__/ladder-xml.test.ts
  • src/frontend/utils/PLC/xml-generator/old-editor/language/ladder-xml.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/data-type-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/instances-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/parse-plcopen-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/parse-xml-document.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/pou-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/type-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/variable-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/__tests__/xml-node.test.ts
  • src/frontend/utils/PLC/xml-parser/data-type-xml.ts
  • src/frontend/utils/PLC/xml-parser/index.ts
  • src/frontend/utils/PLC/xml-parser/instances-xml.ts
  • src/frontend/utils/PLC/xml-parser/language/__tests__/fbd-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/language/__tests__/geometry.test.ts
  • src/frontend/utils/PLC/xml-parser/language/__tests__/ladder-xml.test.ts
  • src/frontend/utils/PLC/xml-parser/language/fbd-xml.ts
  • src/frontend/utils/PLC/xml-parser/language/geometry.ts
  • src/frontend/utils/PLC/xml-parser/language/ladder-xml.ts
  • src/frontend/utils/PLC/xml-parser/parse-xml-document.ts
  • src/frontend/utils/PLC/xml-parser/pou-xml.ts
  • src/frontend/utils/PLC/xml-parser/type-xml.ts
  • src/frontend/utils/PLC/xml-parser/variable-xml.ts
  • src/frontend/utils/PLC/xml-parser/xml-node.ts
  • src/frontend/utils/__tests__/iec-types-registry.test.ts
  • src/frontend/utils/iec-types-registry.ts
  • src/main/modules/ipc/main.ts
  • src/main/modules/ipc/renderer.ts
  • src/middleware/adapters/editor/__tests__/project-adapter.test.ts
  • src/middleware/adapters/editor/project-adapter.ts
  • src/middleware/shared/ports/platform-capabilities.ts
  • src/middleware/shared/ports/project-port.ts

Comment thread jest.config.json
Comment thread src/frontend/services/export-actions.ts
Comment thread src/middleware/shared/ports/platform-capabilities.ts
closeModal() only resets modals listed in ALL_MODAL_TYPES, so the
File -> Import PLCopen XML confirm dialog stayed open after a
successful import since its type was missing from that list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dcoutinho1328

Copy link
Copy Markdown
Collaborator Author

PR #936 Review

Summary

Editor-side counterpart to openplc-web #601 (same byte-identical shared surface: parser tree, services, modal, ports). Adds the editor-specific IPC plumbing (native open/save dialogs) and adapter wiring for PLCopen export/import.

Module: Editor-specific (backend/editor/utils/path-picker.ts, main/modules/ipc/{main,renderer}.ts, middleware/adapters/editor/project-adapter.ts)

Checklist Results

  • CorrectnessgetPlcopenImportFilePath/getPlcopenExportSavePath follow the existing getOpenProjectPath dialog pattern; cancel/read/write failure paths are each distinctly handled and tested against real fs I/O (temp dir), not stubbed.
  • IPC wiring — new project:pick-plcopen-import-file/project:export-plcopen-file handlers registered consistently with the rest of MainProcessBridge; renderer bridge signatures match.
  • Scope correctness — editor's adapter has no "pending PLCopen import" auto-conversion branch, correctly so: that concept only applies to Node's server-hosted pending-marker file, which doesn't exist in the editor's local-filesystem project model.
  • Test config — the three Vitest-authored shared test files (export-actions, import-actions, confirm-plcopen-import-modal) are added to jest.config.json's testPathIgnorePatterns, consistent with the existing notify-no-write-permission.test.ts precedent for shared-but-Vitest-syntax tests.

Code Improvement Findings

None.

Module: Shared (byte-identical with openplc-web #601)

Already reviewed there — no repo-specific findings to add.

Cross-Module Observations

None beyond what's noted in #601.

Verdict

  • Comment only (self-authored PR)
  • No blocking issues found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dcoutinho1328
dcoutinho1328 merged commit c577fa7 into development Jul 21, 2026
12 checks passed
@dcoutinho1328
dcoutinho1328 deleted the feature/plcopen-import-export branch July 21, 2026 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant