Planetary Image Research Laboratory

Department of Planetary Sciences
University of Arizona
Tucson, Arizona

PIRL Parameter Value Logic
Manual


Description

The Parameter Value Language

The Parameter Value Language is commonly used in the label section of image files found on Planetary Data System CDs as well as other data products from the Planetary Science and Astronomical communities. The language specification is provided by the Consultative Committee for Space Data Systems in the Blue Book "Parameter Value Language Specification" (CCSDS0006,8), June 2000 [CCSDS 641.0-B-2] and Green Book "Parameter Value Language - A Tutorial", May 1992 [CCSDS 641.0-G-2] documents.

The language has a deceptively simple syntax for a parameter defining PVL statement:

Name = Value <Units>

A parameter is just a token Name (i.e. a string) that is used to refer to the corresponding Value. The optional value is a single datum representing a number (integer or real) or string, or an array of zero or more of these values. The optional Units string which may follow any value, including an array, is a string enclosed in angle braces.

Without getting into the details (which are in the references), there are complexities to the value representation that make managing the resultant data structures somewhat problematical. The parameter value statement is not required to be on a single line (a string terminated by a line-feed and/or carriage-return character). Comments (C-language style) may be provided on the same or separate lines. An integer may be represented with a specified-base notation in addition to the usual decimal notation. A string may be unquoted, single quoted, or double quoted and may represent a special date/time notation. Quoted strings may extended across multiple lines with the line separators, any optional preceding hyphen, and any following white space being replaced with a single space character. Arrays may be unordered sets contained in curly braces or ordered sequences contained in parentheses, with a possible mix of value types.

