diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 1d94a4fe3..c7996f749 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -36,6 +36,10 @@ jobs: - name: ๐Ÿ’… Run stylelint run: yarn workspace @kaoto/kaoto run lint:style + # Build packages excluding @kaoto/camel-catalog since it was build during installing dependencies + - name: Build packages + run: yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog --exclude @kaoto/xml-schema-ts run build + # Run tests - name: ๐Ÿงช Run tests run: yarn workspaces foreach --verbose --all --topological-dev run test @@ -45,10 +49,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - # Build packages excluding @kaoto/camel-catalog since it was build during installing dependencies - - name: Build packages - run: yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog run build - # Build lib - name: Build @kaoto/kaoto package in lib mode run: yarn workspace @kaoto/kaoto run build:lib diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index 1b7f603de..bd030572d 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -44,7 +44,7 @@ jobs: # Build packages excluding @kaoto/camel-catalog since it was build during installing dependencies - name: '๐Ÿ”ง Build packages' run: | - yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog run build + yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog --exclude @kaoto/xml-schema-ts run build - name: '๐Ÿ”ง Tar UI Dist' shell: bash diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index bdcbc8018..b1eb09c44 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -45,7 +45,7 @@ jobs: # Build packages excluding @kaoto/camel-catalog since it was build during installing dependencies - name: Build packages - run: yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog run build + run: yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog --exclude @kaoto/xml-schema-ts run build # Build lib - name: Build @kaoto/kaoto package in lib mode diff --git a/.github/workflows/release-pipeline.yml b/.github/workflows/release-pipeline.yml index e24b217ff..f9e844958 100644 --- a/.github/workflows/release-pipeline.yml +++ b/.github/workflows/release-pipeline.yml @@ -105,7 +105,7 @@ jobs: - name: '๐Ÿ”ง Build packages' run: | - yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog run build + yarn workspaces foreach --verbose --all --topological-dev --exclude @kaoto/camel-catalog --exclude @kaoto/xml-schema-ts run build - name: '๐Ÿ›ฐ๏ธ Login to Container Registry' uses: docker/login-action@v3 diff --git a/package.json b/package.json index 9724d9246..a4c516973 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "typescript": "5.5.4" }, "scripts": { - "postinstall": "yarn workspace @kaoto/camel-catalog run build", + "postinstall": "yarn workspace @kaoto/camel-catalog run build && yarn workspace @kaoto/xml-schema-ts run build", "version": "lerna version", "publish": "lerna publish from-package" }, diff --git a/packages/ui-tests/stories/datamapper/DataMapperDebugger.stories.tsx b/packages/ui-tests/stories/datamapper/DataMapperDebugger.stories.tsx new file mode 100644 index 000000000..c3293ce19 --- /dev/null +++ b/packages/ui-tests/stories/datamapper/DataMapperDebugger.stories.tsx @@ -0,0 +1,18 @@ +import { DataMapperDebugger } from '@kaoto/kaoto/testing'; +import { Meta, StoryFn } from '@storybook/react'; +import { fn } from '@storybook/test'; + +export default { + title: 'DataMapper/Debugger', + component: DataMapperDebugger, +} as Meta; + +const Template: StoryFn = (args) => { + return ; +}; + +export const Debugger = Template.bind({}); +Debugger.args = { + onUpdateDocument: fn(), + onUpdateMappings: fn(), +}; diff --git a/packages/ui/jest-setup.ts b/packages/ui/jest-setup.ts index 99fc973fc..6fc4f2ace 100644 --- a/packages/ui/jest-setup.ts +++ b/packages/ui/jest-setup.ts @@ -17,6 +17,20 @@ Object.defineProperty(window, 'fetch', { value: jest.fn(), }); +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + jest .spyOn(global, 'crypto', 'get') .mockImplementation(() => ({ getRandomValues: () => [12345678], subtle }) as unknown as Crypto); diff --git a/packages/ui/package.json b/packages/ui/package.json index 56ab2e9ff..50f4ea8ac 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -51,12 +51,17 @@ "lint:style:fix": "yarn lint:style --fix" }, "dependencies": { + "@dnd-kit/core": "^6.1.0", "@kaoto-next/uniforms-patternfly": "^0.7.14", + "@kaoto/xml-schema-ts": "workspace:*", "@kie-tools-core/editor": "0.32.0", "@kie-tools-core/notifications": "0.32.0", "@types/uuid": "^10.0.0", + "@types/xml-name-validator": "^4.0.3", + "@visx/shape": "^3.12.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.0", + "chevrotain": "10.5.0", "clsx": "^2.1.0", "html-to-image": "^1.11.11", "lodash": "^4.17.21", @@ -67,6 +72,8 @@ "uniforms-bridge-json-schema": "4.0.0-alpha.6", "usehooks-ts": "^3.0.0", "uuid": "^10.0.0", + "xml-formatter": "^3.6.2", + "xml-name-validator": "^5.0.0", "yaml": "^2.3.2", "zustand": "^4.3.9" }, diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 20995b843..7a4ff7892 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,4 +1,11 @@ +import { VisualizationProvider } from '@patternfly/react-topology'; +import { useMemo } from 'react'; import { Outlet } from 'react-router-dom'; +import { RenderingProvider } from './components/RenderingAnchor/rendering.provider'; +import { ControllerService } from './components/Visualization/Canvas/controller.service'; +import { RegisterComponents } from './components/registers/RegisterComponents'; +import { RegisterNodeInteractionAddons } from './components/registers/RegisterNodeInteractionAddons'; +import { NodeInteractionAddonProvider } from './components/registers/interactions/node-interaction-addon.provider'; import { useReload } from './hooks/reload.hook'; import { Shell } from './layout/Shell'; import { LocalStorageSettingsAdapter } from './models/settings/localstorage-settings-adapter'; @@ -17,6 +24,7 @@ import { CatalogSchemaLoader } from './utils/catalog-schema-loader'; function App() { const ReloadProvider = useReload(); + const controller = useMemo(() => ControllerService.createController(), []); const settingsAdapter = new LocalStorageSettingsAdapter(); let catalogUrl = CatalogSchemaLoader.DEFAULT_CATALOG_PATH; const settingsCatalogUrl = settingsAdapter.getSettings().catalogUrl; @@ -35,9 +43,19 @@ function App() { - - - + + + + + + + + + + + + + diff --git a/packages/ui/src/assets/components/datamapper.png b/packages/ui/src/assets/components/datamapper.png new file mode 100644 index 000000000..57472858c Binary files /dev/null and b/packages/ui/src/assets/components/datamapper.png differ diff --git a/packages/ui/src/assets/data-mapper/add-parameter.png b/packages/ui/src/assets/data-mapper/add-parameter.png new file mode 100644 index 000000000..104295fda Binary files /dev/null and b/packages/ui/src/assets/data-mapper/add-parameter.png differ diff --git a/packages/ui/src/assets/data-mapper/add_parameter_confirm.png b/packages/ui/src/assets/data-mapper/add_parameter_confirm.png new file mode 100644 index 000000000..cfc2a6a10 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/add_parameter_confirm.png differ diff --git a/packages/ui/src/assets/data-mapper/attach-schema-parameter.png b/packages/ui/src/assets/data-mapper/attach-schema-parameter.png new file mode 100644 index 000000000..38b38a69d Binary files /dev/null and b/packages/ui/src/assets/data-mapper/attach-schema-parameter.png differ diff --git a/packages/ui/src/assets/data-mapper/attach-schema-source-body.png b/packages/ui/src/assets/data-mapper/attach-schema-source-body.png new file mode 100644 index 000000000..973be1a40 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/attach-schema-source-body.png differ diff --git a/packages/ui/src/assets/data-mapper/attach-schema-target-body.png b/packages/ui/src/assets/data-mapper/attach-schema-target-body.png new file mode 100644 index 000000000..0d9818df2 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/attach-schema-target-body.png differ diff --git a/packages/ui/src/assets/data-mapper/configure-button.png b/packages/ui/src/assets/data-mapper/configure-button.png new file mode 100644 index 000000000..f2a77bde3 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/configure-button.png differ diff --git a/packages/ui/src/assets/data-mapper/data-mapping.png b/packages/ui/src/assets/data-mapper/data-mapping.png new file mode 100644 index 000000000..58449c875 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/data-mapping.png differ diff --git a/packages/ui/src/assets/data-mapper/datamapper-step.png b/packages/ui/src/assets/data-mapper/datamapper-step.png new file mode 100644 index 000000000..b38b2b53e Binary files /dev/null and b/packages/ui/src/assets/data-mapper/datamapper-step.png differ diff --git a/packages/ui/src/assets/data-mapper/kaoto-datamapper-catalog.png b/packages/ui/src/assets/data-mapper/kaoto-datamapper-catalog.png new file mode 100644 index 000000000..604c4eaea Binary files /dev/null and b/packages/ui/src/assets/data-mapper/kaoto-datamapper-catalog.png differ diff --git a/packages/ui/src/assets/data-mapper/kaoto-design.png b/packages/ui/src/assets/data-mapper/kaoto-design.png new file mode 100644 index 000000000..46e5aa5fd Binary files /dev/null and b/packages/ui/src/assets/data-mapper/kaoto-design.png differ diff --git a/packages/ui/src/assets/data-mapper/source-target.png b/packages/ui/src/assets/data-mapper/source-target.png new file mode 100644 index 000000000..59a287564 Binary files /dev/null and b/packages/ui/src/assets/data-mapper/source-target.png differ diff --git a/packages/ui/src/assets/kaoto-patterns/kaoto-patterns.json b/packages/ui/src/assets/kaoto-patterns/kaoto-patterns.json new file mode 100644 index 000000000..0ff8ac0a8 --- /dev/null +++ b/packages/ui/src/assets/kaoto-patterns/kaoto-patterns.json @@ -0,0 +1,97 @@ +{ + "kaoto-datamapper" : { + "model" : { + "kind" : "model", + "name" : "kaoto-datamapper", + "title" : "Kaoto DataMapper", + "description" : "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "deprecated" : false, + "label" : "transformation", + "javaType" : "org.apache.camel.model.StepDefinition", + "supportLevel" : "Stable", + "abstract" : false, + "input" : true, + "output" : true + }, + "properties" : { + "description" : { + "index" : 0, + "kind" : "attribute", + "displayName" : "Description", + "group" : "common", + "required" : false, + "type" : "string", + "javaType" : "java.lang.String", + "deprecated" : false, + "autowired" : false, + "secret" : false, + "description" : "Sets the description of this node" + }, + "disabled" : { + "index" : 1, + "kind" : "attribute", + "displayName" : "Disabled", + "group" : "advanced", + "label" : "advanced", + "required" : false, + "type" : "boolean", + "javaType" : "java.lang.Boolean", + "deprecated" : false, + "autowired" : false, + "secret" : false, + "defaultValue" : false, + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." + }, + "outputs" : { + "index" : 2, + "kind" : "element", + "displayName" : "Outputs", + "group" : "common", + "required" : true, + "type" : "array", + "javaType" : "java.util.List", + "oneOf" : [ "aggregate", "bean", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "onFallback", "otherwise", "pausable", "pipeline", "policy", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "serviceCall", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "transacted", "transform", "unmarshal", "validate", "when", "whenSkipSendToEndpoint", "wireTap" ], + "deprecated" : false, + "autowired" : false, + "secret" : false + } + }, + "exchangeProperties" : { + "CamelStepId" : { + "index" : 0, + "kind" : "exchangeProperty", + "displayName" : "Step Id", + "label" : "producer", + "required" : false, + "javaType" : "String", + "deprecated" : false, + "autowired" : false, + "secret" : false, + "description" : "The id of the Step EIP" + } + }, + "propertiesSchema" : { + "title" : "Kaoto DataMapper", + "description" : "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "description" : { + "type" : "string", + "title" : "Description", + "description" : "Sets the description of this node", + "group" : "common" + }, + "disabled" : { + "type" : "boolean", + "title" : "Disabled", + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "group" : "advanced" + } + }, + "$comment" : "steps", + "$schema" : "http://json-schema.org/draft-07/schema#", + "required" : [ ] + } + } +} diff --git a/packages/ui/src/camel-utils/camel-random-id.test.ts b/packages/ui/src/camel-utils/camel-random-id.test.ts index 2fa43aa75..bb9f0a720 100644 --- a/packages/ui/src/camel-utils/camel-random-id.test.ts +++ b/packages/ui/src/camel-utils/camel-random-id.test.ts @@ -1,4 +1,4 @@ -import { getCamelRandomId } from './camel-random-id'; +import { getCamelRandomId, getHexaDecimalRandomId } from './camel-random-id'; describe('camel-random-id', () => { it('should return a random number', () => { @@ -35,4 +35,19 @@ describe('camel-random-id', () => { expect(getCamelRandomId('route')).toEqual(expect.any(String)); }); + + describe('getHexaDecimalRandomId()', () => { + it('should return a random number with Hexadecimal format', async () => { + // crypto.getRandomValues() in Jest returns a fixed number 12345678. Replacing with Date.now() + jest + .spyOn(global, 'crypto', 'get') + .mockImplementation(() => ({ getRandomValues: () => [Date.now()] }) as unknown as Crypto); + const one = getHexaDecimalRandomId('test'); + expect(one).toMatch(/test-[0-9a-f]{1,8}/); + await new Promise((f) => setTimeout(f, 1)); + const two = getHexaDecimalRandomId('test'); + expect(two).toMatch(/test-[0-9a-f]{1,8}/); + expect(one).not.toEqual(two); + }); + }); }); diff --git a/packages/ui/src/camel-utils/camel-random-id.ts b/packages/ui/src/camel-utils/camel-random-id.ts index d6c51b168..651a7635e 100644 --- a/packages/ui/src/camel-utils/camel-random-id.ts +++ b/packages/ui/src/camel-utils/camel-random-id.ts @@ -1,6 +1,14 @@ +const getCryptoObj = () => { + return window.crypto || (window as Window & { msCrypto?: Crypto }).msCrypto; +}; + export const getCamelRandomId = (kind: string, length = 4): string => { - const cryptoObj = window.crypto || (window as Window & { msCrypto?: Crypto }).msCrypto; - const randomNumber = Math.floor(cryptoObj?.getRandomValues(new Uint32Array(1))[0] ?? Date.now()); + const randomNumber = Math.floor(getCryptoObj()?.getRandomValues(new Uint32Array(1))[0] ?? Date.now()); return `${kind}-${randomNumber.toString(10).slice(0, length)}`; }; + +export const getHexaDecimalRandomId = (prefix: string) => { + const randomNumber = getCryptoObj()?.getRandomValues(new Uint32Array(1))[0] ?? Date.now(); + return `${prefix}-${randomNumber.toString(16)}`; +}; diff --git a/packages/ui/src/components/DataMapper/DataMapper.test.tsx b/packages/ui/src/components/DataMapper/DataMapper.test.tsx new file mode 100644 index 000000000..e713e1061 --- /dev/null +++ b/packages/ui/src/components/DataMapper/DataMapper.test.tsx @@ -0,0 +1,113 @@ +import { render, screen } from '@testing-library/react'; +import { IVisualizationNode } from '../../models'; +import { DocumentDefinitionType } from '../../models/datamapper/document'; +import { IDataMapperMetadata } from '../../models/datamapper/metadata'; +import { IMetadataApi, MetadataProvider } from '../../providers'; +import { shipOrderToShipOrderXslt, shipOrderXsd } from '../../stubs/data-mapper'; +import { DataMapper } from './DataMapper'; + +describe('DataMapperPage', () => { + const vizNode = { + getId: () => 'route-1234', + getComponentSchema: () => { + return { + definition: { id: 'kaoto-datamapper-1234' }, + }; + }, + } as unknown as IVisualizationNode; + const defaultMetadata: IDataMapperMetadata = { + sourceBody: { + type: DocumentDefinitionType.Primitive, + filePath: [], + }, + sourceParameters: {}, + targetBody: { + type: DocumentDefinitionType.Primitive, + filePath: [], + }, + xsltPath: `kaoto-datamapper-1234.xsl`, + }; + + let metadata: IDataMapperMetadata; + let fileContents: Record; + const api = { + getMetadata: (_key: string) => { + return Promise.resolve(metadata); + }, + setMetadata: (_key: string, meta: IDataMapperMetadata) => { + Object.assign(metadata, meta); + return Promise.resolve(); + }, + getResourceContent: (path: string) => { + return Promise.resolve(fileContents[path]); + }, + saveResourceContent: (path: string, content: string) => { + fileContents[path] = content; + return Promise.resolve(); + }, + } as IMetadataApi; + + beforeEach(() => { + metadata = defaultMetadata; + fileContents = {}; + }); + + it('should render initial XSLT mappings', async () => { + fileContents[metadata.xsltPath] = shipOrderToShipOrderXslt; + render( + + + , + ); + await screen.findByTestId('card-source-parameters-header'); + // TODO assert mappings are restored even without loading schema... But how? Lines are not drawn... + }); + + it('should render initial XSLT mappings with initial documents', async () => { + fileContents['ShipOrder.xsd'] = shipOrderXsd; + metadata.sourceBody = { + filePath: ['ShipOrder.xsd'], + type: DocumentDefinitionType.XML_SCHEMA, + }; + metadata.targetBody = { + filePath: ['ShipOrder.xsd'], + type: DocumentDefinitionType.XML_SCHEMA, + }; + metadata.sourceParameters['testparam1'] = { + filePath: [], + type: DocumentDefinitionType.Primitive, + }; + render( + + + , + ); + await screen.findByTestId('card-source-parameters-header'); + expect(screen.getByTestId('node-source-doc-param-testparam1')).toBeInTheDocument(); + expect(screen.getByTestId('node-source-doc-sourceBody-Body')).toBeInTheDocument(); + expect(screen.getByTestId('node-target-doc-targetBody-Body')).toBeInTheDocument(); + expect(screen.getByTestId(/node-source-field-OrderId-\n*/)).toBeInTheDocument(); + expect(screen.getByTestId(/node-target-field-OrderId-\n*/)).toBeInTheDocument(); + // TODO assert mappings are restored even without loading schema... But how? Lines are not drawn... + }); + + it('should not render toolbar menu in embedded mode', async () => { + render( + + + , + ); + try { + await screen.findByTestId('main-menu-button'); + fail(); + } catch (e) { + expect(e).toBeTruthy(); + } + }); + + it('should show an error message if vizNode is not provided', async () => { + render(); + const error = await screen.findByText('No associated DataMapper step was provided.'); + expect(error).toBeInTheDocument(); + }); +}); diff --git a/packages/ui/src/components/DataMapper/DataMapper.tsx b/packages/ui/src/components/DataMapper/DataMapper.tsx new file mode 100644 index 000000000..b7d7a5547 --- /dev/null +++ b/packages/ui/src/components/DataMapper/DataMapper.tsx @@ -0,0 +1,116 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { FunctionComponent, useCallback, useContext, useEffect, useState } from 'react'; +import { DataMapperControl } from '../../components/DataMapper/DataMapperControl'; +import { DataMapperProvider } from '../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../providers/datamapper-canvas.provider'; +import { DocumentDefinition, DocumentInitializationModel } from '../../models/datamapper/document'; +import { IVisualizationNode } from '../../models'; +import { EntitiesContext, MetadataContext } from '../../providers'; +import { DataMapperMetadataService } from '../../services/datamapper-metadata.service'; +import { DocumentType } from '../../models/datamapper/path'; +import { IDataMapperMetadata } from '../../models/datamapper/metadata'; +import { Loading } from '../../components/Loading'; + +export interface IDataMapperProps { + vizNode?: IVisualizationNode; +} + +export const DataMapper: FunctionComponent = ({ vizNode }) => { + const entitiesContext = useContext(EntitiesContext)!; + const ctx = useContext(MetadataContext)!; + const metadataId = vizNode && DataMapperMetadataService.getDataMapperMetadataId(vizNode); + const [metadata, setMetadata] = useState(); + const [documentInitializationModel, setDocumentInitializationModel] = useState(); + const [initialXsltFile, setInitialXsltFile] = useState(); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (!metadataId) return; + const initialize = async () => { + let meta = await ctx.getMetadata(metadataId); + if (!meta) { + meta = await DataMapperMetadataService.initializeDataMapperMetadata(entitiesContext, vizNode, ctx, metadataId); + } + setMetadata(meta); + const initModel = await DataMapperMetadataService.loadDocuments(ctx, meta); + setDocumentInitializationModel(initModel); + const mappingFile = await DataMapperMetadataService.loadMappingFile(ctx, meta); + setInitialXsltFile(mappingFile); + }; + initialize().then(() => setIsLoading(false)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onUpdateDocument = useCallback( + (definition: DocumentDefinition) => { + if (!metadataId || !metadata) return; + switch (definition.documentType) { + case DocumentType.SOURCE_BODY: + DataMapperMetadataService.updateSourceBodyMetadata(ctx, metadataId, metadata, definition); + break; + case DocumentType.TARGET_BODY: + DataMapperMetadataService.updateTargetBodyMetadata(ctx, metadataId, metadata, definition); + break; + case DocumentType.PARAM: + DataMapperMetadataService.updateSourceParameterMetadata( + ctx, + metadataId, + metadata, + definition.name!, + definition, + ); + } + }, + [ctx, metadata, metadataId], + ); + + const onDeleteParameter = useCallback( + (name: string) => { + if (!metadataId || !metadata) return; + DataMapperMetadataService.deleteSourceParameterMetadata(ctx, metadataId, metadata, name); + }, + [ctx, metadata, metadataId], + ); + + const onUpdateMappings = useCallback( + (xsltFile: string) => { + if (!metadata) return; + DataMapperMetadataService.updateMappingFile(ctx, metadata, xsltFile); + }, + [ctx, metadata], + ); + + return !metadataId ? ( + <>No associated DataMapper step was provided. + ) : isLoading ? ( + + ) : ( + + + + + + ); +}; + +export default DataMapper; diff --git a/packages/ui/src/components/DataMapper/DataMapperControl.tsx b/packages/ui/src/components/DataMapper/DataMapperControl.tsx new file mode 100644 index 000000000..4579f3a1b --- /dev/null +++ b/packages/ui/src/components/DataMapper/DataMapperControl.tsx @@ -0,0 +1,33 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { FunctionComponent, useMemo } from 'react'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { CanvasView } from '../../models/datamapper/view'; +import { SourceTargetView } from '../View/SourceTargetView'; + +export const DataMapperControl: FunctionComponent = () => { + const { activeView } = useDataMapper(); + const currentView = useMemo(() => { + switch (activeView) { + case CanvasView.SOURCE_TARGET: + return ; + default: + return <>View {activeView} is not supported; + } + }, [activeView]); + + return <>{currentView}; +}; diff --git a/packages/ui/src/components/DataMapper/DataMapperLauncher.scss b/packages/ui/src/components/DataMapper/DataMapperLauncher.scss new file mode 100644 index 000000000..aecd3e85c --- /dev/null +++ b/packages/ui/src/components/DataMapper/DataMapperLauncher.scss @@ -0,0 +1,5 @@ +.data-mapper-launcher { + button { + margin-top: var(--pf-v5-global--spacer--md); + } +} diff --git a/packages/ui/src/components/DataMapper/DataMapperLauncher.tsx b/packages/ui/src/components/DataMapper/DataMapperLauncher.tsx new file mode 100644 index 000000000..afd68bb1a --- /dev/null +++ b/packages/ui/src/components/DataMapper/DataMapperLauncher.tsx @@ -0,0 +1,113 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { + Alert, + Button, + Form, + FormGroup, + HelperText, + HelperTextItem, + Popover, + TextInput, + ValidatedOptions, +} from '@patternfly/react-core'; +import { HelpIcon, WrenchIcon } from '@patternfly/react-icons'; +import { FunctionComponent, useCallback, useContext, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { IVisualizationNode } from '../../models'; +import { MetadataContext } from '../../providers/metadata.provider'; +import { Links } from '../../router/links.models'; +import { DataMapperMetadataService } from '../../services/datamapper-metadata.service'; +import { isDefined, isXSLTComponent } from '../../utils'; +import type { XsltComponentDef } from '../../utils/is-xslt-component'; +import './DataMapperLauncher.scss'; + +export const DataMapperLauncher: FunctionComponent<{ vizNode?: IVisualizationNode }> = ({ vizNode }) => { + const navigate = useNavigate(); + const metadata = useContext(MetadataContext); + const xsltDocument = useMemo(() => { + const xsltComponent = vizNode?.getComponentSchema()?.definition?.steps?.find(isXSLTComponent) as XsltComponentDef; + return DataMapperMetadataService.getXSLTDocumentName(xsltComponent); + }, [vizNode]); + const isXsltDocumentDefined = useMemo(() => { + return isDefined(xsltDocument); + }, [xsltDocument]); + + const onClick = useCallback(() => { + navigate(`${Links.DataMapper}/${vizNode?.getComponentSchema()?.definition?.id}`); + }, [navigate, vizNode]); + + if (!isDefined(metadata)) { + return ( + +

+ At the moment, the Kaoto DataMapper cannot be configured using the browser directly. Please use the VS Code + extension for an enhanced experience. The Kaoto extension is bundled in the  + + Extension Pack for Apache Camel + +

+
+ ); + } + + return ( +
+
+ + +
+ ); +}; + +export default DataMapperLauncher; diff --git a/packages/ui/src/components/DataMapper/debug/CanvasMonitor.tsx b/packages/ui/src/components/DataMapper/debug/CanvasMonitor.tsx new file mode 100644 index 000000000..1380d5ff7 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/CanvasMonitor.tsx @@ -0,0 +1,18 @@ +import { FunctionComponent, useEffect } from 'react'; +import { useCanvas } from '../../../hooks/useCanvas'; + +export const CanvasMonitor: FunctionComponent = () => { + const { getAllNodePaths, reloadNodeReferences } = useCanvas(); + + useEffect(() => { + console.log( + 'Node References: [' + + getAllNodePaths() + .map((p) => p + '\n') + .toString() + + ']', + ); + }, [getAllNodePaths, reloadNodeReferences]); + + return <>; +}; diff --git a/packages/ui/src/components/DataMapper/debug/ContextToolbar.tsx b/packages/ui/src/components/DataMapper/debug/ContextToolbar.tsx new file mode 100644 index 000000000..f5d63665b --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/ContextToolbar.tsx @@ -0,0 +1,34 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { FunctionComponent } from 'react'; +import { Toolbar, ToolbarContent, ToolbarGroup } from '@patternfly/react-core'; +import { MainMenuToolbarItem } from './MainMenuToolbarItem'; +import { ToggleDebugToolbarItem } from './ToggleDebugToolbarItem'; + +export const ContextToolbar: FunctionComponent = () => { + return ( + + + { + + + + + } + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.test.tsx b/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.test.tsx new file mode 100644 index 000000000..583461424 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.test.tsx @@ -0,0 +1,13 @@ +import { DataMapperDebugger } from './DataMapperDebugger'; +import { render, screen } from '@testing-library/react'; + +describe('Debug', () => { + it('should render', async () => { + const mockLog = jest.fn(); + console.log = mockLog; + render(); + await screen.findByTestId('main-menu-button'); + const nodeRefsLog = mockLog.mock.calls.filter((call) => call[0].startsWith('Node References: [')); + expect(nodeRefsLog.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.tsx b/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.tsx new file mode 100644 index 000000000..c9552cdd4 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DataMapperDebugger.tsx @@ -0,0 +1,32 @@ +import { FunctionComponent } from 'react'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { DebugLayout } from './DebugLayout'; +import { DocumentDefinition, DocumentInitializationModel } from '../../../models/datamapper'; + +type DataMapperDebuggerProps = { + documentInitializationModel?: DocumentInitializationModel; + onUpdateDocument?: (definition: DocumentDefinition) => void; + initialXsltFile?: string; + onUpdateMappings?: (xsltFile: string) => void; +}; + +export const DataMapperDebugger: FunctionComponent = ({ + documentInitializationModel, + onUpdateDocument, + initialXsltFile, + onUpdateMappings, +}) => { + return ( + + + + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/DataMapperMonitor.tsx b/packages/ui/src/components/DataMapper/debug/DataMapperMonitor.tsx new file mode 100644 index 000000000..3e69f48df --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DataMapperMonitor.tsx @@ -0,0 +1,15 @@ +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { useEffect } from 'react'; +import { MappingService } from '../../../services/mapping.service'; + +export const DataMapperMonitor = () => { + const { mappingTree, sourceParameterMap, sourceBodyDocument, targetBodyDocument } = useDataMapper(); + + useEffect(() => { + MappingService.extractMappingLinks(mappingTree, sourceParameterMap, sourceBodyDocument).forEach((mapping) => { + console.log(`Mapping: [source={${mapping.sourceNodePath}}, target={${mapping.targetNodePath}}]`); + }); + }, [mappingTree, sourceBodyDocument, sourceParameterMap, targetBodyDocument]); + + return <>; +}; diff --git a/packages/ui/src/components/DataMapper/debug/DebugLayout.scss b/packages/ui/src/components/DataMapper/debug/DebugLayout.scss new file mode 100644 index 000000000..5f7ee499d --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DebugLayout.scss @@ -0,0 +1,11 @@ +.debug-layout { + height: 100%; + + &__drawer-content { + height: 100%; + } + + &__drawer-content-body { + height: 100%; + } +} diff --git a/packages/ui/src/components/DataMapper/debug/DebugLayout.test.tsx b/packages/ui/src/components/DataMapper/debug/DebugLayout.test.tsx new file mode 100644 index 000000000..af80c3213 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DebugLayout.test.tsx @@ -0,0 +1,177 @@ +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { DebugLayout } from './DebugLayout'; +import { FunctionComponent, PropsWithChildren, useEffect } from 'react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { useCanvas } from '../../../hooks/useCanvas'; +import { MappingSerializerService } from '../../../services/mapping-serializer.service'; +import { MappingTree } from '../../../models/datamapper/mapping'; +import { shipOrderToShipOrderXslt, TestUtil } from '../../../stubs/data-mapper'; +import { IMappingLink } from '../../../models/datamapper/visualization'; +import { MappingService } from '../../../services/mapping.service'; + +describe('DebugLayout', () => { + afterAll(() => { + jest.resetModules(); + jest.resetAllMocks(); + }); + + it('should render Documents and mappings', async () => { + let mappingLinks: IMappingLink[] = []; + const LoadMappings: FunctionComponent = ({ children }) => { + const { + mappingTree, + setMappingTree, + sourceParameterMap, + setSourceBodyDocument, + setTargetBodyDocument, + sourceBodyDocument, + } = useDataMapper(); + const { getAllNodePaths, reloadNodeReferences } = useCanvas(); + useEffect(() => { + const sourceDoc = TestUtil.createSourceOrderDoc(); + setSourceBodyDocument(sourceDoc); + const targetDoc = TestUtil.createTargetOrderDoc(); + setTargetBodyDocument(targetDoc); + MappingSerializerService.deserialize(shipOrderToShipOrderXslt, targetDoc, mappingTree, sourceParameterMap); + setMappingTree(mappingTree); + reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + mappingLinks = MappingService.extractMappingLinks(mappingTree, sourceParameterMap, sourceBodyDocument); + }, [getAllNodePaths, mappingTree, sourceBodyDocument, sourceParameterMap]); + return <>{children}; + }; + const mockLog = jest.fn(); + console.log = mockLog; + render( + + + + + + + , + ); + await screen.findAllByText('ShipOrder'); + const targetNodes = screen.getAllByTestId(/node-target-.*/); + expect(targetNodes.length).toEqual(20); + expect(mappingLinks.length).toEqual(11); + const nodeRefsLog = mockLog.mock.calls.filter((call) => call[0].startsWith('Node References: [')); + expect(nodeRefsLog.length).toBeGreaterThan(0); + }); + + describe('Main Menu', () => { + it('should import and export mappings', async () => { + let spyOnMappingTree: MappingTree; + const TestLoader: FunctionComponent = ({ children }) => { + const { mappingTree, setSourceBodyDocument, setTargetBodyDocument } = useDataMapper(); + const { reloadNodeReferences } = useCanvas(); + useEffect(() => { + const sourceDoc = TestUtil.createSourceOrderDoc(); + setSourceBodyDocument(sourceDoc); + const targetDoc = TestUtil.createTargetOrderDoc(); + setTargetBodyDocument(targetDoc); + reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + spyOnMappingTree = mappingTree; + }, [mappingTree]); + return <>{children}; + }; + const mockLog = jest.fn(); + console.log = mockLog; + render( + + + + + + + , + ); + let mainMenuButton = await screen.findByTestId('main-menu-button'); + act(() => { + fireEvent.click(mainMenuButton); + }); + const importButton = screen.getByTestId('import-mappings-button'); + act(() => { + fireEvent.click(importButton); + }); + const fileContent = new File([new Blob([shipOrderToShipOrderXslt])], 'ShipOrderToShipOrder.xsl', { + type: 'text/plain', + }); + const fileInput = screen.getByTestId('import-mappings-file-input'); + expect(spyOnMappingTree!.children.length).toBe(0); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + await waitFor(() => expect(spyOnMappingTree!.children.length).toBe(1)); + + mainMenuButton = screen.getByTestId('main-menu-button'); + act(() => { + fireEvent.click(mainMenuButton); + }); + const exportButton = screen.getByTestId('export-mappings-button'); + act(() => { + fireEvent.click(exportButton.getElementsByTagName('button')[0]); + }); + const modal = await screen.findAllByTestId('export-mappings-modal'); + expect(modal).toBeTruthy(); + const closeModalButton = screen.getByTestId('export-mappings-modal-close-btn'); + act(() => { + fireEvent.click(closeModalButton); + }); + expect(screen.queryByTestId('export-mappings-modal')).toBeFalsy(); + const nodeRefsLog = mockLog.mock.calls.filter((call) => call[0].startsWith('Node References: [')); + expect(nodeRefsLog.length).toBeGreaterThan(0); + }, 15000); + }); + + describe('debug', () => { + it('should output debug info to console', async () => { + const TestLoader: FunctionComponent = ({ children }) => { + const { + setDebug, + mappingTree, + setMappingTree, + sourceParameterMap, + setSourceBodyDocument, + setTargetBodyDocument, + } = useDataMapper(); + const { reloadNodeReferences } = useCanvas(); + useEffect(() => { + setDebug(true); + const sourceDoc = TestUtil.createSourceOrderDoc(); + setSourceBodyDocument(sourceDoc); + const targetDoc = TestUtil.createTargetOrderDoc(); + setTargetBodyDocument(targetDoc); + MappingSerializerService.deserialize(shipOrderToShipOrderXslt, targetDoc, mappingTree, sourceParameterMap); + setMappingTree(mappingTree); + reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return <>{children}; + }; + const mockLog = jest.fn(); + console.log = mockLog; + render( + + + + + + + , + ); + await screen.findByTestId('main-menu-button'); + const nodeRefsLog = mockLog.mock.calls.filter((call) => call[0].startsWith('Node References: [')); + expect(nodeRefsLog.length).toBeGreaterThan(0); + const mappingsLog = mockLog.mock.calls.filter((call) => call[0].startsWith('Mapping: [')); + expect(mappingsLog.length).toBeGreaterThan(0); + }); + }); +}); diff --git a/packages/ui/src/components/DataMapper/debug/DebugLayout.tsx b/packages/ui/src/components/DataMapper/debug/DebugLayout.tsx new file mode 100644 index 000000000..ddcabe979 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/DebugLayout.tsx @@ -0,0 +1,51 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { FunctionComponent, memo, useEffect } from 'react'; +import { Masthead, MastheadContent, Page, PageSection, PageSectionVariants } from '@patternfly/react-core'; +import { ContextToolbar } from './ContextToolbar'; +import './DebugLayout.scss'; +import { DataMapperControl } from '../DataMapperControl'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { CanvasMonitor } from './CanvasMonitor'; +import { DataMapperMonitor } from './DataMapperMonitor'; +import { BrowserFilePickerMetadataProvider } from '../../../stubs/BrowserFilePickerMetadataProvider'; + +export const DebugLayout: FunctionComponent = memo(function DebugLayout() { + const { setDebug } = useDataMapper()!; + useEffect(() => { + setDebug(true); + }, [setDebug]); + + const header = ( + + + + + + ); + + return ( + + + + + + + + + + ); +}); diff --git a/packages/ui/src/components/DataMapper/debug/ExportMappingFileDropdownItem.tsx b/packages/ui/src/components/DataMapper/debug/ExportMappingFileDropdownItem.tsx new file mode 100644 index 000000000..c6da3777f --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/ExportMappingFileDropdownItem.tsx @@ -0,0 +1,75 @@ +import { Button, DropdownItem, Modal } from '@patternfly/react-core'; +import { CodeEditor, Language } from '@patternfly/react-code-editor'; +import { FunctionComponent, useCallback, useMemo, useState } from 'react'; +import { ExportIcon } from '@patternfly/react-icons'; +import { MappingSerializerService } from '../../../services/mapping-serializer.service'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { Monaco } from '@monaco-editor/react'; +import { editor } from 'monaco-editor/esm/vs/editor/editor.api'; +import IStandaloneEditorConstructionOptions = editor.IStandaloneEditorConstructionOptions; + +export const ExportMappingFileDropdownItem: FunctionComponent<{ + onComplete: () => void; +}> = ({ onComplete }) => { + const { mappingTree, sourceParameterMap } = useDataMapper(); + const [isModalOpen, setIsModalOpen] = useState(); + const [serializedMappings, setSerializedMappings] = useState(); + + const handleMenuClick = useCallback(() => { + const serialized = MappingSerializerService.serialize(mappingTree, sourceParameterMap); + setSerializedMappings(serialized); + setIsModalOpen(true); + }, [mappingTree, sourceParameterMap]); + + const handleModalClose = useCallback(() => { + setSerializedMappings(''); + setIsModalOpen(false); + onComplete(); + }, [onComplete]); + + const onEditorDidMount = useCallback((editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { + editor.layout(); + editor.focus(); + monaco.editor.getModels()[0].updateOptions({ tabSize: 2 }); + }, []); + + const modalActions = useMemo(() => { + return [ + , + ]; + }, [handleModalClose]); + + const editorOptions: IStandaloneEditorConstructionOptions = useMemo(() => { + return { wordWrap: 'on' }; + }, []); + + return ( + <> + } onClick={handleMenuClick} data-testid="export-mappings-button"> + Export current mappings (.xsl) + + handleModalClose()} + actions={modalActions} + data-testid="export-mappings-modal" + > + + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/ImportMappingFileDropdownItem.tsx b/packages/ui/src/components/DataMapper/debug/ImportMappingFileDropdownItem.tsx new file mode 100644 index 000000000..9b7ed9f84 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/ImportMappingFileDropdownItem.tsx @@ -0,0 +1,50 @@ +import { DropdownItem } from '@patternfly/react-core'; +import { ChangeEvent, createRef, FunctionComponent, useCallback } from 'react'; +import { ImportIcon } from '@patternfly/react-icons'; +import { MappingSerializerService } from '../../../services/mapping-serializer.service'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { readFileAsString } from '../../../stubs/read-file-as-string'; + +type ImportMappingFileDropdownItemProps = { + onComplete: () => void; +}; + +export const ImportMappingFileDropdownItem: FunctionComponent = ({ + onComplete, +}) => { + const { mappingTree, sourceParameterMap, targetBodyDocument, refreshMappingTree } = useDataMapper(); + const fileInputRef = createRef(); + + const onClick = useCallback(() => { + fileInputRef.current?.click(); + }, [fileInputRef]); + + const onImport = useCallback( + (event: ChangeEvent) => { + const file = event.target.files?.item(0); + if (!file) return; + readFileAsString(file).then((content) => { + MappingSerializerService.deserialize(content, targetBodyDocument, mappingTree, sourceParameterMap); + refreshMappingTree(); + onComplete(); + }); + }, + [mappingTree, onComplete, refreshMappingTree, sourceParameterMap, targetBodyDocument], + ); + + return ( + <> + } onClick={onClick} data-testid="import-mappings-button"> + Import mappings (.xsl) + + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/MainMenuToolbarItem.tsx b/packages/ui/src/components/DataMapper/debug/MainMenuToolbarItem.tsx new file mode 100644 index 000000000..4f0757164 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/MainMenuToolbarItem.tsx @@ -0,0 +1,27 @@ +import { FunctionComponent } from 'react'; +import { useToggle } from '../../../hooks/useToggle'; +import { Dropdown, DropdownList, MenuToggle, ToolbarItem } from '@patternfly/react-core'; +import { ExportMappingFileDropdownItem } from './ExportMappingFileDropdownItem'; +import { ImportMappingFileDropdownItem } from './ImportMappingFileDropdownItem'; + +export const MainMenuToolbarItem: FunctionComponent = () => { + const { state: isOpen, toggle: onToggle, toggleOff } = useToggle(false); + return ( + + ( + + DataMapper + + )} + isOpen={isOpen} + isPlain={true} + > + + + + + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/README.md b/packages/ui/src/components/DataMapper/debug/README.md new file mode 100644 index 000000000..94e76b66e --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/README.md @@ -0,0 +1,7 @@ +### How to use + +#### In this directory +`yarn vite .` + +#### Or from Kaoto `packages/ui` directory +`yarn vite src/components/DataMapper/debug` diff --git a/packages/ui/src/components/DataMapper/debug/ToggleDebugToolbarItem.tsx b/packages/ui/src/components/DataMapper/debug/ToggleDebugToolbarItem.tsx new file mode 100644 index 000000000..4ae9b96d8 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/ToggleDebugToolbarItem.tsx @@ -0,0 +1,23 @@ +import { FunctionComponent } from 'react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { ToggleGroup, ToggleGroupItem, ToolbarItem } from '@patternfly/react-core'; +import { BugIcon } from '@patternfly/react-icons'; + +export const ToggleDebugToolbarItem: FunctionComponent = () => { + const { debug, setDebug } = useDataMapper(); + + return ( + + + } + aria-label="Enable debug mode" + buttonId="enable-debug-mode" + data-testid="enable-debug-mode-btn" + isSelected={debug} + onChange={() => setDebug(!debug)} + > + + + ); +}; diff --git a/packages/ui/src/components/DataMapper/debug/index.html b/packages/ui/src/components/DataMapper/debug/index.html new file mode 100644 index 000000000..0c7f1cd4d --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/index.html @@ -0,0 +1,16 @@ + + + + + + DataMapper Debugger + + + +
+ + + diff --git a/packages/ui/src/components/DataMapper/debug/index.ts b/packages/ui/src/components/DataMapper/debug/index.ts new file mode 100644 index 000000000..5c87e44e1 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/index.ts @@ -0,0 +1 @@ +export * from './DataMapperDebugger'; diff --git a/packages/ui/src/components/DataMapper/debug/main.tsx b/packages/ui/src/components/DataMapper/debug/main.tsx new file mode 100644 index 000000000..5d753db29 --- /dev/null +++ b/packages/ui/src/components/DataMapper/debug/main.tsx @@ -0,0 +1,15 @@ +import '@patternfly/react-core/dist/styles/base.css'; // This import needs to be first +import { createRoot } from 'react-dom/client'; +import { DataMapperDebugger } from './DataMapperDebugger'; +import { DocumentDefinition } from '../../../models/datamapper/document'; + +const onUpdateMappings = (xsltFile: string) => { + console.log('onUpdateMappings() >>>>> ' + xsltFile); +}; +const onUpdateDocument = (definition: DocumentDefinition) => { + console.log('onUpdateDocument() >>>>>', JSON.stringify(definition)); +}; + +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render(); diff --git a/packages/ui/src/components/DataMapper/on-delete-datamapper.ts b/packages/ui/src/components/DataMapper/on-delete-datamapper.ts new file mode 100644 index 000000000..88d7d7e5b --- /dev/null +++ b/packages/ui/src/components/DataMapper/on-delete-datamapper.ts @@ -0,0 +1,33 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { DataMapperMetadataService } from '../../services/datamapper-metadata.service'; +import { IMetadataApi } from '../../providers'; +import { IVisualizationNode } from '../../models'; + +export const ACTION_ID_DELETE_STEP_AND_FILE = 'del-step-and-file'; +export const ACTION_ID_DELETE_STEP_ONLY = 'del-step-only'; + +export const onDeleteDataMapper = async ( + api: IMetadataApi, + vizNode: IVisualizationNode, + modalAnswer: string | undefined, +) => { + const metadataId = DataMapperMetadataService.getDataMapperMetadataId(vizNode); + if (modalAnswer === ACTION_ID_DELETE_STEP_AND_FILE) { + await DataMapperMetadataService.deleteXsltFile(api, metadataId); + } + await DataMapperMetadataService.deleteMetadata(api, metadataId); +}; diff --git a/packages/ui/src/components/Document/Document.scss b/packages/ui/src/components/Document/Document.scss new file mode 100644 index 000000000..120fd6c13 --- /dev/null +++ b/packages/ui/src/components/Document/Document.scss @@ -0,0 +1,95 @@ +@use '../../styles/dnd'; + +.node { + &__row { + display: flex; + flex-flow: row nowrap; + align-items: center; + + &[data-draggable='false'] [data-drag-handler] { + display: none; + } + + &[data-draggable='true'] { + @include dnd.cursor-grab; + } + } + + &__container { + padding: 5px; + padding-left: var(--pf-v5-global--spacer--md); + } + + &__header, + &__children { + border-bottom: 1px solid var(--pf-v5-global--palette--black-300); + border-left: 1px solid var(--pf-v5-global--palette--black-300); + padding-left: var(--pf-v5-global--spacer--md); + } + + &__children { + padding: var(--pf-v5-global--spacer--xs) var(--pf-v5-global--spacer--md); + } + + &__spacer { + margin: var(--pf-v5-global--spacer--sm) 0 var(--pf-v5-global--spacer--sm) var(--pf-v5-global--spacer--sm); + } + + &__target__actions { + flex-grow: 1; + justify-content: end; + } +} + +img { + transition: transform 0.5s ease-in-out; +} + +.toggle-icon { + cursor: pointer; + + &--collapsed { + transform: rotate(-90deg); + } +} + +.truncate { + --pf-v5-c-truncate--MinWidth: 0; +} + +/* stylelint-disable-next-line selector-class-pattern */ +.pf-v5-c-action-list__group { + padding-left: 0.5rem; + padding-right: 0.5rem; + + .pf-v5-c-button { + --pf-v5-c-button--PaddingLeft: 0.5rem; + --pf-v5-c-button--PaddingRight: 0.5rem; + } + + .pf-v5-c-menu-toggle.pf-m-plain:not(.pf-m-text) { + --pf-v5-c-menu-toggle--PaddingLeft: 0.5rem; + --pf-v5-c-menu-toggle--PaddingRight: 0.5rem; + } +} + +.parameter-card { + /* stylelint-disable-next-line selector-class-pattern */ + .pf-v5-c-card__header { + padding-block: 0; + padding-inline: 0; + } +} + +.parameter-actions { + padding-left: 0.5rem; + padding-right: 0.5rem; + + --pf-v5-c-action-list--child--spacer-base: 0.5rem; + --pf-v5-c-action-list--group--spacer-base: 0.5rem; + + .pf-v5-c-button { + --pf-v5-c-button--PaddingLeft: 0.5rem; + --pf-v5-c-button--PaddingRight: 0.5rem; + } +} diff --git a/packages/ui/src/components/Document/NodeContainer.scss b/packages/ui/src/components/Document/NodeContainer.scss new file mode 100644 index 000000000..93bd17fe9 --- /dev/null +++ b/packages/ui/src/components/Document/NodeContainer.scss @@ -0,0 +1,20 @@ +@use '../../styles/dnd'; + +.droppable-container { + color: var(--pf-v5-global--primary-color--200); + border-width: thin; + border-style: dashed; + border-color: var(--pf-v5-global--primary-color--200); + background-color: var(--pf-v5-global--palette--blue-50); +} + +.draggable-container { + font-weight: bold; + color: var(--pf-v5-global--primary-color--100); + border-width: thin; + border-style: solid; + border-color: var(--pf-v5-global--primary-color--200); + background-color: var(--pf-v5-global--palette--black-200); + + @include dnd.cursor-grab; +} diff --git a/packages/ui/src/components/Document/NodeContainer.tsx b/packages/ui/src/components/Document/NodeContainer.tsx new file mode 100644 index 000000000..2ebed9fdd --- /dev/null +++ b/packages/ui/src/components/Document/NodeContainer.tsx @@ -0,0 +1,83 @@ +import { useDraggable, useDroppable } from '@dnd-kit/core'; +import clsx from 'clsx'; +import { forwardRef, FunctionComponent, PropsWithChildren } from 'react'; +import { DocumentNodeData, NodeData } from '../../models/datamapper/visualization'; +import { isDefined } from '../../utils'; +import './NodeContainer.scss'; +import { VisualizationService } from '../../services/visualization.service'; + +type DnDContainerProps = PropsWithChildren & { + nodeData: NodeData; +}; + +type BaseContainerProps = PropsWithChildren & { + className?: string; + id: string; + nodeData: NodeData; +}; + +export const DroppableContainer: FunctionComponent = ({ className, id, nodeData, children }) => { + const { isOver, setNodeRef: setDroppableNodeRef } = useDroppable({ + id: `droppable-${id}`, + data: nodeData, + }); + + return ( +
+ {children} +
+ ); +}; + +export const DraggableContainer: FunctionComponent = ({ id, nodeData, children }) => { + const { + attributes, + listeners, + setNodeRef: setDraggableNodeRef, + transform, + } = useDraggable({ + id: `draggable-${id}`, + data: nodeData, + }); + + return ( +
+ {children} +
+ ); +}; + +const DnDContainer: FunctionComponent = ({ nodeData, children }) => { + const dndId = VisualizationService.generateDndId(nodeData); + return ( + + + {children} + + + ); +}; + +type NodeContainerProps = PropsWithChildren & { + nodeData?: NodeData; +}; + +export const NodeContainer = forwardRef(({ children, nodeData }, forwardedRef) => { + return nodeData && !(nodeData instanceof DocumentNodeData && !nodeData.isPrimitive) ? ( +
+ {children} +
+ ) : ( +
{children}
+ ); +}); diff --git a/packages/ui/src/components/Document/NodeTitle.tsx b/packages/ui/src/components/Document/NodeTitle.tsx new file mode 100644 index 000000000..3a3cc1fbf --- /dev/null +++ b/packages/ui/src/components/Document/NodeTitle.tsx @@ -0,0 +1,31 @@ +import { Label, Title, Truncate } from '@patternfly/react-core'; +import clsx from 'clsx'; +import { FunctionComponent } from 'react'; +import { MappingNodeData, NodeData } from '../../models/datamapper/visualization'; +import './Document.scss'; + +interface INodeTitle { + className?: string; + nodeData: NodeData; + isDocument: boolean; +} + +export const NodeTitle: FunctionComponent = ({ className, nodeData, isDocument }) => { + if (nodeData instanceof MappingNodeData) { + return ( + + ); + } + + if (isDocument) { + return ( + + <Truncate content={nodeData.title ?? ''} className={clsx('truncate', className)} /> + + ); + } + + return ; +}; diff --git a/packages/ui/src/components/Document/Parameters.test.tsx b/packages/ui/src/components/Document/Parameters.test.tsx new file mode 100644 index 000000000..7649beff5 --- /dev/null +++ b/packages/ui/src/components/Document/Parameters.test.tsx @@ -0,0 +1,143 @@ +import { Parameters } from './Parameters'; +import { DataMapperProvider } from '../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../providers/datamapper-canvas.provider'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { shipOrderXsd } from '../../stubs/data-mapper'; +import { BrowserFilePickerMetadataProvider } from '../../stubs/BrowserFilePickerMetadataProvider'; + +describe('Parameters', () => { + it('should add and remove a parameter', async () => { + const mockUpdateDocument = jest.fn(); + const mockDeleteParameter = jest.fn(); + render( + + + + + + + , + ); + expect(mockUpdateDocument.mock.calls.length).toEqual(0); + expect(mockDeleteParameter.mock.calls.length).toEqual(0); + const addButton = await screen.findByTestId('add-parameter-button'); + act(() => { + fireEvent.click(addButton); + }); + const paramNameInput = screen.getByTestId('add-new-parameter-name-input'); + act(() => { + fireEvent.change(paramNameInput, { target: { value: 'testparam1' } }); + }); + const submitButton = screen.getByTestId('add-new-parameter-submit-btn'); + act(() => { + fireEvent.click(submitButton); + }); + expect(mockUpdateDocument.mock.calls.length).toEqual(1); + expect(mockDeleteParameter.mock.calls.length).toEqual(0); + expect(mockUpdateDocument.mock.calls[0][0]['name']).toEqual('testparam1'); + const deleteButton = screen.getByTestId('delete-parameter-testparam1-button'); + act(() => { + fireEvent.click(deleteButton); + }); + const confirmButton = screen.getByTestId('delete-parameter-modal-confirm-btn'); + act(() => { + fireEvent.click(confirmButton); + }); + expect(mockUpdateDocument.mock.calls.length).toEqual(1); + expect(mockDeleteParameter.mock.calls.length).toEqual(1); + expect(mockDeleteParameter.mock.calls[0][0]).toEqual('testparam1'); + await screen.findByTestId('add-parameter-button'); + const notexist = screen.queryByTestId('delete-parameter-testparam1-button'); + expect(notexist).toBeFalsy(); + }); + + it('should show validation error for invalid parameter name', async () => { + const mockUpdateDocument = jest.fn(); + const mockDeleteParameter = jest.fn(); + render( + + + + + + + , + ); + expect(mockUpdateDocument.mock.calls.length).toEqual(0); + expect(mockDeleteParameter.mock.calls.length).toEqual(0); + const addButton = await screen.findByTestId('add-parameter-button'); + act(() => { + fireEvent.click(addButton); + }); + let paramNameInput = screen.getByTestId('add-new-parameter-name-input'); + act(() => { + fireEvent.change(paramNameInput, { target: { value: 'testparam1::' } }); + }); + expect(screen.getByTestId('new-parameter-helper-text-invalid')).toBeInTheDocument(); + let submitButton = screen.getByTestId('add-new-parameter-submit-btn') as HTMLButtonElement; + expect(submitButton.disabled).toBeTruthy(); + act(() => { + fireEvent.change(paramNameInput, { target: { value: 'testparam1' } }); + }); + expect(submitButton.disabled).toBeFalsy(); + act(() => { + fireEvent.click(submitButton); + }); + act(() => { + fireEvent.click(addButton); + }); + paramNameInput = screen.getByTestId('add-new-parameter-name-input'); + act(() => { + fireEvent.change(paramNameInput, { target: { value: 'testparam1' } }); + }); + expect(screen.getByTestId('new-parameter-helper-text-duplicate')).toBeInTheDocument(); + submitButton = screen.getByTestId('add-new-parameter-submit-btn') as HTMLButtonElement; + expect(submitButton.disabled).toBeTruthy(); + }); + + it('should attach and detach a schema', async () => { + render( + + + + + + + , + ); + const addButton = await screen.findByTestId('add-parameter-button'); + act(() => { + fireEvent.click(addButton); + }); + const paramNameInput = screen.getByTestId('add-new-parameter-name-input'); + act(() => { + fireEvent.change(paramNameInput, { target: { value: 'testparam1' } }); + }); + const submitButton = screen.getByTestId('add-new-parameter-submit-btn'); + act(() => { + fireEvent.click(submitButton); + }); + const attachButton = screen.getByTestId('attach-schema-param-testparam1-button'); + const fileContent = new File([new Blob([shipOrderXsd])], 'ShipOrder.xsd', { type: 'text/plain' }); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = screen.getByTestId('attach-schema-file-input'); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + const shipTo = await screen.findByText('ShipTo'); + expect(shipTo).toBeTruthy(); + + const detachButton = screen.getByTestId('detach-schema-param-testparam1-button'); + act(() => { + fireEvent.click(detachButton); + }); + const detachConfirmButton = screen.getByTestId('detach-schema-modal-confirm-btn'); + act(() => { + fireEvent.click(detachConfirmButton); + }); + await screen.findByTestId('add-parameter-button'); + expect(screen.queryByTestId('ShipTo')).toBeFalsy(); + }); +}); diff --git a/packages/ui/src/components/Document/Parameters.tsx b/packages/ui/src/components/Document/Parameters.tsx new file mode 100644 index 000000000..b596dcc10 --- /dev/null +++ b/packages/ui/src/components/Document/Parameters.tsx @@ -0,0 +1,231 @@ +import { + ActionList, + ActionListGroup, + ActionListItem, + Button, + Card, + CardBody, + CardExpandableContent, + CardHeader, + CardTitle, + HelperText, + HelperTextItem, + Stack, + StackItem, + TextInput, +} from '@patternfly/react-core'; +import { CheckIcon, PlusIcon, TimesIcon } from '@patternfly/react-icons'; +import { FunctionComponent, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { qname } from 'xml-name-validator'; +import { useCanvas } from '../../hooks/useCanvas'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { useToggle } from '../../hooks/useToggle'; +import { DocumentDefinition, DocumentDefinitionType } from '../../models/datamapper/document'; +import { DocumentType } from '../../models/datamapper/path'; +import { NodeReference } from '../../providers/datamapper-canvas.provider'; +import './Document.scss'; +import { NodeContainer } from './NodeContainer'; +import { SourceDocument } from './SourceDocument'; + +enum ParameterNameValidation { + EMPTY, + OK, + DUPLICATE, + INVALID, +} + +type AddNewParameterPlaceholderProps = { + onComplete: () => void; +}; + +const AddNewParameterPlaceholder: FunctionComponent = ({ onComplete }) => { + const { sourceParameterMap, updateDocumentDefinition } = useDataMapper(); + const [newParameterName, setNewParameterName] = useState(''); + + const submitNewParameter = useCallback(() => { + if (!sourceParameterMap.has(newParameterName)) { + const definition = new DocumentDefinition(DocumentType.PARAM, DocumentDefinitionType.Primitive, newParameterName); + updateDocumentDefinition(definition); + } + setNewParameterName(''); + onComplete(); + }, [sourceParameterMap, newParameterName, onComplete, updateDocumentDefinition]); + + const cancelNewParameter = useCallback(() => { + setNewParameterName(''); + onComplete(); + }, [onComplete]); + + const newParameterNameValidation: ParameterNameValidation = useMemo(() => { + if (newParameterName === '') return ParameterNameValidation.EMPTY; + if (sourceParameterMap.has(newParameterName)) return ParameterNameValidation.DUPLICATE; + if (!qname(newParameterName)) return ParameterNameValidation.INVALID; + return ParameterNameValidation.OK; + }, [newParameterName, sourceParameterMap]); + + const textInputValidatedProp = useMemo(() => { + switch (newParameterNameValidation) { + case ParameterNameValidation.OK: + return 'success'; + case ParameterNameValidation.EMPTY: + return 'default'; + case ParameterNameValidation.DUPLICATE: + case ParameterNameValidation.INVALID: + return 'error'; + } + }, [newParameterNameValidation]); + + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + + return ( + + + + setNewParameterName(text)} + placeholder="parameter name" + validated={textInputValidatedProp} + /> + + {newParameterNameValidation === ParameterNameValidation.DUPLICATE && ( + + Parameter '{newParameterName}' already exists + + )} + {newParameterNameValidation === ParameterNameValidation.INVALID && ( + + Invalid parameter name '{newParameterName}': it must be a valid QName + + )} + + + + + + + + + + + + + ); +}; + +type ParametersProps = { + isReadOnly: boolean; +}; + +export const Parameters: FunctionComponent = ({ isReadOnly }) => { + const { sourceParameterMap, isSourceParametersExpanded, setSourceParametersExpanded } = useDataMapper(); + const { reloadNodeReferences } = useCanvas(); + const { + state: isAddingNewParameter, + toggleOff: toggleOffAddNewParameter, + toggleOn: toggleOnAddNewParameter, + } = useToggle(false); + + const { getNodeReference, setNodeReference } = useCanvas(); + const nodeReference = useRef({ headerRef: null, containerRef: null }); + const headerRef = useRef(null); + useImperativeHandle(nodeReference, () => ({ + get headerRef() { + return headerRef.current; + }, + get containerRef() { + return headerRef.current; + }, + })); + const nodeRefId = 'param'; + getNodeReference(nodeRefId) !== nodeReference && setNodeReference(nodeRefId, nodeReference); + + const handleAddNewParameter = useCallback(() => { + setSourceParametersExpanded(true); + toggleOnAddNewParameter(); + }, [setSourceParametersExpanded, toggleOnAddNewParameter]); + + const handleOnExpand = useCallback(() => { + setSourceParametersExpanded(!isSourceParametersExpanded); + reloadNodeReferences(); + }, [isSourceParametersExpanded, reloadNodeReferences, setSourceParametersExpanded]); + + const parametersHeaderActions = useMemo(() => { + return ( + + {!isReadOnly && ( + + + + )} + + ); + }, [handleAddNewParameter, isReadOnly]); + + return ( + + + + Parameters + + + + + + {isAddingNewParameter && ( + + toggleOffAddNewParameter()} /> + + )} + {Array.from(sourceParameterMap.entries()).map(([documentId, doc]) => ( + + + + ))} + + + + + ); +}; diff --git a/packages/ui/src/components/Document/SourceDocument.test.tsx b/packages/ui/src/components/Document/SourceDocument.test.tsx new file mode 100644 index 000000000..0f4649a58 --- /dev/null +++ b/packages/ui/src/components/Document/SourceDocument.test.tsx @@ -0,0 +1,50 @@ +import { SourceDocument } from './SourceDocument'; +import { render, screen, waitFor } from '@testing-library/react'; +import { DataMapperProvider } from '../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../providers/datamapper-canvas.provider'; +import { BODY_DOCUMENT_ID, PrimitiveDocument } from '../../models/datamapper/document'; +import { DocumentType } from '../../models/datamapper/path'; +import { TestUtil } from '../../stubs/data-mapper'; + +describe('SourceDocument', () => { + it('should render primitive document', async () => { + const document = new PrimitiveDocument(DocumentType.SOURCE_BODY, BODY_DOCUMENT_ID); + render( + + + + + , + ); + expect(await screen.findByText('Body')).toBeTruthy(); + }); + + it('should render ShipOrder doc', async () => { + const document = TestUtil.createSourceOrderDoc(); + render( + + + + + , + ); + expect(await screen.findByText('OrderPerson')).toBeTruthy(); + expect(await screen.findByText('Country')).toBeInTheDocument(); + }); + + it('should render camel-spring.xsd doc till 2nd level', async () => { + const document = TestUtil.createCamelSpringXsdSourceDoc(); + render( + + + + + , + ); + await waitFor(async () => { + const aggregates = await screen.findAllByText('aggregate'); + expect(aggregates.length).toEqual(2); + }); + expect(await screen.findByText('correlationExpression')).toBeTruthy(); + }, 10000); +}); diff --git a/packages/ui/src/components/Document/SourceDocument.tsx b/packages/ui/src/components/Document/SourceDocument.tsx new file mode 100644 index 000000000..9244a451f --- /dev/null +++ b/packages/ui/src/components/Document/SourceDocument.tsx @@ -0,0 +1,133 @@ +import { Icon } from '@patternfly/react-core'; +import { AngleDownIcon, AtIcon, GripVerticalIcon, LayerGroupIcon } from '@patternfly/react-icons'; +import clsx from 'clsx'; +import { FunctionComponent, useCallback, useRef, useState } from 'react'; +import { useCanvas } from '../../hooks/useCanvas'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { IDocument } from '../../models/datamapper/document'; +import { DocumentNodeData, NodeData } from '../../models/datamapper/visualization'; +import { NodeReference } from '../../providers/datamapper-canvas.provider'; +import { VisualizationService } from '../../services/visualization.service'; +import { DocumentActions } from './actions/DocumentActions'; +import './Document.scss'; +import { NodeContainer } from './NodeContainer'; +import { NodeTitle } from './NodeTitle'; + +type DocumentProps = { + document: IDocument; + isReadOnly: boolean; +}; + +export const SourceDocument: FunctionComponent = ({ document, isReadOnly }) => { + const { initialExpandedFieldRank, maxTotalFieldCountToExpandAll } = useDataMapper(); + const nodeData = new DocumentNodeData(document); + return ( + + ); +}; + +type DocumentNodeProps = { + nodeData: NodeData; + isReadOnly: boolean; + expandAll: boolean; + initialExpandedRank: number; + rank: number; +}; + +export const SourceDocumentNode: FunctionComponent = ({ + nodeData, + isReadOnly, + expandAll, + initialExpandedRank, + rank, +}) => { + const { getNodeReference, reloadNodeReferences, setNodeReference } = useCanvas(); + const shouldCollapseByDefault = + !expandAll && VisualizationService.shouldCollapseByDefault(nodeData, initialExpandedRank, rank); + const [collapsed, setCollapsed] = useState(shouldCollapseByDefault); + + const onClick = useCallback(() => { + setCollapsed(!collapsed); + reloadNodeReferences(); + }, [collapsed, reloadNodeReferences]); + + const isDocument = nodeData instanceof DocumentNodeData; + const hasChildren = VisualizationService.hasChildren(nodeData); + const children = collapsed ? [] : VisualizationService.generateNodeDataChildren(nodeData); + const isCollectionField = VisualizationService.isCollectionField(nodeData); + const isAttributeField = VisualizationService.isAttributeField(nodeData); + const isDraggable = !isDocument || VisualizationService.isPrimitiveDocumentNode(nodeData); + const headerRef = useRef(null); + const containerRef = useRef(null); + const nodeReference = useRef({ + get headerRef() { + return headerRef.current; + }, + get containerRef() { + return containerRef.current; + }, + }); + const nodeRefId = nodeData.path.toString(); + getNodeReference(nodeRefId) !== nodeReference && setNodeReference(nodeRefId, nodeReference); + + return ( +
+ +
+ +
+ {hasChildren && ( + + )} + + + + + + {isCollectionField && ( + + + + )} + + {isAttributeField && ( + + + + )} + + + + {!isReadOnly && isDocument ? ( + + ) : ( + + )} +
+
+
+ + {hasChildren && !collapsed && ( +
+ {children.map((child) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/packages/ui/src/components/Document/TargetDocument.tsx b/packages/ui/src/components/Document/TargetDocument.tsx new file mode 100644 index 000000000..ea2d9fd7f --- /dev/null +++ b/packages/ui/src/components/Document/TargetDocument.tsx @@ -0,0 +1,144 @@ +import { Icon } from '@patternfly/react-core'; +import { AngleDownIcon, AtIcon, GripVerticalIcon, LayerGroupIcon } from '@patternfly/react-icons'; +import clsx from 'clsx'; +import { FunctionComponent, useCallback, useRef, useState } from 'react'; +import { useCanvas } from '../../hooks/useCanvas'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { IDocument } from '../../models/datamapper/document'; +import { TargetDocumentNodeData, TargetNodeData } from '../../models/datamapper/visualization'; +import { NodeReference } from '../../providers/datamapper-canvas.provider'; +import { VisualizationService } from '../../services/visualization.service'; +import { DocumentActions } from './actions/DocumentActions'; +import { TargetNodeActions } from './actions/TargetNodeActions'; +import './Document.scss'; +import { NodeContainer } from './NodeContainer'; +import { NodeTitle } from './NodeTitle'; + +type DocumentProps = { + document: IDocument; +}; + +export const TargetDocument: FunctionComponent = ({ document }) => { + const { initialExpandedFieldRank, mappingTree, maxTotalFieldCountToExpandAll } = useDataMapper(); + const nodeData = new TargetDocumentNodeData(document, mappingTree); + + return ( + + ); +}; + +type DocumentNodeProps = { + nodeData: TargetNodeData; + expandAll: boolean; + initialExpandedRank: number; + rank: number; +}; + +const TargetDocumentNode: FunctionComponent = ({ + nodeData, + expandAll, + initialExpandedRank, + rank, +}) => { + const { getNodeReference, reloadNodeReferences, setNodeReference } = useCanvas(); + const shouldCollapseByDefault = + !expandAll && VisualizationService.shouldCollapseByDefault(nodeData, initialExpandedRank, rank); + const [collapsed, setCollapsed] = useState(shouldCollapseByDefault); + + const isDocument = VisualizationService.isDocumentNode(nodeData); + const isPrimitive = VisualizationService.isPrimitiveDocumentNode(nodeData); + const hasChildren = VisualizationService.hasChildren(nodeData); + + const onClick = useCallback(() => { + if (!hasChildren) return; + + setCollapsed(!collapsed); + reloadNodeReferences(); + }, [collapsed, hasChildren, reloadNodeReferences]); + + const children = VisualizationService.generateNodeDataChildren(nodeData) as TargetNodeData[]; + const isCollectionField = VisualizationService.isCollectionField(nodeData); + const isAttributeField = VisualizationService.isAttributeField(nodeData); + const isDraggable = !isDocument || VisualizationService.isPrimitiveDocumentNode(nodeData); + const headerRef = useRef(null); + const containerRef = useRef(null); + const nodeReference = useRef({ + get headerRef() { + return headerRef.current; + }, + get containerRef() { + return containerRef.current; + }, + }); + const nodeRefId = nodeData.path.toString(); + getNodeReference(nodeRefId) !== nodeReference && setNodeReference(nodeRefId, nodeReference); + + const showNodeActions = (isDocument && isPrimitive) || !isDocument; + const { refreshMappingTree } = useDataMapper(); + const handleUpdate = useCallback(() => { + refreshMappingTree(); + }, [refreshMappingTree]); + + return ( +
+ +
+ +
+ + {hasChildren && ( + + )} + + + + + + {isCollectionField && ( + + + + )} + + {isAttributeField && ( + + + + )} + + + + + {showNodeActions ? ( + + ) : ( + + )} + + {isDocument && } +
+
+
+ + {hasChildren && !collapsed && ( +
+ {children.map((child) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/packages/ui/src/components/Document/actions/AttachSchemaButton.test.tsx b/packages/ui/src/components/Document/actions/AttachSchemaButton.test.tsx new file mode 100644 index 000000000..83e703e10 --- /dev/null +++ b/packages/ui/src/components/Document/actions/AttachSchemaButton.test.tsx @@ -0,0 +1,130 @@ +import { AttachSchemaButton } from './AttachSchemaButton'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { DocumentType } from '../../../models/datamapper/path'; +import { BODY_DOCUMENT_ID } from '../../../models/datamapper/document'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { readFileAsString } from '../../../stubs/read-file-as-string'; + +import { noTopElementXsd, shipOrderEmptyFirstLineXsd, shipOrderXsd } from '../../../stubs/data-mapper'; +import { BrowserFilePickerMetadataProvider } from '../../../stubs/BrowserFilePickerMetadataProvider'; +import { FunctionComponent, PropsWithChildren, useEffect } from 'react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { AlertProps } from '@patternfly/react-core'; + +jest.mock('../../../stubs/read-file-as-string'); +const mockReadFileAsString = readFileAsString as jest.MockedFunction; + +describe('AttachSchemaButton', () => { + afterAll(() => { + mockReadFileAsString.mockReset(); + }); + + it('should invoke onClick()', async () => { + const spyOnClick = jest.spyOn(HTMLInputElement.prototype, 'click'); + render( + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(spyOnClick.mock.calls.length).toEqual(0); + act(() => { + fireEvent.click(attachButton); + }); + expect(spyOnClick.mock.calls.length).toBeGreaterThan(0); + }); + + it('should invoke onImport()', async () => { + mockReadFileAsString.mockResolvedValue(shipOrderXsd); + render( + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-sourceBody-Body-button'); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = await screen.findByTestId('attach-schema-file-input'); + const fileContent = new File([new Blob([shipOrderXsd])], 'ShipOrder.xsd', { type: 'text/plain' }); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(mockReadFileAsString.mock.calls.length).toEqual(1); + }); + + let capturedAlerts: Partial[] = []; + const TestAlertCapture: FunctionComponent = ({ children }) => { + const { alerts } = useDataMapper(); + useEffect(() => { + capturedAlerts = alerts; + }, [alerts]); + return <>{children}; + }; + + it('should show a toast alert for invalid schema', async () => { + mockReadFileAsString.mockResolvedValue(noTopElementXsd); + render( + + + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-sourceBody-Body-button'); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = await screen.findByTestId('attach-schema-file-input'); + const fileContent = new File([new Blob([noTopElementXsd])], 'NoTopElement.xsd', { type: 'text/plain' }); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(capturedAlerts.length).toEqual(1); + expect(capturedAlerts[0].title).toContain('no top level Element'); + }); + + it('should show a toast alert for XML parse error', async () => { + mockReadFileAsString.mockResolvedValue(shipOrderEmptyFirstLineXsd); + render( + + + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-sourceBody-Body-button'); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = await screen.findByTestId('attach-schema-file-input'); + const fileContent = new File([new Blob([shipOrderEmptyFirstLineXsd])], 'ShipOrderEmptyFirstLine.xsd', { + type: 'text/plain', + }); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(capturedAlerts.length).toEqual(1); + expect(capturedAlerts[0].title).toContain('an XML declaration must be at the start of the document'); + }); +}); diff --git a/packages/ui/src/components/Document/actions/AttachSchemaButton.tsx b/packages/ui/src/components/Document/actions/AttachSchemaButton.tsx new file mode 100644 index 000000000..e414e4eaf --- /dev/null +++ b/packages/ui/src/components/Document/actions/AttachSchemaButton.tsx @@ -0,0 +1,88 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { AlertVariant, Button } from '@patternfly/react-core'; +import { FunctionComponent, useCallback, useContext } from 'react'; + +import { ImportIcon } from '@patternfly/react-icons'; +import { useCanvas } from '../../../hooks/useCanvas'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { DocumentDefinitionType } from '../../../models/datamapper/document'; +import { DocumentType } from '../../../models/datamapper/path'; +import { MetadataContext } from '../../../providers'; +import { DataMapperMetadataService } from '../../../services/datamapper-metadata.service'; +import { DocumentService } from '../../../services/document.service'; + +type AttachSchemaProps = { + documentType: DocumentType; + documentId: string; + hasSchema?: boolean; +}; + +export const AttachSchemaButton: FunctionComponent = ({ + documentType, + documentId, + hasSchema = false, +}) => { + const api = useContext(MetadataContext)!; + const { addAlert, setIsLoading, updateDocumentDefinition } = useDataMapper(); + const { clearNodeReferencesForDocument, reloadNodeReferences } = useCanvas(); + + const onClick = useCallback(async () => { + const paths = await DataMapperMetadataService.selectDocumentSchema(api); + if (!paths || (Array.isArray(paths) && paths.length === 0)) return; + setIsLoading(true); + try { + const definition = await DocumentService.createDocumentDefinition( + api, + documentType, + DocumentDefinitionType.XML_SCHEMA, + documentId, + Array.isArray(paths) ? paths : [paths], + ); + if (!definition) return; + await updateDocumentDefinition(definition); + clearNodeReferencesForDocument(documentType, documentId); + reloadNodeReferences(); + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (error: any) { + const cause = error['message'] ? ': ' + error['message'] : ''; + addAlert({ variant: AlertVariant.danger, title: `Cannot read the schema file ${cause}` }); + } finally { + setIsLoading(false); + } + }, [ + addAlert, + api, + clearNodeReferencesForDocument, + documentId, + documentType, + reloadNodeReferences, + setIsLoading, + updateDocumentDefinition, + ]); + + return ( + + ); +}; diff --git a/packages/ui/src/components/Document/actions/ConditionMenuAction.test.tsx b/packages/ui/src/components/Document/actions/ConditionMenuAction.test.tsx new file mode 100644 index 000000000..869baec08 --- /dev/null +++ b/packages/ui/src/components/Document/actions/ConditionMenuAction.test.tsx @@ -0,0 +1,182 @@ +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { BODY_DOCUMENT_ID } from '../../../models/datamapper/document'; +import { ChooseItem, FieldItem, MappingTree } from '../../../models/datamapper/mapping'; +import { DocumentType } from '../../../models/datamapper/path'; +import { MappingNodeData, TargetDocumentNodeData, TargetFieldNodeData } from '../../../models/datamapper/visualization'; +import { MappingService } from '../../../services/mapping.service'; +import { VisualizationService } from '../../../services/visualization.service'; +import { TestUtil } from '../../../stubs/data-mapper'; +import { ConditionMenuAction } from './ConditionMenuAction'; + +describe('ConditionMenuAction', () => { + const targetDoc = TestUtil.createTargetOrderDoc(); + const mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + const documentNodeData = new TargetDocumentNodeData(targetDoc, mappingTree); + + it('should apply ValueSelector', () => { + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0], + new FieldItem(mappingTree, targetDoc.fields[0]), + ); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(VisualizationService, 'applyValueSelector'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const selectorItem = screen.getByTestId('transformation-actions-selector'); + act(() => { + fireEvent.click(selectorItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should apply If', () => { + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0], + new FieldItem(mappingTree, targetDoc.fields[0]), + ); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(VisualizationService, 'applyIf'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const ifItem = screen.getByTestId('transformation-actions-if'); + act(() => { + fireEvent.click(ifItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should apply choose', () => { + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0], + new FieldItem(mappingTree, targetDoc.fields[0]), + ); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(VisualizationService, 'applyChooseWhenOtherwise'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const chooseItem = screen.getByTestId('transformation-actions-choose'); + act(() => { + fireEvent.click(chooseItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should apply when', () => { + const nodeData = new MappingNodeData(documentNodeData, new ChooseItem(mappingTree, targetDoc.fields[0])); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(MappingService, 'addWhen'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const whenItem = screen.getByTestId('transformation-actions-when'); + act(() => { + fireEvent.click(whenItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should apply otherwise', () => { + const nodeData = new MappingNodeData(documentNodeData, new ChooseItem(mappingTree, targetDoc.fields[0])); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(MappingService, 'addOtherwise'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const otherwiseItem = screen.getByTestId('transformation-actions-otherwise'); + act(() => { + fireEvent.click(otherwiseItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should apply for-each', () => { + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0].fields[3], + new FieldItem(mappingTree, targetDoc.fields[0].fields[3]), + ); + const onUpdateMock = jest.fn(); + const spyOnApply = jest.spyOn(VisualizationService, 'applyForEach'); + render(); + const actionToggle = screen.getByTestId('transformation-actions-menu-toggle'); + act(() => { + fireEvent.click(actionToggle); + }); + const foreachItem = screen.getByTestId('transformation-actions-foreach'); + act(() => { + fireEvent.click(foreachItem.getElementsByTagName('button')[0]); + }); + waitFor(() => screen.getByTestId('transformation-actions-menu-toggle').getAttribute('aria-expanded') === 'false'); + expect(onUpdateMock.mock.calls.length).toEqual(1); + expect(spyOnApply.mock.calls.length).toEqual(1); + }); + + it('should stop event propagation upon context menu toggle', () => { + const stopPropagationSpy = jest.fn(); + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0].fields[3], + new FieldItem(mappingTree, targetDoc.fields[0].fields[3]), + ); + + const wrapper = render( {}} />); + + act(() => { + const actionToggle = wrapper.getByTestId('transformation-actions-menu-toggle'); + fireEvent.click(actionToggle, { stopPropagation: stopPropagationSpy }); + }); + + waitFor(() => expect(stopPropagationSpy).toHaveBeenCalled()); + }); + + it('should stop event propagation upon selecting a menu option', () => { + const stopPropagationSpy = jest.fn(); + const nodeData = new TargetFieldNodeData( + documentNodeData, + targetDoc.fields[0].fields[3], + new FieldItem(mappingTree, targetDoc.fields[0].fields[3]), + ); + + const wrapper = render( {}} />); + + act(() => { + const actionToggle = wrapper.getByTestId('transformation-actions-menu-toggle'); + fireEvent.click(actionToggle, { stopPropagation: jest.fn() }); + }); + + act(() => { + const selectorOption = wrapper.getByTestId('transformation-actions-selector'); + fireEvent.click(selectorOption, { stopPropagation: stopPropagationSpy }); + }); + + waitFor(() => expect(stopPropagationSpy).toHaveBeenCalled()); + }); +}); diff --git a/packages/ui/src/components/Document/actions/ConditionMenuAction.tsx b/packages/ui/src/components/Document/actions/ConditionMenuAction.tsx new file mode 100644 index 000000000..43286e7c0 --- /dev/null +++ b/packages/ui/src/components/Document/actions/ConditionMenuAction.tsx @@ -0,0 +1,142 @@ +import { FunctionComponent, Ref, MouseEvent, useCallback, useState } from 'react'; +import { MappingNodeData, TargetFieldNodeData, TargetNodeData } from '../../../models/datamapper/visualization'; +import { VisualizationService } from '../../../services/visualization.service'; +import { + ActionListItem, + Dropdown, + DropdownItem, + DropdownList, + MenuToggle, + MenuToggleElement, +} from '@patternfly/react-core'; +import { EllipsisVIcon } from '@patternfly/react-icons'; +import { ChooseItem } from '../../../models/datamapper/mapping'; + +type ConditionMenuProps = { + nodeData: TargetNodeData; + onUpdate: () => void; +}; + +const DEFAULT_POPPER_PROPS = { + position: 'end', + preventOverflow: true, +} as const; + +export const ConditionMenuAction: FunctionComponent = ({ nodeData, onUpdate }) => { + const [isActionMenuOpen, setIsActionMenuOpen] = useState(false); + const allowIfChoose = VisualizationService.allowIfChoose(nodeData); + const allowForEach = VisualizationService.allowForEach(nodeData); + const isChooseNode = nodeData instanceof MappingNodeData && nodeData.mapping instanceof ChooseItem; + const otherwiseItem = isChooseNode && (nodeData.mapping as ChooseItem).otherwise; + const allowValueSelector = VisualizationService.allowValueSelector(nodeData); + const hasValueSelector = VisualizationService.hasValueSelector(nodeData); + const isValueSelectorNode = VisualizationService.isValueSelectorNode(nodeData); + + const onToggleActionMenu = useCallback( + (_event: MouseEvent | undefined) => { + setIsActionMenuOpen(!isActionMenuOpen); + }, + [isActionMenuOpen], + ); + + const onSelectAction = useCallback( + (event: MouseEvent | undefined, value: string | number | undefined) => { + event?.stopPropagation(); + switch (value) { + case 'selector': + VisualizationService.applyValueSelector(nodeData); + break; + case 'if': + VisualizationService.applyIf(nodeData); + break; + case 'choose': + VisualizationService.applyChooseWhenOtherwise(nodeData); + break; + case 'foreach': + VisualizationService.applyForEach(nodeData as TargetFieldNodeData); + break; + case 'when': + VisualizationService.applyWhen(nodeData); + break; + case 'otherwise': + VisualizationService.applyOtherwise(nodeData); + break; + } + onUpdate(); + setIsActionMenuOpen(false); + }, + [nodeData, onUpdate], + ); + + return ( + !isValueSelectorNode && ( + + ) => ( + + + + )} + isOpen={isActionMenuOpen} + onOpenChange={(isOpen: boolean) => setIsActionMenuOpen(isOpen)} + popperProps={DEFAULT_POPPER_PROPS} + zIndex={100} + > + + {allowValueSelector && ( + + Add selector expression + + )} + {isChooseNode ? ( + <> + + Add when + + + Add otherwise + + + ) : ( + <> + {allowForEach && ( + + Wrap with for-each + + )} + {allowIfChoose && ( + <> + + Wrap with if + + + Wrap with choose-when-otherwise + + + )} + + )} + + + + ) + ); +}; diff --git a/packages/ui/src/components/Document/actions/DeleteMappingItemAction.test.tsx b/packages/ui/src/components/Document/actions/DeleteMappingItemAction.test.tsx new file mode 100644 index 000000000..5612993f2 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DeleteMappingItemAction.test.tsx @@ -0,0 +1,43 @@ +import { DeleteMappingItemAction } from './DeleteMappingItemAction'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { MappingNodeData, TargetDocumentNodeData } from '../../../models/datamapper/visualization'; +import { MappingTree, ValueSelector } from '../../../models/datamapper/mapping'; +import { DocumentType } from '../../../models/datamapper/path'; +import { BODY_DOCUMENT_ID } from '../../../models/datamapper/document'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { TestUtil } from '../../../stubs/data-mapper'; + +describe('DeleteMappingItemAction', () => { + it('should invoke onDelete()', async () => { + const targetDoc = TestUtil.createTargetOrderDoc(); + const mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + const docData = new TargetDocumentNodeData(targetDoc, mappingTree); + const nodeData = new MappingNodeData(docData, new ValueSelector(mappingTree)); + const onDeleteMock = jest.fn(); + render( + + + + + , + ); + const deleteBtn = await screen.findByTestId('delete-mapping-btn'); + act(() => { + fireEvent.click(deleteBtn); + }); + const cancelBtn = screen.getByTestId('delete-mapping-cancel-btn'); + act(() => { + fireEvent.click(cancelBtn); + }); + expect(onDeleteMock.mock.calls.length).toBe(0); + act(() => { + fireEvent.click(deleteBtn); + }); + const confirmBtn = screen.getByTestId('delete-mapping-confirm-btn'); + act(() => { + fireEvent.click(confirmBtn); + }); + expect(onDeleteMock.mock.calls.length).toBe(1); + }); +}); diff --git a/packages/ui/src/components/Document/actions/DeleteMappingItemAction.tsx b/packages/ui/src/components/Document/actions/DeleteMappingItemAction.tsx new file mode 100644 index 000000000..e094bd1a8 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DeleteMappingItemAction.tsx @@ -0,0 +1,54 @@ +import { ActionListItem, Button, Modal, ModalVariant } from '@patternfly/react-core'; +import { TrashIcon } from '@patternfly/react-icons'; +import { FunctionComponent, useCallback } from 'react'; +import { useCanvas } from '../../../hooks/useCanvas'; +import { useToggle } from '../../../hooks/useToggle'; +import { ConditionItem } from '../../../models/datamapper/mapping'; +import { TargetNodeData } from '../../../models/datamapper/visualization'; +import { VisualizationService } from '../../../services/visualization.service'; + +type DeleteItemProps = { + nodeData: TargetNodeData; + onDelete: () => void; +}; + +export const DeleteMappingItemAction: FunctionComponent = ({ nodeData, onDelete }) => { + const { state: isModalOpen, toggleOn: openModal, toggleOff: closeModal } = useToggle(false); + const { clearNodeReferencesForPath, reloadNodeReferences } = useCanvas(); + + const onConfirmDelete = useCallback(() => { + if (nodeData.mapping && nodeData.mapping instanceof ConditionItem) { + clearNodeReferencesForPath(nodeData.mapping.nodePath.toString()); + reloadNodeReferences(); + } + VisualizationService.deleteMappingItem(nodeData); + onDelete(); + closeModal(); + }, [clearNodeReferencesForPath, closeModal, nodeData, onDelete, reloadNodeReferences]); + const title = `Delete ${nodeData.title} mapping`; + + return ( + + + + Confirm + , + , + ]} + > + {title}? + + + ); +}; diff --git a/packages/ui/src/components/Document/actions/DeleteParameterButton.test.tsx b/packages/ui/src/components/Document/actions/DeleteParameterButton.test.tsx new file mode 100644 index 000000000..63e636798 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DeleteParameterButton.test.tsx @@ -0,0 +1,44 @@ +import { FunctionComponent, PropsWithChildren, useEffect } from 'react'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { DeleteParameterButton } from './DeleteParameterButton'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { IDocument, PrimitiveDocument } from '../../../models/datamapper/document'; +import { DocumentType } from '../../../models/datamapper/path'; + +describe('DeleteParameterButton', () => { + it('should delete a parameter', async () => { + let parameterMap: Map; + const ParamTest: FunctionComponent = ({ children }) => { + const { sourceParameterMap } = useDataMapper(); + useEffect(() => { + sourceParameterMap.set('testparam1', new PrimitiveDocument(DocumentType.PARAM, 'testparam1')); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + parameterMap = sourceParameterMap; + }, [sourceParameterMap]); + return <>{children}; + }; + render( + + + + + + + , + ); + const deleteBtn = await screen.findByTestId('delete-parameter-testparam1-button'); + expect(parameterMap!.size).toEqual(1); + act(() => { + fireEvent.click(deleteBtn); + }); + const confirmBtn = screen.getByTestId('delete-parameter-modal-confirm-btn'); + act(() => { + fireEvent.click(confirmBtn); + }); + expect(parameterMap!.size).toEqual(0); + }); +}); diff --git a/packages/ui/src/components/Document/actions/DeleteParameterButton.tsx b/packages/ui/src/components/Document/actions/DeleteParameterButton.tsx new file mode 100644 index 000000000..4ae92dd40 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DeleteParameterButton.tsx @@ -0,0 +1,73 @@ +import { Button, Modal, ModalVariant } from '@patternfly/react-core'; +import { TrashIcon } from '@patternfly/react-icons'; +import { FunctionComponent, useCallback } from 'react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { useToggle } from '../../../hooks/useToggle'; +import { MappingService } from '../../../services/mapping.service'; + +import { useCanvas } from '../../../hooks/useCanvas'; +import { DocumentType } from '../../../models/datamapper/path'; + +type DeleteParameterProps = { + parameterName: string; +}; + +export const DeleteParameterButton: FunctionComponent = ({ parameterName }) => { + const { mappingTree, setMappingTree, deleteSourceParameter } = useDataMapper(); + const { clearNodeReferencesForDocument, reloadNodeReferences } = useCanvas(); + const { state: isModalOpen, toggleOn: openModal, toggleOff: closeModal } = useToggle(false); + + const onConfirmDelete = useCallback(() => { + const cleaned = MappingService.removeAllMappingsForDocument(mappingTree, DocumentType.PARAM, parameterName); + setMappingTree(cleaned); + deleteSourceParameter(parameterName); + clearNodeReferencesForDocument(DocumentType.PARAM, parameterName); + reloadNodeReferences(); + closeModal(); + }, [ + clearNodeReferencesForDocument, + closeModal, + deleteSourceParameter, + mappingTree, + parameterName, + reloadNodeReferences, + setMappingTree, + ]); + + return ( + <> + + + + Confirm + , + , + ]} + > + Delete parameter "{parameterName}"? Related mappings will be also removed. + + + ); +}; diff --git a/packages/ui/src/components/Document/actions/DetachSchemaButton.test.tsx b/packages/ui/src/components/Document/actions/DetachSchemaButton.test.tsx new file mode 100644 index 000000000..79d8316d4 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DetachSchemaButton.test.tsx @@ -0,0 +1,51 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { DetachSchemaButton } from './DetachSchemaButton'; +import { BODY_DOCUMENT_ID, IDocument, PrimitiveDocument } from '../../../models/datamapper/document'; +import { DocumentType } from '../../../models/datamapper/path'; +import { FunctionComponent, PropsWithChildren, useEffect } from 'react'; +import { useDataMapper } from '../../../hooks/useDataMapper'; + +import { TestUtil } from '../../../stubs/data-mapper'; + +describe('DetachSchemaButton', () => { + it('should detach the schema', async () => { + let sourceDoc: IDocument; + let setInitialDoc = true; + const DetachTest: FunctionComponent = ({ children }) => { + const { sourceBodyDocument, setSourceBodyDocument } = useDataMapper(); + useEffect(() => { + if (setInitialDoc) { + setSourceBodyDocument(TestUtil.createSourceOrderDoc()); + setInitialDoc = false; + } + }); + useEffect(() => { + sourceDoc = sourceBodyDocument; + }, [sourceBodyDocument]); + return
{children}
; + }; + render( + + + + + + + , + ); + const detachBtn = await screen.findByTestId('detach-schema-sourceBody-Body-button'); + expect(sourceDoc!.fields.length).toBe(1); + act(() => { + fireEvent.click(detachBtn); + }); + const confirmBtn = screen.getByTestId('detach-schema-modal-confirm-btn'); + act(() => { + fireEvent.click(confirmBtn); + }); + await screen.findByTestId('detachtest'); + expect(sourceDoc!.fields.length).toBe(0); + expect(sourceDoc! instanceof PrimitiveDocument); + }); +}); diff --git a/packages/ui/src/components/Document/actions/DetachSchemaButton.tsx b/packages/ui/src/components/Document/actions/DetachSchemaButton.tsx new file mode 100644 index 000000000..33b014481 --- /dev/null +++ b/packages/ui/src/components/Document/actions/DetachSchemaButton.tsx @@ -0,0 +1,87 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { Button, Modal, ModalVariant } from '@patternfly/react-core'; +import { FunctionComponent, useCallback } from 'react'; + +import { ExportIcon } from '@patternfly/react-icons'; +import { useCanvas } from '../../../hooks/useCanvas'; +import { useDataMapper } from '../../../hooks/useDataMapper'; +import { useToggle } from '../../../hooks/useToggle'; +import { DocumentDefinition, DocumentDefinitionType } from '../../../models/datamapper/document'; +import { DocumentType } from '../../../models/datamapper/path'; + +type DeleteSchemaProps = { + documentType: DocumentType; + documentId: string; +}; + +export const DetachSchemaButton: FunctionComponent = ({ documentType, documentId }) => { + const { updateDocumentDefinition } = useDataMapper(); + const { clearNodeReferencesForDocument, reloadNodeReferences } = useCanvas(); + const { state: isModalOpen, toggleOn: openModal, toggleOff: closeModal } = useToggle(false); + + const onConfirmDelete = useCallback(() => { + const definition = new DocumentDefinition(documentType, DocumentDefinitionType.Primitive, documentId); + updateDocumentDefinition(definition); + clearNodeReferencesForDocument(documentType, documentId); + reloadNodeReferences(); + closeModal(); + }, [ + documentType, + documentId, + updateDocumentDefinition, + clearNodeReferencesForDocument, + reloadNodeReferences, + closeModal, + ]); + + return ( + <> + + + + Confirm + , + , + ]} + > + Detach correlated schema and make it back to be a primitive value? Related mappings will be also removed. + + + ); +}; diff --git a/packages/ui/src/components/Document/actions/DocumentActions.test.tsx b/packages/ui/src/components/Document/actions/DocumentActions.test.tsx new file mode 100644 index 000000000..9ce632f0f --- /dev/null +++ b/packages/ui/src/components/Document/actions/DocumentActions.test.tsx @@ -0,0 +1,20 @@ +import { DocumentActions } from './DocumentActions'; +import { render, screen } from '@testing-library/react'; +import { DocumentNodeData } from '../../../models/datamapper/visualization'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { TestUtil } from '../../../stubs/data-mapper'; + +describe('DocumentActions', () => { + it('should render', async () => { + const docData = new DocumentNodeData(TestUtil.createSourceOrderDoc()); + render( + + + + + , + ); + expect(await screen.findByTestId('attach-schema-sourceBody-ShipOrder.xsd-button')); + }); +}); diff --git a/packages/ui/src/components/Document/actions/DocumentActions.tsx b/packages/ui/src/components/Document/actions/DocumentActions.tsx new file mode 100644 index 000000000..7a965073a --- /dev/null +++ b/packages/ui/src/components/Document/actions/DocumentActions.tsx @@ -0,0 +1,45 @@ +import { ActionListGroup, ActionListItem } from '@patternfly/react-core'; +import { AttachSchemaButton } from './AttachSchemaButton'; +import { DetachSchemaButton } from './DetachSchemaButton'; +import { DocumentType } from '../../../models/datamapper/path'; +import { DocumentNodeData } from '../../../models/datamapper/visualization'; +import { DeleteParameterButton } from './DeleteParameterButton'; +import { FunctionComponent, MouseEvent, useCallback } from 'react'; +import '../Document.scss'; + +type DocumentActionsProps = { + className?: string; + nodeData: DocumentNodeData; +}; + +export const DocumentActions: FunctionComponent = ({ className, nodeData }) => { + const documentType = nodeData.document.documentType; + const documentId = nodeData.document.documentId; + const handleStopPropagation = useCallback((event: MouseEvent) => { + event.stopPropagation(); + }, []); + + return ( + + + + + + + + {documentType === DocumentType.PARAM && ( + + + + )} + + ); +}; diff --git a/packages/ui/src/components/Document/actions/TargetNodeActions.test.tsx b/packages/ui/src/components/Document/actions/TargetNodeActions.test.tsx new file mode 100644 index 000000000..57704858a --- /dev/null +++ b/packages/ui/src/components/Document/actions/TargetNodeActions.test.tsx @@ -0,0 +1,35 @@ +import { TargetNodeActions } from './TargetNodeActions'; +import { render, screen } from '@testing-library/react'; +import { MappingNodeData, TargetDocumentNodeData } from '../../../models/datamapper/visualization'; +import { MappingTree, ValueSelector } from '../../../models/datamapper/mapping'; +import { DocumentType } from '../../../models/datamapper/path'; +import { BODY_DOCUMENT_ID } from '../../../models/datamapper/document'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { TestUtil } from '../../../stubs/data-mapper'; + +describe('TargetNodeActions', () => { + it('should render', async () => { + const targetDoc = TestUtil.createTargetOrderDoc(); + const tree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + const nodeData = new TargetDocumentNodeData(targetDoc, tree); + render(); + expect(await screen.findByTestId('transformation-actions-menu-toggle')).toBeTruthy(); + }); + + it('should render expression action', async () => { + const targetDoc = TestUtil.createTargetOrderDoc(); + const tree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + const docData = new TargetDocumentNodeData(targetDoc, tree); + const mappingData = new MappingNodeData(docData, new ValueSelector(tree)); + render( + + + + + , + ); + expect(await screen.findByTestId('transformation-xpath-input')).toBeTruthy(); + expect(screen.getByTestId(`edit-xpath-button-${mappingData.id}`)).toBeTruthy(); + }); +}); diff --git a/packages/ui/src/components/Document/actions/TargetNodeActions.tsx b/packages/ui/src/components/Document/actions/TargetNodeActions.tsx new file mode 100644 index 000000000..e41ec7939 --- /dev/null +++ b/packages/ui/src/components/Document/actions/TargetNodeActions.tsx @@ -0,0 +1,37 @@ +import { ActionListGroup } from '@patternfly/react-core'; +import { FunctionComponent, MouseEvent, KeyboardEvent, useCallback } from 'react'; +import { XPathInputAction } from './XPathInputAction'; +import { DeleteMappingItemAction } from './DeleteMappingItemAction'; +import { ConditionMenuAction } from './ConditionMenuAction'; +import { XPathEditorAction } from './XPathEditorAction'; +import { TargetNodeData } from '../../../models/datamapper/visualization'; +import { VisualizationService } from '../../../services/visualization.service'; +import '../Document.scss'; + +type TargetNodeActionsProps = { + className?: string; + nodeData: TargetNodeData; + onUpdate: () => void; +}; + +export const TargetNodeActions: FunctionComponent = ({ className, nodeData, onUpdate }) => { + const expressionItem = VisualizationService.getExpressionItemForNode(nodeData); + const allowConditionMenu = VisualizationService.allowConditionMenu(nodeData); + const isDeletable = VisualizationService.isDeletableNode(nodeData); + const handleStopPropagation = useCallback((event: MouseEvent | KeyboardEvent) => { + event.stopPropagation(); + }, []); + + return ( + + {expressionItem && ( + <> + + + + )} + {allowConditionMenu && } + {isDeletable && } + + ); +}; diff --git a/packages/ui/src/components/Document/actions/XPathEditorAction.test.tsx b/packages/ui/src/components/Document/actions/XPathEditorAction.test.tsx new file mode 100644 index 000000000..b4bc9f292 --- /dev/null +++ b/packages/ui/src/components/Document/actions/XPathEditorAction.test.tsx @@ -0,0 +1,39 @@ +import { XPathEditorAction } from './XPathEditorAction'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { MappingTree, ValueSelector } from '../../../models/datamapper/mapping'; +import { DocumentType } from '../../../models/datamapper/path'; +import { BODY_DOCUMENT_ID } from '../../../models/datamapper/document'; +import { TargetDocumentNodeData } from '../../../models/datamapper/visualization'; +import { DataMapperProvider } from '../../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../../providers/datamapper-canvas.provider'; +import { TestUtil } from '../../../stubs/data-mapper'; + +describe('XPathEditorAction', () => { + it('should open xpath editor modal', async () => { + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })); + const doc = TestUtil.createTargetOrderDoc(); + const tree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + const docData = new TargetDocumentNodeData(doc, tree); + render( + + + + + , + ); + const editBtn = await screen.findByTestId(`edit-xpath-button-${docData.id}`); + act(() => { + fireEvent.click(editBtn); + }); + const modal = await screen.findByTestId('xpath-editor-modal'); + expect(modal).toBeInTheDocument(); + const monaco = await screen.findByTestId('xpath-editor'); + expect(monaco).toBeInTheDocument(); + const textbox = await screen.findByRole('textbox'); + expect(textbox).toBeInTheDocument(); + }, 30000); +}); diff --git a/packages/ui/src/components/Document/actions/XPathEditorAction.tsx b/packages/ui/src/components/Document/actions/XPathEditorAction.tsx new file mode 100644 index 000000000..3be40d656 --- /dev/null +++ b/packages/ui/src/components/Document/actions/XPathEditorAction.tsx @@ -0,0 +1,43 @@ +import { TargetNodeData } from '../../../models/datamapper/visualization'; +import { ExpressionItem } from '../../../models/datamapper/mapping'; +import { FunctionComponent, useCallback, useState } from 'react'; +import { ActionListItem, Button } from '@patternfly/react-core'; +import { PencilAltIcon } from '@patternfly/react-icons'; +import { XPathEditorModal } from '../../XPath/XPathEditorModal'; +import { useCanvas } from '../../../hooks/useCanvas'; + +type XPathEditorProps = { + nodeData: TargetNodeData; + mapping: ExpressionItem; + onUpdate: () => void; +}; +export const XPathEditorAction: FunctionComponent = ({ nodeData, mapping, onUpdate }) => { + const { reloadNodeReferences } = useCanvas(); + const [isEditorOpen, setIsEditorOpen] = useState(false); + const launchXPathEditor = useCallback(() => setIsEditorOpen(true), []); + const closeXPathEditor = useCallback(() => { + setIsEditorOpen(false); + reloadNodeReferences(); + }, [reloadNodeReferences]); + + return ( + + + + + )} + + + ); +}; diff --git a/packages/ui/src/components/RenderingAnchor/RenderingAnchor.test.tsx b/packages/ui/src/components/RenderingAnchor/RenderingAnchor.test.tsx new file mode 100644 index 000000000..b5fcfe74e --- /dev/null +++ b/packages/ui/src/components/RenderingAnchor/RenderingAnchor.test.tsx @@ -0,0 +1,61 @@ +import { render } from '@testing-library/react'; +import { createVisualizationNode, IVisualizationNode } from '../../models'; +import { RenderingAnchor } from './RenderingAnchor'; +import { RenderingAnchorContext } from './rendering.provider'; +import { IRegisteredValidatedComponent, IRenderingAnchorContext } from './rendering.provider.model'; + +describe('RenderingAnchor', () => { + const anchorTag = 'example-anchor'; + const vizNode: IVisualizationNode = createVisualizationNode('example-node', {}); + let renderingAnchorContext: IRenderingAnchorContext; + + beforeEach(() => { + renderingAnchorContext = { + getRegisteredComponents: jest.fn().mockReturnValue([]), + registerComponent: jest.fn(), + }; + }); + + it('should not render the `RenderingAnchor` when the `vizNode` prop is undefined', () => { + const wrapper = render(); + + expect(wrapper.container).toBeEmptyDOMElement(); + }); + + it('should not render anything if there is no registered components for a given anchor', () => { + const wrapper = render( + + + , + ); + + expect(wrapper.container).toBeEmptyDOMElement(); + }); + + it('should query the registered components by `anchorTag` and `vizNode`', () => { + render( + + + , + ); + + expect(renderingAnchorContext.getRegisteredComponents).toHaveBeenCalledWith(anchorTag, vizNode); + }); + + it('should render the registered components', () => { + const registeredComponents: IRegisteredValidatedComponent[] = [ + { key: '1', Component: () =>

Component 1

}, + { key: '2', Component: () =>

Component 2

}, + ]; + renderingAnchorContext.getRegisteredComponents = jest.fn().mockReturnValue(registeredComponents); + + const wrapper = render( + + + , + ); + + expect(wrapper.getByText('Component 1')).toBeInTheDocument(); + expect(wrapper.getByText('Component 2')).toBeInTheDocument(); + }); +}); diff --git a/packages/ui/src/components/RenderingAnchor/RenderingAnchor.tsx b/packages/ui/src/components/RenderingAnchor/RenderingAnchor.tsx new file mode 100644 index 000000000..3a84cc3f5 --- /dev/null +++ b/packages/ui/src/components/RenderingAnchor/RenderingAnchor.tsx @@ -0,0 +1,21 @@ +import { FunctionComponent, useContext } from 'react'; +import { IVisualizationNode } from '../../models'; +import { isDefined } from '../../utils'; +import { RenderingAnchorContext } from './rendering.provider'; + +interface IRenderingAnchor { + anchorTag: string; + vizNode: IVisualizationNode | undefined; +} + +export const RenderingAnchor: FunctionComponent = ({ anchorTag, vizNode }) => { + const renderingAnchorContext = useContext(RenderingAnchorContext); + + if (!isDefined(vizNode)) { + return null; + } + + const registeredChildren = renderingAnchorContext.getRegisteredComponents(anchorTag, vizNode); + + return registeredChildren.map(({ key, Component }) => ); +}; diff --git a/packages/ui/src/components/RenderingAnchor/rendering.provider.model.ts b/packages/ui/src/components/RenderingAnchor/rendering.provider.model.ts new file mode 100644 index 000000000..668fb3b75 --- /dev/null +++ b/packages/ui/src/components/RenderingAnchor/rendering.provider.model.ts @@ -0,0 +1,56 @@ +import { FunctionComponent } from 'react'; +import { IVisualizationNode } from '../../models'; + +export interface IRegisteredComponent { + anchor: string; + activationFn: (vizNode: IVisualizationNode) => boolean; + component: FunctionComponent<{ vizNode?: IVisualizationNode }>; +} + +export interface IRegisteredValidatedComponent { + key: string; + Component: IRegisteredComponent['component']; +} + +export interface IRenderingAnchorContext { + /** + * Register a component to be rendered in the given anchor + * + * @example + * ```tsx + * const renderingAnchorContext = useContext(RenderingAnchorContext); + * + * renderingAnchorContext.registerComponent({ + * anchor: 'form-header', + * activationFn: () => true, + * component: ({ vizNode }) =>

{vizNode.getId()}

, + * }); + * ``` + * @param props Registered component definition + * @returns void + */ + registerComponent: (props: IRegisteredComponent) => void; + + /** + * Get components registered to the given anchor and pass the validation function + * + * @example + * ```tsx + * const renderingAnchorContext = useContext(RenderingAnchorContext); + * + * const components = renderingAnchorContext.getRegisteredComponents('form-header', vizNode); + * + * return ( + *
+ * {components.map(({ key, Component }) => ( + * + * ))} + *
+ * ); + * ``` + * @param anchorTag The anchor tag to register the component to + * @param vizNode The visualization node to pass to the component + * @returns `IRegisteredValidatedComponent[]` An array of registered and validated components + */ + getRegisteredComponents: (anchorTag: string, vizNode: IVisualizationNode) => IRegisteredValidatedComponent[]; +} diff --git a/packages/ui/src/components/RenderingAnchor/rendering.provider.test.tsx b/packages/ui/src/components/RenderingAnchor/rendering.provider.test.tsx new file mode 100644 index 000000000..d6c0c26d8 --- /dev/null +++ b/packages/ui/src/components/RenderingAnchor/rendering.provider.test.tsx @@ -0,0 +1,118 @@ +import { render } from '@testing-library/react'; +import { useContext } from 'react'; +import { IVisualizationNode } from '../../models/visualization/base-visual-entity'; +import { createVisualizationNode } from '../../models/visualization/visualization-node'; +import { RenderingAnchorContext, RenderingProvider } from './rendering.provider'; +import { IRegisteredComponent } from './rendering.provider.model'; + +describe('RenderingProvider', () => { + const anchorExample = 'form-header'; + const vizNode: IVisualizationNode = createVisualizationNode('example-node', {}); + + describe('RenderingAnchorContext', () => { + it('should provide a default implementation', () => { + expect(() => { + render(); + }).not.toThrow(); + }); + }); + + it('should render the child component', () => { + const { getByText } = render( + +

Child component

+
, + ); + + expect(getByText('Child component')).toBeInTheDocument(); + }); + + it('should allow consumers to register and render components', () => { + const { getByText } = render( + + true, + component: () =>

Registered component

, + }, + ]} + /> +
, + ); + + expect(getByText('Registered component')).toBeInTheDocument(); + }); + + it('should filter components by anchorTag', async () => { + const { getByText, queryByText } = render( + + true, + component: () =>

Registered component

, + }, + { + anchor: 'another-anchor', + activationFn: () => true, + component: () =>

Another component

, + }, + ]} + /> +
, + ); + + expect(getByText('Registered component')).toBeInTheDocument(); + + const anotherComponent = queryByText('Another component'); + expect(anotherComponent).toBeNull(); + }); + + it('should filter components by activationFn', async () => { + const { getByText, queryByText } = render( + + true, + component: () =>

Registered component

, + }, + { + anchor: anchorExample, + activationFn: () => false, + component: () =>

Another component

, + }, + ]} + /> +
, + ); + + expect(getByText('Registered component')).toBeInTheDocument(); + + const anotherComponent = queryByText('Another component'); + expect(anotherComponent).toBeNull(); + }); + + function ProviderConsumer(props: { anchorTag: string; registerComponents: IRegisteredComponent[] }) { + const { registerComponent, getRegisteredComponents } = useContext(RenderingAnchorContext); + + props.registerComponents.forEach((regComponent) => registerComponent(regComponent)); + + const components = getRegisteredComponents('form-header', vizNode); + + return ( +
+ {components.map(({ key, Component }) => ( + + ))} +
+ ); + } +}); diff --git a/packages/ui/src/components/RenderingAnchor/rendering.provider.tsx b/packages/ui/src/components/RenderingAnchor/rendering.provider.tsx new file mode 100644 index 000000000..1ce101b44 --- /dev/null +++ b/packages/ui/src/components/RenderingAnchor/rendering.provider.tsx @@ -0,0 +1,44 @@ +import { createContext, FunctionComponent, PropsWithChildren, Suspense, useCallback, useMemo, useRef } from 'react'; +import { getCamelRandomId } from '../../camel-utils/camel-random-id'; +import { IVisualizationNode } from '../../models'; +import { Loading } from '../Loading'; +import { IRegisteredComponent, IRenderingAnchorContext } from './rendering.provider.model'; + +export const RenderingAnchorContext = createContext({ + registerComponent: () => {}, + getRegisteredComponents: () => [], +}); + +export const RenderingProvider: FunctionComponent = ({ children }) => { + const registeredComponents = useRef<({ key: string } & IRegisteredComponent)[]>([]); + + const registerComponent = useCallback((props: IRegisteredComponent) => { + const key = getCamelRandomId(props.anchor, 6); + registeredComponents.current.push({ key, ...props }); + }, []); + + const getRegisteredComponents = useCallback((anchorTag: string, vizNode: IVisualizationNode) => { + return registeredComponents.current + .filter( + (registeredComponent) => registeredComponent.anchor === anchorTag && registeredComponent.activationFn(vizNode), + ) + .map(({ key, component }) => ({ key, Component: component })); + }, []); + + const value = useMemo( + () => ({ registerComponent, getRegisteredComponents }), + [getRegisteredComponents, registerComponent], + ); + + return ( + + Loading dynamic components... +
+ } + > + {children} + + ); +}; diff --git a/packages/ui/src/components/View/MappingLink.tsx b/packages/ui/src/components/View/MappingLink.tsx new file mode 100644 index 000000000..c59b45d6e --- /dev/null +++ b/packages/ui/src/components/View/MappingLink.tsx @@ -0,0 +1,196 @@ +import { + CSSProperties, + FunctionComponent, + MutableRefObject, + RefObject, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { useCanvas } from '../../hooks/useCanvas'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { NodeReference } from '../../providers/datamapper-canvas.provider'; +import { MappingService } from '../../services/mapping.service'; +import { Circle, LinePath } from '@visx/shape'; +import { curveMonotoneX } from '@visx/curve'; + +type LineCoord = { + x1: number; + y1: number; + x2: number; + y2: number; +}; + +type LineProps = LineCoord & { + sourceNodePath: string; + targetNodePath: string; + svgRef?: RefObject; +}; + +const MappingLink: FunctionComponent = ({ x1, y1, x2, y2, sourceNodePath, targetNodePath, svgRef }) => { + const { mappingLinkCanvasRef } = useCanvas(); + const [isOver, setIsOver] = useState(false); + const lineStyle = { + stroke: 'gray', + strokeWidth: isOver ? 6 : 3, + pointerEvents: 'auto' as CSSProperties['pointerEvents'], + }; + const dotRadius = isOver ? 6 : 3; + const svgRect = svgRef?.current?.getBoundingClientRect(); + const canvasRect = mappingLinkCanvasRef?.current?.getBoundingClientRect(); + const canvasLeft = canvasRect ? canvasRect.left - (svgRect ? svgRect.left : 0) : undefined; + const canvasRight = canvasRect ? canvasRect.right - (svgRect ? svgRect.left : 0) : undefined; + + const onMouseEnter = useCallback(() => { + setIsOver(true); + }, []); + + const onMouseLeave = useCallback(() => { + setIsOver(false); + }, []); + + return ( + <> + + + data={[ + [x1, y1], + [canvasLeft ? canvasLeft : x1, y1], + [canvasRight ? canvasRight : x2, y2], + [x2, y2], + ]} + x={(d) => d[0]} + y={(d) => d[1]} + curve={curveMonotoneX} + style={lineStyle} + onClick={() => {}} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + data-testid={`mapping-link-${x1}-${y1}-${x2}-${y2}`} + xlinkTitle={`Source: ${sourceNodePath}, Target: ${targetNodePath}`} + /> + + + ); +}; + +export const MappingLinksContainer: FunctionComponent = () => { + const { mappingTree, sourceBodyDocument, sourceParameterMap } = useDataMapper(); + const [lineCoordList, setLineCoordList] = useState([]); + const { getNodeReference } = useCanvas(); + const svgRef = useRef(null); + + const getCoordFromFieldRef = useCallback( + (sourceRef: MutableRefObject, targetRef: MutableRefObject) => { + const svgRect = svgRef.current?.getBoundingClientRect(); + const sourceRect = sourceRef.current?.headerRef?.getBoundingClientRect(); + const targetRect = targetRef.current?.headerRef?.getBoundingClientRect(); + if (!sourceRect || !targetRect) { + return; + } + + return { + x1: sourceRect.right - (svgRect ? svgRect.left : 0), + y1: sourceRect.top + (sourceRect.bottom - sourceRect.top) / 2 - (svgRect ? svgRect.top : 0), + x2: targetRect.left - (svgRect ? svgRect.left : 0), + y2: targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (svgRect ? svgRect.top : 0), + }; + }, + [], + ); + + const getParentPath = useCallback((path: string) => { + if (path.endsWith('://')) return path.substring(0, path.indexOf(':')); + + const lastSeparatorIndex = path.lastIndexOf('/'); + const endIndex = + lastSeparatorIndex !== -1 && path.charAt(lastSeparatorIndex - 1) === '/' + ? lastSeparatorIndex + 1 + : lastSeparatorIndex; + return endIndex !== -1 ? path.substring(0, endIndex) : null; + }, []); + + const getClosestExpandedPath = useCallback( + (path: string) => { + let tracedPath: string | null = path; + while ( + !!tracedPath && + (getNodeReference(tracedPath)?.current == null || + getNodeReference(tracedPath)?.current.headerRef == null || + getNodeReference(tracedPath)?.current.headerRef?.getClientRects().length === 0) + ) { + const parentPath = getParentPath(tracedPath); + if (parentPath === tracedPath) break; + tracedPath = parentPath; + } + return tracedPath; + }, + [getNodeReference, getParentPath], + ); + + const refreshLinks = useCallback(() => { + const links = MappingService.extractMappingLinks(mappingTree, sourceParameterMap, sourceBodyDocument); + const answer: LineProps[] = links.reduce((acc, { sourceNodePath, targetNodePath }) => { + const sourceClosestPath = getClosestExpandedPath(sourceNodePath); + const targetClosestPath = getClosestExpandedPath(targetNodePath); + if (sourceClosestPath && targetClosestPath) { + const sourceFieldRef = getNodeReference(sourceClosestPath); + const targetFieldRef = getNodeReference(targetClosestPath); + if (sourceFieldRef && !!targetFieldRef) { + const coord = getCoordFromFieldRef(sourceFieldRef, targetFieldRef); + if (coord) acc.push({ ...coord, sourceNodePath: sourceNodePath, targetNodePath: targetNodePath }); + } + } + return acc; + }, [] as LineProps[]); + setLineCoordList(answer); + }, [ + mappingTree, + sourceParameterMap, + sourceBodyDocument, + getClosestExpandedPath, + getNodeReference, + getCoordFromFieldRef, + ]); + + useEffect(() => { + refreshLinks(); + window.addEventListener('resize', refreshLinks); + window.addEventListener('scroll', refreshLinks); + return () => { + window.removeEventListener('resize', refreshLinks); + window.removeEventListener('scroll', refreshLinks); + }; + }, [refreshLinks]); + + return ( + + + {lineCoordList.map((lineProps, index) => ( + + ))} + + + ); +}; diff --git a/packages/ui/src/components/View/SourcePanel.test.tsx b/packages/ui/src/components/View/SourcePanel.test.tsx new file mode 100644 index 000000000..f435d4558 --- /dev/null +++ b/packages/ui/src/components/View/SourcePanel.test.tsx @@ -0,0 +1,31 @@ +import { DataMapperProvider } from '../../providers/datamapper.provider'; +import { SourcePanel } from './SourcePanel'; +import { render, screen } from '@testing-library/react'; +import { DataMapperCanvasProvider } from '../../providers/datamapper-canvas.provider'; + +describe('SourcePanel', () => { + it('should render action buttons by default', () => { + render( + + + + + , + ); + expect(screen.getByTestId('add-parameter-button')).toBeInTheDocument(); + expect(screen.getByTestId('attach-schema-sourceBody-Body-button')).toBeInTheDocument(); + expect(screen.getByTestId('detach-schema-sourceBody-Body-button')).toBeInTheDocument(); + }); + it('should not render action buttons if isReadOnly=true', () => { + render( + + + + + , + ); + expect(screen.queryByTestId('add-parameter-button')).toBeFalsy(); + expect(screen.queryByTestId('attach-schema-sourceBody-Body-button')).toBeFalsy(); + expect(screen.queryByTestId('detach-schema-sourceBody-Body-button')).toBeFalsy(); + }); +}); diff --git a/packages/ui/src/components/View/SourcePanel.tsx b/packages/ui/src/components/View/SourcePanel.tsx new file mode 100644 index 000000000..9ce10180c --- /dev/null +++ b/packages/ui/src/components/View/SourcePanel.tsx @@ -0,0 +1,55 @@ +import { FunctionComponent } from 'react'; +import { + Divider, + Panel, + PanelHeader, + PanelMain, + Stack, + StackItem, + Text, + TextContent, + TextVariants, + Truncate, +} from '@patternfly/react-core'; +import { Parameters } from '../Document/Parameters'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { useCanvas } from '../../hooks/useCanvas'; +import { SourceDocument } from '../Document/SourceDocument'; +import './SourceTargetView.scss'; + +type SourcePanelProps = { + isReadOnly?: boolean; +}; + +export const SourcePanel: FunctionComponent = ({ isReadOnly = false }) => { + const { sourceBodyDocument } = useDataMapper(); + const { reloadNodeReferences } = useCanvas(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/ui/src/components/View/SourceTargetView.scss b/packages/ui/src/components/View/SourceTargetView.scss new file mode 100644 index 000000000..e50918f50 --- /dev/null +++ b/packages/ui/src/components/View/SourceTargetView.scss @@ -0,0 +1,50 @@ +.source-target-view { + height: 100%; + overflow-x: auto; + + &__source-split { + width: 30%; + min-width: 300px; + } + + &__source-panel { + height: 100%; + } + + &__source-panel-main { + height: 100%; + max-height: 100%; + overflow: clip auto; + } + + &__line-blank { + width: 10%; + min-width: 1px; + overflow-x: clip; + } + + &__target-split { + width: 60%; + min-width: 300px; + } + + &__target-panel { + display: flex; + flex-flow: column; + height: 100%; + min-width: 400px; + } + + &__target-panel-main { + height: 100%; + max-height: 100%; + } + + &__truncate { + --pf-v5-c-truncate--MinWidth: 0; + } + + &__divider { + padding-bottom: 0.5rem; + } +} diff --git a/packages/ui/src/components/View/SourceTargetView.test.tsx b/packages/ui/src/components/View/SourceTargetView.test.tsx new file mode 100644 index 000000000..466b35b40 --- /dev/null +++ b/packages/ui/src/components/View/SourceTargetView.test.tsx @@ -0,0 +1,85 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { DataMapperProvider } from '../../providers/datamapper.provider'; +import { DataMapperCanvasProvider } from '../../providers/datamapper-canvas.provider'; +import { SourceTargetView } from './SourceTargetView'; + +import { shipOrderXsd } from '../../stubs/data-mapper'; +import { BrowserFilePickerMetadataProvider } from '../../stubs/BrowserFilePickerMetadataProvider'; + +describe('SourceTargetView', () => { + describe('Source Body Document', () => { + it('should attach and detach schema', async () => { + render( + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-sourceBody-Body-button'); + act(() => { + fireEvent.click(attachButton); + }); + const fileContent = new File([new Blob([shipOrderXsd])], 'ShipOrder.xsd', { type: 'text/plain' }); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = screen.getByTestId('attach-schema-file-input'); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + const shipTo = await screen.findByText('ShipTo'); + expect(shipTo).toBeTruthy(); + const detachButton = screen.getByTestId('detach-schema-sourceBody-Body-button'); + act(() => { + fireEvent.click(detachButton); + }); + const detachConfirmButton = screen.getByTestId('detach-schema-modal-confirm-btn'); + act(() => { + fireEvent.click(detachConfirmButton); + }); + await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(screen.queryByTestId('ShipTo')).toBeFalsy(); + }); + }); + + describe('Target Body Document', () => { + it('should attach and detach schema', async () => { + render( + + + + + + + , + ); + const attachButton = await screen.findByTestId('attach-schema-targetBody-Body-button'); + act(() => { + fireEvent.click(attachButton); + }); + const fileContent = new File([new Blob([shipOrderXsd])], 'ShipOrder.xsd', { type: 'text/plain' }); + act(() => { + fireEvent.click(attachButton); + }); + const fileInput = screen.getByTestId('attach-schema-file-input'); + act(() => { + fireEvent.change(fileInput, { target: { files: { item: () => fileContent, length: 1, 0: fileContent } } }); + }); + const shipTo = await screen.findByText('ShipTo'); + expect(shipTo).toBeTruthy(); + const detachButton = screen.getByTestId('detach-schema-targetBody-Body-button'); + act(() => { + fireEvent.click(detachButton); + }); + const detachConfirmButton = screen.getByTestId('detach-schema-modal-confirm-btn'); + act(() => { + fireEvent.click(detachConfirmButton); + }); + await screen.findByTestId('attach-schema-sourceBody-Body-button'); + expect(screen.queryByTestId('ShipTo')).toBeFalsy(); + }); + }); +}); diff --git a/packages/ui/src/components/View/SourceTargetView.tsx b/packages/ui/src/components/View/SourceTargetView.tsx new file mode 100644 index 000000000..bbfc50dfc --- /dev/null +++ b/packages/ui/src/components/View/SourceTargetView.tsx @@ -0,0 +1,66 @@ +import { + Divider, + Panel, + PanelHeader, + PanelMain, + Split, + SplitItem, + Stack, + StackItem, + Text, + TextContent, + TextVariants, + Truncate, +} from '@patternfly/react-core'; +import { FunctionComponent, useEffect, useRef } from 'react'; +import { useDataMapper } from '../../hooks/useDataMapper'; +import { MappingLinksContainer } from './MappingLink'; +import './SourceTargetView.scss'; +import { useCanvas } from '../../hooks/useCanvas'; +import { SourcePanel } from './SourcePanel'; +import { SourceTargetDnDHandler } from '../../providers/dnd/SourceTargetDnDHandler'; +import { TargetDocument } from '../Document/TargetDocument'; + +export const SourceTargetView: FunctionComponent = () => { + const { targetBodyDocument } = useDataMapper(); + const { reloadNodeReferences, setDefaultHandler, setMappingLinkCanvasRef } = useCanvas(); + const mappingLinkCanvasRef = useRef(null); + setMappingLinkCanvasRef(mappingLinkCanvasRef); + + useEffect(() => { + setDefaultHandler(new SourceTargetDnDHandler()); + }, [setDefaultHandler]); + + return ( + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx index 1bb12d4f3..1b2c2cdb8 100644 --- a/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx +++ b/packages/ui/src/components/Visualization/Canvas/Form/CanvasForm.tsx @@ -2,6 +2,8 @@ import { Card, CardBody, CardHeader } from '@patternfly/react-core'; import { FunctionComponent, useCallback, useContext, useEffect, useRef } from 'react'; import { VisibleFlowsContext } from '../../../../providers'; import { ErrorBoundary } from '../../../ErrorBoundary'; +import { Anchors } from '../../../registers/anchors'; +import { RenderingAnchor } from '../../../RenderingAnchor/RenderingAnchor'; import { CanvasNode } from '../canvas.models'; import './CanvasForm.scss'; import { CanvasFormBody } from './CanvasFormBody'; @@ -37,6 +39,7 @@ export const CanvasForm: FunctionComponent = ({ selectedNode, o + diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx index 83270ecab..8e2cb2fef 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx @@ -5,6 +5,8 @@ import { ActionConfirmationModalContext, } from '../../../../providers/action-confirmation-modal.provider'; import { ItemDeleteGroup } from './ItemDeleteGroup'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; import { EntityType } from '../../../../models/camel/entities'; import { TestProvidersWrapper } from '../../../../stubs'; import { CamelRouteResource } from '../../../../models/camel/camel-route-resource'; @@ -72,4 +74,32 @@ describe('ItemDeleteGroup', () => { expect(removeEntitySpy).toHaveBeenCalledWith(entityId); }); }); + + it('should process addon when deleting', async () => { + const mockDeleteModalContext = { + actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM), + }; + const mockAddon = jest.fn(); + const mockNodeInteractionAddonContext = { + registerInteractionAddon: jest.fn(), + getRegisteredInteractionAddons: (_interaction: IInteractionAddonType, _vizNode: IVisualizationNode) => [ + { type: IInteractionAddonType.ON_DELETE, activationFn: () => true, callback: mockAddon }, + ], + }; + + const wrapper = render( + + + + + , + ); + act(() => { + fireEvent.click(wrapper.getByText('Delete')); + }); + + await waitFor(() => { + expect(mockAddon).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx index 8e4b5993c..b12a27fb3 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx @@ -1,10 +1,12 @@ -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { createVisualizationNode, IVisualizationNode } from '../../../../models'; import { ItemDeleteStep } from './ItemDeleteStep'; import { ACTION_ID_CONFIRM, ActionConfirmationModalContext, } from '../../../../providers/action-confirmation-modal.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; describe('ItemDeleteStep', () => { let vizNode: IVisualizationNode; @@ -58,4 +60,30 @@ describe('ItemDeleteStep', () => { expect(removeChildSpy).toHaveBeenCalled(); }); }); + + it('should process addon when deleting', async () => { + const mockDeleteModalContext = { + actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM), + }; + const mockAddon = jest.fn(); + const mockNodeInteractionAddonContext = { + registerInteractionAddon: jest.fn(), + getRegisteredInteractionAddons: (_interaction: IInteractionAddonType, _vizNode: IVisualizationNode) => [ + { type: IInteractionAddonType.ON_DELETE, activationFn: () => true, callback: mockAddon }, + ], + }; + const wrapper = render( + + + + + , + ); + act(() => { + fireEvent.click(wrapper.getByText('Delete')); + }); + await waitFor(() => { + expect(mockAddon).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx index 4fd40cb9e..3fb570d7b 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx @@ -1,7 +1,13 @@ -import { fireEvent, render } from '@testing-library/react'; -import { createVisualizationNode } from '../../../../models'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { createVisualizationNode, DefinedComponent, IVisualizationNode } from '../../../../models'; import { CamelRouteResource } from '../../../../models/camel/camel-route-resource'; -import { ActionConfirmationModalContext } from '../../../../providers/action-confirmation-modal.provider'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; +import { CatalogModalContext } from '../../../../providers'; +import { + ACTION_ID_CONFIRM, + ActionConfirmationModalContext, +} from '../../../../providers/action-confirmation-modal.provider'; import { EntitiesContext } from '../../../../providers/entities.provider'; import { ItemReplaceStep } from './ItemReplaceStep'; @@ -52,4 +58,38 @@ describe('ItemReplaceStep', () => { text: 'Step and its children will be lost.', }); }); + + it('should process addon when replacing', async () => { + const mockCatalogModalContext = { + setIsModalOpen: jest.fn(), + getNewComponent: () => Promise.resolve({} as DefinedComponent), + }; + const mockReplaceModalContext = { + actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM), + }; + const mockAddon = jest.fn(); + const mockNodeInteractionAddonContext = { + registerInteractionAddon: jest.fn(), + getRegisteredInteractionAddons: (_interaction: IInteractionAddonType, _vizNode: IVisualizationNode) => [ + { type: IInteractionAddonType.ON_DELETE, activationFn: () => true, callback: mockAddon }, + ], + }; + const wrapper = render( + + + + + + + + + , + ); + act(() => { + fireEvent.click(wrapper.getByText('Replace')); + }); + await waitFor(() => { + expect(mockAddon).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.test.ts b/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.test.ts new file mode 100644 index 000000000..b122f4f70 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.test.ts @@ -0,0 +1,26 @@ +import { processNodeInteractionAddonRecursively } from './item-delete-helper'; +import { createVisualizationNode } from '../../../../models'; +import { + IInteractionAddonType, + IRegisteredInteractionAddon, +} from '../../../registers/interactions/node-interaction-addon.model'; +import { ACTION_ID_CONFIRM } from '../../../../providers'; + +describe('item-delete-helper', () => { + describe('processNodeInteractionAddonRecursively', () => { + it('should process children', () => { + const addons: Record = {}; + const vizNode = createVisualizationNode('test', {}); + const childVn = createVisualizationNode('child', {}); + const mockAddon: IRegisteredInteractionAddon = { + type: IInteractionAddonType.ON_DELETE, + activationFn: () => true, + callback: jest.fn(), + }; + addons[childVn.id] = [mockAddon]; + vizNode.addChild(childVn); + processNodeInteractionAddonRecursively(vizNode, ACTION_ID_CONFIRM, (vn) => addons[vn.id] ?? []); + expect(mockAddon.callback).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.ts b/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.ts new file mode 100644 index 000000000..72149a567 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/item-delete-helper.ts @@ -0,0 +1,39 @@ +import { IVisualizationNode } from '../../../../models'; +import { + IModalCustomization, + IRegisteredInteractionAddon, +} from '../../../registers/interactions/node-interaction-addon.model'; + +export const processNodeInteractionAddonRecursively = ( + parentVizNode: IVisualizationNode, + modalAnswer: string | undefined, + getAddons: (vizNode: IVisualizationNode) => IRegisteredInteractionAddon[], +) => { + parentVizNode.getChildren()?.forEach((child) => { + processNodeInteractionAddonRecursively(child, modalAnswer, getAddons); + }); + getAddons(parentVizNode).forEach((addon) => { + addon.callback(parentVizNode, modalAnswer); + }); +}; + +export const findModalCustomizationRecursively = ( + parentVizNode: IVisualizationNode, + getAddons: (vizNode: IVisualizationNode) => IRegisteredInteractionAddon[], +) => { + const modalCustomizations: IModalCustomization[] = []; + // going breadth-first while addon processes depth-first... do we want? + getAddons(parentVizNode).forEach((addon) => { + if (addon.modalCustomization && !modalCustomizations.includes(addon.modalCustomization)) { + modalCustomizations.push(addon.modalCustomization); + } + }); + parentVizNode.getChildren()?.forEach((child) => { + findModalCustomizationRecursively(child, getAddons).forEach((custom) => { + if (!modalCustomizations.includes(custom)) { + modalCustomizations.push(custom); + } + }); + }); + return modalCustomizations; +}; diff --git a/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx b/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx index 2df9aa4c8..c86152a2f 100644 --- a/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx +++ b/packages/ui/src/components/Visualization/Custom/hooks/delete-group.hook.tsx @@ -1,25 +1,44 @@ import { useCallback, useContext, useMemo } from 'react'; import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity'; -import { ACTION_ID_CONFIRM, ActionConfirmationModalContext } from '../../../../providers'; +import { ACTION_ID_CANCEL, ActionConfirmationModalContext } from '../../../../providers'; import { EntitiesContext } from '../../../../providers/entities.provider'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; +import { + findModalCustomizationRecursively, + processNodeInteractionAddonRecursively, +} from '../ContextMenu/item-delete-helper'; export const useDeleteGroup = (vizNode: IVisualizationNode) => { const entitiesContext = useContext(EntitiesContext); const deleteModalContext = useContext(ActionConfirmationModalContext); const flowId = vizNode?.getId(); + const { getRegisteredInteractionAddons } = useContext(NodeInteractionAddonContext); + const onDeleteGroup = useCallback(async () => { + const modalCustoms = findModalCustomizationRecursively(vizNode, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); + const additionalModalText = modalCustoms.length > 0 ? modalCustoms[0].additionalText : undefined; + const buttonOptions = modalCustoms.length > 0 ? modalCustoms[0].buttonOptions : undefined; /** Open delete confirm modal, get the confirmation */ - const isDeleteConfirmed = await deleteModalContext?.actionConfirmation({ + const modalAnswer = await deleteModalContext?.actionConfirmation({ title: "Do you want to delete the '" + vizNode.getId() + "' " + vizNode.getTitle() + '?', text: 'All steps will be lost.', + additionalModalText, + buttonOptions, }); - if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return; + if (!modalAnswer || modalAnswer === ACTION_ID_CANCEL) return; + + processNodeInteractionAddonRecursively(vizNode, modalAnswer, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); entitiesContext?.camelResource.removeEntity(flowId); entitiesContext?.updateEntitiesFromCamelResource(); - }, [deleteModalContext, entitiesContext, flowId]); + }, [deleteModalContext, entitiesContext, flowId, getRegisteredInteractionAddons, vizNode]); const value = useMemo( () => ({ diff --git a/packages/ui/src/components/Visualization/Custom/hooks/delete-step.hook.tsx b/packages/ui/src/components/Visualization/Custom/hooks/delete-step.hook.tsx index e5b9434fa..71074047e 100644 --- a/packages/ui/src/components/Visualization/Custom/hooks/delete-step.hook.tsx +++ b/packages/ui/src/components/Visualization/Custom/hooks/delete-step.hook.tsx @@ -1,28 +1,48 @@ import { useCallback, useContext, useMemo } from 'react'; import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity'; -import { ACTION_ID_CONFIRM, ActionConfirmationModalContext } from '../../../../providers'; +import { ACTION_ID_CANCEL, ACTION_ID_CONFIRM, ActionConfirmationModalContext } from '../../../../providers'; import { EntitiesContext } from '../../../../providers/entities.provider'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; +import { + findModalCustomizationRecursively, + processNodeInteractionAddonRecursively, +} from '../ContextMenu/item-delete-helper'; export const useDeleteStep = (vizNode: IVisualizationNode) => { const entitiesContext = useContext(EntitiesContext); const deleteModalContext = useContext(ActionConfirmationModalContext); const childrenNodes = vizNode.getChildren(); const hasChildren = childrenNodes !== undefined && childrenNodes.length > 0; + const { getRegisteredInteractionAddons } = useContext(NodeInteractionAddonContext); const onDeleteStep = useCallback(async () => { - if (hasChildren) { + const modalCustoms = findModalCustomizationRecursively(vizNode, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); + + let modalAnswer: string | undefined = ACTION_ID_CONFIRM; + if (hasChildren || modalCustoms.length > 0) { + const additionalModalText = modalCustoms.length > 0 ? modalCustoms[0].additionalText : undefined; + const buttonOptions = modalCustoms.length > 0 ? modalCustoms[0].buttonOptions : undefined; /** Open delete confirm modal, get the confirmation */ - const isDeleteConfirmed = await deleteModalContext?.actionConfirmation({ + modalAnswer = await deleteModalContext?.actionConfirmation({ title: 'Permanently delete step?', text: 'Step and its children will be lost.', + additionalModalText, + buttonOptions, }); - if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return; + if (!modalAnswer || modalAnswer === ACTION_ID_CANCEL) return; } + processNodeInteractionAddonRecursively(vizNode, modalAnswer, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); + vizNode.removeChild(); entitiesContext?.updateEntitiesFromCamelResource(); - }, [deleteModalContext, entitiesContext, hasChildren, vizNode]); + }, [deleteModalContext, entitiesContext, getRegisteredInteractionAddons, hasChildren, vizNode]); const value = useMemo( () => ({ diff --git a/packages/ui/src/components/Visualization/Custom/hooks/replace-step.hook.tsx b/packages/ui/src/components/Visualization/Custom/hooks/replace-step.hook.tsx index a2424500f..d3d083d29 100644 --- a/packages/ui/src/components/Visualization/Custom/hooks/replace-step.hook.tsx +++ b/packages/ui/src/components/Visualization/Custom/hooks/replace-step.hook.tsx @@ -1,7 +1,18 @@ import { useCallback, useContext, useMemo } from 'react'; import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity'; -import { ACTION_ID_CONFIRM, ActionConfirmationModalContext, CatalogModalContext } from '../../../../providers'; +import { + ACTION_ID_CANCEL, + ACTION_ID_CONFIRM, + ActionConfirmationModalContext, + CatalogModalContext, +} from '../../../../providers'; import { EntitiesContext } from '../../../../providers/entities.provider'; +import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider'; +import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model'; +import { + findModalCustomizationRecursively, + processNodeInteractionAddonRecursively, +} from '../ContextMenu/item-delete-helper'; export const useReplaceStep = (vizNode: IVisualizationNode) => { const entitiesContext = useContext(EntitiesContext); @@ -9,18 +20,27 @@ export const useReplaceStep = (vizNode: IVisualizationNode) => { const replaceModalContext = useContext(ActionConfirmationModalContext); const childrenNodes = vizNode.getChildren(); const hasChildren = childrenNodes !== undefined && childrenNodes.length > 0; + const { getRegisteredInteractionAddons } = useContext(NodeInteractionAddonContext); const onReplaceNode = useCallback(async () => { if (!vizNode || !entitiesContext) return; + const modalCustoms = findModalCustomizationRecursively(vizNode, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); + let modalAnswer: string | undefined = ACTION_ID_CONFIRM; if (hasChildren) { + const additionalModalText = modalCustoms.length > 0 ? modalCustoms[0].additionalText : undefined; + const buttonOptions = modalCustoms.length > 0 ? modalCustoms[0].buttonOptions : undefined; /** Open delete confirm modal, get the confirmation */ - const isReplaceConfirmed = await replaceModalContext?.actionConfirmation({ + modalAnswer = await replaceModalContext?.actionConfirmation({ title: 'Replace step?', text: 'Step and its children will be lost.', + additionalModalText, + buttonOptions, }); - if (isReplaceConfirmed !== ACTION_ID_CONFIRM) return; + if (!modalAnswer || modalAnswer === ACTION_ID_CANCEL) return; } /** Find compatible components */ @@ -30,12 +50,16 @@ export const useReplaceStep = (vizNode: IVisualizationNode) => { const definedComponent = await catalogModalContext?.getNewComponent(catalogFilter); if (!definedComponent) return; + processNodeInteractionAddonRecursively(vizNode, modalAnswer, (vn) => + getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn), + ); + /** Add new node to the entities */ vizNode.addBaseEntityStep(definedComponent, AddStepMode.ReplaceStep); /** Update entity */ entitiesContext.updateEntitiesFromCamelResource(); - }, [catalogModalContext, entitiesContext, hasChildren, replaceModalContext, vizNode]); + }, [catalogModalContext, entitiesContext, getRegisteredInteractionAddons, hasChildren, replaceModalContext, vizNode]); const value = useMemo( () => ({ diff --git a/packages/ui/src/components/Visualization/Visualization.tsx b/packages/ui/src/components/Visualization/Visualization.tsx index a95d905c1..8c9fb7ff8 100644 --- a/packages/ui/src/components/Visualization/Visualization.tsx +++ b/packages/ui/src/components/Visualization/Visualization.tsx @@ -1,10 +1,8 @@ -import { VisualizationProvider } from '@patternfly/react-topology'; -import { FunctionComponent, PropsWithChildren, ReactNode, useMemo } from 'react'; +import { FunctionComponent, PropsWithChildren, ReactNode } from 'react'; import { BaseVisualCamelEntity } from '../../models/visualization/base-visual-entity'; import { CanvasFormTabsProvider } from '../../providers'; import { ErrorBoundary } from '../ErrorBoundary'; import { Canvas } from './Canvas'; -import { ControllerService } from './Canvas/controller.service'; import { CanvasFallback } from './CanvasFallback'; import { ContextToolbar } from './ContextToolbar/ContextToolbar'; import './Visualization.scss'; @@ -16,17 +14,13 @@ interface CanvasProps { } export const Visualization: FunctionComponent> = (props) => { - const controller = useMemo(() => ControllerService.createController(), []); - return ( - -
- - }> - } entities={props.entities} /> - - -
-
+
+ + }> + } entities={props.entities} /> + + +
); }; diff --git a/packages/ui/src/components/XPath/FunctionIcon.scss b/packages/ui/src/components/XPath/FunctionIcon.scss new file mode 100644 index 000000000..49714290b --- /dev/null +++ b/packages/ui/src/components/XPath/FunctionIcon.scss @@ -0,0 +1,4 @@ +.function-icon { + position: relative; + bottom: -3px; +} diff --git a/packages/ui/src/components/XPath/FunctionIcon.test.tsx b/packages/ui/src/components/XPath/FunctionIcon.test.tsx new file mode 100644 index 000000000..761fe9f32 --- /dev/null +++ b/packages/ui/src/components/XPath/FunctionIcon.test.tsx @@ -0,0 +1,9 @@ +import { FunctionIcon } from './FunctionIcon'; +import { render, screen } from '@testing-library/react'; + +describe('FunctionIcon', () => { + it('should render', () => { + render(); + expect(screen.getByText('(x)')).toBeTruthy(); + }); +}); diff --git a/packages/ui/src/components/XPath/FunctionIcon.tsx b/packages/ui/src/components/XPath/FunctionIcon.tsx new file mode 100644 index 000000000..65e552d5a --- /dev/null +++ b/packages/ui/src/components/XPath/FunctionIcon.tsx @@ -0,0 +1,12 @@ +import { FunctionComponent } from 'react'; +import './FunctionIcon.scss'; + +export const FunctionIcon: FunctionComponent = () => { + return ( + + + f(x) + + + ); +}; diff --git a/packages/ui/src/components/XPath/XPathEditor.scss b/packages/ui/src/components/XPath/XPathEditor.scss new file mode 100644 index 000000000..047daa884 --- /dev/null +++ b/packages/ui/src/components/XPath/XPathEditor.scss @@ -0,0 +1,4 @@ +.xpath-editor { + width: 100%; + height: 100%; +} diff --git a/packages/ui/src/components/XPath/XPathEditor.tsx b/packages/ui/src/components/XPath/XPathEditor.tsx new file mode 100644 index 000000000..33aa36a21 --- /dev/null +++ b/packages/ui/src/components/XPath/XPathEditor.tsx @@ -0,0 +1,71 @@ +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import './XPathEditor.scss'; +import { xpathEditorConstrufctionOption, xpathEditorTheme } from './monaco-options'; +import { XPathService } from '../../services/xpath/xpath.service'; +import { ExpressionItem } from '../../models/datamapper'; + +type XPathEditorProps = { + mapping: ExpressionItem; + onChange: (expression: string | undefined) => void; +}; + +export const XPathEditor: FunctionComponent = ({ mapping, onChange }) => { + const [editor, setEditor] = useState(null); + const monacoEl = useRef(null); + const xpathLanguage = XPathService.getMonacoXPathLanguageMetadata(); + + useEffect(() => { + const previousExpression = editor?.getModel()?.getValue(); + if (previousExpression !== mapping.expression) editor?.getModel()?.setValue(mapping.expression); + }, [editor, mapping.expression]); + + useEffect(() => { + if (monacoEl) { + setEditor((editor) => { + if (editor) return editor; + + monaco.languages.register({ id: xpathLanguage.id }); + monaco.languages.setMonarchTokensProvider(xpathLanguage.id, xpathLanguage.tokensProvider); + monaco.languages.setLanguageConfiguration(xpathLanguage.id, xpathLanguage.languageConfiguration); + monaco.languages.registerCompletionItemProvider(xpathLanguage.id, xpathLanguage.completionItemProvider); + monaco.languages.registerHoverProvider(xpathLanguage.id, { + provideHover: (model, position, token, context) => { + console.log(`#### ${model}, ${position}, ${token}, ${context}`); + return { contents: [{ value: 'test' }] }; + }, + }); + const themeName = 'datamapperTheme'; + monaco.editor.defineTheme(themeName, xpathEditorTheme); + + const newEditor = monaco.editor.create(monacoEl.current!, { + ...xpathEditorConstrufctionOption, + theme: themeName, + value: mapping.expression, + minimap: { + enabled: false, + }, + }); + newEditor.onDidChangeModelContent((_e) => onChange(newEditor.getModel()?.getValue())); + return newEditor; + }); + } + + return () => { + if (!monacoEl) { + editor?.dispose(); + setEditor(null); + } + }; + }, [ + editor, + mapping.expression, + onChange, + xpathLanguage.completionItemProvider, + xpathLanguage.id, + xpathLanguage.languageConfiguration, + xpathLanguage.tokensProvider, + ]); + + return
; +}; diff --git a/packages/ui/src/components/XPath/XPathEditorLayout.scss b/packages/ui/src/components/XPath/XPathEditorLayout.scss new file mode 100644 index 000000000..d840018df --- /dev/null +++ b/packages/ui/src/components/XPath/XPathEditorLayout.scss @@ -0,0 +1,14 @@ +@use '../../styles/dnd'; + +.menu-item-drag { + @include dnd.cursor-grab; +} + +.xpath-editor-layout-grid { + height: 90%; +} + +.xpath-editor-layout-tab-content { + position: relative; + height: 100%; +} diff --git a/packages/ui/src/components/XPath/XPathEditorLayout.tsx b/packages/ui/src/components/XPath/XPathEditorLayout.tsx new file mode 100644 index 000000000..1242541e1 --- /dev/null +++ b/packages/ui/src/components/XPath/XPathEditorLayout.tsx @@ -0,0 +1,117 @@ +import { + Grid, + GridItem, + Menu, + MenuContent, + MenuGroup, + MenuItem, + Tab, + TabContent, + Tabs, + TabTitleText, +} from '@patternfly/react-core'; +import { FunctionComponent, MouseEvent, useCallback, useMemo, useState } from 'react'; +import { EditorNodeData, FunctionNodeData } from '../../models/datamapper'; +import { ExpressionItem } from '../../models/datamapper/mapping'; +import { DatamapperDndProvider } from '../../providers/datamapper-dnd.provider'; +import { DataMapperDnDMonitor } from '../../providers/dnd/DataMapperDndMonitor'; +import { ExpressionEditorDnDHandler } from '../../providers/dnd/ExpressionEditorDnDHandler'; +import { FunctionGroup } from '../../services/xpath/xpath-parser'; +import { XPathService } from '../../services/xpath/xpath.service'; +import { DraggableContainer, DroppableContainer } from '../Document/NodeContainer'; +import { SourcePanel } from '../View/SourcePanel'; +import { XPathEditor } from './XPathEditor'; +import './XPathEditorLayout.scss'; + +type XPathEditorLayoutProps = { + mapping: ExpressionItem; + onUpdate: () => void; +}; + +export const XPathEditorLayout: FunctionComponent = ({ mapping, onUpdate }) => { + const dndHandler = useMemo(() => new ExpressionEditorDnDHandler(), []); + + const handleExpressionChange = useCallback( + (expression?: string) => { + mapping.expression = expression ?? ''; + onUpdate(); + }, + [mapping, onUpdate], + ); + const functionDefinitions = XPathService.getXPathFunctionDefinitions(); + + const [activeTabKey, setActiveTabKey] = useState(0); + const handleTabClick = (_event: MouseEvent, tabIndex: string | number) => { + setActiveTabKey(tabIndex); + }; + + return ( + + + + + Field} + className="xpath-editor-layout-tab-content" + > + + + + + Function} + className="xpath-editor-layout-tab-content" + > + + + + {Object.keys(functionDefinitions).map((value) => ( + + {functionDefinitions[value as FunctionGroup].map((func, index) => ( + + + {func.displayName} + + + ))} + + ))} + + + + + + + + {/* TODO: non-DnD operation as an alternative + + , + ]} + > + + + ); +}; diff --git a/packages/ui/src/components/XPath/monaco-options.ts b/packages/ui/src/components/XPath/monaco-options.ts new file mode 100644 index 000000000..4f7df9d0a --- /dev/null +++ b/packages/ui/src/components/XPath/monaco-options.ts @@ -0,0 +1,33 @@ +import * as monaco from 'monaco-editor'; + +export const xpathEditorTheme: monaco.editor.IStandaloneThemeData = { + base: 'vs', + inherit: true, + rules: [ + { token: 'identifier', foreground: '0b3c0b', fontStyle: 'bold' }, + { token: 'action', foreground: '641564', fontStyle: 'italic' }, + { token: 'number', foreground: '0f2386', fontStyle: 'none' }, // notsecret + { token: 'number.float', foreground: '0f2386', fontStyle: 'none' }, // notsecret + ], + colors: { + 'editor.foreground': '#000000', + 'editor.background': '#EDF9FA', + 'editorCursor.foreground': '#8B0000', + 'editor.lineHighlightBackground': '#0000FF20', + 'editor.selectionBackground': '#88000030', + 'editor.inactiveSelectionBackground': '#88000015', + }, +}; + +export const xpathEditorConstrufctionOption: monaco.editor.IStandaloneEditorConstructionOptions = { + language: 'xpath', + automaticLayout: true, + wordWrap: 'on', + scrollBeyondLastColumn: 0, + scrollbar: { + horizontal: 'hidden', + horizontalHasArrows: false, + vertical: 'auto', + verticalHasArrows: false, + }, +}; diff --git a/packages/ui/src/components/registers/RegisterComponents.tsx b/packages/ui/src/components/registers/RegisterComponents.tsx new file mode 100644 index 000000000..4326fb5b4 --- /dev/null +++ b/packages/ui/src/components/registers/RegisterComponents.tsx @@ -0,0 +1,21 @@ +import { FunctionComponent, lazy, PropsWithChildren, useContext, useRef } from 'react'; +import { RenderingAnchorContext } from '../RenderingAnchor/rendering.provider'; +import { IRegisteredComponent } from '../RenderingAnchor/rendering.provider.model'; +import { Anchors } from './anchors'; +import { datamapperActivationFn } from './datamapper.activationfn'; + +export const RegisterComponents: FunctionComponent = ({ children }) => { + const { registerComponent } = useContext(RenderingAnchorContext); + + const componentsToRegister = useRef([ + { + anchor: Anchors.CanvasFormHeader, + activationFn: datamapperActivationFn, + component: lazy(() => import('../DataMapper/DataMapperLauncher')), + }, + ]); + + componentsToRegister.current.forEach((regComponent) => registerComponent(regComponent)); + + return <>{children}; +}; diff --git a/packages/ui/src/components/registers/RegisterNodeInteractionAddons.tsx b/packages/ui/src/components/registers/RegisterNodeInteractionAddons.tsx new file mode 100644 index 000000000..38092632c --- /dev/null +++ b/packages/ui/src/components/registers/RegisterNodeInteractionAddons.tsx @@ -0,0 +1,45 @@ +import { FunctionComponent, PropsWithChildren, useContext, useRef } from 'react'; +import { datamapperActivationFn } from './datamapper.activationfn'; +import { MetadataContext } from '../../providers'; +import { + ACTION_ID_DELETE_STEP_AND_FILE, + ACTION_ID_DELETE_STEP_ONLY, + onDeleteDataMapper, +} from '../DataMapper/on-delete-datamapper'; +import { NodeInteractionAddonContext } from './interactions/node-interaction-addon.provider'; +import { IInteractionAddonType, IRegisteredInteractionAddon } from './interactions/node-interaction-addon.model'; +import { ButtonVariant } from '@patternfly/react-core'; + +export const RegisterNodeInteractionAddons: FunctionComponent = ({ children }) => { + const metadataApi = useContext(MetadataContext)!; + const { registerInteractionAddon } = useContext(NodeInteractionAddonContext); + const addonsToRegister = useRef([ + { + type: IInteractionAddonType.ON_DELETE, + activationFn: datamapperActivationFn, + callback: (vizNode, modalAnswer) => { + metadataApi && onDeleteDataMapper(metadataApi, vizNode, modalAnswer); + }, + modalCustomization: { + additionalText: 'Do you also want to delete the associated Kaoto DataMapper mapping file (XSLT)?', + buttonOptions: { + [ACTION_ID_DELETE_STEP_AND_FILE]: { + variant: ButtonVariant.danger, + buttonText: 'Delete both step and file', + }, + [ACTION_ID_DELETE_STEP_ONLY]: { + variant: ButtonVariant.secondary, + isDanger: true, + buttonText: 'Delete the step, but keep the file', + }, + }, + }, + }, + ]); + + addonsToRegister.current.forEach((interaction) => { + registerInteractionAddon(interaction); + }); + + return <>{children}; +}; diff --git a/packages/ui/src/components/registers/anchors.ts b/packages/ui/src/components/registers/anchors.ts new file mode 100644 index 000000000..ace9fa6cd --- /dev/null +++ b/packages/ui/src/components/registers/anchors.ts @@ -0,0 +1,3 @@ +export const enum Anchors { + CanvasFormHeader = 'CanvasFormHeader', +} diff --git a/packages/ui/src/components/registers/datamapper.activationfn.test.ts b/packages/ui/src/components/registers/datamapper.activationfn.test.ts new file mode 100644 index 000000000..e6db9c3e1 --- /dev/null +++ b/packages/ui/src/components/registers/datamapper.activationfn.test.ts @@ -0,0 +1,34 @@ +import { Step } from '@kaoto/camel-catalog/types'; +import { IVisualizationNode, KaotoSchemaDefinition } from '../../models'; +import { datamapperRouteDefinitionStub } from '../../stubs/data-mapper'; +import { datamapperActivationFn } from './datamapper.activationfn'; + +describe('datamapperActivationFn', () => { + it('should return false if vizNode is `undefined`', () => { + const result = datamapperActivationFn(undefined); + + expect(result).toBe(false); + }); + + it('should return false if stepDefinition is undefined', () => { + const result = datamapperActivationFn({ + getComponentSchema: () => ({ + definition: undefined, + schema: {} as KaotoSchemaDefinition['schema'], + }), + } as unknown as IVisualizationNode); + + expect(result).toBe(false); + }); + + it('should return `true` if stepDefinition is a DataMapper node', () => { + const result = datamapperActivationFn({ + getComponentSchema: () => ({ + definition: datamapperRouteDefinitionStub.from.steps[0].step as Step, + schema: {} as KaotoSchemaDefinition['schema'], + }), + } as unknown as IVisualizationNode); + + expect(result).toBe(true); + }); +}); diff --git a/packages/ui/src/components/registers/datamapper.activationfn.ts b/packages/ui/src/components/registers/datamapper.activationfn.ts new file mode 100644 index 000000000..352940bad --- /dev/null +++ b/packages/ui/src/components/registers/datamapper.activationfn.ts @@ -0,0 +1,16 @@ +import { IVisualizationNode } from '../../models/visualization/base-visual-entity'; +import { isDataMapperNode } from '../../utils'; + +export const datamapperActivationFn = (vizNode?: IVisualizationNode): boolean => { + if (!vizNode) { + return false; + } + + const stepDefinition = vizNode.getComponentSchema()?.definition; + + if (!stepDefinition) { + return false; + } + + return isDataMapperNode(stepDefinition); +}; diff --git a/packages/ui/src/components/registers/interactions/node-interaction-addon.model.ts b/packages/ui/src/components/registers/interactions/node-interaction-addon.model.ts new file mode 100644 index 000000000..c24632946 --- /dev/null +++ b/packages/ui/src/components/registers/interactions/node-interaction-addon.model.ts @@ -0,0 +1,59 @@ +import { IVisualizationNode } from '../../../models'; +import { ActionConfirmationButtonOption } from '../../../providers'; + +export enum IInteractionAddonType { + ON_DELETE = 'onDelete', +} + +export interface IModalCustomization { + buttonOptions: Record; + additionalText?: string; +} + +export interface IRegisteredInteractionAddon { + type: IInteractionAddonType; + activationFn: (vizNode: IVisualizationNode) => boolean; + callback: (vizNode: IVisualizationNode, modalAnswer: string | undefined) => void; + modalCustomization?: IModalCustomization; +} + +export interface INodeInteractionAddonContext { + /** + * Register a node interaction addon to be processed on an associated node interaction + * + * @example + * ```tsx + * const nodeInteractionAddonContext = useContext(NodeInteractionAddonContext); + * + * nodeInteractionAddonContext.registerInteractionAddon({ + * type: IInteractionAddonType.ON_DELETE + * activationFn: () => true, + * callback: () => { doSomething() } + * }); + * ``` + * @param addon Registered node interaction addon + * @returns void + */ + registerInteractionAddon: (addon: IRegisteredInteractionAddon) => void; + + /** + * Get registered interaction addons + * + * @example + * ```tsx + * const nodeInteractionAddonContext = useContext(NodeInteractionAddonContext); + * + * const addons = nodeInteractionAddonContext.getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vizNode); + * addons.forEach((addon) => { + * addon.callback(vizNode, ACTION_ID_CONFIRM); + * }); + * ``` + * @param type The interaction addon type enum value + * @param vizNode The visualization node to pass to the interaction + * @returns `IRegisteredInteraction` An array of registered interactions + */ + getRegisteredInteractionAddons: ( + type: IInteractionAddonType, + vizNode: IVisualizationNode, + ) => IRegisteredInteractionAddon[]; +} diff --git a/packages/ui/src/components/registers/interactions/node-interaction-addon.provider.tsx b/packages/ui/src/components/registers/interactions/node-interaction-addon.provider.tsx new file mode 100644 index 000000000..fa649bdee --- /dev/null +++ b/packages/ui/src/components/registers/interactions/node-interaction-addon.provider.tsx @@ -0,0 +1,39 @@ +import { createContext, FunctionComponent, PropsWithChildren, useCallback, useMemo, useRef } from 'react'; +import { IVisualizationNode } from '../../../models'; +import { + IInteractionAddonType, + INodeInteractionAddonContext, + IRegisteredInteractionAddon, +} from './node-interaction-addon.model'; + +export const NodeInteractionAddonContext = createContext({ + registerInteractionAddon: () => {}, + getRegisteredInteractionAddons: () => [], +}); + +export const NodeInteractionAddonProvider: FunctionComponent = ({ children }) => { + const registeredInteractionAddons = useRef([]); + + const registerInteractionAddon = useCallback((interaction: IRegisteredInteractionAddon) => { + registeredInteractionAddons.current.push(interaction); + }, []); + + const getRegisteredInteractionAddons = useCallback( + (interaction: IInteractionAddonType, vizNode: IVisualizationNode) => { + return registeredInteractionAddons.current.filter( + (addon) => addon.type === interaction && addon.activationFn(vizNode), + ); + }, + [], + ); + + const value = useMemo( + () => ({ + registerInteractionAddon, + getRegisteredInteractionAddons, + }), + [getRegisteredInteractionAddons, registerInteractionAddon], + ); + + return {children}; +}; diff --git a/packages/ui/src/external/RouteVisualization/RouteVisualization.tsx b/packages/ui/src/external/RouteVisualization/RouteVisualization.tsx index 8cf0c1168..74da71922 100644 --- a/packages/ui/src/external/RouteVisualization/RouteVisualization.tsx +++ b/packages/ui/src/external/RouteVisualization/RouteVisualization.tsx @@ -1,4 +1,8 @@ -import React, { useContext, useEffect, useLayoutEffect } from 'react'; +import { VisualizationProvider } from '@patternfly/react-topology'; +import React, { useContext, useEffect, useLayoutEffect, useMemo } from 'react'; +import { Visualization } from '../../components/Visualization'; +import { ControllerService } from '../../components/Visualization/Canvas/controller.service'; +import { useReload } from '../../hooks/reload.hook'; import { CatalogLoaderProvider, EntitiesContext, @@ -8,9 +12,7 @@ import { VisibleFlowsContext, VisibleFlowsProvider, } from '../../providers'; -import { Visualization } from '../../components/Visualization'; import { EventNotifier } from '../../utils'; -import { useReload } from '../../hooks/reload.hook'; const VisibleFlowsVisualization: React.FC<{ className?: string }> = ({ className = '' }) => { const { visibleFlows, visualFlowsApi } = useContext(VisibleFlowsContext)!; @@ -26,15 +28,18 @@ const VisibleFlowsVisualization: React.FC<{ className?: string }> = ({ className const Viz: React.FC<{ catalogUrl: string; className?: string }> = ({ catalogUrl, className = '' }) => { const ReloadProvider = useReload(); + const controller = useMemo(() => ControllerService.createController(), []); return ( - - - + + + + + diff --git a/packages/ui/src/hooks/useCanvas.tsx b/packages/ui/src/hooks/useCanvas.tsx new file mode 100644 index 000000000..8d323556e --- /dev/null +++ b/packages/ui/src/hooks/useCanvas.tsx @@ -0,0 +1,10 @@ +import { CanvasContext, ICanvasContext } from '../providers/datamapper-canvas.provider'; +import { useContext } from 'react'; + +export const errorMessage = 'useCanvas should be called into CanvasProvider'; + +export const useCanvas = (): ICanvasContext => { + const ctx = useContext(CanvasContext); + if (!ctx) throw new Error(errorMessage); + return ctx; +}; diff --git a/packages/ui/src/hooks/useDataMapper.ts b/packages/ui/src/hooks/useDataMapper.ts new file mode 100644 index 000000000..60393692e --- /dev/null +++ b/packages/ui/src/hooks/useDataMapper.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { DataMapperContext, IDataMapperContext } from '../providers/datamapper.provider'; + +export const errorMessage = 'useDataMapper should be called into DataMapperProvider'; + +export const useDataMapper = (): IDataMapperContext => { + const ctx = useContext(DataMapperContext); + if (!ctx) throw new Error(errorMessage); + return ctx; +}; diff --git a/packages/ui/src/hooks/useToggle.ts b/packages/ui/src/hooks/useToggle.ts new file mode 100644 index 000000000..1a8f27b1c --- /dev/null +++ b/packages/ui/src/hooks/useToggle.ts @@ -0,0 +1,30 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { useCallback, useEffect, useState } from 'react'; + +type OnToggleReturnType = Promise | boolean; + +export function useToggle(initialState: boolean, onToggle?: (toggled: boolean) => OnToggleReturnType) { + const [state, setState] = useState(initialState); + useEffect(() => setState(initialState), [initialState]); + const toggle = useCallback(async () => { + const newState = onToggle ? await onToggle(!state) : !state; + setState(newState); + }, [onToggle, state]); + const toggleOff = useCallback(() => setState(false), []); + const toggleOn = useCallback(() => setState(true), []); + return { state, toggle, toggleOff, toggleOn, setToggle: setState }; +} diff --git a/packages/ui/src/layout/Navigation.tsx b/packages/ui/src/layout/Navigation.tsx index e075deedd..80e939e70 100644 --- a/packages/ui/src/layout/Navigation.tsx +++ b/packages/ui/src/layout/Navigation.tsx @@ -38,6 +38,11 @@ export const Navigation: FunctionComponent = (props) => { to: Links.PipeErrorHandler, hidden: () => !NAVIGATION_ELEMENTS.PipeErrorHandler.includes(currentSchemaType), }, + { + title: 'DataMapper', + to: Links.DataMapper, + hidden: () => !NAVIGATION_ELEMENTS.DataMapper.includes(currentSchemaType), + }, { title: 'Catalog', to: Links.Catalog }, ], [currentSchemaType], @@ -113,4 +118,5 @@ const NAVIGATION_ELEMENTS = { SourceSchemaType.Pipe, ], PipeErrorHandler: [SourceSchemaType.KameletBinding, SourceSchemaType.Pipe], + DataMapper: [SourceSchemaType.Route, SourceSchemaType.Kamelet], }; diff --git a/packages/ui/src/layout/__snapshots__/Navigation.test.tsx.snap b/packages/ui/src/layout/__snapshots__/Navigation.test.tsx.snap index 76987f1d6..aa43ddc6b 100644 --- a/packages/ui/src/layout/__snapshots__/Navigation.test.tsx.snap +++ b/packages/ui/src/layout/__snapshots__/Navigation.test.tsx.snap @@ -66,7 +66,7 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = ` >
  • @@ -81,7 +81,7 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = `
  • @@ -98,7 +98,7 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = `
  • @@ -112,7 +112,7 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = `
  • @@ -126,7 +126,7 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = `
  • @@ -138,9 +138,23 @@ exports[`Navigation Component navigation sidebar for: Integration 1`] = ` Pipe ErrorHandler
  • +
  • + + DataMapper + +
  • @@ -225,7 +239,7 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = ` >
  • @@ -240,7 +254,7 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = `
  • @@ -257,7 +271,7 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = `
  • @@ -271,7 +285,7 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = `
  • @@ -285,7 +299,7 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = `
  • @@ -299,7 +313,21 @@ exports[`Navigation Component navigation sidebar for: Kamelet 1`] = `
  • + + DataMapper + +
  • +
  • @@ -384,7 +412,7 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = ` >
  • @@ -399,7 +427,7 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = `
  • @@ -416,7 +444,7 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = `
  • @@ -430,7 +458,7 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = `
  • @@ -444,7 +472,7 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = `
  • @@ -456,9 +484,23 @@ exports[`Navigation Component navigation sidebar for: KameletBinding 1`] = ` Pipe ErrorHandler
  • +
  • + + DataMapper + +
  • @@ -543,7 +585,7 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = ` >
  • @@ -558,7 +600,7 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = `
  • @@ -575,7 +617,7 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = `
  • @@ -589,7 +631,7 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = `
  • @@ -603,7 +645,7 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = `
  • @@ -615,9 +657,23 @@ exports[`Navigation Component navigation sidebar for: Pipe 1`] = ` Pipe ErrorHandler
  • +
  • + + DataMapper + +
  • @@ -779,6 +835,20 @@ exports[`Navigation Component navigation sidebar for: Route 1`] = ` data-ouia-component-id="OUIA-Generated-NavItem-6" data-ouia-component-type="PF5/NavItem" data-ouia-safe="true" + > + + DataMapper + +
  • +
  • IField; +} + +export interface ITypeFragment { + fields: IField[]; + namedTypeFragmentRefs: string[]; +} + +export interface IDocument { + documentType: DocumentType; + documentId: string; + name: string; + schemaType: string; + fields: IField[]; + path: NodePath; + namedTypeFragments: Record; + totalFieldCount: number; + isNamespaceAware: boolean; +} + +export abstract class BaseDocument implements IDocument { + constructor( + public documentType: DocumentType, + public documentId: string, + ) { + this.path = NodePath.fromDocument(documentType, documentId); + } + fields: IField[] = []; + name: string = ''; + schemaType = ''; + path: NodePath; + namedTypeFragments: Record = {}; + abstract totalFieldCount: number; + abstract isNamespaceAware: boolean; +} + +export class PrimitiveDocument extends BaseDocument implements IField { + constructor(documentType: DocumentType, documentId: string) { + super(documentType, documentId); + this.name = this.documentId; + this.id = this.documentId; + this.path = NodePath.fromDocument(documentType, documentId); + } + + ownerDocument: IDocument = this; + defaultValue: string | null = null; + isAttribute: boolean = false; + maxOccurs: number = 1; + minOccurs: number = 0; + namespacePrefix: string | null = null; + namespaceURI: string | null = null; + parent: IParentType = this; + type = Types.AnyType; + path: NodePath; + id: string; + namedTypeFragmentRefs = []; + totalFieldCount = 1; + isNamespaceAware = false; + adopt = () => this; +} + +export class BaseField implements IField { + constructor( + public parent: IParentType, + public ownerDocument: IDocument, + public name: string, + ) { + this.id = getCamelRandomId(`field-${this.name}`, 4); + this.path = NodePath.childOf(parent.path, this.id); + } + + id: string; + path: NodePath; + fields: IField[] = []; + isAttribute: boolean = false; + type = Types.AnyType; + minOccurs: number = DEFAULT_MIN_OCCURS; + maxOccurs: number = DEFAULT_MAX_OCCURS; + defaultValue: string | null = null; + namespacePrefix: string | null = null; + namespaceURI: string | null = null; + namedTypeFragmentRefs: string[] = []; + adopt = (parent: IField) => { + const adopted = new BaseField(parent, parent.ownerDocument, this.name); + adopted.isAttribute = this.isAttribute; + adopted.type = this.type; + adopted.minOccurs = this.minOccurs; + adopted.maxOccurs = this.maxOccurs; + adopted.defaultValue = this.defaultValue; + adopted.namespacePrefix = this.namespacePrefix; + adopted.namespaceURI = this.namespaceURI; + adopted.namedTypeFragmentRefs = this.namedTypeFragmentRefs; + adopted.fields = this.fields.map((child) => child.adopt(adopted)); + parent.fields.push(adopted); + return adopted; + }; +} + +export enum DocumentDefinitionType { + Primitive = 'Primitive', + XML_SCHEMA = 'XML Schema', +} + +export class DocumentDefinition { + constructor( + public documentType: DocumentType, + public definitionType: DocumentDefinitionType, + public name?: string, + public definitionFiles?: Record, + ) { + if (!definitionFiles) this.definitionFiles = {}; + } +} + +export class DocumentInitializationModel { + constructor( + public sourceParameters: Record = {}, + public sourceBody: DocumentDefinition = { + documentType: DocumentType.SOURCE_BODY, + definitionType: DocumentDefinitionType.Primitive, + }, + public targetBody: DocumentDefinition = { + documentType: DocumentType.TARGET_BODY, + definitionType: DocumentDefinitionType.Primitive, + }, + ) {} +} diff --git a/packages/ui/src/models/datamapper/index.ts b/packages/ui/src/models/datamapper/index.ts new file mode 100644 index 000000000..29d39d62d --- /dev/null +++ b/packages/ui/src/models/datamapper/index.ts @@ -0,0 +1,6 @@ +export * from './document'; +export * from './mapping'; +export * from './path'; +export * from './types'; +export * from './view'; +export * from './visualization'; diff --git a/packages/ui/src/models/datamapper/mapping.ts b/packages/ui/src/models/datamapper/mapping.ts new file mode 100644 index 000000000..ae96b8ab3 --- /dev/null +++ b/packages/ui/src/models/datamapper/mapping.ts @@ -0,0 +1,183 @@ +import { IField } from './document'; +import { DocumentType, NodePath, Path } from './path'; +import { Types } from './types'; +import { getCamelRandomId } from '../../camel-utils/camel-random-id'; + +export type MappingParentType = MappingTree | MappingItem; + +export class MappingTree { + constructor(documentType: DocumentType, documentId: string) { + this.nodePath = NodePath.fromDocument(documentType, documentId); + } + children: MappingItem[] = []; + nodePath: NodePath; + contextPath?: Path; + namespaceMap: { [prefix: string]: string } = {}; +} + +export abstract class MappingItem { + constructor( + public parent: MappingParentType, + public name: string, + public id: string, + ) { + this.mappingTree = parent instanceof MappingTree ? parent : parent.mappingTree; + } + mappingTree: MappingTree; + children: MappingItem[] = []; + get nodePath(): NodePath { + return NodePath.childOf(this.parent.nodePath, this.id); + } + get contextPath(): Path | undefined { + return this.parent.contextPath; + } + protected abstract doClone(): MappingItem; + clone(): MappingItem { + const cloned = this.doClone(); + cloned.children = this.children.map((c) => c.clone()); + return cloned; + } +} + +export class FieldItem extends MappingItem { + constructor( + public parent: MappingParentType, + public field: IField, + ) { + super(parent, 'field-' + field.name, field.id); + } + doClone() { + return new FieldItem(this.parent, this.field); + } +} + +export abstract class ConditionItem extends MappingItem { + constructor( + public parent: MappingParentType, + public name: string, + ) { + super(parent, name, getCamelRandomId(name, 4)); + } + readonly isCondition = true; +} + +export abstract class ExpressionItem extends ConditionItem { + constructor( + public parent: MappingParentType, + public name: string, + ) { + super(parent, name); + } + expression = ''; + clone() { + const cloned = super.clone() as ExpressionItem; + cloned.expression = this.expression; + return cloned; + } +} + +export class IfItem extends ExpressionItem { + constructor(public parent: MappingParentType) { + super(parent, 'if'); + } + doClone() { + return new IfItem(this.parent); + } +} + +export class ChooseItem extends ConditionItem { + constructor( + public parent: MappingParentType, + public field?: IField, + ) { + super(parent, 'choose'); + } + get when() { + return this.children.filter((c) => c instanceof WhenItem) as WhenItem[]; + } + get otherwise() { + return this.children.find((c) => c instanceof OtherwiseItem) as OtherwiseItem; + } + doClone() { + return new ChooseItem(this.parent, this.field); + } +} + +export class WhenItem extends ExpressionItem { + constructor(public parent: MappingParentType) { + super(parent, 'when'); + } + doClone() { + return new WhenItem(this.parent); + } +} + +export class OtherwiseItem extends ConditionItem { + constructor(public parent: MappingParentType) { + super(parent, 'otherwise'); + } + doClone() { + return new OtherwiseItem(this.parent); + } +} + +export class ForEachItem extends ExpressionItem { + constructor(public parent: MappingParentType) { + super(parent, 'for-each'); + } + get contextPath() { + return new Path(this.expression, this.parent.contextPath); + } + sortItems: SortItem[] = []; + doClone() { + const cloned = new ForEachItem(this.parent); + cloned.sortItems = this.sortItems.map((sort) => { + return { + expression: sort.expression, + order: sort.order, + } as SortItem; + }); + return cloned; + } +} + +export class SortItem { + expression: string = ''; + order: 'ascending' | 'descending' = 'ascending'; +} + +export enum ValueType { + VALUE = 'value', + CONTAINER = 'container', + ATTRIBUTE = 'attribute', +} + +export class ValueSelector extends ExpressionItem { + constructor( + public parent: MappingParentType, + public valueType: ValueType = ValueType.VALUE, + ) { + super(parent, 'value'); + } + doClone() { + return new ValueSelector(this.parent, this.valueType); + } +} + +export interface IFunctionDefinition { + name: string; + displayName: string; + description: string; + returnType: Types; + returnCollection?: boolean; + arguments: IFunctionArgumentDefinition[]; +} + +export interface IFunctionArgumentDefinition { + name: string; + type: Types; + displayName: string; + description: string; + minOccurs: number; + maxOccurs: number; +} diff --git a/packages/ui/src/models/datamapper/metadata.ts b/packages/ui/src/models/datamapper/metadata.ts new file mode 100644 index 000000000..8043b4fea --- /dev/null +++ b/packages/ui/src/models/datamapper/metadata.ts @@ -0,0 +1,13 @@ +import { DocumentDefinitionType } from './document'; + +export interface IDocumentMetadata { + type: DocumentDefinitionType; + filePath: string[]; +} + +export interface IDataMapperMetadata { + xsltPath: string; + sourceParameters: Record; + sourceBody: IDocumentMetadata; + targetBody: IDocumentMetadata; +} diff --git a/packages/ui/src/models/datamapper/path.ts b/packages/ui/src/models/datamapper/path.ts new file mode 100644 index 000000000..9ca600bd9 --- /dev/null +++ b/packages/ui/src/models/datamapper/path.ts @@ -0,0 +1,99 @@ +export enum DocumentType { + SOURCE_BODY = 'sourceBody', + TARGET_BODY = 'targetBody', + PARAM = 'param', +} + +export class NodePath { + documentType: DocumentType = DocumentType.SOURCE_BODY; + documentId: string = ''; + pathSegments: string[] = []; + + constructor(expression?: string) { + if (!expression) return; + const parts = expression.split('://'); + if (parts.length < 2) return; + const index = parts[0].indexOf(':'); + this.documentType = (index !== -1 ? parts[0].substring(0, index) : parts[0]) as DocumentType; + this.documentId = index !== -1 ? parts[0].substring(index + 1) : this.documentId; + this.pathSegments = parts[1].length > 0 ? parts[1].split('/') : []; + } + + toString() { + const beforePath = `${this.documentType}:${this.documentId}://`; + return this.pathSegments.length > 0 ? `${beforePath}${this.pathSegments.join('/')}` : beforePath; + } + + static fromDocument(documentType: DocumentType, documentId: string) { + return new NodePath(`${documentType}:${documentId}://`); + } + + static childOf(parent: NodePath, childSegment: string) { + const answer = new NodePath(); + answer.documentType = parent.documentType; + answer.documentId = parent.documentId; + answer.pathSegments = [...parent.pathSegments, childSegment]; + return answer; + } +} + +export class Path { + constructor( + public readonly expression: string, + public readonly parent?: Path, + ) { + this.isRelative = true; + let remainingExpression = this.expression; + if (remainingExpression.startsWith('/')) { + this.isRelative = false; + remainingExpression = remainingExpression.substring(1); + } + if (remainingExpression.endsWith('/')) + remainingExpression = remainingExpression.substring(0, remainingExpression.length - 1); + if (remainingExpression.startsWith('$')) { + this.isRelative = false; + const pos = remainingExpression.indexOf('/'); + this.parameterName = pos !== -1 ? remainingExpression.substring(1, pos) : remainingExpression.substring(1); + remainingExpression = pos !== -1 ? remainingExpression.substring(pos + 1) : ''; + } + this.pathSegments = remainingExpression.split('/').map((segmentString) => new PathSegment(segmentString)); + } + + toString() { + const prefix = this.parameterName ? `$${this.parameterName}/` : this.isRelative ? '' : '/'; + return this.pathSegments.length > 0 ? `${prefix}${this.pathSegments.join('/')}` : prefix; + } + + toAbsolutePathString(): string { + return this.isRelative ? this.parent?.toAbsolutePathString() + '/' + this.toString() : this.toString(); + } + + child(segment: string) { + if (segment.startsWith('/')) segment = segment.substring(1); + if (segment.endsWith('/')) segment = segment.substring(0, segment.length - 1); + return new NodePath(this.toString() + '/' + segment); + } + + isRelative: boolean; + parameterName?: string; + pathSegments: PathSegment[]; +} + +export class PathSegment { + constructor(public readonly expression: string) { + const splitted = expression.split(':'); + if (splitted.length > 1) { + this.prefix = splitted[0]; + this.name = splitted[1]; + } else { + this.name = splitted[0]; + } + } + + toString() { + return this.prefix ? `${this.prefix}:${this.name}` : this.name; + } + + prefix?: string; + name: string; +} diff --git a/packages/ui/src/models/datamapper/types.ts b/packages/ui/src/models/datamapper/types.ts new file mode 100644 index 000000000..cc47749f5 --- /dev/null +++ b/packages/ui/src/models/datamapper/types.ts @@ -0,0 +1,29 @@ +/** + * The data types mapped from XML Schema +. + */ +export enum Types { + AnyAtomicType = 'anyAtomicType', + AnyType = 'anyType', + AnyURI = 'anyURI', + String = 'string', + Float = 'float', + Double = 'double', + Integer = 'integer', + Decimal = 'decimal', + Boolean = 'boolean', + Date = 'date', + DateTime = 'dateTime', + Time = 'time', + QName = 'QName', + Duration = 'duration', + DayTimeDuration = 'dayTimeDuration', + NCName = 'NCName', + // not XSD predefined but appear in XPath function signature + Numeric = 'numeric', + Item = 'item()', + Element = 'element()', + Node = 'node()', + DocumentNode = 'document-node()', + // custom types + Container = 'Container', +} diff --git a/packages/ui/src/models/datamapper/view.ts b/packages/ui/src/models/datamapper/view.ts new file mode 100644 index 000000000..3305846a8 --- /dev/null +++ b/packages/ui/src/models/datamapper/view.ts @@ -0,0 +1,5 @@ +export enum CanvasView { + SOURCE_TARGET = 'SourceTarget', + MAPPING_TABLE = 'MappingTable', + NAMESPACE_TABLE = 'NamespaceTable', +} diff --git a/packages/ui/src/models/datamapper/visualization.test.ts b/packages/ui/src/models/datamapper/visualization.test.ts new file mode 100644 index 000000000..10eb7136b --- /dev/null +++ b/packages/ui/src/models/datamapper/visualization.test.ts @@ -0,0 +1,36 @@ +import { DocumentType, NodePath } from './path'; + +describe('visualization.ts', () => { + describe('NodeIdentifier', () => { + it('should represent a document root', () => { + const id = new NodePath('sourceBody:docId://'); + expect(id.documentType).toBe(DocumentType.SOURCE_BODY); + expect(id.documentId).toBe('docId'); + expect(id.pathSegments.length).toBe(0); + }); + + it('should handle without Document ID', () => { + const id = new NodePath('sourceBody://a/b/c'); + expect(id.documentType).toBe(DocumentType.SOURCE_BODY); + expect(id.documentId).toBe(''); + expect(id.pathSegments.length).toBe(3); + const id2 = new NodePath('sourceBody:://a/b/c'); + expect(id2.documentType).toBe(DocumentType.SOURCE_BODY); + expect(id2.documentId).toBe(''); + expect(id2.pathSegments.length).toBe(3); + }); + + it('should handle a colon in the Document ID', () => { + const id = new NodePath('param:global:someGlobalVariable://d/e/f'); + expect(id.documentType).toBe(DocumentType.PARAM); + expect(id.documentId).toBe('global:someGlobalVariable'); + expect(id.pathSegments.length).toBe(3); + }); + + it('should generate a same string', () => { + const expression = 'sourceBody:docId://g/h/i'; + const id = new NodePath(expression); + expect(id.toString()).toEqual(expression); + }); + }); +}); diff --git a/packages/ui/src/models/datamapper/visualization.ts b/packages/ui/src/models/datamapper/visualization.ts new file mode 100644 index 000000000..095915d36 --- /dev/null +++ b/packages/ui/src/models/datamapper/visualization.ts @@ -0,0 +1,134 @@ +import { IDocument, IField, PrimitiveDocument } from './document'; +import { ExpressionItem, FieldItem, IFunctionDefinition, MappingItem, MappingParentType, MappingTree } from './mapping'; +import { DocumentType, NodePath } from './path'; + +export interface NodeData { + title: string; + id: string; + path: NodePath; + isSource: boolean; + isPrimitive: boolean; +} + +export interface TargetNodeData extends NodeData { + mappingTree: MappingTree; + mapping?: MappingParentType; +} + +export type SourceNodeDataType = DocumentNodeData | FieldNodeData; +export type TargetNodeDataType = TargetDocumentNodeData | TargetFieldNodeData; + +export class DocumentNodeData implements NodeData { + constructor(document: IDocument) { + this.title = document.documentId; + this.id = `doc-${document.documentType}-${document.documentId}`; + this.path = NodePath.fromDocument(document.documentType, document.documentId); + this.document = document; + this.isSource = document.documentType !== DocumentType.TARGET_BODY; + this.isPrimitive = document instanceof PrimitiveDocument; + } + + document: IDocument; + title: string; + id: string; + path: NodePath; + isSource: boolean; + isPrimitive: boolean; +} + +export class TargetDocumentNodeData extends DocumentNodeData implements TargetNodeData { + constructor(document: IDocument, mappingTree: MappingTree) { + super(document); + this.mappingTree = mappingTree; + this.mapping = mappingTree; + } + mappingTree: MappingTree; + mapping: MappingTree; +} + +export class FieldNodeData implements NodeData { + constructor( + public parent: NodeData, + public field: IField, + ) { + this.title = field.name; + this.id = field.id; + this.path = NodePath.childOf(parent.path, this.id); + this.isSource = parent.isSource; + this.isPrimitive = parent.isPrimitive; + } + + title: string; + id: string; + path: NodePath; + isSource: boolean; + isPrimitive: boolean; +} + +export class TargetFieldNodeData extends FieldNodeData implements TargetNodeData { + constructor( + public parent: TargetNodeData, + public field: IField, + public mapping?: FieldItem, + ) { + super(parent, field); + this.mappingTree = parent.mappingTree; + } + mappingTree: MappingTree; +} + +export class MappingNodeData implements TargetNodeData { + constructor( + public parent: TargetNodeData, + public mapping: MappingItem, + ) { + this.title = mapping.name; + this.id = mapping.id; + this.path = NodePath.childOf(parent.path, this.id); + this.isPrimitive = parent.isPrimitive; + this.mappingTree = parent.mappingTree; + } + + title: string; + id: string; + path: NodePath; + isSource = false; + isPrimitive: boolean; + mappingTree: MappingTree; +} + +class SimpleNodePath extends NodePath { + constructor(public path: string) { + super(); + } + toString() { + return this.path; + } +} +export class EditorNodeData implements NodeData { + constructor(public mapping: ExpressionItem) {} + id: string = 'editor'; + isPrimitive: boolean = false; + isSource: boolean = false; + path: NodePath = new SimpleNodePath('Editor'); + title: string = 'Editor'; +} + +export class FunctionNodeData implements NodeData { + constructor(public functionDefinition: IFunctionDefinition) { + this.id = functionDefinition.name; + this.title = functionDefinition.displayName; + this.path = new SimpleNodePath('Func:' + functionDefinition.name); + } + + id: string; + isPrimitive: boolean = false; + isSource: boolean = true; + path: NodePath; + title: string; +} + +export interface IMappingLink { + sourceNodePath: string; + targetNodePath: string; +} diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.test.ts new file mode 100644 index 000000000..c8821dd78 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.test.ts @@ -0,0 +1,53 @@ +import { Step } from '@kaoto/camel-catalog/types'; +import { datamapperRouteDefinitionStub as datamapperRouteDefinitionStub } from '../../../../../stubs/data-mapper'; +import { RootNodeMapper } from '../root-node-mapper'; +import { DataMapperNodeMapper } from './datamapper-node-mapper'; +import { noopNodeMapper } from './testing/noop-node-mapper'; + +describe('DataMapperNodeMapper', () => { + let mapper: DataMapperNodeMapper; + let rootNodeMapper: RootNodeMapper; + const routeDefinition = datamapperRouteDefinitionStub; + const path = 'from.steps.0.step'; + + beforeEach(() => { + rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerDefaultMapper(mapper); + rootNodeMapper.registerMapper('log', noopNodeMapper); + + mapper = new DataMapperNodeMapper(rootNodeMapper); + }); + + it('should not return any children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'step' }, routeDefinition); + + expect(vizNode.getChildren()).toBeUndefined(); + }); + + describe('isDataMapperNode', () => { + it('should return true if it has the right id and a xslt component', () => { + const stepDefinition = routeDefinition.from.steps[0].step as Step; + const isDataMapperNode = DataMapperNodeMapper.isDataMapperNode(stepDefinition); + + expect(isDataMapperNode).toBe(true); + }); + + it('should return false if it has the right id but no xslt component', () => { + const stepDefinition = routeDefinition.from.steps[0].step as Step; + stepDefinition.steps![0].to = { uri: 'direct:log' }; + + const isDataMapperNode = DataMapperNodeMapper.isDataMapperNode(stepDefinition); + + expect(isDataMapperNode).toBe(false); + }); + + it('should return false if it has no id but a xslt component', () => { + const stepDefinition = routeDefinition.from.steps[0].step as Step; + stepDefinition.id = 'step-1234'; + + const isDataMapperNode = DataMapperNodeMapper.isDataMapperNode(stepDefinition); + + expect(isDataMapperNode).toBe(false); + }); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts new file mode 100644 index 000000000..334a75187 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/datamapper-node-mapper.ts @@ -0,0 +1,33 @@ +import { Step } from '@kaoto/camel-catalog/types'; +import { DATAMAPPER_ID_PREFIX, isDataMapperNode } from '../../../../../utils'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelRouteVisualEntityData, ICamelElementLookupResult } from '../../support/camel-component-types'; +import { BaseNodeMapper } from './base-node-mapper'; + +export class DataMapperNodeMapper extends BaseNodeMapper { + getVizNodeFromProcessor( + path: string, + _componentLookup: ICamelElementLookupResult, + _entityDefinition: unknown, + ): IVisualizationNode { + const processorName = DATAMAPPER_ID_PREFIX; + + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(processorName, NodeIconType.EIP), + processorName: DATAMAPPER_ID_PREFIX, + isGroup: false, + }; + + const vizNode = createVisualizationNode(processorName, data); + vizNode.setTitle('Kaoto data mapper'); + + return vizNode; + } + + static isDataMapperNode(stepDefinition: Step): boolean { + return isDataMapperNode(stepDefinition); + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.test.ts new file mode 100644 index 000000000..bcad1f671 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.test.ts @@ -0,0 +1,60 @@ +import { RouteDefinition } from '@kaoto/camel-catalog/types'; +import { parse } from 'yaml'; +import { DATAMAPPER_ID_PREFIX } from '../../../../../utils'; +import { RootNodeMapper } from '../root-node-mapper'; +import { DataMapperNodeMapper } from './datamapper-node-mapper'; +import { StepNodeMapper } from './step-node-mapper'; +import { noopNodeMapper } from './testing/noop-node-mapper'; + +describe('StepNodeMapper', () => { + let mapper: StepNodeMapper; + let routeDefinition: RouteDefinition; + let rootNodeMapper: RootNodeMapper; + const path = 'from.steps.0.step'; + + beforeEach(() => { + rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerDefaultMapper(mapper); + rootNodeMapper.registerMapper('log', noopNodeMapper); + rootNodeMapper.registerMapper(DATAMAPPER_ID_PREFIX, noopNodeMapper); + + mapper = new StepNodeMapper(rootNodeMapper); + + routeDefinition = parse(` + from: + id: from-8888 + uri: direct:start + parameters: {} + steps: + - step: + id: step-1234 + steps: + - log: + id: log-1234 + message: \${body}`); + }); + + it('should return children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'step' }, routeDefinition); + + expect(vizNode.getChildren()).toHaveLength(1); + }); + + it('should verify if this step node is a Kaoto DataMapper one', () => { + const dataMapperNodeSpy = jest.spyOn(DataMapperNodeMapper, 'isDataMapperNode'); + + mapper.getVizNodeFromProcessor(path, { processorName: 'step' }, routeDefinition); + + expect(dataMapperNodeSpy).toHaveBeenCalledWith(routeDefinition.from.steps[0].step); + }); + + it('should delegate to the rootNodeMapper if this step node is a Kaoto DataMapper one', () => { + const rootNodeMapperSpy = jest.spyOn(rootNodeMapper, 'getVizNodeFromProcessor'); + const dataMapperNodeSpy = jest.spyOn(DataMapperNodeMapper, 'isDataMapperNode'); + dataMapperNodeSpy.mockReturnValue(true); + + mapper.getVizNodeFromProcessor(path, { processorName: 'step' }, routeDefinition); + + expect(rootNodeMapperSpy).toHaveBeenCalledWith(path, { processorName: DATAMAPPER_ID_PREFIX }, routeDefinition); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.ts new file mode 100644 index 000000000..ddb7dbd31 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/step-node-mapper.ts @@ -0,0 +1,45 @@ +import { ProcessorDefinition, Step } from '@kaoto/camel-catalog/types'; +import { DATAMAPPER_ID_PREFIX, getValue } from '../../../../../utils'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelRouteVisualEntityData, ICamelElementLookupResult } from '../../support/camel-component-types'; +import { BaseNodeMapper } from './base-node-mapper'; +import { DataMapperNodeMapper } from './datamapper-node-mapper'; + +export class StepNodeMapper extends BaseNodeMapper { + getVizNodeFromProcessor( + path: string, + _componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const processorName: keyof ProcessorDefinition = 'step'; + + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(processorName, NodeIconType.EIP), + processorName, + isGroup: true, + }; + + const stepDefinition: Step = getValue(entityDefinition, path); + if (DataMapperNodeMapper.isDataMapperNode(stepDefinition)) { + return this.rootNodeMapper.getVizNodeFromProcessor( + path, + { + processorName: DATAMAPPER_ID_PREFIX, + }, + entityDefinition, + ); + } + + const vizNode = createVisualizationNode(processorName, data); + + const children = this.getChildrenFromBranch(`${path}.steps`, entityDefinition); + children.forEach((child) => { + vizNode.addChild(child); + }); + + return vizNode; + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts index 3ea9ec016..408fdbf4d 100644 --- a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts +++ b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts @@ -1,6 +1,9 @@ +import { DATAMAPPER_ID_PREFIX } from '../../../../utils'; import { BaseNodeMapper } from './mappers/base-node-mapper'; import { ChoiceNodeMapper } from './mappers/choice-node-mapper'; +import { DataMapperNodeMapper } from './mappers/datamapper-node-mapper'; import { OtherwiseNodeMapper } from './mappers/otherwise-node-mapper'; +import { StepNodeMapper } from './mappers/step-node-mapper'; import { WhenNodeMapper } from './mappers/when-node-mapper'; import { NodeMapperService } from './node-mapper.service'; import { RootNodeMapper } from './root-node-mapper'; @@ -16,5 +19,7 @@ describe('NodeMapperService', () => { expect(registerMapperSpy).toHaveBeenCalledWith('choice', expect.any(ChoiceNodeMapper)); expect(registerMapperSpy).toHaveBeenCalledWith('when', expect.any(WhenNodeMapper)); expect(registerMapperSpy).toHaveBeenCalledWith('otherwise', expect.any(OtherwiseNodeMapper)); + expect(registerMapperSpy).toHaveBeenCalledWith('step', expect.any(StepNodeMapper)); + expect(registerMapperSpy).toHaveBeenCalledWith(DATAMAPPER_ID_PREFIX, expect.any(DataMapperNodeMapper)); }); }); diff --git a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts index 2f60395d6..50548a542 100644 --- a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts +++ b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts @@ -1,8 +1,11 @@ +import { DATAMAPPER_ID_PREFIX } from '../../../../utils'; import { IVisualizationNode } from '../../base-visual-entity'; import { ICamelElementLookupResult } from '../support/camel-component-types'; import { BaseNodeMapper } from './mappers/base-node-mapper'; import { ChoiceNodeMapper } from './mappers/choice-node-mapper'; +import { DataMapperNodeMapper } from './mappers/datamapper-node-mapper'; import { OtherwiseNodeMapper } from './mappers/otherwise-node-mapper'; +import { StepNodeMapper } from './mappers/step-node-mapper'; import { WhenNodeMapper } from './mappers/when-node-mapper'; import { MulticastNodeMapper } from './mappers/multicast-node-mapper'; import { LoadBalanceNodeMapper } from './mappers/loadbalance-node-mapper'; @@ -34,6 +37,8 @@ export class NodeMapperService { this.rootNodeMapper.registerMapper('choice', new ChoiceNodeMapper(this.rootNodeMapper)); this.rootNodeMapper.registerMapper('when', new WhenNodeMapper(this.rootNodeMapper)); this.rootNodeMapper.registerMapper('otherwise', new OtherwiseNodeMapper(this.rootNodeMapper)); + this.rootNodeMapper.registerMapper('step', new StepNodeMapper(this.rootNodeMapper)); + this.rootNodeMapper.registerMapper(DATAMAPPER_ID_PREFIX, new DataMapperNodeMapper(this.rootNodeMapper)); this.rootNodeMapper.registerMapper('multicast', new MulticastNodeMapper(this.rootNodeMapper)); this.rootNodeMapper.registerMapper('loadBalance', new LoadBalanceNodeMapper(this.rootNodeMapper)); } diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts index 15131a4c4..b3e8ee1b3 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts @@ -1,8 +1,9 @@ import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; import { parse } from 'yaml'; -import { getCamelRandomId } from '../../../../camel-utils/camel-random-id'; +import { getCamelRandomId, getHexaDecimalRandomId } from '../../../../camel-utils/camel-random-id'; import { DefinedComponent } from '../../../camel-catalog-index'; import { CatalogKind } from '../../../catalog-kind'; +import { XSLT_COMPONENT_NAME } from '../../../../utils'; /** * CamelComponentDefaultService @@ -170,6 +171,17 @@ export class CamelComponentDefaultService { simple: {} `); + case 'kaoto-datamapper' as keyof ProcessorDefinition: + return parse(` + step: + id: ${getHexaDecimalRandomId('kaoto-datamapper')} + steps: + - to: + id: ${getCamelRandomId('kaoto-datamapper-xslt')} + uri: ${XSLT_COMPONENT_NAME} + parameters: {} + `); + default: return { [processorName]: { diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts index aa25b5e13..5c146520a 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts @@ -1,6 +1,7 @@ import catalogLibrary from '@kaoto/camel-catalog/index.json'; import { CatalogLibrary, ProcessorDefinition } from '@kaoto/camel-catalog/types'; import { getFirstCatalogMap } from '../../../../stubs/test-load-catalog'; +import { DATAMAPPER_ID_PREFIX, XSLT_COMPONENT_NAME } from '../../../../utils'; import { ICamelProcessorDefinition } from '../../../camel-processors-catalog'; import { CatalogKind } from '../../../catalog-kind'; import { NodeLabelType } from '../../../settings/settings.model'; @@ -295,6 +296,19 @@ describe('CamelComponentSchemaService', () => { ['from.steps.3.choice.when.0', {}, { processorName: 'when' }], ['from.steps.3.choice.otherwise', {}, { processorName: 'otherwise' }], ['from.steps.3.choice.otherwise', undefined, { processorName: 'otherwise' }], + ['from.steps.0.step', undefined, { processorName: 'step' }], + ['from.steps.0.step', { id: 'step-1234' }, { processorName: 'step' }], + ['from.steps.0.step', { id: `${DATAMAPPER_ID_PREFIX}-1234`, steps: [] }, { processorName: 'step' }], + [ + 'from.steps.0.step', + { id: `modified-${DATAMAPPER_ID_PREFIX}-1234`, steps: [{ to: { uri: `${XSLT_COMPONENT_NAME}:mapping.xsl` } }] }, + { processorName: 'step' }, + ], + [ + 'from.steps.0.step', + { id: `${DATAMAPPER_ID_PREFIX}-1234`, steps: [{ to: { uri: `${XSLT_COMPONENT_NAME}:mapping.xsl` } }] }, + { processorName: DATAMAPPER_ID_PREFIX }, + ], ])('should return the processor and component name for %s', (path, definition, result) => { const camelElementLookup = CamelComponentSchemaService.getCamelComponentLookup(path, definition); @@ -392,6 +406,8 @@ describe('CamelComponentSchemaService', () => { { id: 'interceptSendToEndpoint-1234' }, 'interceptSendToEndpoint-1234', ], + [{ processorName: 'step' }, { id: 'kaoto-datamapper-1234' }, 'kaoto-datamapper-1234'], + [{ processorName: 'step' }, { id: 'step-1234' }, 'step-1234'], ] as Array<[ICamelElementLookupResult, unknown, string]>)( 'should return the processor name if the component name is not provided: %s [%s]', (componentLookup, definition, result) => { diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts index 4a13a6552..5edebd8ae 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts @@ -1,6 +1,13 @@ import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; import { cloneDeep } from 'lodash'; -import { CamelUriHelper, ParsedParameters, getValue, isDefined } from '../../../../utils'; +import { + CamelUriHelper, + DATAMAPPER_ID_PREFIX, + ParsedParameters, + getValue, + isDataMapperNode, + isDefined, +} from '../../../../utils'; import { ICamelComponentDefinition } from '../../../camel-components-catalog'; import { CatalogKind } from '../../../catalog-kind'; import { IKameletDefinition } from '../../../kamelets-catalog'; @@ -92,6 +99,7 @@ export class CamelComponentSchemaService { case 'intercept' as keyof ProcessorDefinition: case 'interceptFrom' as keyof ProcessorDefinition: case 'interceptSendToEndpoint' as keyof ProcessorDefinition: + case 'step': return id ?? camelElementLookup.processorName; case 'from' as keyof ProcessorDefinition: @@ -262,6 +270,12 @@ export class CamelComponentSchemaService { return { processorName }; } + if (processorName === 'step' && isDataMapperNode(definition)) { + return { + processorName: DATAMAPPER_ID_PREFIX, + }; + } + switch (processorName) { case 'from' as keyof ProcessorDefinition: return { diff --git a/packages/ui/src/multiplying-architecture/KaotoBridge.tsx b/packages/ui/src/multiplying-architecture/KaotoBridge.tsx index b1f3dbe08..736843e85 100644 --- a/packages/ui/src/multiplying-architecture/KaotoBridge.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoBridge.tsx @@ -1,14 +1,32 @@ import { ChannelType, EditorApi, StateControlCommand } from '@kie-tools-core/editor/dist/api'; import { Notification } from '@kie-tools-core/notifications/dist/api'; -import { PropsWithChildren, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef } from 'react'; +import { VisualizationProvider } from '@patternfly/react-topology'; +import { + PropsWithChildren, + forwardRef, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useMemo, + useRef, +} from 'react'; +import { NodeInteractionAddonProvider } from '../components/registers/interactions/node-interaction-addon.provider'; +import { RegisterComponents } from '../components/registers/RegisterComponents'; +import { RegisterNodeInteractionAddons } from '../components/registers/RegisterNodeInteractionAddons'; +import { RenderingProvider } from '../components/RenderingAnchor/rendering.provider'; +import { ControllerService } from '../components/Visualization/Canvas/controller.service'; import { useReload } from '../hooks/reload.hook'; -import { CatalogTilesProvider } from '../providers/catalog-tiles.provider'; -import { CatalogLoaderProvider } from '../providers/catalog.provider'; -import { RuntimeProvider } from '../providers/runtime.provider'; -import { SchemasLoaderProvider } from '../providers/schemas.provider'; -import { SettingsContext } from '../providers/settings.provider'; -import { SourceCodeApiContext } from '../providers/source-code.provider'; -import { VisibleFlowsProvider } from '../providers/visible-flows.provider'; +import { + CatalogLoaderProvider, + CatalogTilesProvider, + MetadataProvider, + RuntimeProvider, + SchemasLoaderProvider, + SettingsContext, + SourceCodeApiContext, + VisibleFlowsProvider, +} from '../providers'; import { EventNotifier } from '../utils'; interface KaotoBridgeProps { @@ -40,114 +58,196 @@ interface KaotoBridgeProps { setNotifications: (path: string, notifications: Notification[]) => void; /** - * ChannelType where the component is running. + * Get metadata querying a Kaoto metadata file using the channel API. + * @param key The key to retrieve the metadata from the Kaoto metadata file */ - channelType: ChannelType; -} + getMetadata(key: string): Promise; -export const KaotoBridge = forwardRef>((props, forwardedRef) => { - const ReloadProvider = useReload(); - const eventNotifier = EventNotifier.getInstance(); - const sourceCodeApiContext = useContext(SourceCodeApiContext); - const sourceCodeRef = useRef(''); - const settingsAdapter = useContext(SettingsContext); - const catalogUrl = settingsAdapter.getSettings().catalogUrl; + /** + * Save metadata to a Kaoto metadata file using the channel API. + * @param key The key to set the metadata + * @param metadata The metadata to be saved + */ + setMetadata(key: string, metadata: T): Promise; /** - * Callback is exposed to the Channel that is called when a new file is opened. - * It sets the originalContent to the received value. + * Retrieve resource content using the channel API. + * @param path The path of the resource */ - const setContent = useCallback( - (_path: string, content: string) => { - /** - * If the new content is the same as the current one, we don't need to update the Editor, - * as it will regenerate the Camel Resource, hence disconnecting the configuration form (if open). - * - * This happens due to the multiplying architecture lifecycle, where the content is set - * after saving the file. - * - * The lifecycle is: - * 1. User edits the file either adding a new node or modifying an existing one using the form - * 2. User saves the file - * 3. The Envelope uses the `getContent` callback to retrieve the new content - * 4. The Envelope sets the new content using the `setContent` callback - * - * At this point, both the new content and the current content are the same, so the Editor - * don't need to be updated. - */ - if (sourceCodeRef.current === content) return; - - sourceCodeApiContext.setCodeAndNotify(content); - sourceCodeRef.current = content; - }, - [sourceCodeApiContext], - ); + getResourceContent(path: string): Promise; /** - * Subscribe to the `entities:updated` event to update the File content. + * Save resource content using the channel API. + * @param path The path of the resource + * @param content The content to be saved */ - useEffect(() => { - const unsubscribeFromEntities = eventNotifier.subscribe('entities:updated', (newContent: string) => { - props.onNewEdit(newContent); - sourceCodeRef.current = newContent; - }); + saveResourceContent(path: string, content: string): Promise; - const unsubscribeFromSourceCode = eventNotifier.subscribe('code:updated', (newContent: string) => { - /** Ignore the first change, from an empty string to the file content */ - if (sourceCodeRef.current !== '') { - props.onNewEdit(newContent); - } - sourceCodeRef.current = newContent; - }); + /** + * Delete resource using the channel API. + * @param path The path of the resource + */ + deleteResource(path: string): Promise; - return () => { - unsubscribeFromEntities(); - unsubscribeFromSourceCode(); - }; - }, [eventNotifier, props]); + /** + * Show a Quick Pick widget and ask the user to select one or more files available in the workspace. + * @param include The filter expression for the files to include + * @param exclude The filter expression for the files to exclude + * @param options The options to pass over to VSCode QuickPick + */ + askUserForFileSelection( + include: string, + exclude?: string, + options?: Record, + ): Promise; /** - * The useImperativeHandler gives the control of the Editor component to who has it's reference, - * making it possible to communicate with the Editor. - * It returns all methods that are determined on the EditorApi. + * ChannelType where the component is running. */ - useImperativeHandle(forwardedRef, () => { - return { - /* Callback is exposed to the Channel to set the content of the file into the current Editor. */ - setContent: (path: string, content: string) => Promise.resolve(setContent(path, content)), - /** - * Callback is exposed to the Channel to retrieve the current value of the Editor. It returns the value of - * the editorContent, which is the state that has the kaoto yaml. - */ - getContent: () => Promise.resolve(sourceCodeRef.current), - getPreview: () => Promise.resolve(undefined), - undo: (): Promise => { - return Promise.resolve(); - }, - redo: (): Promise => { - return Promise.resolve(); + channelType: ChannelType; +} + +export const KaotoBridge = forwardRef>( + ( + { + onNewEdit, + onReady, + children, + getMetadata, + setMetadata, + getResourceContent, + saveResourceContent, + deleteResource, + askUserForFileSelection, + }, + forwardedRef, + ) => { + const ReloadProvider = useReload(); + const controller = useMemo(() => ControllerService.createController(), []); + const eventNotifier = EventNotifier.getInstance(); + const sourceCodeApiContext = useContext(SourceCodeApiContext); + const sourceCodeRef = useRef(''); + const settingsAdapter = useContext(SettingsContext); + const catalogUrl = settingsAdapter.getSettings().catalogUrl; + const metadataApi = useMemo( + () => ({ + getMetadata, + setMetadata, + getResourceContent, + saveResourceContent, + deleteResource, + askUserForFileSelection, + shouldSaveSchema: false, + }), + [getMetadata, setMetadata, getResourceContent, saveResourceContent, deleteResource, askUserForFileSelection], + ); + + /** + * Callback is exposed to the Channel that is called when a new file is opened. + * It sets the originalContent to the received value. + */ + const setContent = useCallback( + (_path: string, content: string) => { + /** + * If the new content is the same as the current one, we don't need to update the Editor, + * as it will regenerate the Camel Resource, hence disconnecting the configuration form (if open). + * + * This happens due to the multiplying architecture lifecycle, where the content is set + * after saving the file. + * + * The lifecycle is: + * 1. User edits the file either adding a new node or modifying an existing one using the form + * 2. User saves the file + * 3. The Envelope uses the `getContent` callback to retrieve the new content + * 4. The Envelope sets the new content using the `setContent` callback + * + * At this point, both the new content and the current content are the same, so the Editor + * don't need to be updated. + */ + if (sourceCodeRef.current === content) return; + + sourceCodeApiContext.setCodeAndNotify(content); + sourceCodeRef.current = content; }, - validate: () => Promise.resolve([]), - setTheme: () => Promise.resolve(), - }; - }); - - /** Set editor as Ready */ - useEffect(() => { - props.onReady(); - }, [props]); - - return ( - - - - - - {props.children} - - - - - - ); -}); + [sourceCodeApiContext], + ); + + /** + * Subscribe to the `entities:updated` event to update the File content. + */ + useEffect(() => { + const unsubscribeFromEntities = eventNotifier.subscribe('entities:updated', (newContent: string) => { + onNewEdit(newContent); + sourceCodeRef.current = newContent; + }); + + const unsubscribeFromSourceCode = eventNotifier.subscribe('code:updated', (newContent: string) => { + /** Ignore the first change, from an empty string to the file content */ + if (sourceCodeRef.current !== '') { + onNewEdit(newContent); + } + sourceCodeRef.current = newContent; + }); + + return () => { + unsubscribeFromEntities(); + unsubscribeFromSourceCode(); + }; + }, [eventNotifier, onNewEdit]); + + /** + * The useImperativeHandler gives the control of the Editor component to who has it's reference, + * making it possible to communicate with the Editor. + * It returns all methods that are determined on the EditorApi. + */ + useImperativeHandle(forwardedRef, () => { + return { + /* Callback is exposed to the Channel to set the content of the file into the current Editor. */ + setContent: (path: string, content: string) => Promise.resolve(setContent(path, content)), + /** + * Callback is exposed to the Channel to retrieve the current value of the Editor. It returns the value of + * the editorContent, which is the state that has the kaoto yaml. + */ + getContent: () => Promise.resolve(sourceCodeRef.current), + getPreview: () => Promise.resolve(undefined), + undo: (): Promise => { + return Promise.resolve(); + }, + redo: (): Promise => { + return Promise.resolve(); + }, + validate: () => Promise.resolve([]), + setTheme: () => Promise.resolve(), + }; + }); + + /** Set editor as Ready */ + useEffect(onReady, [onReady]); + + return ( + + + + + + + + + + + + {children} + + + + + + + + + + + + ); + }, +); diff --git a/packages/ui/src/multiplying-architecture/KaotoEditor.tsx b/packages/ui/src/multiplying-architecture/KaotoEditor.tsx index 4b64a8043..8e4cff65e 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditor.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoEditor.tsx @@ -3,6 +3,7 @@ import { CodeIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import clsx from 'clsx'; import { useContext, useMemo, useRef } from 'react'; import { Link, Outlet, useLocation } from 'react-router-dom'; +import icon_component_datamapper from '../assets/components/datamapper.png'; import bean from '../assets/eip/bean.png'; import camelIcon from '../assets/logo-kaoto.svg'; import { SourceSchemaType } from '../models/camel/source-schema-type'; @@ -15,11 +16,12 @@ const enum TabList { Beans, Metadata, ErrorHandler, + KaotoDataMapper, } const SCHEMA_TABS: Record = { - [SourceSchemaType.Route]: [TabList.Design, TabList.Beans], - [SourceSchemaType.Kamelet]: [TabList.Design, TabList.Beans, TabList.Metadata], + [SourceSchemaType.Route]: [TabList.Design, TabList.Beans, TabList.KaotoDataMapper], + [SourceSchemaType.Kamelet]: [TabList.Design, TabList.Beans, TabList.Metadata, TabList.KaotoDataMapper], [SourceSchemaType.Integration]: [], [SourceSchemaType.KameletBinding]: [TabList.Design, TabList.Metadata, TabList.ErrorHandler], [SourceSchemaType.Pipe]: [TabList.Design, TabList.Metadata, TabList.ErrorHandler], @@ -30,6 +32,11 @@ export const KaotoEditor = () => { const resource = entitiesContext?.camelResource; const inset = useRef({ default: 'insetSm' }); const currentLocation = useLocation(); + const secondSlashIndex = currentLocation.pathname.indexOf('/', 1); + const currentPath = currentLocation.pathname.substring(0, secondSlashIndex !== -1 ? secondSlashIndex : undefined); + const dataMapperLink = currentLocation.pathname.startsWith(Links.DataMapper) + ? currentLocation.pathname + : Links.DataMapper; const availableTabs = useMemo(() => { if (!resource) { @@ -38,6 +45,7 @@ export const KaotoEditor = () => { beans: false, metadata: false, errorHandler: false, + kaotoDataMapper: false, }; } @@ -46,6 +54,7 @@ export const KaotoEditor = () => { beans: SCHEMA_TABS[resource.getType()].indexOf(TabList.Beans) >= 0, metadata: SCHEMA_TABS[resource.getType()].indexOf(TabList.Metadata) >= 0, errorHandler: SCHEMA_TABS[resource.getType()].indexOf(TabList.ErrorHandler) >= 0, + kaotoDataMapper: SCHEMA_TABS[resource.getType()].indexOf(TabList.KaotoDataMapper) >= 0, }; }, [resource]); @@ -55,13 +64,14 @@ export const KaotoEditor = () => { inset={inset.current} isBox unmountOnExit - activeKey={currentLocation.pathname} + activeKey={currentPath} aria-label="Tabs in the Kaoto editor" role="region" > {availableTabs.design && ( @@ -81,6 +91,7 @@ export const KaotoEditor = () => { {availableTabs.beans && ( @@ -100,6 +111,7 @@ export const KaotoEditor = () => { {availableTabs.metadata && ( @@ -117,6 +129,7 @@ export const KaotoEditor = () => { {availableTabs.errorHandler && ( @@ -130,6 +143,26 @@ export const KaotoEditor = () => { /> )} + + {availableTabs.kaotoDataMapper && ( + + + + + DataMapper icon + + + DataMapper + + } + aria-label="DataMapper" + /> + + )}
    { kogitoWorkspace_newEdit: getNotificationMock(), kogitoWorkspace_openFile: getNotificationMock(), }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - requests: {} as any, + requests: { + getMetadata: jest.fn(), + setMetadata: jest.fn(), + getResourceContent: jest.fn(), + saveResourceContent: jest.fn(), + } as unknown as ApiRequests, // eslint-disable-next-line @typescript-eslint/no-explicit-any shared: {} as any, }, @@ -194,6 +199,30 @@ describe('KaotoEditorApp', () => { StateControlCommand.REDO, ); }); + + it('should delegate to the channelApi getting metadata from the Kaoto metadata file', async () => { + await kaotoEditorApp.getMetadata('path'); + + expect(envelopeContext.channelApi.requests.getMetadata).toHaveBeenCalledWith('path'); + }); + + it('should delegate to the channelApi setting metadata from the Kaoto metadata file', async () => { + await kaotoEditorApp.setMetadata('key', 'value'); + + expect(envelopeContext.channelApi.requests.setMetadata).toHaveBeenCalledWith('key', 'value'); + }); + + it('should delegate to the channelApi getting a file resource content', async () => { + await kaotoEditorApp.getResourceContent('path'); + + expect(envelopeContext.channelApi.requests.getResourceContent).toHaveBeenCalledWith('path'); + }); + + it('should delegate to the channelApi saving file resource content', async () => { + await kaotoEditorApp.saveResourceContent('path', 'content'); + + expect(envelopeContext.channelApi.requests.saveResourceContent).toHaveBeenCalledWith('path', 'content'); + }); }); const getNotificationMock = () => ({ diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx b/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx index 25a44c358..565c05c08 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx @@ -36,6 +36,12 @@ export class KaotoEditorApp implements Editor { this.sendNewEdit = this.sendNewEdit.bind(this); this.sendNotifications = this.sendNotifications.bind(this); this.sendStateControlCommand = this.sendStateControlCommand.bind(this); + this.getMetadata = this.getMetadata.bind(this); + this.setMetadata = this.setMetadata.bind(this); + this.getResourceContent = this.getResourceContent.bind(this); + this.saveResourceContent = this.saveResourceContent.bind(this); + this.deleteResource = this.deleteResource.bind(this); + this.askUserForFileSelection = this.askUserForFileSelection.bind(this); } async setContent(path: string, content: string): Promise { @@ -94,6 +100,34 @@ export class KaotoEditorApp implements Editor { this.envelopeContext.channelApi.notifications.kogitoEditor_stateControlCommandUpdate.send(command); } + async getMetadata(key: string): Promise { + return this.envelopeContext.channelApi.requests.getMetadata(key); + } + + async setMetadata(key: string, preferences: T): Promise { + return this.envelopeContext.channelApi.requests.setMetadata(key, preferences); + } + + async getResourceContent(path: string): Promise { + return this.envelopeContext.channelApi.requests.getResourceContent(path); + } + + async saveResourceContent(path: string, content: string): Promise { + return this.envelopeContext.channelApi.requests.saveResourceContent(path, content); + } + + async deleteResource(path: string): Promise { + return this.envelopeContext.channelApi.requests.deleteResource(path); + } + + async askUserForFileSelection( + include: string, + exclude?: string, + options?: Record, + ): Promise { + return this.envelopeContext.channelApi.requests.askUserForFileSelection(include, exclude, options); + } + af_componentRoot() { return ( @@ -106,6 +140,12 @@ export class KaotoEditorApp implements Editor { onNewEdit={this.sendNewEdit} setNotifications={this.sendNotifications} onStateControlCommandUpdate={this.sendStateControlCommand} + getMetadata={this.getMetadata} + setMetadata={this.setMetadata} + getResourceContent={this.getResourceContent} + saveResourceContent={this.saveResourceContent} + deleteResource={this.deleteResource} + askUserForFileSelection={this.askUserForFileSelection} > diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorRouter.tsx b/packages/ui/src/multiplying-architecture/KaotoEditorRouter.tsx index 03a24f976..d8fa0fe39 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorRouter.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoEditorRouter.tsx @@ -25,6 +25,14 @@ export const kaotoEditorRouter = createHashRouter([ path: Links.PipeErrorHandler, lazy: async () => import('../pages/PipeErrorHandler'), }, + { + path: Links.DataMapper, + lazy: async () => import('../pages/DataMapperHowTo'), + }, + { + path: `${Links.DataMapper}/:id`, + lazy: async () => import('../pages/DataMapper'), + }, ], }, ]); diff --git a/packages/ui/src/pages/DataMapper/DataMapperPage.tsx b/packages/ui/src/pages/DataMapper/DataMapperPage.tsx new file mode 100644 index 000000000..ef473b980 --- /dev/null +++ b/packages/ui/src/pages/DataMapper/DataMapperPage.tsx @@ -0,0 +1,35 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { useVisualizationController } from '@patternfly/react-topology'; +import { FunctionComponent } from 'react'; +import { useParams } from 'react-router-dom'; +import DataMapper from '../../components/DataMapper/DataMapper'; +import { IVisualizationNode } from '../../models'; +import { getVisualizationNodesFromGraph } from '../../utils'; + +export const DataMapperPage: FunctionComponent = () => { + const controller = useVisualizationController(); + const params = useParams(); + + const vizNode = getVisualizationNodesFromGraph( + controller.getGraph(), + (node: IVisualizationNode) => node.getComponentSchema()?.definition?.id === params.id, + )[0]; + + return ; +}; + +export default DataMapperPage; diff --git a/packages/ui/src/pages/DataMapper/index.ts b/packages/ui/src/pages/DataMapper/index.ts new file mode 100644 index 000000000..222b76e07 --- /dev/null +++ b/packages/ui/src/pages/DataMapper/index.ts @@ -0,0 +1 @@ +export * from './router-exports'; diff --git a/packages/ui/src/pages/DataMapper/router-exports.tsx b/packages/ui/src/pages/DataMapper/router-exports.tsx new file mode 100644 index 000000000..c2ea9cf61 --- /dev/null +++ b/packages/ui/src/pages/DataMapper/router-exports.tsx @@ -0,0 +1,3 @@ +import { DataMapperPage } from './DataMapperPage'; + +export const element = ; diff --git a/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.scss b/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.scss new file mode 100644 index 000000000..c26c733d8 --- /dev/null +++ b/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.scss @@ -0,0 +1,26 @@ +.datamapper-howto { + flex-flow: column; + justify-content: start; + font-size: var(--pf-v5-c-card__body--FontSize); + padding: 0 var(--pf-v5-global--spacer--md); + + & ol { + margin-bottom: calc(var(--pf-v5-global--spacer--xl) * 2); + + li { + margin-bottom: var(--pf-v5-global--spacer--xl); + border-bottom: 1px solid var(--pf-v5-global--palette--black-300); + + &::marker { + font-weight: bold; + } + } + } + + img { + margin: var(--pf-v5-global--spacer--md) 0; + border: 1px solid var(--pf-v5-global--palette--black-300); + padding: var(--pf-v5-global--spacer--sm); + border-radius: calc(var(--pf-v5-global--BorderRadius--sm) * 2); + } +} diff --git a/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.tsx b/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.tsx new file mode 100644 index 000000000..a3af5355d --- /dev/null +++ b/packages/ui/src/pages/DataMapperHowTo/DataMapperHowToPage.tsx @@ -0,0 +1,139 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { Bullseye, List, ListComponent, ListItem, OrderType, Stack, StackItem, Title } from '@patternfly/react-core'; +import { FunctionComponent } from 'react'; +import icon_component_datamapper from '../../assets/components/datamapper.png'; +import './DataMapperHowToPage.scss'; +import catalog_datamapper from '../../assets/data-mapper/kaoto-datamapper-catalog.png'; +import datamapper_step from '../../assets/data-mapper/datamapper-step.png'; +import configure_button from '../../assets/data-mapper/configure-button.png'; +import kaoto_design from '../../assets/data-mapper/kaoto-design.png'; +import source_target from '../../assets/data-mapper/source-target.png'; +import add_parameter from '../../assets/data-mapper/add-parameter.png'; +import add_parameter_confirm from '../../assets/data-mapper/add_parameter_confirm.png'; +import attach_schema_source_body from '../../assets/data-mapper/attach-schema-source-body.png'; +import attach_schema_target_body from '../../assets/data-mapper/attach-schema-target-body.png'; +import attach_schema_parameter from '../../assets/data-mapper/attach-schema-parameter.png'; +import data_mapping from '../../assets/data-mapper/data-mapping.png'; + +export const DataMapperHowToPage: FunctionComponent = () => { + return ( + + Kaoto DataMapper icon + Looking to configure the Kaoto DataMapper? + +
    + + +

    + The Kaoto DataMapper is a powerful graphical tool that allows you to design data mapping between different + formats with Drag and Drop. +

    +
    + +

    To get started, follow the steps below:

    + + +

    + Add a Kaoto DataMapper step in your Camel route. When you Append,{' '} + Prepend or Replace a step in the Kaoto Design view, you can find the{' '} + Kaoto DataMapper step in the catalog. +

    + Kaoto DataMapper step in the catalog +
    + +

    + Click the added Kaoto DataMapper step in the Kaoto Design view. It opens up{' '} + Kaoto DataMapper config form. +

    + Kaoto DataMapper step +
    + +

    + In the Kaoto DataMapper config form, click Configure button. The + blank Kaoto DataMapper canvas will launch. +

    + Configure button +
    + +

    + In the DataMapper canvas, you can see Source at the left and Target{' '} + at the right. +

    + Source and Target +

    + The Source is in an other word input, which the DataMapper step reads the data from. + This is mapped to the incoming Camel Message to the DataMapper step, as well as Camel{' '} + Variables and Exchange Properties. +

    +

    + The Target is in an other word output, which the DataMapper step writes the data to. + This is mapped to the outgoing Camel Message from the DataMapper step. +

    +
    + +

    + Add Parameter(s). The Parameters inside the Source is mapped to any + of incoming Camel Variables, Message Headers and{' '} + Exchange Properties. For example, if there is an incoming Camel Variable{' '} + "orderSequence", you can consume it by adding a Parameter{' '} + "orderSequence" in the DataMapper Source section. +

    +

    + Add Parameter +

    +

    + Add Parameter orderSequence +

    +
    + +

    + Attach Document schema(s). Attach the schema file of the incoming Camel Message Body{' '} + to the Source Body if it's a structured data. +

    + Attach source body schema +

    + Attach the schema file of the outgoing Camel Message Body to the{' '} + Target Body if it's a structured data. +

    + Attach target body schema +

    + If any of the Parameter(s) is/are structured data, you can also attach the schema for{' '} + Parameters as well. +

    + Attach Parameter schema +
    + +

    + Now you can start designing your data mappings by drag and drop between Source and{' '} + Target +

    + Data Mapping +
    + +

    + Once you finish designing data mappings, click Design tab to go back to the Kaoto + main canvas. +

    + Design tab +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/packages/ui/src/pages/DataMapperHowTo/index.ts b/packages/ui/src/pages/DataMapperHowTo/index.ts new file mode 100644 index 000000000..222b76e07 --- /dev/null +++ b/packages/ui/src/pages/DataMapperHowTo/index.ts @@ -0,0 +1 @@ +export * from './router-exports'; diff --git a/packages/ui/src/pages/DataMapperHowTo/router-exports.tsx b/packages/ui/src/pages/DataMapperHowTo/router-exports.tsx new file mode 100644 index 000000000..e695780d1 --- /dev/null +++ b/packages/ui/src/pages/DataMapperHowTo/router-exports.tsx @@ -0,0 +1,3 @@ +import { DataMapperHowToPage } from './DataMapperHowToPage'; + +export const element = ; diff --git a/packages/ui/src/pages/DataMapperNotYetInBrowser/DataMapperNotYetInBrowser.tsx b/packages/ui/src/pages/DataMapperNotYetInBrowser/DataMapperNotYetInBrowser.tsx new file mode 100644 index 000000000..c62a09605 --- /dev/null +++ b/packages/ui/src/pages/DataMapperNotYetInBrowser/DataMapperNotYetInBrowser.tsx @@ -0,0 +1,40 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { Brand, Panel, PanelHeader, PanelMain, PanelMainBody, Title } from '@patternfly/react-core'; +import { FunctionComponent } from 'react'; +import icon_component_datamapper from '../../assets/components/datamapper.png'; + +export const DataMapperNotYetInBrowserPage: FunctionComponent = () => { + return ( + + + + The Kaoto DataMapper cannot be configured + + + +

    + At the moment, the Kaoto DataMapper cannot be configured using the browser directly. Please use the VS Code + extension for an enhanced experience. The Kaoto extension is bundled in the  + + Extension Pack for Apache Camel + +

    + + + + ); +}; diff --git a/packages/ui/src/pages/DataMapperNotYetInBrowser/index.ts b/packages/ui/src/pages/DataMapperNotYetInBrowser/index.ts new file mode 100644 index 000000000..222b76e07 --- /dev/null +++ b/packages/ui/src/pages/DataMapperNotYetInBrowser/index.ts @@ -0,0 +1 @@ +export * from './router-exports'; diff --git a/packages/ui/src/pages/DataMapperNotYetInBrowser/router-exports.tsx b/packages/ui/src/pages/DataMapperNotYetInBrowser/router-exports.tsx new file mode 100644 index 000000000..a06b6ee5d --- /dev/null +++ b/packages/ui/src/pages/DataMapperNotYetInBrowser/router-exports.tsx @@ -0,0 +1,3 @@ +import { DataMapperNotYetInBrowserPage } from './DataMapperNotYetInBrowser'; + +export const element = ; diff --git a/packages/ui/src/providers/__snapshots__/catalog.provider.test.tsx.snap b/packages/ui/src/providers/__snapshots__/catalog.provider.test.tsx.snap new file mode 100644 index 000000000..c48b53171 --- /dev/null +++ b/packages/ui/src/providers/__snapshots__/catalog.provider.test.tsx.snap @@ -0,0 +1,341 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CatalogLoaderProvider should load the CamelCatalogService 1`] = ` +{ + "exchangeProperties": { + "CamelStepId": { + "autowired": false, + "deprecated": false, + "description": "The id of the Step EIP", + "displayName": "Step Id", + "index": 0, + "javaType": "String", + "kind": "exchangeProperty", + "label": "producer", + "required": false, + "secret": false, + }, + }, + "model": { + "abstract": false, + "deprecated": false, + "description": "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "input": true, + "javaType": "org.apache.camel.model.StepDefinition", + "kind": "model", + "label": "transformation", + "name": "kaoto-datamapper", + "output": true, + "supportLevel": "Stable", + "title": "Kaoto DataMapper", + }, + "properties": { + "description": { + "autowired": false, + "deprecated": false, + "description": "Sets the description of this node", + "displayName": "Description", + "group": "common", + "index": 0, + "javaType": "java.lang.String", + "kind": "attribute", + "required": false, + "secret": false, + "type": "string", + }, + "disabled": { + "autowired": false, + "defaultValue": false, + "deprecated": false, + "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "displayName": "Disabled", + "group": "advanced", + "index": 1, + "javaType": "java.lang.Boolean", + "kind": "attribute", + "label": "advanced", + "required": false, + "secret": false, + "type": "boolean", + }, + "outputs": { + "autowired": false, + "deprecated": false, + "displayName": "Outputs", + "group": "common", + "index": 2, + "javaType": "java.util.List", + "kind": "element", + "oneOf": [ + "aggregate", + "bean", + "choice", + "circuitBreaker", + "claimCheck", + "convertBodyTo", + "convertHeaderTo", + "convertVariableTo", + "delay", + "doCatch", + "doFinally", + "doTry", + "dynamicRouter", + "enrich", + "filter", + "idempotentConsumer", + "intercept", + "interceptFrom", + "interceptSendToEndpoint", + "kamelet", + "loadBalance", + "log", + "loop", + "marshal", + "multicast", + "onCompletion", + "onException", + "onFallback", + "otherwise", + "pausable", + "pipeline", + "policy", + "pollEnrich", + "process", + "recipientList", + "removeHeader", + "removeHeaders", + "removeProperties", + "removeProperty", + "removeVariable", + "resequence", + "resumable", + "rollback", + "routingSlip", + "saga", + "sample", + "script", + "serviceCall", + "setBody", + "setExchangePattern", + "setHeader", + "setHeaders", + "setProperty", + "setVariable", + "setVariables", + "sort", + "split", + "step", + "stop", + "threads", + "throttle", + "throwException", + "to", + "toD", + "transacted", + "transform", + "unmarshal", + "validate", + "when", + "whenSkipSendToEndpoint", + "wireTap", + ], + "required": true, + "secret": false, + "type": "array", + }, + }, + "propertiesSchema": { + "$comment": "steps", + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "description": "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "properties": { + "description": { + "description": "Sets the description of this node", + "group": "common", + "title": "Description", + "type": "string", + }, + "disabled": { + "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "group": "advanced", + "title": "Disabled", + "type": "boolean", + }, + }, + "required": [], + "title": "Kaoto DataMapper", + "type": "object", + }, +} +`; + +exports[`CatalogLoaderProvider should load the CamelCatalogService 2`] = ` +{ + "exchangeProperties": { + "CamelStepId": { + "autowired": false, + "deprecated": false, + "description": "The id of the Step EIP", + "displayName": "Step Id", + "index": 0, + "javaType": "String", + "kind": "exchangeProperty", + "label": "producer", + "required": false, + "secret": false, + }, + }, + "model": { + "abstract": false, + "deprecated": false, + "description": "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "input": true, + "javaType": "org.apache.camel.model.StepDefinition", + "kind": "model", + "label": "transformation", + "name": "kaoto-datamapper", + "output": true, + "supportLevel": "Stable", + "title": "Kaoto DataMapper", + }, + "properties": { + "description": { + "autowired": false, + "deprecated": false, + "description": "Sets the description of this node", + "displayName": "Description", + "group": "common", + "index": 0, + "javaType": "java.lang.String", + "kind": "attribute", + "required": false, + "secret": false, + "type": "string", + }, + "disabled": { + "autowired": false, + "defaultValue": false, + "deprecated": false, + "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "displayName": "Disabled", + "group": "advanced", + "index": 1, + "javaType": "java.lang.Boolean", + "kind": "attribute", + "label": "advanced", + "required": false, + "secret": false, + "type": "boolean", + }, + "outputs": { + "autowired": false, + "deprecated": false, + "displayName": "Outputs", + "group": "common", + "index": 2, + "javaType": "java.util.List", + "kind": "element", + "oneOf": [ + "aggregate", + "bean", + "choice", + "circuitBreaker", + "claimCheck", + "convertBodyTo", + "convertHeaderTo", + "convertVariableTo", + "delay", + "doCatch", + "doFinally", + "doTry", + "dynamicRouter", + "enrich", + "filter", + "idempotentConsumer", + "intercept", + "interceptFrom", + "interceptSendToEndpoint", + "kamelet", + "loadBalance", + "log", + "loop", + "marshal", + "multicast", + "onCompletion", + "onException", + "onFallback", + "otherwise", + "pausable", + "pipeline", + "policy", + "pollEnrich", + "process", + "recipientList", + "removeHeader", + "removeHeaders", + "removeProperties", + "removeProperty", + "removeVariable", + "resequence", + "resumable", + "rollback", + "routingSlip", + "saga", + "sample", + "script", + "serviceCall", + "setBody", + "setExchangePattern", + "setHeader", + "setHeaders", + "setProperty", + "setVariable", + "setVariables", + "sort", + "split", + "step", + "stop", + "threads", + "throttle", + "throwException", + "to", + "toD", + "transacted", + "transform", + "unmarshal", + "validate", + "when", + "whenSkipSendToEndpoint", + "wireTap", + ], + "required": true, + "secret": false, + "type": "array", + }, + }, + "propertiesSchema": { + "$comment": "steps", + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "description": "The Kaoto DataMapper maps and transforms data using a set of Camel processors", + "properties": { + "description": { + "description": "Sets the description of this node", + "group": "common", + "title": "Description", + "type": "string", + }, + "disabled": { + "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "group": "advanced", + "title": "Disabled", + "type": "boolean", + }, + }, + "required": [], + "title": "Kaoto DataMapper", + "type": "object", + }, +} +`; diff --git a/packages/ui/src/providers/catalog.provider.test.tsx b/packages/ui/src/providers/catalog.provider.test.tsx index 41e31f7ed..936a6b059 100644 --- a/packages/ui/src/providers/catalog.provider.test.tsx +++ b/packages/ui/src/providers/catalog.provider.test.tsx @@ -209,6 +209,7 @@ describe('CatalogLoaderProvider', () => { ) { expect(call[0]).toEqual(CatalogKind.Processor); expect(Object.values(call[1])[0]).toEqual('dummy-data'); + expect(Object.values(call[1])[1]).toMatchSnapshot(); count++; } else if ( Object.keys(call[1])[0].includes( @@ -217,6 +218,7 @@ describe('CatalogLoaderProvider', () => { ) { expect(call[0]).toEqual(CatalogKind.Pattern); expect(Object.values(call[1])[0]).toEqual('dummy-data'); + expect(Object.values(call[1])[1]).toMatchSnapshot(); count++; } else if ( Object.keys(call[1])[0].includes( diff --git a/packages/ui/src/providers/catalog.provider.tsx b/packages/ui/src/providers/catalog.provider.tsx index e3149f24e..8d94f9b00 100644 --- a/packages/ui/src/providers/catalog.provider.tsx +++ b/packages/ui/src/providers/catalog.provider.tsx @@ -1,5 +1,6 @@ import { Text, TextVariants } from '@patternfly/react-core'; import { FunctionComponent, PropsWithChildren, createContext, useEffect, useState } from 'react'; +import kaotoPatterns from '../assets/kaoto-patterns/kaoto-patterns.json'; import { LoadDefaultCatalog } from '../components/LoadDefaultCatalog'; import { Loading } from '../components/Loading'; import { useRuntimeContext } from '../hooks/useRuntimeContext/useRuntimeContext'; @@ -89,8 +90,14 @@ export const CatalogLoaderProvider: FunctionComponent = (prop ]); CamelCatalogService.setCatalogKey(CatalogKind.Component, camelComponents.body); - CamelCatalogService.setCatalogKey(CatalogKind.Processor, camelModels.body); - CamelCatalogService.setCatalogKey(CatalogKind.Pattern, camelPatterns.body); + CamelCatalogService.setCatalogKey(CatalogKind.Processor, { + ...camelModels.body, + ...(kaotoPatterns as unknown as ComponentsCatalog[CatalogKind.Pattern]), + }); + CamelCatalogService.setCatalogKey(CatalogKind.Pattern, { + ...camelPatterns.body, + ...(kaotoPatterns as unknown as ComponentsCatalog[CatalogKind.Pattern]), + }); CamelCatalogService.setCatalogKey(CatalogKind.Entity, camelEntities.body); CamelCatalogService.setCatalogKey(CatalogKind.Language, camelLanguages.body); CamelCatalogService.setCatalogKey(CatalogKind.Dataformat, camelDataformats.body); diff --git a/packages/ui/src/providers/datamapper-canvas.provider.test.tsx b/packages/ui/src/providers/datamapper-canvas.provider.test.tsx new file mode 100644 index 000000000..d166183d9 --- /dev/null +++ b/packages/ui/src/providers/datamapper-canvas.provider.test.tsx @@ -0,0 +1,121 @@ +import { DataMapperCanvasProvider } from './datamapper-canvas.provider'; +import { render } from '@testing-library/react'; +import { DataMapperProvider } from './datamapper.provider'; +import { FunctionComponent, PropsWithChildren, useEffect } from 'react'; +import { SourceTargetView } from '../components/View/SourceTargetView'; +import { useDataMapper } from '../hooks/useDataMapper'; +import { DocumentType } from '../models/datamapper/path'; +import { useCanvas } from '../hooks/useCanvas'; +import { BODY_DOCUMENT_ID } from '../models/datamapper/document'; +import { screen } from '@testing-library/react'; +import { TestUtil } from '../stubs/data-mapper'; + +describe('CanvasProvider', () => { + it('should render', async () => { + render( + + +
    + + , + ); + expect(await screen.findByTestId('testdiv')).toBeInTheDocument(); + }); + + it('should fail if not within DataMapperProvider', () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const thrower = () => { + render(); + }; + expect(thrower).toThrow(); + consoleSpy.mockRestore(); + }); + + it('clearNodeReferencesForPath() should clear for the path', async () => { + let first = false; + let second = false; + let beforeNodePaths: string[] = []; + let afterNodePaths: string[] = []; + const LoadDocuments: FunctionComponent = ({ children }) => { + const { setSourceBodyDocument, setTargetBodyDocument } = useDataMapper(); + const { clearNodeReferencesForPath, getAllNodePaths, reloadNodeReferences } = useCanvas(); + useEffect(() => { + const sourceDoc = TestUtil.createSourceOrderDoc(); + setSourceBodyDocument(sourceDoc); + const targetDoc = TestUtil.createTargetOrderDoc(); + setTargetBodyDocument(targetDoc); + reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + if (!first) { + first = true; + return; + } + if (!second) { + second = true; + beforeNodePaths = getAllNodePaths(); + clearNodeReferencesForPath('sourceBody:ShipOrder.xsd://'); + afterNodePaths = getAllNodePaths(); + } + }, [clearNodeReferencesForPath, getAllNodePaths, reloadNodeReferences]); + return <>{children}; + }; + render( + + + + + + + , + ); + await screen.findAllByText('ShipOrder'); + expect(afterNodePaths.length).toEqual(17); + expect(beforeNodePaths.length).toBeGreaterThan(afterNodePaths.length); + }); + + it('clearNodeReferencesForDocument() should clear for the Document', async () => { + let first = false; + let second = false; + let beforeNodePaths: string[] = []; + let afterNodePaths: string[] = []; + const LoadDocuments: FunctionComponent = ({ children }) => { + const { setSourceBodyDocument, setTargetBodyDocument } = useDataMapper(); + const { clearNodeReferencesForDocument, getAllNodePaths, reloadNodeReferences } = useCanvas(); + useEffect(() => { + const sourceDoc = TestUtil.createSourceOrderDoc(); + setSourceBodyDocument(sourceDoc); + const targetDoc = TestUtil.createTargetOrderDoc(); + setTargetBodyDocument(targetDoc); + reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + if (!first) { + first = true; + return; + } + if (!second) { + second = true; + beforeNodePaths = getAllNodePaths(); + clearNodeReferencesForDocument(DocumentType.SOURCE_BODY, BODY_DOCUMENT_ID); + afterNodePaths = getAllNodePaths(); + } + }, [clearNodeReferencesForDocument, getAllNodePaths, reloadNodeReferences]); + return <>{children}; + }; + render( + + + + + + + , + ); + await screen.findAllByText('ShipOrder'); + expect(afterNodePaths.length).toBeGreaterThan(10); + expect(beforeNodePaths.length).toBeGreaterThan(afterNodePaths.length); + }); +}); diff --git a/packages/ui/src/providers/datamapper-canvas.provider.tsx b/packages/ui/src/providers/datamapper-canvas.provider.tsx new file mode 100644 index 000000000..4ff55aa00 --- /dev/null +++ b/packages/ui/src/providers/datamapper-canvas.provider.tsx @@ -0,0 +1,141 @@ +import { + createContext, + FunctionComponent, + MutableRefObject, + PropsWithChildren, + RefObject, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; +import { DnDHandler } from './dnd/DnDHandler'; +import { DocumentType, NodePath } from '../models/datamapper/path'; +import { DatamapperDndProvider } from './datamapper-dnd.provider'; +import { useDataMapper } from '../hooks/useDataMapper'; + +export interface NodeReference { + headerRef: HTMLDivElement | null; + containerRef: HTMLDivElement | null; +} + +export interface ICanvasContext { + setNodeReference: (path: string, ref: MutableRefObject) => void; + getNodeReference: (path: string) => MutableRefObject | null; + reloadNodeReferences: () => void; + clearNodeReferencesForPath: (path: string) => void; + clearNodeReferencesForDocument: (documentType: DocumentType, documentId: string) => void; + getAllNodePaths: () => string[]; + setDefaultHandler: (handler: DnDHandler | undefined) => void; + getActiveHandler: () => DnDHandler | undefined; + setActiveHandler: (handler: DnDHandler | undefined) => void; + mappingLinkCanvasRef: RefObject | null; + setMappingLinkCanvasRef: (ref: RefObject) => void; +} + +export const CanvasContext = createContext(undefined); + +export const DataMapperCanvasProvider: FunctionComponent = (props) => { + const { mappingTree } = useDataMapper(); + const [defaultHandler, setDefaultHandler] = useState(); + const [activeHandler, setActiveHandler] = useState(); + const [mappingLinkCanvasRef, setMappingLinkCanvasRef] = useState | null>(null); + + const [nodeReferenceMap, setNodeReferenceMap] = useState>>( + new Map>(), + ); + + const setNodeReference = useCallback( + (path: string, ref: MutableRefObject) => { + nodeReferenceMap.set(path, ref); + }, + [nodeReferenceMap], + ); + + const getNodeReference = useCallback( + (path: string) => { + return nodeReferenceMap.get(path) || null; + }, + [nodeReferenceMap], + ); + + const reloadNodeReferences = useCallback(() => { + setNodeReferenceMap(new Map(nodeReferenceMap)); + }, [nodeReferenceMap]); + + useEffect(() => { + if (mappingTree) reloadNodeReferences(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mappingTree]); + + const clearNodeReferencesForPath = useCallback( + (path: string) => { + Array.from(nodeReferenceMap.keys()) + .filter((key) => key.startsWith(path)) + .forEach((key) => nodeReferenceMap.delete(key)); + }, + [nodeReferenceMap], + ); + + const clearNodeReferencesForDocument = useCallback( + (documentType: DocumentType, documentId: string) => { + const pathPrefix = NodePath.fromDocument(documentType, documentId).toString(); + Array.from(nodeReferenceMap.keys()) + .filter((key) => key.startsWith(pathPrefix)) + .forEach((key) => nodeReferenceMap.delete(key)); + }, + [nodeReferenceMap], + ); + + const getAllNodePaths = useCallback(() => { + return Array.from(nodeReferenceMap.keys()); + }, [nodeReferenceMap]); + + const handleSetDefaultHandler = useCallback( + (handler: DnDHandler | undefined) => { + if (!activeHandler) setActiveHandler(handler); + setDefaultHandler(handler); + }, + [activeHandler, setDefaultHandler], + ); + + const handleSetActiveHandler = useCallback( + (handler: DnDHandler | undefined) => { + setActiveHandler(handler ? handler : defaultHandler); + }, + [defaultHandler], + ); + + const value: ICanvasContext = useMemo(() => { + return { + setNodeReference, + getNodeReference, + reloadNodeReferences, + clearNodeReferencesForPath, + clearNodeReferencesForDocument, + getAllNodePaths, + setDefaultHandler: handleSetDefaultHandler, + getActiveHandler: () => activeHandler, + setActiveHandler: handleSetActiveHandler, + mappingLinkCanvasRef, + setMappingLinkCanvasRef: (ref) => setMappingLinkCanvasRef(ref), + }; + }, [ + setNodeReference, + getNodeReference, + reloadNodeReferences, + clearNodeReferencesForPath, + clearNodeReferencesForDocument, + getAllNodePaths, + handleSetDefaultHandler, + handleSetActiveHandler, + activeHandler, + mappingLinkCanvasRef, + ]); + + return ( + + {props.children} + + ); +}; diff --git a/packages/ui/src/providers/datamapper-dnd.provider.tsx b/packages/ui/src/providers/datamapper-dnd.provider.tsx new file mode 100644 index 000000000..f4e247d0d --- /dev/null +++ b/packages/ui/src/providers/datamapper-dnd.provider.tsx @@ -0,0 +1,89 @@ +import { DnDHandler } from './dnd/DnDHandler'; +import { createContext, FunctionComponent, PropsWithChildren, useCallback, useMemo, useState } from 'react'; +import { + DataRef, + DndContext, + DragEndEvent, + DragOverlay, + DragOverEvent, + DragStartEvent, + useSensor, + MouseSensor, + TouchSensor, + KeyboardSensor, + useSensors, +} from '@dnd-kit/core'; +import { NodeData } from '../models/datamapper'; +import { Label } from '@patternfly/react-core'; +import { useDataMapper } from '../hooks/useDataMapper'; +import { DataMapperDnDMonitor } from './dnd/DataMapperDndMonitor'; + +export interface IDataMapperDndContext { + handler?: DnDHandler; +} + +export const DataMapperDndContext = createContext({}); + +type DataMapperDndContextProps = PropsWithChildren & { + handler: DnDHandler | undefined; +}; + +export const DatamapperDndProvider: FunctionComponent = (props) => { + const { mappingTree, refreshMappingTree, debug } = useDataMapper(); + const [activeData, setActiveData] = useState | null>(null); + + const mouseSensor = useSensor(MouseSensor, { + activationConstraint: { + distance: 10, + }, + }); + const touchSensor = useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 5, + }, + }); + const keyboardSensor = useSensor(KeyboardSensor); + const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor); + + const handleDragStart = useCallback( + (event: DragStartEvent) => { + props.handler && props.handler.handleDragStart(event); + setActiveData(event.active.data as DataRef); + }, + [props.handler], + ); + + const handleDragOver = useCallback( + (event: DragOverEvent) => { + props.handler && props.handler.handleDragOver(event); + }, + [props.handler], + ); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + props.handler && props.handler.handleDragEnd(event, mappingTree, refreshMappingTree); + setActiveData(null); + }, + [mappingTree, props.handler, refreshMappingTree], + ); + + const handler = useMemo(() => { + return { + handler: props.handler, + }; + }, [props.handler]); + + return ( + + + {props.children} + + + + {debug && } + + + ); +}; diff --git a/packages/ui/src/providers/datamapper.provider.test.tsx b/packages/ui/src/providers/datamapper.provider.test.tsx new file mode 100644 index 000000000..5da08f601 --- /dev/null +++ b/packages/ui/src/providers/datamapper.provider.test.tsx @@ -0,0 +1,47 @@ +import { DataMapperProvider } from './datamapper.provider'; +import { render, screen } from '@testing-library/react'; +import { useDataMapper } from '../hooks/useDataMapper'; +import { FieldItem, MappingTree } from '../models/datamapper/mapping'; +import { useEffect } from 'react'; +import { IField } from '../models/datamapper/document'; + +describe('DataMapperProvider', () => { + it('should render', async () => { + render( + +
    + , + ); + await screen.findByTestId('testdiv'); + }); + + it('refreshMappingTree should re-create the MappingTree instance', async () => { + let prevTree: MappingTree; + let nextTree: MappingTree; + let done = false; + const TestRefreshMappingTree = () => { + const { mappingTree, refreshMappingTree } = useDataMapper(); + useEffect(() => { + if (done) { + nextTree = mappingTree; + } else { + prevTree = mappingTree; + prevTree.children.push(new FieldItem(prevTree, {} as IField)); + refreshMappingTree(); + done = true; + } + }, [mappingTree, refreshMappingTree]); + return
    ; + }; + render( + + + , + ); + await screen.findByTestId('testdiv'); + expect(prevTree!).toBeDefined(); + expect(nextTree!).toBeDefined(); + expect(prevTree! !== nextTree!).toBeTruthy(); + expect(prevTree!.children[0] === nextTree!.children[0]).toBeTruthy(); + }); +}); diff --git a/packages/ui/src/providers/datamapper.provider.tsx b/packages/ui/src/providers/datamapper.provider.tsx new file mode 100644 index 000000000..78f522f4e --- /dev/null +++ b/packages/ui/src/providers/datamapper.provider.tsx @@ -0,0 +1,304 @@ +/* + Copyright (C) 2024 Red Hat, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { createContext, FunctionComponent, PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'; + +import { Loading } from '../components/Loading'; +import { MappingTree } from '../models/datamapper/mapping'; +import { + BODY_DOCUMENT_ID, + DocumentDefinition, + DocumentInitializationModel, + IDocument, + PrimitiveDocument, +} from '../models/datamapper/document'; +import { CanvasView } from '../models/datamapper/view'; +import { DocumentType } from '../models/datamapper/path'; +import { MappingSerializerService } from '../services/mapping-serializer.service'; +import { MappingService } from '../services/mapping.service'; +import { DocumentService } from '../services/document.service'; +import { Alert, AlertActionCloseButton, AlertGroup, AlertProps, AlertVariant } from '@patternfly/react-core'; + +export interface IDataMapperContext { + isLoading: boolean; + setIsLoading(isLoading: boolean): void; + + activeView: CanvasView; + setActiveView(view: CanvasView): void; + + initialExpandedFieldRank: number; + setInitialExpandedFieldRank: (rank: number) => void; + maxTotalFieldCountToExpandAll: number; + setMaxTotalFieldCountToExpandAll: (rank: number) => void; + + sourceParameterMap: Map; + refreshSourceParameters: () => void; + deleteSourceParameter: (name: string) => void; + sourceBodyDocument: IDocument; + setSourceBodyDocument: (doc: IDocument) => void; + targetBodyDocument: IDocument; + setTargetBodyDocument: (doc: IDocument) => void; + updateDocumentDefinition: (definition: DocumentDefinition) => void; + + isSourceParametersExpanded: boolean; + setSourceParametersExpanded: (expanded: boolean) => void; + + mappingTree: MappingTree; + refreshMappingTree(): void; + setMappingTree(mappings: MappingTree): void; + + alerts: Partial[]; + addAlert: (alert: Partial) => void; + + debug: boolean; + setDebug(debug: boolean): void; +} + +export const DataMapperContext = createContext(null); + +type DataMapperProviderProps = PropsWithChildren & { + defaultInitialExpandedFieldRank?: number; + defaultMaxTotalFieldCountToExpandAll?: number; + documentInitializationModel?: DocumentInitializationModel; + onUpdateDocument?: (definition: DocumentDefinition) => void; + onDeleteParameter?: (name: string) => void; + initialXsltFile?: string; + onUpdateMappings?: (xsltFile: string) => void; +}; + +export const DataMapperProvider: FunctionComponent = ({ + defaultInitialExpandedFieldRank = 1, + defaultMaxTotalFieldCountToExpandAll = 100, + documentInitializationModel, + onUpdateDocument, + onDeleteParameter, + initialXsltFile, + onUpdateMappings, + children, +}) => { + const [debug, setDebug] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [activeView, setActiveView] = useState(CanvasView.SOURCE_TARGET); + const [initialExpandedFieldRank, setInitialExpandedFieldRank] = useState(defaultInitialExpandedFieldRank); + const [maxTotalFieldCountToExpandAll, setMaxTotalFieldCountToExpandAll] = useState( + defaultMaxTotalFieldCountToExpandAll, + ); + + const [sourceParameterMap, setSourceParameterMap] = useState>(new Map()); + const [isSourceParametersExpanded, setSourceParametersExpanded] = useState(true); + const [sourceBodyDocument, setSourceBodyDocument] = useState( + new PrimitiveDocument(DocumentType.SOURCE_BODY, BODY_DOCUMENT_ID), + ); + const [targetBodyDocument, setTargetBodyDocument] = useState( + new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID), + ); + const [mappingTree, setMappingTree] = useState( + new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID), + ); + + const [alerts, setAlerts] = useState[]>([]); + + useEffect(() => { + const documents = DocumentService.createInitialDocuments(documentInitializationModel); + let latestSourceParameterMap = sourceParameterMap; + let latestTargetBodyDocument = targetBodyDocument; + if (documents) { + documents.sourceBodyDocument && setSourceBodyDocument(documents.sourceBodyDocument); + setSourceParameterMap(documents.sourceParameterMap); + latestSourceParameterMap = documents.sourceParameterMap; + if (documents.targetBodyDocument) { + setTargetBodyDocument(documents.targetBodyDocument); + latestTargetBodyDocument = documents.targetBodyDocument; + } + } + if (initialXsltFile) { + const loaded = MappingSerializerService.deserialize( + initialXsltFile, + latestTargetBodyDocument, + mappingTree, + latestSourceParameterMap, + ); + setMappingTree(loaded); + } + setIsLoading(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const refreshSourceParameters = useCallback(() => { + setSourceParameterMap(new Map(sourceParameterMap)); + }, [sourceParameterMap]); + + const deleteSourceParameter = useCallback( + (name: string) => { + sourceParameterMap.delete(name); + refreshSourceParameters(); + onDeleteParameter && onDeleteParameter(name); + }, + [onDeleteParameter, refreshSourceParameters, sourceParameterMap], + ); + + const refreshMappingTree = useCallback(() => { + const newMapping = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + newMapping.children = mappingTree.children.map((child) => { + child.parent = newMapping; + return child; + }); + newMapping.namespaceMap = mappingTree.namespaceMap; + setMappingTree(newMapping); + onUpdateMappings && onUpdateMappings(MappingSerializerService.serialize(mappingTree, sourceParameterMap!)); + }, [mappingTree, onUpdateMappings, sourceParameterMap]); + + const removeStaleMappings = useCallback( + (documentType: DocumentType, documentId: string, newDocument: IDocument) => { + let isFromPrimitive: boolean; + switch (documentType) { + case DocumentType.SOURCE_BODY: + isFromPrimitive = sourceBodyDocument instanceof PrimitiveDocument; + break; + case DocumentType.TARGET_BODY: + isFromPrimitive = targetBodyDocument instanceof PrimitiveDocument; + break; + case DocumentType.PARAM: + isFromPrimitive = sourceParameterMap!.get(documentId) instanceof PrimitiveDocument; + } + const isToPrimitive = newDocument instanceof PrimitiveDocument; + const cleaned = + isFromPrimitive || isToPrimitive + ? MappingService.removeAllMappingsForDocument(mappingTree, documentType, documentId) + : MappingService.removeStaleMappingsForDocument(mappingTree, newDocument); + setMappingTree(cleaned); + }, + [mappingTree, sourceBodyDocument, sourceParameterMap, targetBodyDocument], + ); + + const setNewDocument = useCallback( + (documentType: DocumentType, documentId: string, newDocument: IDocument) => { + switch (documentType) { + case DocumentType.SOURCE_BODY: + setSourceBodyDocument(newDocument); + break; + case DocumentType.TARGET_BODY: + setTargetBodyDocument(newDocument); + break; + case DocumentType.PARAM: + sourceParameterMap!.set(documentId, newDocument); + refreshSourceParameters(); + break; + } + }, + [refreshSourceParameters, sourceParameterMap], + ); + + const updateDocumentDefinition = useCallback( + (definition: DocumentDefinition) => { + const document = DocumentService.createDocument(definition); + if (!document) return; + removeStaleMappings(document.documentType, document.documentId, document); + setNewDocument(document.documentType, document.documentId, document); + onUpdateDocument && onUpdateDocument(definition); + }, + [onUpdateDocument, removeStaleMappings, setNewDocument], + ); + + const addAlert = useCallback( + (option: Partial) => { + alerts.push(option); + setAlerts([...alerts]); + }, + [alerts], + ); + + const closeAlert = useCallback( + (option: Partial) => { + const index = alerts.indexOf(option); + if (index > -1) { + alerts.splice(index, 1); + setAlerts([...alerts]); + } + }, + [alerts], + ); + + const value = useMemo(() => { + return { + isLoading, + setIsLoading, + activeView, + setActiveView, + initialExpandedFieldRank, + setInitialExpandedFieldRank, + maxTotalFieldCountToExpandAll, + setMaxTotalFieldCountToExpandAll, + sourceParameterMap, + isSourceParametersExpanded, + setSourceParametersExpanded, + refreshSourceParameters, + deleteSourceParameter, + sourceBodyDocument, + setSourceBodyDocument, + targetBodyDocument, + setTargetBodyDocument, + updateDocumentDefinition, + mappingTree, + refreshMappingTree, + setMappingTree, + alerts, + addAlert, + debug, + setDebug, + }; + }, [ + isLoading, + activeView, + initialExpandedFieldRank, + maxTotalFieldCountToExpandAll, + sourceParameterMap, + isSourceParametersExpanded, + refreshSourceParameters, + deleteSourceParameter, + sourceBodyDocument, + targetBodyDocument, + updateDocumentDefinition, + mappingTree, + refreshMappingTree, + alerts, + addAlert, + debug, + ]); + + return ( + + {isLoading ? ( + + ) : ( + <> + + {alerts.map((option, index) => ( + closeAlert(option)} + actionClose={ closeAlert(option)} />} + > + ))} + + {children} + + )} + + ); +}; diff --git a/packages/ui/src/providers/dnd/DataMapperDndMonitor.tsx b/packages/ui/src/providers/dnd/DataMapperDndMonitor.tsx new file mode 100644 index 000000000..1ee1b5629 --- /dev/null +++ b/packages/ui/src/providers/dnd/DataMapperDndMonitor.tsx @@ -0,0 +1,40 @@ +import { FunctionComponent, useContext } from 'react'; +import { DataMapperDndContext } from '../datamapper-dnd.provider'; +import { NodeData } from '../../models/datamapper'; +import { DragOverEvent, useDndMonitor } from '@dnd-kit/core'; + +export const DataMapperDnDMonitor: FunctionComponent = () => { + const { handler } = useContext(DataMapperDndContext); + + useDndMonitor({ + onDragStart(event) { + const fromField = event.active.data.current as NodeData; + console.log(`onDragStart: [active: ${fromField?.path.toString()}, handler=${handler?.constructor.name}]`); + }, + + onDragOver(event: DragOverEvent) { + const fromField = event.active?.data.current as NodeData; + const toField = event.over?.data.current as NodeData; + console.log( + `onDragOver: [active: ${fromField?.path.toString()}, over:${toField?.path.toString()}, handler=${handler?.constructor.name}]`, + ); + }, + + onDragEnd(event) { + const fromField = event.active?.data.current as NodeData; + const toField = event.over?.data.current as NodeData; + console.log( + `onDragEnd: [active: ${fromField?.path.toString()}, over:${toField?.path.toString()}, handler=${handler?.constructor.name}]`, + ); + }, + onDragCancel(event) { + const fromField = event.active?.data.current as NodeData; + const toField = event.over?.data.current as NodeData; + console.log( + `onDragCancel: active: ${fromField?.path.toString()}, over:${toField?.path.toString()}, handler=${handler?.constructor.name}]`, + ); + }, + }); + + return <>; +}; diff --git a/packages/ui/src/providers/dnd/DnDHandler.ts b/packages/ui/src/providers/dnd/DnDHandler.ts new file mode 100644 index 000000000..a00425ea3 --- /dev/null +++ b/packages/ui/src/providers/dnd/DnDHandler.ts @@ -0,0 +1,8 @@ +import { DragEndEvent, DragOverEvent, DragStartEvent } from '@dnd-kit/core'; +import { MappingTree } from '../../models/datamapper/mapping'; + +export interface DnDHandler { + handleDragStart(event: DragStartEvent): void; + handleDragOver(event: DragOverEvent): void; + handleDragEnd(event: DragEndEvent, mappingTree: MappingTree, onUpdate: () => void): void; +} diff --git a/packages/ui/src/providers/dnd/ExpressionEditorDnDHandler.ts b/packages/ui/src/providers/dnd/ExpressionEditorDnDHandler.ts new file mode 100644 index 000000000..70ef01ad9 --- /dev/null +++ b/packages/ui/src/providers/dnd/ExpressionEditorDnDHandler.ts @@ -0,0 +1,28 @@ +import { DnDHandler } from './DnDHandler'; +import { DragEndEvent, DragOverEvent, DragStartEvent } from '@dnd-kit/core'; +import { EditorNodeData, FieldNodeData, FunctionNodeData, MappingTree, NodeData } from '../../models/datamapper'; +import { MappingService } from '../../services/mapping.service'; + +export class ExpressionEditorDnDHandler implements DnDHandler { + handleDragEnd(event: DragEndEvent, _mappingTree: MappingTree, onUpdate: () => void): void { + const fromNode = event.active.data.current as NodeData; + const toNode = event.over?.data.current as NodeData; + if ( + !fromNode || + !toNode || + (!(fromNode instanceof FunctionNodeData) && !(fromNode instanceof FieldNodeData)) || + !(toNode instanceof EditorNodeData) + ) + return; + const editorNodeData = toNode as EditorNodeData; + if (fromNode instanceof FieldNodeData) { + MappingService.mapToCondition(editorNodeData.mapping, fromNode.field); + } else { + MappingService.wrapWithFunction(editorNodeData.mapping, fromNode.functionDefinition); + } + onUpdate(); + } + + handleDragOver(_event: DragOverEvent): void {} + handleDragStart(_event: DragStartEvent): void {} +} diff --git a/packages/ui/src/providers/dnd/SourceTargetDnDHandler.ts b/packages/ui/src/providers/dnd/SourceTargetDnDHandler.ts new file mode 100644 index 000000000..12c3256e1 --- /dev/null +++ b/packages/ui/src/providers/dnd/SourceTargetDnDHandler.ts @@ -0,0 +1,21 @@ +import { DnDHandler } from './DnDHandler'; +import { DragEndEvent, DragOverEvent, DragStartEvent } from '@dnd-kit/core'; +import { MappingTree } from '../../models/datamapper/mapping'; +import { NodeData } from '../../models/datamapper/visualization'; +import { VisualizationService } from '../../services/visualization.service'; + +export class SourceTargetDnDHandler implements DnDHandler { + handleDragEnd(event: DragEndEvent, mappingTree: MappingTree, onUpdate: () => void): void { + const fromNode = event.active.data.current as NodeData; + const toNode = event.over?.data.current as NodeData; + if (!fromNode || !toNode) return; + const { sourceNode, targetNode } = VisualizationService.testNodePair(fromNode, toNode); + if (sourceNode && targetNode) { + VisualizationService.engageMapping(mappingTree, sourceNode, targetNode); + onUpdate(); + } + } + + handleDragOver(_event: DragOverEvent): void {} + handleDragStart(_event: DragStartEvent): void {} +} diff --git a/packages/ui/src/providers/index.ts b/packages/ui/src/providers/index.ts index 44019b2a8..0748b8d9f 100644 --- a/packages/ui/src/providers/index.ts +++ b/packages/ui/src/providers/index.ts @@ -6,6 +6,7 @@ export * from './action-confirmation-modal.provider'; export * from './entities.provider'; export * from './filtered-field.provider'; export * from './settings.provider'; +export * from './metadata.provider'; export * from './reload.context'; export * from './runtime.provider'; export * from './schema-bridge.provider'; diff --git a/packages/ui/src/providers/metadata.provider.tsx b/packages/ui/src/providers/metadata.provider.tsx new file mode 100644 index 000000000..69331924d --- /dev/null +++ b/packages/ui/src/providers/metadata.provider.tsx @@ -0,0 +1,68 @@ +import { FunctionComponent, PropsWithChildren, createContext } from 'react'; + +export interface IMetadataApi { + /** + * Get metadata querying a Kaoto metadata file. + * @param key The key to retrieve the metadata from the Kaoto metadata file + */ + getMetadata(key: string): Promise; + + /** + * Save metadata to a Kaoto metadata file. + * @param key The key to set the metadata + * @param metadata The metadata to be saved + */ + setMetadata(key: string, metadata: T): Promise; + + /** + * Retrieve resource content + * @param path The path of the resource + */ + getResourceContent(path: string): Promise; + + /** + * Save resource content + * @param path The path of the resource + * @param content The content to be saved + */ + saveResourceContent(path: string, content: string): Promise; + + /** + * Delete resource + * @param path The path of the resource + */ + deleteResource(path: string): Promise; + + /** + * Show a Quick Pick widget and ask the user to select one or more files available in the workspace. + * @param include The filter expression for the files to include + * @param exclude The filter expression for the files to exclude + * @param options The options to pass over to VSCode QuickPick + */ + askUserForFileSelection( + include: string, + exclude?: string, + options?: Record, + ): Promise; + + /** + * A flag indicates that if the schema file needs to be saved or not. If it's running inside the VS Code, + * the schema file is supposed to be read from existing workspace file, therefore it should not save it and overwrite. + * On the other hand, if it's running in the browser, the schema file is uploaded directly from the browser, therefore + * it needs to be saved to some store. + * TODO The file saving in the browser is not implemented yet. If we implement the browser side to be same as what's done + * in VS Code, i.e. expecting schema files already in some store and just pick from there, we can remove this. + */ + shouldSaveSchema: boolean; +} + +export const MetadataContext = createContext(undefined); + +/** + * The goal for this provider is to expose a settings adapter to the SettingsForm component + * and its children, so they can be used to render the form fields. + * In addition to that, it also provides a mechanism to read/write the settings values. + */ +export const MetadataProvider: FunctionComponent> = ({ api, children }) => { + return {children}; +}; diff --git a/packages/ui/src/router.tsx b/packages/ui/src/router.tsx index 00237a079..f012bbd3d 100644 --- a/packages/ui/src/router.tsx +++ b/packages/ui/src/router.tsx @@ -37,6 +37,14 @@ export const router = createHashRouter([ path: Links.Settings, lazy: async () => import('./pages/Settings'), }, + { + path: Links.DataMapper, + lazy: async () => import('./pages/DataMapperNotYetInBrowser'), + }, + { + path: `${Links.DataMapper}/:id`, + lazy: async () => import('./pages/DataMapper'), + }, ], }, ]); diff --git a/packages/ui/src/router/links.models.ts b/packages/ui/src/router/links.models.ts index 369bcb0a4..3a1e7cdcf 100644 --- a/packages/ui/src/router/links.models.ts +++ b/packages/ui/src/router/links.models.ts @@ -1,6 +1,7 @@ export const enum Links { Home = '/', // Flows visualization SourceCode = '/code', // Flows source code + DataMapper = '/datamapper', Beans = '/beans', Rest = '/rest', Metadata = '/metadata', diff --git a/packages/ui/src/services/datamapper-metadata.service.ts b/packages/ui/src/services/datamapper-metadata.service.ts new file mode 100644 index 000000000..0d7110f4f --- /dev/null +++ b/packages/ui/src/services/datamapper-metadata.service.ts @@ -0,0 +1,232 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { EntitiesContextResult } from '../hooks'; +import { IVisualizationNode } from '../models'; +import { + BODY_DOCUMENT_ID, + DocumentDefinition, + DocumentDefinitionType, + DocumentInitializationModel, + DocumentType, +} from '../models/datamapper'; +import { IDataMapperMetadata, IDocumentMetadata } from '../models/datamapper/metadata'; +import { IMetadataApi } from '../providers'; +import { isDefined, isXSLTComponent, XSLT_COMPONENT_NAME } from '../utils'; +import type { XsltComponentDef } from '../utils/is-xslt-component'; +import { EMPTY_XSL } from './mapping-serializer.service'; + +export class DataMapperMetadataService { + static readonly SCHEMA_NAME_PATTERN = '**/*.{xsd,xml,XSD,XML}'; + + static getDataMapperMetadataId(vizNode: IVisualizationNode) { + const model = vizNode.getComponentSchema()?.definition; + return model.id; + } + + static async initializeDataMapperMetadata( + entitiesContext: EntitiesContextResult, + vizNode: IVisualizationNode, + api: IMetadataApi, + metadataId: string, + ): Promise { + const model = vizNode.getComponentSchema()?.definition; + const xsltStep = (model.steps as ProcessorDefinition[] | undefined)?.find(isXSLTComponent); + let documentName = this.getXSLTDocumentName(xsltStep); + + if (isDefined(xsltStep) && !isDefined(documentName)) { + /** At this point, the Kaoto DataMapper is not yet configured, hence we create the XSLT mapping document name */ + documentName = `${metadataId}.xsl`; + xsltStep.to.uri = `${XSLT_COMPONENT_NAME}:${documentName}`; + vizNode.updateModel(model); + entitiesContext.updateSourceCodeFromEntities(); + } + + const metadata = { + sourceBody: { + type: DocumentDefinitionType.Primitive, + }, + sourceParameters: {}, + targetBody: { + type: DocumentDefinitionType.Primitive, + }, + xsltPath: documentName, + } as IDataMapperMetadata; + + const metadataPromise = api.setMetadata(metadataId, metadata); + const contentPromise = api.saveResourceContent(metadata.xsltPath, EMPTY_XSL); + await Promise.allSettled([metadataPromise, contentPromise]); + + return metadata; + } + + static loadDocuments(api: IMetadataApi, metadata: IDataMapperMetadata): Promise { + return new Promise((resolve) => { + const answer = new DocumentInitializationModel(); + const sourceBodyPromise = DataMapperMetadataService.doLoadDocument( + api, + DocumentType.SOURCE_BODY, + BODY_DOCUMENT_ID, + metadata.sourceBody, + ).then((definition) => (answer.sourceBody = definition)); + const targetBodyPromise = DataMapperMetadataService.doLoadDocument( + api, + DocumentType.TARGET_BODY, + BODY_DOCUMENT_ID, + metadata.targetBody, + ).then((definition) => (answer.targetBody = definition)); + const paramPromises = Object.entries(metadata.sourceParameters).reduce( + (acc, [key, meta]) => { + acc[key] = DataMapperMetadataService.doLoadDocument(api, DocumentType.PARAM, key, meta).then( + (definition) => (answer.sourceParameters[key] = definition), + ); + return acc; + }, + {} as Record>, + ); + Promise.allSettled([sourceBodyPromise, targetBodyPromise, Object.values(paramPromises)]).then(() => { + resolve(answer); + }); + }); + } + + static getXSLTDocumentName(xsltStep?: XsltComponentDef): string | undefined { + if (!xsltStep) { + return undefined; + } + + const [_componentUri, uriFileName] = xsltStep.to.uri.split(':'); + return uriFileName; + } + + private static doLoadDocument( + api: IMetadataApi, + documentType: DocumentType, + name: string, + documentMetadata: IDocumentMetadata, + ): Promise { + return new Promise((resolve) => { + const definitionType = documentMetadata.type ? documentMetadata.type : DocumentDefinitionType.Primitive; + const definitionFiles: Record = {}; + const fileReadingPromises = !documentMetadata.filePath + ? [] + : documentMetadata.filePath.map((path) => + api + .getResourceContent(path) + .then((value) => { + if (value) definitionFiles[path] = value; + }) + .catch((reason) => console.log(`Could not read a file "${path}": ${reason}`)), + ); + Promise.allSettled(fileReadingPromises).then(() => { + const answer = new DocumentDefinition(documentType, definitionType, name, definitionFiles); + resolve(answer); + }); + }); + } + + static loadMappingFile(api: IMetadataApi, metadata: IDataMapperMetadata): Promise { + return api.getResourceContent(metadata.xsltPath); + } + + static async updateSourceBodyMetadata( + api: IMetadataApi, + metadataId: string, + metadata: IDataMapperMetadata, + definition: DocumentDefinition, + ) { + metadata.sourceBody = await DataMapperMetadataService.doCreateDocumentMetadata( + api, + metadataId, + metadata, + definition, + ); + api.setMetadata(metadataId, metadata); + } + + private static doCreateDocumentMetadata( + api: IMetadataApi, + metadataId: string, + metadata: IDataMapperMetadata, + definition: DocumentDefinition, + ): Promise { + const filePaths = definition.definitionFiles ? Object.keys(definition.definitionFiles) : []; + const answer = { + type: definition.definitionType, + filePath: filePaths, + }; + const metadataPromise = api.setMetadata(metadataId, metadata); + const filePromises = + api.shouldSaveSchema && definition.definitionFiles + ? Object.entries(definition.definitionFiles).map(([path, content]) => { + api + .saveResourceContent(path, content) + .catch((error) => console.log(`Could not save a file "${path}": ${error}`)); + }) + : []; + return new Promise((resolve) => { + Promise.allSettled([metadataPromise, ...filePromises]).then(() => resolve(answer)); + }); + } + + static async updateTargetBodyMetadata( + api: IMetadataApi, + metadataId: string, + metadata: IDataMapperMetadata, + definition: DocumentDefinition, + ) { + metadata.targetBody = await DataMapperMetadataService.doCreateDocumentMetadata( + api, + metadataId, + metadata, + definition, + ); + api.setMetadata(metadataId, metadata); + } + + static async updateSourceParameterMetadata( + api: IMetadataApi, + metadataId: string, + metadata: IDataMapperMetadata, + name: string, + definition: DocumentDefinition, + ) { + metadata.sourceParameters[name] = await DataMapperMetadataService.doCreateDocumentMetadata( + api, + metadataId, + metadata, + definition, + ); + api.setMetadata(metadataId, metadata); + } + + static async deleteSourceParameterMetadata( + api: IMetadataApi, + metadataId: string, + metadata: IDataMapperMetadata, + name: string, + ) { + delete metadata.sourceParameters[name]; + api.setMetadata(metadataId, metadata); + } + + static async updateMappingFile(api: IMetadataApi, metadata: IDataMapperMetadata, xsltFile: string) { + await api.saveResourceContent(metadata.xsltPath, xsltFile); + } + + static async selectDocumentSchema(api: IMetadataApi) { + return await api.askUserForFileSelection(this.SCHEMA_NAME_PATTERN, undefined, { + canPickMany: false, // TODO set to true once we support xs:include/xs:import, i.e. multiple files + placeHolder: + 'Choose the schema file to attach. Type a text to narrow down the candidates. The file path is shown as a relative path from the active Camel file opening with Kaoto.', + title: 'Attaching document schema file', + }); + } + + static async deleteMetadata(api: IMetadataApi, metadataId: string) { + await api.setMetadata(metadataId, undefined); + } + + static async deleteXsltFile(api: IMetadataApi, metadataId: string) { + const metadata = (await api.getMetadata(metadataId)) as IDataMapperMetadata; + await api.deleteResource(metadata.xsltPath); + } +} diff --git a/packages/ui/src/services/document.service.test.ts b/packages/ui/src/services/document.service.test.ts new file mode 100644 index 000000000..097626e79 --- /dev/null +++ b/packages/ui/src/services/document.service.test.ts @@ -0,0 +1,42 @@ +import { DocumentService } from './document.service'; + +import { TestUtil } from '../stubs/data-mapper'; + +describe('DocumentService', () => { + const sourceDoc = TestUtil.createSourceOrderDoc(); + const targetDoc = TestUtil.createTargetOrderDoc(); + + describe('getFieldStack()', () => { + it('', () => { + const stack = DocumentService.getFieldStack(sourceDoc.fields[0].fields[1]); + expect(stack.length).toEqual(1); + expect(stack[0].name).toEqual('ShipOrder'); + const stackWithSelf = DocumentService.getFieldStack(sourceDoc.fields[0].fields[1], true); + expect(stackWithSelf.length).toEqual(2); + expect(stackWithSelf[0].name).toEqual('OrderPerson'); + }); + }); + + describe('hasField()', () => { + it('', () => { + expect(DocumentService.hasField(sourceDoc, sourceDoc.fields[0].fields[0])).toBeTruthy(); + expect(DocumentService.hasField(sourceDoc, targetDoc.fields[0].fields[0])).toBeFalsy(); + }); + }); + + describe('getFieldFromPathExpression()', () => { + it('', () => { + const pathExpression = '/ShipOrder/ShipTo'; + const field = DocumentService.getFieldFromPathExpression(sourceDoc, pathExpression); + expect(field?.name).toEqual('ShipTo'); + }); + }); + + describe('getFieldFromPathSegments()', () => { + it('', () => { + const pathSegments = ['ShipOrder', 'ShipTo']; + const field = DocumentService.getFieldFromPathSegments(sourceDoc, pathSegments); + expect(field?.name).toEqual('ShipTo'); + }); + }); +}); diff --git a/packages/ui/src/services/document.service.ts b/packages/ui/src/services/document.service.ts new file mode 100644 index 000000000..0f4523ef2 --- /dev/null +++ b/packages/ui/src/services/document.service.ts @@ -0,0 +1,193 @@ +import { + BODY_DOCUMENT_ID, + DocumentDefinition, + DocumentDefinitionType, + DocumentInitializationModel, + IDocument, + IField, + IParentType, + ITypeFragment, + PrimitiveDocument, +} from '../models/datamapper/document'; +import { DocumentType } from '../models/datamapper/path'; +import { XmlSchemaDocumentService } from './xml-schema-document.service'; +import { IMetadataApi } from '../providers'; + +interface InitialDocumentsSet { + sourceBodyDocument?: IDocument; + sourceParameterMap: Map; + targetBodyDocument?: IDocument; +} + +export class DocumentService { + static async createDocumentDefinition( + api: IMetadataApi, + documentType: DocumentType, + definitionType: DocumentDefinitionType, + documentId: string, + schemaFilePaths: string[], + ): Promise { + if (!schemaFilePaths || schemaFilePaths.length === 0) return; + const fileContents: Record = {}; + const fileContentPromises: Promise[] = []; + schemaFilePaths.forEach((path: string) => { + const promise = api.getResourceContent(path).then((content: string | undefined) => { + if (content) fileContents[path] = content; + }); + fileContentPromises.push(promise); + }); + await Promise.allSettled(fileContentPromises); + return new DocumentDefinition(documentType, definitionType, documentId, fileContents); + } + + static createDocument(definition: DocumentDefinition): IDocument | null { + if (definition.definitionType === DocumentDefinitionType.Primitive) { + return new PrimitiveDocument(definition.documentType, DocumentType.PARAM ? definition.name! : BODY_DOCUMENT_ID); + } + if (!definition.definitionFiles || Object.keys(definition.definitionFiles).length === 0) return null; + const content = Object.values(definition.definitionFiles)[0]; + const documentId = definition.documentType === DocumentType.PARAM ? definition.name! : BODY_DOCUMENT_ID; + return XmlSchemaDocumentService.createXmlSchemaDocument(definition.documentType, documentId, content); + } + + static createInitialDocuments(initModel?: DocumentInitializationModel): InitialDocumentsSet | null { + if (!initModel) return null; + const answer: InitialDocumentsSet = { + sourceParameterMap: new Map(), + }; + if (initModel.sourceBody) { + const document = DocumentService.createDocument(initModel.sourceBody); + if (document) answer.sourceBodyDocument = document; + } + if (initModel.sourceParameters) { + Object.entries(initModel.sourceParameters).forEach(([key, value]) => { + const document = DocumentService.createDocument(value); + answer.sourceParameterMap.set(key, document ? document : new PrimitiveDocument(DocumentType.PARAM, key)); + }); + } + if (initModel.targetBody) { + const document = DocumentService.createDocument(initModel.targetBody); + if (document) answer.targetBodyDocument = document; + } + return answer; + } + + static getFieldStack(field: IField, includeItself: boolean = false) { + if (field instanceof PrimitiveDocument) return []; + const fieldStack: IField[] = []; + if (includeItself) fieldStack.push(field); + for (let next = field.parent; 'parent' in next && next !== next.parent; next = (next as IField).parent) { + fieldStack.push(next); + } + return fieldStack; + } + + static hasField(document: IDocument, field: IField) { + if ( + document.documentType !== field.ownerDocument.documentType || + document.documentId !== field.ownerDocument.documentId + ) + return false; + if (document instanceof PrimitiveDocument && document === field) return true; + + return DocumentService.isDescendant(document, field); + } + + static isDescendant(parent: IField | IDocument, child: IField): boolean { + return !!parent.fields.find((f) => f === child || DocumentService.isDescendant(f, child)); + } + + static getCompatibleField(document: IDocument, field: IField): IField | undefined { + if (document instanceof PrimitiveDocument) return field instanceof PrimitiveDocument ? document : undefined; + if (field instanceof PrimitiveDocument) return undefined; + + let left: IField | undefined = undefined; + const fieldStack = DocumentService.getFieldStack(field, true); + for (const right of fieldStack.reverse()) { + const parent: IParentType = left ? left : document; + left = parent.fields.find((leftTest: IField) => { + const isAttributeOrElementMatching = leftTest.isAttribute === right.isAttribute; + const isNamespaceMatching = + !leftTest.ownerDocument.isNamespaceAware || + !right.ownerDocument.isNamespaceAware || + leftTest.namespaceURI === right.namespaceURI; + return isAttributeOrElementMatching && isNamespaceMatching && leftTest.name === right.name; + }); + if (!left) return undefined; + } + return left; + } + + static getFieldFromPathSegments(document: IDocument, pathSegments: string[]) { + let parent: IDocument | IField = document; + for (const segment of pathSegments) { + if (!segment) continue; + const child: IField | undefined = parent.fields.find((f) => DocumentService.getFieldExpression(f) === segment); + if (!child) { + return undefined; + } + parent = child; + } + return parent; + } + + static getFieldFromPathExpression(document: IDocument, pathExpression: string) { + return DocumentService.getFieldFromPathSegments(document, pathExpression.split('/')); + } + + static getFieldExpression(field: IField) { + return field.isAttribute ? `@${field.name}` : field.name; + } + + static getFieldExpressionNS(field: IField, namespaceMap: { [prefix: string]: string }) { + let answer = field.isAttribute ? '@' : ''; + const nsPair = + field.namespaceURI && + Object.entries(namespaceMap).find(([prefix, uri]) => prefix && uri && field.namespaceURI === uri); + if (nsPair) answer += nsPair[0] + ':'; + return answer + field.name; + } + + static getOwnerDocument(docOrField: IParentType): DocumentType { + return ('ownerDocument' in docOrField ? docOrField.ownerDocument : docOrField) as DocumentType; + } + + static resolveTypeFragment(field: IField) { + if (field.namedTypeFragmentRefs.length === 0) return field; + const doc = DocumentService.getOwnerDocument(field); + field.namedTypeFragmentRefs.forEach((ref) => { + const fragment = doc.namedTypeFragments[ref]; + DocumentService.adoptTypeFragment(field, fragment); + }); + field.namedTypeFragmentRefs = []; + return field; + } + + private static adoptTypeFragment(field: IField, fragment: ITypeFragment) { + const doc = DocumentService.getOwnerDocument(field); + fragment.fields.forEach((f) => f.adopt(field)); + fragment.namedTypeFragmentRefs.forEach((childRef) => { + const childFragment = doc.namedTypeFragments[childRef]; + DocumentService.adoptTypeFragment(field, childFragment); + }); + } + + static isNonPrimitiveField(parent: IParentType) { + return parent && !('documentType' in parent); + } + + static isRecursiveField(field: IField) { + const name = field.name; + const namespace = field.namespaceURI; + const stack = DocumentService.getFieldStack(field); + return !!stack.find((f) => f.name === name && f.namespaceURI === namespace); + } + + static hasFields(document: IDocument) { + return !(document instanceof PrimitiveDocument) && document.fields.length > 0; + } + + static hasChildren(field: IField) { + return field.fields.length > 0 || field.namedTypeFragmentRefs.length > 0; + } +} diff --git a/packages/ui/src/services/mapping-serializer.service.test.ts b/packages/ui/src/services/mapping-serializer.service.test.ts new file mode 100644 index 000000000..31c4a4436 --- /dev/null +++ b/packages/ui/src/services/mapping-serializer.service.test.ts @@ -0,0 +1,324 @@ +import { EMPTY_XSL, MappingSerializerService, NS_XSL } from './mapping-serializer.service'; +import { BODY_DOCUMENT_ID } from '../models/datamapper/document'; +import { + ChooseItem, + FieldItem, + ForEachItem, + IfItem, + MappingTree, + OtherwiseItem, + ValueSelector, + WhenItem, +} from '../models/datamapper/mapping'; +import { DocumentType } from '../models/datamapper/path'; +import { Types } from '../models/datamapper/types'; + +import { shipOrderToShipOrderXslt, shipOrderToShipOrderInvalidForEachXslt, TestUtil } from '../stubs/data-mapper'; + +describe('MappingSerializerService', () => { + const sourceParameterMap = TestUtil.createParameterMap(); + const targetDoc = TestUtil.createTargetOrderDoc(); + + const domParser = new DOMParser(); + + it('createNew() should create am empty XSLT document', () => { + const xslt = MappingSerializerService.createNew(); + const stylesheet = xslt.getElementsByTagNameNS(NS_XSL, 'stylesheet'); + expect(stylesheet.length).toEqual(1); + expect(stylesheet[0].namespaceURI).toBe(NS_XSL); + expect(stylesheet[0].localName).toBe('stylesheet'); + const template = xslt.getElementsByTagNameNS(NS_XSL, 'template'); + expect(template.length).toEqual(1); + expect(template[0].namespaceURI).toBe(NS_XSL); + expect(template[0].localName).toBe('template'); + }); + + describe('deserialize()', () => { + it('should return an empty MappingTree', () => { + let mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + mappingTree = MappingSerializerService.deserialize(EMPTY_XSL, targetDoc, mappingTree, sourceParameterMap); + expect(mappingTree.children.length).toEqual(0); + mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + mappingTree = MappingSerializerService.deserialize('', targetDoc, mappingTree, sourceParameterMap); + expect(mappingTree.children.length).toEqual(0); + }); + + it('should deserialize XSLT', () => { + let mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + expect(Object.keys(mappingTree.namespaceMap).length).toEqual(0); + mappingTree = MappingSerializerService.deserialize( + shipOrderToShipOrderXslt, + targetDoc, + mappingTree, + sourceParameterMap, + ); + expect(Object.keys(mappingTree.namespaceMap).length).toEqual(1); + expect(mappingTree.namespaceMap['ns0']).toEqual('io.kaoto.datamapper.poc.test'); + expect(mappingTree.children.length).toEqual(1); + const shipOrderFieldItem = mappingTree.children[0] as FieldItem; + expect(shipOrderFieldItem.field.name).toEqual('ShipOrder'); + expect(shipOrderFieldItem.field.type).toEqual(Types.Container); + expect(shipOrderFieldItem.field.isAttribute).toBeFalsy(); + expect(shipOrderFieldItem.field.namespaceURI).toEqual('io.kaoto.datamapper.poc.test'); + expect(shipOrderFieldItem.field.maxOccurs).toEqual(1); + expect(shipOrderFieldItem.children.length).toEqual(4); + + const orderIdFieldItem = shipOrderFieldItem.children[0] as FieldItem; + expect(orderIdFieldItem.field.name).toEqual('OrderId'); + expect(orderIdFieldItem.field.type).not.toEqual(Types.Container); + expect(orderIdFieldItem.field.isAttribute).toBeTruthy(); + expect(orderIdFieldItem.field.namespaceURI).toEqual(''); + expect(orderIdFieldItem.field.maxOccurs).toEqual(1); + expect(orderIdFieldItem.children.length).toEqual(1); + let selector = orderIdFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder/@OrderId'); + + const ifItem = shipOrderFieldItem.children[1] as IfItem; + expect(ifItem.expression).toEqual("/ns0:ShipOrder/ns0:OrderPerson != ''"); + expect(ifItem.children.length).toEqual(1); + const orderPersonFieldItem = ifItem.children[0] as FieldItem; + expect(orderPersonFieldItem.field.name).toEqual('OrderPerson'); + expect(shipOrderFieldItem.field.type).toEqual(Types.Container); + expect(orderPersonFieldItem.field.isAttribute).toBeFalsy(); + expect(orderPersonFieldItem.field.namespaceURI).toEqual('io.kaoto.datamapper.poc.test'); + expect(orderPersonFieldItem.field.maxOccurs).toEqual(1); + expect(orderPersonFieldItem.children.length).toEqual(1); + selector = orderPersonFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder/ns0:OrderPerson'); + + const shipToFieldItem = shipOrderFieldItem.children[2] as FieldItem; + expect(shipToFieldItem.field.name).toEqual('ShipTo'); + expect(shipToFieldItem.field.isAttribute).toBeFalsy(); + expect(shipToFieldItem.field.type).toEqual(Types.Container); + expect(shipToFieldItem.field.namespaceURI).toEqual(''); + expect(shipToFieldItem.field.maxOccurs).toEqual(1); + expect(shipToFieldItem.children.length).toEqual(1); + selector = shipToFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder/ShipTo'); + + const forEachItem = shipOrderFieldItem.children[3] as ForEachItem; + expect(forEachItem.expression).toEqual('/ns0:ShipOrder/Item'); + expect(forEachItem.children.length).toEqual(1); + const itemFieldItem = forEachItem.children[0] as FieldItem; + expect(itemFieldItem.field.name).toEqual('Item'); + expect(itemFieldItem.field.type).toEqual(Types.Container); + expect(itemFieldItem.field.isAttribute).toBeFalsy(); + expect(itemFieldItem.field.namespaceURI).toEqual(''); + expect(itemFieldItem.field.maxOccurs).toBeGreaterThan(1); + expect(itemFieldItem.children.length).toEqual(4); + + const titleFieldItem = itemFieldItem.children[0] as FieldItem; + expect(titleFieldItem.field.name).toEqual('Title'); + expect(titleFieldItem.field.isAttribute).toBeFalsy(); + expect(titleFieldItem.field.type).not.toEqual(Types.Container); + expect(titleFieldItem.field.namespaceURI).toEqual(''); + expect(titleFieldItem.field.maxOccurs).toEqual(1); + expect(titleFieldItem.children.length).toEqual(1); + selector = titleFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('Title'); + + const chooseItem = itemFieldItem.children[1] as ChooseItem; + expect(chooseItem.children.length).toEqual(2); + + const whenItem = chooseItem.children[0] as WhenItem; + expect(whenItem.expression).toEqual("Note != ''"); + expect(whenItem.children.length).toEqual(1); + let noteFieldItem = whenItem.children[0] as FieldItem; + expect(noteFieldItem.field.name).toEqual('Note'); + expect(noteFieldItem.field.type).not.toEqual(Types.Container); + expect(noteFieldItem.field.isAttribute).toBeFalsy(); + expect(noteFieldItem.field.namespaceURI).toEqual(''); + expect(noteFieldItem.field.maxOccurs).toEqual(1); + expect(noteFieldItem.children.length).toEqual(1); + selector = noteFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('Note'); + + const otherwiseItem = chooseItem.children[1] as OtherwiseItem; + expect(otherwiseItem.children.length).toEqual(1); + noteFieldItem = otherwiseItem.children[0] as FieldItem; + expect(noteFieldItem.field.name).toEqual('Note'); + expect(noteFieldItem.field.type).not.toEqual(Types.Container); + expect(noteFieldItem.field.isAttribute).toBeFalsy(); + expect(noteFieldItem.field.namespaceURI).toEqual(''); + expect(noteFieldItem.field.maxOccurs).toEqual(1); + expect(noteFieldItem.children.length).toEqual(1); + selector = noteFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('Title'); + + const quantityFieldItem = itemFieldItem.children[2] as FieldItem; + expect(quantityFieldItem.field.name).toEqual('Quantity'); + expect(quantityFieldItem.field.type).not.toEqual(Types.Container); + expect(quantityFieldItem.field.isAttribute).toBeFalsy(); + expect(quantityFieldItem.field.namespaceURI).toEqual(''); + expect(quantityFieldItem.field.maxOccurs).toEqual(1); + expect(quantityFieldItem.children.length).toEqual(1); + selector = quantityFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('Quantity'); + + const priceFieldItem = itemFieldItem.children[3] as FieldItem; + expect(priceFieldItem.field.name).toEqual('Price'); + expect(priceFieldItem.field.type).not.toEqual(Types.Container); + expect(priceFieldItem.field.isAttribute).toBeFalsy(); + expect(priceFieldItem.field.namespaceURI).toEqual(''); + expect(priceFieldItem.field.maxOccurs).toEqual(1); + expect(priceFieldItem.children.length).toEqual(1); + selector = priceFieldItem.children[0] as ValueSelector; + expect(selector.expression).toEqual('Price'); + }); + + it('should deserialize incomplete XSLT', () => { + let mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + mappingTree = MappingSerializerService.deserialize( + shipOrderToShipOrderInvalidForEachXslt, + targetDoc, + mappingTree, + sourceParameterMap, + ); + const forEachItem = mappingTree.children[0].children[0] as ForEachItem; + expect(forEachItem.expression).toEqual(''); + const itemSelector = forEachItem.children[0].children[0] as ValueSelector; + expect(itemSelector.expression).toEqual('/ns0:ShipOrder/Item'); + }); + }); + + describe('serialize()', () => { + it('should return an empty XSLT document with empty mappings', () => { + const empty = MappingSerializerService.serialize( + new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID), + sourceParameterMap, + ); + expect(empty).toContain('This file is generated by Kaoto DataMapper. Do not edit'); + const dom = domParser.parseFromString(empty, 'application/xml'); + const template = dom + .evaluate('/xsl:stylesheet/xsl:template', dom, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE) + .iterateNext(); + expect(template).toBeTruthy(); + expect(template!.childNodes.length).toEqual(1); + expect(template!.childNodes[0].nodeType).toEqual(Node.TEXT_NODE); + }); + + it('should serialize mappings', () => { + let mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + mappingTree = MappingSerializerService.deserialize( + shipOrderToShipOrderXslt, + targetDoc, + mappingTree, + sourceParameterMap, + ); + const xslt = MappingSerializerService.serialize(mappingTree, sourceParameterMap); + const xsltDocument = domParser.parseFromString(xslt, 'text/xml'); + expect(xsltDocument.documentElement.getAttribute('xmlns:ns0')).toEqual('io.kaoto.datamapper.poc.test'); + const orderIdSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:attribute[@name="OrderId"]/xsl:value-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(orderIdSelect?.nodeValue).toEqual('/ns0:ShipOrder/@OrderId'); + const ifTest = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:if/@test', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(ifTest?.nodeValue).toEqual("/ns0:ShipOrder/ns0:OrderPerson != ''"); + const orderPersonSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:if/OrderPerson/xsl:value-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(orderPersonSelect?.nodeValue).toEqual('/ns0:ShipOrder/ns0:OrderPerson'); + const shipToSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/ShipTo/xsl:copy-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(shipToSelect?.nodeValue).toEqual('/ns0:ShipOrder/ShipTo'); + const forEachSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:for-each/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(forEachSelect?.nodeValue).toEqual('/ns0:ShipOrder/Item'); + const titleSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:for-each/Item/Title/xsl:value-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(titleSelect?.nodeValue).toEqual('Title'); + const chooseWhenTest = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:for-each/Item/xsl:choose/xsl:when/@test', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(chooseWhenTest?.nodeValue).toEqual("Note != ''"); + const chooseWhenSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:for-each/Item/xsl:choose/xsl:when/Note/xsl:value-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(chooseWhenSelect?.nodeValue).toEqual('Note'); + const chooseOtherwiseSelect = xsltDocument + .evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/xsl:for-each/Item/xsl:choose/xsl:otherwise/Note/xsl:value-of/@select', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ) + .iterateNext(); + expect(chooseOtherwiseSelect?.nodeValue).toEqual('Title'); + }); + + it('should serialize mappings with respecting Document field order', () => { + let mappingTree = new MappingTree(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + mappingTree = MappingSerializerService.deserialize( + shipOrderToShipOrderXslt, + targetDoc, + mappingTree, + sourceParameterMap, + ); + const shipOrderItem = mappingTree.children[0]; + shipOrderItem.children = shipOrderItem.children.reverse(); + const xslt = MappingSerializerService.serialize(mappingTree, sourceParameterMap); + const xsltDocument = domParser.parseFromString(xslt, 'text/xml'); + const shipOrderSelect = xsltDocument.evaluate( + '/xsl:stylesheet/xsl:template/ShipOrder/*', + xsltDocument, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + ); + const xslAttribute = shipOrderSelect.iterateNext() as Element; + expect(xslAttribute!.nodeName).toEqual('xsl:attribute'); + const xslIf = shipOrderSelect.iterateNext() as Element; + expect(xslIf!.nodeName).toEqual('xsl:if'); + expect(xslIf!.getAttribute('test')).toEqual("/ns0:ShipOrder/ns0:OrderPerson != ''"); + const shipTo = shipOrderSelect.iterateNext() as Element; + expect(shipTo!.nodeName).toEqual('ShipTo'); + const xslForEach = shipOrderSelect.iterateNext() as Element; + expect(xslForEach!.nodeName).toEqual('xsl:for-each'); + expect(xslForEach.getAttribute('select')).toEqual('/ns0:ShipOrder/Item'); + }); + }); +}); diff --git a/packages/ui/src/services/mapping-serializer.service.ts b/packages/ui/src/services/mapping-serializer.service.ts new file mode 100644 index 000000000..1e538c980 --- /dev/null +++ b/packages/ui/src/services/mapping-serializer.service.ts @@ -0,0 +1,359 @@ +import { BaseField, IDocument, IField, IParentType, PrimitiveDocument } from '../models/datamapper/document'; +import { + ChooseItem, + ConditionItem, + FieldItem, + ForEachItem, + IfItem, + MappingItem, + MappingParentType, + MappingTree, + OtherwiseItem, + ValueSelector, + ValueType, + WhenItem, +} from '../models/datamapper/mapping'; +import xmlFormat from 'xml-formatter'; +import { DocumentType } from '../models/datamapper/path'; +import { MappingService } from './mapping.service'; + +export const DO_NOT_EDIT_COMMENT = ''; +export const NS_XSL = 'http://www.w3.org/1999/XSL/Transform'; +export const EMPTY_XSL = ` +${DO_NOT_EDIT_COMMENT} + + + + + +`; + +export class MappingSerializerService { + static createNew() { + return new DOMParser().parseFromString(EMPTY_XSL, 'application/xml'); + } + + private static sortFields(left: IField, right: IField) { + return left.parent.fields.indexOf(left) - right.parent.fields.indexOf(right); + } + + private static sortMappingItem(left: MappingItem, right: MappingItem) { + const leftFields = + left instanceof FieldItem ? [left.field] : MappingService.getConditionalFields(left as ConditionItem); + if (leftFields.length === 0) return 1; + if (leftFields.find((f) => f.isAttribute)) return -1; + const rightFields = + right instanceof FieldItem ? [right.field] : MappingService.getConditionalFields(right as ConditionItem); + if (rightFields.length === 0) return -1; + if (rightFields.find((f) => f.isAttribute)) return 1; + const leftFirst = leftFields.sort(MappingSerializerService.sortFields)[0]; + const rightFirst = rightFields.sort(MappingSerializerService.sortFields)[0]; + return leftFirst.parent.fields.indexOf(leftFirst) - rightFirst.parent.fields.indexOf(rightFirst); + } + + /** + * Serialize the {@link MappingTree} model object into an XSLT mappings document string. + * @param mappings {@link MappingTree} object to write + * @param sourceParameterMap source paramter map + */ + static serialize(mappings: MappingTree, sourceParameterMap: Map): string { + const xslt = MappingSerializerService.createNew(); + MappingSerializerService.populateNamespaces(xslt, mappings.namespaceMap); + MappingSerializerService.populateParam(xslt, sourceParameterMap); + const template = MappingSerializerService.getRootTemplate(xslt); + mappings.children.sort(MappingSerializerService.sortMappingItem).forEach((mapping) => { + MappingSerializerService.populateMapping(template, mapping); + }); + return xmlFormat(new XMLSerializer().serializeToString(xslt)); + } + + private static populateNamespaces(xslt: Document, namespaceMap: { [prefix: string]: string }) { + const rootElement = xslt.documentElement; + Object.entries(namespaceMap).forEach( + ([prefix, uri]) => prefix && uri && rootElement.setAttribute(`xmlns:${prefix}`, uri), + ); + } + + private static getRootTemplate(xsltDocument: Document) { + const prefix = xsltDocument.lookupPrefix(NS_XSL); + const nsResolver = xsltDocument.createNSResolver(xsltDocument); + return xsltDocument + .evaluate(`/${prefix}:stylesheet/${prefix}:template[@match='/']`, xsltDocument, nsResolver, XPathResult.ANY_TYPE) + .iterateNext()! as Element; + } + + private static populateParam(xsltDocument: Document, sourceParameterMap: Map) { + const template = MappingSerializerService.getRootTemplate(xsltDocument); + sourceParameterMap.forEach((_doc, paramName) => { + const prefix = xsltDocument.lookupPrefix(NS_XSL); + const nsResolver = xsltDocument.createNSResolver(xsltDocument); + const existing = xsltDocument + .evaluate( + `/${prefix}:stylesheet/${prefix}:param[@name='${paramName}']`, + xsltDocument, + nsResolver, + XPathResult.ANY_TYPE, + ) + .iterateNext(); + if (!existing) { + const xsltParam = xsltDocument.createElementNS(NS_XSL, 'param'); + xsltParam.setAttribute('name', paramName); + (template.parentNode as Element).insertBefore(xsltParam, template); + } + }); + } + + private static populateMapping(parent: Element, mapping: MappingItem) { + let child: Element | null = null; + if (mapping instanceof ValueSelector) { + child = MappingSerializerService.populateValueSelector(parent, mapping); + } else if (mapping instanceof FieldItem) { + child = MappingSerializerService.populateFieldItem(parent, mapping); + } else if (mapping instanceof IfItem) { + child = MappingSerializerService.populateIfItem(parent, mapping); + } else if (mapping instanceof ChooseItem) { + child = MappingSerializerService.populateChooseItem(parent, mapping); + } else if (mapping instanceof ForEachItem) { + child = MappingSerializerService.populateForEachItem(parent, mapping); + } else if (mapping instanceof WhenItem) { + child = MappingSerializerService.populateWhenItem(parent, mapping); + } else if (mapping instanceof OtherwiseItem) { + child = MappingSerializerService.populateOtherwiseItem(parent, mapping); + } + if (child) + mapping.children + .sort(MappingSerializerService.sortMappingItem) + .forEach((childItem) => MappingSerializerService.populateMapping(child!, childItem)); + } + + private static populateValueSelector(parent: Element, selector: ValueSelector) { + const xsltDocument = parent.ownerDocument; + switch (selector.valueType) { + case ValueType.CONTAINER: { + const copyOf = xsltDocument.createElementNS(NS_XSL, 'copy-of'); + copyOf.setAttribute('select', selector.expression); + parent.appendChild(copyOf); + return copyOf; + } + default: { + const valueOf = xsltDocument.createElementNS(NS_XSL, 'value-of'); + valueOf.setAttribute('select', selector.expression); + parent.appendChild(valueOf); + return valueOf; + } + } + } + + private static populateFieldItem(parent: Element, mapping: FieldItem) { + const xsltDocument = parent.ownerDocument; + if (mapping.field.isAttribute) { + const xslAttribute = xsltDocument.createElementNS(NS_XSL, 'attribute'); + xslAttribute.setAttribute('name', mapping.field.name); + mapping.field.namespaceURI && xslAttribute.setAttribute('namespace', mapping.field.namespaceURI); + parent.appendChild(xslAttribute); + return xslAttribute; + } else { + const element = mapping.field.namespaceURI + ? xsltDocument.createElementNS(mapping.field.namespaceURI, mapping.field.name) + : xsltDocument.createElement(mapping.field.name); + parent.appendChild(element); + return element; + } + } + + private static populateIfItem(parent: Element, mapping: IfItem) { + const xsltDocument = parent.ownerDocument; + const xslIf = xsltDocument.createElementNS(NS_XSL, 'if'); + xslIf.setAttribute('test', mapping.expression); + parent.appendChild(xslIf); + return xslIf; + } + + private static populateChooseItem(parent: Element, _mapping: ChooseItem) { + const xsltDocument = parent.ownerDocument; + const xslChoose = xsltDocument.createElementNS(NS_XSL, 'choose'); + parent.appendChild(xslChoose); + return xslChoose; + } + + private static populateForEachItem(parent: Element, mapping: ForEachItem) { + const xsltDocument = parent.ownerDocument; + const xslForEach = xsltDocument.createElementNS(NS_XSL, 'for-each'); + xslForEach.setAttribute('select', mapping.expression); + parent.appendChild(xslForEach); + return xslForEach; + } + + private static populateWhenItem(parent: Element, mapping: WhenItem) { + const xsltDocument = parent.ownerDocument; + const xslWhen = xsltDocument.createElementNS(NS_XSL, 'when'); + xslWhen.setAttribute('test', mapping.expression); + parent.appendChild(xslWhen); + return xslWhen; + } + + private static populateOtherwiseItem(parent: Element, _mapping: OtherwiseItem) { + const xsltDocument = parent.ownerDocument; + const xslOtherwise = xsltDocument.createElementNS(NS_XSL, 'otherwise'); + parent.appendChild(xslOtherwise); + return xslOtherwise; + } + + /** + * Deserialize the XSLT mappings document into a {@link MappingTree} model object. + * @param xslt XSLT mappings document in string format + * @param targetDocument Target Document + * @param mappingTree {@link MappingTree} object to write + * @param sourceParameterMap source parameter map + */ + static deserialize( + xslt: string, + targetDocument: IDocument, + mappingTree: MappingTree, + sourceParameterMap: Map, + ): MappingTree { + mappingTree.children = []; + const xsltDoc = new DOMParser().parseFromString(xslt, 'application/xml'); + const template = xsltDoc.getElementsByTagNameNS(NS_XSL, 'template')[0]; + if (!template?.children) return mappingTree; + MappingSerializerService.restoreNamespaces(xsltDoc, mappingTree); + MappingSerializerService.restoreParam(xsltDoc, sourceParameterMap); + Array.from(template.children).forEach((item) => + MappingSerializerService.restoreMapping(item, targetDocument, mappingTree), + ); + return mappingTree; + } + + private static restoreNamespaces(xsltDocument: Document, mappingTree: MappingTree) { + mappingTree.namespaceMap = {}; + const rootElement = xsltDocument.documentElement; + Array.from(rootElement.attributes).forEach((attr) => { + if (!attr.nodeName.includes(':') || !attr.nodeValue || attr.nodeValue === NS_XSL) return; + const splitted = attr.nodeName.split(':'); + if (splitted[0] === 'xmlns') mappingTree.namespaceMap[splitted[1]] = attr.nodeValue; + }); + } + + private static restoreParam(xsltDocument: Document, sourceParameterMap: Map) { + const prefix = xsltDocument.lookupPrefix(NS_XSL); + const nsResolver = xsltDocument.createNSResolver(xsltDocument); + const params = xsltDocument.evaluate( + `/${prefix}:stylesheet/${prefix}:param`, + xsltDocument, + nsResolver, + XPathResult.ANY_TYPE, + ); + let param: Node | null; + while ((param = params.iterateNext())) { + const paramEl = param as Element; + const name = paramEl.getAttribute('name'); + if (!name || sourceParameterMap.has(name)) continue; + sourceParameterMap.set(name, new PrimitiveDocument(DocumentType.PARAM, name)); + } + } + + private static restoreMapping(item: Element, parentField: IParentType, parentMapping: MappingParentType) { + let mappingItem: MappingItem | null = null; + let fieldItem: IParentType | null = null; + if (item.namespaceURI === NS_XSL) { + switch (item.localName) { + case 'copy-of': { + const selector = new ValueSelector(parentMapping, ValueType.CONTAINER); + selector.expression = item.getAttribute('select') || ''; + mappingItem = selector; + break; + } + case 'value-of': { + const valueType = + 'isAttribute' in parentField && parentField.isAttribute ? ValueType.ATTRIBUTE : ValueType.VALUE; + const selector = new ValueSelector(parentMapping, valueType); + selector.expression = item.getAttribute('select') || ''; + mappingItem = selector; + break; + } + case 'if': { + const ifItem = new IfItem(parentMapping); + ifItem.expression = item.getAttribute('test') || ''; + mappingItem = ifItem; + break; + } + case 'choose': { + mappingItem = new ChooseItem(parentMapping); + break; + } + case 'when': { + const whenItem = new WhenItem(parentMapping); + whenItem.expression = item.getAttribute('test') || ''; + mappingItem = whenItem; + break; + } + case 'otherwise': { + mappingItem = new OtherwiseItem(parentMapping); + break; + } + case 'for-each': { + const forEachItem = new ForEachItem(parentMapping); + forEachItem.expression = item.getAttribute('select') || ''; + mappingItem = forEachItem; + break; + } + case 'attribute': { + if (parentField instanceof PrimitiveDocument) return; + const field = MappingSerializerService.getOrCreateAttributeField(item, parentField); + if (!field) return; + fieldItem = field; + mappingItem = new FieldItem(parentMapping, field); + break; + } + } + } else { + if (parentField instanceof PrimitiveDocument) return; + const field = MappingSerializerService.getOrCreateElementField(item, parentField); + if (!field) return; + fieldItem = field; + mappingItem = new FieldItem(parentMapping, field); + } + if (mappingItem) { + parentMapping.children.push(mappingItem); + Array.from(item.children).forEach((childItem) => + MappingSerializerService.restoreMapping(childItem, fieldItem ? fieldItem : parentField, mappingItem!), + ); + } + } + + private static getOrCreateAttributeField(item: Element, parentField: IParentType): IField | null { + const namespace = item.getAttribute('namespace'); + const name = item.getAttribute('name'); + if (!name) return null; + const existing = parentField.fields.find( + (child) => child.name === name && ((!namespace && !child.namespaceURI) || child.namespaceURI === namespace), + ); + if (existing) return existing; + const field = new BaseField( + parentField, + 'ownerDocument' in parentField ? parentField.ownerDocument : parentField, + name, + ); + field.isAttribute = true; + field.namespaceURI = namespace; + parentField.fields.push(field); + return field; + } + + private static getOrCreateElementField(item: Element, parentField: IParentType): IField { + const namespace = item.namespaceURI; + const name = item.localName; + const existing = parentField.fields.find( + (child) => child.name === name && ((!namespace && !child.namespaceURI) || child.namespaceURI === namespace), + ); + if (existing) return existing; + const field = new BaseField( + parentField, + 'ownerDocument' in parentField ? parentField.ownerDocument : parentField, + name, + ); + field.namespaceURI = namespace; + parentField.fields.push(field); + return field; + } +} diff --git a/packages/ui/src/services/mapping.service.test.ts b/packages/ui/src/services/mapping.service.test.ts new file mode 100644 index 000000000..2d8d157f3 --- /dev/null +++ b/packages/ui/src/services/mapping.service.test.ts @@ -0,0 +1,318 @@ +import { MappingService } from './mapping.service'; +import { + ChooseItem, + FieldItem, + IfItem, + MappingTree, + OtherwiseItem, + ValueSelector, + ValueType, + WhenItem, +} from '../models/datamapper/mapping'; +import { MappingSerializerService } from './mapping-serializer.service'; +import { DocumentType } from '../models/datamapper/path'; +import { XmlSchemaDocument } from './xml-schema-document.service'; +import { IDocument } from '../models/datamapper/document'; +import { shipOrderToShipOrderXslt, TestUtil } from '../stubs/data-mapper'; +import { XPathService } from './xpath/xpath.service'; + +describe('MappingService', () => { + let sourceDoc: XmlSchemaDocument; + let targetDoc: XmlSchemaDocument; + let paramsMap: Map; + let tree: MappingTree; + + beforeEach(() => { + sourceDoc = TestUtil.createSourceOrderDoc(); + targetDoc = TestUtil.createTargetOrderDoc(); + paramsMap = TestUtil.createParameterMap(); + tree = new MappingTree(targetDoc.documentType, targetDoc.documentId); + MappingSerializerService.deserialize(shipOrderToShipOrderXslt, targetDoc, tree, paramsMap); + }); + + describe('filterMappingsForField()', () => { + it('should filter mappings', () => { + let filtered = MappingService.filterMappingsForField(tree.children, targetDoc.fields[0]); + expect(filtered.length).toEqual(1); + expect((filtered[0] as FieldItem).field).toEqual(targetDoc.fields[0]); + filtered = MappingService.filterMappingsForField(tree.children, targetDoc.fields[0].fields[0]); + expect(filtered.length).toEqual(0); + }); + }); + + describe('removeAllMappingsForDocument()', () => { + it('should remove mappings for target document', () => { + expect(tree.children.length).toEqual(1); + MappingService.removeAllMappingsForDocument(tree, DocumentType.TARGET_BODY, targetDoc.documentId); + expect(tree.children.length).toEqual(0); + }); + + it('should remove mappings for source document', () => { + expect(tree.children.length).toEqual(1); + MappingService.removeAllMappingsForDocument(tree, DocumentType.SOURCE_BODY, sourceDoc.documentId); + expect(tree.children.length).toEqual(0); + }); + + it('should not remove mappings unrelated to the removed param', () => { + expect(tree.children.length).toEqual(1); + MappingService.removeAllMappingsForDocument(tree, DocumentType.PARAM, 'sourceParam1'); + expect(tree.children.length).toEqual(1); + }); + }); + + describe('removeStaleMappingsForDocument()', () => { + it('should remove mappings for removed source field', () => { + expect(tree.children[0].children.length).toEqual(4); + const orderPersonField = sourceDoc.fields[0].fields[1]; + expect(orderPersonField.name).toEqual('OrderPerson'); + sourceDoc.fields[0].fields.splice(1, 1); + MappingService.removeStaleMappingsForDocument(tree, sourceDoc); + expect(tree.children[0].children.length).toEqual(3); + }); + + it('should remove mappings for removed target field', () => { + expect(tree.children[0].children.length).toEqual(4); + const orderIdField = targetDoc.fields[0].fields[0]; + expect(orderIdField.name).toEqual('OrderId'); + targetDoc.fields[0].fields.splice(0, 1); + MappingService.removeStaleMappingsForDocument(tree, targetDoc); + expect(tree.children[0].children.length).toEqual(3); + }); + + it('should not remove mappings when unrelated field is removed', () => { + expect(tree.children[0].children.length).toEqual(4); + const shipToCountryField = targetDoc.fields[0].fields[2].fields[3]; + expect(shipToCountryField.name).toEqual('Country'); + targetDoc.fields[0].fields[2].fields.splice(3, 1); + MappingService.removeStaleMappingsForDocument(tree, targetDoc); + expect(tree.children[0].children.length).toEqual(4); + }); + + it('should not remove for-each mapping contents (source)', () => { + sourceDoc = TestUtil.createSourceOrderDoc(); + MappingService.removeStaleMappingsForDocument(tree, sourceDoc); + const shipOrderItem = tree.children[0]; + const forEachItem = shipOrderItem.children[3]; + expect(forEachItem.parent).toEqual(shipOrderItem); + expect(forEachItem.children.length).toEqual(1); + + const itemItem = forEachItem.children[0]; + expect(itemItem.parent).toEqual(forEachItem); + expect(itemItem.children.length).toEqual(4); + const titleItem = itemItem.children[0]; + expect(titleItem.parent).toEqual(itemItem); + expect(titleItem.children.length).toEqual(1); + expect((titleItem.children[0] as ValueSelector).expression).toEqual('Title'); + + const chooseItem = itemItem.children[1]; + expect(chooseItem.parent).toEqual(itemItem); + expect(chooseItem.children.length).toEqual(2); + const whenItem = chooseItem.children[0] as WhenItem; + expect(whenItem.parent).toEqual(chooseItem); + expect(whenItem.expression).toEqual("Note != ''"); + expect((whenItem.children[0].children[0] as ValueSelector).expression).toEqual('Note'); + const otherwiseItem = chooseItem.children[1] as OtherwiseItem; + expect(otherwiseItem.parent).toEqual(chooseItem); + expect((otherwiseItem.children[0].children[0] as ValueSelector).expression).toEqual('Title'); + + const quantityItem = itemItem.children[2]; + expect(quantityItem.parent).toEqual(itemItem); + expect(quantityItem.children.length).toEqual(1); + expect((quantityItem.children[0] as ValueSelector).expression).toEqual('Quantity'); + + const priceItem = itemItem.children[3]; + expect(priceItem.parent).toEqual(itemItem); + expect(priceItem.children.length).toEqual(1); + expect((priceItem.children[0] as ValueSelector).expression).toEqual('Price'); + + const links = MappingService.extractMappingLinks(tree, paramsMap, sourceDoc); + expect(links.length).toEqual(11); + links.forEach((link) => expect(link.sourceNodePath.includes(sourceDoc.fields[0].id)).toBeTruthy()); + }); + + it('should not remove for-each mapping contents (target)', () => { + targetDoc = TestUtil.createTargetOrderDoc(); + MappingService.removeStaleMappingsForDocument(tree, targetDoc); + const shipOrderItem = tree.children[0]; + const forEachItem = shipOrderItem.children[3]; + expect(forEachItem.parent).toEqual(shipOrderItem); + expect(forEachItem.children.length).toEqual(1); + + const itemItem = forEachItem.children[0]; + expect(itemItem.parent).toEqual(forEachItem); + expect(itemItem.children.length).toEqual(4); + const titleItem = itemItem.children[0]; + expect(titleItem.parent).toEqual(itemItem); + expect(titleItem.children.length).toEqual(1); + expect((titleItem.children[0] as ValueSelector).expression).toEqual('Title'); + + const chooseItem = itemItem.children[1]; + expect(chooseItem.parent).toEqual(itemItem); + expect(chooseItem.children.length).toEqual(2); + const whenItem = chooseItem.children[0] as WhenItem; + expect(whenItem.parent).toEqual(chooseItem); + expect(whenItem.expression).toEqual("Note != ''"); + expect((whenItem.children[0].children[0] as ValueSelector).expression).toEqual('Note'); + const otherwiseItem = chooseItem.children[1] as OtherwiseItem; + expect(otherwiseItem.parent).toEqual(chooseItem); + expect((otherwiseItem.children[0].children[0] as ValueSelector).expression).toEqual('Title'); + + const quantityItem = itemItem.children[2]; + expect(quantityItem.parent).toEqual(itemItem); + expect(quantityItem.children.length).toEqual(1); + expect((quantityItem.children[0] as ValueSelector).expression).toEqual('Quantity'); + + const priceItem = itemItem.children[3]; + expect(priceItem.parent).toEqual(itemItem); + expect(priceItem.children.length).toEqual(1); + expect((priceItem.children[0] as ValueSelector).expression).toEqual('Price'); + + const links = MappingService.extractMappingLinks(tree, paramsMap, sourceDoc); + expect(links.length).toEqual(11); + links.forEach((link) => expect(link.targetNodePath.includes(targetDoc.fields[0].id)).toBeTruthy()); + }); + + it("should not remove for-each targeted field when it doesn't have children (source)", () => { + const shipOrderItem = tree.children[0]; + const forEachItem = shipOrderItem.children[3]; + shipOrderItem.children = [forEachItem]; + const itemItem = forEachItem.children[0]; + itemItem.children = []; + MappingService.removeStaleMappingsForDocument(tree, sourceDoc); + expect(forEachItem.parent).toEqual(shipOrderItem); + expect(forEachItem.children.length).toEqual(1); + + const links = MappingService.extractMappingLinks(tree, paramsMap, sourceDoc); + expect(links.length).toEqual(1); + expect(links[0].sourceNodePath.includes(sourceDoc.fields[0].id)).toBeTruthy(); + }); + + it("should not remove for-each targeted field when it doesn't have children (target)", () => { + const shipOrderItem = tree.children[0]; + const forEachItem = shipOrderItem.children[3]; + shipOrderItem.children = [forEachItem]; + const itemItem = forEachItem.children[0]; + itemItem.children = []; + MappingService.removeStaleMappingsForDocument(tree, targetDoc); + expect(forEachItem.parent).toEqual(shipOrderItem); + expect(forEachItem.children.length).toEqual(1); + + const links = MappingService.extractMappingLinks(tree, paramsMap, sourceDoc); + expect(links.length).toEqual(1); + expect(links[0].targetNodePath.includes(targetDoc.fields[0].id)).toBeTruthy(); + }); + }); + + describe('addChooseWhenOtherwise()', () => { + it('should add conditions', () => { + const orderIdFieldItem = tree.children[0].children[0]; + expect(orderIdFieldItem.children.length).toEqual(1); + MappingService.addChooseWhenOtherwise(orderIdFieldItem, orderIdFieldItem.children[0]); + expect(orderIdFieldItem.children.length).toEqual(1); + const chooseItem = orderIdFieldItem.children[0]; + expect(chooseItem instanceof ChooseItem).toBeTruthy(); + expect(chooseItem.children[0] instanceof WhenItem).toBeTruthy(); + expect(chooseItem.children[1] instanceof OtherwiseItem).toBeTruthy(); + }); + }); + + describe('wrapWithFunction()', () => { + it('should wrap with xpath function', () => { + const concatFx = XPathService.functions.String.find((f) => f.name === 'concat'); + const valueSelector = new ValueSelector(tree); + valueSelector.expression = '/path/to/field'; + MappingService.wrapWithFunction(valueSelector, concatFx!); + expect(valueSelector.expression).toEqual('concat(/path/to/field)'); + }); + }); + + describe('mapToCondition()', () => { + it('should add to xpath', () => { + expect(tree.children[0].children[1] instanceof IfItem).toBeTruthy(); + const ifItem = tree.children[0].children[1] as IfItem; + expect(ifItem.expression).toEqual("/ns0:ShipOrder/ns0:OrderPerson != ''"); + MappingService.mapToCondition(ifItem, sourceDoc.fields[0].fields[1]); + expect(ifItem.expression).toEqual("/ns0:ShipOrder/ns0:OrderPerson != '', /ns0:ShipOrder/ns0:OrderPerson"); + }); + }); + + describe('mapToDocument()', () => { + it('should add ValueSelector', () => { + expect(tree.children.length).toEqual(1); + MappingService.mapToDocument(tree, sourceDoc.fields[0]); + expect(tree.children.length).toEqual(2); + expect(tree.children[1] instanceof ValueSelector).toBeTruthy(); + const selector = tree.children[1] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder'); + }); + }); + + describe('mapToField()', () => { + it('should add to xpath', () => { + const orderIdFieldItem = tree.children[0].children[0]; + orderIdFieldItem.children = []; + MappingService.mapToField(sourceDoc.fields[0].fields[0], orderIdFieldItem); + expect(orderIdFieldItem.children[0] instanceof ValueSelector); + const orderIdValueSelector = orderIdFieldItem.children[0] as ValueSelector; + expect(orderIdValueSelector.expression).toEqual('/ns0:ShipOrder/@OrderId'); + }); + + it('should populate namespace if not exists', () => { + tree.namespaceMap = {}; + const orderIdFieldItem = tree.children[0].children[0]; + orderIdFieldItem.children = []; + MappingService.mapToField(sourceDoc.fields[0].fields[0], orderIdFieldItem); + expect(orderIdFieldItem.children[0] instanceof ValueSelector); + const orderIdValueSelector = orderIdFieldItem.children[0] as ValueSelector; + expect(orderIdValueSelector.expression).toEqual('/ns0:ShipOrder/@OrderId'); + }); + }); + + describe('createValueSelector()', () => { + it('should create ValueSelector', () => { + const orderIdFieldItem = tree.children[0].children[0] as FieldItem; + expect(orderIdFieldItem.children[0] instanceof ValueSelector).toBeTruthy(); + orderIdFieldItem.children = []; + const valueSelector = MappingService.createValueSelector(orderIdFieldItem); + expect(valueSelector.valueType).toEqual(ValueType.ATTRIBUTE); + }); + }); + + describe('deleteMappingItem()', () => { + it('should delete', () => { + expect(tree.children[0].children.length).toEqual(4); + const orderIdValueSelector = tree.children[0].children[0].children[0] as ValueSelector; + MappingService.deleteMappingItem(orderIdValueSelector); + expect(tree.children[0].children.length).toEqual(3); + }); + }); + + describe('extractMappingLinks()', () => { + it('should return IMappingLink[]', () => { + const links = MappingService.extractMappingLinks(tree, paramsMap, sourceDoc); + expect(links.length).toEqual(11); + expect(links[0].sourceNodePath).toMatch('OrderId'); + expect(links[0].targetNodePath).toMatch('OrderId'); + expect(links[1].sourceNodePath).toMatch('OrderPerson'); + expect(links[1].targetNodePath).toMatch('/if-'); + expect(links[2].sourceNodePath).toMatch('OrderPerson'); + expect(links[2].targetNodePath).toMatch(/if-.*field-OrderPerson/); + expect(links[3].targetNodePath).toMatch('ShipTo'); + expect(links[3].targetNodePath).toMatch('ShipTo'); + expect(links[4].sourceNodePath).toMatch('Item'); + expect(links[4].targetNodePath).toMatch('/for-each'); + expect(links[5].sourceNodePath).toMatch('Title'); + expect(links[5].targetNodePath).toMatch(/for-each-.*field-Item-.*field-Title-.*/); + expect(links[6].sourceNodePath).toMatch('Note'); + expect(links[6].targetNodePath).toMatch(/for-each-.*field-Item-.*choose-.*when-.*/); + expect(links[7].sourceNodePath).toMatch('Note'); + expect(links[7].targetNodePath).toMatch(/for-each-.*field-Item-.*field-Note-.*/); + expect(links[8].sourceNodePath).toMatch('Title'); + expect(links[8].targetNodePath).toMatch(/for-each-.*field-Item-.*choose-.*otherwise-.*field-Note-.*/); + expect(links[9].sourceNodePath).toMatch('Quantity'); + expect(links[9].targetNodePath).toMatch(/for-each-.*field-Item-.*field-Quantity-.*/); + expect(links[10].sourceNodePath).toMatch('Price'); + expect(links[10].targetNodePath).toMatch(/for-each-.*field-Item-.*field-Price-.*/); + }); + }); +}); diff --git a/packages/ui/src/services/mapping.service.ts b/packages/ui/src/services/mapping.service.ts new file mode 100644 index 000000000..205f1794b --- /dev/null +++ b/packages/ui/src/services/mapping.service.ts @@ -0,0 +1,428 @@ +import { + FieldItem, + MappingTree, + MappingItem, + ChooseItem, + WhenItem, + OtherwiseItem, + ConditionItem, + IfItem, + ValueSelector, + MappingParentType, + ForEachItem, + ExpressionItem, + ValueType, + IFunctionDefinition, +} from '../models/datamapper/mapping'; +import { IDocument, IField, PrimitiveDocument } from '../models/datamapper/document'; +import { DocumentService } from './document.service'; +import { XPathService } from './xpath/xpath.service'; +import { IMappingLink } from '../models/datamapper/visualization'; +import { DocumentType, Path } from '../models/datamapper/path'; + +export class MappingService { + static filterMappingsForField(mappings: MappingItem[], field: IField): MappingItem[] { + if (!mappings) return []; + return mappings.filter((mapping) => { + if (mapping instanceof FieldItem) { + return mapping.field === field; + } else if (mapping instanceof ValueSelector) { + return false; + } else { + return MappingService.getConditionalFields(mapping as ConditionItem).includes(field); + } + }); + } + + private static getConditionalFieldItems(mapping: ConditionItem): FieldItem[] { + if (mapping instanceof ChooseItem) { + return [...mapping.when, mapping.otherwise].reduce((acc, branch) => { + branch && acc.push(...MappingService.getConditionalFieldItems(branch)); + return acc; + }, [] as FieldItem[]); + } else if ( + mapping instanceof IfItem || + mapping instanceof WhenItem || + mapping instanceof OtherwiseItem || + mapping instanceof ForEachItem + ) { + return mapping.children.reduce((acc, child) => { + child instanceof FieldItem && acc.push(child); + return acc; + }, [] as FieldItem[]); + } + return []; + } + + static getConditionalFields(mapping: ConditionItem): IField[] { + return MappingService.getConditionalFieldItems(mapping).map((item) => item.field); + } + + static removeAllMappingsForDocument(mappingTree: MappingTree, documentType: DocumentType, documentId: string) { + if (documentType === DocumentType.TARGET_BODY) { + MappingService.doRemoveAllMappingsForTargetDocument(mappingTree); + } else { + MappingService.doRemoveAllMappingsForSourceDocument(mappingTree, documentType, documentId); + } + return mappingTree; + } + + private static doRemoveAllMappingsForTargetDocument(mappingTree: MappingTree) { + mappingTree.children = []; + } + + private static doRemoveAllMappingsForSourceDocument( + item: MappingTree | MappingItem, + documentType: DocumentType, + documentId: string, + ) { + item.children = item.children.reduce((acc, child) => { + MappingService.doRemoveAllMappingsForSourceDocument(child, documentType, documentId); + if ( + child instanceof ExpressionItem && + MappingService.hasStaleSourceDocument(child.expression as string, documentType, documentId) + ) { + return acc; + } + if (child instanceof FieldItem && child.children.length === 0) return acc; + acc.push(child); + return acc; + }, [] as MappingItem[]); + } + + private static hasStaleSourceDocument(expression: string, documentType: DocumentType, documentId: string) { + const stalePath = XPathService.extractFieldPaths(expression).find((xpath) => { + const parsedPath = new Path(xpath); + return ( + (documentType === DocumentType.SOURCE_BODY && !parsedPath.parameterName) || + (documentType === DocumentType.PARAM && parsedPath.parameterName === documentId) + ); + }); + return !!stalePath; + } + + static removeStaleMappingsForDocument(mappingTree: MappingTree, document: IDocument) { + if (document.documentType === DocumentType.TARGET_BODY) { + MappingService.doRemoveStaleMappingsForTargetDocument(mappingTree, document); + } else { + MappingService.doRemoveStaleMappingsForSourceDocument(mappingTree, document); + } + return mappingTree; + } + + private static doRemoveStaleMappingsForTargetDocument(item: MappingTree | MappingItem, document: IDocument) { + item.children = item.children.reduce((acc, child) => { + MappingService.doRemoveStaleMappingsForTargetDocument(child, document); + let compatibleField: IField | undefined = undefined; + if (child instanceof FieldItem) { + compatibleField = DocumentService.getCompatibleField(document, child.field); + if (compatibleField) { + child = MappingService.updateFieldItemField(child, compatibleField); + } + } + if (compatibleField && child.children.length > 0) { + acc.push(child); + } else if (child.parent instanceof ConditionItem || child instanceof ConditionItem) { + acc.push(child); + } + return acc; + }, [] as MappingItem[]); + } + + private static updateFieldItemField(item: FieldItem, newField: IField): FieldItem { + const updated = MappingService.createFieldItem(item.parent, newField); + MappingService.adaptChildren(item, updated); + item.parent.children = item.parent.children.map((child) => (child === item ? updated : child)); + return updated; + } + + private static adaptChildren(from: MappingItem, to: MappingItem) { + to.children = from.children.map((child) => { + child.parent = to; + return child; + }); + } + + private static doRemoveStaleMappingsForSourceDocument(item: MappingTree | MappingItem, document: IDocument) { + item.children = item.children.reduce((acc, child) => { + MappingService.doRemoveStaleMappingsForSourceDocument(child, document); + if (child instanceof ExpressionItem && MappingService.hasStaleSourceField(child, document)) { + return acc; + } + if (!(child.parent instanceof ConditionItem) && child instanceof FieldItem && child.children.length === 0) { + return acc; + } + acc.push(child); + return acc; + }, [] as MappingItem[]); + } + + private static hasStaleSourceField(expressionItem: ExpressionItem, document: IDocument) { + const stalePath = XPathService.extractFieldPaths(expressionItem.expression).find((xpath) => { + const absPath = MappingService.getAbsolutePath(expressionItem, xpath); + const parsedPath = new Path(absPath); + if ( + (document.documentType === DocumentType.SOURCE_BODY && !parsedPath.parameterName) || + (document.documentType === DocumentType.PARAM && parsedPath.parameterName === document.documentId) + ) { + return !DocumentService.getFieldFromPathSegments( + document, + parsedPath.pathSegments.map((seg) => seg.name), + ); + } + }); + return !!stalePath; + } + + static wrapWithItem(wrapped: MappingItem, wrapper: MappingItem) { + wrapper.children.push(wrapped); + wrapped.parent.children = wrapped.parent.children.map((m) => (m !== wrapped ? m : wrapper)); + wrapped.parent = wrapper; + } + + static wrapWithForEach(wrapped: MappingItem) { + MappingService.wrapWithItem(wrapped, new ForEachItem(wrapped.parent)); + } + + static wrapWithIf(wrapped: MappingItem) { + MappingService.wrapWithItem(wrapped, new IfItem(wrapped.parent)); + } + + static wrapWithChooseWhenOtherwise(wrapped: MappingItem) { + const parent = wrapped.parent; + const chooseItem = new ChooseItem(parent, wrapped && wrapped instanceof FieldItem ? wrapped.field : undefined); + const whenItem = MappingService.addWhen(chooseItem); + const otherwiseItem = MappingService.addOtherwise(chooseItem); + whenItem.children = [wrapped]; + wrapped.parent = whenItem; + const otherwiseWrapped = wrapped.clone(); + otherwiseWrapped.parent = otherwiseItem; + otherwiseItem.children = [otherwiseWrapped]; + parent.children = parent.children.map((m) => (m !== wrapped ? m : chooseItem)); + } + + static addIf(parent: MappingParentType, mapping?: MappingItem) { + const ifItem = new IfItem(parent); + parent.children.push(ifItem); + ifItem.children.push(mapping ? mapping : MappingService.createValueSelector(ifItem)); + } + + static addChooseWhenOtherwise(parent: MappingParentType, mapping?: MappingItem) { + const chooseItem = new ChooseItem(parent, mapping && mapping instanceof FieldItem ? mapping.field : undefined); + MappingService.addWhen(chooseItem, mapping); + MappingService.addOtherwise(chooseItem, mapping?.clone()); + if (mapping) { + parent.children = parent.children.map((m) => (m !== mapping ? m : chooseItem)); + } + if (!parent.children.includes(chooseItem)) parent.children.push(chooseItem); + } + + static addWhen(chooseItem: ChooseItem, mapping?: MappingItem, field?: IField) { + const whenItem = new WhenItem(chooseItem); + + if (mapping) { + whenItem.children.push(mapping); + } else { + if (field) { + MappingService.createFieldItem(whenItem, field); + } else { + whenItem.children.push(MappingService.createValueSelector(whenItem)); + } + } + chooseItem.children.push(whenItem); + return whenItem; + } + + static addOtherwise(chooseItem: ChooseItem, mapping?: MappingItem, field?: IField) { + const newChildren = chooseItem.children.filter((c) => !(c instanceof OtherwiseItem)); + const otherwiseItem = new OtherwiseItem(chooseItem); + if (mapping) { + otherwiseItem.children.push(mapping); + } else { + if (field) { + MappingService.createFieldItem(otherwiseItem, field); + } else { + otherwiseItem.children.push(MappingService.createValueSelector(otherwiseItem)); + } + } + newChildren.push(otherwiseItem); + chooseItem.children = newChildren; + return otherwiseItem; + } + + static wrapWithFunction(condition: ExpressionItem, func: IFunctionDefinition) { + condition.expression = `${func.name}(${condition.expression})`; + } + + static mapToCondition(condition: MappingItem, source: PrimitiveDocument | IField) { + MappingService.registerNamespaceFromField(condition.mappingTree, source); + const absPath = XPathService.toXPath(source, condition.mappingTree.namespaceMap); + const relativePath = MappingService.getRelativePath(condition, absPath); + if (condition instanceof ForEachItem) { + condition.expression = relativePath; + } else if (condition instanceof ExpressionItem) { + condition.expression = XPathService.addSource(condition.expression as string, relativePath); + } + } + + private static getRelativePath(condition: MappingItem, absPath: string): string { + const parentPath = condition.parent.contextPath?.toAbsolutePathString(); + if (!parentPath) return absPath; + const index = absPath.indexOf(parentPath); + return index == -1 ? absPath : absPath.substring(index + parentPath.length + 1); + } + + private static getAbsolutePath(condition: MappingItem, xpath: string): string { + return condition.contextPath && !xpath.startsWith('$') && !xpath.startsWith('/') + ? condition.contextPath + '/' + xpath + : xpath; + } + + static mapToDocument(mappingTree: MappingTree, source: PrimitiveDocument | IField) { + let valueSelector = mappingTree.children.find((mapping) => mapping instanceof ValueSelector) as ValueSelector; + if (!valueSelector) { + valueSelector = MappingService.createValueSelector(mappingTree); + mappingTree.children.push(valueSelector); + } + MappingService.registerNamespaceFromField(mappingTree, source); + const path = XPathService.toXPath(source, mappingTree.namespaceMap); + valueSelector.expression = XPathService.addSource(valueSelector.expression, path); + } + + static mapToField(source: PrimitiveDocument | IField, targetFieldItem: MappingItem) { + let valueSelector = targetFieldItem?.children.find((child) => child instanceof ValueSelector) as ValueSelector; + if (!valueSelector) { + valueSelector = MappingService.createValueSelector(targetFieldItem); + targetFieldItem.children.push(valueSelector); + } + MappingService.registerNamespaceFromField(targetFieldItem.mappingTree, source); + const absPath = XPathService.toXPath(source, targetFieldItem.mappingTree.namespaceMap); + const relativePath = MappingService.getRelativePath(valueSelector, absPath); + valueSelector.expression = XPathService.addSource(valueSelector.expression, relativePath); + } + + static createFieldItem(parentItem: MappingParentType, field: IField) { + const fieldItem = new FieldItem(parentItem, field); + parentItem.children.push(fieldItem); + return fieldItem; + } + + private static registerNamespaceFromField(mappingTree: MappingTree, field: IField) { + if (DocumentService.isNonPrimitiveField(field.parent)) { + MappingService.registerNamespaceFromField(mappingTree, field.parent as IField); + } + if (!field.namespaceURI) return; + const existingns = Object.entries(mappingTree.namespaceMap).find( + ([_prefix, uri]) => field.namespaceURI && uri === field.namespaceURI, + ); + if (!existingns && field.namespaceURI) { + const prefix = field.namespacePrefix ?? MappingService.createNSPrefix(mappingTree); + mappingTree.namespaceMap[prefix] = field.namespaceURI; + } + } + + private static createNSPrefix(mappingTree: MappingTree) { + for (let index = 0; ; index++) { + const prefix = `ns${index}`; + if (!mappingTree.namespaceMap[prefix]) return prefix; + } + } + + static createValueSelector(parent: MappingParentType) { + const valueType = parent instanceof MappingTree ? ValueType.VALUE : MappingService.getValueTypeFor(parent); + return new ValueSelector(parent, valueType); + } + + private static getValueTypeFor(mapping: MappingItem): ValueType { + const field = MappingService.getTargetField(mapping); + return field?.isAttribute + ? ValueType.ATTRIBUTE + : field?.fields?.length && field.fields.length > 0 + ? ValueType.CONTAINER + : ValueType.VALUE; + } + + private static getTargetField(mapping: MappingItem) { + let item = mapping; + while (!(item instanceof FieldItem) && !(item.parent instanceof MappingTree)) item = item.parent; + if (item instanceof FieldItem) return item.field; + } + + static deleteMappingItem(item: MappingParentType) { + item.children = item.children.filter((child) => !(child instanceof ValueSelector)); + const isConditionItem = item instanceof ConditionItem; + const isParentFieldItem = 'parent' in item && item.parent instanceof FieldItem; + if (isConditionItem || isParentFieldItem) { + MappingService.deleteFromParent(item); + } + } + + private static deleteFromParent(item: MappingItem) { + item.parent.children = item.parent.children.filter((child) => child !== item); + const isParentFieldItem = item.parent instanceof FieldItem; + const isParentParentFieldItem = 'parent' in item.parent && item.parent.parent instanceof FieldItem; + const areNoChildren = item.parent.children.length === 0; + if (isParentFieldItem && isParentParentFieldItem && areNoChildren) { + MappingService.deleteFromParent(item.parent as FieldItem); + } + } + + static sortMappingItem(left: MappingItem, right: MappingItem) { + if (left instanceof ValueSelector) return -1; + if (right instanceof ValueSelector) return 1; + if (left instanceof WhenItem) return right instanceof OtherwiseItem ? -1 : 0; + if (right instanceof WhenItem) return left instanceof OtherwiseItem ? 1 : 0; + return 0; + } + + static extractMappingLinks( + item: MappingTree | MappingItem, + sourceParameterMap: Map, + sourceBody: IDocument, + ): IMappingLink[] { + const answer = [] as IMappingLink[]; + const targetNodePath = item.nodePath.toString(); + if (item instanceof ExpressionItem) { + answer.push(...MappingService.doExtractMappingLinks(item, targetNodePath, sourceParameterMap, sourceBody)); + } + if ('children' in item) { + item.children.forEach((child) => { + if ( + item instanceof FieldItem && + !(item.field.ownerDocument instanceof PrimitiveDocument) && + child instanceof ValueSelector + ) { + answer.push(...MappingService.doExtractMappingLinks(child, targetNodePath, sourceParameterMap, sourceBody)); + } else { + answer.push(...MappingService.extractMappingLinks(child, sourceParameterMap, sourceBody)); + } + }); + } + return answer; + } + + private static doExtractMappingLinks( + sourceExpressionItem: ExpressionItem, + targetNodePath: string, + sourceParameterMap: Map, + sourceBody: IDocument, + ) { + const sourceXPath = sourceExpressionItem.expression; + const validationResult = XPathService.validate(sourceXPath); + if (!validationResult.getCst() || validationResult.dataMapperErrors.length > 0) return []; + const fieldPaths = XPathService.extractFieldPaths(sourceXPath); + return fieldPaths.reduce((acc, xpath) => { + const absolutePath = MappingService.getAbsolutePath(sourceExpressionItem, xpath); + const path = new Path(absolutePath); + const document = path.parameterName ? sourceParameterMap.get(path.parameterName) : sourceBody; + const sourceNodePath = + document && + DocumentService.getFieldFromPathSegments( + document, + path.pathSegments.map((seg) => seg.name), + )?.path; + sourceNodePath && acc.push({ sourceNodePath: sourceNodePath.toString(), targetNodePath: targetNodePath }); + return acc; + }, [] as IMappingLink[]); + } +} diff --git a/packages/ui/src/services/visualization.service.test.ts b/packages/ui/src/services/visualization.service.test.ts new file mode 100644 index 000000000..24849b26a --- /dev/null +++ b/packages/ui/src/services/visualization.service.test.ts @@ -0,0 +1,560 @@ +import { VisualizationService } from './visualization.service'; +import { + DocumentNodeData, + FieldNodeData, + MappingNodeData, + TargetDocumentNodeData, + TargetFieldNodeData, + TargetNodeData, +} from '../models/datamapper/visualization'; +import { + ChooseItem, + ExpressionItem, + FieldItem, + ForEachItem, + IfItem, + MappingTree, + OtherwiseItem, + ValueSelector, + WhenItem, +} from '../models/datamapper/mapping'; +import { XmlSchemaDocument } from './xml-schema-document.service'; +import { MappingSerializerService } from './mapping-serializer.service'; +import { BODY_DOCUMENT_ID, IDocument, PrimitiveDocument } from '../models/datamapper/document'; +import { shipOrderToShipOrderInvalidForEachXslt, shipOrderToShipOrderXslt, TestUtil } from '../stubs/data-mapper'; +import { DocumentType } from '../models/datamapper/path'; + +describe('VisualizationService', () => { + let sourceDoc: XmlSchemaDocument; + let sourceDocNode: DocumentNodeData; + let targetDoc: XmlSchemaDocument; + let paramsMap: Map; + let tree: MappingTree; + let targetDocNode: TargetDocumentNodeData; + + beforeEach(() => { + sourceDoc = TestUtil.createSourceOrderDoc(); + sourceDocNode = new DocumentNodeData(sourceDoc); + targetDoc = TestUtil.createTargetOrderDoc(); + paramsMap = TestUtil.createParameterMap(); + tree = new MappingTree(targetDoc.documentType, targetDoc.documentId); + }); + + describe('without pre-populated mappings', () => { + beforeEach(() => { + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + }); + + describe('testNodePair()', () => { + it('should sort source, target', () => { + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + const { sourceNode, targetNode } = VisualizationService.testNodePair( + targetDocChildren[0], + sourceDocChildren[0], + ); + expect(sourceNode).toEqual(sourceDocChildren[0]); + expect(targetNode).toEqual(targetDocChildren[0]); + }); + }); + + describe('applyIf()', () => { + it('should add If', () => { + let docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + expect(docChildren.length).toEqual(1); + let shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + expect(shipOrderChildren.length).toEqual(4); + expect(shipOrderChildren[0].title).toEqual('OrderId'); + VisualizationService.applyIf(shipOrderChildren[0] as TargetNodeData); + + expect(tree.children[0].name).toEqual('field-ShipOrder'); + expect(tree.children[0].children[0].name).toEqual('if'); + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + expect(shipOrderChildren[0].title).toEqual('if'); + const ifChildren = VisualizationService.generateNonDocumentNodeDataChildren(shipOrderChildren[0]); + expect(ifChildren.length).toEqual(1); + expect(ifChildren[0].title).toEqual('OrderId'); + }); + + it('should add If on primitive target body', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(primitiveTargetDoc, tree); + VisualizationService.applyIf(targetDocNode); + + expect(VisualizationService.hasChildren(targetDocNode)).toBeTruthy(); + let targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + expect(targetDocChildren.length).toEqual(1); + const ifItem = (targetDocChildren[0] as MappingNodeData).mapping; + expect(ifItem instanceof IfItem).toBeTruthy(); + expect(ifItem.name).toEqual('if'); + + targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + const ifChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(ifChildren.length).toEqual(1); + expect((ifChildren[0] as MappingNodeData).mapping instanceof ValueSelector).toBeTruthy(); + }); + }); + + describe('applyChooseWhenOtherwise()', () => { + it('should add Choose-When-Otherwise', () => { + let docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + expect(docChildren.length).toEqual(1); + let shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + expect(shipOrderChildren.length).toEqual(4); + expect(shipOrderChildren[1].title).toEqual('OrderPerson'); + VisualizationService.applyChooseWhenOtherwise(shipOrderChildren[1] as TargetNodeData); + + expect(tree.children[0].name).toEqual('field-ShipOrder'); + expect(tree.children[0].children[0].name).toEqual('choose'); + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + + expect(shipOrderChildren[1].title).toEqual('choose'); + const chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(shipOrderChildren[1]); + expect(chooseChildren.length).toEqual(2); + + expect(chooseChildren[0].title).toEqual('when'); + const whenChildren = VisualizationService.generateNonDocumentNodeDataChildren(chooseChildren[0]); + expect(whenChildren.length).toEqual(1); + const whenOrderPerson = whenChildren[0] as MappingNodeData; + expect(whenOrderPerson.title).toEqual('OrderPerson'); + expect(whenOrderPerson.mapping.parent instanceof WhenItem).toBeTruthy(); + + expect(chooseChildren[1].title).toEqual('otherwise'); + const otherwiseChildren = VisualizationService.generateNonDocumentNodeDataChildren(chooseChildren[1]); + expect(otherwiseChildren.length).toEqual(1); + const otherwiseOrderPerson = otherwiseChildren[0] as MappingNodeData; + expect(otherwiseOrderPerson.title).toEqual('OrderPerson'); + expect(otherwiseOrderPerson.mapping.parent instanceof OtherwiseItem).toBeTruthy(); + }); + + it('should add Choose-When-Otherwise on primitive target body', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(primitiveTargetDoc, tree); + VisualizationService.applyChooseWhenOtherwise(targetDocNode); + + expect(VisualizationService.hasChildren(targetDocNode)).toBeTruthy(); + let targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + expect(targetDocChildren.length).toEqual(1); + const chooseItem = (targetDocChildren[0] as MappingNodeData).mapping; + expect(chooseItem instanceof ChooseItem).toBeTruthy(); + expect(chooseItem.name).toEqual('choose'); + + targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + const chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(chooseChildren.length).toEqual(2); + const whenItem = (chooseChildren[0] as MappingNodeData).mapping; + expect(whenItem instanceof WhenItem).toBeTruthy(); + expect(whenItem.children[0] instanceof ValueSelector).toBeTruthy(); + + const otherwiseItem = (chooseChildren[1] as MappingNodeData).mapping; + expect(otherwiseItem instanceof OtherwiseItem).toBeTruthy(); + expect(otherwiseItem.children[0] instanceof ValueSelector).toBeTruthy(); + }); + }); + + describe('applyWhen()', () => { + it('should addWhen', () => { + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + VisualizationService.applyChooseWhenOtherwise(targetShipOrderChildren[1] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + VisualizationService.applyWhen(targetShipOrderChildren[1] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[1]); + expect(chooseChildren.length).toEqual(3); + + const whenItem1 = (chooseChildren[0] as MappingNodeData).mapping; + expect(whenItem1 instanceof WhenItem).toBeTruthy(); + expect(whenItem1.children.length).toEqual(1); + expect(whenItem1.children[0] instanceof FieldItem).toBeTruthy(); + + const whenItem2 = (chooseChildren[1] as MappingNodeData).mapping; + expect(whenItem2 instanceof WhenItem).toBeTruthy(); + expect(whenItem2.children.length).toEqual(1); + expect(whenItem2.children[0] instanceof FieldItem).toBeTruthy(); + + const otherwiseItem = (chooseChildren[2] as MappingNodeData).mapping; + expect(otherwiseItem instanceof OtherwiseItem).toBeTruthy(); + expect(otherwiseItem.children.length).toEqual(1); + expect(otherwiseItem.children[0] instanceof FieldItem).toBeTruthy(); + }); + + it('should add When in primitive target body choose', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(primitiveTargetDoc, tree); + VisualizationService.applyChooseWhenOtherwise(targetDocNode); + + let targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + VisualizationService.applyWhen(targetDocChildren[0] as TargetNodeData); + + targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + const chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(chooseChildren.length).toEqual(3); + + const whenItem1 = (chooseChildren[0] as MappingNodeData).mapping; + expect(whenItem1 instanceof WhenItem).toBeTruthy(); + expect(whenItem1.children.length).toEqual(1); + expect(whenItem1.children[0] instanceof ValueSelector).toBeTruthy(); + + const whenItem2 = (chooseChildren[1] as MappingNodeData).mapping; + expect(whenItem2 instanceof WhenItem).toBeTruthy(); + expect(whenItem2.children.length).toEqual(1); + expect(whenItem2.children[0] instanceof ValueSelector).toBeTruthy(); + + const otherwiseItem = (chooseChildren[2] as MappingNodeData).mapping; + expect(otherwiseItem instanceof OtherwiseItem).toBeTruthy(); + expect(otherwiseItem.children.length).toEqual(1); + expect(otherwiseItem.children[0] instanceof ValueSelector).toBeTruthy(); + }); + }); + + describe('applyOtherwise()', () => { + it('should add Otherwise', () => { + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + VisualizationService.applyChooseWhenOtherwise(targetShipOrderChildren[1] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + let chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[1]); + VisualizationService.deleteMappingItem(chooseChildren[1] as MappingNodeData); + VisualizationService.applyOtherwise(targetShipOrderChildren[1] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[1]); + expect(chooseChildren.length).toEqual(2); + + const whenItem = (chooseChildren[0] as MappingNodeData).mapping; + expect(whenItem instanceof WhenItem).toBeTruthy(); + expect(whenItem.children.length).toEqual(1); + expect(whenItem.children[0] instanceof FieldItem).toBeTruthy(); + + const otherwiseItem = (chooseChildren[1] as MappingNodeData).mapping; + expect(otherwiseItem instanceof OtherwiseItem).toBeTruthy(); + expect(otherwiseItem.children.length).toEqual(1); + expect(otherwiseItem.children[0] instanceof FieldItem).toBeTruthy(); + }); + + it('should add Otherwise in primitive target body choose', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(primitiveTargetDoc, tree); + VisualizationService.applyChooseWhenOtherwise(targetDocNode); + + let targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + let chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + VisualizationService.deleteMappingItem(chooseChildren[1] as MappingNodeData); + VisualizationService.applyOtherwise(targetDocChildren[0] as TargetNodeData); + + targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + chooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(chooseChildren.length).toEqual(2); + + const whenItem = (chooseChildren[0] as MappingNodeData).mapping; + expect(whenItem instanceof WhenItem).toBeTruthy(); + expect(whenItem.children.length).toEqual(1); + expect(whenItem.children[0] instanceof ValueSelector).toBeTruthy(); + + const otherwiseItem = (chooseChildren[1] as MappingNodeData).mapping; + expect(otherwiseItem instanceof OtherwiseItem).toBeTruthy(); + expect(otherwiseItem.children.length).toEqual(1); + expect(otherwiseItem.children[0] instanceof ValueSelector).toBeTruthy(); + }); + }); + + describe('applyForEach()', () => { + it('should add for-each', () => { + let docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + expect(docChildren.length).toEqual(1); + let shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + expect(shipOrderChildren.length).toEqual(4); + expect(shipOrderChildren[3].title).toEqual('Item'); + VisualizationService.applyForEach(shipOrderChildren[3] as TargetFieldNodeData); + + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(docChildren[0]); + expect(shipOrderChildren[3].title).toEqual('for-each'); + const forEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(shipOrderChildren[3]); + expect(forEachChildren.length).toEqual(1); + expect(forEachChildren[0].title).toEqual('Item'); + }); + }); + + describe('applyValueSelector()', () => { + it('should apply value selector', () => { + expect(tree.children.length).toEqual(0); + const docChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + VisualizationService.applyValueSelector(docChildren[0] as TargetNodeData); + + expect(tree.children.length).toEqual(1); + expect(tree.children[0].children[0] instanceof ValueSelector).toBeTruthy(); + }); + + it('should apply value selector on primitive target body', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(primitiveTargetDoc, tree); + VisualizationService.applyValueSelector(targetDocNode); + + expect(VisualizationService.hasChildren(targetDocNode)).toBeFalsy(); + const targetDocChildren = VisualizationService.generatePrimitiveDocumentChildren(targetDocNode); + expect(targetDocChildren.length).toEqual(0); + }); + }); + + describe('getExpressionItemForNode()', () => { + it('should return ValueSelector for primitive target body', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + VisualizationService.engageMapping(tree, sourceShipOrderChildren[1] as FieldNodeData, targetDocNode); + + const expressionItem = VisualizationService.getExpressionItemForNode(targetDocNode); + expect(expressionItem?.expression).toEqual('/ns0:ShipOrder/ns0:OrderPerson'); + }); + }); + + describe('deleteMappingItem()', () => { + it('should delete primitive target body mapping', () => { + const primitiveTargetDoc = new PrimitiveDocument(DocumentType.TARGET_BODY, BODY_DOCUMENT_ID); + tree = new MappingTree(primitiveTargetDoc.documentType, primitiveTargetDoc.documentId); + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + VisualizationService.engageMapping(tree, sourceShipOrderChildren[1] as FieldNodeData, targetDocNode); + + VisualizationService.deleteMappingItem(targetDocNode); + const expressionItem = VisualizationService.getExpressionItemForNode(targetDocNode); + expect(expressionItem).toBeUndefined(); + }); + }); + + describe('engageMapping()', () => { + it('should engage mapping to a MappingItem', () => { + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + VisualizationService.applyIf(targetShipOrderChildren[1] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const ifItem = tree.children[0].children[0] as IfItem; + expect(ifItem.expression).toEqual(''); + VisualizationService.engageMapping( + tree, + sourceShipOrderChildren[1] as FieldNodeData, + targetShipOrderChildren[1] as TargetNodeData, + ); + + expect(ifItem.expression).toEqual('/ns0:ShipOrder/ns0:OrderPerson'); + }); + + it('should engage mapping to a Document', () => { + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + expect(tree.children.length).toEqual(0); + VisualizationService.engageMapping(tree, sourceDocChildren[0] as FieldNodeData, targetDocNode); + + expect(tree.children[0] instanceof ValueSelector).toBeTruthy(); + const selector = tree.children[0] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder'); + }); + + it('should engage mapping to a field', () => { + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + const targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(tree.children.length).toEqual(0); + VisualizationService.engageMapping( + tree, + sourceShipOrderChildren[1] as FieldNodeData, + targetShipOrderChildren[1] as TargetNodeData, + ); + + expect(tree.children[0] instanceof FieldItem).toBeTruthy(); + expect(tree.children[0].children[0] instanceof FieldItem).toBeTruthy(); + expect(tree.children[0].children[0].children[0] instanceof ValueSelector).toBeTruthy(); + const selector = tree.children[0].children[0].children[0] as ValueSelector; + expect(selector.expression).toEqual('/ns0:ShipOrder/ns0:OrderPerson'); + }); + + it("should engage regular mapping even if it's dropped to a for-each wrapped collection field", () => { + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + const sourceItem = sourceShipOrderChildren[3] as FieldNodeData; + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const targetItem = targetShipOrderChildren[3] as TargetFieldNodeData; + VisualizationService.applyForEach(targetItem); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const forEach = targetShipOrderChildren[3] as MappingNodeData; + const forEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(forEach); + VisualizationService.engageMapping(tree, sourceItem, forEachChildren[0] as TargetFieldNodeData); + + expect((forEach.mapping as ExpressionItem).expression).toEqual(''); + expect(((forEachChildren[0] as TargetFieldNodeData).mapping!.children[0] as ValueSelector).expression).toEqual( + '/ns0:ShipOrder/Item', + ); + }); + + it('should not remove for-each targeted field item when selector is removed', () => { + MappingSerializerService.deserialize(shipOrderToShipOrderInvalidForEachXslt, targetDoc, tree, paramsMap); + + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + let forEachItem = (targetShipOrderChildren[3] as MappingNodeData).mapping as ForEachItem; + expect(forEachItem.children.length).toEqual(1); + expect(forEachItem.expression).toEqual(''); + let targetForEachChildren = VisualizationService.generateNonDocumentNodeDataChildren( + targetShipOrderChildren[3], + ); + let itemItem = targetForEachChildren[0] as TargetFieldNodeData; + expect((itemItem.mapping?.children[0] as ValueSelector).expression).toEqual('/ns0:ShipOrder/Item'); + VisualizationService.deleteMappingItem(targetForEachChildren[0] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + forEachItem = (targetShipOrderChildren[3] as MappingNodeData).mapping as ForEachItem; + expect(forEachItem).toBeDefined(); + expect(forEachItem.children.length).toEqual(1); + expect(forEachItem.expression).toEqual(''); + targetForEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[3]); + itemItem = targetForEachChildren[0] as TargetFieldNodeData; + expect(itemItem.mapping).toBeDefined(); + expect(itemItem.mapping?.children.length).toEqual(0); + }); + + it('should not remove for-each targeted field item when descendent is removed', () => { + MappingSerializerService.deserialize(shipOrderToShipOrderInvalidForEachXslt, targetDoc, tree, paramsMap); + + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + let targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + let targetForEachChildren = VisualizationService.generateNonDocumentNodeDataChildren( + targetShipOrderChildren[3], + ); + let targetItemChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetForEachChildren[0]); + const sourceDocChildren = VisualizationService.generateStructuredDocumentChildren(sourceDocNode); + const sourceShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceDocChildren[0]); + const sourceItemChildren = VisualizationService.generateNonDocumentNodeDataChildren(sourceShipOrderChildren[3]); + VisualizationService.deleteMappingItem(targetForEachChildren[0] as TargetNodeData); + VisualizationService.engageMapping( + tree, + sourceItemChildren[0] as FieldNodeData, + targetItemChildren[0] as TargetFieldNodeData, + ); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + targetForEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[3]); + targetItemChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetForEachChildren[0]); + expect( + ((targetItemChildren[0] as TargetFieldNodeData).mapping?.children[0] as ValueSelector).expression, + ).toEqual('/ns0:ShipOrder/Item/Title'); + VisualizationService.deleteMappingItem(targetItemChildren[0] as TargetNodeData); + + targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + targetShipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const forEachItem = (targetShipOrderChildren[3] as MappingNodeData).mapping as ForEachItem; + expect(forEachItem).toBeDefined(); + expect(forEachItem.children.length).toEqual(1); + expect(forEachItem.expression).toEqual(''); + targetForEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetShipOrderChildren[3]); + targetItemChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetForEachChildren[0]); + expect((targetItemChildren[0] as TargetFieldNodeData).mapping?.children[0] as ValueSelector).toBeUndefined(); + }); + }); + }); + + describe('with pre-populated mappings', () => { + beforeEach(() => { + MappingSerializerService.deserialize(shipOrderToShipOrderXslt, targetDoc, tree, paramsMap); + targetDocNode = new TargetDocumentNodeData(targetDoc, tree); + }); + + describe('allowIfChoose()', () => { + it('should test if or choose is allowed', () => { + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + const shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + + expect(VisualizationService.allowIfChoose(targetDocNode)).toBeTruthy(); + expect(VisualizationService.allowIfChoose(targetDocChildren[0] as TargetNodeData)).toBeTruthy(); + expect(VisualizationService.allowIfChoose(shipOrderChildren[1] as TargetNodeData)).toBeFalsy(); + }); + }); + + describe('deleteMappingItem()', () => { + it('should delete', () => { + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + let shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(shipOrderChildren[1] instanceof MappingNodeData).toBeTruthy(); + VisualizationService.deleteMappingItem(shipOrderChildren[1] as TargetNodeData); + + shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(shipOrderChildren[1] instanceof TargetFieldNodeData).toBeTruthy(); + }); + }); + + describe('allowConditionMenu()', () => { + it('should test condition menu is allowed', () => { + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + const shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + expect(VisualizationService.allowConditionMenu(targetDocNode)).toBeTruthy(); + expect(VisualizationService.allowConditionMenu(targetDocChildren[0] as TargetNodeData)).toBeTruthy(); + + expect(shipOrderChildren.length).toEqual(4); + const orderIdNode = shipOrderChildren[0] as TargetFieldNodeData; + expect(orderIdNode.title).toEqual('OrderId'); + expect(VisualizationService.allowConditionMenu(orderIdNode)).toBeFalsy(); + + const ifNode = shipOrderChildren[1] as MappingNodeData; + expect(ifNode.title).toEqual('if'); + expect(VisualizationService.allowConditionMenu(ifNode)).toBeTruthy(); + + const shipToNode = shipOrderChildren[2] as TargetFieldNodeData; + expect(shipToNode.title).toEqual('ShipTo'); + expect(VisualizationService.allowConditionMenu(shipToNode)).toBeFalsy(); + + const forEachNode = shipOrderChildren[3] as MappingNodeData; + expect(forEachNode.title).toEqual('for-each'); + expect(VisualizationService.allowConditionMenu(forEachNode)).toBeFalsy(); + }); + }); + + describe('generateDndId()', () => { + it('should generate unique ID for when and otherwise children', () => { + const targetDocChildren = VisualizationService.generateStructuredDocumentChildren(targetDocNode); + const shipOrderChildren = VisualizationService.generateNonDocumentNodeDataChildren(targetDocChildren[0]); + const forEachChildren = VisualizationService.generateNonDocumentNodeDataChildren(shipOrderChildren[3]); + const forEachItemChildren = VisualizationService.generateNonDocumentNodeDataChildren(forEachChildren[0]); + const forEachChooseChildren = VisualizationService.generateNonDocumentNodeDataChildren(forEachItemChildren[1]); + const whenChildren = VisualizationService.generateNonDocumentNodeDataChildren(forEachChooseChildren[0]); + const otherwiseChildren = VisualizationService.generateNonDocumentNodeDataChildren(forEachChooseChildren[1]); + + const whenChildDndId = VisualizationService.generateDndId(whenChildren[0]); + expect(whenChildDndId).toContain('when'); + const otherwiseChildDndId = VisualizationService.generateDndId(otherwiseChildren[0]); + expect(otherwiseChildDndId).toContain('otherwise'); + }); + }); + }); +}); diff --git a/packages/ui/src/services/visualization.service.ts b/packages/ui/src/services/visualization.service.ts new file mode 100644 index 000000000..51e4c996b --- /dev/null +++ b/packages/ui/src/services/visualization.service.ts @@ -0,0 +1,331 @@ +import { + MappingNodeData, + DocumentNodeData, + FieldNodeData, + NodeData, + SourceNodeDataType, + TargetDocumentNodeData, + TargetNodeData, + TargetFieldNodeData, + TargetNodeDataType, +} from '../models/datamapper/visualization'; +import { + ChooseItem, + ExpressionItem, + FieldItem, + ForEachItem, + IfItem, + MappingItem, + MappingTree, + OtherwiseItem, + ValueSelector, + WhenItem, +} from '../models/datamapper/mapping'; +import { IField, PrimitiveDocument } from '../models/datamapper/document'; +import { MappingService } from './mapping.service'; +import { DocumentService } from './document.service'; + +type MappingNodePairType = { + sourceNode?: SourceNodeDataType; + targetNode?: TargetNodeDataType; +}; + +export class VisualizationService { + static generateNodeDataChildren(nodeData: NodeData) { + const isDocument = nodeData instanceof DocumentNodeData; + const isPrimitive = nodeData.isPrimitive; + return isDocument + ? isPrimitive + ? VisualizationService.generatePrimitiveDocumentChildren(nodeData as DocumentNodeData) + : VisualizationService.generateStructuredDocumentChildren(nodeData as DocumentNodeData) + : VisualizationService.generateNonDocumentNodeDataChildren(nodeData); + } + static generatePrimitiveDocumentChildren(document: DocumentNodeData): NodeData[] { + if (!(document instanceof TargetDocumentNodeData) || !document.mapping?.children) return []; + return document.mapping.children + .filter((child) => !(child instanceof ValueSelector)) + .map((child) => new MappingNodeData(document, child)); + } + + static generateStructuredDocumentChildren(document: DocumentNodeData): NodeData[] { + return VisualizationService.doGenerateNodeDataFromFields( + document, + document.document.fields, + document instanceof TargetDocumentNodeData ? document.mappingTree.children : undefined, + ); + } + + private static doGenerateNodeDataFromFields(parent: NodeData, fields: IField[], mappings?: MappingItem[]) { + const answer: NodeData[] = []; + if (parent.isPrimitive && mappings) { + mappings + .filter((m) => m instanceof ValueSelector) + .forEach((m) => answer.push(new MappingNodeData(parent as TargetNodeData, m))); + } + return fields.reduce((acc, field) => { + const mappingsForField = mappings ? MappingService.filterMappingsForField(mappings, field) : []; + if (mappingsForField.length === 0) { + const fieldNodeData = parent.isSource + ? new FieldNodeData(parent, field) + : new TargetFieldNodeData(parent as TargetNodeData, field); + acc.push(fieldNodeData); + return acc; + } + mappingsForField + .filter((mapping) => !VisualizationService.isExistingMapping(acc as TargetNodeData[], mapping)) + .sort((left, right) => MappingService.sortMappingItem(left, right)) + .forEach((mapping) => { + if (mapping instanceof FieldItem) { + const fieldNodeData = new TargetFieldNodeData(parent as TargetNodeData, field, mapping); + acc.push(fieldNodeData); + } else { + acc.push(new MappingNodeData(parent as TargetNodeData, mapping)); + } + }); + return acc; + }, answer); + } + + private static isExistingMapping(nodes: TargetNodeData[], mapping: MappingItem) { + return nodes.find((node) => 'mapping' in node && node.mapping === mapping); + } + + static generateNonDocumentNodeDataChildren(parent: NodeData): NodeData[] { + if (parent instanceof MappingNodeData) { + return parent.mapping?.children + ? parent.mapping.children + .sort((left, right) => MappingService.sortMappingItem(left, right)) + .map((m) => VisualizationService.createNodeDataFromMappingItem(parent, m)) + : []; + } else if (parent instanceof FieldNodeData) { + DocumentService.resolveTypeFragment(parent.field); + return VisualizationService.doGenerateNodeDataFromFields( + parent, + parent.field.fields, + parent instanceof TargetFieldNodeData ? parent.mapping?.children : undefined, + ); + } + return []; + } + + private static createNodeDataFromMappingItem(parent: TargetNodeData, item: MappingItem): NodeData { + return item instanceof FieldItem + ? new TargetFieldNodeData(parent, item.field, item) + : new MappingNodeData(parent, item); + } + + static testNodePair(fromNode: NodeData, toNode: NodeData): MappingNodePairType { + const answer: MappingNodePairType = {}; + if ((fromNode.isSource && toNode.isSource) || (!fromNode.isSource && !toNode.isSource)) return answer; + + const sourceNode = (fromNode.isSource ? fromNode : toNode) as SourceNodeDataType; + const targetNode = (fromNode.isSource ? toNode : fromNode) as TargetNodeDataType; + return { sourceNode, targetNode }; + } + + static isDocumentNode(nodeData: NodeData) { + return nodeData instanceof DocumentNodeData; + } + + static isPrimitiveDocumentNode(nodeData: NodeData) { + return nodeData instanceof DocumentNodeData && nodeData.document instanceof PrimitiveDocument; + } + + static isCollectionField(nodeData: NodeData) { + return nodeData instanceof FieldNodeData && nodeData.field?.maxOccurs && nodeData.field.maxOccurs > 1; + } + + static isAttributeField(nodeData: NodeData) { + return nodeData instanceof FieldNodeData && nodeData.field?.isAttribute; + } + + static isRecursiveField(nodeData: NodeData) { + return nodeData instanceof FieldNodeData && DocumentService.isRecursiveField(nodeData.field); + } + + static hasChildren(nodeData: NodeData) { + if (nodeData instanceof DocumentNodeData) { + if (DocumentService.hasFields(nodeData.document)) return true; + const isPrimitiveDocument = nodeData instanceof TargetDocumentNodeData && nodeData.isPrimitive; + const isPrimitiveDocumentWithConditionItem = + isPrimitiveDocument && !!nodeData.mapping.children.find((m) => !(m instanceof ValueSelector)); + if (isPrimitiveDocumentWithConditionItem) return true; + } + if (nodeData instanceof FieldNodeData) return DocumentService.hasChildren(nodeData.field); + if (nodeData instanceof MappingNodeData) return nodeData.mapping.children.length > 0; + return false; + } + + static shouldCollapseByDefault(nodeData: NodeData, initialExpandedRank: number, rank: number) { + if (nodeData instanceof DocumentNodeData) return false; + const isRecursiveField = VisualizationService.isRecursiveField(nodeData); + return isRecursiveField || rank > initialExpandedRank; + } + + static allowIfChoose(nodeData: TargetNodeData) { + if (nodeData instanceof MappingNodeData) { + const mapping = nodeData.mapping; + if ( + mapping instanceof ValueSelector || + mapping instanceof WhenItem || + mapping instanceof OtherwiseItem || + mapping instanceof IfItem || + mapping instanceof ChooseItem + ) + return false; + } + return true; + } + + static allowForEach(nodeData: TargetNodeData) { + return nodeData instanceof TargetFieldNodeData && VisualizationService.isCollectionField(nodeData); + } + + static isForEachNode(nodeData: TargetNodeData) { + return nodeData instanceof MappingNodeData && nodeData.mapping instanceof ForEachItem; + } + + static isValueSelectorNode(nodeData: TargetNodeData) { + return nodeData instanceof MappingNodeData && nodeData.mapping instanceof ValueSelector; + } + + static isChooseNode(nodeData: TargetNodeData) { + return nodeData instanceof MappingNodeData && nodeData.mapping instanceof ChooseItem; + } + + static isDeletableNode(nodeData: TargetNodeData) { + if (nodeData instanceof MappingNodeData) return true; + return VisualizationService.getFieldValueSelector(nodeData) !== undefined; + } + + static getExpressionItemForNode(nodeData: TargetNodeData) { + if (!nodeData.mapping) return; + if (nodeData.mapping instanceof ExpressionItem) return nodeData.mapping as ExpressionItem; + return VisualizationService.getFieldValueSelector(nodeData); + } + + private static getFieldValueSelector(nodeData: TargetNodeData) { + if (nodeData.mapping instanceof FieldItem || nodeData.mapping instanceof MappingTree) { + return nodeData.mapping.children.find((c) => c instanceof ValueSelector) as ValueSelector; + } + } + + static allowConditionMenu(nodeData: TargetNodeData) { + if (nodeData instanceof TargetFieldNodeData || nodeData instanceof TargetDocumentNodeData) { + const isForEachField = + 'parent' in nodeData && + nodeData.parent instanceof MappingNodeData && + nodeData.parent.mapping instanceof ForEachItem; + return !isForEachField && !VisualizationService.getExpressionItemForNode(nodeData); + } + const mappingNodeData = nodeData as MappingNodeData; + return ( + !(mappingNodeData.mapping instanceof WhenItem) && + !(mappingNodeData.mapping instanceof OtherwiseItem) && + !(mappingNodeData.mapping instanceof ForEachItem) + ); + } + + static allowValueSelector(nodeData: TargetNodeData) { + return ( + !VisualizationService.isChooseNode(nodeData) && + !VisualizationService.isValueSelectorNode(nodeData) && + !VisualizationService.isForEachNode(nodeData) + ); + } + + static hasValueSelector(nodeData: TargetNodeData) { + return !!(nodeData.mapping && nodeData.mapping.children.find((c) => c instanceof ValueSelector)); + } + + static deleteMappingItem(nodeData: TargetNodeData) { + if (nodeData.mapping) { + MappingService.deleteMappingItem(nodeData.mapping); + } + } + + static applyIf(nodeData: TargetNodeData) { + if (nodeData instanceof TargetDocumentNodeData) { + const valueSelector = nodeData.mappingTree.children.find((c) => c instanceof ValueSelector); + MappingService.addIf(nodeData.mappingTree, valueSelector); + } else if (nodeData instanceof MappingNodeData || nodeData instanceof TargetFieldNodeData) { + const mapping = nodeData.mapping + ? nodeData.mapping + : nodeData instanceof TargetFieldNodeData + ? (VisualizationService.getOrCreateFieldItem(nodeData) as FieldItem) + : undefined; + if (!mapping) return; + MappingService.wrapWithIf(mapping); + } + } + + static applyChooseWhenOtherwise(nodeData: TargetNodeData) { + if (nodeData instanceof TargetDocumentNodeData) { + if (nodeData.mappingTree.children.find((c) => c instanceof ChooseItem)) return; + + const valueSelector = nodeData.mappingTree.children.find((c) => c instanceof ValueSelector); + MappingService.addChooseWhenOtherwise(nodeData.mappingTree, valueSelector); + } else if (nodeData instanceof MappingNodeData || nodeData instanceof TargetFieldNodeData) { + if (nodeData.mapping && nodeData.mapping.children.find((c) => c instanceof ChooseItem)) return; + + const mapping = nodeData.mapping + ? nodeData.mapping + : nodeData instanceof TargetFieldNodeData + ? (VisualizationService.getOrCreateFieldItem(nodeData) as FieldItem) + : undefined; + if (!mapping) return; + MappingService.wrapWithChooseWhenOtherwise(mapping); + } + } + + static applyWhen(nodeData: TargetNodeData) { + const chooseItem = nodeData.mapping as ChooseItem; + MappingService.addWhen(chooseItem, undefined, chooseItem.field); + } + + static applyOtherwise(nodeData: TargetNodeData) { + const chooseItem = nodeData.mapping as ChooseItem; + MappingService.addOtherwise(chooseItem, undefined, chooseItem.field); + } + + static applyForEach(nodeData: TargetFieldNodeData) { + const fieldItem = VisualizationService.getOrCreateFieldItem(nodeData); + MappingService.wrapWithForEach(fieldItem as MappingItem); + } + + static applyValueSelector(nodeData: TargetNodeData) { + const mapping = + nodeData instanceof TargetFieldNodeData && !nodeData.mapping + ? VisualizationService.getOrCreateFieldItem(nodeData) + : nodeData.mapping; + if (!mapping) return; + const existing = mapping.children.find((c: MappingItem) => c instanceof ValueSelector); + if (!existing) { + const valueSelector = MappingService.createValueSelector(mapping); + mapping.children.push(valueSelector); + } + } + + static engageMapping(mappingTree: MappingTree, sourceNode: SourceNodeDataType, targetNode: TargetNodeData) { + const sourceField = 'document' in sourceNode ? (sourceNode.document as PrimitiveDocument) : sourceNode.field; + if (targetNode instanceof MappingNodeData) { + MappingService.mapToCondition(targetNode.mapping, sourceField); + } else if (targetNode instanceof TargetDocumentNodeData) { + MappingService.mapToDocument(mappingTree, sourceField); + } else if (targetNode instanceof TargetFieldNodeData) { + const item = VisualizationService.getOrCreateFieldItem(targetNode); + MappingService.mapToField(sourceField, item as MappingItem); + } + } + + private static getOrCreateFieldItem(nodeData: TargetNodeData): MappingItem { + if (nodeData.mapping) return nodeData.mapping as MappingItem; + const fieldNodeData = nodeData as TargetFieldNodeData; + const parentItem = VisualizationService.getOrCreateFieldItem(fieldNodeData.parent); + return MappingService.createFieldItem(parentItem, fieldNodeData.field); + } + + static generateDndId(nodeData: NodeData) { + return nodeData instanceof DocumentNodeData ? nodeData.id : nodeData.path.pathSegments.join('-'); + } +} diff --git a/packages/ui/src/services/xml-schema-document.service.test.ts b/packages/ui/src/services/xml-schema-document.service.test.ts new file mode 100644 index 000000000..f4fc397db --- /dev/null +++ b/packages/ui/src/services/xml-schema-document.service.test.ts @@ -0,0 +1,93 @@ +import { XmlSchemaDocumentService, XmlSchemaField } from './xml-schema-document.service'; +import { BODY_DOCUMENT_ID } from '../models/datamapper/document'; +import { DocumentType } from '../models/datamapper/path'; +import { Types } from '../models/datamapper/types'; +import { camelSpringXsd, shipOrderXsd, shipOrderEmptyFirstLineXsd, testDocumentXsd } from '../stubs/data-mapper'; + +describe('XmlSchemaDocumentService', () => { + it('should parse ShipOrder XML schema', () => { + const document = XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.SOURCE_BODY, + BODY_DOCUMENT_ID, + shipOrderXsd, + ); + expect(document).toBeDefined(); + const shipOrder = XmlSchemaDocumentService.getFirstElement(document.xmlSchema); + const fields: XmlSchemaField[] = []; + XmlSchemaDocumentService.populateElement(document, fields, shipOrder); + expect(fields.length > 0).toBeTruthy(); + expect(fields[0].name).toEqual('ShipOrder'); + expect(fields[0].fields[3].name).toEqual('Item'); + const itemTitleField = fields[0].fields[3].fields[0]; + expect(itemTitleField.name).toEqual('Title'); + expect(itemTitleField.type).not.toEqual(Types.Container); + }); + + it('should parse TestDocument XML schema', () => { + const document = XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.TARGET_BODY, + BODY_DOCUMENT_ID, + testDocumentXsd, + ); + expect(document).toBeDefined(); + const testDoc = XmlSchemaDocumentService.getFirstElement(document.xmlSchema); + const fields: XmlSchemaField[] = []; + XmlSchemaDocumentService.populateElement(document, fields, testDoc); + expect(fields.length > 0).toBeTruthy(); + }); + + it('should parse camel-spring.xsd XML schema', () => { + const document = XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.TARGET_BODY, + BODY_DOCUMENT_ID, + camelSpringXsd, + ); + expect(document).toBeDefined(); + expect(document.fields.length).toEqual(1); + const aggregate = document.fields[0]; + expect(aggregate.fields.length).toBe(0); + expect(aggregate.namedTypeFragmentRefs.length).toEqual(1); + expect(aggregate.namedTypeFragmentRefs[0]).toEqual('{http://camel.apache.org/schema/spring}aggregateDefinition'); + const aggregateDef = document.namedTypeFragments[aggregate.namedTypeFragmentRefs[0]]; + expect(aggregateDef.fields.length).toEqual(100); + expect(aggregateDef.namedTypeFragmentRefs[0]).toEqual('{http://camel.apache.org/schema/spring}output'); + const outputDef = document.namedTypeFragments[aggregateDef.namedTypeFragmentRefs[0]]; + expect(outputDef.fields.length).toEqual(0); + expect(outputDef.namedTypeFragmentRefs[0]).toEqual('{http://camel.apache.org/schema/spring}processorDefinition'); + const processorDef = document.namedTypeFragments[outputDef.namedTypeFragmentRefs[0]]; + expect(processorDef.fields.length).toEqual(2); + expect(processorDef.namedTypeFragmentRefs[0]).toEqual( + '{http://camel.apache.org/schema/spring}optionalIdentifiedDefinition', + ); + const optionalIdentifiedDef = document.namedTypeFragments[processorDef.namedTypeFragmentRefs[0]]; + expect(optionalIdentifiedDef.fields.length).toEqual(3); + expect(optionalIdentifiedDef.namedTypeFragmentRefs.length).toEqual(0); + }); + + it('should create XML Schema Document', () => { + const doc = XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.SOURCE_BODY, + 'ShipOrder.xsd', + shipOrderXsd, + ); + expect(doc.documentType).toEqual(DocumentType.SOURCE_BODY); + expect(doc.documentId).toEqual('ShipOrder.xsd'); + expect(doc.name).toEqual('ShipOrder.xsd'); + expect(doc.fields.length).toEqual(1); + }); + + it('should throw an error if there is a parse error on the XML schema', () => { + try { + XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.SOURCE_BODY, + 'ShipOrderEmptyFirstLine.xsd', + shipOrderEmptyFirstLineXsd, + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + expect(error.message).toContain('an XML declaration must be at the start of the document'); + return; + } + expect(true).toBeFalsy(); + }); +}); diff --git a/packages/ui/src/services/xml-schema-document.service.ts b/packages/ui/src/services/xml-schema-document.service.ts new file mode 100644 index 000000000..756db00b4 --- /dev/null +++ b/packages/ui/src/services/xml-schema-document.service.ts @@ -0,0 +1,415 @@ +import { + XmlSchema, + XmlSchemaAll, + XmlSchemaAllMember, + XmlSchemaAny, + XmlSchemaAttribute, + XmlSchemaAttributeGroup, + XmlSchemaAttributeGroupMember, + XmlSchemaAttributeGroupRef, + XmlSchemaAttributeOrGroupRef, + XmlSchemaChoice, + XmlSchemaChoiceMember, + XmlSchemaCollection, + XmlSchemaComplexContentExtension, + XmlSchemaComplexContentRestriction, + XmlSchemaComplexType, + XmlSchemaContentModel, + XmlSchemaElement, + XmlSchemaGroup, + XmlSchemaGroupParticle, + XmlSchemaGroupRef, + XmlSchemaParticle, + XmlSchemaRef, + XmlSchemaSequence, + XmlSchemaSequenceMember, + XmlSchemaSimpleContentExtension, + XmlSchemaSimpleContentRestriction, + XmlSchemaSimpleType, + XmlSchemaType, + XmlSchemaUse, +} from '@kaoto/xml-schema-ts'; +import { BaseDocument, BaseField, ITypeFragment } from '../models/datamapper/document'; +import { Types } from '../models/datamapper/types'; +import { DocumentType } from '../models/datamapper/path'; +import { DocumentService } from './document.service'; + +export interface XmlSchemaTypeFragment extends ITypeFragment { + fields: XmlSchemaField[]; +} + +export class XmlSchemaDocument extends BaseDocument { + rootElement: XmlSchemaElement; + fields: XmlSchemaField[] = []; + namedTypeFragments: Record = {}; + totalFieldCount = 0; + isNamespaceAware = true; + + constructor( + public xmlSchema: XmlSchema, + documentType: DocumentType, + documentId: string, + ) { + super(documentType, documentId); + this.name = documentId; + if (this.xmlSchema.getElements().size == 0) { + throw Error("There's no top level Element in the schema"); + } + // TODO let user choose the root element from top level elements if there're multiple + this.rootElement = XmlSchemaDocumentService.getFirstElement(this.xmlSchema); + XmlSchemaDocumentService.populateElement(this, this.fields, this.rootElement); + this.schemaType = 'XML'; + } +} + +type XmlSchemaParentType = XmlSchemaDocument | XmlSchemaField; + +export class XmlSchemaField extends BaseField { + fields: XmlSchemaField[] = []; + namespaceURI: string | null = null; + namespacePrefix: string | null = null; + + constructor( + public parent: XmlSchemaParentType, + public name: string, + public isAttribute: boolean, + ) { + super(parent, DocumentService.getOwnerDocument(parent), name); + } +} + +export class XmlSchemaDocumentService { + static parseXmlSchema(content: string): XmlSchema { + const collection = new XmlSchemaCollection(); + return collection.read(content, () => {}); + } + + static createXmlSchemaDocument(documentType: DocumentType, documentId: string, content: string) { + const schema = XmlSchemaDocumentService.parseXmlSchema(content); + return new XmlSchemaDocument(schema, documentType, documentId); + } + + static getFirstElement(xmlSchema: XmlSchema) { + return xmlSchema.getElements().values().next().value; + } + + /** + * Populate XML Element as a field into {@link fields} array passed in as an argument. + * @param parent + * @param fields + * @param element + */ + static populateElement(parent: XmlSchemaParentType, fields: XmlSchemaField[], element: XmlSchemaElement) { + const name = element.getWireName()!.getLocalPart()!; + const refTarget = element.getRef().getTarget(); + const resolvedElement = refTarget ? refTarget : element; + const field: XmlSchemaField = new XmlSchemaField(parent, name, false); + field.namespaceURI = resolvedElement.getWireName()!.getNamespaceURI(); + field.namespacePrefix = resolvedElement.getWireName()!.getPrefix(); + field.defaultValue = resolvedElement.defaultValue || resolvedElement.fixedValue; + field.minOccurs = resolvedElement.getMinOccurs(); + field.maxOccurs = resolvedElement.getMaxOccurs(); + fields.push(field); + + const ownerDoc = DocumentService.getOwnerDocument(parent); + const cachedTypeFragments = ownerDoc.namedTypeFragments; + ownerDoc.totalFieldCount++; + XmlSchemaDocumentService.populateSchemaType(cachedTypeFragments, field, resolvedElement.getSchemaType()); + } + + private static populateSchemaType( + cachedTypeFragments: Record, + field: XmlSchemaField, + schemaType: XmlSchemaType | null, + parentTypeFragment?: XmlSchemaTypeFragment, + ) { + if (!schemaType) return; + if (schemaType instanceof XmlSchemaSimpleType) { + const simple = schemaType as XmlSchemaSimpleType; + field.type = Types[simple.getName() as keyof typeof Types] || Types.AnyType; + return; + } + field.type = Types.Container; + if (schemaType instanceof XmlSchemaComplexType) { + const complex = schemaType as XmlSchemaComplexType; + const typeQName = complex.getQName(); + const children: XmlSchemaField[] = []; + const typeFragment: XmlSchemaTypeFragment = { fields: children, namedTypeFragmentRefs: [] }; + if (typeQName) { + parentTypeFragment + ? parentTypeFragment.namedTypeFragmentRefs.push(typeQName.toString()) + : field.namedTypeFragmentRefs.push(typeQName.toString()); + const cached = typeQName && cachedTypeFragments[typeQName.toString()]; + if (cached) return; + cachedTypeFragments[typeQName.toString()] = typeFragment; + } else { + field.fields = children; + } + + XmlSchemaDocumentService.populateContentModel( + cachedTypeFragments, + field, + children, + complex.getContentModel(), + typeFragment, + ); + const attributes: XmlSchemaAttributeOrGroupRef[] = complex.getAttributes(); + attributes.forEach((attr) => { + XmlSchemaDocumentService.populateAttributeOrGroupRef(field, children, attr); + }); + XmlSchemaDocumentService.populateParticle(field, children, complex.getParticle()); + } + } + + private static populateAttributeOrGroupRef( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + attr: XmlSchemaAttributeOrGroupRef, + ) { + if (attr instanceof XmlSchemaAttribute) { + XmlSchemaDocumentService.populateAttribute(parent, fields, attr); + } else if (attr instanceof XmlSchemaAttributeGroupRef) { + XmlSchemaDocumentService.populateAttributeGroupRef(parent, fields, attr); + } + } + + /** + * Populate XML Attribute as a field into {@link fields} array passed in as an argument. + * @param parent + * @param fields + * @param attr + */ + static populateAttribute(parent: XmlSchemaParentType, fields: XmlSchemaField[], attr: XmlSchemaAttribute) { + const name = attr.getWireName()!.getLocalPart()!; + const field = new XmlSchemaField(parent, name, true); + field.namespaceURI = attr.getWireName()!.getNamespaceURI(); + field.namespacePrefix = attr.getWireName()!.getPrefix(); + field.defaultValue = attr.getDefaultValue() || attr.getFixedValue(); + fields.push(field); + + const ownerDoc = DocumentService.getOwnerDocument(parent); + ownerDoc.totalFieldCount++; + + const use = attr.getUse(); + switch (use) { + case XmlSchemaUse.PROHIBITED: + field.maxOccurs = 0; + field.minOccurs = 0; + break; + case XmlSchemaUse.REQUIRED: + field.minOccurs = 1; + field.maxOccurs = 1; + break; + default: // OPTIONAL, NONE or not specified + field.minOccurs = 0; + field.maxOccurs = 1; + break; + } + } + + private static populateAttributeGroupRef( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + groupRef: XmlSchemaAttributeGroupRef, + ) { + const ref: XmlSchemaRef = groupRef.getRef(); + XmlSchemaDocumentService.populateAttributeGroup(parent, fields, ref.getTarget()); + } + + private static populateAttributeGroupMember( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + member: XmlSchemaAttributeGroupMember, + ) { + if (member instanceof XmlSchemaAttribute) { + XmlSchemaDocumentService.populateAttribute(parent, fields, member); + } else if (member instanceof XmlSchemaAttributeGroup) { + XmlSchemaDocumentService.populateAttributeGroup(parent, fields, member); + } else if (member instanceof XmlSchemaAttributeGroupRef) { + XmlSchemaDocumentService.populateAttributeGroupRef(parent, fields, member); + } + } + + private static populateAttributeGroup( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + group: XmlSchemaAttributeGroup | null, + ) { + if (group == null) { + return; + } + group + .getAttributes() + .forEach((member: XmlSchemaAttributeGroupMember) => + XmlSchemaDocumentService.populateAttributeGroupMember(parent, fields, member), + ); + } + + private static populateParticle( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + particle: XmlSchemaParticle | null, + ) { + if (particle == null) { + return; + } + if (particle instanceof XmlSchemaAny) { + XmlSchemaDocumentService.populateAny(fields, particle); + } else if (particle instanceof XmlSchemaElement) { + XmlSchemaDocumentService.populateElement(parent, fields, particle); + } else if (particle instanceof XmlSchemaGroupParticle) { + XmlSchemaDocumentService.populateGroupParticle(parent, fields, particle); + } else if (particle instanceof XmlSchemaGroupRef) { + XmlSchemaDocumentService.populateGroupRef(parent, fields, particle); + } + } + + private static populateAny(_fields: XmlSchemaField[], _schemaAny: XmlSchemaAny) { + /* TODO - xs:any allows arbitrary elements */ + } + + private static populateGroupParticle( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + groupParticle: XmlSchemaGroupParticle | null, + ) { + if (groupParticle == null) { + return; + } + if (groupParticle instanceof XmlSchemaChoice) { + const choice = groupParticle as XmlSchemaChoice; + choice + .getItems() + .forEach((member: XmlSchemaChoiceMember) => + XmlSchemaDocumentService.populateChoiceMember(parent, fields, member), + ); + } else if (groupParticle instanceof XmlSchemaSequence) { + const sequence = groupParticle as XmlSchemaSequence; + sequence + .getItems() + .forEach((member: XmlSchemaSequenceMember) => + XmlSchemaDocumentService.populateSequenceMember(parent, fields, member), + ); + } else if (groupParticle instanceof XmlSchemaAll) { + const all = groupParticle as XmlSchemaAll; + all + .getItems() + .forEach((member: XmlSchemaAllMember) => XmlSchemaDocumentService.populateAllMember(parent, fields, member)); + } + } + + private static populateGroupRef(parent: XmlSchemaParentType, fields: XmlSchemaField[], groupRef: XmlSchemaGroupRef) { + XmlSchemaDocumentService.populateGroupParticle(parent, fields, groupRef.getParticle()); + } + + private static populateChoiceMember( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + member: XmlSchemaChoiceMember, + ) { + if (member instanceof XmlSchemaGroupRef) { + XmlSchemaDocumentService.populateGroupRef(parent, fields, member); + } else if (member instanceof XmlSchemaGroup) { + XmlSchemaDocumentService.populateGroup(parent, fields, member); + } else if (member instanceof XmlSchemaParticle) { + XmlSchemaDocumentService.populateParticle(parent, fields, member); + } + } + + private static populateSequenceMember( + parent: XmlSchemaParentType, + fields: XmlSchemaField[], + member: XmlSchemaSequenceMember, + ) { + if (member instanceof XmlSchemaGroupRef) { + XmlSchemaDocumentService.populateGroupRef(parent, fields, member); + } else if (member instanceof XmlSchemaGroup) { + XmlSchemaDocumentService.populateGroup(parent, fields, member); + } else if (member instanceof XmlSchemaParticle) { + XmlSchemaDocumentService.populateParticle(parent, fields, member); + } + } + + private static populateAllMember(parent: XmlSchemaParentType, fields: XmlSchemaField[], member: XmlSchemaAllMember) { + if (member instanceof XmlSchemaParticle) { + XmlSchemaDocumentService.populateParticle(parent, fields, member); + } + } + + private static populateGroup(parent: XmlSchemaParentType, fields: XmlSchemaField[], group: XmlSchemaGroup) { + XmlSchemaDocumentService.populateParticle(parent, fields, group.getParticle()); + } + + private static populateContentModel( + cachedTypeFragments: Record, + parent: XmlSchemaField, + fields: XmlSchemaField[], + contentModel: XmlSchemaContentModel | null, + parentTypeFragment?: XmlSchemaTypeFragment, + ) { + if (!contentModel) return; + const content = contentModel.getContent(); + if (content instanceof XmlSchemaSimpleContentExtension) { + XmlSchemaDocumentService.populateSimpleContentExtension(parent, fields, content); + } else if (content instanceof XmlSchemaSimpleContentRestriction) { + XmlSchemaDocumentService.populateSimpleContentRestriction(parent, fields, content); + } else if (content instanceof XmlSchemaComplexContentExtension) { + XmlSchemaDocumentService.populateComplexContentExtension( + cachedTypeFragments, + parent, + fields, + content, + parentTypeFragment, + ); + } else if (content instanceof XmlSchemaComplexContentRestriction) { + XmlSchemaDocumentService.populateComplexContentRestriction(parent, fields, content); + } + } + + private static populateSimpleContentExtension( + parent: XmlSchemaField, + fields: XmlSchemaField[], + extension: XmlSchemaSimpleContentExtension, + ) { + const attributes = extension.getAttributes(); + attributes.forEach((attr) => { + XmlSchemaDocumentService.populateAttributeOrGroupRef(parent, fields, attr); + }); + } + + private static populateSimpleContentRestriction( + _parent: XmlSchemaField, + _fields: XmlSchemaField[], + _restriction: XmlSchemaSimpleContentRestriction, + ) { + // TODO restriction support + } + + private static populateComplexContentExtension( + cachedTypeFragments: Record, + parent: XmlSchemaField, + fields: XmlSchemaField[], + extension: XmlSchemaComplexContentExtension, + parentTypeFragment?: XmlSchemaTypeFragment, + ) { + const baseTypeName = extension.getBaseTypeName(); + const doc = DocumentService.getOwnerDocument(parent); + const baseType = baseTypeName ? doc.xmlSchema.getSchemaTypes().get(baseTypeName) : undefined; + if (baseType) { + XmlSchemaDocumentService.populateSchemaType(cachedTypeFragments, parent, baseType, parentTypeFragment); + } + const attributes = extension.getAttributes(); + attributes.forEach((attr) => { + XmlSchemaDocumentService.populateAttributeOrGroupRef(parent, fields, attr); + }); + XmlSchemaDocumentService.populateParticle(parent, fields, extension.getParticle()); + } + + private static populateComplexContentRestriction( + _parent: XmlSchemaField, + _fields: XmlSchemaField[], + _restriction: XmlSchemaComplexContentRestriction, + ) { + // TODO restriction support + } +} diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-boolean.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-boolean.ts new file mode 100644 index 000000000..2682b99a4 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-boolean.ts @@ -0,0 +1,24 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 9.3 Boolean - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#boolean-value-functions + */ +export const booleanFunctions = [ + { + name: 'not', + displayName: 'Not', + description: 'Inverts the xs:boolean value of the argument.', + returnType: Types.Boolean, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-context.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-context.ts new file mode 100644 index 000000000..31fc0a45c --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-context.ts @@ -0,0 +1,64 @@ +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; +import { Types } from '../../../models/datamapper/types'; + +/** + * 16 Context - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#context + */ +export const contextFunctions = [ + { + name: 'position', + displayName: 'Position', + description: 'Returns the position of the context item within the sequence of items currently being processed.', + returnType: Types.Integer, + arguments: [], + }, + { + name: 'last', + displayName: 'Last', + description: 'Returns the number of items in the sequence of items currently being processed.', + returnType: Types.Integer, + arguments: [], + }, + { + name: 'current-dateTime', + displayName: 'Current DateTime', + description: 'Returns the current xs:dateTime.', + returnType: Types.DateTime, + arguments: [], + }, + { + name: 'current-date', + displayName: 'Current Date', + description: 'Returns the current xs:date.', + returnType: Types.Date, + arguments: [], + }, + { + name: 'current-time', + displayName: 'Current Time', + description: 'Returns the current xs:time.', + returnType: Types.Time, + arguments: [], + }, + { + name: 'implicit-timezone', + displayName: 'Implicit Timezone', + description: 'Returns the value of the implicit timezone property from the dynamic context.', + returnType: Types.DayTimeDuration, + arguments: [], + }, + { + name: 'default-collation', + displayName: 'Default Collation', + description: 'Returns the value of the default collation property from the static context.', + returnType: Types.String, + arguments: [], + }, + { + name: 'static-base-uri', + displayName: 'Static Base URI', + description: 'Returns the value of the Base URI property from the static context.', + returnType: Types.AnyURI, + arguments: [], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-datetime.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-datetime.ts new file mode 100644 index 000000000..869f7a8fd --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-datetime.ts @@ -0,0 +1,248 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 10. Date and Time - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#durations-dates-times + */ +export const dateAndTimeFunctions = [ + { + name: 'years-from-duration', + displayName: 'Years From Duration', + description: 'Returns the year component of an xs:duration value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'months-from-duration', + displayName: 'Months From Duration', + description: 'Returns the months component of an xs:duration value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'days-from-duration', + displayName: 'Days From Duration', + description: 'Returns the days component of an xs:duration value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'hours-from-duration', + displayName: 'Hours From Duration', + description: 'Returns the hours component of an xs:duration value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'minutes-from-duration', + displayName: 'Minutes From Duration', + description: 'Returns the minutes component of an xs:duration value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'seconds-from-duration', + displayName: 'Seconds From Duration', + description: 'Returns the seconds component of an xs:duration value.', + returnType: Types.Decimal, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Duration, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'year-from-dateTime', + displayName: 'Year From Date Time', + description: 'Returns the year from an xs:dateTime value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'month-from-dateTime', + displayName: 'Month From Date Time', + description: 'Returns the month from an xs:dateTime value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'day-from-dateTime', + displayName: 'Day From Date Time', + description: 'Returns the day from an xs:dateTime value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'hours-from-dateTime', + displayName: 'Hours From Date Time', + description: 'Returns the hours from an xs:dateTime value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'minutes-from-dateTime', + displayName: 'Minutes From Date Time', + description: 'Returns the minutes from an xs:dateTime value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'seconds-from-dateTime', + displayName: 'Seconds From Date Time', + description: 'Returns the seconds from an xs:dateTime value.', + returnType: Types.Decimal, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Decimal, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'timezone-from-dateTime', + displayName: 'Timezone From Date Time', + description: 'Returns the timezone from an xs:dateTime value.', + returnType: Types.DayTimeDuration, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'year-from-date', + displayName: 'Year From Date', + description: 'Returns the year from an xs:date value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Date, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'month-from-date', + displayName: 'Month From Date', + description: 'Returns the month from an xs:date value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Date, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'day-from-date', + displayName: 'Day From Date', + description: 'Returns the day from an xs:date value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Date, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'timezone-from-date', + displayName: 'Timezone From Date', + description: 'Returns the timezone from an xs:date value.', + returnType: Types.DayTimeDuration, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Date, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'hours-from-time', + displayName: 'Hours From Time', + description: 'Returns the hours from an xs:time value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Time, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'minutes-from-time', + displayName: 'Minutes From Time', + description: 'Returns the minutes from an xs:time value.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Time, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'seconds-from-time', + displayName: 'Seconds From Time', + description: 'Returns the seconds from an xs:time value.', + returnType: Types.Decimal, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Time, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'timezone-from-time', + displayName: 'Timezone From Time', + description: 'Returns the timezone from an xs:time value.', + returnType: Types.DayTimeDuration, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Time, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'adjust-dateTime-to-timezone', + displayName: 'Adjust DateTime to Timezone', + description: 'Adjusts an xs:dateTime value to a specific timezone, or to no timezone at all.', + returnType: Types.DateTime, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.DateTime, minOccurs: 1, maxOccurs: 1 }, + { + name: 'timezone', + displayName: '$timezone', + description: '$timezone', + type: Types.DayTimeDuration, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'adjust-date-to-timezone', + displayName: 'Adjust Date to Timezone', + description: 'Adjusts an xs:date value to a specific timezone, or to no timezone at all.', + returnType: Types.Date, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Date, minOccurs: 1, maxOccurs: 1 }, + { + name: 'timezone', + displayName: '$timezone', + description: '$timezone', + type: Types.DayTimeDuration, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'adjust-time-to-timezone', + displayName: 'Adjust Time to Timezone', + description: 'Adjusts an xs:time value to a specific timezone, or to no timezone at all.', + returnType: Types.Time, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Time, minOccurs: 1, maxOccurs: 1 }, + { + name: 'timezone', + displayName: '$timezone', + description: '$timezone', + type: Types.DayTimeDuration, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-node.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-node.ts new file mode 100644 index 000000000..d5e7c1e16 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-node.ts @@ -0,0 +1,79 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 14 Node - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#node-functions + */ +export const nodeFunctions = [ + { + name: 'name', + displayName: 'Name', + description: 'Returns the name of the context node or the specified node as an xs:string.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Node, minOccurs: 0, maxOccurs: 1 }, + ], + }, + { + name: 'local-name', + displayName: 'Local Name', + description: 'Returns the local name of the context node or the specified node as an xs:NCName.', + }, + { + name: 'namespace-uri', + displayName: 'Namespace URI', + description: + 'Returns the namespace URI as an xs:anyURI for the xs:QName of the argument node or the context node if' + + ' the argument is omitted. This may be the URI corresponding to the zero-length string if the xs:QName' + + ' is in no namespace.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Node, minOccurs: 0, maxOccurs: 1 }, + ], + }, + { + name: 'number', + displayName: 'Number', + description: + 'Returns the value of the context item after atomization or the specified argument converted to an xs:double.', + returnType: Types.Double, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.AnyAtomicType, minOccurs: 0, maxOccurs: 1 }, + ], + }, + { + name: 'lang', + displayName: 'Lang', + description: + 'Returns true or false, depending on whether the language of the given node or the context node, as defined' + + ' using the xml:lang attribute, is the same as, or a sublanguage of, the language specified by the argument.', + returnType: Types.Boolean, + arguments: [ + { + name: 'testlarg', + displayName: '$testlang', + description: '$testlang', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'node', + displayName: '$node', + description: '$node', + type: Types.Node, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'root', + displayName: 'Root', + description: 'Returns the root of the tree to which the node argument belongs.', + returnType: Types.Node, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.Node, minOccurs: 0, maxOccurs: 1 }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-numeric.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-numeric.ts new file mode 100644 index 000000000..19a21c9f7 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-numeric.ts @@ -0,0 +1,63 @@ +import { Types } from '../../../models/datamapper/types'; + +/** + * 6.4 Numeric - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#numeric-value-functions + */ +export const numericFunctions = [ + { + name: 'abs', + displayName: 'Absolute', + description: 'Returns the absolute value of the argument.', + returnType: Types.Numeric, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.Numeric, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'ceiling', + displayName: 'Ceiling', + description: 'Returns the smallest number with no fractional part that is greater than or equal to the argument.', + returnType: Types.Numeric, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.Numeric, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'floor', + displayName: 'Floor', + group: 'Numeric', + description: 'Returns the largest number with no fractional part that is less than or equal to the argument.', + returnType: Types.Numeric, + arguments: [ + { name: 'arg', displayName: 'arg', description: 'Argument', type: Types.Numeric, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'round', + displayName: 'Round', + description: 'Rounds to the nearest number with no fractional part.', + returnType: Types.Numeric, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.Numeric, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'round-half-to-even', + displayName: 'Round Half To Even', + description: + 'Takes a number and a precision and returns a number rounded to the given precision. If the fractional part' + + ' is exactly half, the result is the number whose least significant digit is even.', + returnType: Types.Numeric, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.Numeric, minOccurs: 1, maxOccurs: 1 }, + { + name: 'precision', + displayName: 'Precision', + description: 'Precision', + type: Types.Integer, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, +]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-qname.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-qname.ts new file mode 100644 index 000000000..74a15930b --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-qname.ts @@ -0,0 +1,117 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 11 QName - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#QName-funcs + */ +export const qnameFunctions = [ + { + name: 'resolve-QName', + displayName: 'Resolve QName', + description: + 'Returns an xs:QName with the lexical form given in the first argument. The prefix is resolved using the' + + ' in-scope namespaces for a given element.', + returnType: Types.QName, + arguments: [ + { name: 'qname', displayName: '$qname', description: '$qname', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'QName', + displayName: 'QName', + description: + 'Returns an xs:QName with the namespace URI given in the first argument and the local name and prefix in' + + ' the second argument.', + returnType: Types.QName, + arguments: [ + { + name: 'paramURI', + displayName: '$paramURI', + description: '$paramURI', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'paramQName', + displayName: '$paramQName', + description: '$paramQName', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'prefix-from-QName', + displayName: 'Prefix From QName', + description: 'Returns an xs:NCName representing the prefix of the xs:QName argument.', + returnType: Types.NCName, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.QName, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'local-name-from-QName', + displayName: 'Local Name From QName', + description: 'Returns an xs:NCName representing the local name of the xs:QName argument.', + returnType: Types.NCName, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.QName, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'namespace-uri--from-QName', + displayName: 'Namespace URI From QName', + description: + 'Returns the namespace URI for the xs:QName argument. If the xs:QName is in no namespace, the zero-length' + + ' string is returned.', + returnType: Types.AnyURI, + arguments: [ + { name: 'arg', displayName: '$arg', description: '$arg', type: Types.QName, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'namespace-uri-for-prefix', + displayName: 'Namespace URI For Prefix', + description: + 'Returns the namespace URI of one of the in-scope namespaces for the given element,' + + ' identified by its namespace prefix.', + returnType: Types.AnyURI, + arguments: [ + { + name: 'prefix', + displayName: '$prefix', + description: '$prefix', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'element', + displayName: '$element', + description: '$element', + type: Types.Element, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'in-scope-prefixes', + displayName: 'In Scope Prefixes', + description: 'Returns the prefixes of the in-scope namespaces for the given element.', + returnType: Types.String, + returnCollection: true, + arguments: [ + { + name: 'element', + displayName: '$element', + description: '$element', + type: Types.Element, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-sequence.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-sequence.ts new file mode 100644 index 000000000..a265c5888 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-sequence.ts @@ -0,0 +1,518 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 15 Sequence - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#sequence-functions + */ +export const sequenceFunctions = [ + { + name: 'boolean', + displayName: 'Boolean', + description: 'Computes the effective boolean value of the argument sequence.', + returnType: Types.Boolean, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'index-of', + displayName: 'Index Of', + description: + 'Returns a sequence of xs:integers, each of which is the index of a member of the sequence specified as' + + ' the first argument that is equal to the value of the second argument. If no members of the specified' + + ' sequence are equal to the value of the second argument, the empty sequence is returned.', + returnType: Types.Integer, + returnCollection: true, + arguments: [ + { + name: 'seqParam', + displayName: '$seqParam', + description: '$seqParam', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'srchParam', + displayName: '$srchParam', + description: '$srchParam', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'empty', + displayName: 'Empty', + description: 'Indicates whether or not the provided sequence is empty.', + returnType: Types.Boolean, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'exists', + displayName: 'Exists', + description: 'Indicates whether or not the provided sequence is not empty.', + returnType: Types.Boolean, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'distinct-values', + displayName: 'Distinct Values', + description: + 'Returns a sequence in which all but one of a set of duplicate values, based on value equality, have been' + + ' deleted. The order in which the distinct values are returned is implementation dependent.', + returnType: Types.AnyAtomicType, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'insert-before', + displayName: 'Insert Before', + description: 'Inserts an item or sequence of items at a specified position in a sequence.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'target', + displayName: '$target', + description: '$target', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'position', + displayName: '$position', + description: '$position', + type: Types.Integer, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'inserts', + displayName: '$inserts', + description: '$inserts', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'remove', + displayName: 'Remove', + description: 'Removes an item from a specified position in a sequence.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'target', + displayName: '$target', + description: '$target', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'position', + displayName: '$position', + description: '$position', + type: Types.Integer, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'reverse', + displayName: 'Reverse', + description: 'Reverses the order of items in a sequence.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'subsequence', + displayName: 'Subsequence', + description: 'Returns the subsequence of a given sequence, identified by location.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'sourceSeq', + displayName: '$sourceSeq', + description: '$sourceSeq', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'startingLoc', + displayName: '$startingLoc', + description: '$startingLoc', + type: Types.Double, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'length', + displayName: '$length', + description: '$length', + type: Types.Double, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'unordered', + displayName: 'Unordered', + description: 'Returns the items in the given sequence in a non-deterministic order.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'sourceSeq', + displayName: '$sourceSeq', + description: '$sourceSeq', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'zero-or-one', + displayName: 'Zero Or One', + description: 'Returns the input sequence if it contains zero or one items. Raises an error otherwise.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'one-or-more', + displayName: 'One Or More', + description: 'Returns the input sequence if it contains one or more items. Raises an error otherwise.', + returnType: Types.Item, + returnCollection: true, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'exactly-one', + displayName: 'Exactly One', + description: 'Returns the input sequence if it contains exactly one item. Raises an error otherwise.', + returnType: Types.Item, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'deep-equal', + displayName: 'Deep Equal', + description: 'Returns true if the two arguments have items that compare equal in corresponding positions.', + returnType: Types.Boolean, + arguments: [ + { + name: 'parameter1', + displayName: '$parameter1', + description: 'parameter1', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'parameter2', + displayName: '$parameter2', + description: 'parameter2', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'collation', + displayName: '$collation', + description: 'collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'count', + displayName: 'Count', + description: 'Returns the number of items in a sequence.', + returnType: Types.Integer, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.Item, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'avg', + displayName: 'Average', + description: 'Returns the average of a sequence of values.', + returnType: Types.AnyAtomicType, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'max', + displayName: 'Maximum', + description: 'Returns the maximum value from a sequence of comparable values.', + returnType: Types.AnyAtomicType, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'collation', + displayName: '$collation', + description: 'collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'min', + displayName: 'Minimum', + description: 'Returns the minimum value from a sequence of comparable values.', + returnType: Types.AnyAtomicType, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'collation', + displayName: '$collation', + description: 'collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'sum', + displayName: 'Sum', + description: 'Returns the sum of a sequence of values.', + returnType: Types.AnyAtomicType, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.AnyAtomicType, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'zero', + displayName: '$zero', + description: 'zero', + type: Types.AnyAtomicType, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'id', + displayName: 'ID', + description: + 'Returns the sequence of element nodes having an ID value matching the one or more of the supplied IDREF values.', + returnType: Types.Element, + returnCollection: true, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.String, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'node', + displayName: '$node', + description: '$node', + type: Types.Node, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'idref', + displayName: 'IDREF', + description: + 'Returns the sequence of element or attribute nodes with an IDREF value matching one or more of the' + + ' supplied ID values.', + returnType: Types.Node, + returnCollection: true, + arguments: [ + { + name: 'arg', + displayName: '$arg', + description: '$arg', + type: Types.String, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'node', + displayName: '$node', + description: '$node', + type: Types.Node, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'doc', + displayName: 'DOC', + description: 'Returns a document node retrieved using the specified URI.', + returnType: Types.DocumentNode, + arguments: [{ name: 'uri', displayName: '$uri', type: Types.String, minOccurs: 1, maxOccurs: 1 }], + }, + { + name: 'doc-available', + displayName: 'DOC available', + description: 'Returns true if a document node can be retrieved using the specified URI.', + returnType: Types.Boolean, + arguments: [{ name: 'uri', displayName: '$uri', type: Types.String, minOccurs: 1, maxOccurs: 1 }], + }, + { + name: 'collection', + displayName: 'Collection', + description: + 'Returns a sequence of nodes retrieved using the specified URI or the nodes in the default collection.', + returnType: Types.Node, + returnCollection: true, + arguments: [{ name: 'arg', displayName: '$arg', type: Types.String, minOccurs: 0, maxOccurs: 1 }], + }, + { + name: 'element-with-id', + displayName: 'Element With ID', + description: + 'Returns the sequence of element nodes that have an ID value matching the value of one or more of the' + + ' IDREF values supplied in $arg.', + returnType: Types.Element, + returnCollection: true, + arguments: [ + { name: 'arg', displayName: '$arg', type: Types.String, minOccurs: 1, maxOccurs: Number.MAX_VALUE }, + { name: 'node', displayName: '$node', type: Types.Node, minOccurs: 0, maxOccurs: 1 }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-string.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-string.ts new file mode 100644 index 000000000..ade04ea0e --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions-string.ts @@ -0,0 +1,402 @@ +import { Types } from '../../../models/datamapper/types'; +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; + +/** + * 7.4 String - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#string-value-functions + */ +export const stringFunctions = [ + { + name: 'concat', + displayName: 'Concatenate', + description: 'Concatenates two or more xs:anyAtomicType arguments cast to xs:string.', + returnType: Types.String, + arguments: [ + { + name: 'args', + displayName: '$args', + description: 'Arguments', + type: Types.AnyAtomicType, + minOccurs: 2, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + ], + }, + { + name: 'string-join', + displayName: 'Concatenate with Delimiter', + description: + 'Returns the xs:string produced by concatenating a sequence of xs:strings using an optional separator.', + returnType: Types.String, + arguments: [ + { + name: 'arg1', + displayName: '$arg1', + description: 'Arguments', + type: Types.String, + minOccurs: 1, + maxOccurs: Number.MAX_SAFE_INTEGER, + }, + { + name: 'arg2', + displayName: '$arg2', + description: 'Separator', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'substring', + displayName: 'Substring', + description: 'Returns the xs:string located at a specified place within an argument xs:string.', + returnType: Types.String, + arguments: [ + { + name: 'sourceString', + displayName: '$sourceString', + description: 'Source String', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'startingLoc', + displayName: '$startingLoc', + description: 'Starting Location', + type: Types.Double, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'length', + displayName: '$length', + description: 'Length', + type: Types.Double, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'string-length', + displayName: 'String Length', + description: 'Returns the length of the argument.', + returnType: Types.Integer, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 0, maxOccurs: 1 }, + ], + }, + { + name: 'normalize-space', + displayName: 'Normalize Space', + description: 'Returns the whitespace-normalized value of the argument.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 0, maxOccurs: 1 }, + ], + }, + { + name: 'normalize-unicode', + displayName: 'Normalize Unicode', + description: + 'Returns the normalized value of the first argument in the normalization form specified by the second argument.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'normalizationForm', + displayName: '$normalizationForm', + description: 'Normalization Form', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'upper-case', + displayName: 'Uppercase', + description: 'Returns the upper-cased value of the argument.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'lower-case', + displayName: 'Lowercase', + description: 'Returns the lower-cased value of the argument.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'translate', + displayName: 'Translate', + description: + 'Returns the first xs:string argument with occurrences of characters contained in the second argument' + + ' replaced by the character at the corresponding position in the third argument.', + returnType: Types.String, + arguments: [ + { name: 'arg', displayName: '$arg', description: 'Argument', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'mapString', + displayName: '$mapString', + description: 'Map String', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'transString', + displayName: '$transString', + description: 'Translate String', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'encode-for-uri', + displayName: 'Encode for URI', + description: + 'Returns the xs:string argument with certain characters escaped to enable the resulting string to be used' + + ' as a path segment in a URI.', + returnType: Types.String, + arguments: [ + { + name: 'uri-part', + displayName: '$uri-part', + description: 'An URI part', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + ], + }, + { + name: 'iri-to-uri', + displayName: 'IRI to URI', + description: + 'Returns the xs:string argument with certain characters escaped to enable the resulting string to be used' + + ' as (part of) a URI.', + returnType: Types.String, + arguments: [ + { name: 'iri', displayName: '$iri', description: 'IRI string', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + ], + }, + { + name: 'escape-html-uri', + displayName: 'Escape HTML URI', + description: + 'Returns the xs:string argument with certain characters escaped in the manner that html user agents handle' + + ' attribute values that expect URIs.', + returnType: Types.String, + arguments: [ + { name: 'uri', displayName: '$uri', description: 'URI string', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + ], + }, +] as IFunctionDefinition[]; + +/** + * 7.5 Substring Matching - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#substring.functions + */ +export const substringMatchingFunctions = [ + { + name: 'contains', + displayName: 'Contains', + description: 'Indicates whether one xs:string contains another xs:string. A collation may be specified.', + returnType: Types.Boolean, + arguments: [ + { name: 'arg1', displayName: '$arg1', description: '$arg1', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { name: 'arg2', displayName: '$arg2', description: '$arg2', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: 'xs:string', + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'starts-with', + displayName: 'Starts With', + description: + 'Indicates whether the value of one xs:string begins with the collation units of another xs:string.' + + ' A collation may be specified.', + returnType: Types.Boolean, + arguments: [ + { name: 'arg1', displayName: '$arg1', description: '$arg1', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { name: 'arg2', displayName: '$arg2', description: '$arg2', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'ends-with', + displayName: 'Ends With', + description: + 'Indicates whether the value of one xs:string ends with the collation units of another xs:string.' + + ' A collation may be specified.', + returnType: Types.Boolean, + arguments: [ + { name: 'arg1', displayName: '$arg1', description: '$arg1', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { name: 'arg2', displayName: '$arg2', description: '$arg2', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'substring-before', + displayName: 'Substring Before', + description: + 'Returns the collation units of one xs:string that precede in that xs:string the collation units of another' + + ' xs:string. A collation may be specified.', + returnType: Types.String, + arguments: [ + { name: 'arg1', displayName: '$arg1', description: '$arg1', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { name: 'arg2', displayName: '$arg2', description: '$arg2', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'substring-after', + displayName: 'Substring After', + description: + 'Returns the collation units of xs:string that follow in that xs:string the collation units of another' + + ' xs:string. A collation may be specified.', + returnType: Types.String, + arguments: [ + { name: 'arg1', displayName: '$arg1', description: '$arg1', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { name: 'arg2', displayName: '$arg2', description: '$arg2', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'collation', + displayName: '$collation', + description: '$collation', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, +] as IFunctionDefinition[]; + +/** + * 7.6 Pattern Matching - https://www.w3.org/TR/2010/REC-xpath-functions-20101214/#string.match + */ +export const patternMatchingFunctions = [ + { + name: 'matches', + displayName: 'Matches', + description: + 'Returns an xs:boolean value that indicates whether the value of the first argument is matched by the' + + ' regular expression that is the value of the second argument.', + returnType: Types.Boolean, + arguments: [ + { name: 'input', displayName: '$input', description: '$input', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'pattern', + displayName: '$pattern', + description: '$pattern', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'flags', + displayName: '$flags', + description: '$flags', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'replace', + displayName: 'Replace', + description: + 'Returns the value of the first argument with every substring matched by the regular expression that is' + + ' the value of the second argument replaced by the replacement string that is the value of the third argument.', + returnType: Types.String, + arguments: [ + { name: 'input', displayName: '$input', description: '$input', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'pattern', + displayName: '$pattern', + description: '$pattern', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'replacement', + displayName: '$replacement', + description: '$replacement', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'flags', + displayName: '$flags', + description: '$flags', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, + { + name: 'tokenize', + displayName: 'Tokenize', + description: + 'Returns a sequence of one or more xs:strings whose values are substrings of the value of the first argument' + + ' separated by substrings that match the regular expression that is the value of the second argument.', + returnType: Types.String, + returnCollection: true, + arguments: [ + { name: 'input', displayName: '$input', description: '$input', type: Types.String, minOccurs: 1, maxOccurs: 1 }, + { + name: 'pattern', + displayName: '$pattern', + description: '$pattern', + type: Types.String, + minOccurs: 1, + maxOccurs: 1, + }, + { + name: 'flags', + displayName: '$flags', + description: '$flags', + type: Types.String, + minOccurs: 0, + maxOccurs: 1, + }, + ], + }, +] as IFunctionDefinition[]; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-functions.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions.ts new file mode 100644 index 000000000..802b7fb23 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-functions.ts @@ -0,0 +1,28 @@ +import { IFunctionDefinition } from '../../../models/datamapper/mapping'; +import { FunctionGroup } from '../xpath-parser'; +import { numericFunctions } from './xpath-2.0-functions-numeric'; +import { patternMatchingFunctions, stringFunctions, substringMatchingFunctions } from './xpath-2.0-functions-string'; +import { dateAndTimeFunctions } from './xpath-2.0-functions-datetime'; +import { booleanFunctions } from './xpath-2.0-functions-boolean'; +import { qnameFunctions } from './xpath-2.0-functions-qname'; +import { nodeFunctions } from './xpath-2.0-functions-node'; +import { sequenceFunctions } from './xpath-2.0-functions-sequence'; +import { contextFunctions } from './xpath-2.0-functions-context'; + +/** + * The XPath 2.0 functions catalog. + * https://www.w3.org/TR/2010/REC-xpath-functions-20101214/ + */ + +export const XPATH_2_0_FUNCTIONS: Record = { + [FunctionGroup.String]: stringFunctions, + [FunctionGroup.SubstringMatching]: substringMatchingFunctions, + [FunctionGroup.PatternMatching]: patternMatchingFunctions, + [FunctionGroup.Numeric]: numericFunctions, + [FunctionGroup.DateAndTime]: dateAndTimeFunctions, + [FunctionGroup.Boolean]: booleanFunctions, + [FunctionGroup.QName]: qnameFunctions, + [FunctionGroup.Node]: nodeFunctions, + [FunctionGroup.Sequence]: sequenceFunctions, + [FunctionGroup.Context]: contextFunctions, +}; diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.test.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.test.ts new file mode 100644 index 000000000..381971843 --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.test.ts @@ -0,0 +1,32 @@ +import { XPath2Parser } from './xpath-2.0-parser'; + +describe('XPath 2.0 parser', () => { + describe('Lexer', () => { + it('should tokenize', () => { + let tokens = XPath2Parser.lexer.tokenize('/shiporder/orderperson'); + expect(tokens.errors.length).toEqual(0); + expect(tokens.tokens.length).toEqual(4); + tokens = XPath2Parser.lexer.tokenize('/shiporder/orderperson/'); + expect(tokens.errors.length).toEqual(0); + expect(tokens.tokens.length).toEqual(5); + tokens = XPath2Parser.lexer.tokenize('/from/me/to/you'); + expect(tokens.errors.length).toEqual(0); + expect(tokens.tokens.length).toEqual(8); + }); + }); + + describe('Parser', () => { + it('should parse', () => { + const parser = new XPath2Parser(); + let result = parser.parseXPath('/shiporder/orderperson'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + result = parser.parseXPath('/shiporder/orderperson/'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + result = parser.parseXPath('/from/me/to/you'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + }); + }); +}); diff --git a/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.ts b/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.ts new file mode 100644 index 000000000..4c78bd67c --- /dev/null +++ b/packages/ui/src/services/xpath/2.0/xpath-2.0-parser.ts @@ -0,0 +1,730 @@ +import { defaultParserErrorProvider, IParserConfig, ITokenConfig, TokenType } from 'chevrotain'; +import { createToken as orgCreateToken, CstParser, Lexer } from 'chevrotain'; +import { XPathParser, XPathParserResult } from '../xpath-parser'; + +const fragments: Record = {}; +const f = fragments; + +fragments['NameStartChar'] = /[a-zA-Z]|\\u2070-\\u218F|\\u2C00-\\u2FEF|\\u3001-\\uD7FF|\\uF900-\\uFDCF|\\uFDF0-\\uFFFD/; +fragments['NameChar'] = new RegExp( + `(${f.NameStartChar.source})|-|_|\\.|\\d|\\u00B7|[\\u0300-\\u036F]|[\\u203F-\\u2040]`, +); +fragments['Name'] = new RegExp(`(${f.NameStartChar.source})(${f.NameChar.source})*`); +fragments['StringLiteral'] = /("(""|[^"])*")|('(''|[^'])*')/; +fragments['Digits'] = /[0-9]+/; +fragments['DecimalLiteral'] = /(\.[0-9]+)|([0-9]+\.[0-9]*)/; +fragments['DoubleLiteral'] = /((\.[0-9]+)|([0-9]+(\.[0-9]*)?))[eE][+-]?[0-9]+/; + +const allTokens: TokenType[] = []; + +function createToken(options: ITokenConfig) { + const newToken = orgCreateToken(options); + allTokens.push(newToken); + return newToken; +} + +createToken({ + name: 'WhiteSpace', + pattern: /\s+/, + group: Lexer.SKIPPED, +}); + +/* In order to refer NCName from keywords for longer_alt, declare earlier here, but push as a last one later */ +const NCName = orgCreateToken({ name: 'NCName', pattern: fragments['Name'] }); + +const Comma = createToken({ name: 'Comma', pattern: /,/ }); +const Return = createToken({ name: 'Returns', pattern: /return/, longer_alt: NCName }); +const For = createToken({ name: 'For', pattern: /for/, longer_alt: NCName }); +const Dollar = createToken({ name: 'Dollar', pattern: /\$/ }); +const Intersect = createToken({ name: 'Intersect', pattern: /intersect/, longer_alt: NCName }); +const Instance = createToken({ name: 'Instance', pattern: /instance/, longer_alt: NCName }); +const In = createToken({ name: 'In', pattern: /in/, longer_alt: NCName }); +const Some = createToken({ name: 'Some', pattern: /some/, longer_alt: NCName }); +const Every = createToken({ name: 'Every', pattern: /every/, longer_alt: NCName }); +const Satisfies = createToken({ name: 'Satisfies', pattern: /satisfies/, longer_alt: NCName }); +const If = createToken({ name: 'If', pattern: /if/, longer_alt: NCName }); +const Then = createToken({ name: 'Then', pattern: /then/, longer_alt: NCName }); +const Else = createToken({ name: 'Else', pattern: /else/, longer_alt: NCName }); +const LParen = createToken({ name: 'LParen', pattern: /\(/ }); +const RParen = createToken({ name: 'RParen', pattern: /\)/ }); +const Or = createToken({ name: 'Or', pattern: /or/, longer_alt: NCName }); +const And = createToken({ name: 'And', pattern: /and/, longer_alt: NCName }); +const To = createToken({ name: 'To', pattern: /to/, longer_alt: NCName }); +const Plus = createToken({ name: 'Plus', pattern: /\+/ }); +const Minus = createToken({ name: 'Minus', pattern: /-/ }); +const Asterisk = createToken({ name: 'Asterisk', pattern: /\*/ }); +const Div = createToken({ name: 'Div', pattern: /div/, longer_alt: NCName }); +const Idiv = createToken({ name: 'Tdiv', pattern: /idiv/, longer_alt: NCName }); +const Mod = createToken({ name: 'Mod', pattern: /mod/, longer_alt: NCName }); +const Union = createToken({ name: 'Union', pattern: /union/, longer_alt: NCName }); +const Pipe = createToken({ name: 'Pipe', pattern: /\|/ }); +const Except = createToken({ name: 'Except', pattern: /except/, longer_alt: NCName }); +const Of = createToken({ name: 'Of', pattern: /of/, longer_alt: NCName }); +const Treat = createToken({ name: 'Treat', pattern: /treat/, longer_alt: NCName }); +const Castable = createToken({ name: 'Castable', pattern: /castable/, longer_alt: NCName }); +const As = createToken({ name: 'As', pattern: /as/, longer_alt: NCName }); +const Cast = createToken({ name: 'Cast', pattern: /cast/, longer_alt: NCName }); +const Equal = createToken({ name: 'Equal', pattern: /=/ }); +const NotEqual = createToken({ name: 'NotEqual', pattern: /!=/ }); +const Precede = createToken({ name: 'Precede', pattern: /<>/ }); +const GreaterThanEqual = createToken({ name: 'GreaterThanEqual', pattern: />=/ }); +const GreaterThan = createToken({ name: 'GreaterThan', pattern: />/ }); +const Eq = createToken({ name: 'Eq', pattern: /eq/, longer_alt: NCName }); +const Ne = createToken({ name: 'Ne', pattern: /ne/, longer_alt: NCName }); +const Lt = createToken({ name: 'Lt', pattern: /lt/, longer_alt: NCName }); +const Le = createToken({ name: 'Le', pattern: /le/, longer_alt: NCName }); +const Gt = createToken({ name: 'Gt', pattern: /gt/, longer_alt: NCName }); +const Ge = createToken({ name: 'Ge', pattern: /ge/, longer_alt: NCName }); +const Is = createToken({ name: 'Is', pattern: /is/, longer_alt: NCName }); +const DoubleSlash = createToken({ name: 'DoubleSlash', pattern: /\/\// }); +const Slash = createToken({ name: 'Slash', pattern: /\// }); +const DoubleColon = createToken({ name: 'DoubleColon', pattern: /::/ }); +const Colon = createToken({ name: 'Colon', pattern: /:/ }); +const Child = createToken({ name: 'Child', pattern: /child/, longer_alt: NCName }); +const DescendantOrSelf = createToken({ name: 'DescendantOrSelf', pattern: /descendant-or-self/, longer_alt: NCName }); +const Descendant = createToken({ name: 'Descendant', pattern: /descendant/, longer_alt: NCName }); +const Attribute = createToken({ name: 'Attribute', pattern: /attribute/, longer_alt: NCName }); +const Self = createToken({ name: 'Self', pattern: /self/, longer_alt: NCName }); +const FollowingSibling = createToken({ name: 'FollowingSibling', pattern: /following-sibling/, longer_alt: NCName }); +const Following = createToken({ name: 'Following', pattern: /followed-sibling/, longer_alt: NCName }); +const Namespace = createToken({ name: 'Namespace', pattern: /namespace/, longer_alt: NCName }); +const Parent = createToken({ name: 'Parent', pattern: /parent/, longer_alt: NCName }); +const AncestorOrSelf = createToken({ name: 'AncestorOrSelf', pattern: /ancestor-or-self/, longer_alt: NCName }); +const Ancestor = createToken({ name: 'Ancestor', pattern: /ancestor/, longer_alt: NCName }); +const PrecedingSibling = createToken({ name: 'PrecedingSibling', pattern: /preceding-sibling/, longer_alt: NCName }); +const Preceding = createToken({ name: 'Preceding', pattern: /preceding/, longer_alt: NCName }); +const ContextItemExpr = createToken({ name: 'ContextItemExpr', pattern: /\./ }); +const AbbrevReverseStep = createToken({ name: 'AbbrevReverseStep', pattern: /\.\./ }); +const At = createToken({ name: 'At', pattern: /@/ }); +const LSquare = createToken({ name: 'LSquare', pattern: /\[/ }); +const RSquare = createToken({ name: 'RSquare', pattern: /]/ }); +const Question = createToken({ name: 'Question', pattern: /\?/ }); +const Item = createToken({ name: 'Item', pattern: /item/, longer_alt: NCName }); +const Node = createToken({ name: 'Node', pattern: /node/, longer_alt: NCName }); +const DocumentNode = createToken({ name: 'DocumentNode', pattern: /document-node/, longer_alt: NCName }); +const Text = createToken({ name: 'Text', pattern: /text/, longer_alt: NCName }); +const Comment = createToken({ name: 'Comment', pattern: /comment/, longer_alt: NCName }); +const ProcessingInstruction = createToken({ + name: 'ProcessingInstruction', + pattern: /processing-instruction/, + longer_alt: NCName, +}); +const SchemaAttribute = createToken({ name: 'SchemaAttribute', pattern: /schema-attribute/, longer_alt: NCName }); +const Element = createToken({ name: 'Element', pattern: /element/, longer_alt: NCName }); +const SchemaElement = createToken({ name: 'SchemaElement', pattern: /schema-element/, longer_alt: NCName }); +const EmptySequence = createToken({ name: 'EmptySequence', pattern: /empty-sequence/, longer_alt: NCName }); + +const StringLiteral = createToken({ name: 'StringLiteral', pattern: fragments['StringLiteral'] }); +const IntegerLiteral = createToken({ name: 'IntegerLiteral', pattern: fragments['Digits'] }); +const DecimalLiteral = createToken({ name: 'DecimalLiteral', pattern: fragments['DecimalLiteral'] }); +const DoubleLiteral = createToken({ name: 'DoubleLiteral', pattern: fragments['DoubleLiteral'] }); + +/** DO NOT CHANGE @see {@link NCName} */ +allTokens.push(NCName); + +/** + * XPath 2.0 Parser which implements the EBNF defined in the W3C specification + * - https://www.w3.org/TR/xpath20/#id-grammar + */ +export class XPath2Parser extends CstParser implements XPathParser { + static lexer = new Lexer(allTokens); + + constructor() { + super(allTokens, { + recoveryEnabled: false, + maxLookahead: 5, + dynamicTokensEnabled: false, + outputCst: true, + errorMessageProvider: defaultParserErrorProvider, + nodeLocationTracking: 'none', + traceInitPerf: false, + skipValidations: false, + } as IParserConfig & { outputCst: boolean }); + this.performSelfAnalysis(); + } + + parseXPath(xpath: string): XPathParserResult { + const lexResult = XPath2Parser.lexer.tokenize(xpath); + this.input = lexResult.tokens; + const cst = this.Expr(); + + return { + cst: cst, + lexErrors: lexResult.errors, + parseErrors: this.errors, + }; + } + + private Expr = this.RULE('Expr', () => { + this.SUBRULE(this.ExprSingle); + this.MANY(() => { + this.CONSUME(Comma); + this.SUBRULE2(this.ExprSingle); + }); + }); + + private ExprSingle = this.RULE('ExprSingle', () => { + this.OR([ + { ALT: () => this.SUBRULE(this.ForExpr) }, + { ALT: () => this.SUBRULE(this.QuantifiedExpr) }, + { ALT: () => this.SUBRULE(this.IfExpr) }, + { ALT: () => this.SUBRULE(this.OrExpr) }, + ]); + }); + + private ForExpr = this.RULE('ForExpr', () => { + this.CONSUME(For); + this.SUBRULE(this.VarRef); + this.CONSUME(In); + this.SUBRULE(this.ExprSingle); + this.MANY(() => { + this.CONSUME(Comma); + this.SUBRULE2(this.VarRef); + this.CONSUME2(In); + this.SUBRULE2(this.ExprSingle); + }); + this.CONSUME(Return); + this.SUBRULE3(this.ExprSingle); + }); + + private QuantifiedExpr = this.RULE('QuantifiedExpr', () => { + this.OR([{ ALT: () => this.CONSUME(Some) }, { ALT: () => this.CONSUME(Every) }]); + this.SUBRULE(this.VarRef); + this.CONSUME(In); + this.SUBRULE(this.ExprSingle); + this.MANY(() => { + this.CONSUME(Comma); + this.SUBRULE2(this.VarRef); + this.CONSUME2(In); + this.SUBRULE2(this.ExprSingle); + }); + this.CONSUME(Satisfies); + this.SUBRULE3(this.ExprSingle); + }); + + private IfExpr = this.RULE('IfExpr', () => { + this.CONSUME(If); + this.CONSUME(LParen); + this.SUBRULE(this.Expr); + this.CONSUME(RParen); + this.CONSUME(Then); + this.SUBRULE(this.ExprSingle); + this.CONSUME(Else); + this.SUBRULE2(this.ExprSingle); + }); + + private OrExpr = this.RULE('OrExpr', () => { + this.SUBRULE(this.AndExpr); + this.MANY(() => { + this.CONSUME(Or); + this.SUBRULE2(this.AndExpr); + }); + }); + + private AndExpr = this.RULE('AndExpr', () => { + this.SUBRULE(this.ComparisonExpr); + this.MANY(() => { + this.CONSUME(And); + this.SUBRULE2(this.ComparisonExpr); + }); + }); + + private ComparisonExpr = this.RULE('ComparisonExpr', () => { + this.SUBRULE(this.RangeExpr); + this.OPTION(() => { + this.OR([ + // ValueComp + { ALT: () => this.CONSUME(Eq) }, + { ALT: () => this.CONSUME(Ne) }, + { ALT: () => this.CONSUME(Lt) }, + { ALT: () => this.CONSUME(Le) }, + { ALT: () => this.CONSUME(Gt) }, + { ALT: () => this.CONSUME(Ge) }, + // GeneralComp + { ALT: () => this.CONSUME(Equal) }, + { ALT: () => this.CONSUME(NotEqual) }, + { ALT: () => this.CONSUME(LessThan) }, + { ALT: () => this.CONSUME(LessThanEqual) }, + { ALT: () => this.CONSUME(GreaterThan) }, + { ALT: () => this.CONSUME(GreaterThanEqual) }, + // NodeComp + { ALT: () => this.CONSUME(Is) }, + { ALT: () => this.CONSUME2(Precede) }, + { ALT: () => this.CONSUME(Follow) }, + { ALT: () => this.SUBRULE2(this.RangeExpr) }, + ]); + }); + }); + + private RangeExpr = this.RULE('RangeExpr', () => { + this.SUBRULE(this.AdditiveExpr); + this.OPTION(() => { + this.CONSUME(To); + this.SUBRULE2(this.AdditiveExpr); + }); + }); + + private AdditiveExpr = this.RULE('AdditiveExpr', () => { + this.SUBRULE(this.MultiplicativeExpr); + this.MANY(() => { + this.OR([{ ALT: () => this.CONSUME(Plus) }, { ALT: () => this.CONSUME(Minus) }]); + this.SUBRULE2(this.MultiplicativeExpr); + }); + }); + + private MultiplicativeExpr = this.RULE('MultiplicativeExpr', () => { + this.SUBRULE(this.UnionExpr); + this.MANY(() => { + this.OR([ + { ALT: () => this.CONSUME(Asterisk) }, + { ALT: () => this.CONSUME(Div) }, + { ALT: () => this.CONSUME(Idiv) }, + { ALT: () => this.CONSUME(Mod) }, + ]); + this.SUBRULE2(this.UnionExpr); + }); + }); + + private UnionExpr = this.RULE('UnionExpr', () => { + this.SUBRULE(this.IntersectExceptExpr); + this.MANY(() => { + this.OR([{ ALT: () => this.CONSUME(Union) }, { ALT: () => this.CONSUME(Pipe) }]); + this.SUBRULE2(this.IntersectExceptExpr); + }); + }); + + private IntersectExceptExpr = this.RULE('IntersectExceptExpr', () => { + this.SUBRULE(this.InstanceofExpr); + this.MANY(() => { + this.OR([{ ALT: () => this.CONSUME(Intersect) }, { ALT: () => this.CONSUME(Except) }]); + this.SUBRULE2(this.InstanceofExpr); + }); + }); + + private InstanceofExpr = this.RULE('InstanceofExpr', () => { + // CastExpr + this.MANY(() => { + this.OR([{ ALT: () => this.CONSUME(Minus) }, { ALT: () => this.CONSUME(Plus) }]); + }); + this.SUBRULE(this.PathExpr); + this.OPTION(() => { + this.CONSUME(Cast); + this.CONSUME(As); + this.SUBRULE(this.SingleType); + }); + // CastableExpr + this.OPTION2(() => { + this.CONSUME(Castable); + this.CONSUME2(As); + this.SUBRULE2(this.SingleType); + }); + // TreatExpr + this.OPTION3(() => { + this.CONSUME(Treat); + this.CONSUME3(As); + this.SUBRULE3(this.SequenceType); + }); + // InstanceofExpr + this.OPTION4(() => { + this.CONSUME(Instance); + this.CONSUME(Of); + this.SUBRULE4(this.SequenceType); + }); + }); + + private PathExpr = this.RULE('PathExpr', () => { + this.OR([ + { + ALT: () => { + this.CONSUME(Slash); + this.OPTION(() => this.SUBRULE(this.RelativePathExpr)); + }, + }, + { + ALT: () => { + this.CONSUME(DoubleSlash); + this.SUBRULE2(this.RelativePathExpr); + }, + }, + { ALT: () => this.SUBRULE3(this.RelativePathExpr) }, + ]); + }); + + private ChildPathSegmentExpr = this.RULE('ChildPathSegmentExpr', () => { + this.OR([{ ALT: () => this.CONSUME(Slash) }, { ALT: () => this.CONSUME(DoubleSlash) }]); + this.SUBRULE2(this.StepExpr); + }); + + private RelativePathExpr = this.RULE('RelativePathExpr', () => { + this.SUBRULE(this.StepExpr); + this.MANY(() => this.SUBRULE2(this.ChildPathSegmentExpr)); + this.OPTION(() => this.CONSUME(Slash)); + }); + + private StepExpr = this.RULE('StepExpr', () => { + this.OR([ + { ALT: () => this.SUBRULE(this.FilterExpr) }, + { + ALT: () => { + this.OR2([ + { ALT: () => this.SUBRULE(this.ReverseStep) }, + { + ALT: () => { + // ForwardStep + this.OR3([ + { + ALT: () => { + // ForwardAxis + this.OR4([ + { + ALT: () => { + this.CONSUME(Child); + this.CONSUME(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Descendant); + this.CONSUME2(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Attribute); + this.CONSUME3(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Self); + this.CONSUME4(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(DescendantOrSelf); + this.CONSUME5(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(FollowingSibling); + this.CONSUME6(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Following); + this.CONSUME7(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Namespace); + this.CONSUME8(DoubleColon); + }, + }, + ]); + this.SUBRULE(this.NodeTest); + }, + }, + { + ALT: () => { + this.OPTION(() => this.CONSUME(At)); + this.SUBRULE2(this.NodeTest); + }, + }, + ]); + }, + }, + ]); + this.SUBRULE(this.PredicateList); + }, + }, + ]); + }); + + private ReverseStep = this.RULE('ReverseStep', () => { + this.OR([ + { + ALT: () => { + this.SUBRULE(this.ReverseAxis); + this.SUBRULE(this.NodeTest); + }, + }, + { ALT: () => this.CONSUME(AbbrevReverseStep) }, + ]); + }); + + private ReverseAxis = this.RULE('ReverseAxis', () => { + this.OR([ + { + ALT: () => { + this.CONSUME(Parent); + this.CONSUME(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Ancestor); + this.CONSUME2(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(PrecedingSibling); + this.CONSUME3(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(Preceding); + this.CONSUME4(DoubleColon); + }, + }, + { + ALT: () => { + this.CONSUME(AncestorOrSelf); + this.CONSUME5(DoubleColon); + }, + }, + ]); + }); + + private NodeTest = this.RULE('NodeTest', () => { + this.OR([{ ALT: () => this.SUBRULE(this.KindTest) }, { ALT: () => this.SUBRULE(this.NameTest) }]); + }); + + private NameTest = this.RULE('NameTest', () => { + this.OR([ + { + ALT: () => { + this.CONSUME(NCName); + this.OPTION(() => { + this.CONSUME(Colon); + this.OR2([{ ALT: () => this.CONSUME(Asterisk) }, { ALT: () => this.CONSUME2(NCName) }]); + }); + }, + }, + { + ALT: () => { + this.CONSUME2(Asterisk); + this.OPTION2(() => { + this.CONSUME2(Colon); + this.CONSUME3(NCName); + }); + }, + }, + ]); + }); + + private FilterExpr = this.RULE('FilterExpr', () => { + this.OR([ + { ALT: () => this.SUBRULE(this.Literal) }, + { ALT: () => this.SUBRULE(this.VarRef) }, + { ALT: () => this.SUBRULE(this.ParenthesizedExpr) }, + { ALT: () => this.CONSUME(ContextItemExpr) }, + { ALT: () => this.SUBRULE(this.FunctionCall) }, + ]); + this.SUBRULE(this.PredicateList); + }); + + private PredicateList = this.RULE('PredicateList', () => { + this.MANY(() => { + this.CONSUME(LSquare); + this.SUBRULE(this.Expr); + this.CONSUME(RSquare); + }); + }); + + private Literal = this.RULE('Literal', () => { + this.OR([{ ALT: () => this.SUBRULE(this.NumericLiteral) }, { ALT: () => this.CONSUME(StringLiteral) }]); + }); + + private NumericLiteral = this.RULE('NumericLiteral', () => { + this.OR([ + { ALT: () => this.CONSUME(IntegerLiteral) }, + { ALT: () => this.CONSUME(DecimalLiteral) }, + { ALT: () => this.CONSUME(DoubleLiteral) }, + ]); + }); + + private VarRef = this.RULE('VarRef', () => { + this.CONSUME(Dollar); + this.SUBRULE(this.QName); + }); + + private ParenthesizedExpr = this.RULE('ParenthesizedExpr', () => { + this.CONSUME(LParen); + this.OPTION(() => this.SUBRULE(this.Expr)); + this.CONSUME(RParen); + }); + + private FunctionCall = this.RULE('FunctionCall', () => { + this.SUBRULE(this.QName); + this.CONSUME(LParen); + this.OPTION(() => { + this.SUBRULE(this.ExprSingle); + this.MANY(() => { + this.CONSUME(Comma); + this.SUBRULE2(this.ExprSingle); + }); + }); + this.CONSUME(RParen); + }); + + private SingleType = this.RULE('SingleType', () => { + this.SUBRULE(this.QName); + this.OPTION(() => this.CONSUME(Question)); + }); + + private SequenceType = this.RULE('SequenceType', () => { + this.OR([ + { + ALT: () => { + this.CONSUME(EmptySequence); + this.CONSUME(LParen); + this.CONSUME(RParen); + }, + }, + { + ALT: () => { + this.SUBRULE(this.ItemType); + this.OPTION(() => this.SUBRULE(this.OccurrenceIndicator)); + }, + }, + ]); + }); + + private OccurrenceIndicator = this.RULE('OccurrenceIndicator', () => { + this.OR([ + { ALT: () => this.CONSUME(Question) }, + { ALT: () => this.CONSUME(Asterisk) }, + { ALT: () => this.CONSUME(Plus) }, + ]); + }); + + private ItemType = this.RULE('ItemType', () => { + this.OR([ + { ALT: () => this.SUBRULE(this.KindTest) }, + { + ALT: () => { + this.CONSUME(Item); + this.CONSUME(LParen); + this.CONSUME(RParen); + }, + }, + { ALT: () => this.SUBRULE(this.QName) }, + ]); + }); + + private KindTest = this.RULE('KindTest', () => { + this.OR([ + { ALT: () => this.SUBRULE(this.DocumentTest) }, + { ALT: () => this.SUBRULE(this.ElementTest) }, + { ALT: () => this.SUBRULE(this.AttributeTest) }, + { ALT: () => this.SUBRULE(this.SchemaElementTest) }, + { ALT: () => this.SUBRULE(this.SchemaAttributeTest) }, + { ALT: () => this.SUBRULE(this.PITest) }, + { ALT: () => this.SUBRULE(this.CommentTest) }, + { ALT: () => this.SUBRULE(this.TextTest) }, + { ALT: () => this.SUBRULE(this.AnyKindTest) }, + ]); + }); + + private AnyKindTest = this.RULE('AnyKindTest', () => { + this.CONSUME(Node); + this.CONSUME(LParen); + this.CONSUME(RParen); + }); + + private DocumentTest = this.RULE('DocumentTest', () => { + this.CONSUME(DocumentNode); + this.CONSUME(LParen); + this.OPTION(() => { + this.OR([{ ALT: () => this.SUBRULE(this.ElementTest) }, { ALT: () => this.SUBRULE(this.SchemaElementTest) }]); + }); + this.CONSUME(RParen); + }); + + private TextTest = this.RULE('TextTest', () => { + this.CONSUME(Text); + this.CONSUME(LParen); + this.CONSUME(RParen); + }); + + private CommentTest = this.RULE('CommentTest', () => { + this.CONSUME(Comment); + this.CONSUME(LParen); + this.CONSUME(RParen); + }); + + private PITest = this.RULE('PITest', () => { + this.CONSUME(ProcessingInstruction); + this.CONSUME(LParen); + this.OPTION(() => { + this.OR([{ ALT: () => this.CONSUME(NCName) }, { ALT: () => this.CONSUME(StringLiteral) }]); + }); + this.CONSUME(RParen); + }); + + private AttributeTest = this.RULE('AttributeTest', () => { + this.CONSUME(Attribute); + this.CONSUME(LParen); + this.OPTION(() => { + this.SUBRULE(this.AttribNameOrWildcard); + this.OPTION2(() => { + this.CONSUME(Comma); + this.SUBRULE(this.QName); + }); + }); + this.CONSUME(RParen); + }); + + private AttribNameOrWildcard = this.RULE('AttribNameOrWildcard', () => { + this.OR([{ ALT: () => this.SUBRULE(this.QName) }, { ALT: () => this.CONSUME(Asterisk) }]); + }); + + private SchemaAttributeTest = this.RULE('SchemaAttributeTest', () => { + this.CONSUME(SchemaAttribute); + this.CONSUME(LParen); + this.SUBRULE(this.QName); + this.CONSUME(RParen); + }); + + private ElementTest = this.RULE('ElementTest', () => { + this.CONSUME(Element); + this.CONSUME(LParen); + this.OPTION(() => { + this.SUBRULE(this.ElementNameOrWildcard); + this.OPTION2(() => { + this.CONSUME(Comma); + this.SUBRULE(this.QName); + this.OPTION3(() => this.CONSUME(Question)); + }); + }); + this.CONSUME(RParen); + }); + + private ElementNameOrWildcard = this.RULE('ElementNameOrWildcard', () => { + this.OR([{ ALT: () => this.SUBRULE(this.QName) }, { ALT: () => this.CONSUME(Asterisk) }]); + }); + + private SchemaElementTest = this.RULE('SchemaElementTest', () => { + this.CONSUME(SchemaElement); + this.CONSUME(LParen); + this.SUBRULE(this.QName); + this.CONSUME(RParen); + }); + + private QName = this.RULE('QName', () => { + this.OPTION(() => { + this.CONSUME(NCName); + this.CONSUME(Colon); + }); + this.CONSUME2(NCName); + }); +} diff --git a/packages/ui/src/services/xpath/monaco-language.ts b/packages/ui/src/services/xpath/monaco-language.ts new file mode 100644 index 000000000..574077ee5 --- /dev/null +++ b/packages/ui/src/services/xpath/monaco-language.ts @@ -0,0 +1,79 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +export const xpathLanguageID = 'xpath'; + +type MonacoXPathLanguageMetadata = monaco.languages.ILanguageExtensionPoint & { + languageConfiguration: monaco.languages.LanguageConfiguration; + tokensProvider: monaco.languages.IMonarchLanguage; + completionItemProvider: monaco.languages.CompletionItemProvider; +}; + +const keywords = [ + 'if', + 'for', + 'idiv', + 'div', + 'mod', + 'eq', + 'ne', + 'gt', + 'lt', + 'ge', + 'le', + 'is', + 'union', + 'intersect', + 'except', + 'to', +]; + +const operators = ['+', '-', '*', '<<', '>>', '|', ',']; + +export const monacoXPathLanguageMetadata: MonacoXPathLanguageMetadata = { + id: xpathLanguageID, + languageConfiguration: { + brackets: [ + ['[', ']'], + ['(', ')'], + ], + autoClosingPairs: [ + { open: '[', close: ']' }, + { open: '(', close: ')' }, + ], + }, + tokensProvider: { + keywords: keywords, + operators: operators, + tokenizer: { + root: [], + }, + }, + + completionItemProvider: { + provideCompletionItems: ( + model: monaco.editor.ITextModel, + position: monaco.Position, + _context: monaco.languages.CompletionContext, + _token: monaco.CancellationToken, + ): monaco.languages.ProviderResult => { + const suggestions = [ + ...keywords.map((k) => { + const word = model.getWordUntilPosition(position); + const range: monaco.IRange = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn, + }; + return { + label: k, + kind: monaco.languages.CompletionItemKind.Keyword, + insertText: k, + range: range, + }; + }), + ]; + return { suggestions: suggestions }; + }, + }, +}; diff --git a/packages/ui/src/services/xpath/xpath-parser.ts b/packages/ui/src/services/xpath/xpath-parser.ts new file mode 100644 index 000000000..07db44e22 --- /dev/null +++ b/packages/ui/src/services/xpath/xpath-parser.ts @@ -0,0 +1,24 @@ +import type { CstNode, ILexingError, IRecognitionException } from 'chevrotain'; + +export interface XPathParserResult { + cst: CstNode; + lexErrors: ILexingError[]; + parseErrors: IRecognitionException[]; +} + +export interface XPathParser { + parseXPath(xpath: string): XPathParserResult; +} + +export enum FunctionGroup { + String = 'String', + SubstringMatching = 'Substring Matching', + PatternMatching = 'Pattern Matching', + Numeric = 'Numeric', + DateAndTime = 'Date and Time', + Boolean = 'Boolean', + QName = 'QName', + Node = 'Node', + Sequence = 'Sequence', + Context = 'Context', +} diff --git a/packages/ui/src/services/xpath/xpath.service.test.ts b/packages/ui/src/services/xpath/xpath.service.test.ts new file mode 100644 index 000000000..4c06dbe41 --- /dev/null +++ b/packages/ui/src/services/xpath/xpath.service.test.ts @@ -0,0 +1,125 @@ +import { XPathService } from './xpath.service'; +import { createSyntaxDiagramsCode } from 'chevrotain'; +import * as fs from 'fs'; +import { IFunctionDefinition } from '../../models/datamapper/mapping'; +import { FunctionGroup } from './xpath-parser'; + +describe('XPathService', () => { + it('Generate Syntax Diagram', () => { + const gastProd = XPathService.parser.getSerializedGastProductions(); + const html = createSyntaxDiagramsCode(gastProd); + fs.writeFileSync('dist/syntax-diagram.html', html); + }); + + describe('parse()', () => { + it('should parse a field path', () => { + const result = XPathService.parse('/aaa/bbb/ccc'); + expect(result.cst).toBeDefined(); + }); + + it('should parse a field which contains a reserved word in its spelling', () => { + let result = XPathService.parse('/shiporder/orderperson'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + expect(result.cst).toBeDefined(); + result = XPathService.parse('/shiporder/orderperson/'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + expect(result.cst).toBeDefined(); + result = XPathService.parse('/from/me/to/you'); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + expect(result.cst).toBeDefined(); + }); + + it('should parse xpath with string literal', () => { + const result = XPathService.parse("'Hello', /shiporder/orderperson, '!'"); + expect(result.lexErrors.length).toEqual(0); + expect(result.parseErrors.length).toEqual(0); + expect(result.cst).toBeDefined(); + }); + }); + + describe('validate()', () => { + it('should detect parse error', () => { + const result = XPathService.validate('(('); + expect(result.hasErrors()).toBeTruthy(); + }); + + it('should validate with empty string literal', () => { + const result = XPathService.validate("/ns0:ShipOrder/ns0:OrderPerson != ''"); + // TODO parser error says it's redundant, possibly a bug in the parser + expect(result.hasErrors()).toBeTruthy(); + expect(result.getCst()).toBeDefined(); + }); + + it('should not get error with valid parenthesis', () => { + let result = XPathService.validate('(/Hello)'); + expect(result.hasErrors()).toBeFalsy(); + result = XPathService.validate('((/Hello))'); + expect(result.hasErrors()).toBeFalsy(); + }); + + it('should not get error with empty parenthesis', () => { + let result = XPathService.validate('()'); + expect(result.hasErrors()).toBeFalsy(); + result = XPathService.validate('(())'); + expect(result.hasErrors()).toBeFalsy(); + }); + + it('should not get error with empty function call', () => { + const result = XPathService.validate('upper-case()'); + expect(result.hasErrors()).toBeFalsy(); + }); + }); + + describe('extractFieldPaths()', () => { + it('extract field', () => { + const paths = XPathService.extractFieldPaths('/aaa/bbb/ccc'); + expect(paths.length).toEqual(1); + expect(paths[0]).toEqual('/aaa/bbb/ccc'); + }); + + it('extract param field', () => { + const paths = XPathService.extractFieldPaths('$param1/aaa/bbb/ccc'); + expect(paths.length).toEqual(1); + expect(paths[0]).toEqual('$param1/aaa/bbb/ccc'); + }); + + it('extract fields from function calls', () => { + const paths = XPathService.extractFieldPaths( + 'concatenate(/aaa/bbb/ccc, upper-case(aaa/bbb/ddd), lower-case($param1/eee/fff))', + ); + expect(paths.length).toEqual(3); + expect(paths[0]).toEqual('/aaa/bbb/ccc'); + expect(paths[1]).toEqual('aaa/bbb/ddd'); + expect(paths[2]).toEqual('$param1/eee/fff'); + }); + + it('extract primitive source body', () => { + const paths = XPathService.extractFieldPaths('.'); + expect(paths.length).toEqual(1); + expect(paths[0]).toEqual('/'); + }); + }); + + it('getXPathFunctionDefinitions()', () => { + const functionDefs = XPathService.getXPathFunctionDefinitions(); + expect(Object.keys(functionDefs).length).toBeGreaterThan(9); + }); + + it('getXPathFunctionNames()', () => { + const functionDefs = XPathService.getXPathFunctionDefinitions(); + const flattened = Object.keys(functionDefs).reduce((acc, value) => { + acc.push(...functionDefs[value as FunctionGroup]); + return acc; + }, [] as IFunctionDefinition[]); + const functionNames = XPathService.getXPathFunctionNames(); + expect(functionNames.length).toEqual(flattened.length); + }); + + it('getMonacoXPathLanguageMetadata()', () => { + const metadata = XPathService.getMonacoXPathLanguageMetadata(); + expect(metadata.id).toEqual('xpath'); + }); +}); diff --git a/packages/ui/src/services/xpath/xpath.service.ts b/packages/ui/src/services/xpath/xpath.service.ts new file mode 100644 index 000000000..76f5b15f4 --- /dev/null +++ b/packages/ui/src/services/xpath/xpath.service.ts @@ -0,0 +1,224 @@ +import { XPath2Parser } from './2.0/xpath-2.0-parser'; +import { FunctionGroup, XPathParserResult } from './xpath-parser'; +import { IFunctionDefinition } from '../../models/datamapper/mapping'; +import { XPATH_2_0_FUNCTIONS } from './2.0/xpath-2.0-functions'; +import { monacoXPathLanguageMetadata } from './monaco-language'; +import { CstElement, CstNode } from 'chevrotain'; +import { IField, PrimitiveDocument } from '../../models/datamapper/document'; +import { DocumentService } from '../document.service'; +import { DocumentType } from '../../models/datamapper/path'; + +export class ValidatedXPathParseResult { + constructor(public parserResult?: XPathParserResult) {} + dataMapperErrors: string[] = []; + warnings: string[] = []; + + hasErrors(): boolean { + return ( + (this.parserResult && this.parserResult.lexErrors.length > 0) || + (this.parserResult && this.parserResult?.parseErrors.length > 0) || + this.dataMapperErrors.length > 0 + ); + } + + getErrors(): string[] { + const answer = []; + if (this.parserResult) { + answer.push(...this.parserResult.lexErrors.map((e) => e.message)); + answer.push(...this.parserResult.parseErrors.map((e) => e.message)); + } + answer.push(...this.dataMapperErrors); + return answer; + } + + hasWarnings(): boolean { + return this.warnings.length > 0; + } + + getWarnings(): string[] { + return this.warnings; + } + + getCst(): CstNode | undefined { + return this.parserResult?.cst; + } +} + +export class XPathService { + static parser = new XPath2Parser(); + static functions = XPATH_2_0_FUNCTIONS; + + static parse(xpath: string): XPathParserResult { + return XPathService.parser.parseXPath(xpath); + } + + static validate(xpath: string): ValidatedXPathParseResult { + if (!xpath) { + const answer = new ValidatedXPathParseResult(); + answer.warnings.push('Empty Expression'); + return answer; + } + const parserResult = XPathService.parse(xpath); + const validationResult = new ValidatedXPathParseResult(parserResult); + if (!validationResult.getCst()) return validationResult; + + try { + XPathService.extractFieldPaths(xpath); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const errorString = + 'DataMapper internal error: failed to render a mapping line from a valid XPath expression: ' + + ('message' in error ? error.message : error.toString()); + validationResult.dataMapperErrors.push(errorString); + } + return validationResult; + } + + static getXPathFunctionDefinitions(): Record { + return XPathService.functions; + } + + static getXPathFunctionNames(): string[] { + return Object.values(XPathService.getXPathFunctionDefinitions()).reduce((acc, functions) => { + acc.push(...functions.map((f) => f.name)); + return acc; + }, [] as string[]); + } + + static getMonacoXPathLanguageMetadata() { + monacoXPathLanguageMetadata.tokensProvider.actions = XPathService.getXPathFunctionNames(); + return monacoXPathLanguageMetadata; + } + + static getNode(node: CstElement, paths: string[]) { + let answer: CstElement = node; + for (const path of paths) { + if (!('children' in answer) || !answer.children[path]) return undefined; + answer = answer.children[path][0]; + } + return answer; + } + + private static pathExprToString(node: CstNode) { + let answer = 'Slash' in node.children ? '/' : 'DoubleSlash' in node.children ? '//' : ''; + if (!('children' in node.children.RelativePathExpr[0])) return answer; + const relativePathExpr = XPathService.getNode(node, ['RelativePathExpr']); + if (!relativePathExpr) return answer; + const stepExpr = XPathService.getNode(relativePathExpr, ['StepExpr']); + if (!stepExpr) return answer; + const varName = XPathService.getNode(stepExpr, ['FilterExpr', 'VarRef', 'QName', 'NCName']); + const contextItem = XPathService.getNode(stepExpr, ['FilterExpr', 'ContextItemExpr']); + const name = XPathService.extractNameFromStepExpr(stepExpr); + if (varName && 'image' in varName) { + answer += '$' + varName.image; + } else if (contextItem && 'image' in contextItem) { + answer += contextItem.image; + } else if (name) { + answer += name; + } else { + throw Error('Unknown RelativePathExpr: ' + relativePathExpr); + } + const following = + relativePathExpr && 'children' in relativePathExpr && relativePathExpr.children.ChildPathSegmentExpr; + return following + ? following.reduce((acc, value) => { + acc += '/'; + const stepExpr = XPathService.getNode(value, ['StepExpr']); + const name = stepExpr && XPathService.extractNameFromStepExpr(stepExpr); + if (name) acc += name; + return acc; + }, answer) + : answer; + } + + private static extractNameFromStepExpr(stepExpr: CstElement) { + const isAttribute = !!('children' in stepExpr && stepExpr.children['At']); + const nameTest = XPathService.getNode(stepExpr, ['NodeTest', 'NameTest']); + if (!nameTest || !('children' in nameTest)) return; + const ncNames = nameTest.children['NCName']; + const colon = nameTest.children['Colon']; + let answer = isAttribute ? '@' : ''; + if (ncNames.length === 1 && (!colon || colon.length === 0) && 'image' in ncNames[0]) answer += ncNames[0].image; + else if (ncNames.length === 2 && colon?.length === 1 && 'image' in ncNames[0] && 'image' in ncNames[1]) + answer += `${ncNames[0].image}:${ncNames[1].image}`; + return answer; + } + + static extractFieldPaths(expression: string): string[] { + const parsed = XPathService.parse(expression); + if (!parsed.cst) return []; + const paths = XPathService.collectPathExpressions(parsed.cst); + return paths.reduce((acc, node) => { + if ('children' in node) { + acc.push(XPathService.pathExprToString(node)); + } else if ('image' in node && node.image === '.') { + acc.push('/'); + } + return acc; + }, [] as string[]); + } + + private static collectPathExpressions(node: CstNode) { + const answer: CstElement[] = []; + if (node.name === 'PathExpr') { + answer.push(...XPathService.extractPathExprNode(node)); + return answer; + } + return Object.entries(node.children).reduce((acc, [key, value]) => { + if (key === 'PathExpr') { + acc.push(...XPathService.extractPathExprNode(value[0] as CstNode)); + } else { + value.forEach((child) => { + if ('children' in child) { + acc.push(...XPathService.collectPathExpressions(child)); + } + }); + } + return acc; + }, [] as CstElement[]); + } + + private static extractPathExprNode(pathExprNode: CstNode): CstElement[] { + const filterExpr = XPathService.getNode(pathExprNode, ['RelativePathExpr', 'StepExpr', 'FilterExpr']); + if (!filterExpr) return [pathExprNode]; + + const literal = XPathService.getNode(filterExpr, ['Literal']); + if (literal) return []; + + const contextItemExpr = XPathService.getNode(filterExpr, ['ContextItemExpr']); + if (contextItemExpr) return [contextItemExpr]; + + // Extract contents in parenthesis + const parenthesizedExpr = XPathService.getNode(filterExpr, ['ParenthesizedExpr']); + if (parenthesizedExpr && 'children' in parenthesizedExpr) { + const expr = parenthesizedExpr.children['Expr']; + return expr && expr.length > 0 ? XPathService.collectPathExpressions(expr[0] as CstNode) : []; + } + + // Extract arguments in FunctionCall + const functionCall = XPathService.getNode(filterExpr, ['FunctionCall']); + if (functionCall && 'children' in functionCall) { + return functionCall.children.ExprSingle + ? functionCall.children.ExprSingle.flatMap((arg) => + 'children' in arg ? XPathService.collectPathExpressions(arg) : [], + ) + : []; + } + + return [pathExprNode]; + } + + static addSource(expression: string, source: string): string { + return expression ? `${expression}, ${source}` : source; + } + + static toXPath(source: PrimitiveDocument | IField, namespaceMap: { [prefix: string]: string }): string { + const doc = source.ownerDocument; + const prefix = doc.documentType === DocumentType.PARAM ? `$${doc.documentId}` : ''; + const xpath = DocumentService.getFieldStack(source, true).reduceRight( + (acc, field) => acc + `/${DocumentService.getFieldExpressionNS(field, namespaceMap)}`, + prefix, + ); + return xpath ? xpath : '.'; + } +} diff --git a/packages/ui/src/stubs/BrowserFilePickerMetadataProvider.tsx b/packages/ui/src/stubs/BrowserFilePickerMetadataProvider.tsx new file mode 100644 index 000000000..1ecffb255 --- /dev/null +++ b/packages/ui/src/stubs/BrowserFilePickerMetadataProvider.tsx @@ -0,0 +1,73 @@ +import { ChangeEvent, createRef, FunctionComponent, PropsWithChildren, useCallback, useRef } from 'react'; +import { readFileAsString } from './read-file-as-string'; +import { IMetadataApi, MetadataContext } from '../providers'; + +export const BrowserFilePickerMetadataProvider: FunctionComponent = (props) => { + const fileInputRef = createRef(); + const fileSelectionRef = useRef<{ + resolve: (files: Record) => void; + reject: (error: unknown) => unknown; + }>(); + const fileContentsRef = useRef>(); + + const askUserForFileSelection = useCallback( + ( + _include: string, + _exclude?: string, + _options?: Record, + ): Promise => { + fileInputRef.current?.click(); + return new Promise>((resolve, reject) => { + fileSelectionRef.current = { resolve, reject }; + }).then((files) => { + fileContentsRef.current = files; + return Object.keys(files); + }); + }, + [fileInputRef], + ); + + const onImport = useCallback(async (event: ChangeEvent) => { + const schemaFiles = event.target.files; + if (!schemaFiles) return; + const fileContents: Record = {}; + const fileContentPromises: Promise[] = []; + Array.from(schemaFiles).forEach((f) => { + const promise = readFileAsString(f).then((content) => (fileContents[f.name] = content)); + fileContentPromises.push(promise); + }); + await Promise.allSettled(fileContentPromises); + + fileSelectionRef.current?.resolve(fileContents); + fileSelectionRef.current = undefined; + event.target.value = ''; + }, []); + + const getResourceContent = useCallback((path: string) => { + return Promise.resolve(fileContentsRef.current && fileContentsRef.current[path]); + }, []); + + const metadataApi: IMetadataApi = { + askUserForFileSelection: askUserForFileSelection, + getResourceContent: getResourceContent, + shouldSaveSchema: true, + getMetadata: () => Promise.resolve(undefined), + setMetadata: () => Promise.resolve(), + deleteResource: () => Promise.resolve(true), + saveResourceContent: () => Promise.resolve(), + }; + + return ( + + {props.children} + + + ); +}; diff --git a/packages/ui/src/stubs/data-mapper.ts b/packages/ui/src/stubs/data-mapper.ts new file mode 100644 index 000000000..ae0948deb --- /dev/null +++ b/packages/ui/src/stubs/data-mapper.ts @@ -0,0 +1,73 @@ +import { parse } from 'yaml'; +import { DATAMAPPER_ID_PREFIX, XSLT_COMPONENT_NAME } from '../utils'; +import fs from 'fs'; +import { XmlSchemaDocumentService } from '../services/xml-schema-document.service'; +import { DocumentType } from '../models/datamapper/path'; +import { IDocument, PrimitiveDocument } from '../models/datamapper/document'; + +export const datamapperRouteDefinitionStub = parse(` + from: + id: from-8888 + uri: direct:start + parameters: {} + steps: + - step: + id: ${DATAMAPPER_ID_PREFIX}-1234 + steps: + - to: + uri: ${XSLT_COMPONENT_NAME}:transform.xsl`); + +export const shipOrderXsd = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/ShipOrder.xsd') + .toString(); +export const testDocumentXsd = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/TestDocument.xsd') + .toString(); +export const noTopElementXsd = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/NoTopElement.xsd') + .toString(); +export const camelSpringXsd = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/camel-spring.xsd') + .toString(); +export const shipOrderToShipOrderXslt = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/ShipOrderToShipOrder.xsl') + .toString(); +export const shipOrderToShipOrderInvalidForEachXslt = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/ShipOrderToShipOrderInvalidForEach.xsl') + .toString(); +export const shipOrderEmptyFirstLineXsd = fs + .readFileSync(__dirname + '/../../../xml-schema-ts/test-resources/ShipOrderEmptyFirstLine.xsd') + .toString(); + +export class TestUtil { + static createSourceOrderDoc() { + return XmlSchemaDocumentService.createXmlSchemaDocument(DocumentType.SOURCE_BODY, 'ShipOrder.xsd', shipOrderXsd); + } + + static createCamelSpringXsdSourceDoc() { + return XmlSchemaDocumentService.createXmlSchemaDocument( + DocumentType.SOURCE_BODY, + 'camel-spring.xsd', + camelSpringXsd, + ); + } + + static createTargetOrderDoc() { + return XmlSchemaDocumentService.createXmlSchemaDocument(DocumentType.TARGET_BODY, 'ShipOrder.xsd', shipOrderXsd); + } + + static createParamOrderDoc(name: string) { + const answer = XmlSchemaDocumentService.createXmlSchemaDocument(DocumentType.PARAM, name, shipOrderXsd); + answer.name = name; + return answer; + } + + static createParameterMap() { + const sourceParamDoc = TestUtil.createParamOrderDoc('sourceParam1'); + const sourcePrimitiveParamDoc = new PrimitiveDocument(DocumentType.PARAM, 'primitive'); + return new Map([ + ['sourceParam1', sourceParamDoc], + ['primitive', sourcePrimitiveParamDoc], + ]); + } +} diff --git a/packages/ui/src/stubs/read-file-as-string.test.ts b/packages/ui/src/stubs/read-file-as-string.test.ts new file mode 100644 index 000000000..fc8bc777c --- /dev/null +++ b/packages/ui/src/stubs/read-file-as-string.test.ts @@ -0,0 +1,11 @@ +import { readFileAsString } from './read-file-as-string'; + +describe('readFileAsString()', () => { + it('should read', async () => { + const file = new File(['foo'], 'foo.txt', { + type: 'text/plain', + }); + const answer = await readFileAsString(file); + expect(answer).toEqual('foo'); + }); +}); diff --git a/packages/ui/src/stubs/read-file-as-string.ts b/packages/ui/src/stubs/read-file-as-string.ts new file mode 100644 index 000000000..b9dfa4178 --- /dev/null +++ b/packages/ui/src/stubs/read-file-as-string.ts @@ -0,0 +1,9 @@ +export const readFileAsString = (file: File): Promise => { + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = (e) => { + e.target ? resolve(e.target.result as string) : reject(); + }; + reader.readAsText(file); + }); +}; diff --git a/packages/ui/src/styles/_dnd.scss b/packages/ui/src/styles/_dnd.scss new file mode 100644 index 000000000..5ea517ed9 --- /dev/null +++ b/packages/ui/src/styles/_dnd.scss @@ -0,0 +1,7 @@ +@mixin cursor-grab { + cursor: grab; + + &:active { + cursor: grabbing; + } +} diff --git a/packages/ui/src/testing-api.ts b/packages/ui/src/testing-api.ts index 689906ee2..4737f6bb1 100644 --- a/packages/ui/src/testing-api.ts +++ b/packages/ui/src/testing-api.ts @@ -5,6 +5,8 @@ export * from './models/camel'; export * from './providers'; export * from './utils'; export * from './stubs'; +export * from './components/DataMapper/debug'; +export * from './models/datamapper'; export type { EntitiesContextResult } from './hooks/entities'; export * from './components/Visualization/Canvas/controller.service'; diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index 28917305a..7a05172ed 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -11,8 +11,11 @@ export * from './get-user-updated-properties-schema'; export * from './get-value'; export * from './get-viznodes-from-graph'; export * from './init-visible-flows'; +export * from './is-datamapper'; export * from './is-defined'; export * from './is-enum-type'; +export * from './is-to-processor'; +export * from './is-xslt-component'; export * from './join-path'; export * from './node-icon-resolver'; export * from './pipe-custom-schema'; diff --git a/packages/ui/src/utils/is-datamapper.test.ts b/packages/ui/src/utils/is-datamapper.test.ts new file mode 100644 index 000000000..d86dc2c45 --- /dev/null +++ b/packages/ui/src/utils/is-datamapper.test.ts @@ -0,0 +1,30 @@ +import { Step } from '@kaoto/camel-catalog/types'; +import { datamapperRouteDefinitionStub } from '../stubs/data-mapper'; +import { isDataMapperNode } from './is-datamapper'; + +describe('isDataMapperNode', () => { + it('should return true if it has the right id and a xslt component', () => { + const stepDefinition = datamapperRouteDefinitionStub.from.steps[0].step as Step; + const result = isDataMapperNode(stepDefinition); + + expect(result).toBe(true); + }); + + it('should return false if it has the right id but no xslt component', () => { + const stepDefinition = datamapperRouteDefinitionStub.from.steps[0].step as Step; + stepDefinition.steps![0].to = { uri: 'direct:log' }; + + const result = isDataMapperNode(stepDefinition); + + expect(result).toBe(false); + }); + + it('should return false if it has no id but a xslt component', () => { + const stepDefinition = datamapperRouteDefinitionStub.from.steps[0].step as Step; + stepDefinition.id = 'step-1234'; + + const result = isDataMapperNode(stepDefinition); + + expect(result).toBe(false); + }); +}); diff --git a/packages/ui/src/utils/is-datamapper.ts b/packages/ui/src/utils/is-datamapper.ts new file mode 100644 index 000000000..dc9f55c10 --- /dev/null +++ b/packages/ui/src/utils/is-datamapper.ts @@ -0,0 +1,18 @@ +import { ProcessorDefinition, Step } from '@kaoto/camel-catalog/types'; + +export const DATAMAPPER_ID_PREFIX = 'kaoto-datamapper' as keyof ProcessorDefinition; +export const XSLT_COMPONENT_NAME = 'xslt-saxon'; + +export const isDataMapperNode = (stepDefinition: Step): stepDefinition is Step => { + const isDatamapperId = stepDefinition.id?.startsWith(DATAMAPPER_ID_PREFIX) ?? false; + const doesContainXslt = + stepDefinition.steps?.some((step) => { + if (typeof step.to === 'string') { + return step.to.startsWith(XSLT_COMPONENT_NAME); + } + + return step.to?.uri?.startsWith(XSLT_COMPONENT_NAME); + }) ?? false; + + return isDatamapperId && doesContainXslt; +}; diff --git a/packages/ui/src/utils/is-to-processor.test.ts b/packages/ui/src/utils/is-to-processor.test.ts new file mode 100644 index 000000000..88bffc814 --- /dev/null +++ b/packages/ui/src/utils/is-to-processor.test.ts @@ -0,0 +1,13 @@ +import { isToProcessor } from './is-to-processor'; + +describe('isToProcessor', () => { + it.each([ + [false, { to: 'mock' }], + [false, { toD: 'mock' }], + [false, {}], + [true, { to: { uri: undefined } }], + [true, { to: { uri: 'timer:myTimer' } }], + ] as const)('should return %s when toDefinition is %s', (result, toDefinition) => { + expect(isToProcessor(toDefinition)).toBe(result); + }); +}); diff --git a/packages/ui/src/utils/is-to-processor.ts b/packages/ui/src/utils/is-to-processor.ts new file mode 100644 index 000000000..6efaa6cd2 --- /dev/null +++ b/packages/ui/src/utils/is-to-processor.ts @@ -0,0 +1,11 @@ +import { ProcessorDefinition, To } from '@kaoto/camel-catalog/types'; +import { isDefined } from './is-defined'; + +export type ToObjectDef = { to: Exclude }; + +export const isToProcessor = (toDefinition: ProcessorDefinition): toDefinition is ToObjectDef => { + const doesHaveTo = 'to' in toDefinition; + const isStringBased = typeof toDefinition.to === 'string'; + + return doesHaveTo && !isStringBased && isDefined(toDefinition.to); +}; diff --git a/packages/ui/src/utils/is-xslt-component.test.ts b/packages/ui/src/utils/is-xslt-component.test.ts new file mode 100644 index 000000000..e3be469f8 --- /dev/null +++ b/packages/ui/src/utils/is-xslt-component.test.ts @@ -0,0 +1,16 @@ +import { XSLT_COMPONENT_NAME } from './is-datamapper'; +import { isXSLTComponent } from './is-xslt-component'; + +describe('isXSLTComponent', () => { + it.each([ + [false, { to: 'mock' }], + [false, { toD: 'mock' }], + [false, {}], + [false, { to: { uri: undefined } }], + [false, { to: { uri: 'timer:myTimer' } }], + [true, { to: { uri: `${XSLT_COMPONENT_NAME}` } }], + [true, { to: { uri: `${XSLT_COMPONENT_NAME}:document.xsl` } }], + ] as const)('should return %s when toDefinition is %s', (result, toDefinition) => { + expect(isXSLTComponent(toDefinition)).toBe(result); + }); +}); diff --git a/packages/ui/src/utils/is-xslt-component.ts b/packages/ui/src/utils/is-xslt-component.ts new file mode 100644 index 000000000..6311d6583 --- /dev/null +++ b/packages/ui/src/utils/is-xslt-component.ts @@ -0,0 +1,14 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { XSLT_COMPONENT_NAME } from './is-datamapper'; +import type { ToObjectDef } from './is-to-processor'; +import { isToProcessor } from './is-to-processor'; + +export type XsltComponentDef = ToObjectDef & { to: { uri: string } }; + +export const isXSLTComponent = (toDefinition: ProcessorDefinition): toDefinition is XsltComponentDef => { + if (!isToProcessor(toDefinition)) { + return false; + } + + return toDefinition.to.uri?.startsWith(XSLT_COMPONENT_NAME) ?? false; +}; diff --git a/packages/ui/src/utils/node-icon-resolver.ts b/packages/ui/src/utils/node-icon-resolver.ts index 4f2d989fe..95341c0b1 100644 --- a/packages/ui/src/utils/node-icon-resolver.ts +++ b/packages/ui/src/utils/node-icon-resolver.ts @@ -105,6 +105,7 @@ import icon_component_couchdb from '../assets/components/couchdb.svg'; import icon_component_cql from '../assets/components/cql.svg'; import icon_component_crypto from '../assets/components/crypto.svg'; import icon_component_cxf from '../assets/components/cxf.png'; +import icon_component_datamapper from '../assets/components/datamapper.png'; import icon_component_debezium from '../assets/components/debezium.svg'; import icon_component_dhis2 from '../assets/components/dhis2.svg'; import icon_component_direct from '../assets/components/direct.svg'; @@ -902,6 +903,8 @@ export class NodeIconResolver { case 'idempotentConsumer': return icon_eip_idempotent_consumer; // case 'kamelet': handled on top + case 'kaoto-datamapper': + return icon_component_datamapper; case 'loadBalance': return icon_eip_load_balance; case 'log': diff --git a/packages/xml-schema-ts/.lintstagedrc.json b/packages/xml-schema-ts/.lintstagedrc.json new file mode 100644 index 000000000..cafe25df7 --- /dev/null +++ b/packages/xml-schema-ts/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "*.ts": "yarn workspace @datamapper-poc/xml-schema-ts run eslint \"src/**/*.ts\"", +} diff --git a/packages/xml-schema-ts/README.md b/packages/xml-schema-ts/README.md new file mode 100644 index 000000000..88e158055 --- /dev/null +++ b/packages/xml-schema-ts/README.md @@ -0,0 +1,16 @@ +XmlSchemaTS - XML Schema Parser written in TypeScript, ported from Apache XmlSchema +============================================== + +This is supposed to be a minimal porting from [Apache XmlSchema](https://ws.apache.org/xmlschema/) +to TypeScript for what is required for the Data Mapper with using standard DOMParser. +The main focus is to parse the XML schema file and build a Data Mapper Document model. + +Always honor the upstream for the internal design so we can share the common sense. +https://github.com/apache/ws-xmlschema + +See this test case for the basic usage +https://github.com/KaotoIO/datamapper-poc/blob/main/packages/ui/src/util/xsd/XmlSchemaCollection.test.ts + +### Behavioral Changes +- From what I understand from XML Schema definition, the top level elements are always assigned to the target namespace. In order to achieve this behavior, I made the following change + - https://github.com/KaotoIO/datamapper-poc/commit/92be1f4b21d37194bf627a845c785aa9462dab16#diff-f4482bd2efd45dc85f74b837385f37fde87ddb67c49dde92608c37549185b3adR35-R36 \ No newline at end of file diff --git a/packages/xml-schema-ts/babel.config.cjs b/packages/xml-schema-ts/babel.config.cjs new file mode 100644 index 000000000..d40df42bf --- /dev/null +++ b/packages/xml-schema-ts/babel.config.cjs @@ -0,0 +1,8 @@ +// eslint-disable-next-line no-undef +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + ['@babel/preset-react', { runtime: 'automatic' }], + '@babel/preset-typescript', + ], +}; diff --git a/packages/xml-schema-ts/jest.config.js b/packages/xml-schema-ts/jest.config.js new file mode 100644 index 000000000..48c95ec7c --- /dev/null +++ b/packages/xml-schema-ts/jest.config.js @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export default { + testEnvironment: 'jsdom', + reporters: ['default'], + setupFilesAfterEnv: ['./jest.setup.ts'], + moduleDirectories: ['node_modules'], + testMatch: ['**/?(*.)+(test).[tj]s?(x)'], + modulePathIgnorePatterns: ['dist'], + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + collectCoverageFrom: [ + // Collect coverage from all ts and tsx files in the src folder + 'src/**/*.{ts,tsx}', + // Ignore all test files + '!src/**/*.test.{ts,tsx}', + // Ignore all declaration files + '!src/**/*.d.ts', + ], + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['\\\\node_modules\\\\'], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'babel', +}; diff --git a/packages/xml-schema-ts/jest.setup.ts b/packages/xml-schema-ts/jest.setup.ts new file mode 100644 index 000000000..e9bb7f2aa --- /dev/null +++ b/packages/xml-schema-ts/jest.setup.ts @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import '@testing-library/jest-dom'; diff --git a/packages/xml-schema-ts/package.json b/packages/xml-schema-ts/package.json new file mode 100644 index 000000000..fbf7d7110 --- /dev/null +++ b/packages/xml-schema-ts/package.json @@ -0,0 +1,46 @@ +{ + "name": "@kaoto/xml-schema-ts", + "version": "2.2.0-dev", + "type": "module", + "description": "Kaoto XmlSchemaTS", + "repository": "https://github.com/KaotoIO/kaoto", + "repositoryDirectory": "packages/xml-schema-ts", + "author": "The Kaoto Team", + "private": true, + "license": "Apache License v2.0", + "types": "./dist/esm/index.d.ts", + "main": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rimraf dist && tsc --build tsconfig.cjs.json && tsc --build tsconfig.esm.json", + "test": "jest", + "test:watch": "jest --watch", + "lint": "yarn eslint \"src/**/*.{ts,tsx}\"", + "lint:fix": "yarn lint:code --fix" + }, + "devDependencies": { + "@babel/core": "^7.23.2", + "@babel/preset-env": "^7.21.5", + "@babel/preset-typescript": "^7.21.5", + "@testing-library/jest-dom": "^6.4.2", + "@types/jest": "^29.5.12", + "eslint": "^8.45.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.7.0", + "prettier": "^3.0.0", + "rimraf": "^6.0.0", + "typescript": "^5.4.2", + "vite": "^5.4.0" + } +} diff --git a/packages/xml-schema-ts/src/DocumentFragmentNodeList.ts b/packages/xml-schema-ts/src/DocumentFragmentNodeList.ts new file mode 100644 index 000000000..947460f8a --- /dev/null +++ b/packages/xml-schema-ts/src/DocumentFragmentNodeList.ts @@ -0,0 +1,62 @@ +export class DocumentFragmentNodeList implements NodeList { + private nodes: Node[] = []; + private fragment?: DocumentFragment; + length: number; + + /** + * Create a list of the children of a given node that are elements with a specified qualified name. + * + * @param parentNode node from which to copy children. + * @param filterUri Namespace URI of children to copy. + * @param filterLocal Local name of children to copy. + */ + constructor(parentNode: Node, filterUri?: string, filterLocal?: string) { + this.length = 0; + if (!parentNode.ownerDocument) { + throw new Error('Could not access the owner Document'); + } + this.fragment = parentNode.ownerDocument.createDocumentFragment(); + for (let child = parentNode.firstChild; child != null; child = child.nextSibling) { + if (filterUri == null && filterLocal == null) { + this.nodes.push(this.fragment.appendChild(child.cloneNode(true))); + continue; + } + if (child.nodeType == Node.ELEMENT_NODE) { + const childElement = child as Element; + if (childElement.namespaceURI === filterUri && childElement.localName === filterLocal) { + this.nodes.push(this.fragment.appendChild(child.cloneNode(true))); + } + } + } + this.length = this.nodes.length; + } + + item(index: number) { + if (this.nodes == null) { + return null; + } else { + return this.nodes[index]; + } + } + + /** + * Java DOM doesn't have followings, but required in TypeScript + */ + + [index: number]: Node; + forEach(_callbackfn: (value: Node, key: number, parent: NodeList) => void, _thisArg?: object): void { + throw new Error('Method not implemented.'); + } + entries(): IterableIterator<[number, Node]> { + throw new Error('Method not implemented.'); + } + keys(): IterableIterator { + throw new Error('Method not implemented.'); + } + values(): IterableIterator { + throw new Error('Method not implemented.'); + } + [Symbol.iterator](): IterableIterator { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/xml-schema-ts/src/QName.ts b/packages/xml-schema-ts/src/QName.ts new file mode 100644 index 000000000..e1979059e --- /dev/null +++ b/packages/xml-schema-ts/src/QName.ts @@ -0,0 +1,47 @@ +export class QName { + constructor( + private namespaceURI: string | null, + private localPart: string | null, + private prefix: string | null = null, + ) {} + + getNamespaceURI(): string | null { + return this.namespaceURI; + } + + getLocalPart(): string | null { + return this.localPart; + } + + getPrefix(): string | null { + return this.prefix; + } + + valueOf(qNameAsString: string): QName { + if (qNameAsString == null) { + throw new Error('cannot create QName from null'); + } else if (qNameAsString === '') { + return new QName(null, qNameAsString, ''); + } else if (qNameAsString.charAt(0) !== '{') { + return new QName(null, qNameAsString, ''); + } else if (qNameAsString.startsWith('{}')) { + return new QName(null, qNameAsString.substring(2), ''); + } else { + const endOfNamespaceURI = qNameAsString.indexOf('}'); + if (endOfNamespaceURI === -1) { + throw new Error(`cannot create QName from ${qNameAsString}, missing closing "}"`); + } else { + return new QName( + qNameAsString.substring(1, endOfNamespaceURI), + qNameAsString.substring(endOfNamespaceURI + 1), + '' /* prefix */, + ); + } + } + } + + toString() { + const answer = this.namespaceURI ? `{${this.namespaceURI}}` : ''; + return answer + this.localPart; + } +} diff --git a/packages/xml-schema-ts/src/SchemaBuilder.ts b/packages/xml-schema-ts/src/SchemaBuilder.ts new file mode 100644 index 000000000..4e5a54ea0 --- /dev/null +++ b/packages/xml-schema-ts/src/SchemaBuilder.ts @@ -0,0 +1,1718 @@ +import type { XmlSchemaCollection } from './XmlSchemaCollection'; +import type { XmlSchemaObject } from './XmlSchemaObject'; +import type { XmlSchemaIdentityConstraint } from './constraint/XmlSchemaIdentityConstraint'; +import type { NamespaceContext } from './utils/NamespaceContext'; +import type { ExtensionRegistry } from './extensions/ExtensionRegistry'; + +import { DocumentFragmentNodeList } from './DocumentFragmentNodeList'; +import { QName } from './QName'; +import { SchemaKey } from './SchemaKey'; +import { XmlSchema } from './XmlSchema'; +import { XmlSchemaAll } from './particle/XmlSchemaAll'; +import { XmlSchemaAny } from './particle/XmlSchemaAny'; +import { XmlSchemaAnnotation } from './annotation/XmlSchemaAnnotation'; +import { XmlSchemaAnyAttribute } from './XmlSchemaAnyAttribute'; +import { XmlSchemaAppInfo } from './annotation/XmlSchemaAppInfo'; +import { XmlSchemaAttribute } from './attribute/XmlSchemaAttribute'; +import { XmlSchemaAttributeGroup } from './attribute/XmlSchemaAttributeGroup'; +import { XmlSchemaAttributeGroupRef } from './attribute/XmlSchemaAttributeGroupRef'; +import { XmlSchemaChoice } from './particle/XmlSchemaChoice'; +import { XmlSchemaComplexContent } from './complex/XmlSchemaComplexContent'; +import { XmlSchemaComplexContentExtension } from './complex/XmlSchemaComplexContentExtension'; +import { XmlSchemaComplexContentRestriction } from './complex/XmlSchemaComplexContentRestriction'; +import { XmlSchemaComplexType } from './complex/XmlSchemaComplexType'; +import { XmlSchemaContentProcessing, xmlSchemaContentProcessingValueOf } from './XmlSchemaContentProcessing'; +import { XmlSchemaDerivationMethod } from './XmlSchemaDerivationMethod'; +import { XmlSchemaElement } from './particle/XmlSchemaElement'; +import { XmlSchemaForm, xmlSchemaFormValueOf } from './XmlSchemaForm'; +import { XmlSchemaGroup } from './XmlSchemaGroup'; +import { XmlSchemaGroupRef } from './particle/XmlSchemaGroupRef'; +import { XmlSchemaImport } from './external/XmlSchemaImport'; +import { XmlSchemaInclude } from './external/XmlSchemaInclude'; +import { XmlSchemaKeyref } from './constraint/XmlSchemaKeyref'; +import { XmlSchemaNotation } from './XmlSchemaNotation'; +import { XmlSchemaSequence } from './particle/XmlSchemaSequence'; +import { XmlSchemaSimpleContent } from './simple/XmlSchemaSimpleContent'; +import { XmlSchemaSimpleContentExtension } from './simple/XmlSchemaSimpleContentExtension'; +import { XmlSchemaSimpleContentRestriction } from './simple/XmlSchemaSimpleContentRestriction'; +import { XmlSchemaSimpleType } from './simple/XmlSchemaSimpleType'; +import { XmlSchemaSimpleTypeList } from './simple/XmlSchemaSimpleTypeList'; +import { XmlSchemaSimpleTypeRestriction } from './simple/XmlSchemaSimpleTypeRestriction'; +import { XmlSchemaSimpleTypeUnion } from './simple/XmlSchemaSimpleTypeUnion'; +import { XmlSchemaUnique } from './constraint/XmlSchemaUnique'; +import { XmlSchemaXPath } from './XmlSchemaXPath'; +import { xmlSchemaUseValueOf } from './XmlSchemaUse'; +import { NodeNamespaceContext } from './utils/NodeNamespaceContext'; +import { XDOMUtil } from './utils/XDOMUtil'; +import * as Constants from './constants'; +import { XmlSchemaKey } from './constraint/XmlSchemaKey'; +import { XmlSchemaDocumentation } from './annotation/XmlSchemaDocumentation'; +import { XmlSchemaRedefine } from './external/XmlSchemaRedefine'; +import { XmlSchemaFacetConstructor } from './facet/XmlSchemaFacetConstructor'; + +export class SchemaBuilder { + private resolvedSchemas = new Map(); + private static readonly RESERVED_ATTRIBUTES = new Set([ + 'name', + 'type', + 'default', + 'fixed', + 'form', + 'id', + 'use', + 'ref', + ]); + private currentSchema = new XmlSchema(); + private extReg?: ExtensionRegistry; + + constructor( + private collection: XmlSchemaCollection, + private currentValidator?: (s: XmlSchema) => void, + ) { + if (this.collection.getExtReg() != null) { + this.extReg = this.collection.getExtReg(); + } + } + + getExtReg() { + return this.extReg; + } + + setExtReg(extReg: ExtensionRegistry) { + this.extReg = extReg; + } + + build(doc: Document, uri?: string): XmlSchema { + const schemaEl = doc.documentElement as Element; + return this.handleXmlSchemaElement(schemaEl, uri); + } + + getDerivation(el: Element, attrName: string) { + if (el.hasAttribute(attrName) && !(el.getAttribute(attrName) === '')) { + // #all | List of (extension | restriction | substitution) + const derivationMethod = el.getAttribute(attrName)!.trim(); + return XmlSchemaDerivationMethod.schemaValueOf(derivationMethod); + } + return XmlSchemaDerivationMethod.NONE; + } + + getEnumString(el: Element, attrName: string) { + if (el.hasAttribute(attrName)) { + return el.getAttribute(attrName)?.trim(); + } + return 'none'; // local convention for empty value. + } + + getFormDefault(el: Element, attrName: string) { + if (el.getAttributeNode(attrName) != null) { + const value = el.getAttribute(attrName)!; + return xmlSchemaFormValueOf(value); + } else { + return XmlSchemaForm.UNQUALIFIED; + } + } + + getMaxOccurs(el: Element) { + if (el.getAttributeNode('maxOccurs') != null) { + const value = el.getAttribute('maxOccurs')!; + if ('unbounded' === value) { + return Number.MAX_SAFE_INTEGER; + } else { + return parseInt(value); + } + } + return 1; + } + + getMinOccurs(el: Element) { + if (el.getAttributeNode('minOccurs') != null) { + const value = el.getAttribute('minOccurs')!; + if ('unbounded' === value) { + return Number.MAX_SAFE_INTEGER; + } else { + return parseInt(value); + } + } + return 1; + } + + /** + * Handles the annotation Traversing if encounter appinfo or documentation add it to annotation collection + */ + handleAnnotation(annotEl: Element) { + const annotation = new XmlSchemaAnnotation(); + const content = annotation.getItems(); + let appInfoObj: XmlSchemaAppInfo | null; + + for ( + let appinfo = XDOMUtil.getFirstChildElementNS(annotEl, XmlSchema.SCHEMA_NS, 'appinfo'); + appinfo != null; + appinfo = XDOMUtil.getNextSiblingElementNS(appinfo, XmlSchema.SCHEMA_NS, 'appinfo') + ) { + appInfoObj = this.handleAppInfo(appinfo); + if (appInfoObj != null) { + content.push(appInfoObj); + } + } + + for ( + let documentation = XDOMUtil.getFirstChildElementNS(annotEl, XmlSchema.SCHEMA_NS, 'documentation'); + documentation != null; + documentation = XDOMUtil.getNextSiblingElementNS(documentation, XmlSchema.SCHEMA_NS, 'documentation') + ) { + const docsObj = this.handleDocumentation(documentation); + if (docsObj != null) { + content.push(docsObj); + } + } + + // process extra attributes and elements + this.processExtensibilityComponents(annotation, annotEl, true); + return annotation; + } + + /** + * create new XmlSchemaAppinfo and add value gotten from element to this obj + * + * @param content + */ + handleAppInfo(content: Element) { + const appInfo = new XmlSchemaAppInfo(); + const markup = new DocumentFragmentNodeList(content); + + if (!content.hasAttribute('source') && markup.length == 0) { + return null; + } + + appInfo.setSource(this.getAttribute(content, 'source')); + appInfo.setMarkup(markup); + return appInfo; + } + + /** + * Handle complex types + * + * @param schema + * @param complexEl + * @param schemaEl + * @param topLevel + */ + handleComplexType(schema: XmlSchema, complexEl: Element, schemaEl: Element, topLevel: boolean) { + const ct = new XmlSchemaComplexType(schema, topLevel); + + if (complexEl.hasAttribute('name')) { + // String namespace = (schema.targetNamespace==null)? + // "":schema.targetNamespace; + + ct.setName(complexEl.getAttribute('name')); + } + for ( + let el = XDOMUtil.getFirstChildElementNS(complexEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + // String elPrefix = el.getPrefix() == null ? "" : + // el.getPrefix(); + // if(elPrefix.equals(schema.schema_ns_prefix)) { + if (el.localName === 'sequence') { + ct.setParticle(this.handleSequence(schema, el, schemaEl)); + } else if (el.localName === 'choice') { + ct.setParticle(this.handleChoice(schema, el, schemaEl)); + } else if (el.localName === 'all') { + ct.setParticle(this.handleAll(schema, el, schemaEl)); + } else if (el.localName === 'attribute') { + ct.getAttributes().push(this.handleAttribute(schema, el, schemaEl)); + } else if (el.localName === 'attributeGroup') { + ct.getAttributes().push(this.handleAttributeGroupRef(schema, el)); + } else if (el.localName === 'group') { + const group = this.handleGroupRef(schema, el, schemaEl); + if (group.getParticle() == null) { + ct.setParticle(group); + } else { + ct.setParticle(group.getParticle()); + } + } else if (el.localName === 'simpleContent') { + ct.setContentModel(this.handleSimpleContent(schema, el, schemaEl)); + } else if (el.localName === 'complexContent') { + ct.setContentModel(this.handleComplexContent(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + ct.setAnnotation(this.handleAnnotation(el)); + } else if (el.localName === 'anyAttribute') { + ct.setAnyAttribute(this.handleAnyAttribute(schema, el, schemaEl)); + } + } + if (complexEl.hasAttribute('block')) { + const blockStr = complexEl.getAttribute('block')!; + ct.setBlock(XmlSchemaDerivationMethod.schemaValueOf(blockStr)); + } + if (complexEl.hasAttribute('final')) { + const finalstr = complexEl.getAttribute('final')!; + ct.setFinal(XmlSchemaDerivationMethod.schemaValueOf(finalstr)); + } + if (complexEl.hasAttribute('abstract')) { + const abs = complexEl.getAttribute('abstract')!; + if (abs.toLowerCase() === 'true') { + ct.setAbstract(true); + } else { + ct.setAbstract(false); + } + } + if (complexEl.hasAttribute('mixed')) { + const mixed = complexEl.getAttribute('mixed')!; + if (mixed.toLowerCase() === 'true') { + ct.setMixed(true); + } else { + ct.setMixed(false); + } + } + + // process extra attributes and elements + this.processExtensibilityComponents(ct, complexEl, true); + + return ct; + } + + /** + * iterate each documentation element, create new XmlSchemaAppinfo and add + * to collection + */ + handleDocumentation(content: Element) { + const documentation = new XmlSchemaDocumentation(); + const markup = this.getChildren(content); + + if (!content.hasAttribute('source') && !content.hasAttribute('xml:lang') && markup == null) { + return null; + } + + documentation.setSource(this.getAttribute(content, 'source')); + documentation.setLanguage(this.getAttribute(content, 'xml:lang')); + documentation.setMarkup(new DocumentFragmentNodeList(content)); + + return documentation; + } + + /** + * handle_complex_content_restriction + */ + /** + * handle elements + * + * @param schema + * @param el + * @param schemaEl + * @param isGlobal + */ + handleElement(schema: XmlSchema, el: Element, schemaEl: Element, isGlobal: boolean) { + const element = new XmlSchemaElement(schema, isGlobal); + + if (el.getAttributeNode('name') != null) { + element.setName(el.getAttribute('name')); + } + + // String namespace = (schema.targetNamespace==null)? + // "" : schema.targetNamespace; + + let isQualified = schema.getElementFormDefault() == XmlSchemaForm.QUALIFIED; + isQualified = this.handleElementForm(el, element, isQualified); + + this.handleElementName(isGlobal, element, isQualified); + this.handleElementAnnotation(el, element); + this.handleElementGlobalType(el, element); + + let complexTypeEl: Element | null; + let keyEl: Element | null; + let keyrefEl: Element | null; + let uniqueEl: Element | null; + const simpleTypeEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'simpleType'); + if (simpleTypeEl != null) { + const simpleType = this.handleSimpleType(schema, simpleTypeEl, schemaEl, false); + element.setSchemaType(simpleType); + element.setSchemaTypeName(simpleType.getQName()); + } else { + complexTypeEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'complexType'); + if (complexTypeEl != null) { + element.setSchemaType(this.handleComplexType(schema, complexTypeEl, schemaEl, false)); + } + } + + keyEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'key'); + if (keyEl != null) { + while (keyEl != null) { + element.getConstraints().push(this.handleConstraint(keyEl, new XmlSchemaKey())); + keyEl = XDOMUtil.getNextSiblingElementNS(keyEl, XmlSchema.SCHEMA_NS, 'key'); + } + } + + keyrefEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'keyref'); + if (keyrefEl != null) { + while (keyrefEl != null) { + const keyRef = this.handleConstraint(keyrefEl, new XmlSchemaKeyref()) as XmlSchemaKeyref; + if (keyrefEl.hasAttribute('refer')) { + const name = keyrefEl.getAttribute('refer')!; + keyRef.refer = this.getRefQName(name, el); + } + element.getConstraints().push(keyRef); + keyrefEl = XDOMUtil.getNextSiblingElementNS(keyrefEl, XmlSchema.SCHEMA_NS, 'keyref'); + } + } + + uniqueEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'unique'); + if (uniqueEl != null) { + while (uniqueEl != null) { + element.getConstraints().push(this.handleConstraint(uniqueEl, new XmlSchemaUnique())); + uniqueEl = XDOMUtil.getNextSiblingElementNS(uniqueEl, XmlSchema.SCHEMA_NS, 'unique'); + } + } + + if (el.hasAttribute('abstract')) { + element.setAbstractElement(/true/i.test(el.getAttribute('abstract')!)); + } + + if (el.hasAttribute('block')) { + element.setBlock(this.getDerivation(el, 'block')); + } + + if (el.hasAttribute('default')) { + element.setDefaultValue(el.getAttribute('default')); + } + + if (el.hasAttribute('final')) { + element.setFinalDerivation(this.getDerivation(el, 'final')); + } + + if (el.hasAttribute('fixed')) { + element.setFixedValue(el.getAttribute('fixed')); + } + + if (el.hasAttribute('id')) { + element.setId(el.getAttribute('id')); + } + + if (el.hasAttribute('nillable')) { + element.setNillable(/true/i.test(el.getAttribute('nillable')!)); + } + + if (el.hasAttribute('substitutionGroup')) { + const substitutionGroup = el.getAttribute('substitutionGroup')!; + element.setSubstitutionGroup(this.getRefQName(substitutionGroup, el)); + } + + element.setMinOccurs(this.getMinOccurs(el)); + element.setMaxOccurs(this.getMaxOccurs(el)); + + // process extra attributes and elements + this.processExtensibilityComponents(element, el, true); + + return element; + } + + /** + * Handle the import + * + * @param schema + * @param importEl + * @param _schemaEl + * @return XmlSchemaObject + */ + handleImport(schema: XmlSchema, importEl: Element, _schemaEl: Element) { + const schemaImport = new XmlSchemaImport(schema); + + const annotationEl = XDOMUtil.getFirstChildElementNS(importEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const importAnnotation = this.handleAnnotation(annotationEl); + schemaImport.setAnnotation(importAnnotation); + } + + schemaImport.namespace = importEl.getAttribute('namespace'); + const uri = schemaImport.namespace; + schemaImport.schemaLocation = importEl.getAttribute('schemaLocation'); + + const validator = (pSchema: XmlSchema) => { + const isEmpty = (pValue: string | null) => { + return pValue == null || Constants.NULL_NS_URI === pValue; + }; + + let valid: boolean; + if (isEmpty(uri)) { + valid = isEmpty(pSchema.getSyntacticalTargetNamespace()); + } else { + valid = pSchema.getSyntacticalTargetNamespace() === uri; + } + if (!valid) { + throw new Error( + 'An imported schema was announced to have the namespace ' + + uri + + ', but has the namespace ' + + pSchema.getSyntacticalTargetNamespace(), + ); + } + }; + schemaImport.schema = this.resolveXmlSchema(uri, schemaImport.schemaLocation, schema.getSourceURI(), validator); + return schemaImport; + } + + /** + * Handles the include + * + * @param schema + * @param includeEl + * @param _schemaEl + */ + handleInclude(schema: XmlSchema, includeEl: Element, _schemaEl: Element) { + const include = new XmlSchemaInclude(schema); + + const annotationEl = XDOMUtil.getFirstChildElementNS(includeEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const includeAnnotation = this.handleAnnotation(annotationEl); + include.setAnnotation(includeAnnotation); + } + + include.schemaLocation = includeEl.getAttribute('schemaLocation'); + + // includes are not supposed to have a target namespace + // we should be passing in a null in place of the target + // namespace + + const validator = this.newIncludeValidator(schema); + include.schema = this.resolveXmlSchema( + schema.getLogicalTargetNamespace(), + include.schemaLocation, + schema.getSourceURI(), + validator, + ); + + // process extra attributes and elements + this.processExtensibilityComponents(include, includeEl, true); + return include; + } + + /** + * Handles simple types + * + * @param schema + * @param simpleEl + * @param schemaEl + * @param topLevel + */ + handleSimpleType(schema: XmlSchema, simpleEl: Element, schemaEl: Element, topLevel: boolean) { + const simpleType = new XmlSchemaSimpleType(schema, topLevel); + if (simpleEl.hasAttribute('name')) { + simpleType.setName(simpleEl.getAttribute('name')); + } + + this.handleSimpleTypeFinal(simpleEl, simpleType); + + const simpleTypeAnnotationEl = XDOMUtil.getFirstChildElementNS(simpleEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (simpleTypeAnnotationEl != null) { + const simpleTypeAnnotation = this.handleAnnotation(simpleTypeAnnotationEl); + + simpleType.setAnnotation(simpleTypeAnnotation); + } + + const unionEl = XDOMUtil.getFirstChildElementNS(simpleEl, XmlSchema.SCHEMA_NS, 'union'); + const listEl = XDOMUtil.getFirstChildElementNS(simpleEl, XmlSchema.SCHEMA_NS, 'list'); + const restrictionEl = XDOMUtil.getFirstChildElementNS(simpleEl, XmlSchema.SCHEMA_NS, 'restriction'); + if (restrictionEl != null) { + this.handleSimpleTypeRestriction(schema, schemaEl, simpleType, restrictionEl); + } else if (listEl != null) { + this.handleSimpleTypeList(schema, schemaEl, simpleType, listEl); + } else if (unionEl != null) { + this.handleSimpleTypeUnion(schema, schemaEl, simpleType, unionEl); + } + + // process extra attributes and elements + this.processExtensibilityComponents(simpleType, simpleEl, true); + + return simpleType; + } + + handleXmlSchemaElement(schemaEl: Element, systemId?: string): XmlSchema { + this.currentSchema.setNamespaceContext(NodeNamespaceContext.getNamespaceContext(schemaEl)); + this.setNamespaceAttributes(this.currentSchema, schemaEl); + + const schemaKey = new SchemaKey(this.currentSchema.getLogicalTargetNamespace(), systemId); + this.handleSchemaElementBasics(schemaEl, systemId, schemaKey); + + let el = XDOMUtil.getFirstChildElementNS(schemaEl, XmlSchema.SCHEMA_NS); + for (; el != null; el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS)) { + this.handleSchemaElementChild(schemaEl, el); + } + + this.processExtensibilityComponents(this.currentSchema, schemaEl, false); + return this.currentSchema; + } + + /** + * Resolve the schemas + * + * @param targetNamespace + * @param schemaLocation + * @param baseUri + * @param validator + */ + resolveXmlSchema( + targetNamespace: string | null, + schemaLocation: string | null, + baseUri: string | null, + validator: (s: XmlSchema) => void, + ) { + if (baseUri == null) { + baseUri = this.collection.baseUri; + } + if (targetNamespace == null) { + targetNamespace = Constants.NULL_NS_URI; + } + + if ( + targetNamespace != null && + schemaLocation != null && + baseUri != null && + this.getCachedSchema(targetNamespace, schemaLocation, baseUri) != null + ) { + return this.getCachedSchema(targetNamespace, schemaLocation, baseUri); + } + + // use the entity resolver provided if the schema location is present + // null + if (schemaLocation != null && !('' === schemaLocation)) { + const source = this.collection.getSchemaResolver().resolveEntity(targetNamespace, schemaLocation, baseUri); + + // the entity resolver was unable to resolve this!! + if (source == null) { + // try resolving it with the target namespace only with the + // known namespace map + return this.collection.getKnownSchema(targetNamespace); + } + //const systemId = source.getSystemId() == null ? schemaLocation : source.getSystemId(); + const systemId = schemaLocation; + // Push repaired system id back into source where read sees it. + // It is perhaps a bad thing to patch the source, but this fixes + // a problem. + //source.setSystemId(systemId); + const key = new SchemaKey(targetNamespace, systemId); + const schema = this.collection.getSchema(key); + if (schema != null) { + return schema; + } + if (this.collection.check(key)) { + this.collection.push(key); + try { + const readSchema = this.collection.read(source, validator); + this.putCachedSchema(targetNamespace, schemaLocation, baseUri || '', readSchema); + return readSchema; + } finally { + this.collection.pop(); + } + } + } else { + const schema = this.collection.getKnownSchema(targetNamespace); + if (schema != null) { + return schema; + } + } + return null; + } + + setNamespaceAttributes(schema: XmlSchema, schemaEl: Element) { + // no targetnamespace found ! + if (schemaEl.getAttributeNode('targetNamespace') != null) { + const contain = schemaEl.getAttribute('targetNamespace')!; + schema.setTargetNamespace(contain); + } + if (this.currentValidator != null) { + this.currentValidator(schema); + } + } + + private getAttribute(content: Element, attrName: string) { + if (content.hasAttribute(attrName)) { + return content.getAttribute(attrName); + } + return null; + } + + /** + * Return a cached schema if one exists for this thread. In order for schemas to be cached the thread must + * have done an initCache() previously. The parameters are used to construct a key used to lookup the + * schema + * + * @param targetNamespace + * @param schemaLocation + * @param baseUri + * @return The cached schema if one exists for this thread or null. + */ + private getCachedSchema(targetNamespace: string, schemaLocation: string, baseUri: string) { + let resolvedSchema: XmlSchema | null = null; + + if (this.resolvedSchemas != null) { + // cache is initialized, use it + const schemaKey = targetNamespace + schemaLocation + baseUri; + resolvedSchema = this.resolvedSchemas.get(schemaKey) || null; + } + return resolvedSchema; + } + + private getChildren(content: Element) { + const result: Node[] = []; + for (let n = content.firstChild; n != null; n = n.nextSibling) { + result.push(n); + } + if (result.length == 0) { + return null; + } else { + return result; + } + } + + private getRefQName(pName: string, pNode?: Node, pContext?: NamespaceContext) { + if (pNode) { + pContext = NodeNamespaceContext.getNamespaceContext(pNode); + } + if (!pContext) { + throw new Error('Either Node or NamespaceContext must be specified'); + } + + const offset = pName.indexOf(':'); + let uri: string; + let localName: string; + let prefix: string; + if (offset == -1) { + uri = pContext.getNamespaceURI(Constants.DEFAULT_NS_PREFIX); + if (Constants.NULL_NS_URI === uri) { + if ( + this.currentSchema.getTargetNamespace() == null && + !(this.currentSchema.getLogicalTargetNamespace() === '') + ) { + // If object is unqualified in a schema without a target namespace then it could + // be that this schema is included in another one. The including namespace + // should then be used for this reference + return new QName(this.currentSchema.getLogicalTargetNamespace(), pName); + } + return new QName(Constants.NULL_NS_URI, pName); + } + localName = pName; + prefix = Constants.DEFAULT_NS_PREFIX; + } else { + prefix = pName.substring(0, offset); + uri = pContext.getNamespaceURI(prefix); + const parentSchema = this.currentSchema.getParent(); + if ( + uri == null || + (Constants.NULL_NS_URI === uri && parentSchema != null && parentSchema.getNamespaceContext() != null) + ) { + uri = parentSchema!.getNamespaceContext()!.getNamespaceURI(prefix); + } + + if (uri == null || Constants.NULL_NS_URI === uri) { + throw new Error('The prefix ' + prefix + ' is not bound.'); + } + localName = pName.substring(offset + 1); + } + return new QName(uri, localName, prefix); + } + + private handleAll(schema: XmlSchema, allEl: Element, schemaEl: Element) { + const all = new XmlSchemaAll(); + + // handle min and max occurences + all.setMinOccurs(this.getMinOccurs(allEl)); + all.setMaxOccurs(this.getMaxOccurs(allEl)); + + for ( + let el = XDOMUtil.getFirstChildElementNS(allEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'element') { + const element = this.handleElement(schema, el, schemaEl, false); + all.getItems().push(element); + } else if (el.localName === 'annotation') { + const annotation = this.handleAnnotation(el); + all.setAnnotation(annotation); + } + } + return all; + } + + private handleAny(schema: XmlSchema, anyEl: Element, _schemaEl: Element) { + const any = new XmlSchemaAny(); + + any.setTargetNamespace(schema.getLogicalTargetNamespace()); + + if (anyEl.hasAttribute('namespace')) { + any.setNamespace(anyEl.getAttribute('namespace')); + } + + if (anyEl.hasAttribute('processContents')) { + const processContent = this.getEnumString(anyEl, 'processContents'); + + processContent != null && any.setProcessContent(xmlSchemaContentProcessingValueOf(processContent)); + } + + const annotationEl = XDOMUtil.getFirstChildElementNS(anyEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + any.setAnnotation(annotation); + } + any.setMinOccurs(this.getMinOccurs(anyEl)); + any.setMaxOccurs(this.getMaxOccurs(anyEl)); + + return any; + } + + private handleAnyAttribute(_schema: XmlSchema, anyAttrEl: Element, _schemaEl: Element) { + const anyAttr = new XmlSchemaAnyAttribute(); + + if (anyAttrEl.hasAttribute('namespace')) { + anyAttr.namespace = anyAttrEl.getAttribute('namespace'); + } + + if (anyAttrEl.hasAttribute('processContents')) { + const contentProcessing = this.getEnumString(anyAttrEl, 'processContents'); + + anyAttr.processContent = + contentProcessing != null + ? xmlSchemaContentProcessingValueOf(contentProcessing) + : XmlSchemaContentProcessing.NONE; + } + if (anyAttrEl.hasAttribute('id')) { + anyAttr.setId(anyAttrEl.getAttribute('id')); + } + + const annotationEl = XDOMUtil.getFirstChildElementNS(anyAttrEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + anyAttr.setAnnotation(annotation); + } + return anyAttr; + } + + /** + * Process attributes + * + * @param schema + * @param attrEl + * @param schemaEl + * @param topLevel + * @return + */ + private handleAttribute(schema: XmlSchema, attrEl: Element, schemaEl: Element, topLevel: boolean = false) { + const attr = new XmlSchemaAttribute(schema, topLevel); + + if (attrEl.hasAttribute('name')) { + const name = attrEl.getAttribute('name')!; + attr.setName(name); + } + + if (attrEl.hasAttribute('type')) { + const name = attrEl.getAttribute('type')!; + attr.setSchemaTypeName(this.getRefQName(name, attrEl)); + } + + if (attrEl.hasAttribute('default')) { + attr.setDefaultValue(attrEl.getAttribute('default')!); + } + + if (attrEl.hasAttribute('fixed')) { + attr.setFixedValue(attrEl.getAttribute('fixed')!); + } + + if (attrEl.hasAttribute('form')) { + const formValue = this.getEnumString(attrEl, 'form')!; + attr.setForm(xmlSchemaFormValueOf(formValue)); + } + + if (attrEl.hasAttribute('id')) { + attr.setId(attrEl.getAttribute('id')!); + } + + if (attrEl.hasAttribute('use')) { + const useType = this.getEnumString(attrEl, 'use')!; + attr.setUse(xmlSchemaUseValueOf(useType)); + } + if (attrEl.hasAttribute('ref')) { + const name = attrEl.getAttribute('ref')!; + attr.getRef().setTargetQName(this.getRefQName(name, attrEl)); + } + + const simpleTypeEl = XDOMUtil.getFirstChildElementNS(attrEl, XmlSchema.SCHEMA_NS, 'simpleType'); + + if (simpleTypeEl != null) { + attr.setSchemaType(this.handleSimpleType(schema, simpleTypeEl, schemaEl, false)); + } + + const annotationEl = XDOMUtil.getFirstChildElementNS(attrEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + attr.setAnnotation(annotation); + } + + const attrNodes = attrEl.attributes; + const attrs: Attr[] = []; + let ctx: NodeNamespaceContext | null = null; + for (let i = 0; i < attrNodes.length; i++) { + const att = attrNodes.item(i) as Attr; + const attName = att.name; + if (!SchemaBuilder.RESERVED_ATTRIBUTES.has(attName)) { + attrs.push(att); + const value = att.value; + + if (value.indexOf(':') > -1) { + // there is a possibility of some namespace mapping + const prefix = value.substring(0, value.indexOf(':')); + if (ctx == null) { + ctx = NodeNamespaceContext.getNamespaceContext(attrEl); + } + const namespace = ctx.getNamespaceURI(prefix); + if (Constants.NULL_NS_URI !== namespace) { + const nsAttr = attrEl.ownerDocument.createAttributeNS(Constants.XMLNS_ATTRIBUTE_NS_URI, 'xmlns:' + prefix); + nsAttr.value = namespace; + attrs.push(nsAttr); + } + } + } + } + + if (attrs.length > 0) { + attr.setUnhandledAttributes(attrs); + } + + // process extra attributes and elements + this.processExtensibilityComponents(attr, attrEl, true); + return attr; + } + + private handleAttributeGroup(schema: XmlSchema, groupEl: Element, schemaEl: Element) { + const attrGroup = new XmlSchemaAttributeGroup(schema); + + if (groupEl.hasAttribute('name')) { + attrGroup.setName(groupEl.getAttribute('name')!); + } + if (groupEl.hasAttribute('id')) { + attrGroup.setId(groupEl.getAttribute('id')); + } + + for ( + let el = XDOMUtil.getFirstChildElementNS(groupEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'attribute') { + const attr = this.handleAttribute(schema, el, schemaEl); + attrGroup.getAttributes().push(attr); + } else if (el.localName === 'attributeGroup') { + const attrGroupRef = this.handleAttributeGroupRef(schema, el); + attrGroup.getAttributes().push(attrGroupRef); + } else if (el.localName === 'anyAttribute') { + attrGroup.setAnyAttribute(this.handleAnyAttribute(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + const ann = this.handleAnnotation(el); + attrGroup.setAnnotation(ann); + } + } + return attrGroup; + } + + private handleAttributeGroupRef(schema: XmlSchema, attrGroupEl: Element) { + const attrGroup = new XmlSchemaAttributeGroupRef(schema); + + if (attrGroupEl.hasAttribute('ref')) { + const ref = attrGroupEl.getAttribute('ref')!; + attrGroup.getRef().setTargetQName(this.getRefQName(ref, attrGroupEl)); + } + + if (attrGroupEl.hasAttribute('id')) { + attrGroup.setId(attrGroupEl.getAttribute('id')); + } + + const annotationEl = XDOMUtil.getFirstChildElementNS(attrGroupEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + attrGroup.setAnnotation(annotation); + } + return attrGroup; + } + + private handleChoice(schema: XmlSchema, choiceEl: Element, schemaEl: Element) { + const choice = new XmlSchemaChoice(); + + if (choiceEl.hasAttribute('id')) { + choice.setId(choiceEl.getAttribute('id')); + } + + choice.setMinOccurs(this.getMinOccurs(choiceEl)); + choice.setMaxOccurs(this.getMaxOccurs(choiceEl)); + + for ( + let el = XDOMUtil.getFirstChildElementNS(choiceEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'sequence') { + const seq = this.handleSequence(schema, el, schemaEl); + choice.getItems().push(seq); + } else if (el.localName === 'element') { + const element = this.handleElement(schema, el, schemaEl, false); + choice.getItems().push(element); + } else if (el.localName === 'group') { + const group = this.handleGroupRef(schema, el, schemaEl); + choice.getItems().push(group); + } else if (el.localName === 'choice') { + const choiceItem = this.handleChoice(schema, el, schemaEl); + choice.getItems().push(choiceItem); + } else if (el.localName === 'any') { + const any = this.handleAny(schema, el, schemaEl); + choice.getItems().push(any); + } else if (el.localName === 'annotation') { + const annotation = this.handleAnnotation(el); + choice.setAnnotation(annotation); + } + } + return choice; + } + + private handleComplexContent(schema: XmlSchema, complexEl: Element, schemaEl: Element) { + const complexContent = new XmlSchemaComplexContent(); + + for ( + let el = XDOMUtil.getFirstChildElementNS(complexEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'restriction') { + complexContent.content = this.handleComplexContentRestriction(schema, el, schemaEl); + } else if (el.localName === 'extension') { + complexContent.content = this.handleComplexContentExtension(schema, el, schemaEl); + } else if (el.localName === 'annotation') { + complexContent.setAnnotation(this.handleAnnotation(el)); + } + } + + if (complexEl.hasAttribute('mixed')) { + const mixed = complexEl.getAttribute('mixed')!; + if (mixed.toLowerCase() === 'true') { + complexContent.setMixed(true); + } else { + complexContent.setMixed(false); + } + } + + return complexContent; + } + + private handleComplexContentExtension(schema: XmlSchema, extEl: Element, schemaEl: Element) { + const ext = new XmlSchemaComplexContentExtension(); + + if (extEl.hasAttribute('base')) { + const name = extEl.getAttribute('base')!; + ext.setBaseTypeName(this.getRefQName(name, extEl)); + } + + for ( + let el = XDOMUtil.getFirstChildElementNS(extEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'sequence') { + ext.setParticle(this.handleSequence(schema, el, schemaEl)); + } else if (el.localName === 'choice') { + ext.setParticle(this.handleChoice(schema, el, schemaEl)); + } else if (el.localName === 'all') { + ext.setParticle(this.handleAll(schema, el, schemaEl)); + } else if (el.localName === 'attribute') { + ext.getAttributes().push(this.handleAttribute(schema, el, schemaEl)); + } else if (el.localName === 'attributeGroup') { + ext.getAttributes().push(this.handleAttributeGroupRef(schema, el)); + } else if (el.localName === 'group') { + ext.setParticle(this.handleGroupRef(schema, el, schemaEl)); + } else if (el.localName === 'anyAttribute') { + ext.setAnyAttribute(this.handleAnyAttribute(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + ext.setAnnotation(this.handleAnnotation(el)); + } + } + return ext; + } + + private handleComplexContentRestriction(schema: XmlSchema, restrictionEl: Element, schemaEl: Element) { + const restriction = new XmlSchemaComplexContentRestriction(); + + if (restrictionEl.hasAttribute('base')) { + const name = restrictionEl.getAttribute('base')!; + restriction.setBaseTypeName(this.getRefQName(name, restrictionEl)); + } + for ( + let el = XDOMUtil.getFirstChildElementNS(restrictionEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'sequence') { + restriction.setParticle(this.handleSequence(schema, el, schemaEl)); + } else if (el.localName === 'choice') { + restriction.setParticle(this.handleChoice(schema, el, schemaEl)); + } else if (el.localName === 'all') { + restriction.setParticle(this.handleAll(schema, el, schemaEl)); + } else if (el.localName === 'attribute') { + restriction.getAttributes().push(this.handleAttribute(schema, el, schemaEl)); + } else if (el.localName === 'attributeGroup') { + restriction.getAttributes().push(this.handleAttributeGroupRef(schema, el)); + } else if (el.localName === 'group') { + restriction.setParticle(this.handleGroupRef(schema, el, schemaEl)); + } else if (el.localName === 'anyAttribute') { + restriction.setAnyAttribute(this.handleAnyAttribute(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + restriction.setAnnotation(this.handleAnnotation(el)); + } + } + return restriction; + } + + private handleConstraint(constraintEl: Element, constraint: XmlSchemaIdentityConstraint) { + if (constraintEl.hasAttribute('name')) { + constraint.setName(constraintEl.getAttribute('name')!); + } + + if (constraintEl.hasAttribute('refer')) { + const name = constraintEl.getAttribute('refer')!; + (constraint as XmlSchemaKeyref).refer = this.getRefQName(name, constraintEl); + } + for ( + let el = XDOMUtil.getFirstChildElementNS(constraintEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + // String elPrefix = el.getPrefix() == null ? "" + // : el.getPrefix(); + // if(elPrefix.equals(schema.schema_ns_prefix)) { + if (el.localName === 'selector') { + const selectorXPath = new XmlSchemaXPath(); + selectorXPath.xpath = el.getAttribute('xpath'); + + const annotationEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'annotation'); + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + selectorXPath.setAnnotation(annotation); + } + constraint.setSelector(selectorXPath); + } else if (el.localName === 'field') { + const fieldXPath = new XmlSchemaXPath(); + fieldXPath.xpath = el.getAttribute('xpath'); + constraint.getFields().push(fieldXPath); + + const annotationEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + fieldXPath.setAnnotation(annotation); + } + } else if (el.localName === 'annotation') { + const constraintAnnotation = this.handleAnnotation(el); + constraint.setAnnotation(constraintAnnotation); + } + } + return constraint; + } + + private handleElementAnnotation(el: Element, element: XmlSchemaElement) { + const annotationEl = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + element.setAnnotation(annotation); + } + } + + private handleElementForm(el: Element, element: XmlSchemaElement, _isQualified: boolean) { + if (el.hasAttribute('form')) { + const formDef = el.getAttribute('form')!; + element.setForm(xmlSchemaFormValueOf(formDef)); + } + return element.getForm() == XmlSchemaForm.QUALIFIED; + } + + private handleElementGlobalType(el: Element, element: XmlSchemaElement) { + if (el.getAttributeNode('type') != null) { + const typeName = el.getAttribute('type')!; + element.setSchemaTypeName(this.getRefQName(typeName, el)); + const typeQName = element.getSchemaTypeName()!; + + const type = this.collection.getTypeByQName(typeQName); + if (type == null) { + // Could be a forward reference... + this.collection.addUnresolvedType(typeQName, element); + } + element.setSchemaType(type); + } else if (el.getAttributeNode('ref') != null) { + const refName = el.getAttribute('ref')!; + const refQName = this.getRefQName(refName, el); + element.getRef().setTargetQName(refQName); + } + } + + private handleElementName(_isGlobal: boolean, _element: XmlSchemaElement, _isQualified: boolean) {} + + /* + * handle_simple_content_restriction if( restriction has base attribute ) set the baseType else if( + * restriction has an inline simpleType ) handleSimpleType add facets if any to the restriction + */ + + /* + * handle_simple_content_extension extension should have a base name and cannot have any inline defn for( + * each childNode ) if( attribute) handleAttribute else if( attributeGroup) handleAttributeGroup else if( + * anyAttribute) handleAnyAttribute + */ + + private handleGroup(schema: XmlSchema, groupEl: Element, schemaEl: Element) { + const group = new XmlSchemaGroup(schema); + group.setName(groupEl.getAttribute('name')); + + for ( + let el = XDOMUtil.getFirstChildElementNS(groupEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'all') { + group.setParticle(this.handleAll(schema, el, schemaEl)); + } else if (el.localName === 'sequence') { + group.setParticle(this.handleSequence(schema, el, schemaEl)); + } else if (el.localName === 'choice') { + group.setParticle(this.handleChoice(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + const groupAnnotation = this.handleAnnotation(el); + group.setAnnotation(groupAnnotation); + } + } + return group; + } + + private handleGroupRef(schema: XmlSchema, groupEl: Element, schemaEl: Element) { + const group = new XmlSchemaGroupRef(); + + group.setMaxOccurs(this.getMaxOccurs(groupEl)); + group.setMinOccurs(this.getMinOccurs(groupEl)); + + const annotationEl = XDOMUtil.getFirstChildElementNS(groupEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + + group.setAnnotation(annotation); + } + + if (groupEl.hasAttribute('ref')) { + const ref = groupEl.getAttribute('ref')!; + group.setRefName(this.getRefQName(ref, groupEl)); + return group; + } + for ( + let el = XDOMUtil.getFirstChildElementNS(groupEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElement(el) + ) { + if (el.localName === 'sequence') { + group.setParticle(this.handleSequence(schema, el, schemaEl)); + } else if (el.localName === 'all') { + group.setParticle(this.handleAll(schema, el, schemaEl)); + } else if (el.localName === 'choice') { + group.setParticle(this.handleChoice(schema, el, schemaEl)); + } + } + return group; + } + + private handleNotation(schema: XmlSchema, notationEl: Element) { + const notation = new XmlSchemaNotation(schema); + + if (notationEl.hasAttribute('id')) { + notation.setId(notationEl.getAttribute('id')!); + } + + if (notationEl.hasAttribute('name')) { + notation.setName(notationEl.getAttribute('name')!); + } + + if (notationEl.hasAttribute('public')) { + notation.setPublicNotation(notationEl.getAttribute('public')!); + } + + if (notationEl.hasAttribute('system')) { + notation.setSystem(notationEl.getAttribute('system')!); + } + + const annotationEl = XDOMUtil.getFirstChildElementNS(notationEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotationEl != null) { + const annotation = this.handleAnnotation(annotationEl); + notation.setAnnotation(annotation); + } + + return notation; + } + + /** + * Handle redefine + * + * @param schema + * @param redefineEl + * @param schemaEl + * @return + */ + private handleRedefine(schema: XmlSchema, redefineEl: Element, schemaEl: Element) { + const redefine = new XmlSchemaRedefine(schema); + redefine.schemaLocation = redefineEl.getAttribute('schemaLocation'); + const validator = this.newIncludeValidator(schema); + + redefine.schema = this.resolveXmlSchema( + schema.getLogicalTargetNamespace(), + redefine.schemaLocation, + schema.getSourceURI(), + validator, + ); + + /* + * FIXME - This seems not right. Since the redefine should take into account the attributes of the + * original element we cannot just build the type defined in the redefine section - what we need to do + * is to get the original type object and modify it. However one may argue (quite reasonably) that the + * purpose of this object model is to provide just the representation and not the validation (as it + * has been always the case) + */ + + for ( + let el = XDOMUtil.getFirstChildElementNS(redefineEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'simpleType') { + const type = this.handleSimpleType(schema, el, schemaEl, false); + + redefine.getSchemaTypes().set(type.getQName()!, type); + redefine.getItems().push(type); + } else if (el.localName === 'complexType') { + const type = this.handleComplexType(schema, el, schemaEl, true); + + redefine.getSchemaTypes().set(type.getQName()!, type); + redefine.getItems().push(type); + } else if (el.localName === 'group') { + const group = this.handleGroup(schema, el, schemaEl); + redefine.getGroups().set(group.getQName()!, group); + redefine.getItems().push(group); + } else if (el.localName === 'attributeGroup') { + const group = this.handleAttributeGroup(schema, el, schemaEl); + + redefine.getAttributeGroups().set(group.getQName()!, group); + redefine.getItems().push(group); + } else if (el.localName === 'annotation') { + const annotation = this.handleAnnotation(el); + redefine.setAnnotation(annotation); + } + } + return redefine; + } + + private handleSchemaElementBasics(schemaEl: Element, systemId: string | null = null, schemaKey: SchemaKey): void { + if (!this.collection.containsSchema(schemaKey)) { + this.collection.addSchema(schemaKey, this.currentSchema); + this.currentSchema.setParent(this.collection); // establish parentage now. + } else { + throw new Error( + 'Schema name conflict in collection. Namespace: ' + this.currentSchema.getLogicalTargetNamespace(), + ); + } + + this.currentSchema.setElementFormDefault(this.getFormDefault(schemaEl, 'elementFormDefault')); + this.currentSchema.setAttributeFormDefault(this.getFormDefault(schemaEl, 'attributeFormDefault')); + this.currentSchema.setBlockDefault(this.getDerivation(schemaEl, 'blockDefault')); + this.currentSchema.setFinalDefault(this.getDerivation(schemaEl, 'finalDefault')); + + /* set id and version attributes */ + if (schemaEl.hasAttribute('id')) { + this.currentSchema.setId(schemaEl.getAttribute('id')); + } + if (schemaEl.hasAttribute('version')) { + this.currentSchema.setVersion(schemaEl.getAttribute('version')); + } + + this.currentSchema.setSourceURI(systemId); + } + + private handleSchemaElementChild(schemaEl: Element, el: Element): void { + if (el.localName === 'simpleType') { + const type = this.handleSimpleType(this.currentSchema, el, schemaEl, true); + this.collection.resolveType(type.getQName()!, type); + } else if (el.localName === 'complexType') { + const type = this.handleComplexType(this.currentSchema, el, schemaEl, true); + this.collection.resolveType(type.getQName()!, type); + } else if (el.localName === 'element') { + this.handleElement(this.currentSchema, el, schemaEl, true); + } else if (el.localName === 'include') { + this.handleInclude(this.currentSchema, el, schemaEl); + } else if (el.localName === 'import') { + this.handleImport(this.currentSchema, el, schemaEl); + } else if (el.localName === 'group') { + this.handleGroup(this.currentSchema, el, schemaEl); + } else if (el.localName === 'attributeGroup') { + this.handleAttributeGroup(this.currentSchema, el, schemaEl); + } else if (el.localName === 'attribute') { + this.handleAttribute(this.currentSchema, el, schemaEl, true); + } else if (el.localName === 'redefine') { + this.handleRedefine(this.currentSchema, el, schemaEl); + } else if (el.localName === 'notation') { + this.handleNotation(this.currentSchema, el); + } else if (el.localName === 'annotation') { + const annotation = this.handleAnnotation(el); + this.currentSchema.setAnnotation(annotation); + } + } + + private handleSequence(schema: XmlSchema, sequenceEl: Element, schemaEl: Element) { + const sequence = new XmlSchemaSequence(); + + // handle min and max occurences + sequence.setMinOccurs(this.getMinOccurs(sequenceEl)); + sequence.setMaxOccurs(this.getMaxOccurs(sequenceEl)); + + for ( + let el = XDOMUtil.getFirstChildElementNS(sequenceEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'sequence') { + const seq = this.handleSequence(schema, el, schemaEl); + sequence.getItems().push(seq); + } else if (el.localName === 'element') { + const element = this.handleElement(schema, el, schemaEl, false); + sequence.getItems().push(element); + } else if (el.localName === 'group') { + const group = this.handleGroupRef(schema, el, schemaEl); + sequence.getItems().push(group); + } else if (el.localName === 'choice') { + const choice = this.handleChoice(schema, el, schemaEl); + sequence.getItems().push(choice); + } else if (el.localName === 'any') { + const any = this.handleAny(schema, el, schemaEl); + sequence.getItems().push(any); + } else if (el.localName === 'annotation') { + const annotation = this.handleAnnotation(el); + sequence.setAnnotation(annotation); + } + } + return sequence; + } + + private handleSimpleContent(schema: XmlSchema, simpleEl: Element, schemaEl: Element) { + const simpleContent = new XmlSchemaSimpleContent(); + + for ( + let el = XDOMUtil.getFirstChildElementNS(simpleEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'restriction') { + simpleContent.content = this.handleSimpleContentRestriction(schema, el, schemaEl); + } else if (el.localName === 'extension') { + simpleContent.content = this.handleSimpleContentExtension(schema, el, schemaEl); + } else if (el.localName === 'annotation') { + simpleContent.setAnnotation(this.handleAnnotation(el)); + } + } + return simpleContent; + } + + private handleSimpleContentExtension(schema: XmlSchema, extEl: Element, schemaEl: Element) { + const ext = new XmlSchemaSimpleContentExtension(); + + if (extEl.hasAttribute('base')) { + const name = extEl.getAttribute('base')!; + ext.setBaseTypeName(this.getRefQName(name, extEl)); + } + + for ( + let el = XDOMUtil.getFirstChildElementNS(extEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'attribute') { + const attr = this.handleAttribute(schema, el, schemaEl); + ext.getAttributes().push(attr); + } else if (el.localName === 'attributeGroup') { + const attrGroup = this.handleAttributeGroupRef(schema, el); + ext.getAttributes().push(attrGroup); + } else if (el.localName === 'anyAttribute') { + ext.setAnyAttribute(this.handleAnyAttribute(schema, el, schemaEl)); + } else if (el.localName === 'annotation') { + const ann = this.handleAnnotation(el); + ext.setAnnotation(ann); + } + } + return ext; + } + + private handleSimpleContentRestriction(schema: XmlSchema, restrictionEl: Element, schemaEl: Element) { + const restriction = new XmlSchemaSimpleContentRestriction(); + + if (restrictionEl.hasAttribute('base')) { + const name = restrictionEl.getAttribute('base')!; + restriction.setBaseTypeName(this.getRefQName(name, restrictionEl)); + } + + if (restrictionEl.hasAttribute('id')) { + restriction.setId(restrictionEl.getAttribute('id')); + } + + // check back simpleContent tag children to add attributes and + // simpleType if any occur + for ( + let el = XDOMUtil.getFirstChildElementNS(restrictionEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName === 'attribute') { + const attr = this.handleAttribute(schema, el, schemaEl); + restriction.getAttributes().push(attr); + } else if (el.localName === 'attributeGroup') { + const attrGroup = this.handleAttributeGroupRef(schema, el); + restriction.getAttributes().push(attrGroup); + } else if (el.localName === 'simpleType') { + restriction.setBaseType(this.handleSimpleType(schema, el, schemaEl, false)); + } else if (el.localName === 'anyAttribute') { + restriction.anyAttribute = this.handleAnyAttribute(schema, el, schemaEl); + } else if (el.localName === 'annotation') { + restriction.setAnnotation(this.handleAnnotation(el)); + } else { + const facet = XmlSchemaFacetConstructor.construct(el); + const annotation = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotation != null) { + const facetAnnotation = this.handleAnnotation(annotation); + facet.setAnnotation(facetAnnotation); + } + restriction.getFacets().push(facet); + // process extra attributes and elements + this.processExtensibilityComponents(facet, el, true); + } + } + return restriction; + } + + private handleSimpleTypeFinal(simpleEl: Element, simpleType: XmlSchemaSimpleType) { + if (simpleEl.hasAttribute('final')) { + const finalstr = simpleEl.getAttribute('final')!; + simpleType.setFinal(XmlSchemaDerivationMethod.schemaValueOf(finalstr)); + } + } + + private handleSimpleTypeList(schema: XmlSchema, schemaEl: Element, simpleType: XmlSchemaSimpleType, listEl: Element) { + const list = new XmlSchemaSimpleTypeList(); + + /****** + * if( list has an itemType attribute ) set the baseTypeName and look up the base type else if( list + * has a SimpleTypeElement as child) get that element and do a handleSimpleType set the list has the + * content of the simpleType + */ + const inlineListType = XDOMUtil.getFirstChildElementNS(listEl, XmlSchema.SCHEMA_NS, 'simpleType'); + if (listEl.hasAttribute('itemType')) { + const name = listEl.getAttribute('itemType')!; + list.itemTypeName = this.getRefQName(name, listEl); + } else if (inlineListType != null) { + list.itemType = this.handleSimpleType(schema, inlineListType, schemaEl, false); + } + + const listAnnotationEl = XDOMUtil.getFirstChildElementNS(listEl, XmlSchema.SCHEMA_NS, 'annotation'); + if (listAnnotationEl != null) { + const listAnnotation = this.handleAnnotation(listAnnotationEl); + list.setAnnotation(listAnnotation); + } + simpleType.content = list; + } + + private handleSimpleTypeRestriction( + schema: XmlSchema, + schemaEl: Element, + simpleType: XmlSchemaSimpleType, + restrictionEl: Element, + ) { + const restriction = new XmlSchemaSimpleTypeRestriction(); + + const restAnnotationEl = XDOMUtil.getFirstChildElementNS(restrictionEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (restAnnotationEl != null) { + const restAnnotation = this.handleAnnotation(restAnnotationEl); + restriction.setAnnotation(restAnnotation); + } + /** + * if (restriction has a base attribute ) set the baseTypeName and look up the base type else if( + * restriction has a SimpleType Element as child) get that element and do a handleSimpleType; get the + * children of restriction other than annotation and simpleTypes and construct facets from it; set the + * restriction has the content of the simpleType + **/ + + const inlineSimpleType = XDOMUtil.getFirstChildElementNS(restrictionEl, XmlSchema.SCHEMA_NS, 'simpleType'); + + if (restrictionEl.hasAttribute('base')) { + const ctx = NodeNamespaceContext.getNamespaceContext(restrictionEl); + restriction.setBaseTypeName(this.getRefQName(restrictionEl.getAttribute('base')!, undefined, ctx)); + } else if (inlineSimpleType != null) { + restriction.setBaseType(this.handleSimpleType(schema, inlineSimpleType, schemaEl, false)); + } + for ( + let el = XDOMUtil.getFirstChildElementNS(restrictionEl, XmlSchema.SCHEMA_NS); + el != null; + el = XDOMUtil.getNextSiblingElementNS(el, XmlSchema.SCHEMA_NS) + ) { + if (el.localName !== 'annotation' && el.localName !== 'simpleType') { + const facet = XmlSchemaFacetConstructor.construct(el); + const annotation = XDOMUtil.getFirstChildElementNS(el, XmlSchema.SCHEMA_NS, 'annotation'); + + if (annotation != null) { + const facetAnnotation = this.handleAnnotation(annotation); + facet.setAnnotation(facetAnnotation); + } + // process extra attributes and elements + this.processExtensibilityComponents(facet, el, true); + restriction.getFacets().push(facet); + } + } + simpleType.content = restriction; + } + + private handleSimpleTypeUnion( + schema: XmlSchema, + schemaEl: Element, + simpleType: XmlSchemaSimpleType, + unionEl: Element, + ) { + const union = new XmlSchemaSimpleTypeUnion(); + + /****** + * if( union has a memberTypes attribute ) add the memberTypeSources string for (each memberType in + * the list ) lookup(memberType) for( all SimpleType child Elements) add the simpleTypeName (if any) + * to the memberType Sources do a handleSimpleType with the simpleTypeElement + */ + if (unionEl.hasAttribute('memberTypes')) { + const memberTypes = unionEl.getAttribute('memberTypes')!; + union.setMemberTypesSource(memberTypes); + const v: QName[] = []; + for (const member of memberTypes.split(' ')) { + v.push(this.getRefQName(member, unionEl)); + } + union.setMemberTypesQNames(v); + } + + let inlineUnionType = XDOMUtil.getFirstChildElementNS(unionEl, XmlSchema.SCHEMA_NS, 'simpleType'); + while (inlineUnionType != null) { + const unionSimpleType = this.handleSimpleType(schema, inlineUnionType, schemaEl, false); + + union.getBaseTypes().push(unionSimpleType); + + if (!unionSimpleType.isAnonymous()) { + union.setMemberTypesSource(union.getMemberTypesSource() + ' ' + unionSimpleType.getName()); + } + + inlineUnionType = XDOMUtil.getNextSiblingElementNS(inlineUnionType, XmlSchema.SCHEMA_NS, 'simpleType'); + } + + // NodeList annotations = unionEl.getElementsByTagNameNS( + // XmlSchema.SCHEMA_NS, "annotation"); + const unionAnnotationEl = XDOMUtil.getFirstChildElementNS(unionEl, XmlSchema.SCHEMA_NS, 'annotation'); + + if (unionAnnotationEl != null) { + const unionAnnotation = this.handleAnnotation(unionAnnotationEl); + + union.setAnnotation(unionAnnotation); + } + simpleType.content = union; + } + + private newIncludeValidator(schema: XmlSchema) { + return (pSchema: XmlSchema) => { + const isEmpty = (pValue?: string | null) => { + return pValue == null || Constants.NULL_NS_URI === pValue; + }; + if (isEmpty(pSchema.getSyntacticalTargetNamespace())) { + pSchema.setLogicalTargetNamespace(schema.getLogicalTargetNamespace()); + } else { + if (pSchema.getSyntacticalTargetNamespace() !== schema.getLogicalTargetNamespace()) { + let msg = 'An included schema was announced to have the default target namespace'; + if (!isEmpty(schema.getLogicalTargetNamespace())) { + msg += ' or the target namespace ' + schema.getLogicalTargetNamespace(); + } + throw new Error(msg + ', but has the target namespace ' + pSchema.getLogicalTargetNamespace()); + } + } + }; + } + + private processExtensibilityComponents( + schemaObject: XmlSchemaObject, + parentElement: Element, + namespaces: boolean, + ): void { + if (this.extReg != null) { + // process attributes + const attributes = parentElement.attributes; + for (let i = 0; i < attributes.length; i++) { + const attribute = attributes.item(i) as Attr; + + const namespaceURI = attribute.namespaceURI; + const name = attribute.localName; + + if ( + namespaceURI != null && + '' !== namespaceURI && // ignore unqualified attributes + // ignore namespaces + (namespaces || !namespaceURI.startsWith(Constants.XMLNS_ATTRIBUTE_NS_URI)) && + // does not belong to the schema namespace by any chance! + Constants.URI_2001_SCHEMA_XSD !== namespaceURI + ) { + const qName = new QName(namespaceURI, name); + this.extReg.deserializeExtension(schemaObject, qName, attribute); + } + } + + // process elements + let child = parentElement.firstChild; + while (child != null) { + if (child.nodeType == Node.ELEMENT_NODE) { + const extElement = child as Element; + const namespaceURI = extElement.namespaceURI; + const name = extElement.localName; + + if (namespaceURI != null && Constants.URI_2001_SCHEMA_XSD !== namespaceURI) { + // does not belong to the schema namespace + const qName = new QName(namespaceURI, name); + this.extReg.deserializeExtension(schemaObject, qName, extElement); + } + } + child = child.nextSibling; + } + } + } + + /** + * Add an XmlSchema to the cache if the current thread has the cache enabled. The first three parameters + * are used to construct a key + * + * @param targetNamespace + * @param schemaLocation + * @param baseUri This parameter is the value put under the key (if the cache is enabled) + * @param readSchema + */ + private putCachedSchema(targetNamespace: string, schemaLocation: string, baseUri: string, readSchema: XmlSchema) { + if (this.resolvedSchemas != null) { + const schemaKey = targetNamespace + schemaLocation + baseUri; + this.resolvedSchemas.set(schemaKey, readSchema); + } + } +} diff --git a/packages/xml-schema-ts/src/SchemaKey.ts b/packages/xml-schema-ts/src/SchemaKey.ts new file mode 100644 index 000000000..ed71570bd --- /dev/null +++ b/packages/xml-schema-ts/src/SchemaKey.ts @@ -0,0 +1,21 @@ +export class SchemaKey { + private namespace: string; + private systemId: string; + + constructor(namespace?: string | null, systemId?: string | null) { + this.namespace = namespace != null ? namespace : ''; + this.systemId = systemId != null ? systemId : ''; + } + + toString() { + return this.namespace === '' ? this.systemId : `{${this.namespace}}${this.systemId}`; + } + + getNamespace() { + return this.namespace; + } + + getSystemId() { + return this.systemId; + } +} diff --git a/packages/xml-schema-ts/src/TypeReceiver.ts b/packages/xml-schema-ts/src/TypeReceiver.ts new file mode 100644 index 000000000..10d9bbec0 --- /dev/null +++ b/packages/xml-schema-ts/src/TypeReceiver.ts @@ -0,0 +1,5 @@ +import { XmlSchemaType } from './XmlSchemaType'; + +export interface TypeReceiver { + setType(type: XmlSchemaType): void; +} diff --git a/packages/xml-schema-ts/src/XmlSchema.ts b/packages/xml-schema-ts/src/XmlSchema.ts new file mode 100644 index 000000000..d7cb02b12 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchema.ts @@ -0,0 +1,801 @@ +import type { XmlSchemaAttribute } from './attribute/XmlSchemaAttribute'; +import type { XmlSchemaAttributeGroup } from './attribute/XmlSchemaAttributeGroup'; +import type { XmlSchemaCollection } from './XmlSchemaCollection'; +import type { XmlSchemaElement } from './particle/XmlSchemaElement'; +import type { XmlSchemaExternal } from './external/XmlSchemaExternal'; +import type { XmlSchemaGroup } from './XmlSchemaGroup'; +import type { XmlSchemaNotation } from './XmlSchemaNotation'; +import type { XmlSchemaObject } from './XmlSchemaObject'; +import type { XmlSchemaType } from './XmlSchemaType'; +import type { NamespaceContextOwner } from './utils/NamespaceContextOwner'; +import type { NamespacePrefixList } from './utils/NamespacePrefixList'; + +import { QName } from './QName'; +import { SchemaKey } from './SchemaKey'; +import { URI_2001_SCHEMA_XSD } from './constants'; +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; +import { XmlSchemaDerivationMethod } from './XmlSchemaDerivationMethod'; +import { XmlSchemaForm } from './XmlSchemaForm'; +import { XmlSchemaImport } from './external/XmlSchemaImport'; +import { XmlSchemaInclude } from './external/XmlSchemaInclude'; +import { QNameMap } from './utils/ObjectMap'; + +export class XmlSchema extends XmlSchemaAnnotated implements NamespaceContextOwner { + static readonly SCHEMA_NS = URI_2001_SCHEMA_XSD; + static readonly UTF_8_ENCODING = 'UTF-8'; + + private items: XmlSchemaObject[] = []; + private parent: XmlSchemaCollection | null = null; + private blockDefault = XmlSchemaDerivationMethod.NONE; + private finalDefault = XmlSchemaDerivationMethod.NONE; + private elementFormDefault = XmlSchemaForm.UNQUALIFIED; + private attributeFormDefault = XmlSchemaForm.UNQUALIFIED; + private externals: XmlSchemaExternal[] = []; + private attributeGroups = new QNameMap(); + private attributes = new QNameMap(); + private elements = new QNameMap(); + private groups = new QNameMap(); + private notations = new QNameMap(); + private schemaTypes = new QNameMap(); + private syntacticalTargetNamespace: string | null = null; + private schemaNamespacePrefix: string | null = null; + private logicalTargetNamespace: string | null = null; + private version: string | null = null; + private namespaceContext: NamespacePrefixList | null = null; + private inputEncoding: string | null = null; + + constructor(namespace?: string, systemId?: string, parent?: XmlSchemaCollection) { + super(); + if (namespace == null) return; + const systemIdToUse = systemId ? systemId : namespace; + this.parent = parent || null; + this.logicalTargetNamespace = namespace; + this.syntacticalTargetNamespace = namespace; + const schemaKey = new SchemaKey(this.logicalTargetNamespace, systemIdToUse); + if (this.parent?.containsSchema(schemaKey)) { + throw new Error(`Schema name '${schemaKey.toString()}' conflicts in collection`); + } + this.parent?.addSchema(schemaKey, this); + } + + /** + * Return an array of DOM documents consisting of this schema and any schemas that it references. + * Referenced schemas are only returned if the {@link XmlSchemaExternal} objects corresponding to them + * have their 'schema' fields filled in. + * + * @return DOM documents. + getAllSchemas() { + const xser = new XmlSchemaSerializer(); + xser.setExtReg(this.parent.getExtReg()); + return xser.serializeSchema(this, true); + + } catch (XmlSchemaSerializer.XmlSchemaSerializerException e) { + throw new XmlSchemaException("Error serializing schema", e); + } + } + */ + + /** + * + * @param name + * @param deep + * @param schemaStack + * @protected + */ + getAttributeByQName(name: QName, deep: boolean = true, schemaStack?: XmlSchema[]): XmlSchemaAttribute | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + let attribute = this.attributes.get(name) as XmlSchemaAttribute | null; + if (deep) { + if (attribute == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + attribute = schema.getAttributeByQName(name, deep, schemaStack); + if (attribute != null) { + return attribute; + } + } + } + } else { + return attribute; + } + } + return attribute; + } + + /** + * Look for an attribute by its local name. + * + * @param name + * @return the attribute + */ + getAttributeByName(name: string) { + const nameToSearchFor = new QName(this.getTargetNamespace(), name); + return this.getAttributeByQName(nameToSearchFor, false); + } + + /** + * @return the default attribute form for this schema. + */ + getAttributeFormDefault() { + return this.attributeFormDefault; + } + + /** + * Retrieve an attribute group by QName. + * + * @param name + * @param deep + * @param schemaStack + * @return + */ + getAttributeGroupByQName( + name: QName, + deep: boolean = true, + schemaStack?: XmlSchema[], + ): XmlSchemaAttributeGroup | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + + let group = this.attributeGroups.get(name) || null; + if (deep) { + if (group == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + // create an empty stack - push the current parent in + // and + // use the protected method to process the schema + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + group = schema.getAttributeGroupByQName(name, deep, schemaStack); + if (group != null) { + return group; + } + } + } + } else { + return group; + } + } + return group; + } + + /** + * Return a map containing all the defined attribute groups of this schema. The keys are QNames, where the + * namespace will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references. + *
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime. + * + * @return the map of attribute groups. + */ + getAttributeGroups() { + return this.attributeGroups; + } + + /** + * Return a map containing all the defined attributes of this schema. The keys are QNames, where the + * namespace will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references. + *
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime. + * + * @return the map of attributes. + */ + getAttributes() { + return this.attributes; + } + + /** + * Return the default block value for this schema. + * + * @return the default block value. + */ + getBlockDefault() { + return this.blockDefault; + } + + /** + * Look for a element by its QName. + * + * @param name + * @param deep + * @param schemaStack + * @return the element. + */ + getElementByQName(name: QName, deep: boolean = true, schemaStack?: XmlSchema[]): XmlSchemaElement | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + + let element = this.elements.get(name) || null; + if (deep) { + if (element == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + // create an empty stack - push the current parent in + // and + // use the protected method to process the schema + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + element = schema.getElementByQName(name, deep, schemaStack); + if (element != null) { + return element; + } + } + } + } else { + return element; + } + } + return element; + } + + /** + * get an element by its local name. + * + * @param name + * @return the element. + */ + getElementByName(name: string) { + const nameToSearchFor = new QName(this.getTargetNamespace(), name); + return this.getElementByQName(nameToSearchFor, false); + } + + /** + * @return the default element form for this schema. + */ + getElementFormDefault() { + return this.elementFormDefault; + } + + /** + * Return a map containing all the defined elements of this schema. The keys are QNames, where the + * namespace will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references. + *
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime + * + * @return the map of elements. + */ + getElements(): QNameMap { + return this.elements; + } + + /** + * Return all of the includes, imports, and redefines for this schema. + *
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a list that checks at runtime + * + * @return a list of the objects representing includes, imports, and redefines. + */ + getExternals() { + return this.externals; + } + + /** + * @return the default 'final' value for this schema. + */ + getFinalDefault() { + return this.finalDefault; + } + + /** + * Retrieve a group by QName. + * + * @param name + * @param deep + * @param schemaStack + * @return + */ + getGroupByQName(name: QName, deep: boolean = true, schemaStack?: XmlSchema[]): XmlSchemaGroup | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + let group = this.groups.get(name) || null; + if (deep) { + if (group == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + // create an empty stack - push the current parent in + // and + // use the protected method to process the schema + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + group = schema.getGroupByQName(name, deep, schemaStack); + if (group != null) { + return group; + } + } + } + } else { + return group; + } + } + return group; + } + + /** + * Return a map containing all the defined groups of this schema. The keys are QNames, where the namespace + * will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references.
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime + * + * @return the map of groups. + */ + getGroups() { + return this.groups; + } + + /** + * Return the character encoding for this schema. This will only be present if either the schema was read + * from an XML document or there was a call to {@link #setInputEncoding(String)}. + * + * @return + */ + getInputEncoding() { + return this.inputEncoding; + } + + /** + * Return all of the global items in this schema.
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime. + * @return all of the global items from this schema. + * + */ + getItems() { + return this.items; + } + + /** + * Return the logical target namespace. If a schema document has no target namespace, but it is referenced + * via an xs:include or xs:redefine, its logical target namespace is the target namespace of the including + * schema. + * + * @return the logical target namespace. + */ + getLogicalTargetNamespace() { + return this.logicalTargetNamespace; + } + + getNamespaceContext(): NamespacePrefixList | null { + return this.namespaceContext; + } + + /** + * Retrieve a notation by QName. + * + * @param name + * @param deep + * @param schemaStack + * @return the notation + */ + getNotationByQName(name: QName, deep: boolean = true, schemaStack?: XmlSchema[]): XmlSchemaNotation | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + let notation = this.notations.get(name) || null; + if (deep) { + if (notation == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + // create an empty stack - push the current parent in + // and + // use the protected method to process the schema + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + notation = schema.getNotationByQName(name, deep, schemaStack); + if (notation != null) { + return notation; + } + } + } + } else { + return notation; + } + } + return notation; + } + + /** + * Return a map containing all the defined notations of this schema. The keys are QNames, where the + * namespace will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references. + *
    + * If org.apache.ws.commons.schema.protectReadOnlyCollections + * is 'true', this will return a map that checks at runtime. + * + * @return the map of notations. + */ + getNotations() { + return this.notations; + } + + /** + * Return the parent XmlSchemaCollection. If this schema was not initialized in a collection the return + * value will be null. + * + * @return the parent collection. + */ + getParent() { + return this.parent; + } + + /** + * Retrieve a DOM tree for this one schema, independent of any included or related schemas. + * + * @return The DOM document. + * @throws XmlSchemaSerializerException + getSchemaDocument() { + const xser = new XmlSchemaSerializer(); + xser.setExtReg(this.parent.getExtReg()); + return xser.serializeSchema(this, false)[0]; + } + */ + + /** + * @return the namespace prefix for the target namespace. + */ + getSchemaNamespacePrefix() { + return this.schemaNamespacePrefix; + } + + /** + * Return a map containing all the defined types of this schema. The keys are QNames, where the namespace + * will always be the target namespace of this schema. This makes it easier to look up items for + * cross-schema references. + * + * @return the map of types. + */ + getSchemaTypes() { + return this.schemaTypes; + } + + /** + * Return the declared target namespace of this schema. + * + * @see #getLogicalTargetNamespace() + * @return the namespace URI. + */ + getTargetNamespace() { + return this.syntacticalTargetNamespace; + } + + /** + * Search this schema, and its peers in its parent collection, for a schema type specified by QName. + * + * @param name the type name. + * @param deep + * @param schemaStack + * @return the type. + */ + getTypeByQName(name: QName, deep: boolean = true, schemaStack?: XmlSchema[]): XmlSchemaType | null { + if (schemaStack != null && schemaStack.includes(this)) { + // recursive schema - just return null + return null; + } + let type = this.schemaTypes.get(name) || null; + + if (deep) { + if (type == null) { + // search the imports + for (const item of this.externals) { + const schema = this.getSchema(item); + + if (schema != null) { + // create an empty stack - push the current parent + // use the protected method to process the schema + if (schemaStack == null) { + schemaStack = []; + } + schemaStack.push(this); + type = schema.getTypeByQName(name, deep, schemaStack); + if (type != null) { + return type; + } + } + } + } else { + return type; + } + } + return type; + } + + /** + * Retrieve a named type from this schema. + * + * @param name + * @return the type. + */ + getTypeByName(name: string) { + const nameToSearchFor = new QName(this.getTargetNamespace(), name); + return this.getTypeByQName(nameToSearchFor, false); + } + + /** + * Return the declared XML Schema version of this schema. XmlSchema supports only version 1.0. + * + * @return + */ + getVersion() { + return this.version; + } + + /** + * Set the declared XML Schema version of this schema + * + * @param version the new version. + */ + setVersion(version: string | null) { + this.version = version; + } + + /** + * Set the default attribute form for this schema. + * + * @param attributeFormDefault + */ + setAttributeFormDefault(attributeFormDefault: XmlSchemaForm) { + this.attributeFormDefault = attributeFormDefault; + } + + /** + * Set the default block value for this schema. + * + * @param blockDefault the new block value. + */ + setBlockDefault(blockDefault: XmlSchemaDerivationMethod) { + this.blockDefault = blockDefault; + } + + /** + * Set the default element form for this schema. + * + * @param elementFormDefault the element form. This may not be null. + */ + setElementFormDefault(elementFormDefault: XmlSchemaForm) { + this.elementFormDefault = elementFormDefault; + } + + /** + * Set the default 'final' value for this schema. The value may not be null. + * + * @param finalDefault the new final value. + */ + setFinalDefault(finalDefault: XmlSchemaDerivationMethod) { + this.finalDefault = finalDefault; + } + + /** + * Set the character encoding name for the schema. This is typically set when reading a schema from an XML + * file, so that it can be written back out in the same encoding. + * + * @param encoding Character encoding name. + */ + setInputEncoding(encoding: string) { + this.inputEncoding = encoding; + } + + /** + * Sets the schema elements namespace context. This may be used for schema serialization, until a better + * mechanism was found. + */ + setNamespaceContext(namespaceContext: NamespacePrefixList): void { + this.namespaceContext = namespaceContext; + } + + /** + * Set the namespace prefix corresponding to the target namespace. + * + * @param schemaNamespacePrefix + */ + setSchemaNamespacePrefix(schemaNamespacePrefix: string) { + this.schemaNamespacePrefix = schemaNamespacePrefix; + } + + /** + * Set the target namespace for this schema. + * + * @param targetNamespace the new target namespace URI. A value of "" is ignored. + */ + setTargetNamespace(targetNamespace: string) { + if ('' !== targetNamespace) { + this.logicalTargetNamespace = targetNamespace; + this.syntacticalTargetNamespace = targetNamespace; + } + } + + /** + * Serialize the schema as XML to the specified stream using the encoding established with + * {@link #setInputEncoding(String)}. + * + * @param out - the output stream to write to + * @throws UnsupportedEncodingException for an invalid encoding. + public void write(OutputStream out) throws UnsupportedEncodingException { + if (this.inputEncoding != null && !"".equals(this.inputEncoding)) { + write(new OutputStreamWriter(out, this.inputEncoding)); +} else { + // As per the XML spec the default is taken to be UTF 8 + write(new OutputStreamWriter(out, UTF_8_ENCODING)); +} + +} + */ + + /** + * Serialize the schema as XML to the specified stream using the encoding established with + * {@link #setInputEncoding(String)}. + * + * @param out - the output stream to write to + * @param options - a map of options + * @throws UnsupportedEncodingException + * + * +public void write(OutputStream out, Map options) throws UnsupportedEncodingException { + if (this.inputEncoding != null && !"".equals(this.inputEncoding)) { + write(new OutputStreamWriter(out, this.inputEncoding), options); + } else { + write(new OutputStreamWriter(out, UTF_8_ENCODING), options); + } +} +*/ + + /** + * Serialize the schema to a {@link java.io.Writer}. + * + * @param writer - the writer to write this +public void write(Writer writer) { + serializeInternal(writer, null); +} + */ + + /** + * Serialize the schema to a {@link java.io.Writer}. + * + * @param writer - the writer to write this +public void write(Writer writer, Map options) { + serializeInternal(writer, options); +} + */ + + getSyntacticalTargetNamespace() { + return this.syntacticalTargetNamespace; + } + + setLogicalTargetNamespace(logicalTargetNamespace: string | null) { + this.logicalTargetNamespace = logicalTargetNamespace; + } + + setParent(parent: XmlSchemaCollection) { + this.parent = parent; + } + + setSyntacticalTargetNamespace(syntacticalTargetNamespace: string) { + this.syntacticalTargetNamespace = syntacticalTargetNamespace; + } + + /** + * Get a schema from an import + * + * @param includeOrImport + * @return return the schema object. + */ + private getSchema(includeOrImport: object): XmlSchema | null { + let schema: XmlSchema | null = null; + if (includeOrImport instanceof XmlSchemaImport) { + schema = (includeOrImport as XmlSchemaImport).getSchema(); + } else if (includeOrImport instanceof XmlSchemaInclude) { + schema = (includeOrImport as XmlSchemaInclude).getSchema(); + } + return schema; + } + + /** + * Load the default options + * + * @param options - the map of + private loadDefaultOptions(options: Map ) { + options.put(OutputKeys.OMIT_XML_DECLARATION, "yes"); + options.put(OutputKeys.INDENT, "yes"); + } + */ + + /** + * serialize the schema - this is the method tht does to work + * + * @param out + * @param options +private void serializeInternal(Writer out, Map options) { + + try { + XmlSchemaSerializer xser = new XmlSchemaSerializer(); + xser.setExtReg(this.parent.getExtReg()); + Document[] serializedSchemas = xser.serializeSchema(this, false); + TransformerFactory trFac = TransformerFactory.newInstance(); + trFac.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + + try { + trFac.setAttribute("indent-number", "4"); + } catch (IllegalArgumentException e) { + // do nothing - we'll just silently let this pass if it + // was not compatible + } + + Source source = new DOMSource(serializedSchemas[0]); + Result result = new StreamResult(out); + javax.xml.transform.Transformer tr = trFac.newTransformer(); + + // use the input encoding if there is one + if (this.inputEncoding != null && !"".equals(this.inputEncoding)) { + tr.setOutputProperty(OutputKeys.ENCODING, this.inputEncoding); + } + + // let these be configured from outside if any is present + // Note that one can enforce the encoding by passing the necessary + // property in options + + if (options == null) { + options = new HashMap(); + loadDefaultOptions(options); + } + Iterator keys = options.keySet().iterator(); + while (keys.hasNext()) { + Object key = keys.next(); + tr.setOutputProperty((String)key, options.get(key)); + } + + tr.transform(source, result); + out.flush(); + } catch (TransformerConfigurationException e) { + throw new XmlSchemaException(e.getMessage()); + } catch (TransformerException e) { + throw new XmlSchemaException(e.getMessage()); + } catch (XmlSchemaSerializer.XmlSchemaSerializerException e) { + throw new XmlSchemaException(e.getMessage()); + } catch (IOException e) { + throw new XmlSchemaException(e.getMessage()); + } +} + */ +} diff --git a/packages/xml-schema-ts/src/XmlSchemaAnnotated.ts b/packages/xml-schema-ts/src/XmlSchemaAnnotated.ts new file mode 100644 index 000000000..179b0b32b --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaAnnotated.ts @@ -0,0 +1,30 @@ +import type { XmlSchemaAnnotation } from './annotation/XmlSchemaAnnotation'; +import { XmlSchemaObject } from './XmlSchemaObject'; + +export class XmlSchemaAnnotated extends XmlSchemaObject { + private annotation?: XmlSchemaAnnotation; + private id?: string | null; + private unhandledAttributes?: Attr[]; + + getId() { + return this.id; + } + setId(id: string | null) { + this.id = id; + } + getAnnotation() { + return this.annotation; + } + setAnnotation(annotation: XmlSchemaAnnotation) { + this.annotation = annotation; + } + getUnhandledAttributes() { + return this.unhandledAttributes; + } + setUnhandledAttributes(unhandledAttributes: Attr[]) { + this.unhandledAttributes = unhandledAttributes; + } + toString() { + return this.id == null ? super.toString() : super.toString() + `[id:${this.id}]`; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaAnyAttribute.ts b/packages/xml-schema-ts/src/XmlSchemaAnyAttribute.ts new file mode 100644 index 000000000..067cf8752 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaAnyAttribute.ts @@ -0,0 +1,27 @@ +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; +import { XmlSchemaContentProcessing } from './XmlSchemaContentProcessing'; + +/** + * Enables any attribute from the specified namespace or namespaces to appear in the containing complexType + * element. Represents the World Wide Web Consortium (W3C) anyAttribute element. + */ +export class XmlSchemaAnyAttribute extends XmlSchemaAnnotated { + namespace: string | null = null; + processContent: XmlSchemaContentProcessing = XmlSchemaContentProcessing.NONE; + + getNamespace() { + return this.namespace; + } + + setNamespace(namespace: string) { + this.namespace = namespace; + } + + getProcessContent() { + return this.processContent; + } + + setProcessContent(processContent: XmlSchemaContentProcessing) { + this.processContent = processContent; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaCollection.test.ts b/packages/xml-schema-ts/src/XmlSchemaCollection.test.ts new file mode 100644 index 000000000..cf66d6044 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaCollection.test.ts @@ -0,0 +1,144 @@ +import { XmlSchemaCollection } from './XmlSchemaCollection'; +import fs from 'fs'; +import { QName } from './QName'; +import { XmlSchemaComplexType } from './complex/XmlSchemaComplexType'; +import { XmlSchemaAttribute } from './attribute/XmlSchemaAttribute'; +import { XmlSchemaUse } from './XmlSchemaUse'; +import { XmlSchemaSequence } from './particle/XmlSchemaSequence'; +import { XmlSchemaElement } from './particle/XmlSchemaElement'; +import { XmlSchemaAttributeGroupRef } from './attribute/XmlSchemaAttributeGroupRef'; +import { XmlSchemaSimpleType } from './simple/XmlSchemaSimpleType'; +import { screen } from '@testing-library/react'; +import { XmlSchemaComplexContentExtension } from './complex/XmlSchemaComplexContentExtension'; + +describe('XmlSchemaCollection', () => { + const orderXsd = fs.readFileSync(__dirname + '/../test-resources/ShipOrder.xsd').toString(); + const testXsd = fs.readFileSync(__dirname + '/../test-resources/TestDocument.xsd').toString(); + const namedTypesXsd = fs.readFileSync(__dirname + '/../test-resources/NamedTypes.xsd').toString(); + const camelSpringXsd = fs.readFileSync(__dirname + '/../test-resources/camel-spring.xsd').toString(); + const orderXsdEmptyFirstLine = fs + .readFileSync(__dirname + '/../test-resources/ShipOrderEmptyFirstLine.xsd') + .toString(); + + it('should parse ShipOrder XML schema', () => { + const collection = new XmlSchemaCollection(); + const xmlSchema = collection.read(orderXsd, () => {}); + const attributes = xmlSchema.getAttributes(); + expect(attributes.size).toEqual(0); + + const elements = xmlSchema.getElements(); + expect(elements.size).toEqual(1); + const shipOrderElement = elements.get(new QName('io.kaoto.datamapper.poc.test', 'ShipOrder')); + expect(shipOrderElement!.getName()).toEqual('ShipOrder'); + expect(shipOrderElement!.getWireName()?.getNamespaceURI()).toEqual('io.kaoto.datamapper.poc.test'); + expect(shipOrderElement!.getWireName()?.getLocalPart()).toEqual('ShipOrder'); + expect(shipOrderElement!.getMinOccurs()).toEqual(1); + expect(shipOrderElement!.getMaxOccurs()).toEqual(1); + const shipOrderComplexType = shipOrderElement!.getSchemaType() as XmlSchemaComplexType; + const shipOrderAttributes = shipOrderComplexType.getAttributes(); + expect(shipOrderAttributes.length).toBe(1); + const orderIdAttr = shipOrderAttributes[0] as XmlSchemaAttribute; + expect(orderIdAttr.getName()).toEqual('OrderId'); + expect(orderIdAttr.getWireName()?.getNamespaceURI()).toBe(''); + expect(orderIdAttr.getWireName()?.getLocalPart()).toBe('OrderId'); + expect(orderIdAttr.getSchemaType()).toBeNull(); + expect(orderIdAttr.getSchemaTypeName()?.getLocalPart()).toEqual('string'); + expect(orderIdAttr.getUse()).toEqual(XmlSchemaUse.REQUIRED); + expect(orderIdAttr.getFixedValue()).toEqual('2'); + + const shipOrderSequence = shipOrderComplexType.getParticle() as XmlSchemaSequence; + const shipOrderSequenceMembers = shipOrderSequence.getItems(); + expect(shipOrderSequenceMembers.length).toBe(3); + + const orderPerson = shipOrderSequenceMembers[0] as XmlSchemaElement; + expect(orderPerson.getSchemaType() instanceof XmlSchemaSimpleType).toBeTruthy(); + expect(orderPerson.getSchemaTypeName()?.getLocalPart()).toEqual('string'); + expect(orderPerson.getName()).toEqual('OrderPerson'); + expect(orderPerson.getWireName()?.getNamespaceURI()).toEqual('io.kaoto.datamapper.poc.test'); + expect(orderPerson.getWireName()?.getLocalPart()).toEqual('OrderPerson'); + expect(orderPerson.getMinOccurs()).toEqual(1); + expect(orderPerson.getMaxOccurs()).toEqual(1); + + const shipTo = shipOrderSequenceMembers[1] as XmlSchemaElement; + const shipToSchemaType = shipTo.getSchemaType() as XmlSchemaComplexType; + const shipToSequence = shipToSchemaType.getParticle() as XmlSchemaSequence; + const shipToSequenceMenbers = shipToSequence.getItems(); + expect(shipToSequenceMenbers.length).toBe(4); + expect(shipTo.getSchemaTypeName()).toBeNull(); + expect(shipTo.getName()).toEqual('ShipTo'); + expect(shipTo.getWireName()?.getNamespaceURI()).toEqual(''); + expect(shipTo.getWireName()?.getLocalPart()).toEqual('ShipTo'); + expect(shipTo.getMinOccurs()).toEqual(1); + expect(shipTo.getMaxOccurs()).toEqual(1); + + const item = shipOrderSequenceMembers[2] as XmlSchemaElement; + expect(item.getMaxOccurs()).toBe(Number.MAX_SAFE_INTEGER); + const itemSchemaType = item.getSchemaType() as XmlSchemaComplexType; + const itemSequence = itemSchemaType.getParticle() as XmlSchemaSequence; + const itemSequenceMembers = itemSequence.getItems(); + expect(itemSequenceMembers.length).toEqual(4); + const itemNote = itemSequenceMembers[1] as XmlSchemaElement; + expect(itemNote.getMinOccurs()).toEqual(0); + const itemQuantity = itemSequenceMembers[2] as XmlSchemaElement; + expect(itemQuantity.getSchemaTypeName()?.getLocalPart()).toEqual('positiveInteger'); + const itemPrice = itemSequenceMembers[3] as XmlSchemaElement; + expect(itemPrice.getSchemaTypeName()?.getLocalPart()).toEqual('decimal'); + }); + + it('should parse TestDocument XML schema', () => { + const collection = new XmlSchemaCollection(); + const xmlSchema = collection.read(testXsd, () => {}); + const attributes = xmlSchema.getAttributes(); + expect(attributes.size).toEqual(0); + const elements = xmlSchema.getElements(); + expect(elements.size).toEqual(1); + const testDocumentElement = elements.get(new QName('io.kaoto.datamapper.poc.test', 'TestDocument')); + const testDocumentComplexType = testDocumentElement!.getSchemaType() as XmlSchemaComplexType; + const testDocumentAttributes = testDocumentComplexType.getAttributes(); + expect(testDocumentAttributes.length).toBe(1); + const attrGroupRef = (testDocumentAttributes[0] as XmlSchemaAttributeGroupRef).getRef(); + expect(attrGroupRef.getTarget()).toBeTruthy(); + }); + + it('should parse NamedTypes XML schema', () => { + const collection = new XmlSchemaCollection(); + const xmlSchema = collection.read(namedTypesXsd, () => {}); + const elements = xmlSchema.getElements(); + expect(elements.size).toEqual(1); + const element1 = elements.get(new QName('io.kaoto.datamapper.poc.test', 'Element1')); + const element1ComplexType = element1!.getSchemaType() as XmlSchemaComplexType; + const element1Sequence = element1ComplexType.getParticle() as XmlSchemaSequence; + const element1SequenceMembers = element1Sequence.getItems(); + expect(element1SequenceMembers.length).toEqual(1); + const element1Simple1 = element1SequenceMembers[0] as XmlSchemaElement; + const element1Simple1SchemaType = element1Simple1.getSchemaType(); + expect(element1Simple1.getWireName()?.getNamespaceURI()).toEqual(''); + expect(element1Simple1.getWireName()?.getLocalPart()).toEqual('Element1Simple1'); + }); + + it('should parse camel-spring XML schema', () => { + const collection = new XmlSchemaCollection(); + const xmlSchema = collection.read(camelSpringXsd, () => {}); + const aggregate = xmlSchema.getElements().get(new QName('http://camel.apache.org/schema/spring', 'aggregate')); + const aggregateComplexType = aggregate!.getSchemaType() as XmlSchemaComplexType; + expect(aggregateComplexType.getParticle()).toBeNull(); + const aggregateComplexContent = aggregateComplexType + .getContentModel() + ?.getContent() as XmlSchemaComplexContentExtension; + expect(aggregateComplexContent.getBaseTypeName()?.getLocalPart()).toEqual('output'); + expect(aggregateComplexContent.getAttributes().length).toEqual(22); + const particle = aggregateComplexContent.getParticle() as XmlSchemaSequence; + expect(particle.getItems().length).toEqual(6); + }); + + it('should parse ShipOrderEmptyFirstLine.xsd', () => { + const collection = new XmlSchemaCollection(); + try { + collection.read(orderXsdEmptyFirstLine, () => {}); + } catch (error) { + expect(error.message).toContain('an XML declaration must be at the start of the document'); + return; + } + fail('No error was thrown'); + }); +}); diff --git a/packages/xml-schema-ts/src/XmlSchemaCollection.ts b/packages/xml-schema-ts/src/XmlSchemaCollection.ts new file mode 100644 index 000000000..eb1b8a64e --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaCollection.ts @@ -0,0 +1,602 @@ +import type { QName } from './QName'; +import type { SchemaKey } from './SchemaKey'; +import type { TypeReceiver } from './TypeReceiver'; +import type { XmlSchemaFacet } from './facet/XmlSchemaFacet'; +import type { XmlSchemaType } from './XmlSchemaType'; +import type { CollectionURIResolver } from './resolver/CollectionURIResolver'; +import type { URIResolver } from './resolver/URIResolver'; +import type { NamespacePrefixList } from './utils/NamespacePrefixList'; + +import { XmlSchema } from './XmlSchema'; +import { XmlSchemaMaxInclusiveFacet } from './facet/XmlSchemaMaxInclusiveFacet'; +import { XmlSchemaMinInclusiveFacet } from './facet/XmlSchemaMinInclusiveFacet'; +import { XmlSchemaPatternFacet } from './facet/XmlSchemaPatternFacet'; +import { XmlSchemaWhiteSpaceFacet } from './facet/XmlSchemaWhiteSpaceFacet'; +import { XmlSchemaSimpleTypeRestriction } from './simple/XmlSchemaSimpleTypeRestriction'; +import { XmlSchemaSimpleTypeList } from './simple/XmlSchemaSimpleTypeList'; +import { SchemaBuilder } from './SchemaBuilder'; +import { XmlSchemaSimpleType } from './simple/XmlSchemaSimpleType'; +import { ExtensionRegistry } from './extensions/ExtensionRegistry'; +import * as Constants from './constants'; +import { DefaultURIResolver } from './resolver/DefaultURIResolver'; +import { XmlSchemaFractionDigitsFacet } from './facet/XmlSchemaFractionDigitsFacet'; +import { QNameMap, SchemaKeyMap } from './utils/ObjectMap'; + +export class XmlSchemaCollection { + baseUri: string | null; + private stack: SchemaKey[]; + private unresolvedTypes: QNameMap; + private xsd: XmlSchema; + private extReg: ExtensionRegistry; + + private knownNamespaceMap: Record; + private namespaceContext: NamespacePrefixList | null; + private schemaResolver: URIResolver; + private schemas: SchemaKeyMap; + constructor() { + this.baseUri = null; + this.stack = []; + this.unresolvedTypes = new QNameMap(); + this.extReg = new ExtensionRegistry(); + this.knownNamespaceMap = {}; + this.namespaceContext = null; + this.schemaResolver = new DefaultURIResolver(); + this.schemas = new SchemaKeyMap(); + this.xsd = new XmlSchema(XmlSchema.SCHEMA_NS, undefined, this); + this.init(); + } + + /** + * Return an indication of whether a particular schema is not in the working stack of schemas. This function, + * while public, is probably not useful outside of the implementation. + * + * @param pKey schema key + * @return false if the schema is in the stack. + */ + check(pKey: SchemaKey) { + return !this.stack.includes(pKey); + } + + getExtReg() { + return this.extReg; + } + + /** + * get the namespace map + * + * @return a map of previously known XMLSchema objects keyed by their namespace (String) + */ + getKnownNamespaceMap() { + return this.knownNamespaceMap; + } + + /** + * Retrieve the namespace context. + * + * @return the namespace context. + */ + getNamespaceContext() { + return this.namespaceContext; + } + + /** + * Retrieve the custom URI resolver, if any. + * + * @return the current resolver. + */ + getSchemaResolver() { + return this.schemaResolver; + } + + /** + * Retrieve a global type from the schema collection. + * + * @param schemaTypeName the QName of the type. + * @return the type object, or null. + */ + getTypeByQName(schemaTypeName: QName) { + const uri = schemaTypeName.getNamespaceURI(); + for (const entry of this.schemas.entries()) { + if (entry[0].getNamespace() === uri) { + const type = entry[1].getTypeByQName(schemaTypeName); + if (type != null) { + return type; + } + } + } + return null; + } + + /** + * Retrieve a set containing the XmlSchema instances with the given system ID. In general, this will + * return a single instance, or none. However, if the schema has no targetNamespace attribute and was + * included from schemata with different target namespaces, then it may occur, that multiple schema + * instances with different logical target namespaces may be returned. + * + * @param systemId the system id for this schema + * @return array of XmlSchema objects + */ + getXmlSchema(systemId: string | null) { + if (systemId == null) { + systemId = ''; + } + const result: XmlSchema[] = []; + for (const entry of this.schemas.entries()) { + if (entry[0].getSystemId() === systemId) { + result.push(entry[1]); + } + } + return result; + } + + getXmlSchemas() { + return Array.from(this.schemas.values()); + } + + init(): void { + /* + * Defined in section 4. + */ + this.addSimpleType(this.xsd, Constants.XSD_ANYSIMPLETYPE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_ANYTYPE.getLocalPart()!); + + /* + * Primitive types 3.2.1 string 3.2.2 boolean 3.2.3 decimal 3.2.4 float 3.2.5 double 3.2.6 duration + * 3.2.7 dateTime 3.2.8 time 3.2.9 date 3.2.10 gYearMonth 3.2.11 gYear 3.2.12 gMonthDay 3.2.13 gDay + * 3.2.14 gMonth 3.2.15 hexBinary 3.2.16 base64Binary 3.2.17 anyURI 3.2.18 QName 3.2.19 NOTATION + */ + this.addSimpleType(this.xsd, Constants.XSD_STRING.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_BOOLEAN.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_FLOAT.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DOUBLE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_QNAME.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DECIMAL.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DURATION.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DATE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_TIME.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DATETIME.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_DAY.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_MONTH.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_MONTHDAY.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_YEAR.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_YEARMONTH.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NOTATION.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_HEXBIN.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_BASE64.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_ANYURI.getLocalPart()!); + + /* + * 3.3.1 normalizedString 3.3.2 token 3.3.3 language 3.3.4 NMTOKEN 3.3.5 NMTOKENS 3.3.6 Name 3.3.7 + * NCName 3.3.8 ID 3.3.9 IDREF 3.3.10 IDREFS 3.3.11 ENTITY 3.3.12 ENTITIES 3.3.13 integer 3.3.14 + * nonPositiveInteger 3.3.15 negativeInteger 3.3.16 long 3.3.17 int 3.3.18 short 3.3.19 byte 3.3.20 + * nonNegativeInteger 3.3.21 unsignedLong 3.3.22 unsignedInt 3.3.23 unsignedShort 3.3.24 unsignedByte + * 3.3.25 positiveInteger + */ + + // derived types from decimal + this.addSimpleType(this.xsd, Constants.XSD_LONG.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_SHORT.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_BYTE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_INTEGER.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_INT.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_POSITIVEINTEGER.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NEGATIVEINTEGER.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NONPOSITIVEINTEGER.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NONNEGATIVEINTEGER.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_UNSIGNEDBYTE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_UNSIGNEDINT.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_UNSIGNEDLONG.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_UNSIGNEDSHORT.getLocalPart()!); + + // derived types from string + this.addSimpleType(this.xsd, Constants.XSD_NAME.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NORMALIZEDSTRING.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NCNAME.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NMTOKEN.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_NMTOKENS.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_ENTITY.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_ENTITIES.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_ID.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_IDREF.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_IDREFS.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_LANGUAGE.getLocalPart()!); + this.addSimpleType(this.xsd, Constants.XSD_TOKEN.getLocalPart()!); + + // 2.5.3 setup built-in datatype hierarchy + this.setupBuiltinDatatypeHierarchy(this.xsd); + + /* + * Removed loading ExtensionRegistry from system property. + * It should be enough to register it from application side when necessary. + */ + } + + private setupBuiltinDatatypeHierarchy(xsd: XmlSchema) { + this.setDerivationByRestriction(xsd, Constants.XSD_ANYSIMPLETYPE, Constants.XSD_ANYTYPE); + this.setDerivationByRestriction(xsd, Constants.XSD_DURATION, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_DATETIME, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_TIME, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_DATE, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_YEARMONTH, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_YEAR, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_MONTHDAY, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_DAY, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_MONTH, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_BOOLEAN, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_BASE64, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_HEXBIN, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_FLOAT, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_DOUBLE, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_ANYURI, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_QNAME, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NOTATION, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_DECIMAL, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('collapse', true), + ]); + + this.setDerivationByRestriction(xsd, Constants.XSD_INTEGER, Constants.XSD_DECIMAL, [ + new XmlSchemaFractionDigitsFacet(0, true), + new XmlSchemaPatternFacet('[\\-+]?[0-9]+', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NONPOSITIVEINTEGER, Constants.XSD_INTEGER, [ + new XmlSchemaMaxInclusiveFacet(0, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NEGATIVEINTEGER, Constants.XSD_NONPOSITIVEINTEGER, [ + new XmlSchemaMaxInclusiveFacet(-1, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_LONG, Constants.XSD_INTEGER, [ + new XmlSchemaMinInclusiveFacet(BigInt('-9223372036854775808'), false), + new XmlSchemaMaxInclusiveFacet(BigInt('9223372036854775807'), false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_INT, Constants.XSD_LONG, [ + new XmlSchemaMinInclusiveFacet(-2147483648, false), + new XmlSchemaMaxInclusiveFacet(2147483647, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_SHORT, Constants.XSD_INT, [ + new XmlSchemaMinInclusiveFacet(-32768, false), + new XmlSchemaMaxInclusiveFacet(32767, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_BYTE, Constants.XSD_SHORT, [ + new XmlSchemaMinInclusiveFacet(-128, false), + new XmlSchemaMaxInclusiveFacet(127, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NONNEGATIVEINTEGER, Constants.XSD_INTEGER, [ + new XmlSchemaMinInclusiveFacet(0, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_POSITIVEINTEGER, Constants.XSD_NONNEGATIVEINTEGER, [ + new XmlSchemaMinInclusiveFacet(1, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_UNSIGNEDLONG, Constants.XSD_NONNEGATIVEINTEGER, [ + new XmlSchemaMaxInclusiveFacet(BigInt('18446744073709551615'), false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_UNSIGNEDINT, Constants.XSD_UNSIGNEDLONG, [ + new XmlSchemaMaxInclusiveFacet(4294967295, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_UNSIGNEDSHORT, Constants.XSD_UNSIGNEDINT, [ + new XmlSchemaMaxInclusiveFacet(65535, false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_UNSIGNEDBYTE, Constants.XSD_UNSIGNEDSHORT, [ + new XmlSchemaMaxInclusiveFacet(255, false), + ]); + + this.setDerivationByRestriction(xsd, Constants.XSD_STRING, Constants.XSD_ANYSIMPLETYPE, [ + new XmlSchemaWhiteSpaceFacet('preserve', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NORMALIZEDSTRING, Constants.XSD_STRING, [ + new XmlSchemaWhiteSpaceFacet('replace', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_TOKEN, Constants.XSD_NORMALIZEDSTRING, [ + new XmlSchemaWhiteSpaceFacet('collapse', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_LANGUAGE, Constants.XSD_TOKEN, [ + new XmlSchemaPatternFacet('[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NMTOKEN, Constants.XSD_TOKEN, [ + new XmlSchemaPatternFacet('\\c+', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NAME, Constants.XSD_NMTOKEN, [ + new XmlSchemaPatternFacet('\\i\\c*', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_NCNAME, Constants.XSD_TOKEN, [ + new XmlSchemaPatternFacet('[\\i-[:]][\\c-[:]]*', false), + ]); + this.setDerivationByRestriction(xsd, Constants.XSD_ID, Constants.XSD_NCNAME); + this.setDerivationByRestriction(xsd, Constants.XSD_IDREF, Constants.XSD_NCNAME); + this.setDerivationByRestriction(xsd, Constants.XSD_ENTITY, Constants.XSD_NCNAME); + + this.setDerivationByList(xsd, Constants.XSD_NMTOKENS, Constants.XSD_NMTOKEN); + this.setDerivationByList(xsd, Constants.XSD_IDREFS, Constants.XSD_IDREF); + this.setDerivationByList(xsd, Constants.XSD_ENTITIES, Constants.XSD_ENTITY); + } + + private setDerivationByRestriction(xsd: XmlSchema, child: QName, parent: QName, facets?: XmlSchemaFacet[]) { + const simple = xsd.getTypeByQName(child) as XmlSchemaSimpleType; + const restriction = new XmlSchemaSimpleTypeRestriction(); + restriction.setBaseTypeName(parent); + restriction.setBaseType(xsd.getTypeByQName(parent) as XmlSchemaSimpleType); + + facets != null && restriction.getFacets().push(...facets); + simple.setContent(restriction); + } + + private setDerivationByList(xsd: XmlSchema, child: QName, parent: QName) { + const simple = xsd.getTypeByQName(child) as XmlSchemaSimpleType; + const restriction = new XmlSchemaSimpleTypeList(); + restriction.setItemTypeName(parent); + restriction.setItemType(xsd.getTypeByQName(parent) as XmlSchemaSimpleType); + simple.setContent(restriction); + } + + /** + * Pop the stack of schemas. This function, while public, is probably not useful outside of the + * implementation. + */ + pop() { + this.stack.pop(); + } + + /** + * Push a schema onto the stack of schemas. This function, while public, is probably not useful outside of + * the implementation. + * + * @param pKey the schema key. + */ + push(pKey: SchemaKey) { + this.stack.push(pKey); + } + + read(content: string, validator: (schema: XmlSchema) => void): XmlSchema { + const parser = new DOMParser(); + const document = parser.parseFromString(content, 'text/xml'); + const error = document.querySelector('parsererror'); + if (error) + throw new Error( + `XML Parser Error: ${error.textContent ?? 'The XML schema file had a parse error, but there was no reason provided'}`, + ); + const builder = new SchemaBuilder(this, validator); + return builder.build(document); + } + + /** + * Return the schema from this collection for a particular targetNamespace. + * + * @param uri target namespace URI. + * @return the schema. + */ + schemaForNamespace(uri: string) { + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + return entry[1]; + } + } + return null; + } + + /** + * Set the base URI. This is used when schemas need to be loaded from relative locations + * + * @param baseUri baseUri for this collection. + */ + setBaseUri(baseUri: string) { + this.baseUri = baseUri; + const target = (this.schemaResolver as CollectionURIResolver)?.setCollectionBaseURI; + target && target(baseUri); + } + + setExtReg(extReg: ExtensionRegistry) { + this.extReg = extReg; + } + + /** + * sets the known namespace map + * + * @param knownNamespaceMap a map of previously known XMLSchema objects keyed by their namespace (String) + */ + setKnownNamespaceMap(knownNamespaceMap: Record) { + this.knownNamespaceMap = knownNamespaceMap; + } + + /** + * Set the namespace context for this collection, which controls the assignment of namespace prefixes to + * namespaces. + * + * @param namespaceContext the context. + */ + setNamespaceContext(namespaceContext: NamespacePrefixList) { + this.namespaceContext = namespaceContext; + } + + /** + * Register a custom URI resolver + * + * @param schemaResolver resolver + */ + setSchemaResolver(schemaResolver: URIResolver) { + this.schemaResolver = schemaResolver; + } + + addSchema(pKey: SchemaKey, pSchema: XmlSchema) { + if (this.schemas?.has(pKey)) { + throw new Error( + `A schema with target namespace ${pKey.getNamespace()} and system ID ${pKey.getSystemId()} is already present.`, + ); + } + this.schemas.set(pKey, pSchema); + } + + addUnresolvedType(type: QName, receiver: TypeReceiver) { + let receivers = this.unresolvedTypes.get(type); + if (receivers == null) { + receivers = []; + this.unresolvedTypes.set(type, receivers); + } + receivers.push(receiver); + } + + containsSchema(pKey: SchemaKey): boolean { + return !!this.schemas && this.schemas.has(pKey); + } + + /** + * gets a schema from the external namespace map + * + * @param namespace + * @return + */ + getKnownSchema(namespace: string | null) { + return namespace == null ? null : this.knownNamespaceMap[namespace]; + } + + /** + * Get a schema given a SchemaKey + * + * @param pKey + * @return + */ + getSchema(pKey: SchemaKey) { + return this.schemas.get(pKey); + } + + resolveType(typeName: QName, type: XmlSchemaType) { + const receivers = this.unresolvedTypes.get(typeName); + if (receivers == null) { + return; + } + for (const receiver of receivers) { + receiver.setType(type); + } + this.unresolvedTypes.delete(typeName); + } + + private addSimpleType(schema: XmlSchema, typeName: string) { + const type = new XmlSchemaSimpleType(schema, true); + type.setName(typeName); + } + + /** + * Find a global attribute by QName in this collection of schemas. + * + * @param schemaAttributeName the name of the attribute. + * @return the attribute or null. + */ + getAttributeByQName(schemaAttributeName: QName | null) { + if (schemaAttributeName == null) { + return null; + } + const uri = schemaAttributeName.getNamespaceURI(); + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + const attribute = entry[1].getAttributeByQName(schemaAttributeName); + if (attribute != null) { + return attribute; + } + } + } + return null; + } + + /** + * Retrieve a global element from the schema collection. + * + * @param qname the element QName. + * @return the element object, or null. + */ + getElementByQName(qname: QName | null) { + if (qname == null) { + return null; + } + const uri = qname.getNamespaceURI(); + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + const element = entry[1].getElementByQName(qname); + if (element != null) { + return element; + } + } + } + return null; + } + + getAttributeGroupByQName(name: QName | null) { + if (name == null) { + return null; + } + const uri = name.getNamespaceURI(); + const entries = this.schemas.entries(); + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + const group = entry[1].getAttributeGroupByQName(name); + if (group != null) { + return group; + } + } + } + return null; + } + + getGroupByQName(name: QName | null) { + if (name == null) { + return null; + } + const uri = name.getNamespaceURI(); + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + const group = entry[1].getGroupByQName(name); + if (group != null) { + return group; + } + } + } + return null; + } + + getNotationByQName(name: QName | null) { + if (name == null) { + return null; + } + const uri = name.getNamespaceURI(); + for (const entry of Array.from(this.schemas.entries())) { + if (entry[0].getNamespace() === uri) { + const notation = entry[1].getNotationByQName(name); + if (notation != null) { + return notation; + } + } + } + return null; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaContent.ts b/packages/xml-schema-ts/src/XmlSchemaContent.ts new file mode 100644 index 000000000..0d524117a --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaContent.ts @@ -0,0 +1,6 @@ +/** + * An abstract class for schema content. + */ +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; + +export abstract class XmlSchemaContent extends XmlSchemaAnnotated {} diff --git a/packages/xml-schema-ts/src/XmlSchemaContentModel.ts b/packages/xml-schema-ts/src/XmlSchemaContentModel.ts new file mode 100644 index 000000000..63033eed3 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaContentModel.ts @@ -0,0 +1,10 @@ +import type { XmlSchemaContent } from './XmlSchemaContent'; +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; + +/** + * An abstract class for the schema content model. + */ +export abstract class XmlSchemaContentModel extends XmlSchemaAnnotated { + abstract setContent(content: XmlSchemaContent | null): void; + abstract getContent(): XmlSchemaContent | null; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaContentProcessing.ts b/packages/xml-schema-ts/src/XmlSchemaContentProcessing.ts new file mode 100644 index 000000000..a74b71b7e --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaContentProcessing.ts @@ -0,0 +1,14 @@ +/** + * Provides information about the validation mode of any and anyAttribute element replacements. + */ + +export enum XmlSchemaContentProcessing { + LAX = 'LAX', + NONE = 'NONE', + SKIP = 'SKIP', + STRICT = 'STRICT', +} + +export function xmlSchemaContentProcessingValueOf(name: string) { + return XmlSchemaContentProcessing[name.toUpperCase() as keyof typeof XmlSchemaContentProcessing]; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaContentType.ts b/packages/xml-schema-ts/src/XmlSchemaContentType.ts new file mode 100644 index 000000000..89faf4a35 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaContentType.ts @@ -0,0 +1,15 @@ +/** + * Enumerations for the content model of the complex type. This represents the content in the + * post-schema-validation infoset. + */ + +export enum XmlSchemaContentType { + ELEMENT_ONLY = 'ELEMENT_ONLY', + EMPTY = 'EMPTY', + MIXED = 'MIXED', + TEXT_ONLY = 'TEXT_ONLY', +} + +export function xmlSchemaContentTypeValueOf(name: string) { + return XmlSchemaContentType[name.toUpperCase() as keyof typeof XmlSchemaContentType]; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaDerivationMethod.ts b/packages/xml-schema-ts/src/XmlSchemaDerivationMethod.ts new file mode 100644 index 000000000..e734e4c19 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaDerivationMethod.ts @@ -0,0 +1,130 @@ +export class XmlSchemaDerivationMethod { + static readonly NONE = new XmlSchemaDerivationMethod(); + private all = false; + private empty = false; + private extension = false; + private list = false; + private restriction = false; + private substitution = false; + private union = false; + + static schemaValueOf(name: string): XmlSchemaDerivationMethod { + const tokens = name.split('\\s'); + const method = new XmlSchemaDerivationMethod(); + for (const t in tokens) { + if ('#all' === t.toLowerCase() || 'all' === t.toLowerCase()) { + if (method.notAll()) { + throw new Error('Derivation method cannot be #all and something else.'); + } else { + method.setAll(true); + } + } else { + if (method.isAll()) { + throw new Error('Derivation method cannot be #all and something else.'); + } + if ('extension' === t) { + method.setExtension(true); + } else if ('list' === t) { + method.setList(true); + } else if ('restriction' === t) { + method.setRestriction(true); + } else if ('substitution' === t) { + method.setSubstitution(true); + } else if ('union' === t) { + method.setUnion(true); + } + } + } + return method; + } + + notAll(): boolean { + return this.empty || this.extension || this.list || this.restriction || this.substitution || this.union; + } + + isAll(): boolean { + return this.all; + } + + setAll(all: boolean) { + this.all = all; + if (all) { + this.empty = false; + this.extension = false; + this.list = false; + this.restriction = false; + this.substitution = false; + this.union = false; + } + } + + isEmpty() { + return this.empty; + } + + setEmpty(empty: boolean) { + this.empty = empty; + } + + isExtension() { + return this.extension; + } + + setExtension(extension: boolean) { + this.extension = extension; + } + + isList() { + return this.list; + } + + setList(list: boolean) { + this.list = list; + } + + isNone() { + return !( + this.all || + this.empty || + this.extension || + this.list || + this.restriction || + this.substitution || + this.union + ); + } + + setNone(_none: boolean) { + this.all = false; + this.empty = false; + this.extension = false; + this.list = false; + this.restriction = false; + this.substitution = false; + this.union = false; + } + + isRestriction() { + return this.restriction; + } + + setRestriction(restriction: boolean) { + this.restriction = restriction; + } + + isSubstitution() { + return this.substitution; + } + + setSubstitution(substitution: boolean) { + this.substitution = substitution; + } + + isUnion() { + return this.union; + } + + setUnion(union: boolean) { + this.union = union; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaDocumentation.ts b/packages/xml-schema-ts/src/XmlSchemaDocumentation.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/xml-schema-ts/src/XmlSchemaForm.ts b/packages/xml-schema-ts/src/XmlSchemaForm.ts new file mode 100644 index 000000000..5e76fa151 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaForm.ts @@ -0,0 +1,9 @@ +export enum XmlSchemaForm { + NONE = 'none', + QUALIFIED = 'qualified', + UNQUALIFIED = 'unqualified', +} + +export function xmlSchemaFormValueOf(value: string) { + return XmlSchemaForm[value.toUpperCase() as keyof typeof XmlSchemaForm]; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaGroup.ts b/packages/xml-schema-ts/src/XmlSchemaGroup.ts new file mode 100644 index 000000000..24bbf2114 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaGroup.ts @@ -0,0 +1,63 @@ +import type { XmlSchema } from './XmlSchema'; +import type { XmlSchemaAllMember } from './particle/XmlSchemaAllMember'; +import type { XmlSchemaChoiceMember } from './particle/XmlSchemaChoiceMember'; +import type { XmlSchemaGroupParticle } from './particle/XmlSchemaGroupParticle'; +import type { XmlSchemaSequenceMember } from './particle/XmlSchemaSequenceMember'; +import type { XmlSchemaNamed } from './utils/XmlSchemaNamed'; + +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; +import { XmlSchemaNamedImpl } from './utils/XmlSchemaNamedImpl'; + +export class XmlSchemaGroup + extends XmlSchemaAnnotated + implements XmlSchemaNamed, XmlSchemaChoiceMember, XmlSchemaSequenceMember, XmlSchemaAllMember +{ + private particle: XmlSchemaGroupParticle | null = null; + private namedDelegate: XmlSchemaNamedImpl; + + constructor(parent: XmlSchema) { + super(); + this.namedDelegate = new XmlSchemaNamedImpl(parent, true); + const fParent = parent; + fParent.getItems().push(this); + } + + getParticle() { + return this.particle; + } + + setParticle(particle: XmlSchemaGroupParticle) { + this.particle = particle; + } + + getName() { + return this.namedDelegate.getName(); + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setName(name: string | null) { + const fName = name; + if (this.getQName() != null) { + this.getParent().getGroups().delete(this.getQName()!); + } + this.namedDelegate.setName(fName); + if (fName != null) { + this.getParent().getGroups().set(this.getQName()!, this); + } + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaItemWithRef.ts b/packages/xml-schema-ts/src/XmlSchemaItemWithRef.ts new file mode 100644 index 000000000..7e8983939 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaItemWithRef.ts @@ -0,0 +1,7 @@ +import { XmlSchemaNamed } from './utils/XmlSchemaNamed'; +import { XmlSchemaRef } from './utils/XmlSchemaRef'; +import { XmlSchemaItemWithRefBase } from './XmlSchemaItemWithRefBase'; + +export interface XmlSchemaItemWithRef extends XmlSchemaItemWithRefBase { + getRef(): XmlSchemaRef; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaItemWithRefBase.ts b/packages/xml-schema-ts/src/XmlSchemaItemWithRefBase.ts new file mode 100644 index 000000000..06547c309 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaItemWithRefBase.ts @@ -0,0 +1,19 @@ +import { QName } from './QName'; +import { XmlSchemaRefBase } from './utils/XmlSchemaRefBase'; + +export interface XmlSchemaItemWithRefBase { + /** + * @return true if this object has a non-null ref. + */ + isRef(): boolean; + + /** + * @return the Qualified Name of the target of the ref. + */ + getTargetQName(): QName | null; + + /** + * @return the non-generic reference object. + */ + getRefBase(): XmlSchemaRefBase; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaNotation.ts b/packages/xml-schema-ts/src/XmlSchemaNotation.ts new file mode 100644 index 000000000..d55bc6289 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaNotation.ts @@ -0,0 +1,73 @@ +import type { XmlSchema } from './XmlSchema'; +import type { XmlSchemaNamed } from './utils/XmlSchemaNamed'; +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; +import { XmlSchemaNamedImpl } from './utils/XmlSchemaNamedImpl'; + +export class XmlSchemaNotation extends XmlSchemaAnnotated implements XmlSchemaNamed { + private system: string | null = null; + private publicNotation: string | null = null; + private namedDelegate: XmlSchemaNamedImpl; + + /** + * Creates new XmlSchemaNotation + */ + constructor(parent: XmlSchema) { + super(); + this.namedDelegate = new XmlSchemaNamedImpl(parent, true); + const fParent = parent; + fParent.getItems().push(this); + } + + getPublic() { + return this.publicNotation; + } + + setPublic(isPublic: string) { + this.publicNotation = isPublic; + } + + getSystem() { + return this.system; + } + + setSystem(system: string) { + this.system = system; + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setPublicNotation(publicNotation: string) { + this.publicNotation = publicNotation; + } + + getPublicNotation() { + return this.publicNotation; + } + + getName() { + return this.namedDelegate.getName(); + } + + setName(name: string) { + const fName = name; + if (this.getName() != null) { + this.getParent().getNotations().delete(this.getQName()!); + } + this.namedDelegate.setName(fName); + this.getParent().getNotations().set(this.getQName()!, this); + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaObject.ts b/packages/xml-schema-ts/src/XmlSchemaObject.ts new file mode 100644 index 000000000..72bad56d0 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaObject.ts @@ -0,0 +1,45 @@ +import { XmlSchemaObjectBase } from './utils/XmlSchemaObjectBase'; + +export abstract class XmlSchemaObject implements XmlSchemaObjectBase { + lineNumber?: number; + linePosition?: number; + sourceURI: string | null = null; + + private metaInfoMap = new Map(); + + addMetaInfo(key: string, value: object) { + this.metaInfoMap.set(key, value); + } + + getLineNumber() { + return this.lineNumber; + } + + getLinePosition() { + return this.linePosition; + } + + getMetaInfoMap() { + return this.metaInfoMap; + } + + getSourceURI() { + return this.sourceURI; + } + + setLineNumber(lineNumber: number) { + this.lineNumber = lineNumber; + } + + setLinePosition(linePosition: number) { + this.linePosition = linePosition; + } + + setMetaInfoMap(metaInfoMap: Map) { + this.metaInfoMap = metaInfoMap; + } + + setSourceURI(sourceURI: string | null) { + this.sourceURI = sourceURI; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaType.ts b/packages/xml-schema-ts/src/XmlSchemaType.ts new file mode 100644 index 000000000..815b56993 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaType.ts @@ -0,0 +1,102 @@ +import type { XmlSchema } from './XmlSchema'; +import type { XmlSchemaNamed } from './utils/XmlSchemaNamed'; +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; +import { XmlSchemaDerivationMethod } from './XmlSchemaDerivationMethod'; +import { XmlSchemaNamedImpl } from './utils/XmlSchemaNamedImpl'; + +export class XmlSchemaType extends XmlSchemaAnnotated implements XmlSchemaNamed { + private deriveBy: XmlSchemaDerivationMethod | null = null; + private finalDerivation: XmlSchemaDerivationMethod; + private finalResolved?: XmlSchemaDerivationMethod; + private _isMixed = false; + private namedDelegate: XmlSchemaNamedImpl; + + constructor(schema: XmlSchema, topLevel: boolean) { + super(); + this.namedDelegate = new XmlSchemaNamedImpl(schema, topLevel); + this.finalDerivation = XmlSchemaDerivationMethod.NONE; + if (topLevel) { + schema.getItems().push(this); + } + } + + getDeriveBy() { + return this.deriveBy; + } + + getFinal() { + return this.finalDerivation; + } + + setFinal(finalDerivationValue: XmlSchemaDerivationMethod) { + this.finalDerivation = finalDerivationValue; + } + + getFinalResolved() { + return this.finalResolved; + } + + isMixed() { + return this._isMixed; + } + + setMixed(isMixedValue: boolean) { + this._isMixed = isMixedValue; + } + + getName() { + return this.namedDelegate.getName(); + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setName(name: string | null) { + /* + * Inside a redefine, a 'non-top-level' type can have a name. + * This requires us to tolerate this case (non-top-level, named) even it + * in any other case it's completely invalid. + */ + if (this.isTopLevel() && name == null) { + throw new Error('A non-top-level type may not be anonyous.'); + } + if (this.isTopLevel() && this.getName() != null) { + const qname = this.getQName(); + qname && this.getParent().getSchemaTypes().delete(qname); + } + this.namedDelegate.setName(name!); + if (this.isTopLevel()) { + const qname = this.getQName(); + qname && this.getParent().getSchemaTypes().set(qname, this); + } + } + + setFinalResolved(finalResolved: XmlSchemaDerivationMethod) { + this.finalResolved = finalResolved; + } + + setFinalDerivation(finalDerivation: XmlSchemaDerivationMethod) { + this.finalDerivation = finalDerivation; + } + + getFinalDerivation() { + return this.finalDerivation; + } + + setDeriveBy(deriveBy: XmlSchemaDerivationMethod) { + this.deriveBy = deriveBy; + } +} diff --git a/packages/xml-schema-ts/src/XmlSchemaUse.ts b/packages/xml-schema-ts/src/XmlSchemaUse.ts new file mode 100644 index 000000000..28fbc0a47 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaUse.ts @@ -0,0 +1,13 @@ +/** + * use= values. + */ +export enum XmlSchemaUse { + NONE = 'NONE', + OPTIONAL = 'OPTIONAL', + PROHIBITED = 'PROHIBITED', + REQUIRED = 'REQUIRED', +} + +export function xmlSchemaUseValueOf(name: string) { + return XmlSchemaUse[name.toUpperCase() as keyof typeof XmlSchemaUse]; +} diff --git a/packages/xml-schema-ts/src/XmlSchemaXPath.ts b/packages/xml-schema-ts/src/XmlSchemaXPath.ts new file mode 100644 index 000000000..8f126ac11 --- /dev/null +++ b/packages/xml-schema-ts/src/XmlSchemaXPath.ts @@ -0,0 +1,17 @@ +/** + * Class for XML Path Language (XPath) expressions. Represents the World Wide Web Consortium (W3C) selector + * element. The World Wide Web Consortium (W3C) field element is a collection of XmlSchemaXPath classes. + */ +import { XmlSchemaAnnotated } from './XmlSchemaAnnotated'; + +export class XmlSchemaXPath extends XmlSchemaAnnotated { + xpath: string | null = null; + + getXPath() { + return this.xpath; + } + + setXPath(xpathString: string) { + this.xpath = xpathString; + } +} diff --git a/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotation.ts b/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotation.ts new file mode 100644 index 000000000..98cd02829 --- /dev/null +++ b/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotation.ts @@ -0,0 +1,10 @@ +import type { XmlSchemaAnnotationItem } from './XmlSchemaAnnotationItem'; +import { XmlSchemaObject } from '../XmlSchemaObject'; + +export class XmlSchemaAnnotation extends XmlSchemaObject { + private items: XmlSchemaAnnotationItem[] = []; + + public getItems() { + return this.items; + } +} diff --git a/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotationItem.ts b/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotationItem.ts new file mode 100644 index 000000000..22b1c0851 --- /dev/null +++ b/packages/xml-schema-ts/src/annotation/XmlSchemaAnnotationItem.ts @@ -0,0 +1,3 @@ +import { XmlSchemaObject } from '../XmlSchemaObject'; + +export abstract class XmlSchemaAnnotationItem extends XmlSchemaObject {} diff --git a/packages/xml-schema-ts/src/annotation/XmlSchemaAppInfo.ts b/packages/xml-schema-ts/src/annotation/XmlSchemaAppInfo.ts new file mode 100644 index 000000000..92aa20ffb --- /dev/null +++ b/packages/xml-schema-ts/src/annotation/XmlSchemaAppInfo.ts @@ -0,0 +1,29 @@ +import { XmlSchemaAnnotationItem } from './XmlSchemaAnnotationItem'; + +export class XmlSchemaAppInfo extends XmlSchemaAnnotationItem { + /** + * Provides the source of the application information. + */ + source: string | null = null; + + /** + * Returns an array of XmlNode that represents the document text markup. + */ + markup: NodeList | null = null; + + public getSource() { + return this.source; + } + + public setSource(source: string | null) { + this.source = source; + } + + public getMarkup() { + return this.markup; + } + + public setMarkup(markup: NodeList | null) { + this.markup = markup; + } +} diff --git a/packages/xml-schema-ts/src/annotation/XmlSchemaDocumentation.ts b/packages/xml-schema-ts/src/annotation/XmlSchemaDocumentation.ts new file mode 100644 index 000000000..389c1dedb --- /dev/null +++ b/packages/xml-schema-ts/src/annotation/XmlSchemaDocumentation.ts @@ -0,0 +1,38 @@ +import { XmlSchemaAnnotationItem } from './XmlSchemaAnnotationItem'; + +export class XmlSchemaDocumentation extends XmlSchemaAnnotationItem { + /** + * Provides the source of the application information. + */ + source: string | null = null; + language: string | null = null; + + /** + * Returns an array of XmlNode that represents the document text markup. + */ + markup: NodeList | null = null; + + getSource() { + return this.source; + } + + setSource(source: string | null) { + this.source = source; + } + + getMarkup() { + return this.markup; + } + + setMarkup(markup: NodeList) { + this.markup = markup; + } + + getLanguage() { + return this.language; + } + + setLanguage(language: string | null) { + this.language = language; + } +} diff --git a/packages/xml-schema-ts/src/attribute/XmlSchemaAttribute.ts b/packages/xml-schema-ts/src/attribute/XmlSchemaAttribute.ts new file mode 100644 index 000000000..64e3d7b67 --- /dev/null +++ b/packages/xml-schema-ts/src/attribute/XmlSchemaAttribute.ts @@ -0,0 +1,155 @@ +import type { QName } from '../QName'; +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaAttributeGroupMember } from './XmlSchemaAttributeGroupMember'; +import type { XmlSchemaItemWithRef } from '../XmlSchemaItemWithRef'; +import type { XmlSchemaSimpleType } from '../simple/XmlSchemaSimpleType'; +import type { XmlSchemaNamedWithForm } from '../utils/XmlSchemaNamedWithForm'; + +import { XmlSchemaNamedWithFormImpl } from '../utils/XmlSchemaNamedWithFormImpl'; +import { XmlSchemaNamedType } from '../utils/XmlSchemaNamedType'; +import { XmlSchemaRef } from '../utils/XmlSchemaRef'; +import { XmlSchemaAttributeOrGroupRef } from './XmlSchemaAttributeOrGroupRef'; +import { XmlSchemaForm } from '../XmlSchemaForm'; +import { XmlSchemaUse } from '../XmlSchemaUse'; + +export class XmlSchemaAttribute + extends XmlSchemaAttributeOrGroupRef + implements XmlSchemaNamedWithForm, XmlSchemaAttributeGroupMember, XmlSchemaItemWithRef +{ + private defaultValue: string | null = null; + private fixedValue: string | null = null; + private schemaType: XmlSchemaSimpleType | null = null; + private schemaTypeName: QName | null = null; + private use: XmlSchemaUse; + private namedDelegate: XmlSchemaNamedWithFormImpl; + private ref: XmlSchemaRef; + + /** + * Create a new attribute. + * @param schema containing scheme. + * @param topLevel true if a global attribute. + */ + constructor(schema: XmlSchema, topLevel: boolean) { + super(); + this.namedDelegate = new XmlSchemaNamedWithFormImpl(schema, topLevel, false); + this.ref = new XmlSchemaRef(schema, XmlSchemaNamedType.XmlSchemaAttribute); + this.namedDelegate.setRefObject(this.ref); + this.ref.setNamedObject(this.namedDelegate); + this.use = XmlSchemaUse.NONE; + if (topLevel) { + schema.getItems().push(this); + } + } + + getDefaultValue() { + return this.defaultValue; + } + + setDefaultValue(defaultValue: string) { + this.defaultValue = defaultValue; + } + + getFixedValue() { + return this.fixedValue; + } + + setFixedValue(fixedValue: string) { + this.fixedValue = fixedValue; + } + + getRef(): XmlSchemaRef { + return this.ref; + } + + getSchemaType() { + return this.schemaType; + } + + setSchemaType(schemaType: XmlSchemaSimpleType) { + this.schemaType = schemaType; + } + + getSchemaTypeName() { + return this.schemaTypeName; + } + + setSchemaTypeName(schemaTypeName: QName) { + this.schemaTypeName = schemaTypeName; + } + + getUse() { + return this.use; + } + + setUse(use: XmlSchemaUse) { + if (this.namedDelegate.isTopLevel() && use != null) { + throw new Error("Top-level attributes may not have a 'use'"); + } + this.use = use; + } + + getName() { + return this.namedDelegate.getName(); + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setName(name: string) { + const fName = name; + if (this.isTopLevel() && this.getName() != null) { + this.getParent().getAttributes().delete(this.getQName()!); + } + this.namedDelegate.setName(fName); + if (this.isTopLevel()) { + if (fName == null) { + throw new Error('Top-level attributes may not be anonymous'); + } + this.getParent().getAttributes().set(this.getQName()!, this); + } + } + + isFormSpecified() { + return this.namedDelegate.isFormSpecified(); + } + + getForm() { + return this.namedDelegate.getForm(); + } + + setForm(form: XmlSchemaForm) { + if (this.isTopLevel() && form != XmlSchemaForm.NONE) { + throw new Error("Top-level attributes may not have a 'form'"); + } + this.namedDelegate.setForm(form); + } + + getWireName() { + return this.namedDelegate.getWireName(); + } + + isRef() { + return this.ref.getTargetQName() != null; + } + + getTargetQName() { + return this.ref.getTargetQName(); + } + + getRefBase() { + return this.ref; + } +} diff --git a/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroup.ts b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroup.ts new file mode 100644 index 000000000..228f34b6e --- /dev/null +++ b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroup.ts @@ -0,0 +1,66 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { XmlSchemaAttributeGroupMember } from './XmlSchemaAttributeGroupMember'; +import type { XmlSchemaNamed } from '../utils/XmlSchemaNamed'; +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; +import { XmlSchemaNamedImpl } from '../utils/XmlSchemaNamedImpl'; + +export class XmlSchemaAttributeGroup + extends XmlSchemaAnnotated + implements XmlSchemaNamed, XmlSchemaAttributeGroupMember +{ + private anyAttribute: XmlSchemaAnyAttribute | null = null; + private attributes: XmlSchemaAttributeGroupMember[] = []; + private namedDelegate: XmlSchemaNamedImpl; + + /** + * Creates new XmlSchemaAttributeGroup + */ + constructor(parent: XmlSchema) { + super(); + const fParent = parent; + this.namedDelegate = new XmlSchemaNamedImpl(parent, true); + fParent.getItems().push(this); + } + + getAnyAttribute() { + return this.anyAttribute; + } + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + getAttributes() { + return this.attributes; + } + + getName() { + return this.namedDelegate.getName(); + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setName(name: string) { + const fName = name; + if (fName != null) { + this.getQName() != null && this.getParent().getAttributeGroups().delete(this.getQName()!); + } + this.namedDelegate.setName(fName); + this.getQName() != null && this.getParent().getAttributeGroups().set(this.getQName()!, this); + } +} diff --git a/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupMember.ts b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupMember.ts new file mode 100644 index 000000000..f7b91af9c --- /dev/null +++ b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupMember.ts @@ -0,0 +1,5 @@ +/** + * Implemented by the types that can go into an attribute group. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaAttributeGroupMember {} diff --git a/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupRef.ts b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupRef.ts new file mode 100644 index 000000000..1b5aced89 --- /dev/null +++ b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeGroupRef.ts @@ -0,0 +1,48 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaAttributeGroup } from './XmlSchemaAttributeGroup'; +import type { XmlSchemaAttributeGroupMember } from './XmlSchemaAttributeGroupMember'; +import type { XmlSchemaItemWithRef } from '../XmlSchemaItemWithRef'; +import { XmlSchemaAttributeOrGroupRef } from './XmlSchemaAttributeOrGroupRef'; +import { XmlSchemaNamedType } from '../utils/XmlSchemaNamedType'; +import { XmlSchemaRef } from '../utils/XmlSchemaRef'; + +/** + * Class for the attribute group reference. + * Represents the World Wide Web Consortium (W3C) attributeGroup + * element with the ref attribute. + */ +export class XmlSchemaAttributeGroupRef + extends XmlSchemaAttributeOrGroupRef + implements XmlSchemaAttributeGroupMember, XmlSchemaItemWithRef +{ + private ref: XmlSchemaRef; + + /** + * Create an attribute group reference. + * @param parent containing schema. + */ + constructor(parent: XmlSchema) { + super(); + this.ref = new XmlSchemaRef(parent, XmlSchemaNamedType.XmlSchemaAttributeGroup); + } + + /** + * Return the reference object. + * @return + */ + public getRef() { + return this.ref; + } + + isRef() { + return this.ref.getTargetQName() != null; + } + + getTargetQName() { + return this.ref.getTargetQName(); + } + + public getRefBase() { + return this.ref; + } +} diff --git a/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeOrGroupRef.ts b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeOrGroupRef.ts new file mode 100644 index 000000000..26c395d79 --- /dev/null +++ b/packages/xml-schema-ts/src/attribute/XmlSchemaAttributeOrGroupRef.ts @@ -0,0 +1,8 @@ +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +/** + * Several objects in the model allow either an XmlSchemaAttribute or + * an XmlSchemaAttributeGroupRef. This type is here only allow + * tight type specifications for them. + */ +export class XmlSchemaAttributeOrGroupRef extends XmlSchemaAnnotated {} diff --git a/packages/xml-schema-ts/src/complex/XmlSchemaComplexContent.ts b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContent.ts new file mode 100644 index 000000000..bfb32de6e --- /dev/null +++ b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContent.ts @@ -0,0 +1,35 @@ +import type { XmlSchemaContent } from '../XmlSchemaContent'; +import { XmlSchemaContentModel } from '../XmlSchemaContentModel'; + +/** + * Class that represents the complex content model for complex types. Contains extensions or restrictions on a + * complex type that has mixed content or elements only. Represents the World Wide Web Consortium (W3C) + * complexContent element. + */ +export class XmlSchemaComplexContent extends XmlSchemaContentModel { + /* + * One of either the XmlSchemaComplexContentRestriction or XmlSchemaComplexContentExtension classes. + */ + content: XmlSchemaContent | null = null; + /* + * Indicates that this type has a mixed content model. Character data is allowed to appear between the + * child elements of the complex type. + */ + private mixed: boolean = false; + + getContent() { + return this.content; + } + + setContent(content: XmlSchemaContent) { + this.content = content; + } + + isMixed() { + return this.mixed; + } + + setMixed(mixed: boolean) { + this.mixed = mixed; + } +} diff --git a/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentExtension.ts b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentExtension.ts new file mode 100644 index 000000000..13a2f9ba0 --- /dev/null +++ b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentExtension.ts @@ -0,0 +1,57 @@ +/** + * Class for complex types with a complex content model derived by extension. Extends the complex type by + * adding attributes or elements. Represents the World Wide Web Consortium (W3C) extension element for complex + * content. + */ +import type { QName } from '../QName'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { XmlSchemaAttributeOrGroupRef } from '../attribute/XmlSchemaAttributeOrGroupRef'; +import type { XmlSchemaParticle } from '../particle/XmlSchemaParticle'; +import { XmlSchemaContent } from '../XmlSchemaContent'; + +export class XmlSchemaComplexContentExtension extends XmlSchemaContent { + /* Allows an XmlSchemaAnyAttribute to be used for the attribute value. */ + private anyAttribute: XmlSchemaAnyAttribute | null = null; + /* + * Contains XmlSchemaAttribute and XmlSchemaAttributeGroupRef. Collection of attributes for the simple + * type. + */ + private attributes: XmlSchemaAttributeOrGroupRef[] = []; + /* Name of the built-in data type, simple type, or complex type. */ + private baseTypeName: QName | null = null; + + /* One of the XmlSchemaGroupRef, XmlSchemaChoice, XmlSchemaAll, or XmlSchemaSequence classes. */ + private particle: XmlSchemaParticle | null = null; + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + getAnyAttribute() { + return this.anyAttribute; + } + + public getAttributes() { + return this.attributes; + } + + setBaseTypeName(baseTypeName: QName) { + this.baseTypeName = baseTypeName; + } + + getBaseTypeName() { + return this.baseTypeName; + } + + getParticle() { + return this.particle; + } + + setParticle(particle: XmlSchemaParticle) { + this.particle = particle; + } + + setAttributes(attributes: XmlSchemaAttributeOrGroupRef[]) { + this.attributes = attributes; + } +} diff --git a/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentRestriction.ts b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentRestriction.ts new file mode 100644 index 000000000..a133f75ae --- /dev/null +++ b/packages/xml-schema-ts/src/complex/XmlSchemaComplexContentRestriction.ts @@ -0,0 +1,58 @@ +/** + * Class for complex types with a complex content model that are derived by restriction. Restricts the + * contents of the complex type to a subset of the inherited complex type. Represents the World Wide Web + * Consortium (W3C) restriction element for complex content. + */ +import type { QName } from '../QName'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { XmlSchemaAttributeOrGroupRef } from '../attribute/XmlSchemaAttributeOrGroupRef'; +import type { XmlSchemaParticle } from '../particle/XmlSchemaParticle'; +import { XmlSchemaContent } from '../XmlSchemaContent'; + +export class XmlSchemaComplexContentRestriction extends XmlSchemaContent { + /* Allows an XmlSchemaAnyAttribute to be used for the attribute value. */ + private anyAttribute: XmlSchemaAnyAttribute | null = null; + /* + * Contains XmlSchemaAttribute and XmlSchemaAttributeGroupRef. Collection of attributes for the simple + * type. + */ + private attributes: XmlSchemaAttributeOrGroupRef[] = []; + /* Name of the built-in data type, simple type, or complex type. */ + private baseTypeName: QName | null = null; + /* + * One of the XmlSchemaGroupRef, XmlSchemaChoice, XmlSchemaAll, or XmlSchemaSequence classes. + */ + private particle: XmlSchemaParticle | null = null; + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + getAnyAttribute() { + return this.anyAttribute; + } + + getAttributes() { + return this.attributes; + } + + setBaseTypeName(baseTypeName: QName) { + this.baseTypeName = baseTypeName; + } + + getBaseTypeName() { + return this.baseTypeName; + } + + getParticle() { + return this.particle; + } + + setParticle(particle: XmlSchemaParticle) { + this.particle = particle; + } + + setAttributes(attributes: XmlSchemaAttributeOrGroupRef[]) { + this.attributes = attributes; + } +} diff --git a/packages/xml-schema-ts/src/complex/XmlSchemaComplexType.ts b/packages/xml-schema-ts/src/complex/XmlSchemaComplexType.ts new file mode 100644 index 000000000..247ff579a --- /dev/null +++ b/packages/xml-schema-ts/src/complex/XmlSchemaComplexType.ts @@ -0,0 +1,137 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { XmlSchemaAttributeOrGroupRef } from '../attribute/XmlSchemaAttributeOrGroupRef'; +import type { XmlSchemaContentModel } from '../XmlSchemaContentModel'; +import type { XmlSchemaContentType } from '../XmlSchemaContentType'; +import type { XmlSchemaParticle } from '../particle/XmlSchemaParticle'; + +import { XmlSchemaType } from '../XmlSchemaType'; +import { XmlSchemaDerivationMethod } from '../XmlSchemaDerivationMethod'; +import { XmlSchemaComplexContentExtension } from './XmlSchemaComplexContentExtension'; +import { XmlSchemaComplexContentRestriction } from './XmlSchemaComplexContentRestriction'; + +export class XmlSchemaComplexType extends XmlSchemaType { + private anyAttribute: XmlSchemaAnyAttribute | null = null; + private attributeWildcard: XmlSchemaAnyAttribute | null = null; + private attributes: XmlSchemaAttributeOrGroupRef[] = []; + private block: XmlSchemaDerivationMethod = XmlSchemaDerivationMethod.NONE; + private blockResolved: XmlSchemaDerivationMethod | null = null; + private contentModel: XmlSchemaContentModel | null = null; + private contentType: XmlSchemaContentType | null = null; + private particleType: XmlSchemaParticle | null = null; + private particle: XmlSchemaParticle | null = null; + private _isAbstract: boolean = false; + + /** + * Creates new XmlSchemaComplexType + */ + constructor(schema: XmlSchema, topLevel: boolean) { + super(schema, topLevel); + } + + getAnyAttribute() { + return this.anyAttribute; + } + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + public getAttributes() { + return this.attributes; + } + + public getAttributeWildcard() { + return this.attributeWildcard; + } + + getBlock() { + return this.block; + } + + setBlock(block: XmlSchemaDerivationMethod) { + this.block = block; + } + + getBlockResolved() { + return this.blockResolved; + } + + getContentModel() { + return this.contentModel; + } + + setContentModel(contentModel: XmlSchemaContentModel) { + this.contentModel = contentModel; + } + + getContentType() { + return this.contentType; + } + + setContentType(contentType: XmlSchemaContentType) { + this.contentType = contentType; + } + + getContentTypeParticle() { + return this.particleType; + } + + isAbstract() { + return this._isAbstract; + } + + setAbstract(b: boolean) { + this._isAbstract = b; + } + + getParticle() { + return this.particle; + } + + setParticle(particle: XmlSchemaParticle | null) { + this.particle = particle; + } + + /** + * Return the QName of the base schema type, if any, as defined in the content model. + */ + getBaseSchemaTypeName() { + const model = this.getContentModel(); + if (model == null) { + return null; + } + const content = model.getContent(); + if (content == null) { + return null; + } + + if (content instanceof XmlSchemaComplexContentExtension) { + return (content as XmlSchemaComplexContentExtension).getBaseTypeName(); + } + if (content instanceof XmlSchemaComplexContentRestriction) { + return (content as XmlSchemaComplexContentRestriction).getBaseTypeName(); + } + return null; + } + + setAttributeWildcard(attributeWildcard: XmlSchemaAnyAttribute) { + this.attributeWildcard = attributeWildcard; + } + + setAttributes(attributes: XmlSchemaAttributeOrGroupRef[]) { + this.attributes = attributes; + } + + setBlockResolved(blockResolved: XmlSchemaDerivationMethod) { + this.blockResolved = blockResolved; + } + + setParticleType(particleType: XmlSchemaParticle) { + this.particleType = particleType; + } + + getParticleType() { + return this.particleType; + } +} diff --git a/packages/xml-schema-ts/src/constants.ts b/packages/xml-schema-ts/src/constants.ts new file mode 100644 index 000000000..1e580f874 --- /dev/null +++ b/packages/xml-schema-ts/src/constants.ts @@ -0,0 +1,83 @@ +import { QName } from './QName'; + +export const DEFAULT_NS_PREFIX = ''; +export const NULL_NS_URI = ''; + +// +// Schema Namespaces +// +export const URI_2001_SCHEMA_XSD = 'http://www.w3.org/2001/XMLSchema'; +export const URI_2001_SCHEMA_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; +export const XML_NS_PREFIX = 'xml'; +export const XML_NS_URI = 'http://www.w3.org/XML/1998/namespace'; +export const XMLNS_ATTRIBUTE = 'xmlns'; +export const XMLNS_ATTRIBUTE_NS_URI = 'http://www.w3.org/2000/xmlns/'; +export const XMLNS_PREFIX = 'xml'; +export const XMLNS_URI = 'http://www.w3.org/XML/1998/namespace'; +export const XSD_ANY = new QName(URI_2001_SCHEMA_XSD, 'any'); +export const XSD_ANYATOMICTYPE = new QName(URI_2001_SCHEMA_XSD, 'anyAtomicType'); +export const XSD_ANYSIMPLETYPE = new QName(URI_2001_SCHEMA_XSD, 'anySimpleType'); +export const XSD_ANYTYPE = new QName(URI_2001_SCHEMA_XSD, 'anyType'); +export const XSD_ANYURI = new QName(URI_2001_SCHEMA_XSD, 'anyURI'); +export const XSD_BASE64 = new QName(URI_2001_SCHEMA_XSD, 'base64Binary'); +export const XSD_BOOLEAN = new QName(URI_2001_SCHEMA_XSD, 'boolean'); +export const XSD_BYTE = new QName(URI_2001_SCHEMA_XSD, 'byte'); +export const XSD_DATE = new QName(URI_2001_SCHEMA_XSD, 'date'); +export const XSD_DATETIME = new QName(URI_2001_SCHEMA_XSD, 'dateTime'); +export const XSD_DAY = new QName(URI_2001_SCHEMA_XSD, 'gDay'); +export const XSD_DECIMAL = new QName(URI_2001_SCHEMA_XSD, 'decimal'); + +export const XSD_DOUBLE = new QName(URI_2001_SCHEMA_XSD, 'double'); +export const XSD_DURATION = new QName(URI_2001_SCHEMA_XSD, 'duration'); + +export const XSD_ENTITIES = new QName(URI_2001_SCHEMA_XSD, 'ENTITIES'); +export const XSD_ENTITY = new QName(URI_2001_SCHEMA_XSD, 'ENTITY'); +export const XSD_FLOAT = new QName(URI_2001_SCHEMA_XSD, 'float'); +export const XSD_HEXBIN = new QName(URI_2001_SCHEMA_XSD, 'hexBinary'); +export const XSD_ID = new QName(URI_2001_SCHEMA_XSD, 'ID'); +export const XSD_IDREF = new QName(URI_2001_SCHEMA_XSD, 'IDREF'); +export const XSD_IDREFS = new QName(URI_2001_SCHEMA_XSD, 'IDREFS'); +export const XSD_INT = new QName(URI_2001_SCHEMA_XSD, 'int'); + +export const XSD_INTEGER = new QName(URI_2001_SCHEMA_XSD, 'integer'); +export const XSD_LANGUAGE = new QName(URI_2001_SCHEMA_XSD, 'language'); +export const XSD_LONG = new QName(URI_2001_SCHEMA_XSD, 'long'); +export const XSD_MONTH = new QName(URI_2001_SCHEMA_XSD, 'gMonth'); +export const XSD_MONTHDAY = new QName(URI_2001_SCHEMA_XSD, 'gMonthDay'); +export const XSD_NAME = new QName(URI_2001_SCHEMA_XSD, 'Name'); + +export const XSD_NCNAME = new QName(URI_2001_SCHEMA_XSD, 'NCName'); +export const XSD_NEGATIVEINTEGER = new QName(URI_2001_SCHEMA_XSD, 'negativeInteger'); +export const XSD_NMTOKEN = new QName(URI_2001_SCHEMA_XSD, 'NMTOKEN'); +export const XSD_NMTOKENS = new QName(URI_2001_SCHEMA_XSD, 'NMTOKENS'); +export const XSD_NONNEGATIVEINTEGER = new QName(URI_2001_SCHEMA_XSD, 'nonNegativeInteger'); +export const XSD_NONPOSITIVEINTEGER = new QName(URI_2001_SCHEMA_XSD, 'nonPositiveInteger'); +export const XSD_NORMALIZEDSTRING = new QName(URI_2001_SCHEMA_XSD, 'normalizedString'); +export const XSD_NOTATION = new QName(URI_2001_SCHEMA_XSD, 'NOTATION'); +export const XSD_POSITIVEINTEGER = new QName(URI_2001_SCHEMA_XSD, 'positiveInteger'); +export const XSD_QNAME = new QName(URI_2001_SCHEMA_XSD, 'QName'); +export const XSD_SCHEMA = new QName(URI_2001_SCHEMA_XSD, 'schema'); +export const XSD_SHORT = new QName(URI_2001_SCHEMA_XSD, 'short'); +// Define qnames for the all of the XSD and SOAP-ENC encodings +export const XSD_STRING = new QName(URI_2001_SCHEMA_XSD, 'string'); + +export const XSD_TIME = new QName(URI_2001_SCHEMA_XSD, 'time'); + +export const XSD_TOKEN = new QName(URI_2001_SCHEMA_XSD, 'token'); + +export const XSD_UNSIGNEDBYTE = new QName(URI_2001_SCHEMA_XSD, 'unsignedByte'); + +export const XSD_UNSIGNEDINT = new QName(URI_2001_SCHEMA_XSD, 'unsignedInt'); + +export const XSD_UNSIGNEDLONG = new QName(URI_2001_SCHEMA_XSD, 'unsignedLong'); + +export const XSD_UNSIGNEDSHORT = new QName(URI_2001_SCHEMA_XSD, 'unsignedShort'); + +export const XSD_YEAR = new QName(URI_2001_SCHEMA_XSD, 'gYear'); + +export const XSD_YEARMONTH = new QName(URI_2001_SCHEMA_XSD, 'gYearMonth'); + +export class MetaDataConstants { + static readonly EXTERNAL_ATTRIBUTES = 'EXTERNAL_ATTRIBUTES'; + static readonly EXTERNAL_ELEMENTS = 'EXTERNAL_ELEMENTS'; +} diff --git a/packages/xml-schema-ts/src/constraint/XmlSchemaIdentityConstraint.ts b/packages/xml-schema-ts/src/constraint/XmlSchemaIdentityConstraint.ts new file mode 100644 index 000000000..96ec9550d --- /dev/null +++ b/packages/xml-schema-ts/src/constraint/XmlSchemaIdentityConstraint.ts @@ -0,0 +1,34 @@ +import type { XmlSchemaXPath } from '../XmlSchemaXPath'; +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +export class XmlSchemaIdentityConstraint extends XmlSchemaAnnotated { + private fields: XmlSchemaXPath[] = []; + + private name: string | null = null; + + private selector: XmlSchemaXPath | null = null; + + getFields() { + return this.fields; + } + + getName() { + return this.name; + } + + getSelector() { + return this.selector; + } + + setName(name: string) { + this.name = name; + } + + setSelector(selector: XmlSchemaXPath) { + this.selector = selector; + } + + setFields(fields: XmlSchemaXPath[]) { + this.fields = fields; + } +} diff --git a/packages/xml-schema-ts/src/constraint/XmlSchemaKey.ts b/packages/xml-schema-ts/src/constraint/XmlSchemaKey.ts new file mode 100644 index 000000000..16bb16d17 --- /dev/null +++ b/packages/xml-schema-ts/src/constraint/XmlSchemaKey.ts @@ -0,0 +1,3 @@ +import { XmlSchemaIdentityConstraint } from './XmlSchemaIdentityConstraint'; + +export class XmlSchemaKey extends XmlSchemaIdentityConstraint {} diff --git a/packages/xml-schema-ts/src/constraint/XmlSchemaKeyref.ts b/packages/xml-schema-ts/src/constraint/XmlSchemaKeyref.ts new file mode 100644 index 000000000..5e0a0ed1b --- /dev/null +++ b/packages/xml-schema-ts/src/constraint/XmlSchemaKeyref.ts @@ -0,0 +1,14 @@ +import type { QName } from '../QName'; +import { XmlSchemaIdentityConstraint } from './XmlSchemaIdentityConstraint'; + +export class XmlSchemaKeyref extends XmlSchemaIdentityConstraint { + refer: QName | null = null; + + getRefer() { + return this.refer; + } + + setRefer(refer: QName) { + this.refer = refer; + } +} diff --git a/packages/xml-schema-ts/src/constraint/XmlSchemaUnique.ts b/packages/xml-schema-ts/src/constraint/XmlSchemaUnique.ts new file mode 100644 index 000000000..de67139ab --- /dev/null +++ b/packages/xml-schema-ts/src/constraint/XmlSchemaUnique.ts @@ -0,0 +1,3 @@ +import { XmlSchemaIdentityConstraint } from './XmlSchemaIdentityConstraint'; + +export class XmlSchemaUnique extends XmlSchemaIdentityConstraint {} diff --git a/packages/xml-schema-ts/src/extensions/DefaultExtensionDeserializer.ts b/packages/xml-schema-ts/src/extensions/DefaultExtensionDeserializer.ts new file mode 100644 index 000000000..0c0818822 --- /dev/null +++ b/packages/xml-schema-ts/src/extensions/DefaultExtensionDeserializer.ts @@ -0,0 +1,60 @@ +import type { ExtensionDeserializer } from './ExtensionDeserializer'; +import type { QName } from '../QName'; +import type { XmlSchemaObject } from '../XmlSchemaObject'; +import { MetaDataConstants } from '../constants'; + +/** + * Default deserializer. The action taken when there is nothing specific to be done would be to attach the raw + * element object as it is to the meta information map for an element or the raw attribute object + */ +export class DefaultExtensionDeserializer implements ExtensionDeserializer { + /** + * deserialize the given element + * + * @param schemaObject - Parent schema element + * @param name - the QName of the element/attribute to be deserialized. in the case where a deserializer + * is used to handle multiple elements/attributes this may be useful to determine the correct + * deserialization + * @param node - the raw DOM Node read from the source. This will be the extension element itself if for + * an element or the extension attribute object if it is an attribute + */ + deserialize(schemaObject: XmlSchemaObject, name: QName, node: Node) { + // we just attach the raw node either to the meta map of + // elements or the attributes + + let metaInfoMap = schemaObject.getMetaInfoMap(); + if (metaInfoMap == null) { + metaInfoMap = new Map(); + } + + if (node.nodeType == Node.ATTRIBUTE_NODE) { + let attribMap: Map; + if (metaInfoMap.has(MetaDataConstants.EXTERNAL_ATTRIBUTES)) { + attribMap = metaInfoMap.get(MetaDataConstants.EXTERNAL_ATTRIBUTES) as Map; + } else { + attribMap = new Map(); + metaInfoMap.set(MetaDataConstants.EXTERNAL_ATTRIBUTES, attribMap); + } + attribMap.set(name, node); + } else if (node.nodeType == Node.ELEMENT_NODE) { + let elementMap; + if (metaInfoMap.has(MetaDataConstants.EXTERNAL_ELEMENTS)) { + elementMap = metaInfoMap.get(MetaDataConstants.EXTERNAL_ELEMENTS) as Map; + } else { + elementMap = new Map(); + metaInfoMap.set(MetaDataConstants.EXTERNAL_ELEMENTS, elementMap); + } + elementMap.set(name, node); + } + + // subsequent processing takes place only if this map is not empty + if (metaInfoMap.size !== 0) { + const metaInfoMapFromSchemaElement = schemaObject.getMetaInfoMap(); + if (metaInfoMapFromSchemaElement == null) { + schemaObject.setMetaInfoMap(metaInfoMap); + } else { + metaInfoMap.forEach((value, key) => metaInfoMapFromSchemaElement.set(key, value)); + } + } + } +} diff --git a/packages/xml-schema-ts/src/extensions/DefaultExtensionSerializer.ts b/packages/xml-schema-ts/src/extensions/DefaultExtensionSerializer.ts new file mode 100644 index 000000000..f20543fc1 --- /dev/null +++ b/packages/xml-schema-ts/src/extensions/DefaultExtensionSerializer.ts @@ -0,0 +1,40 @@ +import type { ExtensionSerializer } from './ExtensionSerializer'; +import type { XmlSchemaObject } from '../XmlSchemaObject'; +import { MetaDataConstants } from '../constants'; + +/** + + */ +export class DefaultExtensionSerializer implements ExtensionSerializer { + /** + * serialize the given element + * + * @param schemaObject - Parent schema element + * @param _typeName - the class of the object to be serialized + * @param node - The DOM Node that is the parent of the serialzation + */ + serialize(schemaObject: XmlSchemaObject, _typeName: string, node: Node) { + // serialization is somewhat tricky in most cases hence this default serializer will + // do the exact reverse of the deserializer - look for any plain 'as is' items + // and attach them to the parent node. + // we just attach the raw node either to the meta map of + // elements or the attributes + const metaInfoMap = schemaObject.getMetaInfoMap(); + const parentDoc = node.ownerDocument; + if (metaInfoMap.has(MetaDataConstants.EXTERNAL_ATTRIBUTES)) { + const attribMap = metaInfoMap.get(MetaDataConstants.EXTERNAL_ATTRIBUTES) as Map; + for (const value of attribMap.values()) { + if (node.nodeType == Node.ELEMENT_NODE) { + (node as Element).setAttributeNodeNS(parentDoc!.importNode(value, true) as Attr); + } + } + } + + if (metaInfoMap.has(MetaDataConstants.EXTERNAL_ELEMENTS)) { + const elementMap = metaInfoMap.get(MetaDataConstants.EXTERNAL_ELEMENTS) as Map; + for (const value of elementMap.values()) { + node.appendChild(parentDoc!.importNode(value, true)); + } + } + } +} diff --git a/packages/xml-schema-ts/src/extensions/ExtensionDeserializer.ts b/packages/xml-schema-ts/src/extensions/ExtensionDeserializer.ts new file mode 100644 index 000000000..ab0ed4697 --- /dev/null +++ b/packages/xml-schema-ts/src/extensions/ExtensionDeserializer.ts @@ -0,0 +1,22 @@ +import { QName } from '../QName'; +import { XmlSchemaObject } from '../XmlSchemaObject'; + +/** + * Interface for the extension deserializer. The purpose of an instance of this is to deserialize the relevant + * attribute/element and perhaps generate a desired custom object. This custom object can be stored in the + * metadata map of the parent schema object. When to invoke a given deserializer is a decision taken by the + * extension registry + */ +export interface ExtensionDeserializer { + /** + * deserialize the given element + * + * @param schemaObject - Parent schema element + * @param name - the QName of the element/attribute to be deserialized. in the case where a deserializer + * is used to handle multiple elements/attributes this may be useful to determine the correct + * deserialization + * @param domNode - the raw DOM Node read from the source. This will be the extension element itself if + * for an element or the extension attribute object if it is an attribute + */ + deserialize(schemaObject: XmlSchemaObject, name: QName, domNode: Node): void; +} diff --git a/packages/xml-schema-ts/src/extensions/ExtensionRegistry.ts b/packages/xml-schema-ts/src/extensions/ExtensionRegistry.ts new file mode 100644 index 000000000..f6969dba8 --- /dev/null +++ b/packages/xml-schema-ts/src/extensions/ExtensionRegistry.ts @@ -0,0 +1,113 @@ +import { QName } from '../QName'; +import { XmlSchemaObject } from '../XmlSchemaObject'; +import { DefaultExtensionDeserializer } from './DefaultExtensionDeserializer'; +import { DefaultExtensionSerializer } from './DefaultExtensionSerializer'; +import { ExtensionDeserializer } from './ExtensionDeserializer'; +import { ExtensionSerializer } from './ExtensionSerializer'; + +export class ExtensionRegistry { + /** + * Maps for the storage of extension serializers /deserializers + */ + private extensionSerializers = new Map(); + private extensionDeserializers = new Map(); + + /** + * Default serializer and serializer + */ + private defaultExtensionSerializer = new DefaultExtensionSerializer(); + private defaultExtensionDeserializer = new DefaultExtensionDeserializer(); + + getDefaultExtensionSerializer() { + return this.defaultExtensionSerializer; + } + + setDefaultExtensionSerializer(defaultExtensionSerializer: ExtensionSerializer) { + this.defaultExtensionSerializer = defaultExtensionSerializer; + } + + getDefaultExtensionDeserializer() { + return this.defaultExtensionDeserializer; + } + + setDefaultExtensionDeserializer(defaultExtensionDeserializer: ExtensionDeserializer) { + this.defaultExtensionDeserializer = defaultExtensionDeserializer; + } + + /** + * Register a deserializer with a QName + * + * @param name + * @param deserializer + */ + registerDeserializer(name: QName, deserializer: ExtensionDeserializer) { + this.extensionDeserializers.set(name, deserializer); + } + + /** + * Register a serializer with a Class + * + * @param classOfType - the class of the object that would be serialized + * @param serializer - an instance of the deserializer + */ + registerSerializer(typeName: string, serializer: ExtensionSerializer) { + this.extensionSerializers.set(typeName, serializer); + } + + /** + * remove the registration for a serializer with a Class + * + * @param classOfType - the Class of the element/attribute the serializer is associated with + */ + unregisterSerializer(typeName: string) { + this.extensionSerializers.delete(typeName); + } + + /** + * remove the registration for a deserializer with a QName + * + * @param name - the QName fo the element that the deserializer is associated with + */ + unregisterDeserializer(name: QName) { + this.extensionDeserializers.delete(name); + } + + /** + * Serialize a given extension element + * + * @param parentSchemaObject - the parent schema object. This is what would contain the extension object, + * probably in side its meta information map + * @param typeName - The class of type to be serialized + * @param node - the parent DOM Node that will ultimately be serialized. The XMLSchema serialization + * mechanism is to create a DOM tree first and serialize it + */ + serializeExtension(parentSchemaObject: XmlSchemaObject, typeName: string, node: Node) { + const serializerObject = this.extensionSerializers.get(typeName); + if (serializerObject != null) { + serializerObject.serialize(parentSchemaObject, typeName, node); + } else if (this.defaultExtensionSerializer != null) { + this.defaultExtensionSerializer.serialize(parentSchemaObject, typeName, node); + } + } + + /** + * Deserialize a given extension element + * + * @param parentSchemaObject - the parent schema object. This is anticipated to be created already and the + * relevant object would contain the extension object, probably in side its meta information + * map + * @param name - The qname of the element/attribute to be deserialized. This will be used to search for + * the extension as well as by the deserializer if a single deserializer is registered against + * a number of qnames + * @param rawNode - the raw DOM Node read from the source. This will be the extension element itself if + * for an element or extension attribute itself in case of an attribute + */ + deserializeExtension(parentSchemaObject: XmlSchemaObject, name: QName, rawNode: Node) { + const deserializerObject = this.extensionDeserializers.get(name); + if (deserializerObject != null) { + deserializerObject.deserialize(parentSchemaObject, name, rawNode); + } else if (this.defaultExtensionDeserializer != null) { + this.defaultExtensionDeserializer.deserialize(parentSchemaObject, name, rawNode); + } + } +} diff --git a/packages/xml-schema-ts/src/extensions/ExtensionSerializer.ts b/packages/xml-schema-ts/src/extensions/ExtensionSerializer.ts new file mode 100644 index 000000000..24f387c6a --- /dev/null +++ b/packages/xml-schema-ts/src/extensions/ExtensionSerializer.ts @@ -0,0 +1,19 @@ +import { XmlSchemaObject } from '../XmlSchemaObject'; + +/** + * Interface for the extension serializer. The purpose of an instance of this is to serialize the relevant + * custom object and generate attribute/elementa desired . This custom object may be stored in the metadata + * map of the parent schema object. When to invoke a given serializer is a decision taken by the extension + * registry + */ +export interface ExtensionSerializer { + /** + * serialize the given element + * + * @param schemaObject - Parent schema object.contains the extension to be serialized + * @param typeName - The class of type to be serialized + * @param domNode - the parent DOM Node that will ultimately be serialized. The XMLSchema serialization + * mechanism is to create a DOM tree first and serialize it + */ + serialize(schemaObject: XmlSchemaObject, typeName: string, domNode: Node): void; +} diff --git a/packages/xml-schema-ts/src/external/XmlSchemaExternal.ts b/packages/xml-schema-ts/src/external/XmlSchemaExternal.ts new file mode 100644 index 000000000..5c4bd4e57 --- /dev/null +++ b/packages/xml-schema-ts/src/external/XmlSchemaExternal.ts @@ -0,0 +1,39 @@ +import type { XmlSchema } from '../XmlSchema'; +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +export abstract class XmlSchemaExternal extends XmlSchemaAnnotated { + schema: XmlSchema | null = null; + schemaLocation: string | null = null; + + /** + * Creates new XmlSchemaExternal + */ + protected constructor(parent: XmlSchema) { + super(); + const fParent = parent; + fParent.getExternals().push(this); + fParent.getItems().push(this); + } + + public getSchema() { + return this.schema; + } + + /** + * Store a reference to an XmlSchema corresponding to this item. This only + * case in which this will be read is if you ask the XmlSchemaSerializer + * to serialize external schemas. + * @param sc schema reference + */ + public setSchema(sc: XmlSchema) { + this.schema = sc; + } + + public getSchemaLocation() { + return this.schemaLocation; + } + + public setSchemaLocation(schemaLocation: string) { + this.schemaLocation = schemaLocation; + } +} diff --git a/packages/xml-schema-ts/src/external/XmlSchemaImport.ts b/packages/xml-schema-ts/src/external/XmlSchemaImport.ts new file mode 100644 index 000000000..fb5928512 --- /dev/null +++ b/packages/xml-schema-ts/src/external/XmlSchemaImport.ts @@ -0,0 +1,21 @@ +import { XmlSchemaExternal } from './XmlSchemaExternal'; +import type { XmlSchema } from '../XmlSchema'; + +export class XmlSchemaImport extends XmlSchemaExternal { + namespace: string | null = null; + + /** + * Creates new XmlSchemaImport + */ + constructor(parent: XmlSchema) { + super(parent); + } + + getNamespace(): string | null { + return this.namespace; + } + + setNamespace(namespace: string) { + this.namespace = namespace; + } +} diff --git a/packages/xml-schema-ts/src/external/XmlSchemaInclude.ts b/packages/xml-schema-ts/src/external/XmlSchemaInclude.ts new file mode 100644 index 000000000..d4f8979ff --- /dev/null +++ b/packages/xml-schema-ts/src/external/XmlSchemaInclude.ts @@ -0,0 +1,11 @@ +import { XmlSchemaExternal } from './XmlSchemaExternal'; +import type { XmlSchema } from '../XmlSchema'; + +export class XmlSchemaInclude extends XmlSchemaExternal { + /** + * Creates new XmlSchemaInclude + */ + constructor(parent: XmlSchema) { + super(parent); + } +} diff --git a/packages/xml-schema-ts/src/external/XmlSchemaRedefine.ts b/packages/xml-schema-ts/src/external/XmlSchemaRedefine.ts new file mode 100644 index 000000000..9294636df --- /dev/null +++ b/packages/xml-schema-ts/src/external/XmlSchemaRedefine.ts @@ -0,0 +1,43 @@ +import type { QName } from '../QName'; +import type { XmlSchemaGroup } from '../XmlSchemaGroup'; +import type { XmlSchemaType } from '../XmlSchemaType'; +import type { XmlSchemaAttributeGroup } from '../attribute/XmlSchemaAttributeGroup'; +import type { XmlSchemaObject } from '../XmlSchemaObject'; +import type { XmlSchema } from '../XmlSchema'; +import { XmlSchemaExternal } from './XmlSchemaExternal'; + +/** + * Allows simple and complex types, groups, and attribute groups from external schema files to be redefined in + * the current schema. This class provides versioning for the schema elements. Represents the World Wide Web + * Consortium (W3C) redefine element. + */ +export class XmlSchemaRedefine extends XmlSchemaExternal { + private attributeGroups = new Map(); + private groups = new Map(); + private schemaTypes = new Map(); + + private items: XmlSchemaObject[] = []; + + /** + * Creates new XmlSchemaRedefine + */ + constructor(parent: XmlSchema) { + super(parent); + } + + getAttributeGroups() { + return this.attributeGroups; + } + + getGroups() { + return this.groups; + } + + getItems() { + return this.items; + } + + getSchemaTypes() { + return this.schemaTypes; + } +} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaEnumerationFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaEnumerationFacet.ts new file mode 100644 index 000000000..3171cf5ae --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaEnumerationFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaEnumerationFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaFacet.ts new file mode 100644 index 000000000..f7099129f --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaFacet.ts @@ -0,0 +1,25 @@ +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +export abstract class XmlSchemaFacet extends XmlSchemaAnnotated { + fixed: boolean | undefined; + value: object | string | number | bigint | null; + + constructor(value?: object | string | number | bigint | null, fixed?: boolean) { + super(); + this.value = value || null; + this.fixed = fixed; + } + + getValue(): object | string | number | bigint | null { + return this.value; + } + isFixed(): boolean { + return !!this.fixed && this.fixed; + } + setFixed(fixed: boolean) { + this.fixed = fixed; + } + setValue(value: object | string | number | bigint | null) { + this.value = value; + } +} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaFacetConstructor.ts b/packages/xml-schema-ts/src/facet/XmlSchemaFacetConstructor.ts new file mode 100644 index 000000000..59cf844fc --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaFacetConstructor.ts @@ -0,0 +1,57 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; +import { XmlSchemaEnumerationFacet } from './XmlSchemaEnumerationFacet'; +import { XmlSchemaFractionDigitsFacet } from './XmlSchemaFractionDigitsFacet'; +import { XmlSchemaLengthFacet } from './XmlSchemaLengthFacet'; +import { XmlSchemaMaxExclusiveFacet } from './XmlSchemaMaxExclusiveFacet'; +import { XmlSchemaMaxInclusiveFacet } from './XmlSchemaMaxInclusiveFacet'; +import { XmlSchemaMaxLengthFacet } from './XmlSchemaMaxLengthFacet'; +import { XmlSchemaMinLengthFacet } from './XmlSchemaMinLengthFacet'; +import { XmlSchemaMinExclusiveFacet } from './XmlSchemaMinExclusiveFacet'; +import { XmlSchemaMinInclusiveFacet } from './XmlSchemaMinInclusiveFacet'; +import { XmlSchemaPatternFacet } from './XmlSchemaPatternFacet'; +import { XmlSchemaTotalDigitsFacet } from './XmlSchemaTotalDigitsFacet'; +import { XmlSchemaWhiteSpaceFacet } from './XmlSchemaWhiteSpaceFacet'; + +export class XmlSchemaFacetConstructor { + static construct(el: Element) { + const name = el.localName; + let fixed = false; + if (el.getAttribute('fixed') === 'true') { + fixed = true; + } + let facet: XmlSchemaFacet; + if ('enumeration' === name) { + facet = new XmlSchemaEnumerationFacet(); + } else if ('fractionDigits' === name) { + facet = new XmlSchemaFractionDigitsFacet(); + } else if ('length' === name) { + facet = new XmlSchemaLengthFacet(); + } else if ('maxExclusive' === name) { + facet = new XmlSchemaMaxExclusiveFacet(); + } else if ('maxInclusive' === name) { + facet = new XmlSchemaMaxInclusiveFacet(); + } else if ('maxLength' === name) { + facet = new XmlSchemaMaxLengthFacet(); + } else if ('minLength' === name) { + facet = new XmlSchemaMinLengthFacet(); + } else if ('minExclusive' === name) { + facet = new XmlSchemaMinExclusiveFacet(); + } else if ('minInclusive' === name) { + facet = new XmlSchemaMinInclusiveFacet(); + } else if ('pattern' === name) { + facet = new XmlSchemaPatternFacet(); + } else if ('totalDigits' === name) { + facet = new XmlSchemaTotalDigitsFacet(); + } else if ('whiteSpace' === name) { + facet = new XmlSchemaWhiteSpaceFacet(); + } else { + throw new Error('Incorrect facet with name "' + name + '" found.'); + } + if (el.hasAttribute('id')) { + facet.setId(el.getAttribute('id')); + } + facet.setFixed(fixed); + facet.setValue(el.getAttribute('value')); + return facet; + } +} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaFractionDigitsFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaFractionDigitsFacet.ts new file mode 100644 index 000000000..a219293c8 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaFractionDigitsFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaNumericFacet } from './XmlSchemaNumericFacet'; + +export class XmlSchemaFractionDigitsFacet extends XmlSchemaNumericFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaLengthFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaLengthFacet.ts new file mode 100644 index 000000000..ce929a29f --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaLengthFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaLengthFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMaxExclusiveFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMaxExclusiveFacet.ts new file mode 100644 index 000000000..d1d793e92 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMaxExclusiveFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMaxExclusiveFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMaxInclusiveFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMaxInclusiveFacet.ts new file mode 100644 index 000000000..92d263824 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMaxInclusiveFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMaxInclusiveFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMaxLengthFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMaxLengthFacet.ts new file mode 100644 index 000000000..3fd56f34a --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMaxLengthFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMaxLengthFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMinExclusiveFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMinExclusiveFacet.ts new file mode 100644 index 000000000..c10a9f5d4 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMinExclusiveFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMinExclusiveFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMinInclusiveFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMinInclusiveFacet.ts new file mode 100644 index 000000000..ca8056018 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMinInclusiveFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMinInclusiveFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaMinLengthFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaMinLengthFacet.ts new file mode 100644 index 000000000..ca9218fea --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaMinLengthFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaMinLengthFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaNumericFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaNumericFacet.ts new file mode 100644 index 000000000..d66a2be75 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaNumericFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export abstract class XmlSchemaNumericFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaPatternFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaPatternFacet.ts new file mode 100644 index 000000000..61b6ef521 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaPatternFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaPatternFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaTotalDigitsFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaTotalDigitsFacet.ts new file mode 100644 index 000000000..39c540a86 --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaTotalDigitsFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaTotalDigitsFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/facet/XmlSchemaWhiteSpaceFacet.ts b/packages/xml-schema-ts/src/facet/XmlSchemaWhiteSpaceFacet.ts new file mode 100644 index 000000000..96371ed0f --- /dev/null +++ b/packages/xml-schema-ts/src/facet/XmlSchemaWhiteSpaceFacet.ts @@ -0,0 +1,3 @@ +import { XmlSchemaFacet } from './XmlSchemaFacet'; + +export class XmlSchemaWhiteSpaceFacet extends XmlSchemaFacet {} diff --git a/packages/xml-schema-ts/src/index.ts b/packages/xml-schema-ts/src/index.ts new file mode 100644 index 000000000..fa40c2691 --- /dev/null +++ b/packages/xml-schema-ts/src/index.ts @@ -0,0 +1,31 @@ +export { QNameMap } from './utils/ObjectMap'; +export { XmlSchema } from './XmlSchema'; +export { XmlSchemaAll } from './particle/XmlSchemaAll'; +export { XmlSchemaAny } from './particle/XmlSchemaAny'; +export { XmlSchemaAllMember } from './particle/XmlSchemaAllMember'; +export { XmlSchemaAttribute } from './attribute/XmlSchemaAttribute'; +export { XmlSchemaAttributeGroup } from './attribute/XmlSchemaAttributeGroup'; +export { XmlSchemaAttributeGroupMember } from './attribute/XmlSchemaAttributeGroupMember'; +export { XmlSchemaAttributeGroupRef } from './attribute/XmlSchemaAttributeGroupRef'; +export { XmlSchemaAttributeOrGroupRef } from './attribute/XmlSchemaAttributeOrGroupRef'; +export { XmlSchemaChoice } from './particle/XmlSchemaChoice'; +export { XmlSchemaChoiceMember } from './particle/XmlSchemaChoiceMember'; +export { XmlSchemaCollection } from './XmlSchemaCollection'; +export { XmlSchemaComplexContentExtension } from './complex/XmlSchemaComplexContentExtension'; +export { XmlSchemaComplexContentRestriction } from './complex/XmlSchemaComplexContentRestriction'; +export { XmlSchemaComplexType } from './complex/XmlSchemaComplexType'; +export { XmlSchemaContentModel } from './XmlSchemaContentModel'; +export { XmlSchemaElement } from './particle/XmlSchemaElement'; +export { XmlSchemaGroup } from './XmlSchemaGroup'; +export { XmlSchemaGroupParticle } from './particle/XmlSchemaGroupParticle'; +export { XmlSchemaGroupRef } from './particle/XmlSchemaGroupRef'; +export { XmlSchemaObjectBase } from './utils/XmlSchemaObjectBase'; +export { XmlSchemaParticle } from './particle/XmlSchemaParticle'; +export { XmlSchemaRef } from './utils/XmlSchemaRef'; +export { XmlSchemaSequence } from './particle/XmlSchemaSequence'; +export { XmlSchemaSequenceMember } from './particle/XmlSchemaSequenceMember'; +export { XmlSchemaSimpleContentExtension } from './simple/XmlSchemaSimpleContentExtension'; +export { XmlSchemaSimpleContentRestriction } from './simple/XmlSchemaSimpleContentRestriction'; +export { XmlSchemaSimpleType } from './simple/XmlSchemaSimpleType'; +export { XmlSchemaType } from './XmlSchemaType'; +export { XmlSchemaUse } from './XmlSchemaUse'; diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaAll.ts b/packages/xml-schema-ts/src/particle/XmlSchemaAll.ts new file mode 100644 index 000000000..adf607f50 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaAll.ts @@ -0,0 +1,10 @@ +import { XmlSchemaAllMember } from './XmlSchemaAllMember'; +import { XmlSchemaGroupParticle } from './XmlSchemaGroupParticle'; + +export class XmlSchemaAll extends XmlSchemaGroupParticle { + private items: XmlSchemaAllMember[] = []; + + public getItems() { + return this.items; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaAllMember.ts b/packages/xml-schema-ts/src/particle/XmlSchemaAllMember.ts new file mode 100644 index 000000000..336959901 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaAllMember.ts @@ -0,0 +1,4 @@ +import { XmlSchemaObjectBase } from '../utils/XmlSchemaObjectBase'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaAllMember extends XmlSchemaObjectBase {} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaAny.ts b/packages/xml-schema-ts/src/particle/XmlSchemaAny.ts new file mode 100644 index 000000000..c34dd9a26 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaAny.ts @@ -0,0 +1,64 @@ +/** + * Enables any element from the specified namespace or namespaces to appear in the containing complexType + * element. Represents the World Wide Web Consortium (W3C) any element. + */ +import type { XmlSchemaAllMember } from './XmlSchemaAllMember'; +import type { XmlSchemaChoiceMember } from './XmlSchemaChoiceMember'; +import type { XmlSchemaSequenceMember } from './XmlSchemaSequenceMember'; +import { XmlSchemaContentProcessing } from '../XmlSchemaContentProcessing'; +import { XmlSchemaParticle } from './XmlSchemaParticle'; + +export class XmlSchemaAny + extends XmlSchemaParticle + implements XmlSchemaChoiceMember, XmlSchemaSequenceMember, XmlSchemaAllMember +{ + /** + * Namespaces containing the elements that can be used. + */ + private namespace: string | null = null; + private processContent: XmlSchemaContentProcessing = XmlSchemaContentProcessing.NONE; + private targetNamespace: string | null = null; + + getNamespace() { + return this.namespace; + } + + setNamespace(namespace: string | null) { + this.namespace = namespace; + } + + getProcessContent() { + return this.processContent; + } + + setProcessContent(processContent: XmlSchemaContentProcessing) { + this.processContent = processContent; + } + + /** + * {@link #getNamespace()} returns the namespace or set of namespaces + * that this wildcard element is valid for. The target namespaces may + * include ##other, ##targetNamespace. The + * ##other directive means any namespace other than the + * schema's target namespace, while the ##targetNamespace + * directive means the element must be in the schema's target + * namespace. Resolving either of these requires knowledge of what + * the schema's target namespace is, which is returned by this method. + * + * @return The wildcard element's target namespace. + */ + getTargetNamespace() { + return this.targetNamespace; + } + + /** + * Sets the schema's target namespace. + * + * @param namespace The schema's target namespace. + * + * @see #getTargetNamespace() + */ + setTargetNamespace(namespace: string | null) { + this.targetNamespace = namespace; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaChoice.ts b/packages/xml-schema-ts/src/particle/XmlSchemaChoice.ts new file mode 100644 index 000000000..87066cdbc --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaChoice.ts @@ -0,0 +1,17 @@ +import { XmlSchemaChoiceMember } from './XmlSchemaChoiceMember'; +import { XmlSchemaGroupParticle } from './XmlSchemaGroupParticle'; +import { XmlSchemaSequenceMember } from './XmlSchemaSequenceMember'; + +/** + * Allows only one of its children to appear in an instance. Represents the World Wide Web Consortium (W3C) + * choice (compositor) element. + * + * This can contain any of (element|group|choice|sequence|any)*. + */ +export class XmlSchemaChoice extends XmlSchemaGroupParticle implements XmlSchemaChoiceMember, XmlSchemaSequenceMember { + private items: XmlSchemaChoiceMember[] = []; + + public getItems() { + return this.items; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaChoiceMember.ts b/packages/xml-schema-ts/src/particle/XmlSchemaChoiceMember.ts new file mode 100644 index 000000000..fe661a4da --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaChoiceMember.ts @@ -0,0 +1,4 @@ +import { XmlSchemaObjectBase } from '../utils/XmlSchemaObjectBase'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaChoiceMember extends XmlSchemaObjectBase {} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaElement.ts b/packages/xml-schema-ts/src/particle/XmlSchemaElement.ts new file mode 100644 index 000000000..3c998e4c0 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaElement.ts @@ -0,0 +1,248 @@ +import type { QName } from '../QName'; +import type { TypeReceiver } from '../TypeReceiver'; +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaAllMember } from './XmlSchemaAllMember'; +import type { XmlSchemaChoiceMember } from './XmlSchemaChoiceMember'; +import type { XmlSchemaForm } from '../XmlSchemaForm'; +import type { XmlSchemaIdentityConstraint } from '../constraint/XmlSchemaIdentityConstraint'; +import type { XmlSchemaItemWithRef } from '../XmlSchemaItemWithRef'; +import type { XmlSchemaSequenceMember } from './XmlSchemaSequenceMember'; +import type { XmlSchemaType } from '../XmlSchemaType'; +import type { XmlSchemaNamedWithForm } from '../utils/XmlSchemaNamedWithForm'; + +import { XmlSchemaNamedWithFormImpl } from '../utils/XmlSchemaNamedWithFormImpl'; +import { XmlSchemaRef } from '../utils/XmlSchemaRef'; +import { XmlSchemaNamedType } from '../utils/XmlSchemaNamedType'; +import { XmlSchemaParticle } from './XmlSchemaParticle'; +import { XmlSchemaDerivationMethod } from '../XmlSchemaDerivationMethod'; + +export class XmlSchemaElement + extends XmlSchemaParticle + implements + TypeReceiver, + XmlSchemaNamedWithForm, + XmlSchemaChoiceMember, + XmlSchemaSequenceMember, + XmlSchemaAllMember, + XmlSchemaItemWithRef +{ + /** + * Attribute used to block a type derivation. + */ + private block: XmlSchemaDerivationMethod; + + private constraints: XmlSchemaIdentityConstraint[] = []; + + /** + * Provides the default value of the element if its content is a simple type or the element's content is + * textOnly. + */ + defaultValue: string | null = null; + fixedValue: string | null = null; + + finalDerivation: XmlSchemaDerivationMethod; + + abstractElement: boolean; + nillable: boolean; + ref: XmlSchemaRef; + + /** + * Returns the type of the element. This can either be a complex type or a simple type. + */ + private schemaType: XmlSchemaType | null = null; + + /** + * QName of a built-in data type defined in this schema or another schema indicated by the specified + * namespace. + */ + private schemaTypeName: QName | null = null; + + /** + * QName of an element that can be a substitute for this element. + */ + private substitutionGroup: QName | null = null; + + private namedDelegate: XmlSchemaNamedWithFormImpl; + + /** + * Creates new XmlSchemaElement + */ + constructor(parentSchema: XmlSchema, topLevel: boolean) { + super(); + this.namedDelegate = new XmlSchemaNamedWithFormImpl(parentSchema, topLevel, true); + this.ref = new XmlSchemaRef(parentSchema, XmlSchemaNamedType.XmlSchemaElement); + this.namedDelegate.setRefObject(this.ref); + this.ref.setNamedObject(this.namedDelegate); + + this.abstractElement = false; + this.nillable = false; + this.finalDerivation = XmlSchemaDerivationMethod.NONE; + this.block = XmlSchemaDerivationMethod.NONE; + const fParentSchema = parentSchema; + if (topLevel) { + fParentSchema.getItems().push(this); + } + } + + /** + * Returns a collection of constraints on the element. + */ + getConstraints() { + return this.constraints; + } + + getDefaultValue() { + return this.defaultValue; + } + + setDefaultValue(defaultValue: string | null) { + this.defaultValue = defaultValue; + } + + getBlock(): XmlSchemaDerivationMethod { + return this.block; + } + + setBlock(block: XmlSchemaDerivationMethod) { + this.block = block; + } + + getFinal(): XmlSchemaDerivationMethod { + return this.finalDerivation; + } + + setFinal(finalDerivationValue: XmlSchemaDerivationMethod) { + this.finalDerivation = finalDerivationValue; + } + + getFixedValue() { + return this.fixedValue; + } + + setFixedValue(fixedValue: string | null) { + this.fixedValue = fixedValue; + } + + isAbstract() { + return this.abstractElement; + } + + setAbstract(isAbstract: boolean) { + this.abstractElement = isAbstract; + } + + isNillable() { + return this.nillable; + } + + setNillable(isNillable: boolean) { + this.nillable = isNillable; + } + + getRef() { + return this.ref; + } + + getSchemaType() { + return this.schemaType; + } + + setSchemaType(schemaType: XmlSchemaType | null) { + this.schemaType = schemaType; + } + + getSchemaTypeName() { + return this.schemaTypeName; + } + + setSchemaTypeName(schemaTypeName: QName | null) { + this.schemaTypeName = schemaTypeName; + } + + getSubstitutionGroup() { + return this.substitutionGroup; + } + + setSubstitutionGroup(substitutionGroup: QName) { + this.substitutionGroup = substitutionGroup; + } + + setType(type: XmlSchemaType) { + this.schemaType = type; + } + + getName() { + return this.namedDelegate.getName(); + } + + getParent() { + return this.namedDelegate.getParent(); + } + + getQName() { + return this.namedDelegate.getQName(); + } + + isAnonymous() { + return this.namedDelegate.isAnonymous(); + } + + isTopLevel() { + return this.namedDelegate.isTopLevel(); + } + + setName(name: string | null) { + const fName = name; + if (this.isTopLevel() && this.getName() != null) { + this.getParent().getElements().delete(this.getQName()!); + } + this.namedDelegate.setName(fName); + if (this.namedDelegate.isTopLevel()) { + this.getParent().getElements().set(this.getQName()!, this); + } + } + + getForm() { + return this.namedDelegate.getForm(); + } + + isFormSpecified() { + return this.namedDelegate.isFormSpecified(); + } + + setForm(form: XmlSchemaForm) { + this.namedDelegate.setForm(form); + } + + getWireName() { + return this.namedDelegate.getWireName(); + } + + setFinalDerivation(finalDerivation: XmlSchemaDerivationMethod) { + this.finalDerivation = finalDerivation; + } + + getFinalDerivation() { + return this.finalDerivation; + } + + setAbstractElement(abstractElement: boolean) { + this.abstractElement = abstractElement; + } + + isAbstractElement() { + return this.abstractElement; + } + + isRef() { + return this.ref.getTargetQName() != null; + } + + getTargetQName() { + return this.ref.getTargetQName(); + } + + getRefBase() { + return this.ref; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaGroupParticle.ts b/packages/xml-schema-ts/src/particle/XmlSchemaGroupParticle.ts new file mode 100644 index 000000000..a729ec0b3 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaGroupParticle.ts @@ -0,0 +1,6 @@ +import { XmlSchemaParticle } from './XmlSchemaParticle'; + +/** + * Common type for items that can serve as the particle of a group. + */ +export abstract class XmlSchemaGroupParticle extends XmlSchemaParticle {} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaGroupRef.ts b/packages/xml-schema-ts/src/particle/XmlSchemaGroupRef.ts new file mode 100644 index 000000000..c3af24e22 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaGroupRef.ts @@ -0,0 +1,35 @@ +import type { QName } from '../QName'; +import type { XmlSchemaGroupParticle } from './XmlSchemaGroupParticle'; +import type { XmlSchemaAllMember } from './XmlSchemaAllMember'; +import type { XmlSchemaChoiceMember } from './XmlSchemaChoiceMember'; +import type { XmlSchemaSequenceMember } from './XmlSchemaSequenceMember'; +import { XmlSchemaParticle } from './XmlSchemaParticle'; + +/** + * Class used within complex types that defines the reference to groups defined at the schema level. + * Represents the World Wide Web Consortium (W3C) group element with ref attribute. + */ +export class XmlSchemaGroupRef + extends XmlSchemaParticle + implements XmlSchemaSequenceMember, XmlSchemaChoiceMember, XmlSchemaAllMember +{ + private particle: XmlSchemaGroupParticle | null = null; + + private refName: QName | null = null; + + getParticle() { + return this.particle; + } + + getRefName() { + return this.refName; + } + + setRefName(refName: QName) { + this.refName = refName; + } + + setParticle(particle: XmlSchemaGroupParticle) { + this.particle = particle; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaParticle.ts b/packages/xml-schema-ts/src/particle/XmlSchemaParticle.ts new file mode 100644 index 000000000..2394f4e24 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaParticle.ts @@ -0,0 +1,25 @@ +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +export abstract class XmlSchemaParticle extends XmlSchemaAnnotated { + static readonly DEFAULT_MAX_OCCURS = 1; + static readonly DEFAULT_MIN_OCCURS = 1; + + private maxOccurs = XmlSchemaParticle.DEFAULT_MAX_OCCURS; + private minOccurs = XmlSchemaParticle.DEFAULT_MIN_OCCURS; + + setMaxOccurs(maxOccurs: number) { + this.maxOccurs = maxOccurs; + } + + getMaxOccurs() { + return this.maxOccurs; + } + + setMinOccurs(minOccurs: number) { + this.minOccurs = minOccurs; + } + + getMinOccurs() { + return this.minOccurs; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaSequence.ts b/packages/xml-schema-ts/src/particle/XmlSchemaSequence.ts new file mode 100644 index 000000000..79d0221f1 --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaSequence.ts @@ -0,0 +1,24 @@ +/** + * Requires the elements in the group to appear in the specified sequence within the containing element. + * Represents the World Wide Web Consortium (W3C) sequence (compositor) element. + * + * (element|group|choice|sequence|any) + */ +import { XmlSchemaSequenceMember } from './XmlSchemaSequenceMember'; +import { XmlSchemaChoiceMember } from './XmlSchemaChoiceMember'; +import { XmlSchemaGroupParticle } from './XmlSchemaGroupParticle'; + +export class XmlSchemaSequence + extends XmlSchemaGroupParticle + implements XmlSchemaChoiceMember, XmlSchemaSequenceMember +{ + private items: XmlSchemaSequenceMember[] = []; + + /** + * The elements contained within the compositor. Collection of XmlSchemaElement, XmlSchemaGroupRef, + * XmlSchemaChoice, XmlSchemaSequence, or XmlSchemaAny. + */ + getItems() { + return this.items; + } +} diff --git a/packages/xml-schema-ts/src/particle/XmlSchemaSequenceMember.ts b/packages/xml-schema-ts/src/particle/XmlSchemaSequenceMember.ts new file mode 100644 index 000000000..1f191026e --- /dev/null +++ b/packages/xml-schema-ts/src/particle/XmlSchemaSequenceMember.ts @@ -0,0 +1,4 @@ +import { XmlSchemaObjectBase } from '../utils/XmlSchemaObjectBase'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaSequenceMember extends XmlSchemaObjectBase {} diff --git a/packages/xml-schema-ts/src/resolver/CollectionURIResolver.ts b/packages/xml-schema-ts/src/resolver/CollectionURIResolver.ts new file mode 100644 index 000000000..0724f483d --- /dev/null +++ b/packages/xml-schema-ts/src/resolver/CollectionURIResolver.ts @@ -0,0 +1,7 @@ +import { URIResolver } from './URIResolver'; + +export interface CollectionURIResolver extends URIResolver { + setCollectionBaseURI(uri: string): void; + + getCollectionBaseURI(): string | undefined; +} diff --git a/packages/xml-schema-ts/src/resolver/DefaultURIResolver.ts b/packages/xml-schema-ts/src/resolver/DefaultURIResolver.ts new file mode 100644 index 000000000..7ad9a0418 --- /dev/null +++ b/packages/xml-schema-ts/src/resolver/DefaultURIResolver.ts @@ -0,0 +1,19 @@ +import { CollectionURIResolver } from './CollectionURIResolver'; + +export class DefaultURIResolver implements CollectionURIResolver { + private collectionBaseUri?: string; + + getCollectionBaseURI(): string | undefined { + return this.collectionBaseUri; + } + + resolveEntity(targetNamespace: string | null, schemaLocation: string, baseUri: string | null): string { + throw new Error( + `XML schema External entity resolution is not yet supported: [namespace:${targetNamespace}, schemaLocation:${schemaLocation}, baseUri:${baseUri}]`, + ); + } + + setCollectionBaseURI(uri: string): void { + this.collectionBaseUri = uri; + } +} diff --git a/packages/xml-schema-ts/src/resolver/URIResolver.ts b/packages/xml-schema-ts/src/resolver/URIResolver.ts new file mode 100644 index 000000000..9a50e10f5 --- /dev/null +++ b/packages/xml-schema-ts/src/resolver/URIResolver.ts @@ -0,0 +1,3 @@ +export interface URIResolver { + resolveEntity(targetNamespace: string | null, schemaLocation: string, baseUri: string | null): string; +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContent.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContent.ts new file mode 100644 index 000000000..0eb04b0ab --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContent.ts @@ -0,0 +1,19 @@ +/** + * Class for simple types and complex types with a simple content model. Represents the World Wide Web + * Consortium (W3C) simpleContent element. + */ +import type { XmlSchemaContent } from '../XmlSchemaContent'; +import { XmlSchemaContentModel } from '../XmlSchemaContentModel'; + +export class XmlSchemaSimpleContent extends XmlSchemaContentModel { + /* One of XmlSchemaSimpleContentRestriction or XmlSchemaSimpleContentExtension. */ + content: XmlSchemaContent | null = null; + + getContent() { + return this.content; + } + + setContent(content: XmlSchemaContent) { + this.content = content; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentExtension.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentExtension.ts new file mode 100644 index 000000000..1148d1c39 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentExtension.ts @@ -0,0 +1,46 @@ +import type { XmlSchemaAttributeOrGroupRef } from '../attribute/XmlSchemaAttributeOrGroupRef'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { QName } from '../QName'; +import { XmlSchemaContent } from '../XmlSchemaContent'; + +/** + * Class for simple types that are derived by extension. Extends the simple type content of the element by + * adding attributes. Represents the World Wide Web Consortium (W3C) extension element for simple content. + */ +export class XmlSchemaSimpleContentExtension extends XmlSchemaContent { + /* Allows an XmlSchemaAnyAttribute to be used for the attribute value. */ + private anyAttribute: XmlSchemaAnyAttribute | null = null; + + /* + * Contains XmlSchemaAttribute and XmlSchemaAttributeGroupRef. Collection of attributes for the simple + * type. + */ + private attributes: XmlSchemaAttributeOrGroupRef[] = []; + + /* Name of the built-in data type, simple type, or complex type. */ + private baseTypeName: QName | null = null; + + getAnyAttribute() { + return this.anyAttribute; + } + + getAttributes() { + return this.attributes; + } + + getBaseTypeName() { + return this.baseTypeName; + } + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + setBaseTypeName(baseTypeName: QName) { + this.baseTypeName = baseTypeName; + } + + setAttributes(attributes: XmlSchemaAttributeOrGroupRef[]) { + this.attributes = attributes; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentRestriction.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentRestriction.ts new file mode 100644 index 000000000..2451d5ee5 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleContentRestriction.ts @@ -0,0 +1,64 @@ +/** + * Class for simple types that are derived by restriction. Restricts the range of values for the element to a + * subset of the inherited simple types. Represents the World Wide Web Consortium (W3C) restriction element + * for simple content. + */ + +import type { QName } from '../QName'; +import type { XmlSchemaAnyAttribute } from '../XmlSchemaAnyAttribute'; +import type { XmlSchemaAttributeOrGroupRef } from '../attribute/XmlSchemaAttributeOrGroupRef'; +import type { XmlSchemaFacet } from '../facet/XmlSchemaFacet'; +import type { XmlSchemaSimpleType } from './XmlSchemaSimpleType'; +import { XmlSchemaContent } from '../XmlSchemaContent'; + +export class XmlSchemaSimpleContentRestriction extends XmlSchemaContent { + anyAttribute: XmlSchemaAnyAttribute | null = null; + /* + * Contains XmlSchemaAttribute and XmlSchemaAttributeGroupRef. Collection of attributes for the simple + * type. + */ + private attributes: XmlSchemaAttributeOrGroupRef[] = []; + + /* Derived from the type specified by the base value. */ + private baseType: XmlSchemaSimpleType | null = null; + + /* Name of the built-in data type, simple type, or complex type. */ + private baseTypeName: QName | null = null; + + /* One or more of the facet classes: */ + private facets: XmlSchemaFacet[] = []; + + /* Allows an XmlSchemaAnyAttribute to be used for the attribute value. */ + + setAnyAttribute(anyAttribute: XmlSchemaAnyAttribute) { + this.anyAttribute = anyAttribute; + } + + getAnyAttribute() { + return this.anyAttribute; + } + + getAttributes() { + return this.attributes; + } + + setBaseType(baseType: XmlSchemaSimpleType) { + this.baseType = baseType; + } + + getBaseType() { + return this.baseType; + } + + setBaseTypeName(baseTypeName: QName) { + this.baseTypeName = baseTypeName; + } + + getBaseTypeName() { + return this.baseTypeName; + } + + getFacets() { + return this.facets; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleType.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleType.ts new file mode 100644 index 000000000..88c66b339 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleType.ts @@ -0,0 +1,14 @@ +import type { XmlSchemaSimpleTypeContent } from './XmlSchemaSimpleTypeContent'; + +import { XmlSchemaType } from '../XmlSchemaType'; + +export class XmlSchemaSimpleType extends XmlSchemaType { + content?: XmlSchemaSimpleTypeContent; + + getContent() { + return this.content; + } + setContent(content: XmlSchemaSimpleTypeContent) { + this.content = content; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeContent.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeContent.ts new file mode 100644 index 000000000..868336ccd --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeContent.ts @@ -0,0 +1,3 @@ +import { XmlSchemaAnnotated } from '../XmlSchemaAnnotated'; + +export abstract class XmlSchemaSimpleTypeContent extends XmlSchemaAnnotated {} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeList.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeList.ts new file mode 100644 index 000000000..b3d145013 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeList.ts @@ -0,0 +1,25 @@ +import type { QName } from '../QName'; +import type { XmlSchemaSimpleType } from './XmlSchemaSimpleType'; + +import { XmlSchemaSimpleTypeContent } from './XmlSchemaSimpleTypeContent'; + +export class XmlSchemaSimpleTypeList extends XmlSchemaSimpleTypeContent { + itemType?: XmlSchemaSimpleType; + itemTypeName?: QName; + + getItemType() { + return this.itemType; + } + + setItemType(itemType: XmlSchemaSimpleType) { + this.itemType = itemType; + } + + getItemTypeName() { + return this.itemTypeName; + } + + setItemTypeName(itemTypeName: QName) { + this.itemTypeName = itemTypeName; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeRestriction.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeRestriction.ts new file mode 100644 index 000000000..04b44d790 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeRestriction.ts @@ -0,0 +1,31 @@ +import type { XmlSchemaSimpleType } from './XmlSchemaSimpleType'; +import type { XmlSchemaFacet } from '../facet/XmlSchemaFacet'; +import type { QName } from '../QName'; + +import { XmlSchemaSimpleTypeContent } from './XmlSchemaSimpleTypeContent'; + +export class XmlSchemaSimpleTypeRestriction extends XmlSchemaSimpleTypeContent { + private baseType?: XmlSchemaSimpleType; + private baseTypeName?: QName; + private facets: XmlSchemaFacet[] = []; + + getBaseType() { + return this.baseType; + } + + setBaseType(baseType: XmlSchemaSimpleType) { + this.baseType = baseType; + } + + getBaseTypeName() { + return this.baseTypeName; + } + + setBaseTypeName(baseTypeName: QName) { + this.baseTypeName = baseTypeName; + } + + getFacets() { + return this.facets; + } +} diff --git a/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeUnion.ts b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeUnion.ts new file mode 100644 index 000000000..44fd301b9 --- /dev/null +++ b/packages/xml-schema-ts/src/simple/XmlSchemaSimpleTypeUnion.ts @@ -0,0 +1,33 @@ +/** + * Class for the union of simpleType elements. Defines a simpleType element as a list of values of a specified + * data type. Represents the World Wide Web Consortium (W3C) union element. + */ +import type { QName } from '../QName'; +import type { XmlSchemaSimpleType } from './XmlSchemaSimpleType'; +import { XmlSchemaSimpleTypeContent } from './XmlSchemaSimpleTypeContent'; + +export class XmlSchemaSimpleTypeUnion extends XmlSchemaSimpleTypeContent { + private baseTypes: XmlSchemaSimpleType[] = []; + private memberTypesSource: string | null = null; + private memberTypesQNames: QName[] = []; + + getBaseTypes() { + return this.baseTypes; + } + + setMemberTypesSource(memberTypesSources: string) { + this.memberTypesSource = memberTypesSources; + } + + getMemberTypesSource() { + return this.memberTypesSource; + } + + getMemberTypesQNames() { + return this.memberTypesQNames; + } + + setMemberTypesQNames(memberTypesQNames: QName[]) { + this.memberTypesQNames = memberTypesQNames; + } +} diff --git a/packages/xml-schema-ts/src/utils/NamespaceContext.ts b/packages/xml-schema-ts/src/utils/NamespaceContext.ts new file mode 100644 index 000000000..dbbded459 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/NamespaceContext.ts @@ -0,0 +1,7 @@ +export interface NamespaceContext { + getNamespaceURI(var1: string): string; + + getPrefix(var1: string): string; + + getPrefixes(var1: string): string[]; +} diff --git a/packages/xml-schema-ts/src/utils/NamespaceContextOwner.ts b/packages/xml-schema-ts/src/utils/NamespaceContextOwner.ts new file mode 100644 index 000000000..2877281e3 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/NamespaceContextOwner.ts @@ -0,0 +1,5 @@ +import { NamespacePrefixList } from './NamespacePrefixList'; + +export interface NamespaceContextOwner { + getNamespaceContext(): NamespacePrefixList | null; +} diff --git a/packages/xml-schema-ts/src/utils/NamespacePrefixList.ts b/packages/xml-schema-ts/src/utils/NamespacePrefixList.ts new file mode 100644 index 000000000..5a7408639 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/NamespacePrefixList.ts @@ -0,0 +1,5 @@ +import { NamespaceContext } from './NamespaceContext'; + +export interface NamespacePrefixList extends NamespaceContext { + getDeclaredPrefixes(): string[]; +} diff --git a/packages/xml-schema-ts/src/utils/NodeNamespaceContext.ts b/packages/xml-schema-ts/src/utils/NodeNamespaceContext.ts new file mode 100644 index 000000000..43f2127fc --- /dev/null +++ b/packages/xml-schema-ts/src/utils/NodeNamespaceContext.ts @@ -0,0 +1,67 @@ +import type { NamespacePrefixList } from './NamespacePrefixList'; +import { NULL_NS_URI, XML_NS_PREFIX, XML_NS_URI, XMLNS_ATTRIBUTE, XMLNS_ATTRIBUTE_NS_URI } from '../constants'; +import { PrefixCollector } from './PrefixCollector'; + +export class NodeNamespaceContext implements NamespacePrefixList { + private prefixes?: string[]; + + private constructor(private declarations: Record) {} + + static getNamespaceContext(pNode: Node): NodeNamespaceContext { + const declarations: Record = {}; + PrefixCollector.searchAllPrefixDeclarations(pNode, (pPrefix: string, pNamespaceURI: string) => { + declarations[pPrefix] = pNamespaceURI; + }); + return new NodeNamespaceContext(declarations); + } + + getDeclaredPrefixes(): string[] { + if (this.prefixes == null) { + this.prefixes = Object.keys(this.declarations); + } + return this.prefixes; + } + + getNamespaceURI(pPrefix: string): string { + if (pPrefix == null) { + throw new Error('Prefix cannot be null'); + } + if (XML_NS_PREFIX === pPrefix) { + return XML_NS_URI; + } + if (XMLNS_ATTRIBUTE === pPrefix) { + return XMLNS_ATTRIBUTE_NS_URI; + } + const uri = this.declarations[pPrefix]; + return uri == null ? NULL_NS_URI : uri; + } + + getPrefix(pNamespaceURI: string): string { + if (pNamespaceURI == null) { + throw new Error('Namespace URI cannot be null'); + } + if (XML_NS_URI === pNamespaceURI) { + return XML_NS_PREFIX; + } + if (XMLNS_ATTRIBUTE_NS_URI === pNamespaceURI) { + return XMLNS_ATTRIBUTE; + } + const found = Object.entries(this.declarations).find((entry) => entry[1] === pNamespaceURI); + return found ? found[0] : ''; + } + + getPrefixes(pNamespaceURI: string): string[] { + if (pNamespaceURI == null) { + throw new Error('Namespace URI cannot be null'); + } + if (XML_NS_URI === pNamespaceURI) { + return [XML_NS_PREFIX]; + } + if (XMLNS_ATTRIBUTE_NS_URI === pNamespaceURI) { + return [XMLNS_ATTRIBUTE]; + } + return Object.entries(this.declarations) + .filter((entry) => entry[1] === pNamespaceURI) + .map((entry) => entry[0]); + } +} diff --git a/packages/xml-schema-ts/src/utils/ObjectMap.test.ts b/packages/xml-schema-ts/src/utils/ObjectMap.test.ts new file mode 100644 index 000000000..ed776a44f --- /dev/null +++ b/packages/xml-schema-ts/src/utils/ObjectMap.test.ts @@ -0,0 +1,33 @@ +import { QNameMap, SchemaKeyMap } from './ObjectMap'; +import { XmlSchema } from '../XmlSchema'; +import { SchemaKey } from '../SchemaKey'; +import { QName } from '../QName'; + +describe('ObjectMap', () => { + describe('SchemaKeyMap', () => { + it('keys(), values() and entries() should work', () => { + const map = new SchemaKeyMap(); + map.set(new SchemaKey('a', 'b'), new XmlSchema()); + map.set(new SchemaKey('c', 'd'), new XmlSchema()); + let keys = map.keys(); + expect(keys.next().value.getNamespace()).toEqual('a'); + expect(keys.next().value.getNamespace()).toEqual('c'); + keys = map.keys(); + const keysArray = Array.from(keys); + expect(keysArray.length).toEqual(2); + const values = map.values(); + expect(Array.from(values).length).toEqual(2); + const entries = map.entries(); + expect(Array.from(entries).length).toEqual(2); + }); + }); + + describe('QNameMap', () => { + it('should ignore QName prefix', () => { + const map = new QNameMap(); + map.set(new QName('a', 'b'), new XmlSchema()); + map.set(new QName('c', 'd'), new XmlSchema()); + expect(map.get(new QName('a', 'b', 'c'))).toBeTruthy(); + }); + }); +}); diff --git a/packages/xml-schema-ts/src/utils/ObjectMap.ts b/packages/xml-schema-ts/src/utils/ObjectMap.ts new file mode 100644 index 000000000..587a6b524 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/ObjectMap.ts @@ -0,0 +1,112 @@ +import { QName } from '../QName'; +import { SchemaKey } from '../SchemaKey'; + +/** + * The Map which uses a class instance as a key where if the stringified key matches then it's considered same. + */ +abstract class ObjectMap { + private delegate = new Map(); + + get(key: K) { + return this.delegate.get(this.keyToString(key)); + } + + set(key: K, value: V) { + this.delegate.set(this.keyToString(key), value); + } + + has(key: K) { + return this.delegate.has(this.keyToString(key)); + } + + delete(key: K) { + this.delegate.delete(this.keyToString(key)); + } + + get size() { + return this.delegate.size; + } + + clear() { + this.delegate.clear(); + } + + abstract stringToKey(stringified: string): K; + abstract keyToString(key: K): string; + + keys(): IterableIterator { + const delegateKeys = this.delegate.keys(); + return new KeysBridge(delegateKeys, this.stringToKey); + } + + entries(): IterableIterator<[K, V]> { + const delegateEntries = this.delegate.entries(); + return new EntriesBridge(delegateEntries, this.stringToKey); + } + + values(): IterableIterator { + return this.delegate.values(); + } +} + +class KeysBridge implements IterableIterator { + constructor( + private delegateKeys: IterableIterator, + private stringToKey: (stringifiedKey: string) => K, + ) {} + + [Symbol.iterator](): IterableIterator { + return this; + } + + /* eslint-disable @typescript-eslint/no-explicit-any */ + next(): IteratorResult { + const next = this.delegateKeys.next(); + if (!next.value) { + return next as any; + } + const key: K = this.stringToKey(next.value); + return { value: key }; + } +} + +class EntriesBridge implements IterableIterator<[K, V]> { + constructor( + private delegateEntries: IterableIterator<[string, V]>, + private stringToKey: (stringifiedKey: string) => K, + ) {} + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this; + } + + /* eslint-disable @typescript-eslint/no-explicit-any */ + next(): IteratorResult<[K, V], [K, V] | undefined> { + const next = this.delegateEntries.next(); + if (!next.value) { + return next as any; + } + const key: K = this.stringToKey(next.value[0]); + return { value: [key, next.value[1]] }; + } +} + +export class QNameMap extends ObjectMap { + stringToKey(stringified: string): QName { + const obj = Object.assign(new QName(null, null), JSON.parse(stringified)); + obj.prefix = undefined; + return obj; + } + keyToString(key: QName): string { + return JSON.stringify(new QName(key.getNamespaceURI(), key.getLocalPart())); + } +} + +export class SchemaKeyMap extends ObjectMap { + stringToKey(stringified: string): SchemaKey { + return Object.assign(new SchemaKey(), JSON.parse(stringified)); + } + keyToString(key: SchemaKey): string { + return JSON.stringify(key); + } +} diff --git a/packages/xml-schema-ts/src/utils/PrefixCollector.ts b/packages/xml-schema-ts/src/utils/PrefixCollector.ts new file mode 100644 index 000000000..4eb1617cb --- /dev/null +++ b/packages/xml-schema-ts/src/utils/PrefixCollector.ts @@ -0,0 +1,33 @@ +import { DEFAULT_NS_PREFIX, XMLNS_ATTRIBUTE, XMLNS_ATTRIBUTE_NS_URI } from '../constants'; + +export class PrefixCollector { + static searchLocalPrefixDeclarations( + pNode: Node, + declarePrefix: (pPrefix: string, pNamespaceURI: string) => void, + ): void { + const type = pNode.nodeType; + if (type === Node.ELEMENT_NODE || type === Node.DOCUMENT_NODE) { + const map = (pNode as Element).attributes; + for (let i = 0; map != null && i < map.length; i++) { + const attr = map.item(i); + const uri = attr?.namespaceURI; + if (XMLNS_ATTRIBUTE_NS_URI === uri) { + const localName = attr?.localName; + const prefix = XMLNS_ATTRIBUTE === localName ? DEFAULT_NS_PREFIX : localName; + declarePrefix(prefix!, attr!.nodeValue!); + } + } + } + } + + static searchAllPrefixDeclarations( + pNode: Node, + declarePrefix: (pPrefix: string, pNamespaceURI: string) => void, + ): void { + const parent = pNode.parentNode; + if (parent != null) { + PrefixCollector.searchAllPrefixDeclarations(parent, declarePrefix); + } + PrefixCollector.searchLocalPrefixDeclarations(pNode, declarePrefix); + } +} diff --git a/packages/xml-schema-ts/src/utils/XDOMUtil.ts b/packages/xml-schema-ts/src/utils/XDOMUtil.ts new file mode 100644 index 000000000..d728d4e01 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XDOMUtil.ts @@ -0,0 +1,85 @@ +/** + * Merging DOMUtil and XDOMUtil. In TypeScript, inheritance and method overloading + * is different from Java, which caused a problem on these relationship. + */ +export class XDOMUtil { + /** + * Finds and returns the next sibling element node. + */ + static getNextSiblingElement(node: Node): Element | null { + // search for node + let sibling = node.nextSibling; + while (sibling != null) { + if (sibling.nodeType == Node.ELEMENT_NODE) { + return sibling as Element; + } + sibling = sibling.nextSibling; + } + // not found + return null; + } // getNextSiblingElement(Node):Element + + /** + * Finds and returns the first child node with the given qualified name. + */ + static getFirstChildElementNS(parent: Node, uri: string, localpart?: string): Element | null { + // search for node + let child = parent.firstChild; + while (child != null) { + if (child.nodeType == Node.ELEMENT_NODE) { + const childElement = child as Element; + const childURI = childElement.namespaceURI; + if (childURI != null && childURI === uri) { + if (localpart == null || childElement.localName === localpart) { + return childElement; + } + } + } + child = child.nextSibling; + } + // not found + return null; + } // getFirstChildElementNS(Node,String,String):Element + + /** + * Finds and returns the next sibling node with the given qualified name. + */ + static getNextSiblingElementByNamesNS(node: Node, elemNames: string[][]) { + // search for node + let sibling = node.nextSibling; + while (sibling != null) { + if (sibling.nodeType == Node.ELEMENT_NODE) { + const siblingElement = sibling as Element; + for (const elemName of elemNames) { + const uri = siblingElement.namespaceURI; + if (uri != null && uri === elemName[0] && siblingElement.localName === elemName[1]) { + return siblingElement; + } + } + } + sibling = sibling.nextSibling; + } + // not found + return null; + } // getNextSiblingdElementNS(Node,String[][]):Element + + /** + * Finds and returns the next sibling node with the given qualified name. + */ + static getNextSiblingElementNS(node: Node, uri: string, localpart?: string) { + // search for node + let sibling = node.nextSibling; + while (sibling != null) { + if (sibling.nodeType == Node.ELEMENT_NODE) { + const siblingElement = sibling as Element; + const siblingURI = siblingElement.namespaceURI; + if (siblingURI != null && siblingURI === uri && (localpart == null || siblingElement.localName === localpart)) { + return siblingElement; + } + } + sibling = sibling.nextSibling; + } + // not found + return null; + } // getNextSiblingdElementNS(Node,String,String):Element +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaNamed.ts b/packages/xml-schema-ts/src/utils/XmlSchemaNamed.ts new file mode 100644 index 000000000..87421280f --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaNamed.ts @@ -0,0 +1,46 @@ +import type { QName } from '../QName'; +import type { XmlSchema } from '../XmlSchema'; +import { XmlSchemaObjectBase } from './XmlSchemaObjectBase'; + +export interface XmlSchemaNamed extends XmlSchemaObjectBase { + /** + * Retrieve the name. + * @return the local name of this object within its schema. + */ + getName(): string | null; + + /** + * @return true if this object has no name. + */ + isAnonymous(): boolean; + + /** + * Set the name. Set to null to render the object anonymous, or to prepare to + * change it to refer to some other object. + * @param name the name. + */ + setName(name: string): void; + + /** + * Retrieve the parent schema. + * @return the containing schema. + */ + getParent(): XmlSchema; + + /** + * Get the QName for this object. This is always the formal name that identifies this + * item in the schema. If the item has a form (an element or attribute), and the form + * is 'unqualified', this is not the appropriate QName in an instance + * document. For those items, the getWiredName method returns the appropriate + * QName for an instance document. + * @see XmlSchemaNamedWithForm#getWireName() + * @return The qualified name of this object. + */ + getQName(): QName | null; + + /** + * @return true if this item is a top-level item of the schema; false if this item + * is nested inside of some other schema object. + */ + isTopLevel(): boolean; +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaNamedImpl.ts b/packages/xml-schema-ts/src/utils/XmlSchemaNamedImpl.ts new file mode 100644 index 000000000..d0c1ee86d --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaNamedImpl.ts @@ -0,0 +1,72 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaNamed } from './XmlSchemaNamed'; +import type { XmlSchemaRefBase } from './XmlSchemaRefBase'; +import { QName } from '../QName'; + +export class XmlSchemaNamedImpl implements XmlSchemaNamed { + protected parentSchema: XmlSchema; + /* + * Some objects implement both name= and ref=. This reference allows us some error + * checking. + */ + protected refTwin?: XmlSchemaRefBase; + // Store the name as a QName for the convenience of QName fans. + private qname: QName | null = null; + private topLevel: boolean = false; + + /** + * Create a new named object. + * @param parent the parent schema. + * @param topLevel + */ + constructor(parent: XmlSchema, topLevel: boolean) { + this.parentSchema = parent; + this.topLevel = topLevel; + } + + /** + * If the named object also implements ref=, it should pass the reference object + * here for some error checking. + * @param refBase + */ + setRefObject(refBase: XmlSchemaRefBase) { + this.refTwin = refBase; + } + + getName() { + if (this.qname == null) { + return null; + } else { + return this.qname.getLocalPart(); + } + } + + isAnonymous() { + return this.qname == null; + } + + setName(name: string | null) { + if (name == null) { + this.qname = null; + } else if ('' === name) { + throw new Error('Attempt to set empty name.'); + } else { + if (this.refTwin != null && this.refTwin.getTargetQName() != null) { + throw new Error("Attempt to set name on object with ref='xxx'"); + } + this.qname = new QName(this.parentSchema.getLogicalTargetNamespace(), name); + } + } + + getParent() { + return this.parentSchema; + } + + getQName() { + return this.qname; + } + + public isTopLevel() { + return this.topLevel; + } +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaNamedType.ts b/packages/xml-schema-ts/src/utils/XmlSchemaNamedType.ts new file mode 100644 index 000000000..0bcbe50d6 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaNamedType.ts @@ -0,0 +1,8 @@ +export enum XmlSchemaNamedType { + XmlSchemaElement, + XmlSchemaAttribute, + XmlSchemaType, + XmlSchemaAttributeGroup, + XmlSchemaGroup, + XmlSchemaNotation, +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithForm.ts b/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithForm.ts new file mode 100644 index 000000000..c540bc6be --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithForm.ts @@ -0,0 +1,4 @@ +import { XmlSchemaNamed } from './XmlSchemaNamed'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaNamedWithForm extends XmlSchemaNamed {} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithFormImpl.ts b/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithFormImpl.ts new file mode 100644 index 000000000..529f72dcb --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaNamedWithFormImpl.ts @@ -0,0 +1,74 @@ +import type { XmlSchemaNamedWithForm } from './XmlSchemaNamedWithForm'; +import type { XmlSchema } from '../XmlSchema'; +import { XmlSchemaNamedImpl } from './XmlSchemaNamedImpl'; +import { QName } from '../QName'; +import { XmlSchemaForm } from '../XmlSchemaForm'; + +/** + * + */ +export class XmlSchemaNamedWithFormImpl extends XmlSchemaNamedImpl implements XmlSchemaNamedWithForm { + private form = XmlSchemaForm.NONE; + private element = false; + private wireName: QName | null = null; + + /** + * Delegate object for managing names for attributes and elements. + * @param parent containing schema. + * @param topLevel if this object is global. + * @param element true for an element, false for an attribute. + */ + constructor(parent: XmlSchema, topLevel: boolean, element: boolean) { + super(parent, topLevel); + this.element = element; + } + + /** + * Return the effective 'form' for this item. If the item + * has an explicit form declaration, this returns that declared form. If not, + * it returns the appropriate default form from the containing schema. + * @return {@link XmlSchemaForm#QUALIFIED} or {@link XmlSchemaForm#UNQUALIFIED}. + */ + getForm() { + if (this.form != XmlSchemaForm.NONE) { + return this.form; + } else if (this.isTopLevel()) { + return XmlSchemaForm.QUALIFIED; + } else if (this.element) { + return this.parentSchema.getElementFormDefault(); + } else { + return this.parentSchema.getAttributeFormDefault(); + } + } + + isFormSpecified() { + return this.form != XmlSchemaForm.NONE; + } + + setForm(form: XmlSchemaForm) { + if (form == null) { + throw new Error('form may not be null. ' + 'Pass XmlSchemaForm.NONE to use schema default.'); + } + this.form = form; + this.setName(this.getName()); + } + + setName(name: string | null) { + super.setName(name); + if (this.getForm() == XmlSchemaForm.QUALIFIED) { + this.wireName = this.getQName(); + } else { + this.wireName = new QName('', this.getName()); + } + } + + getWireName() { + // If this is a ref= case, then we take the name from the ref=, not from the QName. + // what about ref='foo' form='unqualified'? Is that possible? + if (this.refTwin != null && this.refTwin.getTargetQName() != null) { + return this.refTwin.getTargetQName(); + } else { + return this.wireName; + } + } +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaObjectBase.ts b/packages/xml-schema-ts/src/utils/XmlSchemaObjectBase.ts new file mode 100644 index 000000000..eac8e3378 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaObjectBase.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface XmlSchemaObjectBase {} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaRef.ts b/packages/xml-schema-ts/src/utils/XmlSchemaRef.ts new file mode 100644 index 000000000..18e56f2fa --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaRef.ts @@ -0,0 +1,43 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaNamed } from './XmlSchemaNamed'; +import { XmlSchemaRefBase } from './XmlSchemaRefBase'; +import { XmlSchemaNamedType } from './XmlSchemaNamedType'; + +export class XmlSchemaRef extends XmlSchemaRefBase { + private targetObject: XmlSchemaNamed | null = null; + private targetType: XmlSchemaNamedType; + + constructor(parent: XmlSchema, targetType: XmlSchemaNamedType) { + super(); + this.parent = parent; + this.targetType = targetType; + } + + forgetTargetObject() { + this.targetObject = null; + } + + getTarget(): T | null { + if (this.targetObject == null && this.targetQName != null) { + const parentCollection = this.parent!.getParent(); + if (parentCollection == null) { + return this.targetObject; + } + + if (this.targetType === XmlSchemaNamedType.XmlSchemaElement) { + this.targetObject = parentCollection.getElementByQName(this.targetQName); + } else if (this.targetType == XmlSchemaNamedType.XmlSchemaAttribute) { + this.targetObject = parentCollection.getAttributeByQName(this.targetQName); + } else if (this.targetType == XmlSchemaNamedType.XmlSchemaType) { + this.targetObject = parentCollection.getTypeByQName(this.targetQName); + } else if (this.targetType == XmlSchemaNamedType.XmlSchemaAttributeGroup) { + this.targetObject = parentCollection.getAttributeGroupByQName(this.targetQName); + } else if (this.targetType == XmlSchemaNamedType.XmlSchemaGroup) { + this.targetObject = parentCollection.getGroupByQName(this.targetQName); + } else if (this.targetType == XmlSchemaNamedType.XmlSchemaNotation) { + this.targetObject = parentCollection.getNotationByQName(this.targetQName); + } + } + return this.targetObject as T; + } +} diff --git a/packages/xml-schema-ts/src/utils/XmlSchemaRefBase.ts b/packages/xml-schema-ts/src/utils/XmlSchemaRefBase.ts new file mode 100644 index 000000000..dcf409588 --- /dev/null +++ b/packages/xml-schema-ts/src/utils/XmlSchemaRefBase.ts @@ -0,0 +1,30 @@ +import type { XmlSchema } from '../XmlSchema'; +import type { XmlSchemaNamed } from './XmlSchemaNamed'; +import { QName } from '../QName'; + +export abstract class XmlSchemaRefBase { + protected parent: XmlSchema | null = null; + protected targetQName: QName | null = null; + private namedTwin?: XmlSchemaNamed; + + protected abstract forgetTargetObject(): void; + + setNamedObject(named: XmlSchemaNamed) { + this.namedTwin = named; + } + + getTargetQName() { + return this.targetQName; + } + + setTargetQName(targetQName: QName) { + if (this.targetQName != null && this.namedTwin != null && !this.namedTwin.isAnonymous()) { + throw new Error('It is invalid to set the ref= name for an item that has a name.'); + } + /** + * We could possibly complain about no ref and also no name. + */ + this.targetQName = targetQName; + this.forgetTargetObject(); + } +} diff --git a/packages/xml-schema-ts/src/xml-parser.test.ts b/packages/xml-schema-ts/src/xml-parser.test.ts new file mode 100644 index 000000000..98afab7ee --- /dev/null +++ b/packages/xml-schema-ts/src/xml-parser.test.ts @@ -0,0 +1,30 @@ +import * as fs from 'fs'; + +describe.skip('XML parser', () => { + describe('DOMParser', () => { + const parser = new DOMParser(); + + it('should parse XML schema', () => { + const orderXsd = fs.readFileSync(__dirname + '/../../../../test-resources/ShipOrder.xsd').toString(); + const xmlDoc = parser.parseFromString(orderXsd, 'text/xml'); + expect(xmlDoc).toBeDefined(); + const schema = xmlDoc.getElementsByTagName('xs:schema')[0]; + console.log( + `nodeName=${schema!.nodeName}, localName=${schema!.localName}, namespaceURI=${schema!.namespaceURI}, prefix=${schema.prefix}`, + ); + const children = schema.childNodes; + children.forEach((v) => { + const el = v as Element; + console.log( + `nodeName=${el!.nodeName}, localName=${el!.localName}, namespaceURI=${el!.namespaceURI}, prefix=${el.prefix}`, + ); + }); + }); + + it('should parse XML document', () => { + const orderXml = fs.readFileSync(__dirname + '/../../../../test-resources/ExampleOrder.xml').toString(); + const xmlDoc = parser.parseFromString(orderXml, 'text/xml'); + expect(xmlDoc).toBeDefined(); + }); + }); +}); diff --git a/packages/xml-schema-ts/test-resources/Account.xsd b/packages/xml-schema-ts/test-resources/Account.xsd new file mode 100644 index 000000000..03003ad77 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/Account.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/Cart.xsd b/packages/xml-schema-ts/test-resources/Cart.xsd new file mode 100644 index 000000000..84ce73691 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/Cart.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/ExampleOrder.xml b/packages/xml-schema-ts/test-resources/ExampleOrder.xml new file mode 100644 index 000000000..dfe89181b --- /dev/null +++ b/packages/xml-schema-ts/test-resources/ExampleOrder.xml @@ -0,0 +1,23 @@ + + + + Jon Doh + + sometext + + Jon Doh +
    123 Jon Rd
    + Doh + MA +
    + + Apple + 1 + 3 + + + Lemon + 1 + 3 + +
    \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/NamedTypes.xsd b/packages/xml-schema-ts/test-resources/NamedTypes.xsd new file mode 100644 index 000000000..f64216551 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/NamedTypes.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/NoTopElement.xsd b/packages/xml-schema-ts/test-resources/NoTopElement.xsd new file mode 100644 index 000000000..6850c5ee0 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/NoTopElement.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/ShipOrder.xsd b/packages/xml-schema-ts/test-resources/ShipOrder.xsd new file mode 100644 index 000000000..cda29d71e --- /dev/null +++ b/packages/xml-schema-ts/test-resources/ShipOrder.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/ShipOrderEmptyFirstLine.xsd b/packages/xml-schema-ts/test-resources/ShipOrderEmptyFirstLine.xsd new file mode 100644 index 000000000..cfab0b133 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/ShipOrderEmptyFirstLine.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/ShipOrderToShipOrder.xsl b/packages/xml-schema-ts/test-resources/ShipOrderToShipOrder.xsl new file mode 100644 index 000000000..cbf4f2d5d --- /dev/null +++ b/packages/xml-schema-ts/test-resources/ShipOrderToShipOrder.xsl @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="Title"/> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/ShipOrderToShipOrderInvalidForEach.xsl b/packages/xml-schema-ts/test-resources/ShipOrderToShipOrderInvalidForEach.xsl new file mode 100644 index 000000000..d55fb7073 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/ShipOrderToShipOrderInvalidForEach.xsl @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/TestDocument.xsd b/packages/xml-schema-ts/test-resources/TestDocument.xsd new file mode 100644 index 000000000..ce46edb95 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/TestDocument.xsd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/account-ns.xsd b/packages/xml-schema-ts/test-resources/account-ns.xsd new file mode 100644 index 000000000..624aacfe7 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/account-ns.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/account.xsd b/packages/xml-schema-ts/test-resources/account.xsd new file mode 100644 index 000000000..f59e17cc1 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/account.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/camel-spring.xsd b/packages/xml-schema-ts/test-resources/camel-spring.xsd new file mode 100644 index 000000000..8ab41d30d --- /dev/null +++ b/packages/xml-schema-ts/test-resources/camel-spring.xsd @@ -0,0 +1,18474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/cart-ns.xsd b/packages/xml-schema-ts/test-resources/cart-ns.xsd new file mode 100644 index 000000000..c81f1484d --- /dev/null +++ b/packages/xml-schema-ts/test-resources/cart-ns.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/cart.xsd b/packages/xml-schema-ts/test-resources/cart.xsd new file mode 100644 index 000000000..79b256355 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/cart.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/hello.xsd b/packages/xml-schema-ts/test-resources/hello.xsd new file mode 100644 index 000000000..8dc58b8a4 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/hello.xsd @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/hello.xsl b/packages/xml-schema-ts/test-resources/hello.xsl new file mode 100644 index 000000000..dbe9922cb --- /dev/null +++ b/packages/xml-schema-ts/test-resources/hello.xsl @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/xml-schema-ts/test-resources/shiporder-ns.xsd b/packages/xml-schema-ts/test-resources/shiporder-ns.xsd new file mode 100644 index 000000000..bfa8aae31 --- /dev/null +++ b/packages/xml-schema-ts/test-resources/shiporder-ns.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/test-resources/shiporder.xsd b/packages/xml-schema-ts/test-resources/shiporder.xsd new file mode 100644 index 000000000..0d14b13ba --- /dev/null +++ b/packages/xml-schema-ts/test-resources/shiporder.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/xml-schema-ts/tsconfig.cjs.json b/packages/xml-schema-ts/tsconfig.cjs.json new file mode 100644 index 000000000..7bd691fdf --- /dev/null +++ b/packages/xml-schema-ts/tsconfig.cjs.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist/cjs", + "rootDir": "src", + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "node" + }, + "include": ["src"] +} diff --git a/packages/xml-schema-ts/tsconfig.esm.json b/packages/xml-schema-ts/tsconfig.esm.json new file mode 100644 index 000000000..147793c44 --- /dev/null +++ b/packages/xml-schema-ts/tsconfig.esm.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist/esm", + "rootDir": "src", + "module": "es6", + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "node" + }, + "include": ["src"] +} diff --git a/packages/xml-schema-ts/tsconfig.json b/packages/xml-schema-ts/tsconfig.json new file mode 100644 index 000000000..64a09d2f2 --- /dev/null +++ b/packages/xml-schema-ts/tsconfig.json @@ -0,0 +1,29 @@ +{ + "exclude": ["node_modules"], + "compilerOptions": { + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "useDefineForClassFields": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noImplicitAny": false, + "noImplicitThis": true, + "removeComments": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "types": [ + "@testing-library/jest-dom", + "@types/jest", + "@types/node", + ], + } +} diff --git a/yarn.lock b/yarn.lock index 0a58730c7..c392b3f98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,14 +50,14 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": +"@babel/compat-data@npm:^7.25.2": version: 7.25.4 resolution: "@babel/compat-data@npm:7.25.4" checksum: 10/d37a8936cc355a9ca3050102e03d179bdae26bd2e5c99a977637376c192b23637a039795f153c849437a086727628c9860e2c6af92d7151396e2362c09176337 languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.25.2": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4": version: 7.25.2 resolution: "@babel/core@npm:7.25.2" dependencies: @@ -80,6 +80,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.24.5": + version: 7.24.7 + resolution: "@babel/core@npm:7.24.7" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helpers": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/template": "npm:^7.24.7" + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/ef8cc1afa3ccecee6d1f5660c487ccc2a3f25106830ea9040e80ef4b2092e053607ee4ddd03493e4f7ef2f9967a956ca53b830d54c5bee738eeb58cce679dd4a + languageName: node + linkType: hard + "@babel/generator@npm:^7.24.7, @babel/generator@npm:^7.7.2": version: 7.24.7 resolution: "@babel/generator@npm:7.24.7" @@ -136,7 +159,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2": +"@babel/helper-compilation-targets@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-compilation-targets@npm:7.25.2" dependencies: @@ -168,23 +191,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.4" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/47218da9fd964af30d41f0635d9e33eed7518e03aa8f10c3eb8a563bb2c14f52be3e3199db5912ae0e26058c23bb511c811e565c55ecec09427b04b867ed13c2 - languageName: node - linkType: hard - "@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.24.7" @@ -198,20 +204,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - regexpu-core: "npm:^5.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/33dd627eef9e4229aba66789efd8fb7342fc2667b821d4b7947c7294f6d472cf025ff2db9b358a1e03de98376de44e839f0611a456a57127fd6e4b4dbfc96c51 - languageName: node - linkType: hard - -"@babel/helper-define-polyfill-provider@npm:^0.6.2": +"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.2": version: 0.6.2 resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" dependencies: @@ -264,16 +257,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" - dependencies: - "@babel/traverse": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" - checksum: 10/ac878761cfd0a46c081cda0da75cc186f922cf16e8ecdd0c4fb6dca4330d9fe4871b41a9976224cf9669c9e7fe0421b5c27349f2e99c125fa0be871b327fa770 - languageName: node - linkType: hard - "@babel/helper-module-imports@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-module-imports@npm:7.24.7" @@ -299,7 +282,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2": +"@babel/helper-module-transforms@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-module-transforms@npm:7.25.2" dependencies: @@ -329,13 +312,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-plugin-utils@npm:7.24.8" - checksum: 10/adbc9fc1142800a35a5eb0793296924ee8057fe35c61657774208670468a9fbfbb216f2d0bc46c680c5fefa785e5ff917cc1674b10bd75cdf9a6aa3444780630 - languageName: node - linkType: hard - "@babel/helper-remap-async-to-generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-remap-async-to-generator@npm:7.24.7" @@ -349,19 +325,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-wrap-function": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/6b1ab73a067008c92e2fe5b7a9f39aab32e7f5a8c5eaf0a864436c21791f708ad8619d4a509febdfe934aeb373af4baa7c7d9f41181b385e09f39eaf11ca108e - languageName: node - linkType: hard - "@babel/helper-replace-supers@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-replace-supers@npm:7.24.7" @@ -375,19 +338,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-replace-supers@npm:7.25.0" - dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/97c6c17780cb9692132f7243f5a21fb6420104cb8ff8752dc03cfc9a1912a243994c0290c77ff096637ab6f2a7363b63811cfc68c2bad44e6b39460ac2f6a63f - languageName: node - linkType: hard - "@babel/helper-simple-access@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-simple-access@npm:7.24.7" @@ -464,14 +414,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-wrap-function@npm:7.25.0" +"@babel/helpers@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helpers@npm:7.24.7" dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10/08724128b9c540c02a59f02f9c1c9940fe5363d85d0f30ec826a4f926afdb26fa4ec33ca2b88b4aa745fe3dbe1f44be2969b8a03af259af7945d8cd3262168d3 + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10/f7496f0d7a0b13ea86136ac2053371027125734170328215f8a90eac96fafaaae4e5398c0729bdadf23261c00582a31e14bc70113427653b718220641a917f9d languageName: node linkType: hard @@ -517,37 +466,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/9743feb0152f2ac686aaee6dfd41e8ea211989a459d4c2b10b531442f6865057cd1a502515634c25462b155bc58f0710267afed72396780e9b72be25370dd577 - languageName: node - linkType: hard - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/5e504bba884a4500e71224d344efb1e70ebbeabd621e07a58f2d3c0d14a71a49c97b4989259a288cdbbfacebfea224397acf1217d26c77aebf9aa35bdd988249 + checksum: 10/d5091ca6b58c54316c4d3b6e8120a1bb70cfe2e61cb7ec11f5fdc8ba3ff5124de21e527fabc28f239bf6efc0660046aa416e8fc1e3d920d0e57b78edb507ec3f languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/f574beb1d4f723bb9b913ce379259a55b50a308364585ccb83e00d933465c26c04cbbc85a06e6d4c829279eb1021b3236133d486b3ff11cfd90ad815c8b478d2 + checksum: 10/f0e0e9bdcf5479f8c5b4494353dc64dee37205e5ffd30920e649e75537a8f795cdcf32dfb40a00e908469a5d61cf62806bc359294cb2a6f2e604bf4efe086301 languageName: node linkType: hard @@ -564,15 +502,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.0" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/de04a9342e9a0db1673683112c83cdc52173f489f45aeed864ceba72dfba8c8588e565171e64cb2a408a09269e5fb35c6ab4ef50e3e649c4f8c0c787feb5c048 + checksum: 10/ad63317eb72ca7e160394e9223768b1f826287eaf65297f2794d0203510225f20dd9858bce217af4a050754abf94565841617b45b35a2de355c4e2bba546b39c languageName: node linkType: hard @@ -839,17 +777,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" +"@babel/plugin-transform-async-generator-functions@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-remap-async-to-generator": "npm:^7.25.0" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-remap-async-to-generator": "npm:^7.24.7" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/traverse": "npm:^7.25.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0004d910bbec3ef916acf5c7cf8b11671e65d2dd425a82f1101838b9b6243bfdf9578335584d9dedd20acc162796b687930e127c6042484e05b758af695e6cb8 + checksum: 10/cf0a4b5ffc6d7f3f3bf12d4792535e8a46332714211326fd5058a6e45988891ee402b26cb9cc6c7121b2c8283ebd160e431827f885bdfa51d6127f934bd9ba7f languageName: node linkType: hard @@ -877,18 +815,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" +"@babel/plugin-transform-block-scoping@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-block-scoping@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/981e565a8ff1e1f8d539b5ff067328517233142b131329d11e6c60405204e2a4a993828c367f7dc729a9608aabebdada869616563816e5f8f1385e91ac0fa4d6 + checksum: 10/9656e7bb0673279e18d9f9408027786f1b20d657e2cc106456e0bd7826bd12d81813299adbef2b2a5837b05740f2295fe8fb62389122d38c9e961b3005270777 languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.5": +"@babel/plugin-transform-class-properties@npm:^7.22.5, @babel/plugin-transform-class-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" dependencies: @@ -900,18 +838,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/203a21384303d66fb5d841b77cba8b8994623ff4d26d208e3d05b36858c4919626a8d74871fa4b9195310c2e7883bf180359c4f5a76481ea55190c224d9746f4 - languageName: node - linkType: hard - "@babel/plugin-transform-class-static-block@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7" @@ -925,19 +851,21 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-classes@npm:7.25.4" +"@babel/plugin-transform-classes@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-classes@npm:7.24.7" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.4" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/17db5889803529bec366c6f0602687fdd605c2fec8cb6fe918261cb55cd89e9d8c9aa2aa6f3fd64d36492ce02d7d0752b09a284b0f833c1185f7dad9b9506310 + checksum: 10/5d5577fcb0ec9ef33d889358c54720abe462325bed5483d71f9aa0a704f491520777be5411d6fd8a08a8ebe352e2445d46d1e6577a5a2c9333bc37b9ff8b9a74 languageName: node linkType: hard @@ -953,14 +881,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" +"@babel/plugin-transform-destructuring@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-destructuring@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e3bba0bb050592615fbf062ea07ae94f99e9cf22add006eaa66ed672d67ff7051b578a5ea68a7d79f9184fb3c27c65333d86b0b8ea04f9810bcccbeea2ffbe76 + checksum: 10/eec43df24a07b3c61f335883e50c6642762fdd3cc5c5f95532cebeb51ea9bf77ca9a38011b678d91549dd75e29e1c58bd6e0ebc34bb763c300bc2cc65801e663 languageName: node linkType: hard @@ -987,18 +915,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/869c08def8eb80e3619c77e7af962dd82323a8447697298f461624077593c7b7082fc2238989880a0c0ba94bc6442300fd23e33255ac225760bc8bb755268941 - languageName: node - linkType: hard - "@babel/plugin-transform-dynamic-import@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" @@ -1059,16 +975,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.25.1 - resolution: "@babel/plugin-transform-function-name@npm:7.25.1" +"@babel/plugin-transform-function-name@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-function-name@npm:7.24.7" dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.1" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1b4cd214c8523f7fa024fcda540ffe5503eda0e0be08b7c21405c96a870b5fe8bb1bda9e23a43a31467bf3dfc3a08edca250cf7f55f09dc40759a1ca6c6d6a4a + checksum: 10/9d4dcffea45acd255fed4a97e372ada234579f9bae01a4d0ced657091f159edf1635ff2a666508a08f8e59390def09ae6ce8372679faad894aa6f3247728ebe1 languageName: node linkType: hard @@ -1084,14 +1000,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/plugin-transform-literals@npm:7.25.2" +"@babel/plugin-transform-literals@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-literals@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d9728625a6d55305610dd37057fe1a3473df4f3789fef693c900516caf8958dfb341394ecf69ce9b60c82c422ad2954491a7e4d4533432fd5df812827443d6e9 + checksum: 10/bf341a5a0ffb5129670ac9a14ea53b67bd1d3d0e13173ce7ac2d4184c4b405d33f67df68c59a2e94a895bf80269ec1df82c011d9ddb686f9f08a40c37b881177 languageName: node linkType: hard @@ -1143,30 +1059,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" - dependencies: - "@babel/helper-module-transforms": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-simple-access": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/18e5d229767c7b5b6ff0cbf1a8d2d555965b90201839d0ac2dc043b56857624ea344e59f733f028142a8c1d54923b82e2a0185694ef36f988d797bfbaf59819c - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" +"@babel/plugin-transform-modules-systemjs@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.24.7" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-hoist-variables": "npm:^7.24.7" + "@babel/helper-module-transforms": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/2c38efdbaf6faf730cdcb0c5e42d2d15bb114eecf184db078319de496b5e3ce68d499e531265a0e13e29f0dcaa001f240773db5c4c078eac7f4456d6c8bddd88 + checksum: 10/14f0ed1a252a2a04e075cd9051b809e33cd45374a2495dc0a428517893b8e951819acc8343c61d348c51ba54e42660bc93990a77aa3460d16a1c21d52d9c2cf1 languageName: node linkType: hard @@ -1280,19 +1183,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/1f873fb9d86c280b64dfe5ebc59244b459b717ed72a7682da2386db3d9e11fc9d831cfc2e11d37262b4325a7a0e3ccbccfb8cd0b944caf199d3c9e03fff7b0af - languageName: node - linkType: hard - "@babel/plugin-transform-parameters@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-parameters@npm:7.24.7" @@ -1304,7 +1194,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.22.5": +"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-private-methods@npm:7.24.7" dependencies: @@ -1316,18 +1206,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/d5c29ba121d6ce40e8055a632c32e69006c513607145a29701f93b416a8c53a60e53565df417218e2d8b7f1ba73adb837601e8e9d0a3215da50e4c9507f9f1fa - languageName: node - linkType: hard - "@babel/plugin-transform-private-property-in-object@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7" @@ -1375,7 +1253,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-self@npm:^7.24.7": +"@babel/plugin-transform-react-jsx-self@npm:^7.24.5": version: 7.24.7 resolution: "@babel/plugin-transform-react-jsx-self@npm:7.24.7" dependencies: @@ -1386,7 +1264,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-source@npm:^7.24.7": +"@babel/plugin-transform-react-jsx-source@npm:^7.24.1": version: 7.24.7 resolution: "@babel/plugin-transform-react-jsx-source@npm:7.24.7" dependencies: @@ -1492,14 +1370,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" +"@babel/plugin-transform-typeof-symbol@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.7" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/5f113fed94b694ec4a40a27b8628ce736cfa172b69fcffa2833c9a41895032127f3daeea552e94fdb4a3ce4e8cd51de67a670ab87a1f447a0cf55c9cb2d7ed11 + checksum: 10/c07847a3bcb27509d392de7a59b9836669b90ca508d4b63b36bb73b63413bc0b2571a64410b65999a73abeac99957b31053225877dcbfaf4eb21d8cc0ae4002f languageName: node linkType: hard @@ -1552,31 +1430,30 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.24.7" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/d5d07d17932656fa4d62fd67ecaa1a5e4c2e92365a924f1a2a8cf8108762f137a30cd55eb3a7d0504258f27a19ad0decca6b62a5c37a5aada709cbb46c4a871f + checksum: 10/183b72d5987dc93f9971667ce3f26d28b0e1058e71b129733dd9d5282aecba4c062b67c9567526780d2defd2bfbf950ca58d8306dc90b2761fd1e960d867ddb7 languageName: node linkType: hard "@babel/preset-env@npm:^7.21.5, @babel/preset-env@npm:^7.24.4": - version: 7.25.4 - resolution: "@babel/preset-env@npm:7.25.4" + version: 7.24.7 + resolution: "@babel/preset-env@npm:7.24.7" dependencies: - "@babel/compat-data": "npm:^7.25.4" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-option": "npm:^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3" - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0" + "@babel/compat-data": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-validator-option": "npm:^7.24.7" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.24.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.24.7" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.24.7" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-class-properties": "npm:^7.12.13" @@ -1597,30 +1474,29 @@ __metadata: "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-generator-functions": "npm:^7.24.7" "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" + "@babel/plugin-transform-block-scoping": "npm:^7.24.7" + "@babel/plugin-transform-class-properties": "npm:^7.24.7" "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-classes": "npm:^7.24.7" "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" + "@babel/plugin-transform-destructuring": "npm:^7.24.7" "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0" "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7" "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7" "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" + "@babel/plugin-transform-function-name": "npm:^7.24.7" "@babel/plugin-transform-json-strings": "npm:^7.24.7" - "@babel/plugin-transform-literals": "npm:^7.25.2" + "@babel/plugin-transform-literals": "npm:^7.24.7" "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" "@babel/plugin-transform-modules-amd": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" + "@babel/plugin-transform-modules-systemjs": "npm:^7.24.7" "@babel/plugin-transform-modules-umd": "npm:^7.24.7" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" "@babel/plugin-transform-new-target": "npm:^7.24.7" @@ -1629,9 +1505,9 @@ __metadata: "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" "@babel/plugin-transform-object-super": "npm:^7.24.7" "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.25.4" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" "@babel/plugin-transform-property-literals": "npm:^7.24.7" "@babel/plugin-transform-regenerator": "npm:^7.24.7" @@ -1640,20 +1516,20 @@ __metadata: "@babel/plugin-transform-spread": "npm:^7.24.7" "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" "@babel/plugin-transform-template-literals": "npm:^7.24.7" - "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8" + "@babel/plugin-transform-typeof-symbol": "npm:^7.24.7" "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.24.7" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.6" + babel-plugin-polyfill-corejs3: "npm:^0.10.4" babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.37.1" + core-js-compat: "npm:^3.31.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/45ca65bdc7fa11ca51167804052460eda32bf2e6620c7ba998e2d95bc867595913532ee7d748e97e808eabcc66aabe796bd75c59014d996ec8183fa5a7245862 + checksum: 10/2fd90c46efefadb48dae6d13de190ac48753af187ee394924cf532c79870ebb87658bd31f06649630827a478b17a4adc41717cc6d4c460ff2ed9fafa51e5b515 languageName: node linkType: hard @@ -1785,7 +1661,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": +"@babel/traverse@npm:^7.25.2": version: 7.25.6 resolution: "@babel/traverse@npm:7.25.6" dependencies: @@ -1811,7 +1687,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.6": +"@babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.6": version: 7.25.6 resolution: "@babel/types@npm:7.25.6" dependencies: @@ -1861,13 +1737,38 @@ __metadata: languageName: node linkType: hard -"@bundled-es-modules/tough-cookie@npm:^0.1.6": - version: 0.1.6 - resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6" +"@chevrotain/cst-dts-gen@npm:10.5.0": + version: 10.5.0 + resolution: "@chevrotain/cst-dts-gen@npm:10.5.0" dependencies: - "@types/tough-cookie": "npm:^4.0.5" - tough-cookie: "npm:^4.1.4" - checksum: 10/4f24a820f02c08c3ca0ff21272317357152093f76f9c8cc182517f61fa426ae53dadc4d68a3d6da5078e8d73f0ff8c0907a9f994c0be756162ba9c7358533e57 + "@chevrotain/gast": "npm:10.5.0" + "@chevrotain/types": "npm:10.5.0" + lodash: "npm:4.17.21" + checksum: 10/99027773daed40c80ce676eee3401a12a92be78679f977097d1afeeb6e9c05b14e5404d909b6b62c8ad6fed354fad215273297e2cf3b89353b600fe2bb8e483f + languageName: node + linkType: hard + +"@chevrotain/gast@npm:10.5.0": + version: 10.5.0 + resolution: "@chevrotain/gast@npm:10.5.0" + dependencies: + "@chevrotain/types": "npm:10.5.0" + lodash: "npm:4.17.21" + checksum: 10/539453540abd6b3005d69deb3e9504233ef52d91f5881adfb865a1c394731ab59983a223a906def79e7f4740ee8e97d2b7c8e0292c8158b567a2ea4a23f6c33d + languageName: node + linkType: hard + +"@chevrotain/types@npm:10.5.0": + version: 10.5.0 + resolution: "@chevrotain/types@npm:10.5.0" + checksum: 10/71526b43651124d06c9432871bba64b701b5c6ed4cf3715aa5803448f5c9d27ac65b441e6ce255392ed0f408e2db95e3246f133bd91914eb852f01973fbc8b8b + languageName: node + linkType: hard + +"@chevrotain/utils@npm:10.5.0": + version: 10.5.0 + resolution: "@chevrotain/utils@npm:10.5.0" + checksum: 10/92571b905e33c8cb7ba83935e9b8c5a46b4528d9906ad26d74f1530da33e4888cfac15fc4cea6212843cb62350906a88671d586afd9019ceb176ae2c3a1a402e languageName: node linkType: hard @@ -1887,44 +1788,44 @@ __metadata: languageName: node linkType: hard -"@csstools/css-parser-algorithms@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/css-parser-algorithms@npm:3.0.1" +"@csstools/css-parser-algorithms@npm:^2.6.3": + version: 2.6.3 + resolution: "@csstools/css-parser-algorithms@npm:2.6.3" peerDependencies: - "@csstools/css-tokenizer": ^3.0.1 - checksum: 10/02649a70ab7bab1fd000ca1d196ffb93ad3e2e0f36b4aa064f7973cd31edc5f7e63f8eaf7b94d801a0bfd207386b8b23cbe40be6e871c27042b084c3a717349e + "@csstools/css-tokenizer": ^2.3.1 + checksum: 10/b893e284ebcccf37d7928be31be94fb0d6725defc544b39892d5e59ed5950b413366491817539b0add08deb9fc258c57588053d4436f84b7bd3b43bfeee67bb1 languageName: node linkType: hard -"@csstools/css-tokenizer@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/css-tokenizer@npm:3.0.1" - checksum: 10/81ae01b2d3ec40ed3dc78f8507cbfdfe1dbc4ae3f8c8e29b8bb4414216a8c7a7a936fa0faa3d11a1e49ad72209aec7c05ad8450a4ffc30ba288aa074b4a0e3b3 +"@csstools/css-tokenizer@npm:^2.3.1": + version: 2.3.1 + resolution: "@csstools/css-tokenizer@npm:2.3.1" + checksum: 10/25c8643151667bfc2ce653174786d9f97fea93aa38d48432937bc634d8478dfa03e5e6ad18d3fff3d6fa245e9f6578f87ca07d9fd764a274702e4bb8dd34dede languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^3.0.1": - version: 3.0.1 - resolution: "@csstools/media-query-list-parser@npm:3.0.1" +"@csstools/media-query-list-parser@npm:^2.1.11": + version: 2.1.11 + resolution: "@csstools/media-query-list-parser@npm:2.1.11" peerDependencies: - "@csstools/css-parser-algorithms": ^3.0.1 - "@csstools/css-tokenizer": ^3.0.1 - checksum: 10/794344c67b126ad93d516ab3f01254d44cfa794c3401e34e8cc62ddc7fc13c9ab6c76cb517b643dbda47b57f2eb578c6a11c4a9a4b516d88e260a4016b64ce7f + "@csstools/css-parser-algorithms": ^2.6.3 + "@csstools/css-tokenizer": ^2.3.1 + checksum: 10/23ede5583c6f1f51ec45b9293fcaf1ecac0f69c7ea750bfe2245926a66a6ae8f7dea8b3604fc4a5b8be4a25c1bccf519a357bf926d486a7ff479e89685011ff4 languageName: node linkType: hard -"@csstools/selector-specificity@npm:^4.0.0": - version: 4.0.0 - resolution: "@csstools/selector-specificity@npm:4.0.0" +"@csstools/selector-specificity@npm:^3.1.1": + version: 3.1.1 + resolution: "@csstools/selector-specificity@npm:3.1.1" peerDependencies: - postcss-selector-parser: ^6.1.0 - checksum: 10/7076c1d8af0fba94f06718f87fba5bfea583f39089efa906ae38b5ecd6912d3d5865f7047a871ac524b1057e4c970622b2ade456b90d69fb9393902250057994 + postcss-selector-parser: ^6.0.13 + checksum: 10/3786a6afea97b08ad739ee8f4004f7e0a9e25049cee13af809dbda6462090744012a54bd9275a44712791e8f103f85d21641f14e81799f9dab946b0459a5e1ef languageName: node linkType: hard -"@cypress/request@npm:^3.0.6": - version: 3.0.6 - resolution: "@cypress/request@npm:3.0.6" +"@cypress/request@npm:^3.0.0": + version: 3.0.1 + resolution: "@cypress/request@npm:3.0.1" dependencies: aws-sign2: "npm:~0.7.0" aws4: "npm:^1.8.0" @@ -1932,19 +1833,19 @@ __metadata: combined-stream: "npm:~1.0.6" extend: "npm:~3.0.2" forever-agent: "npm:~0.6.1" - form-data: "npm:~4.0.0" - http-signature: "npm:~1.4.0" + form-data: "npm:~2.3.2" + http-signature: "npm:~1.3.6" is-typedarray: "npm:~1.0.0" isstream: "npm:~0.1.2" json-stringify-safe: "npm:~5.0.1" mime-types: "npm:~2.1.19" performance-now: "npm:^2.1.0" - qs: "npm:6.13.0" + qs: "npm:6.10.4" safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:^5.0.0" + tough-cookie: "npm:^4.1.3" tunnel-agent: "npm:^0.6.0" uuid: "npm:^8.3.2" - checksum: 10/ac1782111d93e0dbee2d2b2f35d9acf6821ef36eef9f4c3991e5903138fe2b8394a207c8c6e50a2b6cb2057e0ee5ebfc37cb7571c460c9685e80c948c25f6972 + checksum: 10/bf48bed6d6e493c05493902fb08b1d0646e7ec4300cf834816c2616f781db1a7fc447bd6f81de7c3076d738e8a6d75354e21d332f8f7ef8d9101d9b2f8e15b3a languageName: node linkType: hard @@ -1974,6 +1875,42 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/accessibility@npm:^3.1.0": + version: 3.1.0 + resolution: "@dnd-kit/accessibility@npm:3.1.0" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/750a0537877d5dde3753e9ef59d19628b553567e90fc3e3b14a79bded08f47f4a7161bc0d003d7cd6b3bd9e10aa233628dca07d2aa5a2120cac84555ba1653d8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.1.0": + version: 6.1.0 + resolution: "@dnd-kit/core@npm:6.1.0" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.0" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10/cf9e99763fbd9220cb6fdde2950c19fdf6248391234f5ee835601814124445fd8a6e4b3f5bc35543c802d359db8cc47f07d87046577adc41952ae981a03fbda0 + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/6cfe46a5fcdaced943982e7ae66b08b89235493e106eb5bc833737c25905e13375c6ecc3aa0c357d136cb21dae3966213dba063f19b7a60b1235a29a7b05ff84 + languageName: node + linkType: hard + "@dual-bundle/import-meta-resolve@npm:^4.1.0": version: 4.1.0 resolution: "@dual-bundle/import-meta-resolve@npm:4.1.0" @@ -2153,7 +2090,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": version: 4.10.1 resolution: "@eslint-community/regexpp@npm:4.10.1" checksum: 10/54f13817caf90545502d7a19e1b61df79087aee9584342ffc558b6d067530764a47f1c484f493f43e2c70cfdff59ccfd5f26df2af298c4ad528469e599bd1d53 @@ -2178,6 +2115,23 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^9.6.0" + globals: "npm:^13.19.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10/7a3b14f4b40fc1a22624c3f84d9f467a3d9ea1ca6e9a372116cb92507e485260359465b58e25bcb6c9981b155416b98c9973ad9b796053fd7b3f776a6946bce8 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^3.1.0": version: 3.1.0 resolution: "@eslint/eslintrc@npm:3.1.0" @@ -2195,6 +2149,13 @@ __metadata: languageName: node linkType: hard +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 10/3c501ce8a997cf6cbbaf4ed358af5492875e3550c19b9621413b82caa9ae5382c584b0efa79835639e6e0ddaa568caf3499318e5bdab68643ef4199dce5eb0a0 + languageName: node + linkType: hard + "@eslint/js@npm:9.10.0, @eslint/js@npm:^9.10.0": version: 9.10.0 resolution: "@eslint/js@npm:9.10.0" @@ -2234,6 +2195,17 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/config-array@npm:^0.11.14": + version: 0.11.14 + resolution: "@humanwhocodes/config-array@npm:0.11.14" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.2" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10/3ffb24ecdfab64014a230e127118d50a1a04d11080cbb748bc21629393d100850496456bbcb4e8c438957fe0934430d731042f1264d6a167b62d32fc2863580a + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2241,6 +2213,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/object-schema@npm:^2.0.2": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10/05bb99ed06c16408a45a833f03a732f59bf6184795d4efadd33238ff8699190a8c871ad1121241bb6501589a9598dc83bf25b99dcbcf41e155cdf36e35e937a3 + languageName: node + linkType: hard + "@humanwhocodes/retry@npm:^0.3.0": version: 0.3.0 resolution: "@humanwhocodes/retry@npm:0.3.0" @@ -2286,28 +2265,60 @@ __metadata: languageName: node linkType: hard -"@inquirer/core@npm:^9.2.1": - version: 9.2.1 - resolution: "@inquirer/core@npm:9.2.1" +"@inquirer/core@npm:^9.1.0": + version: 9.1.0 + resolution: "@inquirer/core@npm:9.1.0" dependencies: - "@inquirer/figures": "npm:^1.0.6" - "@inquirer/type": "npm:^2.0.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" "@types/mute-stream": "npm:^0.0.4" - "@types/node": "npm:^22.5.5" + "@types/node": "npm:^22.5.2" "@types/wrap-ansi": "npm:^3.0.0" ansi-escapes: "npm:^4.3.2" + cli-spinners: "npm:^2.9.2" cli-width: "npm:^4.1.0" mute-stream: "npm:^1.0.0" signal-exit: "npm:^4.1.0" strip-ansi: "npm:^6.0.1" wrap-ansi: "npm:^6.2.0" yoctocolors-cjs: "npm:^2.1.2" - checksum: 10/bf35e46e70add8ffa9e9d4ae6b528ac660484afca082bca31af95ce8a145a2f8c8d0d07cc7a8627771452e68ade9849c9c9c450a004133ed10ac2d6730900452 + checksum: 10/4aff54e4df53c2d8b93ab12e543f67b2f3193b448f8b0111eb111c21df1a7fdd0a7d081bf0a9029db8bda1a25031c11e643ac046df21c2b12edb3f096bb7c119 languageName: node linkType: hard -"@inquirer/expand@npm:^3.0.1": - version: 3.0.1 +"@inquirer/core@npm:^9.2.1": + version: 9.2.1 + resolution: "@inquirer/core@npm:9.2.1" + dependencies: + "@inquirer/figures": "npm:^1.0.6" + "@inquirer/type": "npm:^2.0.0" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^22.5.5" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/bf35e46e70add8ffa9e9d4ae6b528ac660484afca082bca31af95ce8a145a2f8c8d0d07cc7a8627771452e68ade9849c9c9c450a004133ed10ac2d6730900452 + languageName: node + linkType: hard + +"@inquirer/expand@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/expand@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/fdc9b2d0a509e9c0120fda8e1c044593b1f4b68038fd13e19fc35cc305f1e132fddd482bc1b44966cbcab06d579d130cabbb5cac50d917549d16d7e66fad4e74 + languageName: node + linkType: hard + +"@inquirer/expand@npm:^3.0.1": + version: 3.0.1 resolution: "@inquirer/expand@npm:3.0.1" dependencies: "@inquirer/core": "npm:^9.2.1" @@ -2324,6 +2335,13 @@ __metadata: languageName: node linkType: hard +"@inquirer/figures@npm:^1.0.5": + version: 1.0.5 + resolution: "@inquirer/figures@npm:1.0.5" + checksum: 10/60a51b2cdef03c89be25071c23d8c4ae427c56d8ac1b00bf054ca7be446674adc4edd66c15465fe6a81ff0726b024bf37f8a2903a8387ef968d33058da3e7a15 + languageName: node + linkType: hard + "@inquirer/figures@npm:^1.0.6": version: 1.0.6 resolution: "@inquirer/figures@npm:1.0.6" @@ -2331,6 +2349,16 @@ __metadata: languageName: node linkType: hard +"@inquirer/input@npm:^2.3.0": + version: 2.3.0 + resolution: "@inquirer/input@npm:2.3.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/type": "npm:^1.5.3" + checksum: 10/1b6291f49be4e0ba6150b1b9971676cc5aec0271a946b9115975da21c7e32ee8bea6edd7b72689ed403f79f759d12e909920cca44684c02173ad9524de143341 + languageName: node + linkType: hard + "@inquirer/input@npm:^3.0.1": version: 3.0.1 resolution: "@inquirer/input@npm:3.0.1" @@ -2341,6 +2369,19 @@ __metadata: languageName: node linkType: hard +"@inquirer/select@npm:^2.5.0": + version: 2.5.0 + resolution: "@inquirer/select@npm:2.5.0" + dependencies: + "@inquirer/core": "npm:^9.1.0" + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.3" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/c47ec8ad1133bd7218d3c62e0fa62ecd2476ac291c87535e37956f47d12e2c1c2e4232fcc7b90de036bfe111bb9071a690d8d6d34b9839eb4c5c236bac309a6e + languageName: node + linkType: hard + "@inquirer/select@npm:^3.0.1": version: 3.0.1 resolution: "@inquirer/select@npm:3.0.1" @@ -2361,6 +2402,15 @@ __metadata: languageName: node linkType: hard +"@inquirer/type@npm:^1.5.3": + version: 1.5.3 + resolution: "@inquirer/type@npm:1.5.3" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10/63976016f10a0cba725243dbe0beea88ee0090874e454482070e62e54a478405f3662acd3d97ce24516ada049e6e2aa72db627b65b2308f90a7398e11a287bd5 + languageName: node + linkType: hard + "@inquirer/type@npm:^2.0.0": version: 2.0.0 resolution: "@inquirer/type@npm:2.0.0" @@ -2384,13 +2434,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/string-locale-compare@npm:^1.1.0": - version: 1.1.0 - resolution: "@isaacs/string-locale-compare@npm:1.1.0" - checksum: 10/85682b14602f32023e487f62bc4076fe13cd3e887df9cca36acc0d41ea99b403100d586acb9367331526f3ee737d802ecaa582f59020998d75991e62a7ef0db5 - languageName: node - linkType: hard - "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -2821,9 +2864,11 @@ __metadata: "@babel/preset-env": "npm:^7.21.5" "@babel/preset-react": "npm:^7.18.6" "@babel/preset-typescript": "npm:^7.21.5" + "@dnd-kit/core": "npm:^6.1.0" "@eslint/js": "npm:^9.10.0" "@kaoto-next/uniforms-patternfly": "npm:^0.7.14" "@kaoto/camel-catalog": "workspace:*" + "@kaoto/xml-schema-ts": "workspace:*" "@kie-tools-core/editor": "npm:0.32.0" "@kie-tools-core/notifications": "npm:0.32.0" "@patternfly/patternfly": "npm:5.4.2" @@ -2846,10 +2891,13 @@ __metadata: "@types/react": "npm:^18.2.25" "@types/react-dom": "npm:^18.2.10" "@types/uuid": "npm:^10.0.0" + "@types/xml-name-validator": "npm:^4.0.3" + "@visx/shape": "npm:^3.12.0" "@vitejs/plugin-react": "npm:^4.0.3" ajv: "npm:^8.12.0" ajv-formats: "npm:^3.0.0" babel-jest: "npm:^29.4.2" + chevrotain: "npm:10.5.0" clsx: "npm:^2.1.0" copyfiles: "npm:^2.4.1" eslint: "npm:^9.10.0" @@ -2889,6 +2937,8 @@ __metadata: vite: "npm:^5.4.0" vite-plugin-dts: "npm:^4.0.2" vite-plugin-static-copy: "npm:^1.0.6" + xml-formatter: "npm:^3.6.2" + xml-name-validator: "npm:^5.0.0" yaml: "npm:^2.3.2" zustand: "npm:^4.3.9" peerDependencies: @@ -2905,6 +2955,28 @@ __metadata: languageName: unknown linkType: soft +"@kaoto/xml-schema-ts@workspace:*, @kaoto/xml-schema-ts@workspace:packages/xml-schema-ts": + version: 0.0.0-use.local + resolution: "@kaoto/xml-schema-ts@workspace:packages/xml-schema-ts" + dependencies: + "@babel/core": "npm:^7.23.2" + "@babel/preset-env": "npm:^7.21.5" + "@babel/preset-typescript": "npm:^7.21.5" + "@testing-library/jest-dom": "npm:^6.4.2" + "@types/jest": "npm:^29.5.12" + eslint: "npm:^8.45.0" + eslint-config-prettier: "npm:^9.0.0" + eslint-plugin-import: "npm:^2.26.0" + eslint-plugin-jest: "npm:^27.2.1" + eslint-plugin-prettier: "npm:^5.0.0" + jest: "npm:^29.7.0" + prettier: "npm:^3.0.0" + rimraf: "npm:^6.0.0" + typescript: "npm:^5.4.2" + vite: "npm:^5.4.0" + languageName: unknown + linkType: soft + "@kie-tools-core/backend@npm:0.32.0": version: 0.32.0 resolution: "@kie-tools-core/backend@npm:0.32.0" @@ -3036,7 +3108,69 @@ __metadata: languageName: node linkType: hard -"@lerna-lite/cli@npm:3.9.2, @lerna-lite/cli@npm:^3.0.0": +"@lerna-lite/cli@npm:3.5.1": + version: 3.5.1 + resolution: "@lerna-lite/cli@npm:3.5.1" + dependencies: + "@lerna-lite/core": "npm:3.5.1" + "@lerna-lite/init": "npm:3.5.1" + dedent: "npm:^1.5.3" + dotenv: "npm:^16.4.5" + import-local: "npm:^3.1.0" + load-json-file: "npm:^7.0.1" + npmlog: "npm:^7.0.1" + yargs: "npm:^17.7.2" + peerDependenciesMeta: + "@lerna-lite/exec": + optional: true + "@lerna-lite/list": + optional: true + "@lerna-lite/publish": + optional: true + "@lerna-lite/run": + optional: true + "@lerna-lite/version": + optional: true + "@lerna-lite/watch": + optional: true + bin: + lerna: dist/cli.js + checksum: 10/f75aa82856140ccde90c5ab4269de1f81a23b411000c1f5e73b8d0d2f93e4bc97171267a852f8d71f4f6a95cd9f2daebd85452e57c6094413277606d7417e9b8 + languageName: node + linkType: hard + +"@lerna-lite/cli@npm:3.9.1": + version: 3.9.1 + resolution: "@lerna-lite/cli@npm:3.9.1" + dependencies: + "@lerna-lite/core": "npm:3.9.1" + "@lerna-lite/init": "npm:3.9.1" + "@lerna-lite/npmlog": "npm:3.8.0" + dedent: "npm:^1.5.3" + dotenv: "npm:^16.4.5" + import-local: "npm:^3.2.0" + load-json-file: "npm:^7.0.1" + yargs: "npm:^17.7.2" + peerDependenciesMeta: + "@lerna-lite/exec": + optional: true + "@lerna-lite/list": + optional: true + "@lerna-lite/publish": + optional: true + "@lerna-lite/run": + optional: true + "@lerna-lite/version": + optional: true + "@lerna-lite/watch": + optional: true + bin: + lerna: dist/cli.js + checksum: 10/94e8837711d3a18c829cc5051d480ef551dc4e7462ca6581524690d1a1f2971caad40f0c40b95c90ea39ba442e506a1174ab05c1920f1fbb200d97b73d5b2515 + languageName: node + linkType: hard + +"@lerna-lite/cli@npm:^3.0.0": version: 3.9.2 resolution: "@lerna-lite/cli@npm:3.9.2" dependencies: @@ -3067,6 +3201,76 @@ __metadata: languageName: node linkType: hard +"@lerna-lite/core@npm:3.5.1": + version: 3.5.1 + resolution: "@lerna-lite/core@npm:3.5.1" + dependencies: + "@npmcli/run-script": "npm:^8.1.0" + chalk: "npm:^5.3.0" + clone-deep: "npm:^4.0.1" + config-chain: "npm:^1.1.13" + cosmiconfig: "npm:^9.0.0" + dedent: "npm:^1.5.3" + execa: "npm:^8.0.1" + fs-extra: "npm:^11.2.0" + glob-parent: "npm:^6.0.2" + globby: "npm:^14.0.1" + inquirer: "npm:^9.2.23" + is-ci: "npm:^3.0.1" + json5: "npm:^2.2.3" + load-json-file: "npm:^7.0.1" + minimatch: "npm:^9.0.4" + npm-package-arg: "npm:^11.0.2" + npmlog: "npm:^7.0.1" + p-map: "npm:^7.0.2" + p-queue: "npm:^8.0.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.2" + slash: "npm:^5.1.0" + strong-log-transformer: "npm:^2.1.0" + write-file-atomic: "npm:^5.0.1" + write-json-file: "npm:^5.0.0" + write-package: "npm:^7.0.1" + checksum: 10/82c5cec0fb591a5bedf2a86edfc0ae4c4e3102ae42fddee11767ff76ebcbe2c5b0d3a620e283edc2c97dd93e44c43a4e7b412bb30dbeaba36d80365c9e0c2fb4 + languageName: node + linkType: hard + +"@lerna-lite/core@npm:3.9.1": + version: 3.9.1 + resolution: "@lerna-lite/core@npm:3.9.1" + dependencies: + "@inquirer/expand": "npm:^2.3.0" + "@inquirer/input": "npm:^2.3.0" + "@inquirer/select": "npm:^2.5.0" + "@lerna-lite/npmlog": "npm:^3.8.0" + "@npmcli/run-script": "npm:^8.1.0" + chalk: "npm:^5.3.0" + clone-deep: "npm:^4.0.1" + config-chain: "npm:^1.1.13" + cosmiconfig: "npm:^9.0.0" + dedent: "npm:^1.5.3" + execa: "npm:^8.0.1" + fs-extra: "npm:^11.2.0" + glob-parent: "npm:^6.0.2" + globby: "npm:^14.0.2" + is-ci: "npm:^3.0.1" + json5: "npm:^2.2.3" + load-json-file: "npm:^7.0.1" + minimatch: "npm:^9.0.5" + npm-package-arg: "npm:^11.0.3" + p-map: "npm:^7.0.2" + p-queue: "npm:^8.0.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.6.3" + slash: "npm:^5.1.0" + strong-log-transformer: "npm:^2.1.0" + write-file-atomic: "npm:^5.0.1" + write-json-file: "npm:^6.0.0" + write-package: "npm:^7.1.0" + checksum: 10/494c4c3c2568b127670bc2ad74059041290ede4d24f5e12fb07506f3979f0fd8ad1ef6dcd762bf3057c25a78108a79ad9adebcd831ade1e2f18533edf8e9526a + languageName: node + linkType: hard + "@lerna-lite/core@npm:3.9.2": version: 3.9.2 resolution: "@lerna-lite/core@npm:3.9.2" @@ -3103,6 +3307,30 @@ __metadata: languageName: node linkType: hard +"@lerna-lite/init@npm:3.5.1": + version: 3.5.1 + resolution: "@lerna-lite/init@npm:3.5.1" + dependencies: + "@lerna-lite/core": "npm:3.5.1" + fs-extra: "npm:^11.2.0" + p-map: "npm:^7.0.2" + write-json-file: "npm:^5.0.0" + checksum: 10/7e03e90468dc7b49e37abf79e26e9e3997985159ec5622b6e081d54c68463336fe7b07fe9a2d356bd0c7ea3356d12cc0c7678da173080685eee503d87b00ddd0 + languageName: node + linkType: hard + +"@lerna-lite/init@npm:3.9.1": + version: 3.9.1 + resolution: "@lerna-lite/init@npm:3.9.1" + dependencies: + "@lerna-lite/core": "npm:3.9.1" + fs-extra: "npm:^11.2.0" + p-map: "npm:^7.0.2" + write-json-file: "npm:^6.0.0" + checksum: 10/5193e963b6d4b57376389fea1db67eda579e96ad49be03d0d3f7686881ae0640c66ed7823b2ec462d2add5acd52beb5011380f75ca2146a6c4a035d8b4b70b1e + languageName: node + linkType: hard + "@lerna-lite/init@npm:3.9.2": version: 3.9.2 resolution: "@lerna-lite/init@npm:3.9.2" @@ -3133,47 +3361,89 @@ __metadata: linkType: hard "@lerna-lite/publish@npm:^3.0.0": - version: 3.9.2 - resolution: "@lerna-lite/publish@npm:3.9.2" - dependencies: - "@lerna-lite/cli": "npm:3.9.2" - "@lerna-lite/core": "npm:3.9.2" - "@lerna-lite/npmlog": "npm:^3.8.0" - "@lerna-lite/version": "npm:3.9.2" - "@npmcli/arborist": "npm:^7.5.4" - "@npmcli/package-json": "npm:^5.2.1" - byte-size: "npm:^9.0.0" + version: 3.5.2 + resolution: "@lerna-lite/publish@npm:3.5.2" + dependencies: + "@lerna-lite/cli": "npm:3.5.1" + "@lerna-lite/core": "npm:3.5.1" + "@lerna-lite/version": "npm:3.5.2" + byte-size: "npm:^8.1.1" + chalk: "npm:^5.3.0" columnify: "npm:^1.6.0" fs-extra: "npm:^11.2.0" - globby: "npm:^14.0.2" + glob: "npm:^10.4.1" has-unicode: "npm:^2.0.1" libnpmaccess: "npm:^8.0.6" libnpmpublish: "npm:^9.0.9" normalize-path: "npm:^3.0.0" - npm-package-arg: "npm:^11.0.3" - npm-packlist: "npm:^8.0.2" - npm-registry-fetch: "npm:^17.1.0" + npm-package-arg: "npm:^11.0.2" + npm-packlist: "npm:^5.1.3" + npm-registry-fetch: "npm:^17.0.1" + npmlog: "npm:^7.0.1" p-map: "npm:^7.0.2" p-pipe: "npm:^4.0.0" pacote: "npm:^18.0.6" - picocolors: "npm:^1.1.0" - semver: "npm:^7.6.3" - ssri: "npm:^11.0.0" + pify: "npm:^6.1.0" + read-package-json: "npm:^7.0.1" + semver: "npm:^7.6.2" + ssri: "npm:^10.0.6" tar: "npm:^6.2.1" temp-dir: "npm:^3.0.0" - checksum: 10/eb06506e3dd0b161e193115851b671b421fa178db078714be1e79832cc33cf86bcdabb77d1b87b90497bc7086e4e60b55e68f0d9a1c4e733dfd66fdb129dd4ce + checksum: 10/f05b4cc762a4be7cda2e0bd686c5bd716ff736959141094ff0951be577630f66416d3b45911510be9549a1bc27964840a96125b4ebf8c85fd0246a0e1e3bb217 languageName: node linkType: hard -"@lerna-lite/version@npm:3.9.2, @lerna-lite/version@npm:^3.0.0": - version: 3.9.2 - resolution: "@lerna-lite/version@npm:3.9.2" +"@lerna-lite/version@npm:3.5.2": + version: 3.5.2 + resolution: "@lerna-lite/version@npm:3.5.2" dependencies: - "@lerna-lite/cli": "npm:3.9.2" - "@lerna-lite/core": "npm:3.9.2" + "@lerna-lite/cli": "npm:3.5.1" + "@lerna-lite/core": "npm:3.5.1" + "@octokit/plugin-enterprise-rest": "npm:^6.0.1" + "@octokit/rest": "npm:^20.1.1" + chalk: "npm:^5.3.0" + conventional-changelog-angular: "npm:^7.0.0" + conventional-changelog-core: "npm:^7.0.0" + conventional-changelog-writer: "npm:^7.0.1" + conventional-commits-parser: "npm:^5.0.0" + conventional-recommended-bump: "npm:^9.0.0" + dedent: "npm:^1.5.3" + fs-extra: "npm:^11.2.0" + get-stream: "npm:^9.0.1" + git-url-parse: "npm:^14.0.0" + graceful-fs: "npm:^4.2.11" + is-stream: "npm:^4.0.1" + load-json-file: "npm:^7.0.1" + make-dir: "npm:^5.0.0" + minimatch: "npm:^9.0.4" + new-github-release-url: "npm:^2.0.0" + node-fetch: "npm:^3.3.2" + npm-package-arg: "npm:^11.0.2" + npmlog: "npm:^7.0.1" + p-limit: "npm:^5.0.0" + p-map: "npm:^7.0.2" + p-pipe: "npm:^4.0.0" + p-reduce: "npm:^3.0.0" + pify: "npm:^6.1.0" + semver: "npm:^7.6.2" + slash: "npm:^5.1.0" + temp-dir: "npm:^3.0.0" + uuid: "npm:^9.0.1" + write-json-file: "npm:^5.0.0" + checksum: 10/392c502ab89eb5adad9f2624b73b0b112cefceeed1ca2e327d41052dd84c55385b203fcdf94a0a2874de7d17715564a2faa07e787a2cbf8251c2d1f5d1ddc8f1 + languageName: node + linkType: hard + +"@lerna-lite/version@npm:^3.0.0": + version: 3.9.1 + resolution: "@lerna-lite/version@npm:3.9.1" + dependencies: + "@lerna-lite/cli": "npm:3.9.1" + "@lerna-lite/core": "npm:3.9.1" "@lerna-lite/npmlog": "npm:^3.8.0" "@octokit/plugin-enterprise-rest": "npm:^6.0.1" "@octokit/rest": "npm:^21.0.2" + chalk: "npm:^5.3.0" conventional-changelog-angular: "npm:^7.0.0" conventional-changelog-core: "npm:^7.0.0" conventional-changelog-writer: "npm:^7.0.1" @@ -3195,14 +3465,22 @@ __metadata: p-map: "npm:^7.0.2" p-pipe: "npm:^4.0.0" p-reduce: "npm:^3.0.0" - picocolors: "npm:^1.1.0" pify: "npm:^6.1.0" semver: "npm:^7.6.3" slash: "npm:^5.1.0" temp-dir: "npm:^3.0.0" uuid: "npm:^10.0.0" write-json-file: "npm:^6.0.0" - checksum: 10/1b032db7674535914783765914fc4c1800dd616d264466f59782faf700f4cfdaa25e842ae57e268e0496dd5bbdfecb26a45a29ac6b93301f7b2741518cb0566d + checksum: 10/25c693ab84cf094535dd0bff26ef0683083f46bb17f7ff00f9cb16c20ab15cfd8925604476a61a59e0d8a69845d82087484546658f83e331eb06ad5cb4e65119 + languageName: node + linkType: hard + +"@ljharb/through@npm:^2.3.13": + version: 2.3.13 + resolution: "@ljharb/through@npm:2.3.13" + dependencies: + call-bind: "npm:^1.0.7" + checksum: 10/6150c6c43a726d52c26863ed6dc4ab54fa7cf625c81463a5ddec86278c99e23bf94dfc99ebf09a9ac3191332d4a27344e092f7e07f252b8cd600e2b38e645870 languageName: node linkType: hard @@ -3218,28 +3496,28 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.29.6": - version: 7.29.6 - resolution: "@microsoft/api-extractor-model@npm:7.29.6" +"@microsoft/api-extractor-model@npm:7.29.4": + version: 7.29.4 + resolution: "@microsoft/api-extractor-model@npm:7.29.4" dependencies: "@microsoft/tsdoc": "npm:~0.15.0" "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.7.0" - checksum: 10/7339366297a4438e33aef34fac4fa1fea3b7357fefd0a88da18221fa80152397326fb383563106f995899a382119f5c95f357abc3aa7448fcc1a2bee021696aa + "@rushstack/node-core-library": "npm:5.5.1" + checksum: 10/50bd4e58bfe9d43e0ca4a72324601ce17c015e517b0803bb787d1067bef9372a23bbbf2551f47d1c5a7fef742826ffa18aea43b96c823b82c07f745e3ea25657 languageName: node linkType: hard -"@microsoft/api-extractor@npm:7.47.7": - version: 7.47.7 - resolution: "@microsoft/api-extractor@npm:7.47.7" +"@microsoft/api-extractor@npm:7.47.4": + version: 7.47.4 + resolution: "@microsoft/api-extractor@npm:7.47.4" dependencies: - "@microsoft/api-extractor-model": "npm:7.29.6" + "@microsoft/api-extractor-model": "npm:7.29.4" "@microsoft/tsdoc": "npm:~0.15.0" "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.7.0" + "@rushstack/node-core-library": "npm:5.5.1" "@rushstack/rig-package": "npm:0.5.3" - "@rushstack/terminal": "npm:0.14.0" - "@rushstack/ts-command-line": "npm:4.22.6" + "@rushstack/terminal": "npm:0.13.3" + "@rushstack/ts-command-line": "npm:4.22.3" lodash: "npm:~4.17.15" minimatch: "npm:~3.0.3" resolve: "npm:~1.22.1" @@ -3248,7 +3526,7 @@ __metadata: typescript: "npm:5.4.2" bin: api-extractor: bin/api-extractor - checksum: 10/eef9d5087c72e23181aebcc8ee17678a99c27ee54a527a0f347e3a2b1432fc4f660127b3e1b9cc83e7375dfe174ec4699b7e433b91f789837290c943994a8ab2 + checksum: 10/e7be27981cc4ba34d3fcc694d044bb952835d77ab908ce1d528cd76d7100cee33137c0d13257332dd12e07dae1d0937f0f5218348f3c9f7b2258dda89dce47cc languageName: node linkType: hard @@ -3295,17 +3573,24 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.35.8": - version: 0.35.9 - resolution: "@mswjs/interceptors@npm:0.35.9" +"@mswjs/cookies@npm:^1.1.0": + version: 1.1.1 + resolution: "@mswjs/cookies@npm:1.1.1" + checksum: 10/85ece5b3e6e480fb86e8970ef35a945fdbc9041cfa2414d9bc15ee407560a8b53175af91d36056bd7ec0b21c6af667dc12989a8f7ba2d59a13b3302e00a624c6 + languageName: node + linkType: hard + +"@mswjs/interceptors@npm:^0.29.0": + version: 0.29.1 + resolution: "@mswjs/interceptors@npm:0.29.1" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" "@open-draft/until": "npm:^2.0.0" is-node-process: "npm:^1.2.0" - outvariant: "npm:^1.4.3" + outvariant: "npm:^1.2.1" strict-event-emitter: "npm:^0.5.1" - checksum: 10/9eaf8d7876c9a38c2c9a1259873f8ad27ab41c68a49f7e14a55cd9f596458d9232adb85a5084b044d4eead3be1e7ef5bf54ed6d774d16b02d96caf1e7faa2ab3 + checksum: 10/6a6ee6eb3db0fed60bbeb710288f8c1e2cac84f08254756b684dbd553b04449dfe4cce1261fcc83772ee114be2043d9777e2ee6d72bc8d14fd394f961827e528 languageName: node linkType: hard @@ -3349,52 +3634,7 @@ __metadata: languageName: node linkType: hard -"@npmcli/arborist@npm:^7.5.4": - version: 7.5.4 - resolution: "@npmcli/arborist@npm:7.5.4" - dependencies: - "@isaacs/string-locale-compare": "npm:^1.1.0" - "@npmcli/fs": "npm:^3.1.1" - "@npmcli/installed-package-contents": "npm:^2.1.0" - "@npmcli/map-workspaces": "npm:^3.0.2" - "@npmcli/metavuln-calculator": "npm:^7.1.1" - "@npmcli/name-from-folder": "npm:^2.0.0" - "@npmcli/node-gyp": "npm:^3.0.0" - "@npmcli/package-json": "npm:^5.1.0" - "@npmcli/query": "npm:^3.1.0" - "@npmcli/redact": "npm:^2.0.0" - "@npmcli/run-script": "npm:^8.1.0" - bin-links: "npm:^4.0.4" - cacache: "npm:^18.0.3" - common-ancestor-path: "npm:^1.0.1" - hosted-git-info: "npm:^7.0.2" - json-parse-even-better-errors: "npm:^3.0.2" - json-stringify-nice: "npm:^1.1.4" - lru-cache: "npm:^10.2.2" - minimatch: "npm:^9.0.4" - nopt: "npm:^7.2.1" - npm-install-checks: "npm:^6.2.0" - npm-package-arg: "npm:^11.0.2" - npm-pick-manifest: "npm:^9.0.1" - npm-registry-fetch: "npm:^17.0.1" - pacote: "npm:^18.0.6" - parse-conflict-json: "npm:^3.0.0" - proc-log: "npm:^4.2.0" - proggy: "npm:^2.0.0" - promise-all-reject-late: "npm:^1.0.0" - promise-call-limit: "npm:^3.0.1" - read-package-json-fast: "npm:^3.0.2" - semver: "npm:^7.3.7" - ssri: "npm:^10.0.6" - treeverse: "npm:^3.0.0" - walk-up-path: "npm:^3.0.1" - bin: - arborist: bin/index.js - checksum: 10/b77170754f419171e5ca2abfb679a9c811443e2b67036916a62eda81fd069f12c98186941cd73a0d36c2ec76cda638b43ceeb4c5fae39de1bb9df825432f3ef7 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^3.1.0, @npmcli/fs@npm:^3.1.1": +"@npmcli/fs@npm:^3.1.0": version: 3.1.1 resolution: "@npmcli/fs@npm:3.1.1" dependencies: @@ -3419,7 +3659,7 @@ __metadata: languageName: node linkType: hard -"@npmcli/installed-package-contents@npm:^2.0.1, @npmcli/installed-package-contents@npm:^2.1.0": +"@npmcli/installed-package-contents@npm:^2.0.1": version: 2.1.0 resolution: "@npmcli/installed-package-contents@npm:2.1.0" dependencies: @@ -3431,63 +3671,16 @@ __metadata: languageName: node linkType: hard -"@npmcli/map-workspaces@npm:^3.0.2": - version: 3.0.6 - resolution: "@npmcli/map-workspaces@npm:3.0.6" - dependencies: - "@npmcli/name-from-folder": "npm:^2.0.0" - glob: "npm:^10.2.2" - minimatch: "npm:^9.0.0" - read-package-json-fast: "npm:^3.0.0" - checksum: 10/b364b155991a4ff85db5ea5b9f809ab65936350fc36fe1e51d5ab8cd479bba57e69f02e17215c0e2126e383074c2987c268d8e589aacd26c9962e028f4da98f2 - languageName: node - linkType: hard - -"@npmcli/metavuln-calculator@npm:^7.1.1": - version: 7.1.1 - resolution: "@npmcli/metavuln-calculator@npm:7.1.1" - dependencies: - cacache: "npm:^18.0.0" - json-parse-even-better-errors: "npm:^3.0.0" - pacote: "npm:^18.0.0" - proc-log: "npm:^4.1.0" - semver: "npm:^7.3.5" - checksum: 10/57163b4bde4af3f5badb0c9b0c868f9539e2a112ee73c606680b7548b148bf58e793952d74eb1e581c9cc2e630bc03bc60adc04b3f1e7960482f97af817f28d2 - languageName: node - linkType: hard - -"@npmcli/name-from-folder@npm:^2.0.0": - version: 2.0.0 - resolution: "@npmcli/name-from-folder@npm:2.0.0" - checksum: 10/75beb40373f916cfcf7327958b3ab920ab4e32d24217197927dd1c76a325c7645695011fce9cb2a8f93616f8b74946e84eebe3830303e11ed9d400dae623a99b - languageName: node - linkType: hard - "@npmcli/node-gyp@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/node-gyp@npm:3.0.0" - checksum: 10/dd9fed3e80df8fbb20443f28651a8ed7235f2c15286ecc010e2d3cd392c85912e59ef29218c0b02f098defb4cbc8cdf045aab1d32d5cef6ace289913196ed5df - languageName: node - linkType: hard - -"@npmcli/package-json@npm:^5.0.0, @npmcli/package-json@npm:^5.1.0": - version: 5.2.0 - resolution: "@npmcli/package-json@npm:5.2.0" - dependencies: - "@npmcli/git": "npm:^5.0.0" - glob: "npm:^10.2.2" - hosted-git-info: "npm:^7.0.0" - json-parse-even-better-errors: "npm:^3.0.0" - normalize-package-data: "npm:^6.0.0" - proc-log: "npm:^4.0.0" - semver: "npm:^7.5.3" - checksum: 10/c3d2218877bfc005bca3b7a11f53825bf16a68811b8e8ed0c9b219cceb8e8e646d70efab8c5d6decbd8007f286076468b3f456dab4d41d648aff73a5f3a6fce2 + version: 3.0.0 + resolution: "@npmcli/node-gyp@npm:3.0.0" + checksum: 10/dd9fed3e80df8fbb20443f28651a8ed7235f2c15286ecc010e2d3cd392c85912e59ef29218c0b02f098defb4cbc8cdf045aab1d32d5cef6ace289913196ed5df languageName: node linkType: hard -"@npmcli/package-json@npm:^5.2.1": - version: 5.2.1 - resolution: "@npmcli/package-json@npm:5.2.1" +"@npmcli/package-json@npm:^5.0.0, @npmcli/package-json@npm:^5.1.0": + version: 5.2.0 + resolution: "@npmcli/package-json@npm:5.2.0" dependencies: "@npmcli/git": "npm:^5.0.0" glob: "npm:^10.2.2" @@ -3496,7 +3689,7 @@ __metadata: normalize-package-data: "npm:^6.0.0" proc-log: "npm:^4.0.0" semver: "npm:^7.5.3" - checksum: 10/304a819b93f79a6e0e56cb371961a66d2db72142e310d545ecbbbe4d917025a30601aa8e63a5f0cc28f0fe281c116bdaf79b334619b105a1d027a2b769ecd137 + checksum: 10/c3d2218877bfc005bca3b7a11f53825bf16a68811b8e8ed0c9b219cceb8e8e646d70efab8c5d6decbd8007f286076468b3f456dab4d41d648aff73a5f3a6fce2 languageName: node linkType: hard @@ -3509,15 +3702,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/query@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/query@npm:3.1.0" - dependencies: - postcss-selector-parser: "npm:^6.0.10" - checksum: 10/fa79ae317934c95d14b89cb149cb8eb0b2a4e611acf0661681cfa964bf9af6740f60efe095c8bb7e880398e0955666408cc8a3ffede90e87922cb81cce1efcdb - languageName: node - linkType: hard - "@npmcli/redact@npm:^2.0.0": version: 2.0.1 resolution: "@npmcli/redact@npm:2.0.1" @@ -3539,6 +3723,13 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^4.0.0": + version: 4.0.0 + resolution: "@octokit/auth-token@npm:4.0.0" + checksum: 10/60e42701e341d700f73c518c7a35675d36d79fa9d5e838cc3ade96d147e49f5ba74db2e07b2337c2b95aaa540aa42088116df2122daa25633f9e70a2c8785c44 + languageName: node + linkType: hard + "@octokit/auth-token@npm:^5.0.0": version: 5.1.1 resolution: "@octokit/auth-token@npm:5.1.1" @@ -3546,6 +3737,21 @@ __metadata: languageName: node linkType: hard +"@octokit/core@npm:^5.0.2": + version: 5.2.0 + resolution: "@octokit/core@npm:5.2.0" + dependencies: + "@octokit/auth-token": "npm:^4.0.0" + "@octokit/graphql": "npm:^7.1.0" + "@octokit/request": "npm:^8.3.1" + "@octokit/request-error": "npm:^5.1.0" + "@octokit/types": "npm:^13.0.0" + before-after-hook: "npm:^2.2.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10/2e40baf0b5c6949922436a653c213be43befd9690c43dd89872f669f3ac23117ae8ae5e5d6c18094813756c71c3f4fbedd575a891f0b89e12f58b2c38b7f3c13 + languageName: node + linkType: hard + "@octokit/core@npm:^6.1.2": version: 6.1.2 resolution: "@octokit/core@npm:6.1.2" @@ -3571,6 +3777,27 @@ __metadata: languageName: node linkType: hard +"@octokit/endpoint@npm:^9.0.1": + version: 9.0.5 + resolution: "@octokit/endpoint@npm:9.0.5" + dependencies: + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10/212122f653bf076ec37dd7de44bd54db74aa3cd16be4c395c91444488331becd83351e26b30248168e2cc28fc07b1a96e8f74adbbab02826f76de92e069f391f + languageName: node + linkType: hard + +"@octokit/graphql@npm:^7.1.0": + version: 7.1.0 + resolution: "@octokit/graphql@npm:7.1.0" + dependencies: + "@octokit/request": "npm:^8.3.0" + "@octokit/types": "npm:^13.0.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10/da6857a69dc93cd20a11d3a905db4214d269d246a6aaee1d8734f922024b08ffdef0b3cba2ac79917633043b4f50464242b0bd92a265c960083dfff5b833dbbe + languageName: node + linkType: hard + "@octokit/graphql@npm:^8.0.0": version: 8.1.1 resolution: "@octokit/graphql@npm:8.1.1" @@ -3596,6 +3823,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-paginate-rest@npm:11.3.1": + version: 11.3.1 + resolution: "@octokit/plugin-paginate-rest@npm:11.3.1" + dependencies: + "@octokit/types": "npm:^13.5.0" + peerDependencies: + "@octokit/core": 5 + checksum: 10/82f5bcc3a536a44bed0a205c8301176c0d210b7a1c6d035a79b31a102e2e02f46234a38629cc984a21be544194ac69151814e9a909416aa7389cdffd1297bcd9 + languageName: node + linkType: hard + "@octokit/plugin-paginate-rest@npm:^11.0.0": version: 11.3.3 resolution: "@octokit/plugin-paginate-rest@npm:11.3.3" @@ -3607,6 +3845,15 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-request-log@npm:^4.0.0": + version: 4.0.1 + resolution: "@octokit/plugin-request-log@npm:4.0.1" + peerDependencies: + "@octokit/core": 5 + checksum: 10/fd8c0a201490cba00084689a0d1d54fc7b5ab5b6bdb7e447056b947b1754f78526e9685400eab10d3522bfa7b5bc49c555f41ec412c788610b96500b168f3789 + languageName: node + linkType: hard + "@octokit/plugin-request-log@npm:^5.3.1": version: 5.3.1 resolution: "@octokit/plugin-request-log@npm:5.3.1" @@ -3616,6 +3863,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-rest-endpoint-methods@npm:13.2.2": + version: 13.2.2 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.2" + dependencies: + "@octokit/types": "npm:^13.5.0" + peerDependencies: + "@octokit/core": ^5 + checksum: 10/9eccc1a22aa0b65f3f9378f26a74c386683db420c33202998918df1eef492e93212e1849e1d85530f425602663cfc2bfbf385a30991b8a04470334c74ba2386b + languageName: node + linkType: hard + "@octokit/plugin-rest-endpoint-methods@npm:^13.0.0": version: 13.2.4 resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.4" @@ -3627,6 +3885,17 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^5.1.0": + version: 5.1.0 + resolution: "@octokit/request-error@npm:5.1.0" + dependencies: + "@octokit/types": "npm:^13.1.0" + deprecation: "npm:^2.0.0" + once: "npm:^1.4.0" + checksum: 10/d03f9f7a408af673cd991eeb450b6f4a5cee6c368f6349eb0211dfc0404fddfcff8b5225ef186020a2a1829adba0aa8c9174155b49ab2ed00a94fb9a886a1dd3 + languageName: node + linkType: hard + "@octokit/request-error@npm:^6.0.1": version: 6.1.4 resolution: "@octokit/request-error@npm:6.1.4" @@ -3636,6 +3905,18 @@ __metadata: languageName: node linkType: hard +"@octokit/request@npm:^8.3.0, @octokit/request@npm:^8.3.1": + version: 8.4.0 + resolution: "@octokit/request@npm:8.4.0" + dependencies: + "@octokit/endpoint": "npm:^9.0.1" + "@octokit/request-error": "npm:^5.1.0" + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10/176cd83c68bde87111a01d50e2d21cf12ec362c1a30b33649eb8771d37397f6d6dd0b0844aab8d59b16d74c825252e39cadd52e37a4b1669d6facd1cb2cdc995 + languageName: node + linkType: hard + "@octokit/request@npm:^9.0.0": version: 9.1.3 resolution: "@octokit/request@npm:9.1.3" @@ -3648,6 +3929,18 @@ __metadata: languageName: node linkType: hard +"@octokit/rest@npm:^20.1.1": + version: 20.1.1 + resolution: "@octokit/rest@npm:20.1.1" + dependencies: + "@octokit/core": "npm:^5.0.2" + "@octokit/plugin-paginate-rest": "npm:11.3.1" + "@octokit/plugin-request-log": "npm:^4.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:13.2.2" + checksum: 10/a5d557323f3ebcf813bf0965f04084dc52e71525315f865646e084713099a2baa340752caebafb17595b31c5011df0f42a15359e145046d85b5051af37a516f9 + languageName: node + linkType: hard + "@octokit/rest@npm:^21.0.2": version: 21.0.2 resolution: "@octokit/rest@npm:21.0.2" @@ -3845,121 +4138,121 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.21.2" +"@rollup/rollup-android-arm-eabi@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.20.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-android-arm64@npm:4.21.2" +"@rollup/rollup-android-arm64@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-android-arm64@npm:4.20.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.21.2" +"@rollup/rollup-darwin-arm64@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.20.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.21.2" +"@rollup/rollup-darwin-x64@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.20.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.20.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.21.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.20.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.21.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.20.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.21.2" +"@rollup/rollup-linux-arm64-musl@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.20.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.2" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.20.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.21.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.20.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.21.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.20.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.21.2" +"@rollup/rollup-linux-x64-gnu@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.20.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.21.2" +"@rollup/rollup-linux-x64-musl@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.20.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.21.2" +"@rollup/rollup-win32-arm64-msvc@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.20.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.21.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.20.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.21.2" +"@rollup/rollup-win32-x64-msvc@npm:4.20.0": + version: 4.20.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.20.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rushstack/node-core-library@npm:5.7.0": - version: 5.7.0 - resolution: "@rushstack/node-core-library@npm:5.7.0" +"@rushstack/node-core-library@npm:5.5.1": + version: 5.5.1 + resolution: "@rushstack/node-core-library@npm:5.5.1" dependencies: ajv: "npm:~8.13.0" ajv-draft-04: "npm:~1.0.0" @@ -3974,7 +4267,7 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10/ed2bd40f5b99fb3853564e0c493f33926cd78008e4e265cb2849f8887ba4d48ea1b1940b6e7286f047ee9a3a419fbf826141c96e4798b7ecf80a4e4709e429bc + checksum: 10/e2d44c9bd00ecff3a108ae4aeff707a724e50cd3c6cb229f42fcee5be0aeafb1f3a420dd4e3eeaad4968c47b280d5d1a4017adf557479fe9314271b8efd44468 languageName: node linkType: hard @@ -3988,30 +4281,30 @@ __metadata: languageName: node linkType: hard -"@rushstack/terminal@npm:0.14.0": - version: 0.14.0 - resolution: "@rushstack/terminal@npm:0.14.0" +"@rushstack/terminal@npm:0.13.3": + version: 0.13.3 + resolution: "@rushstack/terminal@npm:0.13.3" dependencies: - "@rushstack/node-core-library": "npm:5.7.0" + "@rushstack/node-core-library": "npm:5.5.1" supports-color: "npm:~8.1.1" peerDependencies: "@types/node": "*" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/3a25652cad37d61c7d66a8bf3ea037f45b7fa639e15fc8af4b5e4cf6b45b0b7b534d211e0861b33322cf89888a70f2da97ec1a9675af3ae228998535d347c608 + checksum: 10/2cc2fc62d811e539f7f6cd7902cf0fcc78a5061732c444e05c3e9466cf45f8129e605da763e1b94e78420699fab8ede4421c6b97ca8733bc87e660f6d0e39acb languageName: node linkType: hard -"@rushstack/ts-command-line@npm:4.22.6": - version: 4.22.6 - resolution: "@rushstack/ts-command-line@npm:4.22.6" +"@rushstack/ts-command-line@npm:4.22.3": + version: 4.22.3 + resolution: "@rushstack/ts-command-line@npm:4.22.3" dependencies: - "@rushstack/terminal": "npm:0.14.0" + "@rushstack/terminal": "npm:0.13.3" "@types/argparse": "npm:1.0.38" argparse: "npm:~1.0.9" string-argv: "npm:~0.3.1" - checksum: 10/18dcdad213ce33bb7a354664e1e58f7f8f94f16f635b27f4e974a93514b7dafb7a08e181a9888d3fb82b846856a44e7e2d417f2fde18686ddb83b28f12eab0e7 + checksum: 10/64509a787022944ee8bbb5e860f6791d004bc5c8a0c8a74f35b4e30622ea3ef5b1912acf1c7af3eb183407d62059eb4982c9ad2d69eea7e3b53c4d1c76736b43 languageName: node linkType: hard @@ -4180,9 +4473,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-actions@npm:8.2.9" +"@storybook/addon-actions@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-actions@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" "@types/uuid": "npm:^9.0.1" @@ -4190,47 +4483,47 @@ __metadata: polished: "npm:^4.2.2" uuid: "npm:^9.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/afde25d35194911daaa6aee025cd9da51397100ab78602b5969767bd6d26e12d86f1148b1daf5bb97b2e1836565e492cc4458a7494bedeea2e4601a8b03a2175 + storybook: ^8.2.8 + checksum: 10/775ad1e21f9e13188e02c46dbd6f71dbbd0919e1942eb3d86c12b8b12aaefec59f9e8afb874a4494e668effb1c9d6a0a998accd8628dde37c0935cdbf0c1ebf9 languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-backgrounds@npm:8.2.9" +"@storybook/addon-backgrounds@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-backgrounds@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" memoizerific: "npm:^1.11.3" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/a93cd5a68c012de5ed4dafa4e1d1c3c06406ca8d5d19809e3dcf7c2f369d5c28b4ccf0d846a45f91665a33bef8831093376a070d61155d167494dd88f4b9b901 + storybook: ^8.2.8 + checksum: 10/87a692eaba48352ea1324a36ec13486953b073a3fca847c12c6353403ca3de5c18472985a028ea059380c19010e9babc7af78bbeb4cd9ae3318d5ceaa8ef9e9e languageName: node linkType: hard -"@storybook/addon-controls@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-controls@npm:8.2.9" +"@storybook/addon-controls@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-controls@npm:8.2.8" dependencies: dequal: "npm:^2.0.2" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/0027db4387f909268d60dba205f07e57a22873634463a8c28aa24d2945610ac6a4b9d199697ffb1cf2eeba907dd76e4e84c8e6583e55443d109f4210db6210c5 + storybook: ^8.2.8 + checksum: 10/c66f5099ccb6ec284ad39149181dd621cbc9f4abdc303b01871dcd2e5279f7deb9a5901d66d13e6b8a8f9ea363bae34950024a6b2de953fb6c9abaf1d660c31b languageName: node linkType: hard -"@storybook/addon-docs@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-docs@npm:8.2.9" +"@storybook/addon-docs@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-docs@npm:8.2.8" dependencies: "@babel/core": "npm:^7.24.4" "@mdx-js/react": "npm:^3.0.0" - "@storybook/blocks": "npm:8.2.9" - "@storybook/csf-plugin": "npm:8.2.9" + "@storybook/blocks": "npm:8.2.8" + "@storybook/csf-plugin": "npm:8.2.8" "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:8.2.9" + "@storybook/react-dom-shim": "npm:8.2.8" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0" fs-extra: "npm:^11.1.0" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" @@ -4239,115 +4532,115 @@ __metadata: rehype-slug: "npm:^6.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/7e940327b84cc257da72498e6295d8124044b7e3ae00453f9302a86c19642915743b567237f47989bdc5d3fbeb7d7cf8e9035a91d6b34c08c795838e8d00a82a + storybook: ^8.2.8 + checksum: 10/4f8617e4d5d41d912f033a5f0b1ee6699f0e37bf257c8d313aa9f85c03dd33d834aa12f8fedb7cdf8111146d6394ce4687a83d2ce88f32ddfbfce564fc5a2448 languageName: node linkType: hard "@storybook/addon-essentials@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/addon-essentials@npm:8.2.9" - dependencies: - "@storybook/addon-actions": "npm:8.2.9" - "@storybook/addon-backgrounds": "npm:8.2.9" - "@storybook/addon-controls": "npm:8.2.9" - "@storybook/addon-docs": "npm:8.2.9" - "@storybook/addon-highlight": "npm:8.2.9" - "@storybook/addon-measure": "npm:8.2.9" - "@storybook/addon-outline": "npm:8.2.9" - "@storybook/addon-toolbars": "npm:8.2.9" - "@storybook/addon-viewport": "npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/addon-essentials@npm:8.2.8" + dependencies: + "@storybook/addon-actions": "npm:8.2.8" + "@storybook/addon-backgrounds": "npm:8.2.8" + "@storybook/addon-controls": "npm:8.2.8" + "@storybook/addon-docs": "npm:8.2.8" + "@storybook/addon-highlight": "npm:8.2.8" + "@storybook/addon-measure": "npm:8.2.8" + "@storybook/addon-outline": "npm:8.2.8" + "@storybook/addon-toolbars": "npm:8.2.8" + "@storybook/addon-viewport": "npm:8.2.8" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/70cc46b9188cf61a30af578fa79d15135e6c51e9406f9d044668fd395c4c93b9a408481039da6dc824100016dd76da711daef79897252e982382d2262292103d + storybook: ^8.2.8 + checksum: 10/8c41e118b0f745ffef400d6d50008f8da8b24190da293e423585b1c94b7528c2327dcf20631d3198b76052892b46a49b7bed5daba6ac88f849e7118373a01445 languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-highlight@npm:8.2.9" +"@storybook/addon-highlight@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-highlight@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/273a10768ec0abcc4f816972ec581c0be3963a6c85cb99dda7be0c605bf47fb92538c9a1b3339f2c38c38f9ad9ca3f784ec0b2c8b3bb55e153407351faff8f1b + storybook: ^8.2.8 + checksum: 10/7791a7c5e153a5b3cf5c94343baea1d0dcffc926c7c919ff30080ee46ed9d6e42a192755dcc18dd82113db38020295c86f7f816d987e9ccb810e3fd51cb08add languageName: node linkType: hard "@storybook/addon-interactions@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/addon-interactions@npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/addon-interactions@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.2.9" - "@storybook/test": "npm:8.2.9" + "@storybook/instrumenter": "npm:8.2.8" + "@storybook/test": "npm:8.2.8" polished: "npm:^4.2.2" ts-dedent: "npm:^2.2.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/6ed572a281b7b295e858324b961b02c32a18d0f7a1870cde5f00d111a8fade57dd7f975f32ffcfdf3a6565b03943e98c8fcc1875be866fa6bbc17d26b67f412d + storybook: ^8.2.8 + checksum: 10/510d072eadb12c25cb15ee6544173e7bfec7bfb3c0f4a8bc727817067ff440bc89b827697c2bdf6ff3cce3eeaf22391741c87ef8baa1f99e3b784d2d4ccf64a9 languageName: node linkType: hard "@storybook/addon-links@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/addon-links@npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/addon-links@npm:8.2.8" dependencies: "@storybook/csf": "npm:0.1.11" "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.9 + storybook: ^8.2.8 peerDependenciesMeta: react: optional: true - checksum: 10/e6b14e2cb6763f25027965c90404afb2572b27298e3d1fafa136176113ad4296b1ce48eaa8caf4f521fb6d3404921f17eb3cbe62061ebdc5f2324e0c85333742 + checksum: 10/b4aba8ce96bb93a3e456e0e75f6d30215aeb5fd9a72de8336231837fbc8e8656b742882aaec9acf247153001c2ed0b1574c86aaa17ec9d01cf5dda579d74d2dd languageName: node linkType: hard -"@storybook/addon-measure@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-measure@npm:8.2.9" +"@storybook/addon-measure@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-measure@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" tiny-invariant: "npm:^1.3.1" peerDependencies: - storybook: ^8.2.9 - checksum: 10/5a0c31b617bfdcd024c5325ab48771b8cf7b726336e24b9b0c7d4a4e8bda2093a8c2c264272b7fb36bf010f1bd54896df45b6f9092d020e696226b34e23ce208 + storybook: ^8.2.8 + checksum: 10/6aef93238a10e04f95ce838cdaec3676422a81c8ca1ec0dc2ddd4d61943e32d15328c496d801cc995ac0b3d7912d584d2044cc651adc9b2071af2271fb0bb4ad languageName: node linkType: hard -"@storybook/addon-outline@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-outline@npm:8.2.9" +"@storybook/addon-outline@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-outline@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.2.9 - checksum: 10/ad88e5d501270e7e47e955ff9e9c2aa3c5a3d9b38fe592cc7e4b5890d5c905a5ab9b644bdf7d566cdd9f66ae9ca9b9ac481f95f759a41cdfff5a3dd43103602d + storybook: ^8.2.8 + checksum: 10/ef7aee9ffb930e2f29d87237709d5117fb398a9ce3b639ec1b26170d22a744f277f3867287902c16a9ea96e1ed520b92a4de2d8abbcb45fa5859fdd13603b406 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-toolbars@npm:8.2.9" +"@storybook/addon-toolbars@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-toolbars@npm:8.2.8" peerDependencies: - storybook: ^8.2.9 - checksum: 10/77811c752d74f4fb0f5ab6d4a836a5c940a00a7ed9c4779327e1531ea704b4950ea542d5b3bd88380414d13218a3acd93fa7b67f923830cc2aef70e70881d43f + storybook: ^8.2.8 + checksum: 10/e24a6e65c3b543c19bd737291f559fd0242ee6f4745dc6abab1d0ff6f4627d23c17746590c23498d02816a637d6bc0ff53150daa52be47e22787ae7e1b21d373 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/addon-viewport@npm:8.2.9" +"@storybook/addon-viewport@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/addon-viewport@npm:8.2.8" dependencies: memoizerific: "npm:^1.11.3" peerDependencies: - storybook: ^8.2.9 - checksum: 10/1e634e8bdae61d5d89b4a04ccfc0ddbde3c480e9bf8655772be27ff88edc0d6556305eb48dd3e47b105446e02856511f9f4988399de633663f5f08dcf2610dca + storybook: ^8.2.8 + checksum: 10/c44da651f8373ebea9eb74c8a89764e75dc68489576d3b176b9f67649e986826b9d978554fb2be731fbd84a506e9e3b6f8d1bf463ce7bfd1bc890c047a0bf169 languageName: node linkType: hard @@ -4401,7 +4694,38 @@ __metadata: languageName: node linkType: hard -"@storybook/blocks@npm:8.2.9, @storybook/blocks@npm:^8.2.8": +"@storybook/blocks@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/blocks@npm:8.2.8" + dependencies: + "@storybook/csf": "npm:0.1.11" + "@storybook/global": "npm:^5.0.0" + "@storybook/icons": "npm:^1.2.5" + "@types/lodash": "npm:^4.14.167" + color-convert: "npm:^2.0.1" + dequal: "npm:^2.0.2" + lodash: "npm:^4.17.21" + markdown-to-jsx: "npm:^7.4.5" + memoizerific: "npm:^1.11.3" + polished: "npm:^4.2.2" + react-colorful: "npm:^5.1.2" + telejson: "npm:^7.2.0" + ts-dedent: "npm:^2.0.0" + util-deprecate: "npm:^1.0.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.2.8 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: 10/257e6fb22fc2c5a8fed817a3a9d5c793089dbb11d736fae09fab703611c103ce2ba895c41b68d82529123d0d25cafa27a8a8f5ee2bc04d54c5e0c40440daf398 + languageName: node + linkType: hard + +"@storybook/blocks@npm:^8.2.8": version: 8.2.9 resolution: "@storybook/blocks@npm:8.2.9" dependencies: @@ -4432,11 +4756,11 @@ __metadata: languageName: node linkType: hard -"@storybook/builder-vite@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/builder-vite@npm:8.2.9" +"@storybook/builder-vite@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/builder-vite@npm:8.2.8" dependencies: - "@storybook/csf-plugin": "npm:8.2.9" + "@storybook/csf-plugin": "npm:8.2.8" "@types/find-cache-dir": "npm:^3.2.1" browser-assert: "npm:^1.2.1" es-module-lexer: "npm:^1.5.0" @@ -4447,7 +4771,7 @@ __metadata: ts-dedent: "npm:^2.0.0" peerDependencies: "@preact/preset-vite": "*" - storybook: ^8.2.9 + storybook: ^8.2.8 typescript: ">= 4.3.x" vite: ^4.0.0 || ^5.0.0 vite-plugin-glimmerx: "*" @@ -4458,7 +4782,7 @@ __metadata: optional: true vite-plugin-glimmerx: optional: true - checksum: 10/ed8e28b6949089939611ed25530be019c8bc80d79a232a022862c9052e3a683ca2122e9ec1fd3a349a476a14fabede98fb24d4cc88c5fc518de66afef026f771 + checksum: 10/b742c90dfad3fb6a56df010027287fca66ab41594799b0ff3ebeeb4fdbc3825d0d7d52171ef539686939afa40d054591c77fef839c28da2edce490fb8e9a36c2 languageName: node linkType: hard @@ -4489,11 +4813,11 @@ __metadata: linkType: hard "@storybook/channels@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/channels@npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/channels@npm:8.2.8" peerDependencies: - storybook: ^8.2.9 - checksum: 10/377b325433fb350bb2e1fbb1c2744cbc5d22ac6af542adf9f3d895f66cde95273462c80fa3d51a9f53235196c685182601277be7e165bdd192dfeecc03e9b2b9 + storybook: ^8.2.8 + checksum: 10/ab3ead0f1a6cb0ba689c8e3febd687a8beee2d0f14f3855adae690c3e78d6820aed3f333b3a1c14e24e37e25574369889577446e21a90218e06a4df1dae9a573 languageName: node linkType: hard @@ -4538,14 +4862,14 @@ __metadata: languageName: node linkType: hard -"@storybook/codemod@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/codemod@npm:8.2.9" +"@storybook/codemod@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/codemod@npm:8.2.8" dependencies: "@babel/core": "npm:^7.24.4" "@babel/preset-env": "npm:^7.24.4" "@babel/types": "npm:^7.24.0" - "@storybook/core": "npm:8.2.9" + "@storybook/core": "npm:8.2.8" "@storybook/csf": "npm:0.1.11" "@types/cross-spawn": "npm:^6.0.2" cross-spawn: "npm:^7.0.3" @@ -4555,7 +4879,7 @@ __metadata: prettier: "npm:^3.1.1" recast: "npm:^0.23.5" tiny-invariant: "npm:^1.3.1" - checksum: 10/a0760f6612038f1a771c89c0d9054439af5b29865b44bdebb26ea6bbbf4ed768db92d349f44deb72297085e5c6c43a029b9b7d29ac531f8d998752d9c2273bf4 + checksum: 10/2eb4d35e1226364b2dc03a86c5eb588bc7cfb7cd8d316068ed960ff8285de7598ce96d3270b58037525ed525c26b2dbc8f82a03fe1b1696ca9037cbb4af15226 languageName: node linkType: hard @@ -4597,17 +4921,17 @@ __metadata: linkType: hard "@storybook/core-events@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/core-events@npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/core-events@npm:8.2.8" peerDependencies: - storybook: ^8.2.9 - checksum: 10/6ac658a75702c645695d82fbd69da5cf4d559050ffa1f0023729ad34c0d84965b2abeeb65efd168b0cdb049314de002c00267eaf692064e3efeae1337cc3ba52 + storybook: ^8.2.8 + checksum: 10/0e2831a9b1454a11c0c87ada530ebc680d96ffc830593b0494ffa7a99572105c6dbfbd4e05e1865bce44bb6100ca1622c128becd663213f36549f24bbe8a9a37 languageName: node linkType: hard -"@storybook/core@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/core@npm:8.2.9" +"@storybook/core@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/core@npm:8.2.8" dependencies: "@storybook/csf": "npm:0.1.11" "@types/express": "npm:^4.17.21" @@ -4620,18 +4944,18 @@ __metadata: recast: "npm:^0.23.5" util: "npm:^0.12.4" ws: "npm:^8.2.3" - checksum: 10/38602bae881a9824520b9369fdb37c4178bbdcc158934905af6d11963df289e9b958bdc05ef61773c70274a41188c473040e7d9113cc3043475f48005ec8f479 + checksum: 10/baebc94d56169419e0223df8942aa5c4ee36f67e141d3cdd47add95ff39ef3676b7924eeaf518da81b2bce663421f10820cb8071c36df7e330bb2531fb47d58c languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/csf-plugin@npm:8.2.9" +"@storybook/csf-plugin@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/csf-plugin@npm:8.2.8" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^8.2.9 - checksum: 10/514171f66a4e71849ee7a4efacc3051de0714fda56dfdb7581f5d08a268d5a9d0bee6264404bd766f631f2ab8a0358b4c226ecfcee8965d8560d1afc5d17c1b9 + storybook: ^8.2.8 + checksum: 10/400fb62367279ead2a2c4c5e856aa8c6e7bfeed081206a3f5a2c73554b725c277281b0360e9407997d434fa228148a04b73f27ee082486980803c6a74eb49846 languageName: node linkType: hard @@ -4670,16 +4994,16 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/instrumenter@npm:8.2.9" +"@storybook/instrumenter@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/instrumenter@npm:8.2.8" dependencies: "@storybook/global": "npm:^5.0.0" "@vitest/utils": "npm:^1.3.1" util: "npm:^0.12.4" peerDependencies: - storybook: ^8.2.9 - checksum: 10/c31a3ec70e252975f56d141942db1e8187b976fbb69e718f6ec83fe1b693457a1ca9173341ae17a8bff294ff02dfe734449c7120098442f01d3e14dd3b20f667 + storybook: ^8.2.8 + checksum: 10/6a6f109e09075adfa96ca131f98774555c7fba50a182ce20014dbd4458fd74a57b97266b7558feb0da82242a984d7fabf29062b71867d8b530825d6c8cedd159 languageName: node linkType: hard @@ -4701,6 +5025,17 @@ __metadata: languageName: node linkType: hard +"@storybook/react-dom-shim@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/react-dom-shim@npm:8.2.8" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.2.8 + checksum: 10/2fd275f7a3c1a1e10ecc99a22533408b8da622ffd3b40cff6728883cb9ada7fe3eae97b05da56874573b2ab575129ad45a5466362d7b8deede9929c6596d8780 + languageName: node + linkType: hard + "@storybook/react-dom-shim@npm:8.2.9": version: 8.2.9 resolution: "@storybook/react-dom-shim@npm:8.2.9" @@ -4713,13 +5048,13 @@ __metadata: linkType: hard "@storybook/react-vite@npm:^8.2.8": - version: 8.2.9 - resolution: "@storybook/react-vite@npm:8.2.9" + version: 8.2.8 + resolution: "@storybook/react-vite@npm:8.2.8" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.1" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:8.2.9" - "@storybook/react": "npm:8.2.9" + "@storybook/builder-vite": "npm:8.2.8" + "@storybook/react": "npm:8.2.8" find-up: "npm:^5.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^7.0.0" @@ -4728,13 +5063,50 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.9 + storybook: ^8.2.8 vite: ^4.0.0 || ^5.0.0 - checksum: 10/1acae6bae3c5f4cb0067a23235f2e5030f28b35b74e279b7a74795b49a230e331cf3dec498645ccfe24726bb04dbb1d56b696b03a51c281ccb26e6000416e2c2 + checksum: 10/24c9b77978d7667d7365210951a5cc221ace1f0c915bf21a9cfb5a53b6a3dc65c732bd04b444dd97e62ba00b36267260c2fdcb3d34a8848980c5d6ccb1136f95 + languageName: node + linkType: hard + +"@storybook/react@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/react@npm:8.2.8" + dependencies: + "@storybook/components": "npm:^8.2.8" + "@storybook/global": "npm:^5.0.0" + "@storybook/manager-api": "npm:^8.2.8" + "@storybook/preview-api": "npm:^8.2.8" + "@storybook/react-dom-shim": "npm:8.2.8" + "@storybook/theming": "npm:^8.2.8" + "@types/escodegen": "npm:^0.0.6" + "@types/estree": "npm:^0.0.51" + "@types/node": "npm:^18.0.0" + acorn: "npm:^7.4.1" + acorn-jsx: "npm:^5.3.1" + acorn-walk: "npm:^7.2.0" + escodegen: "npm:^2.1.0" + html-tags: "npm:^3.1.0" + lodash: "npm:^4.17.21" + prop-types: "npm:^15.7.2" + react-element-to-jsx-string: "npm:^15.0.0" + semver: "npm:^7.3.7" + ts-dedent: "npm:^2.0.0" + type-fest: "npm:~2.19" + util-deprecate: "npm:^1.0.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.2.8 + typescript: ">= 4.2.x" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/bc48a9460326d0a3e2558eac803078d2d91d40946c14123a303018a33fc4c1795606c7a004aab39b29c818f1e0c15d23e541582d401c0409c1bc911e537545d5 languageName: node linkType: hard -"@storybook/react@npm:8.2.9, @storybook/react@npm:^8.2.8": +"@storybook/react@npm:^8.2.8": version: 8.2.9 resolution: "@storybook/react@npm:8.2.9" dependencies: @@ -4825,12 +5197,12 @@ __metadata: languageName: node linkType: hard -"@storybook/test@npm:8.2.9": - version: 8.2.9 - resolution: "@storybook/test@npm:8.2.9" +"@storybook/test@npm:8.2.8": + version: 8.2.8 + resolution: "@storybook/test@npm:8.2.8" dependencies: "@storybook/csf": "npm:0.1.11" - "@storybook/instrumenter": "npm:8.2.9" + "@storybook/instrumenter": "npm:8.2.8" "@testing-library/dom": "npm:10.1.0" "@testing-library/jest-dom": "npm:6.4.5" "@testing-library/user-event": "npm:14.5.2" @@ -4838,8 +5210,8 @@ __metadata: "@vitest/spy": "npm:1.6.0" util: "npm:^0.12.4" peerDependencies: - storybook: ^8.2.9 - checksum: 10/2440fac3b9f2205f5ef9762dccbfcb72bbe4f5db881c57c5ceb06fecfd072e039643ed2456d2b3260af6a4419f6fecafa77d247f9570afd553e4b0e8a19175a3 + storybook: ^8.2.8 + checksum: 10/b804085d04923333fa7223be0edb34e8c1b1d5ae86f4382369680da7f1af351fc2220f702a402397ecd0712bcdeca6a1cad2c70f293ffd13855a4258c5258d4f languageName: node linkType: hard @@ -4878,90 +5250,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-darwin-arm64@npm:1.7.28" +"@swc/core-darwin-arm64@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-darwin-arm64@npm:1.7.14" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-darwin-x64@npm:1.7.28" +"@swc/core-darwin-x64@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-darwin-x64@npm:1.7.14" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.28" +"@swc/core-linux-arm-gnueabihf@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.14" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-linux-arm64-gnu@npm:1.7.28" +"@swc/core-linux-arm64-gnu@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm64-gnu@npm:1.7.14" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-linux-arm64-musl@npm:1.7.28" +"@swc/core-linux-arm64-musl@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm64-musl@npm:1.7.14" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-linux-x64-gnu@npm:1.7.28" +"@swc/core-linux-x64-gnu@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-x64-gnu@npm:1.7.14" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-linux-x64-musl@npm:1.7.28" +"@swc/core-linux-x64-musl@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-x64-musl@npm:1.7.14" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-win32-arm64-msvc@npm:1.7.28" +"@swc/core-win32-arm64-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-arm64-msvc@npm:1.7.14" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-win32-ia32-msvc@npm:1.7.28" +"@swc/core-win32-ia32-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-ia32-msvc@npm:1.7.14" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.7.28": - version: 1.7.28 - resolution: "@swc/core-win32-x64-msvc@npm:1.7.28" +"@swc/core-win32-x64-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-x64-msvc@npm:1.7.14" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.7.14": - version: 1.7.28 - resolution: "@swc/core@npm:1.7.28" - dependencies: - "@swc/core-darwin-arm64": "npm:1.7.28" - "@swc/core-darwin-x64": "npm:1.7.28" - "@swc/core-linux-arm-gnueabihf": "npm:1.7.28" - "@swc/core-linux-arm64-gnu": "npm:1.7.28" - "@swc/core-linux-arm64-musl": "npm:1.7.28" - "@swc/core-linux-x64-gnu": "npm:1.7.28" - "@swc/core-linux-x64-musl": "npm:1.7.28" - "@swc/core-win32-arm64-msvc": "npm:1.7.28" - "@swc/core-win32-ia32-msvc": "npm:1.7.28" - "@swc/core-win32-x64-msvc": "npm:1.7.28" + version: 1.7.14 + resolution: "@swc/core@npm:1.7.14" + dependencies: + "@swc/core-darwin-arm64": "npm:1.7.14" + "@swc/core-darwin-x64": "npm:1.7.14" + "@swc/core-linux-arm-gnueabihf": "npm:1.7.14" + "@swc/core-linux-arm64-gnu": "npm:1.7.14" + "@swc/core-linux-arm64-musl": "npm:1.7.14" + "@swc/core-linux-x64-gnu": "npm:1.7.14" + "@swc/core-linux-x64-musl": "npm:1.7.14" + "@swc/core-win32-arm64-msvc": "npm:1.7.14" + "@swc/core-win32-ia32-msvc": "npm:1.7.14" + "@swc/core-win32-x64-msvc": "npm:1.7.14" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.12" peerDependencies: @@ -4990,7 +5362,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/a477e79387ecc8b68c2bdbbdc88cc61f27a02c5d00f0d77134f9e2de166786a4ee9f7388d6ffd44fc01bfef5311a15cc3132052bab72fb43246dc42705fedb60 + checksum: 10/75cc386a7538da58fd2c8e141503b6efc9d1fddda4fa87c0e54e32422b5e5276eac259fc6a6a83d5f6758480f739d178a5c36d1209127c0459ca006f7075b0a6 languageName: node linkType: hard @@ -5121,6 +5493,21 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-dom@npm:^6.4.2": + version: 6.5.0 + resolution: "@testing-library/jest-dom@npm:6.5.0" + dependencies: + "@adobe/css-tools": "npm:^4.4.0" + aria-query: "npm:^5.0.0" + chalk: "npm:^3.0.0" + css.escape: "npm:^1.5.1" + dom-accessibility-api: "npm:^0.6.3" + lodash: "npm:^4.17.21" + redent: "npm:^3.0.0" + checksum: 10/3d2080888af5fd7306f57448beb5a23f55d965e265b5e53394fffc112dfb0678d616a5274ff0200c46c7618f293520f86fc8562eecd8bdbc0dbb3294d63ec431 + languageName: node + linkType: hard + "@testing-library/react@npm:^16.0.0": version: 16.0.0 resolution: "@testing-library/react@npm:16.0.0" @@ -5299,6 +5686,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:3.0.3": + version: 3.0.3 + resolution: "@types/d3-array@npm:3.0.3" + checksum: 10/2fee8da651edf772dfa1f1527cc2fd3f39a711de341a93faf4c304dc6a45ae498341481fe0805bfa0c6f7d329453e65cae2f9635503c2cdc8480903ebed67db7 + languageName: node + linkType: hard + "@types/d3-axis@npm:*": version: 3.0.6 resolution: "@types/d3-axis@npm:3.0.6" @@ -5331,6 +5725,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-color@npm:3.1.0": + version: 3.1.0 + resolution: "@types/d3-color@npm:3.1.0" + checksum: 10/c5e2129602d5d28bd178632198b158f5d27c958252a9ddf9fdc86ac38b987ae96fdd465be88913914d2ce8a4975358f86ea56362d0ac01365b234a5a8bc31861 + languageName: node + linkType: hard + "@types/d3-contour@npm:*": version: 3.0.6 resolution: "@types/d3-contour@npm:3.0.6" @@ -5348,6 +5749,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-delaunay@npm:6.0.1": + version: 6.0.1 + resolution: "@types/d3-delaunay@npm:6.0.1" + checksum: 10/a4c62fdc6fe4a579ed84696d0c208b0b43249a7a6789cee8f6d80f75d31d09b02ca9d831a5a724bd7933ff1a56224a9d07a629b31a6602836f9cbd0629faca64 + languageName: node + linkType: hard + "@types/d3-dispatch@npm:*": version: 3.0.6 resolution: "@types/d3-dispatch@npm:3.0.6" @@ -5408,7 +5816,14 @@ __metadata: languageName: node linkType: hard -"@types/d3-geo@npm:*": +"@types/d3-format@npm:3.0.1": + version: 3.0.1 + resolution: "@types/d3-format@npm:3.0.1" + checksum: 10/f52886eeec5d471c8fcb83d9731fd8d98a6e119666a7480880513c7cc8654c1e43d0e6d5ae8023d0c22c376ba17c560c9aaca8daea488a2d1fb117366d228f0a + languageName: node + linkType: hard + +"@types/d3-geo@npm:*, @types/d3-geo@npm:3.1.0": version: 3.1.0 resolution: "@types/d3-geo@npm:3.1.0" dependencies: @@ -5433,6 +5848,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-interpolate@npm:3.0.1": + version: 3.0.1 + resolution: "@types/d3-interpolate@npm:3.0.1" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10/806642d869366ec1b2caeb47a716654611728d3281ac7e2d9f06e2a5a0482ec6028952abbec394b722da8d50f81b4ce9158c741620ea8fec231226ea3bc4bb18 + languageName: node + linkType: hard + "@types/d3-path@npm:*": version: 3.1.0 resolution: "@types/d3-path@npm:3.1.0" @@ -5440,6 +5864,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-path@npm:^1, @types/d3-path@npm:^1.0.8": + version: 1.0.11 + resolution: "@types/d3-path@npm:1.0.11" + checksum: 10/faddcf4d636317aed4191afc55e0c49bfc9ccb24ab1594b8d0cd3aee5a522cd1fe51eb8ebc4e43494b6f78d8b0e8cae858495240830d32052ea1c10f0926626c + languageName: node + linkType: hard + "@types/d3-polygon@npm:*": version: 3.0.2 resolution: "@types/d3-polygon@npm:3.0.2" @@ -5477,6 +5908,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-scale@npm:4.0.2": + version: 4.0.2 + resolution: "@types/d3-scale@npm:4.0.2" + dependencies: + "@types/d3-time": "npm:*" + checksum: 10/09ded5f71b915f512718774c08663f76d6518f5085d7e94ae562f9ffd1fe48ee493a33d4766cb9d324df0144bcf67a36853329c84122653bb2660e024ebf579b + languageName: node + linkType: hard + "@types/d3-selection@npm:*": version: 3.0.10 resolution: "@types/d3-selection@npm:3.0.10" @@ -5493,6 +5933,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-shape@npm:^1.3.1": + version: 1.3.12 + resolution: "@types/d3-shape@npm:1.3.12" + dependencies: + "@types/d3-path": "npm:^1" + checksum: 10/fcb51938332c9af5d749956385fe5b6a9cd9c174619f6bf5e419efa3c4d0d0b3df919eb971bf94cacd95a24dac10f97bcf373d1fe53dcd9ae2d5dd909cbec7f0 + languageName: node + linkType: hard + "@types/d3-time-format@npm:*": version: 4.0.3 resolution: "@types/d3-time-format@npm:4.0.3" @@ -5500,6 +5949,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-time-format@npm:2.1.0": + version: 2.1.0 + resolution: "@types/d3-time-format@npm:2.1.0" + checksum: 10/803931c31f2725606fab35b8d6ae6fa04bda80f35675e57e9e4668988391728284f7993f76798d5dbd9d11f815abae55e9f831e36da80b1c6c354060cdc09f1b + languageName: node + linkType: hard + "@types/d3-time@npm:*": version: 3.0.3 resolution: "@types/d3-time@npm:3.0.3" @@ -5507,6 +5963,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-time@npm:3.0.0": + version: 3.0.0 + resolution: "@types/d3-time@npm:3.0.0" + checksum: 10/7ef819c98eb0254dc5df901c94ec7f435f9becdedff048c89f905403b934a4d8dee7730659549d60e5ce18e0e9010009b1ab1522aadc724751edaecafc54ba53 + languageName: node + linkType: hard + "@types/d3-timer@npm:*": version: 3.0.2 resolution: "@types/d3-timer@npm:3.0.2" @@ -5730,13 +6193,13 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^29.4.0": - version: 29.5.14 - resolution: "@types/jest@npm:29.5.14" +"@types/jest@npm:^29.4.0, @types/jest@npm:^29.5.12": + version: 29.5.12 + resolution: "@types/jest@npm:29.5.12" dependencies: expect: "npm:^29.0.0" pretty-format: "npm:^29.0.0" - checksum: 10/59ec7a9c4688aae8ee529316c43853468b6034f453d08a2e1064b281af9c81234cec986be796288f1bbb29efe943bc950e70c8fa8faae1e460d50e3cf9760f9b + checksum: 10/312e8dcf92cdd5a5847d6426f0940829bca6fe6b5a917248f3d7f7ef5d85c9ce78ef05e47d2bbabc40d41a930e0e36db2d443d2610a9e3db9062da2d5c904211 languageName: node linkType: hard @@ -5751,34 +6214,34 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 languageName: node linkType: hard -"@types/lodash@npm:^4": +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 10/4e5aed58cabb2bbf6f725da13421aa50a49abb6bc17bfab6c31b8774b073fa7b50d557c61f961a09a85f6056151190f8ac95f13f5b48136ba5841f7d4484ec56 + languageName: node + linkType: hard + +"@types/lodash@npm:^4, @types/lodash@npm:^4.14.172": version: 4.17.13 resolution: "@types/lodash@npm:4.17.13" checksum: 10/ddb34e20810c71be2d9445bcc4b64ec25b83976738454de709854b79c7f655b03704b76235445699956d65012987720e0e429a35489de65495cdb5420202d905 languageName: node linkType: hard -"@types/lodash@npm:^4.14.167": +"@types/lodash@npm:^4.14.167, @types/lodash@npm:^4.17.0": version: 4.17.5 resolution: "@types/lodash@npm:4.17.5" checksum: 10/10e2e9cbeb16998026f4071f9f5f2a38b651eba15302f512e0b8ab904c07c197ca0282d2821f64e53c2b692d7046af0a1ce3ead190fb077cbe4036948fce1924 languageName: node linkType: hard -"@types/lodash@npm:^4.17.7": - version: 4.17.7 - resolution: "@types/lodash@npm:4.17.7" - checksum: 10/b8177f19cf962414a66989837481b13f546afc2e98e8d465bec59e6ac03a59c584eb7053ce511cde3a09c5f3096d22a5ae22cfb56b23f3b0da75b0743b6b1a44 - languageName: node - linkType: hard - "@types/mdx@npm:^2.0.0": version: 2.0.13 resolution: "@types/mdx@npm:2.0.13" @@ -5819,20 +6282,29 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.54 - resolution: "@types/node@npm:18.19.54" + version: 18.19.36 + resolution: "@types/node@npm:18.19.36" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/99cbad8b8d7493c8c9b1df77229f5684dc1477383db8bd7692792700df1a059bc66cd49625bde29d8da072da6a553ed08cf914866a6989a66300355571db124e + checksum: 10/417756b2ee26babd92bbf5fe626e08f192f67b03219f897b479fe3329997f8a251bfb8488d2468d59a7ada42f5bb8283411d3be72184b74bd142e3b9b3b4bfc5 languageName: node linkType: hard "@types/node@npm:^20.0.0, @types/node@npm:^20.12.13": - version: 20.16.10 - resolution: "@types/node@npm:20.16.10" + version: 20.16.5 + resolution: "@types/node@npm:20.16.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10/39a8457149dc17cdea57afc90d4da53182fdb8b958d5bb065a15d123d81d4efa6b51a0de92428d05ead2e63ce07195586f71083401b99cdbcd04662344fbf7a1 + languageName: node + linkType: hard + +"@types/node@npm:^22.5.2": + version: 22.5.4 + resolution: "@types/node@npm:22.5.4" dependencies: undici-types: "npm:~6.19.2" - checksum: 10/f0832d16fed07737c2c3edd6cb6414a22e8379173e56e701ab8890b8798c8f9bc37337332631818f813ff7f8c0e168e9900e8f44cdfd2406d15150289c813acc + checksum: 10/d46e0abf437b36bdf89011287aa43873d68ea6f2521a11b5c9a033056fd0d07af36daf51439010e8d41c62c55d0b00e9b5e09ed00bb2617723f73f28a873903a languageName: node linkType: hard @@ -5865,11 +6337,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.2.10": - version: 18.3.1 - resolution: "@types/react-dom@npm:18.3.1" + version: 18.3.0 + resolution: "@types/react-dom@npm:18.3.0" dependencies: "@types/react": "npm:*" - checksum: 10/33f9ba79b26641ddf00a8699c30066b7e3573ab254e97475bf08f82fab83a6d3ce8d4ebad86afeb49bb8df3374390a9ba93125cece33badc4b3e8f7eac3c84d8 + checksum: 10/6ff53f5a7b7fba952a68e114d3b542ebdc1e87a794234785ebab0bcd9bde7fb4885f21ebaf93d26dc0a1b5b93287f42cad68b78ae04dddf6b20da7aceff0beaf languageName: node linkType: hard @@ -5883,12 +6355,12 @@ __metadata: linkType: hard "@types/react@npm:*, @types/react@npm:^16.8.0 || ^17.0.0 || ^18.0.0, @types/react@npm:^18.2.25": - version: 18.3.12 - resolution: "@types/react@npm:18.3.12" + version: 18.3.3 + resolution: "@types/react@npm:18.3.3" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/c9bbdfeacd5347d2240e0d2cb5336bc57dbc1b9ff557b6c4024b49df83419e4955553518169d3736039f1b62608e15b35762a6c03d49bd86e33add4b43b19033 + checksum: 10/68e203b7f1f91d6cf21f33fc7af9d6d228035a26c83f514981e54aa3da695d0ec6af10c277c6336de1dd76c4adbe9563f3a21f80c4462000f41e5f370b46e96c languageName: node linkType: hard @@ -5899,7 +6371,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.3.4": +"@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 @@ -5955,7 +6427,7 @@ __metadata: languageName: node linkType: hard -"@types/tough-cookie@npm:*, @types/tough-cookie@npm:^4.0.5": +"@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" checksum: 10/01fd82efc8202670865928629697b62fe9bf0c0dcbc5b1c115831caeb073a2c0abb871ff393d7df1ae94ea41e256cb87d2a5a91fd03cdb1b0b4384e08d4ee482 @@ -5997,6 +6469,13 @@ __metadata: languageName: node linkType: hard +"@types/xml-name-validator@npm:^4.0.3": + version: 4.0.3 + resolution: "@types/xml-name-validator@npm:4.0.3" + checksum: 10/2a89715fcc09731506c295d21f65f553c303ef61c61a8979ff87c2098d77f0e229f0ab1e1ce4019b9ffb7575ddb6c55cc1f98951f768338a662f8c34bdc5f09b + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -6063,6 +6542,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + checksum: 10/e827770baa202223bc0387e2fd24f630690809e460435b7dc9af336c77322290a770d62bd5284260fa881c86074d6a9fd6c97b07382520b115f6786b8ed499da + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:8.5.0": version: 8.5.0 resolution: "@typescript-eslint/scope-manager@npm:8.5.0" @@ -6088,6 +6577,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 10/24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45 + languageName: node + linkType: hard + "@typescript-eslint/types@npm:8.5.0": version: 8.5.0 resolution: "@typescript-eslint/types@npm:8.5.0" @@ -6095,6 +6591,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/visitor-keys": "npm:5.62.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + semver: "npm:^7.3.7" + tsutils: "npm:^3.21.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.5.0": version: 8.5.0 resolution: "@typescript-eslint/typescript-estree@npm:8.5.0" @@ -6128,6 +6642,34 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^5.10.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@types/json-schema": "npm:^7.0.9" + "@types/semver": "npm:^7.3.12" + "@typescript-eslint/scope-manager": "npm:5.62.0" + "@typescript-eslint/types": "npm:5.62.0" + "@typescript-eslint/typescript-estree": "npm:5.62.0" + eslint-scope: "npm:^5.1.1" + semver: "npm:^7.3.7" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/15ef13e43998a082b15f85db979f8d3ceb1f9ce4467b8016c267b1738d5e7cdb12aa90faf4b4e6dd6486c236cf9d33c463200465cf25ff997dbc0f12358550a1 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" + dependencies: + "@typescript-eslint/types": "npm:5.62.0" + eslint-visitor-keys: "npm:^3.3.0" + checksum: 10/dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.5.0": version: 8.5.0 resolution: "@typescript-eslint/visitor-keys@npm:8.5.0" @@ -6138,25 +6680,106 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.0.0": +"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" checksum: 10/c6fe89a505e513a7592e1438280db1c075764793a2397877ff1351721fe8792a966a5359769e30242b3cd023f2efb9e63ca2ca88019d73b564488cc20e3eab12 languageName: node linkType: hard +"@visx/curve@npm:3.12.0": + version: 3.12.0 + resolution: "@visx/curve@npm:3.12.0" + dependencies: + "@types/d3-shape": "npm:^1.3.1" + d3-shape: "npm:^1.0.6" + checksum: 10/aa066ce88751d49919254b7a43b62803e315e7a203dfe186ad7e546a47112e0299312550ea54586d3f1e05ed2a53253a5fbf1530d06d8fead3a1cb270406c1f3 + languageName: node + linkType: hard + +"@visx/group@npm:3.12.0": + version: 3.12.0 + resolution: "@visx/group@npm:3.12.0" + dependencies: + "@types/react": "npm:*" + classnames: "npm:^2.3.1" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 + checksum: 10/bf96859d6a38d7038eb757bd39f47e4f5861f15a1dd33dd6ba6b0e823071b77214721dfa5e6e0d3a0085a3316e48b7d703f82847f7afec54b50aa8188f6149ff + languageName: node + linkType: hard + +"@visx/scale@npm:3.12.0": + version: 3.12.0 + resolution: "@visx/scale@npm:3.12.0" + dependencies: + "@visx/vendor": "npm:3.12.0" + checksum: 10/6d0741a59af520de14960c5111e145a7f823670911259f19b3f9712cf1791ee9b1abd15a045172032928aae8ba76dc50b01e59b634255de74b56585449b47196 + languageName: node + linkType: hard + +"@visx/shape@npm:^3.12.0": + version: 3.12.0 + resolution: "@visx/shape@npm:3.12.0" + dependencies: + "@types/d3-path": "npm:^1.0.8" + "@types/d3-shape": "npm:^1.3.1" + "@types/lodash": "npm:^4.14.172" + "@types/react": "npm:*" + "@visx/curve": "npm:3.12.0" + "@visx/group": "npm:3.12.0" + "@visx/scale": "npm:3.12.0" + classnames: "npm:^2.3.1" + d3-path: "npm:^1.0.5" + d3-shape: "npm:^1.2.0" + lodash: "npm:^4.17.21" + prop-types: "npm:^15.5.10" + peerDependencies: + react: ^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0 + checksum: 10/68a7e9ba651048dec721429c38f5d81a49e5b5a50460875b23ce0e7a41f62b6900920f544874756abd27f9341b2d452f448ff91710d32be439f0700ca1b95519 + languageName: node + linkType: hard + +"@visx/vendor@npm:3.12.0": + version: 3.12.0 + resolution: "@visx/vendor@npm:3.12.0" + dependencies: + "@types/d3-array": "npm:3.0.3" + "@types/d3-color": "npm:3.1.0" + "@types/d3-delaunay": "npm:6.0.1" + "@types/d3-format": "npm:3.0.1" + "@types/d3-geo": "npm:3.1.0" + "@types/d3-interpolate": "npm:3.0.1" + "@types/d3-scale": "npm:4.0.2" + "@types/d3-time": "npm:3.0.0" + "@types/d3-time-format": "npm:2.1.0" + d3-array: "npm:3.2.1" + d3-color: "npm:3.1.0" + d3-delaunay: "npm:6.0.2" + d3-format: "npm:3.1.0" + d3-geo: "npm:3.1.0" + d3-interpolate: "npm:3.0.1" + d3-scale: "npm:4.0.2" + d3-time: "npm:3.1.0" + d3-time-format: "npm:4.1.0" + internmap: "npm:2.0.3" + checksum: 10/34375e0bbd0b18232acd9241875a657c95d9c8cd97e52cd5a17d3c8364a88d43495ab730ecd6fea2e9641d364b49f68a030599133a1130fad887917bf5cdd379 + languageName: node + linkType: hard + "@vitejs/plugin-react@npm:^4.0.3": - version: 4.3.3 - resolution: "@vitejs/plugin-react@npm:4.3.3" + version: 4.3.1 + resolution: "@vitejs/plugin-react@npm:4.3.1" dependencies: - "@babel/core": "npm:^7.25.2" - "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" + "@babel/core": "npm:^7.24.5" + "@babel/plugin-transform-react-jsx-self": "npm:^7.24.5" + "@babel/plugin-transform-react-jsx-source": "npm:^7.24.1" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.14.2" peerDependencies: vite: ^4.2.0 || ^5.0.0 - checksum: 10/816b47c54aefce198ce2fb2b3e63b5f158ab33b04713dbec0e780a89c5126d4ea6b08544972464c43096b16e90e7f467fcf19692fad30d4f8ca5bf9a386f38b3 + checksum: 10/a9d1eb30c968bf719a3277067211493746579aee14a7af8c0edb2cde38e8e5bbd461e62a41c3590e2c6eb04a047114eb3e97dcd591967625fbbc7aead8dfaf90 languageName: node linkType: hard @@ -6192,30 +6815,57 @@ __metadata: languageName: node linkType: hard -"@volar/language-core@npm:2.4.4, @volar/language-core@npm:~2.4.1": - version: 2.4.4 - resolution: "@volar/language-core@npm:2.4.4" +"@volar/language-core@npm:2.3.4": + version: 2.3.4 + resolution: "@volar/language-core@npm:2.3.4" dependencies: - "@volar/source-map": "npm:2.4.4" - checksum: 10/f4bd77ff08ae2c2568afa72a96d585d02376b2196584c4a521e1edb748133ff702600e519032aee280b980d40f79ece2323fc332897d7f3115963012ecde4233 + "@volar/source-map": "npm:2.3.4" + checksum: 10/eff3d21aecfd01d61f10808169d8fe7dcb30cafabaa369803ce9e1fb1c3b22f42a89fe742c3e75bb80386d5d372ab09b1024378d64f5537d171d8d128569f809 languageName: node linkType: hard -"@volar/source-map@npm:2.4.4": - version: 2.4.4 - resolution: "@volar/source-map@npm:2.4.4" - checksum: 10/513c6f23a1e5f59efa6914fd395243392277bd15217ff6986cd36eb965f68b26c29b7b14cc65bc9e5fe514d9ba801379e5118498b4104052764e70c7f1a96609 +"@volar/language-core@npm:2.4.0-alpha.18, @volar/language-core@npm:~2.4.0-alpha.18": + version: 2.4.0-alpha.18 + resolution: "@volar/language-core@npm:2.4.0-alpha.18" + dependencies: + "@volar/source-map": "npm:2.4.0-alpha.18" + checksum: 10/82cb160aeccca770a937235f7bdcb2ab51dd4aa5182a16d1a30b057728111b7828bc3e22c7287d80888043a4ec5b67339ac3eaf1ce7338e8cd5efdb4a933608d languageName: node linkType: hard -"@volar/typescript@npm:^2.4.4": - version: 2.4.4 - resolution: "@volar/typescript@npm:2.4.4" +"@volar/source-map@npm:2.3.4": + version: 2.3.4 + resolution: "@volar/source-map@npm:2.3.4" + checksum: 10/4892024d34661e3687560077565a53759b90feea01cd98d941eb4d82a77e8928a87c791d9ff5f9734702d9180b6d0e2c39ebd2d0a557de913089b3594eecc5ba + languageName: node + linkType: hard + +"@volar/source-map@npm:2.4.0-alpha.18": + version: 2.4.0-alpha.18 + resolution: "@volar/source-map@npm:2.4.0-alpha.18" + checksum: 10/7cf5540fffc86a2528222caa87accfe4f8e18f331b1a72b9608a557f17014bf6b3d7a09a3157502e7515fd55ac448b0535e067e0127f3bfefb711003efb2fad3 + languageName: node + linkType: hard + +"@volar/typescript@npm:^2.3.4": + version: 2.3.4 + resolution: "@volar/typescript@npm:2.3.4" + dependencies: + "@volar/language-core": "npm:2.3.4" + path-browserify: "npm:^1.0.1" + vscode-uri: "npm:^3.0.8" + checksum: 10/6bdf2295cb0a9ff3ab5d8d00825765171e9d3e738c540001401b83ccf07edfe50df8eec248b55cade773cec9c0108e6bad5663b949f3cfed8e109d0bb9204809 + languageName: node + linkType: hard + +"@volar/typescript@npm:~2.4.0-alpha.18": + version: 2.4.0-alpha.18 + resolution: "@volar/typescript@npm:2.4.0-alpha.18" dependencies: - "@volar/language-core": "npm:2.4.4" + "@volar/language-core": "npm:2.4.0-alpha.18" path-browserify: "npm:^1.0.1" vscode-uri: "npm:^3.0.8" - checksum: 10/c04ac64bff10cb79327f7b4157cc2e6e94147d673043bd273184afe2f01a6ded0181bc1207c32ac97c4da270e245da629baf121d7ba275194c42aa2d2ad4c64b + checksum: 10/860dce51f5f9442ef4a6ff34840a3303ce07adcc1ec080f113ab62b1284d765dfd587cee5af500c2960b73d196e2d3e58329e760b1a6fb45658fb0e950d37f13 languageName: node linkType: hard @@ -6252,11 +6902,11 @@ __metadata: languageName: node linkType: hard -"@vue/language-core@npm:2.1.6": - version: 2.1.6 - resolution: "@vue/language-core@npm:2.1.6" +"@vue/language-core@npm:2.0.29": + version: 2.0.29 + resolution: "@vue/language-core@npm:2.0.29" dependencies: - "@volar/language-core": "npm:~2.4.1" + "@volar/language-core": "npm:~2.4.0-alpha.18" "@vue/compiler-dom": "npm:^3.4.0" "@vue/compiler-vue2": "npm:^2.7.16" "@vue/shared": "npm:^3.4.0" @@ -6269,7 +6919,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/640d4af0031975620cd3a8050bb4b0f4ed333f241ded195e3bf8c4e571c720b4e3bec3947caf2b10e4e2de19deb7621982d15439de3732d510cd43e325c74a50 + checksum: 10/60859b53f8df2f8da336c126750c4f96ffa1c3da050181dc1e8bb80b40d482fa43194e7f2d255e31f2ebd842e11b57f89789b57a8bd9cc79f3b584eb2e0fe251 languageName: node linkType: hard @@ -6380,7 +7030,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.8.1": +"acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.9.0": version: 8.12.0 resolution: "acorn@npm:8.12.0" bin: @@ -6523,12 +7173,10 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^7.0.0": - version: 7.0.0 - resolution: "ansi-escapes@npm:7.0.0" - dependencies: - environment: "npm:^1.0.0" - checksum: 10/2d0e2345087bd7ae6bf122b9cc05ee35560d40dcc061146edcdc02bc2d7c7c50143cd12a22e69a0b5c0f62b948b7bc9a4539ee888b80f5bd33cdfd82d01a70ab +"ansi-escapes@npm:^6.2.0": + version: 6.2.1 + resolution: "ansi-escapes@npm:6.2.1" + checksum: 10/3b064937dc8a0645ed8094bc8b09483ee718f3aa3139746280e6c2ea80e28c0a3ce66973d0f33e88e60021abbf67e5f877deabfc810e75edf8a19dfa128850be languageName: node linkType: hard @@ -6588,7 +7236,7 @@ __metadata: languageName: node linkType: hard -"aproba@npm:^2.0.0": +"aproba@npm:^1.0.3 || ^2.0.0, aproba@npm:^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" checksum: 10/c2b9a631298e8d6f3797547e866db642f68493808f5b37cd61da778d5f6ada890d16f668285f7d60bd4fc3b03889bd590ffe62cf81b700e9bb353431238a0a7b @@ -6602,6 +7250,13 @@ __metadata: languageName: node linkType: hard +"are-we-there-yet@npm:^4.0.0": + version: 4.0.2 + resolution: "are-we-there-yet@npm:4.0.2" + checksum: 10/86feb4e8384b0820adaf7693bd02f602d001b0e5f051744dc2d05b30b74f9bd3e1e6f1a0c70fdadeddd837b8e5f8f77569a1a286078fb39b32a0a8f3724660d7 + languageName: node + linkType: hard + "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -6674,7 +7329,7 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.7, array-includes@npm:^3.1.8": version: 3.1.8 resolution: "array-includes@npm:3.1.8" dependencies: @@ -6709,7 +7364,21 @@ __metadata: languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1": +"array.prototype.findlastindex@npm:^1.2.3": + version: 1.2.5 + resolution: "array.prototype.findlastindex@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": version: 1.3.2 resolution: "array.prototype.flat@npm:1.3.2" dependencies: @@ -6936,15 +7605,15 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.10.6": - version: 0.10.6 - resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" +"babel-plugin-polyfill-corejs3@npm:^0.10.4": + version: 0.10.4 + resolution: "babel-plugin-polyfill-corejs3@npm:0.10.4" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - core-js-compat: "npm:^3.38.0" + "@babel/helper-define-polyfill-provider": "npm:^0.6.1" + core-js-compat: "npm:^3.36.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/360ac9054a57a18c540059dc627ad5d84d15f79790cb3d84d19a02eec7188c67d08a07db789c3822d6f5df22d918e296d1f27c4055fec2e287d328f09ea8a78a + checksum: 10/a69ed5a95bb55e9b7ea37307d56113f7e24054d479c15de6d50fa61388b5334bed1f9b6414cde6c575fa910a4de4d1ab4f2d22720967d57c4fec9d1b8f61b355 languageName: node linkType: hard @@ -7023,6 +7692,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: 10/e676f769dbc4abcf4b3317db2fd2badb4a92c0710e0a7da12cf14b59c3482d4febf835ad7de7874499060fd4e13adf0191628e504728b3c5bb4ec7a878c09940 + languageName: node + linkType: hard + "before-after-hook@npm:^3.0.2": version: 3.0.2 resolution: "before-after-hook@npm:3.0.2" @@ -7030,18 +7706,6 @@ __metadata: languageName: node linkType: hard -"bin-links@npm:^4.0.4": - version: 4.0.4 - resolution: "bin-links@npm:4.0.4" - dependencies: - cmd-shim: "npm:^6.0.0" - npm-normalize-package-bin: "npm:^3.0.0" - read-cmd-shim: "npm:^4.0.0" - write-file-atomic: "npm:^5.0.0" - checksum: 10/58d62143aacdbb783b076e9bdd970d8470f2750e1076d6fd1ae559fa532c4647478dd2550a911ba22d4c9e6339881451046e2fbc4b8958f4bf3bf8e5144d1e4d - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -7129,7 +7793,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.22.2": +"browserslist@npm:^4.22.2, browserslist@npm:^4.23.0": version: 4.23.1 resolution: "browserslist@npm:4.23.1" dependencies: @@ -7143,7 +7807,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": +"browserslist@npm:^4.23.1": version: 4.23.3 resolution: "browserslist@npm:4.23.3" dependencies: @@ -7197,10 +7861,10 @@ __metadata: languageName: node linkType: hard -"byte-size@npm:^9.0.0": - version: 9.0.0 - resolution: "byte-size@npm:9.0.0" - checksum: 10/10a2ce3433e83526d6151055aa82e414e7b15e20a9714314a17a11313b9029a4f7b0acda441d3ad8f61f1592b4ffc1649ae30a0faeb3f5561ee9995ba7de0c8e +"byte-size@npm:^8.1.1": + version: 8.1.1 + resolution: "byte-size@npm:8.1.1" + checksum: 10/eacd83b5f39b4b35115160201553150c3c085473ddb1e788d0f4ee22a2f3461470de5732eef8d7874efbbd883b7ae1277190b579128060e616d606ff419fe1e0 languageName: node linkType: hard @@ -7231,26 +7895,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^18.0.3": - version: 18.0.4 - resolution: "cacache@npm:18.0.4" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10/ca2f7b2d3003f84d362da9580b5561058ccaecd46cba661cbcff0375c90734b610520d46b472a339fd032d91597ad6ed12dde8af81571197f3c9772b5d35b104 - languageName: node - linkType: hard - "cachedir@npm:^2.3.0": version: 2.4.0 resolution: "cachedir@npm:2.4.0" @@ -7359,7 +8003,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:~5.3.0": +"chalk@npm:^5.3.0, chalk@npm:~5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea @@ -7373,6 +8017,13 @@ __metadata: languageName: node linkType: hard +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 10/b0ec668fba5eeec575ed2559a0917ba41a6481f49063c8445400e476754e0957ee09e44dc032310f526182b8f1bf25e9d4ed371f74050af7be1383e06bc44952 + languageName: node + linkType: hard + "check-error@npm:^1.0.3": version: 1.0.3 resolution: "check-error@npm:1.0.3" @@ -7389,6 +8040,20 @@ __metadata: languageName: node linkType: hard +"chevrotain@npm:10.5.0": + version: 10.5.0 + resolution: "chevrotain@npm:10.5.0" + dependencies: + "@chevrotain/cst-dts-gen": "npm:10.5.0" + "@chevrotain/gast": "npm:10.5.0" + "@chevrotain/types": "npm:10.5.0" + "@chevrotain/utils": "npm:10.5.0" + lodash: "npm:4.17.21" + regexp-to-ast: "npm:0.5.0" + checksum: 10/f39738bf4429736bc68130548c4fd00846b5ef2b057cfab0e757abae0568519271040f403f60aeb2a4aee826b64cd2c026b6bf5ae746fcf632b458b10731568f + languageName: node + linkType: hard + "chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" @@ -7416,8 +8081,8 @@ __metadata: linkType: hard "chromatic@npm:^11.0.0": - version: 11.11.0 - resolution: "chromatic@npm:11.11.0" + version: 11.5.4 + resolution: "chromatic@npm:11.5.4" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -7430,7 +8095,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10/5b1fd78af5b0c68b4a3d85f0886326c8bb790e3da7b69c8375a829cc9fa697c7d701d2ef2891109d0b9024102d402b163e5653b3f597d40d01baa74393ed4599 + checksum: 10/7d89292a2f3470f57f342d64776c915c6a27e83b0fc422d2bb9018ea0d838b330c81373dd188b27f48eb1adb0b3aafb6a750f9720bdb29e478932c92c3978fd1 languageName: node linkType: hard @@ -7464,6 +8129,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.3.1": + version: 2.5.1 + resolution: "classnames@npm:2.5.1" + checksum: 10/58eb394e8817021b153bb6e7d782cfb667e4ab390cb2e9dac2fc7c6b979d1cc2b2a733093955fc5c94aa79ef5c8c89f11ab77780894509be6afbb91dddd79d15 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -7471,6 +8143,19 @@ __metadata: languageName: node linkType: hard +"cli-color@npm:^2.0.4": + version: 2.0.4 + resolution: "cli-color@npm:2.0.4" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.64" + es6-iterator: "npm:^2.0.3" + memoizee: "npm:^0.4.15" + timers-ext: "npm:^0.1.7" + checksum: 10/6706fbb98f5db62c47deaba7116a1e37470c936dc861b84a180b5ce1a58fbf50ae6582b30a65e4b30ddb39e0469d3bac6851a9d925ded02b7e0c1c00858ef14b + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -7480,12 +8165,12 @@ __metadata: languageName: node linkType: hard -"cli-cursor@npm:^5.0.0": - version: 5.0.0 - resolution: "cli-cursor@npm:5.0.0" +"cli-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-cursor@npm:4.0.0" dependencies: - restore-cursor: "npm:^5.0.0" - checksum: 10/1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 + restore-cursor: "npm:^4.0.0" + checksum: 10/ab3f3ea2076e2176a1da29f9d64f72ec3efad51c0960898b56c8a17671365c26e67b735920530eaf7328d61f8bd41c27f46b9cf6e4e10fe2fa44b5e8c0e392cc languageName: node linkType: hard @@ -7583,13 +8268,6 @@ __metadata: languageName: node linkType: hard -"cmd-shim@npm:^6.0.0": - version: 6.0.3 - resolution: "cmd-shim@npm:6.0.3" - checksum: 10/791c9779cf57deae978ef24daf7e49e7fdb2070cc273aa7d691ed258a660ad3861edbc9f39daa2b6e5f72a64526b6812c04f08becc54402618b99946ccad7d71 - languageName: node - linkType: hard - "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -7676,7 +8354,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -7706,13 +8384,6 @@ __metadata: languageName: node linkType: hard -"common-ancestor-path@npm:^1.0.1": - version: 1.0.1 - resolution: "common-ancestor-path@npm:1.0.1" - checksum: 10/1d2e4186067083d8cc413f00fc2908225f04ae4e19417ded67faa6494fb313c4fcd5b28a52326d1a62b466e2b3a4325e92c31133c5fee628cdf8856b3a57c3d7 - languageName: node - linkType: hard - "common-tags@npm:^1.8.0": version: 1.8.2 resolution: "common-tags@npm:1.8.2" @@ -7938,12 +8609,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": - version: 3.38.1 - resolution: "core-js-compat@npm:3.38.1" +"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.36.1": + version: 3.37.1 + resolution: "core-js-compat@npm:3.37.1" dependencies: - browserslist: "npm:^4.23.3" - checksum: 10/4e2f219354fd268895f79486461a12df96f24ed307321482fe2a43529c5a64e7c16bcba654980ba217d603444f5141d43a79058aeac77511085f065c5da72207 + browserslist: "npm:^4.23.0" + checksum: 10/30c6fdbd9ff179cc53951814689b8aabec106e5de6cddfa7a7feacc96b66d415b8eebcf5ec8f7c68ef35c552fe7d39edb8b15b1ce0f27379a272295b6e937061 languageName: node linkType: hard @@ -8118,10 +8789,10 @@ __metadata: linkType: hard "cypress@npm:^13.11.0": - version: 13.15.2 - resolution: "cypress@npm:13.15.2" + version: 13.11.0 + resolution: "cypress@npm:13.11.0" dependencies: - "@cypress/request": "npm:^3.0.6" + "@cypress/request": "npm:^3.0.0" "@cypress/xvfb": "npm:^1.2.4" "@types/sinonjs__fake-timers": "npm:8.1.1" "@types/sizzle": "npm:^2.3.2" @@ -8132,7 +8803,6 @@ __metadata: cachedir: "npm:^2.3.0" chalk: "npm:^4.1.0" check-more-types: "npm:^2.24.0" - ci-info: "npm:^4.0.0" cli-cursor: "npm:^3.1.0" cli-table3: "npm:~0.6.1" commander: "npm:^6.2.1" @@ -8147,6 +8817,7 @@ __metadata: figures: "npm:^3.2.0" fs-extra: "npm:^9.1.0" getos: "npm:^3.2.1" + is-ci: "npm:^3.0.1" is-installed-globally: "npm:~0.4.0" lazy-ass: "npm:^1.6.0" listr2: "npm:^3.8.3" @@ -8160,13 +8831,12 @@ __metadata: request-progress: "npm:^3.0.0" semver: "npm:^7.5.3" supports-color: "npm:^8.1.1" - tmp: "npm:~0.2.3" - tree-kill: "npm:1.2.2" + tmp: "npm:~0.2.1" untildify: "npm:^4.0.0" yauzl: "npm:^2.10.0" bin: cypress: bin/cypress - checksum: 10/cf5746744adf2cca916dc1c99bf7d9794599bb0ee42d78d5f656f05140f73962788104d5df88314cc0bee2540795186256c75f2d4fc3652a17d5fba1495e1b06 + checksum: 10/3890f8db0ad91b5ff103dde4e7a8f47575bf55c3304ce3aa44776596580463fb2eead56f1b8ae29ebb2592879ffbccca158ae67a7db33dabdeb818c619bb5c2d languageName: node linkType: hard @@ -8179,6 +8849,15 @@ __metadata: languageName: node linkType: hard +"d3-array@npm:3.2.1": + version: 3.2.1 + resolution: "d3-array@npm:3.2.1" + dependencies: + internmap: "npm:1 - 2" + checksum: 10/5522a1e193f87a6a2e1ee784065b0e9c11b3602a388a1060aac91a6a0374fa2234436bc253312ca7b1517b91b2eea2ea9191b1e37e76c8b2bf4d422f41990dec + languageName: node + linkType: hard + "d3-axis@npm:3": version: 3.0.0 resolution: "d3-axis@npm:3.0.0" @@ -8208,7 +8887,7 @@ __metadata: languageName: node linkType: hard -"d3-color@npm:1 - 3, d3-color@npm:3": +"d3-color@npm:1 - 3, d3-color@npm:3, d3-color@npm:3.1.0": version: 3.1.0 resolution: "d3-color@npm:3.1.0" checksum: 10/536ba05bfd9f4fcd6fa289b5974f5c846b21d186875684637e22bf6855e6aba93e24a2eb3712985c6af3f502fbbfa03708edb72f58142f626241a8a17258e545 @@ -8233,6 +8912,15 @@ __metadata: languageName: node linkType: hard +"d3-delaunay@npm:6.0.2": + version: 6.0.2 + resolution: "d3-delaunay@npm:6.0.2" + dependencies: + delaunator: "npm:5" + checksum: 10/66b25c023eaf3335f1cca2d375954573e583d907aff9d1212cef714bbccb6c84d860ed0bcebaf2bd1fba88c1f0827a3515dd1d43127732a029a83c30ffb4d9f9 + languageName: node + linkType: hard + "d3-dispatch@npm:1 - 3, d3-dispatch@npm:3": version: 3.0.1 resolution: "d3-dispatch@npm:3.0.1" @@ -8315,7 +9003,7 @@ __metadata: languageName: node linkType: hard -"d3-format@npm:1 - 3, d3-format@npm:3": +"d3-format@npm:1 - 3, d3-format@npm:3, d3-format@npm:3.1.0": version: 3.1.0 resolution: "d3-format@npm:3.1.0" checksum: 10/a0fe23d2575f738027a3db0ce57160e5a473ccf24808c1ed46d45ef4f3211076b34a18b585547d34e365e78dcc26dd4ab15c069731fc4b1c07a26bfced09ea31 @@ -8331,6 +9019,15 @@ __metadata: languageName: node linkType: hard +"d3-geo@npm:3.1.0": + version: 3.1.0 + resolution: "d3-geo@npm:3.1.0" + dependencies: + d3-array: "npm:2.5.0 - 3" + checksum: 10/d214c2951c327501699b49f73fcbf417284468f41b31cd8f34c1975137a2544e4bb8080f35fa216659dba91c60f35b7bc857cd6b8297cf4f0fd37343269d9f8a + languageName: node + linkType: hard + "d3-hierarchy@npm:3": version: 3.1.2 resolution: "d3-hierarchy@npm:3.1.2" @@ -8338,7 +9035,7 @@ __metadata: languageName: node linkType: hard -"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3": +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3, d3-interpolate@npm:3.0.1": version: 3.0.1 resolution: "d3-interpolate@npm:3.0.1" dependencies: @@ -8347,13 +9044,6 @@ __metadata: languageName: node linkType: hard -"d3-path@npm:1": - version: 1.0.9 - resolution: "d3-path@npm:1.0.9" - checksum: 10/6ce1747837ea2a449d9ea32e169a382978ab09a4805eb408feb6bbc12cb5f5f6ce29aefc252dd9a815d420f4813d672f75578b78b3bbaf7811f54d8c7f93fd11 - languageName: node - linkType: hard - "d3-path@npm:1 - 3, d3-path@npm:3, d3-path@npm:^3.1.0": version: 3.1.0 resolution: "d3-path@npm:3.1.0" @@ -8361,6 +9051,13 @@ __metadata: languageName: node linkType: hard +"d3-path@npm:1, d3-path@npm:^1.0.5": + version: 1.0.9 + resolution: "d3-path@npm:1.0.9" + checksum: 10/6ce1747837ea2a449d9ea32e169a382978ab09a4805eb408feb6bbc12cb5f5f6ce29aefc252dd9a815d420f4813d672f75578b78b3bbaf7811f54d8c7f93fd11 + languageName: node + linkType: hard + "d3-polygon@npm:3": version: 3.0.1 resolution: "d3-polygon@npm:3.0.1" @@ -8392,7 +9089,7 @@ __metadata: languageName: node linkType: hard -"d3-scale@npm:4": +"d3-scale@npm:4, d3-scale@npm:4.0.2": version: 4.0.2 resolution: "d3-scale@npm:4.0.2" dependencies: @@ -8428,7 +9125,7 @@ __metadata: languageName: node linkType: hard -"d3-shape@npm:^1.3.5": +"d3-shape@npm:^1.0.6, d3-shape@npm:^1.2.0, d3-shape@npm:^1.3.5": version: 1.3.7 resolution: "d3-shape@npm:1.3.7" dependencies: @@ -8437,7 +9134,7 @@ __metadata: languageName: node linkType: hard -"d3-time-format@npm:2 - 4, d3-time-format@npm:4": +"d3-time-format@npm:2 - 4, d3-time-format@npm:4, d3-time-format@npm:4.1.0": version: 4.1.0 resolution: "d3-time-format@npm:4.1.0" dependencies: @@ -8446,7 +9143,7 @@ __metadata: languageName: node linkType: hard -"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3": +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3, d3-time@npm:3.1.0": version: 3.1.0 resolution: "d3-time@npm:3.1.0" dependencies: @@ -8535,6 +9232,16 @@ __metadata: languageName: node linkType: hard +"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2": + version: 1.0.2 + resolution: "d@npm:1.0.2" + dependencies: + es5-ext: "npm:^0.10.64" + type: "npm:^2.7.2" + checksum: 10/a3f45ef964622f683f6a1cb9b8dcbd75ce490cd2f4ac9794099db3d8f0e2814d412d84cd3fe522e58feb1f273117bb480f29c5381f6225f0abca82517caaa77a + languageName: node + linkType: hard + "dargs@npm:^8.0.0": version: 8.1.0 resolution: "dargs@npm:8.1.0" @@ -8625,7 +9332,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5": +"debug@npm:4, debug@npm:4.3.5, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.3.4": version: 4.3.5 resolution: "debug@npm:4.3.5" dependencies: @@ -8637,18 +9344,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.3.7": - version: 4.3.7 - resolution: "debug@npm:4.3.7" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a - languageName: node - linkType: hard - "debug@npm:^3.1.0, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -8658,7 +9353,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.6, debug@npm:~4.3.6": +"debug@npm:^4.3.6": version: 4.3.6 resolution: "debug@npm:4.3.6" dependencies: @@ -8731,6 +9426,13 @@ __metadata: languageName: node linkType: hard +"deepmerge-ts@npm:^5.1.0": + version: 5.1.0 + resolution: "deepmerge-ts@npm:5.1.0" + checksum: 10/0f615ccfb27b93a286abc315d7d1ec171f1befe9c511c2799ca7184c11fc6a6f29f5368d446c6885338de0d95cf6cb66a5ff4c55141a1265012730bd69408cf9 + languageName: node + linkType: hard + "deepmerge-ts@npm:^7.1.0": version: 7.1.0 resolution: "deepmerge-ts@npm:7.1.0" @@ -8806,6 +9508,13 @@ __metadata: languageName: node linkType: hard +"deprecation@npm:^2.0.0": + version: 2.3.1 + resolution: "deprecation@npm:2.3.1" + checksum: 10/f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132 + languageName: node + linkType: hard + "dequal@npm:^2.0.2, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -8827,7 +9536,7 @@ __metadata: languageName: node linkType: hard -"detect-indent@npm:^7.0.1": +"detect-indent@npm:^7.0.0, detect-indent@npm:^7.0.1": version: 7.0.1 resolution: "detect-indent@npm:7.0.1" checksum: 10/cbf3f0b1c3c881934ca94428e1179b26ab2a587e0d719031d37a67fb506d49d067de54ff057cb1e772e75975fed5155c01cd4518306fee60988b1486e3fc7768 @@ -9073,13 +9782,6 @@ __metadata: languageName: node linkType: hard -"environment@npm:^1.0.0": - version: 1.1.0 - resolution: "environment@npm:1.1.0" - checksum: 10/dd3c1b9825e7f71f1e72b03c2344799ac73f2e9ef81b78ea8b373e55db021786c6b9f3858ea43a436a2c4611052670ec0afe85bc029c384cc71165feee2f4ba6 - languageName: node - linkType: hard - "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -9252,6 +9954,51 @@ __metadata: languageName: node linkType: hard +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14, es5-ext@npm:~0.10.2": + version: 0.10.64 + resolution: "es5-ext@npm:0.10.64" + dependencies: + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.3" + esniff: "npm:^2.0.1" + next-tick: "npm:^1.1.0" + checksum: 10/0c5d8657708b1695ddc4b06f4e0b9fbdda4d2fe46d037b6bedb49a7d1931e542ec9eecf4824d59e1d357e93229deab014bb4b86485db2d41b1d68e54439689ce + languageName: node + linkType: hard + +"es6-iterator@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-iterator@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.35" + es6-symbol: "npm:^3.1.1" + checksum: 10/dbadecf3d0e467692815c2b438dfa99e5a97cbbecf4a58720adcb467a04220e0e36282399ba297911fd472c50ae4158fffba7ed0b7d4273fe322b69d03f9e3a5 + languageName: node + linkType: hard + +"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3": + version: 3.1.4 + resolution: "es6-symbol@npm:3.1.4" + dependencies: + d: "npm:^1.0.2" + ext: "npm:^1.7.0" + checksum: 10/3743119fe61f89e2f049a6ce52bd82fab5f65d13e2faa72453b73f95c15292c3cb9bdf3747940d504517e675e45fd375554c6b5d35d2bcbefd35f5489ecba546 + languageName: node + linkType: hard + +"es6-weak-map@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-weak-map@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.46" + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.1" + checksum: 10/5958a321cf8dfadc82b79eeaa57dc855893a4afd062b4ef5c9ded0010d3932099311272965c3d3fdd3c85df1d7236013a570e704fa6c1f159bbf979c203dd3a3 + languageName: node + linkType: hard + "esbuild-register@npm:^3.5.0": version: 3.5.0 resolution: "esbuild-register@npm:3.5.0" @@ -9396,7 +10143,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^9.1.0": +"eslint-config-prettier@npm:^9.0.0, eslint-config-prettier@npm:^9.1.0": version: 9.1.0 resolution: "eslint-config-prettier@npm:9.1.0" peerDependencies: @@ -9407,6 +10154,94 @@ __metadata: languageName: node linkType: hard +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" + dependencies: + debug: "npm:^3.2.7" + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: 10/d52e08e1d96cf630957272e4f2644dcfb531e49dcfd1edd2e07e43369eb2ec7a7d4423d417beee613201206ff2efa4eb9a582b5825ee28802fc7c71fcd53ca83 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.8.0": + version: 2.8.1 + resolution: "eslint-module-utils@npm:2.8.1" + dependencies: + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10/3e7892c0a984c963632da56b30ccf8254c29b535467138f91086c2ecdb2ebd10e2be61b54e553f30e5abf1d14d47a7baa0dac890e3a658fd3cd07dca63afbe6d + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.26.0": + version: 2.29.1 + resolution: "eslint-plugin-import@npm:2.29.1" + dependencies: + array-includes: "npm:^3.1.7" + array.prototype.findlastindex: "npm:^1.2.3" + array.prototype.flat: "npm:^1.3.2" + array.prototype.flatmap: "npm:^1.3.2" + debug: "npm:^3.2.7" + doctrine: "npm:^2.1.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.8.0" + hasown: "npm:^2.0.0" + is-core-module: "npm:^2.13.1" + is-glob: "npm:^4.0.3" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.7" + object.groupby: "npm:^1.0.1" + object.values: "npm:^1.1.7" + semver: "npm:^6.3.1" + tsconfig-paths: "npm:^3.15.0" + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + checksum: 10/5865f05c38552145423c535326ec9a7113ab2305c7614c8b896ff905cfabc859c8805cac21e979c9f6f742afa333e6f62f812eabf891a7e8f5f0b853a32593c1 + languageName: node + linkType: hard + +"eslint-plugin-jest@npm:^27.2.1": + version: 27.9.0 + resolution: "eslint-plugin-jest@npm:27.9.0" + dependencies: + "@typescript-eslint/utils": "npm:^5.10.0" + peerDependencies: + "@typescript-eslint/eslint-plugin": ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: "*" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + jest: + optional: true + checksum: 10/bca54347280c06c56516faea76042134dd74355c2de6c23361ba0e8736ecc01c62b144eea7eda7570ea4f4ee511c583bb8dab00d7153a1bd1740eb77b0038fd4 + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^5.0.0": + version: 5.1.3 + resolution: "eslint-plugin-prettier@npm:5.1.3" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + synckit: "npm:^0.8.6" + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: "*" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + checksum: 10/4f26a30444adc61ed692cdb5a9f7e8d9f5794f0917151051e66755ce032a08c3cc72c8b5d56101412e90f6d77035bd8194ea8731e9c16aacdd5ae345a8dae188 + languageName: node + linkType: hard + "eslint-plugin-prettier@npm:^5.2.1": version: 5.2.1 resolution: "eslint-plugin-prettier@npm:5.2.1" @@ -9464,6 +10299,26 @@ __metadata: languageName: node linkType: hard +"eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10/c541ef384c92eb5c999b7d3443d80195fcafb3da335500946f6db76539b87d5826c8f2e1d23bf6afc3154ba8cd7c8e566f8dc00f1eea25fdf3afc8fb9c87b238 + languageName: node + linkType: hard + +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10/5c660fb905d5883ad018a6fea2b49f3cb5b1cbf2cd4bd08e98646e9864f9bc2c74c0839bed2d292e90a4a328833accc197c8f0baed89cbe8d605d6f918465491 + languageName: node + linkType: hard + "eslint-scope@npm:^8.0.2": version: 8.0.2 resolution: "eslint-scope@npm:8.0.2" @@ -9474,7 +10329,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10/3f357c554a9ea794b094a09bd4187e5eacd1bc0d0653c3adeb87962c548e6a1ab8f982b86963ae1337f5d976004146536dcee5d0e2806665b193fbfbf1a9231b @@ -9488,6 +10343,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.45.0": + version: 8.57.0 + resolution: "eslint@npm:8.57.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.0" + "@humanwhocodes/config-array": "npm:^0.11.14" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10/00496e218b23747a7a9817bf58b522276d0dc1f2e546dceb4eea49f9871574088f72f1f069a6b560ef537efa3a75261b8ef70e51ef19033da1cc4c86a755ef15 + languageName: node + linkType: hard + "eslint@npm:^9.10.0": version: 9.10.0 resolution: "eslint@npm:9.10.0" @@ -9537,6 +10440,18 @@ __metadata: languageName: node linkType: hard +"esniff@npm:^2.0.1": + version: 2.0.1 + resolution: "esniff@npm:2.0.1" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.62" + event-emitter: "npm:^0.3.5" + type: "npm:^2.7.2" + checksum: 10/f6a2abd2f8c5fe57c5fcf53e5407c278023313d0f6c3a92688e7122ab9ac233029fd424508a196ae5bc561aa1f67d23f4e2435b1a0d378030f476596129056ac + languageName: node + linkType: hard + "espree@npm:^10.0.1, espree@npm:^10.1.0": version: 10.1.0 resolution: "espree@npm:10.1.0" @@ -9548,6 +10463,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: "npm:^8.9.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^3.4.1" + checksum: 10/255ab260f0d711a54096bdeda93adff0eadf02a6f9b92f02b323e83a2b7fc258797919437ad331efec3930475feb0142c5ecaaf3cdab4befebd336d47d3f3134 + languageName: node + linkType: hard + "esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -9558,6 +10484,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.4.2": + version: 1.5.0 + resolution: "esquery@npm:1.5.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10/e65fcdfc1e0ff5effbf50fb4f31ea20143ae5df92bb2e4953653d8d40aa4bc148e0d06117a592ce4ea53eeab1dafdfded7ea7e22a5be87e82d73757329a1b01d + languageName: node + linkType: hard + "esquery@npm:^1.5.0": version: 1.6.0 resolution: "esquery@npm:1.6.0" @@ -9576,6 +10511,13 @@ __metadata: languageName: node linkType: hard +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10/3f67ad02b6dbfaddd9ea459cf2b6ef4ecff9a6082a7af9d22e445b9abc082ad9ca47e1825557b293fcdae477f4714e561123e30bb6a5b2f184fb2bad4a9497eb + languageName: node + linkType: hard + "estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": version: 5.3.0 resolution: "estraverse@npm:5.3.0" @@ -9613,6 +10555,16 @@ __metadata: languageName: node linkType: hard +"event-emitter@npm:^0.3.5": + version: 0.3.5 + resolution: "event-emitter@npm:0.3.5" + dependencies: + d: "npm:1" + es5-ext: "npm:~0.10.14" + checksum: 10/a7f5ea80029193f4869782d34ef7eb43baa49cd397013add1953491b24588468efbe7e3cc9eb87d53f33397e7aab690fd74c079ec440bf8b12856f6bdb6e9396 + languageName: node + linkType: hard + "event-stream@npm:=3.3.4": version: 3.3.4 resolution: "event-stream@npm:3.3.4" @@ -9768,6 +10720,15 @@ __metadata: languageName: node linkType: hard +"ext@npm:^1.7.0": + version: 1.7.0 + resolution: "ext@npm:1.7.0" + dependencies: + type: "npm:^2.7.2" + checksum: 10/666a135980b002df0e75c8ac6c389140cdc59ac953db62770479ee2856d58ce69d2f845e5f2586716350b725400f6945e51e9159573158c39f369984c72dcd84 + languageName: node + linkType: hard + "extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -9775,6 +10736,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: "npm:^0.7.0" + iconv-lite: "npm:^0.4.24" + tmp: "npm:^0.0.33" + checksum: 10/776dff1d64a1d28f77ff93e9e75421a81c062983fd1544279d0a32f563c0b18c52abbb211f31262e2827e48edef5c9dc8f960d06dd2d42d1654443b88568056b + languageName: node + linkType: hard + "extract-zip@npm:2.0.1": version: 2.0.1 resolution: "extract-zip@npm:2.0.1" @@ -9920,6 +10892,15 @@ __metadata: languageName: node linkType: hard +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: "npm:^3.0.4" + checksum: 10/099bb9d4ab332cb93c48b14807a6918a1da87c45dce91d4b61fd40e6505d56d0697da060cb901c729c90487067d93c9243f5da3dc9c41f0358483bfdebca736b + languageName: node + linkType: hard + "file-entry-cache@npm:^8.0.0": version: 8.0.0 resolution: "file-entry-cache@npm:8.0.0" @@ -10032,6 +11013,17 @@ __metadata: languageName: node linkType: hard +"flat-cache@npm:^3.0.4": + version: 3.2.0 + resolution: "flat-cache@npm:3.2.0" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.3" + rimraf: "npm:^3.0.2" + checksum: 10/02381c6ece5e9fa5b826c9bbea481d7fd77645d96e4b0b1395238124d581d10e56f17f723d897b6d133970f7a57f0fab9148cbbb67237a0a0ffe794ba60c0c70 + languageName: node + linkType: hard + "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -10111,7 +11103,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0, form-data@npm:~4.0.0": +"form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" dependencies: @@ -10122,6 +11114,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:~2.3.2": + version: 2.3.3 + resolution: "form-data@npm:2.3.3" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.6" + mime-types: "npm:^2.1.12" + checksum: 10/1b6f3ccbf4540e535887b42218a2431a3f6cfdea320119c2affa2a7a374ad8fdd1e60166fc865181f45d49b1684c3e90e7b2190d3fe016692957afb9cf0d0d02 + languageName: node + linkType: hard + "formdata-polyfill@npm:^4.0.10": version: 4.0.10 resolution: "formdata-polyfill@npm:4.0.10" @@ -10256,6 +11259,22 @@ __metadata: languageName: node linkType: hard +"gauge@npm:^5.0.0": + version: 5.0.2 + resolution: "gauge@npm:5.0.2" + dependencies: + aproba: "npm:^1.0.3 || ^2.0.0" + color-support: "npm:^1.1.3" + console-control-strings: "npm:^1.1.0" + has-unicode: "npm:^2.0.1" + signal-exit: "npm:^4.0.1" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wide-align: "npm:^1.1.5" + checksum: 10/7ed6930e03622e3651182d9312ea763e208e7e71bf126505a59a4424820af2715fdbc4f0655f92a092f3f1015c425094421c9d1a898dbe33cf6779d3e7e91e5c + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -10426,6 +11445,15 @@ __metadata: languageName: node linkType: hard +"git-url-parse@npm:^14.0.0": + version: 14.0.0 + resolution: "git-url-parse@npm:14.0.0" + dependencies: + git-up: "npm:^7.0.0" + checksum: 10/c19430947895676c59ce472d534c88e5d2d9f443e6b6e4deaa8ad9ad921ded6c27a996b219503775c37fbb90f4a3c02a5f106f14b61286386f9e5098dff7d634 + languageName: node + linkType: hard + "git-url-parse@npm:^15.0.0": version: 15.0.0 resolution: "git-url-parse@npm:15.0.0" @@ -10471,7 +11499,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.12": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.12, glob@npm:^10.4.1": version: 10.4.1 resolution: "glob@npm:10.4.1" dependencies: @@ -10516,6 +11544,19 @@ __metadata: languageName: node linkType: hard +"glob@npm:^8.0.1": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^5.0.1" + once: "npm:^1.3.0" + checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e + languageName: node + linkType: hard + "global-dirs@npm:^3.0.0": version: 3.0.1 resolution: "global-dirs@npm:3.0.1" @@ -10562,6 +11603,15 @@ __metadata: languageName: node linkType: hard +"globals@npm:^13.19.0": + version: 13.24.0 + resolution: "globals@npm:13.24.0" + dependencies: + type-fest: "npm:^0.20.2" + checksum: 10/62c5b1997d06674fc7191d3e01e324d3eda4d65ac9cc4e78329fa3b5c4fd42a0e1c8722822497a6964eee075255ce21ccf1eec2d83f92ef3f06653af4d0ee28e + languageName: node + linkType: hard + "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -10570,9 +11620,9 @@ __metadata: linkType: hard "globals@npm:^15.9.0": - version: 15.10.0 - resolution: "globals@npm:15.10.0" - checksum: 10/d649208c62406fa71f131be643cb6d18703be5e612f8fa4da8a35bda56ce5a6a8caeb13a2f5a927e4d3324d73872e897067eb6f92ddd46a29876ffa5c4910cb8 + version: 15.9.0 + resolution: "globals@npm:15.9.0" + checksum: 10/19bca70131c5d3e0d4171deed0f8ae16adda19f18d39b67421056f1eaa160b4433c3ffc8eb69b8b19adebbbdad4834d8a0494c5fe1ae295f0f769a5c0331d794 languageName: node linkType: hard @@ -10795,7 +11845,7 @@ __metadata: languageName: node linkType: hard -"hosted-git-info@npm:^7.0.0, hosted-git-info@npm:^7.0.2": +"hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" dependencies: @@ -10875,14 +11925,14 @@ __metadata: languageName: node linkType: hard -"http-signature@npm:~1.4.0": - version: 1.4.0 - resolution: "http-signature@npm:1.4.0" +"http-signature@npm:~1.3.6": + version: 1.3.6 + resolution: "http-signature@npm:1.3.6" dependencies: assert-plus: "npm:^1.0.0" jsprim: "npm:^2.0.2" - sshpk: "npm:^1.18.0" - checksum: 10/f9f5eed4ac5db5e1ec6d00652680c7d8b76d553560017e34505c0c22c37abb2e6d22b9268ed4a8542aa9746852a2d64850531091e443393c9c8e0f4fd4174455 + sshpk: "npm:^1.14.1" + checksum: 10/5f08e0c82174999da97114facb0d0d47e268d60b6fc10f92cb87b99d5ccccd36f79b9508c29dda0b4f4e3a1b2f7bcaf847e68ecd5da2f1fc465fcd1d054b7884 languageName: node linkType: hard @@ -10928,15 +11978,15 @@ __metadata: linkType: hard "husky@npm:^9.0.0": - version: 9.1.6 - resolution: "husky@npm:9.1.6" + version: 9.0.11 + resolution: "husky@npm:9.0.11" bin: - husky: bin.js - checksum: 10/421ccd8850378231aaefd70dbe9e4f1549b84ffe3a6897f93a202242bbc04e48bd498169aef43849411105d9fcf7c192b757d42661e28d06b934a609a4eb8771 + husky: bin.mjs + checksum: 10/8a9b7cb9dc8494b470b3b47b386e65d579608c6206da80d3cc8b71d10e37947264af3dfe00092368dad9673b51d2a5ee87afb4b2291e77ba9e7ec1ac36e56cd1 languageName: node linkType: hard -"iconv-lite@npm:0.4.24": +"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -10961,6 +12011,15 @@ __metadata: languageName: node linkType: hard +"ignore-walk@npm:^5.0.1": + version: 5.0.1 + resolution: "ignore-walk@npm:5.0.1" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10/a88b3fbda155496363fb3db66c7c7b85cf04d614fb51146f0aa5fc6b35c65370c57f9e6c550cd6048651fc378985b7a2bb9015c9fcb3e0dc798fc0728746703c + languageName: node + linkType: hard + "ignore-walk@npm:^6.0.4": version: 6.0.5 resolution: "ignore-walk@npm:6.0.5" @@ -10977,17 +12036,10 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.3.2": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 - languageName: node - linkType: hard - "immutable@npm:^4.0.0": - version: 4.3.7 - resolution: "immutable@npm:4.3.7" - checksum: 10/37d963c5050f03ae5f3714ba7a43d469aa482051087f4c65d673d1501c309ea231d87480c792e19fa85e2eaf965f76af5d0aa92726505f3cfe4af91619dfb80b + version: 4.3.6 + resolution: "immutable@npm:4.3.6" + checksum: 10/59fedb67f26e265035616b27e33ef90b53b434cf76fb09212ec2d6ae32ee8d2fe2641e6dc32dbc78498c521fbf5f72c6740d39affba63a0a36a3884272371857 languageName: node linkType: hard @@ -11008,7 +12060,7 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2": +"import-local@npm:^3.0.2, import-local@npm:^3.1.0": version: 3.1.0 resolution: "import-local@npm:3.1.0" dependencies: @@ -11084,6 +12136,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.23": + version: 9.2.23 + resolution: "inquirer@npm:9.2.23" + dependencies: + "@inquirer/figures": "npm:^1.0.3" + "@ljharb/through": "npm:^2.3.13" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^5.3.0" + cli-cursor: "npm:^3.1.0" + cli-width: "npm:^4.1.0" + external-editor: "npm:^3.1.0" + lodash: "npm:^4.17.21" + mute-stream: "npm:1.0.0" + ora: "npm:^5.4.1" + run-async: "npm:^3.0.0" + rxjs: "npm:^7.8.1" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + checksum: 10/ccc05c4d64ee583ac6d1ad602ae1b76c25da6d6ca8923bbac9158f76bad2eb9c628b5b5201b135fcc32f9651ed1b9ed99e6489f9afa5f00db443447f1f9ad391 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7": version: 1.0.7 resolution: "internal-slot@npm:1.0.7" @@ -11095,7 +12170,7 @@ __metadata: languageName: node linkType: hard -"internmap@npm:1 - 2": +"internmap@npm:1 - 2, internmap@npm:2.0.3": version: 2.0.3 resolution: "internmap@npm:2.0.3" checksum: 10/873e0e7fcfe32f999aa0997a0b648b1244508e56e3ea6b8259b5245b50b5eeb3853fba221f96692bd6d1def501da76c32d64a5cb22a0b26cdd9b445664f805e0 @@ -11217,7 +12292,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.8.1": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1, is-core-module@npm:^2.8.1": version: 2.13.1 resolution: "is-core-module@npm:2.13.1" dependencies: @@ -11420,6 +12495,13 @@ __metadata: languageName: node linkType: hard +"is-promise@npm:^2.2.2": + version: 2.2.2 + resolution: "is-promise@npm:2.2.2" + checksum: 10/18bf7d1c59953e0ad82a1ed963fb3dc0d135c8f299a14f89a17af312fc918373136e56028e8831700e1933519630cc2fd4179a777030330fde20d34e96f40c78 + languageName: node + linkType: hard + "is-regex@npm:^1.1.2, is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" @@ -11512,7 +12594,7 @@ __metadata: languageName: node linkType: hard -"is-typedarray@npm:~1.0.0": +"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" checksum: 10/4b433bfb0f9026f079f4eb3fbaa4ed2de17c9995c3a0b5c800bec40799b4b2a8b4e051b1ada77749deb9ded4ae52fe2096973f3a93ff83df1a5a7184a669478c @@ -12189,16 +13271,16 @@ __metadata: languageName: node linkType: hard -"joi@npm:^17.13.3": - version: 17.13.3 - resolution: "joi@npm:17.13.3" +"joi@npm:^17.11.0": + version: 17.13.1 + resolution: "joi@npm:17.13.1" dependencies: "@hapi/hoek": "npm:^9.3.0" "@hapi/topo": "npm:^5.1.0" "@sideway/address": "npm:^4.1.5" "@sideway/formula": "npm:^3.0.1" "@sideway/pinpoint": "npm:^2.0.0" - checksum: 10/4c150db0c820c3a52f4a55c82c1fc5e144a5b5f4da9ffebc7339a15469d1a447ebb427ced446efcb9709ab56bd71a06c4c67c9381bc1b9f9ae63fc7c89209bdf + checksum: 10/9e34f93afbb490e12d7ec4aa05803788cd9ff4de00af30389c9d0f4af193ae85941365f80cb0ac38d0d04a45b85ee3a8b78cb0c10b5efeccce8922d68719603c languageName: node linkType: hard @@ -12352,7 +13434,7 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^3.0.0, json-parse-even-better-errors@npm:^3.0.2": +"json-parse-even-better-errors@npm:^3.0.0": version: 3.0.2 resolution: "json-parse-even-better-errors@npm:3.0.2" checksum: 10/6f04ea6c9ccb783630a59297959247e921cc90b917b8351197ca7fd058fccc7079268fd9362be21ba876fc26aa5039369dd0a2280aae49aae425784794a94927 @@ -12360,21 +13442,24 @@ __metadata: linkType: hard "json-schema-to-typescript@npm:^15.0.0": - version: 15.0.2 - resolution: "json-schema-to-typescript@npm:15.0.2" + version: 15.0.0 + resolution: "json-schema-to-typescript@npm:15.0.0" dependencies: "@apidevtools/json-schema-ref-parser": "npm:^11.5.5" "@types/json-schema": "npm:^7.0.15" - "@types/lodash": "npm:^4.17.7" + "@types/lodash": "npm:^4.17.0" + cli-color: "npm:^2.0.4" glob: "npm:^10.3.12" is-glob: "npm:^4.0.3" js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" minimist: "npm:^1.2.8" + mkdirp: "npm:^3.0.1" + node-fetch: "npm:^3.3.2" prettier: "npm:^3.2.5" bin: json2ts: dist/src/cli.js - checksum: 10/bdb6772822226a3d53ec6ac51cb68b79dc9eaee8019411e7b5b3fc4ecc6c1aa79923206f634f126ead709078e2d306985c45e8d715178a8a9103321a3552d948 + checksum: 10/ac62f4190932087c67d0a2ee23e659ce9a9b3413cdb60d0e109fefc4795305022033a8965cc9cbba08cd515aa3c9bdc9727fd71a714f6b8cf96a2d20b6faf082 languageName: node linkType: hard @@ -12406,13 +13491,6 @@ __metadata: languageName: node linkType: hard -"json-stringify-nice@npm:^1.1.4": - version: 1.1.4 - resolution: "json-stringify-nice@npm:1.1.4" - checksum: 10/0e02cae900a1f24df64613dd10a54b354e4ba2b17822f0d7f0d2708182e71a8bbbfac107d54d3ae8fa3d8bab3556e20cef84f193ace92c9df7bc30872ec2926e - languageName: node - linkType: hard - "json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -12420,6 +13498,17 @@ __metadata: languageName: node linkType: hard +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: "npm:^1.2.0" + bin: + json5: lib/cli.js + checksum: 10/a78d812dbbd5642c4f637dd130954acfd231b074965871c3e28a5bbd571f099d623ecf9161f1960c4ddf68e0cc98dee8bebfdb94a71ad4551f85a1afc94b63f6 + languageName: node + linkType: hard + "json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -12499,20 +13588,6 @@ __metadata: languageName: node linkType: hard -"just-diff-apply@npm:^5.2.0": - version: 5.5.0 - resolution: "just-diff-apply@npm:5.5.0" - checksum: 10/5515c436c89e9ef934f1ea2aac447588c38dd017247ed85254537b005706e64321ca7a9c246fe7106338da1ef3a693f8550ebf11759c854713e9ccffb788a43b - languageName: node - linkType: hard - -"just-diff@npm:^6.0.0": - version: 6.0.2 - resolution: "just-diff@npm:6.0.2" - checksum: 10/4c6b14d6be2a3391b020ea2b3d1a0acf2f4c60fcb16681c7f6f76d4c0f1841fae5b00c1a2e719980992e46320e4b6c55a4713683cb1873dd41a2621f08c9f8e8 - languageName: node - linkType: hard - "just-extend@npm:^6.2.0": version: 6.2.0 resolution: "just-extend@npm:6.2.0" @@ -12532,7 +13607,7 @@ __metadata: languageName: unknown linkType: soft -"keyv@npm:^4.5.4": +"keyv@npm:^4.5.3, keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -12562,13 +13637,6 @@ __metadata: languageName: node linkType: hard -"known-css-properties@npm:^0.34.0": - version: 0.34.0 - resolution: "known-css-properties@npm:0.34.0" - checksum: 10/0e93e83f84537e89b9dc56c16aff511ed9f24128fe509c3f601ce495eb10bf6678e2f4ff521f6b53feabc7bd18088e43efb31aae4cb771da831ef1408c23211a - languageName: node - linkType: hard - "kolorist@npm:^1.8.0": version: 1.8.0 resolution: "kolorist@npm:1.8.0" @@ -12626,7 +13694,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:~3.1.2": +"lilconfig@npm:~3.1.1": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" checksum: 10/8058403850cfad76d6041b23db23f730e52b6c17a8c28d87b90766639ca0ee40c748a3e85c2d7bd133d572efabff166c4b015e5d25e01fd666cb4b13cfada7f0 @@ -12648,22 +13716,22 @@ __metadata: linkType: hard "lint-staged@npm:^15.0.0": - version: 15.2.10 - resolution: "lint-staged@npm:15.2.10" + version: 15.2.7 + resolution: "lint-staged@npm:15.2.7" dependencies: chalk: "npm:~5.3.0" commander: "npm:~12.1.0" - debug: "npm:~4.3.6" + debug: "npm:~4.3.4" execa: "npm:~8.0.1" - lilconfig: "npm:~3.1.2" - listr2: "npm:~8.2.4" - micromatch: "npm:~4.0.8" + lilconfig: "npm:~3.1.1" + listr2: "npm:~8.2.1" + micromatch: "npm:~4.0.7" pidtree: "npm:~0.6.0" string-argv: "npm:~0.3.2" - yaml: "npm:~2.5.0" + yaml: "npm:~2.4.2" bin: lint-staged: bin/lint-staged.js - checksum: 10/ab6930cd633dbb5b6ec7c81fc06c65df41e9f80d93dd22e0d79c6e272cdfd8110a0fbdec60303d46a06b30bcd92261153630e2c937531b77ec5ae41e7e9d90d3 + checksum: 10/7557bcf4e8dc0555f2c7e6a8ab6f5dfd7faaaed632a5d9e598768c86f786267ca216f8005068796a8118884d322a1c7f8f93e57c01b3e556475b77297ddad34f languageName: node linkType: hard @@ -12688,17 +13756,17 @@ __metadata: languageName: node linkType: hard -"listr2@npm:~8.2.4": - version: 8.2.4 - resolution: "listr2@npm:8.2.4" +"listr2@npm:~8.2.1": + version: 8.2.1 + resolution: "listr2@npm:8.2.1" dependencies: cli-truncate: "npm:^4.0.0" colorette: "npm:^2.0.20" eventemitter3: "npm:^5.0.1" - log-update: "npm:^6.1.0" - rfdc: "npm:^1.4.1" + log-update: "npm:^6.0.0" + rfdc: "npm:^1.3.1" wrap-ansi: "npm:^9.0.0" - checksum: 10/344d2397e127bf802935925e95b54468eef745fbbaf9326eb33a1634ae2d6e86cdb527ef48cb83a19a50671955d39b3e2608c74db85530df07b5674f5de115e1 + checksum: 10/1d33348682fee7af49c91d508f970fb58897fbc722a17ae6b9d4d904c909e105ead8c123652238bc99462b1e323c56e6e62877a03d788ca32fe706cfec283789 languageName: node linkType: hard @@ -12798,7 +13866,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.0.0, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:~4.17.15": +"lodash@npm:4.17.21, lodash@npm:^4.0.0, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 @@ -12827,16 +13895,16 @@ __metadata: languageName: node linkType: hard -"log-update@npm:^6.1.0": - version: 6.1.0 - resolution: "log-update@npm:6.1.0" +"log-update@npm:^6.0.0": + version: 6.0.0 + resolution: "log-update@npm:6.0.0" dependencies: - ansi-escapes: "npm:^7.0.0" - cli-cursor: "npm:^5.0.0" - slice-ansi: "npm:^7.1.0" + ansi-escapes: "npm:^6.2.0" + cli-cursor: "npm:^4.0.0" + slice-ansi: "npm:^7.0.0" strip-ansi: "npm:^7.1.0" wrap-ansi: "npm:^9.0.0" - checksum: 10/5abb4131e33b1e7f8416bb194fe17a3603d83e4657c5bf5bb81ce4187f3b00ea481643b85c3d5cefe6037a452cdcf7f1391ab8ea0d9c23e75d19589830ec4f11 + checksum: 10/b345f392c356087290918f1bdaae84ee38699c89c9274fafbb6f4cee2fe6f89f9737000111279a40e651fbe0e9c08803b0457c2a4800d8a405752804f73058a8 languageName: node linkType: hard @@ -12867,13 +13935,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.2": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a - languageName: node - linkType: hard - "lru-cache@npm:^11.0.0": version: 11.0.0 resolution: "lru-cache@npm:11.0.0" @@ -12899,6 +13960,15 @@ __metadata: languageName: node linkType: hard +"lru-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "lru-queue@npm:0.1.0" + dependencies: + es5-ext: "npm:~0.10.2" + checksum: 10/55b08ee3a7dbefb7d8ee2d14e0a97c69a887f78bddd9e28a687a1944b57e09513d4b401db515279e8829d52331df12a767f3ed27ca67c3322c723cc25c06403f + languageName: node + linkType: hard + "lz-string@npm:^1.5.0": version: 1.5.0 resolution: "lz-string@npm:1.5.0" @@ -13050,6 +14120,22 @@ __metadata: languageName: node linkType: hard +"memoizee@npm:^0.4.15": + version: 0.4.17 + resolution: "memoizee@npm:0.4.17" + dependencies: + d: "npm:^1.0.2" + es5-ext: "npm:^0.10.64" + es6-weak-map: "npm:^2.0.3" + event-emitter: "npm:^0.3.5" + is-promise: "npm:^2.2.2" + lru-queue: "npm:^0.1.0" + next-tick: "npm:^1.1.0" + timers-ext: "npm:^0.1.7" + checksum: 10/b7abda74d1057878f3570c45995f24da8a4f8636e0e9a7c29a6709be2314bf40c7d78e3be93c0b1660ba419de5740fa5e447c400ab5df407ffbd236421066380 + languageName: node + linkType: hard + "memoizerific@npm:^1.11.3": version: 1.11.3 resolution: "memoizerific@npm:1.11.3" @@ -13101,7 +14187,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.4": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.7, micromatch@npm:~4.0.7": version: 4.0.7 resolution: "micromatch@npm:4.0.7" dependencies: @@ -13111,16 +14197,6 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.8, micromatch@npm:~4.0.8": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10/6bf2a01672e7965eb9941d1f02044fad2bd12486b5553dc1116ff24c09a8723157601dc992e74c911d896175918448762df3b3fd0a6b61037dd1a9766ddfbf58 - languageName: node - linkType: hard - "mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -13160,13 +14236,6 @@ __metadata: languageName: node linkType: hard -"mimic-function@npm:^5.0.0": - version: 5.0.1 - resolution: "mimic-function@npm:5.0.1" - checksum: 10/eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c - languageName: node - linkType: hard - "min-document@npm:^2.19.0": version: 2.19.0 resolution: "min-document@npm:2.19.0" @@ -13201,6 +14270,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/126b36485b821daf96d33b5c821dac600cc1ab36c87e7a532594f9b1652b1fa89a1eebcaad4dff17c764dce1a7ac1531327f190fed5f97d8f6e5f889c116c429 + languageName: node + linkType: hard + "minimatch@npm:^9.0.0, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4": version: 9.0.4 resolution: "minimatch@npm:9.0.4" @@ -13339,6 +14417,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "mlly@npm:^1.4.2, mlly@npm:^1.7.1": version: 1.7.1 resolution: "mlly@npm:1.7.1" @@ -13477,7 +14564,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.1.1": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -13496,14 +14583,14 @@ __metadata: linkType: hard "msw@npm:^2.0.2": - version: 2.4.9 - resolution: "msw@npm:2.4.9" + version: 2.3.1 + resolution: "msw@npm:2.3.1" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.0" "@bundled-es-modules/statuses": "npm:^1.0.1" - "@bundled-es-modules/tough-cookie": "npm:^0.1.6" "@inquirer/confirm": "npm:^3.0.0" - "@mswjs/interceptors": "npm:^0.35.8" + "@mswjs/cookies": "npm:^1.1.0" + "@mswjs/interceptors": "npm:^0.29.0" "@open-draft/until": "npm:^2.1.0" "@types/cookie": "npm:^0.6.0" "@types/statuses": "npm:^2.0.4" @@ -13512,18 +14599,18 @@ __metadata: headers-polyfill: "npm:^4.0.2" is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.2" - path-to-regexp: "npm:^6.3.0" + path-to-regexp: "npm:^6.2.0" strict-event-emitter: "npm:^0.5.1" type-fest: "npm:^4.9.0" yargs: "npm:^17.7.2" peerDependencies: - typescript: ">= 4.8.x" + typescript: ">= 4.7.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: 10/fe00b2d2934993cfb26661ab919944a677d68f097d1e5990a0a4245334741412855abe499dd77822212c586dd8dd002ef8062f0f4f451a6955bd5dccab1905a8 + checksum: 10/449df7c48f82eaa3de4b40ca106be232b09dcf7f736b1bb7410109702f803262016db35247b299c1ec378346678f48d1d50752ee18fc90329c2531326cec7ec4 languageName: node linkType: hard @@ -13534,7 +14621,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:^1.0.0": +"mute-stream@npm:1.0.0, mute-stream@npm:^1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 @@ -13580,6 +14667,13 @@ __metadata: languageName: node linkType: hard +"next-tick@npm:^1.1.0": + version: 1.1.0 + resolution: "next-tick@npm:1.1.0" + checksum: 10/83b5cf36027a53ee6d8b7f9c0782f2ba87f4858d977342bfc3c20c21629290a2111f8374d13a81221179603ffc4364f38374b5655d17b6a8f8a8c77bdea4fe8b + languageName: node + linkType: hard + "nise@npm:^5.1.0": version: 5.1.9 resolution: "nise@npm:5.1.9" @@ -13692,7 +14786,7 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^7.0.0, nopt@npm:^7.2.1": +"nopt@npm:^7.0.0": version: 7.2.1 resolution: "nopt@npm:7.2.1" dependencies: @@ -13722,6 +14816,15 @@ __metadata: languageName: node linkType: hard +"npm-bundled@npm:^2.0.0": + version: 2.0.1 + resolution: "npm-bundled@npm:2.0.1" + dependencies: + npm-normalize-package-bin: "npm:^2.0.0" + checksum: 10/adf5d727915cbd61603e2171ba67e39319efa343ceb72868348232a36ad774a8365d5af5e1aad29acc41c3caeda4ebd80e5b7a3da319985509aeedf79e352c0d + languageName: node + linkType: hard + "npm-bundled@npm:^3.0.0": version: 3.0.1 resolution: "npm-bundled@npm:3.0.1" @@ -13731,7 +14834,7 @@ __metadata: languageName: node linkType: hard -"npm-install-checks@npm:^6.0.0, npm-install-checks@npm:^6.2.0": +"npm-install-checks@npm:^6.0.0": version: 6.3.0 resolution: "npm-install-checks@npm:6.3.0" dependencies: @@ -13740,6 +14843,13 @@ __metadata: languageName: node linkType: hard +"npm-normalize-package-bin@npm:^2.0.0": + version: 2.0.0 + resolution: "npm-normalize-package-bin@npm:2.0.0" + checksum: 10/7c5379f9b188b564c4332c97bdd9a5d6b7b15f02b5823b00989d6a0e6fb31eb0280f02b0a924f930e1fcaf00e60fae333aec8923d2a4c7747613c7d629d8aa25 + languageName: node + linkType: hard + "npm-normalize-package-bin@npm:^3.0.0": version: 3.0.1 resolution: "npm-normalize-package-bin@npm:3.0.1" @@ -13771,7 +14881,21 @@ __metadata: languageName: node linkType: hard -"npm-packlist@npm:^8.0.0, npm-packlist@npm:^8.0.2": +"npm-packlist@npm:^5.1.3": + version: 5.1.3 + resolution: "npm-packlist@npm:5.1.3" + dependencies: + glob: "npm:^8.0.1" + ignore-walk: "npm:^5.0.1" + npm-bundled: "npm:^2.0.0" + npm-normalize-package-bin: "npm:^2.0.0" + bin: + npm-packlist: bin/index.js + checksum: 10/78aa1c69a349c40cf7ba556581bff2dd5cbc1455614a44bd673e076f7f402096ac7c01660c45ec17cbd51bf0db3a4df7e9bc3a0a8e8e497ebf6d53848f33dfad + languageName: node + linkType: hard + +"npm-packlist@npm:^8.0.0": version: 8.0.2 resolution: "npm-packlist@npm:8.0.2" dependencies: @@ -13781,30 +14905,18 @@ __metadata: linkType: hard "npm-pick-manifest@npm:^9.0.0": - version: 9.0.1 - resolution: "npm-pick-manifest@npm:9.0.1" - dependencies: - npm-install-checks: "npm:^6.0.0" - npm-normalize-package-bin: "npm:^3.0.0" - npm-package-arg: "npm:^11.0.0" - semver: "npm:^7.3.5" - checksum: 10/870053b63c8765a5d22df3aabcf09505342dd30398c68e15a57cc32e9da629c361b12285d72bd0bac100786623d2f2dc5ced16270f39dda7c14660fae677590e - languageName: node - linkType: hard - -"npm-pick-manifest@npm:^9.0.1": - version: 9.1.0 - resolution: "npm-pick-manifest@npm:9.1.0" + version: 9.0.1 + resolution: "npm-pick-manifest@npm:9.0.1" dependencies: npm-install-checks: "npm:^6.0.0" npm-normalize-package-bin: "npm:^3.0.0" npm-package-arg: "npm:^11.0.0" semver: "npm:^7.3.5" - checksum: 10/e759e4fe4076da9169cf522964a80bbc096d50cd24c8c44b50b44706c4479bd9d9d018fbdb76c6ea0c6037e012e07c6c917a1ecaa7ae1a1169cddfae1c0f24b6 + checksum: 10/870053b63c8765a5d22df3aabcf09505342dd30398c68e15a57cc32e9da629c361b12285d72bd0bac100786623d2f2dc5ced16270f39dda7c14660fae677590e languageName: node linkType: hard -"npm-registry-fetch@npm:^17.0.0, npm-registry-fetch@npm:^17.0.1, npm-registry-fetch@npm:^17.1.0": +"npm-registry-fetch@npm:^17.0.0, npm-registry-fetch@npm:^17.0.1": version: 17.1.0 resolution: "npm-registry-fetch@npm:17.1.0" dependencies: @@ -13838,6 +14950,18 @@ __metadata: languageName: node linkType: hard +"npmlog@npm:^7.0.1": + version: 7.0.1 + resolution: "npmlog@npm:7.0.1" + dependencies: + are-we-there-yet: "npm:^4.0.0" + console-control-strings: "npm:^1.1.0" + gauge: "npm:^5.0.0" + set-blocking: "npm:^2.0.0" + checksum: 10/37cc2796a4b47bb82b5fc5d111f812d5856b30f8dd29d3e9ecce30fe966bd4389926e818ec5e7f11e9fcc60220ef9c65d7e4c56dd5101ee19d8f5e60320e558b + languageName: node + linkType: hard + "nwsapi@npm:^2.2.2": version: 2.2.10 resolution: "nwsapi@npm:2.2.10" @@ -13914,7 +15038,7 @@ __metadata: languageName: node linkType: hard -"object.fromentries@npm:^2.0.8": +"object.fromentries@npm:^2.0.7, object.fromentries@npm:^2.0.8": version: 2.0.8 resolution: "object.fromentries@npm:2.0.8" dependencies: @@ -13926,7 +15050,18 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0": +"object.groupby@npm:^1.0.1": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + checksum: 10/44cb86dd2c660434be65f7585c54b62f0425b0c96b5c948d2756be253ef06737da7e68d7106e35506ce4a44d16aa85a413d11c5034eb7ce5579ec28752eb42d0 + languageName: node + linkType: hard + +"object.values@npm:^1.1.6, object.values@npm:^1.1.7, object.values@npm:^1.2.0": version: 1.2.0 resolution: "object.values@npm:1.2.0" dependencies: @@ -13980,15 +15115,6 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^7.0.0": - version: 7.0.0 - resolution: "onetime@npm:7.0.0" - dependencies: - mimic-function: "npm:^5.0.0" - checksum: 10/eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -14020,6 +15146,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10/5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "ospath@npm:^1.2.2": version: 1.2.2 resolution: "ospath@npm:1.2.2" @@ -14027,20 +15160,13 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.4.0, outvariant@npm:^1.4.2": +"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0, outvariant@npm:^1.4.2": version: 1.4.2 resolution: "outvariant@npm:1.4.2" checksum: 10/f16ba035fb65d1cbe7d2e06693dd42183c46bc8456713d9ddb5182d067defa7d78217edab0a2d3e173d3bacd627b2bd692195c7087c225b82548fbf52c677b38 languageName: node linkType: hard -"outvariant@npm:^1.4.3": - version: 1.4.3 - resolution: "outvariant@npm:1.4.3" - checksum: 10/3a7582745850cb344d49641867a4c080858c54f4091afd91b9c0765ba6e471c2bc841348f0fff344845ddd0a4db42fd5d68c6f7ebaf32d4b676a3a9987b2488a - languageName: node - linkType: hard - "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -14068,6 +15194,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^5.0.0": + version: 5.0.0 + resolution: "p-limit@npm:5.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10/87bf5837dee6942f0dbeff318436179931d9a97848d1b07dbd86140a477a5d2e6b90d9701b210b4e21fe7beaea2979dfde366e4f576fa644a59bd4d6a6371da7 + languageName: node + linkType: hard + "p-limit@npm:^6.1.0": version: 6.1.0 resolution: "p-limit@npm:6.1.0" @@ -14174,7 +15309,7 @@ __metadata: languageName: node linkType: hard -"pacote@npm:^18.0.0, pacote@npm:^18.0.6": +"pacote@npm:^18.0.6": version: 18.0.6 resolution: "pacote@npm:18.0.6" dependencies: @@ -14210,17 +15345,6 @@ __metadata: languageName: node linkType: hard -"parse-conflict-json@npm:^3.0.0": - version: 3.0.1 - resolution: "parse-conflict-json@npm:3.0.1" - dependencies: - json-parse-even-better-errors: "npm:^3.0.0" - just-diff: "npm:^6.0.0" - just-diff-apply: "npm:^5.2.0" - checksum: 10/ceb13ca90bd75610559125dc7b519e2806c096640142d6524e9b1ffdf08d6625b03a29d8afe4630d95460f703b9d5bc6dac21fcdcb00089213ffdb70800c900b - languageName: node - linkType: hard - "parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -14374,20 +15498,13 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.2.1": +"path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.2.1": version: 6.2.2 resolution: "path-to-regexp@npm:6.2.2" checksum: 10/f7d11c1a9e02576ce0294f4efdc523c11b73894947afdf7b23a0d0f7c6465d7a7772166e770ddf1495a8017cc0ee99e3e8a15ed7302b6b948b89a6dd4eea895e languageName: node linkType: hard -"path-to-regexp@npm:^6.3.0": - version: 6.3.0 - resolution: "path-to-regexp@npm:6.3.0" - checksum: 10/6822f686f01556d99538b350722ef761541ec0ce95ca40ce4c29e20a5b492fe8361961f57993c71b2418de12e604478dcf7c430de34b2c31a688363a7a944d9c - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -14581,13 +15698,6 @@ __metadata: languageName: node linkType: hard -"postcss-resolve-nested-selector@npm:^0.1.6": - version: 0.1.6 - resolution: "postcss-resolve-nested-selector@npm:0.1.6" - checksum: 10/85453901afe2a4db497b4e0d2c9cf2a097a08fa5d45bc646547025176217050334e423475519a1e6c74a1f31ade819d16bb37a39914e5321e250695ee3feea14 - languageName: node - linkType: hard - "postcss-safe-parser@npm:^7.0.0": version: 7.0.0 resolution: "postcss-safe-parser@npm:7.0.0" @@ -14606,16 +15716,6 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.1.2": - version: 6.1.2 - resolution: "postcss-selector-parser@npm:6.1.2" - dependencies: - cssesc: "npm:^3.0.0" - util-deprecate: "npm:^1.0.2" - checksum: 10/190034c94d809c115cd2f32ee6aade84e933450a43ec3899c3e78e7d7b33efd3a2a975bb45d7700b6c5b196c06a7d9acf3f1ba6f1d87032d9675a29d8bca1dd3 - languageName: node - linkType: hard - "postcss-selector-parser@npm:^6.1.0": version: 6.1.0 resolution: "postcss-selector-parser@npm:6.1.0" @@ -14633,14 +15733,36 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.41, postcss@npm:^8.4.43, postcss@npm:^8.4.45": - version: 8.4.47 - resolution: "postcss@npm:8.4.47" +"postcss@npm:^8.4.38": + version: 8.4.38 + resolution: "postcss@npm:8.4.38" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.1.0" - source-map-js: "npm:^1.2.1" - checksum: 10/f2b50ba9b6fcb795232b6bb20de7cdc538c0025989a8ed9c4438d1960196ba3b7eaff41fdb1a5c701b3504651ea87aeb685577707f0ae4d6ce6f3eae5df79a81 + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.2.0" + checksum: 10/6e44a7ed835ffa9a2b096e8d3e5dfc6bcf331a25c48aeb862dd54e3aaecadf814fa22be224fd308f87d08adf2299164f88c5fd5ab1c4ef6cbd693ceb295377f4 + languageName: node + linkType: hard + +"postcss@npm:^8.4.40": + version: 8.4.41 + resolution: "postcss@npm:8.4.41" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.1" + source-map-js: "npm:^1.2.0" + checksum: 10/6e6176c2407eff60493ca60a706c6b7def20a722c3adda94ea1ece38345eb99964191336fd62b62652279cec6938e79e0b1e1d477142c8d3516e7a725a74ee37 + languageName: node + linkType: hard + +"postcss@npm:^8.4.45": + version: 8.4.45 + resolution: "postcss@npm:8.4.45" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.1" + source-map-js: "npm:^1.2.0" + checksum: 10/7eaf7346d04929ee979548ece5e34d253eae6f175346e298b2c4621ad6f4ee00adfe7abe72688640e910c0361ae50537c5dda3e35fd1066491282c342b3ee5c8 languageName: node linkType: hard @@ -14705,7 +15827,7 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^4.0.0, proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": +"proc-log@npm:^4.0.0, proc-log@npm:^4.2.0": version: 4.2.0 resolution: "proc-log@npm:4.2.0" checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a @@ -14726,27 +15848,6 @@ __metadata: languageName: node linkType: hard -"proggy@npm:^2.0.0": - version: 2.0.0 - resolution: "proggy@npm:2.0.0" - checksum: 10/9c96830d30516534c91e1260cae98d2c12aa32ea4ca7ff979876557ae293581c4874c95daf80497a7350179e7fec6d119cd589ef09af9c925f5842161897ed7e - languageName: node - linkType: hard - -"promise-all-reject-late@npm:^1.0.0": - version: 1.0.1 - resolution: "promise-all-reject-late@npm:1.0.1" - checksum: 10/f5e5c1bfed975c26b6dec007393e1026c437716d87c9c688cfa026bb904c190155211d23fe795c03c4394f88563471aec56b3ad263bff5ed68dad734513c2912 - languageName: node - linkType: hard - -"promise-call-limit@npm:^3.0.1": - version: 3.0.1 - resolution: "promise-call-limit@npm:3.0.1" - checksum: 10/f1b3c4d3a9c5482ce27ec5f40311e1389adb9bb10c16166e61c96d29ab22c701691d5225bf6745a162858f45dfb46cc82275fd09e7aa57846fc446c7855c2f06 - languageName: node - linkType: hard - "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -14774,7 +15875,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -14865,21 +15966,21 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" +"qs@npm:6.10.4": + version: 6.10.4 + resolution: "qs@npm:6.10.4" dependencies: side-channel: "npm:^1.0.4" - checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e + checksum: 10/8887a53f63180e0e0291deafef581e550bc3656f2453adc8d3ca34b49c04354d31079962f7faf90ab8f5fd6e3d70ee6645042b27814a757a3a5d5708ae3f58e0 languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + side-channel: "npm:^1.0.4" + checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e languageName: node linkType: hard @@ -15054,15 +16155,15 @@ __metadata: linkType: hard "react-monaco-editor@npm:^0.56.0": - version: 0.56.2 - resolution: "react-monaco-editor@npm:0.56.2" + version: 0.56.0 + resolution: "react-monaco-editor@npm:0.56.0" dependencies: prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ">=16 <= 18" - monaco-editor: ^0.52.0 + monaco-editor: ^0.50.0 react: ">=16 <= 18" - checksum: 10/9e891803f766ab81349e1579ffc8cd7cba27eb34ac5113aaf34798f0998629da68415f67e2a0e76057ae632fe019c355eeab89baa607e612239315381777fff4 + checksum: 10/6d6b351cb3b41b727d5f1d54c8e8bf45499e127cbaf667ff3a3fbb200cfb7eaeeca4f79a262a572b87724061ad863268ba8bd5dc89f42e22ebf9e68169b9984b languageName: node linkType: hard @@ -15131,20 +16232,15 @@ __metadata: languageName: node linkType: hard -"read-cmd-shim@npm:^4.0.0": - version: 4.0.0 - resolution: "read-cmd-shim@npm:4.0.0" - checksum: 10/69a83acf0a3e2357762d5944a6f4a3f3c5527d0f9fe8a5c9362225aaf702ccfa580ff3bc0b84809c99e88861a5e5be147629717f02ff9befdac68fca1ccc7664 - languageName: node - linkType: hard - -"read-package-json-fast@npm:^3.0.0, read-package-json-fast@npm:^3.0.2": - version: 3.0.2 - resolution: "read-package-json-fast@npm:3.0.2" +"read-package-json@npm:^7.0.1": + version: 7.0.1 + resolution: "read-package-json@npm:7.0.1" dependencies: + glob: "npm:^10.2.2" json-parse-even-better-errors: "npm:^3.0.0" + normalize-package-data: "npm:^6.0.0" npm-normalize-package-bin: "npm:^3.0.0" - checksum: 10/8d406869f045f1d76e2a99865a8fd1c1af9c1dc06200b94d2b07eef87ed734b22703a8d72e1cd36ea36cc48e22020bdd187f88243c7dd0563f72114d38c17072 + checksum: 10/4b5684f4ee96ff29d0ec62452d2b1ed2b3fb7e452cd1a1f40869d896082816a231a1e157fa3e72137e140ca961cbe7eeabc952658fc38235c85b202c91f2e584 languageName: node linkType: hard @@ -15171,7 +16267,7 @@ __metadata: languageName: node linkType: hard -"read-pkg@npm:^9.0.1": +"read-pkg@npm:^9.0.0, read-pkg@npm:^9.0.1": version: 9.0.1 resolution: "read-pkg@npm:9.0.1" dependencies: @@ -15308,6 +16404,13 @@ __metadata: languageName: node linkType: hard +"regexp-to-ast@npm:0.5.0": + version: 0.5.0 + resolution: "regexp-to-ast@npm:0.5.0" + checksum: 10/41f5c38f568cb64378812e8e77b1dc383a3975e311063bfb2a83179fc1e6601c59b1b32d5b85996eb2ae50b9ccb73e551268b9e32caae3ec60acd0f705d4a58c + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" @@ -15439,7 +16542,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": +"resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -15465,7 +16568,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -15501,13 +16604,13 @@ __metadata: languageName: node linkType: hard -"restore-cursor@npm:^5.0.0": - version: 5.1.0 - resolution: "restore-cursor@npm:5.1.0" +"restore-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "restore-cursor@npm:4.0.0" dependencies: - onetime: "npm:^7.0.0" - signal-exit: "npm:^4.1.0" - checksum: 10/838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10/5b675c5a59763bf26e604289eab35711525f11388d77f409453904e1e69c0d37ae5889295706b2c81d23bd780165084d040f9b68fffc32cc921519031c4fa4af languageName: node linkType: hard @@ -15525,13 +16628,24 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0, rfdc@npm:^1.4.1": +"rfdc@npm:^1.3.0, rfdc@npm:^1.3.1": version: 1.4.1 resolution: "rfdc@npm:1.4.1" checksum: 10/2f3d11d3d8929b4bfeefc9acb03aae90f971401de0add5ae6c5e38fec14f0405e6a4aad8fdb76344bfdd20c5193110e3750cbbd28ba86d73729d222b6cf4a729 languageName: node linkType: hard +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: bin.js + checksum: 10/063ffaccaaaca2cfd0ef3beafb12d6a03dd7ff1260d752d62a6077b5dfff6ae81bea571f655bb6b589d366930ec1bdd285d40d560c0dae9b12f125e54eb743d5 + languageName: node + linkType: hard + "rimraf@npm:^6.0.0": version: 6.0.1 resolution: "rimraf@npm:6.0.1" @@ -15562,26 +16676,26 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.20.0": - version: 4.21.2 - resolution: "rollup@npm:4.21.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.21.2" - "@rollup/rollup-android-arm64": "npm:4.21.2" - "@rollup/rollup-darwin-arm64": "npm:4.21.2" - "@rollup/rollup-darwin-x64": "npm:4.21.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.21.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.21.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.21.2" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.21.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.21.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-x64-musl": "npm:4.21.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.21.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.21.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.21.2" +"rollup@npm:^4.13.0": + version: 4.20.0 + resolution: "rollup@npm:4.20.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.20.0" + "@rollup/rollup-android-arm64": "npm:4.20.0" + "@rollup/rollup-darwin-arm64": "npm:4.20.0" + "@rollup/rollup-darwin-x64": "npm:4.20.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.20.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.20.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.20.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.20.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.20.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.20.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.20.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.20.0" + "@rollup/rollup-linux-x64-musl": "npm:4.20.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.20.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.20.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.20.0" "@types/estree": "npm:1.0.5" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -15621,7 +16735,14 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/5d679af1a04170f7164e3e975a375adb76f9bbf34d1ad8d9c3fa789252d377e7d364dfee054a4283121f9f9368d7b35404b9d42fb260be314d34739243ab0722 + checksum: 10/448bd835715aa0f78c6888314e31fb92f1b83325ef0ff861a5a322c2bc87d200b2b6c4acb9223fb669c27ae0c4b071003b6877eec1d3411174615a4057db8946 + languageName: node + linkType: hard + +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10/97fb8747f7765b77ebcd311d3a33548099336f04c6434e0763039b98c1de0f1b4421000695aff8751f309c0b995d8dfd620c1f1e4c35572da38c101488165305 languageName: node linkType: hard @@ -15948,7 +17069,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.1, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": +"semver@npm:^7.1.1, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": version: 7.6.2 resolution: "semver@npm:7.6.2" bin: @@ -16116,13 +17237,13 @@ __metadata: linkType: hard "simple-git@npm:^3.22.0": - version: 3.27.0 - resolution: "simple-git@npm:3.27.0" + version: 3.25.0 + resolution: "simple-git@npm:3.25.0" dependencies: "@kwsites/file-exists": "npm:^1.1.1" "@kwsites/promise-deferred": "npm:^1.1.1" debug: "npm:^4.3.5" - checksum: 10/c56c88dd1b5f6ad45c6034eb44f25ee61929a2a5832bd015b8b1b71331071e43bf631edbcc4a8529fa692f9b17576595d95c89218bc5d67be66ed0c1aedc7a76 + checksum: 10/f284162c941e36970db171eacfe0df1f7ce50313fa7841b95abb704ec78ffe4f27b6a46a4b621ed88711104faa295eb47fb2b54b23cead4400cb2204ed491074 languageName: node linkType: hard @@ -16206,7 +17327,7 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^7.1.0": +"slice-ansi@npm:^7.0.0": version: 7.1.0 resolution: "slice-ansi@npm:7.1.0" dependencies: @@ -16260,13 +17381,6 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.1": - version: 1.2.1 - resolution: "source-map-js@npm:1.2.1" - checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 - languageName: node - linkType: hard - "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -16365,7 +17479,7 @@ __metadata: languageName: node linkType: hard -"sshpk@npm:^1.18.0": +"sshpk@npm:^1.14.1": version: 1.18.0 resolution: "sshpk@npm:1.18.0" dependencies: @@ -16395,15 +17509,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^11.0.0": - version: 11.0.0 - resolution: "ssri@npm:11.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/e2fb25d3b010586ed50a89a94dbe04a15ac3b7c60492031c8c6b22e5880dbc80ef9f8b1f7df928be7a8fbe198acef57811f0fb647394f698a4b870f43ac9fb5c - languageName: node - linkType: hard - "stable@npm:^0.1.8": version: 0.1.8 resolution: "stable@npm:0.1.8" @@ -16421,22 +17526,22 @@ __metadata: linkType: hard "start-server-and-test@npm:^2.0.0": - version: 2.0.8 - resolution: "start-server-and-test@npm:2.0.8" + version: 2.0.4 + resolution: "start-server-and-test@npm:2.0.4" dependencies: arg: "npm:^5.0.2" bluebird: "npm:3.7.2" check-more-types: "npm:2.24.0" - debug: "npm:4.3.7" + debug: "npm:4.3.5" execa: "npm:5.1.1" lazy-ass: "npm:1.6.0" ps-tree: "npm:1.2.0" - wait-on: "npm:8.0.1" + wait-on: "npm:7.2.0" bin: server-test: src/bin/start.js start-server-and-test: src/bin/start.js start-test: src/bin/start.js - checksum: 10/4067c3dd120e1e515e4d087f2a60faebaae512517374dc30d14b29492a6761e2ab72f82c8bcb72f0eefc060fd7345674821e1255bb58a6ceaa5a1fac4257790a + checksum: 10/2125ed5ab7a0c9ece9fc3f3f6192f1b6d66a55ca7a8eab5c82a5d3b85d9299072c3f079b4b34ee81745668015ce1a00653aa764fbf650eb92de1dfa050ee0a07 languageName: node linkType: hard @@ -16471,8 +17576,8 @@ __metadata: linkType: hard "storybook-addon-remix-react-router@npm:^3.0.0": - version: 3.0.1 - resolution: "storybook-addon-remix-react-router@npm:3.0.1" + version: 3.0.0 + resolution: "storybook-addon-remix-react-router@npm:3.0.0" dependencies: compare-versions: "npm:^6.0.0" react-inspector: "npm:6.0.2" @@ -16492,7 +17597,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/13fa62a60500813e53a508c03e8c8fc5e29021ab03cd42e27edd272ad26ac106d54e4f6f986b773c2501ee962de6d655b4f634c3f7aff920094668422955d704 + checksum: 10/2213883135e894399c05f1ced0245d61fe679f4b61fe0a97cf2c5ceb0456aa9d2ad538e9fcdeebf4c9971220c36e839303059c3313561299fadf91c45d87c04c languageName: node linkType: hard @@ -16517,13 +17622,13 @@ __metadata: linkType: hard "storybook@npm:^8.2.8": - version: 8.2.9 - resolution: "storybook@npm:8.2.9" + version: 8.2.8 + resolution: "storybook@npm:8.2.8" dependencies: "@babel/core": "npm:^7.24.4" "@babel/types": "npm:^7.24.0" - "@storybook/codemod": "npm:8.2.9" - "@storybook/core": "npm:8.2.9" + "@storybook/codemod": "npm:8.2.8" + "@storybook/core": "npm:8.2.8" "@types/semver": "npm:^7.3.4" "@yarnpkg/fslib": "npm:2.10.3" "@yarnpkg/libzip": "npm:2.3.0" @@ -16552,7 +17657,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/2d5473ba1ab31067d07c63d79799db05cf81927f517945999d124a337f209d685b2e1e4ff37d13924410ec5582d28f474fee6dee98be08f079869ec831c10df8 + checksum: 10/f00d98cb89792a1e66087f5f72752daea83983afe97a8ba5691f43629ede4b3e96d276eb49398d3167136beb05d92b18873191b5798b639ba20986bcd2cdd852 languageName: node linkType: hard @@ -16867,14 +17972,14 @@ __metadata: linkType: hard "stylelint-prettier@npm:^5.0.0": - version: 5.0.2 - resolution: "stylelint-prettier@npm:5.0.2" + version: 5.0.0 + resolution: "stylelint-prettier@npm:5.0.0" dependencies: prettier-linter-helpers: "npm:^1.0.0" peerDependencies: prettier: ">=3.0.0" stylelint: ">=16.0.0" - checksum: 10/bee52ac6bfd03bfec07a429556d05e1cc754be9f0cf6802e19f06410d7d4cf6b2a01ca987f5e6f7816d8c88a0e73d04aa5b04be43fd121a9f805a9ab079a3719 + checksum: 10/1d55f03bbc66c769643672789ebc6f48d6af573e8ef867ea919c7fd0fa70b9750183405641808c16a9a024f895092b23d0732d7ddda4c05ba6a21cceceee9205 languageName: node linkType: hard @@ -16894,20 +17999,20 @@ __metadata: linkType: hard "stylelint@npm:^16.1.0": - version: 16.9.0 - resolution: "stylelint@npm:16.9.0" + version: 16.6.1 + resolution: "stylelint@npm:16.6.1" dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.1" - "@csstools/css-tokenizer": "npm:^3.0.1" - "@csstools/media-query-list-parser": "npm:^3.0.1" - "@csstools/selector-specificity": "npm:^4.0.0" + "@csstools/css-parser-algorithms": "npm:^2.6.3" + "@csstools/css-tokenizer": "npm:^2.3.1" + "@csstools/media-query-list-parser": "npm:^2.1.11" + "@csstools/selector-specificity": "npm:^3.1.1" "@dual-bundle/import-meta-resolve": "npm:^4.1.0" balanced-match: "npm:^2.0.0" colord: "npm:^2.9.3" cosmiconfig: "npm:^9.0.0" css-functions-list: "npm:^3.2.2" css-tree: "npm:^2.3.1" - debug: "npm:^4.3.6" + debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" fastest-levenshtein: "npm:^1.0.16" file-entry-cache: "npm:^9.0.0" @@ -16915,30 +18020,30 @@ __metadata: globby: "npm:^11.1.0" globjoin: "npm:^0.1.4" html-tags: "npm:^3.3.1" - ignore: "npm:^5.3.2" + ignore: "npm:^5.3.1" imurmurhash: "npm:^0.1.4" is-plain-object: "npm:^5.0.0" - known-css-properties: "npm:^0.34.0" + known-css-properties: "npm:^0.31.0" mathml-tag-names: "npm:^2.1.3" meow: "npm:^13.2.0" - micromatch: "npm:^4.0.8" + micromatch: "npm:^4.0.7" normalize-path: "npm:^3.0.0" picocolors: "npm:^1.0.1" - postcss: "npm:^8.4.41" - postcss-resolve-nested-selector: "npm:^0.1.6" + postcss: "npm:^8.4.38" + postcss-resolve-nested-selector: "npm:^0.1.1" postcss-safe-parser: "npm:^7.0.0" - postcss-selector-parser: "npm:^6.1.2" + postcss-selector-parser: "npm:^6.1.0" postcss-value-parser: "npm:^4.2.0" resolve-from: "npm:^5.0.0" string-width: "npm:^4.2.3" strip-ansi: "npm:^7.1.0" - supports-hyperlinks: "npm:^3.1.0" + supports-hyperlinks: "npm:^3.0.0" svg-tags: "npm:^1.0.0" table: "npm:^6.8.2" write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10/0a7a697b066af36047fd02c952d59d5b26ac5db7f5772b75481310734c0e722970abb830d60460ea95afb618c51520cc09363dd9b83ae237afb0767b55fb0331 + checksum: 10/81c6f97f9fb2ae31a9abc9f10ddbe595cde697e42cab56f7d745dda2e5378bd9e083f2c12f9d0082745c6283974ad0537bbc0ea71a1bf910fb4de836b1407bd9 languageName: node linkType: hard @@ -16969,13 +18074,13 @@ __metadata: languageName: node linkType: hard -"supports-hyperlinks@npm:^3.1.0": - version: 3.1.0 - resolution: "supports-hyperlinks@npm:3.1.0" +"supports-hyperlinks@npm:^3.0.0": + version: 3.0.0 + resolution: "supports-hyperlinks@npm:3.0.0" dependencies: has-flag: "npm:^4.0.0" supports-color: "npm:^7.0.0" - checksum: 10/e893fb035ecd86e42c5225dc1cd24db56eb950ed77b2e8f59c7aaf2836b8b2ef276ffd11f0df88b0b12184832aa2333f875eefcb74d3c47ed2633b6b41d4be43 + checksum: 10/911075a412d8bcfbbca413e8963d56ed0975e35ff98d599ef85301aed4221428653145263828b6c58cb4cb6ff24596be83ead3cca221a88a70428af93d5e2a73 languageName: node linkType: hard @@ -17007,6 +18112,16 @@ __metadata: languageName: node linkType: hard +"synckit@npm:^0.8.6": + version: 0.8.8 + resolution: "synckit@npm:0.8.8" + dependencies: + "@pkgr/core": "npm:^0.1.0" + tslib: "npm:^2.6.2" + checksum: 10/2864a5c3e689ad5b991bebbd8a583c5682c4fa08a4f39986b510b6b5d160c08fc3672444069f8f96ed6a9d12772879c674c1f61e728573eadfa90af40a765b74 + languageName: node + linkType: hard + "synckit@npm:^0.9.1": version: 0.9.1 resolution: "synckit@npm:0.9.1" @@ -17153,6 +18268,16 @@ __metadata: languageName: node linkType: hard +"timers-ext@npm:^0.1.7": + version: 0.1.8 + resolution: "timers-ext@npm:0.1.8" + dependencies: + es5-ext: "npm:^0.10.64" + next-tick: "npm:^1.1.0" + checksum: 10/8abd168c57029e25d1fa4b7e101b053e261479e43ba4a32ead76e601e7037f74f850c311e22dc3dbb50dc211b34b092e0a349274d3997a493295e9ec725e6395 + languageName: node + linkType: hard + "tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -17167,25 +18292,16 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.1.59": - version: 6.1.59 - resolution: "tldts-core@npm:6.1.59" - checksum: 10/0a3ed78384409aeb33e41d186f273ee423574f33947301746178fd9cdd5f730c647cbc27ef40b214a16101169d3f8b92384231064021887f1f25b6d70ac72aaf - languageName: node - linkType: hard - -"tldts@npm:^6.1.32": - version: 6.1.59 - resolution: "tldts@npm:6.1.59" +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" dependencies: - tldts-core: "npm:^6.1.59" - bin: - tldts: bin/cli.js - checksum: 10/c460c79fd110cf8fdac68fe4c084c4fb11c8a541880fc74c812189cf5d42bb16abe4acdaebd2d5dbff172cd87a2678b7f4a80cccf8b9e72d2378885b1a80a2c8 + os-tmpdir: "npm:~1.0.2" + checksum: 10/09c0abfd165cff29b32be42bc35e80b8c64727d97dedde6550022e88fa9fd39a084660415ed8e3ebaa2aca1ee142f86df8b31d4196d4f81c774a3a20fd4b6abf languageName: node linkType: hard -"tmp@npm:~0.2.3": +"tmp@npm:~0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa @@ -17222,7 +18338,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.4": +"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.3": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" dependencies: @@ -17234,15 +18350,6 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^5.0.0": - version: 5.0.0 - resolution: "tough-cookie@npm:5.0.0" - dependencies: - tldts: "npm:^6.1.32" - checksum: 10/a98d3846ed386e399e8b470c1eb08a6a296944246eabc55c9fe79d629bd2cdaa62f5a6572f271fe0060987906bd20468d72a219a3b4cbe51086bea48d2d677b6 - languageName: node - linkType: hard - "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -17259,22 +18366,6 @@ __metadata: languageName: node linkType: hard -"tree-kill@npm:1.2.2": - version: 1.2.2 - resolution: "tree-kill@npm:1.2.2" - bin: - tree-kill: cli.js - checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 - languageName: node - linkType: hard - -"treeverse@npm:^3.0.0": - version: 3.0.0 - resolution: "treeverse@npm:3.0.0" - checksum: 10/a053ad73f800c64c53ecf0effe7ea12e16eae1cf03f0901ac6b61390b6440d05d0aa8c942b6e77d2e9237d247b36fd405284942419f3817c9c3ef43bc5236218 - languageName: node - linkType: hard - "ts-api-utils@npm:^1.3.0": version: 1.3.0 resolution: "ts-api-utils@npm:1.3.0" @@ -17329,6 +18420,18 @@ __metadata: languageName: node linkType: hard +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" + dependencies: + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10/2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14 + languageName: node + linkType: hard + "tsconfig-paths@npm:^4.2.0": version: 4.2.0 resolution: "tsconfig-paths@npm:4.2.0" @@ -17340,7 +18443,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.13.0": +"tslib@npm:^1.13.0, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb @@ -17368,6 +18471,17 @@ __metadata: languageName: node linkType: hard +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: "npm:^1.8.1" + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 + languageName: node + linkType: hard + "tuf-js@npm:^2.2.1": version: 2.2.1 resolution: "tuf-js@npm:2.2.1" @@ -17411,6 +18525,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10/8907e16284b2d6cfa4f4817e93520121941baba36b39219ea36acfe64c86b9dbc10c9941af450bd60832c8f43464974d51c0957f9858bc66b952b66b6914cbb9 + languageName: node + linkType: hard + "type-fest@npm:^0.21.3": version: 0.21.3 resolution: "type-fest@npm:0.21.3" @@ -17463,6 +18584,13 @@ __metadata: languageName: node linkType: hard +"type@npm:^2.7.2": + version: 2.7.3 + resolution: "type@npm:2.7.3" + checksum: 10/82e99e7795b3de3ecfe685680685e79a77aea515fad9f60b7c55fbf6d43a5c360b1e6e9443354ec8906b38cdf5325829c69f094cb7cd2a1238e85bef9026dc04 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "typed-array-buffer@npm:1.0.2" @@ -17515,6 +18643,15 @@ __metadata: languageName: node linkType: hard +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: "npm:^1.0.0" + checksum: 10/7c850c3433fbdf4d04f04edfc751743b8f577828b8e1eb93b95a3bce782d156e267d83e20fb32b3b47813e69a69ab5e9b5342653332f7d21c7d1210661a7a72c + languageName: node + linkType: hard + "typescript-eslint@npm:^8.5.0": version: 8.5.0 resolution: "typescript-eslint@npm:8.5.0" @@ -17718,6 +18855,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^6.0.0": + version: 6.0.1 + resolution: "universal-user-agent@npm:6.0.1" + checksum: 10/fdc8e1ae48a05decfc7ded09b62071f571c7fe0bd793d700704c80cea316101d4eac15cc27ed2bb64f4ce166d2684777c3198b9ab16034f547abea0d3aa1c93c + languageName: node + linkType: hard + "universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": version: 7.0.2 resolution: "universal-user-agent@npm:7.0.2" @@ -17819,12 +18963,12 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:1.2.2": - version: 1.2.2 - resolution: "use-sync-external-store@npm:1.2.2" +"use-sync-external-store@npm:1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/671e9c190aab9a8374a5d468c6ba17f52c38b6fae970110bc196fc1e2b57204149aea9619be49a1bb5207fb6e51d8afd19c3bcb94afe61813fed039821461dc0 + checksum: 10/a676216affc203876bd47981103f201f28c2731361bb186367e12d287a7566763213a8816910c6eb88265eccd4c230426eb783d64c373c4a180905be8820ed8e languageName: node linkType: hard @@ -17884,7 +19028,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.0": +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" bin: @@ -17954,25 +19098,26 @@ __metadata: linkType: hard "vite-plugin-dts@npm:^4.0.2": - version: 4.2.3 - resolution: "vite-plugin-dts@npm:4.2.3" + version: 4.0.2 + resolution: "vite-plugin-dts@npm:4.0.2" dependencies: - "@microsoft/api-extractor": "npm:7.47.7" + "@microsoft/api-extractor": "npm:7.47.4" "@rollup/pluginutils": "npm:^5.1.0" - "@volar/typescript": "npm:^2.4.4" - "@vue/language-core": "npm:2.1.6" + "@volar/typescript": "npm:^2.3.4" + "@vue/language-core": "npm:2.0.29" compare-versions: "npm:^6.1.1" debug: "npm:^4.3.6" kolorist: "npm:^1.8.0" local-pkg: "npm:^0.5.0" magic-string: "npm:^0.30.11" + vue-tsc: "npm:2.0.29" peerDependencies: typescript: "*" vite: "*" peerDependenciesMeta: vite: optional: true - checksum: 10/7031d3d2dff9aeb5f9378ec22b9deade7ea95608eb551a028c0232bac66ecfc079f94e9dff46cab5d60a41cbc1913b729f161c666ab305d011e326194514e76c + checksum: 10/7028ee320d4e743317d244c553524a3dbfbf871651ad59368a6a5fba71ff4778548ce109fa5348500ff7d55c6b64570cfe14207489bad9ba9ef04948d62fe40d languageName: node linkType: hard @@ -17991,13 +19136,13 @@ __metadata: linkType: hard "vite@npm:^5.4.0": - version: 5.4.11 - resolution: "vite@npm:5.4.11" + version: 5.4.0 + resolution: "vite@npm:5.4.0" dependencies: esbuild: "npm:^0.21.3" fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.43" - rollup: "npm:^4.20.0" + postcss: "npm:^8.4.40" + rollup: "npm:^4.13.0" peerDependencies: "@types/node": ^18.0.0 || >=20.0.0 less: "*" @@ -18029,7 +19174,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/719c4dea896e9547958643354003c8c9ea98e5367196d98f5f46cffb3ec963fead3ea5853f5af941c79bbfb73583dec19bbb0d28d2f644b95d7f59c55e22919d + checksum: 10/5a98b1d30cc8c0263551f417a360102d40b78b9170e9e58a99c1a394ab700c7e604d3c52dafda323c11356a76eba865800d3d8588b33a8febdeaddd5a8981410 languageName: node linkType: hard @@ -18071,6 +19216,21 @@ __metadata: languageName: node linkType: hard +"vue-tsc@npm:2.0.29": + version: 2.0.29 + resolution: "vue-tsc@npm:2.0.29" + dependencies: + "@volar/typescript": "npm:~2.4.0-alpha.18" + "@vue/language-core": "npm:2.0.29" + semver: "npm:^7.5.4" + peerDependencies: + typescript: ">=5.0.0" + bin: + vue-tsc: ./bin/vue-tsc.js + checksum: 10/deabe919d3d3a9c9974791d91c32244c7e831a7d613a281e1e9a9f65bfaa917723883afda79d1d06b67f886dbd23cfdf3ec902a40674f907f0bb792e8eeab088 + languageName: node + linkType: hard + "w3c-xmlserializer@npm:^4.0.0": version: 4.0.0 resolution: "w3c-xmlserializer@npm:4.0.0" @@ -18080,18 +19240,18 @@ __metadata: languageName: node linkType: hard -"wait-on@npm:8.0.1": - version: 8.0.1 - resolution: "wait-on@npm:8.0.1" +"wait-on@npm:7.2.0": + version: 7.2.0 + resolution: "wait-on@npm:7.2.0" dependencies: - axios: "npm:^1.7.7" - joi: "npm:^17.13.3" + axios: "npm:^1.6.1" + joi: "npm:^17.11.0" lodash: "npm:^4.17.21" minimist: "npm:^1.2.8" rxjs: "npm:^7.8.1" bin: wait-on: bin/wait-on - checksum: 10/41f933031b994718dfb50af35bb843f7f7017d601ef22927e92c211736fadd21808fdbf7ae367e998bcaf995cb9c05cf6160552dc655db9082aeecc346bc926d + checksum: 10/00299e3b651c70d7082d02b93d9d4784cbe851914f1674d795d578d4826876193fdc7bee7e9491264b7c2d242ac9fe6e1fd09e1143409f730f13a7ee2da67fff languageName: node linkType: hard @@ -18379,6 +19539,18 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^3.0.3": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: "npm:^0.1.4" + is-typedarray: "npm:^1.0.0" + signal-exit: "npm:^3.0.2" + typedarray-to-buffer: "npm:^3.1.5" + checksum: 10/0955ab94308b74d32bc252afe69d8b42ba4b8a28b8d79f399f3f405969f82623f981e35d13129a52aa2973450f342107c06d86047572637584e85a1c0c246bf3 + languageName: node + linkType: hard + "write-file-atomic@npm:^4.0.2": version: 4.0.2 resolution: "write-file-atomic@npm:4.0.2" @@ -18389,7 +19561,7 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^5.0.0, write-file-atomic@npm:^5.0.1": +"write-file-atomic@npm:^5.0.1": version: 5.0.1 resolution: "write-file-atomic@npm:5.0.1" dependencies: @@ -18399,6 +19571,18 @@ __metadata: languageName: node linkType: hard +"write-json-file@npm:^5.0.0": + version: 5.0.0 + resolution: "write-json-file@npm:5.0.0" + dependencies: + detect-indent: "npm:^7.0.0" + is-plain-obj: "npm:^4.0.0" + sort-keys: "npm:^5.0.0" + write-file-atomic: "npm:^3.0.3" + checksum: 10/6df0e8857c6ebe091cf8d23fa3405f004e7febf10307ad3d4da7b1ddfe1fa80e7cdd9832d3a554e253b6d3a6255d67b66f419cea0f8724abb25949be392eb23b + languageName: node + linkType: hard + "write-json-file@npm:^6.0.0": version: 6.0.0 resolution: "write-json-file@npm:6.0.0" @@ -18411,6 +19595,19 @@ __metadata: languageName: node linkType: hard +"write-package@npm:^7.0.1": + version: 7.0.1 + resolution: "write-package@npm:7.0.1" + dependencies: + deepmerge-ts: "npm:^5.1.0" + read-pkg: "npm:^9.0.0" + sort-keys: "npm:^5.0.0" + type-fest: "npm:^4.6.0" + write-json-file: "npm:^5.0.0" + checksum: 10/e4ac07ff5d240bf1eaa2dd587bfa2bdb13092ba2d36989edff2db197947ba0b573227c1f11f4b3c7dc1fc4431a97bd5fa9810e14136e9405cdcc8945ebe70263 + languageName: node + linkType: hard + "write-package@npm:^7.1.0": version: 7.1.0 resolution: "write-package@npm:7.1.0" @@ -18439,6 +19636,15 @@ __metadata: languageName: node linkType: hard +"xml-formatter@npm:^3.6.2": + version: 3.6.3 + resolution: "xml-formatter@npm:3.6.3" + dependencies: + xml-parser-xo: "npm:^4.1.2" + checksum: 10/5089a30d179499f15760e4492d3dac21cce653e96375e56fc95769ec779d5ac8c9c465f2f78f41eb74391b6af5e8331040553c7194c1832e6627d663d3d41216 + languageName: node + linkType: hard + "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0" @@ -18446,6 +19652,20 @@ __metadata: languageName: node linkType: hard +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 10/43f30f3f6786e406dd665acf08cd742d5f8a46486bd72517edb04b27d1bcd1599664c2a4a99fc3f1e56a3194bff588b12f178b7972bc45c8047bdc4c3ac8d4a1 + languageName: node + linkType: hard + +"xml-parser-xo@npm:^4.1.2": + version: 4.1.2 + resolution: "xml-parser-xo@npm:4.1.2" + checksum: 10/e75387bdcaa03ddc53628e0b9697c7cc16ba11ed25afb5194a8d87f6c7aef2e879b00ceca64377e939f751414282e9b236e20031a6d0fe287981170ae217c34c + languageName: node + linkType: hard + "xmlchars@npm:^2.2.0": version: 2.2.0 resolution: "xmlchars@npm:2.2.0" @@ -18481,7 +19701,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.3.2": +"yaml@npm:^2.0.0, yaml@npm:^2.3.2, yaml@npm:~2.4.2": version: 2.4.5 resolution: "yaml@npm:2.4.5" bin: @@ -18490,15 +19710,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:~2.5.0": - version: 2.5.1 - resolution: "yaml@npm:2.5.1" - bin: - yaml: bin.mjs - checksum: 10/0eecb679db75ea6a989ad97715a9fa5d946972945aa6aa7d2175bca66c213b5564502ccb1cdd04b1bf816ee38b5c43e4e2fda3ff6f5e09da24dabb51ae92c57d - languageName: node - linkType: hard - "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" @@ -18589,10 +19800,10 @@ __metadata: linkType: hard "zustand@npm:^4.3.9": - version: 4.5.5 - resolution: "zustand@npm:4.5.5" + version: 4.5.2 + resolution: "zustand@npm:4.5.2" dependencies: - use-sync-external-store: "npm:1.2.2" + use-sync-external-store: "npm:1.2.0" peerDependencies: "@types/react": ">=16.8" immer: ">=9.0.6" @@ -18604,6 +19815,6 @@ __metadata: optional: true react: optional: true - checksum: 10/481b8210187b69678074a1ca51107654c2379688e90407bfcb7961e0803a259742bfd0d77171c3f07e290896ad55fe9659b3863f30d34cb2572650ead1249f25 + checksum: 10/9e9e92ce7378c5de1d7682f4f10340a1c07a81b673ad0a125b59883a6ade3f2bf39eac6ccc5b05630f9df6ed925291f681592db59ccd3815685c2e83f67c8525 languageName: node linkType: hard