diff --git a/OMCompiler/Compiler/FrontEnd/ModelicaBuiltin.mo b/OMCompiler/Compiler/FrontEnd/ModelicaBuiltin.mo index 4a23c48398f..380c5d9f51d 100644 --- a/OMCompiler/Compiler/FrontEnd/ModelicaBuiltin.mo +++ b/OMCompiler/Compiler/FrontEnd/ModelicaBuiltin.mo @@ -2963,7 +2963,7 @@ end plotParametric; function readSimulationResult "Reads a result file, returning a matrix corresponding to the variables and size given." input String filename; - input VariableNames variables; + input VariableNames variables "e.g. {a.b, a[1].b[3].c}, or a single VariableName"; input Integer size = 0 "0=read any size... If the size is not the same as the result-file, this function fails"; output Real result[:,:]; external "builtin"; diff --git a/OMCompiler/Compiler/NFFrontEnd/NFModelicaBuiltin.mo b/OMCompiler/Compiler/NFFrontEnd/NFModelicaBuiltin.mo index 63a58db14f8..77276916e99 100644 --- a/OMCompiler/Compiler/NFFrontEnd/NFModelicaBuiltin.mo +++ b/OMCompiler/Compiler/NFFrontEnd/NFModelicaBuiltin.mo @@ -3216,7 +3216,7 @@ end plotParametric; function readSimulationResult "Reads a result file, returning a matrix corresponding to the variables and size given." input String filename; - input VariableNames variables; + input VariableNames variables "e.g. {a.b, a[1].b[3].c}, or a single VariableName"; input Integer size = 0 "0=read any size... If the size is not the same as the result-file, this function fails"; output Real result[:,:]; external "builtin"; diff --git a/OMCompiler/Compiler/Template/CodegenC.tpl b/OMCompiler/Compiler/Template/CodegenC.tpl index 8b17af590a0..de3397c5eb5 100644 --- a/OMCompiler/Compiler/Template/CodegenC.tpl +++ b/OMCompiler/Compiler/Template/CodegenC.tpl @@ -5244,9 +5244,9 @@ template functionlinearmodel(ModelInfo modelInfo, String modelNamePrefix) "templ <%vectorU%> <%vectorY%> "\n" - <%getVarName(vars.stateVars, "x")%> - <%getVarName(vars.inputVars, "u")%> - <%getVarName(vars.outputVars, "y")%> + <%getVarNameC(vars.stateVars, "x")%> + <%getVarNameC(vars.inputVars, "u")%> + <%getVarNameC(vars.outputVars, "y")%> "equation\n" " der(x) = A * x + B * u;\n" " y = C * x + D * u;\n" @@ -5275,10 +5275,10 @@ template functionlinearmodel(ModelInfo modelInfo, String modelNamePrefix) "templ <%vectorY%> <%vectorZ%> "\n" - <%getVarName(vars.stateVars, "x")%> - <%getVarName(vars.inputVars, "u")%> - <%getVarName(vars.outputVars, "y")%> - <%getVarName(vars.algVars, "z")%> + <%getVarNameC(vars.stateVars, "x")%> + <%getVarNameC(vars.inputVars, "u")%> + <%getVarNameC(vars.outputVars, "y")%> + <%getVarNameC(vars.algVars, "z")%> "equation\n" " der(x) = A * x + B * u;\n" " y = C * x + D * u;\n" @@ -5416,17 +5416,18 @@ template functionlinearmodelPython(ModelInfo modelInfo, String modelNamePrefix) end match end functionlinearmodelPython; -template getVarName(list simVars, String arrayName) "template getVarName - Generates name for a varables." +template getVarNameC(list simVars, String arrayName) + "template getVarNameC + Generates name for a variable inside a C string." ::= simVars |> var hasindex arrindex fromindex 1 => (match var case SIMVAR(__) then - <<" Real '<%arrayName%>_<%crefStrNoUnderscore(name)%>' = <%arrayName%>[<%arrindex%>];\n">> + <<" Real '<%arrayName%>_<%Util.escapeModelicaStringToCString(escapeSingleQuoteIdent(crefStrNoUnderscore(name)))%>' = <%arrayName%>[<%arrindex%>];\n">> end match) ;separator="\n" -end getVarName; +end getVarNameC; template getVarNameMatlab(list simVars, String arrayName) "template getVarName - Generates name for a varables." + Generates name for a variable." ::= simVars |> var hasindex arrindex fromindex 1 => (match var case SIMVAR(__) then @@ -5435,7 +5436,7 @@ template getVarNameMatlab(list simVars, String arrayName) "template getV end getVarNameMatlab; template getVarNamePython(list simVars, String arrayName) "template getVarName - Generates name for a variables." + Generates name for a variable." ::= simVars |> var hasindex arrindex fromindex 0 => (match var case SIMVAR(__) then @@ -5444,7 +5445,7 @@ template getVarNamePython(list simVars, String arrayName) "template getV end getVarNamePython; template getVarNameJulia(list simVars, String arrayName) "template getVarName - Generates name for a varables." + Generates name for a variable." ::= simVars |> var hasindex arrindex fromindex 0 => (match var case SIMVAR(__) then diff --git a/OMCompiler/Compiler/Template/CodegenFMUCommon.tpl b/OMCompiler/Compiler/Template/CodegenFMUCommon.tpl index 6366394373c..31b7d157563 100644 --- a/OMCompiler/Compiler/Template/CodegenFMUCommon.tpl +++ b/OMCompiler/Compiler/Template/CodegenFMUCommon.tpl @@ -475,6 +475,7 @@ template ScalarVariableAttribute2(SimVar simVar, SimCode simCode) ::= match simVar case SIMVAR(__) then + let name = Util.escapeModelicaStringToXmlString(System.stringReplace(crefStrNoUnderscore(Util.getOption(exportVar)),"$", "_D_")) let defaultValueReference = '<%System.tmpTick()%>' let valueReference = getValueReference(simVar, simCode, false) let description = if comment then 'description="<%Util.escapeModelicaStringToXmlString(comment)%>"' @@ -484,7 +485,7 @@ match simVar let caus = getCausality2(causality) let initial = getFmiInitialAttributeStr(simVar) << - name="<%System.stringReplace(crefStrNoUnderscore(Util.getOption(exportVar)),"$", "_D_")%>" + name="<%name%>" valueReference="<%valueReference%>" <%description%> <%if boolNot(stringEq(variability_, "")) then 'variability="'+variability_+'"' %> diff --git a/OMCompiler/Compiler/Template/CodegenUtil.tpl b/OMCompiler/Compiler/Template/CodegenUtil.tpl index 849b274bb96..83432037156 100644 --- a/OMCompiler/Compiler/Template/CodegenUtil.tpl +++ b/OMCompiler/Compiler/Template/CodegenUtil.tpl @@ -107,14 +107,12 @@ template crefStrNoUnderscore(ComponentRef cr) "Generates the name of a variable for variable name array. However does not use underscores on qualified names. a.b not a._b. - Escaping single and double quotes from quoted identifiers. - Used for generating variable names that are exported e.g. xml files" + Used for generating variable names that are exported e.g. to xml files" ::= match cr case CREF_IDENT(__) then - let escapedIdent = Util.escapeModelicaStringToCString(escapeSingleQuoteIdent(ident)) - '<%escapedIdent%><%subscriptsStr(subscriptLst)%>' + '<%ident%><%subscriptsStr(subscriptLst)%>' case CREF_QUAL(ident = "$DER") then 'der(<%crefStrNoUnderscore(componentRef)%>)' case CREF_QUAL(ident = "$CLKPRE") then 'previous(<%crefStrNoUnderscore(componentRef)%>)' case CREF_QUAL(__) then '<%ident%><%subscriptsStr(subscriptLst)%>.<%crefStrNoUnderscore(componentRef)%>' diff --git a/OMCompiler/Parser/BaseModelica_Lexer.g b/OMCompiler/Parser/BaseModelica_Lexer.g index 409e39f249f..b17ee7fd01e 100644 --- a/OMCompiler/Parser/BaseModelica_Lexer.g +++ b/OMCompiler/Parser/BaseModelica_Lexer.g @@ -428,7 +428,7 @@ IDENT2 : NONDIGIT (NONDIGIT | DIGIT)* | '$cpuTime'; fragment QIDENT : - '\'' (QCHAR | SESCAPE)* '\'' ; + '\'' (QCHAR | SESCAPE) (QCHAR | SESCAPE)* '\'' ; fragment QCHAR : (DIGIT | NONDIGIT | '!' | '#' | '$' | '%' | '&' | '(' | ')' diff --git a/OMCompiler/SimulationRuntime/c/simulation/results/simulation_result_csv.cpp b/OMCompiler/SimulationRuntime/c/simulation/results/simulation_result_csv.cpp index ab91000eba4..c051d34d46c 100644 --- a/OMCompiler/SimulationRuntime/c/simulation/results/simulation_result_csv.cpp +++ b/OMCompiler/SimulationRuntime/c/simulation/results/simulation_result_csv.cpp @@ -48,8 +48,73 @@ #include #include +#define MAX_IDENT_LENGTH 4096 + extern "C" { +/** + * @brief Count the occurrences of a substring in a string + * + * @param str String to search. + * @param substr Substring to count in `str`. + * @return int Number of occurrences. + */ +int countSubstring(const char* str, const char* substr) { + int count = 0; + size_t substr_len = strlen(substr); + const char* ptr = str; + while ((ptr = strstr(ptr, substr)) != NULL) { + count++; + ptr += substr_len; + } + return count; +} + +/** + * @brief Escape CSV style. + * + * Escape double-quotes with another double-quote to handle quoted identifiers + * with double-quotes in original. Example: `'a"b'` --> `a""b` + * + * @param original Variable to escape characters into CSV style. + * @param replaced Buffer to store escaped version of `original`. + * @param n Size of buffer `replaced`. + */ +void csvEscapedString(const char* original, char* replaced, size_t n, threadData_t* threadData) +{ + size_t original_length = strlen(original); + + // Count the occurrences of \" + int num_occurrences = countSubstring(original, "\\\""); + size_t replaced_length = original_length + num_occurrences; + + if (replaced == NULL || n < replaced_length + 1) { + throwStreamPrint(threadData, "Buffer too small. Failed to escape identifier for CSV result file."); + return; + } + + size_t j = 0; + for (size_t i = 0; i < original_length; i++) { + if (original[i] == '"') { + replaced[j++] = '"'; + replaced[j++] = '"'; + } else { + replaced[j++] = original[i]; + } + } + + // Null-terminate the replaced string + replaced[j] = '\0'; + return; +} + +/** + * @brief Write CSV data row. + * + * @param self Simulation result. + * @param data Simulation data. + * @param threadData Thread data for error handling. + */ void omc_csv_emit(simulation_result *self, DATA *data, threadData_t *threadData) { FILE *fout = (FILE*) self->storage; @@ -112,6 +177,13 @@ void omc_csv_emit(simulation_result *self, DATA *data, threadData_t *threadData) rt_accumulate(SIM_TIMER_OUTPUT); } +/** + * @brief Write CSV header. + * + * @param self Simulation result. + * @param data Simulation data. + * @param threadData Thread data for error handling. + */ void omc_csv_init(simulation_result *self, DATA *data, threadData_t *threadData) { int i; @@ -119,29 +191,49 @@ void omc_csv_init(simulation_result *self, DATA *data, threadData_t *threadData) const char* format = ",\"%s\""; FILE *fout = omc_fopen(self->filename, "w"); + char escapedNameBuffer[MAX_IDENT_LENGTH]; assertStreamPrint(threadData, 0!=fout, "Error, couldn't create output file: [%s] because of %s", self->filename, strerror(errno)); fprintf(fout, "\"time\""); - if(self->cpuTime) + if(self->cpuTime) { fprintf(fout, format, "$cpuTime"); - for(i = 0; i < mData->nVariablesReal; i++) if(!mData->realVarsData[i].filterOutput) - fprintf(fout, format, mData->realVarsData[i].info.name); - for(i = 0; i < mData->nVariablesInteger; i++) if(!mData->integerVarsData[i].filterOutput) - fprintf(fout, format, mData->integerVarsData[i].info.name); - for(i = 0; i < mData->nVariablesBoolean; i++) if(!mData->booleanVarsData[i].filterOutput) - fprintf(fout, format, mData->booleanVarsData[i].info.name); - //for(i = 0; i < mData->nVariablesString; i++) if(!mData->stringVarsData[i].filterOutput) - // fprintf(fout, format, mData->stringVarsData[i].info.name); - - for(i = 0; i < mData->nAliasReal; i++) if(!mData->realAlias[i].filterOutput && data->modelData->realAlias[i].aliasType != 1) - fprintf(fout, format, mData->realAlias[i].info.name); - for(i = 0; i < mData->nAliasInteger; i++) if(!mData->integerAlias[i].filterOutput && data->modelData->integerAlias[i].aliasType != 1) - fprintf(fout, format, mData->integerAlias[i].info.name); - for(i = 0; i < mData->nAliasBoolean; i++) if(!mData->booleanAlias[i].filterOutput && data->modelData->booleanAlias[i].aliasType != 1) - fprintf(fout, format, mData->booleanAlias[i].info.name); - //for(i = 0; i < mData->nAliasString; i++) if(!mData->stringAlias[i].filterOutput && data->modelData->stringAlias[i].aliasType != 1) - // fprintf(fout, format, mData->stringAlias[i].info.name); + } + for(i = 0; i < mData->nVariablesReal; i++) if(!mData->realVarsData[i].filterOutput) { + csvEscapedString(mData->realVarsData[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + for(i = 0; i < mData->nVariablesInteger; i++) { + if(!mData->integerVarsData[i].filterOutput) { + csvEscapedString(mData->integerVarsData[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + } + for(i = 0; i < mData->nVariablesBoolean; i++) { + if(!mData->booleanVarsData[i].filterOutput) { + csvEscapedString(mData->booleanVarsData[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + } + + for(i = 0; i < mData->nAliasReal; i++) { + if(!mData->realAlias[i].filterOutput && data->modelData->realAlias[i].aliasType != 1) { + csvEscapedString(mData->realAlias[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + } + for(i = 0; i < mData->nAliasInteger; i++) { + if(!mData->integerAlias[i].filterOutput && data->modelData->integerAlias[i].aliasType != 1) { + csvEscapedString(mData->integerAlias[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + } + for(i = 0; i < mData->nAliasBoolean; i++) { + if(!mData->booleanAlias[i].filterOutput && data->modelData->booleanAlias[i].aliasType != 1) { + csvEscapedString(mData->booleanAlias[i].info.name, escapedNameBuffer, MAX_IDENT_LENGTH, threadData); + fprintf(fout, format, escapedNameBuffer); + } + } fprintf(fout, "\n"); self->storage = fout; } diff --git a/testsuite/flattening/modelica/mosfiles/Makefile b/testsuite/flattening/modelica/mosfiles/Makefile index d99d2734dda..d8e2872a7f2 100644 --- a/testsuite/flattening/modelica/mosfiles/Makefile +++ b/testsuite/flattening/modelica/mosfiles/Makefile @@ -29,6 +29,7 @@ NotbasicType.mos NotbasicType2.mos \ OverloadingFunc.mos \ QuotedFunction.mos \ QuotedIdentifier.mos \ +QuotedIdentifierCSV.mos \ Return.mos \ StringArrayReturn.mos \ TestLoadModel.mos \ diff --git a/testsuite/flattening/modelica/mosfiles/QuotedIdentifier.mo b/testsuite/flattening/modelica/mosfiles/QuotedIdentifier.mo index ff7a80b7371..927946db9ec 100644 --- a/testsuite/flattening/modelica/mosfiles/QuotedIdentifier.mo +++ b/testsuite/flattening/modelica/mosfiles/QuotedIdentifier.mo @@ -4,7 +4,6 @@ model QuotedIdentifier Real 'e"f g'(start = 1, fixed = true); Real 'h\'i //j'(start = 1, fixed = true); Real '\\\\\''(start = 1, fixed = true); - Real ''(start = 1, fixed = true); Real '*/ no code injection'(start = 1, fixed = true); Real '\''(start = 1, fixed = true); Real /*(y)*/ 'stupid,name'(start = 1, fixed = true); @@ -14,7 +13,6 @@ equation der('e"f g') = 2; der('h\'i //j') = -2; der('\\\\\'') = 3; - der('') = -3; der('*/ no code injection') = 4; der('\'') = -4; der('stupid,name') = 5; diff --git a/testsuite/flattening/modelica/mosfiles/QuotedIdentifierCSV.mos b/testsuite/flattening/modelica/mosfiles/QuotedIdentifierCSV.mos new file mode 100644 index 00000000000..8caf98ef300 --- /dev/null +++ b/testsuite/flattening/modelica/mosfiles/QuotedIdentifierCSV.mos @@ -0,0 +1,31 @@ +// name: QuotedIdentifierCSV +// status: correct +// teardown_command: rm -rf QuotedIdentifierCSV_tmp +// +// Test CSV result file generation of variables containing commas. + +loadFile("QuotedIdentifier.mo");getErrorString(); + +echo(false); +mkdir("QuotedIdentifierCSV_tmp"); cd("QuotedIdentifierCSV_tmp"); +echo(true); + +simulate(QuotedIdentifier, outputFormat="csv"); getErrorString(); + +readSimulationResultVars("QuotedIdentifier_res.csv"); getErrorString(); + +// Result: +// true +// "" +// true +// record SimulationResult +// resultFile = "QuotedIdentifier_res.csv", +// simulationOptions = "startTime = 0.0, stopTime = 1.0, numberOfIntervals = 500, tolerance = 1e-6, method = 'dassl', fileNamePrefix = 'QuotedIdentifier', options = '', outputFormat = 'csv', variableFilter = '.*', cflags = '', simflags = ''", +// messages = "LOG_SUCCESS | info | The initialization finished successfully without homotopy method. +// LOG_SUCCESS | info | The simulation finished successfully. +// " +// end SimulationResult; +// "" +// {"time","'*/ no code injection'","'\\''","'\\\\\\\\\\''","'a\"b'","'c d'","'e\"f g'","'h\\'i //j'","'stupid,name'","der('*/ no code injection')","der('\\'')","der('\\\\\\\\\\'')","der('a\"b')","der('c d')","der('e\"f g')","der('h\\'i //j')","der('stupid,name')"} +// "" +// endResult diff --git a/testsuite/openmodelica/fmi/ModelExchange/2.0/.gitignore b/testsuite/openmodelica/fmi/ModelExchange/2.0/.gitignore new file mode 100644 index 00000000000..b210b46c865 --- /dev/null +++ b/testsuite/openmodelica/fmi/ModelExchange/2.0/.gitignore @@ -0,0 +1 @@ +/QuotedIdentifier_tmp/ diff --git a/testsuite/openmodelica/fmi/ModelExchange/2.0/Makefile b/testsuite/openmodelica/fmi/ModelExchange/2.0/Makefile index 4d895b61ebb..8f28df1a279 100644 --- a/testsuite/openmodelica/fmi/ModelExchange/2.0/Makefile +++ b/testsuite/openmodelica/fmi/ModelExchange/2.0/Makefile @@ -24,6 +24,7 @@ fmi_attributes_20.mos \ fmi_attributes_21.mos \ fmi_attributes_22.mos \ FMUResourceTest.mos \ +QuotedIdentifierExport.mos \ testBug2764.mos \ testBug2765.mos \ testBug3049.mos \ diff --git a/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifier.mo b/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifier.mo new file mode 100644 index 00000000000..fb87bf6273b --- /dev/null +++ b/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifier.mo @@ -0,0 +1,21 @@ +model QuotedIdentifier + Real 'a"b'(start = 1, fixed = true); + Real 'c d'(start = 2, fixed = true); + Real 'e"f g'(start = 3, fixed = true); + Real 'h\'i //j'(start = 4, fixed = true); + Real '\\\\\''(start = 5, fixed = true); + Real '<&>'(start = 6, fixed = true); + Real '*/ no code injection'(start = 7, fixed = true); + Real '\''(start = 8, fixed = true); + Real /*(y)*/ 'stupid,name'(start = 9, fixed = true); +equation + der('a"b') = 1; + der('c d') = -1; + der('e"f g') = 2; + der('h\'i //j') = -2; + der('\\\\\'') = 3; + der('<&>') = -3; + der('*/ no code injection') = 4; + der('\'') = -4; + der('stupid,name') = 5; +end QuotedIdentifier; diff --git a/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifierExport.mos b/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifierExport.mos new file mode 100644 index 00000000000..49dcf303dd6 --- /dev/null +++ b/testsuite/openmodelica/fmi/ModelExchange/2.0/QuotedIdentifierExport.mos @@ -0,0 +1,174 @@ +// name: QuotedIdentifier +// status: correct +// teardown_command: rm -rf QuotedIdentifier_tmp +// +// Test compilation of C FMU containing quoted identifiers. +// Test modelDescription.xml has valid XML code. +// Test simulation with OMSimulator. + +loadFile("QuotedIdentifier.mo");getErrorString(); + +echo(false); +mkdir("QuotedIdentifier_tmp"); cd("QuotedIdentifier_tmp"); +echo(true); + +buildModelFMU(QuotedIdentifier); getErrorString(); + +// Get ModelVariables part from modelDescription +system("unzip -cqq QuotedIdentifier.fmu modelDescription.xml > QuotedIdentifier_modelDescription_tmp.xml"); getErrorString(); +system("sed -n \"//,/<\\/ModelVariables>/p\" QuotedIdentifier_modelDescription_tmp.xml > QuotedIdentifier_modelDescription.xml"); getErrorString(); +readFile("QuotedIdentifier_modelDescription.xml"); + +// Simulate with OMSimulator +system(getInstallationDirectoryPath() + "/bin/OMSimulator QuotedIdentifier.fmu --mode=me --tolerance=1e-6 --resultFile=\"QuotedIdentifier_dynamic_res.mat\" --stopTime=1.0 --suppressPath=true --tempDir=\"QuotedIdentifier-dynamic-tmp\"", "QuotedIdentifier_me_systemCall.log"); getErrorString(); +readFile("QuotedIdentifier_me_systemCall.log"); + + +// Result: +// true +// "" +// true +// "QuotedIdentifier.fmu" +// "" +// 0 +// "" +// 0 +// "" +// " +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// " +// 0 +// "" +// "info: maximum step size for 'model.root': 0.002000 +// info: Result file: QuotedIdentifier_dynamic_res.mat (bufferSize=1) +// info: Final Statistics for 'model.root': +// NumSteps = 501 NumRhsEvals = 502 NumLinSolvSetups = 26 +// NumNonlinSolvIters = 501 NumNonlinSolvConvFails = 0 NumErrTestFails = 0 +// " +// endResult