|
| 1 | +import { XMLBuilder, XMLParser } from "fast-xml-parser"; |
| 2 | +import { readFile, writeFile } from "fs/promises"; |
| 3 | +import { Version, VersionString } from "../version"; |
| 4 | +import { ClientModulePackageFile } from "./schema"; |
| 5 | + |
| 6 | +export function xmlTextToXmlJson(xmlText: string | Buffer): unknown { |
| 7 | + const parser = new XMLParser({ ignoreAttributes: false }); |
| 8 | + return parser.parse(xmlText); |
| 9 | +} |
| 10 | + |
| 11 | +export function xmlJsonToXmlText(xmlObject: any): string { |
| 12 | + const builder = new XMLBuilder({ |
| 13 | + ignoreAttributes: false, |
| 14 | + format: true, |
| 15 | + indentBy: " ", |
| 16 | + suppressEmptyNode: true |
| 17 | + }); |
| 18 | + return builder |
| 19 | + .build(xmlObject) |
| 20 | + .replaceAll(/(<[^>]*?)\/>/g, "$1 />") // Add space before /> in self-closing tags |
| 21 | + .replaceAll(/(<\?[^>]*?)\?>/g, "$1 ?>"); // Add space before ?> in XML declarations |
| 22 | +} |
| 23 | + |
| 24 | +export interface ClientPackageXML { |
| 25 | + name: string; |
| 26 | + version: Version; |
| 27 | + widgetFiles: string[]; |
| 28 | + files: string[]; |
| 29 | +} |
| 30 | + |
| 31 | +export async function readClientPackageXml(path: string): Promise<ClientPackageXML> { |
| 32 | + return parseClientPackageXml(ClientModulePackageFile.passthrough().parse(xmlTextToXmlJson(await readFile(path)))); |
| 33 | +} |
| 34 | + |
| 35 | +export async function writeClientPackageXml(path: string, data: ClientPackageXML): Promise<void> { |
| 36 | + await writeFile(path, xmlJsonToXmlText(buildClientPackageXml(data))); |
| 37 | +} |
| 38 | + |
| 39 | +function parseClientPackageXml(xmlJson: ClientModulePackageFile): ClientPackageXML { |
| 40 | + const clientModule = xmlJson?.package?.clientModule ?? {}; |
| 41 | + const widgetFilesNode = clientModule.widgetFiles !== "" ? clientModule.widgetFiles?.widgetFile : undefined; |
| 42 | + const filesNode = clientModule.files !== "" ? clientModule.files?.file : undefined; |
| 43 | + |
| 44 | + const extractPaths = (node: any): string[] => { |
| 45 | + if (!node) return []; |
| 46 | + if (Array.isArray(node)) { |
| 47 | + return node.map((item: any) => item["@_path"]); |
| 48 | + } |
| 49 | + return [node["@_path"]]; |
| 50 | + }; |
| 51 | + |
| 52 | + const name = clientModule["@_name"] ?? ""; |
| 53 | + const versionString = clientModule["@_version"] ?? "1.0.0"; |
| 54 | + |
| 55 | + return { |
| 56 | + name, |
| 57 | + version: Version.fromString(versionString as VersionString), |
| 58 | + widgetFiles: extractPaths(widgetFilesNode), |
| 59 | + files: extractPaths(filesNode) |
| 60 | + }; |
| 61 | +} |
| 62 | + |
| 63 | +function buildClientPackageXml(clientPackage: ClientPackageXML): ClientModulePackageFile { |
| 64 | + const toXmlNode = <T extends "file" | "widgetFile">(arr: string[], tag: T) => { |
| 65 | + if (arr.length === 0) return ""; |
| 66 | + if (arr.length === 1) { |
| 67 | + return { [tag]: { "@_path": arr[0] } }; |
| 68 | + } |
| 69 | + return { [tag]: arr.map(path => ({ "@_path": path })) }; |
| 70 | + }; |
| 71 | + |
| 72 | + return { |
| 73 | + "?xml": { |
| 74 | + "@_version": "1.0", |
| 75 | + "@_encoding": "utf-8" |
| 76 | + }, |
| 77 | + package: { |
| 78 | + clientModule: { |
| 79 | + widgetFiles: toXmlNode( |
| 80 | + clientPackage.widgetFiles, |
| 81 | + "widgetFile" |
| 82 | + ) as ClientModulePackageFile["package"]["clientModule"]["widgetFiles"], |
| 83 | + files: toXmlNode( |
| 84 | + clientPackage.files, |
| 85 | + "file" |
| 86 | + ) as ClientModulePackageFile["package"]["clientModule"]["files"], |
| 87 | + "@_name": clientPackage.name, |
| 88 | + "@_version": clientPackage.version.format(), |
| 89 | + "@_xmlns": "http://www.mendix.com/clientModule/1.0/" |
| 90 | + }, |
| 91 | + "@_xmlns": "http://www.mendix.com/package/1.0/" |
| 92 | + } |
| 93 | + }; |
| 94 | +} |
0 commit comments