diff --git a/src/api/Tools.ts b/src/api/Tools.ts index 1e651c109..8afbab90c 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -101,11 +101,36 @@ export namespace Tools { figuredLengths = true; } else { let row: DB2Row = {}; + let slideBytesBy = 0; headers.forEach(header => { - const strValue = line.substring(header.from, header.from + header.length).trimEnd(); + const fromPos = header.from - slideBytesBy; + let strValue = line.substring(fromPos, fromPos + header.length); - let realValue: string | number | null = strValue; + /* For each DBCS character, add 1 + Since we are reading characters as UTF8 here, we assume any UTF8 character made up of more than 2 bytes is DBCS + + https://stackoverflow.com/a/14495321/4763757 + + Look at a list of Unicode blocks and their code point ranges, e.g. + the browsable http://www.fileformat.info/info/unicode/block/index.htm or + the official http://www.unicode.org/Public/UNIDATA/Blocks.txt : + + Anything up to U+007F takes 1 byte: Basic Latin + Then up to U+07FF it takes 2 bytes: Greek, Arabic, Cyrillic, Hebrew, etc + Then up to U+FFFF it takes 3 bytes: Chinese, Japanese, Korean, Devanagari, etc + Beyond that it takes 4 bytes + + */ + + const extendedBytes = strValue.split(``).map(c => Buffer.byteLength(c) < 3 ? 0 : 1).reduce((a: number, b: number) => a + b, 0); + + slideBytesBy += extendedBytes; + if (extendedBytes > 0) { + strValue = strValue.substring(0, strValue.length - extendedBytes); + } + + let realValue: string | number | null = strValue.trimEnd(); // is value a number? if (strValue.startsWith(` `)) { diff --git a/src/testing/tools.ts b/src/testing/tools.ts index e1501b089..eaab959dc 100644 --- a/src/testing/tools.ts +++ b/src/testing/tools.ts @@ -48,6 +48,176 @@ export const ToolsSuite: TestSuite = { statement = Tools.fixSQL("@COMMAND LIB(QTEMP/*ALL) TEXT('Hello!');\nSelect * From QTEMP.MYTABLE -- This is mytable"); assert.deepStrictEqual(statement, "Call QSYS2.QCMDEXC('COMMAND LIB(QTEMP/*ALL) TEXT(''Hello!'')');\nSelect * From QTEMP.MYTABLE \n-- This is mytable"); } + }, + { + name: `EN result set test`, test: async () => { + const lines = [ + `DB2>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ``, + `CUSNUM LSTNAM INIT STREET CITY STATE ZIPCOD CDTLMT CHGCOD BALDUE CDTDUE `, + `-------- -------- ----- ------------- ------ ------ ------- ------- ------- -------- --------`, + ` 938472 Henning G K 4859 Elm Ave Dallas TX 75217 5000 3 37.00 0.00`, + ``, + ` 1 RECORD(S) SELECTED.`, + ] + + const rows = Tools.db2Parse(lines.join(`\n`)); + + assert.strictEqual(rows.length, 1); + + assert.strictEqual(rows[0].CUSNUM, 938472); + assert.strictEqual(rows[0].LSTNAM, `Henning`); + assert.strictEqual(rows[0].INIT, `G K`); + assert.strictEqual(rows[0].STREET, `4859 Elm Ave`); + assert.strictEqual(rows[0].CITY, `Dallas`); + assert.strictEqual(rows[0].STATE, `TX`); + assert.strictEqual(rows[0].ZIPCOD, 75217); + assert.strictEqual(rows[0].CDTLMT, 5000); + assert.strictEqual(rows[0].CHGCOD, 3); + assert.strictEqual(rows[0].BALDUE, 37); + assert.strictEqual(rows[0].CDTDUE, 0); + } + }, + { + name: `FR result set test`, test: async () => { + const lines = [ + `DB2>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ``, + `ALPHA NUMÉRIQUE état unité MAJUSCULES minuscules aperçu hélas où? `, + `---------- ----------- ---------- ---------- ---------- ---------- ---------- ---------- -------`, + `Valeur1 1 Français mètre ÀÉÈÇÙ àéèçù déterminé oui? LÀ-BAS!`, + ``, + ` 1 RECORD(S) SELECTED.`, + ] + + const rows = Tools.db2Parse(lines.join(`\n`)); + + assert.strictEqual(rows.length, 1); + + assert.strictEqual(rows[0].ALPHA, `Valeur1`); + assert.strictEqual(rows[0].NUMÉRIQUE, 1); + assert.strictEqual(rows[0].état, `Français`); + assert.strictEqual(rows[0].unité, `mètre`); + assert.strictEqual(rows[0].MAJUSCULES, `ÀÉÈÇÙ`); + assert.strictEqual(rows[0].minuscules, `àéèçù`); + assert.strictEqual(rows[0].aperçu, `déterminé`); + assert.strictEqual(rows[0].hélas, `oui?`); + assert.strictEqual(rows[0]["où?"], `LÀ-BAS!`); + } + }, + { + name: `DA result set test`, test: async () => { + const lines = [ + `DB2>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ``, + `COL1A COL2N COL3Æ COL4Ø COL5Å ÆCOL6 ØCOL7 ÅCOL8 ÆCOL9 ØCOL10 ÅCOL11 `, + `---------- ----------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------`, + `Val1 1 Val3æÆ Val4øØ Val5åÅ æÆVal6 øØVal7 åÅVal8 ValæÆ9 ValøØ10 ValåÅ11 `, + ``, + ` 1 RECORD(S) SELECTED.`, + ] + + const rows = Tools.db2Parse(lines.join(`\n`)); + + assert.strictEqual(rows.length, 1); + + assert.strictEqual(rows[0].COL1A, `Val1`); + assert.strictEqual(rows[0].COL2N, 1); + assert.strictEqual(rows[0].COL3Æ, `Val3æÆ`); + assert.strictEqual(rows[0].COL4Ø, `Val4øØ`); + assert.strictEqual(rows[0].COL5Å, `Val5åÅ`); + assert.strictEqual(rows[0].ÆCOL6, `æÆVal6`); + assert.strictEqual(rows[0].ØCOL7, `øØVal7`); + assert.strictEqual(rows[0].ÅCOL8, `åÅVal8`); + assert.strictEqual(rows[0].ÆCOL9, `ValæÆ9`); + assert.strictEqual(rows[0].ØCOL10, `ValøØ10`); + assert.strictEqual(rows[0].ÅCOL11, `ValåÅ11`); + } + }, + { + name: `JP result set test (A)`, test: async () => { + const lines = [ + `DB2>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ``, + ``, + `LIBRARY RECORD_LENGTH ASP SOURCE_FILE NAME TYPE TEXT LINES CREATED CHANGED `, + `---------- -------------------- ------ ------------ ---------- ---------- ------------------------------------------------------------------------------------------------------------------------------ -------------------- -------------------- --------------------`, + `SNDLIB 112 0 QRPGLESRC SNDDEFD DSPF 送信定義一覧/追加・修正 124 1712670631000 1712683676000`, + `SNDLIB 112 0 QRPGLESRC SNDDEFR RPGLE 送信定義一覧/追加・修正 386 1712683661000 1712683692000`, + ``, + ` 2 RECORD(S) SELECTED.`, + ]; + + const rows = Tools.db2Parse(lines.join(`\n`)); + + assert.strictEqual(rows.length, 2); + + assert.strictEqual(rows[0].LIBRARY, `SNDLIB`); + assert.strictEqual(rows[0].RECORD_LENGTH, 112); + assert.strictEqual(rows[0].ASP, 0); + assert.strictEqual(rows[0].SOURCE_FILE, `QRPGLESRC`); + assert.strictEqual(rows[0].NAME, `SNDDEFD`); + assert.strictEqual(rows[0].TYPE, `DSPF`); + assert.strictEqual(rows[0].TEXT, `送信定義一覧/追加・修正`); + assert.strictEqual(rows[0].LINES, 124); + assert.strictEqual(rows[0].CREATED, 1712670631000); + assert.strictEqual(rows[0].CHANGED, 1712683676000); + } + }, + { + name: `JP result set test (B)`, test: async () => { + const lines = [ + `DB2>`, + ` ?>`, + ` ?>`, + ` ?>`, + ` ?>`, + ``, + ``, + `LIBRARY RECORD_LENGTH ASP SOURCE_FILE NAME TYPE TEXT LINES CREATED CHANGED`, + `--------- -------------------- ------ ------------ ---------- ---------- ------------------------------------------------------------------------------------------------------------------------------ -------------------- -------------------- --------------------`, + `SNDLIB 112 0 QRPGLESRC TESTEDTW RPGLE 日付と時刻を先行0付きで表示-> 8桁では無理? 9 1713451802000 1713453741000`, + ``, + ` 1 RECORD(S) SELECTED.`, + ]; + + const rows = Tools.db2Parse(lines.join(`\n`)); + + assert.strictEqual(rows.length, 1); + + assert.strictEqual(rows[0].LIBRARY, `SNDLIB`); + assert.strictEqual(rows[0].RECORD_LENGTH, 112); + assert.strictEqual(rows[0].ASP, 0); + assert.strictEqual(rows[0].SOURCE_FILE, `QRPGLESRC`); + assert.strictEqual(rows[0].NAME, `TESTEDTW`); + assert.strictEqual(rows[0].TYPE, `RPGLE`); + assert.strictEqual(rows[0].TEXT, `日付と時刻を先行0付きで表示-> 8桁では無理?`); + assert.strictEqual(rows[0].LINES, 9); + assert.strictEqual(rows[0].CREATED, 1713451802000); + assert.strictEqual(rows[0].CHANGED, 1713453741000); + } } ] };