1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2017-2018 Artem Bishev, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28
29 // Interface header.
30 #include "iesparser.h"
31
32 // Boost headers.
33 #include "boost/algorithm/string.hpp"
34 #include "boost/algorithm/cxx11/any_of.hpp"
35 #include "boost/lexical_cast.hpp"
36 #include "boost/regex.hpp"
37
38 // Standard headers.
39 #include <string>
40 #include <vector>
41
42 // Foundation headers.
43 #include "foundation/math/scalar.h"
44 #include "foundation/utility/countof.h"
45 #include "foundation/utility/foreach.h"
46
47 namespace foundation
48 {
49
50 const char* const IESParser::KeywordLineRegex = "\\[(\\w*)\\]\\s*(\\S.*)?";
51 const char* const IESParser::TiltLineRegex = "TILT\\s*=\\s*(\\S.*)";
52
53
54 //
55 // Auxiliary functions.
56 //
57
58 namespace
59 {
60 // Predicate that checks if the value is negative.
61 template <typename ValueType>
is_negative(const ValueType & type)62 bool is_negative(const ValueType& type)
63 {
64 return type < ValueType(0);
65 }
66
67 // Lexical cast without exception.
68 template <typename ValueType>
string_to_number(const std::string & str,ValueType * value)69 bool string_to_number(const std::string& str, ValueType* value)
70 {
71 try
72 {
73 *value = boost::lexical_cast<ValueType>(str);
74 }
75 catch (boost::bad_lexical_cast&)
76 {
77 return false;
78 }
79 return true;
80 }
81
82 // Convert string to positive number.
83 template <typename ValueType>
string_to_positive_number(const std::string & str,ValueType * value)84 bool string_to_positive_number(const std::string& str, ValueType* value)
85 {
86 try
87 {
88 *value = boost::lexical_cast<ValueType>(str);
89 }
90 catch (boost::bad_lexical_cast&)
91 {
92 return false;
93 }
94 if (*value <= ValueType(0))
95 {
96 return false;
97 }
98 return true;
99 }
100
101 // Convert string to photometric type.
string_to_photometric_type(const std::string & str,IESParser::PhotometricType * type)102 bool string_to_photometric_type(
103 const std::string& str, IESParser::PhotometricType* type)
104 {
105 if (str == "1")
106 {
107 *type = IESParser::PhotometricTypeC;
108 }
109 else if (str == "2")
110 {
111 *type = IESParser::PhotometricTypeB;
112 }
113 else if (str == "3")
114 {
115 *type = IESParser::PhotometricTypeA;
116 }
117 else
118 {
119 return false;
120 }
121 return true;
122 }
123
124 // Convert string to units type.
string_to_units_type(const std::string & str,IESParser::LuminousOpeningShape::UnitsType * type)125 bool string_to_units_type(
126 const std::string& str, IESParser::LuminousOpeningShape::UnitsType* type)
127 {
128 if (str == "1")
129 {
130 *type = IESParser::LuminousOpeningShape::Feet;
131 }
132 else if (str == "2")
133 {
134 *type = IESParser::LuminousOpeningShape::Meters;
135 }
136 else
137 {
138 return false;
139 }
140 return true;
141 }
142
143 // Split string and convert it to the vector of values.
144 // Returns false, if one of the tokens cannot be converted
145 // to the value of specified type.
146 template <typename ValueType>
string_to_vector(const std::string & str,std::vector<ValueType> * output)147 bool string_to_vector(
148 const std::string& str, std::vector<ValueType>* output)
149 {
150 std::vector<std::string> tokens;
151 boost::split(tokens, str, isspace, boost::token_compress_on);
152
153 output->clear();
154 output->reserve(tokens.size());
155
156 for (each< std::vector<std::string>> token = tokens; token; ++token)
157 {
158 try
159 {
160 output->push_back(boost::lexical_cast<ValueType>(*token));
161 }
162 catch (boost::bad_lexical_cast&)
163 {
164 return false;
165 }
166 }
167
168 return true;
169 }
170
171 template <typename ValueType>
check_increasing_order(const std::vector<ValueType> & values)172 bool check_increasing_order(const std::vector<ValueType>& values)
173 {
174 for (size_t i = 1; i < values.size(); ++i)
175 {
176 if (values[i] <= values[i - 1])
177 return false;
178 }
179 return true;
180 }
181 }
182
183 //
184 // Class implementation.
185 //
186
Exception(const char * what,const int line)187 IESParser::Exception::Exception(const char* what, const int line)
188 : m_line_number(line)
189 {
190 set_what(format("{0} (line {1})", what, line).c_str());
191 }
192
IESParser()193 IESParser::IESParser()
194 : m_ignore_allowed_keywords(false)
195 , m_ignore_required_keywords(false)
196 , m_ignore_empty_lines(true)
197 , m_ignore_tilt(false)
198 {
199 }
200
parse(std::istream & input_stream)201 void IESParser::parse(std::istream& input_stream)
202 {
203 reset(input_stream);
204
205 // Parse version.
206 parse_format_version(input_stream);
207
208 // Parse block before TILT and TILT line.
209 parse_keywords_and_tilt(input_stream);
210
211 // Parse TILT data if it is included.
212 if (m_tilt_specification == IncludeTilt)
213 {
214 parse_tilt_data(input_stream);
215 }
216
217 // Parse the rest of the data.
218 parse_photometric_data(input_stream);
219 parse_angles(input_stream);
220 parse_candela_values(input_stream);
221
222 if (!input_stream.eof())
223 {
224 throw ParsingException("Expected end of file", m_line_counter);
225 }
226 }
227
is_keyword_allowed_by_iesna02(const std::string & keyword)228 bool IESParser::is_keyword_allowed_by_iesna02(const std::string& keyword)
229 {
230 return
231 keyword == "TEST" ||
232 keyword == "TESTLAB" ||
233 keyword == "TESTDATE" ||
234 keyword == "TESTMETHOD" ||
235 keyword == "NEARFIELD" ||
236 keyword == "MANUFAC" ||
237 keyword == "LUMCAT" ||
238 keyword == "LUMINAIRE" ||
239 keyword == "LAMPCAT" ||
240 keyword == "LAMP" ||
241 keyword == "BALLAST" ||
242 keyword == "BALLASTCAT" ||
243 keyword == "MAINTCAT" ||
244 keyword == "DISTRIBUTION" ||
245 keyword == "FLASHAREA" ||
246 keyword == "COLORCONSTANT" ||
247 keyword == "LAMPPOSITION" ||
248 keyword == "ISSUEDATE" ||
249 keyword == "OTHER" ||
250 keyword == "SEARCH" ||
251 keyword == "MORE";
252 }
253
is_keyword_allowed_by_iesna95(const std::string & keyword)254 bool IESParser::is_keyword_allowed_by_iesna95(const std::string& keyword)
255 {
256 return
257 keyword == "TEST" ||
258 keyword == "DATE" ||
259 keyword == "NEARFIELD" ||
260 keyword == "MANUFAC" ||
261 keyword == "LUMCAT" ||
262 keyword == "LUMINAIRE" ||
263 keyword == "LAMPCAT" ||
264 keyword == "LAMP" ||
265 keyword == "BALLAST" ||
266 keyword == "BALLASTCAT" ||
267 keyword == "MAINTCAT" ||
268 keyword == "DISTRIBUTION" ||
269 keyword == "FLASHAREA" ||
270 keyword == "COLORCONSTANT" ||
271 keyword == "OTHER" ||
272 keyword == "SEARCH" ||
273 keyword == "MORE" ||
274 keyword == "BLOCK" ||
275 keyword == "ENDBLOCK";
276 }
277
is_keyword_allowed_by_iesna91(const std::string & keyword)278 bool IESParser::is_keyword_allowed_by_iesna91(const std::string& keyword)
279 {
280 return
281 keyword == "TEST" ||
282 keyword == "DATE" ||
283 keyword == "MANUFAC" ||
284 keyword == "LUMCAT" ||
285 keyword == "LUMINAIRE" ||
286 keyword == "LAMPCAT" ||
287 keyword == "LAMP" ||
288 keyword == "BALLAST" ||
289 keyword == "BALLASTCAT" ||
290 keyword == "MAINTCAT" ||
291 keyword == "DISTRIBUTION" ||
292 keyword == "FLASHAREA" ||
293 keyword == "COLORCONSTANT" ||
294 keyword == "MORE";
295 }
296
reset(std::istream & input_stream)297 void IESParser::reset(std::istream& input_stream)
298 {
299 m_line_counter = 0;
300 m_line = "";
301 read_trimmed_line(input_stream);
302 m_keywords_dictionary.clear();
303 m_last_added_keyword = m_keywords_dictionary.end();
304 }
305
read_line(std::istream & input_stream)306 void IESParser::read_line(std::istream& input_stream)
307 {
308 m_line_counter++;
309 std::getline(input_stream, m_line);
310 }
311
read_trimmed_line(std::istream & input_stream)312 void IESParser::read_trimmed_line(std::istream& input_stream)
313 {
314 do
315 {
316 read_line(input_stream);
317 boost::trim(m_line);
318 } while (m_ignore_empty_lines && input_stream && m_line.empty());
319 }
320
parse_format_version(std::istream & input_stream)321 void IESParser::parse_format_version(std::istream& input_stream)
322 {
323 check_empty(input_stream);
324
325 if (m_line == "IESNA91")
326 {
327 m_format = Format1991;
328 }
329 else if (m_line == "IESNA:LM-63-1995")
330 {
331 m_format = Format1995;
332 }
333 else if (m_line == "IESNA:LM-63-2002")
334 {
335 m_format = Format2002;
336 }
337 else
338 {
339 // The first line is not a format line,
340 // which is possible only when the format is LM-63-1986.
341 m_format = Format1986;
342 // Do not read the next line, since the current one is not processed:
343 return;
344 }
345
346 read_trimmed_line(input_stream);
347 }
348
parse_keywords_and_tilt(std::istream & input_stream)349 void IESParser::parse_keywords_and_tilt(std::istream& input_stream)
350 {
351 m_last_added_keyword = m_keywords_dictionary.end();
352
353 bool tilt_reached = false;
354 while (!tilt_reached)
355 {
356 check_empty(input_stream);
357
358 if (is_keyword_line(m_line))
359 {
360 parse_keyword_line(m_line);
361 }
362 else if (is_tilt_line(m_line))
363 {
364 if (!m_ignore_required_keywords) check_required_keywords();
365 tilt_reached = true;
366 parse_tilt_line(m_line);
367 }
368 else
369 {
370 if (m_format != Format1986)
371 {
372 // LM-63-1986 format allows arbitrary data until TILT=<...> is reached.
373 // In any other case, raise the parsing exception:
374 throw ParsingException("Expected keyword line or TILT line", m_line_counter);
375 }
376 }
377
378 read_trimmed_line(input_stream);
379 }
380 }
381
parse_tilt_data(std::istream & input_stream)382 void IESParser::parse_tilt_data(std::istream& input_stream)
383 {
384 check_empty(input_stream);
385
386 // parse lamp-to-luminaire geometry
387 if (m_line == "1")
388 m_lamp_to_luminaire_geometry = VerticalGeometry;
389 else if (m_line == "2")
390 m_lamp_to_luminaire_geometry = HorizontalInvariantGeometry;
391 else if (m_line == "3")
392 m_lamp_to_luminaire_geometry = HorizontalNonInvariantGeometry;
393 else
394 {
395 throw ParsingException(
396 "Wrong lamp-to-luminaire geometry value, expected 1, 2 or 3", m_line_counter);
397 }
398
399 read_trimmed_line(input_stream);
400
401 check_empty(input_stream);
402
403 // parse number of tilt entries
404 int number_of_tilt_entries;
405 try
406 {
407 number_of_tilt_entries = boost::lexical_cast<int>(m_line);
408 }
409 catch (boost::bad_lexical_cast&)
410 {
411 throw ParsingException(
412 "Wrong number of tilt entries: expected an integer value", m_line_counter);
413 }
414 if (number_of_tilt_entries < 0)
415 {
416 throw ParsingException(
417 "Wrong number of tilt entries: "
418 "number of tilt entries must be non-negative", m_line_counter);
419 }
420
421 read_trimmed_line(input_stream);
422
423 check_empty(input_stream);
424
425 // parse tilt angles
426 if (!string_to_vector<double>(m_line, &m_tilt_angles))
427 {
428 throw ParsingException(
429 "Error while parsing tilt angles: "
430 "value is not a floating point number", m_line_counter);
431 }
432 if (m_tilt_angles.size() != number_of_tilt_entries)
433 {
434 throw ParsingException(
435 "The specified number of tilt entries does not match "
436 "the actual number of entries in the line", m_line_counter);
437 }
438
439 read_trimmed_line(input_stream);
440
441 check_empty(input_stream);
442
443 // parse tilt multipliers
444 if (!string_to_vector<double>(m_line, &m_tilt_multiplying_factors))
445 {
446 throw ParsingException(
447 "Error while parsing tilt multipliers: "
448 "value is not a floating point number", m_line_counter);
449 }
450 if (m_tilt_multiplying_factors.size() != number_of_tilt_entries)
451 {
452 throw ParsingException(
453 "The specified number of tilt entries does not match "
454 "the actual number of tilt multipliers in the line", m_line_counter);
455 }
456 if (boost::algorithm::any_of(m_tilt_multiplying_factors, is_negative<double>))
457 {
458 throw ParsingException(
459 "Error while parsing tilt multipliers: "
460 "value must be non-negative", m_line_counter);
461 }
462
463 read_trimmed_line(input_stream);
464 }
465
parse_photometric_data(std::istream & input_stream)466 void IESParser::parse_photometric_data(std::istream& input_stream)
467 {
468 std::vector<std::string> values =
469 parse_to_vector<std::string>(input_stream, 10);
470
471 if (!string_to_positive_number(values[0], &m_number_of_lamps))
472 {
473 throw ParsingException(
474 "Number of lamps is expected to be a positive integer number",
475 m_line_counter);
476 }
477
478 m_absolute_photometry = false;
479 if (values[1] == "-1")
480 {
481 m_lumens_per_lamp = -1.0;
482 m_absolute_photometry = true;
483 }
484 else if (!string_to_positive_number(values[1], &m_lumens_per_lamp))
485 {
486 throw ParsingException(
487 "Lumens per lamp are expected to be a positive floating point number or -1",
488 m_line_counter);
489 }
490
491 if (!string_to_positive_number(values[2], &m_candela_multiplier))
492 {
493 throw ParsingException(
494 "Candela multiplier is expected to be a positive floating point number",
495 m_line_counter);
496 }
497
498 if (!string_to_positive_number(values[3], &m_number_of_vertical_angles))
499 {
500 throw ParsingException(
501 "Number of vertical angles is expected to be a positive integer number",
502 m_line_counter);
503 }
504
505 if (!string_to_positive_number(values[4], &m_number_of_horizontal_angles))
506 {
507 throw ParsingException(
508 "Number of horizontal angles is expected to be a positive integer number",
509 m_line_counter);
510 }
511
512 if (!string_to_photometric_type(values[5], &m_photometric_type))
513 {
514 throw ParsingException(
515 "Wrong photometric type: expected 1, 2 or 3",
516 m_line_counter);
517 }
518
519 if (!string_to_units_type(values[6], &m_luminous_opening.m_units_type))
520 {
521 throw ParsingException(
522 "Wrong units type for luminous opening: expected 1 or 2",
523 m_line_counter);
524 }
525
526 if (!string_to_number(values[7], &m_luminous_opening.m_width))
527 {
528 throw ParsingException(
529 "Wrong width for luminous opening: expected floating point number",
530 m_line_counter);
531 }
532
533 if (!string_to_number(values[8], &m_luminous_opening.m_length))
534 {
535 throw ParsingException(
536 "Wrong length for luminous opening: expected floating point number",
537 m_line_counter);
538 }
539
540 if (!string_to_number(values[9], &m_luminous_opening.m_height))
541 {
542 throw ParsingException(
543 "Wrong height for luminous opening: expected floating point number",
544 m_line_counter);
545 }
546
547 check_empty(input_stream);
548
549 values = parse_to_vector<std::string>(input_stream, 3);
550
551 if (!string_to_positive_number(values[0], &m_ballast_factor))
552 {
553 throw ParsingException(
554 "Ballast factor is expected to be a positive floating point number",
555 m_line_counter);
556 }
557
558 if (!string_to_positive_number(values[1], &m_ballast_lamp_photometric_factor))
559 {
560 throw ParsingException(
561 "Ballast-lamp photometric factor is expected to be a positive floating point number",
562 m_line_counter);
563 }
564
565 if (!string_to_positive_number(values[2], &m_input_watts))
566 {
567 throw ParsingException(
568 "Input watts are expected to be a positive floating point number",
569 m_line_counter);
570 }
571 }
572
parse_candela_values(std::istream & input_stream)573 void IESParser::parse_candela_values(std::istream& input_stream)
574 {
575 m_candela_values.resize(m_number_of_horizontal_angles);
576 for (each<PhotometricGrid> i = m_candela_values; i; ++i)
577 {
578 try
579 {
580 *i = parse_to_vector<double>(input_stream, m_number_of_vertical_angles);
581 }
582 catch (boost::bad_lexical_cast&)
583 {
584 throw ParsingException(
585 "Error while parsing candela values: "
586 "value is not a floating point number", m_line_counter);
587 }
588 }
589 }
590
parse_angles(std::istream & input_stream)591 void IESParser::parse_angles(std::istream& input_stream)
592 {
593 // Parse vertical angles:
594
595 try
596 {
597 m_vertical_angles = parse_to_vector<double>(input_stream, m_number_of_vertical_angles);
598 }
599 catch (boost::bad_lexical_cast&)
600 {
601 throw ParsingException(
602 "Error while parsing vertical angles: "
603 "value is not a floating point number", m_line_counter);
604 }
605
606 assert(m_photometric_type != PhotometricTypeNotSpecified);
607
608 if (m_photometric_type == PhotometricTypeC)
609 {
610 if (!feq(m_vertical_angles.front(), 0.0) &&
611 !feq(m_vertical_angles.front(), 90.0))
612 {
613 throw ParsingException(
614 "With photometric type C, first vertical angle "
615 "must be either 0 or 90 degrees", m_line_counter);
616 }
617 if (!feq(m_vertical_angles.back(), 90.0) &&
618 !feq(m_vertical_angles.back(), 180.0))
619 {
620 throw ParsingException(
621 "With photometric type C, last vertical angle "
622 "must be either 90 or 180 degrees", m_line_counter);
623 }
624 }
625 else if (m_photometric_type == PhotometricTypeA ||
626 m_photometric_type == PhotometricTypeB)
627 {
628 if (!feq(m_vertical_angles.front(), -90.0) &&
629 !feq(m_vertical_angles.front(), 0.0))
630 {
631 throw ParsingException(
632 "With photometric types A and B, first vertical angle "
633 "must be either -90 or 0 degrees", m_line_counter);
634 }
635 if (!feq(m_vertical_angles.back(), 90.0))
636 {
637 throw ParsingException(
638 "With photometric types A and B, last vertical angle "
639 "must be 90 degrees", m_line_counter);
640 }
641 }
642 if (feq(m_vertical_angles.front(), m_vertical_angles.back()))
643 {
644 throw ParsingException(
645 "First and last vertical angles must be different", m_line_counter);
646 }
647 if (!check_increasing_order(m_vertical_angles))
648 {
649 throw ParsingException(
650 "Vertical angles must be mentioned in increasing order", m_line_counter);
651 }
652
653 // Parse horizontal angles:
654
655 try
656 {
657 m_horizontal_angles = parse_to_vector<double>(input_stream, m_number_of_horizontal_angles);
658 }
659 catch (boost::bad_lexical_cast&)
660 {
661 throw ParsingException(
662 "Error while parsing horizontal angles: "
663 "value is not a floating point number", m_line_counter);
664 }
665 if (m_photometric_type == PhotometricTypeC)
666 {
667 if (feq(m_horizontal_angles.front(), 90.0))
668 {
669 if (feq(m_horizontal_angles.back(), 270.0))
670 {
671 m_symmetry = SymmetricHalvesY;
672 }
673 else
674 {
675 throw ParsingException(
676 "With photometric type C, if the first horizontal angle "
677 "is 90 degrees, then the last angle can be only 270 degrees",
678 m_line_counter);
679 }
680 }
681 else if (feq(m_horizontal_angles.front(), 0.0))
682 {
683 if (feq(m_horizontal_angles.back(), 0.0))
684 {
685 m_symmetry = FullySymmetric;
686 }
687 else if (feq(m_horizontal_angles.back(), 90.0))
688 {
689 m_symmetry = SymmetricQuadrants;
690 }
691 else if (feq(m_horizontal_angles.back(), 180.0))
692 {
693 m_symmetry = SymmetricHalvesX;
694 }
695 else if (feq(m_horizontal_angles.back(), 360.0))
696 {
697 m_symmetry = NoSymmetry;
698 }
699 else
700 {
701 throw ParsingException(
702 "With photometric type C, the last horizontal angle "
703 "can be only 0, 90, 180 or 360 degrees", m_line_counter);
704 }
705 }
706 else
707 {
708 throw ParsingException(
709 "With photometric type C, the first horizontal angle "
710 "can be only 0 or 90 degrees", m_line_counter);
711 }
712 }
713 else if (m_photometric_type == PhotometricTypeA ||
714 m_photometric_type == PhotometricTypeB)
715 {
716 if (feq(m_horizontal_angles.front(), 0.0) &&
717 feq(m_horizontal_angles.back(), 90.0))
718 {
719 m_symmetry = SymmetricHalvesX;
720 }
721 else if (feq(m_horizontal_angles.front(), -90.0) &&
722 feq(m_horizontal_angles.back(), 90.0))
723 {
724 m_symmetry = NoSymmetry;
725 }
726 else
727 {
728 throw ParsingException(
729 "With photometric types A and B, the first horizontal angle "
730 "can be only 0 or -90 degrees, and the last horizontal "
731 "angle must be 90 degrees", m_line_counter);
732 }
733 }
734 if (!check_increasing_order(m_horizontal_angles))
735 {
736 throw ParsingException(
737 "Horizontal angles must be mentioned in increasing order", m_line_counter);
738 }
739 }
740
is_keyword_line(const std::string & line)741 bool IESParser::is_keyword_line(const std::string& line)
742 {
743 static const boost::regex Regex(KeywordLineRegex);
744 return boost::regex_match(line, Regex);
745 }
746
is_tilt_line(const std::string & line)747 bool IESParser::is_tilt_line(const std::string& line)
748 {
749 static const boost::regex Regex(TiltLineRegex);
750 return boost::regex_match(line, Regex);
751 }
752
parse_keyword_line(const std::string & line)753 void IESParser::parse_keyword_line(const std::string& line)
754 {
755 static const boost::regex Regex(KeywordLineRegex);
756 boost::smatch what;
757 if (!boost::regex_match(line, what, Regex))
758 {
759 throw ParsingException("Keyword is expected", m_line_counter);
760 }
761
762 const std::string key = what[1];
763 const std::string value = what[2];
764
765 // Check if the specified standard allows this keyword.
766 if (!m_ignore_allowed_keywords) accept_keyword(key);
767
768 // Process MORE, BLOCK and ENDBLOCK keywords separately.
769 // For all other keywords - just add them to dictionary.
770 process_block_keyword(key);
771 if (key == "MORE")
772 {
773 if (m_last_added_keyword == m_keywords_dictionary.end())
774 {
775 throw ParsingException(
776 "Keyword MORE occured before any other keyword", m_line_counter);
777 }
778 m_last_added_keyword->second += ("\n" + value);
779 }
780 else
781 {
782 KeywordsDictionary::iterator it = m_keywords_dictionary.find(key);
783 if (it != m_keywords_dictionary.end())
784 {
785 static const char* ErrorMsg = "Keyword {0} is duplicated";
786 throw ParsingException(format(ErrorMsg, key).c_str(), m_line_counter);
787 }
788 m_keywords_dictionary[key] = value;
789 m_last_added_keyword = m_keywords_dictionary.find(key);
790 }
791 }
792
parse_tilt_line(const std::string & line)793 void IESParser::parse_tilt_line(const std::string& line)
794 {
795 static const boost::regex Regex(TiltLineRegex);
796 boost::smatch what;
797 if (!boost::regex_match(line, what, Regex))
798 {
799 throw ParsingException("TILT line is expected", m_line_counter);
800 }
801
802 const std::string value = what[1];
803
804 if (value == "INCLUDE")
805 m_tilt_specification = IncludeTilt;
806 else if (value == "NONE")
807 m_tilt_specification = NoTilt;
808 else if (!m_ignore_tilt)
809 {
810 throw NotSupportedException(
811 "TILT specification from file is not supported", m_line_counter);
812 }
813 }
814
process_block_keyword(const std::string & keyword)815 void IESParser::process_block_keyword(const std::string& keyword)
816 {
817 if (keyword == "ENDBLOCK" || keyword == "BLOCK")
818 {
819 throw NotSupportedException("Block keywords are not supported", m_line_counter);
820 }
821 }
822
accept_keyword(const std::string & keyword)823 void IESParser::accept_keyword(const std::string& keyword)
824 {
825 if (keyword.empty())
826 {
827 throw ParsingException("Keyword is empty", m_line_counter);
828 }
829
830 assert(m_format != UnknownFormat);
831
832 switch (m_format)
833 {
834 case Format2002:
835 if (!(keyword[0] == '_' || is_keyword_allowed_by_iesna02(keyword)))
836 {
837 static const char* ErrorMsg =
838 "Keyword {0} is not allowed by IESNA LM-63-2002 standard";
839 throw ParsingException(format(ErrorMsg, keyword).c_str(), m_line_counter);
840 }
841 break;
842 case Format1995:
843 if (!(keyword[0] == '_' || is_keyword_allowed_by_iesna95(keyword)))
844 {
845 static const char* ErrorMsg =
846 "Keyword {0} is not allowed by IESNA LM-63-95 standard";
847 throw ParsingException(format(ErrorMsg, keyword).c_str(), m_line_counter);
848 }
849 break;
850 case Format1991:
851 if (keyword[0] == '_')
852 {
853 throw ParsingException(
854 "User keywords are not allowed by IESNA LM-63-91 standard",
855 m_line_counter);
856 }
857 if (!is_keyword_allowed_by_iesna91(keyword))
858 {
859 static const char* ErrorMsg = "Keyword {0} is not allowed by IESNA LM-63-91 standard";
860 throw ParsingException(format(ErrorMsg, keyword).c_str(), m_line_counter);
861 }
862 break;
863 }
864 }
865
check_required_keywords() const866 void IESParser::check_required_keywords() const
867 {
868 assert(m_format != UnknownFormat);
869
870 switch (m_format)
871 {
872 case Format2002:
873 check_iesna02_required_keywords();
874 break;
875 case Format1995:
876 // There are no required keywords in LM-63-95 standard.
877 break;
878 case Format1991:
879 check_iesna91_required_keywords();
880 break;
881 default:
882 break;
883 }
884 }
885
check_iesna02_required_keywords() const886 void IESParser::check_iesna02_required_keywords() const
887 {
888 static const char* RequiredKeywords[] =
889 {
890 "TEST",
891 "TESTLAB",
892 "ISSUEDATE",
893 "MANUFAC"
894 };
895 for (size_t i = 0; i < countof(RequiredKeywords); ++i)
896 {
897 const char* required_keyword = RequiredKeywords[i];
898 if (m_keywords_dictionary.count(required_keyword) == 0)
899 {
900 static const char* ErrorMsg =
901 "Keyword {0}, required by IESNA LM-63-2002 standard, was not found";
902 throw ParsingException(format(ErrorMsg, required_keyword).c_str(), m_line_counter);
903 }
904 }
905 }
906
check_iesna91_required_keywords() const907 void IESParser::check_iesna91_required_keywords() const
908 {
909 static const char* RequiredKeywords[] =
910 {
911 "TEST",
912 "MANUFAC"
913 };
914 for (size_t i = 0; i < countof(RequiredKeywords); ++i)
915 {
916 const char* required_keyword = RequiredKeywords[i];
917 if (m_keywords_dictionary.count(required_keyword) == 0)
918 {
919 static const char* ErrorMsg =
920 "Keyword {0}, required by IESNA LM-63-91 standard, was not found";
921 throw ParsingException(format(ErrorMsg, required_keyword).c_str(), m_line_counter);
922 }
923 }
924 }
925
check_empty(std::istream & input_stream) const926 void IESParser::check_empty(std::istream& input_stream) const
927 {
928 if (!input_stream)
929 throw ParsingException("End of file is not expectied", m_line_counter);
930 if (m_line.empty())
931 throw ParsingException("Empty line is not expected", m_line_counter);
932 }
933
934 } // namespace foundation
935