1 /*
2   This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.
3 
4   Author: Uwe Schulzweida
5 
6 */
7 
8 #include <cstring>
9 #include <cstdlib>
10 #include <vector>
11 
12 #include "pmlist.h"
13 #include "namelist.h"
14 #include "cdo_output.h"
15 
16 static void
keyValuesPrint(const KeyValues & keyValues)17 keyValuesPrint(const KeyValues &keyValues)
18 {
19   printf("  %s =", keyValues.key.c_str());
20   for (int i = 0; i < keyValues.nvalues; ++i) printf(" '%s'", keyValues.values[i].c_str());
21   printf("\n");
22 }
23 
24 void
print() const25 KVList::print() const
26 {
27   for (const auto &keyValues : *this) keyValuesPrint(keyValues);
28 }
29 
30 int
parse_arguments(const int argc,const std::vector<std::string> & argv)31 KVList::parse_arguments(const int argc, const std::vector<std::string> &argv)
32 {
33   // Assume key = value pairs. That is, if argv[i] contains no '=' then treat it as if it belongs to the values of argv[i-1].
34   char key[256];
35   int i = 0;
36   while (i < argc)
37     {
38       const char *currentArgv = argv[i].c_str();
39       const char *end = strchr(currentArgv, '=');
40       if (end == nullptr)
41         {
42           fprintf(stderr, "Missing '=' in key/value string: >%s<\n", currentArgv);
43           return -1;
44         }
45 
46       snprintf(key, sizeof(key), "%.*s", (int) (end - currentArgv), currentArgv);
47       key[sizeof(key) - 1] = 0;
48 
49       int j = 1;
50       while (i + j < argc && strchr(argv[i + j].c_str(), '=') == nullptr) j++;
51 
52       int nvalues = j;
53 
54       KeyValues kv;
55       kv.values.resize(1);
56       kv.values[0] = end + 1;
57       if (kv.values[0][0] == 0) nvalues = 0;
58 
59       kv.key = key;
60       kv.nvalues = nvalues;
61       kv.values.resize(nvalues);
62 
63       for (j = 1; j < nvalues; ++j) kv.values[j] = argv[i + j];
64       this->push_back(kv);
65 
66       i += j;
67     }
68 
69   return 0;
70 }
71 
72 const KeyValues *
search(const std::string & key) const73 KVList::search(const std::string &key) const
74 {
75   for (const auto &kv : *this)
76     {
77       if (kv.key == key) return &kv;
78     }
79 
80   return nullptr;
81 }
82 
83 char *
get_first_value(const char * key,const char * replacer)84 KVList::get_first_value(const char *key, const char *replacer)
85 {
86   auto kv = this->search(key);
87   if (kv && kv->nvalues > 0) return (char *) kv->values[0].c_str();
88   if (replacer)
89     return (char *) replacer;
90   else
91     return nullptr;
92 }
93 
94 void
append(const char * key,const char * const * values,int nvalues)95 KVList::append(const char *key, const char *const *values, int nvalues)
96 {
97   KeyValues kv;
98   kv.key = strdup(key);
99   kv.nvalues = nvalues;
100   kv.values.resize(nvalues);
101   for (int i = 0; i < nvalues; ++i) kv.values[i] = strdup(values[i]);
102   this->push_back(kv);
103 }
104 
105 // Remove only one list item
106 void
remove(const std::string & inkey)107 KVList::remove(const std::string &inkey)
108 {
109   std::list<KeyValues>::iterator i;
110   for (i = this->begin(); i != this->end(); ++i)
111     if (i->key == inkey) break;
112   if (i->key == inkey) this->erase(i);
113 }
114 
115 const KVList *
searchKVListVentry(const std::string & key,const std::string & value,const std::vector<std::string> & entry)116 PMList::searchKVListVentry(const std::string &key, const std::string &value, const std::vector<std::string> &entry)
117 {
118   for (const auto &kvlist : *this)
119     {
120       for (const auto &s : entry)
121         if (kvlist.name == s)
122           {
123             auto kv = kvlist.search(key);
124             if (kv && kv->nvalues > 0 && kv->values[0] == value) return &kvlist;
125           }
126     }
127 
128   return nullptr;
129 }
130 
131 const KVList *
getKVListVentry(const std::vector<std::string> & entry)132 PMList::getKVListVentry(const std::vector<std::string> &entry)
133 {
134   for (const auto &kvlist : *this)
135     {
136       for (const auto &s : entry)
137         if (kvlist.name == s) return &kvlist;
138     }
139 
140   return nullptr;
141 }
142 
143 static void
KVListAppendNamelist(KVList & kvlist,const char * key,const char * buffer,NamelistToken * t,int nvalues,bool cdocmor)144 KVListAppendNamelist(KVList &kvlist, const char *key, const char *buffer, NamelistToken *t, int nvalues, bool cdocmor)
145 {
146   std::vector<char> value;
147   KeyValues kv;
148   kv.key = key;
149   kv.nvalues = nvalues;
150   if (nvalues > 0) kv.values.resize(nvalues);
151 
152   for (int i = 0; i < nvalues; ++i)
153     {
154       const size_t len = t[i].end - t[i].start;
155       /** CMOR_MAX_STRING cannot be used **/
156       if (cdocmor && len > 1024) cdo_abort("A string value is larger than the maximum size allowed by CMOR (1024 signs).");
157 
158       if (value.size() < (len + 1)) value.resize(len + 1);
159       const auto pval = buffer + t[i].start;
160 
161       if (cdocmor && strcmp(key, "code") == 0 && !strstr(pval, ","))
162         {
163           value.resize(4);
164           const auto code = atol(pval);
165           if (code > 0 && code < 1000)
166             snprintf(value.data(), 4, "%03ld", code);
167           else
168             cdo_warning("In parsing a line of a file:\n          "
169                         "Codes could not be transformed into the code format (three digit integer). Codes wont be used.");
170         }
171       else
172         {
173           memcpy(value.data(), pval, len);
174           value[len] = 0;
175         }
176 
177       kv.values[i] = value.data();
178     }
179 
180   kvlist.push_back(std::move(kv));
181 }
182 
183 static unsigned long
get_number_of_values(const unsigned long ntok,const NamelistToken * tokens)184 get_number_of_values(const unsigned long ntok, const NamelistToken *tokens)
185 {
186   unsigned long it;
187 
188   for (it = 0; it < ntok; ++it)
189     {
190       const auto type = tokens[it].type;
191       if (type != NamelistType::WORD && type != NamelistType::STRING) break;
192     }
193 
194   return it;
195 }
196 
197 static void
replace_name(char * name)198 replace_name(char *name)
199 {
200   for (size_t pos = 0; pos < strlen(name); pos++) name[pos] = tolower(name[pos]);
201 
202   if (strcmp(name, "conventions") == 0) strcpy(name, "Conventions");
203   if (strcmp(name, "cn") == 0) strcpy(name, "cmor_name");
204   if (strcmp(name, "c") == 0) strcpy(name, "code");
205   if (strcmp(name, "n") == 0) strcpy(name, "name");
206   if (strcmp(name, "pmt") == 0) strcpy(name, "project_mip_table");
207   if (strcmp(name, "cordex_domain") == 0) strcpy(name, "CORDEX_domain");
208   if (strcmp(name, "char_axis_landuse") == 0) strcpy(name, "char_axis_landUse");
209   if (strcmp(name, "_formula_var_file") == 0) strcpy(name, "_FORMULA_VAR_FILE");
210   if (strcmp(name, "_axis_entry_file") == 0) strcpy(name, "_AXIS_ENTRY_FILE");
211 }
212 
213 int
parse_namelist(PMList & pmlist,NamelistParser & parser,char * buf,bool cdocmor)214 parse_namelist(PMList &pmlist, NamelistParser &parser, char *buf, bool cdocmor)
215 {
216   char name[4096];
217   KVList kvlist;
218   auto &tokens = parser.tokens;
219   const auto ntok = parser.toknext;
220 
221   for (unsigned long it = 0; it < ntok; ++it)
222     {
223       const auto &t = tokens[it];
224       // printf("Token %u", it+1);
225       if (t.type == NamelistType::OBJECT)
226         {
227           name[0] = 0;
228           if (it + 1 < ntok && tokens[it + 1].type == NamelistType::WORD)
229             {
230               it++;
231               const auto &t2 = tokens[it];
232               snprintf(name, sizeof(name), "%.*s", (int)(t2.end - t2.start), buf + t2.start);
233               name[sizeof(name) - 1] = 0;
234             }
235 
236           if (kvlist.size())
237             {
238               pmlist.push_back(kvlist);
239               kvlist.clear();
240             }
241 
242           kvlist.name = name;
243         }
244       else if (t.type == NamelistType::KEY)
245         {
246           // printf(" key >%.*s<\n", t.end - t.start, buf+t.start);
247           snprintf(name, sizeof(name), "%.*s", (int)(t.end - t.start), buf + t.start);
248           name[sizeof(name) - 1] = 0;
249           const auto nvalues = get_number_of_values(ntok - it - 1, &tokens[it + 1]);
250 
251           if (cdocmor)
252             {
253               if (nvalues == 0)
254                 {
255                   cdo_warning("Could not find values for key '%s'.", name);
256                   continue;
257                 }
258               replace_name(name);
259             }
260 
261           KVListAppendNamelist(kvlist, name, buf, &tokens[it + 1], nvalues, cdocmor);
262           it += nvalues;
263         }
264       else
265         {
266           // printf(" token >%.*s<\n", (int)(t.end - t.start), buf+t.start);
267           break;
268         }
269     }
270 
271   if (kvlist.size()) pmlist.push_back(kvlist);
272 
273   return 0;
274 }
275 
276 int
parse_list_buffer(NamelistParser & p,ListBuffer & listBuffer)277 parse_list_buffer(NamelistParser &p, ListBuffer &listBuffer)
278 {
279   const char *errMsg = "Namelist error";
280   const auto name = listBuffer.name.c_str();
281 
282   const auto status = p.parse(listBuffer.buffer.data(), listBuffer.buffer.size());
283   if (status != NamelistError::UNDEFINED)
284     {
285       switch (status)
286         {
287         case NamelistError::INVAL:
288           fprintf(stderr, "%s: Invalid character in %s (line=%lu character='%c' dec=%u)!\n", errMsg, name, p.lineno,
289                   listBuffer.buffer[p.pos], (unsigned char) listBuffer.buffer[p.pos]);
290           break;
291         case NamelistError::PART: fprintf(stderr, "%s: End of string not found in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
292         case NamelistError::INKEY: fprintf(stderr, "%s: Invalid keyword in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
293         case NamelistError::INTYP: fprintf(stderr, "%s: Invalid keyword type in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
294         case NamelistError::INOBJ: fprintf(stderr, "%s: Invalid object in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
295         case NamelistError::EMKEY: fprintf(stderr, "%s: Empty key name in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
296         default: fprintf(stderr, "%s in %s (line=%lu)!\n", errMsg, name, p.lineno); break;
297         }
298       cdo_abort("%s!", errMsg);
299     }
300 
301   // p.dump(listBuffer.buffer.data());
302   if (p.verify())
303     {
304       fprintf(stderr, "%s: Invalid contents in %s!\n", errMsg, name);
305       cdo_abort("Namelist error!");
306     }
307 
308   return 0;
309 }
310 
311 void
read_namelist(FILE * fp,const char * name)312 PMList::read_namelist(FILE *fp, const char *name)
313 {
314   ListBuffer listBuffer;
315   if (listBuffer.read(fp, name)) cdo_abort("Read error on namelist %s!", name);
316 
317   NamelistParser p;
318   const auto status = parse_list_buffer(p, listBuffer);
319   if (status) cdo_abort("Namelist not found!");
320 
321   parse_namelist(*this, p, listBuffer.buffer.data(), false);
322 }
323 
324 void
print()325 PMList::print()
326 {
327   for (const auto &kvlist : *this)
328     {
329       const auto listname = kvlist.name.c_str();
330       printf("\nFound %s list with %zu key/values: \n", listname ? listname : "", kvlist.size());
331       kvlist.print();
332     }
333 }
334