import { Expression, type ExpressionOrValue, type ExpressionSource, type VmValue } from '@cloudpss/expression'; import { fillArgumentMap, generateTrackId, type ArgumentMap, type ArgumentValue, type Parameter, type ParameterGroup, type PinLikeParameter, } from '@cloudpss/expression/definitions'; import { isResourceId } from '../../resource-id.js'; import type { ResourceRevisionVersion } from '../../index.js'; import type { RootFuncConfiguration } from '../../function/configuration.js'; import type { DiagramComponent, DiagramEdge, DiagramEdgeTerminal, DiagramImplement, Model, ModelConfiguration, ModelGraphic, ModelId, PinDefinition, PowerFlowImplement, } from '../../model/index.js'; import { MERGE_DEMERGE_COMPONENTS, SECONDARY_ARG_ID, type SecondaryArg } from '../../model/utils/merge-demerge.js'; /** 转换表达式 */ function migrateExpressionImpl(exp: unknown): ExpressionOrValue { if (typeof exp != 'string') return exp as T; if (exp.startsWith('=')) { const expContent = exp.slice(1); if (/^[0-9]+(\.[0-9]+)?$/.test(expContent)) { return Number(expContent) as T; } return Expression(expContent ? expContent : ' '); } const trimmed = exp.trim(); const number = Number(trimmed); if (trimmed && Number.isFinite(number)) { return trimmed as T; } return exp as T; } /** 转换表达式 */ function migrateExpressionSourceImpl( exp: V | ExpressionSource | undefined, ): ExpressionSource | undefined { if (exp == null) return undefined; if (typeof exp == 'string' && exp.startsWith('=')) { return exp.slice(1) as ExpressionSource; } return String(exp) as ExpressionSource; } /** 转换 diagram */ function diagram(impl: DiagramImplement | undefined): void { if (!impl) return; if (Array.isArray(impl.variables)) { for (const variable of impl.variables) { variable.value = migrateExpressionImpl(variable.value); } } for (const key in impl.cells) { const cell = impl.cells[key] as DiagramComponent; if (!cell) continue; if (cell.shape !== 'diagram-component') continue; config(cell, undefined); for (const key in cell.pins) { cell.pins[key] = migrateExpressionImpl(cell.pins[key]); } if (cell.props == null) { cell.props = { enabled: true, outlineLevel: 0 }; } else { if ( cell.props.enabled == null || (typeof cell.props.enabled == 'string' && (cell.props.enabled as string).trim() === '') ) { cell.props.enabled = true; } else if (typeof cell.props.enabled == 'string') { cell.props.enabled = Expression(migrateExpressionSourceImpl(cell.props.enabled)!, 'b'); } } } } /** 转换 powerFlow */ function powerFlow(impl: PowerFlowImplement | undefined): void { if (!impl) return; if (Array.isArray(impl.models)) { for (const model of impl.models) { model.condition = migrateExpressionSourceImpl(model.condition); const m = model as unknown as Record; for (const key in m) { if (['key', 'name', 'description', 'condition', 'type', 'pins'].includes(key)) { // 这些不是表达式 continue; } m[key] = migrateExpressionImpl(m[key]); } } } if (Array.isArray(impl.outputs)) { for (const output of impl.outputs) { output.condition = migrateExpressionSourceImpl(output.condition); output.value = migrateExpressionSourceImpl(output.value)!; } } } /** 转换 pin */ function pin(p: PinDefinition | PinLikeParameter): void { p.condition = migrateExpressionSourceImpl(p.condition); if (Array.isArray(p.dim)) { for (let i = 0; i < p.dim.length; i++) { p.dim[i] = migrateExpressionSourceImpl(p.dim[i])!; } } } /** 转换 parameterGroup */ function parameterGroup(g: ParameterGroup): void { g.condition = migrateExpressionSourceImpl(g.condition); for (const p of g.items ?? []) { parameter(p); } } /** 转换 parameter */ function parameter(p: Parameter): void { p.condition = migrateExpressionSourceImpl(p.condition); p.input ??= 'constant'; if (p.type === 'pinLike') { pin(p); } else if (p.type === 'choice' || p.type === 'multiSelect' || p.type === 'logical') { if (Array.isArray(p.choices)) { for (const c of p.choices) { c.condition = migrateExpressionSourceImpl(c.condition); } } else { p.choices = migrateExpressionImpl(p.choices); } } } /** 转换 config */ function config(c: ModelConfiguration | undefined, definition: Model | undefined): void { if (!c) return; // 首先转换表达式 for (const key in c.args) { c.args[key] = migrateExpressionImpl(c.args[key]); } const def = definition?.revision?.parameters; // 如果有定义,修正部分参数 c.args = fillArgumentMap(def, c.args); } /** 转换 job */ function job(j: RootFuncConfiguration | undefined): void { if (!j) return; const { output_channels } = j.args; if (Array.isArray(output_channels)) { j.args['output_channels'] = (output_channels as string[][]).map((c) => [ c[0], c[1], c[2], c[3], String(c[4]) ? String(c[4]).split(',') : [], ]); } for (const key in j.args) { j.args[key] = migrateExpressionImpl(j.args[key]); } } /** 转换 graphic */ function graphic(g: ModelGraphic | undefined): void { if (!g) return; for (const m of g.markup) { m.condition = migrateExpressionSourceImpl(m.condition); } } /** * 将旧 expression 转换为新的 expression */ function migrateExpression(model: Model): void { diagram(model.revision.implements.diagram); powerFlow(model.revision.implements.powerFlow); graphic(model.revision.graphic); for (const p of model.revision.pins ?? []) { pin(p); } for (const p of model.revision.parameters ?? []) { parameterGroup(p); } for (const c of model.configs ?? []) { config(c, model); } for (const j of model.jobs ?? []) { job(j); } } /** 更新 RID */ function updateComponentRid(model: Model): void { const { revision } = model; const { diagram } = revision.implements; if (!diagram?.cells) return; for (const key in diagram.cells) { const cell = diagram.cells[key] as DiagramComponent | null; if (cell?.shape !== 'diagram-component') continue; if (!isResourceId(cell.definition)) continue; cell.definition = cell.definition.replace(/^project\//, 'model/') as ModelId; } } /** 更新 MergeDemerge */ function migrateMergeDemerge(model: Model): void { const { revision } = model; const { diagram } = revision.implements; if (!diagram?.cells) return; // K: cellId V: (K: oldPinId V: newPinId)) const pinIdMapping = new Map>(); for (const key in diagram.cells) { const cell = diagram.cells[key] as DiagramComponent | null; if (cell?.shape !== 'diagram-component') continue; if (!isResourceId(cell.definition)) continue; const mergeDef = MERGE_DEMERGE_COMPONENTS[cell.definition]; if (!mergeDef) continue; const mapping = new Map<`id-${string}`, number>(); pinIdMapping.set(cell.id, mapping); const pinDef = (cell.args[mergeDef.secondaryArgs] as Array>) ?? []; if (!Array.isArray(pinDef)) continue; const pinMapping = (cell.args[`${mergeDef.secondaryArgs}$mapping$`] as Array<`id-${string}`>) ?? []; delete cell.args[`${mergeDef.secondaryArgs}$mapping$`]; for (let index = 0; index < pinDef.length; index++) { const element = { ...pinDef[index] }; const oldId = pinMapping[index]; pinDef[index] = element; const newId = generateTrackId(); element[SECONDARY_ARG_ID] = newId; if (oldId) { mapping.set(oldId, newId); const pinValue = cell.pins[oldId]; delete cell.pins[oldId]; cell.pins[`id-${newId}`] = pinValue ?? ''; } else { cell.pins[`id-${newId}`] = ''; } } } /** 更新连接 */ function migrateTerminal(terminal?: DiagramEdgeTerminal): void { if (terminal == null) return; if (!('cell' in terminal) || terminal.cell == null) return; const mapping = pinIdMapping.get(terminal.cell); if (mapping == null) return; const newPort = mapping.get(terminal.port as `id-${string}`); if (newPort == null) return; terminal.port = `id-${newPort}`; } for (const key in diagram.cells) { const cell = diagram.cells[key] as DiagramEdge | null; if (!cell?.shape.endsWith('-edge')) continue; migrateTerminal(cell.source); migrateTerminal(cell.target); } } /** * 升级算例 */ export function migrate(model: Model): void { updateComponentRid(model); migrateExpression(model); migrateMergeDemerge(model); model.revision.version = 3 as ResourceRevisionVersion; }