Skip to content

Commit 60627c6

Browse files
authored
Add command for extracting UDL documents from an XML file (#1299)
1 parent cc753a2 commit 60627c6

File tree

6 files changed

+285
-83
lines changed

6 files changed

+285
-83
lines changed

package.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
},
142142
{
143143
"command": "vscode-objectscript.previewXml",
144-
"when": "editorLangId == xml && vscode-objectscript.connectActive"
144+
"when": "vscode-objectscript.connectActive && resourceExtname =~ /^\\.xml$/i && !(resourceScheme =~ /^isfs(-readonly)?$/)"
145145
},
146146
{
147147
"command": "vscode-objectscript.explorer.export",
@@ -334,6 +334,10 @@
334334
{
335335
"command": "vscode-objectscript.exportToXMLFile",
336336
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
337+
},
338+
{
339+
"command": "vscode-objectscript.extractXMLFileContents",
340+
"when": "vscode-objectscript.connectActive && workspaceFolderCount != 0"
337341
}
338342
],
339343
"view/title": [
@@ -483,7 +487,7 @@
483487
},
484488
{
485489
"command": "vscode-objectscript.previewXml",
486-
"when": "editorLangId =~ /^xml/",
490+
"when": "vscode-objectscript.connectActive && resourceExtname =~ /^\\.xml$/i && !(resourceScheme =~ /^isfs(-readonly)?$/)",
487491
"group": "objectscript@4"
488492
},
489493
{
@@ -603,6 +607,11 @@
603607
"command": "vscode-objectscript.modifyWsFolder",
604608
"when": "vscode-objectscript.connectActive && resourceScheme =~ /^isfs(-readonly)?$/ && explorerResourceIsRoot",
605609
"group": "objectscript_modify@3"
610+
},
611+
{
612+
"command": "vscode-objectscript.extractXMLFileContents",
613+
"when": "vscode-objectscript.connectActive && resourceExtname =~ /^\\.xml$/i && !(resourceScheme =~ /^isfs(-readonly)?$/)",
614+
"group": "objectscript_modify@4"
606615
}
607616
],
608617
"file/newFile": [
@@ -1145,6 +1154,11 @@
11451154
"category": "ObjectScript",
11461155
"command": "vscode-objectscript.exportToXMLFile",
11471156
"title": "Export Documents to XML File..."
1157+
},
1158+
{
1159+
"category": "ObjectScript",
1160+
"command": "vscode-objectscript.extractXMLFileContents",
1161+
"title": "Extract Documents from XML File..."
11481162
}
11491163
],
11501164
"keybindings": [

src/commands/export.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ export const getFileName = (
4444
addCategory: boolean,
4545
map: {
4646
[key: string]: string;
47-
}
47+
},
48+
sep = path.sep
4849
): string => {
4950
if (name.includes("/")) {
5051
// This is a file from a web application
5152
const nameArr: string[] = name.split("/");
5253
const cat = addCategory ? getCategory(name, addCategory) : null;
53-
return [folder, cat, ...nameArr].filter(notNull).join(path.sep);
54+
return [folder, cat, ...nameArr].filter(notNull).join(sep);
5455
} else {
5556
let fileNameArray: string[];
5657
let fileExt: string;
@@ -79,10 +80,10 @@ export const getFileName = (
7980
}
8081
const cat = addCategory ? getCategory(name, addCategory) : null;
8182
if (split) {
82-
const fileName = [folder, cat, ...fileNameArray].filter(notNull).join(path.sep);
83+
const fileName = [folder, cat, ...fileNameArray].filter(notNull).join(sep);
8384
return [fileName, fileExt].join(".");
8485
}
85-
return [folder, cat, name].filter(notNull).join(path.sep);
86+
return [folder, cat, name].filter(notNull).join(sep);
8687
}
8788
};
8889

