diff --git a/packages/datalib/src/JslGridDisplay.ts b/packages/datalib/src/JslGridDisplay.ts index 265c2f05f..b371277f2 100644 --- a/packages/datalib/src/JslGridDisplay.ts +++ b/packages/datalib/src/JslGridDisplay.ts @@ -18,7 +18,7 @@ export class JslGridDisplay extends GridDisplay { this.filterable = true; - if (structure.columns) { + if (structure?.columns) { this.columns = _.uniqBy( structure.columns .map(col => ({ @@ -39,7 +39,7 @@ export class JslGridDisplay extends GridDisplay { ); } - if (structure.__isDynamicStructure) { + if (structure?.__isDynamicStructure) { this.columns = analyseCollectionDisplayColumns(rows, this); } diff --git a/packages/web/src/freetable/FreeTableColumnEditor.svelte b/packages/web/src/freetable/FreeTableColumnEditor.svelte index a3cf54495..fece23122 100644 --- a/packages/web/src/freetable/FreeTableColumnEditor.svelte +++ b/packages/web/src/freetable/FreeTableColumnEditor.svelte @@ -9,7 +9,7 @@ rows: rowFunc ? model.rows.map(rowFunc) : model.rows, structure: { ...model.structure, - columns: func(model.structure.columns), + columns: func(model.structure?.columns), }, }, }); @@ -40,7 +40,7 @@ - {#each structure.columns || [] as column, index} + {#each structure?.columns || [] as column, index} {#if index == editingColumn} (editingColumn = null)} focusOnCreate blurOnEnter - existingNames={structure.columns.map(x => x.columnName)} + existingNames={structure?.columns.map(x => x.columnName)} /> {:else} [...cols, { columnName }]); }} placeholder="New column" - existingNames={(structure.columns || []).map(x => x.columnName)} + existingNames={(structure?.columns || []).map(x => x.columnName)} /> diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index 90aebcfeb..8b17149ae 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -38,6 +38,7 @@ 'icon file': 'mdi mdi-file', 'icon loading': 'mdi mdi-loading mdi-spin', 'icon close': 'mdi mdi-close', + 'icon stop': 'mdi mdi-close-octagon', 'icon filter': 'mdi mdi-filter', 'icon filter-off': 'mdi mdi-filter-off', 'icon reload': 'mdi mdi-reload', diff --git a/packages/web/src/modals/ImportExportModal.svelte b/packages/web/src/modals/ImportExportModal.svelte index caefd0d8e..92b20983d 100644 --- a/packages/web/src/modals/ImportExportModal.svelte +++ b/packages/web/src/modals/ImportExportModal.svelte @@ -174,7 +174,7 @@
{#if busy} - Cancel + Stop {:else} Run {/if} diff --git a/plugins/dbgate-plugin-xml/src/backend/reader.js b/plugins/dbgate-plugin-xml/src/backend/reader.js index be9531c01..47ecc7d37 100644 --- a/plugins/dbgate-plugin-xml/src/backend/reader.js +++ b/plugins/dbgate-plugin-xml/src/backend/reader.js @@ -3,7 +3,7 @@ const stream = require('stream'); const NodeXmlStream = require('node-xml-stream'); class ParseStream extends stream.Transform { - constructor({ elementName }) { + constructor({ itemElementName }) { super({ objectMode: true }); this.rowsWritten = 0; this.parser = new NodeXmlStream(); @@ -17,7 +17,7 @@ class ParseStream extends stream.Transform { } }); this.parser.on('closetag', (name, attrs) => { - if (name == elementName) { + if (name == itemElementName) { this.rowsWritten += 1; this.push({ ...this.stack[this.stack.length - 1].attrs, ...this.stack[this.stack.length - 1].nodes }); } @@ -30,11 +30,11 @@ class ParseStream extends stream.Transform { } } -async function reader({ fileName, encoding = 'utf-8', elementName }) { +async function reader({ fileName, encoding = 'utf-8', itemElementName }) { console.log(`Reading file ${fileName}`); const fileStream = fs.createReadStream(fileName, encoding); - const parser = new ParseStream({ elementName }); + const parser = new ParseStream({ itemElementName }); fileStream.pipe(parser); return parser; } diff --git a/plugins/dbgate-plugin-xml/src/backend/writer.js b/plugins/dbgate-plugin-xml/src/backend/writer.js index 975460116..a881a1a3c 100644 --- a/plugins/dbgate-plugin-xml/src/backend/writer.js +++ b/plugins/dbgate-plugin-xml/src/backend/writer.js @@ -1,19 +1,68 @@ const fs = require('fs'); const stream = require('stream'); +function escapeXml(value) { + return value.replace(/[<>&'"]/g, function (c) { + switch (c) { + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; + case "'": + return '''; + case '"': + return '"'; + } + }); +} + class StringifyStream extends stream.Transform { - constructor() { + constructor({ itemElementName, rootElementName }) { super({ objectMode: true }); + this.itemElementName = itemElementName; + this.rootElementName = rootElementName; + + this.startElement(this.rootElementName); } + + startElement(element) { + this.push('<'); + this.push(element); + this.push('>\n'); + } + + endElement(element) { + this.push('\n'); + } + + elementValue(element, value) { + this.startElement(element); + this.push(escapeXml(`${value}`)); + this.endElement(element); + } + _transform(chunk, encoding, done) { - this.push(JSON.stringify(chunk) + '\n'); + this.startElement(this.itemElementName); + for (const key of Object.keys(chunk)) { + this.elementValue(key, chunk[key]); + } + this.endElement(this.itemElementName); done(); } + + _final(callback) { + this.endElement(this.rootElementName); + callback(); + } } -async function writer({ fileName, encoding = 'utf-8' }) { +async function writer({ fileName, encoding = 'utf-8', itemElementName, rootElementName }) { console.log(`Writing file ${fileName}`); - const stringify = new StringifyStream(); + const stringify = new StringifyStream({ itemElementName, rootElementName }); const fileStream = fs.createWriteStream(fileName, encoding); stringify.pipe(fileStream); stringify['finisher'] = fileStream; diff --git a/plugins/dbgate-plugin-xml/src/frontend/index.js b/plugins/dbgate-plugin-xml/src/frontend/index.js index d4e30602f..930d2ed39 100644 --- a/plugins/dbgate-plugin-xml/src/frontend/index.js +++ b/plugins/dbgate-plugin-xml/src/frontend/index.js @@ -14,9 +14,16 @@ const fileFormat = { args: [ { type: 'text', - name: 'elementName', - label: 'Element name', - apiName: 'elementName', + name: 'rootElementName', + label: 'Root element name', + apiName: 'rootElementName', + direction: 'target', + }, + { + type: 'text', + name: 'itemElementName', + label: 'Item element name', + apiName: 'itemElementName', }, ], };