@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.
Files changed (2) hide show
  1. package/index.js +53 -8
  2. 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
- const numberFormatToType = (nf) => {
23
- if (nf.Type === "REAL") return { type: "Float" };
24
- if (nf.Type === "INTEGER") return { type: "Integer" };
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
- throw new Error("Unknown NumberFormat: " + JSON.stringify(nf));
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
- const fields =
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 (const field of fields) {
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
- ...numberFormatToType(field.NumberFormat),
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.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",