Skip to content
Snippets Groups Projects
Commit 6f3d5fdc authored by Timothee P's avatar Timothee P :sunflower:
Browse files

enforce CSV checks

parent e01cf97a
No related branches found
No related tags found
1 merge request!724REDMINE_ISSUE-19251 | Listes de valeurs pré-enr. - Vérification que l'option existe bien dans la liste ne fonctionne pas dans le cas où j'ai un string
......@@ -515,7 +515,8 @@ export default {
});
// Look for 2 decimal fields in first line of csv
// corresponding to lon and lat
if (checkLonLatValues(headers, rows) && headersCoord.length === 2) {
const hasCoordValues = checkLonLatValues(headers, rows);
if (headersCoord.length === 2 && hasCoordValues) {
this.csvError = null;
csv()
.fromString(fr.result)
......
......@@ -388,11 +388,11 @@ import { escape as _escape } from 'lodash';
import { mapActions, mapMutations, mapGetters, mapState } from 'vuex';
import { formatStringDate, transformProperties } from '@/utils';
import FeatureFetchOffsetRoute from '@/components/Feature/FeatureFetchOffsetRoute';
import ImportTask from '@/components/ImportTask';
import featureAPI from '@/services/feature-api';
import { fileConvertSizeToMo } from '@/assets/js/utils'; // TODO: refactor with above utils, those files are similar
import { fileConvertSizeToMo, determineDelimiter, parseCSV, checkLonLatValues } from '@/assets/js/utils';
const geojsonFileToImport = {
name: 'Sélectionner un fichier GeoJSON ...',
......@@ -648,107 +648,186 @@ export default {
return true;
},
/**
* Checks the validity of a CSV string. It ensures the CSV uses a recognized delimiter,
* contains 'lat' and 'lon' headers, and that these columns contain decimal values within valid ranges.
* Additionally, it verifies the consistency and presence of data in the CSV, and that the types of values are valid.
*
* @param {string} csvString - The CSV content in string format.
* @returns {boolean|Promise<boolean>} Returns a boolean or a Promise resolving to a boolean,
* indicating the validity of the CSV.
*/
async checkCsvValidity(csvString) {
this.importError = '';
// Find csvString delimiter
const commaDelimited = csvString.split('\n')[0].includes(',');
const semicolonDelimited = csvString.split('\n')[0].includes(';');
const delimiter = commaDelimited && !semicolonDelimited ? ',' : semicolonDelimited ? ';' : false;
if ((commaDelimited && semicolonDelimited) || !delimiter) {
// Determine the delimiter of the CSV
const delimiter = determineDelimiter(csvString);
if (!delimiter) {
this.importError = `Le fichier ${this.csvFileToImport.name} n'est pas formaté correctement`;
return false;
}
// Check if file contains 'lat' and 'long' fields
const headersLine =
csvString
.split('\n')[0]
.replace(/(\r\n|\n|\r)/gm, '')
.split(delimiter)
.filter(el => {
return el === 'lat' || el === 'lon';
});
if (headersLine.length !== 2) {
this.importError = 'Le fichier ne semble pas contenir de champs de coordonnées.';
// Parse the CSV string into rows
const rows = parseCSV(csvString, delimiter);
// Extract headers and check for required fields 'lat' and 'lon'
const headers = rows.shift();
if (!headers.includes('lat') || !headers.includes('lon')) {
this.importError = 'Les champs obligatoires "lat" et "lon" sont absents.';
return false;
}
// Ensure there are data rows after the headers
if (rows.length === 0) {
this.importError = 'Aucune donnée trouvée après les en-têtes.';
return false;
}
const sampleLine =
csvString
.split('\n')[1]
.split(delimiter)
.map(el => {
return !isNaN(el) && el.indexOf('.') != -1;
})
.filter(Boolean);
if (sampleLine.length > 1 && headersLine.length === 2) {
const features = await csv().fromString(csvString);
return this.isValidTypes({ features });
} else {
// Ensure that each row has the same number of columns as the headers
if (rows.some(row => row.length !== headers.length)) {
this.importError = 'Incohérence dans le nombre de colonnes par ligne.';
return false;
}
// Verify the presence and validity of coordinate values
const hasCoordValues = checkLonLatValues(headers, rows);
if (!hasCoordValues) {
this.importError = 'Les valeurs de "lon" et "lat" ne sont pas valides.';
return false;
}
// Convert the CSV string to a JSON object for further processing
const jsonFromCsv = await csv().fromString(csvString);
// Validate the types of values in the JSON object
const validity = await this.isValidTypes({ features: jsonFromCsv });
return validity;
},
onGeojsonFileChange(e) {
/**
* Handles the change event for GeoJSON file input. This function is triggered when a user selects a file.
* It reads the file, checks its validity if it's not too large, and updates the component state accordingly.
*
* @param {Event} e - The event triggered by file input change.
*/
async onGeojsonFileChange(e) {
// Start loading process
this.loadingImportFile = true;
this.csvFileToImport = csvFileToImport; // empty csv file to avoid sending it instead of geojson file
// Clear any previously selected CSV file to avoid confusion
this.csvFileToImport = csvFileToImport;
// Retrieve the files from the event
const files = e.target.files || e.dataTransfer.files;
// If no file is selected, stop the loading process and return
if (!files.length) {
this.loadingImportFile = false;
return;
}
const reader = new FileReader();
reader.addEventListener('load', (e) => {
// bypass json check for files larger then 10 Mo
/**
* Asynchronously processes the content of the file.
* Checks the validity of the GeoJSON file if it's smaller than a certain size.
* Updates the state with the GeoJSON file if it's valid.
*
* @param {string} fileContent - The content of the file read by FileReader.
*/
const processFile = async (fileContent) => {
let jsonValidity;
// Check the file size and determine the GeoJSON validity
if (parseFloat(fileConvertSizeToMo(files[0].size)) <= 10) {
// If the file is smaller than 10 Mo, check its validity
try {
jsonValidity = this.isValidTypes(JSON.parse(e.target.result));
} catch(e) {
this.DISPLAY_MESSAGE({ comment: e, level: 'negative' });
const json = JSON.parse(fileContent);
jsonValidity = await this.isValidTypes(json);
} catch (error) {
this.DISPLAY_MESSAGE({ comment: error, level: 'negative' });
jsonValidity = false;
}
} else {
// Assume validity for larger files
jsonValidity = true;
}
// If the GeoJSON is valid, update the component state with the file
if (jsonValidity) {
this.geojsonFileToImport = files[0]; // todo : remove this value from state as it stored (first attempt didn't work)
this.SET_FILE_TO_IMPORT(
this.geojsonFileToImport
);
this.geojsonFileToImport = files[0]; // TODO: Remove this value from state as it is stored (first attempt didn't work)
this.SET_FILE_TO_IMPORT(this.geojsonFileToImport);
}
// Stop the loading process
this.loadingImportFile = false;
});
};
// Setup the load event listener for FileReader
reader.addEventListener('load', (e) => processFile(e.target.result));
// Read the text from the selected file
reader.readAsText(files[0]);
},
onCsvFileChange(e) {
/**
* Handles the change event for CSV file input. This function is triggered when a user selects a file.
* It reads the file, checks its validity if it's not too large, and updates the component state accordingly.
*
* @param {Event} e - The event triggered by file input change.
*/
async onCsvFileChange(e) {
// Start loading process
this.loadingImportFile = true;
this.geojsonFileToImport = geojsonFileToImport; // empty geojson file to avoid sending it instead of csv file
// Clear any previously selected geojson file to avoid confusion
this.geojsonFileToImport = geojsonFileToImport;
// Retrieve the files from the event
const files = e.target.files || e.dataTransfer.files;
// If no file is selected, stop the loading process and return
if (!files.length) {
this.loadingImportFile = false;
return;
}
// Create a new FileReader to read the selected file
const reader = new FileReader();
reader.addEventListener('load', (e) => {
// bypass csv check for files larger then 10 Mo
/**
* Asynchronously processes the content of the file.
* Checks the validity of the CSV file if it's smaller than a certain size.
* Updates the state with the CSV file if it's valid.
*
* @param {string} fileContent - The content of the file read by FileReader.
*/
const processFile = async (fileContent) => {
let csvValidity;
// Check the file size and determine the CSV validity
if (parseFloat(fileConvertSizeToMo(files[0].size)) <= 10) {
csvValidity = this.checkCsvValidity(e.target.result);
// If the file is smaller than 10 Mo, check its validity
csvValidity = await this.checkCsvValidity(fileContent);
} else {
// Assume validity for larger files
csvValidity = true;
}
// If the CSV is valid, update the component state with the file
if (csvValidity) {
this.csvFileToImport = files[0]; // todo : remove this value from state as it stored (first attempt didn't work)
this.SET_FILE_TO_IMPORT(
this.csvFileToImport
);
this.csvFileToImport = files[0]; // TODO: Remove this value from state as it is stored (first attempt didn't work)
this.SET_FILE_TO_IMPORT(this.csvFileToImport);
}
// Stop the loading process
this.loadingImportFile = false;
});
};
// Setup the load event listener for FileReader
reader.addEventListener('load', (e) => processFile(e.target.result));
// Read the text from the selected file
reader.readAsText(files[0]);
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment