1 // This is mul/mbl/mbl_read_multi_props.cxx
2 #include <sstream>
3 #include <iostream>
4 #include <string>
5 #include <cctype>
6 #include <utility>
7 #include <iterator>
8 #include "mbl_read_multi_props.h"
9 //:
10 // \file
11 
12 #include "vsl/vsl_indent.h"
13 #ifdef _MSC_VER
14 #  include "vcl_msvc_warnings.h"
15 #endif
16 
17 #include <mbl/mbl_parse_block.h>
18 #include <mbl/mbl_exception.h>
19 
mbl_read_multi_props_print(std::ostream & afs,mbl_read_multi_props_type props)20 void mbl_read_multi_props_print(std::ostream &afs, mbl_read_multi_props_type props)
21 {
22   afs << vsl_indent() << "{\n";
23   vsl_indent_inc(afs);
24   for (auto & prop : props)
25     afs << vsl_indent() << prop.first << ": " << prop.second << '\n';
26   vsl_indent_dec(afs);
27   afs << vsl_indent() << "}\n";
28 }
29 
mbl_read_multi_props_print(std::ostream & afs,mbl_read_multi_props_type props,unsigned max_chars)30 void mbl_read_multi_props_print(std::ostream &afs, mbl_read_multi_props_type props, unsigned max_chars)
31 {
32   typedef mbl_read_multi_props_type::iterator ITER;
33   afs << vsl_indent() << "{\n";
34   vsl_indent_inc(afs);
35   for (auto & prop : props)
36   {
37     afs << vsl_indent() << prop.first << ": " << prop.second.substr(0, max_chars) << '\n';
38     if (max_chars < prop.second.size()) afs << vsl_indent() << "...\n";
39   }
40   vsl_indent_dec(afs);
41   afs << vsl_indent() << "}\n";
42 }
43 
strip_trailing_ws(std::string & s)44 static void strip_trailing_ws(std::string &s)
45 {
46   int p=s.length()-1;
47   while (p>0 && std::isspace(s[p])) --p;
48   s.erase(p+1);
49 }
50 
51 
52 //: Read properties from a text stream.
53 // The function will terminate on an eof. If one of
54 // the opening lines contains an opening brace '{', then the function
55 // will also stop reading the stream after finding a line containing
56 // a closing brace '}'
57 //
58 // Every property label ends in ":", and should not contain
59 // any whitespace.
60 // Differs from mbl_read_multi_props(afs) in that all whitespace is treated
61 // as a separator.
62 // If there is a brace after the first string following the label,
63 // the following text up to matching
64 // braces is included in the property value.
65 // Each property label should not contain
66 // any whitespace.
mbl_read_multi_props_ws(std::istream & afs)67 mbl_read_multi_props_type mbl_read_multi_props_ws(std::istream &afs)
68 {
69   if (!afs) return mbl_read_multi_props_type();
70 
71   std::string label, str1;
72 
73   while ( afs>>std::ws, !afs.eof() )
74   {
75     afs >> label;
76     if (label.substr(0,2) =="//")
77     {
78       // Comment line, so read to end
79       std::getline( afs, str1 );
80     }
81     else break;
82   }
83 
84   bool need_closing_brace = false;
85 
86   if (label[0] == '{')
87   {
88     need_closing_brace = true;
89     label.erase(0,1);
90   }
91 
92   mbl_read_multi_props_type props;
93 
94   if ( label.empty() )
95   {
96     afs >> std::ws;
97 
98     // Several tests with Borland 5.5.1 fail because this next
99     // statement 'afs >> label;' moves past the '\n' char when the
100     // next section of the stream looks like "//comment\n a: a".  With
101     // Borland 5.5.1, after this statement, afs.peek() returns 32
102     // (space), while other compilers it returns 10 ('\n').  Seems
103     // like a Borland standard library problem.  -Fred Wheeler
104 
105     afs >> label;
106 
107     // std::cout << "debug label " << label << std::endl
108     //          << "debug peek() " << afs.peek() << std::endl;
109   }
110 
111   typedef mbl_read_multi_props_type::iterator ITER;
112   std::string last_label( label );
113   auto last_label_iter = props.end();
114 
115   do
116   {
117     if ( label.substr(0,2) =="//" )
118     {
119       // Comment line, so read to end
120       std::getline(afs, str1);
121     }
122     else if ( need_closing_brace && label[0] == '}' )
123     {
124       // Strip rest of line
125       return props;
126     }
127     else if ( !label.empty() )
128     {
129       if ( label.size() > 1 &&
130            label[label.size() -1] == ':' )
131       {
132         label.erase( label.size() -1, 1 );
133         afs >> std::ws >> str1;
134 
135         if ( str1.substr(0,1) == "{" )
136           str1 = mbl_parse_block(afs, true);
137 
138         strip_trailing_ws(str1);
139         last_label_iter = props.insert(std::make_pair(label, str1));
140         last_label = label;
141       }
142       else if ( label.substr(0,1) == "{" )
143       {
144         std::string block = mbl_parse_block( afs, true );
145         if ( block.substr(0,2) != "{}" )
146         {
147           if (last_label_iter == props.end())
148           {
149             props.insert(std::make_pair(last_label, str1));
150             last_label_iter = props.insert(std::make_pair(last_label, std::string(" ")+block));
151           }
152           else
153           {
154             last_label_iter->second += " ";
155             last_label_iter->second += block;
156           }
157         }
158       }
159       else
160       {
161         char c;
162         afs >> std::ws;
163         afs >> c;
164 
165         if (c != ':')
166         {
167           std::getline(afs, str1);
168           // The next loop replaces any characters outside the ASCII range
169           // 32-126 with their XML equivalent, e.g. a TAB with &#9;
170           // This is necessary for the tests dashboard since otherwise the
171           // the Dart server gives up on interpreting the XML file sent. - PVr
172           for (int i=-1; i<256; ++i)
173           {
174             char c= i<0 ? '&' : char(i); std::string s(1,c); // first do '&'
175             if (i>=32 && i<127 && c!='<')
176               continue; // keep "normal" chars
177 
178             std::ostringstream os; os << "&#" << (i<0?int(c):i) << ';';
179             std::string::size_type pos;
180 
181             while ((pos=str1.find(s)) != std::string::npos)
182               str1.replace(pos,1,os.str());
183 
184             while ((pos=label.find(s)) != std::string::npos)
185               label.replace(pos,1,os.str());
186           }
187           mbl_exception_warning(
188             mbl_exception_read_props_parse_error( std::string(
189               "Could not find colon ':' separator while reading line ")
190               + label + " " + str1) );
191           return props;
192         }
193       }
194     }
195 
196     afs >> std::ws >> label;
197   }
198 
199   while ( !afs.eof() );
200 
201   if ( need_closing_brace && label != "}" )
202     mbl_exception_warning(
203       mbl_exception_read_props_parse_error( std::string(
204         "Unexpected end of file while "
205         "looking for '}'. Last read string = \"")
206         + label + '"') );
207 
208   return props;
209 }
210 
211 
212 //: merge two property sets.
213 // \param first_overrides
214 // properties in "a" will override identically named properties in "b"
mbl_read_multi_props_merge(const mbl_read_multi_props_type & a,const mbl_read_multi_props_type & b,bool first_overrides)215 mbl_read_multi_props_type mbl_read_multi_props_merge(const mbl_read_multi_props_type& a,
216                                          const mbl_read_multi_props_type& b,
217                                          bool first_overrides/*=true*/)
218 {
219   mbl_read_multi_props_type output;
220 
221   auto a_it = a.begin();
222   auto b_it = b.begin();
223 
224 
225   while (a_it != a.end() || b_it != b.end())
226   {
227     if (a_it == a.end())
228       output.insert(*(b_it++));
229     else if (b_it == b.end())
230       output.insert(*(a_it++));
231     else if (a_it->first < b_it->first)
232       output.insert(*(a_it++));
233     else if (a_it->first > b_it->first)
234       output.insert(*(b_it++));
235     else
236     {
237       if (first_overrides)
238         output.insert(*a_it);
239       else
240         output.insert(*b_it);
241       ++a_it; ++b_it;
242     }
243   }
244   return output;
245 }
246 
247 //: Throw error if there are any keys in props that aren't in ignore.
248 // \throw mbl_exception_unused_props
mbl_read_multi_props_look_for_unused_props(const std::string & function_name,const mbl_read_multi_props_type & props,const mbl_read_multi_props_type & ignore)249 void mbl_read_multi_props_look_for_unused_props(
250   const std::string & function_name,
251   const mbl_read_multi_props_type &props,
252   const mbl_read_multi_props_type &ignore)
253 {
254   mbl_read_multi_props_type p2(props);
255 
256   // Remove ignoreable properties
257   for (const auto & it : ignore)
258     p2.erase(it.first);
259 
260   if (!p2.empty())
261   {
262     std::ostringstream ss;
263     mbl_read_multi_props_print(ss, p2, 1000);
264     mbl_exception_error(mbl_exception_unused_props(function_name, ss.str()));
265   }
266 }
267 
268 
269 //: Return a single expected value of the given property \a label.
270 // The matching entry is removed from the property list.
271 // \throws mbl_exception_missing_property if \a label doesn't exist.
272 // \throws mbl_exception_read_props_parse_error if there are two or more values of \a label.
get_required_property(const std::string & label)273 std::string mbl_read_multi_props_type::get_required_property(const std::string& label)
274 {
275   std::pair<mbl_read_multi_props_type::iterator, mbl_read_multi_props_type::iterator>
276     its = this->equal_range(label);
277   if (its.first==its.second)
278     mbl_exception_error(mbl_exception_missing_property(label));
279   else if (std::distance(its.first, its.second) > 1)
280     mbl_exception_error(mbl_exception_read_props_parse_error(
281       std::string("Property label \"") + label + "\" occurs more than once.") );
282 
283   std::string s = its.first->second;
284   this->erase(its.first);
285   return s;
286 }
287 
288 
289 //: Return a single value of the given property \a label.
290 // The matching entry is removed from the property list.
291 // returns empty string or \a default_prop if \a label doesn't exist.
292 // \throws mbl_exception_read_props_parse_error if there are two or more values of \a label.
get_optional_property(const std::string & label,const std::string & default_prop)293 std::string mbl_read_multi_props_type::get_optional_property(
294   const std::string& label, const std::string& default_prop /*=""*/)
295 {
296   std::pair<mbl_read_multi_props_type::iterator, mbl_read_multi_props_type::iterator>
297     its = this->equal_range(label);
298   if (its.first==its.second) return default_prop;
299   else if (std::distance(its.first, its.second) > 1)
300     mbl_exception_error(mbl_exception_read_props_parse_error(
301       std::string("Property label \"") + label + "\" occurs more than once.") );
302 
303   std::string s = its.first->second;
304   this->erase(its.first);
305   return s;
306 }
307 
308 
309 // Return a vector of all values for a given property label.
310 // Throw exception if label doesn't occur at least once.
get_required_properties(const std::string & label,std::vector<std::string> & values,const unsigned nmax,const unsigned nmin)311 void mbl_read_multi_props_type::get_required_properties(
312   const std::string& label,
313   std::vector<std::string>& values,
314   const unsigned nmax/*=-1*/, //=max<unsigned>
315   const unsigned nmin/*=1*/)
316 {
317   values.clear();
318 
319   auto beg = this->lower_bound(label);
320   auto fin = this->upper_bound(label);
321   if (beg==fin)
322     mbl_exception_error(mbl_exception_missing_property(label));
323   for (auto it=beg; it!=fin; ++it)
324   {
325     values.push_back(it->second);
326   }
327 
328   const unsigned nval = values.size();
329   if (nval<nmin)
330   {
331     const std::string msg = "property label \"" + label + "\" occurs too few times.";
332     mbl_exception_error(mbl_exception_read_props_parse_error(msg));
333   }
334   if (nval>nmax)
335   {
336     const std::string msg = "property label \"" + label + "\" occurs too many times.";
337     mbl_exception_error(mbl_exception_read_props_parse_error(msg));
338   }
339 
340   this->erase(beg, fin);
341 }
342 
343 
344 // Return a vector of all values for a given property label.
345 // Vector is empty if label doesn't occur at least once.
get_optional_properties(const std::string & label,std::vector<std::string> & values,const unsigned nmax)346 void mbl_read_multi_props_type::get_optional_properties(
347   const std::string& label,
348   std::vector<std::string>& values,
349   const unsigned nmax/*=-1*/) //=max<unsigned>
350 {
351   values.clear();
352 
353   auto beg = this->lower_bound(label);
354   auto fin = this->upper_bound(label);
355 
356   for (auto it=beg; it!=fin; ++it)
357   {
358     values.push_back(it->second);
359   }
360 
361   const unsigned nval = values.size();
362   if (nval>nmax)
363   {
364     const std::string msg = "property label \"" + label + "\" occurs too many times.";
365     mbl_exception_error(mbl_exception_read_props_parse_error(msg));
366   }
367 
368   this->erase(beg, fin);
369 }
370