source: miniini/miniini/src/inisection.cpp @ 6:b16aa103775c

Last change on this file since 6:b16aa103775c was 6:b16aa103775c, checked in by vadim@…, 3 years ago

More VC++ comaptibility fixes

File size: 24.9 KB
Line 
1// Copyright (C) 2009-2010 Ferdinand Majerech
2// This file is part of MiniINI
3// For conditions of distribution and use, see copyright notice in LICENSE.txt
4
5#include <cstring>
6#include <cerrno>
7#include <cstdlib>
8#include <cassert>
9#include <climits>
10
11#include "typedefs.h"               
12#include "globals.h"
13#include "log.h"
14#include "linetoken.h"
15#include "inisection.h"
16#include "util.h"
17
18#ifdef _WIN32_WCE
19float strtof(const char* nptr, char** endptr)
20{
21        float val = atof(nptr);
22        while(isspace(*nptr))
23                nptr++;
24        while(isdigit(*nptr))
25                nptr++;
26        if (*nptr == '.')
27                nptr++;
28        while(isdigit(*nptr))
29                nptr++;
30        *endptr = (char*) nptr;
31        return val;
32}
33
34#endif
35using namespace miniini_private;
36
37MINIINI_EXPORT ui INISection::temptagscap = 0;
38MINIINI_EXPORT c * * INISection::temptags = NULL;
39MINIINI_EXPORT ui INISection::tagcap = 0;
40MINIINI_EXPORT c * INISection::tagbuf = NULL;
41
42/// @cond PRIVATE
43///Perform given code 4 times. Used for manual loop unrolling.
44#define FOR_4(instr) \
45{\
46  instr;\
47  instr;\
48  instr;\
49  instr;\
50}
51/// @endcond
52
53inline LineToken INISection::TagName(const c * & currentcharref, ui & tagsize)
54{
55    //ptr to the current character
56    const c * currentchar = currentcharref;
57    //current character
58    register c ch;
59    for(;;)
60    {
61        //reallocate header buffer if not enough space to add new chars
62        //need 4 chars for unrolled part of the loop, 2 for trailing zeroes
63        //since tag and value are stored in the same buffer separated by a zero.
64        if(tagcap < tagsize + 6)
65        {
66            REALLOCATE(tagbuf, tagcap, tagsize, c);
67        }
68        //unrolled part of the loop (process 4 chars)
69        FOR_4
70        (
71        {
72            ch = *currentchar;
73            switch(ch)
74            {
75                case ' ':
76                case '\t':
77                {
78                    //ignore spaces
79                    break;
80                }
81                //CR, LF
82                case 10:
83                case 13:
84                {
85                    currentcharref = NextLine(currentchar);
86                    return LT_NAME;
87                    break;
88                }
89                //header start found.
90                case '[':
91                {
92                    currentcharref = currentchar;
93                    return LT_HEADER;
94                    break;
95                }
96                case '\0':
97                {
98                    currentcharref = currentchar;
99                    return LT_NAME;
100                    break;
101                }
102                default:
103                {       
104                    //value found, if tag name is not empty, start reading val
105                    if(ch == namevalsep)
106                    {
107                        if(tagsize)
108                        {
109                            //adding trailing zero to the tag name
110                            tagbuf[tagsize] = 0;
111                            ++tagsize;
112                            currentcharref = currentchar + 1;
113                            return LT_VAL;
114                        }
115                        else
116                        {
117                            WARNING("Empty tag name.");
118                            currentcharref = NextLine(currentchar);
119                            return LT_NAME;
120                        }
121                    } 
122                    if(ch == comment)
123                    {
124                        currentcharref = NextLine(currentchar);
125                        return LT_NAME;
126                    }
127                    //add new char to tag name.
128                    tagbuf[tagsize] = ch;
129                    ++tagsize;
130                    break;
131                }     
132            }
133            ++currentchar;
134        }
135        )
136    }
137}
138
139inline ui INISection::TagValue(const c * & currentcharref, ui tagsize)
140{
141    //ptr to the current character
142    const c * currentchar = currentcharref;
143    //current character
144    register c ch;
145    //reading characters of value
146    for(; ; ++currentchar)
147    {
148        ch = *currentchar;
149        switch(ch)
150        {
151            case ' ':
152            case '\t':
153            {
154                //ignore spaces
155                break;
156            }
157            //CR, LF
158            case 10:
159            case 13:
160            {
161                currentcharref = NextLine(currentchar);
162                return tagsize;
163                break;
164            }
165            case '\0':
166            {
167                currentcharref = currentchar;
168                return tagsize;
169                break;
170            }
171            default:
172            {
173                if(ch == comment)
174                {
175                    currentcharref = NextLine(currentchar);
176                    return tagsize;
177                }
178                //need 1 more char for trailing 0
179                if(tagcap < tagsize + 2)
180                {
181                    REALLOCATE(tagbuf, tagcap, tagsize, c);
182                }
183                //add new char to value
184                tagbuf[tagsize] = ch;
185                ++tagsize;
186            }
187        }
188    }
189}
190
191inline bool INISection::Header(const c * & currentcharref)
192{
193    //ptr to the current character
194    const c * currentchar = currentcharref;
195    //current character
196    register c ch;
197    //Name doesn't skip [ so we skip it here
198    ++currentchar;
199    //searching for first newline
200    for(; ; ++currentchar)
201    {
202        ch = *currentchar;
203        switch(ch)
204        {
205            //CR, LF
206            case 10:
207            case 13:
208            {
209                //not a header
210                currentcharref = NextLine(currentchar);
211                return false;
212                break;
213            }
214            case ']':
215            {
216                //if empty, ignore
217                return static_cast<bool>(currentchar - currentcharref);
218                break;
219            }
220            case '\0':
221            {
222                //not a header
223                currentcharref = currentchar;
224                return false;
225                break;
226            }
227            default:
228            {
229                if(ch == comment)
230                {
231                    currentcharref = NextLine(currentchar);
232                    return false;
233                }
234                break;
235            }
236        }
237    }
238}
239
240void INISection::Init(const c * const sectionname, const c * * const currentcharptr, 
241                      Allocator * const alloc)
242{
243    assert(sectionname);
244    assert(alloc);
245    assert(currentcharptr);
246    assert(*currentcharptr);
247    assert(temptags);
248    assert(tagbuf);
249    //Copying allocator ptr
250    Alloc = alloc;
251    //Copying name of the section
252    const ui namelen = strlen(sectionname) + 1;
253    assert(namelen > 1);
254    Name = Alloc->NewSpace(namelen);
255    memcpy(Name, sectionname, namelen);
256    //ptr to the current character in buffer
257    const c * currentchar = *currentcharptr;
258    //Iterating through lines in the buffer
259    while(*currentchar != '\0')
260    {
261        //size of tag name (if any) read
262        ui namesize = 0;
263        //goes to start of next line if nothing found,
264        //if value found, stops right after name=value separator,
265        //if header found, stays at beginning of line
266        LineToken token = TagName(currentchar, namesize);
267        //value found (this line is a name=value pair)
268        if(token == LT_VAL)
269        {
270            //size of name and value
271            ui tagsize = TagValue(currentchar, namesize);
272            //value is empty
273            if(namesize == tagsize)
274            {
275                WARNING("Empty value in a tag (no characters after name=value " 
276                        "separator. Ignoring. Section: %s", Name);
277                continue;
278            }
279            //adding trailing zero to the tag value
280            tagbuf[tagsize] = 0;
281            ++tagsize;
282            //if needed, reallocate the temp tags buffer
283            if(Length >= temptagscap)
284            {
285                REALLOCATE(temptags, temptagscap, Length, c *);
286            }
287            //Add new tag to temp tags buffer.
288            c * newtag = Alloc->NewSpace(tagsize);
289            memcpy(newtag, tagbuf, tagsize * sizeof(c));
290            Insert(temptags, Length, newtag);
291            ++Length;
292        }
293        //header found
294        else if(token == LT_HEADER)
295        {
296            //if this line is a header, we're finished loading the section
297            if(Header(currentchar))
298            {
299                break;
300            }
301            //else Header() leaves currentchar at the start of next line
302        }
303    }
304    //Updating line token ptr of the caller
305    *currentcharptr = currentchar;
306    //There are no tags in the section
307    if(!Length)
308    {
309        WARNING("Empty section. Section: %s", Name);
310    }
311    //Copy temp lines buffer to Lines
312    Tags = new c * [Length];
313    memcpy(Tags, temptags, Length * sizeof(c *));
314}
315
316INISection::~INISection()
317{
318    //There is no need to tell allocator to delete data here,
319    //since this is only called on INIFile destruction and that's
320    //when Allocator gets destroyed and deletes the data anyway.
321    //However, if/when section deletion is implemented, there
322    //will need to be a separate dtor to handle that case.
323    if(Tags)
324    {
325        delete [] Tags;
326    }
327}
328
329inline bool INISection::ReadString(const char * const name, const char * & out) const
330{
331    assert(name);
332    i pos = BinarySearch(Tags, Length, name);
333    if(pos >= 0)
334    {
335        out = Tags [pos] + strlen(Tags[pos]) + 1;
336        return true;
337    }
338    return false;
339}
340
341bool INISection::ReadInt(const char * const name, int & out) const
342{
343    assert(name);
344    //Points to char where strtol finished processing. Used to check for errors
345    c * tail;
346    errno = 0;
347    const c * valstr;
348    //Requested tag does not exist
349    if(!ReadString(name, valstr))
350    {
351        return false;
352    }
353    const long tempout = strtol(valstr, &tail, 0);
354    const c tailc = tail[0];
355    if(tailc == valstr[0])
356    {
357        ERROR("Non-integer value in a tag where integer is expected."
358              "Section: %s Tag: %s Value: %s", Name, name, valstr);
359        return false;
360    }
361    if(errno)
362    {
363        //this is an error because in my tests strtol returned -1 on overflow.
364        ERROR("Integer value out of range."
365              "Section: %s Tag: %s Value: %s", Name, name, valstr);
366        return false;
367    }
368    #ifdef INI_DEBUG
369    if(tailc)
370    {
371        WARNING("Redunant characters in a tag where integer is "
372                "expected. Reading integer."
373                "Section: %s Tag: %s Value: %s", Name, name, valstr);
374    }
375    #endif
376    out = static_cast<int>(tempout);
377    return true;
378}
379
380bool INISection::ReadFloat(const char * const name, float & out) const
381{
382    assert(name);
383    //Points to char where strtof finished processing. Used to check for errors
384    c * tail;
385    errno = 0;
386    const c * valstr;
387    //Requested tag does not exist
388    if(!ReadString(name, valstr))
389    {
390        return false;
391    }
392    const float tempout = strtof(valstr, &tail);
393    const c tailc = tail[0];
394    if(tailc == valstr[0])
395    {
396        ERROR("Non-float value in a tag where float is expected."
397              "Section: %s Tag: %s Value: %s", Name, name, valstr);
398        return false;
399    }
400    if(errno)
401    {
402        ERROR("Float value out of range."
403              "Section: %s Tag: %s Value: %s", Name, name, valstr);
404        return false;
405    }
406    #ifdef INI_DEBUG
407    if(tailc)
408    {
409        WARNING("Redunant characters in a tag where float is "
410                "expected. Reading float."
411                "Section: %s Tag: %s Value: %s", Name, name, valstr);
412    }
413    #endif
414    out = tempout;
415    return true;
416}
417
418bool INISection::ReadBool( const char * const name, bool & out) const
419{
420    assert(name);
421    const c * valstr;
422    //Requested tag does not exist
423    if(!ReadString(name, valstr))
424    {
425        return false;
426    }
427    //Parsing bool using the first character only
428    switch(valstr[0])
429    {
430        case 't':
431        case 'T':
432        case 'y':
433        case 'Y':
434        case '1':
435            out = true;
436            return true;
437        case 'f':
438        case 'F':
439        case 'n':
440        case 'N':
441        case '0':
442            out = false;
443            return true;
444        default:
445            return false;
446    }
447    return false;
448}
449
450unsigned INISection::ArraySize(const char * const name) const
451{
452    assert(name);
453    ui namelen = strlen(name);
454    //ptr to current tag
455    const c * tag;
456    //Points to char where strtol finished processing. Used to check for errors
457    c * tail;
458    //Index of current array element
459    ui elemidx; 
460    ui indicescap = 32;
461    ui indicessize = 0;
462    //Stores indices of found elements
463    ui * indices = new ui [indicescap];
464    for(ui idx = 0; idx < Length; ++idx)
465    {
466        tag = Tags[idx];
467        //if tag name starts by name
468        if(!strncmp(name, tag, namelen))
469        {
470            errno = 0;
471            elemidx = strtol(tag + namelen, &tail, 0);   
472            //Note: If tag contains no integer, elemidx will be set to 0 which
473            //will never be read.
474            //Redunant chars after int - probably a different tag name or error
475            if(tail[0])
476            {
477                WARNING("Redunant characters after integer in tag name. "
478                        "Ignoring. Section: %s Tag name: %s", Name, tag);
479                continue;
480            }
481            #ifdef INI_DEBUG
482            //Huge (out of range) integer in tag name-
483            //Probably a different tag name or error
484            if(errno == ERANGE)
485            {
486                WARNING("Integer in tag name out of range. Ignoring. "
487                        "Section: %s Tag name: %s", Name, tag);
488                continue;
489            }
490            #endif
491            //elemidx can be 0 if tag contains no integer
492            if(elemidx)
493            {
494                //Reallocating indices if out of space
495                if(indicessize >= indicescap)
496                {
497                    REALLOCATE(indices, indicescap, indicessize, ui);
498                }
499                //Correct index, counting this array element
500                indices[indicessize] = elemidx - 1;
501                ++indicessize;
502            }
503        }
504    }
505    ui elems = 0;
506    bool finished = false;
507    //Count number of consecutive indices
508    while(!finished)
509    {
510        finished = true;
511        for(ui idx = 0; idx < indicessize; ++idx)
512        {
513            if(indices[idx] == elems)
514            {
515                ++elems;
516                finished = false;
517            }
518        }
519    }
520    return static_cast<unsigned>(elems);
521}
522
523unsigned INISection::ReadStrings(const char * const name, const char * * out, 
524                                 const unsigned cap) const
525{
526    assert(name);
527    assert(out);
528    ui namelen = strlen(name);
529    //we fill out with zeroes, so we can search for null ptr to determine how
530    //many elements we've read
531    memset(out, 0, sizeof(const char *) * cap);
532    //ptr to current tag
533    const c * tag;
534    //Points to char where strtol finished processing. Used to check for errors
535    c * tail;
536    //Index of current array element
537    ui elemidx; 
538    for(ui idx = 0; idx < Length; ++idx)
539    {
540        tag = Tags[idx];
541        //if tag name starts by name
542        if(!strncmp(name, tag, namelen))
543        {
544            errno = 0;
545            elemidx = strtol(tag + namelen, &tail, 0);   
546            //Note: If tag contains no integer, elemidx will be set to 0 which
547            //will never be read.
548            //Redunant chars after int - probably a different tag name or error
549            if(tail[0])
550            {
551                WARNING("Redunant characters after integer in tag name. "
552                        "Ignoring. Section: %s Tag name: %s", Name, tag);
553                continue;
554            }
555            #ifdef INI_DEBUG
556            //Huge (out of range) integer in tag name-
557            //Probably a different tag name or error
558            if(errno == ERANGE)
559            {
560                WARNING("Integer in tag name out of range. Ignoring. "
561                        "Section: %s Tag name: %s", Name, tag);
562                continue;
563            }
564            #endif
565            //Out of capacity 
566            if(elemidx > cap)
567            {
568                WARNING("Array element out of range."
569                        "Section: %s Tag name: %s Range: 1-%d", Name, tag, cap);
570                continue;
571            }
572            //elemidx can be 0 if tag contains no integer
573            if(elemidx)
574            {
575                //Correct index at the end of name, reading array element
576                out[elemidx - 1] = (tag) + strlen(tag) + 1;
577            }
578        }
579    }
580    //return number of valid elements read
581    //(if, say, elem 19 is missing, only elems 0-18 are valid)
582    for(unsigned elem = 0; elem < cap; ++elem)
583    {
584        if(!out[elem])
585        {
586            return elem;
587        }
588    }
589    return cap;
590}
591
592unsigned INISection::ReadInts(const char * const name, int * out, 
593                              const unsigned cap) const
594{
595    assert(name);
596    assert(out);
597    //Array of value strings to be converted to ints
598    const c * * const valstrs = new const c * [cap];
599    //Number of strings read by ReadStrings to valstrs
600    const ui tempelems = ReadStrings(name, valstrs, cap);
601    //Ptr to current string in valstrs
602    const c * const * valstr = valstrs;
603    //When valstr reaches this, we've iterated over all the strings
604    const c * const * maxvalstr = valstrs + tempelems;
605    //Number of actual valid ints written to out
606    unsigned elems = 0;
607    //Points to char where strtol finished processing. Used to check for errors
608    c * tail;
609    //Iterating through strings read by ReadStrings and converting them to ints
610    for(; valstr < maxvalstr; ++valstr)
611    {   
612        errno = 0;
613        const long tempelem = strtol(*valstr, &tail, 0);
614        const c tailc = tail[0];
615        if(tailc == *valstr[0])
616        {
617            ERROR("Non-integer value in an array tag where "
618                  "integer is expected. Terminating array "
619                  "reading. Section: %s Tag: %s%u Value: %s", 
620                  Name, name, elems + 1, *valstr);
621            break;
622        }
623        if(errno)
624        {
625            //this is an error because in some of my tests strtol returned -1 on overflow.
626            ERROR("Integer value in an array tag out of range."
627                  "Terminating array reading.Section: %s Tag: "
628                  "%s%u Value: %s", Name, name, elems + 1, *valstr);
629            break;
630        }
631        #ifdef INI_DEBUG
632        if(tailc)
633        {
634            WARNING("Redunant characters in a tag where integer "
635                    "is expected. Reading integer. Section: %s "
636                    "Tag: %s%u Value: %s", Name, name, elems + 1,
637                    *valstr);
638        }
639        #endif
640        out[elems] = static_cast<int>(tempelem);   
641        ++elems;
642    }
643    delete [] valstrs;
644    return elems;
645}       
646
647unsigned INISection::ReadFloats(const char * const name, float * out, 
648                                const unsigned cap) const
649{
650    assert(name);
651    assert(out);
652    //Array of value strings to be converted to floats
653    const c * * const valstrs = new const c * [cap];
654    //Number of strings read by ReadStrings to valstrs
655    const ui tempelems = ReadStrings(name, valstrs, cap);
656    //Ptr to current string in valstrs
657    const c * const * valstr = valstrs;
658    //When valstr reaches this, we've iterated over all the strings
659    const c * const * const maxvalstr = valstrs + tempelems;
660    //Number of actual valid floats written to out
661    unsigned elems = 0;
662    //Points to char where strtof finished processing. Used to check for errors
663    c * tail;
664    //Iterating through strings read by ReadStrings and converting them to floats
665    for(; valstr < maxvalstr; ++valstr)
666    {   
667        errno = 0;
668        const f tempelem = strtof(*valstr, &tail);
669        const c tailc = tail[0];
670        if(tailc == *valstr[0])
671        {
672            ERROR("Non-float value in an array tag where "
673                  "float is expected. Terminating array "
674                  "reading. Section: %s Tag: %s%u Value: %s", 
675                  Name, name, elems + 1, *valstr);
676            break;
677        }
678        if(errno)
679        {
680            ERROR("Float value in an array tag out of range."
681                  "Terminating array reading.Section: %s Tag: "
682                  "%s%u Value: %s", Name, name, elems + 1, *valstr);
683            break;
684        }
685        #ifdef INI_DEBUG
686        if(tailc)
687        {
688            WARNING("Redunant characters in a tag where float "
689                    "is expected. Reading float. Section: %s "
690                    "Tag: %s%u Value: %s", Name, name, elems + 1,
691                    *valstr);
692        }
693        #endif
694        out[elems] = tempelem;   
695        ++elems;
696    }
697    delete [] valstrs;
698    return elems;
699}     
700
701unsigned INISection::ReadBools(const char * const name, bool * out, 
702                               const unsigned cap) const
703{
704    assert(name);
705    assert(out);
706    //Array of value strings to be converted to bools
707    const c * * const valstrs = new const c * [cap];
708    //Number of strings read by ReadStrings to valstrs
709    const ui tempelems = ReadStrings(name, valstrs, cap);
710    //Ptr to current string in valstrs
711    const c * const * valstr = valstrs;
712    //When valstr reaches this, we've iterated over all the strings
713    const c * const * const maxvalstr = valstrs + tempelems;
714    //Number of actual valid bools written to out
715    unsigned elems = 0;
716    //Iterating through strings read by ReadStrings and converting them to bools
717    for(; valstr < maxvalstr; ++valstr, ++elems)
718    {
719        switch(*valstr[0])
720        {
721            case 't':
722            case 'T':
723            case 'y':
724            case 'Y':
725            case '1':
726                out[elems] = true;
727                break;
728            case 'f':
729            case 'F':
730            case 'n':
731            case 'N':
732            case '0':
733                out[elems] = false;
734                break;
735            default:
736                goto BREAK_FOR;
737        }
738    }
739    BREAK_FOR:;
740    delete [] valstrs;
741    return elems;
742}
743
744#ifndef INI_NO_STL
745unsigned INISection::ReadStrings(const std::string & name, 
746                                 std::vector<std::string> & out) const
747{
748    const char * cname = name.c_str();
749    unsigned numelems = ArraySize(cname);
750    //Read data to this array first
751    const char * * tempstrs = new const char * [numelems];
752    numelems = ReadStrings(cname, tempstrs, numelems);
753    out.reserve(numelems);
754    //Move data to output vector
755    for(ui str = 0; str < numelems; ++str)
756    {
757        out.push_back(std::string(tempstrs[str]));
758    }
759    delete [] tempstrs;
760    return numelems;
761}
762
763unsigned INISection::ReadInts(const std::string & name, 
764                                 std::vector<int> & out) const
765{
766    const char * cname = name.c_str();
767    unsigned  numelems = ArraySize(cname);
768    //Read data to this array first
769    int * tempints = new int [numelems];
770    numelems = ReadInts(cname, tempints, numelems);
771    out.reserve(numelems);
772    //Move data to output vector
773    for(ui in = 0; in < numelems; ++in)
774    {
775        out.push_back(tempints[in]);
776    }
777    delete [] tempints;
778    return numelems;
779}
780
781unsigned INISection::ReadFloats(const std::string & name, 
782                                 std::vector<float> & out) const
783{
784    const char * cname = name.c_str();
785    unsigned  numelems = ArraySize(cname);
786    //Read data to this array first
787    float * tempfloats = new float [numelems];
788    numelems = ReadFloats(cname, tempfloats, numelems);
789    out.reserve(numelems);
790    //Move data to output vector
791    for(ui fl = 0; fl < numelems; ++fl)
792    {
793        out.push_back(tempfloats[fl]);
794    }
795    delete [] tempfloats;
796    return numelems;
797}
798
799unsigned INISection::ReadBools(const std::string & name, 
800                                 std::vector<bool> & out) const
801{
802    const char * cname = name.c_str();
803    unsigned numelems = ArraySize(cname);
804    //Read data to this array first
805    bool * tempbools = new bool [numelems];
806    numelems = ReadBools(cname, tempbools, numelems);
807    out.reserve(numelems);
808    //Move data to output vector
809    for(ui bo = 0; bo < numelems; ++bo)
810    {
811        out.push_back(tempbools[bo]);
812    }
813    delete [] tempbools;
814    return numelems;
815}
816#endif
817
818
819unsigned INISection::GetTags(const char *tags[], unsigned n) const {
820
821  const unsigned N = n > Length ? Length : n;
822
823  for(unsigned i = 0; i < N; i++)
824    tags[i] = Tags[i];
825
826  return static_cast<unsigned>(N);
827
828
829}
830
831unsigned INISection::GetTags(tagval tags[], unsigned n) const {
832
833  const unsigned N = n > Length ? Length : n;
834
835  for(unsigned  i = 0; i < N; i++) {
836    tags[i].tag = Tags[i];
837    tags[i].val = Tags[i] + strlen(Tags[i]) + 1;
838  }
839
840  return static_cast<unsigned>(N);
841
842
843}
844
845
846#ifndef INI_NO_STL
847void INISection::GetTags(std::vector<std::string>& tags) const {
848
849
850  for(unsigned i = 0; i < Length; i++)
851    tags.push_back(Tags[i]);
852
853
854}
855
856
857void INISection::GetTags(std::vector<std::pair<std::string, std::string> >& tags) const {
858
859
860  for(unsigned i = 0; i < Length; i++) {
861    std::pair<std::string, std::string> p(Tags[i], Tags[i]+strlen(Tags[i])+1);
862    tags.push_back(p);
863  }
864
865
866}
867
868
869
870#endif
Note: See TracBrowser for help on using the repository browser.