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