It is an unfortunate fact of life that the Parameter Value Language has not always been used with strict adherence to the syntax rules. Therefore, the PIRL implementation of the language parser is designed to be very tolerant, rather than requiring strict adherence to a rigid specification; i.e. the intent is to "get the information" if possible, and let the user decide what to do with it. For example: Any string may be quoted (with single or double quotation marks), including the parameter name. Sets and sequences are treated identically as arrays. A list of values that is not enclosed in curly braces or parentheses is treated as a set. Arrays may be nested to any depth. Base notation (N#MMM#) allows any base value that can be handled by strtol. Date and time values are treated as strings (they are not interpreted). Comments may be anywhere in a statement (except inside quoted strings where they are treated as part of the string), including crossing record/line boundaries. Comments that lack termination will be assumed to end at the next line break. Unterminated units strings will be assumed to end at the next parameter value separator.

The PPVL_strict global variable controls whether the PVL will be interpreted strictly or with tolerance. By default PPVL_strict is initialized to FALSE (i.e. use tolerance). Whenever strict PVL interpretation would produce an error condition, a warning is registered (see Error Handling, below, for more details).


Structures

Parameters

The parser stores the information it gathers about each parameter definition in a PPVL_Parameter structure:

typedef int     PPVL_Class;

typedef struct PPVL_parameter
    {
    PPVL_Class  classification; /* Classification of parameter */
    char        *name;          /* Parameter name (keyword) */
    union
        {
        /*    For all classes of parameters EXCEPT begin aggregate:
        */
        struct PPVL_value       *value;

        /*    ONLY for begin aggregate class parameters:
        */
        struct PPVL_parameter   **parameters;
        }
                content;        /* Parameter content (value) */

    char        *comments;      /* Leading comments */
    void        *user_data;     /* Link to user-defined data */
    }
    PPVL_Parameter;

Each parameter is of a particular classification:

Unknown (PPVL_CLASS_UNKNOWN)
This should only occur in the case of a parsing error.
Token (PPVL_CLASS_TOKEN)
A parameter with no values (this does not include parameters with empty sets or sequences).
End (PPVL_CLASS_END)
A token with the special name "End".
Assignment (PPVL_CLASS_ASSIGNMENT)
The usual parameter value assignment.
Aggregate (PPVL_CLASS_AGGREGATE or PPVL_CLASS_BEGIN_AGGREGATE)
One of the special classifications indicated by a parameter with a special (reserved) name:

Both Objects and Groups are in the Aggregate classification. Special names are not case sensitive. Aggregate classification parameters (both begin and end) may optionally be assigned a value - which is expected to be a string which names the aggregate - used as the parameter name, when available. The distinction between an Object and a Group may be meaningful to the user (e.g. an Object may include parameters that collectively form a larger object, while the parameters of a Group may only have some common quality), but they are otherwise identical.

Note: End-class parameters are not retained in parameter lists (i.e. aggregate values). They are processed on input (PPVL_scan_parameter) and output (PPVL_write_parameter) only. Except for specialized application purposes, they are not needed.

The classification code of a parameter is actually a pattern of bit flags. Only specific aggregate classification codes (group, object), as opposed to general codes (aggregate) can be meaningfully used directly. A set of PPVL_Class_Is_Class macros are provided for ease in evaluating classification codes. For Class substitute any (except Unknown) of the classification names (as in PPVL_CLASS_Class) in initial caps form. For example:

    if (PPVL_Class_Is_Assignment (The_Parameter->classification))
        {
        /* Process assignment parameter ...
        */
        }
    else if (PPVL_Class_Is_Aggregate (The_Parameter->classification))
        {
        if (PPVL_Class_Is_Group (The_Parameter->classification))
            {
            /* Group specific processing ...
            */
            }

The parameter's name symbolically identifies the parameter. When the parameter is in the aggregate classification, the name is reset to the identifier string value, if it has one (otherwise the name is left as the special aggregate name). Parameter names (or aggregate identifiers) need not be unique within the aggregate in which they occur (but then it is left to the user to distinguish them).

Any comments that occur immediately preceding a PVL statement are collected together in the parameter's comments string. A comment is identified as a string enclosed in C-language style comment characters (/* ... */). Multiple comments are delimited by a single new-line ('\n') character in the comments string. Comments that are embedded within a parameter statement are ignored.

A user_data pointer is provided for the user to link any desired data to a parameter. This pointer is not used in this module in any way (other than as a potential selection to PPVL_find_parameter).

Values

The content element of a PPVL_Parameter is either a value - a PPVL_Value pointer - or, for aggregate parameters only, a list of parameters - a pointer to an array of PPVL_Parameter pointers (see the Aggregates section below). A PPVL_Value contains the value portion of the PVL statement:

typedef int     PPVL_Type;

typedef struct PPVL_value
    {
    PPVL_Type   type;               /* Data type */
    union
        {
        unsigned long       integer;
        double              real;
        char                *string;
        struct PPVL_value   **array;
        }
                data;               /* Actual data value */
    int         base;               /* Number system base (radix) */
    char        *units;             /* Measurement units */
    }
    PPVL_Value;

The data for each value is either an integer number (long precision), a real number (double precision), a string pointer (char *), or itself an array of value structures. In this latter case, data.array points to a NULL terminated list of PPVL_Value pointers.

Note: Values in the list of an array may be a mix of any types. This includes another array value. Arrays may be nested to any depth. There is also no limitation on the size of any array, including subarrays of different sizes.

The data type of each value is indicated by a type code:

PPVL_TYPE_UNKNOWN
Only occur in the case of a parsing error.
PPVL_TYPE_NUMERIC
Includes these specific types:
PPVL_TYPE_STRING
Includes these specific types:
PPVL_TYPE_ARRAY
Includes these specific types:

The type code of a parameter is actually a collection of bit flags. Only specific type codes (e.g. integer or text), as opposed to general codes (e.g. numeric or string) can be meaningfully used directly. A set of PPVL_Type_Is_Type macros are provided for ease in evaluating type codes. For Type substitute any (except Unknown) of the type names (as in PPVL_TYPE_Type) in initial caps form. For example:

    if (PPVL_Type_Is_String (The_Value->type))
        {
        /* Process string value ...
        */
        }
    else if (PPVL_Type_Is_Numeric (The_Value->type))
        {
        if (PPVL_Type_Is_Real (The_Value->type))
            {
            /* Real value processing ...
            */
            }

Each value may have a units string. The string is typically used to provide the units of measurement for the corresponding value, but its interpretation is left to the user. An array (set or sequence) value may have a units string in addition to a possible units string for each value of the array. The units string of an array value may be taken as being applied to each value of the array unless the array value has its own units string, however, this is just a suggested use.

The base is used when an integer value is output to determine what numerical base will be used to represent it. See the description of the PPVL_write_value function for details.

Aggregates

When a parameter is of the Begin_Aggregate classification - Object, Begin_Object (or BeginObject), Group, or Begin_Group (or BeginGroup) parameter name - its content.value is a pointer to a NULL terminated list of PPVL_Parameter pointers (PPVL_Parameter **). Each parameter in the list is a member of the aggregate parameter. The PVL statement that ends the list of aggregate parameter members is not in this list (the NULL implicitly represents the End_Aggregate parameter); it is swallowed on input ( PPVL_read_aggregate), and automatically recreated on output ( PPVL_write_parameter).

The previous value of the aggregate parameter (as returned from PPVL_scan_parameter), if any, is assigned to the parameter's name. Aggregate parameters without a single string value retain their original name. Inappropriate values (not a single string) will generate a warning (or error in strict mode).


Functions

A complete list of the PPVL functions and their purpose is in the Function Index, which links to the specific function description below. Each function description provides its calling syntax. Every source code file using the PPVL module should include the module header:

    #include    "PPVL.h"

A pseudo-object-oriented interface is also provided. These functions (actually macros employing the specific functions) operate on typeless PPVL_Object handles, which may be PPVL_Parameter or PPVL_Value pointers. The macros PPVL_Is_Parameter and PPVL_Is_Value determine the specific type of a PPVL_Object.

The PPVL functions are organized in such a manner that applications usually only need to call a very few functions. This becomes clearer when the relationship amongst the functions is understood:

Input Functions

These functions manage the conversion of external PVL statements to internal PPVL objects. Usually only the PPVL_read_aggregate function is needed, though the PPVL_scan_aggregate function is used for special circumstances where the application itself reads the PVL text into a buffer for processing. In general, the lower level functions are not needed by the application. They are made available for applications that need to handle the PVL input process in some unique, specialized manner.

PPVL_read_aggregate

    PPVL_Parameter *
    PPVL_read_aggregate
        (
        FILE            *File,
        unsigned long   Read_Limit,
        unsigned long   *Total_Scanned
        );

An aggregate parameter ( PPVL_Parameter), given the name "The Container" (but use the symbol PPVL_CONTAINER_NAME), is assembled from all of the PVL parameters that can be read from the specified File. The total number of characters scanned to produce the aggregate parameter is returned through Total_Scanned (if not NULL). Thus Total_Scanned will provide the offset from the position of the file when the function was called to the location of the byte that ended the last PVL parameter statement scan (i.e. the next byte after those that were used by the function). If the function fails to produce an aggregate parameter, then Total_Scanned returns the location of the character that caused the failure. If the file is seekable (a disk file), then it will be repositioned to the location specified by Total_Scanned before the function returns (regardless of whether the function succeeds or not, or if Total_Scanned is NULL or not).

The specified File reference (i.e. a stream opened for input) is used to read characters starting at the file's current position into a dynamically sized sliding window buffer. This buffer is used as the source of The_String to repeatedly pass to the PPVL_scan_parameter function. An internal function is recursively called to assemble any aggregate parameters that are encountered during the file scan.

The scan of the file contents ends when any one of these conditions occurs (only the first is strictly legal):

Sized (a.k.a. variable) record formatted files, in which each record is preceded with a binary count value and possibly padded with a null byte, are handled correctly. Because some files do not provide a PVL End statement (or any other mark for the end of the PVL statements list!), it is possible for file scanning to proceed into non-PVL file data. This can be controlled by a user-provided Read_Limit (see the Examples section below), but a default limit (256 kb) will be used if unlimited file scanning is specified (Read_Limit set to PPVL_NO_READ_LIMIT). It is possible that one or more bogus parameters can thus be appended to the list returned, however this is usually not a problem as they will probably be distinctly outrageous to the application.

On success a pointer to "The Container" aggregate parameter is returned. On failure a NULL is returned. A failure occurs if, and only if, a fatal error occurs during the file scan (see the Error handling section for a list of fatal errors). When sized records are encountered the warning status PPVL_ERROR_SIZED_RECORDS is set in PPVL_errno.


The scan functions scan a character string searching for and collecting their appropriate PVL element. Each function takes an argument called The_String which indicates the starting location of the string to be scanned. And each function will return through the Next_Character argument (if it is not NULL) where the scan left off. The_String is never modified in any way. When the function successfully acquires its PVL element the Next_Character will be the beginning of the next PVL element (or the end of the string). This allows the user to repeatedly call the function to obtain all contiguous PVL elements, setting The_String to the value returned through Next_Character after each call. When no corresponding PVL element is available from The_String the function will return a failure condition and the location in The_String where the scan failed will be returned through the Next_Character argument. Note that for some PVL elements, only one element is expected from The_String (e.g. the datum or units for a value), but the Next_Character still correctly indicates where the scan left off for use by the next PPVL function.

PPVL_scan_aggregate

    PPVL_Parameter *
    PPVL_scan_aggregate
        (
        char            *The_String,
        char            **Next_Character
        );

Just like PPVL_read_aggregate, but operating on a string instead of a file, this function returns an aggregate named "The Container" that has a list of all parameters found in the string.

On success a pointer to "The Container" aggregate parameter is returned. On failure a NULL is returned. A failure occurs if, and only if, a fatal error occurs during The_String scan.

PPVL_scan_parameter

    PPVL_Parameter *
    PPVL_scan_parameter
        (
        char            *The_String,
        char            **Next_Character
        );

A parameter (PPVL_Parameter) is assembled from the contents of The_String. The next character after the the PVL statement is returned through Next_Character (if it is not NULL) on success; the location in The_String where the error occurred otherwise.

After collecting the leading comments and the parameter name, if an equals sign character ('=') is present the remainder of The_String is passed to PPVL_scan_value to obtain the value of the parameter.

When the parameter name is not a quoted string it is checked for reserved characters (from the PVL specification) and a warning (or error in strict mode) is generated if any are found. When the parameter's name is one of the special names - Object, Begin_Object (or BeginObject), End_Object (or EndObject), Group, Begin_Group (or BeginGroup), or End_Group (or EndGroup) - it becomes one of the corresponding aggregate classifications. In this case when the value obtained, if any, is a single string type this string is reassigned as the parameter name, and the remainder of the PPVL_value is freed. When there is no string value, or the value is inappropriate, the parameter retains its special name (and a warning is generated). On success a pointer to the new parameter is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory for the new parameter structure, or any of its components, could not be allocated.
PPVL_ERROR_EMPTY_STATEMENT
The_String is empty (nothing to parse).
PPVL_ERROR_ILLEGAL_SYNTAX
The parameter name is quoted (strict mode only).
PPVL_ERROR_RESERVED_CHARACTER
The unquoted parameter name contains a reserved character or unprintable character.
PPVL_ERROR_GROUP_VALUE
The parameter is an aggregate with a value that is not a single string.

PPVL_scan_value

    PPVL_Value *
    PPVL_scan_value
        (
        char            *The_String,
        char            **Next_Character
        );

A value (PPVL_Value) is assembled from the contents of The_String. The next character after the the PVL statement is returned through Next_Character (if it is not NULL) on success; the location in The_String where the error occurred otherwise.

It is initially assumed that the value being assembled will be an array, so a list of PPVL_scan_datum function along with an empty PPVL_Value structure. If, however, the beginning of an array enclosure (the '(' and ')' characters enclose sequences; the '{' and '}' characters enclose sets) is encountered, then the PPVL_scan_value function is recursively called to assemble the array value.

After each datum has been scanned (whether an array or not) the PPVL_scan_units function is passed the remainder of the The_String in an attempt to obtain a units of measurement string for the value. If a syntax error results it is ignored and the scan for datum symbols continues where it left off.

When there are no more datum symbols, or an end of array enclosure character, end of PVL statement character (';'), or the end of string is encountered the scan ends.

When only one value has been found and it is not inside an array enclosure, the initial array value and its value list are freed and the single value found is returned instead.

On success a pointer to the new value structure is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory for the new value structure, or any of its components, could not be allocated.
PPVL_ERROR_ILLEGAL_SYNTAX
A parameter name delimiter ('='), value delimiter (','), units start or end delimiter ('<', '>'), or number base (radix) delimiter ('#') was encountered when a value datum was expected. These cases are treated as non-recoverable since they are so egregious.
A set or sequence array opening ('{', '(') was encountered when a datum was expected.
PPVL_ERROR_ARRAY_CLOSURE_MISMATCH
The matching set or sequence array closure ('}', ')') is missing.

PPVL_scan_datum

    PPVL_Error_Code
    PPVL_scan_datum
        (
        PPVL_Value      *The_Value,
        char            *The_String,
        char            **Next_Character
        );

The_String is expected to contain a single datum symbol to be converted and assigned as the appropriate data of The_Value.

A datum symbol with enclosing single or double quotation marks is duplicated (without the enclosing marks) as the data.string of The_Value and the data type code is set accordingly. For unadorned symbols an attempt is first made to convert the symbol as an integer (unsigned long) numeric value. An integer may be represented in decimal, hexadecimal (with a leading "0X" or "0x"), or octal (with a leading '0') form. If the entire symbol is not converted and it failed on the special base notation symbol then an attempt is made to convert it assuming base notation:

[Sign]Base#Radix#

Where:

Sign is either '+' or '-'.
Base is an integer (2-36) that is the base of the value.
Radix is an integer value with the specified Base.
For example:
2#101101# is base 2 (binary) for decimal 45.
16#2D# is base 16 (hexadecimal) for the same decimal 45.

When an integer value can not be converted an attempt is made to convert the symbol into a real (double) value. If the entire symbol is not converted in this way it is taken to be an unquoted string. The latter has any embedded escape sequences converted to special format control characters, unless PPVL_verbatim_strings is TRUE, and is then checked for characters that might indicate that it is a date/time string, and if so the data type code is set accordingly but no attempt is made to interpret this string. Unquoted string symbols are also checked for the presence of reserved characters.

This function returns a PPVL_Error_Code. It will be zero (PPVL_SUCCESS) on success; non-zero otherwise. The character immediately following the datum symbol is returned through Next_Character (if not NULL) on success; the location in The_String where the error occurred otherwise. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT The_Value or The_String is NULL.
PPVL_ERROR_EMPTY_STATEMENT
No datum symbol was found.
A datum symbol is missing.
These are non-recoverable errors.
PPVL_ERROR_ILLEGAL_SYNTAX
A parameter or value delimiter; a set, sequence, or units open or close delimiter; or a number base delimiter was found at the beginning of a datum symbol. Instead of tolerantly taking these to be the beginning of a string they are treated as non-recoverable errors.
PPVL_ERROR_VALUE_OVERFLOW
Numeric conversion of the datum string produced an overflow condition (the value will not fit in the available precision).
A base value is out of range (2-36).
PPVL_ERROR_RESERVED_CHARACTER
An unquoted string value contains a reserved character. The presence of an unprintable character always produces a non-recoverable error.

PPVL_scan_quoted_string

    char *
    PPVL_scan_quoted_string
        (
        char            *The_String,
        char            **Next_Character
        );

The first character of The_String is used as the enclosing quotation mark (this is usually a single or double quote character, but it is the caller's responsibility to decide what constitutes a quote mark). Line delimiters and any following white space (leading white space on the next line) are replaced by a single space character. If, however, the last character before the line delimiters is a dash ('-') then the line delimiters and following white space are simply compressed out entirely.

Normally string formatting for appearance when printed is controlled by embedded escape sequence format characters which are converted to special listing format control characters that will appear correctly when written:

\n - line break.
\t - horizontal tab.
\f - form feed (page break).
\\ - backslash character.
\X - the character X.
\v - verbatim (no formatting) till next \v.

The quotation mark may also be included in the quoted string by escaping its normal meaning with a preceding backslash character.

Because some applications format a string value to appear as written (without the use of embedded format characters), the global variable PPVL_verbatim_strings is provided to prevent escape sequence conversion to special characters and override the normal hyphen and leading white space elimination when its value is set to TRUE.

On success a pointer to a copy (in dynamically allocated memory) of the string (without the enclosing quote characters) is returned. On failure a NULL is returned. If The_String is empty (no characters at all) NULL will be returned but PPVL_errno will indicate PPVL_ERORR_NO_ERROR (0). The character immediately following the next occurrence of the quote character is returned through Next_Character (if not NULL) on success; the location in The_String where the error occurred otherwise. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory could not be allocated for a copy of the string.
PPVL_ERROR_MISSING_QUOTE_END
The terminating quote character can not be found. This is a non-recoverable error.

PPVL_scan_units

    char *
    PPVL_scan_units
        (
        char            *The_String,
        char            **Next_Character
        );

The_String is scanned for a units of measure string. This string must be enclosed in angle brackets ('<' and '>'). Leading and trailing white space and comments inside this enclosure are ignored and all other white space sequences are reduced to a single space character.

On success a pointer to a copy (in dynamically allocated memory) of the units string is returned. On failure a NULL is returned. Note that the inability to find a units string enclosure is a syntax error (PPVL_errno is set to PPVL_ERROR_ILLEGAL_SYNTAX). However, this is likely to be an expected outcome when the function is just used in an attempt to see if a units string is present. The character immediately following the units string enclosure is returned through Next_Character (if not NULL) on success; the location in The_String where the error occurred otherwise. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory could not be allocated for a copy of the units string.
PPVL_ERROR_ILLEGAL_SYNTAX
No units start delimiter was found. This is always a warning.
PPVL_ERROR_MISSING_UNITS_END
No units end delimiter was found. This is only an error in strict mode, otherwise the units string is taken to be the single word after the units start delimiter.

PPVL_scan_comments

    char *
    PPVL_scan_comments
        (
        char            *The_String,
        char            **Next_Character
        );

The_String is scanned for C-language style comments. A comment string starts, after any whitespace (and crosshatch comments if not in strict mode; see PPVL_skip_white_space_and_comments), at the character after a forward slash ('/') that is immediately followed by an asterisk ('*'), and ends just before an asterisk that is immediately followed by a forward slash. Sequential comments, with nothing but white space intervening, are accumulated with a single new-line ('\n') character separating them in the resulting string that is returned. In strict mode comments are not allowed to wrap across line breaks. Tolerant interpretation allows this but replaces sequences of line delimiter characters with a single new-line character. Tolerant mode also allows comments to be implicitly terminated at the next line break if normal termination can not be found. On success a pointer to a copy (in dynamically allocated memory) of the comment string is returned. If no comments are found before a non-comment sequence is encountered, the function returns NULL to indicate a failure, but PPVL_errno is set to PPVL_SUCCESS (0). A failure for any other reason will set PPVL_errno to a non-zero error code. The location of the next non-comment string (after any white space) is returned through Next_Character (if not NULL) on success; the location in The_String where the error occurred otherwise. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory could not be allocated for a copy of the comments string.
PPVL_ERROR_MISSING_COMMENT_END
The comment termination sequence was not found. This is an error in strict mode, otherwise the comment is taken to end at the next line break.

PPVL_skip_white_space_and_comments

    char *
    PPVL_skip_white_space_and_comments
        (
        char            *The_String
        );

The_String is scanned for contiguous white space characters and comment strings in search of the next occurrence of a character that is neither whitespace nor the start of a comment. White space characters are the space (' ') and horizontal tab ('\t') characters plus the line delimiter characters - carriage return ('\r'), new line ('\n'), formfeed ('\f'), and vertical tab ('\v'; note that this is a single character not to be confused with the verbatim string formatting character pair). Comment strings are identified in the same manner as in PPVL_scan_comments. As a special case, crosshatch comments - from a '#' following any whitespace up to the end of the line - are also skipped unless strict mode is in effect.

On success a pointer to the next character after any white space or comments is returned (note that this may be a pointer to the end of The_String). On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_String is NULL.
PPVL_ERROR_MISSING_COMMENT_END
The comment termination sequence was not found. This is an error in strict mode, otherwise the comment is taken to end at the next line break.

PPVL_comb_symbol

    char *
    PPVL_comb_symbol
        (
        char            *The_String
        );

The_String is checked for a reserved character or a non-printable character. The reserved characters are:

"{}()[]<>&\"',=;#%~|+! \t\r\n\f\v"

A pointer to the first illegal character found is returned, or NULL if none is found. This function can not produce any error/warning conditions and does not set PPVL_errno to PPVL_SUCCESS.


Object Manipulation Functions

These functions are used to manage the complete life cycle of PPVL_Parameter and PPVL_Value objects.

PPVL_new_parameter

    PPVL_Parameter *
    PPVL_new_parameter
        (
        char            *name,
        PPVL_Class      classification,
        PPVL_Object     *content,
        char            *comments,
        void            *user_data
        );

A new PPVL_Parameter is created in dynamic memory with contents derived from the function arguments. Cross checks for consistency are made and parameter elements that are not specified are filled in, when possible, from implicit information in other arguments. The name argument is the copied as the name element of the parameter. When the name is one of the special names indicating an aggregate parameter, then the parameter classification is set accordingly. Normally the name for an aggregate is its identifier string and the classification of the parameter is determined from the classification argument or derived from the content argument. The classification argument, when not zero, must be for a specific classification (e.g. PPVL_CLASS_GROUP), not a general classification (e.g. PPVL_CLASS_AGGREGATE). The content argument is a pointer to a PPVL_Parameter or PPVL_Value, or NULL. The content is not duplicated, and sharing a parameter or value already associated with another parameter or value, while possible, runs the risk of being "lost" when removed or freed from one context and thus becoming invalid in the other context. It is recommended that content be either new or duplicated objects. Comments are copied into dynamic memory. Embedded new-line characters ('\n') will be interpreted on output as separate sequential comment lines preceding the parameter statement. The user_data is anything the user wants to attach to the parameter. Note that it is user's responsibility to manage the user data. It is possible to create a new empty parameter, but this rarely is useful. On success a pointer to the new parameter structure is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
A classification argument is specified that is not a recognized parameter classification.
This is a warning if comments or user_data is specified but no classification can be determined.
PPVL_ERROR_NO_MEMORY
Dynamic memory could not be allocated for the parameter structure or any of its elements.
PPVL_ERROR_ILLEGAL_SYNTAX
The classification (explicit or implicit) conflicts with the content.

PPVL_new_value

    PPVL_Value *
    PPVL_new_value
        (
        PPVL_Type       type,
        PPVL_Object     *data,
        int             base,
        char            *units
        );

Like PPVL_new_parameter, a new PPVL_Value is created in dynamic memory with contents provided by the function arguments. It is possible to create an empty value structure, but this is rarely useful. The type must be a specific type (e.g. PPVL_TYPE_INTEGER), not a general type (e.g. PPVL_TYPE_NUMERIC). The interpretation of the data argument is based on the type argument:

PPVL_TYPE_INTEGER - (unsigned long *)data
PPVL_TYPE_REAL - (double *)data
Any string type - (char *)data
Any array type - (PPVL_Value *)data

Note: There is a difference in how numeric and other data arguments are treated. For numeric data the data argument is the address of the variable, not the variable value itself. For other string types the data argument is the string pointer variable value (char *), not the address of the string pointer variable. The same applies to array types for which the data arguments are PPVL_Value*, not PPVL_Value** (see the Examples section below). Note that value data for array types should not be shared by multiple arrays (see the discussion for PPVL_new_parameter above).

The base argument is only meaningful for integer data. If the base is not 10 (or zero which is implicitly 10), then radix notation will be used when the value is printed. Note that the base may be negative, in which case the integer value is taken to be negative (this offers one more bit of precision). The units argument string is copied into dynamic memory. On success a pointer to the new value structure is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
A non-zero data, base, or units argument is specified, but the type argument is zero.
The type argument is not a recognized value type.
The base argument is non-zero and its absolute value is not in the range 2-36 inclusive.
PPVL_ERROR_NO_MEMORY
Dynamic memory could not be allocated for the value structure or any of its elements.
PPVL_ERROR_ILLEGAL_SYNTAX
The type is for an array, but the data is not a value object.

PPVL_new

    PPVL_Object *
    PPVL_new
        (
        /*              Parameter        Value
                        ---------        ----- */
        int             Classification,  Type,
        PPVL_Object     *Content,        *Content,
        char            *Name,           (int)Base,
        char            *Comments,       *Units
        void            *User_Data
        );

This macro creates either a PPVL_Parameter using PPVL_new_parameter or a PPVL_Value using PPVL_new_value, depending on the initial Classification/Type argument.

Note: When creating a PPVL_Parameter, the order of arguments is not the same as PPVL_new_parameter. When creating a PPVL_Value the third argument is expected to be an integer.

PPVL_duplicate_parameter

    PPVL_Parameter *
    PPVL_duplicate_parameter
        (
        PPVL_Parameter  *The_Parameter
        );

A new PPVL_Parameter is created in dynamic memory and the contents of The_Parameter are copied into it. If The_Parameter is an aggregate, all of its constituent parameters are duplicated recursively. The user_data element of the parameter is not duplicated, but its value is copied. On success a pointer to the new parameter structure is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Parameter is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory for the new parameter or any of its constituents could not be allocated.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Parameter is not a pointer to a valid PPVL_Parameter structure.

PPVL_duplicate_value

    PPVL_Value *
    PPVL_duplicate_value
        (
        PPVL_Value      *The_Value
        );

Just like PPVL_duplicate_parameter, but for PPVL_Value structures.

PPVL_duplicate

    PPVL_Object *
    PPVL_duplicate
        (
        PPVL_Object     *The_Object
        );

This macro will recognize The_Object as a PPVL_Parameter or a PPVL_Value and use either PPVL_duplicate_parameter or PPVL_duplicate_value, respectively, to create a duplicate.

PPVL_add_parameter

    PPVL_Parameter *
    PPVL_add_parameter
        (
        PPVL_Parameter  *The_Aggregate,
        PPVL_Parameter  *The_Parameter
        );

The_Parameter object is added to the list of parameters for The_Aggregate parameter. If the The_Aggregate is empty, a new parameter list is created. Note: The parameter list is in dynamic memory and adding a new parameter pointer to the list requires the reallocation of its memory. On success The_Aggregate pointer is returned. This allows this function to be an argument to other parameter manipulation functions such as PPVL_new_parameter or other PPVL_add_parameter calls (see the Examples section below). On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Aggregate or The_Parameter is NULL.
PPVL_ERROR_NO_MEMORY
Dynamic memory to create or enlarge the parameter list could not be allocated.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Aggregate is not a pointer to a valid aggregate parameter.
The_Parameter is not a pointer to a valid PPVL_Parameter structure (e.g. an empty parameter structure).

PPVL_add_value

    PPVL_Value *
    PPVL_add_value
        (
        PPVL_Value      *The_Array,
        PPVL_Value      *The_Value
        );

Just like PPVL_add_parameter, but for PPVL_Value structures.

PPVL_add

    PPVL_Object *
    PPVL_add
        (
        PPVL_Object     *The_Container,
        PPVL_Object     *The_Object
        );

This macro will recognize The_Container as an aggregate or an array and The_Object as a parameter or a value and use either PPVL_add_parameter or PPVL_add_value, respectively, to add the later to the former.

PPVL_remove_parameter

    PPVL_Parameter *
    PPVL_remove_parameter
        (
        PPVL_Parameter  *The_Aggregate,
        PPVL_Parameter  *The_Parameter
        );

The_Parameter is found in the parameter list of The_Aggregate, or any aggregates in the parameter list, recursively. The parameter list where The_Parameter is found is shortened so as to remove the specified parameter pointer from the list. The dynamic memory for the removed parameter structure and all its contents are freed. On success The_Aggregate pointer is returned. On failure a NULL is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Aggregate or The_Parameter is NULL.
PPVL_ERROR_NO_MEMORY
The shortened parameter list could not be reallocated.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Aggregate is not a begin aggregate classification parameter.
PPVL_ERROR_EMPTY_STATEMENT
The_Parameter could not be found in any parameter list.

PPVL_remove_value

    PPVL_Value *
    PPVL_remove_value
        (
        PPVL_Value      *The_Array,
        PPVL_Value      *The_Value
        );

Just like PPVL_remove_parameter, but for PPVL_Value structures.

PVL_remove

    PPVL_Object *
    PPVL_remove
        (
        PPVL_Object     *The_Container,
        PPVL_Object     *The_Object
        );

This macro will recognize The_Container as an aggregate or an array and The_Object as a parameter or a value and use either PPVL_remove_parameter or PPVL_remove_value, respectively, to remove the later from the former.

PPVL_free_parameter

    void
    PPVL_free_parameter
        (
        PPVL_Parameter  *The_Parameter
        );

The_Parameter and all its contents are freed. This includes the parameter name and comments strings. If The_Parameter is an aggregate all the parameters on its list are recursively freed as is the list itself.

This function has no return value. It does not modify the PPVL_errno value.

PPVL_free_value

    void
    PPVL_free_value
        (
        PPVL_Value      *The_Value
        );

Just like PPVL_free_parameter, but for PPVL_Value structures. String data and units strings are freed. For array type values all values in the array are recursively freed along with the arrays value list.

PPVL_free

    void
    PPVL_free
        (
        PPVL_Object     *The_Object
        );

This macro will recognize The_Object as a PPVL_Parameter or a PPVL_Value and use either PPVL_free_parameter or PPVL_free_value, respectively, to free the dynamically allocated memory associated with The_Object.


Search Functions

These functions are used to search PPVL_Parameter or PPVL_Value objects for selected elements with specified values.

PPVL_find_parameter

    PPVL_Parameter *
    PPVL_find_parameter
        (
        PPVL_Parameter  *The_Aggregate,
        PPVL_Parameter  *Last_Parameter,
        PPVL_Select     Select,
        PPVL_Object     *The_Selection,
        PPVL_Parameter  **The_Parent
        );

The parameter list of The_Aggregate is first searched, recursively, for the parameter pointer with the Last_Parameter value. This identifies the starting point for The_Selection search which follows. If Last_Parameter is PPVL_SEARCH_FROM_THE_TOP (NULL), then The_Selection search starts with the first parameter in The_Aggregate list. The search process is depth-wise; i.e. whenever an aggregate is encountered in a parameter list it is searched before the remainder of the parameters in the list. This corresponds to the order in which PVL statements appear. When the The_Selection is found a pointer the parameter that matched is returned. A pointer to the parent aggregate that contains the selected parameter in its list is returned through The_Parent, if not NULL, an address of a parameter pointer (PPVL_Parameter **).

Select indicates how The_Selection is to be interpreted:

PPVL_SELECT_ANY (0)

The next parameter after Last_Parameter (or the first parameter if Last_Parameter is PPVL_SEARCH_FROM_THE_TOP) is selected. This allows a parameter hierarchy to be stepped through in the order in which the corresponding PVL statements appear externally. This could be done as follows:

            for (The_Parameter = PPVL_find_parameter
                    (
                    The_Aggregate,
                    PPVL_SEARCH_FROM_THE_TOP,
                    PPVL_SELECT_ANY,
                    NULL,
                    &The_Parent
                    );
                The_Parameter;
                The_Parameter = PPVL_find_parameter
                    (
                    The_Aggregate,
                    The_Parameter,
                    PPVL_SELECT_ANY,
                    NULL,
                    &The_Parent
                    ))
                {
                /* Process The_Parameter...
                */
                }
PPVL_SELECT_NAME (1)

The_Selection is taken to be a string (char *) that is compared with the name element. The_Selection string may be a simple name which will match with the first occurrence of a parameter with the same (but case insensitive) name. It may also be a pathname of the form:

[/]Name[/Name[...]]

A pathname beginning with a forward slash ( '/') is "absolute" in that the first Name must be found in The_Aggregate parameter list before the next Name, delimited by a forward slash, will qualify for a match in a parameter aggregate in this list; etc. This is the same as file pathnames in Unix.

A pathname that does not begin with a forward slash is "relative" in that the first Name is searched for like a simple name and then the remainder of the path is searched for relative to the location of the first parameter found.

PPVL_SELECT_CLASS (2)

The_Selection is taken to be a PPVL_Class code (note that this is the classification code value itself, not a pointer to the value). Both specific and general classification codes are valid. The first occurrence of a parameter having the selected classification will match.

PPVL_SELECT_PARAMETER (3)

The_Selection is taken to be a parameter pointer (PPVL_Parameter *). The parameter with the identical pointer value in a parameter list is selected. This is typically used to traverse back up the parameter hierarchy by searching for the parent parameter returned from a successful search, which returns its parent, until the parent returned is The_Aggregate itself. This could be done as follows:

            The_Parameter = PPVL_find_parameter
                (
                The_Aggregate,
                PPVL_SEARCH_FROM_THE_TOP,
                PPVL_SELECT_NAME,
                "deeply/nested/parameter",
                &The_Parent
                );
            for (PPVL_find_parameter
                    (
                    The_Aggregate,
                    PPVL_SEARCH_FROM_THE_TOP,
                    PPVL_SELECT_PARAMETER,
                    The_Parameter,
                    &The_Parent
                    );
                The_Parent != The_Aggregate;
                PPVL_find_parameter
                    (
                    The_Aggregate,
                    PPVL_SEARCH_FROM_THE_TOP,
                    PPVL_SELECT_PARAMETER,
                    The_Parent,
                    &The_Parent
                    ))
                {
                /* Process The_Parent...
                */
                }
            /* Process The_Aggregate...
            */
PPVL_SELECT_USER_DATA (4)

The_Selection is taken to be a void pointer (void *). The first parameter that has user_data with the identical value is selected.

On success a pointer to the selected parameter is returned. If The_Parent argument is not NULL a pointer to the parent aggregate that has the list where the selected parameter was found is returned there. On failure a NULL is returned. A failure status may be returned with PPVL_errno set to PPVL_SUCCESS(0) which indicates that the Last_Parameter was found, but not The_Selection. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Aggregate is NULL.
Select is not one of the valid values.
The_Selection is NULL and Select is either PPVL_SELECT_NAME or PPVL_SELECT_PARAMETER.
PPVL_ERROR_NO_MEMORY
Dynamic memory for parsable copy of the pathname (thus Select is set to PPVL_SELECT_NAME) could not be allocated.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Aggregate is not a valid begin aggregate parameter.
PPVL_ERROR_EMPTY_STATEMENT
The Last_Parameter could not be found.

PPVL_find_value

    PPVL_Value *
    PPVL_find_value
        (
        PPVL_Value      *The_Array,
        PPVL_Value      *Last_Value,
        PPVL_Type       Type,
        PPVL_Object     *Data,
        PPVL_Value      **The_Parent
        );

The value list of The_Array is first searched, recursively, for the value pointer with the Last_Value value. This identifies the starting point for the Type search which follows. If Last_Value is PPVL_SEARCH_FROM_THE_TOP (NULL), then the Type search starts with the first value in The_Array list. The search process is depth-wise; i.e. whenever an array is encountered in a value list it is searched before the remainder of the values in the list. This corresponds to the order in which values appear in PVL statements. When the the value with the specified Type is found it is checked for a match with the Data argument. When a successful match is made a pointer the value that matched is returned, and, if not NULL, a pointer to the parent array that contains the selected value in its list is returned through The_Parent, an address of a value pointer (PPVL_Value **).

The Type argument may be for a specific numeric type or any (specific or general) string or array type. It may also be the value PPVL_TYPE_ANY (0) in which case the next value, if any, after Last_Value is selected. When the value with the specified Type is found, its data value is compared against the Data argument (this does not occurs when Type is PPVL_TYPE_ANY). If the Data argument is PPVL_SELECT_ANY no data comparison is done (the value, regardless of its data, is selected). Otherwise the Data value is interpreted according the Type code:

PPVL_TYPE_INTEGER - (unsigned long *)Data
PPVL_TYPE_REAL - (double *)Data
Any string type - (char *)Data
Any array type - (PPVL_Value *)Data

Note: There is a difference in how numeric and other Data arguments are treated. For numeric data the Data argument is the address of the variable, not the variable value itself. For other string types the Data argument is the string pointer variable value (char *), not the address of the string pointer variable. The same applies to array types for which the Data arguments are PPVL_Value*, not PPVL_Value**.

On success a pointer to the value with matching Type and Data is returned. If The_Parent is not NULL, a pointer to the array value that contains the matching value in its list is returned there. On failure NULL is returned. A failure status may be returned with PPVL_errno set to PPVL_SUCCESS (0) which indicates that the Last_Value was found but not the value with matching Type and Data. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Array is NULL.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Array is not a valid array type value.
PPVL_ERROR_EMPTY_STATEMENT
The Last_Value could not be found.

PPVL_find

    PPVL_Object *
    PPVL_find
        (
        PPVL_Object     *The_Container,
        PPVL_Object     *Last_Object,
        int             Select,             PPVL_Select or PPVL_Type
        PPVL_Object     *The_Selection,     Parameter selection or value data
        PPVL_Object     *The_Parent
        );
    PPVL_Object *

This macro will recognize The_Container as an aggregate or an array and use either PPVL_find_parameter or PPVL_find_value, respectively, to find The_Selection (known as the Data to PPVL_find_value) based on the Select criteria (known as the Type to PPVL_find_value). Last_Object and The_Parent are used as expected by the find functions.


Output Functions

These functions translate internal PPVL_Parameter or PPVL_Value data structures into external Parameter Value Language statements. The PVL that is generated conforms strictly to the PDS PVL standards documents.

PPVL_write_parameter

    PPVL_Error_Code
    PPVL_write_parameter
        (
        PPVL_Parameter  *The_Parameter,
        FILE            *The_File,
        int             Indent_Level
        );

The contents of The_Parameter are written to The_File (stdout if NULL). If the content of The_Parameter is a value (PPVL_Value *) then PPVL_write_value is used to write its contents. If The_Parameter is an aggregate, then each parameter in its parameter list is recursively written as well. Any comments associated with the parameter are written before the PVL statement proper, also indented. Occurrences of new-line characters ('\n') in the comment string cause separate comment lines to be written. The PVL statement is preceded by Indent_Level horizontal tab characters. Each recursively processed parameter list causes the Indent_Level to be incremented. If, however, Indent_Level is negative no indenting is applied. Parameter names for aggregates are restored to their proper names according to their specific classification, and their internal names are written as the PVL string value identifying the aggregate. Parameter names with any embedded white space are quoted. At the end of each parameter list for an aggregate parameter a corresponding End_Group (or EndGroup) or End_Object (or EndObject) PVL statement is provided. When the parameter is an aggregate named "The Container", as returned from PPVL_read_aggregate and PPVL_scan_aggregate, a PVL End statement is written after the entire contents of the aggregate have been written. Thus writing out one of these aggregates produces a PVL statement list that is formally the same, if not exactly identical, to what was read in. On success a PPVL_Error_Code of PPVL_SUCCESS (0) is returned. On failure (which may be inherited from PPVL_write_value) the value of PPVL_errno is returned (which will be non-zero). A failure condition will also generate a comment line containing the error message describing the error code. Possible error conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Parameter is NULL or its name is NULL.

PPVL_write_value

    PPVL_Error_Code
    PPVL_write_value
        (
        PPVL_Value      *The_Value,
        FILE            *The_File
        );

The contents of The_Value are written to The_File (stdout if NULL). If The_Value is an array type, then each value in its list is recursively written. Proper set or sequence enclosures are provided for arrays, and between writing each parameter in an array list the comma ( ',') separator followed by a single space (' ') is written.

Simple (non-array) values are written according to their type. Integer data is written in signed decimal notation unless the value's base is not 10, in which case radix notation is used:

Base#Radix#

The Base is The_Value's base value as a signed decimal integer. The Radix is The_Value's data value represented in the corresponding base notation. Real data is written using the PPVL_real_number_format string to format the output. By default this is "%#G", but, since control of precision representation varies from system to system, PPVL_real_number_format is a global variable (char *) that may be assigned a different format string by the user. For example, "%#.15E" might be used to provide scientific notation with 15 digits of precision. The format string must be used cautiously, and only to format the appearance of the double precision real number data, or illegal PVL output could result. String data is enclosed in double quotes ('"') if the value is type PPVL_TYPE_TEXT, single quotes ('\'') if the value is type PPVL_TYPE_SYMBOL, and undecorated otherwise. On success a PPVL_Error_Code of PPVL_SUCCESS (0) is returned. On failure the value of PPVL_errno is returned (which will be non-zero). Possible error conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Value is NULL or its type is NULL.
PPVL_ERROR_VALUE_OVERFLOW
The base of an integer type value is not in the range 2-36 inclusive (absolute value).

PPVL_value_string

    char*
    PPVL_value_string
        (
        PPVL_Value      *The_Value
        );

An attempt is made to convert The_Value data to a string representation. The conversion follows the same rules as for the PPVL_write_value function except: no quoting is applied to a String value, only the data is represented (no units), and an Array is not converted.

On success a dynamically allocated string (char*) containing The_Value representation is returned. On failure NULL is returned. Failure occurs when The_Value is an array type, the base of an integer type value is not in the range 2-36 inclusive (absolute value), or no dynamic memory is available.


List Counting Functions

These functions are used to obtain the number of entries in parameter or value lists, or the index to a particular parameter or value in a list.

PPVL_count_all_parameters

    int
    PPVL_count_all_parameters
        (
        PPVL_Parameter  *The_Aggregate
        );

The total number of parameters in The_Aggregate's parameter list, and recursively all aggregate parameters in that list, is returned. This function uses the PPVL_count_parameters function. It does not modify the value of PPVL_errno.

PPVL_count_parameters

    int
    PPVL_count_parameters
        (
        PPVL_Parameter  *The_Aggregate
        );

The number of parameters in The_Aggregate's parameter list, and only that list, is returned.

On success a non-negative count value is returned. On failure a value of -1 is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Aggregate is NULL.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Aggregate is not an aggregate parameter.
PPVL_ERROR_EMPTY_STATEMENT
The_Aggregate has no parameters. This is just a warning. A count of zero is returned.

PPVL_count_all_values

    int
    PPVL_count_all_values
        (
        PPVL_Value      *The_Array
        );

The total number of values in The_Array's value list, and recursively all array values in that list, is returned. This function uses the PPVL_count_values function. It does not modify the value of PPVL_errno.

PPVL_count_values

The number of values in The_Array's value list, and only that list, is returned.

    int
    PPVL_count_values
        (
        PPVL_Value      *The_Array
        );

On success a non-negative count value is returned. On failure a value of -1 is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Array is NULL.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Array is not an array type value. This is just a warning. A count of 1 is returned.
PPVL_ERROR_EMPTY_STATEMENT
The_Array has no values. This is just a warning. A count of 0 is returned.

PPVL_count_all

    int
    PPVL_count_all
        (
        PPVL_Object     *The_Container
        );

This macro will recognize The_Container as an aggregate or an array and use either PPVL_count_all_parameters or PPVL_count_all_values, respectively, to provide a count of all the parameters or values it - or, recursively, any container in it - contains.

PPVL_count

    int
    PPVL_count
        (
        PPVL_Object     *The_Container
        );

This macro will recognize The_Container as an aggregate or an array and use either PPVL_count_parameters or PPVL_count_values, respectively, to provide a count of all the parameters or values it contains.

PPVL_index_parameter

    int
    PPVL_index_parameter
        (
        PPVL_Parameter  *The_Aggregate,
        PPVL_Parameter  *The_Parameter
        );

The_Parameter is located in the parameter list of The_Aggregate and its index in this list is returned. Thus The_Parameter is found at The_Aggregate->content.parameters[index]. Parameter lists are always NULL terminated and so are usually processed with handle (pointer to parameter pointer) references, but there may be a need to use array index references for some applications.

On success a non-negative index value is returned. On failure a value of -1 is returned. Possible error/warning conditions:

PPVL_ERROR_BAD_ARGUMENT
The_Aggregate is NULL.
PPVL_ERROR_ILLEGAL_SYNTAX
The_Aggregate is not an aggregate parameter.
PPVL_ERROR_EMPTY_STATEMENT
The_Parameter was not found in The_Aggregate parameter list.

PPVL_index_value

    int
    PPVL_index_value
        (
        PPVL_Value      *The_Array,
        PPVL_Value      *The_Value
        );

This function works just like PPVL_index_parameter, but searches for The_Value in The_Array.

PPVL_index

    int
    PPVL_index
        (
        PPVL_Object     *The_Container
        PPVL_Object     *The_Object
        );

This macro will recognize The_Container as an aggregate or an array and use either PPVL_index_parameter or PPVL_index_value, respectively, to provide the array index of The_Object in the container's contents list.


Error Handling

Almost every PPVL function sets the global integer variable PPVL_errno. Initially it is set to PPVL_SUCCESS on entering the function. If an error condition is detected, it is set to an appropriate value before the function cleans up and returns an error status value. The condition, however, may be considered to be only a warning (this may depend on PPVL_strict being FALSE) in which case PPVL_errno is set accordingly but no error return is taken (though some recovery action may be taken). Note that only the first warning condition in a function is registered. If an error condition occurs after a warning condition has been encountered the error condition takes precedence over the warning. There are three levels of error codes (four if you count PPVL_SUCCESS, a.k.a PPVL_ERROR_NO_ERROR):

typedef enum    PPVL_error_code
    {
    PPVL_ERROR_NO_ERROR,

    /* Fatal errors:
    */
    PPVL_ERROR_NO_MEMORY,
    PPVL_ERROR_BAD_ARGUMENT,
    PPVL_ERROR_READING_FILE,

    /* Syntax errors:
    */
    PPVL_ERROR_MISSING_QUOTE_END,
    PPVL_ERROR_MISSING_COMMENT_END,
    PPVL_ERROR_MISSING_UNITS_END,
    PPVL_ERROR_RESERVED_CHARACTER,
    PPVL_ERROR_ILLEGAL_SYNTAX,
    PPVL_ERROR_EMPTY_STATEMENT,
    PPVL_ERROR_VALUE_OVERFLOW,
    PPVL_ERROR_ARRAY_CLOSURE_MISMATCH,

    /* Abstract (high level) usage problems:
    */
    PPVL_ERROR_GROUP_VALUE,
    PPVL_ERROR_MISSING_AGGREGATE_END,
    PPVL_ERROR_AGGREGATE_CLOSURE_MISMATCH,
    PPVL_ERROR_SIZED_RECORDS,

    PPVL_ILLEGAL_ERROR_CODE
    }
    PPVL_Error_Code;

Fatal errors are non-recoverable and always result in a failure status value from the function. Syntax errors are usually recoverable and are treated as warnings if PPVL_strict is FALSE. Abstract errors are treated like syntax errors. Every error code has a corresponding brief (one short line) descriptive error message contained in the global PPVL_errmsg strings array. The macro PPVL_ERROR_MESSAGE provides the description string for the current value of PPVL_errno (this is commonly used as a printf-type function argument).

Dynamic memory

The PPVL module uses dynamically allocated memory throughout. All PPVL objects (parameter structures and aggregate lists, value structures and array lists) and any string contents are stored in dynamic memory. Some (actually not much) effort has been made to be efficient in the use of the system's dynamic memory functions. It is unnecessary, and potentially unsafe, to use static or stack memory for PPVL objects. Parameter and value structures, once created (directly or indirectly), will remain stable until freed (only use the PPVL_free functions), but parameter and value lists will may be reallocated whenever a PPVL function feels the need. The use of dynamic memory is not expected to produce any hardship for the application as long as the PPVL functions are used exclusively to manipulate its objects.


Examples

Here is a program that creates a new aggregate parameter containing a few parameters with different types of values:

#include    "PPVL.h"

main ()
{
PPVL_Parameter
    *The_Aggregate;
int
    value;
double
    number;

The_Aggregate =
    PPVL_add_parameter (
    PPVL_add_parameter (
    PPVL_add_parameter (
    PPVL_add_parameter (
        PPVL_new_parameter
            (
            "The_Aggregate",
            PPVL_CLASS_BEGIN_GROUP,
            NULL,
            "**\n** The base aggregate. **\n**",
            NULL
            ),
            PPVL_new_parameter
                (
                "Parameter_0",
                PPVL_CLASS_TOKEN,
                NULL,
                " A token ",
                NULL
                )),
            PPVL_new_parameter
                (
                "Parameter_1",
                PPVL_CLASS_ASSIGNMENT,
                PPVL_new_value
                    (
                    PPVL_TYPE_INTEGER,
                    (value = 1, &value),
                    0,
                    "int decimal"
                    ),
                " A single decimal integer value = 1 ",
                NULL
                )),
            PPVL_new_parameter
                (
                "Parameter_2",
                PPVL_CLASS_ASSIGNMENT,
                PPVL_add_value (
                PPVL_add_value (
                PPVL_add_value (
                    PPVL_new_value
                        (
                        PPVL_TYPE_SET,
                        NULL,
                        0,
                        "set"
                        ),
                        PPVL_new_value
                            (
                            PPVL_TYPE_IDENTIFIER,
                            "IDENTIFIER",
                            0,
                            NULL
                            )),
                        PPVL_new_value
                            (
                            PPVL_TYPE_SYMBOL,
                            "SYMBOL",
                            0,
                            NULL
                            )),
                        PPVL_new_value
                            (
                            PPVL_TYPE_TEXT,
                            "A text value",
                            0,
                            NULL
                            )),
                " An array of three string values. ",
                NULL
                )),
            PPVL_new_parameter
                (
                "Parameter_3",
                PPVL_CLASS_ASSIGNMENT,
                PPVL_new_value
                    (
                    PPVL_TYPE_REAL,
                    (number = 3.3, &number),
                    0,
                    "double"
                    ),
                " A single double number = 3.3 ",
                NULL
                )
        );

PPVL_write_parameter (The_Aggregate, stdout, 0);
exit (0);
}
The program, above, generates the following output:
/****/
/*** The base aggregate. ***/
/****/
BEGIN_GROUP = The_Aggregate;
        /* A token */
        Parameter_0;
        /* A single decimal integer value = 1 */
        Parameter_1 = 1 ;
        /* An array of three string values. */
        Parameter_2 = {IDENTIFIER, 'SYMBOL', "A text value"} <set>;
        /* A single double number = 3.3 */
        Parameter_3 = 3.30000 <double>;
END_GROUP = The_Aggregate;

A common practice is to have a couple of PVL parameters near the beginning of the file label that indicate the record size and number for the label. These are obtained from a little bite of the file, and then used to read the entire label. Here is some sample code (sans error checking for clarity) that demonstrates this procedure:

file = fopen (filename, "r");
The_Aggregate = PPVL_read_aggregate
    (
    file,
    512,
    NULL
    );
The_Parameter = PPVL_find_parameter
    (
    The_Aggregate,
    PPVL_SEARCH_FROM_THE_TOP,
    PPVL_SELECT_NAME,
    "RECORD_BYTES",
    NULL
    );
count = The_Parameter->content.value->data.integer;
The_Parameter = PPVL_find_parameter
    (
    The_Aggregate,
    PPVL_SEARCH_FROM_THE_TOP,
    PPVL_SELECT_NAME,
    "LABEL_RECORDS",
    NULL
    );
count *= The_Parameter->content.value->data.integer;
PPVL_free_parameter (The_Aggregate);
rewind (file);
The_Aggregate = PPVL_read_aggregate (file, count, NULL);
PPVL_write_parameter (The_Aggregate, stdout, 0);

Here's an even simpler example that (should) produce the same result (along with some error checking and processing info):

file = fopen (filename, "r");
if (The_Aggregate = PPVL_read_aggregate
        (
        file,
        PPVL_NO_READ_LIMIT,
        &count
        ))
    {
    printf ("PPVL_read_aggregate scanned %d characters.\n",
        count);
    if (PPVL_errno)
        printf ("Warning: %s\n",
            PPVL_ERROR_MESSAGE);
    PPVL_write_parameter (The_Aggregate, stdout, 0);
    }
else
    fprintf (stderr, "PPVL_read_aggregate failed at character %d: %s\n",
        count, PPVL_ERROR_MESSAGE);