| /*- |
| * ============LICENSE_START======================================================= |
| * ONAP CLAMP |
| * ================================================================================ |
| * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. |
| * ================================================================================ |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * ============LICENSE_END============================================ |
| * =================================================================== |
| * |
| */ |
| |
| export default function CsvToJson(rawCsvData, delimiter, internalDelimiter, csvHeaderNames, jsonKeyNames, mandatory) { |
| |
| let printDictKeys = ''; |
| let result = { jsonObjArray: [], errorMessages: '' }; |
| |
| // Validate that all parallel arrays passed in have same number of elements; |
| // this would be a developer error. |
| |
| let checkLength = csvHeaderNames.length; |
| |
| if (checkLength !== jsonKeyNames.length || checkLength !== mandatory.length) { |
| result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays parameters are not the same length'; |
| return result; |
| } |
| |
| if (checkLength < 1) { |
| result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays have no entries'; |
| return result; |
| } |
| |
| // Make a nice string to print in the error case to tell user what is the |
| // required heaer row format |
| |
| for (let i=0; i < csvHeaderNames.length; ++i) { |
| if (i === 0) { |
| printDictKeys = csvHeaderNames[i]; |
| } else { |
| printDictKeys += ',' + csvHeaderNames[i]; |
| } |
| } |
| |
| let dictElems = rawCsvData.split('\n'); |
| let numColumns = 0; |
| let filteredDictElems = []; |
| |
| // The task of the following loop is to convert raw CSV rows into easily parseable |
| // and streamlined versions of the rows with an internalDelimiter replacing the standard |
| // comma; it is presumed (and checked) that the internalDelimiter cannot exist as a valid |
| // sequence of characters in the user's data. |
| |
| // This conversion process also strips leading and trailing whitespace from each row, |
| // discards empty rows, correctly interprets and removes all double quotes that programs like |
| // Excel use to support user columns that contain special characters, most notably, the comma |
| // delimiter. A double-quote that is contained within a double-quoted column value |
| // must appear in this raw data as a sequence of two double quotes. Furthermore, any column |
| // value in the raw CSV data that does not contain a delimiter may or may not be enclosed in |
| // double quotes. It is the Excel convention to not use double qoutes unless necessary, and |
| // there is no reasonable way to tell Excel to surround every column value with double quotes. |
| // Any files that were directly "exported" by CLAMP itself from the Managing Dictionaries |
| // capability, surround all columns with double quotes. |
| |
| for (let i = 0; i < dictElems.length; i++) { |
| |
| let oneRow = dictElems[i].trim(); |
| let j = 0; |
| let inQuote = false |
| let nextChar = undefined; |
| let prevChar = null; |
| |
| |
| if (oneRow === '') { |
| continue; // Skip blank rows |
| } else if (oneRow.indexOf(internalDelimiter) !== -1) { |
| result.errorMessages += '\nRow #' + i + ' contains illegal sequence of characters (' + internalDelimiter + ')'; |
| break; |
| } else { |
| nextChar = oneRow[1]; |
| } |
| |
| let newStr = ''; |
| numColumns = 1; |
| |
| // This "while loop" performs the very meticulous task of removing double quotes that |
| // are used by Excel to encase special characters as user string value data, |
| // and manages to correctly identify columns that are defined with or without |
| // double quotes and to process the comma delimiter correctly when encountered |
| // as a user value within a column. Such a column would have to be encased in |
| // double quotes; a comma found outside double quotes IS a delimiter. |
| |
| while (j < oneRow.length) { |
| if (oneRow[j] === '"') { |
| if (inQuote === false) { |
| if (prevChar !== delimiter && prevChar !== null) { |
| result.errorMessages += '\nMismatched double quotes or illegal whitespace around delimiter at row #' + (i + 1) + ' near column #' + numColumns; |
| break; |
| } else { |
| inQuote = true; |
| } |
| } else { |
| if (nextChar === '"') { |
| newStr += '"'; |
| ++j; |
| } else if ((nextChar !== delimiter) && (nextChar !== undefined)) { |
| result.errorMessages += '\nRow #' + (i + 1) + ' is badly formatted at column #' + numColumns + '. Perhaps an unescaped double quote.'; |
| break; |
| } else if (nextChar === delimiter) { |
| ++numColumns; |
| inQuote = false; |
| newStr += internalDelimiter; |
| prevChar = delimiter; |
| j += 2; |
| nextChar = oneRow[j+1]; |
| continue; |
| } else { |
| ++numColumns; |
| inQuote = false; |
| break; |
| } |
| } |
| } else { |
| if (oneRow[j] === delimiter && inQuote === false) { |
| newStr += internalDelimiter; |
| ++numColumns; |
| } else { |
| newStr += oneRow[j]; |
| } |
| } |
| prevChar = oneRow[j]; |
| ++j; |
| nextChar = oneRow[j+1]; // can result in undefined at the end |
| } |
| |
| if (result.errorMessages === '' && inQuote !== false) { |
| result.errorMessages += '\nMismatched double quotes at row #' + (i + 1); |
| break; |
| } else if (result.errorMessages === '' && numColumns < jsonKeyNames.length) { |
| result.errorMessages += '\nNot enough columns (' + jsonKeyNames.length + ') at row #' + (i + 1); |
| break; |
| } |
| |
| filteredDictElems.push(newStr); |
| } |
| |
| if (result.errorMessages !== '') { |
| return result; |
| } |
| |
| // Perform further checks on data that is now in JSON form |
| if (filteredDictElems.length < 2) { |
| result.errorMessages += '\nNot enough row data found in import file. Need at least a header row and one row of data'; |
| return result; |
| } |
| |
| // Now that we have something reliably parsed into sanitized columns lets run some checks |
| // and convert it all into an array of JSON objects to push to the back end if all the |
| // checks pass. |
| |
| let headers = filteredDictElems[0].split(internalDelimiter); |
| |
| // check that headers are included in proper order |
| for (let i=0; i < jsonKeyNames.length; ++i) { |
| if (csvHeaderNames[i] !== headers[i]) { |
| result.errorMessages += 'Row 1 header key at column #' + (i + 1) + ' is a mismatch. Expected row header must contain at least:\n' + printDictKeys; |
| return result; |
| } |
| } |
| |
| // Convert the ASCII rows of data into an array of JSON obects that omit the header |
| // row which is not sent to the back end. |
| |
| for (let i = 1; i < filteredDictElems.length; i++) { |
| let data = filteredDictElems[i].split(internalDelimiter); |
| let obj = {}; |
| for (let j = 0; j < data.length && j < jsonKeyNames.length; j++) { |
| let value = data[j].trim(); |
| if (mandatory[j] === true && value === '') { |
| result.errorMessages += '\n' + csvHeaderNames[j] + ' at row #' + (i+1) + ' is empty but requires a value.'; |
| } |
| obj[jsonKeyNames[j]] = value; |
| } |
| result.jsonObjArray.push(obj); |
| } |
| |
| if (result.errorMessages !== '') { |
| // If we have errors, return empty parse result even though some things |
| // may have parsed properly. We do not want to encourage the caller |
| // to think the data is good for use. |
| result.jsonObjArray = []; |
| } |
| |
| return result; |
| } |