2020-04-16 23:38:25 +00:00
|
|
|
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
|
|
|
|
/* Please make sure that the TAB width in your editor is set to 4 spaces */
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@file confini.c
|
|
|
|
@brief libconfini functions
|
|
|
|
@author Stefano Gioffré
|
|
|
|
@copyright GNU General Public License, version 3 or any later version
|
|
|
|
@version 1.14.0
|
|
|
|
@date 2016-2020
|
|
|
|
@see https://madmurphy.github.io/libconfini
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
|
|
/*/|
|
|
|
|
(_|_) _ _ _ __ _ _
|
|
|
|
| (_) |__ ___ ___ _ __ / _(_)_ __ (_)
|
|
|
|
| | | '_ \ / __/ _ \| '_ \| |_| | '_ \| |
|
|
|
|
| | | |_) | (_| (_) | | | | _| | | | | |
|
|
|
|
|_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _
|
|
|
|
( | )
|
|
|
|
|/*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
|
|
|
|
@def INIFORMAT_TABLE_AS(_____)
|
|
|
|
|
|
|
|
Content of the table:
|
|
|
|
|
|
|
|
- Bits 1-19: INI syntax
|
|
|
|
- Bits 20-22: INI semantics
|
|
|
|
- Bits 23-24: Human syntax (disabled entries)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@typedef int (* IniStatsHandler) (
|
|
|
|
IniStatistics * statistics,
|
|
|
|
void * user_data
|
|
|
|
)
|
|
|
|
|
|
|
|
@param statistics
|
|
|
|
A pointer to the #IniStatistics to handle
|
|
|
|
@param user_data
|
|
|
|
The custom argument previously passed to the caller function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@typedef int (* IniDispHandler) (
|
|
|
|
IniDispatch *dispatch,
|
|
|
|
void * user_data
|
|
|
|
)
|
|
|
|
|
|
|
|
@param dispatch
|
|
|
|
A pointer to the #IniDispatch to handle
|
|
|
|
@param user_data
|
|
|
|
The custom argument previously passed to the caller function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@typedef int (* IniStrHandler) (
|
|
|
|
char * ini_string,
|
|
|
|
size_t string_length,
|
|
|
|
size_t string_num,
|
|
|
|
IniFormat format,
|
|
|
|
void * user_data
|
|
|
|
)
|
|
|
|
|
|
|
|
@param ini_string
|
|
|
|
The INI string to handle
|
|
|
|
@param string_length
|
|
|
|
The length of the INI string in bytes
|
|
|
|
@param string_num
|
|
|
|
The unique number that identifies @p ini_string within a
|
|
|
|
sequence of INI strings; it equals zero if @p ini_string is the
|
|
|
|
first or the only member of the sequence
|
|
|
|
@param format
|
|
|
|
The format of the INI file from which @p ini_string has been extracted
|
|
|
|
@param user_data
|
|
|
|
The custom argument previously passed to the caller function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@typedef int (* IniSubstrHandler) (
|
|
|
|
const char * ini_string,
|
|
|
|
size_t fragm_offset,
|
|
|
|
size_t fragm_length,
|
|
|
|
size_t fragm_num,
|
|
|
|
IniFormat format,
|
|
|
|
void * user_data
|
|
|
|
)
|
|
|
|
|
|
|
|
@param ini_string
|
|
|
|
The INI string containing the fragment to handle
|
|
|
|
@param fragm_offset
|
|
|
|
The offset of the selected fragment in bytes
|
|
|
|
@param fragm_length
|
|
|
|
The length of the selected fragment in bytes
|
|
|
|
@param fragm_num
|
|
|
|
The unique number that identifies the selected fragment within a
|
|
|
|
sequence of fragments of @p ini_string; it equals zero if the
|
|
|
|
fragment is the first or the only member of the sequence
|
|
|
|
@param format
|
|
|
|
The format of the INI file from which @p ini_string has been
|
|
|
|
extracted
|
|
|
|
@param user_data
|
|
|
|
The custom argument previously passed to the caller function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@struct IniFormat
|
|
|
|
|
|
|
|
@property IniFormat::delimiter_symbol
|
|
|
|
The key-value delimiter character (ASCII only allowed); if set
|
|
|
|
to `\0`, any space is delimiter
|
|
|
|
(`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format
|
|
|
|
given, `IniFormat::delimiter_symbol` matches a metacharacter
|
|
|
|
(`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have
|
|
|
|
higher priority than its role as delimiter symbol (i.e., the
|
|
|
|
format will have no key-value delimiter); you may use the
|
|
|
|
#IniDelimiters `enum` for this.
|
|
|
|
@property IniFormat::case_sensitive
|
|
|
|
If set to `true`, string comparisons will be always
|
|
|
|
case-sensitive.
|
|
|
|
@property IniFormat::semicolon_marker
|
|
|
|
The rule of the semicolon character (use `enum`
|
|
|
|
#IniCommentMarker for this).
|
|
|
|
@property IniFormat::hash_marker
|
|
|
|
The rule of the hash character (use `enum` #IniCommentMarker for
|
|
|
|
this).
|
|
|
|
@property IniFormat::section_paths
|
|
|
|
Defines whether and how the format supports sections (use `enum`
|
|
|
|
#IniSectionPaths for this).
|
|
|
|
@property IniFormat::multiline_nodes
|
|
|
|
Defines which class of entries are allowed to be multi-line (use
|
|
|
|
`enum` #IniMultiline for this).
|
|
|
|
@property IniFormat::no_spaces_in_names
|
|
|
|
If set to `true`, key and section names containing spaces (even
|
|
|
|
within quotes) will be rendered as #INI_UNKNOWN. Note that
|
|
|
|
setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not
|
|
|
|
automatically set this option to `true` (spaces will still be
|
|
|
|
allowed in section names).
|
|
|
|
@property IniFormat::no_single_quotes
|
|
|
|
If set to `true`, the single-quote character (`'`) will be
|
|
|
|
considered as a normal character.
|
|
|
|
@property IniFormat::no_double_quotes
|
|
|
|
If set to `true`, the double-quote character (`"`) will be
|
|
|
|
considered as a normal character.
|
|
|
|
@property IniFormat::implicit_is_not_empty
|
|
|
|
If set to `true`, implicit keys (see @ref libconfini) will
|
|
|
|
be always dispatched using the values given by the global
|
|
|
|
variables #INI_GLOBAL_IMPLICIT_VALUE and
|
|
|
|
#INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value
|
|
|
|
and to #IniDispatch::v_len respectively; if set to `false`,
|
|
|
|
implicit keys will be considered to be empty keys.
|
|
|
|
@property IniFormat::do_not_collapse_values
|
|
|
|
If set to `true`, sequences of one or more spaces in values
|
|
|
|
(`/\s+/`) will be dispatched verbatim.
|
|
|
|
@property IniFormat::preserve_empty_quotes
|
|
|
|
If set to `true`, and if single/double quotes are
|
|
|
|
metacharacters, ensures that, within values, empty strings
|
|
|
|
enclosed between quotes (`""` or `''`) will not be collapsed
|
|
|
|
together with the spaces that surround them. This option is
|
|
|
|
useful for values containing space-delimited arrays, in order to
|
|
|
|
preserve their empty members -- as in, for instance:
|
|
|
|
`coordinates = "" ""`. Note that, in section and key names,
|
|
|
|
empty strings enclosed between quotes are _always_ collapsed
|
|
|
|
together with their surrounding spaces.
|
|
|
|
@property IniFormat::disabled_after_space
|
|
|
|
If set to `true`, what follows `/[#;]\s/` is allowed to be
|
|
|
|
parsed as a disabled entry.
|
|
|
|
@property IniFormat::disabled_can_be_implicit
|
|
|
|
If set to `false`, comments that do not contain a key-value
|
|
|
|
delimiter will never be parsed as disabled keys, but always as
|
|
|
|
simple comments (even if the format supports implicit keys).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@struct IniStatistics
|
|
|
|
|
|
|
|
@property IniStatistics::format
|
|
|
|
The format of the INI file (see #IniFormat)
|
|
|
|
@property IniStatistics::bytes
|
|
|
|
The size in bytes of the parsed file
|
|
|
|
@property IniStatistics::members
|
|
|
|
The size in number of members (nodes) of the parsed file -- this
|
|
|
|
number always equals the number of dispatches that will be sent
|
|
|
|
by #load_ini_file(), #load_ini_path() or #strip_ini_cache()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@struct IniDispatch
|
|
|
|
|
|
|
|
@property IniDispatch::format
|
|
|
|
The format of the INI file (see #IniFormat)
|
|
|
|
@property IniDispatch::type
|
|
|
|
The dispatch type (see `enum` #IniNodeType)
|
|
|
|
@property IniDispatch::data
|
|
|
|
#IniDispatch::data can contain a comment, a section path or a
|
|
|
|
key name depending, on #IniDispatch::type; cannot be `NULL`
|
|
|
|
@property IniDispatch::value
|
|
|
|
It can be the value of a key element, an empty string or it can
|
|
|
|
point to the address pointed by the global variable
|
|
|
|
#INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in
|
|
|
|
which `IniDispatch::value` can be `NULL`_)
|
|
|
|
@property IniDispatch::append_to
|
|
|
|
The current section path; cannot be `NULL`
|
|
|
|
@property IniDispatch::d_len
|
|
|
|
The length of the string #IniDispatch::data
|
|
|
|
@property IniDispatch::v_len
|
|
|
|
The length of the string #IniDispatch::value
|
|
|
|
@property IniDispatch::at_len
|
|
|
|
The length of the string #IniDispatch::append_to
|
|
|
|
@property IniDispatch::dispatch_id
|
|
|
|
The dispatch number (the first dispatch is number zero)
|
|
|
|
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*\
|
|
|
|
|*|
|
|
|
|
|*| LOCAL ENVIRONMENT
|
|
|
|
|*| ________________________________
|
|
|
|
\*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* PREPROCESSOR PREAMBLE */
|
|
|
|
|
|
|
|
|
|
|
|
/* String concatenation facilities */
|
|
|
|
#define __PP_CAT__(STR1, STR2) STR1##STR2
|
|
|
|
#define __PP_UCAT__(STR1, STR2) STR1##_##STR2
|
|
|
|
#define __PP_EVALUCAT__(STR1, STR2) __PP_UCAT__(STR1, STR2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* PREPROCESSOR ENVIRONMENT */
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef CONFINI_IO_FLAVOR
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief The I/O API to use (possibly overridden via
|
|
|
|
`-DCONFINI_IO_FLAVOR=[FLAVOR]`)
|
|
|
|
|
|
|
|
Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX`
|
|
|
|
|
|
|
|
**/
|
|
|
|
#define CONFINI_IO_FLAVOR CONFINI_STANDARD
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* PREPROCESSOR GLUE */
|
|
|
|
|
|
|
|
|
|
|
|
#define _LIBCONFINI_PRIVATE_ITEM_(GROUP, ITEM) \
|
|
|
|
__PP_EVALUCAT__(__PP_CAT__(_LIB, GROUP), __PP_CAT__(ITEM, _))
|
|
|
|
#define _LIBCONFINI_CURRENT_FLAVOR_GET_(NAME) \
|
|
|
|
_LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME)
|
|
|
|
#define _LIBCONFINI_IS_FLAVOR_(NAME) \
|
|
|
|
_LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \
|
|
|
|
_LIBCONFINI_PRIVATE_ITEM_(NAME, FLAVOR)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* AVAILABLE I/O FLAVORS */
|
|
|
|
|
|
|
|
|
|
|
|
/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default, REQUIRED) */
|
|
|
|
#define _LIBCONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END)
|
|
|
|
#define _LIBCONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR)
|
|
|
|
#define _LIBCONFINI_STANDARD_FT_T_ long signed int
|
|
|
|
/* Any unique non-zero integer to identify this I/O API */
|
|
|
|
#define _LIBCONFINI_STANDARD_FLAVOR_ 1
|
|
|
|
|
|
|
|
/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */
|
|
|
|
#define _LIBCONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END)
|
|
|
|
#define _LIBCONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR)
|
|
|
|
#define _LIBCONFINI_POSIX_FT_T_ off_t
|
|
|
|
/* Any unique non-zero integer to identify this I/O API */
|
|
|
|
#define _LIBCONFINI_POSIX_FLAVOR_ 2
|
|
|
|
|
|
|
|
/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */
|
|
|
|
#if _LIBCONFINI_IS_FLAVOR_(CONFINI_POSIX)
|
|
|
|
#ifndef _POSIX_C_SOURCE
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* It is possible to add other I/O APIs here. Feel free to contribute! */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* CHECKS */
|
|
|
|
|
|
|
|
|
|
|
|
#if _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0
|
|
|
|
#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* HEADERS */
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "confini.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ALIASES */
|
|
|
|
|
|
|
|
|
|
|
|
#define _LIBCONFINI_FALSE_ 0
|
|
|
|
#define _LIBCONFINI_TRUE_ 1
|
|
|
|
#define _LIBCONFINI_CHARBOOL_ unsigned char
|
|
|
|
#define _LIBCONFINI_SIMPLE_SPACE_ ' '
|
|
|
|
#define _LIBCONFINI_HT_ '\t'
|
|
|
|
#define _LIBCONFINI_FF_ '\f'
|
|
|
|
#define _LIBCONFINI_VT_ '\v'
|
|
|
|
#define _LIBCONFINI_CR_ '\r'
|
|
|
|
#define _LIBCONFINI_LF_ '\n'
|
|
|
|
#define _LIBCONFINI_BACKSLASH_ '\\'
|
|
|
|
#define _LIBCONFINI_OPEN_SECTION_ '['
|
|
|
|
#define _LIBCONFINI_CLOSE_SECTION_ ']'
|
|
|
|
#define _LIBCONFINI_SUBSECTION_ '.'
|
|
|
|
#define _LIBCONFINI_SEMICOLON_ ';'
|
|
|
|
#define _LIBCONFINI_HASH_ '#'
|
|
|
|
#define _LIBCONFINI_DOUBLE_QUOTES_ '"'
|
|
|
|
#define _LIBCONFINI_SINGLE_QUOTES_ '\''
|
|
|
|
#define _LIBCONFINI_SEEK_EOF_(FILEPTR) \
|
|
|
|
_LIBCONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR)
|
|
|
|
#define _LIBCONFINI_FTELL_(FILEPTR) \
|
|
|
|
_LIBCONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR)
|
|
|
|
#define _LIBCONFINI_OFF_T_ \
|
|
|
|
_LIBCONFINI_CURRENT_FLAVOR_GET_(FT_T)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* FUNCTIONAL MACROS AND CONSTANTS */
|
|
|
|
|
|
|
|
|
|
|
|
/* The character that will replace sequences of one or more spaces (`/\s+/`) */
|
|
|
|
#define _LIBCONFINI_COLLAPSED_ _LIBCONFINI_SIMPLE_SPACE_
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
These may be any character in theory... But after the left-trim of each node
|
|
|
|
leading spaces work pretty well as metacharacters...
|
|
|
|
|
|
|
|
*/
|
|
|
|
/* Internal marker of standard comments */
|
|
|
|
#define _LIBCONFINI_SC_INT_MARKER_ _LIBCONFINI_SIMPLE_SPACE_
|
|
|
|
/* Internal marker of inline comments */
|
|
|
|
#define _LIBCONFINI_IC_INT_MARKER_ _LIBCONFINI_HT_
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a character can be escaped within a given format
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IS_ESC_CHAR_(CHR, FMT) ( \
|
|
|
|
CHR == _LIBCONFINI_BACKSLASH_ ? \
|
|
|
|
!INIFORMAT_HAS_NO_ESC(FMT) \
|
|
|
|
: CHR == _LIBCONFINI_DOUBLE_QUOTES_ ? \
|
|
|
|
!FMT.no_double_quotes \
|
|
|
|
: \
|
|
|
|
CHR == _LIBCONFINI_SINGLE_QUOTES_ && !FMT.no_single_quotes \
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a character represents any marker within a given format
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IS_ANY_MARKER_(CHR, FMT) ( \
|
|
|
|
CHR == _LIBCONFINI_HASH_ ? \
|
|
|
|
FMT.hash_marker != INI_IS_NOT_A_MARKER \
|
|
|
|
: \
|
|
|
|
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER \
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a character represents any marker except `INI_IGNORE` within a
|
|
|
|
given format
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IS_COM_MARKER_(CHR, FMT) ( \
|
|
|
|
CHR == _LIBCONFINI_HASH_ ? \
|
|
|
|
FMT.hash_marker != INI_IS_NOT_A_MARKER && FMT.hash_marker != INI_IGNORE \
|
|
|
|
: \
|
|
|
|
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER && FMT.semicolon_marker != INI_IGNORE \
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a character represents a marker of type `INI_DISABLED_OR_COMMENT`
|
|
|
|
within a given format
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IS_DIS_MARKER_(CHR, FMT) ( \
|
|
|
|
CHR == _LIBCONFINI_HASH_ ? \
|
|
|
|
FMT.hash_marker == INI_DISABLED_OR_COMMENT \
|
|
|
|
: \
|
|
|
|
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a character represents a marker of type `INI_IGNORE` within a
|
|
|
|
given format
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IS_IGN_MARKER_(CHR, FMT) ( \
|
|
|
|
CHR == _LIBCONFINI_HASH_ ? \
|
|
|
|
FMT.hash_marker == INI_IGNORE \
|
|
|
|
: \
|
|
|
|
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_IGNORE \
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Checks whether a pointer is within the range
|
|
|
|
`INI_GLOBAL_IMPLICIT_VALUE >= PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN`
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_IMPLICIT_RANGE_(PTR) \
|
|
|
|
(PTR >= INI_GLOBAL_IMPLICIT_VALUE && PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Maybe in the future there will be support for UTF-8 casefold (although probably
|
|
|
|
not -- see "Code considerations" in the Manual), but for now only ASCII...
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_CHR_CASEFOLD_(CHR) (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Possible depths of `_LIBCONFINI_SPACES_` (see function #is_some_space()).
|
|
|
|
|
|
|
|
Please, consider the following three constants as belonging together to a
|
|
|
|
virtual opaque `enum`.
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_WITH_EOL_ -1
|
|
|
|
#define _LIBCONFINI_NO_EOL_ 1
|
|
|
|
#define _LIBCONFINI_JUST_S_T_ 3
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Other constants related to `_LIBCONFINI_SPACES_`
|
|
|
|
|
|
|
|
*/
|
|
|
|
#define _LIBCONFINI_EOL_IDX_ 0
|
|
|
|
#define _LIBCONFINI_SPALEN_ 6
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
The list of space characters -- do not change its order or its content!
|
|
|
|
|
|
|
|
*/
|
|
|
|
static const char _LIBCONFINI_SPACES_[_LIBCONFINI_SPALEN_] = {
|
|
|
|
_LIBCONFINI_LF_,
|
|
|
|
_LIBCONFINI_CR_,
|
|
|
|
_LIBCONFINI_VT_,
|
|
|
|
_LIBCONFINI_FF_,
|
|
|
|
_LIBCONFINI_HT_,
|
|
|
|
_LIBCONFINI_SIMPLE_SPACE_
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief A list of possible string representations of boolean pairs
|
|
|
|
|
|
|
|
There may be infinite pairs here. Each pair must be organized according to the
|
|
|
|
following order:
|
|
|
|
|
|
|
|
1. Signifier of `false`
|
|
|
|
2. Signifier of `true`
|
|
|
|
|
|
|
|
@note Everything **must** be lowercase in this list.
|
|
|
|
|
|
|
|
**/
|
|
|
|
static const char * const INI_BOOLEANS[][2] = {
|
|
|
|
{ "no", "yes" },
|
|
|
|
{ "false", "true" },
|
|
|
|
{ "off", "on" }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ABSTRACT UTILITIES */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Checks whether a character is a space
|
|
|
|
@param chr The target character
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return A boolean: `true` if the character matches, `false` otherwise
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline _LIBCONFINI_CHARBOOL_ is_some_space (const char chr, const int8_t depth) {
|
|
|
|
register int8_t idx = depth;
|
|
|
|
while (++idx < _LIBCONFINI_SPALEN_ && chr != _LIBCONFINI_SPACES_[idx]);
|
|
|
|
return idx < _LIBCONFINI_SPALEN_;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Soft left trim -- does not change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param offs The offset where to start the left trim
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return The offset of the first non-space character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t ltrim_s (const char * const str, const size_t offs, const int8_t depth) {
|
|
|
|
register size_t idx = offs;
|
|
|
|
while (is_some_space(str[idx++], depth));
|
|
|
|
return idx - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Hard left trim -- **does** change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param offs The offset where to start the left trim
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return The offset of the first non-space character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t ltrim_h (char * const str, const size_t offs, const int8_t depth) {
|
|
|
|
register size_t idx = offs;
|
|
|
|
while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; }
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Shifting left trim -- **does** change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param offs The offset where to start the left trim
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t ltrim_hh (char * const str, const size_t offs, const int8_t depth) {
|
|
|
|
register size_t idx_d = offs, idx_s = offs;
|
|
|
|
while (is_some_space(str[idx_s++], depth));
|
|
|
|
if (--idx_s - idx_d) {
|
|
|
|
while ((str[idx_d++] = str[idx_s++]));
|
|
|
|
for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0');
|
|
|
|
return idx_d - 1;
|
|
|
|
}
|
|
|
|
while (str[idx_s++]);
|
|
|
|
return idx_s - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Soft right trim -- does not change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param len The length of the string
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return The length of the string until the last non-space character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t rtrim_s (const char * const str, const size_t len, const int8_t depth) {
|
|
|
|
register size_t idx = len + 1;
|
|
|
|
while (--idx > 0 && is_some_space(str[idx - 1], depth));
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Hard right trim -- **does** change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param len The length of the string
|
|
|
|
@param depth What is actually considered a space (possible
|
|
|
|
values: `_LIBCONFINI_WITH_EOL_`,
|
|
|
|
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t rtrim_h (char * const str, const size_t len, const int8_t depth) {
|
|
|
|
register size_t idx = len;
|
|
|
|
while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; }
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Unescaped soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`)
|
|
|
|
-- does not change the buffer
|
|
|
|
@param str The target string
|
|
|
|
@param len The length of the string
|
|
|
|
@return The length of the string until the last non-space character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t urtrim_s (const char * const str, const size_t len) {
|
|
|
|
|
|
|
|
register uint8_t abcd = 1;
|
|
|
|
register size_t idx = len;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ continue_urtrim: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
if (idx < 1) {
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (str[--idx]) {
|
|
|
|
|
|
|
|
case _LIBCONFINI_VT_:
|
|
|
|
case _LIBCONFINI_FF_:
|
|
|
|
case _LIBCONFINI_HT_:
|
|
|
|
case _LIBCONFINI_SIMPLE_SPACE_:
|
|
|
|
|
|
|
|
abcd = 1;
|
|
|
|
goto continue_urtrim;
|
|
|
|
|
|
|
|
case _LIBCONFINI_LF_:
|
|
|
|
case _LIBCONFINI_CR_:
|
|
|
|
|
|
|
|
abcd = 3;
|
|
|
|
goto continue_urtrim;
|
|
|
|
|
|
|
|
case _LIBCONFINI_BACKSLASH_:
|
|
|
|
|
|
|
|
if (abcd >>= 1) {
|
|
|
|
|
|
|
|
goto continue_urtrim;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Converts an ASCII string to lower case
|
|
|
|
@param str The target string
|
|
|
|
@return Nothing
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline void string_tolower (char * const str) {
|
|
|
|
for (register char * chrptr = str; *chrptr; chrptr++) {
|
|
|
|
*chrptr = _LIBCONFINI_CHR_CASEFOLD_(*chrptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* CONCRETE UTILITIES */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Unparsed hard left trim (left trim of
|
|
|
|
`/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer
|
|
|
|
@param srcstr The target string (it may contain multi-line
|
|
|
|
escape sequences)
|
|
|
|
@param offs The offset where to start the left trim
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The offset of the first non-trivial character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t qultrim_h (char * const srcstr, const size_t offs, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 Erase the previous character
|
|
|
|
FLAG_64 Erase this character
|
|
|
|
FLAG_128 Continue the loop
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.no_double_quotes ? 130 : 128) | format.no_single_quotes;
|
|
|
|
size_t idx = offs;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
abcd = !(abcd & 28) && is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
|
|
|
|
(abcd & 207) | 64
|
|
|
|
: !(abcd & 12) && (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) ?
|
|
|
|
(
|
|
|
|
abcd & 16 ?
|
|
|
|
(abcd & 239) | 96
|
|
|
|
:
|
|
|
|
abcd | 64
|
|
|
|
)
|
|
|
|
: !(abcd & 25) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
abcd & 4 ?
|
|
|
|
(abcd & 235) | 96
|
|
|
|
:
|
|
|
|
(abcd & 143) | 4
|
|
|
|
)
|
|
|
|
: !(abcd & 22) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
abcd & 8 ?
|
|
|
|
(abcd & 231) | 96
|
|
|
|
:
|
|
|
|
(abcd & 159) | 8
|
|
|
|
)
|
|
|
|
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd & 159) ^ 16
|
|
|
|
:
|
|
|
|
abcd & 31;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 32) {
|
|
|
|
|
|
|
|
srcstr[idx - 1] = '\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (abcd & 64) {
|
|
|
|
|
|
|
|
srcstr[idx] = '\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
|
|
|
|
} while (abcd & 128);
|
|
|
|
|
|
|
|
return abcd & 28 ? idx - 2 : idx - 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Soft left trim within an unparsed disabled entry (left trim of
|
|
|
|
`/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`)
|
|
|
|
-- does not change the buffer
|
|
|
|
@param srcstr The target string (it may contain multi-line
|
|
|
|
escape sequences)
|
|
|
|
@param offs The offset where to start the left trim
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The offset of the first non-trivial character
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t dqultrim_s (const char * const srcstr, const size_t offs, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (7 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 A new line has just begun
|
|
|
|
FLAG_64 Continue the left trim
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
register uint16_t abcd = format.no_single_quotes |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
96;
|
|
|
|
|
|
|
|
register size_t idx = offs;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
abcd = is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
|
|
|
|
(
|
|
|
|
abcd & 28 ?
|
|
|
|
abcd & 63
|
|
|
|
:
|
|
|
|
abcd
|
|
|
|
)
|
|
|
|
: srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ?
|
|
|
|
(
|
|
|
|
abcd & 12 ?
|
|
|
|
(abcd & 47) | 32
|
|
|
|
:
|
|
|
|
(abcd & 111) | 32
|
|
|
|
)
|
|
|
|
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(
|
|
|
|
abcd & 28 ?
|
|
|
|
(abcd & 31) | 16
|
|
|
|
:
|
|
|
|
(abcd & 95) | 16
|
|
|
|
)
|
|
|
|
: (abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
|
|
|
|
abcd & 79
|
|
|
|
: !(abcd & 54) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(abcd & 95) ^ 8
|
|
|
|
: !(abcd & 57) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(abcd & 95) ^ 4
|
|
|
|
: srcstr[idx] ?
|
|
|
|
abcd & 31
|
|
|
|
:
|
|
|
|
abcd & 19;
|
|
|
|
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
|
|
|
|
} while (abcd & 64);
|
|
|
|
|
|
|
|
return abcd & 28 ? idx - 2 : idx - 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Gets the position of the first occurence out of quotes of a
|
|
|
|
given character, stopping after a given number of charcters
|
|
|
|
@param str The string where to search
|
|
|
|
@param chr The character to to search
|
|
|
|
@param len The maximum number of characters to read
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The offset of the first occurence of @p chr, or @p len if
|
|
|
|
@p chr has not been not found
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t getn_metachar_pos (const char * const str, const char chr, const size_t len, const IniFormat format) {
|
|
|
|
|
|
|
|
size_t idx = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (5 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
idx < len && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_WITH_EOL_)));
|
|
|
|
|
|
|
|
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16
|
|
|
|
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8
|
|
|
|
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4
|
|
|
|
: abcd & 15,
|
|
|
|
idx++
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Gets the position of the first occurence out of quotes of a
|
|
|
|
given character
|
|
|
|
@param str The string where to search
|
|
|
|
@param chr The character to to search
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The offset of the first occurence of @p chr or the length of
|
|
|
|
@p str if @p chr has not been not found
|
|
|
|
|
|
|
|
**/
|
|
|
|
static inline size_t get_metachar_pos (const char * const str, const char chr, const IniFormat format) {
|
|
|
|
|
|
|
|
size_t idx = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (5 bits used):
|
|
|
|
|
|
|
|
--> As in #getn_metachar_pos()
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
str[idx] && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_NO_EOL_)));
|
|
|
|
|
|
|
|
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16
|
|
|
|
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8
|
|
|
|
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4
|
|
|
|
: abcd & 15,
|
|
|
|
idx++
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Replaces `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/`
|
|
|
|
with `"$1"`
|
|
|
|
@param srcstr The target string (it may contain multi-line
|
|
|
|
escape sequences)
|
|
|
|
@param len Length of the string
|
|
|
|
@param is_disabled The string represents a disabled entry
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t unescape_cr_lf (char * const srcstr, const size_t len, const _LIBCONFINI_CHARBOOL_ is_disabled, const IniFormat format) {
|
|
|
|
|
|
|
|
register size_t idx_s = 0, idx_d = 0;
|
|
|
|
register uint8_t eol_i = _LIBCONFINI_EOL_IDX_;
|
|
|
|
register _LIBCONFINI_CHARBOOL_ is_escaped = _LIBCONFINI_FALSE_;
|
|
|
|
size_t probe;
|
|
|
|
|
|
|
|
while (idx_s < len) {
|
|
|
|
|
|
|
|
if (
|
|
|
|
is_escaped && (
|
|
|
|
srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i] || srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^= 1]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
srcstr[idx_d - 1] = srcstr[idx_s++];
|
|
|
|
|
|
|
|
if (srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^ 1]) {
|
|
|
|
|
|
|
|
srcstr[idx_d++] = srcstr[idx_s++];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_disabled) {
|
|
|
|
|
|
|
|
probe = ltrim_s(srcstr, idx_s, _LIBCONFINI_NO_EOL_);
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_DIS_MARKER_(srcstr[probe], format)) {
|
|
|
|
|
|
|
|
idx_s = probe + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
is_escaped = _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
is_escaped = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
!is_escaped
|
|
|
|
:
|
|
|
|
_LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
|
|
|
|
srcstr[idx_d++] = srcstr[idx_s++];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0');
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Sanitizes a section path
|
|
|
|
@param secpath The section path
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
Out of quotes, similar to ECMAScript
|
|
|
|
`secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")`
|
|
|
|
|
|
|
|
A section path can start with a dot (append), but cannot end with a dot. Spaces
|
|
|
|
surrounding dots will be removed. Fragments surrounded by single or double
|
|
|
|
quotes (if these are enabled) are prevented from changes.
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t sanitize_section_path (char * const secpath, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (12 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 These are initial spaces
|
|
|
|
FLAG_64 This is a space out of quotes
|
|
|
|
FLAG_128 This is a dot out of quotes
|
|
|
|
FLAG_256 This is anything *but* an opening single/double quote
|
|
|
|
FLAG_512 Don't ignore the last two characters
|
|
|
|
FLAG_1024 Don't overwrite the previous character
|
|
|
|
FLAG_2048 Path contains at least one name
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint16_t abcd = (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes;
|
|
|
|
register size_t idx_s = 0, idx_d = 0;
|
|
|
|
|
|
|
|
for (; secpath[idx_s]; idx_s++) {
|
|
|
|
|
|
|
|
/* Revision #2 */
|
|
|
|
|
|
|
|
abcd = !(abcd & 12) && is_some_space(secpath[idx_s], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
abcd & 224 ?
|
|
|
|
(abcd & 3055) | 832
|
|
|
|
:
|
|
|
|
(abcd & 4079) | 1856
|
|
|
|
)
|
|
|
|
: !(abcd & 12) && secpath[idx_s] == _LIBCONFINI_SUBSECTION_ ?
|
|
|
|
(
|
|
|
|
abcd & (abcd & 32 ? 128 : 192) ?
|
|
|
|
(abcd & 2959) | 896
|
|
|
|
:
|
|
|
|
(abcd & 3983) | 1920
|
|
|
|
)
|
|
|
|
: !(abcd & 25) && secpath[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 4 ?
|
|
|
|
(abcd & 3839) | 1540
|
|
|
|
: abcd & 256 ?
|
|
|
|
(abcd & 3867) | 3840
|
|
|
|
:
|
|
|
|
(abcd & 3579) | 1280
|
|
|
|
)
|
|
|
|
: !(abcd & 22) && secpath[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 8 ?
|
|
|
|
(abcd & 3839) | 1544
|
|
|
|
: abcd & 256 ?
|
|
|
|
(abcd & 3863) | 3840
|
|
|
|
:
|
|
|
|
(abcd & 3575) | 1280
|
|
|
|
)
|
|
|
|
: secpath[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
((abcd & 3871) | 3840) ^ 16
|
|
|
|
:
|
|
|
|
(abcd & 3855) | 3840;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 512) {
|
|
|
|
|
|
|
|
secpath[
|
|
|
|
abcd & 1024 ?
|
|
|
|
idx_d++
|
|
|
|
: idx_d ?
|
|
|
|
idx_d - 1
|
|
|
|
:
|
|
|
|
idx_d
|
|
|
|
] = !(~abcd & 384) ?
|
|
|
|
_LIBCONFINI_SUBSECTION_
|
|
|
|
: !(~abcd & 320) ?
|
|
|
|
_LIBCONFINI_COLLAPSED_
|
|
|
|
:
|
|
|
|
secpath[idx_s];
|
|
|
|
|
|
|
|
} else if (idx_d) {
|
|
|
|
|
|
|
|
idx_d--;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx_s = idx_d && (abcd & 2048) && (abcd & 192) ?
|
|
|
|
--idx_d
|
|
|
|
:
|
|
|
|
idx_d;
|
|
|
|
|
|
|
|
secpath[idx_s];
|
|
|
|
|
|
|
|
secpath[idx_s++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Out of quotes similar to ECMAScript
|
|
|
|
`ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, " ")`
|
|
|
|
@param ini_string The string to collapse -- multi-line escape
|
|
|
|
sequences must be already unescaped at
|
|
|
|
this stage
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t collapse_everything (char * const ini_string, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (9 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 This is *not* a space out of quotes
|
|
|
|
FLAG_64 This is an opening single/double quote
|
|
|
|
FLAG_128 Don't ignore this character
|
|
|
|
FLAG_256 Jump this character and the one before this
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register size_t idx_s = 0, idx_d = 0;
|
|
|
|
|
|
|
|
register uint16_t abcd = (is_some_space(*ini_string, _LIBCONFINI_WITH_EOL_) ? 128 : 160) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
for (; ini_string[idx_s]; idx_s++) {
|
|
|
|
|
|
|
|
/* Revision #2 */
|
|
|
|
|
|
|
|
abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
abcd & 32 ?
|
|
|
|
(abcd & 143) | 128
|
|
|
|
:
|
|
|
|
abcd & 47
|
|
|
|
)
|
|
|
|
: !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 4 ?
|
|
|
|
(abcd & 239) | 196
|
|
|
|
: abcd & 64 ?
|
|
|
|
(abcd & 299) | 256
|
|
|
|
:
|
|
|
|
(abcd & 171) | 160
|
|
|
|
)
|
|
|
|
: !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 8 ?
|
|
|
|
(abcd & 239) | 200
|
|
|
|
: abcd & 64 ?
|
|
|
|
(abcd & 295) | 256
|
|
|
|
:
|
|
|
|
(abcd & 167) | 160
|
|
|
|
)
|
|
|
|
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
((abcd & 191) | 160) ^ 16
|
|
|
|
:
|
|
|
|
(abcd & 175) | 160;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 256) {
|
|
|
|
|
|
|
|
idx_d--;
|
|
|
|
|
|
|
|
} else if (abcd & 128) {
|
|
|
|
|
|
|
|
ini_string[idx_d++] = abcd & 44 ? ini_string[idx_s] : _LIBCONFINI_COLLAPSED_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx_s = !(abcd & 32) && idx_d ?
|
|
|
|
--idx_d
|
|
|
|
:
|
|
|
|
idx_d;
|
|
|
|
|
|
|
|
ini_string[idx_s];
|
|
|
|
|
|
|
|
ini_string[idx_s++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Out of quotes similar to ECMAScript
|
|
|
|
`ini_string.replace(/\s+/g, " ")`
|
|
|
|
@param ini_string The string to collapse -- multi-line escape
|
|
|
|
sequences must be already unescaped at this
|
|
|
|
stage
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t collapse_spaces (char * const ini_string, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (7 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 This is a space out of quotes
|
|
|
|
FLAG_64 Jump this character
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes;
|
|
|
|
register size_t idx_s = 0;
|
|
|
|
size_t idx_d = 0;
|
|
|
|
|
|
|
|
for (; ini_string[idx_s]; idx_s++) {
|
|
|
|
|
|
|
|
/* Revision #1 */
|
|
|
|
|
|
|
|
abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
abcd & 32 ?
|
|
|
|
(abcd & 111) | 64
|
|
|
|
:
|
|
|
|
(abcd & 47) | 32
|
|
|
|
)
|
|
|
|
: !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(abcd & 15) ^ 4
|
|
|
|
: !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(abcd & 15) ^ 8
|
|
|
|
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd & 31) ^ 16
|
|
|
|
:
|
|
|
|
abcd & 15;
|
|
|
|
|
|
|
|
|
|
|
|
if (~abcd & 64) {
|
|
|
|
|
|
|
|
ini_string[idx_d++] = abcd & 32 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx_s];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx_s = (abcd & 32) && idx_d ?
|
|
|
|
--idx_d
|
|
|
|
:
|
|
|
|
idx_d;
|
|
|
|
|
|
|
|
ini_string[idx_s];
|
|
|
|
|
|
|
|
ini_string[idx_s++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Similar to ECMAScript `str.replace(/''|""/g, "")`
|
|
|
|
@param str The string to collapse
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t collapse_empty_quotes (char * const str, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (7 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 This is an opening single/double quote
|
|
|
|
FLAG_64 These are empty quotes
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
register size_t lshift = ltrim_s(str, 0, _LIBCONFINI_WITH_EOL_), idx = lshift;
|
|
|
|
|
|
|
|
for (; str[idx]; idx++) {
|
|
|
|
|
|
|
|
/* Revision #1 */
|
|
|
|
|
|
|
|
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd & 31) ^ 16
|
|
|
|
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 40 ?
|
|
|
|
((abcd & 47) | 32) ^ 8
|
|
|
|
:
|
|
|
|
(abcd & 71) | 64
|
|
|
|
)
|
|
|
|
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
~abcd & 36 ?
|
|
|
|
((abcd & 47) | 32) ^ 4
|
|
|
|
:
|
|
|
|
(abcd & 75) | 64
|
|
|
|
)
|
|
|
|
:
|
|
|
|
abcd & 15;
|
|
|
|
|
|
|
|
|
|
|
|
str[idx - lshift] = str[idx];
|
|
|
|
|
|
|
|
if (abcd & 64) {
|
|
|
|
|
|
|
|
lshift += 2;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (idx -= lshift; str[idx]; str[idx++] = '\0');
|
|
|
|
|
|
|
|
return rtrim_h(str, idx - lshift, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Removes all comment initializers (`#` and/or `;`) from the
|
|
|
|
beginning of each line of a comment
|
|
|
|
@param srcstr The comment to parse (it may contain multi-line
|
|
|
|
escape sequences)
|
|
|
|
@param len The length of @p srcstr
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
- In multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, "$1")`
|
|
|
|
- In single-line comments: `srcstr.replace(/^[#;]+/, "")`
|
|
|
|
|
|
|
|
The argument @p srcstr may begin with a comment initializer (`#` or `;`
|
|
|
|
depending on the format), or with the character that immediately follows it.
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t uncomment (char * const srcstr, size_t len, const IniFormat format) {
|
|
|
|
|
|
|
|
register size_t idx_s = 0, idx_d = 0;
|
|
|
|
|
|
|
|
if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
The comment can be multi-line
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (6 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Don't erase any character
|
|
|
|
FLAG_2 We are in an odd sequence of backslashes
|
|
|
|
FLAG_4 This new line character is escaped
|
|
|
|
FLAG_8 This character is a comment character and follows
|
|
|
|
`/(\n\s*|\r\s*)/`
|
|
|
|
FLAG_16 This character is a part of a group of spaces that follow
|
|
|
|
a new line (`/(\n|\r)[\t \v\f]+/`)
|
|
|
|
FLAG_32 This character is *not* a new line character (`/[\r\n]/`)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (register uint8_t abcd = 8; idx_s < len; idx_s++) {
|
|
|
|
|
|
|
|
abcd = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
((abcd & 35) | 32) ^ 2
|
|
|
|
: srcstr[idx_s] == _LIBCONFINI_LF_ || srcstr[idx_s] == _LIBCONFINI_CR_ ?
|
|
|
|
(abcd << 1) & 4
|
|
|
|
: !(abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ?
|
|
|
|
(abcd & 40) | 8
|
|
|
|
: !(abcd & 40) && is_some_space(srcstr[idx_s], _LIBCONFINI_NO_EOL_) ?
|
|
|
|
(abcd & 57) | 16
|
|
|
|
:
|
|
|
|
(abcd & 33) | 32;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(abcd & 25)) {
|
|
|
|
|
|
|
|
srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s];
|
|
|
|
|
|
|
|
} else if (!(abcd & 28)) {
|
|
|
|
|
|
|
|
idx_d++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
The comment cannot be multi-line
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (; idx_s < len && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format); idx_s++);
|
|
|
|
|
|
|
|
if (!idx_s) {
|
|
|
|
|
|
|
|
return len;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; idx_s < len; srcstr[idx_d++] = srcstr[idx_s++]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0');
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Tries to determine the type of a member "as if it was active"
|
|
|
|
@param srcstr String containing an individual node (it may
|
|
|
|
contain multi-line escape sequences)
|
|
|
|
@param len Length of the node
|
|
|
|
@param allow_implicit A boolean: `true` if keys without a key-value
|
|
|
|
delimiter are allowed, `false` otherwise
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The node type (see header)
|
|
|
|
|
|
|
|
**/
|
|
|
|
static uint8_t get_type_as_active (
|
|
|
|
const char * const srcstr,
|
|
|
|
const size_t len,
|
|
|
|
const _LIBCONFINI_CHARBOOL_ allow_implicit,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
const _LIBCONFINI_CHARBOOL_ invalid_delimiter = _LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
|
|
|
|
|
|
|
|
if (
|
|
|
|
!len || _LIBCONFINI_IS_ANY_MARKER_(*srcstr, format) || (
|
|
|
|
*((unsigned char *) srcstr) == format.delimiter_symbol && !invalid_delimiter
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
register uint16_t abcd;
|
|
|
|
register size_t idx;
|
|
|
|
|
|
|
|
if (format.section_paths != INI_NO_SECTIONS && *srcstr == _LIBCONFINI_OPEN_SECTION_) {
|
|
|
|
|
|
|
|
if (format.no_spaces_in_names) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Search for the CLOSE SECTION character and possible spaces in names
|
|
|
|
-- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The
|
|
|
|
algorithm is made more complex by the fact that LF and CR characters
|
|
|
|
are still escaped at this stage.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (10 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Only one level of nesting is allowed (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd right now
|
|
|
|
FLAG_16 Unescaped double quotes are odd right now
|
|
|
|
FLAG_32 We are in an odd sequence of backslashes
|
|
|
|
FLAG_64 This is a space
|
|
|
|
FLAG_128 What follows cannot contain spaces
|
|
|
|
FLAG_256 Continue the loop
|
|
|
|
FLAG_512 Section path is *not* valid
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
idx = 1;
|
|
|
|
|
|
|
|
abcd = (format.section_paths == INI_ONE_LEVEL_ONLY ? 772: 768) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
/* Revision #2 */
|
|
|
|
|
|
|
|
abcd = idx >= len ?
|
|
|
|
abcd & 767
|
|
|
|
: !(abcd & 42) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(abcd & 991) ^ 16
|
|
|
|
: !(abcd & 49) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(abcd & 991) ^ 8
|
|
|
|
: srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ?
|
|
|
|
(abcd & 991) | 64
|
|
|
|
: is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
|
|
|
|
(
|
|
|
|
~abcd & 32 ?
|
|
|
|
abcd | 64
|
|
|
|
: ~abcd & 192 ?
|
|
|
|
(abcd & 991) | 192
|
|
|
|
:
|
|
|
|
(abcd & 767) | 128
|
|
|
|
)
|
|
|
|
: !(abcd & 28) && srcstr[idx] == _LIBCONFINI_SUBSECTION_ ?
|
|
|
|
(
|
|
|
|
~abcd & 224 ?
|
|
|
|
abcd & 799
|
|
|
|
:
|
|
|
|
abcd & 767
|
|
|
|
)
|
|
|
|
: !(abcd & 24) && srcstr[idx] == _LIBCONFINI_CLOSE_SECTION_ ?
|
|
|
|
(
|
|
|
|
~abcd & 224 ?
|
|
|
|
abcd & 159
|
|
|
|
:
|
|
|
|
abcd & 767
|
|
|
|
)
|
|
|
|
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(
|
|
|
|
~abcd & 32 ?
|
|
|
|
abcd | 32
|
|
|
|
: ~abcd & 192 ?
|
|
|
|
(abcd & 991) | 128
|
|
|
|
:
|
|
|
|
(abcd & 735)
|
|
|
|
)
|
|
|
|
: ~abcd & 192 ?
|
|
|
|
(abcd & 927) | 128
|
|
|
|
:
|
|
|
|
(abcd & 671) | 128;
|
|
|
|
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
|
|
|
|
} while (abcd & 256);
|
|
|
|
|
|
|
|
if (abcd & 512) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if ((idx = getn_metachar_pos(srcstr, _LIBCONFINI_CLOSE_SECTION_, len, format) + 1) > len) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Scan for possible non-space characters following the CLOSE SECTION
|
|
|
|
character: if found the node cannot represent a section path (but it can
|
|
|
|
possibly represent a key). Empty quotes surrounded by spaces will be
|
|
|
|
tolerated.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Recycling variable `abcd` (6 bits used)...:
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes
|
|
|
|
FLAG_32 Continue the loop
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
abcd = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ nonspace_check: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd) {
|
|
|
|
|
|
|
|
if (idx >= len) {
|
|
|
|
|
|
|
|
return INI_SECTION;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (srcstr[idx++]) {
|
|
|
|
|
|
|
|
case _LIBCONFINI_VT_:
|
|
|
|
case _LIBCONFINI_FF_:
|
|
|
|
case _LIBCONFINI_HT_:
|
|
|
|
case _LIBCONFINI_SIMPLE_SPACE_:
|
|
|
|
|
|
|
|
abcd = abcd & 28 ? 0 : abcd & 47;
|
|
|
|
goto nonspace_check;
|
|
|
|
|
|
|
|
case _LIBCONFINI_LF_:
|
|
|
|
case _LIBCONFINI_CR_:
|
|
|
|
|
|
|
|
abcd = abcd & 12 ? 0 : abcd & 47;
|
|
|
|
goto nonspace_check;
|
|
|
|
|
|
|
|
case _LIBCONFINI_BACKSLASH_:
|
|
|
|
|
|
|
|
abcd = abcd & 28 ? 0 : abcd | 16;
|
|
|
|
goto nonspace_check;
|
|
|
|
|
|
|
|
case _LIBCONFINI_DOUBLE_QUOTES_:
|
|
|
|
|
|
|
|
abcd = abcd & 22 ? 0 : (abcd & 47) ^ 8;
|
|
|
|
goto nonspace_check;
|
|
|
|
|
|
|
|
case _LIBCONFINI_SINGLE_QUOTES_:
|
|
|
|
|
|
|
|
abcd = abcd & 25 ? 0 : (abcd & 47) ^ 4;
|
|
|
|
goto nonspace_check;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
It can be just a key...
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (invalid_delimiter && !allow_implicit) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Recycling variable `abcd` (2 bits used)...:
|
|
|
|
|
|
|
|
FLAG_1 The delimiter **must** be present
|
|
|
|
FLAG_2 Search for spaces in names
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
abcd = (format.no_spaces_in_names << 1) | (allow_implicit ? 0 : 1);
|
|
|
|
|
|
|
|
if (abcd) {
|
|
|
|
|
|
|
|
idx = getn_metachar_pos(srcstr, (char) format.delimiter_symbol, len, format);
|
|
|
|
|
|
|
|
if ((abcd & 1) && idx == len) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (abcd & 2) {
|
|
|
|
|
|
|
|
idx = urtrim_s(srcstr, idx);
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
if (is_some_space(srcstr[--idx], _LIBCONFINI_WITH_EOL_)) {
|
|
|
|
|
|
|
|
return INI_UNKNOWN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (idx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return INI_KEY;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Examines a (single-/multi-line) segment and checks whether
|
|
|
|
it contains more than just one node
|
|
|
|
@param srcstr Segment to examine (it may contain multi-line
|
|
|
|
escape sequences)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return Number of entries found
|
|
|
|
|
|
|
|
**/
|
|
|
|
static size_t further_cuts (char * const srcstr, const IniFormat format) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Shared flags of mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Do not allow disabled entries after space (const)
|
|
|
|
FLAG_8 Formats supports multi-line entries everywhere (const)
|
|
|
|
FLAG_16 Formats supports multi-line entries everywhere except in
|
|
|
|
comments (const)
|
|
|
|
FLAG_32 Unescaped single quotes are odd right now
|
|
|
|
FLAG_64 Unescaped double quotes are odd right now
|
|
|
|
FLAG_128 We are in an odd sequence of backslashes
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint16_t abcd = ((format.disabled_after_space << 2) ^ 4) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes | (
|
|
|
|
format.multiline_nodes == INI_MULTILINE_EVERYWHERE ?
|
|
|
|
8
|
|
|
|
: format.multiline_nodes == INI_BUT_COMMENTS ?
|
|
|
|
16
|
|
|
|
:
|
|
|
|
0
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
register size_t idx;
|
|
|
|
size_t focus_at, unparsable_at, search_at = 0, num_entries = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ search_for_cuts: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
if (!srcstr[search_at]) {
|
|
|
|
|
|
|
|
return num_entries;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
unparsable_at = 0;
|
|
|
|
|
|
|
|
abcd = _LIBCONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && (
|
|
|
|
!(abcd & 4) || !is_some_space(srcstr[search_at + 1], _LIBCONFINI_NO_EOL_)
|
|
|
|
) ?
|
|
|
|
(abcd & 31) | 2560
|
|
|
|
: _LIBCONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ?
|
|
|
|
(abcd & 8 ? (abcd & 31) | 1024 : abcd & 31)
|
|
|
|
: (abcd & 8) && (
|
|
|
|
srcstr[search_at] == _LIBCONFINI_IC_INT_MARKER_ || _LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)
|
|
|
|
) ?
|
|
|
|
(abcd & 31) | 3072
|
|
|
|
:
|
|
|
|
(abcd & 31) | 2048;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 2048) {
|
|
|
|
|
|
|
|
num_entries++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (abcd & 1536) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Node starts with `/[;#]/` and can be a disabled entry in any format, or
|
|
|
|
a simple comment or a block that must be ignored in multi-line formats
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (14 bits used):
|
|
|
|
|
|
|
|
FLAG_256 This or the previous character was not a space
|
|
|
|
FLAG_512 We are in a disabled entry or a comment (semi-const)
|
|
|
|
FLAG_1024 We are in a simple comment or in a block that must be ignored
|
|
|
|
and format supports multi-line entries (semi-const)
|
|
|
|
FLAG_2048 We are *not* in a block that must be ignored (semi-const)
|
|
|
|
FLAG_4096 We have *not* just found an inline comment nested within a
|
|
|
|
disabled entry
|
|
|
|
FLAG_8192 We had previously found an inline comment nested in this
|
|
|
|
segment, but the entry that preceded it had been checked and
|
|
|
|
did not seem to represent a valid disabled entry
|
|
|
|
|
|
|
|
NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at
|
|
|
|
the beginning of the function; all other flags are already set
|
|
|
|
to zero. For the meaning of flags FLAG_1-FLAG_128 see the
|
|
|
|
beginning of the function.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
idx = ltrim_s(srcstr, search_at + 1, _LIBCONFINI_NO_EOL_) - 1;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ inactive_cut: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
switch (srcstr[++idx]) {
|
|
|
|
|
|
|
|
case '\0':
|
|
|
|
|
|
|
|
/* End of string */
|
|
|
|
|
|
|
|
if (~abcd & 8) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Check if this is a valid disabled entry. If it is not,
|
|
|
|
search for line breaks.
|
|
|
|
|
|
|
|
If the code has reached this point it means that according
|
|
|
|
to the format disabled entries can be multi-line but
|
|
|
|
comments cannot, and #get_type_as_active() has never been
|
|
|
|
invoked on this entry.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
focus_at = dqultrim_s(srcstr, search_at, format);
|
|
|
|
|
|
|
|
if (
|
|
|
|
srcstr[focus_at] && !get_type_as_active(
|
|
|
|
srcstr + focus_at,
|
|
|
|
idx - focus_at,
|
|
|
|
format.disabled_can_be_implicit,
|
|
|
|
format
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_;
|
|
|
|
unparsable_at = search_at + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _LIBCONFINI_LF_:
|
|
|
|
case _LIBCONFINI_CR_:
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Line break has been found in a multi-line disabled entry or
|
|
|
|
a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
focus_at = dqultrim_s(srcstr, search_at, format);
|
|
|
|
idx = ltrim_s(srcstr, idx + 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (
|
|
|
|
abcd & 2048 ?
|
|
|
|
!(
|
|
|
|
_LIBCONFINI_IS_DIS_MARKER_(srcstr[idx], format) && (abcd & 24) && (
|
|
|
|
(~abcd & 516) || !is_some_space(srcstr[idx + 1], _LIBCONFINI_NO_EOL_)
|
|
|
|
)
|
|
|
|
) && !(
|
|
|
|
_LIBCONFINI_IS_COM_MARKER_(srcstr[idx], format) && (abcd & 8) && (
|
|
|
|
((abcd ^ 512) & 8704) || !get_type_as_active(
|
|
|
|
srcstr + focus_at,
|
|
|
|
idx - focus_at,
|
|
|
|
format.disabled_can_be_implicit,
|
|
|
|
format
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
:
|
|
|
|
!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)
|
|
|
|
) {
|
|
|
|
|
|
|
|
rtrim_h(srcstr, idx, _LIBCONFINI_WITH_EOL_);
|
|
|
|
search_at = qultrim_h(srcstr, idx, format);
|
|
|
|
goto search_for_cuts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
No case break here, keep it like this! `case /[ \t\v\f]/` must
|
|
|
|
follow (switch case fallthrough).
|
|
|
|
|
|
|
|
*/
|
2020-04-23 14:59:46 +00:00
|
|
|
__attribute__((fallthrough));
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
case _LIBCONFINI_VT_:
|
|
|
|
case _LIBCONFINI_FF_:
|
|
|
|
case _LIBCONFINI_HT_:
|
|
|
|
case _LIBCONFINI_SIMPLE_SPACE_:
|
|
|
|
|
|
|
|
abcd = (abcd & 15999) | 4096;
|
|
|
|
goto inactive_cut;
|
|
|
|
|
|
|
|
case _LIBCONFINI_BACKSLASH_:
|
|
|
|
|
|
|
|
abcd = (abcd | 4352) ^ 128;
|
|
|
|
goto inactive_cut;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
abcd = !(abcd & 1376) && (~abcd & 8200) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
|
|
|
|
(abcd & 12159) | 256
|
|
|
|
: !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd & 16255) | 4352) ^ 64
|
|
|
|
: !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd & 16255) | 4352) ^ 32
|
|
|
|
:
|
|
|
|
(abcd & 16255) | 4352;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 4096) {
|
|
|
|
|
|
|
|
goto inactive_cut;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (~abcd & 8192) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Inline comment has been found in a (supposedly) disabled entry.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
focus_at = dqultrim_s(srcstr, search_at, format);
|
|
|
|
|
|
|
|
if (get_type_as_active(
|
|
|
|
srcstr + focus_at,
|
|
|
|
idx - focus_at,
|
|
|
|
format.disabled_can_be_implicit,
|
|
|
|
format
|
|
|
|
)) {
|
|
|
|
|
|
|
|
if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
|
|
|
|
|
|
|
|
srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_;
|
|
|
|
num_entries++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
srcstr[idx - 1] = '\0';
|
|
|
|
|
|
|
|
if (abcd & 8) {
|
|
|
|
|
|
|
|
goto inactive_cut;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
unparsable_at = idx + 1;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
abcd |= 8192;
|
|
|
|
srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_;
|
|
|
|
|
|
|
|
if (abcd & 8) {
|
|
|
|
|
|
|
|
goto inactive_cut;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
unparsable_at = search_at + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No case break here (last case) */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (_LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Node starts with `/[;#]/` but cannot be multi-line or represent a
|
|
|
|
disabled entry
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
unparsable_at = search_at + 1;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Node is active: search for inline comments
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Recycling variable `abcd` (11 bits used)...:
|
|
|
|
|
|
|
|
FLAG_256 Comment marker follows an escaped new line made of only one
|
|
|
|
character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"`
|
|
|
|
or `"\\\n\r"`)
|
|
|
|
FLAG_512 This was neither a hash nor a semicolon character
|
|
|
|
FLAG_1024 This was not a space
|
|
|
|
|
|
|
|
NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at the
|
|
|
|
beginning of the function; all other flags are already set to zero
|
|
|
|
(see previous usage of `abcd` within this function), with the only
|
|
|
|
exception of FLAG_2048, which I am going to overwrite immediately.
|
|
|
|
For the meaning of flags FLAG_1-FLAG_128 see the beginning of the
|
|
|
|
function.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
abcd = (abcd & 2047) | 1536;
|
|
|
|
idx = search_at;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ active_cut: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
switch (srcstr[++idx]) {
|
|
|
|
|
|
|
|
case '\0':
|
|
|
|
|
|
|
|
/* End of string */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _LIBCONFINI_VT_:
|
|
|
|
case _LIBCONFINI_FF_:
|
|
|
|
case _LIBCONFINI_HT_:
|
|
|
|
case _LIBCONFINI_SIMPLE_SPACE_:
|
|
|
|
|
|
|
|
abcd = (abcd & 639) | 512;
|
|
|
|
goto active_cut;
|
|
|
|
|
|
|
|
case _LIBCONFINI_LF_:
|
|
|
|
case _LIBCONFINI_CR_:
|
|
|
|
|
|
|
|
abcd = (abcd & 639) | ((abcd << 1) & 256) | 512;
|
|
|
|
goto active_cut;
|
|
|
|
|
|
|
|
case _LIBCONFINI_BACKSLASH_:
|
|
|
|
|
|
|
|
abcd = ((abcd & 1791) | 1536) ^ 128;
|
|
|
|
goto active_cut;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
abcd = _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
|
|
|
|
abcd & 1407
|
|
|
|
: !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd & 1791) | 1536) ^ 64
|
|
|
|
: !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd & 1791) | 1536) ^ 32
|
|
|
|
:
|
|
|
|
(abcd & 1791) | 1536;
|
|
|
|
|
|
|
|
if (abcd & 1760) {
|
|
|
|
|
|
|
|
goto active_cut;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Inline comment has been found in an active entry.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (abcd & 256) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Remove the backslash if the comment immediately follows an
|
|
|
|
escaped new line expressed by one chararacter
|
|
|
|
(`/\\[\r\n]/`). In case of CR + LF or LF + CR
|
|
|
|
(`/\\\n\r|\\\r\n/`) the backslash will be removed later by
|
|
|
|
#strip_ini_cache().
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
srcstr[idx - 2] = '\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
srcstr[idx - 1] = '\0';
|
|
|
|
|
|
|
|
if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
|
|
|
|
|
|
|
|
srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_;
|
|
|
|
|
|
|
|
if (abcd & 8) {
|
|
|
|
|
|
|
|
search_at = idx;
|
|
|
|
goto search_for_cuts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
num_entries++;
|
|
|
|
|
|
|
|
} else if (abcd & 8) {
|
|
|
|
|
|
|
|
search_at = idx;
|
|
|
|
goto search_for_cuts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
unparsable_at = idx + 1;
|
|
|
|
/* No case break here (last case) */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unparsable_at) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Cut unparsable multi-line comments
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (idx = unparsable_at; srcstr[idx]; idx++) {
|
|
|
|
|
|
|
|
if (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) {
|
|
|
|
|
|
|
|
search_at = qultrim_h(srcstr, idx, format);
|
|
|
|
goto search_for_cuts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return num_entries;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @startfnlist **/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*\
|
|
|
|
|*|
|
|
|
|
|*| GLOBAL ENVIRONMENT
|
|
|
|
|*| ________________________________
|
|
|
|
\*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* LIBRARY'S MAIN FUNCTIONS */
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{strip_ini_cache} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Parses and tokenizes a buffer containing an INI file, then
|
|
|
|
dispatches its content to a custom callback
|
|
|
|
@param ini_source The buffer containing the INI file to tokenize
|
|
|
|
@param ini_length The length of @p ini_source without counting the
|
|
|
|
NUL terminator (if any -- se below)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@param f_init The function that will be invoked before the
|
|
|
|
first dispatch, or `NULL`
|
|
|
|
@param f_foreach The function that will be invoked for each
|
|
|
|
dispatch, or `NULL`
|
|
|
|
@param user_data A custom argument, or `NULL`
|
|
|
|
@return Zero for success, otherwise an error code (see `enum`
|
|
|
|
#ConfiniInterruptNo)
|
|
|
|
|
|
|
|
The @p ini_source parameter must be a valid pointer to a buffer of size
|
|
|
|
@p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need
|
|
|
|
to be NUL-terminated, but _it does need one extra byte where to append a NUL
|
|
|
|
terminator_ -- in fact, as soon as this function is invoked,
|
|
|
|
`ini_source[ini_length]` will be immediately set to `\0`.
|
|
|
|
|
|
|
|
In most cases, as when using `strlen()` for computing @p ini_length, this is not
|
|
|
|
a concern, since `ini_source[ini_length]` will always be `\0` by the very
|
|
|
|
definition of `strlen()`, and will only get overwritten with the same value.
|
|
|
|
However, if you are passing a substring of a string, for example the fragment
|
|
|
|
`foo=bar` of the string `foo=barracuda`, you must expect the string to be
|
|
|
|
immediately truncated into `foo=bar\0acuda`.
|
|
|
|
|
|
|
|
In other words, @p ini_source must point to a memory location where at least
|
|
|
|
`ini_length + 1` bytes are freely usable.
|
|
|
|
|
|
|
|
The user given function @p f_init (see #IniStatsHandler data type) will be
|
|
|
|
invoked with two arguments: `statistics` (a pointer to an #IniStatistics
|
|
|
|
structure containing some properties about the file read) and `user_data` (the
|
|
|
|
custom argument @p user_data previously passed). If @p f_init returns a non-zero
|
|
|
|
value the caller function will be interrupted.
|
|
|
|
|
|
|
|
The user given function @p f_foreach (see #IniDispHandler data type) will be
|
|
|
|
invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure
|
|
|
|
containing the parsed member of the INI file) and `user_data` (the custom
|
|
|
|
argument @p user_data previously passed). If @p f_foreach returns a non-zero
|
|
|
|
value the caller function will be interrupted.
|
|
|
|
|
|
|
|
After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source
|
|
|
|
parameter must be considered as a _corrupted buffer_ and should be freed or
|
|
|
|
overwritten. For more information about this function, please refer to the
|
|
|
|
@ref libconfini.
|
|
|
|
|
|
|
|
The parsing algorithms used by **libconfini** are able to parse any type of file
|
|
|
|
encoded in 8-bit code units, as long as the characters that match the regular
|
|
|
|
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
|
|
|
|
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
|
|
|
|
platform-specific conventions.
|
|
|
|
|
|
|
|
@note In order to be null-byte-injection-safe, before dispatching the parsed
|
|
|
|
content this function will strip all `NUL` characters possibly present
|
|
|
|
in the buffer (with the exception of the last one).
|
|
|
|
|
|
|
|
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
|
|
|
|
#CONFINI_EOOR.
|
|
|
|
|
|
|
|
@include topics/strip_ini_cache.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
int strip_ini_cache (
|
|
|
|
register char * const ini_source,
|
|
|
|
const size_t ini_length,
|
|
|
|
const IniFormat format,
|
|
|
|
const IniStatsHandler f_init,
|
|
|
|
const IniDispHandler f_foreach,
|
|
|
|
void * const user_data
|
|
|
|
) {
|
|
|
|
|
|
|
|
const _LIBCONFINI_CHARBOOL_ valid_delimiter = !_LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
|
|
|
|
_LIBCONFINI_CHARBOOL_ tmp_bool;
|
|
|
|
register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2;
|
|
|
|
size_t tmp_size_t_1, tmp_size_t_2;
|
|
|
|
|
|
|
|
ini_source[ini_length] = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
PART ONE: Examine and isolate each segment
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define __ISNT_ESCAPED__ tmp_bool
|
|
|
|
#define __LSHIFT__ tmp_fast_size_t_1
|
|
|
|
#define __EOL_N__ tmp_fast_size_t_2
|
|
|
|
#define __N_MEMBERS__ tmp_size_t_1
|
|
|
|
#define __NODE_AT__ tmp_size_t_2
|
|
|
|
|
|
|
|
/* UTF-8 BOM */
|
|
|
|
__LSHIFT__ = *((unsigned char *) ini_source) == 0xEF &&
|
|
|
|
*((unsigned char *) ini_source + 1) == 0xBB &&
|
|
|
|
*((unsigned char *) ini_source + 2) == 0xBF
|
|
|
|
? 3 : 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
__N_MEMBERS__ = 0,
|
|
|
|
__EOL_N__ = _LIBCONFINI_EOL_IDX_,
|
|
|
|
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_,
|
|
|
|
__NODE_AT__ = 0,
|
|
|
|
idx = __LSHIFT__;
|
|
|
|
|
|
|
|
idx < ini_length;
|
|
|
|
|
|
|
|
idx++
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
ini_source[idx - __LSHIFT__] = ini_source[idx];
|
|
|
|
|
|
|
|
if (ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__] || ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__ ^= 1]) {
|
|
|
|
|
|
|
|
if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) {
|
|
|
|
|
|
|
|
ini_source[idx - __LSHIFT__] = '\0';
|
|
|
|
__N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format);
|
|
|
|
__NODE_AT__ = idx - __LSHIFT__ + 1;
|
|
|
|
|
|
|
|
} else if (ini_source[idx + 1] == _LIBCONFINI_SPACES_[__EOL_N__ ^ 1]) {
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
ini_source[idx - __LSHIFT__] = ini_source[idx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
} else if (ini_source[idx] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
__ISNT_ESCAPED__ = !__ISNT_ESCAPED__;
|
|
|
|
|
|
|
|
} else if (ini_source[idx]) {
|
|
|
|
|
|
|
|
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* Remove `NUL` characters from the buffer (if any) */
|
|
|
|
__LSHIFT__++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t real_length = idx - __LSHIFT__;
|
|
|
|
|
|
|
|
while (idx > real_length) {
|
|
|
|
|
|
|
|
ini_source[--idx] = '\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
__N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format);
|
|
|
|
|
|
|
|
/* Debug */
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
for (size_t tmp = 0; tmp < ini_length + 1; tmp++) {
|
|
|
|
putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]);
|
|
|
|
}
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
IniStatistics this_doc = {
|
|
|
|
.format = format,
|
|
|
|
.bytes = ini_length,
|
|
|
|
.members = __N_MEMBERS__
|
|
|
|
};
|
|
|
|
|
|
|
|
if (f_init && f_init(&this_doc, user_data)) {
|
|
|
|
|
|
|
|
return CONFINI_IINTR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef __NODE_AT__
|
|
|
|
#undef __N_MEMBERS__
|
|
|
|
#undef __EOL_N__
|
|
|
|
#undef __LSHIFT__
|
|
|
|
#undef __ISNT_ESCAPED__
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
PART TWO: Dispatch the parsed input
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!f_foreach) {
|
|
|
|
|
|
|
|
return CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#define __ITER__ tmp_fast_size_t_1
|
|
|
|
#define __NODE_AT__ tmp_fast_size_t_2
|
|
|
|
#define __PARENT_IS_DISABLED__ tmp_bool
|
|
|
|
#define __REAL_PARENT_LEN__ tmp_size_t_1
|
|
|
|
#define __CURR_PARENT_LEN__ tmp_size_t_2
|
|
|
|
|
|
|
|
__REAL_PARENT_LEN__ = 0, __CURR_PARENT_LEN__ = 0;
|
|
|
|
|
|
|
|
char
|
|
|
|
* curr_parent_str = ini_source + real_length,
|
|
|
|
* subparent_str = curr_parent_str,
|
|
|
|
* real_parent_str = curr_parent_str;
|
|
|
|
|
|
|
|
IniDispatch dsp = {
|
|
|
|
.format = format,
|
|
|
|
.dispatch_id = 0
|
|
|
|
};
|
|
|
|
|
|
|
|
__PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) {
|
|
|
|
|
|
|
|
if (ini_source[idx]) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ini_source[__NODE_AT__] || _LIBCONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)) {
|
|
|
|
|
|
|
|
__NODE_AT__ = idx + 1;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dsp.dispatch_id >= this_doc.members) {
|
|
|
|
|
|
|
|
return CONFINI_EOOR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.data = ini_source + __NODE_AT__;
|
|
|
|
dsp.d_len = idx - __NODE_AT__;
|
|
|
|
|
|
|
|
/* Set `dsp.value` to an empty string */
|
|
|
|
dsp.value = ini_source + idx;
|
|
|
|
|
|
|
|
if (
|
|
|
|
_LIBCONFINI_IS_DIS_MARKER_(*dsp.data, format) && (
|
|
|
|
format.disabled_after_space || !is_some_space(dsp.data[1], _LIBCONFINI_NO_EOL_)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
__ITER__ = dqultrim_s(dsp.data, 0, format);
|
|
|
|
|
|
|
|
dsp.type = get_type_as_active(
|
|
|
|
dsp.data + __ITER__,
|
|
|
|
dsp.d_len - __ITER__,
|
|
|
|
format.disabled_can_be_implicit,
|
|
|
|
format
|
|
|
|
);
|
|
|
|
|
|
|
|
if (dsp.type) {
|
|
|
|
|
|
|
|
dsp.data += __ITER__;
|
|
|
|
dsp.d_len -= __ITER__;
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
// Not strictly needed...
|
|
|
|
for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0');
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.type |= 4;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
switch (*dsp.data) {
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
if (!_LIBCONFINI_IS_ANY_MARKER_(*dsp.data, format)) {
|
|
|
|
|
|
|
|
dsp.type = get_type_as_active(dsp.data, dsp.d_len, _LIBCONFINI_TRUE_, format);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
No case break here, keep it like this!
|
|
|
|
`case _LIBCONFINI_SC_INT_MARKER_` must follow
|
|
|
|
(switch case fallthrough).
|
|
|
|
|
|
|
|
*/
|
2020-04-23 14:59:46 +00:00
|
|
|
__attribute__((fallthrough));
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
case _LIBCONFINI_SC_INT_MARKER_:
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
// Not strictly needed...
|
|
|
|
*dsp.data = '\0';
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
dsp.type = INI_COMMENT;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _LIBCONFINI_IC_INT_MARKER_:
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
// Not strictly needed...
|
|
|
|
*dsp.data = '\0';
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
dsp.type = INI_INLINE_COMMENT;
|
|
|
|
/* No case break here (last case) */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (__CURR_PARENT_LEN__ && *subparent_str) {
|
|
|
|
|
|
|
|
__ITER__ = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
curr_parent_str[__ITER__ + __CURR_PARENT_LEN__] = subparent_str[__ITER__];
|
|
|
|
|
|
|
|
} while (subparent_str[__ITER__++]);
|
|
|
|
|
|
|
|
__CURR_PARENT_LEN__ += __ITER__ - 1;
|
|
|
|
subparent_str = curr_parent_str + __CURR_PARENT_LEN__;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (__PARENT_IS_DISABLED__ && !(dsp.type & 4)) {
|
|
|
|
|
|
|
|
real_parent_str[__REAL_PARENT_LEN__] = '\0';
|
|
|
|
__CURR_PARENT_LEN__ = __REAL_PARENT_LEN__;
|
|
|
|
curr_parent_str = real_parent_str;
|
|
|
|
__PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
} else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) {
|
|
|
|
|
|
|
|
__REAL_PARENT_LEN__ = __CURR_PARENT_LEN__;
|
|
|
|
real_parent_str = curr_parent_str;
|
|
|
|
__PARENT_IS_DISABLED__ = _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.append_to = curr_parent_str;
|
|
|
|
dsp.at_len = __CURR_PARENT_LEN__;
|
|
|
|
|
|
|
|
if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) {
|
|
|
|
|
|
|
|
dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format);
|
|
|
|
|
|
|
|
} else if (format.multiline_nodes != INI_NO_MULTILINE) {
|
|
|
|
|
|
|
|
dsp.d_len = unescape_cr_lf(dsp.data, dsp.d_len, dsp.type & 4, format);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dsp.type) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
case INI_UNKNOWN:
|
|
|
|
|
|
|
|
// Do nothing
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
case INI_SECTION:
|
|
|
|
case INI_DISABLED_SECTION:
|
|
|
|
|
|
|
|
*dsp.data++ = '\0';
|
|
|
|
__ITER__ = getn_metachar_pos(dsp.data, _LIBCONFINI_CLOSE_SECTION_, dsp.d_len, format);
|
|
|
|
|
|
|
|
while (dsp.data[__ITER__]) {
|
|
|
|
|
|
|
|
dsp.data[__ITER__++] = '\0';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.d_len = format.section_paths == INI_ONE_LEVEL_ONLY ?
|
|
|
|
collapse_everything(dsp.data, format)
|
|
|
|
:
|
|
|
|
sanitize_section_path(dsp.data, format);
|
|
|
|
|
|
|
|
|
|
|
|
if (format.section_paths == INI_ONE_LEVEL_ONLY || *dsp.data != _LIBCONFINI_SUBSECTION_) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Append to root (this is an absolute path)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
curr_parent_str = dsp.data;
|
|
|
|
__CURR_PARENT_LEN__ = dsp.d_len;
|
|
|
|
subparent_str = ini_source + idx;
|
|
|
|
dsp.append_to = subparent_str;
|
|
|
|
dsp.at_len = 0;
|
|
|
|
|
|
|
|
} else if (format.section_paths == INI_ABSOLUTE_ONLY || !__CURR_PARENT_LEN__) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Append to root and remove the leading dot (parent is root or
|
|
|
|
relative paths are not allowed)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
curr_parent_str = ++dsp.data;
|
|
|
|
__CURR_PARENT_LEN__ = --dsp.d_len;
|
|
|
|
subparent_str = ini_source + idx;
|
|
|
|
dsp.append_to = subparent_str;
|
|
|
|
dsp.at_len = 0;
|
|
|
|
|
|
|
|
} else if (dsp.d_len != 1) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Append to the current parent (this is a relative path
|
|
|
|
and parent is not root)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
subparent_str = dsp.data;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
|
|
|
|
|
|
|
|
string_tolower(dsp.data);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INI_KEY:
|
|
|
|
case INI_DISABLED_KEY:
|
|
|
|
|
|
|
|
if (
|
|
|
|
valid_delimiter && (
|
|
|
|
__ITER__ = getn_metachar_pos(dsp.data, (char) dsp.format.delimiter_symbol, dsp.d_len, format)
|
|
|
|
) < dsp.d_len
|
|
|
|
) {
|
|
|
|
|
|
|
|
dsp.data[__ITER__] = '\0';
|
|
|
|
dsp.value = dsp.data + __ITER__ + 1;
|
|
|
|
|
|
|
|
|
|
|
|
switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) {
|
|
|
|
|
|
|
|
case 0: dsp.v_len = collapse_everything(dsp.value, format); break;
|
|
|
|
|
|
|
|
case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break;
|
|
|
|
|
|
|
|
case 2: dsp.v_len = collapse_spaces(dsp.value, format); break;
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
|
|
|
dsp.value += ltrim_h(dsp.value, 0, _LIBCONFINI_WITH_EOL_);
|
|
|
|
dsp.v_len = rtrim_h(dsp.value, dsp.d_len + dsp.data - dsp.value, _LIBCONFINI_WITH_EOL_);
|
|
|
|
/* No case break here (last case) */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (format.implicit_is_not_empty) {
|
|
|
|
|
|
|
|
dsp.value = INI_GLOBAL_IMPLICIT_VALUE;
|
|
|
|
dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.d_len = collapse_everything(dsp.data, format);
|
|
|
|
|
|
|
|
if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
|
|
|
|
|
|
|
|
string_tolower(dsp.data);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INI_COMMENT:
|
|
|
|
case INI_INLINE_COMMENT:
|
|
|
|
|
|
|
|
dsp.append_to = ini_source + idx;
|
|
|
|
dsp.at_len = 0;
|
|
|
|
/* No case break here (last case) */
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f_foreach(&dsp, user_data)) {
|
|
|
|
|
|
|
|
return CONFINI_FEINTR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
dsp.dispatch_id++;
|
|
|
|
__NODE_AT__ = idx + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef __CURR_PARENT_LEN__
|
|
|
|
#undef __REAL_PARENT_LEN__
|
|
|
|
#undef __PARENT_IS_DISABLED__
|
|
|
|
#undef __NODE_AT__
|
|
|
|
#undef __ITER__
|
|
|
|
|
|
|
|
return CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{load_ini_file} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Parses an INI file and dispatches its content to a custom
|
|
|
|
callback using a `FILE` structure as argument
|
|
|
|
@param ini_file The `FILE` handle pointing to the INI file to
|
|
|
|
parse
|
|
|
|
@param format The format of the INI file
|
|
|
|
@param f_init The function that will be invoked before the
|
|
|
|
first dispatch, or `NULL`
|
|
|
|
@param f_foreach The function that will be invoked for each
|
|
|
|
dispatch, or `NULL`
|
|
|
|
@param user_data A custom argument, or `NULL`
|
|
|
|
@return Zero for success, otherwise an error code (see `enum`
|
|
|
|
#ConfiniInterruptNo)
|
|
|
|
|
|
|
|
@note This function is absent if the `--without-io-api` option was passed to
|
|
|
|
the `configure` script when the library was compiled
|
|
|
|
|
|
|
|
The @p ini_file parameter must be a `FILE` handle with read privileges. On some
|
|
|
|
platforms, such as Microsoft Windows, it might be needed to add the binary
|
|
|
|
specifier to the mode string (`"b"`) in order to prevent discrepancies between
|
|
|
|
the physical size of the file and its computed size:
|
|
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
|
|
|
FILE * my_file = fopen("example.conf", "rb");
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
For the two parameters @p f_init and @p f_foreach see function
|
|
|
|
#strip_ini_cache().
|
|
|
|
|
|
|
|
The parsing algorithms used by **libconfini** are able to parse any type of file
|
|
|
|
encoded in 8-bit code units, as long as the characters that match the regular
|
|
|
|
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
|
|
|
|
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
|
|
|
|
platform-specific conventions.
|
|
|
|
|
|
|
|
@note In order to be null-byte-injection safe, `NUL` characters, if present in
|
|
|
|
the file, will be removed from the dispatched strings.
|
|
|
|
|
|
|
|
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
|
|
|
|
#CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG.
|
|
|
|
|
|
|
|
@include topics/load_ini_file.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
int load_ini_file (
|
|
|
|
FILE * const ini_file,
|
|
|
|
const IniFormat format,
|
|
|
|
const IniStatsHandler f_init,
|
|
|
|
const IniDispHandler f_foreach,
|
|
|
|
void * const user_data
|
|
|
|
) {
|
|
|
|
|
|
|
|
_LIBCONFINI_OFF_T_ file_size;
|
|
|
|
|
|
|
|
if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) {
|
|
|
|
|
|
|
|
return CONFINI_EBADF;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-04-23 14:59:46 +00:00
|
|
|
if ((size_t) file_size > SIZE_MAX) {
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
return CONFINI_EFBIG;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
char * const cache = (char *) malloc((size_t) file_size + 1);
|
|
|
|
|
|
|
|
if (!cache) {
|
|
|
|
|
|
|
|
return CONFINI_ENOMEM;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
rewind(ini_file);
|
|
|
|
|
2020-04-23 14:59:46 +00:00
|
|
|
if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) {
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
free(cache);
|
|
|
|
return CONFINI_EIO;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data);
|
|
|
|
|
|
|
|
free(cache);
|
|
|
|
return return_value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{load_ini_path} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Parses an INI file and dispatches its content to a custom
|
|
|
|
callback using a path as argument
|
|
|
|
@param path The path of the INI file
|
|
|
|
@param format The format of the INI file
|
|
|
|
@param f_init The function that will be invoked before the
|
|
|
|
first dispatch, or `NULL`
|
|
|
|
@param f_foreach The function that will be invoked for each
|
|
|
|
dispatch, or `NULL`
|
|
|
|
@param user_data A custom argument, or `NULL`
|
|
|
|
@return Zero for success, otherwise an error code (see `enum`
|
|
|
|
#ConfiniInterruptNo)
|
|
|
|
|
|
|
|
@note This function is absent if the `--without-io-api` option was passed to
|
|
|
|
the `configure` script when the library was compiled
|
|
|
|
|
|
|
|
For the two parameters @p f_init and @p f_foreach see function
|
|
|
|
#strip_ini_cache().
|
|
|
|
|
|
|
|
The parsing algorithms used by **libconfini** are able to parse any type of file
|
|
|
|
encoded in 8-bit code units, as long as the characters that match the regular
|
|
|
|
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
|
|
|
|
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
|
|
|
|
platform-specific conventions.
|
|
|
|
|
|
|
|
@note In order to be null-byte-injection safe, `NUL` characters, if present in
|
|
|
|
the file, will be removed from the dispatched strings.
|
|
|
|
|
|
|
|
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
|
|
|
|
#CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF,
|
|
|
|
#CONFINI_EFBIG.
|
|
|
|
|
|
|
|
@include topics/load_ini_path.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
int load_ini_path (
|
|
|
|
const char * const path,
|
|
|
|
const IniFormat format,
|
|
|
|
const IniStatsHandler f_init,
|
|
|
|
const IniDispHandler f_foreach,
|
|
|
|
void * const user_data
|
|
|
|
) {
|
|
|
|
|
|
|
|
FILE * const ini_file = fopen(path, "rb");
|
|
|
|
|
|
|
|
if (!ini_file) {
|
|
|
|
|
|
|
|
return CONFINI_ENOENT;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_LIBCONFINI_OFF_T_ file_size;
|
|
|
|
|
|
|
|
if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) {
|
|
|
|
|
|
|
|
return CONFINI_EBADF;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-04-23 14:59:46 +00:00
|
|
|
if ((size_t) file_size > SIZE_MAX) {
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
return CONFINI_EFBIG;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
char * const cache = (char *) malloc((size_t) file_size + 1);
|
|
|
|
|
|
|
|
if (!cache) {
|
|
|
|
|
|
|
|
return CONFINI_ENOMEM;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
rewind(ini_file);
|
|
|
|
|
2020-04-23 14:59:46 +00:00
|
|
|
if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) {
|
2020-04-16 23:38:25 +00:00
|
|
|
|
|
|
|
free(cache);
|
|
|
|
return CONFINI_EIO;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No checks here, as there is nothing we can do about it... */
|
|
|
|
fclose(ini_file);
|
|
|
|
|
|
|
|
const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data);
|
|
|
|
|
|
|
|
free(cache);
|
|
|
|
return return_value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_string_match_ss} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Compares two simple strings and checks whether they match
|
|
|
|
@param simple_string_a The first simple string
|
|
|
|
@param simple_string_b The second simple string
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return A boolean: `true` if the two strings match, `false` otherwise
|
|
|
|
|
|
|
|
Simple strings are user-given strings or the result of #ini_string_parse(). The
|
|
|
|
@p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.case_sensitive`
|
|
|
|
|
|
|
|
**/
|
|
|
|
bool ini_string_match_ss (
|
|
|
|
const char * const simple_string_a,
|
|
|
|
const char * const simple_string_b,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
register size_t idx = 0;
|
|
|
|
|
|
|
|
if (format.case_sensitive) {
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
if (simple_string_a[idx] != simple_string_b[idx]) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (simple_string_a[idx++]);
|
|
|
|
|
|
|
|
return _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
if (_LIBCONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string_b[idx])) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (simple_string_a[idx++]);
|
|
|
|
|
|
|
|
return _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_string_match_si} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Compares a simple string and an INI string and and checks
|
|
|
|
whether they match
|
|
|
|
@param ini_string The INI string escaped according to
|
|
|
|
@p format
|
|
|
|
@param simple_string The simple string
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return A boolean: `true` if the two strings match, `false` otherwise
|
|
|
|
|
|
|
|
This function grants that the result of the comparison between a simple string
|
|
|
|
and an INI string
|
|
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
|
|
|
printf(
|
|
|
|
"%s\n",
|
|
|
|
ini_string_match_si(my_simple_string, my_ini_string, format) ?
|
|
|
|
"They match"
|
|
|
|
:
|
|
|
|
"They don't match"
|
|
|
|
);
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
will always match the result of the _literal_ comparison between the simple
|
|
|
|
string and the INI string after the latter has been parsed by
|
|
|
|
#ini_string_parse() when `format.do_not_collapse_values` is set to `false`.
|
|
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
|
|
|
ini_string_parse(my_ini_string, format);
|
|
|
|
|
|
|
|
printf(
|
|
|
|
"%s\n",
|
|
|
|
ini_string_match_ss(my_simple_string, my_ini_string, format) ?
|
|
|
|
"They match"
|
|
|
|
:
|
|
|
|
"They don't match"
|
|
|
|
);
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
INI strings are the strings typically dispatched by #load_ini_file(),
|
|
|
|
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
|
|
|
|
escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or
|
|
|
|
the result of #ini_string_parse().
|
|
|
|
|
|
|
|
In order to be suitable for both names and values, **this function always
|
|
|
|
considers sequences of one or more spaces out of quotes in the INI string as
|
|
|
|
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.case_sensitive`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.multiline_nodes`
|
|
|
|
|
|
|
|
@include topics/ini_string_match_si.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
bool ini_string_match_si (
|
|
|
|
const char * const simple_string,
|
|
|
|
const char * const ini_string,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Format supports escape sequences (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd right now
|
|
|
|
FLAG_16 Unescaped double quotes are odd right now
|
|
|
|
FLAG_32 This is an escaped single/double quote in a format that supports
|
|
|
|
single/double quotes
|
|
|
|
FLAG_64 This is a space
|
|
|
|
FLAG_128 Skip this character
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = INIFORMAT_HAS_NO_ESC(format) ?
|
|
|
|
67
|
|
|
|
:
|
|
|
|
68 | (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
register size_t idx_i = 0;
|
|
|
|
size_t idx_s = 0, nbacksl = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ si_match: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
if ((abcd & 4) && ini_string[idx_i] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
for (abcd &= 63, nbacksl++; ini_string[++idx_i] == _LIBCONFINI_BACKSLASH_; nbacksl++);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
abcd = !(abcd & 10) && ini_string[idx_i] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
nbacksl & 1 ?
|
|
|
|
(abcd & 63) | 32
|
|
|
|
:
|
|
|
|
((abcd & 223) | 128) ^ 16
|
|
|
|
)
|
|
|
|
: !(abcd & 17) && ini_string[idx_i] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
nbacksl & 1 ?
|
|
|
|
(abcd & 63) | 32
|
|
|
|
:
|
|
|
|
((abcd & 223) | 128) ^ 8
|
|
|
|
)
|
|
|
|
: (abcd & 24) || !is_some_space(ini_string[idx_i], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
abcd & 31
|
|
|
|
: abcd & 64 ?
|
|
|
|
(abcd & 223) | 128
|
|
|
|
:
|
|
|
|
(abcd & 95) | 64;
|
|
|
|
|
|
|
|
|
|
|
|
if (nbacksl) {
|
|
|
|
|
|
|
|
nbacksl = ((abcd & 32 ? nbacksl : nbacksl + 1) >> 1) + 1;
|
|
|
|
|
|
|
|
while (--nbacksl) {
|
|
|
|
|
|
|
|
if (simple_string[idx_s++] != _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((abcd & 128) || ((abcd & 64) && !simple_string[idx_s])) {
|
|
|
|
|
|
|
|
idx_i++;
|
|
|
|
goto si_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
abcd & 64 ?
|
|
|
|
simple_string[idx_s] != _LIBCONFINI_COLLAPSED_ || !simple_string[idx_s + 1]
|
|
|
|
: format.case_sensitive ?
|
|
|
|
ini_string[idx_i] != simple_string[idx_s]
|
|
|
|
:
|
|
|
|
_LIBCONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string[idx_s])
|
|
|
|
) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
idx_s++;
|
|
|
|
|
|
|
|
if (ini_string[idx_i++]) {
|
|
|
|
|
|
|
|
goto si_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_string_match_ii} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Compares two INI strings and checks whether they match
|
|
|
|
@param ini_string_a The first INI string unescaped according to
|
|
|
|
@p format
|
|
|
|
@param ini_string_b The second INI string unescaped according to
|
|
|
|
@p format
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return A boolean: `true` if the two strings match, `false` otherwise
|
|
|
|
|
|
|
|
This function grants that the result of the comparison between two INI strings
|
|
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
|
|
|
printf(
|
|
|
|
"%s\n",
|
|
|
|
ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ?
|
|
|
|
"They match"
|
|
|
|
:
|
|
|
|
"They don't match"
|
|
|
|
);
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
will always match the result of the _literal_ comparison between the same two
|
|
|
|
INI strings after these have been parsed by #ini_string_parse() when
|
|
|
|
`format.do_not_collapse_values` is set to `false`.
|
|
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
|
|
|
ini_string_parse(my_ini_string_1, format);
|
|
|
|
ini_string_parse(my_ini_string_2, format);
|
|
|
|
|
|
|
|
printf("%s\n",
|
|
|
|
ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ?
|
|
|
|
"They match"
|
|
|
|
:
|
|
|
|
"They don't match"
|
|
|
|
);
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
INI strings are the strings typically dispatched by #load_ini_file(),
|
|
|
|
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
|
|
|
|
escape sequences `\\`, `\'` and `\"`.
|
|
|
|
|
|
|
|
In order to be suitable for both names and values, **this function always
|
|
|
|
considers sequences of one or more spaces out of quotes in both strings as
|
|
|
|
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.case_sensitive`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.multiline_nodes`
|
|
|
|
|
|
|
|
**/
|
|
|
|
bool ini_string_match_ii (
|
|
|
|
const char * const ini_string_a,
|
|
|
|
const char * const ini_string_b,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
|
|
|
|
register uint8_t side = 1;
|
|
|
|
register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_;
|
|
|
|
uint8_t abcd_pair[2];
|
|
|
|
const char * chrptr_pair[2] = { ini_string_a, ini_string_b };
|
|
|
|
size_t nbacksl_pair[2];
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes and format supports
|
|
|
|
escape sequences
|
|
|
|
FLAG_32 This is a space
|
|
|
|
FLAG_64 Skip this character
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ ii_match: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
nbacksl_pair[side] = 0;
|
|
|
|
|
|
|
|
if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++);
|
|
|
|
|
|
|
|
abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15;
|
|
|
|
|
|
|
|
if (
|
|
|
|
(
|
|
|
|
(abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_
|
|
|
|
) && (
|
|
|
|
(abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
nbacksl_pair[side]++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
abcd_pair[side] = !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd_pair[side] & 111) | 64) ^ 4
|
|
|
|
: !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd_pair[side] & 111) | 64) ^ 8
|
|
|
|
: !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(abcd_pair[side] & 111) | 96
|
|
|
|
: *chrptr_pair[side] ?
|
|
|
|
abcd_pair[side] & 47
|
|
|
|
:
|
|
|
|
abcd_pair[side] & 15;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd_pair[side] & 64) {
|
|
|
|
|
|
|
|
chrptr_pair[side]++;
|
|
|
|
goto ii_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (side && turn_allowed) {
|
|
|
|
|
|
|
|
side ^= 1;
|
|
|
|
goto ii_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
turn_allowed = _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
if (*nbacksl_pair || nbacksl_pair[1]) {
|
|
|
|
|
|
|
|
if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
side = 1;
|
|
|
|
goto ii_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
(
|
|
|
|
abcd_pair[side ^ 1] & 32 ?
|
|
|
|
!(abcd_pair[side] & 32)
|
|
|
|
:
|
|
|
|
abcd_pair[(side ^= 1) ^ 1] & 32
|
|
|
|
) && *chrptr_pair[side]
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
abcd_pair[side ^ 1] &= 95;
|
|
|
|
turn_allowed = _LIBCONFINI_FALSE_;
|
|
|
|
goto ii_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
format.case_sensitive ?
|
|
|
|
**chrptr_pair != *chrptr_pair[1]
|
|
|
|
:
|
|
|
|
_LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1])
|
|
|
|
) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (**chrptr_pair) {
|
|
|
|
|
|
|
|
(*chrptr_pair)++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*chrptr_pair[1]) {
|
|
|
|
|
|
|
|
chrptr_pair[1]++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (**chrptr_pair || *chrptr_pair[1]) {
|
|
|
|
|
|
|
|
*abcd_pair &= 95;
|
|
|
|
abcd_pair[1] &= 95;
|
|
|
|
side = 1;
|
|
|
|
goto ii_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_match} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Compares two INI arrays and checks whether they match
|
|
|
|
@param ini_string_a The first INI array
|
|
|
|
@param ini_string_b The second INI array
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return A boolean: `true` if the two arrays match, `false` otherwise
|
|
|
|
|
|
|
|
This function grants that the result of the comparison between two INI arrays
|
|
|
|
will always match the the _literal_ comparison between the individual members
|
|
|
|
of both arrays after these have been parsed, one by one, by #ini_string_parse()
|
|
|
|
(with `format.do_not_collapse_values` set to `false`).
|
|
|
|
|
|
|
|
This function can be used, with `'.'` as delimiter, to compare section paths.
|
|
|
|
|
|
|
|
INI strings are the strings typically dispatched by #load_ini_file(),
|
|
|
|
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
|
|
|
|
escape sequences `\\`, `\'` and `\"`.
|
|
|
|
|
|
|
|
In order to be suitable for both names and values, **this function always
|
|
|
|
considers sequences of one or more spaces out of quotes in both strings as
|
|
|
|
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.case_sensitive`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.multiline_nodes`
|
|
|
|
|
|
|
|
**/
|
|
|
|
bool ini_array_match (
|
|
|
|
const char * const ini_string_a,
|
|
|
|
const char * const ini_string_b,
|
|
|
|
const char delimiter,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
return ini_string_match_ii(ini_string_a, ini_string_b, format);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
|
|
|
|
register uint8_t side = 1;
|
|
|
|
register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_;
|
|
|
|
uint8_t abcd_pair[2];
|
|
|
|
size_t nbacksl_pair[2];
|
|
|
|
const char * chrptr_pair[2] = {
|
|
|
|
ini_string_a + ltrim_s(ini_string_a, 0, _LIBCONFINI_WITH_EOL_),
|
|
|
|
ini_string_b + ltrim_s(ini_string_b, 0, _LIBCONFINI_WITH_EOL_)
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 We are in an odd sequence of backslashes and format supports
|
|
|
|
escape sequences
|
|
|
|
FLAG_32 This is a space
|
|
|
|
FLAG_64 This is a delimiter
|
|
|
|
FLAG_128 Skip this character
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
/* \ /\
|
|
|
|
\ */ delimited_match: /* \
|
|
|
|
\/ ______________________ \ */
|
|
|
|
|
|
|
|
|
|
|
|
nbacksl_pair[side] = 0;
|
|
|
|
|
|
|
|
if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++);
|
|
|
|
|
|
|
|
abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15;
|
|
|
|
|
|
|
|
if (
|
|
|
|
(
|
|
|
|
(abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_
|
|
|
|
) && (
|
|
|
|
(abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
nbacksl_pair[side]++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
abcd_pair[side] = !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
delimiter || (abcd_pair[side] & 64) ?
|
|
|
|
(abcd_pair[side] & 239) | 160
|
|
|
|
:
|
|
|
|
(abcd_pair[side] & 111) | 96
|
|
|
|
)
|
|
|
|
: delimiter && !(abcd_pair[side] & 12) && *chrptr_pair[side] == delimiter ?
|
|
|
|
(abcd_pair[side] & 111) | 96
|
|
|
|
: !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd_pair[side] & 175) | 128) ^ 4
|
|
|
|
: !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd_pair[side] & 175) | 128) ^ 8
|
|
|
|
: *chrptr_pair[side] ?
|
|
|
|
abcd_pair[side] & 47
|
|
|
|
: delimiter ?
|
|
|
|
abcd_pair[side] & 15
|
|
|
|
:
|
|
|
|
(abcd_pair[side] & 79) ^ 64;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd_pair[side] & 128) {
|
|
|
|
|
|
|
|
chrptr_pair[side]++;
|
|
|
|
goto delimited_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (side && turn_allowed) {
|
|
|
|
|
|
|
|
side ^= 1;
|
|
|
|
goto delimited_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
turn_allowed = _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
if (*nbacksl_pair || nbacksl_pair[1]) {
|
|
|
|
|
|
|
|
if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
side = 1;
|
|
|
|
goto delimited_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*abcd_pair ^ abcd_pair[1]) & 64) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!(
|
|
|
|
abcd_pair[side ^ 1] & 32 ?
|
|
|
|
abcd_pair[side] & 96
|
|
|
|
:
|
|
|
|
(abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32
|
|
|
|
) && *chrptr_pair[side]
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
abcd_pair[side ^ 1] &= 223;
|
|
|
|
turn_allowed = _LIBCONFINI_FALSE_;
|
|
|
|
goto delimited_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (~*abcd_pair & 64) {
|
|
|
|
|
|
|
|
if (
|
|
|
|
format.case_sensitive ?
|
|
|
|
**chrptr_pair != *chrptr_pair[1]
|
|
|
|
:
|
|
|
|
_LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1])
|
|
|
|
) {
|
|
|
|
|
|
|
|
return _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*abcd_pair &= 223;
|
|
|
|
abcd_pair[1] &= 223;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (**chrptr_pair) {
|
|
|
|
|
|
|
|
(*chrptr_pair)++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*chrptr_pair[1]) {
|
|
|
|
|
|
|
|
chrptr_pair[1]++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (**chrptr_pair || *chrptr_pair[1]) {
|
|
|
|
|
|
|
|
side = 1;
|
|
|
|
goto delimited_match;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return _LIBCONFINI_TRUE_;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_unquote} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes
|
|
|
|
(if single/double quotes are considered metacharacters in
|
|
|
|
respect to the format given)
|
|
|
|
@param ini_string The string to be unescaped
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
This function is very similar to #ini_string_parse(), except that does not
|
|
|
|
bother collapsing the sequences of more than one space that might result from
|
|
|
|
removing empty quotes. Its purpose is to be used to parse key and section names,
|
|
|
|
since these are always dispatched as already collapsed. In order to parse
|
|
|
|
values, or array parts listed in values, please use #ini_string_parse().
|
|
|
|
|
|
|
|
If you only need to compare @p ini_string with another string, consider to use
|
|
|
|
#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
|
|
|
|
and perform a simple comparison afterwards. These two functions are in fact able
|
|
|
|
to check directly for equality between unparsed INI strings without actually
|
|
|
|
modifiyng them.
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well). If the string does not contain quotes, or if quotes are
|
|
|
|
considered to be normal characters, no changes will be made.
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
|
|
|
|
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
|
|
|
|
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.multiline_nodes`
|
|
|
|
|
|
|
|
@include topics/ini_string_parse.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
size_t ini_unquote (char * const ini_string, const IniFormat format) {
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
|
|
|
|
|
|
|
|
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
register size_t idx = 0;
|
|
|
|
|
|
|
|
if (INIFORMAT_HAS_NO_ESC(format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
There are no escape sequences... I will just return the length of the
|
|
|
|
string...
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (ini_string[idx++]);
|
|
|
|
|
|
|
|
return idx - 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t lshift = 0, nbacksl = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (6 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Unescaped single quotes are odd right now
|
|
|
|
FLAG_8 Unescaped double quotes are odd right now
|
|
|
|
FLAG_16 This is an unescaped single quote and format supports single
|
|
|
|
quotes
|
|
|
|
FLAG_32 This is an unescaped double quote and format supports double
|
|
|
|
quotes
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; ini_string[idx]; idx++) {
|
|
|
|
|
|
|
|
abcd = !(abcd & 6) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(abcd & 47) | 32
|
|
|
|
: !(abcd & 9) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(abcd & 31) | 16
|
|
|
|
:
|
|
|
|
abcd & 15;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(nbacksl & 1) && (abcd & 48)) {
|
|
|
|
|
|
|
|
abcd ^= (abcd >> 2) & 12;
|
|
|
|
lshift++;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
nbacksl++;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if ((nbacksl & 1) && (abcd & 48)) {
|
|
|
|
|
|
|
|
lshift++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lshift += nbacksl >> 1;
|
|
|
|
nbacksl = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lshift) {
|
|
|
|
|
|
|
|
ini_string[idx - lshift] = ini_string[idx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lshift += nbacksl >> 1;
|
|
|
|
|
|
|
|
for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0');
|
|
|
|
|
|
|
|
return idx - lshift;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_string_parse} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes
|
|
|
|
(if single/double quotes are considered metacharacters in
|
|
|
|
respect to the format given); if the format allows it, sequences
|
|
|
|
of one or more spaces out of quotes will be collapsed
|
|
|
|
@param ini_string The string to be unescaped
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the string
|
|
|
|
|
|
|
|
This function is meant to be used to parse values. In order to parse key and
|
|
|
|
section names please use #ini_unquote().
|
|
|
|
|
|
|
|
If you only need to compare @p ini_string with another string, consider to use
|
|
|
|
#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
|
|
|
|
and perform a simple comparison afterwards. These two functions are in fact able
|
|
|
|
to check directly for equality between unparsed INI strings without actually
|
|
|
|
modifying them.
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well). If `format.do_not_collapse_values` is set to non-zero, spaces
|
|
|
|
surrounding empty quotes will be collapsed together with the latter.
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
|
|
|
|
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
|
|
|
|
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.multiline_nodes`
|
|
|
|
- `format.do_not_collapse_values`
|
|
|
|
|
|
|
|
@note `format.multiline_nodes` is used only to figure out whether there are
|
|
|
|
escape sequences or not. For all other purposes new line characters will
|
|
|
|
be considered to be equal to any other space character, even if the
|
|
|
|
format is not multi-line -- new line characters should never appear in
|
|
|
|
non-multi-line formats.
|
|
|
|
|
|
|
|
@include topics/ini_string_parse.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
size_t ini_string_parse (char * const ini_string, const IniFormat format) {
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
|
|
|
|
|
|
|
|
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Do not collapse spaces within members (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd right now
|
|
|
|
FLAG_16 Unescaped double quotes are odd right now
|
|
|
|
FLAG_32 This is an *escaped* single/double quote and format supports
|
|
|
|
single/double quotes
|
|
|
|
FLAG_64 This is a space
|
|
|
|
FLAG_128 Skip this character
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = (format.do_not_collapse_values ? 68 : 64) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
size_t idx, lshift;
|
|
|
|
|
|
|
|
if (format.multiline_nodes == INI_NO_MULTILINE) {
|
|
|
|
|
|
|
|
switch (abcd) {
|
|
|
|
|
|
|
|
case 64 | 2 | 1 :
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
There are no escape sequences, but spaces might still need to
|
|
|
|
be collapsed.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (idx = 0, lshift = 0; ini_string[idx]; idx++) {
|
|
|
|
|
|
|
|
abcd = !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? 3 : abcd & 64 ? 195 : 67;
|
|
|
|
|
|
|
|
if (abcd & 128) {
|
|
|
|
|
|
|
|
lshift++;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx -= (abcd & 64) && lshift < idx ?
|
|
|
|
++lshift
|
|
|
|
:
|
|
|
|
lshift;
|
|
|
|
|
|
|
|
ini_string[idx];
|
|
|
|
|
|
|
|
ini_string[idx++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx - lshift;
|
|
|
|
|
|
|
|
case 64 | 4 | 2 | 1 :
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
There are no escape sequences and spaces don't need to be
|
|
|
|
collapsed, but left and right trim might still be necessary.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
There might be escape sequences...
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
size_t nbacksl = 0;
|
|
|
|
|
|
|
|
for (idx = lshift = 0; ini_string[idx]; idx++) {
|
|
|
|
|
|
|
|
abcd = !(abcd & 10) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
nbacksl & 1 ?
|
|
|
|
(abcd & 63) | 32
|
|
|
|
:
|
|
|
|
((abcd & 223) | 128) ^ 16
|
|
|
|
)
|
|
|
|
: !(abcd & 17) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
nbacksl & 1 ?
|
|
|
|
(abcd & 63) | 32
|
|
|
|
:
|
|
|
|
((abcd & 223) | 128) ^ 8
|
|
|
|
)
|
|
|
|
: (abcd & 28) || !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
abcd & 31
|
|
|
|
: abcd & 64 ?
|
|
|
|
(abcd & 223) | 128
|
|
|
|
:
|
|
|
|
(abcd & 95) | 64;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 128) {
|
|
|
|
|
|
|
|
lshift++;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) {
|
|
|
|
|
|
|
|
nbacksl++;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (abcd & 32) {
|
|
|
|
|
|
|
|
lshift++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lshift += nbacksl >> 1;
|
|
|
|
nbacksl = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lshift += nbacksl >> 1;
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx -= (abcd & 64) && lshift < idx ?
|
|
|
|
++lshift
|
|
|
|
:
|
|
|
|
lshift;
|
|
|
|
|
|
|
|
ini_string[idx];
|
|
|
|
|
|
|
|
ini_string[idx++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return (abcd & 28) ^ 4 ?
|
|
|
|
idx - lshift
|
|
|
|
:
|
|
|
|
rtrim_h(ini_string, idx - lshift, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_get_length} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Gets the length of a stringified INI array in number of members
|
|
|
|
@param ini_string The stringified array (it can be `NULL`)
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The length of the INI array
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
**/
|
|
|
|
size_t ini_array_get_length (
|
|
|
|
const char * const ini_string,
|
|
|
|
const char delimiter,
|
|
|
|
const IniFormat format
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (!ini_string) {
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Delimiter is not any space (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd right now
|
|
|
|
FLAG_16 Unescaped double quotes are odd right now
|
|
|
|
FLAG_32 We are in an odd sequence of backslashes
|
|
|
|
FLAG_64 This is a space
|
|
|
|
FLAG_128 This is a delimiter
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register uint8_t abcd = (delimiter ? 64 : 68) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
size_t counter = 0;
|
|
|
|
|
|
|
|
for (size_t idx = 0; ini_string[idx]; idx++) {
|
|
|
|
|
|
|
|
/* Revision #1 */
|
|
|
|
|
|
|
|
abcd = !(abcd & 28) && ini_string[idx] == delimiter ?
|
|
|
|
(abcd & 159) | 128
|
|
|
|
: !(abcd & 24) && is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
(abcd & 68) ^ 4 ?
|
|
|
|
(abcd & 95) | 64
|
|
|
|
:
|
|
|
|
(abcd & 223) | 192
|
|
|
|
)
|
|
|
|
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd & 63) ^ 32
|
|
|
|
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(abcd & 31) ^ 16
|
|
|
|
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(abcd & 31) ^ 8
|
|
|
|
:
|
|
|
|
abcd & 31;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 128) {
|
|
|
|
|
|
|
|
counter++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return !counter || (~abcd & 68) ?
|
|
|
|
counter + 1
|
|
|
|
:
|
|
|
|
counter;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_foreach} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Calls a custom function for each member of a stringified INI
|
|
|
|
array, without modifying the content of the buffer -- useful for
|
|
|
|
read-only (`const`) stringified arrays
|
|
|
|
@param ini_string The stringified array (it can be `NULL`)
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@param f_foreach The function that will be invoked for each array
|
|
|
|
member
|
|
|
|
@param user_data A custom argument, or `NULL`
|
|
|
|
@return Zero for success, otherwise an error code (see `enum`
|
|
|
|
#ConfiniInterruptNo)
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
The user given function @p f_foreach (see #IniSubstrHandler data type) will be
|
|
|
|
invoked with six arguments: `ini_string`, `memb_offset` (the offset of the
|
|
|
|
member in bytes), `memb_length` (the length of the member in bytes), `memb_num`
|
|
|
|
(the offset of the member in number of members), `format` (the format of the INI
|
|
|
|
file), `user_data` (the custom argument @p user_data previously passed). If
|
|
|
|
@p f_foreach returns a non-zero value the function #ini_array_foreach() will be
|
|
|
|
interrupted.
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR.
|
|
|
|
|
|
|
|
@include topics/ini_array_foreach.c.
|
|
|
|
|
|
|
|
**/
|
|
|
|
int ini_array_foreach (
|
|
|
|
const char * const ini_string,
|
|
|
|
const char delimiter,
|
|
|
|
const IniFormat format,
|
|
|
|
const IniSubstrHandler f_foreach,
|
|
|
|
void * const user_data
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (!ini_string) {
|
|
|
|
|
|
|
|
return CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Delimiter is not any space (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd until now
|
|
|
|
FLAG_16 Unescaped double quotes are odd until now
|
|
|
|
FLAG_32 We are in an odd sequence of backslashes
|
|
|
|
FLAG_64 This is not a delimiter
|
|
|
|
FLAG_128 Stop the loop
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register size_t idx;
|
|
|
|
size_t offs = ltrim_s(ini_string, 0, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
idx = 0;
|
|
|
|
|
|
|
|
while (ini_string[idx++]);
|
|
|
|
|
|
|
|
return f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ?
|
|
|
|
CONFINI_FEINTR
|
|
|
|
:
|
|
|
|
CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
register uint8_t abcd = (delimiter ? 4 : 0) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
size_t memb_num = 0;
|
|
|
|
|
|
|
|
idx = offs;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ?
|
|
|
|
abcd & 159
|
|
|
|
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd | 64) ^ 32
|
|
|
|
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd & 223) | 64) ^ 16
|
|
|
|
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd & 223) | 64) ^ 8
|
|
|
|
: ini_string[idx] ?
|
|
|
|
(abcd & 223) | 64
|
|
|
|
:
|
|
|
|
128;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(abcd & 88)) {
|
|
|
|
|
|
|
|
if (
|
|
|
|
f_foreach(
|
|
|
|
ini_string,
|
|
|
|
offs,
|
|
|
|
rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_),
|
|
|
|
memb_num++,
|
|
|
|
format,
|
|
|
|
user_data
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
return CONFINI_FEINTR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
offs = abcd & 128 ? idx + 1 : ltrim_s(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = abcd & 216 ? idx + 1 : offs;
|
|
|
|
|
|
|
|
} while (!(abcd & 128) && ((abcd & 92) || ini_string[idx]));
|
|
|
|
|
|
|
|
return CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_shift} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Shifts the location pointed by @p ini_strptr to the next member
|
|
|
|
of the INI array (without modifying the content of the buffer),
|
|
|
|
or to `NULL` if the INI array has no more members -- useful for
|
|
|
|
read-only (`const`) stringified arrays
|
|
|
|
@param ini_strptr The memory location of the stringified array --
|
|
|
|
it cannot be `NULL`, but it can point to `NULL`
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The length of the array member that has been left behind
|
|
|
|
|
|
|
|
Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
@include topics/ini_array_shift.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
size_t ini_array_shift (const char ** const ini_strptr, const char delimiter, const IniFormat format) {
|
|
|
|
|
|
|
|
size_t toklen = 0;
|
|
|
|
|
|
|
|
if (*ini_strptr && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
if (!delimiter) {
|
|
|
|
|
|
|
|
toklen = ltrim_s(*ini_strptr, 0, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format);
|
|
|
|
*ini_strptr += toklen;
|
|
|
|
toklen = rtrim_s(*ini_strptr - toklen, toklen, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (**ini_strptr) {
|
|
|
|
|
|
|
|
*ini_strptr += ltrim_s(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (delimiter || **ini_strptr) {
|
|
|
|
|
|
|
|
return toklen;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*ini_strptr = (char *) 0;
|
|
|
|
return toklen;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_collapse} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Compresses the distribution of the data in a stringified INI
|
|
|
|
array by removing all the white spaces that surround its
|
|
|
|
delimiters, empty quotes, collapsable spaces, etc
|
|
|
|
@param ini_string The stringified array
|
|
|
|
@param delimiter The delimiter between the array members --
|
|
|
|
if zero (`INI_ANY_SPACE`) any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The new length of the stringified array
|
|
|
|
|
|
|
|
Out of quotes similar to ECMAScript
|
|
|
|
`ini_string.replace(new RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`.
|
|
|
|
If #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces
|
|
|
|
(`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of
|
|
|
|
what the format says.
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
This function can be useful before invoking `memcpy()` using @p ini_string as
|
|
|
|
source, when saving memory is a priority.
|
|
|
|
|
|
|
|
The @p format argument is used for the following fields:
|
|
|
|
|
|
|
|
- `format.no_single_quotes`
|
|
|
|
- `format.no_double_quotes`
|
|
|
|
- `format.do_not_collapse_values`
|
|
|
|
- `format.preserve_empty_quotes`
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
1. Using comma as delimiter:
|
|
|
|
- Before: ` first , second
|
|
|
|
, third , etc. `
|
|
|
|
- After: `first,second,third,etc.`
|
|
|
|
2. Using `INI_ANY_SPACE` as delimiter:
|
|
|
|
- Before: ` first second
|
|
|
|
third etc. `
|
|
|
|
- After: `first second third etc.`
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
|
|
|
|
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
|
|
|
|
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
@include topics/ini_array_collapse.c
|
|
|
|
|
|
|
|
@note The actual space occupied by the array might get reduced further after
|
|
|
|
each member is parsed by #ini_string_parse().
|
|
|
|
|
|
|
|
**/
|
|
|
|
size_t ini_array_collapse (char * const ini_string, const char delimiter, const IniFormat format) {
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
|
|
|
|
|
|
|
|
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) {
|
|
|
|
|
|
|
|
case 0: return collapse_everything(ini_string, format);
|
|
|
|
case 1: return collapse_empty_quotes(ini_string, format);
|
|
|
|
case 2: return collapse_spaces(ini_string, format);
|
|
|
|
case 3: return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (16 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Do not collapse spaces within members (const)
|
|
|
|
FLAG_8 Preserve empty quotes (const)
|
|
|
|
FLAG_16 Any space is delimiter (const)
|
|
|
|
FLAG_32 Unescaped single quotes are odd right now
|
|
|
|
FLAG_64 Unescaped double quotes are odd right now
|
|
|
|
FLAG_128 We are in an odd sequence of backslashes
|
|
|
|
FLAG_256 This is *not* a delimiter out of quotes
|
|
|
|
FLAG_512 This is *not* a space out of quotes
|
|
|
|
FLAG_1024 These are some quotes
|
|
|
|
FLAG_2048 These are some quotes or among the last spaces are some empty
|
|
|
|
quotes
|
|
|
|
FLAG_4096 Save current `idx_d` in `fallback`
|
|
|
|
FLAG_8192 Restore `idx_d` from `fallback` before writing
|
|
|
|
FLAG_16384 Decrease `idx_d` before writing
|
|
|
|
FLAG_32768 Keep increasing `idx_d` after writing
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
size_t idx_s = 0, idx_d = 0, fallback = 0;
|
|
|
|
|
|
|
|
register uint16_t abcd = (delimiter ? 0 : 16) |
|
|
|
|
(format.preserve_empty_quotes << 3) |
|
|
|
|
(format.do_not_collapse_values << 2) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
|
|
|
|
for (; ini_string[idx_s]; idx_s++) {
|
|
|
|
|
|
|
|
/* Revision #1 */
|
|
|
|
|
|
|
|
abcd = !(abcd & 112) && ini_string[idx_s] == delimiter ?
|
|
|
|
(
|
|
|
|
(abcd & 536) && ((abcd & 1560) ^ 8) && ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ?
|
|
|
|
(abcd & 33407) | 33280
|
|
|
|
:
|
|
|
|
(abcd & 41599) | 41472
|
|
|
|
)
|
|
|
|
: !(abcd & 96) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
|
|
|
|
(
|
|
|
|
!((abcd & 1816) ^ 1800) ?
|
|
|
|
(abcd & 43391) | 40960
|
|
|
|
: !(~abcd & 1560) ?
|
|
|
|
(abcd & 41087) | 40960
|
|
|
|
: !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || !((abcd & 1560) ^ 1048) ?
|
|
|
|
(abcd & 32895) | 32768
|
|
|
|
: !(abcd & 540) || !((abcd & 1564) ^ 8) || !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ?
|
|
|
|
abcd & 2431
|
|
|
|
: ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ?
|
|
|
|
(abcd & 39295) | 36864
|
|
|
|
:
|
|
|
|
(abcd & 35199) | 32768
|
|
|
|
)
|
|
|
|
: !(abcd & 193) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
!((abcd & 3896) ^ 8) ?
|
|
|
|
(abcd & 44927) | 44064
|
|
|
|
: !((abcd & 3896) ^ 2056) ?
|
|
|
|
(abcd & 36735) | 36128
|
|
|
|
: !((abcd & 1056) ^ 32) ?
|
|
|
|
(abcd & 33631) | 33536
|
|
|
|
: !(abcd & 40) || !(~abcd & 1064) ?
|
|
|
|
((abcd & 36735) | 35840) ^ 32
|
|
|
|
: ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ?
|
|
|
|
(abcd & 40831) | 39968
|
|
|
|
:
|
|
|
|
((abcd & 20351) | 19456) ^ 32
|
|
|
|
)
|
|
|
|
: !(abcd & 162) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
(
|
|
|
|
!((abcd & 3928) ^ 8) ?
|
|
|
|
(abcd & 44927) | 44096
|
|
|
|
: !((abcd & 3928) ^ 2056) ?
|
|
|
|
(abcd & 36735) | 36160
|
|
|
|
: !((abcd & 1088) ^ 64) ?
|
|
|
|
(abcd & 33599) | 33536
|
|
|
|
: !(abcd & 72) || !(~abcd & 1096) ?
|
|
|
|
((abcd & 36735) | 35840) ^ 64
|
|
|
|
: ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ?
|
|
|
|
(abcd & 40831) | 40000
|
|
|
|
:
|
|
|
|
((abcd & 20351) | 19456) ^ 64
|
|
|
|
)
|
|
|
|
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(
|
|
|
|
(abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
|
|
|
|
((abcd & 33791) | 33536) ^ 128
|
|
|
|
:
|
|
|
|
((abcd & 41983) | 41728) ^ 128
|
|
|
|
)
|
|
|
|
: (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
|
|
|
|
(abcd & 33663) | 33536
|
|
|
|
:
|
|
|
|
(abcd & 41855) | 41728;
|
|
|
|
|
|
|
|
|
|
|
|
ini_string[
|
|
|
|
abcd & 16384 ?
|
|
|
|
--idx_d
|
|
|
|
: abcd & 8192 ?
|
|
|
|
(idx_d = fallback)
|
|
|
|
: abcd & 4096 ?
|
|
|
|
(fallback = idx_d)
|
|
|
|
:
|
|
|
|
idx_d
|
|
|
|
] = (abcd & 1636) && ((abcd & 1392) ^ 16) ?
|
|
|
|
ini_string[idx_s]
|
|
|
|
:
|
|
|
|
_LIBCONFINI_COLLAPSED_;
|
|
|
|
|
|
|
|
|
|
|
|
if (abcd & 32768) {
|
|
|
|
|
|
|
|
idx_d++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
|
|
|
idx_s = ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ?
|
|
|
|
(idx_d = 0)
|
|
|
|
: !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || !((abcd & 1304) ^ 1032) ?
|
|
|
|
(idx_d = fallback)
|
|
|
|
: !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ?
|
|
|
|
(idx_d -= 2)
|
|
|
|
: ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ?
|
|
|
|
idx_d
|
|
|
|
:
|
|
|
|
--idx_d;
|
|
|
|
|
|
|
|
ini_string[idx_s];
|
|
|
|
|
|
|
|
ini_string[idx_s++] = '\0'
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
return idx_d;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_break} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Replaces the first delimiter found (together with the spaces
|
|
|
|
that surround it) with `\0`
|
|
|
|
@param ini_string The stringified array (it can be `NULL`)
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return A pointer to the remaining INI array or `NULL` if the remaining
|
|
|
|
array is empty
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
Similarly to `strtok_r()` this function can be used only once for a given
|
|
|
|
string.
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
|
|
|
|
no-op and will return `NULL`.
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
@include topics/ini_array_break.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
char * ini_array_break (char * const ini_string, const char delimiter, const IniFormat format) {
|
|
|
|
|
|
|
|
if (ini_string && !_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
|
|
|
|
|
|
|
|
char * remnant;
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
remnant = ini_string;
|
|
|
|
|
|
|
|
while (*remnant++);
|
|
|
|
|
|
|
|
rtrim_h(ini_string, remnant - ini_string - 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
remnant = ini_string + get_metachar_pos(ini_string, delimiter, format);
|
|
|
|
|
|
|
|
if (*remnant) {
|
|
|
|
|
|
|
|
*remnant = '\0';
|
|
|
|
rtrim_h(ini_string, remnant - ini_string, _LIBCONFINI_WITH_EOL_);
|
|
|
|
remnant += ltrim_h(remnant, 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (delimiter || *remnant) {
|
|
|
|
|
|
|
|
return remnant;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return (char *) 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_release} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Replaces the first delimiter found (together with the spaces
|
|
|
|
that surround it) with `\0`, then shifts the location pointed by
|
|
|
|
@p ini_strptr to the next member of the INI array, or to `NULL`
|
|
|
|
if the INI array has no more members
|
|
|
|
@param ini_strptr The memory location of the stringified array --
|
|
|
|
it cannot be `NULL`, but it can point to `NULL`
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@return The array member that has been released
|
|
|
|
|
|
|
|
Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
Similarly to `strtok_r()` this function can be used only once for a given
|
|
|
|
string.
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
|
|
|
|
no-op and will set @p ini_strptr to `NULL`.
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
@include topics/ini_array_release.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
char * ini_array_release (char ** const ini_strptr, const char delimiter, const IniFormat format) {
|
|
|
|
|
|
|
|
char * const token = *ini_strptr;
|
|
|
|
|
|
|
|
if (token && !_LIBCONFINI_IMPLICIT_RANGE_(token) && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
*ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format);
|
|
|
|
|
|
|
|
if (**ini_strptr) {
|
|
|
|
|
|
|
|
**ini_strptr = '\0';
|
|
|
|
rtrim_h(token, *ini_strptr - token, _LIBCONFINI_WITH_EOL_);
|
|
|
|
*ini_strptr += ltrim_h(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (delimiter || **ini_strptr) {
|
|
|
|
|
|
|
|
return token;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*ini_strptr = (char *) 0;
|
|
|
|
return token;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_array_split} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Splits a stringified INI array into NUL-separated members and
|
|
|
|
calls a custom function for each member
|
|
|
|
@param ini_string The stringified array (it cannot be `NULL`)
|
|
|
|
@param delimiter The delimiter between the array members -- if
|
|
|
|
zero (see #INI_ANY_SPACE), any space is
|
|
|
|
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
|
|
|
|
@param format The format of the INI file
|
|
|
|
@param f_foreach The function that will be invoked for each array
|
|
|
|
member
|
|
|
|
@param user_data A custom argument, or `NULL`
|
|
|
|
@return Zero for success, otherwise an error code (see `enum`
|
|
|
|
#ConfiniInterruptNo)
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
The user given function @p f_foreach (see #IniStrHandler data type) will be
|
|
|
|
invoked with five arguments: `member` (the member of the array), `memb_length`
|
|
|
|
(the length of the member in bytes), `memb_num` (the offset of the member in
|
|
|
|
number of members), `format` (the format of the INI file), `user_data` (the
|
|
|
|
custom argument @p user_data previously passed). If @p f_foreach returns a
|
|
|
|
non-zero value the function #ini_array_split() will be interrupted.
|
|
|
|
|
|
|
|
Similarly to `strtok_r()` this function can be used only once for a given
|
|
|
|
string.
|
|
|
|
|
|
|
|
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this
|
|
|
|
function is no-op and will return an error code.
|
|
|
|
|
|
|
|
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
|
|
|
|
`'\''` or `'\"'`), its role as metacharacter will have higher priority
|
|
|
|
than its role as delimiter (i.e., the array will have no delimiters and
|
|
|
|
will contain only one member).
|
|
|
|
|
|
|
|
Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR.
|
|
|
|
|
|
|
|
@include topics/ini_array_split.c.
|
|
|
|
|
|
|
|
**/
|
|
|
|
int ini_array_split (
|
|
|
|
char * const ini_string,
|
|
|
|
const char delimiter,
|
|
|
|
const IniFormat format,
|
|
|
|
const IniStrHandler f_foreach,
|
|
|
|
void * const user_data
|
|
|
|
) {
|
|
|
|
|
|
|
|
if (!ini_string || _LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
|
|
|
|
|
|
|
|
return CONFINI_EROADDR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Mask `abcd` (8 bits used):
|
|
|
|
|
|
|
|
FLAG_1 Single quotes are not metacharacters (const)
|
|
|
|
FLAG_2 Double quotes are not metacharacters (const)
|
|
|
|
FLAG_4 Delimiter is not any space (const)
|
|
|
|
FLAG_8 Unescaped single quotes are odd until now
|
|
|
|
FLAG_16 Unescaped double quotes are odd until now
|
|
|
|
FLAG_32 We are in an odd sequence of backslashes
|
|
|
|
FLAG_64 This is not a delimiter
|
|
|
|
FLAG_128 Stop the loop
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
register size_t idx;
|
|
|
|
size_t offs = ltrim_h(ini_string, 0, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
We have no delimiters (array has only one member)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
idx = 0;
|
|
|
|
|
|
|
|
while (ini_string[idx++]);
|
|
|
|
|
|
|
|
return f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ?
|
|
|
|
CONFINI_FEINTR
|
|
|
|
:
|
|
|
|
CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
register uint8_t abcd = (delimiter ? 4 : 0) |
|
|
|
|
(format.no_double_quotes << 1) |
|
|
|
|
format.no_single_quotes;
|
|
|
|
|
|
|
|
size_t memb_num = 0;
|
|
|
|
|
|
|
|
idx = offs;
|
|
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ?
|
|
|
|
abcd & 159
|
|
|
|
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
|
|
|
|
(abcd | 64) ^ 32
|
|
|
|
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
|
|
|
|
((abcd & 223) | 64) ^ 16
|
|
|
|
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
|
|
|
|
((abcd & 223) | 64) ^ 8
|
|
|
|
: ini_string[idx] ?
|
|
|
|
(abcd & 223) | 64
|
|
|
|
:
|
|
|
|
128;
|
|
|
|
|
|
|
|
|
|
|
|
if (!(abcd & 88)) {
|
|
|
|
|
|
|
|
ini_string[idx] = '\0';
|
|
|
|
|
|
|
|
if (
|
|
|
|
f_foreach(
|
|
|
|
ini_string + offs,
|
|
|
|
rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_),
|
|
|
|
memb_num++,
|
|
|
|
format,
|
|
|
|
user_data
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
return CONFINI_FEINTR;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
offs = abcd & 128 ? idx + 1 : ltrim_h(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = abcd & 216 ? idx + 1 : offs;
|
|
|
|
|
|
|
|
} while (!(abcd & 128) && ((abcd & 92) || ini_string[idx]));
|
|
|
|
|
|
|
|
return CONFINI_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_global_set_lowercase_mode} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Sets the value of the global variable
|
|
|
|
#INI_GLOBAL_LOWERCASE_MODE
|
|
|
|
@param lowercase The new value
|
|
|
|
@return Nothing
|
|
|
|
|
|
|
|
If @p lowercase is `true`, key and section names in case-insensitive INI formats
|
|
|
|
will be dispatched lowercase, verbatim otherwise (default value: `true`).
|
|
|
|
|
|
|
|
@warning This function changes the value of one or more global variables. In
|
|
|
|
order to be thread-safe this function should be used only once at
|
|
|
|
beginning of execution, or otherwise a mutex logic must be
|
|
|
|
introduced.
|
|
|
|
|
|
|
|
**/
|
|
|
|
void ini_global_set_lowercase_mode (const bool lowercase) {
|
|
|
|
|
|
|
|
INI_GLOBAL_LOWERCASE_MODE = lowercase;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_global_set_implicit_value} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Sets the value to be to be assigned to implicit keys
|
|
|
|
@param implicit_value The string to be used as implicit value
|
|
|
|
(usually `"YES"`, or `"TRUE"`)
|
|
|
|
@param implicit_v_len The length of @p implicit_value (usually
|
|
|
|
`0`, independently of its real length)
|
|
|
|
@return Nothing
|
|
|
|
|
|
|
|
@warning This function changes the value of one or more global variables. In
|
|
|
|
order to be thread-safe this function should be used only once at
|
|
|
|
beginning of execution, or otherwise a mutex logic must be
|
|
|
|
introduced.
|
|
|
|
|
|
|
|
@include topics/ini_global_set_implicit_value.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
void ini_global_set_implicit_value (char * const implicit_value, const size_t implicit_v_len) {
|
|
|
|
|
|
|
|
INI_GLOBAL_IMPLICIT_VALUE = implicit_value;
|
|
|
|
INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_fton} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Calculates the #IniFormatNum of an #IniFormat
|
|
|
|
@param source The #IniFormat to compute
|
|
|
|
@return The unique unsigned integer that identifies the format given
|
|
|
|
|
|
|
|
**/
|
|
|
|
IniFormatNum ini_fton (const IniFormat source) {
|
|
|
|
|
|
|
|
#define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) |
|
|
|
|
|
|
|
|
return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0;
|
|
|
|
|
|
|
|
#undef __INIFORMAT_ID__
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_ntof} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Constructs a new #IniFormat according to an #IniFormatNum
|
|
|
|
@param format_num The #IniFormatNum to parse
|
|
|
|
@return The new #IniFormat constructed
|
|
|
|
|
|
|
|
@note If @p format_num `>` `16777215` it will be truncated to 24 bits.
|
|
|
|
|
|
|
|
**/
|
|
|
|
IniFormat ini_ntof (const IniFormatNum format_num) {
|
|
|
|
|
|
|
|
#define __MAX_1_BITS__ 1
|
|
|
|
#define __MAX_2_BITS__ 3
|
|
|
|
#define __MAX_3_BITS__ 7
|
|
|
|
#define __MAX_4_BITS__ 15
|
|
|
|
#define __MAX_5_BITS__ 31
|
|
|
|
#define __MAX_6_BITS__ 63
|
|
|
|
#define __MAX_7_BITS__ 127
|
|
|
|
#define __MAX_8_BITS__ 255
|
|
|
|
#define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \
|
|
|
|
(unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__),
|
|
|
|
|
|
|
|
return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) };
|
|
|
|
|
|
|
|
#undef __INIFORMAT_PROPERTIES__
|
|
|
|
#undef __MAX_8_BITS__
|
|
|
|
#undef __MAX_7_BITS__
|
|
|
|
#undef __MAX_6_BITS__
|
|
|
|
#undef __MAX_5_BITS__
|
|
|
|
#undef __MAX_4_BITS__
|
|
|
|
#undef __MAX_3_BITS__
|
|
|
|
#undef __MAX_2_BITS__
|
|
|
|
#undef __MAX_1_BITS__
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @utility{ini_get_bool} **/
|
|
|
|
/**
|
|
|
|
|
|
|
|
@brief Checks whether a string matches one of the booleans listed in
|
|
|
|
the private constant #INI_BOOLEANS (case-insensitive)
|
|
|
|
@param ini_string A string to check (it can be `NULL`)
|
|
|
|
@param when_fail A value that is returned if no matching
|
|
|
|
boolean is found
|
|
|
|
@return The matching boolean (`0` or `1`) or @p when_fail if
|
|
|
|
@p ini_string does not contain a valid INI boolean
|
|
|
|
|
|
|
|
Usually @p ini_string comes from an #IniDispatch (but any other string may be
|
|
|
|
used as well).
|
|
|
|
|
|
|
|
@include miscellanea/typed_ini.c
|
|
|
|
|
|
|
|
**/
|
|
|
|
int ini_get_bool (const char * const ini_string, const int when_fail) {
|
|
|
|
|
|
|
|
if (!ini_string) {
|
|
|
|
|
|
|
|
return when_fail;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
register uint8_t bool_idx;
|
|
|
|
register size_t pair_idx, chr_idx;
|
|
|
|
|
|
|
|
for (pair_idx = 0; pair_idx < sizeof(INI_BOOLEANS) / sizeof(const char * const [2]); pair_idx++) {
|
|
|
|
|
|
|
|
for (bool_idx = 0; bool_idx < 2; bool_idx++) {
|
|
|
|
|
|
|
|
chr_idx = 0;
|
|
|
|
|
|
|
|
while (_LIBCONFINI_CHR_CASEFOLD_(ini_string[chr_idx]) == INI_BOOLEANS[pair_idx][bool_idx][chr_idx]) {
|
|
|
|
|
|
|
|
if (!ini_string[chr_idx++]) {
|
|
|
|
|
|
|
|
return (int) bool_idx;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return when_fail;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* LINKS - In case you don't have `#include <stdlib.h>` in your source */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
@alias{ini_get_int}
|
|
|
|
Pointer to
|
|
|
|
[`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi)
|
|
|
|
@alias{ini_get_lint}
|
|
|
|
Pointer to
|
|
|
|
[`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol)
|
|
|
|
@alias{ini_get_llint}
|
|
|
|
Pointer to
|
|
|
|
[`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll)
|
|
|
|
@alias{ini_get_double}
|
|
|
|
Pointer to
|
|
|
|
[`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof)
|
|
|
|
|
|
|
|
**/
|
|
|
|
|
|
|
|
int (* const ini_get_int) (const char * ini_string) = atoi;
|
|
|
|
|
|
|
|
long int (* const ini_get_lint) (const char * ini_string) = atol;
|
|
|
|
|
|
|
|
long long int (* const ini_get_llint) (const char * ini_string) = atoll;
|
|
|
|
|
|
|
|
double (* const ini_get_double) (const char * ini_string) = atof;
|
|
|
|
|
|
|
|
/* Legacy support -- please **do not use this**! */
|
|
|
|
#ifdef ini_get_float
|
|
|
|
#undef ini_get_float
|
|
|
|
#endif
|
|
|
|
double (* const ini_get_float) (const char * ini_string) = atof;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* GLOBAL VARIABLES */
|
|
|
|
|
|
|
|
|
|
|
|
bool INI_GLOBAL_LOWERCASE_MODE = _LIBCONFINI_FALSE_;
|
|
|
|
|
|
|
|
char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0;
|
|
|
|
|
|
|
|
size_t INI_GLOBAL_IMPLICIT_V_LEN = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @endfnlist **/
|
|
|
|
|
|
|
|
/* EOF */
|
|
|
|
|