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             template <typename T>
opl_parse_int(const char ** s)210             inline T opl_parse_int(const char** s) {
211                 const bool negative = (**s == '-');
212                 if (negative) {
213                     ++*s;
214                 }
215 
216                 if (**s < '0' || **s > '9') {
217                     throw opl_error{"expected integer", *s};
218                 }
219 
220                 int64_t value = 0;
221                 while (**s >= '0' && **s <= '9') {
222                     if (value <= -922337203685477580) {
223                         if ((value < -922337203685477580) || (**s > '8')) {
224                             throw opl_error("integer too long", *s);
225                         }
226                     }
227                     value *= 10;
228                     value -= **s - '0';
229                     ++*s;
230                 }
231 
232                 if (negative) {
233                     if (value < std::numeric_limits<T>::min()) {
234                         throw opl_error{"integer too long", *s};
235                     }
236                 } else {
237                     if (value == std::numeric_limits<int64_t>::min()) {
238                         throw opl_error{"integer too long", *s};
239                     }
240                     value = -value;
241                     if (value > std::numeric_limits<T>::max()) {
242                         throw opl_error{"integer too long", *s};
243                     }
244                 }
245 
246                 return T(value);
247             }
248 
opl_parse_id(const char ** s)249             inline osmium::object_id_type opl_parse_id(const char** s) {
250                 return opl_parse_int<osmium::object_id_type>(s);
251             }
252 
opl_parse_changeset_id(const char ** s)253             inline osmium::changeset_id_type opl_parse_changeset_id(const char** s) {
254                 return opl_parse_int<osmium::changeset_id_type>(s);
255             }
256 
opl_parse_version(const char ** s)257             inline osmium::object_version_type opl_parse_version(const char** s) {
258                 return opl_parse_int<osmium::object_version_type>(s);
259             }
260 
opl_parse_visible(const char ** data)261             inline bool opl_parse_visible(const char** data) {
262                 if (**data == 'V') {
263                     ++*data;
264                     return true;
265                 }
266                 if (**data == 'D') {
267                     ++*data;
268                     return false;
269                 }
270                 throw opl_error{"invalid visible flag", *data};
271             }
272 
opl_parse_uid(const char ** s)273             inline osmium::user_id_type opl_parse_uid(const char** s) {
274                 return opl_parse_int<osmium::user_id_type>(s);
275             }
276 
opl_parse_timestamp(const char ** s)277             inline osmium::Timestamp opl_parse_timestamp(const char** s) {
278                 try {
279                     if (**s == '\0' || **s == ' ' || **s == '\t') {
280                         return osmium::Timestamp{};
281                     }
282                     osmium::Timestamp timestamp{*s};
283                     *s += 20;
284                     return timestamp;
285                 } catch (const std::invalid_argument&) {
286                     throw opl_error{"can not parse timestamp", *s};
287                 }
288             }
289 
290             /**
291              * Check if data points to given character and consume it.
292              * Throw error otherwise.
293              */
opl_parse_char(const char ** data,char c)294             inline void opl_parse_char(const char** data, char c) {
295                 if (**data == c) {
296                     ++*data;
297                     return;
298                 }
299                 std::string msg{"expected '"};
300                 msg += c;
301                 msg += "'";
302                 throw opl_error{msg, *data};
303             }
304 
305             /**
306              * Parse a list of tags in the format 'key=value,key=value,...'
307              *
308              * Tags will be added to the buffer using a TagListBuilder.
309              */
opl_parse_tags(const char * s,osmium::memory::Buffer & buffer,osmium::builder::Builder * parent_builder=nullptr)310             inline void opl_parse_tags(const char* s, osmium::memory::Buffer& buffer, osmium::builder::Builder* parent_builder = nullptr) {
311                 osmium::builder::TagListBuilder builder{buffer, parent_builder};
312                 std::string key;
313                 std::string value;
314                 while (true) {
315                     opl_parse_string(&s, key);
316                     opl_parse_char(&s, '=');
317                     opl_parse_string(&s, value);
318                     builder.add_tag(key, value);
319                     if (*s == ' ' || *s == '\t' || *s == '\0') {
320                         break;
321                     }
322                     opl_parse_char(&s, ',');
323                     key.clear();
324                     value.clear();
325                 }
326             }
327 
328             /**
329              * Parse a number of nodes in the format "nID,nID,nID..."
330              *
331              * Nodes will be added to the buffer using a WayNodeListBuilder.
332              */
opl_parse_way_nodes(const char * s,const char * e,osmium::memory::Buffer & buffer,osmium::builder::WayBuilder * parent_builder=nullptr)333             inline void opl_parse_way_nodes(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::WayBuilder* parent_builder = nullptr) {
334                 if (s == e) {
335                     return;
336                 }
337                 osmium::builder::WayNodeListBuilder builder{buffer, parent_builder};
338 
339                 while (s < e) {
340                     opl_parse_char(&s, 'n');
341                     if (s == e) {
342                         throw opl_error{"expected integer", s};
343                     }
344 
345                     const osmium::object_id_type ref = opl_parse_id(&s);
346                     if (s == e) {
347                         builder.add_node_ref(ref);
348                         return;
349                     }
350 
351                     osmium::Location location;
352                     if (*s == 'x') {
353                         ++s;
354                         location.set_lon_partial(&s);
355                         if (*s == 'y') {
356                             ++s;
357                             location.set_lat_partial(&s);
358                         }
359                     }
360 
361                     builder.add_node_ref(ref, location);
362 
363                     if (s == e) {
364                         return;
365                     }
366 
367                     opl_parse_char(&s, ',');
368                 }
369             }
370 
opl_parse_node(const char ** data,osmium::memory::Buffer & buffer)371             inline void opl_parse_node(const char** data, osmium::memory::Buffer& buffer) {
372                 osmium::builder::NodeBuilder builder{buffer};
373 
374                 builder.set_id(opl_parse_id(data));
375 
376                 const char* tags_begin = nullptr;
377 
378                 bool has_version = false;
379                 bool has_visible = false;
380                 bool has_changeset_id = false;
381                 bool has_timestamp = false;
382                 bool has_uid = false;
383                 bool has_user = false;
384                 bool has_tags = false;
385                 bool has_lon = false;
386                 bool has_lat = false;
387 
388                 std::string user;
389                 osmium::Location location;
390                 while (**data) {
391                     opl_parse_space(data);
392                     const char c = **data;
393                     if (c == '\0') {
394                         break;
395                     }
396                     ++(*data);
397                     switch (c) {
398                         case 'v':
399                             if (has_version) {
400                                 throw opl_error{"Duplicate attribute: version (v)"};
401                             }
402                             has_version = true;
403                             builder.set_version(opl_parse_version(data));
404                             break;
405                         case 'd':
406                             if (has_visible) {
407                                 throw opl_error{"Duplicate attribute: visible (d)"};
408                             }
409                             has_visible = true;
410                             builder.set_visible(opl_parse_visible(data));
411                             break;
412                         case 'c':
413                             if (has_changeset_id) {
414                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
415                             }
416                             has_changeset_id = true;
417                             builder.set_changeset(opl_parse_changeset_id(data));
418                             break;
419                         case 't':
420                             if (has_timestamp) {
421                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
422                             }
423                             has_timestamp = true;
424                             builder.set_timestamp(opl_parse_timestamp(data));
425                             break;
426                         case 'i':
427                             if (has_uid) {
428                                 throw opl_error{"Duplicate attribute: uid (i)"};
429                             }
430                             has_uid = true;
431                             builder.set_uid(opl_parse_uid(data));
432                             break;
433                         case 'u':
434                             if (has_user) {
435                                 throw opl_error{"Duplicate attribute: user (u)"};
436                             }
437                             has_user = true;
438                             opl_parse_string(data, user);
439                             break;
440                         case 'T':
441                             if (has_tags) {
442                                 throw opl_error{"Duplicate attribute: tags (T)"};
443                             }
444                             has_tags = true;
445                             if (opl_non_empty(*data)) {
446                                 tags_begin = *data;
447                                 opl_skip_section(data);
448                             }
449                             break;
450                         case 'x':
451                             if (has_lon) {
452                                 throw opl_error{"Duplicate attribute: lon (x)"};
453                             }
454                             has_lon = true;
455                             if (opl_non_empty(*data)) {
456                                 location.set_lon_partial(data);
457                             }
458                             break;
459                         case 'y':
460                             if (has_lat) {
461                                 throw opl_error{"Duplicate attribute: lat (y)"};
462                             }
463                             has_lat = true;
464                             if (opl_non_empty(*data)) {
465                                 location.set_lat_partial(data);
466                             }
467                             break;
468                         default:
469                             --(*data);
470                             throw opl_error{"unknown attribute", *data};
471                     }
472                 }
473 
474                 if (location.valid()) {
475                     builder.set_location(location);
476                 }
477 
478                 builder.set_user(user);
479 
480                 if (tags_begin) {
481                     opl_parse_tags(tags_begin, buffer, &builder);
482                 }
483             }
484 
opl_parse_way(const char ** data,osmium::memory::Buffer & buffer)485             inline void opl_parse_way(const char** data, osmium::memory::Buffer& buffer) {
486                 osmium::builder::WayBuilder builder{buffer};
487 
488                 builder.set_id(opl_parse_id(data));
489 
490                 const char* tags_begin = nullptr;
491 
492                 const char* nodes_begin = nullptr;
493                 const char* nodes_end = nullptr;
494 
495                 bool has_version = false;
496                 bool has_visible = false;
497                 bool has_changeset_id = false;
498                 bool has_timestamp = false;
499                 bool has_uid = false;
500                 bool has_user = false;
501                 bool has_tags = false;
502                 bool has_nodes = false;
503 
504                 std::string user;
505                 while (**data) {
506                     opl_parse_space(data);
507                     const char c = **data;
508                     if (c == '\0') {
509                         break;
510                     }
511                     ++(*data);
512                     switch (c) {
513                         case 'v':
514                             if (has_version) {
515                                 throw opl_error{"Duplicate attribute: version (v)"};
516                             }
517                             has_version = true;
518                             builder.set_version(opl_parse_version(data));
519                             break;
520                         case 'd':
521                             if (has_visible) {
522                                 throw opl_error{"Duplicate attribute: visible (d)"};
523                             }
524                             has_visible = true;
525                             builder.set_visible(opl_parse_visible(data));
526                             break;
527                         case 'c':
528                             if (has_changeset_id) {
529                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
530                             }
531                             has_changeset_id = true;
532                             builder.set_changeset(opl_parse_changeset_id(data));
533                             break;
534                         case 't':
535                             if (has_timestamp) {
536                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
537                             }
538                             has_timestamp = true;
539                             builder.set_timestamp(opl_parse_timestamp(data));
540                             break;
541                         case 'i':
542                             if (has_uid) {
543                                 throw opl_error{"Duplicate attribute: uid (i)"};
544                             }
545                             has_uid = true;
546                             builder.set_uid(opl_parse_uid(data));
547                             break;
548                         case 'u':
549                             if (has_user) {
550                                 throw opl_error{"Duplicate attribute: user (u)"};
551                             }
552                             has_user = true;
553                             opl_parse_string(data, user);
554                             break;
555                         case 'T':
556                             if (has_tags) {
557                                 throw opl_error{"Duplicate attribute: tags (T)"};
558                             }
559                             has_tags = true;
560                             if (opl_non_empty(*data)) {
561                                 tags_begin = *data;
562                                 opl_skip_section(data);
563                             }
564                             break;
565                         case 'N':
566                             if (has_nodes) {
567                                 throw opl_error{"Duplicate attribute: nodes (N)"};
568                             }
569                             has_nodes = true;
570                             nodes_begin = *data;
571                             nodes_end = opl_skip_section(data);
572                             break;
573                         default:
574                             --(*data);
575                             throw opl_error{"unknown attribute", *data};
576                     }
577                 }
578 
579                 builder.set_user(user);
580 
581                 if (tags_begin) {
582                     opl_parse_tags(tags_begin, buffer, &builder);
583                 }
584 
585                 opl_parse_way_nodes(nodes_begin, nodes_end, buffer, &builder);
586             }
587 
opl_parse_relation_members(const char * s,const char * e,osmium::memory::Buffer & buffer,osmium::builder::RelationBuilder * parent_builder=nullptr)588             inline void opl_parse_relation_members(const char* s, const char* e, osmium::memory::Buffer& buffer, osmium::builder::RelationBuilder* parent_builder = nullptr) {
589                 if (s == e) {
590                     return;
591                 }
592                 osmium::builder::RelationMemberListBuilder builder{buffer, parent_builder};
593 
594                 while (s < e) {
595                     osmium::item_type type = osmium::char_to_item_type(*s);
596                     if (type != osmium::item_type::node &&
597                         type != osmium::item_type::way &&
598                         type != osmium::item_type::relation) {
599                         throw opl_error{"unknown object type", s};
600                     }
601                     ++s;
602 
603                     if (s == e) {
604                         throw opl_error{"expected integer", s};
605                     }
606                     osmium::object_id_type ref = opl_parse_id(&s);
607                     opl_parse_char(&s, '@');
608                     if (s == e) {
609                         builder.add_member(type, ref, "");
610                         return;
611                     }
612                     std::string role;
613                     opl_parse_string(&s, role);
614                     builder.add_member(type, ref, role);
615 
616                     if (s == e) {
617                         return;
618                     }
619                     opl_parse_char(&s, ',');
620                 }
621             }
622 
opl_parse_relation(const char ** data,osmium::memory::Buffer & buffer)623             inline void opl_parse_relation(const char** data, osmium::memory::Buffer& buffer) {
624                 osmium::builder::RelationBuilder builder{buffer};
625 
626                 builder.set_id(opl_parse_id(data));
627 
628                 const char* tags_begin = nullptr;
629 
630                 const char* members_begin = nullptr;
631                 const char* members_end = nullptr;
632 
633                 bool has_version = false;
634                 bool has_visible = false;
635                 bool has_changeset_id = false;
636                 bool has_timestamp = false;
637                 bool has_uid = false;
638                 bool has_user = false;
639                 bool has_tags = false;
640                 bool has_members = false;
641 
642                 std::string user;
643                 while (**data) {
644                     opl_parse_space(data);
645                     const char c = **data;
646                     if (c == '\0') {
647                         break;
648                     }
649                     ++(*data);
650                     switch (c) {
651                         case 'v':
652                             if (has_version) {
653                                 throw opl_error{"Duplicate attribute: version (v)"};
654                             }
655                             has_version = true;
656                             builder.set_version(opl_parse_version(data));
657                             break;
658                         case 'd':
659                             if (has_visible) {
660                                 throw opl_error{"Duplicate attribute: visible (d)"};
661                             }
662                             has_visible = true;
663                             builder.set_visible(opl_parse_visible(data));
664                             break;
665                         case 'c':
666                             if (has_changeset_id) {
667                                 throw opl_error{"Duplicate attribute: changeset_id (c)"};
668                             }
669                             has_changeset_id = true;
670                             builder.set_changeset(opl_parse_changeset_id(data));
671                             break;
672                         case 't':
673                             if (has_timestamp) {
674                                 throw opl_error{"Duplicate attribute: timestamp (t)"};
675                             }
676                             has_timestamp = true;
677                             builder.set_timestamp(opl_parse_timestamp(data));
678                             break;
679                         case 'i':
680                             if (has_uid) {
681                                 throw opl_error{"Duplicate attribute: uid (i)"};
682                             }
683                             has_uid = true;
684                             builder.set_uid(opl_parse_uid(data));
685                             break;
686                         case 'u':
687                             if (has_user) {
688                                 throw opl_error{"Duplicate attribute: user (u)"};
689                             }
690                             has_user = true;
691                             opl_parse_string(data, user);
692                             break;
693                         case 'T':
694                             if (has_tags) {
695                                 throw opl_error{"Duplicate attribute: tags (T)"};
696                             }
697                             has_tags = true;
698                             if (opl_non_empty(*data)) {
699                                 tags_begin = *data;
700                                 opl_skip_section(data);
701                             }
702                             break;
703                         case 'M':
704                             if (has_members) {
705                                 throw opl_error{"Duplicate attribute: members (M)"};
706                             }
707                             has_members = true;
708                             members_begin = *data;
709                             members_end = opl_skip_section(data);
710                             break;
711                         default:
712                             --(*data);
713                             throw opl_error{"unknown attribute", *data};
714                     }
715                 }
716 
717                 builder.set_user(user);
718 
719                 if (tags_begin) {
720                     opl_parse_tags(tags_begin, buffer, &builder);
721                 }
722 
723                 if (members_begin != members_end) {
724                     opl_parse_relation_members(members_begin, members_end, buffer, &builder);
725                 }
726             }
727 
opl_parse_changeset(const char ** data,osmium::memory::Buffer & buffer)728             inline void opl_parse_changeset(const char** data, osmium::memory::Buffer& buffer) {
729                 osmium::builder::ChangesetBuilder builder{buffer};
730 
731                 builder.set_id(opl_parse_changeset_id(data));
732 
733                 const char* tags_begin = nullptr;
734 
735                 bool has_num_changes = false;
736                 bool has_created_at = false;
737                 bool has_closed_at = false;
738                 bool has_num_comments = false;
739                 bool has_uid = false;
740                 bool has_user = false;
741                 bool has_tags = false;
742                 bool has_min_x = false;
743                 bool has_min_y = false;
744                 bool has_max_x = false;
745                 bool has_max_y = false;
746 
747                 osmium::Box box;
748                 std::string user;
749                 while (**data) {
750                     opl_parse_space(data);
751                     const char c = **data;
752                     if (c == '\0') {
753                         break;
754                     }
755                     ++(*data);
756                     switch (c) {
757                         case 'k':
758                             if (has_num_changes) {
759                                 throw opl_error{"Duplicate attribute: num_changes (k)"};
760                             }
761                             has_num_changes = true;
762                             builder.set_num_changes(opl_parse_int<osmium::num_changes_type>(data));
763                             break;
764                         case 's':
765                             if (has_created_at) {
766                                 throw opl_error{"Duplicate attribute: created_at (s)"};
767                             }
768                             has_created_at = true;
769                             builder.set_created_at(opl_parse_timestamp(data));
770                             break;
771                         case 'e':
772                             if (has_closed_at) {
773                                 throw opl_error{"Duplicate attribute: closed_at (e)"};
774                             }
775                             has_closed_at = true;
776                             builder.set_closed_at(opl_parse_timestamp(data));
777                             break;
778                         case 'd':
779                             if (has_num_comments) {
780                                 throw opl_error{"Duplicate attribute: num_comments (d)"};
781                             }
782                             has_num_comments = true;
783                             builder.set_num_comments(opl_parse_int<osmium::num_comments_type>(data));
784                             break;
785                         case 'i':
786                             if (has_uid) {
787                                 throw opl_error{"Duplicate attribute: uid (i)"};
788                             }
789                             has_uid = true;
790                             builder.set_uid(opl_parse_uid(data));
791                             break;
792                         case 'u':
793                             if (has_user) {
794                                 throw opl_error{"Duplicate attribute: user (u)"};
795                             }
796                             has_user = true;
797                             opl_parse_string(data, user);
798                             break;
799                         case 'x':
800                             if (has_min_x) {
801                                 throw opl_error{"Duplicate attribute: min_x (x)"};
802                             }
803                             has_min_x = true;
804                             if (opl_non_empty(*data)) {
805                                 box.bottom_left().set_lon_partial(data);
806                             }
807                             break;
808                         case 'y':
809                             if (has_min_y) {
810                                 throw opl_error{"Duplicate attribute: min_y (y)"};
811                             }
812                             has_min_y = true;
813                             if (opl_non_empty(*data)) {
814                                 box.bottom_left().set_lat_partial(data);
815                             }
816                             break;
817                         case 'X':
818                             if (has_max_x) {
819                                 throw opl_error{"Duplicate attribute: max_x (X)"};
820                             }
821                             has_max_x = true;
822                             if (opl_non_empty(*data)) {
823                                 box.top_right().set_lon_partial(data);
824                             }
825                             break;
826                         case 'Y':
827                             if (has_max_y) {
828                                 throw opl_error{"Duplicate attribute: max_y (Y)"};
829                             }
830                             has_max_y = true;
831                             if (opl_non_empty(*data)) {
832                                 box.top_right().set_lat_partial(data);
833                             }
834                             break;
835                         case 'T':
836                             if (has_tags) {
837                                 throw opl_error{"Duplicate attribute: tags (T)"};
838                             }
839                             has_tags = true;
840                             if (opl_non_empty(*data)) {
841                                 tags_begin = *data;
842                                 opl_skip_section(data);
843                             }
844                             break;
845                         default:
846                             --(*data);
847                             throw opl_error{"unknown attribute", *data};
848                     }
849 
850                 }
851 
852                 builder.set_bounds(box);
853                 builder.set_user(user);
854 
855                 if (tags_begin) {
856                     opl_parse_tags(tags_begin, buffer, &builder);
857                 }
858             }
859 
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)860             inline bool opl_parse_line(uint64_t line_count,
861                                        const char* data,
862                                        osmium::memory::Buffer& buffer,
863                                        osmium::osm_entity_bits::type read_types = osmium::osm_entity_bits::all) {
864                 const char* start_of_line = data;
865                 try {
866                     switch (*data) {
867                         case '\0':
868                             // ignore empty lines
869                             break;
870                         case '#':
871                             // ignore lines starting with #
872                             break;
873                         case 'n':
874                             if (read_types & osmium::osm_entity_bits::node) {
875                                 ++data;
876                                 opl_parse_node(&data, buffer);
877                                 buffer.commit();
878                                 return true;
879                             }
880                             break;
881                         case 'w':
882                             if (read_types & osmium::osm_entity_bits::way) {
883                                 ++data;
884                                 opl_parse_way(&data, buffer);
885                                 buffer.commit();
886                                 return true;
887                             }
888                             break;
889                         case 'r':
890                             if (read_types & osmium::osm_entity_bits::relation) {
891                                 ++data;
892                                 opl_parse_relation(&data, buffer);
893                                 buffer.commit();
894                                 return true;
895                             }
896                             break;
897                         case 'c':
898                             if (read_types & osmium::osm_entity_bits::changeset) {
899                                 ++data;
900                                 opl_parse_changeset(&data, buffer);
901                                 buffer.commit();
902                                 return true;
903                             }
904                             break;
905                         default:
906                             throw opl_error{"unknown type", data};
907                     }
908                 } catch (opl_error& e) {
909                     e.set_pos(line_count, e.data ? e.data - start_of_line : 0);
910                     throw;
911                 }
912 
913                 return false;
914             }
915 
916         } // namespace detail
917 
918     } // namespace io
919 
920 } // namespace osmium
921 
922 
923 #endif // OSMIUM_IO_DETAIL_OPL_PARSER_FUNCTIONS_HPP
924