From c746f26abdf0858cf573e763ca5eb1253fd25dee Mon Sep 17 00:00:00 2001 From: CAPEL Maxime <83071634+fortyup@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:49:19 +0100 Subject: [PATCH 1/5] Restrict to allowed extensions import --- src/main/kotlin/main/Main.kt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index 8268642..e28bbab 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -93,24 +93,9 @@ fun app() { glossairePage( onAjouterMotClick = { currentPage.value = "formulaire" }, onImporterClick = { - val fileDialog = FileDialog(Frame(), "Select a file", FileDialog.LOAD) - fileDialog.file = "Untitled.csv" // Initial file name - fileDialog.isMultipleMode = false // To enable selecting only one file - - fileDialog.setFile("*.csv") - fileDialog.isVisible = true - - val selectedFile = fileDialog.file - val selectedDirectory = fileDialog.directory - - if (selectedFile != null) { - val filePath = selectedDirectory + selectedFile - println("Opening: $filePath") - + selectFile(setOf("csv")) { filePath -> + println("Importing file: $filePath") importCSV(filePath) - - } else { - println("Open command cancelled by user.") } }, onExporterClick = { From 96999dff8b55e71f5415d0a95122deeb3a7c8df9 Mon Sep 17 00:00:00 2001 From: CAPEL Maxime <83071634+fortyup@users.noreply.github.com> Date: Thu, 7 Dec 2023 10:52:15 +0100 Subject: [PATCH 2/5] Add check for existing word in glossary --- src/main/kotlin/main/Main.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index e28bbab..50401d9 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -564,8 +564,18 @@ fun sauvegarderDonneesDansFichier(listeMots: List) { fun ajouterMotAuGlossaire(nouveauMot: Mot) { val listeMots = chargerDonneesDepuisFichier().toMutableList() + + // Vérifier si le mot existe déjà dans le glossaire + if (listeMots.any { it.nom.equals(nouveauMot.nom, ignoreCase = true) }) { + println("Le mot '${nouveauMot.nom}' existe déjà dans le glossaire. Ajout annulé.") + return + } + + // Ajouter le nouveau mot seulement s'il n'existe pas déjà listeMots.add(nouveauMot) sauvegarderDonneesDansFichier(listeMots) + + println("Mot ajouté avec succès : ${nouveauMot.nom}") } From 7aa1bb2759c50b9db6ec20a28def90c8c2c3ce73 Mon Sep 17 00:00:00 2001 From: CAPEL Maxime <83071634+fortyup@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:09:49 +0100 Subject: [PATCH 3/5] 'Mot' and 'Contexte principal' are now required --- src/main/kotlin/main/Main.kt | 98 +++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index 50401d9..ca66af5 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -1,29 +1,27 @@ package main import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.foundation.layout.* import androidx.compose.material.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close -import java.awt.FileDialog -import java.awt.Frame -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.awt.FileDialog +import java.awt.Frame import java.io.File import java.io.FileWriter import java.io.IOException -import kotlinx.serialization.encodeToString val customRedColor = Color(0xFFB70D1B) @@ -56,15 +54,18 @@ fun homePage( Text("Glossaire") } - Button(onClick = onCodeAVerifierClick, + Button( + onClick = onCodeAVerifierClick, colors = ButtonDefaults.buttonColors( - backgroundColor = customRedColor, - contentColor = Color.White - ) ) { + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { Text("Code à Vérifier") } - Button(onClick = { /* Action de Comparer */ }, + Button( + onClick = { /* Action de Comparer */ }, colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White @@ -89,6 +90,7 @@ fun app() { onCodeAVerifierClick = { currentPage.value = "choixLangage" } ) } + "glossaire" -> { glossairePage( onAjouterMotClick = { currentPage.value = "formulaire" }, @@ -115,9 +117,11 @@ fun app() { onRetourClick = { currentPage.value = "accueil" } ) } + "formulaire" -> { formulairePage(onAnnulerClick = { currentPage.value = "glossaire" }) } + "choixLangage" -> { val pythonExtensions = setOf("py") val javaExtensions = setOf("java") @@ -127,7 +131,8 @@ fun app() { onPythonClick = { selectFile(pythonExtensions) { filePath -> println("Python file selected: $filePath") // Change by parser functions - } }, + } + }, onJavaClick = { selectFile(javaExtensions) { filePath -> println("Java file selected: $filePath") // Change by parser functions @@ -152,7 +157,6 @@ fun selectFile(extensions: Set, onFileSelected: (String) -> Unit) { fileDialog.isVisible = true - val selectedFile = fileDialog.file val selectedDirectory = fileDialog.directory @@ -233,7 +237,6 @@ fun importCSV(cheminFichier: String) { } - fun verifierChampsRequis(mot: Map): Boolean { //Vérifier que les headers sont égales aux champs requis val champsRequis = listOf( @@ -299,7 +302,8 @@ fun choixLangagePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Java") } @@ -308,7 +312,8 @@ fun choixLangagePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("JavaScript") } } @@ -324,7 +329,8 @@ fun choixLangagePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Retour") } } @@ -361,7 +367,8 @@ fun glossairePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Ajouter un mot") } @@ -370,7 +377,8 @@ fun glossairePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Importer un fichier CSV") } @@ -379,7 +387,8 @@ fun glossairePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Exporter un fichier CSV") } @@ -396,7 +405,8 @@ fun glossairePage( colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White - )) { + ) + ) { Text("Retour") } } @@ -413,17 +423,13 @@ fun formulairePage(onAnnulerClick: () -> Unit) { verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text(text = "Nouveau contexte métier", style = MaterialTheme.typography.h5) + Text(text = "Nouveau mot", style = MaterialTheme.typography.h5) val nom = remember { mutableStateOf("") } TextField( value = nom.value, onValueChange = { nom.value = it }, - label = { Text("Mot") }, - colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want - ) + label = { Text("Mot *") }, ) val description = remember { mutableStateOf("") } @@ -441,7 +447,7 @@ fun formulairePage(onAnnulerClick: () -> Unit) { TextField( value = contextePrincipal.value, onValueChange = { contextePrincipal.value = it }, - label = { Text("Contexte principal") }, + label = { Text("Contexte principal *") }, colors = TextFieldDefaults.textFieldColors( focusedIndicatorColor = customRedColor, // Change this to the color you want unfocusedIndicatorColor = Color.Gray // Change this to the color you want @@ -509,19 +515,31 @@ fun formulairePage(onAnnulerClick: () -> Unit) { Button( onClick = { // Validation du formulaire + if (nom.value.isBlank() || contextePrincipal.value.isBlank()) { + println("Les champs 'Mot' et 'Contexte principal' sont obligatoires.") + return@Button + } val nouveauMot = Mot( - nom=nom.value, - description=description.value, - contextePrincipal=contextePrincipal.value, - contexte2=contexte2.value, - lieA=lieA.value, - synonyme=synonyme.value, - antonyme=antonyme.value + nom = nom.value, + description = description.value, + contextePrincipal = contextePrincipal.value, + contexte2 = contexte2.value, + lieA = lieA.value, + synonyme = synonyme.value, + antonyme = antonyme.value ) ajouterMotAuGlossaire(nouveauMot) + // Réinitialiser les champs après l'ajout + nom.value = "" + description.value = "" + contextePrincipal.value = "" + contexte2.value = "" + lieA.value = "" + synonyme.value = "" + antonyme.value = "" }, colors = ButtonDefaults.buttonColors( From 45a9304c4102bd12152b13e753f77992335c16eb Mon Sep 17 00:00:00 2001 From: ByrmGkcn Date: Thu, 7 Dec 2023 13:47:30 +0100 Subject: [PATCH 4/5] ajout de l'import en XLSX --- build.gradle.kts | 2 + src/main/kotlin/main/Main.kt | 129 ++++++++++++++++++++++++++++++----- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 49fdc59..6dddd18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,8 @@ dependencies { implementation("io.ktor:ktor-html-builder:1.6.3") implementation("junit:junit:4.13.1") testImplementation("junit:junit:4.13.1") + implementation("org.apache.poi:poi:5.0.0") + implementation("org.apache.poi:poi-ooxml:5.0.0") } diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index ca66af5..495b26b 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -22,6 +22,10 @@ import java.awt.Frame import java.io.File import java.io.FileWriter import java.io.IOException +import org.apache.poi.ss.usermodel.CellType +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.io.FileInputStream val customRedColor = Color(0xFFB70D1B) @@ -95,9 +99,9 @@ fun app() { glossairePage( onAjouterMotClick = { currentPage.value = "formulaire" }, onImporterClick = { - selectFile(setOf("csv")) { filePath -> + selectFile(setOf("csv", "xlsx")) { filePath -> println("Importing file: $filePath") - importCSV(filePath) + importFile(filePath) } }, onExporterClick = { @@ -196,7 +200,23 @@ fun exportToCSV(csvFilePath: String) { } } -fun importCSV(cheminFichier: String) { +fun importFile(cheminFichier: String) { + val fileExtension = File(cheminFichier).extension.lowercase() + + when { + fileExtension == "csv" -> { + importCSVFile(cheminFichier) + } + fileExtension == "xlsx" -> { + importXLSXFile(cheminFichier) + } + else -> { + println("Unsupported file format.") + } + } +} + +fun importCSVFile(cheminFichier: String) { val file = File(cheminFichier).readText(Charsets.UTF_8) val lines = file.split("\n") @@ -236,6 +256,63 @@ fun importCSV(cheminFichier: String) { } } +private fun importXLSXFile(cheminFichier: String) { + val workbook: Workbook + try { + FileInputStream(cheminFichier).use { fileInputStream -> + workbook = XSSFWorkbook(fileInputStream) + } + + val sheet = workbook.getSheetAt(0) // Assuming the data is in the first sheet + + val headerRow = sheet.getRow(0) + val header = mutableListOf() + for (i in 0 until headerRow.lastCellNum) { + header.add(headerRow.getCell(i).stringCellValue) + } + + val dataLines = mutableListOf>() + + for (i in 1 until sheet.physicalNumberOfRows) { + val currentRow = sheet.getRow(i) + val data = mutableMapOf() + + for (j in 0 until currentRow.lastCellNum) { + val cell = currentRow.getCell(j) + val cellValue = when (cell.cellType) { + CellType.STRING -> cell.stringCellValue + CellType.NUMERIC -> cell.numericCellValue.toString() + else -> "" + } + data[header[j]] = cellValue + } + + dataLines.add(data) + } + + dataLines.forEach { line -> + // Check if the line is not empty or blank + if (line.isNotEmpty()) { + // Process each line as needed + val nouveauMot = Mot( + nom = line["Mot"] ?: "", + description = line["Description"] ?: "", + contextePrincipal = line["Contexte principal"] ?: "", + contexte2 = line["Contexte 2"] ?: "", + lieA = line["Lié à"] ?: "", + synonyme = line["Synonyme"] ?: "", + antonyme = line["Antonyme"] ?: "" + ) + + if (nouveauMot.nom.isNotBlank() && verifierChampsRequis(line)) { + ajouterMotAuGlossaire(nouveauMot) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } +} fun verifierChampsRequis(mot: Map): Boolean { //Vérifier que les headers sont égales aux champs requis @@ -379,7 +456,7 @@ fun glossairePage( contentColor = Color.White ) ) { - Text("Importer un fichier CSV") + Text("Importer un fichier CSV ou XLSX") } Button( @@ -413,9 +490,10 @@ fun glossairePage( } - @Composable fun formulairePage(onAnnulerClick: () -> Unit) { + // State to track whether to show the snackbar + val snackbarVisibleState = remember { mutableStateOf(false) } MaterialTheme { Column( @@ -438,8 +516,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { description.value = it }, label = { Text("Description") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -449,8 +527,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { contextePrincipal.value = it }, label = { Text("Contexte principal *") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -460,8 +538,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { contexte2.value = it }, label = { Text("Contexte 2") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -471,8 +549,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { lieA.value = it }, label = { Text("Lié à") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -482,8 +560,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { synonyme.value = it }, label = { Text("Synonyme") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -493,8 +571,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onValueChange = { antonyme.value = it }, label = { Text("Antonyme") }, colors = TextFieldDefaults.textFieldColors( - focusedIndicatorColor = customRedColor, // Change this to the color you want - unfocusedIndicatorColor = Color.Gray // Change this to the color you want + focusedIndicatorColor = customRedColor, + unfocusedIndicatorColor = Color.Gray ) ) @@ -516,7 +594,8 @@ fun formulairePage(onAnnulerClick: () -> Unit) { onClick = { // Validation du formulaire if (nom.value.isBlank() || contextePrincipal.value.isBlank()) { - println("Les champs 'Mot' et 'Contexte principal' sont obligatoires.") + // Show the snackbar when validation fails + snackbarVisibleState.value = true return@Button } @@ -552,10 +631,24 @@ fun formulairePage(onAnnulerClick: () -> Unit) { } } + // Show the snackbar when the state is true + if (snackbarVisibleState.value) { + Snackbar( + modifier = Modifier.padding(16.dp), + action = { + Button(onClick = { snackbarVisibleState.value = false }) { + Text("OK") + } + } + ) { + Text("Les champs 'Mot' et 'Contexte principal' sont obligatoires.") + } + } } } } + fun chargerDonneesDepuisFichier(): List { //if file is empty, return empty list if (!File("glossaire.json").exists() || File("glossaire.json").length() == 0L) { From 1430821849c8ebdf9a0bcb2b6cd4b4654a8d35a0 Mon Sep 17 00:00:00 2001 From: ByrmGkcn Date: Thu, 7 Dec 2023 14:06:15 +0100 Subject: [PATCH 5/5] the json is prettier --- src/main/kotlin/main/Main.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index 495b26b..5d03c4f 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -663,13 +663,14 @@ fun chargerDonneesDepuisFichier(): List { } } +private val json = Json { prettyPrint = true } + fun sauvegarderDonneesDansFichier(listeMots: List) { try { - val content = Json.encodeToString(listeMots) + val content = json.encodeToString(listeMots) File("glossaire.json").writeText(content) } catch (e: IOException) { e.printStackTrace() - // Handle the exception as needed } }