1 #ifndef OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
2 #define OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
3 
4 /*
5 
6 This file is part of Osmium (https://osmcode.org/libosmium).
7 
8 Copyright 2013-2021 Jochen Topf <jochen@topf.org> and others (see README).
9 
10 Boost Software License - Version 1.0 - August 17th, 2003
11 
12 Permission is hereby granted, free of charge, to any person or organization
13 obtaining a copy of the software and accompanying documentation covered by
14 this license (the "Software") to use, reproduce, display, distribute,
15 execute, and transmit the Software, and to prepare derivative works of the
16 Software, and to permit third-parties to whom the Software is furnished to
17 do so, all subject to the following:
18 
19 The copyright notices in the Software and this entire statement, including
20 the above license grant, this restriction and the following disclaimer,
21 must be included in all copies of the Software, in whole or in part, and
22 all derivative works of the Software, unless such copies or derivative
23 works are solely in the form of machine-executable object code generated by
24 a source language processor.
25 
26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
29 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
30 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
31 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 DEALINGS IN THE SOFTWARE.
33 
34 */
35 
36 #include <osmium/builder/osm_object_builder.hpp>
37 #include <osmium/io/detail/string_util.hpp>
38 #include <osmium/io/error.hpp>
39 #include <osmium/memory/buffer.hpp>
40 #include <osmium/osm/box.hpp>
41 #include <osmium/osm/changeset.hpp>
42 #include <osmium/osm/entity_bits.hpp>
43 #include <osmium/osm/item_type.hpp>
44 #include <osmium/osm/location.hpp>
45 #include <osmium/osm/node.hpp>
46 #include <osmium/osm/relation.hpp>
47 #include <osmium/osm/timestamp.hpp>
48 #include <osmium/osm/types.hpp>
49 #include <osmium/osm/way.hpp>
50 
51 #include <cstdint>
52 #include <cstdlib>
53 #include <iterator>
54 #include <limits>
55 #include <stdexcept>
56 #include <string>
57 
58 namespace osmium {
59 
60     namespace builder {
61         class Builder;
62     } // namespace builder
63 
64     /**
65      * Exception thrown when there was a problem with parsing the OPL format
66      * of a file.
67      */
68     struct opl_error : public io_error {
69 
70         uint64_t line = 0;
71         uint64_t column = 0;
72         const char* data;
73         std::string msg;
74 
opl_errorosmium::opl_error75         explicit opl_error(const std::string& what, const char* d = nullptr) :
76             io_error(std::string("OPL error: ") + what),
77             data(d),
78             msg("OPL error: ") {
79             msg.append(what);
80         }
81 
opl_errorosmium::opl_error82         explicit opl_error(const char* what, const char* d = nullptr) :
83             io_error(std::string{"OPL error: "} + what),
84             data(d),
85             msg("OPL error: ") {
86             msg.append(what);
87         }
88 
set_pososmium::opl_error89         void set_pos(uint64_t l, uint64_t col) {
90             line = l;
91             column = col;
92             msg.append(" on line ");
93             msg.append(std::to_string(line));
94             msg.append(" column ");
95             msg.append(std::to_string(column));
96         }
97 
whatosmium::opl_error98         const char* what() const noexcept override {
99             return msg.c_str();
100         }
101 
102     }; // struct opl_error
103 
104     namespace io {
105 
106         namespace detail {
107 
108             /**
109              * Consume consecutive space and tab characters. There must be
110              * at least one.
111              */
opl_parse_space(const char ** s)112             inline void opl_parse_space(const char** s) {
113                 if (**s != ' ' && **s != '\t') {
114                     throw opl_error{"expected space or tab character", *s};
115                 }
116                 do {
117                     ++*s;
118                 } while (**s == ' ' || **s == '\t');
119             }
120 
121             /**
122              * Check whether s points to something else than the end of the
123              * string or a space or tab.
124              */
opl_non_empty(const char * s)125             inline bool opl_non_empty(const char *s) {
126                 return *s != '\0' && *s != ' ' && *s != '\t';
127             }
128 
129             /**
130              * Skip to the next space or tab character or the end of the
131              * string.
132              */
opl_skip_section(const char ** s)133             inline const char* opl_skip_section(const char** s) noexcept {
134                 while (opl_non_empty(*s)) {
135                     ++*s;
136                 }
137                 return *s;
138             }
139 
140             /**
141              * Parse OPL-escaped strings with hex code with a '%' at the end.
142              * Appends resulting unicode character to the result string.
143              *
144              * Returns a pointer to next character that needs to be consumed.
145              */
opl_parse_escaped(const char ** data,std::string & result)146             inline void opl_parse_escaped(const char** data, std::string& result) {
147                 assert(data);
148                 assert(*data);
149                 const char* s = *data;
150                 uint32_t value = 0;
151                 const int max_length = sizeof(value) * 2 /* hex chars per byte */;
152                 int length = 0;
153                 while (++length <= max_length) {
154                     if (*s == '\0') {
155                         throw opl_error{"eol", s};
156                     }
157                     if (*s == '%') {
158                         ++s;
159                         if (value == 0) {
160                             result += '%';
161                         } else {
162                             append_codepoint_as_utf8(value, std::back_inserter(result));
163                         }
164                         *data = s;
165                         return;
166                     }
167                     value <<= 4U;
168                     if (*s >= '0' && *s <= '9') {
169                         value += *s - '0';
170                     } else if (*s >= 'a' && *s <= 'f') {
171                         value += *s - 'a' + 10;
172                     } else if (*s >= 'A' && *s <= 'F') {
173                         value += *s - 'A' + 10;
174                     } else {
175                         throw opl_error{"not a hex char", s};
176                     }
177                     ++s;
178                 }
179                 throw opl_error{"hex escape too long", s};
180             }
181 
182             /**
183              * Parse a string up to end of string or next space, tab, comma, or
184              * equal sign.
185              *
186              * Appends characters to the result string.
187              *
188              * Returns a pointer to next character that needs to be consumed.
189              */
opl_parse_string(const char ** data,std::string & result)190             inline void opl_parse_string(const char** data, std::string& result) {
191                 assert(data);
192                 assert(*data);
193                 const char* s = *data;
194                 while (true) {
195                     if (*s == '\0' || *s == ' ' || *s == '\t' || *s == ',' || *s == '=') {
196                         break;
197                     }
198                     if (*s == '%') {
199                         ++s;
200                         opl_parse_escaped(&s, result);
201                     } else {
202                         result += *s;
203                         ++s;
204                     }
205                 }
206                 *data = s;
207             }
208 
209             // Arbitrary limit how long integers can get
210             enum {
211                 max_int_len = 16
212             };
213 
214             template <typename T>
opl_parse_int(const char ** s)215             inline T opl_parse_int(const char** s) {
216                 if (**s == '\0') {
217                     throw opl_error{"expected integer", *s};
218                 }
219                 const bool negative = (**s == '-');
220                 if (negative) {
221                     ++*s;
222                 }
223 
224                 int64_t value = 0;
225 
226                 int n = max_int_len;
227                 while (**s >= '0' && **s <= '9') {
228                     if (--n == 0) {
229                         throw opl_error{"integer too long", *s};
230                     }
231                     value *= 10;
232                     value += **s - '0';
233                     ++*s;
234                 }
235 
236                 if (n == max_int_len) {
237                     throw opl_error{"expected integer", *s};
238                 }
239 
240                 if (negative) {
241                     value = -value;
242                     if (value < std::numeric_limits<T>::min()) {
243                         throw opl_error{"integer too long", *s};
244                     }
245                 } else {
246                     if (value > std::numeric_limits<T>::max()) {
247                         throw opl_error{"integer too long", *s};
248                     }
249                 }
250 
251                 return T(value);
252             }
253 
opl_parse_id(const char ** s)254             inline osmium::object_id_type opl_parse_id(const char** s) {
255                 return opl_parse_int<osmium::object_id_type>(s);
256             }
257 
opl_parse_changeset_id(const char ** s)258             inline osmium::changeset_id_type opl_parse_changeset_id(const char** s) {
259                 return opl_parse_int<osmium::changeset_id_type>(s);
260             }
261 
opl_parse_version(const char ** s)262             inline osmium::object_version_type opl_parse_version(const char** s) {
263                 return opl_parse_int<osmium::object_version_type>(s);
264             }
265 
opl_parse_visible(const char ** data)266             inline bool opl_parse_visible(const char** data) {
267                 if (**data == 'V') {
268                     ++*data;
269                     return true;
270                 }
271                 if (**data == 'D') {
272                     ++*data;
273                     return false;
274                 }
275                 throw opl_error{"invalid visible flag", *data};
276             }
277 
opl_parse_uid(const char ** s)278             inline osmium::user_id_type opl_parse_uid(const char** s) {
279                 return opl_parse_int<osmium::user_id_type>(s);
280             }
281 
opl_parse_timestamp(const char ** s)282             inline osmium::Timestamp opl_parse_timestamp(const char** s) {
283                 try {
284                     if (**s == '\0' || **s == ' ' || **s == '\t') {
285                         return osmium::Timestamp{};
286                     }
287                     osmium::Timestamp timestamp{*s};
288                     *s += 20;
289                     return timestamp;
290                 } catch (const std::invalid_argument&) {
291                     throw opl_error{"can not parse timestamp", *s};
292                 }
293             }
294 
295             /**
296              * Check if data points to given character and consume it.
297              * Throw error otherwise.
298              */
opl_parse_char(const char ** data,char c)299             inline void opl_parse_char(const char** data, char c) {
300                 if (**data == c) {
301                     ++*data;
302                     return;
303                 }
304                 std::string msg{"expected '"};
305                 msg += c;
306                 msg += "'";
307                 throw opl_error{msg, *data};
308             }
309 
310             /**
311              * Parse a list of tags in the format 'key=value,key=value,...'
312              *
313              * Tags will be added to the buffer using a TagListBuilder.
314              */
opl_parse_tags(const char * s,osmium::memory::Buffer & buffer,osmium::builder::Builder * parent_builder=nullptr)315             inline void opl_parse_tags(const char* s, osmium::memory::Buffer& buffer, osmium::builder::Builder* parent_builder = nullptr) {
316                 osmium::builder::TagListBuilder builder{buffer, parent_builder};
317                 std::string key;
318                 std::string value;
319                 while (true) {
320                     opl_parse_string(&s, key);
321                     opl_parse_char(&s, '=');
322                     opl_parse_string(&s, value);
323                     builder.add_tag(key, value);
324                     if (*s == ' ' || *s == '\t' || *s == '\0') {
325                         break;
326                     }
327                     opl_parse_char(&s, ',');
328                     key.clear();
329                     value.clear();
330                 }
331             }
332 
333             /**
334              * Parse a number of nodes in the format "nID,nID,nID..."
335              *
336              * Nodes will be added to the buffer using a WayNodeListBuilder.
337              */
opl_parse_way_nodes(const char * s,const char * e,osmium::memory::Buffer & buffer,osmium::builder::WayBuilder * parent_builder=nullptr)338             inline void opl_parse_way_nodes(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::WayBuilder* parent_builder = nullptr) {
339                 if (s == e) {
340                     return;
341                 }
342                 osmium::builder::WayNodeListBuilder builder{buffer, parent_builder};
343 
344                 while (s < e) {
345                     opl_parse_char(&s, 'n');
346                     if (s == e) {
347                         throw opl_error{"expected integer", s};
348                     }
349 
350                     const osmium::object_id_type ref = opl_parse_id(&s);
351                     if (s == e) {
352                         builder.add_node_ref(ref);
353                         return;
354                     }
355 
356                     osmium::Location location;
357                     if (*s == 'x') {
358                         ++s;
359                         location.set_lon_partial(&s);
360                         if (*s == 'y') {
361                             ++s;
362                             location.set_lat_partial(&s);
363                         }
364                     }
365 
366                     builder.add_node_ref(ref, location);
367 
368                     if (s == e) {
369                         return;
370                     }
371 
372                     opl_parse_char(&s, ',');
373                 }
374             }
375 
opl_parse_node(const char ** data,osmium::memory::Buffer & buffer)376             inline void opl_parse_node(const char** data, osmium::memory::Buffer& buffer) {
377                 osmium::builder::NodeBuilder builder{buffer};
378 
379                 builder.set_id(opl_parse_id(data));
380 
381                 const char* tags_begin = nullptr;
382 
383                 bool has_version = false;
384                 bool has_visible = false;
385                 bool has_changeset_id = false;
386                 bool has_timestamp = false;
387                 bool has_uid = false;
388                 bool has_user = false;
389                 bool has_tags = false;
390                 bool has_lon = false;
391                 bool has_lat = false;
392 
393                 std::string user;
394                 osmium::Location location;
395                 while (**data) {
396                     opl_parse_space(data);
397                     const char c = **data;
398                     if (c == '\0') {
399                         break;
400                     }
401                     ++(*data);
402                     switch (c) {
403                         case 'v':
404                             if (has_version) {
405                                 throw opl_error{"Duplicate attribute: version (v)"};
406                             }
407                             has_version = true;
408                             builder.set_version(opl_parse_version(data));
409                             break;
410                         case 'd':
411                             if (has_visible) {
412                                 throw opl_error{"Duplicate attribute: visible (d)"};
413                             }
414                             has_visible = true;
415                             builder.set_visible(opl_parse_visible(data));
416                             break;
417                         case 'c':
418                             if (has_changeset_id) {
419                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
420                             }
421                             has_changeset_id = true;
422                             builder.set_changeset(opl_parse_changeset_id(data));
423                             break;
424                         case 't':
425                             if (has_timestamp) {
426                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
427                             }
428                             has_timestamp = true;
429                             builder.set_timestamp(opl_parse_timestamp(data));
430                             break;
431                         case 'i':
432                             if (has_uid) {
433                                 throw opl_error{"Duplicate attribute: uid (i)"};
434                             }
435                             has_uid = true;
436                             builder.set_uid(opl_parse_uid(data));
437                             break;
438                         case 'u':
439                             if (has_user) {
440                                 throw opl_error{"Duplicate attribute: user (u)"};
441                             }
442                             has_user = true;
443                             opl_parse_string(data, user);
444                             break;
445                         case 'T':
446                             if (has_tags) {
447                                 throw opl_error{"Duplicate attribute: tags (T)"};
448                             }
449                             has_tags = true;
450                             if (opl_non_empty(*data)) {
451                                 tags_begin = *data;
452                                 opl_skip_section(data);
453                             }
454                             break;
455                         case 'x':
456                             if (has_lon) {
457                                 throw opl_error{"Duplicate attribute: lon (x)"};
458                             }
459                             has_lon = true;
460                             if (opl_non_empty(*data)) {
461                                 location.set_lon_partial(data);
462                             }
463                             break;
464                         case 'y':
465                             if (has_lat) {
466                                 throw opl_error{"Duplicate attribute: lat (y)"};
467                             }
468                             has_lat = true;
469                             if (opl_non_empty(*data)) {
470                                 location.set_lat_partial(data);
471                             }
472                             break;
473                         default:
474                             --(*data);
475                             throw opl_error{"unknown attribute", *data};
476                     }
477                 }
478 
479                 if (location.valid()) {
480                     builder.set_location(location);
481                 }
482 
483                 builder.set_user(user);
484 
485                 if (tags_begin) {
486                     opl_parse_tags(tags_begin, buffer, &builder);
487                 }
488             }
489 
opl_parse_way(const char ** data,osmium::memory::Buffer & buffer)490             inline void opl_parse_way(const char** data, osmium::memory::Buffer& buffer) {
491                 osmium::builder::WayBuilder builder{buffer};
492 
493                 builder.set_id(opl_parse_id(data));
494 
495                 const char* tags_begin = nullptr;
496 
497                 const char* nodes_begin = nullptr;
498                 const char* nodes_end = nullptr;
499 
500                 bool has_version = false;
501                 bool has_visible = false;
502                 bool has_changeset_id = false;
503                 bool has_timestamp = false;
504                 bool has_uid = false;
505                 bool has_user = false;
506                 bool has_tags = false;
507                 bool has_nodes = false;
508 
509                 std::string user;
510                 while (**data) {
511                     opl_parse_space(data);
512                     const char c = **data;
513                     if (c == '\0') {
514                         break;
515                     }
516                     ++(*data);
517                     switch (c) {
518                         case 'v':
519                             if (has_version) {
520                                 throw opl_error{"Duplicate attribute: version (v)"};
521                             }
522                             has_version = true;
523                             builder.set_version(opl_parse_version(data));
524                             break;
525                         case 'd':
526                             if (has_visible) {
527                                 throw opl_error{"Duplicate attribute: visible (d)"};
528                             }
529                             has_visible = true;
530                             builder.set_visible(opl_parse_visible(data));
531                             break;
532                         case 'c':
533                             if (has_changeset_id) {
534                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
535                             }
536                             has_changeset_id = true;
537                             builder.set_changeset(opl_parse_changeset_id(data));
538                             break;
539                         case 't':
540                             if (has_timestamp) {
541                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
542                             }
543                             has_timestamp = true;
544                             builder.set_timestamp(opl_parse_timestamp(data));
545                             break;
546                         case 'i':
547                             if (has_uid) {
548                                 throw opl_error{"Duplicate attribute: uid (i)"};
549                             }
550                             has_uid = true;
551                             builder.set_uid(opl_parse_uid(data));
552                             break;
553                         case 'u':
554                             if (has_user) {
555                                 throw opl_error{"Duplicate attribute: user (u)"};
556                             }
557                             has_user = true;
558                             opl_parse_string(data, user);
559                             break;
560                         case 'T':
561                             if (has_tags) {
562                                 throw opl_error{"Duplicate attribute: tags (T)"};
563                             }
564                             has_tags = true;
565                             if (opl_non_empty(*data)) {
566                                 tags_begin = *data;
567                                 opl_skip_section(data);
568                             }
569                             break;
570                         case 'N':
571                             if (has_nodes) {
572                                 throw opl_error{"Duplicate attribute: nodes (N)"};
573                             }
574                             has_nodes = true;
575                             nodes_begin = *data;
576                             nodes_end = opl_skip_section(data);
577                             break;
578                         default:
579                             --(*data);
580                             throw opl_error{"unknown attribute", *data};
581                     }
582                 }
583 
584                 builder.set_user(user);
585 
586                 if (tags_begin) {
587                     opl_parse_tags(tags_begin, buffer, &builder);
588                 }
589 
590                 opl_parse_way_nodes(nodes_begin, nodes_end, buffer, &builder);
591             }
592 
opl_parse_relation_members(const char * s,const char * e,osmium::memory::Buffer & buffer,osmium::builder::RelationBuilder * parent_builder=nullptr)593             inline void opl_parse_relation_members(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::RelationBuilder* parent_builder = nullptr) {
594                 if (s == e) {
595                     return;
596                 }
597                 osmium::builder::RelationMemberListBuilder builder{buffer, parent_builder};
598 
599                 while (s < e) {
600                     osmium::item_type type = osmium::char_to_item_type(*s);
601                     if (type != osmium::item_type::node &&
602                         type != osmium::item_type::way &&
603                         type != osmium::item_type::relation) {
604                         throw opl_error{"unknown object type", s};
605                     }
606                     ++s;
607 
608                     if (s == e) {
609                         throw opl_error{"expected integer", s};
610                     }
611                     osmium::object_id_type ref = opl_parse_id(&s);
612                     opl_parse_char(&s, '@');
613                     if (s == e) {
614                         builder.add_member(type, ref, "");
615                         return;
616                     }
617                     std::string role;
618                     opl_parse_string(&s, role);
619                     builder.add_member(type, ref, role);
620 
621                     if (s == e) {
622                         return;
623                     }
624                     opl_parse_char(&s, ',');
625                 }
626             }
627 
opl_parse_relation(const char ** data,osmium::memory::Buffer & buffer)628             inline void opl_parse_relation(const char** data, osmium::memory::Buffer& buffer) {
629                 osmium::builder::RelationBuilder builder{buffer};
630 
631                 builder.set_id(opl_parse_id(data));
632 
633                 const char* tags_begin = nullptr;
634 
635                 const char* members_begin = nullptr;
636                 const char* members_end = nullptr;
637 
638                 bool has_version = false;
639                 bool has_visible = false;
640                 bool has_changeset_id = false;
641                 bool has_timestamp = false;
642                 bool has_uid = false;
643                 bool has_user = false;
644                 bool has_tags = false;
645                 bool has_members = false;
646 
647                 std::string user;
648                 while (**data) {
649                     opl_parse_space(data);
650                     const char c = **data;
651                     if (c == '\0') {
652                         break;
653                     }
654                     ++(*data);
655                     switch (c) {
656                         case 'v':
657                             if (has_version) {
658                                 throw opl_error{"Duplicate attribute: version (v)"};
659                             }
660                             has_version = true;
661                             builder.set_version(opl_parse_version(data));
662                             break;
663                         case 'd':
664                             if (has_visible) {
665                                 throw opl_error{"Duplicate attribute: visible (d)"};
666                             }
667                             has_visible = true;
668                             builder.set_visible(opl_parse_visible(data));
669                             break;
670                         case 'c':
671                             if (has_changeset_id) {
672                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
673                             }
674                             has_changeset_id = true;
675                             builder.set_changeset(opl_parse_changeset_id(data));
676                             break;
677                         case 't':
678                             if (has_timestamp) {
679                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
680                             }
681                             has_timestamp = true;
682                             builder.set_timestamp(opl_parse_timestamp(data));
683                             break;
684                         case 'i':
685                             if (has_uid) {
686                                 throw opl_error{"Duplicate attribute: uid (i)"};
687                             }
688                             has_uid = true;
689                             builder.set_uid(opl_parse_uid(data));
690                             break;
691                         case 'u':
692                             if (has_user) {
693                                 throw opl_error{"Duplicate attribute: user (u)"};
694                             }
695                             has_user = true;
696                             opl_parse_string(data, user);
697                             break;
698                         case 'T':
699                             if (has_tags) {
700                                 throw opl_error{"Duplicate attribute: tags (T)"};
701                             }
702                             has_tags = true;
703                             if (opl_non_empty(*data)) {
704                                 tags_begin = *data;
705                                 opl_skip_section(data);
706                             }
707                             break;
708                         case 'M':
709                             if (has_members) {
710                                 throw opl_error{"Duplicate attribute: members (M)"};
711                             }
712                             has_members = true;
713                             members_begin = *data;
714                             members_end = opl_skip_section(data);
715                             break;
716                         default:
717                             --(*data);
718                             throw opl_error{"unknown attribute", *data};
719                     }
720                 }
721 
722                 builder.set_user(user);
723 
724                 if (tags_begin) {
725                     opl_parse_tags(tags_begin, buffer, &builder);
726                 }
727 
728                 if (members_begin != members_end) {
729                     opl_parse_relation_members(members_begin, members_end, buffer, &builder);
730                 }
731             }
732 
opl_parse_changeset(const char ** data,osmium::memory::Buffer & buffer)733             inline void opl_parse_changeset(const char** data, osmium::memory::Buffer& buffer) {
734                 osmium::builder::ChangesetBuilder builder{buffer};
735 
736                 builder.set_id(opl_parse_changeset_id(data));
737 
738                 const char* tags_begin = nullptr;
739 
740                 bool has_num_changes = false;
741                 bool has_created_at = false;
742                 bool has_closed_at = false;
743                 bool has_num_comments = false;
744                 bool has_uid = false;
745                 bool has_user = false;
746                 bool has_tags = false;
747                 bool has_min_x = false;
748                 bool has_min_y = false;
749                 bool has_max_x = false;
750                 bool has_max_y = false;
751 
752                 osmium::Box box;
753                 std::string user;
754                 while (**data) {
755                     opl_parse_space(data);
756                     const char c = **data;
757                     if (c == '\0') {
758                         break;
759                     }
760                     ++(*data);
761                     switch (c) {
762                         case 'k':
763                             if (has_num_changes) {
764                                 throw opl_error{"Duplicate attribute: num_changes (k)"};
765                             }
766                             has_num_changes = true;
767                             builder.set_num_changes(opl_parse_int<osmium::num_changes_type>(data));
768                             break;
769                         case 's':
770                             if (has_created_at) {
771                                 throw opl_error{"Duplicate attribute: created_at (s)"};
772                             }
773                             has_created_at = true;
774                             builder.set_created_at(opl_parse_timestamp(data));
775                             break;
776                         case 'e':
777                             if (has_closed_at) {
778                                 throw opl_error{"Duplicate attribute: closed_at (e)"};
779                             }
780                             has_closed_at = true;
781                             builder.set_closed_at(opl_parse_timestamp(data));
782                             break;
783                         case 'd':
784                             if (has_num_comments) {
785                                 throw opl_error{"Duplicate attribute: num_comments (d)"};
786                             }
787                             has_num_comments = true;
788                             builder.set_num_comments(opl_parse_int<osmium::num_comments_type>(data));
789                             break;
790                         case 'i':
791                             if (has_uid) {
792                                 throw opl_error{"Duplicate attribute: uid (i)"};
793                             }
794                             has_uid = true;
795                             builder.set_uid(opl_parse_uid(data));
796                             break;
797                         case 'u':
798                             if (has_user) {
799                                 throw opl_error{"Duplicate attribute: user (u)"};
800                             }
801                             has_user = true;
802                             opl_parse_string(data, user);
803                             break;
804                         case 'x':
805                             if (has_min_x) {
806                                 throw opl_error{"Duplicate attribute: min_x (x)"};
807                             }
808                             has_min_x = true;
809                             if (opl_non_empty(*data)) {
810                                 box.bottom_left().set_lon_partial(data);
811                             }
812                             break;
813                         case 'y':
814                             if (has_min_y) {
815                                 throw opl_error{"Duplicate attribute: min_y (y)"};
816                             }
817                             has_min_y = true;
818                             if (opl_non_empty(*data)) {
819                                 box.bottom_left().set_lat_partial(data);
820                             }
821                             break;
822                         case 'X':
823                             if (has_max_x) {
824                                 throw opl_error{"Duplicate attribute: max_x (X)"};
825                             }
826                             has_max_x = true;
827                             if (opl_non_empty(*data)) {
828                                 box.top_right().set_lon_partial(data);
829                             }
830                             break;
831                         case 'Y':
832                             if (has_max_y) {
833                                 throw opl_error{"Duplicate attribute: max_y (Y)"};
834                             }
835                             has_max_y = true;
836                             if (opl_non_empty(*data)) {
837                                 box.top_right().set_lat_partial(data);
838                             }
839                             break;
840                         case 'T':
841                             if (has_tags) {
842                                 throw opl_error{"Duplicate attribute: tags (T)"};
843                             }
844                             has_tags = true;
845                             if (opl_non_empty(*data)) {
846                                 tags_begin = *data;
847                                 opl_skip_section(data);
848                             }
849                             break;
850                         default:
851                             --(*data);
852                             throw opl_error{"unknown attribute", *data};
853                     }
854 
855                 }
856 
857                 builder.set_bounds(box);
858                 builder.set_user(user);
859 
860                 if (tags_begin) {
861                     opl_parse_tags(tags_begin, buffer, &builder);
862                 }
863             }
864 
opl_parse_line(uint64_t line_count,const char * data,osmium::memory::Buffer & buffer,osmium::osm_entity_bits::type read_types=osmium::osm_entity_bits::all)865             inline bool opl_parse_line(uint64_t line_count,
866                                        const char* data,
867                                        osmium::memory::Buffer& buffer,
868                                        osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) {
869                 const char* start_of_line = data;
870                 try {
871                     switch (*data) {
872                         case '\0':
873                             // ignore empty lines
874                             break;
875                         case '#':
876                             // ignore lines starting with #
877                             break;
878                         case 'n':
879                             if (read_types & osmium::osm_entity_bits::node) {
880                                 ++data;
881                                 opl_parse_node(&data, buffer);
882                                 buffer.commit();
883                                 return true;
884                             }
885                             break;
886                         case 'w':
887                             if (read_types & osmium::osm_entity_bits::way) {
888                                 ++data;
889                                 opl_parse_way(&data, buffer);
890                                 buffer.commit();
891                                 return true;
892                             }
893                             break;
894                         case 'r':
895                             if (read_types & osmium::osm_entity_bits::relation) {
896                                 ++data;
897                                 opl_parse_relation(&data, buffer);
898                                 buffer.commit();
899                                 return true;
900                             }
901                             break;
902                         case 'c':
903                             if (read_types & osmium::osm_entity_bits::changeset) {
904                                 ++data;
905                                 opl_parse_changeset(&data, buffer);
906                                 buffer.commit();
907                                 return true;
908                             }
909                             break;
910                         default:
911                             throw opl_error{"unknown type", data};
912                     }
913                 } catch (opl_error& e) {
914                     e.set_pos(line_count, e.data ? e.data - start_of_line : 0);
915                     throw;
916                 }
917 
918                 return false;
919             }
920 
921         } // namespace detail
922 
923     } // namespace io
924 
925 } // namespace osmium
926 
927 
928 #endif // OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
929