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 	
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