@saltcorn/qlik-qvd 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +53 -8
- package/package.json +1 -2
package/index.js
CHANGED
|
@@ -19,11 +19,42 @@ const csvCell = (v) => {
|
|
|
19
19
|
return s;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// Resolve a single QVD symbol to the value qvd4js would actually place in the
|
|
23
|
+
// data frame: QvdSymbol.toPrimaryValue() prioritises the string representation,
|
|
24
|
+
// and QvdFileReader.load() then coerces numeric-looking strings to numbers.
|
|
25
|
+
const symbolValue = (symbol) => {
|
|
26
|
+
const value = symbol.toPrimaryValue();
|
|
27
|
+
if (typeof value === "string" && value !== "" && !isNaN(Number(value)))
|
|
28
|
+
return Number(value);
|
|
29
|
+
return value;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Inspect every distinct symbol of a field to discover what it really holds.
|
|
33
|
+
// The QVD NumberFormat cannot be trusted on its own: a field tagged INTEGER may
|
|
34
|
+
// still contain pure strings (e.g. "KBS108244839"), so the symbol table is the
|
|
35
|
+
// authoritative source for the column type.
|
|
36
|
+
const scanSymbols = (symbols) => {
|
|
37
|
+
let hasString = false;
|
|
38
|
+
let hasNumber = false;
|
|
39
|
+
let hasFloat = false;
|
|
40
|
+
for (const symbol of symbols || []) {
|
|
41
|
+
const value = symbolValue(symbol);
|
|
42
|
+
if (value === null || value === undefined) continue;
|
|
43
|
+
if (typeof value === "number") {
|
|
44
|
+
hasNumber = true;
|
|
45
|
+
if (!Number.isInteger(value)) hasFloat = true;
|
|
46
|
+
} else {
|
|
47
|
+
hasString = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { hasString, hasNumber, hasFloat };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const deduceFieldType = (field, symbols) => {
|
|
54
|
+
const nf = field.NumberFormat;
|
|
55
|
+
// Dates/times are identified through the number format only; in the symbol
|
|
56
|
+
// table they appear as numeric serials or formatted strings.
|
|
25
57
|
if (nf.Type === "TIME") return { type: "String" };
|
|
26
|
-
if (nf.Type === "UNKNOWN") return { type: "String" };
|
|
27
58
|
if (nf.Type === "DATE")
|
|
28
59
|
return {
|
|
29
60
|
type: "Date",
|
|
@@ -33,7 +64,16 @@ const numberFormatToType = (nf) => {
|
|
|
33
64
|
return {
|
|
34
65
|
type: "Date",
|
|
35
66
|
};
|
|
36
|
-
|
|
67
|
+
|
|
68
|
+
// For everything else, let the actual symbol contents decide between String,
|
|
69
|
+
// Float and Integer rather than relying on the (unreliable) NumberFormat.
|
|
70
|
+
const { hasString, hasNumber, hasFloat } = scanSymbols(symbols);
|
|
71
|
+
if (hasString) return { type: "String" };
|
|
72
|
+
if (hasFloat || nf.Type === "REAL" || nf.Type === "FIX")
|
|
73
|
+
return { type: "Float" };
|
|
74
|
+
if (hasNumber || nf.Type === "INTEGER") return { type: "Integer" };
|
|
75
|
+
// No usable symbols (e.g. an all-null column): fall back to String.
|
|
76
|
+
return { type: "String" };
|
|
37
77
|
};
|
|
38
78
|
|
|
39
79
|
const QLIK_EPOCH_MS = Date.UTC(1899, 11, 30); // 1899-12-30 00:00:00 (month is 0-indexed)
|
|
@@ -56,19 +96,24 @@ module.exports = {
|
|
|
56
96
|
const reader = new QvdFileReader(file.location);
|
|
57
97
|
const df = await reader.load();
|
|
58
98
|
const qvdTableName = reader._header.QvdTableHeader.TableName;
|
|
59
|
-
|
|
99
|
+
let fields =
|
|
60
100
|
reader._header["QvdTableHeader"]["Fields"]["QvdFieldHeader"];
|
|
101
|
+
// A QVD with a single field is parsed as an object, not an array.
|
|
102
|
+
// Normalise so it lines up with reader._symbolTable, which is always an
|
|
103
|
+
// array indexed by field position.
|
|
104
|
+
if (!Array.isArray(fields)) fields = [fields];
|
|
61
105
|
|
|
62
106
|
let table = Table.findOne({ name: table_name || qvdTableName });
|
|
63
107
|
let field_names = [];
|
|
64
108
|
if (!table) {
|
|
65
109
|
table = await Table.create(table_name || qvdTableName);
|
|
66
110
|
|
|
67
|
-
for (
|
|
111
|
+
for (let i = 0; i < fields.length; i++) {
|
|
112
|
+
const field = fields[i];
|
|
68
113
|
const fld = {
|
|
69
114
|
table,
|
|
70
115
|
label: field.FieldName,
|
|
71
|
-
...
|
|
116
|
+
...deduceFieldType(field, reader._symbolTable[i]),
|
|
72
117
|
};
|
|
73
118
|
|
|
74
119
|
const f = await Field.create(fld);
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/qlik-qvd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Interacting with Qlik QVD files",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@saltcorn/markup": "^0.8.0",
|
|
8
8
|
"@saltcorn/data": "^0.8.0",
|
|
9
|
-
"qvdrs": "0.7.0",
|
|
10
9
|
"qvd4js":"1.0.5"
|
|
11
10
|
},
|
|
12
11
|
"author": "Tom Nielsen",
|