From 4e2c4c092c3c5370dcfa44b2353c552e9e4beeaf Mon Sep 17 00:00:00 2001 From: Manuele Conti Date: Thu, 9 Apr 2026 21:46:34 +0200 Subject: [PATCH 1/2] Fix CODESYS XML export namespace for variable documentation --- .../PLC/xml-data/codesys/base-diagram.ts | 1 + .../PLC/xml-generator/codesys/base-xml.ts | 1 + .../xml-generator-initial-values.spec.ts | 108 ++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts diff --git a/src/types/PLC/xml-data/codesys/base-diagram.ts b/src/types/PLC/xml-data/codesys/base-diagram.ts index 5d9d0fa60..488700fc7 100644 --- a/src/types/PLC/xml-data/codesys/base-diagram.ts +++ b/src/types/PLC/xml-data/codesys/base-diagram.ts @@ -9,6 +9,7 @@ import { variableXMLSchema } from './variable/variable-diagram' const baseXmlSchema = z.object({ project: z.object({ '@xmlns': z.string().default('http://www.plcopen.org/xml/tc6_0200'), + '@xmlns:xhtml': z.string().default('http://www.w3.org/1999/xhtml'), fileHeader: z.object({ '@companyName': z.string().default('Unknown'), diff --git a/src/utils/PLC/xml-generator/codesys/base-xml.ts b/src/utils/PLC/xml-generator/codesys/base-xml.ts index 638cbe36a..6ec5dada5 100644 --- a/src/utils/PLC/xml-generator/codesys/base-xml.ts +++ b/src/utils/PLC/xml-generator/codesys/base-xml.ts @@ -5,6 +5,7 @@ import formatDate from '../../../formatDate' const getBaseCodeSysXmlStructure = (): BaseXml => ({ project: { '@xmlns': 'http://www.plcopen.org/xml/tc6_0200', + '@xmlns:xhtml': 'http://www.w3.org/1999/xhtml', fileHeader: { '@companyName': 'Unknown', '@productName': 'Unnamed', diff --git a/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts b/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts new file mode 100644 index 000000000..ce8cc8d8a --- /dev/null +++ b/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts @@ -0,0 +1,108 @@ +import { codeSysInstanceToXml } from './codesys/instances-xml' +import { getBaseCodeSysXmlStructure } from './codesys/base-xml' +import { codeSysParseInterface } from './codesys/pou-xml' +import { oldEditorInstanceToXml } from './old-editor/instances-xml' +import { oldEditorParseInterface } from './old-editor/pou-xml' + +describe('XML generator initial values', () => { + const buildPou = (initialValue: boolean | number) => + ({ + type: 'program', + data: { + name: 'Main', + variables: [ + { + name: 'flag', + class: 'local', + type: { definition: 'base-type', value: 'bool' }, + location: '%QX0.0', + initialValue, + documentation: '', + }, + ], + }, + }) as any + + const buildConfiguration = (initialValue: boolean | number) => + ({ + resource: { + tasks: [], + instances: [], + globalVariables: [ + { + name: 'globalFlag', + location: '%QX0.0', + type: { definition: 'base-type', value: 'bool' }, + initialValue, + documentation: '', + }, + ], + }, + }) as any + + const buildCodeSysXml = () => + ({ + project: { + instances: { + configurations: { + configuration: { + resource: {}, + }, + }, + }, + }, + }) as any + + const buildOldEditorXml = buildCodeSysXml + + it.each([ + ['codesys interface', codeSysParseInterface], + ['old editor interface', oldEditorParseInterface], + ])('keeps false BOOL initial values for %s', (_label, parseInterface) => { + const xml = parseInterface(buildPou(false)) + expect(xml.localVars?.variable?.[0]?.initialValue).toEqual({ + simpleValue: { + '@value': 'false', + }, + }) + }) + + it.each([ + ['codesys globals', codeSysInstanceToXml, buildCodeSysXml], + ['old editor globals', oldEditorInstanceToXml, buildOldEditorXml], + ])('keeps numeric zero initial values for %s', (_label, toXml, buildXml) => { + const xml = toXml(buildXml(), buildConfiguration(0)) + expect(xml.project.instances.configurations.configuration.globalVars?.variable?.[0]?.initialValue).toEqual({ + simpleValue: { + '@value': '0', + }, + }) + }) + + it('declares the xhtml namespace in codesys exports when variable documentation is present', () => { + const xml = getBaseCodeSysXmlStructure() + const withDocumentation = codeSysParseInterface({ + type: 'program', + data: { + name: 'Main', + variables: [ + { + name: 'flag', + class: 'local', + type: { definition: 'base-type', value: 'bool' }, + location: '', + initialValue: '', + documentation: 'commento reset', + }, + ], + }, + } as any) + + expect(xml.project['@xmlns:xhtml']).toBe('http://www.w3.org/1999/xhtml') + expect(withDocumentation.localVars?.variable?.[0]?.documentation).toEqual({ + 'xhtml:p': { + $: 'commento reset', + }, + }) + }) +}) From a9cbccf6b7b7d61a99d5ec69eadeabc77b7de98d Mon Sep 17 00:00:00 2001 From: Manuele Conti Date: Thu, 9 Apr 2026 22:00:09 +0200 Subject: [PATCH 2/2] Tighten XML generator test fixtures --- .../xml-generator-initial-values.spec.ts | 81 +++++++++++++------ 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts b/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts index ce8cc8d8a..8034ddf3f 100644 --- a/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts +++ b/src/utils/PLC/xml-generator/xml-generator-initial-values.spec.ts @@ -1,15 +1,37 @@ +import { PLCConfiguration, PLCPou } from '@root/types/PLC/open-plc' + import { codeSysInstanceToXml } from './codesys/instances-xml' import { getBaseCodeSysXmlStructure } from './codesys/base-xml' import { codeSysParseInterface } from './codesys/pou-xml' +import { getBaseOldEditorXmlStructure } from './old-editor/base-xml' import { oldEditorInstanceToXml } from './old-editor/instances-xml' import { oldEditorParseInterface } from './old-editor/pou-xml' +type RuntimeVariableInitialValue = boolean | number | string | null + +type TestPou = Omit & { + data: Omit & { + variables: Array & { initialValue: RuntimeVariableInitialValue }> + } +} + +type TestConfiguration = Omit & { + resource: Omit & { + globalVariables: Array< + Omit & { + initialValue: RuntimeVariableInitialValue + } + > + } +} + describe('XML generator initial values', () => { - const buildPou = (initialValue: boolean | number) => - ({ + const buildPou = (initialValue: boolean | number): PLCPou => { + const pou = { type: 'program', data: { name: 'Main', + language: 'st', variables: [ { name: 'flag', @@ -20,11 +42,16 @@ describe('XML generator initial values', () => { documentation: '', }, ], + body: { language: 'st', value: '' }, + documentation: '', }, - }) as any + } satisfies TestPou - const buildConfiguration = (initialValue: boolean | number) => - ({ + return pou as unknown as PLCPou + } + + const buildConfiguration = (initialValue: boolean | number): PLCConfiguration => { + const configuration = { resource: { tasks: [], instances: [], @@ -38,22 +65,14 @@ describe('XML generator initial values', () => { }, ], }, - }) as any + } satisfies TestConfiguration - const buildCodeSysXml = () => - ({ - project: { - instances: { - configurations: { - configuration: { - resource: {}, - }, - }, - }, - }, - }) as any + return configuration as unknown as PLCConfiguration + } + + const buildCodeSysXml = () => getBaseCodeSysXmlStructure() - const buildOldEditorXml = buildCodeSysXml + const buildOldEditorXml = () => getBaseOldEditorXmlStructure() it.each([ ['codesys interface', codeSysParseInterface], @@ -67,11 +86,17 @@ describe('XML generator initial values', () => { }) }) - it.each([ - ['codesys globals', codeSysInstanceToXml, buildCodeSysXml], - ['old editor globals', oldEditorInstanceToXml, buildOldEditorXml], - ])('keeps numeric zero initial values for %s', (_label, toXml, buildXml) => { - const xml = toXml(buildXml(), buildConfiguration(0)) + it('keeps numeric zero initial values for codesys globals', () => { + const xml = codeSysInstanceToXml(buildCodeSysXml(), buildConfiguration(0)) + expect(xml.project.instances.configurations.configuration.globalVars?.variable?.[0]?.initialValue).toEqual({ + simpleValue: { + '@value': '0', + }, + }) + }) + + it('keeps numeric zero initial values for old editor globals', () => { + const xml = oldEditorInstanceToXml(buildOldEditorXml(), buildConfiguration(0)) expect(xml.project.instances.configurations.configuration.globalVars?.variable?.[0]?.initialValue).toEqual({ simpleValue: { '@value': '0', @@ -81,10 +106,11 @@ describe('XML generator initial values', () => { it('declares the xhtml namespace in codesys exports when variable documentation is present', () => { const xml = getBaseCodeSysXmlStructure() - const withDocumentation = codeSysParseInterface({ + const pouWithDocumentation = { type: 'program', data: { name: 'Main', + language: 'st', variables: [ { name: 'flag', @@ -95,8 +121,11 @@ describe('XML generator initial values', () => { documentation: 'commento reset', }, ], + body: { language: 'st', value: '' }, + documentation: '', }, - } as any) + } satisfies PLCPou + const withDocumentation = codeSysParseInterface(pouWithDocumentation) expect(xml.project['@xmlns:xhtml']).toBe('http://www.w3.org/1999/xhtml') expect(withDocumentation.localVars?.variable?.[0]?.documentation).toEqual({