blob: 6d95b31194ef70687ac5add17d42fb294878dfc6 [file] [log] [blame]
brunomilitzer87111ee2021-05-18 12:50:32 +01001/*-
2 * ============LICENSE_START=======================================================
3 * ONAP CLAMP
4 * ================================================================================
5 * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END============================================
19 * ===================================================================
20 *
21 */
22
23export default function CsvToJson(rawCsvData, delimiter, internalDelimiter, csvHeaderNames, jsonKeyNames, mandatory) {
24
25 let printDictKeys = '';
26 let result = { jsonObjArray: [], errorMessages: '' };
27
28 // Validate that all parallel arrays passed in have same number of elements;
29 // this would be a developer error.
30
31 let checkLength = csvHeaderNames.length;
32
33 if (checkLength !== jsonKeyNames.length || checkLength !== mandatory.length) {
34 result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays parameters are not the same length';
35 return result;
36 }
37
38 if (checkLength < 1) {
39 result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays have no entries';
40 return result;
41 }
42
43 // Make a nice string to print in the error case to tell user what is the
44 // required heaer row format
45
46 for (let i = 0; i < csvHeaderNames.length; ++i) {
47 if (i === 0) {
48 printDictKeys = csvHeaderNames[i];
49 } else {
50 printDictKeys += ',' + csvHeaderNames[i];
51 }
52 }
53
54 let dictElems = rawCsvData.split('\n');
55 let numColumns = 0;
56 let filteredDictElems = [];
57
58 // The task of the following loop is to convert raw CSV rows into easily parseable
59 // and streamlined versions of the rows with an internalDelimiter replacing the standard
60 // comma; it is presumed (and checked) that the internalDelimiter cannot exist as a valid
61 // sequence of characters in the user's data.
62
63 // This conversion process also strips leading and trailing whitespace from each row,
64 // discards empty rows, correctly interprets and removes all double quotes that programs like
65 // Excel use to support user columns that contain special characters, most notably, the comma
66 // delimiter. A double-quote that is contained within a double-quoted column value
67 // must appear in this raw data as a sequence of two double quotes. Furthermore, any column
68 // value in the raw CSV data that does not contain a delimiter may or may not be enclosed in
69 // double quotes. It is the Excel convention to not use double qoutes unless necessary, and
70 // there is no reasonable way to tell Excel to surround every column value with double quotes.
71 // Any files that were directly "exported" by CLAMP itself from the Managing Dictionaries
72 // capability, surround all columns with double quotes.
73
74 for (let i = 0; i < dictElems.length; i++) {
75
76 let oneRow = dictElems[i].trim();
77 let j = 0;
78 let inQuote = false
79 let nextChar = undefined;
80 let prevChar = null;
81
82
83 if (oneRow === '') {
84 continue; // Skip blank rows
85 } else if (oneRow.indexOf(internalDelimiter) !== -1) {
86 result.errorMessages += '\nRow #' + i + ' contains illegal sequence of characters (' + internalDelimiter + ')';
87 break;
88 } else {
89 nextChar = oneRow[1];
90 }
91
92 let newStr = '';
93 numColumns = 1;
94
95 // This "while loop" performs the very meticulous task of removing double quotes that
96 // are used by Excel to encase special characters as user string value data,
97 // and manages to correctly identify columns that are defined with or without
98 // double quotes and to process the comma delimiter correctly when encountered
99 // as a user value within a column. Such a column would have to be encased in
100 // double quotes; a comma found outside double quotes IS a delimiter.
101
102 while (j < oneRow.length) {
103 if (oneRow[j] === '"') {
104 if (inQuote === false) {
105 if (prevChar !== delimiter && prevChar !== null) {
106 result.errorMessages += '\nMismatched double quotes or illegal whitespace around delimiter at row #' + (i + 1) + ' near column #' + numColumns;
107 break;
108 } else {
109 inQuote = true;
110 }
111 } else {
112 if (nextChar === '"') {
113 newStr += '"';
114 ++j;
115 } else if ((nextChar !== delimiter) && (nextChar !== undefined)) {
116 result.errorMessages += '\nRow #' + (i + 1) + ' is badly formatted at column #' + numColumns + '. Perhaps an unescaped double quote.';
117 break;
118 } else if (nextChar === delimiter) {
119 ++numColumns;
120 inQuote = false;
121 newStr += internalDelimiter;
122 prevChar = delimiter;
123 j += 2;
124 nextChar = oneRow[j + 1];
125 continue;
126 } else {
127 ++numColumns;
128 inQuote = false;
129 break;
130 }
131 }
132 } else {
133 if (oneRow[j] === delimiter && inQuote === false) {
134 newStr += internalDelimiter;
135 ++numColumns;
136 } else {
137 newStr += oneRow[j];
138 }
139 }
140 prevChar = oneRow[j];
141 ++j;
142 nextChar = oneRow[j + 1]; // can result in undefined at the end
143 }
144
145 if (result.errorMessages === '' && inQuote !== false) {
146 result.errorMessages += '\nMismatched double quotes at row #' + (i + 1);
147 break;
148 } else if (result.errorMessages === '' && numColumns < jsonKeyNames.length) {
149 result.errorMessages += '\nNot enough columns (' + jsonKeyNames.length + ') at row #' + (i + 1);
150 break;
151 }
152
153 filteredDictElems.push(newStr);
154 }
155
156 if (result.errorMessages !== '') {
157 return result;
158 }
159
160 // Perform further checks on data that is now in JSON form
161 if (filteredDictElems.length < 2) {
162 result.errorMessages += '\nNot enough row data found in import file. Need at least a header row and one row of data';
163 return result;
164 }
165
166 // Now that we have something reliably parsed into sanitized columns lets run some checks
167 // and convert it all into an array of JSON objects to push to the back end if all the
168 // checks pass.
169
170 let headers = filteredDictElems[0].split(internalDelimiter);
171
172 // check that headers are included in proper order
173 for (let i = 0; i < jsonKeyNames.length; ++i) {
174 if (csvHeaderNames[i] !== headers[i]) {
175 result.errorMessages += 'Row 1 header key at column #' + (i + 1) + ' is a mismatch. Expected row header must contain at least:\n' + printDictKeys;
176 return result;
177 }
178 }
179
180 // Convert the ASCII rows of data into an array of JSON obects that omit the header
181 // row which is not sent to the back end.
182
183 for (let i = 1; i < filteredDictElems.length; i++) {
184 let data = filteredDictElems[i].split(internalDelimiter);
185 let obj = {};
186 for (let j = 0; j < data.length && j < jsonKeyNames.length; j++) {
187 let value = data[j].trim();
188 if (mandatory[j] === true && value === '') {
189 result.errorMessages += '\n' + csvHeaderNames[j] + ' at row #' + (i + 1) + ' is empty but requires a value.';
190 }
191 obj[jsonKeyNames[j]] = value;
192 }
193 result.jsonObjArray.push(obj);
194 }
195
196 if (result.errorMessages !== '') {
197 // If we have errors, return empty parse result even though some things
198 // may have parsed properly. We do not want to encourage the caller
199 // to think the data is good for use.
200 result.jsonObjArray = [];
201 }
202
203 return result;
204}