From 2959070c731baeb64d3ae1f70595a6a3dd477821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Boull=C3=A9?= Date: Fri, 11 Oct 2024 11:14:09 +0200 Subject: [PATCH] Implement reading and in-depth checks of input json parameter file Mise en place de tests complet pour la verification des fichiers de parametre json en entree de la ligne de commande UIObject - ParseMainParameters - nouvelle option -j pour les fichiers de parametrage json - nouvelle option -O, comme -o mais sans rejouer les comandes - CheckCommandLineOptions: ajout des contraintes entre les options CommandFile - LoadJsonParameters: lecture et verification du fichier de parametres json en entree - CheckVariableName - IsByteVariableName - ToByteVariableName, ToStandardVariableName - CheckStringValue - ... CommandFile - nMaxVariableNameLength = 100: taille max des noms de variable - lMaxInputParameterFileSize lMB: taille max des fichier de commande - nMaxStringValueLength = 300: longueur max des avleurs de type chaine de caracteres KWTextService: service d'encodege et decodage au format base64 - Base64StringToBytes - BytesToBase64String Tests unitaires - ajout du test TextService::Test dans test/UnitTests/Norm_test.cpp, avec la reference base_TestService.txt LearningTestTool - extension de kht_test tester la gestion des fichiers de parametre json en entree - uniquement s'il existe un fichier test.json associe au test.prm dans le repertoire de test courant - ajout d'une famille de test LearningTest/TestKhiops/JsonParameters - kht_export.py: analyse heuristique du fichier de parametre json pour extraire les datasets utilises Tests intensifs dans LearningTest\TestKhiops\JsonParameters --- .../DTForest/DTDecisionTreeCreationTask.cpp | 1 - src/Learning/MODL/MODL.cpp | 1 - src/Norm/base/CommandFile.cpp | 555 +++++++++++++++++- src/Norm/base/CommandFile.h | 72 ++- src/Norm/base/JsonObject.cpp | 112 ++-- src/Norm/base/JsonObject.h | 58 +- src/Norm/base/JsonYac.cpp | 1 - src/Norm/base/JsonYac.yac | 1 - src/Norm/base/TextService.cpp | 244 +++++++- src/Norm/base/TextService.h | 19 +- src/Norm/base/UIObject.cpp | 113 +++- src/Norm/base/UserInterface.h | 3 + test/LearningTestTool/py/_kht_constants.py | 1 + test/LearningTestTool/py/_kht_families.py | 1 + test/LearningTestTool/py/kht_export.py | 67 ++- test/LearningTestTool/py/kht_test.py | 3 + test/UnitTests/Norm/Nom_test.cpp | 2 + .../Norm/results.ref/base_TextService.txt | 283 +++++++++ 18 files changed, 1420 insertions(+), 117 deletions(-) create mode 100644 test/UnitTests/Norm/results.ref/base_TextService.txt diff --git a/src/Learning/DTForest/DTDecisionTreeCreationTask.cpp b/src/Learning/DTForest/DTDecisionTreeCreationTask.cpp index 93d24d73..22454a22 100644 --- a/src/Learning/DTForest/DTDecisionTreeCreationTask.cpp +++ b/src/Learning/DTForest/DTDecisionTreeCreationTask.cpp @@ -1166,7 +1166,6 @@ boolean DTDecisionTreeCreationTask::SlaveProcess() boolean DTDecisionTreeCreationTask::ComputeResourceRequirements() { - int nMaxSlaveProcessNumber = 0; longint lSharedMemory = 0; longint lMasterMemory = 0; longint lBiggestTreeMemory = 0; diff --git a/src/Learning/MODL/MODL.cpp b/src/Learning/MODL/MODL.cpp index 9d2dcb6f..e76e8fed 100644 --- a/src/Learning/MODL/MODL.cpp +++ b/src/Learning/MODL/MODL.cpp @@ -39,7 +39,6 @@ int main(int argc, char** argv) // Choix du repertoire de lancement pour le debugage sous Windows (a commenter apres fin du debug) // SetWindowsDebugDir("Standard", "IrisLight"); // SetWindowsDebugDir("Standard", "IrisU2D"); - // SetWindowsDebugDir("MultiTables", "DataPathCheck"); // Parametrage des logs memoires depuis les variables d'environnement, pris en compte dans KWLearningProject // KhiopsMemStatsLogFileName, KhiopsMemStatsLogFrequency, KhiopsMemStatsLogToCollect diff --git a/src/Norm/base/CommandFile.cpp b/src/Norm/base/CommandFile.cpp index 870926b1..228eeab2 100644 --- a/src/Norm/base/CommandFile.cpp +++ b/src/Norm/base/CommandFile.cpp @@ -73,19 +73,19 @@ void CommandFile::AddInputSearchReplaceValues(const ALString& sSearchValue, cons svInputCommandReplaceValues.Add(sReplaceValue); } -int CommandFile::GetInputSearchReplaceValueNumber() +int CommandFile::GetInputSearchReplaceValueNumber() const { assert(svInputCommandSearchValues.GetSize() == svInputCommandReplaceValues.GetSize()); return svInputCommandSearchValues.GetSize(); } -const ALString& CommandFile::GetInputSearchValueAt(int nIndex) +const ALString& CommandFile::GetInputSearchValueAt(int nIndex) const { require(0 <= nIndex and nIndex < GetInputSearchReplaceValueNumber()); return svInputCommandSearchValues.GetAt(nIndex); } -const ALString& CommandFile::GetInputReplaceValueAt(int nIndex) +const ALString& CommandFile::GetInputReplaceValueAt(int nIndex) const { require(0 <= nIndex and nIndex < GetInputSearchReplaceValueNumber()); return svInputCommandReplaceValues.GetAt(nIndex); @@ -111,6 +111,20 @@ const ALString& CommandFile::GetInputParameterFileName() const boolean CommandFile::Check() const { + boolean bOk; + + // Le parametrage des search/replace ne peut pas etre utilise s'il n'y a pas de fichier de commande en entree + if (GetInputSearchReplaceValueNumber() > 0 and GetInputCommandFileName() == "") + bOk = false; + + // Le fichier de parametre json ne peut pas etre utilise s'il n'y a pas de fichier de commande en entree + if (GetInputParameterFileName() != "" and GetInputCommandFileName() == "") + bOk = false; + + // Le fichier de parametre json et le parametrage des search/replace sont exclusifs + if (GetInputParameterFileName() != "" and GetInputSearchReplaceValueNumber() > 0) + bOk = false; + return true; } @@ -138,9 +152,18 @@ boolean CommandFile::OpenInputCommandFile() fInputCommands = p_fopen(sLocalInputCommandFileName, "r"); if (fInputCommands == NULL) { - Global::AddError("Input command file", sInputCommandFileName, "Unable to open file"); + AddInputCommandFileError("Unable to open file"); bOk = false; } + + // Chargement des parametre json si specifie + if (bOk and GetInputParameterFileName() != "") + { + bOk = LoadJsonParameters(); + + // Message d'erreur synthetique + AddInputParameterFileError("Unable to exploit json parameters"); + } return bOk; } @@ -177,7 +200,7 @@ boolean CommandFile::OpenOutputCommandFile() fOutputCommands = p_fopen(sLocalOutputCommandFileName, "w"); if (fOutputCommands == NULL) { - Global::AddError("Output command file", sOutputCommandFileName, "Unable to open file"); + AddOutputCommandFileError("Unable to open file"); bOk = false; } return bOk; @@ -380,7 +403,500 @@ void CommandFile::WriteOutputCommand(const ALString& sIdentifierPath, const ALSt } } -const ALString CommandFile::ProcessSearchReplaceCommand(const ALString& sInputCommand) +boolean CommandFile::LoadJsonParameters() +{ + boolean bOk = true; + longint lInputParameterFileSize; + JsonMember* jsonMember; + JsonArray* jsonArray; + JsonValue* jsonValue; + JsonObject* firstJsonSubObject; + JsonObject* jsonSubObject; + JsonMember* jsonSubObjectMember; + JsonMember* firstJsonSubObjectMember; + ALString sMessage; + boolean bIsFirstArrayObjectValid; + boolean bIsArrayObjectValid; + int i; + int j; + int k; + ALString sLabelSuffix; + ALString sTmp; + + require(GetInputParameterFileName() != ""); + + // Nettoyage prealable + jsonParameters.DeleteAll(); + + // Test sur la longueur max du fichier de parametree + lInputParameterFileSize = PLRemoteFileService::GetFileSize(GetInputParameterFileName()); + if (lInputParameterFileSize > lMaxInputParameterFileSize) + { + AddInputParameterFileError( + sTmp + "the size of the parameter file (" + LongintToHumanReadableString(lInputParameterFileSize) + + ") exceeds the limit of " + LongintToHumanReadableString(lMaxInputParameterFileSize)); + bOk = false; + } + + // Chargement du fichier json + if (bOk) + bOk = jsonParameters.ReadFile(GetInputParameterFileName()); + + // Verification de la structure, qui doit respecter l'ensemble des contraintes du parametrage json + if (bOk) + { + // Analyse des membres de l'objet principal + Global::ActivateErrorFlowControl(); + for (i = 0; i < jsonParameters.GetMemberNumber(); i++) + { + jsonMember = jsonParameters.GetMemberAt(i); + + // Verification de la cle + if (not CheckVariableName(jsonMember->GetKey(), sMessage)) + { + AddInputParameterFileError("in main json object, " + sMessage); + bOk = false; + } + // Verification une seule fois de la non-collision de la cle avec sa variante de type byte + else if (IsByteVariableName(jsonMember->GetKey()) and + jsonParameters.LookupMember(ToStandardVariableName(jsonMember->GetKey())) != NULL) + { + AddInputParameterFileError("in main json object, the \"" + + ToStandardVariableName(jsonMember->GetKey()) + + "\" key is used twice, along with its \"" + + jsonMember->GetKey() + "\" byte variant"); + bOk = false; + } + // Verification du type, au premier niveau de la structure de l'objet json + else if (jsonMember->GetValueType() != JsonObject::StringValue and + jsonMember->GetValueType() != JsonObject::NumberValue and + jsonMember->GetValueType() != JsonObject::BooleanValue and + jsonMember->GetValueType() != JsonObject::ArrayValue) + { + AddInputParameterFileError("in main json object, the " + + jsonMember->GetValue()->TypeToString() + + " type of value at " + BuildJsonPath(jsonMember, -1, NULL) + + " should be string, number, boolean or array"); + bOk = false; + } + // Verification du type string dans le cas d'une cle avec sa variante de type byte + else if (IsByteVariableName(jsonMember->GetKey()) and + jsonMember->GetValueType() != JsonObject::StringValue) + { + AddInputParameterFileError( + "in main json object, the " + jsonMember->GetValue()->TypeToString() + + " type of value at " + BuildJsonPath(jsonMember, -1, NULL) + + " should be string, as the key prefix is \"" + sByteVariablePrefix + "\""); + bOk = false; + } + // Verification de de la longueur et de l'encodage base64 de la valeur string + else if (jsonMember->GetValueType() == JsonObject::StringValue and + not CheckStringValue(jsonMember->GetStringValue()->GetString(), + IsByteVariableName(jsonMember->GetKey()), sMessage)) + { + AddInputParameterFileError("in main json object, at " + + BuildJsonPath(jsonMember, -1, NULL) + ", " + sMessage); + bOk = false; + } + + // Cas d'une valeur tableau + if (jsonMember->GetValueType() == JsonObject::ArrayValue) + { + jsonArray = jsonMember->GetArrayValue(); + + // Toutes les valeurs d'un tableau doivent etre de type objet + bIsFirstArrayObjectValid = true; + for (j = 0; j < jsonArray->GetValueNumber(); j++) + { + jsonValue = jsonArray->GetValueAt(j); + bIsArrayObjectValid = true; + + // Verification du type, au premier niveau de la structure de l'objet json + if (jsonValue->GetType() != JsonObject::ObjectValue) + { + AddInputParameterFileError("in array member, type " + + jsonValue->TypeToString() + " of value at " + + BuildJsonPath(jsonMember, j, NULL) + + " should be object"); + bOk = false; + bIsArrayObjectValid = false; + } + // Analyse de l'objet si valide + else + { + jsonSubObject = jsonValue->GetObjectValue(); + + // Toutes les valeurs du subobjet dovent etre de type simple + for (k = 0; k < jsonSubObject->GetMemberNumber(); k++) + { + jsonSubObjectMember = jsonSubObject->GetMemberAt(k); + + // Verification de la cle + if (not CheckVariableName(jsonSubObjectMember->GetKey(), + sMessage)) + { + AddInputParameterFileError( + "in object value of array at " + + BuildJsonPath(jsonMember, j, NULL) + ", " + + sMessage); + bOk = false; + bIsArrayObjectValid = false; + } + // Verification de la non-collision de la cle avec sa variante de type byte + else if (IsByteVariableName(jsonSubObjectMember->GetKey()) and + jsonSubObject->LookupMember(ToStandardVariableName( + jsonSubObjectMember->GetKey())) != NULL) + { + AddInputParameterFileError( + "in object value of array at " + + BuildJsonPath(jsonMember, j, NULL) + ", the \"" + + ToStandardVariableName( + jsonSubObjectMember->GetKey()) + + "\" key is used twice, along with its \"" + + jsonSubObjectMember->GetKey() + "\" byte variant"); + bOk = false; + } + // Verification que la cle du sous-objet n'entre pas en collision avec une cle de l'objet principal + else if (jsonParameters.LookupMember( + jsonSubObjectMember->GetKey()) != NULL or + jsonParameters.LookupMember(ToVariantVariableName( + jsonSubObjectMember->GetKey())) != NULL) + { + // Message d'erreur avec precision dans le cas d'utilisation de variantes standard ou byte differents + sLabelSuffix = ""; + if (jsonParameters.LookupMember(ToVariantVariableName( + jsonSubObjectMember->GetKey())) != NULL) + sLabelSuffix = " (in any standard or " + "byte variants)"; + AddInputParameterFileError( + "in object value of array at " + + BuildJsonPath(jsonMember, j, NULL) + ", the \"" + + jsonSubObjectMember->GetKey() + + "\" key is already used in main json " + "object" + + sLabelSuffix); + bOk = false; + bIsArrayObjectValid = false; + } + // Verification du type, au second niveau de la structure de l'objet json + else if (jsonSubObjectMember->GetValueType() != + JsonObject::StringValue and + jsonSubObjectMember->GetValueType() != + JsonObject::NumberValue) + { + AddInputParameterFileError( + "in object value of array, the " + + jsonSubObjectMember->GetValue()->TypeToString() + + " type of value at " + + BuildJsonPath(jsonMember, j, jsonSubObjectMember) + + " should be string or number"); + bIsArrayObjectValid = false; + } + // Verification du type string dans le cas d'une cle avec sa variante de type byte + else if (IsByteVariableName(jsonSubObjectMember->GetKey()) and + jsonSubObjectMember->GetValueType() != + JsonObject::StringValue) + { + AddInputParameterFileError( + "in object value of array, the " + + jsonSubObjectMember->GetValue()->TypeToString() + + " type of value at " + + BuildJsonPath(jsonMember, j, jsonSubObjectMember) + + " should be string, as the key prefix is \"" + + sByteVariablePrefix + "\""); + bOk = false; + } + // Verification de de la longueur et de l'encodage base64 de la valeur string + else if (jsonSubObjectMember->GetValueType() == + JsonObject::StringValue and + not CheckStringValue( + jsonSubObjectMember->GetStringValue()->GetString(), + IsByteVariableName(jsonSubObjectMember->GetKey()), + sMessage)) + { + AddInputParameterFileError( + "in object value of array, at " + + BuildJsonPath(jsonMember, j, jsonSubObjectMember) + + ", " + sMessage); + bOk = false; + } + } + } + if (j == 0) + bIsFirstArrayObjectValid = bIsArrayObjectValid; + + // Verification de la coherence de structure avec le premier objet du tableau + if (bIsArrayObjectValid and bIsFirstArrayObjectValid and j > 0) + { + firstJsonSubObject = jsonArray->GetValueAt(0)->GetObjectValue(); + jsonSubObject = jsonArray->GetValueAt(j)->GetObjectValue(); + + // Le nombre de membre doit etre egal + if (jsonSubObject->GetMemberNumber() != + firstJsonSubObject->GetMemberNumber()) + { + AddInputParameterFileError( + "number of members in object " + + BuildJsonPath(jsonMember, j, NULL) + " (" + + IntToString(jsonSubObject->GetMemberNumber()) + + " member) should be the same as in the the first object " + "in the array at " + + BuildJsonPath(jsonMember, 0, NULL) + " (" + + IntToString(firstJsonSubObject->GetMemberNumber()) + + " member)"); + bOk = false; + } + // Sinon, on compare les cle et type membre a membre + else + { + for (k = 0; k < jsonSubObject->GetMemberNumber(); k++) + { + jsonSubObjectMember = jsonSubObject->GetMemberAt(k); + firstJsonSubObjectMember = + firstJsonSubObject->GetMemberAt(k); + + // Verification de la coherence de la cle + if (jsonSubObjectMember->GetKey() != + firstJsonSubObjectMember->GetKey() and + jsonSubObjectMember->GetKey() != + ToVariantVariableName( + firstJsonSubObjectMember->GetKey())) + { + AddInputParameterFileError( + "key \"" + jsonSubObjectMember->GetKey() + + "\" at " + + BuildJsonPath(jsonMember, j, + jsonSubObjectMember) + + " should be \"" + + firstJsonSubObjectMember->GetKey() + + "\", as in the the first object in the " + "array"); + bOk = false; + } + // Verification de la coherence de la valeur + else if (jsonSubObjectMember->GetValueType() != + firstJsonSubObjectMember->GetValueType()) + { + AddInputParameterFileError( + "type " + + jsonSubObjectMember->GetValue() + ->TypeToString() + + " at " + + BuildJsonPath(jsonMember, j, + jsonSubObjectMember) + + " should be " + + firstJsonSubObjectMember->GetValue() + ->TypeToString() + + ", as in the the first object in the " + "array"); + bOk = false; + } + } + } + } + } + } + } + Global::DesactivateErrorFlowControl(); + } + + // Nettoyage en cas d'erreur + if (not bOk) + jsonParameters.DeleteAll(); + return bOk; +} + +boolean CommandFile::CheckVariableName(const ALString& sValue, ALString& sMessage) const +{ + boolean bOk = true; + ALString sTmp; + + // Test si non vide + sMessage = ""; + if (bOk) + { + bOk = sValue.GetLength() > 0; + if (not bOk) + sMessage = "empty key"; + } + + // Test de la longueur max + if (bOk) + { + bOk = sValue.GetLength() <= nMaxVariableNameLength; + if (not bOk) + sMessage = sTmp + "overlengthy key \"" + GetPrintableValue(sValue) + "\", with length " + + IntToString(sValue.GetLength()) + " > " + IntToString(nMaxVariableNameLength); + } + + // Test du format camelCase + if (bOk) + { + bOk = IsCamelCaseVariableName(sValue); + if (not bOk) + sMessage = "incorrect key \"" + GetPrintableValue(sValue) + "\", which should be camelCase"; + } + + ensure(not bOk or sMessage == ""); + return bOk; +} + +boolean CommandFile::IsCamelCaseVariableName(const ALString& sValue) const +{ + boolean bOk; + char c; + int i; + + // Le nom ne doit pas etre vide + bOk = sValue.GetLength() > 0; + + // Test des type de caracteres sans utiliser isalpha ou Il doit commencer par une lettre minuscule + for (i = 0; i < sValue.GetLength(); i++) + { + c = sValue.GetAt(i); + + // On doit etre en ascii + bOk = isascii(c); + + // Le premier caractere doit etre une lettre minuscule + if (bOk) + { + if (i == 0) + bOk = isalpha(c) and islower(c); + // Les autre caracteres doivent etre alpha-numeriques + else + bOk = isalnum(c); + } + + // Arret si necessaire + if (not bOk) + break; + } + return bOk; +} + +boolean CommandFile::IsByteVariableName(const ALString& sValue) const +{ + boolean bOk; + char c; + + require(IsCamelCaseVariableName(sValue)); + + // Test si on est prefixe correctement, suivi d'un caractere alphabetique en majuscule + bOk = sValue.GetLength() > sByteVariablePrefix.GetLength(); + if (bOk) + bOk = sValue.Left(sByteVariablePrefix.GetLength()) == sByteVariablePrefix; + if (bOk) + { + c = sValue.GetAt(sByteVariablePrefix.GetLength()); + bOk = isalpha(c) and isupper(c); + } + return bOk; +} + +const ALString CommandFile::ToByteVariableName(const ALString& sValue) const +{ + ALString sByteVariableName; + + require(IsCamelCaseVariableName(sValue)); + + if (IsByteVariableName(sValue)) + return sValue; + else + { + sByteVariableName = sValue; + sByteVariableName.SetAt(0, char(toupper(sByteVariableName.GetAt(0)))); + sByteVariableName = sByteVariablePrefix + sByteVariableName; + return sByteVariableName; + } +} + +const ALString CommandFile::ToStandardVariableName(const ALString& sValue) const +{ + ALString sStandardVariableName; + + require(IsCamelCaseVariableName(sValue)); + + if (IsByteVariableName(sValue)) + { + + sStandardVariableName = sValue.Right(sValue.GetLength() - sByteVariablePrefix.GetLength()); + sStandardVariableName.SetAt(0, char(tolower(sStandardVariableName.GetAt(0)))); + return sStandardVariableName; + } + else + return sValue; +} + +const ALString CommandFile::ToVariantVariableName(const ALString& sValue) const +{ + require(IsCamelCaseVariableName(sValue)); + + if (IsByteVariableName(sValue)) + return ToStandardVariableName(sValue); + else + return ToByteVariableName(sValue); +} + +boolean CommandFile::CheckStringValue(const ALString& sValue, boolean bCheckBase64Encoding, ALString& sMessage) const +{ + boolean bOk = true; + char* sBytes; + ALString sTmp; + + // Test de la longueur max + sMessage = ""; + if (bOk) + { + bOk = sValue.GetLength() <= nMaxStringValueLength; + if (not bOk) + sMessage = sTmp + "overlengthy string value \"" + GetPrintableValue(sValue) + + "\", with length " + IntToString(sValue.GetLength()) + " > " + + IntToString(nMaxStringValueLength); + } + + // Test de l'encodage base64 + if (bOk and bCheckBase64Encoding) + { + // Reherche d'un buffer de caracteres + sBytes = StandardGetBuffer(); + assert(nMaxStringValueLength <= BUFFER_LENGTH); + + // Test de l'encodage base64 + bOk = TextService::Base64StringToBytes(sValue, sBytes) != -1; + if (not bOk) + sMessage = "incorrect string value \"" + GetPrintableValue(sValue) + + "\", which should be encoding using base64"; + } + + ensure(not bOk or sMessage == ""); + return bOk; +} + +const ALString CommandFile::GetPrintableValue(const ALString& sValue) const +{ + if (sValue.GetLength() <= nMaxPrintableLength) + return sValue; + else + return sValue.Left(nMaxPrintableLength) + "..."; +} + +void CommandFile::AddInputCommandFileError(const ALString& sMessage) const +{ + Global::AddError("Input command file", sInputCommandFileName, sMessage); +} + +void CommandFile::AddInputParameterFileError(const ALString& sMessage) const +{ + Global::AddError("Input parameter file", sInputParameterFileName, sMessage); +} + +void CommandFile::AddOutputCommandFileError(const ALString& sMessage) const +{ + Global::AddError("Input command file", sOutputCommandFileName, sMessage); +} + +const ALString CommandFile::ProcessSearchReplaceCommand(const ALString& sInputCommand) const { ALString sInputString; ALString sBeginString; @@ -425,3 +941,30 @@ const ALString CommandFile::ProcessSearchReplaceCommand(const ALString& sInputCo } return sOutputCommand; } + +ALString CommandFile::BuildJsonPath(JsonMember* member, int nArrayRank, JsonMember* arrayObjectmember) +{ + ALString sJsonPath; + + require(member != NULL); + require(arrayObjectmember == NULL or nArrayRank >= 0); + + // Construction du chemin + sJsonPath = '"'; + sJsonPath += '/'; + sJsonPath += member->GetKey(); + if (nArrayRank >= 0) + { + sJsonPath += '/'; + sJsonPath += IntToString(nArrayRank); + } + if (arrayObjectmember != NULL) + { + sJsonPath += '/'; + sJsonPath += arrayObjectmember->GetKey(); + } + sJsonPath += '"'; + return sJsonPath; +} + +const ALString CommandFile::sByteVariablePrefix = "byte"; diff --git a/src/Norm/base/CommandFile.h b/src/Norm/base/CommandFile.h index 86a7c4a2..2ed8920b 100644 --- a/src/Norm/base/CommandFile.h +++ b/src/Norm/base/CommandFile.h @@ -9,8 +9,10 @@ class CommandFile; #include "Object.h" #include "ALString.h" #include "Vector.h" -#include "PLRemoteFileService.h" +#include "Longint.h" #include "FileService.h" +#include "PLRemoteFileService.h" +#include "JsonObject.h" /////////////////////////////////////////////////////////////////////////////////////// // Classe CommandFile @@ -49,11 +51,11 @@ class CommandFile : public Object void AddInputSearchReplaceValues(const ALString& sSearchValue, const ALString& sReplaceValue); // Nombre de paires de personnalisation - int GetInputSearchReplaceValueNumber(); + int GetInputSearchReplaceValueNumber() const; // Acces aux valeurs d'une paire de personnalisation - const ALString& GetInputSearchValueAt(int nIndex); - const ALString& GetInputReplaceValueAt(int nIndex); + const ALString& GetInputSearchValueAt(int nIndex) const; + const ALString& GetInputReplaceValueAt(int nIndex) const; // Nettoyage de toutes les paires void DeleteAllInputSearchReplaceValues(); @@ -145,7 +147,7 @@ class CommandFile : public Object // Verification de la validite des parametres, avec emission de messages d'erreurs boolean Check() const override; - // Ouverture du fichier de commande en entree, avec son son eventuele fichier de parametrage json + // Ouverture du fichier de commande en entree, avec son son eventuel fichier de parametrage json boolean OpenInputCommandFile(); boolean IsInputCommandFileOpened() const; @@ -170,8 +172,48 @@ class CommandFile : public Object /////////////////////////////////////////////////////////////////////////////////////// ///// Implementation protected: + // Chargement du fichier json en entree et verification de sa validite + boolean LoadJsonParameters(); + + // Test de la validite d'un nom de variable, avec creation si necessaire d'un message d'erreur + // complet, y compris la valeur testee + boolean CheckVariableName(const ALString& sValue, ALString& sMessage) const; + + // Test si une valeur correspond a un nom de variable au format camelCase + boolean IsCamelCaseVariableName(const ALString& sValue) const; + + // Test si un nom de variable correspond a un contenu de type byte, donc encode au format base64 + // Un tel nom de variable au format camelCase doit etre prefixe par byte + boolean IsByteVariableName(const ALString& sValue) const; + + // Transformation d'un nom de variable en sa variante byte ou standard + // On renvoie la valeur initiale si elle est deja dans sa bonne variante + const ALString ToByteVariableName(const ALString& sValue) const; + const ALString ToStandardVariableName(const ALString& sValue) const; + + // Transformation d'un nom de variable en sa variante opposee + const ALString ToVariantVariableName(const ALString& sValue) const; + + // Test de la validite d'une valeur de type string, avec creation si necessaire d'un message d'erreur + // complet, y compris la valeur testee + boolean CheckStringValue(const ALString& sValue, boolean bCheckBase64Encoding, ALString& sMessage) const; + + // Variante affichable d'une valeur, en completant si necessaire par des "..." + const ALString GetPrintableValue(const ALString& sValue) const; + + // Personnalisation des messages d'erreur + void AddInputCommandFileError(const ALString& sMessage) const; + void AddInputParameterFileError(const ALString& sMessage) const; + void AddOutputCommandFileError(const ALString& sMessage) const; + // Application des recherche/remplacement de valeurs successivement sur une commande - const ALString ProcessSearchReplaceCommand(const ALString& sInputCommand); + const ALString ProcessSearchReplaceCommand(const ALString& sInputCommand) const; + + // Construit d'un path json pour designer une valeur dans unse structure json + // Cf. https://jsonpatch.com/ + // On suit les element de structure valides dans le parametrage json + // La fin du parametrage peut etre non utilises (NULL ou -1) + ALString BuildJsonPath(JsonMember* member, int nArrayRank, JsonMember* arrayObjectmember); // Nom des fichiers ALString sInputCommandFileName; @@ -192,4 +234,22 @@ class CommandFile : public Object // Gestion des chaines des patterns a remplacer par des valeurs dans les fichiers d'input de scenario StringVector svInputCommandSearchValues; StringVector svInputCommandReplaceValues; + + // Object json pour les parametres en entree + JsonObject jsonParameters; + + // Prefixe des noms de variable ayant un contenu de type byte + static const ALString sByteVariablePrefix; + + // Longueur max d'un nom de variable + static const int nMaxVariableNameLength = 100; + + // Longueur max d'une valeur de type chaine de caracteres + static const int nMaxStringValueLength = 300; + + // Longueur max affichee pour une valeur dans les messages d'erreur + static const int nMaxPrintableLength = 30; + + // Taille max d'un fichier de parametrage + static const longint lMaxInputParameterFileSize = lMB; }; diff --git a/src/Norm/base/JsonObject.cpp b/src/Norm/base/JsonObject.cpp index fd7fac07..133b2c32 100644 --- a/src/Norm/base/JsonObject.cpp +++ b/src/Norm/base/JsonObject.cpp @@ -4,6 +4,50 @@ #include "JsonObject.h" +///////////////////////////////////////////// +// Classe JsonValue + +JsonObject* JsonValue::GetObjectValue() const +{ + require(GetType() == JsonValue::ObjectValue); + return cast(JsonObject*, this); +} + +JsonArray* JsonValue::GetArrayValue() const +{ + require(GetType() == JsonValue::ArrayValue); + return cast(JsonArray*, this); +} + +JsonString* JsonValue::GetStringValue() const +{ + require(GetType() == JsonValue::StringValue); + return cast(JsonString*, this); +} + +JsonNumber* JsonValue::GetNumberValue() const +{ + require(GetType() == JsonValue::NumberValue); + return cast(JsonNumber*, this); +} + +JsonBoolean* JsonValue::GetBooleanValue() const +{ + require(GetType() == JsonValue::BooleanValue); + return cast(JsonBoolean*, this); +} + +JsonNull* JsonValue::GetNullValue() const +{ + require(GetType() == JsonValue::NullValue); + return cast(JsonNull*, this); +} + +const ALString JsonValue::GetClassLabel() const +{ + return "json " + TypeToString(); +} + ///////////////////////////////////////////// // Classe JsonObject @@ -14,7 +58,7 @@ JsonObject::~JsonObject() oaMembers.DeleteAll(); } -int JsonObject::GetType() +int JsonObject::GetType() const { return ObjectValue; } @@ -116,14 +160,9 @@ void JsonObject::WriteIndent(ostream& ost, int nIndentLevel) const } } -const ALString JsonObject::JsonObject::GetClassLabel() const -{ - return "json object"; -} - -const ALString JsonObject::GetObjectLabel() const +const ALString JsonObject::JsonObject::TypeToString() const { - return ""; + return "object"; } void JsonObject::TestReadWrite(const ALString& sReadFileName, const ALString& sWriteFileName) @@ -153,7 +192,7 @@ JsonArray::~JsonArray() oaValues.DeleteAll(); } -int JsonArray::GetType() +int JsonArray::GetType() const { return ArrayValue; } @@ -232,14 +271,9 @@ void JsonArray::WriteIndent(ostream& ost, int nIndentLevel) const } } -const ALString JsonArray::GetClassLabel() const +const ALString JsonArray::TypeToString() const { - return "json array"; -} - -const ALString JsonArray::GetObjectLabel() const -{ - return ""; + return "array"; } ////////////////////////////////////////////////////////////////////////////// @@ -249,7 +283,7 @@ JsonString::JsonString() {} JsonString::~JsonString() {} -int JsonString::GetType() +int JsonString::GetType() const { return StringValue; } @@ -275,14 +309,9 @@ void JsonString::Write(ostream& ost) const ost << '"'; } -const ALString JsonString::GetClassLabel() const -{ - return "json string"; -} - -const ALString JsonString::GetObjectLabel() const +const ALString JsonString::TypeToString() const { - return ""; + return "string"; } ////////////////////////////////////////////////////////////////////////////// @@ -295,7 +324,7 @@ JsonNumber::JsonNumber() JsonNumber::~JsonNumber() {} -int JsonNumber::GetType() +int JsonNumber::GetType() const { return NumberValue; } @@ -315,14 +344,9 @@ void JsonNumber::Write(ostream& ost) const ost << std::setprecision(10) << dNumberValue; } -const ALString JsonNumber::GetClassLabel() const +const ALString JsonNumber::TypeToString() const { - return "json number"; -} - -const ALString JsonNumber::GetObjectLabel() const -{ - return ""; + return "number"; } ////////////////////////////////////////////////////////////////////////////// @@ -335,7 +359,7 @@ JsonBoolean::JsonBoolean() JsonBoolean::~JsonBoolean() {} -int JsonBoolean::GetType() +int JsonBoolean::GetType() const { return BooleanValue; } @@ -358,14 +382,9 @@ void JsonBoolean::Write(ostream& ost) const ost << "false"; } -const ALString JsonBoolean::GetClassLabel() const -{ - return "json boolean"; -} - -const ALString JsonBoolean::GetObjectLabel() const +const ALString JsonBoolean::TypeToString() const { - return ""; + return "boolean"; } ////////////////////////////////////////////////////////////////////////////// @@ -375,7 +394,7 @@ JsonNull::JsonNull() {} JsonNull::~JsonNull() {} -int JsonNull::GetType() +int JsonNull::GetType() const { return NullValue; } @@ -385,14 +404,9 @@ void JsonNull::Write(ostream& ost) const ost << "null"; } -const ALString JsonNull::GetClassLabel() const -{ - return "json null"; -} - -const ALString JsonNull::GetObjectLabel() const +const ALString JsonNull::TypeToString() const { - return ""; + return "null"; } ////////////////////////////////////////////////////////////////////////////// @@ -504,7 +518,7 @@ void JsonMember::WriteIndent(ostream& ost, int nIndentLevel) const const ALString JsonMember::GetClassLabel() const { - return "json member"; + return "member"; } const ALString JsonMember::GetObjectLabel() const diff --git a/src/Norm/base/JsonObject.h b/src/Norm/base/JsonObject.h index d007e632..ee7f2206 100644 --- a/src/Norm/base/JsonObject.h +++ b/src/Norm/base/JsonObject.h @@ -40,7 +40,21 @@ class JsonValue : public Object }; // Type de valeur - virtual int GetType() = 0; + virtual int GetType() const = 0; + + // Acces type a la valeur + JsonObject* GetObjectValue() const; + JsonArray* GetArrayValue() const; + JsonString* GetStringValue() const; + JsonNumber* GetNumberValue() const; + JsonBoolean* GetBooleanValue() const; + JsonNull* GetNullValue() const; + + // Conversion du type en chaine de caracteres + virtual const ALString TypeToString() const = 0; + + // Libelle de la classe + const ALString GetClassLabel() const override; }; ////////////////////////////////////////////////////////////////////////////// @@ -54,7 +68,7 @@ class JsonObject : public JsonValue ~JsonObject(); // Type de valeur - int GetType() override; + int GetType() const override; // Ajout d'un membre a l'objet void AddMember(JsonMember* member); @@ -82,9 +96,8 @@ class JsonObject : public JsonValue void Write(ostream& ost) const override; void WriteIndent(ostream& ost, int nIndentLevel) const; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; // Methodes de test static void TestReadWrite(const ALString& sReadFileName, const ALString& sWriteFileName); @@ -119,7 +132,7 @@ class JsonArray : public JsonValue ~JsonArray(); // Type de valeur - int GetType() override; + int GetType() const override; // Ajout d'une valeur au tableau void AddValue(JsonValue* value); @@ -138,9 +151,8 @@ class JsonArray : public JsonValue void Write(ostream& ost) const override; void WriteIndent(ostream& ost, int nIndentLevel) const; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; /////////////////////////////////////////////////////////////////// //// Implementation @@ -160,7 +172,7 @@ class JsonString : public JsonValue ~JsonString(); // Type de valeur - int GetType() override; + int GetType() const override; // Valeur de la chaine // La valeur est au format C, et non json @@ -170,9 +182,8 @@ class JsonString : public JsonValue // Affichage, ecriture dans un fichier void Write(ostream& ost) const override; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; /////////////////////////////////////////////////////////////////// //// Implementation @@ -191,7 +202,7 @@ class JsonNumber : public JsonValue ~JsonNumber(); // Type de valeur - int GetType() override; + int GetType() const override; // Valeur du nombre void SetNumber(double dValue); @@ -200,9 +211,8 @@ class JsonNumber : public JsonValue // Affichage, ecriture dans un fichier void Write(ostream& ost) const override; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; /////////////////////////////////////////////////////////////////// //// Implementation @@ -221,7 +231,7 @@ class JsonBoolean : public JsonValue ~JsonBoolean(); // Type de valeur - int GetType() override; + int GetType() const override; // Valeur du booleen void SetBoolean(boolean bValue); @@ -230,9 +240,8 @@ class JsonBoolean : public JsonValue // Affichage, ecriture dans un fichier void Write(ostream& ost) const override; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; /////////////////////////////////////////////////////////////////// //// Implementation @@ -251,14 +260,13 @@ class JsonNull : public JsonValue ~JsonNull(); // Type de valeur - int GetType() override; + int GetType() const override; // Affichage, ecriture dans un fichier void Write(ostream& ost) const override; - // Libelles utilisateurs - const ALString GetClassLabel() const override; - const ALString GetObjectLabel() const override; + // Conversion du type en chaine de caracteres + const ALString TypeToString() const override; /////////////////////////////////////////////////////////////////// //// Implementation diff --git a/src/Norm/base/JsonYac.cpp b/src/Norm/base/JsonYac.cpp index 8b5d023e..5a3ad677 100644 --- a/src/Norm/base/JsonYac.cpp +++ b/src/Norm/base/JsonYac.cpp @@ -1656,7 +1656,6 @@ boolean JsonObject::ReadFile(const ALString& sFileName) /* En cas d'erreur, ajout d'une ligne blanche pour separer des autres logs */ AddError("Errors detected during parsing " + sFileName + ": read operation cancelled"); - AddSimpleMessage(""); bOk = false; } nJsonFileParsingErrorNumber = 0; diff --git a/src/Norm/base/JsonYac.yac b/src/Norm/base/JsonYac.yac index 135b01e5..5bcf7b6c 100644 --- a/src/Norm/base/JsonYac.yac +++ b/src/Norm/base/JsonYac.yac @@ -368,7 +368,6 @@ boolean JsonObject::ReadFile(const ALString& sFileName) /* En cas d'erreur, ajout d'une ligne blanche pour separer des autres logs */ AddError("Errors detected during parsing " + sFileName + ": read operation cancelled"); - AddSimpleMessage(""); bOk = false; } nJsonFileParsingErrorNumber = 0; diff --git a/src/Norm/base/TextService.cpp b/src/Norm/base/TextService.cpp index 4e46de0a..d0eb3e00 100644 --- a/src/Norm/base/TextService.cpp +++ b/src/Norm/base/TextService.cpp @@ -712,6 +712,7 @@ int TextService::UnicodeHexToWindows1252(const ALString& sUnicodeHexChars) { int nCode; int i; + debug(ALString sUpperUnicodeHexChars); require(sUnicodeHexChars.GetLength() == 2 or sUnicodeHexChars.GetLength() == 4); require(AreEncodingStructuresInitialized()); @@ -740,11 +741,221 @@ int TextService::UnicodeHexToWindows1252(const ALString& sUnicodeHexChars) } } } + debug(sUpperUnicodeHexChars = sUnicodeHexChars); + debug(sUpperUnicodeHexChars.MakeUpper()); ensure(nCode == -1 or (0 <= nCode and nCode <= 255)); - ensure(nCode == -1 or svWindows1252UnicodeHexEncoding.GetAt(nCode) == sUnicodeHexChars); + debug(ensure(nCode == -1 or svWindows1252UnicodeHexEncoding.GetAt(nCode) == sUpperUnicodeHexChars)); return nCode; } +int TextService::Base64StringToBytes(const ALString& sBase64String, char* sBytes) +{ + // Index des caracteres de l'alphabet base64, et -1 pour les caracteres n'en faisant pas partie + static const unsigned char cBase64CharIndexes[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, + 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + boolean bOk; + int nOutputByteNumber; + int nInputLength; + int nMainInputLength; + int nTrailingEqualNumber; + int iInput; + int iOutput; + unsigned char cCharIndex1; + unsigned char cCharIndex2; + unsigned char cCharIndex3; + unsigned char cCharIndex4; + + require(sBytes != NULL); + assert(sizeof(cBase64CharIndexes) == 256 * sizeof(unsigned char)); + + // Calcul du nombre de bytes en sortie + nInputLength = sBase64String.GetLength(); + nOutputByteNumber = 3 * (nInputLength / 4); + + // La chaine a decoder doit avoir une longueur en multiple de 4 + bOk = nInputLength % 4 == 0; + + // Decodage si OK + if (bOk) + { + // Recherche du nombre de caractere de pading '=' en fin de chaine + nTrailingEqualNumber = 0; + if (nInputLength > 0 and sBase64String.GetAt(nInputLength - 1) == '=') + { + nTrailingEqualNumber++; + if (nInputLength > 1 and sBase64String.GetAt(nInputLength - 2) == '=') + nTrailingEqualNumber++; + } + nOutputByteNumber -= nTrailingEqualNumber; + + // Taille a traiter par bloc de 4 + nMainInputLength = nInputLength; + if (nTrailingEqualNumber > 0) + nMainInputLength -= 4; + + // Alimentation de la partie principale pour les groupes de 4 bytes + iInput = 0; + iOutput = 0; + while (iInput < nMainInputLength) + { + // Lecture de quatre bytes en entree et recherche de leur index en encodage base64 + cCharIndex1 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput)]; + cCharIndex2 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 1)]; + cCharIndex3 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 2)]; + cCharIndex4 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 3)]; + iInput += 4; + + // Arret si erreur + if (cCharIndex1 == 255 or cCharIndex2 == 255 or cCharIndex3 == 255 or cCharIndex4 == 255) + { + bOk = false; + break; + } + + // Transformation en trois bytes et memorisation + sBytes[iOutput] = ((cCharIndex1 & 0x3f) << 2) + ((cCharIndex2 & 0x30) >> 4); + sBytes[iOutput + 1] = ((cCharIndex2 & 0x0f) << 4) + ((cCharIndex3 & 0x3c) >> 2); + sBytes[iOutput + 2] = ((cCharIndex3 & 0x03) << 6) + ((cCharIndex4 & 0x3f) >> 0); + iOutput += 3; + } + assert(not bOk or iInput == nMainInputLength); + assert(not bOk or iOutput == 3 * (nMainInputLength / 4)); + + // Gestion de la fin de chaine + if (bOk) + { + if (nTrailingEqualNumber == 1) + { + // Lecture de trois bytes en entree et recherche de leur index en encodage base64 + cCharIndex1 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput)]; + cCharIndex2 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 1)]; + cCharIndex3 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 2)]; + + // Arret si erreur + if (cCharIndex1 == 255 or cCharIndex2 == 255 or cCharIndex3 == 255) + bOk = false; + // Transformation en deux bytes et memorisation + else + { + sBytes[iOutput] = ((cCharIndex1 & 0x3f) << 2) + ((cCharIndex2 & 0x30) >> 4); + sBytes[iOutput + 1] = ((cCharIndex2 & 0x0f) << 4) + ((cCharIndex3 & 0x3c) >> 2); + } + } + else if (nTrailingEqualNumber == 2) + { + // Lecture de deux bytes en entree et recherche de leur index en encodage base64 + cCharIndex1 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput)]; + cCharIndex2 = cBase64CharIndexes[(unsigned char)sBase64String.GetAt(iInput + 1)]; + + // Arret si erreur + if (cCharIndex1 == 255 or cCharIndex2 == 255) + bOk = false; + // Transformation en un byte et memorisation + else + { + sBytes[iOutput] = ((cCharIndex1 & 0x3f) << 2) + ((cCharIndex2 & 0x30) >> 4); + } + } + } + assert(not bOk or + iOutput + (nTrailingEqualNumber > 0 ? 3 - nTrailingEqualNumber : 0) == nOutputByteNumber); + } + + // On renvoie -1 si erreur + if (not bOk) + nOutputByteNumber = -1; + return nOutputByteNumber; +} + +void TextService::BytesToBase64String(const char* sBytes, int nByteNumber, ALString& sBase64String) +{ + // Caracteres utilise dans l'alphabet base64 + static const char sBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int nMainByteNumber; + int nRestByteNumber; + int nOutputLength; + int iInput; + int iOutput; + unsigned char cByte1; + unsigned char cByte2; + unsigned char cByte3; + + require(sBytes != NULL); + require(nByteNumber >= 0); + assert(sizeof(sBase64Chars) == 65 * sizeof(char)); + + // Decomposition de la longueur en entree en la partie multiple de 3 et son reste + nRestByteNumber = nByteNumber % 3; + nMainByteNumber = nByteNumber - nRestByteNumber; + + // Calcul de la longueur en sortie + nOutputLength = 4 * (nMainByteNumber / 3); + if (nRestByteNumber > 0) + nOutputLength += 4; + + // Allocation de la chaine en sortie + sBase64String.GetBufferSetLength(nOutputLength); + + // Alimentation de la partie principale pour les groupes de 3 bytes + iInput = 0; + iOutput = 0; + while (iInput < nMainByteNumber) + { + // Lecture de trois bytes en entree + cByte1 = sBytes[iInput]; + cByte2 = sBytes[iInput + 1]; + cByte3 = sBytes[iInput + 2]; + iInput += 3; + + // Transformation en quatre caracteres au format base64 et memorisation + sBase64String.SetAt(iOutput, sBase64Chars[((cByte1 & 0xfc) >> 2)]); + sBase64String.SetAt(iOutput + 1, sBase64Chars[((cByte1 & 0x03) << 4) + ((cByte2 & 0xf0) >> 4)]); + sBase64String.SetAt(iOutput + 2, sBase64Chars[((cByte2 & 0x0f) << 2) + ((cByte3 & 0xc0) >> 6)]); + sBase64String.SetAt(iOutput + 3, sBase64Chars[(cByte3 & 0x3f)]); + iOutput += 4; + } + assert(iInput == nMainByteNumber); + assert(iOutput == 4 * (nMainByteNumber / 3)); + + // Gestion de la fin de la chaine + if (nRestByteNumber == 1) + { + cByte1 = sBytes[iInput]; + cByte2 = 0; + + // Transformation en quatre bytes au format base64 avec deux '=' a la fin + sBase64String.SetAt(iOutput, sBase64Chars[((cByte1 & 0xfc) >> 2)]); + sBase64String.SetAt(iOutput + 1, sBase64Chars[((cByte1 & 0x03) << 4) + ((cByte2 & 0xf0) >> 4)]); + sBase64String.SetAt(iOutput + 2, '='); + sBase64String.SetAt(iOutput + 3, '='); + } + else if (nRestByteNumber == 2) + { + cByte1 = sBytes[iInput]; + cByte2 = sBytes[iInput + 1]; + cByte3 = 0; + + // Transformation en quatre bytes au format base64 avec un '=' a la fin + sBase64String.SetAt(iOutput, sBase64Chars[((cByte1 & 0xfc) >> 2)]); + sBase64String.SetAt(iOutput + 1, sBase64Chars[((cByte1 & 0x03) << 4) + ((cByte2 & 0xf0) >> 4)]); + sBase64String.SetAt(iOutput + 2, sBase64Chars[((cByte2 & 0x0f) << 2) + ((cByte3 & 0xc0) >> 6)]); + sBase64String.SetAt(iOutput + 3, '='); + } + assert(iOutput + (nRestByteNumber > 0 ? 4 : 0) == sBase64String.GetLength()); +} + const ALString TextService::ToPrintable(const ALString& sBytes) { ALString sPrintableBytes; @@ -855,16 +1066,18 @@ void TextService::BuildTextSample(StringVector* svTextValues) void TextService::Test() { - int nEncodingNumber = 10000000; + int nEncodingNumber = 1000000; ALString sTest; ALString sCString; StringVector svTextValues; ALString sValue; ALString sWord; ALString sHexaString; + ALString sBase64String; ALString sRetrievedValue; ALString sUnicodeChars; ALString sUtf8Chars; + char* sBuffer; int nAnsiCode; int nUtf8Code; int nAnsiCodeFromUtf8; @@ -910,7 +1123,8 @@ void TextService::Test() // Initialisation de l'echantillon de texts BuildTextSample(&svTextValues); - // Conversion des valeurs vers des mots et chaines de caracteres heaxa + // Conversion des valeurs vers des mots, chaines de caracteres heaxa, et format base64 + sBuffer = StandardGetBuffer(); for (i = 0; i < svTextValues.GetSize(); i++) { // Encodage/decodage de la valeur en mot @@ -925,11 +1139,28 @@ void TextService::Test() sRetrievedValue = HexCharStringToByteString(sHexaString); assert(sRetrievedValue == sValue); + // Encodage/decodage de la valeur au format base64 + sValue = svTextValues.GetAt(i); + BytesToBase64String(sValue, sValue.GetLength(), sBase64String); + nLength = Base64StringToBytes(sBase64String, sBuffer); + assert(nLength >= 0 and nLength < BUFFER_LENGTH - 1); + sBuffer[nLength] = '\0'; + sRetrievedValue = sBuffer; + assert(sRetrievedValue == sValue); + + // Verification de la non possibilite de decode une chaine avec encodage invalide + // en ajoutant un a quatre caracteres invalides + assert(Base64StringToBytes(sValue + " ", sBuffer) == -1); + assert(Base64StringToBytes(sValue + " ", sBuffer) == -1); + assert(Base64StringToBytes(sValue + " ", sBuffer) == -1); + assert(Base64StringToBytes(sValue + " ", sBuffer) == -1); + // Affichage cout << i << "\t"; cout << ToPrintable(sValue) << "\t"; cout << "(" << sWord << ")\t"; - cout << sHexaString << "\n"; + cout << sHexaString << "\t"; + cout << "(base64: " << sBase64String << ")\n"; } // Conversion de valeurs aleatoire vers des mots @@ -949,7 +1180,8 @@ void TextService::Test() timer.Stop(); assert(sRetrievedValue == sValue); } - cout << "Word encoding/decoding time for " << nEncodingNumber << " values: " << timer.GetElapsedTime() << "\n"; + cout << "SYS TIME\tWord encoding/decoding time for " << nEncodingNumber << " values: " << timer.GetElapsedTime() + << "\n"; } TextService::TextService() @@ -1004,7 +1236,7 @@ void TextService::Windows1252ToUtf8Hex(int nAnsiCode, ALString& sUtf8HexChars) require(AreEncodingStructuresInitialized()); sUtf8HexChars = svWindows1252Utf8HexEncoding.GetAt(nAnsiCode); - ensure(sUtf8HexChars.GetLength() <= nWindows1252EncodingMaxByteNumber); + ensure(sUtf8HexChars.GetLength() <= nWindows1252EncodingMaxByteNumber * 2); } int TextService::Windows1252Utf8CodeToWindows1252(int nWindows1252Utf8Code) diff --git a/src/Norm/base/TextService.h b/src/Norm/base/TextService.h index 49933246..0853f85f 100644 --- a/src/Norm/base/TextService.h +++ b/src/Norm/base/TextService.h @@ -96,6 +96,19 @@ class TextService : public Object // Renvoie un code ansi entre 0 et 255 si ok, -1 sinon static int UnicodeHexToWindows1252(const ALString& sUnicodeHexChars); + /////////////////////////////////////////////////////////////////////////////// + // Gestion de l'encodage des donnees binaires au format base64 + + // Conversion d'une chaine base64 vers une chaine C + // Le tableau de byte en sortie doit etre de taille au moins 3 * inputLength/4, + // conformement aux besoins de l'encodage base64 + // On renvoie la longueur de la chaine encode en cas de succes, -1 sinon + static int Base64StringToBytes(const ALString& sBase64String, char* sBytes); + + // Encodage d'un tableau de bytes vers le format base64, sans les double-quotes de debut et fin + // Le teableau en entree peut contenir n'importe quel byte, y comrpis des '\0' + static void BytesToBase64String(const char* sBytes, int nByteNumber, ALString& sBase64String); + ////////////////////////////////////////////////////////////////////////////////////////// // Services divers @@ -186,13 +199,15 @@ class TextService : public Object inline boolean TextService::IsHexChar(char c) { - return ('0' <= c and c <= '9') or ('A' <= c and c <= 'F'); + return ('0' <= c and c <= '9') or ('A' <= c and c <= 'F') or ('a' <= c and c <= 'f'); } inline int TextService::GetHexCharCode(char c) { require(IsHexChar(c)); - if (c >= 'A') + if (c >= 'a') + return c - 'a' + 10; + else if (c >= 'A') return c - 'A' + 10; else return c - '0'; diff --git a/src/Norm/base/UIObject.cpp b/src/Norm/base/UIObject.cpp index 12bbe21e..19f24415 100644 --- a/src/Norm/base/UIObject.cpp +++ b/src/Norm/base/UIObject.cpp @@ -5,15 +5,6 @@ #define UIDEV #include "UserInterface.h" -ALString UIObject::sIconImageJarPath; -void* UIObject::jvmHandle = NULL; -boolean UIObject::bIsJVMLoaded = false; -ALString UIObject::sLocalErrorLogFileName; -ALString UIObject::sErrorLogFileName; -ALString UIObject::sTaskProgressionLogFileName; -CommandLine UIObject::commandLineOptions; -CommandFile UIObject::commandFile; - const ALString UIObject::GetClassLabel() const { return "UI Object"; @@ -1674,18 +1665,18 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) int nCurrentUIMode; CommandLineOption* option; FileSpec* fsInputFile; + FileSpec* fsInputJsonFile; FileSpec* fsProgressionFile; FileSpec* fsOutputFile; + FileSpec* fsOutputNoReplayFile; FileSpec* fsErrorFile; FileSpec* fsFile; FileSpec* fsFileToCompare; ObjectArray oaSpecFiles; - boolean bIsbFlag; - boolean bIsiFlag; + ObjectDictionary odUsedOptions; int i; int nRef; - bIsbFlag = false; - bIsiFlag = false; + ALString sTmp; // Gestion des erreurs en mode textuel nCurrentUIMode = GetUIMode(); @@ -1700,6 +1691,7 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) for (i = 0; i < oaOptions.GetSize(); i++) { option = cast(CommandLineOption*, oaOptions.GetAt(i)); + odUsedOptions.SetAt(sTmp + option->GetFlag(), option); switch (option->GetFlag()) { case 'i': @@ -1708,7 +1700,13 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) fsInputFile->SetFilePathName(option->GetParameters()->GetAt(0)); fsInputFile->SetLabel("input commands file"); oaSpecFiles.Add(fsInputFile); - bIsiFlag = true; + break; + case 'j': + assert(option->GetParameters()->GetSize() == 1); + fsInputJsonFile = new FileSpec; + fsInputJsonFile->SetFilePathName(option->GetParameters()->GetAt(0)); + fsInputJsonFile->SetLabel("input parameters json file"); + oaSpecFiles.Add(fsInputJsonFile); break; case 'p': assert(option->GetParameters()->GetSize() == 1); @@ -1724,6 +1722,13 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) fsOutputFile->SetLabel("output commands file"); oaSpecFiles.Add(fsOutputFile); break; + case 'O': + assert(option->GetParameters()->GetSize() == 1); + fsOutputNoReplayFile = new FileSpec; + fsOutputNoReplayFile->SetFilePathName(option->GetParameters()->GetAt(0)); + fsOutputNoReplayFile->SetLabel("output commands file without replay"); + oaSpecFiles.Add(fsOutputNoReplayFile); + break; case 'e': assert(option->GetParameters()->GetSize() == 1); fsErrorFile = new FileSpec; @@ -1731,8 +1736,6 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) fsErrorFile->SetLabel("logs file"); oaSpecFiles.Add(fsErrorFile); break; - case 'b': - bIsbFlag = true; default: break; } @@ -1775,17 +1778,47 @@ boolean UIObject::CheckCommandLineOptions(const ObjectArray& oaOptions) if (not bOk) break; } + oaSpecFiles.DeleteAll(); - if (bIsbFlag and not bIsiFlag) + // Le mode batch ne peut etre utilise qu'avec un fichier de commande en entree + if (odUsedOptions.Lookup("b") != NULL and odUsedOptions.Lookup("i") == NULL) { Global::AddError("Command line parameters", "", "-b flag must be used with -i"); bOk = false; } - oaSpecFiles.DeleteAll(); + + // Le parametrage des search/replace ne peut etre utilise qu'avec un fichier de commande en entree + if (odUsedOptions.Lookup("r") != NULL and odUsedOptions.Lookup("i") == NULL) + { + Global::AddError("Command line parameters", "", "-r flag must be used with -i"); + bOk = false; + } + + // Le parametrage par un fichier json en entree ne peut etre utilise qu'avec un fichier de commande en entree + if (odUsedOptions.Lookup("j") != NULL and odUsedOptions.Lookup("i") == NULL) + { + Global::AddError("Command line parameters", "", "-j flag must be used with -i"); + bOk = false; + } + + // Les option -j et -r sont exclusives + if (odUsedOptions.Lookup("j") != NULL and odUsedOptions.Lookup("r") != NULL) + { + Global::AddError("Command line parameters", "", "-j and -r flags are exclusive"); + bOk = false; + } + + // Les option -o et -O sont exclusives + if (odUsedOptions.Lookup("o") != NULL and odUsedOptions.Lookup("O") != NULL) + { + Global::AddError("Command line parameters", "", "-o and -O flags are exclusive"); + bOk = false; + } // Retour au mode initial if (nCurrentUIMode == Graphic) SetUIMode(Graphic); + ensure(not bOk or commandFile.Check()); return bOk; } @@ -1794,8 +1827,10 @@ void UIObject::ParseMainParameters(int argc, char** argv) static boolean bIsUserExitHandlerSet = false; boolean bOk = true; CommandLineOption* oInputScenario; + CommandLineOption* oInputParameters; CommandLineOption* oError; CommandLineOption* oOutputScenario; + CommandLineOption* oOutputScenarioNoReplay; CommandLineOption* oReplace; CommandLineOption* oBatchMode; CommandLineOption* oTask; @@ -1819,6 +1854,7 @@ void UIObject::ParseMainParameters(int argc, char** argv) sGlobalHelp += "In the last example, " + sToolName + " replays all user interactions stored in the\n"; sGlobalHelp += "file scenario.txt after having replaced 'less' by 'more' and '70' by '90'\n"; + // Option sur le fichier d'erreur oError = new CommandLineOption; oError->SetFlag('e'); oError->AddDescriptionLine("store logs in the file"); @@ -1828,6 +1864,7 @@ void UIObject::ParseMainParameters(int argc, char** argv) oError->SetPriority(1); commandLineOptions.AddOption(oError); + // Option sur le mode batch oBatchMode = new CommandLineOption; oBatchMode->SetFlag('b'); oBatchMode->AddDescriptionLine("batch mode, with no GUI"); @@ -1836,6 +1873,7 @@ void UIObject::ParseMainParameters(int argc, char** argv) oBatchMode->SetPriority(0); commandLineOptions.AddOption(oBatchMode); + // Option sur le fichier de commandes en entree oInputScenario = new CommandLineOption; oInputScenario->SetFlag('i'); oInputScenario->AddDescriptionLine("replay commands stored in the file"); @@ -1844,6 +1882,16 @@ void UIObject::ParseMainParameters(int argc, char** argv) oInputScenario->SetMethod(InputCommand); commandLineOptions.AddOption(oInputScenario); + // Option sur le fichier de parametres en entree + oInputParameters = new CommandLineOption; + oInputParameters->SetFlag('j'); + oInputParameters->AddDescriptionLine("json file used to set replay parameters"); + oInputParameters->SetParameterRequired(true); + oInputParameters->SetParameterDescription(CommandLineOption::sParameterFile); + oInputParameters->SetMethod(JsonCommand); + commandLineOptions.AddOption(oInputParameters); + + // Option sur le fichier de commandes en en sortie oOutputScenario = new CommandLineOption; oOutputScenario->SetFlag('o'); oOutputScenario->AddDescriptionLine("record commands in the file"); @@ -1852,6 +1900,16 @@ void UIObject::ParseMainParameters(int argc, char** argv) oOutputScenario->SetMethod(OutputCommand); commandLineOptions.AddOption(oOutputScenario); + // Option sur le fichier de commandes en en sortie + oOutputScenarioNoReplay = new CommandLineOption; + oOutputScenarioNoReplay->SetFlag('O'); + oOutputScenarioNoReplay->AddDescriptionLine("same as -o option, but without replay"); + oOutputScenarioNoReplay->SetParameterRequired(true); + oOutputScenarioNoReplay->SetParameterDescription(CommandLineOption::sParameterFile); + oOutputScenarioNoReplay->SetMethod(OutputCommand); + commandLineOptions.AddOption(oOutputScenarioNoReplay); + + // Option sur le parametrage des serach/replace oReplace = new CommandLineOption; oReplace->SetFlag('r'); oReplace->AddDescriptionLine("search and replace in the command file"); @@ -1862,6 +1920,7 @@ void UIObject::ParseMainParameters(int argc, char** argv) oReplace->SetRepetitionAllowed(true); commandLineOptions.AddOption(oReplace); + // Option sur le fichier de progression oTask = new CommandLineOption; oTask->SetFlag('p'); oTask->AddDescriptionLine("store last progression messages"); @@ -1986,6 +2045,12 @@ boolean UIObject::OutputCommand(const ALString& sFileName) return true; } +boolean UIObject::JsonCommand(const ALString& sFileName) +{ + commandFile.SetInputParameterFileName(sFileName); + return true; +} + boolean UIObject::ReplaceCommand(const ALString& sSearchReplacePattern) { int nEqualPosition; @@ -2046,6 +2111,9 @@ boolean UIObject::TaskProgressionCommand(const ALString& sTaskFile) void UIObject::CleanCommandLineManagement() { + // Par defaut, on rehoue les commande + bNoReplayMode = false; + // Fermeture des fichiers de commande en entree et sortie commandFile.CloseCommandFiles(); @@ -2139,3 +2207,12 @@ boolean UIObject::bBatchMode = false; boolean UIObject::bFastExitMode = true; boolean UIObject::bTextualInteractiveModeAllowed = false; ObjectDictionary UIObject::odListIndexCommands; +ALString UIObject::sIconImageJarPath; +void* UIObject::jvmHandle = NULL; +boolean UIObject::bIsJVMLoaded = false; +ALString UIObject::sLocalErrorLogFileName; +ALString UIObject::sErrorLogFileName; +ALString UIObject::sTaskProgressionLogFileName; +CommandLine UIObject::commandLineOptions; +boolean UIObject::bNoReplayMode = false; +CommandFile UIObject::commandFile; diff --git a/src/Norm/base/UserInterface.h b/src/Norm/base/UserInterface.h index 35d986a7..7736985c 100644 --- a/src/Norm/base/UserInterface.h +++ b/src/Norm/base/UserInterface.h @@ -386,6 +386,8 @@ class UIObject : public Object // Parametrage des options de la ligne de commande static boolean InputCommand(const ALString& sFileName); static boolean OutputCommand(const ALString& sFileName); + static boolean OutputCommandNoReplay(const ALString& sFileName); + static boolean JsonCommand(const ALString& sFileName); static boolean ReplaceCommand(const ALString& sSearchReplacePattern); static boolean BatchCommand(const ALString& sParameter); static boolean ErrorCommand(const ALString& sErrorLog); @@ -436,6 +438,7 @@ class UIObject : public Object static ALString sErrorLogFileName; static ALString sTaskProgressionLogFileName; static CommandLine commandLineOptions; + static boolean bNoReplayMode; // Gestion des fichiers de commande static CommandFile commandFile; diff --git a/test/LearningTestTool/py/_kht_constants.py b/test/LearningTestTool/py/_kht_constants.py index b3cd8010..93d06752 100644 --- a/test/LearningTestTool/py/_kht_constants.py +++ b/test/LearningTestTool/py/_kht_constants.py @@ -19,6 +19,7 @@ """ Fichiers se trouvant d'un repertoire de test """ TEST_PRM = "test.prm" +TEST_JSON = "test.json" COMPARISON_RESULTS_LOG = "comparisonResults.log" """ Fichiers se trouvant d'un repertoire de resultats """ diff --git a/test/LearningTestTool/py/_kht_families.py b/test/LearningTestTool/py/_kht_families.py index 1605d14e..0c56412b 100644 --- a/test/LearningTestTool/py/_kht_families.py +++ b/test/LearningTestTool/py/_kht_families.py @@ -71,6 +71,7 @@ def check_family(family): "DTClassification", "VariableConstruction", "NewV10", + "JsonParameters", "CrashTests", "SmallInstability", "CharacterEncoding", diff --git a/test/LearningTestTool/py/kht_export.py b/test/LearningTestTool/py/kht_export.py index 0c567bfe..0e1ff39b 100644 --- a/test/LearningTestTool/py/kht_export.py +++ b/test/LearningTestTool/py/kht_export.py @@ -5,6 +5,7 @@ import os import os.path import argparse +import json import _kht_constants as kht import _kht_utils as utils @@ -194,7 +195,7 @@ def export_learning_test_tree( if len(fields) >= 2: candidate_file_path = fields[-1] if candidate_file_path[0] == ".": - # Le separateur est normalise dans les fichier scenario + # Le separateur est normalise dans les fichiers scenario path_components = candidate_file_path.split( "/" ) @@ -214,6 +215,70 @@ def export_learning_test_tree( used_dataset_collections[ dataset_collection ][dataset_name] = True + # On traite de facon heuristique le cas d'un fichier de parametrage json + # C'est en fait complexe a analyser dans le cas general en raison des search/replace + # entre fichier de parametre json et fichier de scenario. Mais, c'est inutile d'avoir + # une implementation rigouruse pour les usages basiques utilises dans les suites de test + test_json_path = os.path.join(source_test_dir, kht.TEST_JSON) + try: + if os.path.isfile(test_prm_path): + with open( + test_json_path, "r", errors="ignore" + ) as test_json_file: + json_data = json.load(test_json_file) + # Parcours des cles de type string du json pour collecter toutes les valeurs correspondants + all_json_string_values = [] + for json_key in json_data: + json_value = json_data[json_key] + # Cas d'une cle chaine de caracteres + if isinstance(json_value, str): + all_json_string_values.append(json_value) + # Cas d'une cle list + elif isinstance(json_value, list): + # Parcours des objets de la Liste + for list_value in json_value: + # On ne traite que les valeur de type dictionnaire + if isinstance(list_value, dict): + # Parcours des cle de l'objet du tableau + for json_sub_key in list_value: + json_sub_value = list_value[ + json_sub_key + ] + # Cas d'une cle chaine de caracteres + if isinstance( + json_sub_value, str + ): + all_json_string_values.append( + json_sub_value + ) + # Analyse des chaines de caracteres du json + # On reprend la meme heuristique que pour l'analyse du fichier scenario + for candidate_file_path in all_json_string_values: + if candidate_file_path[0] == ".": + # Le separateur est normalise dans les fichiers scenario + path_components = candidate_file_path.split( + "/" + ) + # les datasets se trouvent toujours au meme niveau relatif de l'arborescence + if ( + len(path_components) > 5 + and path_components[0] == ".." + and path_components[1] == ".." + and path_components[2] == ".." + ): + dataset_collection = path_components[3] + if ( + dataset_collection + in kht.DATASET_COLLECTION_NAMES + ): + dataset_name = path_components[4] + used_dataset_collections[ + dataset_collection + ][dataset_name] = True + except Exception as exception: + # On ignore les json erronnes pour ne pas etre verbeux dans le cas des fichiers json + # utilises pour tester les erreurs + pass else: print("error : suite directory not found: " + source_suite_dir) diff --git a/test/LearningTestTool/py/kht_test.py b/test/LearningTestTool/py/kht_test.py index 53ffb641..03729b41 100644 --- a/test/LearningTestTool/py/kht_test.py +++ b/test/LearningTestTool/py/kht_test.py @@ -381,6 +381,9 @@ def evaluate_tool_on_test_dir( khiops_params.append("-b") khiops_params.append("-i") khiops_params.append(kht.TEST_PRM) + if os.path.isfile(kht.TEST_JSON): + khiops_params.append("-j") + khiops_params.append(kht.TEST_JSON) khiops_params.append("-e") khiops_params.append(os.path.join(results_dir, kht.ERR_TXT)) if output_scenario: diff --git a/test/UnitTests/Norm/Nom_test.cpp b/test/UnitTests/Norm/Nom_test.cpp index c6139839..0c045966 100644 --- a/test/UnitTests/Norm/Nom_test.cpp +++ b/test/UnitTests/Norm/Nom_test.cpp @@ -15,6 +15,7 @@ #include "OutputBufferedFile.h" #include "Regexp.h" #include "MemoryTest.h" +#include "TextService.h" namespace { @@ -38,6 +39,7 @@ KHIOPS_TEST(base, IntVector, IntVector::Test); KHIOPS_TEST(base, LongintVector, LongintVector::Test); KHIOPS_TEST(base, CharVector, CharVector::Test); KHIOPS_TEST(base, StringVector, StringVector::Test); +KHIOPS_TEST(base, TextService, TextService::Test); TEST(long, InputBufferedFile) { diff --git a/test/UnitTests/Norm/results.ref/base_TextService.txt b/test/UnitTests/Norm/results.ref/base_TextService.txt new file mode 100644 index 00000000..78119af7 --- /dev/null +++ b/test/UnitTests/Norm/results.ref/base_TextService.txt @@ -0,0 +1,283 @@ +Json string aa\u221Ebb\u00e9cc\/\\dd\tee aa?bbécc/\dd ee Index Char Unicode Utf8 Ansi code Utf8 code Ansi code from utf8 Valid +0 0000 00 0 0 0 1 +1 0001 01 1 1 1 1 +2 0002 02 2 2 2 1 +3 0003 03 3 3 3 1 +4 0004 04 4 4 4 1 +5 0005 05 5 5 5 1 +6 0006 06 6 6 6 1 +7 0007 07 7 7 7 1 +8 0008 08 8 8 8 1 +9 0009 09 9 9 9 1 +10 000A 0A 10 10 10 1 +11 000B 0B 11 11 11 1 +12 000C 0C 12 12 12 1 +13 000D 0D 13 13 13 1 +14 000E 0E 14 14 14 1 +15 000F 0F 15 15 15 1 +16 0010 10 16 16 16 1 +17 0011 11 17 17 17 1 +18 0012 12 18 18 18 1 +19 0013 13 19 19 19 1 +20 0014 14 20 20 20 1 +21 0015 15 21 21 21 1 +22 0016 16 22 22 22 1 +23 0017 17 23 23 23 1 +24 0018 18 24 24 24 1 +25 0019 19 25 25 25 1 +26 001A 1A 26 26 26 1 +27 001B 1B 27 27 27 1 +28 001C 1C 28 28 28 1 +29 001D 1D 29 29 29 1 +30 001E 1E 30 30 30 1 +31 001F 1F 31 31 31 1 +32 0020 20 32 32 32 1 +33 ! 0021 21 33 33 33 1 +34 0022 22 34 34 34 1 +35 # 0023 23 35 35 35 1 +36 $ 0024 24 36 36 36 1 +37 % 0025 25 37 37 37 1 +38 & 0026 26 38 38 38 1 +39 ' 0027 27 39 39 39 1 +40 ( 0028 28 40 40 40 1 +41 ) 0029 29 41 41 41 1 +42 * 002A 2A 42 42 42 1 +43 + 002B 2B 43 43 43 1 +44 , 002C 2C 44 44 44 1 +45 - 002D 2D 45 45 45 1 +46 . 002E 2E 46 46 46 1 +47 / 002F 2F 47 47 47 1 +48 0 0030 30 48 48 48 1 +49 1 0031 31 49 49 49 1 +50 2 0032 32 50 50 50 1 +51 3 0033 33 51 51 51 1 +52 4 0034 34 52 52 52 1 +53 5 0035 35 53 53 53 1 +54 6 0036 36 54 54 54 1 +55 7 0037 37 55 55 55 1 +56 8 0038 38 56 56 56 1 +57 9 0039 39 57 57 57 1 +58 : 003A 3A 58 58 58 1 +59 ; 003B 3B 59 59 59 1 +60 < 003C 3C 60 60 60 1 +61 = 003D 3D 61 61 61 1 +62 > 003E 3E 62 62 62 1 +63 ? 003F 3F 63 63 63 1 +64 @ 0040 40 64 64 64 1 +65 A 0041 41 65 65 65 1 +66 B 0042 42 66 66 66 1 +67 C 0043 43 67 67 67 1 +68 D 0044 44 68 68 68 1 +69 E 0045 45 69 69 69 1 +70 F 0046 46 70 70 70 1 +71 G 0047 47 71 71 71 1 +72 H 0048 48 72 72 72 1 +73 I 0049 49 73 73 73 1 +74 J 004A 4A 74 74 74 1 +75 K 004B 4B 75 75 75 1 +76 L 004C 4C 76 76 76 1 +77 M 004D 4D 77 77 77 1 +78 N 004E 4E 78 78 78 1 +79 O 004F 4F 79 79 79 1 +80 P 0050 50 80 80 80 1 +81 Q 0051 51 81 81 81 1 +82 R 0052 52 82 82 82 1 +83 S 0053 53 83 83 83 1 +84 T 0054 54 84 84 84 1 +85 U 0055 55 85 85 85 1 +86 V 0056 56 86 86 86 1 +87 W 0057 57 87 87 87 1 +88 X 0058 58 88 88 88 1 +89 Y 0059 59 89 89 89 1 +90 Z 005A 5A 90 90 90 1 +91 [ 005B 5B 91 91 91 1 +92 \ 005C 5C 92 92 92 1 +93 ] 005D 5D 93 93 93 1 +94 ^ 005E 5E 94 94 94 1 +95 _ 005F 5F 95 95 95 1 +96 ` 0060 60 96 96 96 1 +97 a 0061 61 97 97 97 1 +98 b 0062 62 98 98 98 1 +99 c 0063 63 99 99 99 1 +100 d 0064 64 100 100 100 1 +101 e 0065 65 101 101 101 1 +102 f 0066 66 102 102 102 1 +103 g 0067 67 103 103 103 1 +104 h 0068 68 104 104 104 1 +105 i 0069 69 105 105 105 1 +106 j 006A 6A 106 106 106 1 +107 k 006B 6B 107 107 107 1 +108 l 006C 6C 108 108 108 1 +109 m 006D 6D 109 109 109 1 +110 n 006E 6E 110 110 110 1 +111 o 006F 6F 111 111 111 1 +112 p 0070 70 112 112 112 1 +113 q 0071 71 113 113 113 1 +114 r 0072 72 114 114 114 1 +115 s 0073 73 115 115 115 1 +116 t 0074 74 116 116 116 1 +117 u 0075 75 117 117 117 1 +118 v 0076 76 118 118 118 1 +119 w 0077 77 119 119 119 1 +120 x 0078 78 120 120 120 1 +121 y 0079 79 121 121 121 1 +122 z 007A 7A 122 122 122 1 +123 { 007B 7B 123 123 123 1 +124 | 007C 7C 124 124 124 1 +125 } 007D 7D 125 125 125 1 +126 ~ 007E 7E 126 126 126 1 +127  007F 7F 127 127 127 1 +128 20AC E282AC 128 14844588 128 1 +129 0081 C281 129 49793 129 1 +130 201A E2809A 130 14844058 130 1 +131 0192 C692 131 50834 131 1 +132 201E E2809E 132 14844062 132 1 +133 2026 E280A6 133 14844070 133 1 +134 2020 E280A0 134 14844064 134 1 +135 2021 E280A1 135 14844065 135 1 +136 02C6 CB86 136 52102 136 1 +137 2030 E280B0 137 14844080 137 1 +138 0160 C5A0 138 50592 138 1 +139 2039 E280B9 139 14844089 139 1 +140 0152 C592 140 50578 140 1 +141 008D C28D 141 49805 141 1 +142 017D C5BD 142 50621 142 1 +143 008F C28F 143 49807 143 1 +144 0090 C290 144 49808 144 1 +145 2018 E28098 145 14844056 145 1 +146 2019 E28099 146 14844057 146 1 +147 201C E2809C 147 14844060 147 1 +148 201D E2809D 148 14844061 148 1 +149 2022 E280A2 149 14844066 149 1 +150 2013 E28093 150 14844051 150 1 +151 2014 E28094 151 14844052 151 1 +152 02DC CB9C 152 52124 152 1 +153 2122 E284A2 153 14845090 153 1 +154 0161 C5A1 154 50593 154 1 +155 203A E280BA 155 14844090 155 1 +156 0153 C593 156 50579 156 1 +157 009D C29D 157 49821 157 1 +158 017E C5BE 158 50622 158 1 +159 0178 C5B8 159 50616 159 1 +160 00A0 C2A0 160 49824 160 1 +161 00A1 C2A1 161 49825 161 1 +162 00A2 C2A2 162 49826 162 1 +163 00A3 C2A3 163 49827 163 1 +164 00A4 C2A4 164 49828 164 1 +165 00A5 C2A5 165 49829 165 1 +166 00A6 C2A6 166 49830 166 1 +167 00A7 C2A7 167 49831 167 1 +168 00A8 C2A8 168 49832 168 1 +169 00A9 C2A9 169 49833 169 1 +170 00AA C2AA 170 49834 170 1 +171 00AB C2AB 171 49835 171 1 +172 00AC C2AC 172 49836 172 1 +173 00AD C2AD 173 49837 173 1 +174 00AE C2AE 174 49838 174 1 +175 00AF C2AF 175 49839 175 1 +176 00B0 C2B0 176 49840 176 1 +177 00B1 C2B1 177 49841 177 1 +178 00B2 C2B2 178 49842 178 1 +179 00B3 C2B3 179 49843 179 1 +180 00B4 C2B4 180 49844 180 1 +181 00B5 C2B5 181 49845 181 1 +182 00B6 C2B6 182 49846 182 1 +183 00B7 C2B7 183 49847 183 1 +184 00B8 C2B8 184 49848 184 1 +185 00B9 C2B9 185 49849 185 1 +186 00BA C2BA 186 49850 186 1 +187 00BB C2BB 187 49851 187 1 +188 00BC C2BC 188 49852 188 1 +189 00BD C2BD 189 49853 189 1 +190 00BE C2BE 190 49854 190 1 +191 00BF C2BF 191 49855 191 1 +192 00C0 C380 192 50048 192 1 +193 00C1 C381 193 50049 193 1 +194 00C2 C382 194 50050 194 1 +195 00C3 C383 195 50051 195 1 +196 00C4 C384 196 50052 196 1 +197 00C5 C385 197 50053 197 1 +198 00C6 C386 198 50054 198 1 +199 00C7 C387 199 50055 199 1 +200 00C8 C388 200 50056 200 1 +201 00C9 C389 201 50057 201 1 +202 00CA C38A 202 50058 202 1 +203 00CB C38B 203 50059 203 1 +204 00CC C38C 204 50060 204 1 +205 00CD C38D 205 50061 205 1 +206 00CE C38E 206 50062 206 1 +207 00CF C38F 207 50063 207 1 +208 00D0 C390 208 50064 208 1 +209 00D1 C391 209 50065 209 1 +210 00D2 C392 210 50066 210 1 +211 00D3 C393 211 50067 211 1 +212 00D4 C394 212 50068 212 1 +213 00D5 C395 213 50069 213 1 +214 00D6 C396 214 50070 214 1 +215 00D7 C397 215 50071 215 1 +216 00D8 C398 216 50072 216 1 +217 00D9 C399 217 50073 217 1 +218 00DA C39A 218 50074 218 1 +219 00DB C39B 219 50075 219 1 +220 00DC C39C 220 50076 220 1 +221 00DD C39D 221 50077 221 1 +222 00DE C39E 222 50078 222 1 +223 00DF C39F 223 50079 223 1 +224 00E0 C3A0 224 50080 224 1 +225 00E1 C3A1 225 50081 225 1 +226 00E2 C3A2 226 50082 226 1 +227 00E3 C3A3 227 50083 227 1 +228 00E4 C3A4 228 50084 228 1 +229 00E5 C3A5 229 50085 229 1 +230 00E6 C3A6 230 50086 230 1 +231 00E7 C3A7 231 50087 231 1 +232 00E8 C3A8 232 50088 232 1 +233 00E9 C3A9 233 50089 233 1 +234 00EA C3AA 234 50090 234 1 +235 00EB C3AB 235 50091 235 1 +236 00EC C3AC 236 50092 236 1 +237 00ED C3AD 237 50093 237 1 +238 00EE C3AE 238 50094 238 1 +239 00EF C3AF 239 50095 239 1 +240 00F0 C3B0 240 50096 240 1 +241 00F1 C3B1 241 50097 241 1 +242 00F2 C3B2 242 50098 242 1 +243 00F3 C3B3 243 50099 243 1 +244 00F4 C3B4 244 50100 244 1 +245 00F5 C3B5 245 50101 245 1 +246 00F6 C3B6 246 50102 246 1 +247 00F7 C3B7 247 50103 247 1 +248 00F8 C3B8 248 50104 248 1 +249 00F9 C3B9 249 50105 249 1 +250 00FA C3BA 250 50106 250 1 +251 00FB C3BB 251 50107 251 1 +252 00FC C3BC 252 50108 252 1 +253 00FD C3BD 253 50109 253 1 +254 00FE C3BE 254 50110 254 1 +255 00FF C3BF 255 50111 255 1 +0 bonjour (bonjour) 626F6E6A6F7572 (base64: Ym9uam91cg==) +1 a toto (a toto) 6120746F746F (base64: YSB0b3Rv) +2 bonjour#et (bonjour#et) 626F6E6A6F7572236574 (base64: Ym9uam91ciNldA==) +3 bonjour{et} (bonjour{{et}) 626F6E6A6F75727B65747D (base64: Ym9uam91cntldH0=) +4 !%$)#' (!%$)#') 212524292327 (base64: ISUkKSMn) +5 !%$){' (!%$){{') 212524297B27 (base64: ISUkKXsn) +6 ### (###) 232323 (base64: IyMj) +7 {{{ ({{{{{{) 7B7B7B (base64: e3t7) +8 ( ) 20 (base64: IA==) +9 ({09}) 09 (base64: CQ==) +10 # (#) 23 (base64: Iw==) +11 { ({{) 7B (base64: ew==) +12 ? ({0C}) 0C (base64: DA==) +13 ? ({E9}) E9 (base64: 6Q==) +14 ??? ({E8E9EA}) E8E9EA (base64: 6Onq) +15 ?bonjour ({E9}bonjour) E9626F6E6A6F7572 (base64: 6WJvbmpvdXI=) +16 bonjour??? (bonjour{E8E9EA}) 626F6E6A6F7572E8E9EA (base64: Ym9uam91cujp6g==) +17 bon???jour (bon{E8E9EA}jour) 626F6EE8E9EA6A6F7572 (base64: Ym9u6Onqam91cg==) +18 bonne ann?e (bonne ann{E9}e) 626F6E6E6520616E6EE965 (base64: Ym9ubmUgYW5u6WU=) +19 ??bon?jour?? ({E8E9}bon{EA}jour{E9EA}) E8E9626F6EEA6A6F7572E9EA (base64: 6Olib27qam91cunq) +20 ??bon?jour?#? ({E8E9}bon{EA}jour{E9}#{EA}) E8E9626F6EEA6A6F7572E923EA (base64: 6Olib27qam91cukj6g==) +21 ??? (€) E282AC (base64: 4oKs) +22 ??? (â„¢) E284A2 (base64: 4oSi) +23 ?? (Ëœ) CB9C (base64: y5w=) +24 ???? (ð’€€) F0928080 (base64: 8JKAgA==) +SYS TIME Word encoding/decoding time for 1000000 values: 0.338258