src/commands/xml2doc.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/commands/xmlToUdl.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import * as vscode from "vscode";
2+
import { config, filesystemSchemas, OBJECTSCRIPTXML_FILE_SCHEMA, xmlContentProvider } from "../extension";
3+
import { AtelierAPI } from "../api";
4+
import { fileExists, outputChannel } from "../utils";
5+
import { getFileName } from "./export";
6+
7+
const exportHeader = /^\s*<Export generator="(Cache|IRIS)" version="\d+"/;
8+
9+
export async function previewXMLAsUDL(textEditor: vscode.TextEditor, auto = false): Promise<void> {
10+
const uri = textEditor.document.uri;
11+
const content = textEditor.document.getText();
12+
if (
13+
!filesystemSchemas.includes(uri.scheme) &&
14+
uri.path.toLowerCase().endsWith("xml") &&
15+
textEditor.document.lineCount > 2
16+
) {
17+
if (exportHeader.test(textEditor.document.lineAt(1).text)) {
18+
const api = new AtelierAPI(uri);
19+
if (!api.active) return;
20+
try {
21+
// Convert the file
22+
const udlDocs: { name: string; content: string[] }[] = await api
23+
.cvtXmlUdl(content)
24+
.then((data) => data.result.content);
25+
if (udlDocs.length == 0) {
26+
vscode.window.showErrorMessage(
27+
`File '${uri.toString(true)}' contains no documents that can be previewed.`,
28+
"Dismiss"
29+
);
30+
return;
31+
}
32+
// Prompt the user for documents to preview
33+
const docsToPreview = await vscode.window.showQuickPick(
34+
udlDocs.map((d) => {
35+
return { label: d.name, picked: true };
36+
}),
37+
{
38+
canPickMany: true,
39+
ignoreFocusOut: true,
40+
title: "Select the documents to preview",
41+
}
42+
);
43+
if (docsToPreview == undefined || docsToPreview.length == 0) {
44+
return;
45+
}
46+
const docWhitelist = docsToPreview.map((d) => d.label);
47+
// Send the UDL text to the content provider
48+
xmlContentProvider.addUdlDocsForFile(uri.toString(), udlDocs);
49+
// Open the files
50+
for (const udlDoc of udlDocs) {
51+
if (!docWhitelist.includes(udlDoc.name)) continue; // This file wasn't selected
52+
// await for response so we know when it's safe to clear the provider's cache
53+
await vscode.window
54+
.showTextDocument(
55+
vscode.Uri.from({
56+
path: udlDoc.name,
57+
fragment: uri.toString(),
58+
scheme: OBJECTSCRIPTXML_FILE_SCHEMA,
59+
}),
60+
{
61+
preserveFocus: true,
62+
preview: false,
63+
viewColumn: vscode.ViewColumn.Beside,
64+
}
65+
)
66+
.then(
67+
() => {
68+
// Don't need return value
69+
},
70+
() => {
71+
// Swallow errors
72+
}
73+
);
74+
}
75+
// Remove the UDL text from the content provider's cache
76+
xmlContentProvider.removeUdlDocsForFile(uri.toString());
77+
} catch (error) {
78+
let errorMsg = "Error executing 'Preview XML as UDL' command.";
79+
if (error && error.errorText && error.errorText !== "") {
80+
outputChannel.appendLine("\n" + error.errorText);
81+
outputChannel.show(true);
82+
errorMsg += " Check 'ObjectScript' output channel for details.";
83+
}
84+
vscode.window.showErrorMessage(errorMsg, "Dismiss");
85+
}
86+
} else if (!auto) {
87+
vscode.window.showErrorMessage(`XML file '${uri.toString(true)}' is not an InterSystems export.`, "Dismiss");
88+
}
89+
}
90+
}
91+
92+
/** Extract the source documents in an XML file as UDL and create the UDL files using the export settings. */
93+
export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise<void> {
94+
if (!xmlUri && vscode.window.activeTextEditor) {
95+
// Check if the active text editor contains an XML file
96+
const activeDoc = vscode.window.activeTextEditor.document;
97+
if (
98+
!filesystemSchemas.includes(activeDoc.uri.scheme) &&
99+
activeDoc.uri.path.toLowerCase().endsWith("xml") &&
100+
activeDoc.lineCount > 2
101+
) {
102+
// The active text editor contains an XML file, so process it
103+
xmlUri = activeDoc.uri;
104+
}
105+
}
106+
try {
107+
// Determine the workspace folder
108+
let wsFolder: vscode.WorkspaceFolder;
109+
if (xmlUri) {
110+
wsFolder = vscode.workspace.getWorkspaceFolder(xmlUri);
111+
} else {
112+
// Can only run this command on non-isfs folders with an active server connection
113+
const options = vscode.workspace.workspaceFolders.filter(
114+
(f) => !filesystemSchemas.includes(f.uri.scheme) && new AtelierAPI(f.uri).active
115+
);
116+
if (options.length == 0) {
117+
vscode.window.showErrorMessage(
118+
"'Extract Documents from XML File...' command requires a non-isfs workspace folder with an active server connection.",
119+
"Dismiss"
120+
);
121+
return;
122+
} else if (options.length == 1) {
123+
wsFolder = options[0];
124+
} else {
125+
// Prompt the user to select a workspace folder
126+
wsFolder = (
127+
await vscode.window.showQuickPick(
128+
options.map((f) => {
129+
return { label: f.name, wf: f };
130+
}),
131+
{
132+
ignoreFocusOut: true,
133+
placeHolder: "Pick the workspace folder to run the command in",
134+
}
135+
)
136+
)?.wf;
137+
}
138+
}
139+
if (!wsFolder) return;
140+
const api = new AtelierAPI(wsFolder.uri);
141+
if (!xmlUri) {
142+
// Prompt the user the file to extract
143+
const uris = await vscode.window.showOpenDialog({
144+
canSelectFiles: true,
145+
canSelectFolders: false,
146+
canSelectMany: false,
147+
openLabel: "Extract",
148+
filters: {
149+
"XML Files": ["xml"],
150+
},
151+
defaultUri: wsFolder.uri,
152+
});
153+
if (!Array.isArray(uris) || uris.length == 0) {
154+
// No file to extract
155+
return;
156+
}
157+
xmlUri = uris[0];
158+
if (xmlUri.path.split(".").pop().toLowerCase() != "xml") {
159+
vscode.window.showErrorMessage("The selected file was not XML.", "Dismiss");
160+
return;
161+
}
162+
}
163+
// Read the XML file
164+
const xmlContent = new TextDecoder().decode(await vscode.workspace.fs.readFile(xmlUri)).split(/\r?\n/);
165+
if (xmlContent.length < 3 || !exportHeader.test(xmlContent[1])) {
166+
vscode.window.showErrorMessage(`XML file '${xmlUri.toString(true)}' is not an InterSystems export.`, "Dismiss");
167+
return;
168+
}
169+
// Convert the file
170+
const udlDocs: { name: string; content: string[] }[] = await api
171+
.cvtXmlUdl(xmlContent.join("\n"))
172+
.then((data) => data.result.content);
173+
if (udlDocs.length == 0) {
174+
vscode.window.showErrorMessage(
175+
`File '${xmlUri.toString(true)}' contains no documents that can be extracted.`,
176+
"Dismiss"
177+
);
178+
return;
179+
}
180+
// Prompt the user for documents to extract
181+
const docsToExtract = await vscode.window.showQuickPick(
182+
udlDocs.map((d) => {
183+
return { label: d.name, picked: true };
184+
}),
185+
{
186+
canPickMany: true,
187+
ignoreFocusOut: true,
188+
title: "Select the documents to extract",
189+
placeHolder: "Files are created using your 'objectscript.export' settings",
190+
}
191+
);
192+
if (docsToExtract == undefined || docsToExtract.length == 0) {
193+
return;
194+
}
195+
const docWhitelist = docsToExtract.map((d) => d.label);
196+
// Write the UDL files
197+
const { atelier, folder, addCategory, map } = config("export", wsFolder.name);
198+
const rootFolder = wsFolder.uri.path + (typeof folder == "string" && folder.length ? `/${folder}` : "");
199+
const textEncoder = new TextEncoder();
200+
let errs = 0;
201+
for (const udlDoc of udlDocs) {
202+
if (!docWhitelist.includes(udlDoc.name)) continue; // This file wasn't selected
203+
const fileUri = wsFolder.uri.with({ path: getFileName(rootFolder, udlDoc.name, atelier, addCategory, map, "/") });
204+
if (await fileExists(fileUri)) {
205+
outputChannel.appendLine(`File '${fileUri.toString(true)}' already exists.`);
206+
errs++;
207+
continue;
208+
}
209+
try {
210+
await vscode.workspace.fs.writeFile(fileUri, textEncoder.encode(udlDoc.content.join("\n")));
211+
} catch (error) {
212+
outputChannel.appendLine(
213+
typeof error == "string" ? error : error instanceof Error ? error.message : JSON.stringify(error)
214+
);
215+
errs++;
216+
}
217+
}
218+
if (errs) {
219+
vscode.window.showErrorMessage(
220+
`Failed to write ${errs} file${errs > 1 ? "s" : ""}. Check 'ObjectScript' output channel for details.`,
221+
"Dismiss"
222+
);
223+
}
224+
} catch (error) {
225+
let errorMsg = "Error executing 'Extract Documents from XML File...' command.";
226+
if (error && error.errorText && error.errorText !== "") {
227+
outputChannel.appendLine("\n" + error.errorText);
228+
outputChannel.show(true);
229+
errorMsg += " Check 'ObjectScript' output channel for details.";
230+
}
231+
vscode.window.showErrorMessage(errorMsg, "Dismiss");
232+
}
233+
}

0 commit comments

Comments
 (0)