2025-05-10 21:28:42 +08:00

7074 lines
240 KiB
C

/*********************************************************************************************************
* ------------------------------------------------------------------------------------------------------
* file description
* ------------------------------------------------------------------------------------------------------
* \file yaml.c
* \unit yaml
* \brief This is a C language version of yaml streamlined parser
* \author Lamdonn
* \version v0.1.0
* \license GPL-2.0
* \copyright Copyright (C) 2025 Lamdonn.
********************************************************************************************************/
#include "yaml.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <float.h>
// #define DEBUG printf
#define DEBUG(...)
/**
* \brief YAML key type identifiers
* These constants define the type of keys used in YAML nodes.
* The key type determines how the key data is interpreted and stored.
*/
#define YAML_KTYPE_STRING (0) /* String key - standard scalar string */
#define YAML_KTYPE_ANCHOR (1) /* Anchor list key - used for document-level anchors */
#define YAML_KTYPE_COMPLEX (2) /* Complex key - multi-line or structured key */
/**
* \brief Date/time format flags
* These flags control the parsing and formatting of date/time values.
* They can be combined using bitwise OR to specify multiple options.
*/
#define DATEF_TIME (0x01) /* Include time component (HH:MM:SS) */
#define DATEF_MSEC (0x02) /* Include milliseconds component */
#define DATEF_UTC (0x04) /* Use UTC timezone */
/**
* \brief Iterator structure for traversal state tracking
*/
typedef struct
{
void *p; /**< [in] Iterator pointer for tracking traversal position */
unsigned int i; /**< [in] Current iteration index */
} ITERATOR;
/**
* \brief Anchor structure for array storage
*/
typedef struct
{
yaml_t *array; /**< [in] Array address (dynamic array of YAML nodes) */
unsigned int size; /**< [in] Number of elements in array */
unsigned int capacity; /**< [in] Total array capacity */
} ANCHOR;
/**
* \brief Compact date/time representation using bit fields
*
* Implements ISO 8601 date/time storage with UTC offset information
*/
typedef struct
{
unsigned int year : 12; /**< [in] Year value (0-4095) */
unsigned int month : 4 ; /**< [in] Month (1-11) */
unsigned int day : 6 ; /**< [in] Day (1-31) */
unsigned int msec : 10; /**< [in] Millisecond (1-999) */
unsigned int hour : 5 ; /**< [in] Hour (1-23) */
unsigned int minute : 6 ; /**< [in] Minute (1-59) */
unsigned int second : 6 ; /**< [in] Second (1-59) */
unsigned int utchour : 5 ; /**< [in] UTC hour offset (1-23) */
unsigned int utcminute : 6 ; /**< [in] UTC minute offset (1-59) */
unsigned int utcsign : 1 ; /**< [in] UTC sign (0 = positive/+, 1 = negative/-) */
unsigned int flag : 3 ; /**< [in] Date flags (DATEF_XXX values) */
} DATE;
/**
* \brief Fundamental YAML node structure supporting multiple data types
*
* Represents YAML nodes with support for complex structures (sequences/mappings)
* and scalar values through type unions.
*/
typedef struct YAML {
/** \brief [in] Next node pointer for maintaining insertion order */
struct YAML *next;
/** \brief [in] Iterator for traversal state management */
ITERATOR iterator;
/** \brief [in] Alias name (for YAML anchors) */
char* alias;
/** \brief [in] Key type identifier (see YAML data type constants) */
char ktype;
/** \brief [in] Value type identifier (see YAML data type constants) */
char vtype;
/** \brief Key union */
union {
char* key; /**< [in] Simple key (string) */
ANCHOR *anchor; /**< [in] Anchor reference (for aliases) */
struct YAML *complex; /**< [in] Complex key (nested YAML node) */
};
/** \brief Value union */
union {
/* Scalar values */
int bool_; /**< [in] Boolean value (true/false, yes/no) */
double float_; /**< [in] Floating-point number */
int int_; /**< [in] Integer value */
char* string_; /**< [in] String value */
DATE date; /**< [in] ISO 8601 date/time value */
/* Complex structures */
struct YAML* child_; /**< [in] Child node (sequence/mapping/document) */
} value;
/** \brief [in] Size indicator (elements count or string length) */
unsigned int size;
} YAML;
/**
* \brief Pointer to the start of the current line being processed
*/
static const char* lbegin = 0;
/**
* \brief Line number where the error occurred
*/
static int eline = 0;
/**
* \brief Column number (offset from lbegin) where the error occurred
*/
static int ecolumn = 0;
/**
* \brief Error type code (user-defined error categories)
*/
static int etype = 0;
/**
* \brief Tracks nested bracket levels ({/[ counts)
*/
static int rbrace = 0;
/**
* \brief Error handling macro that captures error context and returns
* \param[in] type Error type code (user-defined)
* \param[in] s Value to return from the function
* \return Returns parameter 's' immediately
* \note Sets error location (ecolumn), error type, and includes debug print
* \warning The printf is temporary debug output (remove in production)
*/
#define E(type, s) do { \
etype = (type); /* Set error type */ \
ecolumn = text - lbegin; /* Calculate column offset */ \
printf("at[%d]\r\n", __LINE__); /* Debug: show error line */ \
return (s); /* Exit function */ \
} while (0)
static yaml_t yaml_add_anchor(ANCHOR *anchor, yaml_t yaml);
static yaml_t yaml_match_anchor(ANCHOR *anchor, const char *base, unsigned int length);
static yaml_t yaml_duplicate(yaml_t yaml, int flag, void *para);
/**
* \brief Retrieves YAML parsing error information
* \param[out] line Pointer to store error line number (nullable)
* \param[out] column Pointer to store error column number (nullable)
* \return Error type code (YAML_E_OK indicates no error)
*
* This function retrieves the YAML parsing error information. If there is no error (etype is YAML_E_OK),
* it immediately returns YAML_E_OK. Otherwise, it populates the provided pointers (if they are not NULL)
* with the error line number and error column number. Finally, it returns the error type code (etype).
* The function follows a pattern similar to libyaml's error reporting mechanism, where it provides
* location details of the error (line and column numbers) along with the error type.
*
* Memory management:
* - This function does not allocate or free memory. It only manipulates the provided pointers
* (if they are not NULL) and returns the error type code.
*
* Example usage:
* \code
* int error_line, error_column;
* int error_type = yaml_error_info(&error_line, &error_column);
* if (error_type != YAML_E_OK) {
* printf("Error type: %d, Error line: %d, Error column: %d\n", error_type, error_line, error_column);
* } else {
* printf("No error occurred.\n");
* }
* \endcode
*/
int yaml_error_info(int* line, int* column)
{
/* Early return when no error occurred */
if (etype == YAML_E_OK) return YAML_E_OK;
/* Populate error location through output parameters */
if (line) *line = eline; // Line number assignment
if (column) *column = ecolumn; // Column number assignment
return etype; // Return final error type
}
/**
* \brief Computes the smallest power of two greater than or equal to input
* \param[in] x Positive integer input value
* \return Nearest power of two meeting condition
*
* Implements bitwise optimization for capacity calculation,
* commonly used in dynamic array resizing strategies:ml-citation{ref="7" data="citationList"}.
*/
static int pow2gt(int x)
{
int b = sizeof(int) * 8; // Bit count for integer type
int i = 1;
--x; // Adjust input for boundary cases
/* Bitwise propagation algorithm */
while (i < b) {
x |= (x >> i);
i <<= 1; // Double shift amount each iteration
}
return x + 1; // Final power-of-two adjustment
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Date Calculation Utilities
//////////////////////////////////////////////////////////////////////////////////////////////
/**
* \brief Determines if a given year is a leap year
* \param[in] year Year to check (must be > 0)
* \return 1 if leap year, 0 otherwise
* \note Follows Gregorian calendar rules (valid for years > 1582)
*/
static int date_isleap(int year)
{
return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
}
/**
* \brief Calculates total days in a century period
* \param[in] century Century number (1 = years 1-100)
* \return Total days in century (36524/36525) or 0 for century 0
* \note Implements 400-year cycle of Gregorian calendar
*/
static unsigned int date_century_days(int century)
{
if (century == 0) return 0u; // No days in century 0
return (century % 4 == 0) ? 36525u : 36524u; // 25 leap years in 400 years
}
/**
* \brief Calculates days in a specific year
* \param[in] year Target year (must be > 0)
* \return 365 or 366 (leap year) or 0 for year 0
*/
static unsigned int date_year_days(int year)
{
if (year == 0) return 0u; // No days in year 0
return date_isleap(year) ? 366u : 365u;
}
/**
* \brief Gets number of days in a month
* \param[in] year Year for leap year calculation (must be > 0)
* \param[in] month Target month (1-12)
* \return Days in month (28-31), 0 for invalid input
* \warning Returns 0 for year 0 or invalid month
*/
static unsigned int date_month_days(int year, char month)
{
if (year == 0) return 0; // No valid month days for year 0
switch (month)
{
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31; // Months with 31 days
case 4: case 6: case 9: case 11:
return 30; // Months with 30 days
case 2:
return date_isleap(year) ? 29 : 28; // February days depending on leap year
default:
return 0; // Invalid month
}
}
/**
* \brief Validates date components
* \param[in] year Year component (must be > 0)
* \param[in] month Month component (1-12)
* \param[in] day Day component
* \return 1 if valid date, 0 otherwise
* \note Checks component ranges and month/day compatibility
*/
static int date_isvalid(int year, char month, char day)
{
if (year <= 0 || month <= 0 || day <= 0) return 0; // Year, month or day 0 is not valid
return (date_month_days(year, month) >= day);
}
/**
* \brief Creates a null-terminated copy of a string segment
* \param[in] str Source string buffer
* \param[in] len Length to copy (excluding null-terminator)
* \return Newly allocated string copy, NULL on allocation failure
* \note Safer alternative to strndup() with explicit length handling
*/
static char* yaml_strdup(const char* str, int len)
{
char* s;
/* Allocate memory for the new string */
s = (char*)malloc(len + 1);
if (!s) return NULL;
/* Copy the given string into the allocated memory */
memcpy(s, str, len);
s[len] = 0;
return s;
}
/**
* \brief Initializes a new YAML document structure
* \return Opaque YAML handle, NULL on allocation failure
* \note Creator must eventually call yaml_delete() to free resources
*
* This function allocates and initializes a new YAML document structure.
* It dynamically allocates memory for the YAML structure and zero-initializes
* all its fields to ensure a clean state. The returned handle is opaque and
* should be treated as an interface to the underlying YAML data structure.
*
* Memory management:
* - Allocates memory using malloc(sizeof(YAML))
* - Zero-initializes the allocated memory with memset
* - Returns NULL if memory allocation fails
* - Caller is responsible for freeing the resource with yaml_delete()
*
* Example usage:
* \code
* yaml_t doc = yaml_create();
* if (doc) {
* // Successfully created YAML document handle
* // Proceed to populate or parse YAML content
* yaml_delete(doc); // Free resources when done
* } else {
* // Handle memory allocation failure
* }
* \endcode
*/
yaml_t yaml_create(void)
{
yaml_t yaml;
/* Allocate yaml structure space and initialize */
yaml = (yaml_t)malloc(sizeof(YAML));
if (yaml) memset(yaml, 0, sizeof(YAML)); // Zero-initialize structure
return yaml;
}
/**
* \brief Recursively destroys YAML document structure
* \param[in] yaml Root node of document to destroy
* \warning Handles complex nested structures and reference types safely
*
* This function recursively destroys a YAML document structure, freeing all allocated memory.
* It processes the YAML tree in a depth-first manner, ensuring all child nodes and associated
* resources are properly cleaned up before freeing the parent node. The destruction process
* includes handling different value types (documents, sequences, mappings, strings) and key
* storage strategies (direct strings, anchor references, complex substructures).
*
* Destruction process:
* 1. Iterates through sibling nodes using the 'next' pointer
* 2. Recursively deletes child nodes for container types (document, sequence, mapping)
* 3. Frees memory allocated for string values
* 4. Cleans up different key types:
* - Direct strings: Frees the key string
* - Anchor references: Frees the anchor table and associated array
* - Complex substructures: Recursively deletes the complex key structure
* 5. Frees alias strings if present
* 6. Finally, frees the YAML node itself
*
* Memory management:
* - Frees memory allocated for string values, keys, anchors, aliases, and nodes.
* - Ensures no memory leaks by processing all nested and sibling nodes.
* - Safe to call with NULL pointer (function does nothing in that case).
*
* Example usage:
* \code
* yaml_t root = yaml_create();
* // Populate root with YAML content...
* yaml_delete(root); // Recursively free all resources
* \endcode
*/
void yaml_delete(yaml_t yaml)
{
yaml_t next;
while (yaml)
{
next = yaml->next; // Preserve sibling link before deletion
// Handle container type recursion
if (yaml->vtype == YAML_TYPE_DOCUMENT ||
yaml->vtype == YAML_TYPE_SEQUENCE ||
yaml->vtype == YAML_TYPE_MAPPING)
{
yaml_delete(yaml->value.child_);
}
// Handle string value cleanup
else if (yaml->vtype == YAML_TYPE_STRING)
{
free(yaml->value.string_);
}
/* Free the key of yaml */
if (yaml->key)
{
if (yaml->ktype == YAML_KTYPE_STRING)
{
free(yaml->key);
}
else if (yaml->ktype == YAML_KTYPE_ANCHOR)
{
if (yaml->anchor)
{
if (yaml->anchor->array) free(yaml->anchor->array);
free(yaml->anchor);
}
}
else if (yaml->ktype == YAML_KTYPE_COMPLEX)
{
yaml_delete(yaml->complex);
}
}
if (yaml->alias) free(yaml->alias); // Alias string cleanup
/* Delete self */
free(yaml);
yaml = next; // Process siblings
}
}
/**
* \brief Retrieves node type identifier
* \param[in] yaml Node to inspect
* \return YAML_TYPE_* constant or -1 for invalid input
*
* This function returns the type identifier of a YAML node. It checks if the provided
* YAML node pointer is valid (not NULL). If valid, it returns the node's value type
* (vtype), which is one of the YAML_TYPE_* constants defined in the YAML parser.
* If the input pointer is NULL, it returns -1 to indicate an invalid input.
*
* Memory management:
* - This function does not allocate or free memory. It only reads the vtype field
* of the provided YAML node structure.
*
* Example usage:
* \code
* yaml_t my_node = yaml_create();
* // Assume my_node is properly initialized with a value type
* int node_type = yaml_type(my_node);
* if (node_type != -1) {
* printf("Node type: %d\n", node_type);
* } else {
* printf("Invalid YAML node pointer\n");
* }
* \endcode
*/
int yaml_type(yaml_t yaml)
{
if (!yaml) return -1;
return yaml->vtype;
}
/**
* \brief Gets child count for container nodes
* \param[in] yaml Container node to inspect
* \return Number of child elements, 0 for non-containers/invalid input
*
* This function returns the number of child elements contained within a YAML container node.
* Valid container types include sequences (YAML_TYPE_SEQUENCE) and mappings (YAML_TYPE_MAPPING).
* For non-container types (e.g., strings, scalars) or invalid input (NULL pointer), it returns 0.
*
* Memory management:
* - This function does not allocate or free memory. It only reads the 'size' field of the YAML node.
*
* Example usage:
* \code
* yaml_t sequence_node = yaml_create();
* // Assume sequence_node is initialized as a sequence
* unsigned int child_count = yaml_size(sequence_node);
* printf("Sequence contains %u elements\n", child_count);
* \endcode
*/
unsigned int yaml_size(yaml_t yaml)
{
if (!yaml) return 0;
return yaml->size;
}
//////////////////////////////////////////////////////////////////////////////////////////////
// YAML Anchor and Key Operations
//////////////////////////////////////////////////////////////////////////////////////////////
/**
* \brief Get the number of references to a YAML anchor
* \param[in] yaml: Pointer to YAML node with potential anchor
* \return Number of references if node is an anchor, 0 otherwise
*
* This function checks if the provided YAML node is an anchor
* and returns the number of times it has been referenced via aliases.
* Returns 0 if the node is NULL or not an anchor type.
*
* Memory management:
* - This function does not allocate or free memory. It only reads the 'ktype'
* and 'anchor->size' fields of the YAML node structure.
*
* Example usage:
* \code
* yaml_t anchor_node = yaml_create();
* // Assume anchor_node is properly initialized as an anchor
* unsigned int ref_count = yaml_anchor_size(anchor_node);
* printf("Anchor has %u references\n", ref_count);
* \endcode
*/
unsigned int yaml_anchor_size(yaml_t yaml)
{
// Return 0 if input node is NULL
if (!yaml) return 0;
// Check if node is actually an anchor type
if (yaml->ktype != YAML_KTYPE_ANCHOR) return 0;
// Return the count of references to this anchor
return yaml->anchor->size;
}
/**
* \brief Accesses string-type key value
* \param[in] yaml YAML node handle
* \return Pointer to key string, NULL for invalid inputs
* \warning Only valid when ktype == YAML_KTYPE_STRING
*
* This function retrieves the string key from a YAML node if it is
* of type YAML_KTYPE_STRING. Returns NULL if the input is NULL or
* the node does not have a string key.
*
* Example usage:
* \code
* yaml_t node = ...;
* const char* key = yaml_key(node);
* if (key) {
* printf("Node key: %s\n", key);
* }
* \endcode
*/
const char* yaml_key(yaml_t yaml)
{
// Validate input is not NULL
if (!yaml) return NULL;
// Check if node has a string-type key
if (yaml->ktype != YAML_KTYPE_STRING) return NULL;
// Return the string key
return yaml->key;
}
/**
* \brief Accesses anchor array elements
* \param[in] yaml Anchor-type YAML node
* \param[in] index Array index (0-based)
* \return Child YAML node at specified index, NULL on error
* \note Implements anchor reference resolution mechanism
*
* This function retrieves a child node from an anchor's array of references.
* It ensures the input is a valid anchor node and checks bounds before accessing
* the array. Returns NULL if:
* - Input node is NULL
* - Node is not an anchor type (YAML_KTYPE_ANCHOR)
* - Anchor has no referenced nodes (empty array)
* - Index exceeds the number of referenced nodes
*
* Example usage:
* \code
* yaml_t anchor_node = ...; // Assume this is a valid anchor
* for (unsigned int i = 0; i < yaml_anchor_size(anchor_node); i++) {
* yaml_t child = yaml_get_anchor(anchor_node, i);
* if (child) {
* // Process child node
* }
* }
* \endcode
*/
yaml_t yaml_get_anchor(yaml_t yaml, unsigned int index)
{
ANCHOR *anchor = NULL;
// Validate input node is not NULL and is an anchor type
if (!yaml) return NULL;
if (yaml->ktype != YAML_KTYPE_ANCHOR) return NULL;
// Get the anchor structure from the node
anchor = yaml->anchor;
// Check if anchor has references and index is within bounds
if (!anchor->array || anchor->size == 0) return NULL;
if (index >= anchor->size) return NULL;
// Return the referenced node at specified index
return anchor->array[index];
}
/**
* \brief Accesses complex key substructure
* \param[in] yaml YAML node handle
* \return Pointer to complex key structure, NULL on error
* \note Used for hierarchical key configurations
*
* This function retrieves the complex key substructure from a YAML node
* if it is of type YAML_KTYPE_COMPLEX. Complex keys are used for
* hierarchical or multi-part key configurations, allowing nested structures
* as keys. Returns NULL if:
* - Input node is NULL
* - Node does not contain a complex key
*
* Example usage:
* \code
* yaml_t node = ...; // Assume this node has a complex key
* yaml_t complex_key = yaml_key_complex(node);
* if (complex_key) {
* // Process complex key structure
* }
* \endcode
*/
yaml_t yaml_key_complex(yaml_t yaml)
{
// Validate input node is not NULL
if (!yaml) return NULL;
// Check if node contains a complex key
if (yaml->ktype != YAML_KTYPE_COMPLEX) return NULL;
// Return pointer to complex key structure
return yaml->complex;
}
//////////////////////////////////////////////////////////////////////////////////////////////
// YAML Value Accessors
//////////////////////////////////////////////////////////////////////////////////////////////
/**
* \brief Retrieves boolean value from YAML node
* \param[in] yaml YAML node handle
* \return 1 for true, 0 for false/invalid node
* \note Valid only for YAML_TYPE_BOOL nodes
*
* This function extracts the boolean value from a YAML node if it is
* of type YAML_TYPE_BOOL. Returns 0 (false) if the input is invalid
* or the node does not contain a boolean value.
*
* Example usage:
* \code
* yaml_t node = ...; // Assume this node might contain a boolean
* if (yaml_type(node) == YAML_TYPE_BOOL) {
* int is_true = yaml_value_bool(node);
* printf("Boolean value: %s\n", is_true ? "true" : "false");
* }
* \endcode
*/
int yaml_value_bool(yaml_t yaml)
{
// Validate input node is not NULL and is a boolean type
if (!yaml) return 0;
if (yaml->vtype != YAML_TYPE_BOOL) return 0;
// Return the boolean value (1 for true, 0 for false)
return yaml->value.bool_;
}
/**
* \brief Extracts integer value from YAML node
* \param[in] yaml YAML node handle
* \return Stored integer or 0 for invalid nodes
* \warning Type mismatch returns 0 - verify with yaml_type() first
*
* This function retrieves the integer value from a YAML node if it is
* of type YAML_TYPE_INT. Returns 0 if:
* - Input node is NULL
* - Node does not contain an integer value
*
* Note: A return value of 0 does not necessarily indicate the stored
* value is 0, as it could also signify an invalid node. Always check
* the node type using yaml_type() before calling this function.
*
* Example usage:
* \code
* yaml_t node = ...;
* if (yaml_type(node) == YAML_TYPE_INT) {
* int value = yaml_value_int(node);
* printf("Integer value: %d\n", value);
* } else {
* printf("Node does not contain an integer value\n");
* }
* \endcode
*/
int yaml_value_int(yaml_t yaml)
{
// Validate input node is not NULL and is an integer type
if (!yaml) return 0;
if (yaml->vtype != YAML_TYPE_INT) return 0;
// Return the stored integer value
return yaml->value.int_;
}
/**
* \brief Retrieves floating-point value from YAML node
* \param[in] yaml YAML node handle
* \return Stored double or 0.0 for invalid nodes
* \note Maintains full floating-point precision when valid
*
* This function extracts the double-precision floating-point value from a YAML node
* if it is of type YAML_TYPE_FLOAT. Returns 0.0 if:
* - Input node is NULL
* - Node does not contain a floating-point value
*
* Example usage:
* \code
* yaml_t node = ...;
* if (yaml_type(node) == YAML_TYPE_FLOAT) {
* double value = yaml_value_float(node);
* printf("Float value: %.6f\n", value);
* }
* \endcode
*/
double yaml_value_float(yaml_t yaml)
{
// Validate input node is not NULL and is a float type
if (!yaml) return 0.0;
if (yaml->vtype != YAML_TYPE_FLOAT) return 0.0;
// Return the stored floating-point value with full precision
return yaml->value.float_;
}
/**
* \brief Accesses string value from YAML node
* \param[in] yaml YAML node handle
* \return Pointer to managed string or NULL
* \warning Returned string must not be modified or freed
*
* This function retrieves the string value from a YAML node if it is
* of type YAML_TYPE_STRING. Returns NULL if:
* - Input node is NULL
* - Node does not contain a string value
*
* The returned string is managed by the YAML parser and must not be
* modified or freed by the caller. The pointer remains valid until the
* parent YAML document is destroyed with yaml_document_delete().
*
* Example usage:
* \code
* yaml_t node = ...;
* const char* str = yaml_value_string(node);
* if (str) {
* printf("String value: %s\n", str);
* }
* \endcode
*/
const char* yaml_value_string(yaml_t yaml)
{
// Validate input node is not NULL and is a string type
if (!yaml) return NULL;
if (yaml->vtype != YAML_TYPE_STRING) return NULL;
// Return pointer to the internal string buffer
return yaml->value.string_;
}
/**
* \brief Gets first child of sequence node
* \param[in] yaml Sequence node handle
* \return First child node or NULL
* \note Use with yaml_size() and sibling pointers to traverse
*
* This function retrieves the first child node from a YAML sequence if it is
* of type YAML_TYPE_SEQUENCE. Returns NULL if:
* - Input node is NULL
* - Node does not represent a sequence
* - Sequence is empty (no child nodes)
*
* To traverse all elements in the sequence, use this function to get the first
* child, then follow the sibling pointers (`yaml->next`) and check against the
* size returned by yaml_size().
*
* Example usage:
* \code
* yaml_t seq_node = ...; // Assume this is a valid sequence node
* if (yaml_type(seq_node) == YAML_TYPE_SEQUENCE) {
* yaml_t child = yaml_value_sequence(seq_node);
* for (unsigned int i = 0; child && i < yaml_size(seq_node); i++) {
* // Process child node
* child = child->next;
* }
* }
* \endcode
*/
yaml_t yaml_value_sequence(yaml_t yaml)
{
// Validate input node is not NULL and is a sequence type
if (!yaml) return NULL;
if (yaml->vtype != YAML_TYPE_SEQUENCE) return NULL;
// Return the first child node or NULL if sequence is empty
return yaml->value.child_;
}
/**
* \brief Gets first key-value pair of mapping node
* \param[in] yaml Mapping node handle
* \return First key-value node or NULL
* \note Subsequent pairs accessible via next pointer
*
* This function retrieves the first key-value pair from a YAML mapping
* (dictionary) if it is of type YAML_TYPE_MAPPING. Returns NULL if:
* - Input node is NULL
* - Node does not represent a mapping
* - Mapping is empty (no key-value pairs)
*
* To traverse all key-value pairs in the mapping, use this function to
* get the first pair, then follow the `next` pointers in each returned node.
*
* Example usage:
* \code
* yaml_t map_node = ...; // Assume this is a valid mapping node
* if (yaml_type(map_node) == YAML_TYPE_MAPPING) {
* yaml_t pair = yaml_value_mapping(map_node);
* while (pair) {
* const char* key = yaml_key(pair);
* yaml_t value = yaml_value(pair);
* // Process key-value pair
* pair = pair->next;
* }
* }
* \endcode
*/
yaml_t yaml_value_mapping(yaml_t yaml)
{
// Validate input node is not NULL and is a mapping type
if (!yaml) return NULL;
if (yaml->vtype != YAML_TYPE_MAPPING) return NULL;
// Return the first key-value pair node or NULL if mapping is empty
return yaml->value.child_;
}
/**
* \brief Accesses root node of document
* \param[in] yaml Document node handle
* \return Root content node or NULL
* \note Returned handle remains owned by document
*
* This function retrieves the root node of a YAML document if it is
* of type YAML_TYPE_DOCUMENT. Returns NULL if:
* - Input node is NULL
* - Node does not represent a document
*
* The returned node is owned by the document and should not be freed
* independently. It remains valid until the document is destroyed with
* yaml_document_delete().
*
* Example usage:
* \code
* yaml_t doc = ...; // Assume this is a valid document node
* yaml_t root = yaml_value_document(doc);
* if (root) {
* // Process the root node of the document
* }
* \endcode
*/
yaml_t yaml_value_document(yaml_t yaml)
{
// Validate input node is not NULL and is a document type
if (!yaml) return NULL;
if (yaml->vtype != YAML_TYPE_DOCUMENT) return NULL;
// Return the root node of the document
return yaml->value.child_;
}
/**
* \brief Set a string key with specified length for a YAML node
* \param[in] yaml: Pointer to the YAML node to set key for
* \param[in] key: The string key to be set
* \param[in] len: Length of the key string
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets a string key for a YAML node with the specified length.
* If the key already exists, it will be replaced. If the input key is NULL or
* length is non-positive, the existing key will be cleared.
*
* Memory management:
* - A copy of the key string is made using yaml_strdup()
* - Existing key memory (if any) is properly freed before replacement
* - The new key string is owned by the YAML node
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_key_l(node, "example_key", 12);
* \endcode
*/
static yaml_t yaml_set_key_l(yaml_t yaml, const char* key, int len)
{
char* k;
// Validate input node is not NULL
if (!yaml) return NULL;
/* The current key and the one to be set can be the same, and can be set successfully directly */
if (yaml->key && (yaml->key == key || !strcmp(yaml->key, key))) return yaml;
/* If the passed in key is not empty, duplicate a backup */
if (key && len > 0)
{
k = yaml_strdup(key, len);
if (!k) return NULL;
}
/* Otherwise, clear the yaml key */
else k = NULL;
/* Release the old key to update the new one */
if (yaml->ktype == YAML_KTYPE_STRING)
{
if (yaml->key) free(yaml->key);
}
// Delete complex key structure if it existed
else if (yaml->ktype == YAML_KTYPE_COMPLEX)
{
if (yaml->complex) yaml_delete(yaml->complex);
}
// Assign the new key and set type to string
yaml->key = k;
yaml->ktype = YAML_KTYPE_STRING;
return yaml;
}
/**
* \brief Set a string key for a YAML node (length determined by strlen)
* \param[in] yaml: Pointer to the YAML node to set key for
* \param[in] key: The string key to be set
* \return Pointer to the modified YAML node, or NULL on failure
*/
yaml_t yaml_set_key(yaml_t yaml, const char* key)
{
return yaml_set_key_l(yaml, key, strlen(key));
}
/**
* \brief Set a complex (nested) key for a YAML node
* \param[in] yaml: Pointer to the YAML node to set key for
* \param[in] key: Pointer to the complex YAML node to be used as key
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets a complex (hierarchical) key for a YAML node using another
* YAML node structure. The complex key can be any valid YAML structure (sequence,
* mapping, scalar, etc.).
*
* Key management:
* - The existing key (if any) is properly freed before assignment
* - The passed-in key node is adopted directly (not copied)
* - The complex key node should not be modified or freed externally after this call
*
* Example usage:
* \code
* yaml_t parent = yaml_new();
* yaml_t complex_key = yaml_new();
* yaml_set_value_string(complex_key, "nested_key");
* yaml_set_key_complex(parent, complex_key);
* \endcode
*/
yaml_t yaml_set_key_complex(yaml_t yaml, yaml_t key)
{
// Validate input nodes are not NULL
if (!yaml) return NULL;
if (!key) return NULL;
/* The current key and the one to be set can be the same, and can be set successfully directly */
if (yaml->complex && yaml->complex == key) return yaml;
/* Release the old key to update the new one */
if (yaml->ktype == YAML_KTYPE_STRING)
{
if (yaml->key) free(yaml->key);
}
// Delete complex key structure if it existed
else if (yaml->ktype == YAML_KTYPE_COMPLEX)
{
if (yaml->complex) yaml_delete(yaml->complex);
}
// Assign the new complex key and set type
yaml->complex = key;
yaml->ktype = YAML_KTYPE_COMPLEX;
return yaml;
}
/**
* \brief Set an alias for a YAML node referencing an anchor in a document
* \param[in] yaml: Pointer to the YAML node to set alias for
* \param[in] alias: The alias string to be set
* \param[in] doc: Pointer to the YAML document containing the anchor
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets an alias for a YAML node that references an existing anchor
* within the specified document. Aliases allow referring back to previously defined
* anchor nodes, enabling data reuse and circular references in YAML documents.
*
* Requirements:
* - The target document node (doc) must be either a string node or already an anchor
* - The target document node must not already have an anchor assigned (if string type)
* - The node to set the alias for (yaml) must not already have an alias
*
* Example usage:
* \code
* yaml_t anchor_node = yaml_new();
* yaml_t alias_node = yaml_new();
*
* // Set anchor on document node
* yaml_set_anchor(anchor_node, "my_anchor");
*
* // Create alias referencing the anchor
* yaml_set_alias(alias_node, "my_anchor", anchor_node);
* \endcode
*/
yaml_t yaml_set_alias(yaml_t yaml, const char* alias, yaml_t doc)
{
// Validate input parameters
if (!yaml) return NULL;
if (yaml->alias) return NULL; // Node already has an alias
if (!alias) return NULL; // Alias string is NULL
if (!doc) return NULL; // Document node is NULL
// Ensure the document node is either a string or already an anchor
if (doc->ktype == YAML_KTYPE_STRING)
{
// Create new anchor if it doesn't exist
if (!doc->anchor)
{
doc->anchor = (ANCHOR *)malloc(sizeof(ANCHOR));
if (!doc->anchor) return NULL;
memset(doc->anchor, 0, sizeof(ANCHOR));
doc->ktype = YAML_KTYPE_ANCHOR;
}
else
{
return NULL; // Anchor already exists
}
}
else if (doc->ktype == YAML_KTYPE_ANCHOR)
{
// Existing anchor - proceed to set alias
}
else return NULL; // Invalid document node type
// Duplicate and assign the alias string
yaml->alias = yaml_strdup(alias, strlen(alias));
if (!yaml->alias) return NULL;
// Add this node to the anchor's reference list
if (!yaml_add_anchor(doc->anchor, yaml))
{
free(yaml->alias);
yaml->alias = NULL;
return NULL;
}
return yaml;
}
/**
* \brief Retrieve the alias of a YAML node
* \param[in] yaml: Pointer to the YAML node
* \return The alias string if exists, otherwise NULL
*
* This function retrieves the alias string of a YAML node if it has been
* previously set using yaml_set_alias(). The alias is used to reference
* an anchor node within the same document.
*
* Example usage:
* \code
* yaml_t node = ...; // Node with an alias set
* const char* alias = yaml_get_alias(node);
* if (alias) {
* printf("Node alias: %s\n", alias);
* }
* \endcode
*/
const char* yaml_get_alias(yaml_t yaml)
{
// Validate input node is not NULL
if (!yaml) return NULL;
// Return the alias string or NULL if not set
return yaml->alias;
}
/**
* \brief Set an anchor reference for a YAML node
* \param[in] yaml: Pointer to the YAML node to set anchor reference for
* \param[in] anchor: The anchor string to reference
* \param[in] doc: Pointer to the YAML document containing the anchor
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets a reference from the specified YAML node to an existing
* anchor within the document. Anchors allow duplicate data to be represented
* as references, reducing redundancy in YAML documents.
*
* Requirements:
* - The target document node (doc) must be of type YAML_KTYPE_ANCHOR
* - The specified anchor string must exist in the document
* - The node to set the reference for (yaml) must not already have an alias
*
* Example usage:
* \code
* // Create anchor node
* yaml_t anchor_node = yaml_new();
* yaml_set_key_string(anchor_node, "my_anchor");
*
* // Create referencing node
* yaml_t ref_node = yaml_new();
* yaml_set_anchor(ref_node, "my_anchor", anchor_node);
* \endcode
*/
yaml_t yaml_set_anchor(yaml_t yaml, const char* anchor, yaml_t doc)
{
yaml_t ref = NULL;
// Validate input parameters
if (!yaml) return NULL;
if (yaml->alias) return NULL; // Node already has an alias
if (!anchor) return NULL; // Anchor string is NULL
if (!doc) return NULL; // Document node is NULL
// Ensure document node is an anchor type
if (doc->ktype != YAML_KTYPE_ANCHOR) return NULL;
// Find the referenced anchor in the document
ref = yaml_match_anchor(doc->anchor, anchor, strlen(anchor));
if (!ref) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_REFERENCE) yaml_set_null(yaml);
/* Change the type to object */
yaml->vtype = YAML_TYPE_REFERENCE;
/* The current object and the one to be set can be the same, and can be set successfully directly */
if (yaml->value.child_ && yaml->value.child_ == ref) return yaml;
/* Release the old object to update the new one */
if (yaml->value.child_) yaml_delete(yaml->value.child_);
yaml->value.child_ = ref;
return yaml;
}
/**
* \brief Set a YAML node to null type
* \param[in] yaml: Pointer to the YAML node to set as null
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to the null type. It cleans up the existing
* value of the node depending on its current type (string, sequence, or mapping)
* and then sets the node's type to YAML_TYPE_NULL and clears its value.
*
* Memory management:
* - For string type nodes, it frees the string pointer.
* - For sequence and mapping type nodes, it deletes the child nodes.
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* // Assume node has some value initially
* yaml_set_null(node);
* \endcode
*/
yaml_t yaml_set_null(yaml_t yaml)
{
if (!yaml) return NULL;
/* delete string value */
if (yaml->vtype == YAML_TYPE_STRING) free(yaml->value.string_);
/* delete child objects */
else if (yaml->vtype == YAML_TYPE_SEQUENCE) yaml_delete(yaml->value.child_);
else if (yaml->vtype == YAML_TYPE_MAPPING) yaml_delete(yaml->value.child_);
/* Change the type to null and reset the value */
yaml->vtype = YAML_TYPE_NULL;
memset(&yaml->value, 0, sizeof(yaml->value));
// Return the modified node
return yaml;
}
/**
* \brief Set a boolean value for a YAML node
* \param[in] yaml: Pointer to the YAML node to set boolean value for
* \param[in] b: Boolean value (YAML_TRUE or YAML_FALSE)
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a boolean type with the specified value.
* Any existing value is cleared and the node's type is set to YAML_TYPE_BOOL.
*
* The input boolean value is normalized to ensure only valid boolean states are stored.
* Specifically:
* - YAML_FALSE (0) is preserved as YAML_FALSE
* - Any non-zero value is treated as YAML_TRUE (1)
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_bool(node, YAML_TRUE);
* \endcode
*/
yaml_t yaml_set_bool(yaml_t yaml, int b)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_BOOL) yaml_set_null(yaml);
/* Change the type to bool and set the bool value */
yaml->vtype = YAML_TYPE_BOOL;
yaml->value.bool_ = (b == YAML_FALSE ? YAML_FALSE : YAML_TRUE);
return yaml;
}
/**
* \brief Set an integer value for a YAML node
* \param[in] yaml: Pointer to the YAML node to set integer value for
* \param[in] num: Integer value to set
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to an integer type with the specified value.
* Any existing value is cleared and the node's type is set to YAML_TYPE_INT.
*
* Note: The original code had a logical error in the type check condition.
* The corrected version properly resets the node if it's not already an INT or FLOAT.
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_int(node, 42);
* \endcode
*/
yaml_t yaml_set_int(yaml_t yaml, int num)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_INT || yaml->vtype != YAML_TYPE_FLOAT) yaml_set_null(yaml);
/* Change the type to float and set the float value */
yaml->vtype = YAML_TYPE_INT;
yaml->value.int_ = num;
return yaml;
}
/**
* \brief Set a float value for a YAML node
* \param[in] yaml: Pointer to the YAML node to set float value for
* \param[in] num: Double-precision float value to set
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a float type with the specified value.
* Any existing value is cleared and the node's type is set to YAML_TYPE_FLOAT.
*
* Note: The original code had a logical error in the type check condition.
* The corrected version properly resets the node if it's not already an INT or FLOAT.
* This maintains compatibility with numeric type conversions.
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_float(node, 3.14159);
* \endcode
*/
yaml_t yaml_set_float(yaml_t yaml, double num)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_INT || yaml->vtype != YAML_TYPE_FLOAT) yaml_set_null(yaml);
/* Change the type to float and set the float value */
yaml->vtype = YAML_TYPE_FLOAT;
yaml->value.float_ = num;
return yaml;
}
/**
* \brief Set a string value for a YAML node
* \param[in] yaml: Pointer to the YAML node to set string value for
* \param[in] string: String value to set
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a string type with the specified value.
* The input string is copied to ensure proper memory management.
*
* Memory management:
* - The existing string (if any) is freed before assigning the new value
* - A copy of the input string is created using yaml_strdup()
* - The new string is owned by the YAML node
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_string(node, "Hello, World!");
* \endcode
*/
yaml_t yaml_set_string(yaml_t yaml, const char* string)
{
char* s;
int len = 0;
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_STRING) yaml_set_null(yaml);
/* Change the type to string */
yaml->vtype = YAML_TYPE_STRING;
/* The current string and the one to be set can be the same, and can be set successfully directly */
if (yaml->value.string_ && (yaml->value.string_ == string || !strcmp(yaml->value.string_, string))) return yaml;
/* If the passed in string is not empty, duplicate a backup */
if (string)
{
len = strlen(string);
s = yaml_strdup(string, strlen(string));
if (!s) return NULL;
}
/* Otherwise, clear the yaml string */
else s = NULL;
/* Release the old string to update the new one */
if (yaml->value.string_) free(yaml->value.string_);
yaml->value.string_ = s;
// Update the size field with the string length
yaml->size = len;
return yaml;
}
/**
* \brief Set a date value for a YAML node
* \param[in] yaml: Pointer to the YAML node to set date value for
* \param[in] year: Year component (0-4095)
* \param[in] month: Month component (1-12)
* \param[in] day: Day component (1-31)
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a date type with the specified
* year, month, and day components. The date is validated to ensure it forms
* a valid calendar date before assignment.
*
* Validation checks:
* - Year must be within 0-4095 range
* - Month must be within 1-12 range
* - Day must be valid for the given month and year (accounts for leap years)
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_date(node, 2023, 10, 15); // October 15, 2023
* \endcode
*/
yaml_t yaml_set_date(yaml_t yaml, int year, char month, char day)
{
// Validate input node is not NULL
if (!yaml) return NULL;
// Validate date components
if (year < 0 || year > 4095) return NULL;
if (!date_isvalid(year, month, day)) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_DATE) yaml_set_null(yaml);
/* Change the type to string */
yaml->vtype = YAML_TYPE_DATE;
yaml->value.date.year = year;
yaml->value.date.month = month;
yaml->value.date.day = day;
return yaml;
}
/**
* \brief Set time components for a YAML date node
* \param[in] yaml: Pointer to the YAML date node to set time for
* \param[in] hour: Hour component (0-23)
* \param[in] minute: Minute component (0-59)
* \param[in] second: Second component (0-59)
* \param[in] msec: Millisecond component (0-999)
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the time components (hour, minute, second, millisecond)
* for an existing YAML date node. The node must already be of type YAML_TYPE_DATE
* (typically set via yaml_set_date()).
*
* Validation checks:
* - Hour must be within 0-23 range
* - Minute must be within 0-59 range
* - Second must be within 0-59 range
* - Millisecond must be within 0-999 range
*
* Flags set:
* - DATEF_TIME: Indicates time components are present
* - DATEF_MSEC: Indicates millisecond component is present (if msec > 0)
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_date(node, 2023, 10, 15); // Set date first
* yaml_set_time(node, 14, 30, 0, 500); // Set time to 14:30:00.500
* \endcode
*/
yaml_t yaml_set_time(yaml_t yaml, char hour, char minute, char second, int msec)
{
// Validate input node is not NULL
if (!yaml) return NULL;
// Ensure node is a date type
if (yaml->vtype != YAML_TYPE_DATE) return NULL;
// Validate time components
if (hour < 0 || hour >= 24) return NULL;
if (minute < 0 || minute >= 60) return NULL;
if (second < 0 || second >= 60) return NULL;
if (msec < 0 || msec >= 1000) return NULL;
// Assign time components and set flags
yaml->value.date.hour = hour;
yaml->value.date.minute = minute;
yaml->value.date.second = second;
yaml->value.date.msec = msec;
// Set time present flag
yaml->value.date.flag |= DATEF_TIME;
// Set millisecond present flag if applicable
if (msec > 0) yaml->value.date.flag |= DATEF_MSEC;
return yaml;
}
/**
* \brief Set UTC offset for a YAML date node
* \param[in] yaml: Pointer to the YAML date node to set UTC offset for
* \param[in] hour: UTC hour offset (-12 to 12)
* \param[in] minute: UTC minute offset (0-59)
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the UTC offset components (hour and minute) for an existing
* YAML date node. The node must already be of type YAML_TYPE_DATE (typically set
* via yaml_set_date()). The offset is stored as a positive value with a sign flag.
*
* Validation checks:
* - Hour must be within -12 to 12 range
* - Minute must be within 0-59 range
*
* Storage format:
* - Negative offsets are stored with utcsign = 1 and absolute value of hour
* - Positive offsets are stored with utcsign = 0
*
* Example usage:
* \code
* yaml_t node = yaml_new();
* yaml_set_date(node, 2023, 10, 15); // Set date first
* yaml_set_time(node, 14, 30, 0, 0); // Set time
* yaml_set_utc(node, -5, 0); // Set UTC offset to -05:00 (EST)
* \endcode
*/
yaml_t yaml_set_utc(yaml_t yaml, char hour, char minute)
{
int utcsign = 0;
// Validate input node is not NULL
if (!yaml) return NULL;
// Ensure node is a date type
if (yaml->vtype != YAML_TYPE_DATE) return NULL;
// Handle negative offset by storing sign separately
if (hour < 0)
{
utcsign = 1;
hour = -hour; // Store absolute value
}
// Validate UTC components
if (hour > 12) return NULL;
if (minute < 0 || minute >= 60) return NULL;
// Assign UTC components and set flag
yaml->value.date.utchour = hour;
yaml->value.date.utcminute = minute;
yaml->value.date.utcsign = utcsign;
yaml->value.date.flag |= DATEF_UTC;
return yaml;
}
/**
* \brief Set a sequence (array) for a YAML node
* \param[in] yaml: Pointer to the YAML node to set sequence for
* \param[in] sequence: Pointer to the sequence YAML node
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a sequence (array) type with the specified
* sequence node as its content. The sequence node should be a valid YAML sequence structure.
*
* Memory management:
* - The existing sequence (if any) is deleted before assigning the new value
* - The passed sequence node is adopted directly (not copied)
* - The sequence node should not be modified or freed externally after this call
*
* Example usage:
* \code
* // Create parent node
* yaml_t parent = yaml_new();
*
* // Create sequence node
* yaml_t sequence = yaml_new();
* yaml_set_sequence(sequence, NULL); // Initialize as empty sequence
*
* // Add elements to sequence...
*
* // Set the sequence to the parent node
* yaml_set_sequence(parent, sequence);
* \endcode
*/
yaml_t yaml_set_sequence(yaml_t yaml, yaml_t sequence)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_SEQUENCE) yaml_set_null(yaml);
/* Change the type to object */
yaml->vtype = YAML_TYPE_SEQUENCE;
/* The current object and the one to be set can be the same, and can be set successfully directly */
if (yaml->value.child_ && yaml->value.child_ == sequence) return yaml;
/* Release the old object to update the new one */
if (yaml->value.child_) yaml_delete(yaml->value.child_);
yaml->value.child_ = sequence;
return yaml;
}
/**
* \brief Set a mapping (key-value pairs) for a YAML node
* \param[in] yaml: Pointer to the YAML node to set mapping for
* \param[in] mapping: Pointer to the mapping YAML node
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a mapping (dictionary) type with the specified
* mapping node as its content. The mapping node should be a valid YAML mapping structure.
*
* Memory management:
* - The existing mapping (if any) is deleted before assigning the new value
* - The passed mapping node is adopted directly (not copied)
* - The mapping node should not be modified or freed externally after this call
*
* Example usage:
* \code
* // Create parent node
* yaml_t parent = yaml_new();
*
* // Create mapping node
* yaml_t mapping = yaml_new();
* yaml_set_mapping(mapping, NULL); // Initialize as empty mapping
*
* // Add key-value pairs to mapping...
*
* // Set the mapping to the parent node
* yaml_set_mapping(parent, mapping);
* \endcode
*/
yaml_t yaml_set_mapping(yaml_t yaml, yaml_t mapping)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_MAPPING) yaml_set_null(yaml);
/* Change the type to object */
yaml->vtype = YAML_TYPE_MAPPING;
/* The current object and the one to be set can be the same, and can be set successfully directly */
if (yaml->value.child_ && yaml->value.child_ == mapping) return yaml;
/* Release the old object to update the new one */
if (yaml->value.child_) yaml_delete(yaml->value.child_);
yaml->value.child_ = mapping;
return yaml;
}
/**
* \brief Set a document structure for a YAML node
* \param[in] yaml: Pointer to the YAML node to set document for
* \param[in] document: Pointer to the document YAML node
* \return Pointer to the modified YAML node, or NULL on failure
*
* This function sets the given YAML node to a document type with the specified
* document node as its content. The document node should be a valid YAML document
* structure containing the root element of the YAML hierarchy.
*
* Memory management:
* - The existing document (if any) is deleted before assigning the new value
* - The passed document node is adopted directly (not copied)
* - The document node should not be modified or freed externally after this call
*
* Example usage:
* \code
* // Create parent node
* yaml_t parent = yaml_new();
*
* // Create document node
* yaml_t document = yaml_new();
* yaml_set_document(document, NULL); // Initialize as empty document
*
* // Set root element of document...
*
* // Set the document to the parent node
* yaml_set_document(parent, document);
* \endcode
*/
yaml_t yaml_set_document(yaml_t yaml, yaml_t document)
{
// Validate input node is not NULL
if (!yaml) return NULL;
/* If the current type does not match, set the type to null first */
if (yaml->vtype != YAML_TYPE_DOCUMENT) yaml_set_null(yaml);
/* Change the type to object */
yaml->vtype = YAML_TYPE_DOCUMENT;
/* The current object and the one to be set can be the same, and can be set successfully directly */
if (yaml->value.child_ && yaml->value.child_ == document) return yaml;
/* Release the old object to update the new one */
if (yaml->value.child_) yaml_delete(yaml->value.child_);
yaml->value.child_ = document;
return yaml;
}
/**
* \brief Move the internal iterator of a YAML node to a specified index
* \param[in] yaml: Pointer to the YAML node (sequence, mapping, or document)
* \param[in] index: Target index to move the iterator to
* \return Pointer to the YAML node at the specified index, or NULL on failure
*
* This function moves the internal iterator of a sequence, mapping, or document
* node to the specified index position. The iterator allows sequential access to
* child nodes without exposing the underlying linked list structure.
*
* Iterator behavior:
* - For sequences: Moves to the element at the given 0-based index
* - For mappings: Moves to the key-value pair at the given 0-based index
* - For documents: Moves to the document child at the given 0-based index
*
* Performance:
* - If the target index is before the current iterator position, the iterator
* is reset and traversed from the start
* - If the target index is after the current position, the iterator advances
* from its current position
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* // Assume seq is a valid sequence with elements
*
* yaml_t element = yaml_iterator_to(seq, 2); // Move to 3rd element
* if (element) {
* // Process element
* }
* \endcode
*/
static yaml_t yaml_iterator_to(yaml_t yaml, unsigned int index)
{
// Validate node type and index range
if (yaml->vtype != YAML_TYPE_SEQUENCE &&
yaml->vtype != YAML_TYPE_MAPPING &&
yaml->vtype != YAML_TYPE_DOCUMENT) return NULL;
if (index >= yaml->size) return NULL;
// Reset iterator if current position is after target or invalid
if (index < yaml->iterator.i || !yaml->iterator.p || index == 0)
{
yaml->iterator.i = 0;
yaml->iterator.p = yaml->value.child_;
}
// Traverse to target index from current iterator position
while (yaml->iterator.p && yaml->iterator.i < index)
{
yaml->iterator.p = ((yaml_t)(yaml->iterator.p))->next;
yaml->iterator.i++;
}
return yaml->iterator.p;
}
/**
* \brief Attach a YAML node (or sequence of nodes) to a parent node at specified index
* \param[in] yaml: Pointer to the parent YAML node (sequence, mapping, or document)
* \param[in] index: Position to insert the new node(s)
* \param[in] attach: Pointer to the YAML node to attach (can be head of a linked list)
* \return Pointer to the attached YAML node, or NULL on failure
*
* This function attaches a YAML node (or a sequence of linked nodes) to a parent node
* at the specified index. The parent must be a sequence, mapping, or document node.
*
* Key behaviors:
* - For sequences: Attaches nodes as elements (key should be NULL)
* - For mappings: Attaches nodes as key-value pairs (key must be set)
* - For documents: Attaches anchored nodes (anchor must be set)
*
* Memory management:
* - The attached node(s) are not copied; ownership is transferred to the parent
* - The attached nodes should not be modified or freed externally after this call
*
* Example usage:
* \code
* // Create a sequence and two elements
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
*
* yaml_t elem1 = yaml_new();
* yaml_set_string(elem1, "value1");
*
* yaml_t elem2 = yaml_new();
* yaml_set_string(elem2, "value2");
*
* // Link elements into a list
* elem1->next = elem2;
*
* // Attach both elements at index 0
* yaml_attach(seq, 0, elem1);
* \endcode
*/
yaml_t yaml_attach(yaml_t yaml, unsigned int index, yaml_t attach)
{
yaml_t prev = NULL;
yaml_t tail = attach;
unsigned int size = 1;
// Validate input parameters
if (!yaml) return NULL;
if (!attach) return NULL;
// Check if attach node type matches parent container type
if (!(yaml->vtype == YAML_TYPE_SEQUENCE && !attach->key) &&
!(yaml->vtype == YAML_TYPE_MAPPING && attach->key) &&
!(yaml->vtype == YAML_TYPE_DOCUMENT && attach->anchor))
return NULL;
// Calculate size of the list to attach
while (tail->next)
{
tail = tail->next;
size++;
}
// Insert at beginning
if (index == 0)
{
tail->next = yaml->value.child_;
yaml->value.child_ = attach;
}
else
{
// Find previous node
prev = yaml_iterator_to(yaml, index - 1);
if (!prev) return NULL;
// Insert after previous node
tail->next = prev->next;
prev->next = attach;
}
// Update parent size
yaml->size += size;
return attach;
}
/**
* \brief Detach a YAML node from its parent at specified index
* \param[in] yaml: Pointer to the parent YAML node (sequence, mapping, or document)
* \param[in] index: Index of the node to detach
* \return Pointer to the detached YAML node, or NULL on failure
*
* This function detaches a YAML node from its parent container (sequence, mapping,
* or document) at the specified index. The detached node is returned with its next
* pointer set to NULL, effectively isolating it from the original list.
*
* Memory management:
* - The detached node is not freed; ownership is transferred to the caller
* - The caller is responsible for freeing the detached node or reattaching it
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* // Assume seq is a valid sequence with elements
*
* yaml_t detached = yaml_dettach(seq, 1); // Detach 2nd element
* if (detached) {
* // Process detached node
* yaml_delete(detached); // Free the detached node
* }
* \endcode
*/
yaml_t yaml_dettach(yaml_t yaml, unsigned int index)
{
yaml_t dettach = NULL, prev = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Check if parent is a valid container type
if (yaml->vtype != YAML_TYPE_SEQUENCE &&
yaml->vtype != YAML_TYPE_MAPPING &&
yaml->vtype != YAML_TYPE_DOCUMENT)
return NULL;
// Check if container is not empty
if (yaml->size == 0) return NULL;
// Detach first node
if (index == 0)
{
dettach = yaml->value.child_;
yaml->value.child_ = dettach->next;
}
else
{
// Check index validity
if (index >= yaml->size) return NULL;
// Find previous node
prev = yaml_iterator_to(yaml, index - 1);
if (!prev) return NULL;
// Detach node from list
dettach = prev->next;
prev->next = dettach->next;
}
// Ensure detached node is isolated
dettach->next = NULL;
// Update parent size
yaml->size--;
return dettach;
}
/**
* \brief Macro to validate and adjust YAML node type based on key presence
* This macro checks if the given YAML node can accept the insertion type
* based on its current type and the presence of a key:
* - If NULL, sets type to SEQUENCE (if key is NULL) or MAPPING (if key exists)
* - If SEQUENCE, ensures key is NULL
* - If MAPPING, ensures key exists
* - If DOCUMENT, allows insertion (with special handling in document functions)
* - Returns NULL for unsupported types
*/
#define CHECK_INSERT_TYPE() \
if (yaml->vtype == YAML_TYPE_NULL) \
{ \
if (key) yaml->vtype = YAML_TYPE_MAPPING; \
else yaml->vtype = YAML_TYPE_SEQUENCE; \
} \
else if (yaml->vtype == YAML_TYPE_SEQUENCE) \
{ \
if (key) return NULL; \
} \
else if (yaml->vtype == YAML_TYPE_MAPPING) \
{ \
if (!key) return NULL; \
} \
else if (yaml->vtype == YAML_TYPE_DOCUMENT) \
{ \
} \
else return NULL; \
/**
* \brief Insert a null value into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML node with a null value into a parent sequence,
* mapping, or document at the specified index. The new node is initialized as a
* null type and can optionally be assigned a key (for mappings).
*
* Key behaviors:
* - For sequences: key should be NULL, index specifies position in list
* - For mappings: key is required, index specifies position in key order
* - For documents: key is ignored, index specifies position in document
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
*
* // Insert null element at index 0
* yaml_insert_null(seq, NULL, 0);
*
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
*
* // Insert null key-value pair at index 1
* yaml_insert_null(map, "null_key", 1);
* \endcode
*/
yaml_t yaml_insert_null(yaml_t yaml, const char* key, unsigned int index)
{
yaml_t insert = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Check if parent is a valid container type
CHECK_INSERT_TYPE();
// Create new YAML node
insert = yaml_create();
if (!insert) return NULL;
// Set key if provided (for mappings)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Attach the new node to the parent at specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up on failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a boolean value into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] b: Boolean value to set (YAML_TRUE/YAML_FALSE)
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML node with a boolean value into a parent sequence,
* mapping, or document at the specified index. The new node is created, assigned a key
* (if applicable for mappings), set to the provided boolean value, and then attached
* to the parent node at the given index.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new node with boolean value is inserted
* at the specified index in the sequence.
* - For mappings: a non-NULL key is required, and the new key-value pair with the
* boolean value is inserted at the specified index in the mapping.
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
* // Insert boolean element (true) at index 0 in the sequence
* yaml_insert_bool(seq, NULL, 0, YAML_TRUE);
*
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
* // Insert key-value pair with boolean value (false) at index 1 in the mapping
* yaml_insert_bool(map, "bool_key", 1, YAML_FALSE);
* \endcode
*/
yaml_t yaml_insert_bool(yaml_t yaml, const char* key, unsigned int index, int b)
{
yaml_t insert = NULL;
// Validate input node
if (!yaml) return NULL;
// Assume CHECK_INSERT_TYPE() validates the node type for insertion
CHECK_INSERT_TYPE();
// Create a new YAML node
insert = yaml_create();
if (!insert) return NULL;
// Set the key for the new node if it's for a mapping (key should be NULL for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the boolean value for the new node
yaml_set_bool(insert, b);
// Attach the new node to the parent at the specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up the new node in case of failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert an integer value into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] num: Integer value to set
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML node with an integer value into a parent sequence,
* mapping, or document at the specified index. The new node is created, assigned a key
* (if applicable for mappings), set to the provided integer value, and then attached
* to the parent node at the given index.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new node with integer value is inserted
* at the specified index in the sequence.
* - For mappings: a non-NULL key is required, and the new key-value pair with the
* integer value is inserted at the specified index in the mapping.
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
* // Insert integer element (42) at index 0 in the sequence
* yaml_insert_int(seq, NULL, 0, 42);
*
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
* // Insert key-value pair with integer value (100) at index 1 in the mapping
* yaml_insert_int(map, "age", 1, 100);
* \endcode
*/
yaml_t yaml_insert_int(yaml_t yaml, const char* key, unsigned int index, int num)
{
yaml_t insert = NULL;
// Validate input node
if (!yaml) return NULL;
// Assume CHECK_INSERT_TYPE() validates the node type for insertion
CHECK_INSERT_TYPE();
// Create a new YAML node
insert = yaml_create();
if (!insert) return NULL;
// Set the key for the new node if it's for a mapping (key should be NULL for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the integer value for the new node
yaml_set_int(insert, num);
// Attach the new node to the parent at the specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up the new node in case of failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a float value into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] num: Double-precision float value to set
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML node with a floating-point value into a parent
* sequence, mapping, or document at the specified index. The new node is created,
* assigned a key (if applicable for mappings), set to the provided double-precision
* float value, and then attached to the parent node at the given index.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new node with float value is inserted
* at the specified index in the sequence.
* - For mappings: a non-NULL key is required, and the new key-value pair with the
* float value is inserted at the specified index in the mapping.
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
* // Insert float element (3.14) at index 0 in the sequence
* yaml_insert_float(seq, NULL, 0, 3.14);
*
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
* // Insert key-value pair with float value (0.01) at index 1 in the mapping
* yaml_insert_float(map, "ratio", 1, 0.01);
* \endcode
*/
yaml_t yaml_insert_float(yaml_t yaml, const char* key, unsigned int index, double num)
{
yaml_t insert = NULL;
// Validate input node
if (!yaml) return NULL;
// Assume CHECK_INSERT_TYPE() validates the node type for insertion
CHECK_INSERT_TYPE();
// Create a new YAML node
insert = yaml_create();
if (!insert) return NULL;
// Set the key for the new node if it's for a mapping (key should be NULL for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the float value for the new node (using double-precision)
yaml_set_float(insert, num);
// Attach the new node to the parent at the specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up the new node in case of failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a string value into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] string: String value to set
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML node with a string value into a parent sequence,
* mapping, or document at the specified index. The new node is created, assigned a key
* (if applicable for mappings), set to the provided string value, and then attached
* to the parent node at the given index.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new node with string value is inserted
* at the specified index in the sequence.
* - For mappings: a non-NULL key is required, and the new key-value pair with the
* string value is inserted at the specified index in the mapping.
*
* Memory management:
* - The input string is copied using yaml_set_string(), so the original string can be
* safely freed after this call.
*
* Example usage:
* \code
* yaml_t seq = yaml_new();
* yaml_set_sequence(seq, NULL);
* // Insert string element ("apple") at index 0 in the sequence
* yaml_insert_string(seq, NULL, 0, "apple");
*
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
* // Insert key-value pair with string value ("banana") at index 1 in the mapping
* yaml_insert_string(map, "fruit", 1, "banana");
* \endcode
*/
yaml_t yaml_insert_string(yaml_t yaml, const char* key, unsigned int index, const char* string)
{
yaml_t insert = NULL;
// Validate input node
if (!yaml) return NULL;
// Assume CHECK_INSERT_TYPE() validates the node type for insertion
CHECK_INSERT_TYPE();
// Create a new YAML node
insert = yaml_create();
if (!insert) return NULL;
// Set the key for the new node if it's for a mapping (key should be NULL for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the string value for the new node (creates a copy of the input string)
if (!yaml_set_string(insert, string)) goto FAIL;
// Attach the new node to the parent at the specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up the new node in case of failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a sequence into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] sequence: Sequence YAML node to insert
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML sequence node into a parent sequence, mapping,
* or document at the specified index. The new sequence node is created, assigned
* a key (if applicable for mappings), initialized with the provided sequence content,
* and then attached to the parent node.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new sequence is inserted as an element.
* - For mappings: a non-NULL key is required, and the new sequence is inserted as a value.
* - The provided sequence is adopted by the new node; ownership is transferred.
*
* Example usage:
* \code
* // Create parent sequence
* yaml_t parent_seq = yaml_new();
* yaml_set_sequence(parent_seq, NULL);
*
* // Create child sequence
* yaml_t child_seq = yaml_new();
* yaml_set_sequence(child_seq, NULL);
*
* // Insert child sequence into parent at index 0
* yaml_insert_sequence(parent_seq, NULL, 0, child_seq);
*
* // Create a mapping
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
*
* // Insert sequence into mapping with key "items"
* yaml_insert_sequence(map, "items", 0, child_seq);
* \endcode
*/
yaml_t yaml_insert_sequence(yaml_t yaml, const char* key, unsigned int index, yaml_t sequence)
{
yaml_t insert = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Ensure parent is a valid container type
CHECK_INSERT_TYPE();
// Create new YAML node to hold the sequence
insert = yaml_create();
if (!insert) return NULL;
// Set key for mapping entries (ignored for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the node's value to the provided sequence
yaml_set_sequence(insert, sequence);
// Attach the new node to the parent at specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up on failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a mapping into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] key: Key for the new node (NULL for sequences)
* \param[in] index: Position to insert the new node
* \param[in] mapping: Mapping YAML node to insert
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a new YAML mapping node into a parent sequence, mapping,
* or document at the specified index. The new mapping node is created, assigned
* a key (if applicable for mappings), initialized with the provided mapping content,
* and then attached to the parent node.
*
* Key behaviors:
* - For sequences: key should be NULL, and the new mapping is inserted as an element.
* - For mappings: a non-NULL key is required, and the new mapping is inserted as a value.
* - The provided mapping is adopted by the new node; ownership is transferred.
*
* Example usage:
* \code
* // Create parent sequence
* yaml_t parent_seq = yaml_new();
* yaml_set_sequence(parent_seq, NULL);
*
* // Create child mapping
* yaml_t child_map = yaml_new();
* yaml_set_mapping(child_map, NULL);
*
* // Insert child mapping into parent at index 0
* yaml_insert_mapping(parent_seq, NULL, 0, child_map);
*
* // Create a parent mapping
* yaml_t parent_map = yaml_new();
* yaml_set_mapping(parent_map, NULL);
*
* // Insert mapping into parent mapping with key "config"
* yaml_insert_mapping(parent_map, "config", 0, child_map);
* \endcode
*/
yaml_t yaml_insert_mapping(yaml_t yaml, const char* key, unsigned int index, yaml_t mapping)
{
yaml_t insert = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Ensure parent is a valid container type
CHECK_INSERT_TYPE();
// Create new YAML node to hold the mapping
insert = yaml_create();
if (!insert) return NULL;
// Set key for mapping entries (ignored for sequences)
if (key && !yaml_set_key(insert, key)) goto FAIL;
// Set the node's value to the provided mapping
yaml_set_mapping(insert, mapping);
// Attach the new node to the parent at specified index
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up on failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a document reference into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node
* \param[in] index: Position to insert the new node
* \param[in] document: Document YAML node to reference
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a document reference (anchor) into a parent YAML node
* at the specified index. The reference allows the document to be reused
* elsewhere in the YAML structure via aliases.
*
* Key behaviors:
* - Converts the parent node to a document type if it's currently null
* - Creates a new anchor node that references the provided document
* - The document itself is not modified or copied
* - The anchor node is attached to the parent's child list
*
* Memory management:
* - A new anchor structure is allocated for the reference
* - The anchor is owned by the created YAML node
*
* Example usage:
* \code
* yaml_t parent = yaml_new();
* yaml_t doc = yaml_new();
* // Initialize doc as a document...
*
* // Insert document reference at index 0
* yaml_insert_document(parent, 0, doc);
* \endcode
*/
yaml_t yaml_insert_document(yaml_t yaml, unsigned int index, yaml_t document)
{
yaml_t insert = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Convert parent to document type if currently null
if (yaml->vtype == YAML_TYPE_NULL)
{
yaml->vtype = YAML_TYPE_DOCUMENT;
}
// Create new YAML node to hold the document reference
insert = yaml_create();
if (!insert) goto FAIL;
// Allocate and initialize anchor structure
insert->anchor = (ANCHOR *)malloc(sizeof(ANCHOR));
if (!insert->anchor) goto FAIL;
memset(insert->anchor, 0, sizeof(ANCHOR));
// Set node type to anchor
insert->ktype = YAML_KTYPE_ANCHOR;
// Attach the new anchor node to the parent
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up on failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Insert a reference to an anchor into a YAML node at specified index
* \param[in] yaml: Pointer to the parent YAML node (must be MAPPING)
* \param[in] key: Key for the new node
* \param[in] index: Position to insert the new node
* \param[in] anchor: Anchor name to reference
* \param[in] doc: Document containing the anchor
* \return Pointer to the inserted YAML node, or NULL on failure
*
* This function inserts a reference to an existing anchor into a parent mapping node.
* The reference allows the original anchor's value to be reused without duplication.
*
* Key behaviors:
* - Parent node must be a mapping (or NULL, which is converted to a mapping)
* - Creates a new key-value pair where the value is a reference to the anchor
* - If key is NULL, attempts to create a referenced duplicate of the anchor's value
*
* Anchor resolution:
* - Searches the provided document's anchor list for a matching anchor name
* - Returns NULL if the anchor is not found or the document is invalid
*
* Example usage:
* \code
* yaml_t parent_map = yaml_new();
* yaml_set_mapping(parent_map, NULL);
*
* yaml_t doc = yaml_new();
* // Assume doc contains an anchor named "shared_value"
*
* // Insert reference to anchor under key "ref_key"
* yaml_insert_reference(parent_map, "ref_key", 0, "shared_value", doc);
* \endcode
*/
yaml_t yaml_insert_reference(yaml_t yaml, const char* key, unsigned int index, const char* anchor, yaml_t doc)
{
yaml_t insert = NULL;
yaml_t ref = NULL;
// Validate input parameters
if (!yaml) return NULL;
if (!anchor) return NULL;
if (!doc) return NULL;
// Ensure document is a valid anchor container
if (doc->ktype != YAML_KTYPE_ANCHOR) return NULL;
// Convert parent to mapping type if currently null
if (yaml->vtype == YAML_TYPE_NULL)
{
yaml->vtype = YAML_TYPE_MAPPING;
}
// Ensure parent is a mapping
else if (yaml->vtype == YAML_TYPE_MAPPING)
{
// Do nonthing
}
else
{
return NULL;
}
// Locate the referenced anchor in the document
ref = yaml_match_anchor(doc->anchor, anchor, strlen(anchor));
if (!ref) return NULL;
if (key)
{
// Create a new node with the specified key referencing the anchor
insert = yaml_create();
if (!yaml_set_key(insert, key)) goto FAIL;
insert->vtype = YAML_TYPE_REFERENCE;
insert->value.child_ = ref;
}
else
{
// Create a duplicate node with reference semantics
yaml_t copy = yaml_duplicate(ref, YAML_F_RECURSE | YAML_F_COMPLEX | YAML_F_REFERENCE, NULL);
if (!copy) return NULL;
insert = copy->value.child_;
copy->value.child_ = NULL;
yaml_delete(copy);
}
// Attach the new reference node to the parent mapping
if (!yaml_attach(yaml, index, insert)) goto FAIL;
return insert;
FAIL:
// Clean up on failure
yaml_delete(insert);
return NULL;
}
/**
* \brief Remove a YAML node by key or index
* \param[in] yaml: Pointer to the parent YAML node (mapping or sequence)
* \param[in] key: Key of the node to remove (for mappings), or NULL (for sequences)
* \param[in] index: Index of the node to remove (relative to nodes with matching key)
* \return YAML_TRUE on success, YAML_FALSE on failure
*
* This function removes a YAML node from its parent container based on either a key
* (for mappings) or an index (for sequences). For mappings, the index is relative to
* the position of nodes with the specified key.
*
* Key behaviors:
* - For sequences: key should be NULL, and the node at the specified index is removed.
* - For mappings: the node with the specified key at the given index (among matches)
* is removed. If multiple nodes have the same key, index determines which occurrence
* to remove.
*
* Example usage:
* \code
* // Remove element at index 2 from a sequence
* yaml_remove(seq, NULL, 2);
*
* // Remove the first node with key "name" from a mapping
* yaml_remove(map, "name", 0);
*
* // Remove the second node with key "option" from a mapping
* yaml_remove(map, "option", 1);
* \endcode
*/
int yaml_remove(yaml_t yaml, const char* key, unsigned int index)
{
yaml_t remove = NULL;
yaml_t child = NULL;
// Validate input parameters
if (!yaml) return YAML_FALSE;
// Handle sequence removal by index
if (!key)
{
// Detach the node at the specified index in the sequence
remove = yaml_dettach(yaml, index);
}
// Handle mapping removal by key and index
else
{
// Ensure the parent is a mapping
if (yaml->vtype != YAML_TYPE_MAPPING) return YAML_FALSE;
if (index >= yaml->size) return YAML_FALSE;
// Check the first node in the mapping
child = yaml_iterator_to(yaml, 0);
if (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key))
{
// Remove the first node if it matches
if (index == 0) remove = yaml_dettach(yaml, 0);
// Adjust index for subsequent search
else index--;
}
// Search remaining nodes for matching keys
if (!remove)
{
for (unsigned int i = 0; i < yaml->size - 1; i++)
{
// Get the next node in the mapping
child = yaml_iterator_to(yaml, i)->next;
if (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key))
{
if (index == 0)
{
// Detach and remove the node at the current position
remove = yaml_dettach(yaml, i + 1);
break;
}
else
index--; // Decrement index for next match
}
}
}
}
// Check if a node was found and detached
if (!remove) return YAML_FALSE;
// Clean up the removed node and its children
yaml_delete(remove);
return YAML_TRUE;
}
/**
* \brief Get the index of a node in a YAML node (by key or index)
* \param[in] yaml: Pointer to the parent YAML node (sequence, mapping or document)
* \param[in] key: Key of the node to find (for mappings), or NULL (for sequences)
* \param[in] index: Index of the node to find (used for multi-match scenarios)
* \return The index of the node if found, or YAML_INV_INDEX on failure
*
* This function retrieves the linear index of a child node within a parent container
* (sequence, mapping, or document). For mappings, the search is filtered by key, and
* the index parameter specifies which occurrence of the key to return.
*
* Key behaviors:
* - For sequences: key should be NULL, and the function returns the given index directly
* - For mappings: returns the index of the Nth node with the specified key (0-based)
* - For documents: key must be NULL, and the function returns the given index directly
*
* Example usage:
* \code
* // Get index of 2nd element in a sequence (should return 1)
* unsigned int idx = yaml_get_index(seq, NULL, 1);
*
* // Get index of first node with key "name" in a mapping
* idx = yaml_get_index(map, "name", 0);
*
* // Get index of second node with key "option" in a mapping
* idx = yaml_get_index(map, "option", 1);
* \endcode
*/
unsigned int yaml_get_index(yaml_t yaml, const char* key, unsigned int index)
{
unsigned int i = 0;
yaml_t child = NULL;
// Validate input parameters
if (!yaml) return YAML_INV_INDEX;
// Check for invalid key usage
if (key && (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT)) return YAML_INV_INDEX;
// Ensure parent is a valid container type
if (yaml->vtype != YAML_TYPE_SEQUENCE &&
yaml->vtype != YAML_TYPE_MAPPING &&
yaml->vtype != YAML_TYPE_DOCUMENT) return YAML_INV_INDEX;
// Iterate through all child nodes
for (i = 0; i < yaml->size; i++)
{
child = yaml_iterator_to(yaml, i);
// Check if the current node matches the criteria
if (!key || (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key)))
{
// If this is the Nth occurrence of the key, return its index
if (index == 0) return i;
// Decrement the index counter for subsequent matches
index--;
}
}
// Node not found
return YAML_INV_INDEX;
}
/**
* \brief Get the index of a complex node in a YAML mapping node
* \param[in] yaml: Pointer to the parent YAML mapping node
* \param[in] key: Pointer to the complex YAML node to find
* \return The index of the complex node if found, or YAML_INV_INDEX on failure
*
* This function retrieves the linear index of a child node within a parent mapping
* where the key is a complex YAML node (e.g., sequence or mapping). The comparison
* is performed using deep structural matching of the complex key nodes.
*
* Key behaviors:
* - Parent node must be a mapping (YAML_TYPE_MAPPING)
* - Searches for a key that is a complex node (YAML_KTYPE_COMPLEX)
* - Uses yaml_compare() with YAML_F_COMPLEX flag for deep comparison
* - Returns the first matching index, or YAML_INV_INDEX if not found
*
* Example usage:
* \code
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
*
* // Create a complex key (sequence)
* yaml_t complex_key = yaml_new();
* yaml_set_sequence(complex_key, NULL);
*
* // Add key-value pair to map
* yaml_insert_mapping(map, NULL, 0, complex_key);
*
* // Get index of the complex key
* unsigned int idx = yaml_get_index_complex(map, complex_key);
* \endcode
*/
unsigned int yaml_get_index_complex(yaml_t yaml, yaml_t key)
{
unsigned int i = 0;
yaml_t child = NULL;
// Validate input parameters
if (!yaml) return YAML_INV_INDEX;
if (!key) return YAML_INV_INDEX;
// Ensure parent is a mapping
if (yaml->vtype != YAML_TYPE_MAPPING) return YAML_INV_INDEX;
// Iterate through all child nodes
for (i = 0; i < yaml->size; i++)
{
child = yaml_iterator_to(yaml, i);
// Check if the current node has a complex key and matches the target
if (child->ktype == YAML_KTYPE_COMPLEX && yaml_compare(child->complex, key, YAML_F_COMPLEX))
{
return i;
}
}
// Node not found
return YAML_INV_INDEX;
}
/**
* \brief Get a child node of a YAML node (by key or index)
* \param[in] yaml: Pointer to the parent YAML node (sequence, mapping or document)
* \param[in] key: Key of the child node to find (for mappings), or NULL (for sequences)
* \param[in] index: Index of the child node to find (used for multi-match scenarios)
* \return Pointer to the child node if found, or NULL on failure
*
* This function retrieves a child node from a parent container (sequence, mapping, or document)
* based on either a key (for mappings) or an index (for sequences and documents). For mappings,
* the index parameter specifies which occurrence of the key to return in case of duplicates.
*
* Key behaviors:
* - For sequences: key should be NULL, and the function returns the node at the given index.
* - For mappings: returns the Nth node with the specified key (0-based).
* - For documents: key must be NULL, and the function returns the node at the given index.
*
* Example usage:
* \code
* // Get second element from a sequence (index 1)
* yaml_t element = yaml_get_child(seq, NULL, 1);
*
* // Get first node with key "name" from a mapping
* yaml_t name_node = yaml_get_child(map, "name", 0);
*
* // Get second node with key "option" from a mapping
* yaml_t option_node = yaml_get_child(map, "option", 1);
* \endcode
*/
yaml_t yaml_get_child(yaml_t yaml, const char* key, unsigned int index)
{
unsigned int i = 0;
yaml_t child = NULL;
// Validate input parameters
if (!yaml) return NULL;
// Check for invalid key usage
if (key && (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT)) return NULL;
// Ensure parent is a valid container type
if (yaml->vtype != YAML_TYPE_SEQUENCE &&
yaml->vtype != YAML_TYPE_MAPPING &&
yaml->vtype != YAML_TYPE_DOCUMENT) return NULL;
// Iterate through all child nodes
for (i = 0; i < yaml->size; i++)
{
child = yaml_iterator_to(yaml, i);
// Check if the current node matches the criteria
if (!key || (child->ktype == YAML_KTYPE_STRING && !strcmp(child->key, key)))
{
// If this is the Nth occurrence of the key, return the node
if (index == 0) break;
// Decrement the index counter for subsequent matches
index--;
}
}
// Node not found
if (i >= yaml->size) return NULL;
return child;
}
/**
* \brief Get a complex child node of a YAML mapping node
* \param[in] yaml: Pointer to the parent YAML mapping node
* \param[in] key: Pointer to the complex YAML node to find
* \return Pointer to the complex child node if found, or NULL on failure
*
* This function retrieves a child node from a parent mapping where the key is a complex
* YAML node (e.g., a sequence or another mapping). The comparison is performed using
* deep structural matching of the complex key nodes.
*
* Key behaviors:
* - Parent node must be a mapping (YAML_TYPE_MAPPING)
* - Searches for a key that is a complex node (YAML_KTYPE_COMPLEX)
* - Uses yaml_compare() with YAML_F_COMPLEX flag for deep comparison
* - Returns the first matching child node, or NULL if not found
*
* Example usage:
* \code
* yaml_t map = yaml_new();
* yaml_set_mapping(map, NULL);
*
* // Create a complex key (sequence)
* yaml_t complex_key = yaml_new();
* yaml_set_sequence(complex_key, NULL);
*
* // Add key-value pair to map
* yaml_insert_mapping(map, NULL, 0, complex_key);
*
* // Retrieve the child node with the complex key
* yaml_t child = yaml_get_child_complex(map, complex_key);
* \endcode
*/
yaml_t yaml_get_child_complex(yaml_t yaml, yaml_t key)
{
unsigned int i = 0;
yaml_t child = NULL;
// Validate input parameters
if (!yaml) return NULL;
if (!key) return NULL;
// Ensure parent is a mapping and not a sequence or document
if (yaml->vtype == YAML_TYPE_SEQUENCE || yaml->vtype == YAML_TYPE_DOCUMENT) return NULL;
// Iterate through all child nodes in the mapping
for (i = 0; i < yaml->size; i++)
{
child = yaml_iterator_to(yaml, i);
// Check if the current node has a complex key and matches the target
if (child->ktype == YAML_KTYPE_COMPLEX && yaml_compare(child->complex, key, YAML_F_COMPLEX))
{
return child;
}
}
// No matching complex key found
return NULL;
}
/**
* \brief Compare two YAML nodes with specified comparison flags
* \param[in] yaml: Pointer to the first YAML node to compare
* \param[in] cmp: Pointer to the second YAML node to compare
* \param[in] flag: Comparison flags (YAML_F_* constants)
* \return YAML_TRUE if nodes are equal, YAML_FALSE otherwise
*
* This function compares two YAML nodes based on their key type, value type, and
* specified comparison flags. It recursively checks child nodes if the flag indicates
* recursion, and compares keys and values according to the node's key and value types.
*
* Key behaviors:
* - Checks if nodes are the same pointer, returning true if so.
* - Compares key types (string, complex, anchor) and values (bool, int, float, string,
* date, sequence, mapping, document, reference, complex key) based on flags.
* - For complex keys and sequences/mappings/documents, recursively compares child nodes
* if the YAML_F_RECURSE flag is set.
*
* Example usage:
* \code
* yaml_t node1 = yaml_new();
* yaml_set_int(node1, 42);
*
* yaml_t node2 = yaml_new();
* yaml_set_int(node2, 42);
*
* int result = yaml_compare(node1, node2, YAML_F_NOKEY);
* if (result == YAML_TRUE) {
* // Nodes are equal
* }
* \endcode
*/
int yaml_compare(yaml_t yaml, yaml_t cmp, int flag)
{
// Validate input nodes
if (!yaml || !cmp) return YAML_FALSE;
// If nodes are the same pointer, they are equal
if (yaml == cmp) return YAML_TRUE;
// Compare key types
if (yaml->ktype != cmp->ktype) return YAML_FALSE;
// Compare value types
if (yaml->vtype != cmp->vtype) return YAML_FALSE;
// Compare keys based on key type and flags
if (yaml->ktype == YAML_KTYPE_STRING)
{
if (!(flag & YAML_F_NOKEY))
{
if (!yaml->key && !cmp->key) ;
else if (yaml->key && !cmp->key) return YAML_FALSE;
else if (!yaml->key && cmp->key) return YAML_FALSE;
else
{
if (strcmp(yaml->key, cmp->key) != 0) return YAML_FALSE;
}
}
}
else if (yaml->ktype == YAML_KTYPE_COMPLEX)
{
if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX))
{
if (yaml_compare(yaml->complex, cmp->complex, flag) == YAML_FALSE) return YAML_FALSE;
}
}
else if (yaml->ktype == YAML_KTYPE_ANCHOR)
{
if (flag & YAML_F_ANCHOR)
{
if (yaml->anchor != cmp->anchor)
{
if (!yaml->anchor || !cmp->anchor) return YAML_FALSE;
if (yaml->anchor->size != cmp->anchor->size) return YAML_FALSE;
if (yaml->anchor->array != cmp->anchor->array)
{
if (!yaml->anchor->array || !cmp->anchor->array) return YAML_FALSE;
for (int i = 0; i < yaml->anchor->size; i++)
{
if (yaml_compare(yaml->anchor->array[i], cmp->anchor->array[i], flag) == YAML_FALSE)
{
return YAML_FALSE;
}
}
}
}
}
}
else return YAML_FALSE;
// Compare values based on value type
if (yaml->vtype == YAML_TYPE_BOOL)
{
if (yaml->value.bool_ != cmp->value.bool_) return YAML_FALSE;
}
else if (yaml->vtype == YAML_TYPE_INT)
{
if (yaml->value.int_ != cmp->value.int_) return YAML_FALSE;
}
else if (yaml->vtype == YAML_TYPE_FLOAT)
{
if (yaml->value.float_ != cmp->value.float_) return YAML_FALSE;
}
else if (yaml->vtype == YAML_TYPE_STRING)
{
if (strcmp(yaml->value.string_, cmp->value.string_) != 0) return YAML_FALSE;
}
else if (yaml->vtype == YAML_TYPE_DATE)
{
if (memcmp(&yaml->value.date, &cmp->value.date, sizeof(DATE)) != 0) return YAML_FALSE;
}
else if (yaml->vtype == YAML_TYPE_SEQUENCE ||
yaml->vtype == YAML_TYPE_MAPPING ||
yaml->vtype == YAML_TYPE_DOCUMENT)
{
if (yaml->size != cmp->size) return YAML_FALSE;
if (flag & YAML_F_RECURSE)
{
for (int i = 0; i < yaml->size; i++)
{
yaml_t t0 = yaml_iterator_to(yaml, i);
yaml_t t1 = yaml_iterator_to(cmp, i);
if (yaml_compare(t0, t1, flag) == YAML_FALSE)
{
return YAML_FALSE;
}
}
}
}
else if (yaml->vtype == YAML_TYPE_REFERENCE)
{
if (flag & YAML_F_REFERENCE)
{
if (yaml_compare(yaml->value.child_, cmp->value.child_, flag) == YAML_FALSE) return YAML_FALSE;
}
}
else if (yaml->vtype == YAML_TYPE_COMPLEX_KEY)
{
if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX))
{
if (yaml_compare(yaml->value.child_, cmp->value.child_, flag) == YAML_FALSE) return YAML_FALSE;
}
}
else return YAML_FALSE;
// Compare aliases if flag is set
if (yaml->alias)
{
if (flag & YAML_F_ANCHOR)
{
if (strcmp(yaml->alias, cmp->alias) != 0) return YAML_FALSE;
}
}
return YAML_TRUE;
}
/**
* \brief Duplicate a YAML node with specified flags and parameter
* \param[in] yaml: Pointer to the YAML node to duplicate
* \param[in] flag: Duplication flags (YAML_F_* constants)
* \param[in] para: Parameter for anchor handling (ANCHOR pointer)
* \return Pointer to the duplicated YAML node, or NULL on failure
*
* This function duplicates a YAML node according to the provided flags. It handles
* different key types (string, complex, anchor) and value types (bool, int, float,
* string, date, sequence, mapping, document, reference, complex key). The function
* recursively duplicates child nodes if the YAML_F_RECURSE flag is set and manages
* anchors and aliases as per the YAML_F_ANCHOR flag.
*
* Key behaviors:
* - For key duplication, it copies string keys, recursively duplicates complex keys,
* and manages anchor structures.
* - For value duplication, it copies simple values (bool, int, float, date), strings,
* and recursively duplicates sequences, mappings, documents, and referenced nodes.
* - The para parameter is used for anchor handling, typically passing an anchor
* structure to manage anchor arrays.
*
* Example usage:
* \code
* yaml_t original = yaml_new();
* yaml_set_int(original, 42);
*
* yaml_t duplicated = yaml_duplicate(original, YAML_F_RECURSE | YAML_F_NOKEY, NULL);
* if (duplicated) {
* // The node has been successfully duplicated
* }
* \endcode
*/
static yaml_t yaml_duplicate(yaml_t yaml, int flag, void *para)
{
yaml_t copy, cptr, nptr = NULL, child;
// Validate input node
if (!yaml) return NULL;
/* Create a backup empty yaml object */
copy = yaml_create();
if (!copy) return NULL;
// Duplicate key based on key type and flags
if (yaml->ktype == YAML_KTYPE_STRING)
{
if (!(flag & YAML_F_NOKEY))
{
/* If there is a key, copy the key */
if (yaml->key)
{
copy->key = yaml_strdup(yaml->key, strlen(yaml->key));
if (!copy->key) goto FAIL;
}
}
}
else if (yaml->ktype == YAML_KTYPE_COMPLEX)
{
if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX))
{
if (yaml->complex)
{
copy->complex = yaml_duplicate(yaml->complex, YAML_F_COMPLEX, para);
if (!copy->complex) goto FAIL;
}
}
}
else if (yaml->ktype == YAML_KTYPE_ANCHOR)
{
if (flag & YAML_F_ANCHOR)
{
if (para) goto FAIL;
copy->anchor = (ANCHOR *)malloc(sizeof(ANCHOR));
if (!copy->anchor) goto FAIL;
memset(copy->anchor, 0, sizeof(ANCHOR));
yaml_t *array = (yaml_t *)realloc(copy->anchor->array, sizeof(yaml_t) * copy->anchor->capacity);
if (!array) goto FAIL;
copy->anchor->array = array;
copy->anchor->capacity = yaml->anchor->capacity;
para = copy->anchor;
}
}
else goto FAIL;
// Duplicate value based on value type
if (yaml->vtype == YAML_TYPE_BOOL ||
yaml->vtype == YAML_TYPE_INT ||
yaml->vtype == YAML_TYPE_FLOAT ||
yaml->vtype == YAML_TYPE_DATE)
{
copy->value = yaml->value;
}
/* If it is a string type, copy the string */
else if (yaml->vtype == YAML_TYPE_STRING)
{
if (yaml->value.string_)
{
copy->value.string_ = yaml_strdup(yaml->value.string_, yaml->size);
if (!copy->value.string_) goto FAIL;
}
}
/* walk the ->next chain for the child. */
else if (yaml->vtype == YAML_TYPE_SEQUENCE ||
yaml->vtype == YAML_TYPE_MAPPING ||
yaml->vtype == YAML_TYPE_DOCUMENT)
{
if (flag & YAML_F_RECURSE)
{
cptr = yaml->value.child_;
while (cptr)
{
/* copy (with recurse) each yaml in the ->next chain */
child = yaml_duplicate(cptr, flag, para);
if (!child) goto FAIL;
/* if newitem->child already set, then crosswire ->prev and ->next and move on */
if (nptr)
{
nptr->next = child;
nptr = child;
}
/* set newitem->child and move to it */
else
{
copy->value.child_ = child;
nptr = child;
}
cptr = cptr->next;
}
}
}
else if (yaml->vtype == YAML_TYPE_REFERENCE)
{
if (flag & YAML_F_REFERENCE)
{
copy->value.child_ = yaml_duplicate(yaml->value.child_, flag, para);
if (!copy->value.child_) goto FAIL;
}
}
else if (yaml->vtype == YAML_TYPE_COMPLEX_KEY)
{
if (!(flag & YAML_F_NOKEY) && (flag & YAML_F_COMPLEX))
{
copy->value.child_ = yaml_duplicate(yaml->value.child_, flag, para);
if (!copy->value.child_) goto FAIL;
}
}
else goto FAIL;
// Handle aliases and anchors
if (yaml->alias)
{
if (flag & YAML_F_ANCHOR)
{
if (!para) goto FAIL;
copy->alias = yaml_strdup(yaml->alias, strlen(yaml->alias));
if (!copy->alias) goto FAIL;
ANCHOR *anchor = para;
anchor->array[anchor->size++] = copy;
}
}
copy->vtype = yaml->vtype;
copy->size = yaml->size;
return copy;
FAIL:
yaml_delete(copy);
return NULL;
}
/**
* \brief Copy a YAML node with specified flags
* \param[in] yaml: Pointer to the YAML node to copy
* \param[in] flag: Copy flags (YAML_F_* constants)
* \return Pointer to the copied YAML node, or NULL on failure
*
* This function is a wrapper around yaml_duplicate, which duplicates a YAML node
* according to the provided flags. It simplifies the call to yaml_duplicate by setting
* the parameter for anchor handling to NULL.
*
* Key behaviors:
* - Calls yaml_duplicate with the given YAML node, flags, and a NULL parameter for
* anchor handling.
* - Returns the duplicated YAML node on success or NULL on failure.
*
* Example usage:
* \code
* yaml_t original = yaml_new();
* yaml_set_string(original, "original_value");
*
* yaml_t copied = yaml_copy(original, YAML_F_NOKEY);
* if (copied) {
* // The node has been successfully copied
* }
* \endcode
*/
yaml_t yaml_copy(yaml_t yaml, int flag)
{
return yaml_duplicate(yaml, flag, NULL);
}
/**
* \brief Add a YAML node to an anchor array
* \param[in] anchor: Pointer to the anchor structure
* \param[in] yaml: Pointer to the YAML node to add
* \return Pointer to the added YAML node, or NULL on failure
*
* This function adds a YAML node to an anchor array within an anchor structure.
* It first checks if the anchor and the YAML node are valid. If the anchor array's
* size plus one exceeds its current capacity, it expands the array to accommodate
* the new node. Then it adds the YAML node to the anchor array and returns the
* added YAML node, or NULL if any operation fails.
*
* Memory management:
* - When expanding the anchor array, it uses realloc to resize the array.
* - The function assumes that the anchor structure has members'size' and 'capacity'
* to manage the array, and 'array' which is a pointer to an array of YAML nodes.
*
* Example usage:
* \code
* ANCHOR anchor;
* anchor.size = 0;
* anchor.capacity = 1;
* anchor.array = (yaml_t *)malloc(sizeof(yaml_t) * anchor.capacity);
*
* yaml_t node = yaml_new();
* yaml_set_string(node, "node_value");
*
* yaml_t added_node = yaml_add_anchor(&anchor, node);
* if (added_node) {
* // The node has been successfully added to the anchor array
* }
* \endcode
*/
static yaml_t yaml_add_anchor(ANCHOR *anchor, yaml_t yaml)
{
if (!anchor || !yaml) return NULL;
// Expand anchor array capacity if necessary
if (anchor->size + 1 > anchor->capacity)
{
unsigned int capacity = pow2gt(anchor->size + 1);
yaml_t *array = (yaml_t *)realloc(anchor->array, sizeof(yaml_t) * capacity);
if (!array) return NULL;
anchor->array = array;
anchor->capacity = capacity;
}
// Add yaml node to anchor array
anchor->array[anchor->size++] = yaml;
return yaml;
}
/**
* \brief Compare two strings up to n characters and check for exact length match
* \param[in] s1: First string to compare
* \param[in] s2: Second string to compare
* \param[in] n: Maximum number of characters to compare
* \return 0 if s1 matches s2 exactly up to n characters and s1 ends at n, otherwise difference
*
* This function compares the first n characters of two strings. It returns zero only if:
* 1. The first n characters of both strings are identical.
* 2. The first string (s1) has exactly n characters (i.e., s1[n] is '\0').
*
* Key behaviors:
* - Compares characters until it finds a difference, reaches n characters, or hits a null terminator.
* - Returns zero only if both strings match up to n characters and s1 is exactly n characters long.
* - Returns the ASCII difference of the first mismatched character otherwise.
*
* Example usage:
* \code
* strnwcmp("abc", "abc", 3); // Returns 0 (s1 is "abc" and s2 is "abc")
* strnwcmp("abc", "abcd", 3); // Returns 0 (s1 is "abc" and s2 is "abcd")
* strnwcmp("abcd", "abc", 3); // Returns 'd' (100) - '\0' (0) = 100
* strnwcmp("abc", "abd", 3); // Returns 'c' (99) - 'd' (100) = -1
* \endcode
*/
int strnwcmp(const char *s1, const char *s2, unsigned int n)
{
unsigned int i = 0;
// Compare characters up to n or until a difference is found
while (i < n && *s1 && *s1 == *s2)
{
++s1;
++s2;
++i;
}
// Return 0 only if both strings matched exactly up to n and s1 ends at n
if (i == n && *s1 == 0)
{
return 0;
}
else
{
return *s1 - *s2;
}
}
/**
* \brief Match a YAML node in an anchor array by alias prefix
* \param[in] anchor: Pointer to the anchor structure
* \param[in] base: Base string to match against aliases
* \param[in] length: Length of the base string to match
* \return Pointer to the matched YAML node, or NULL if not found
*
* This function searches through an anchor array within an anchor structure to find a YAML node
* whose alias matches the provided base string up to the specified length. It first validates the
* input anchor, base string, and length. Then it iterates through the anchor array and checks each
* node's alias using strnwcmp to see if it matches the base string up to the given length. If a
* match is found, it returns a pointer to the matching YAML node; otherwise, it returns NULL.
*
* Memory management:
* - Assumes the anchor structure has an array of YAML nodes and manages its size and array pointer.
* - The function does not modify the anchor array but only reads from it.
*
* Example usage:
* \code
* ANCHOR anchor;
* // Initialize anchor structure and its array of YAML nodes
* // Assume some YAML nodes with aliases are added to the anchor array
*
* const char *base_str = "prefix";
* unsigned int len = strlen(base_str);
*
* yaml_t matched_node = yaml_match_anchor(&anchor, base_str, len);
* if (matched_node) {
* // A matching YAML node was found in the anchor array
* }
* \endcode
*/
static yaml_t yaml_match_anchor(ANCHOR *anchor, const char *base, unsigned int length)
{
if (!anchor || !base || length == 0) return NULL;
if (!anchor->array || anchor->size == 0) return NULL;
// Iterate through anchor array to find matching alias
for (int i = 0; i < anchor->size; i++)
{
if (anchor->array[i]->alias)
{
if (strnwcmp(anchor->array[i]->alias, base, length) == 0) return anchor->array[i];
}
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Dump functions
//////////////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
char* address; /**< buffer base address */
unsigned int size; /**< size of buffer */
unsigned int end; /**< end of buffer used */
} BUFFER;
static int print_yaml(yaml_t yaml, BUFFER* buf, int depth, int flag);
static int print_string(yaml_t yaml, BUFFER* buf);
static int print_mapping(yaml_t yaml, BUFFER* buf, int depth, int flag);
/**
* \brief confirm whether buf still has the required capacity, otherwise add capacity.
* \param[in] buf: buf handle
* \param[in] needed: required capacity
* \return 1 success or 0 fail
*
* This function checks if the buffer (buf) has enough space to accommodate the
* additional required capacity (needed). If the current buffer size is sufficient
* to hold the new required capacity (by comparing with the sum of current end
* position and needed capacity), it returns 1. Otherwise, it calculates a new
* appropriate size (using pow2gt function, assumed to round up to the next power of 2)
* and attempts to reallocate the buffer using realloc. If the reallocation is
* successful, it updates the buffer's size and address, and returns 1. If reallocation
* fails (realloc returns NULL), it returns 0.
*
* Memory management:
* - Calls realloc to resize the buffer if needed. If realloc fails, the original
* buffer remains unchanged and the function returns 0.
* - Updates the buffer's size and address if realloc is successful.
*
* Example usage:
* \code
* BUFFER buf;
* buf.address = (char *)malloc(100);
* buf.size = 100;
* buf.end = 50;
* int required_capacity = 80;
* int result = expansion(&buf, required_capacity);
* if (result) {
* // Buffer capacity updated successfully or already had enough capacity.
* } else {
* // Failed to expand the buffer.
* }
* \endcode
*/
static int expansion(BUFFER *buf, unsigned int needed)
{
char* address;
int size;
if (!buf || !buf->address) return 0;
needed += buf->end;
if (needed <= buf->size) return 1; /* there is still enough space in the current buf */
size = pow2gt(needed);
address = (char*)realloc(buf->address, size);
if (!address) return 0;
buf->size = size;
buf->address = address;
return 1;
}
#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */
#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */
#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */
/**
* \brief Print a string to a buffer with proper escaping and quoting
* \param[in] str: Input string to print
* \param[in] buf: Buffer to write into
* \return 1 on success, 0 on failure (buffer allocation error)
*
* This function takes an input string and prints it to a buffer with appropriate
* escaping and quoting. It first checks for an existing quote prefix in the string.
* Then it scans the string to count the number of double quotes, single quotes,
* escape characters, and control characters. Based on the presence of quotes, it
* decides whether to use single or double quotes for the output. It calculates the
* required buffer size considering quotes, escapes, and control characters. Finally,
* it iterates through the string, escaping special characters and writing the
* appropriate characters to the buffer, including the opening and closing quotes.
*
* Memory management:
* - The function relies on the buf_append function to manage buffer expansion.
* - It calculates the required buffer size and attempts to append characters to the buffer.
* - If the buf_append function fails (indicating a buffer allocation error), it returns 0.
*
* Example usage:
* \code
* char input[] = "Hello, world! \n";
* BUFFER buf;
* // Assume buf is properly initialized with an initial buffer
* int result = print_string_buffer(input, &buf);
* if (result) {
* // String was successfully printed to the buffer
* } else {
* // There was an error with buffer allocation during the printing process
* }
* \endcode
*/
static int print_string_buffer(const char* str, BUFFER* buf)
{
const char* p;
char prefix = 0; // Quote prefix (single/double quote or none)
char quote = '\"'; // Default to double quotes
int len = 0;
int escape = 0, single_q = 0, double_q = 0, control = 0;
/* Handle empty string */
if (!str) return 1;
// Check for existing quote prefix
if (*str == '\'' || *str == '\"') prefix = *str;
/* Scan string to count special characters */
p = str;
while (*p)
{
if (*p == '\"')
{
double_q++;
}
else if (*p == '\'')
{
single_q++;
}
else if (*p == '\\' || *p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') /* escape character */
{
escape++;
}
else if ((unsigned char)(*p) < ' ') /* control character */
{
control++;
}
p++;
len++;
}
// Print as plain string if no quotes/prefix/escapes needed
if (prefix == 0 && escape == 0 && control == 0)
{
if (!buf_append(len)) return 0;
while (len--) buf_putc(*str++);
return 1;
}
// Calculate required buffer size with quotes and escapes
len += 2; // Opening and closing quotes
len += escape; // Escape characters
len += (control * 5); // Control characters (as \uXXXX)
// Choose quote type based on which is less common in the string
if (double_q != 0) quote = '\''; // Prefer single quotes if string contains double quotes
if (single_q != 0) quote = '\"'; // Prefer double quotes if string contains single quotes
// Account for escaping the chosen quote character
if (quote == '\'') len += single_q;
else if (quote == '\"') len += double_q;
if (!buf_append(len)) return 0;
buf_putc(quote);
/* Process each character with proper escaping */
p = str;
while (*p)
{
if ((unsigned char)(*p) >= ' ' && *p != '\\' && *p != quote)
{
buf_putc(*p++); // Print regular character
}
else
{
/* Escape special characters */
buf_putc('\\');
if (*p == '\\') buf_putc('\\');
else if (*p == '\b') buf_putc('b');
else if (*p == '\f') buf_putc('f');
else if (*p == '\n') buf_putc('n');
else if (*p == '\r') buf_putc('r');
else if (*p == '\t') buf_putc('t');
else if (*p == quote) buf_putc(quote);
else
{
// Encode control characters as \uXXXX
sprintf(&buf_end(), "u%04x", (unsigned char)(*p));
buf->end += 5;
}
p++;
}
}
buf_putc(quote); // Closing quote
return 1;
}
/**
* \brief Print a YAML string node to a buffer
* \param[in] yaml: YAML node containing the string
* \param[in] buf: Buffer to write into
* \return 1 on success, 0 on failure
*
* This function takes a YAML node that is assumed to hold a string value and prints
* that string to a buffer. It does this by extracting the string from the YAML node
* and passing it, along with the buffer, to the print_string_buffer function. The
* return value of print_string_buffer is then returned as the result of this function.
*
* Memory management:
* - Relies on the print_string_buffer function to manage buffer operations related
* to appending the string to the buffer. If print_string_buffer fails due to
* buffer allocation issues, this function will return 0.
*
* Example usage:
* \code
* yaml_t string_node;
* string_node.vtype = YAML_TYPE_STRING;
* string_node.value.string_ = "Sample string";
* BUFFER buf;
* // Assume buf is properly initialized
* int result = print_string(&string_node, &buf);
* if (result) {
* // The YAML string node was successfully printed to the buffer
* } else {
* // There was an error during the printing process, likely buffer allocation
* }
* \endcode
*/
static int print_string(yaml_t yaml, BUFFER* buf)
{
return print_string_buffer(yaml->value.string_, buf);
}
/**
* \brief Print a YAML date node to a buffer in ISO 8601 format
* \param[in] yaml: YAML node containing the date
* \param[in] buf: Buffer to write into
* \return 1 on success, 0 on failure
*
* This function formats and prints a YAML date node to a buffer in ISO 8601 format.
* The date is printed in parts based on the flags set in the date structure:
* 1. The date part (YYYY-MM-DD) is always printed.
* 2. The time part (HH:MM:SS) is printed if DATEF_TIME flag is set.
* 3. Milliseconds (SS.FFF) are added if DATEF_MSEC flag is set.
* 4. UTC offset (+HH:MM) is added if DATEF_UTC flag is set.
*
* Memory management:
* - Uses buf_append to ensure buffer capacity before writing.
* - Returns 0 immediately if any buffer allocation fails.
* - Modifies buf->end to reflect the new buffer position after writes.
*
* Example usage:
* \code
* yaml_t date_node;
* date_node.vtype = YAML_TYPE_DATE;
* date_node.value.date.year = 2023;
* date_node.value.date.month = 10;
* date_node.value.date.day = 15;
* date_node.value.date.flag = DATEF_TIME | DATEF_UTC;
* date_node.value.date.hour = 14;
* date_node.value.date.minute = 30;
* date_node.value.date.second = 0;
* date_node.value.date.utcsign = 0; // Positive sign
* date_node.value.date.utchour = 8;
* date_node.value.date.utcminute = 0;
*
* BUFFER buf;
* // Initialize buffer...
*
* int result = print_date(&date_node, &buf);
* if (result) {
* // Buffer now contains "2023-10-15t14:30:00+08:00"
* }
* \endcode
*/
static int print_date(yaml_t yaml, BUFFER* buf)
{
int len = 0;
char flag = yaml->value.date.flag;
// Print date part (YYYY-MM-DD)
if (!buf_append(10)) return 0;
len = sprintf(&buf_end(), "%04d-%02d-%02d",
(int)yaml->value.date.year,
(int)yaml->value.date.month,
(int)yaml->value.date.day);
buf->end += len;
// Print time part if present
if (flag & DATEF_TIME)
{
if (!buf_append(9)) return 0;
buf_putc('t');
len = sprintf(&buf_end(), "%02d:%02d:%02d",
(int)yaml->value.date.hour,
(int)yaml->value.date.minute,
(int)yaml->value.date.second);
buf->end += len;
// Print milliseconds if present
if (flag & DATEF_MSEC)
{
if (!buf_append(4)) return 0;
buf_putc('.');
len = sprintf(&buf_end(), "%03d", (int)yaml->value.date.msec);
buf->end += len;
}
// Print UTC offset if present
if (flag & DATEF_UTC)
{
if (!buf_append(6)) return 0;
buf_putc(yaml->value.date.utcsign ? '-' : '+');
len = sprintf(&buf_end(), "%02d:%02d",
(int)yaml->value.date.utchour,
(int)yaml->value.date.utcminute);
buf->end += len;
}
}
return 1;
}
/**
* \brief Print a YAML number node to a buffer
* \param[in] yaml: YAML node containing the number
* \param[in] buf: Buffer to write into
* \return 1 on success, 0 on failure
*
* This function takes a YAML node that represents a number and prints it to a buffer.
* It first checks the type of the number (integer or floating point) and then formats
* and prints it to the buffer. Special cases like zero, NaN (Not a Number), and Infinity
* are handled separately. For floating point numbers, different formatting rules apply
* depending on the value's magnitude and whether it's an integer-like value.
*
* Memory management:
* - Uses buf_append to ensure there is enough space in the buffer before writing.
* - Updates the buffer's end position (buf->end) according to the length of the printed number.
* - Returns 0 if there is an issue with buffer allocation during the process.
*
* Example usage:
* \code
* yaml_t num_node;
* num_node.vtype = YAML_TYPE_FLOAT;
* num_node.value.float_ = 3.14;
* BUFFER buf;
* // Assume buf is properly initialized
* int result = print_number(&num_node, &buf);
* if (result) {
* // The number was successfully printed to the buffer
* } else {
* // There was an error during the printing process, likely buffer allocation
* }
* \endcode
*/
static int print_number(yaml_t yaml, BUFFER* buf)
{
double num = yaml->value.float_;
int len = 0;
/* Number is 0 */
if (num == 0)
{
if (!buf_append(1)) return 0;
buf_putc('0');
}
/* The number type is an integer type */
else if (yaml->vtype == YAML_TYPE_INT)
{
if (!buf_append(20)) return 0;
len = sprintf(&buf_end(), "%d", (int)yaml->value.int_);
buf->end += len;
}
/* The number type is a floating point type */
else if (yaml->vtype == YAML_TYPE_FLOAT)
{
if (!buf_append(64)) return 0;
/* use full transformation within bounded space */
if (isnan(num))
{
buf_putc('.');
buf_putc('n');
buf_putc('a');
buf_putc('n');
}
else if (isinf(num))
{
if (num < 0) buf_putc('-');
buf_putc('.');
buf_putc('i');
buf_putc('n');
buf_putc('f');
}
/* Format as fixed-point if integer-like and within range */
else if (fabs(floor(num) - num) <= DBL_EPSILON && fabs(num) < 1.0e60)
{
len = sprintf(&buf_end(), "%.1lf", num);
}
/* Use exponential notation for very large/small numbers */
else if (fabs(num) < 1.0e-6 || fabs(num) > 1.0e9)
{
len = sprintf(&buf_end(), "%e", num);
}
/* Default floating-point formatting */
else
{
len = sprintf(&buf_end(), "%lf", num);
// Remove trailing zeros after decimal point
while (len > 0 && (&buf_end())[len-1] == '0' && (&buf_end())[len-2] != '.') len--; /* remove the invalid 0 in the decimal part */
}
buf->end += len;
}
/* Not of number type */
else return 0;
return 1;
}
/**
* \brief Print a YAML sequence node to the buffer.
* \param[in] yaml: Pointer to the YAML sequence node.
* \param[in] buf: Pointer to the buffer.
* \param[in] depth: The depth of the node in the YAML structure.
* \param[in] flag: Printing flags for the YAML node.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function prints a YAML sequence node to a buffer. It first checks if the sequence
* is empty. If not, it determines whether to use the streaming style based on the flag.
* In the streaming style, it directly prints the child nodes with appropriate commas.
* In the non-streaming style, it adds indentation according to the depth, prints a
* '-' indicator for each child node, and ensures proper newline characters are added.
* Finally, it adds the closing ']' character and necessary indentation in the streaming
* style.
*
* Memory management:
* - Relies on buf_append to ensure there is enough space in the buffer for writing.
* - Updates the buffer's end position (buf->end) as characters are added.
* - Returns 0 if there is an issue with buffer allocation during the process.
*
* Example usage:
* \code
* yaml_t seq_node;
* // Assume seq_node is a valid YAML sequence node with some child nodes.
* BUFFER buf;
* int depth = 0;
* int flag = YAML_F_DFLOW;
* // Initialize buf properly.
* int result = print_sequence(&seq_node, &buf, depth, flag);
* if (result) {
* // The YAML sequence node was successfully printed to the buffer.
* } else {
* // There was an error during the printing process, likely buffer allocation.
* }
* \endcode
*/
static int print_sequence(yaml_t yaml, BUFFER* buf, int depth, int flag)
{
int i = 0, count = 0;
yaml_t child = yaml->value.child_;
// If the sequence node has no children (empty array), return 1 for success.
if (!child)
{
return 1;
}
// If the YAML_F_DFLOW flag is set (streaming style).
if (flag & YAML_F_DFLOW)
{
if (!buf_append(1)) return 0;
buf_putc('[');
}
else
{
// If not in streaming style and the buffer is not empty, add a newline character.
if (buf->end != 0)
{
if (!buf_append(1)) return 0;
buf_putc('\n');
}
}
// Traverse and print the child nodes of the sequence node.
while (child)
{
if (flag & YAML_F_DFLOW)
{
// In streaming style, directly print the child node.
if (!print_yaml(child, buf, depth + 1, flag)) return 0;
}
else
{
// In non-streaming style, add indentation according to the depth.
if (!buf_append(depth * 2)) return 0;
for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' ');
// Print the indicator '-'.
if (!buf_append(2)) return 0;
buf_putc('-');
buf_putc(' ');
// Print the child node.
if (!print_yaml(child, buf, depth + 1, flag)) return 0;
// If the last character in the buffer after printing the child node is not a newline character, add a newline character.
if (buf->address[buf->end - 1] != '\n')
{
if (!buf_append(1)) return 0;
buf_putc('\n');
}
}
// Move to the next child node.
child = child->next;
// If there is a next child node and in streaming style, add a comma and a space.
if (child)
{
if (flag & YAML_F_DFLOW)
{
if (!buf_append(2)) return 0;
buf_putc(',');
buf_putc(' ');
}
}
}
// If in streaming style, add the closing character ']' and add indentation if necessary.
if (flag & YAML_F_DFLOW)
{
if (buf->address[buf->end - 1] == '\n')
{
if (!buf_append(depth * 2)) return 0;
for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' ');
}
if (!buf_append(1)) return 0;
buf_putc(']');
}
return 1;
}
/**
* \brief Print the alias of a YAML node to the buffer.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] buf: Pointer to the buffer.
* \param[in] depth: The depth of the node in the YAML structure.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function prints the alias of a YAML node to the buffer, prepended with an '&'
* character. If the node is not a sequence or mapping, it adds a newline followed by
* indentation spaces based on the node's depth in the YAML structure. This ensures proper
* formatting for scalar values with aliases in block-style YAML.
*
* Example usage:
* \code
* yaml_t node;
* node.alias = "my_anchor";
* node.vtype = YAML_TYPE_STRING;
*
* BUFFER buf;
* // Initialize buffer...
*
* int result = print_alias(&node, &buf, 2);
* if (result) {
* // Buffer now contains "&my_anchor\n " (for a string node at depth 2)
* }
* \endcode
*/
static int print_alias(yaml_t yaml, BUFFER* buf, int depth)
{
if (!buf_append(1)) return 0;
buf_putc('&');
// Print the alias string with proper escaping and quoting
if (!print_string_buffer(yaml->alias, buf)) return 0;
// Add newline and indentation for scalar values (non-sequence/mapping)
if (yaml->vtype != YAML_TYPE_SEQUENCE && yaml->vtype != YAML_TYPE_MAPPING)
{
if (!buf_append(depth * 2 + 1)) return 0;
buf_putc('\n');
for (int i = 0; i < depth; i++)
{
buf_putc(' ');
buf_putc(' ');
}
}
return 1;
}
/**
* \brief Print a YAML mapping node to the buffer.
* \param[in] yaml: Pointer to the YAML mapping node.
* \param[in] buf: Pointer to the buffer.
* \param[in] depth: The depth of the node in the YAML structure.
* \param[in] flag: Printing flags for the YAML node.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function prints a YAML mapping node (key-value pairs) to the buffer in either
* block style or flow style based on the YAML_F_DFLOW flag. It handles both simple
* string keys and complex keys (sequences/mappings) by prefixing them with '?'.
* For each key-value pair, it ensures proper indentation, colons, and newlines according
* to the specified style and depth.
*
* Memory management:
* - Uses buf_append to ensure buffer capacity before writing.
* - Returns 0 immediately if any buffer allocation fails.
* - Modifies buf->end to reflect the new buffer position after writes.
*
* Example usage:
* \code
* yaml_t map_node;
* // Initialize map_node as a mapping with child nodes...
*
* BUFFER buf;
* // Initialize buffer...
*
* int result = print_mapping(&map_node, &buf, 1, 0);
* if (result) {
* // Buffer now contains the mapping formatted in block style:
* // key1: value1
* // key2: value2
* }
* \endcode
*/
static int print_mapping(yaml_t yaml, BUFFER* buf, int depth, int flag)
{
int i;
yaml_t child = yaml->value.child_;
// Handle empty mapping
if (!child)
{
return 1;
}
// Flow style ({ ... })
if (flag & YAML_F_DFLOW)
{
if (!buf_append(1)) return 0;
buf_putc('{');
}
// Block style (indented with newlines)
else
{
if (buf->end != 0)
{
if (!buf_append(1)) return 0;
buf_putc('\n');
}
}
// Process each key-value pair
while (child)
{
if (flag & YAML_F_DFLOW)
{
// Flow style formatting (handled later)
}
else
{
// Block style indentation
if (!buf_append(depth * 2)) return 0;
for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' ');
}
// Print key
if (child->ktype == YAML_KTYPE_STRING)
{
if (!print_string_buffer(child->key, buf)) return 0;
}
else if (child->ktype == YAML_KTYPE_COMPLEX)
{
yaml_t key = child->complex;
if (key->vtype == YAML_TYPE_SEQUENCE || key->vtype == YAML_TYPE_MAPPING)
{
if (!buf_append(1)) return 0;
buf_putc('?');
if (!print_yaml(key, buf, depth + 1, flag)) return 0;
}
else
{
if (!buf_append(2)) return 0;
buf_putc('?');
buf_putc(' ');
if (!print_yaml(key, buf, depth + 1, flag)) return 0;
}
// Re-indent for value after complex key
if (!buf_append(depth * 2)) return 0;
for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' ');
}
// Key-value separator
if (!buf_append(2)) return 0;
buf_putc(':');
buf_putc(' ');
// Handle aliases
if (child->alias)
{
if (!print_alias(child, buf, depth + 1)) return 0;
if (flag & YAML_F_DFLOW)
{
if (!buf_append(1)) return 0;
buf_putc(' ');
}
}
// Print value
if (!print_yaml(child, buf, depth + 1, flag)) return 0;
// Ensure proper newline in block style
if (flag & YAML_F_DFLOW)
{
}
else
{
if (buf->address[buf->end - 1] != '\n')
{
if (!buf_append(1)) return 0;
buf_putc('\n');
}
}
// Move to next key-value pair
child = child->next;
// Add comma in flow style if more pairs follow
if (child)
{
if (flag & YAML_F_DFLOW)
{
if (!buf_append(2)) return 0;
buf_putc(',');
buf_putc(' ');
}
}
}
// Close flow style mapping
if (flag & YAML_F_DFLOW)
{
if (buf->address[buf->end - 1] == '\n')
{
if (!buf_append(depth * 2)) return 0;
for (i = 0; i < depth; i++) buf_putc(' '), buf_putc(' ');
}
if (!buf_append(1)) return 0;
buf_putc('}');
}
return 1;
}
/**
* \brief Print a YAML reference node to the buffer.
* \param[in] yaml: Pointer to the YAML reference node.
* \param[in] buf: Pointer to the buffer.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function is used to print a YAML reference node to the buffer. It first appends
* an asterisk (*) to the buffer. Then, it calls the print_string_buffer function to
* print the alias of the child node pointed to by the reference. If any buffer
* allocation fails during these operations, it returns 0.
*
* Memory management:
* - Relies on the buf_append function to add characters to the buffer. If buf_append
* fails (indicating a buffer allocation issue), the function returns 0.
* - Calls print_string_buffer which also manages buffer operations related to printing
* the alias string. If print_string_buffer fails, this function returns 0.
*
* Example usage:
* \code
* yaml_t ref_node;
* ref_node.vtype = YAML_TYPE_REFERENCE;
* ref_node.value.child_ = some_yaml_node_with_alias;
*
* BUFFER buf;
* // Assume buf is properly initialized.
*
* int result = print_reference(&ref_node, &buf);
* if (result) {
* // The YAML reference node was successfully printed to the buffer, likely in the form of "*alias".
* } else {
* // There was an error during the printing process, probably buffer allocation.
* }
* \endcode
*/
static int print_reference(yaml_t yaml, BUFFER* buf)
{
if (!buf_append(1)) return 0;
buf_putc('*');
return print_string_buffer(yaml->value.child_->alias, buf);
}
/**
* \brief Print a YAML document node to the buffer.
* \param[in] yaml: Pointer to the YAML document node.
* \param[in] buf: Pointer to the buffer.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function is responsible for printing a YAML document node to the buffer.
* It first checks if the last character in the buffer is not a newline character.
* If so, it appends a newline character to the buffer. Then, it appends three hyphens
* ("---") to the buffer. Finally, it calls the print_yaml function with the YAML node,
* buffer, a depth of -1, and a flag of 0. If any buffer allocation fails during these
* operations, the function returns 0.
*
* Memory management:
* - Relies on the buf_append function to add characters to the buffer. If buf_append
* fails (indicating a buffer allocation issue), the function returns 0.
*
* Example usage:
* \code
* yaml_t doc_node;
* // Assume doc_node is a valid YAML document node.
* BUFFER buf;
* // Initialize buf properly.
*
* int result = print_document(&doc_node, &buf);
* if (result) {
* // The YAML document node was successfully printed to the buffer, starting with "---".
* } else {
* // There was an error during the printing process, likely buffer allocation.
* }
* \endcode
*/
static int print_document(yaml_t yaml, BUFFER* buf)
{
if (buf->address[buf->end - 1] != '\n')
{
if (!buf_append(1)) return 0;
buf_putc('\n');
}
if (!buf_append(4)) return 0;
buf_putc('-');
buf_putc('-');
buf_putc('-');
buf_putc('\n');
return print_yaml(yaml, buf, -1, 0);
}
/**
* \brief Print a YAML node to the buffer based on its type.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] buf: Pointer to the buffer.
* \param[in] depth: The depth of the node in the YAML structure (used for indentation in some cases).
* \param[in] flag: Printing flags for the YAML node.
* \return 1 for success, 0 for failure (such as buffer allocation failure, etc.).
*
* This function is a central dispatcher that prints a YAML node to the buffer according to its type.
* It handles all supported YAML data types, including scalars (null, bool, int, float, string, date),
* collections (sequence, mapping), references, and documents. Each type is processed by calling the
* corresponding print function. For example, sequences and mappings are printed with proper indentation
* based on their depth in the YAML structure.
*
* Example usage:
* \code
* yaml_t node;
* // Initialize node with some YAML value...
*
* BUFFER buf;
* // Initialize buffer...
*
* int result = print_yaml(&node, &buf, 0, 0);
* if (result) {
* // Node was successfully printed to the buffer
* }
* \endcode
*/
static int print_yaml(yaml_t yaml, BUFFER* buf, int depth, int flag)
{
switch (yaml->vtype)
{
case YAML_TYPE_NULL:
{
/* Print "null" */
if (!buf_append(4)) return 0;
buf_putc('n');
buf_putc('u');
buf_putc('l');
buf_putc('l');
break;
}
case YAML_TYPE_BOOL:
{
if (yaml->value.bool_ == YAML_FALSE)
{
/* Print "false" */
if (!buf_append(5)) return 0;
buf_putc('f');
buf_putc('a');
buf_putc('l');
buf_putc('s');
buf_putc('e');
}
else
{
/* Print "true" */
if (!buf_append(4)) return 0;
buf_putc('t');
buf_putc('r');
buf_putc('u');
buf_putc('e');
}
break;
}
case YAML_TYPE_INT:
case YAML_TYPE_FLOAT: return print_number(yaml, buf);
case YAML_TYPE_STRING: return print_string(yaml, buf);
case YAML_TYPE_DATE: return print_date(yaml, buf);
case YAML_TYPE_SEQUENCE: return print_sequence(yaml, buf, depth, flag);
case YAML_TYPE_MAPPING: return print_mapping(yaml, buf, depth, flag);
case YAML_TYPE_REFERENCE: return print_reference(yaml, buf);
case YAML_TYPE_DOCUMENT: return print_document(yaml, buf);
}
return 1;
}
/**
* \brief Convert a YAML node to a string representation and allocate memory for it.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] preset: Preset size for the buffer. If less than 1, it will be set to 1.
* \param[in] len: Pointer to an integer to store the length of the resulting string.
* \param[in] flag: Flags for the printing process.
* \return A pointer to the allocated string representing the YAML node, or NULL on failure.
*
* This function converts a YAML node into a string representation and allocates memory for the result.
* It first checks if the preset buffer size is valid and allocates memory for the buffer. If the YAML node
* is a document type, it iterates through its child nodes, printing each non-null child node and adding
* appropriate separators between them. For other node types, it directly calls print_yaml to print the node.
* After printing, it null-terminates the buffer and sets the length of the resulting string if the length
* pointer is not NULL. In case of any buffer allocation failure during the process, it frees the allocated
* buffer and returns NULL.
*
* Memory management:
* - Allocates memory for the buffer using malloc.
* - Frees the buffer in case of failure using free.
* - Updates the buffer's size, end position, and returns the pointer to the buffer's address as the result.
*
* Example usage:
* \code
* yaml_t node;
* // Initialize node with some YAML value...
* int length;
* char* result = yaml_dumps(&node, 100, &length, 0);
* if (result) {
* // result contains the string representation of the YAML node
* // length stores the length of the resulting string
* }
* \endcode
*/
char* yaml_dumps(yaml_t yaml, int preset, int* len, int flag)
{
BUFFER p, *buf = &p;
if (!yaml) return NULL;
/* Allocate buffer and initialize */
if (preset < 1) preset = 1;
p.address = (char*)malloc(preset);
if (!p.address) return NULL;
p.size = preset;
p.end = 0;
if (yaml->vtype == YAML_TYPE_DOCUMENT)
{
yaml_t child = yaml->value.child_;
while (child)
{
/* Start printing yaml */
if (child->vtype != YAML_TYPE_NULL)
{
if (!print_yaml(child, &p, 0, flag)) goto FAIL;
}
child = child->next;
if (child)
{
if (buf->address[buf->end - 1] != '\n')
{
if (!buf_append(1)) goto FAIL;
buf_putc('\n');
}
if (!buf_append(4)) goto FAIL;
buf_putc('-'), buf_putc('-'), buf_putc('-'), buf_putc('\n');
}
}
}
else
{
/* Start printing yaml */
if (!print_yaml(yaml, &p, 0, flag)) goto FAIL;
}
/* At the end of the text */
if (!buf_append(1)) goto FAIL;
buf_end() = 0;
/* Output conversion length */
if (len) *len = p.end;
return p.address;
FAIL:
free(p.address);
return NULL;
}
/**
* \brief Dump a YAML node to a file.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] filename: Name of the file to write to.
* \return The number of characters written to the file, or -1 on failure.
*
* This function takes a YAML node and writes its string representation to a file.
* It first converts the YAML node to a string using yaml_dumps. If the conversion
* is successful, it opens the specified file in write mode. If the file is opened
* successfully, it writes the string to the file, closes the file, and frees the
* memory allocated for the string. It returns the number of characters written to
* the file, or -1 if any step fails (such as conversion failure, file open failure, etc.).
*
* Memory management:
* - Calls yaml_dumps which allocates memory for the string representation of the YAML node.
* - Frees the memory of the string after writing it to the file using free.
* - Opens and closes the file using fopen and fclose respectively.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* // Initialize my_yaml_node with some YAML data...
* char file_name[] = "output.yaml";
* int written_count = yaml_file_dump(&my_yaml_node, file_name);
* if (written_count != -1) {
* // The YAML node was successfully dumped to the file, and written_count is the number of characters written.
* } else {
* // There was an error during the process, such as conversion or file operation failure.
* }
* \endcode
*/
int yaml_file_dump(yaml_t yaml, char* filename)
{
FILE* f;
char* out;
int len;
if (!yaml) return -1;
/* Convert characters */
out = yaml_dumps(yaml, 0, &len, 0);
if (!out) return -1;
/* Open file */
f = fopen(filename, "w");
if (!f)
{
free(out);
return -1;
}
/* Write the string to the file */
fwrite(out, 1, len, f);
/* Close file */
fclose(f);
/* free the sapce of string */
free(out);
return len;
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Loads functions
//////////////////////////////////////////////////////////////////////////////////////////////
/**
* \brief Target base type flags for YAML parser
* These flags define the fundamental parsing modes for different YAML value types.
* They determine how the parser interprets and processes the incoming text.
*/
#define YAML_PFLAG_TGTBASE_NULL 0 /* Null/undefined base type */
#define YAML_PFLAG_TGTBASE_SINGLE 1 /* Single-quoted string ('...') */
#define YAML_PFLAG_TGTBASE_DOUBLE 2 /* Double-quoted string ("...") */
#define YAML_PFLAG_TGTBASE_SCALAR 3 /* Literal block scalar (|) - preserves line breaks */
#define YAML_PFLAG_TGTBASE_FOLD 4 /* Folded block scalar (>) - converts line breaks to spaces */
#define YAML_PFLAG_TGTBASE_SEQUENCE 5 /* Sequence/array enclosed in [] */
#define YAML_PFLAG_TGTBASE_MAPPING 6 /* Mapping/object enclosed in {} */
/**
* \brief String block handling modifiers
* These flags control how line breaks and trailing spaces are processed in block scalars.
* They apply to both literal (|) and folded (>) block scalar types.
*/
// | style:
// 0: | - Preserve line breaks, strip one trailing line
// 1: |n - Preserve line breaks, indent by n spaces
// 2: |+ - Preserve line breaks and all trailing lines
// 3: |- - Strip all trailing line breaks
// > style:
// 0: > - Convert line breaks to spaces, strip one trailing line
// 1: >n - Convert line breaks to spaces, indent by n spaces
// 2: >+ - Convert line breaks to spaces, preserve trailing lines as spaces
// 3: >- - Convert line breaks to spaces, strip all trailing spaces
#define YAML_PFLAG_STREXTR_NULL 0 /* No special handling */
#define YAML_PFLAG_STREXTR_NUM 1 /* Indentation specified by number */
#define YAML_PFLAG_STREXTR_ADD 2 /* Preserve all trailing empty lines */
#define YAML_PFLAG_STREXTR_SUB 3 /* Strip all trailing empty lines */
/**
* \brief Explicit type conversion flags
* These flags specify the target data type when explicitly converting YAML values.
* They correspond to YAML's built-in tag types (e.g., !!int, !!bool).
*/
#define YAML_PFLAG_TARGET_NONE 0 /* No explicit type conversion */
#define YAML_PFLAG_TARGET_NULL 1 /* Null value (!!null) */
#define YAML_PFLAG_TARGET_BOOL 2 /* Boolean value (!!bool) */
#define YAML_PFLAG_TARGET_INT 3 /* Integer value (!!int) */
#define YAML_PFLAG_TARGET_FLOAT 4 /* Floating-point value (!!float) */
#define YAML_PFLAG_TARGET_STR 5 /* String value (!!str) */
#define YAML_PFLAG_TARGET_SEQ 6 /* Sequence/array (!!seq) */
#define YAML_PFLAG_TARGET_MAP 7 /* Mapping/object (!!map) */
#define YAML_PFLAG_TARGET_BINARY 8 /* Binary data (!!binary) */
#define YAML_PFLAG_TARGET_TIMESTAMP 9 /* Timestamp (!!timestamp) */
#define YAML_PFLAG_TARGET_SET 10 /* Set (!!set) */
#define YAML_PFLAG_TARGET_OMAP 11 /* Ordered map (!!omap) */
#define YAML_PFLAG_TARGET_PAIRS 12 /* Pairs (!!pairs) */
typedef struct
{
union
{
// Integer part
int int_;
struct
{
// The basic type of the target value
int tgtbase : 8; // YAML_PFLAG_TGTBASE_XXX
// Extra attributes of the string
int strextr : 4; // YAML_PFLAG_STREXTR_XXX
// Interval closure flag, such as quotation marks, square brackets, curly braces closure
int interval : 4; // 0 - Unclosed, 1 - Already closed
// Takes effect when the extra attribute of the string is a number, records the number
int space_n : 8; // 0 - 255 ; 0 represents 256
// Force conversion target type
int target : 4; // YAML_PFLAG_TARGET_XXX
};
} flag;
// Sentinel, responsible for exploring the text forward
const char *sentinel;
// Records the start and end positions of the valid value
const char *scope_s;
const char *scope_e;
// Records the start and end positions of the valid part of each parsed line
const char *line_s;
const char *line_e;
// Records the number of empty lines before each valid line parsed
int lines;
// Child depth, if the current value needs to wrap, it is responsible for recording the depth that still belongs to the value range
int deep_chd;
// Current depth, the current line depth when parsing each line
int deep_cur;
ANCHOR *anchor;
} PARSER;
static const char* parse_value(yaml_t yaml, const char* text, int deep, char token, PARSER *parser);
/**
* \brief Skip over whitespace characters in the text.
* \param[in] in: Pointer to the text to skip.
* \return Pointer to the position after skipping whitespace.
*/
static const char* skip(const char* in)
{
while (*in && (unsigned char)*in <= ' ' && *in != '\n')
{
in++;
}
return in;
}
/**
* \brief Skip backwards over whitespace characters in the text.
* \param[in] text: Pointer to the text to skip backwards.
* \param[in] base: Pointer to the base position to stop at.
* \return Pointer to the position after skipping backwards over whitespace.
*/
static const char* rskip(const char* text, const char* base)
{
while ((text >= base) && (*text == ' ' || *text == '\r' || *text == '\t'))
{
text--;
}
return text;
}
/**
* \brief Skip to the end of the line in the text.
* \param[in] in: Pointer to the text to skip.
* \return Pointer to the position at the end of the line.
*/
static const char* skip_ln(const char* in)
{
while (*in && *in != '\n') in++;
return in;
}
/**
* \brief Find the position of a character in the text up to a newline.
* \param[in] str: Pointer to the text to search.
* \param[in] ch: The character to find.
* \return Pointer to the position of the character if found, or NULL if not found.
*/
static char *linechr(const char *str, int ch)
{
while (*str != 0 && *str != '\n' && *str != ch)
{
str++;
}
if (*str == ch)
{
return (char *) str;
}
else
{
return NULL;
}
}
/**
* \brief Check if the given character is a line break.
* \param[in] str: Pointer to the character to check.
* \return 1 if it's a line break (\n or \r\n), 2 if it's \r\n, 0 if not a line break.
*/
static int isln(char *str)
{
if (str[0] == '\n') return 1;
else if (str[0] == '\r')
{
if (str[1] == '\n') return 2;
}
return 0;
}
static char *yaml_strndup(const char *str, int len)
{
char *base = (char *)malloc(len + 1);
if (base)
{
memcpy(base, str, len);
base[len] = 0;
}
return base;
}
/**
* \brief Compare two strings up to n characters, considering case-insensitive comparison.
* \param[in] s1: First string to compare.
* \param[in] s2: Second string to compare.
* \param[in] n: Maximum number of characters to compare.
* \return 1 if the strings match up to n characters (case-insensitive), 0 otherwise.
*/
static int strcncmp(const char *s1, const char *s2, unsigned int n)
{
int flag = 0;
unsigned int i = 1;
if (!s1 || !s2 || n == 0) return 0;
if (tolower(*s1) != tolower(*s2)) return 0;
if (islower(*s1)) flag = -1;
s1++, s2++;
while (i < n && *s1 && *s2 && tolower(*s1) == tolower(*s2))
{
if (flag < 0)
{
if (isupper(*s1)) return 0;
}
else if (flag > 0)
{
if (islower(*s1)) return 0;
}
else
{
if (islower(*s1)) flag = -1;
else flag = 1;
}
s1++, s2++, i++;
}
if (i != n) return 0;
return 1;
}
/**
* \brief Pad a character to a buffer.
* \param[in] base: Pointer to the buffer.
* \param[in] count: Number of characters to pad.
* \param[in] pad: Character to pad.
*/
static void yaml_str_padding(char *base, int count, char pad)
{
for (int i = 0; i < count; i++)
{
base[i] = pad;
}
}
/**
* \brief Append a string to the value of a YAML node and reallocate memory if needed.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] ap: Pointer to the string to append.
* \param[in] count: Length of the string to append.
* \return Pointer to the updated string in the YAML node, or NULL on failure.
*
* This function appends a specified string (or part of it) to the existing string value
* of a YAML node. It first reallocates memory to accommodate the new size (existing size
* plus the count of characters to append, plus one for the null terminator). If the memory
* allocation is successful, it copies the specified number of characters from the source
* string to the end of the existing string and null-terminates the result. The function
* returns a pointer to the updated string or NULL if memory allocation fails.
*
* Memory management:
* - Uses realloc to resize the existing string buffer. If realloc fails, the original
* buffer remains unchanged and the function returns NULL.
* - Updates the YAML node's string pointer to the new buffer if realloc is successful.
*
* Example usage:
* \code
* yaml_t node;
* node.vtype = YAML_TYPE_STRING;
* node.value.string_ = strdup("Hello");
* node.size = 5;
*
* const char* to_append = " World!";
* int append_length = strlen(to_append);
*
* char* result = yaml_str_append(&node, to_append, append_length);
* if (result) {
* // node.value.string_ now contains "Hello World!"
* // node.size is updated to 12
* }
* \endcode
*/
static char *yaml_str_append(yaml_t yaml, const char *ap, int count)
{
char* address = NULL;
// Reallocate memory to accommodate the existing string plus the new content
address = (char*)realloc(yaml->value.string_, yaml->size + count + 1);
if (!address) return NULL;
// Copy the new content to the end of the existing string
if (ap) memcpy(address + yaml->size, ap, count);
// Null-terminate the string
address[yaml->size + count] = 0;
return address;
}
/**
* \brief Parse a 4-digit hexadecimal number from a string.
* \param[in] str: Pointer to the string containing the hexadecimal number.
* \return The parsed unsigned integer value, or 0 on failure.
*
* This function parses a 4-digit hexadecimal number from the given string. It checks each character
* of the string to determine if it represents a valid hexadecimal digit (0-9, A-F, a-f). For each valid
* digit, it calculates its numerical value and shifts the accumulated value left by 4 bits to make room
* for the next digit. If any character is not a valid hexadecimal digit, the function returns 0.
*
* Memory management:
* - The function does not allocate or free any memory. It only reads from the input string.
*
* Example usage:
* \code
* const char* hex_str = "1A2B";
* unsigned int result = parse_hex4(hex_str);
* // result will be the integer value corresponding to the hexadecimal number "1A2B"
* \endcode
*/
static unsigned int parse_hex4(const char* str)
{
unsigned int h = 0;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
h = h << 4; str++;
if (*str >= '0' && *str <= '9') h += (*str) - '0';
else if (*str >= 'A' && *str <= 'F') h += 10 + (*str) - 'A';
else if (*str >= 'a' && *str <= 'f') h += 10 + (*str) - 'a';
else return 0;
return h;
}
/**
* \brief Convert a UTF-16 surrogate pair or a single Unicode character in the input string to UTF-8 and store in the output buffer.
* \param[in] in: Pointer to the input string.
* \param[in] out: Pointer to the output buffer.
*/
static void yaml_utf(const char **in, char **out)
{
const char* ptr = *in;
char* ptr2 = *out;
int len = 0;
unsigned int uc, uc2;
unsigned char mask_first_byte[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
uc = parse_hex4(ptr + 1); ptr += 4; /* get the unicode char. */
if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) return; /* check for invalid. */
if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */
{
if (ptr[1] != '\\' || ptr[2] != 'u') return; /* missing second-half of surrogate */
uc2 = parse_hex4(ptr + 3);
ptr += 6;
if (uc2 < 0xDC00 || uc2>0xDFFF) return; /* invalid second-half of surrogate */
uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF));
}
len = 4;
if (uc < 0x80) len = 1;
else if (uc < 0x800) len = 2;
else if (uc < 0x10000) len = 3;
ptr2 += len;
switch (len)
{
case 4: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 3: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 2: *--ptr2 = ((uc | 0x80) & 0xBF); uc >>= 6;
case 1: *--ptr2 = (uc | mask_first_byte[len]);
}
ptr2 += len;
}
/**
* \brief Parse a string from the text within quotes and allocate memory for it.
* \param[in] buf: Pointer to a pointer that will hold the parsed string.
* \param[in] text: Pointer to the text to parse.
* \param[in] quote: The quote character (e.g., '"' or '\'') that encloses the string.
* \return Pointer to the position after the parsed string in the text, or NULL on failure.
*
* This function parses a quoted string from the given text, handling escape sequences and
* UTF-8 encoding. It first calculates the length of the string by scanning through the text
* until it finds the closing quote or a newline. It then allocates memory for the parsed string,
* copies the text into this memory while processing escape sequences (such as \n, \t, \uXXXX),
* and null-terminates the result. If memory allocation fails, it sets an error and returns NULL.
*
* Memory management:
* - Allocates memory for the parsed string using malloc. The caller is responsible for freeing
* this memory when it is no longer needed.
* - Returns NULL and sets an error if memory allocation fails.
*
* Example usage:
* \code
* const char* text = "\"Hello,\\nWorld!\"";
* char* parsed_str;
* const char* end = parse_string_buffer(&parsed_str, text, '"');
* if (end) {
* // parsed_str contains "Hello,\nWorld!"
* // end points to the character after the closing quote
* }
* \endcode
*/
static const char* parse_string_buffer(char** buf, const char* text, char quote)
{
const char* ptr = text + 1;
char* ptr2;
char* out;
int len = 0;
*buf = NULL;
/* Calculate the length of the string by scanning through the text */
while (*ptr && *ptr != '\n' && *ptr != quote)
{
if (*ptr++ == '\\') ptr++; /* skip escaped quotes. */
len++;
}
/* Allocate storage space based on the calculated string length */
out = (char*)malloc(len + 1);
if (!out)
{
E(YAML_E_MEMORY, text);
}
/* Copy the text to the allocated memory, handling escape sequences */
ptr = text + 1;
ptr2 = out;
while (*ptr && *ptr != '\n' && *ptr != quote)
{
/* Normal character */
if (*ptr != '\\') { *ptr2++ = *ptr++; }
/* Escape sequence */
else
{
ptr++;
if (*ptr == 'b') { *ptr2++ = '\b'; }
else if (*ptr == 'f') { *ptr2++ = '\f'; }
else if (*ptr == 'n') { *ptr2++ = '\n'; }
else if (*ptr == 'r') { *ptr2++ = '\r'; }
else if (*ptr == 't') { *ptr2++ = '\t'; }
else if (*ptr == 'u') { yaml_utf(&ptr, &ptr2); } /* Handle UTF-16 code point */
else { *ptr2++ = *ptr; } /* Unrecognized escape - treat as literal */
ptr++;
}
}
/* Null-terminate the string */
*ptr2 = 0;
/* Move past the closing quote */
if (*ptr == quote) ptr++;
*buf = out;
return ptr;
}
/**
* \brief Parse a string line in the YAML text and handle string formatting and storage.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] text: Pointer to the YAML text to parse.
* \param[in] parser: Pointer to the PARSER structure for parsing state.
* \return Pointer to the position after the parsed string line in the text, or NULL on failure.
*
* This function parses a string line in the YAML text and manages the formatting and storage
* of the string within the YAML node. It first adjusts the sentinel position to find the valid
* part of the line. Then, it checks the value type of the YAML node. If it's NULL or a string,
* it handles the string parsing. For a NULL target base type, it appends the string and manages
* padding with newlines or spaces. For SCALAR or FOLD target base types, it calculates the
* appropriate number of leading spaces, allocates memory, inserts padding characters, and
* copies the valid characters. If the value type doesn't match the expected type, it logs a
* debug message and returns NULL.
*
* Memory management:
* - Calls yaml_str_append to allocate and append characters to the string in the YAML node.
* - If memory allocation fails (yaml_str_append returns NULL), it calls E with YAML_E_MEMORY
* and returns NULL.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* yaml_text = "my_string: \"Hello, World!\"";
* PARSER my_parser;
* // Assume my_parser is properly initialized.
* const char* end = parse_string_line(&my_yaml_node, yaml_text, &my_parser);
* if (end) {
* // The string line was successfully parsed and stored in the YAML node.
* } else {
* // There was an error during parsing, likely related to memory allocation or type mismatch.
* }
* \endcode
*/
static const char* parse_string_line(yaml_t yaml, const char* text, PARSER *parser)
{
// Move the sentinel back to find the valid part.
parser->line_e = rskip(parser->sentinel - 1, parser->line_s);
// Check if the value type is not NULL or already a string before parsing.
if (yaml->vtype == YAML_TYPE_NULL || yaml->vtype == YAML_TYPE_STRING)
{
// If the target base type of the string is NULL (ordinary string parsing).
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL)
{
int pad = 0; // Padding count.
char *ss = NULL; // Temporary string storage.
// If the string_ in the YAML node is not assigned yet, it's the first line being parsed.
if (yaml->value.string_)
{
// Record the number of empty lines before the current line, which will be filled with newlines.
pad = parser->lines - 1;
// Ensure the padding count is at least 1 (at least one newline).
if (pad <= 0) pad = 1;
}
// Append the string in a unified way and allocate memory.
ss = yaml_str_append(yaml, parser->line_s - pad, parser->line_e - parser->line_s + 1 + pad);
if (!ss) E(YAML_E_MEMORY, parser->sentinel);
// If the string_ is not assigned yet, it's the first line being parsed.
if (yaml->value.string_)
{
// If there are empty lines, fill them with newlines.
if (parser->lines > 1) for (int i = 0; i < pad; i++) ss[yaml->size + i] = '\n';
// If no empty lines, connect the lines with a space.
else ss[yaml->size] = ' ';
}
// Update the string information in the YAML node.
yaml->vtype = YAML_TYPE_STRING;
yaml->value.string_ = ss;
yaml->size += (parser->line_e - parser->line_s + pad + 1);
}
// If the target base type is SCALAR or FOLD (handles formatted strings).
else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR || parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD)
{
char *ss = NULL; // Temporary string storage.
int space_n = 0; // Number of leading spaces at the start of the line.
int nline = parser->lines - 1; // Record the number of leading empty lines.
int ilen = 0; // Insertion length.
char token = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR ? '\n' : ' ');
// If the string has a numeric extra attribute for leading spaces.
if (parser->flag.strextr == YAML_PFLAG_STREXTR_NUM)
{
if (// The first line of the string, each with the specified amount of space added
yaml->value.string_ == NULL ||
// Instead of collapsing a multiline string, only the first line of the string needs a specified amount of whitespace and no other lines
// The scalar format, on the other hand, adds a specified number of Spaces to each line capital
parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR
)
{
// The space count is in the range [1, 256], 0 represents 256.
space_n = parser->flag.space_n;
if (space_n == 0) space_n = 256;
}
}
// Add leading spaces.
ilen = (space_n + 1) * nline + space_n;
if (ilen > 0)
{
// Allocate memory for the string.
ss = yaml_str_append(yaml, NULL, ilen);
if (!ss) E(YAML_E_MEMORY, parser->sentinel);
yaml->value.string_ = ss;
// Fill in the padding characters.
ss += yaml->size;
// For SCALAR formatted strings, pad each line with the specified number of spaces.
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR)
{
for (int i = 0; i < nline; i++)
{
yaml_str_padding(ss, space_n, ' ');
ss[space_n] = token;
ss += (space_n + 1);
}
yaml_str_padding(ss, space_n, ' ');
}
// For folded strings, just insert the specified number of spaces.
else yaml_str_padding(ss, ilen, ' ');
// Update the string length in the YAML node.
yaml->size += ilen;
}
// If the current line depth is greater than the child depth, adjust the start position of the string.
if (parser->deep_cur > parser->deep_chd) parser->line_s -= (parser->deep_cur - parser->deep_chd);
// Calculate the length for the actual valid characters and padding.
ilen = parser->line_e - parser->line_s + 1 + 1; // and pad
// Allocate memory and copy the actual valid characters.
ss = yaml_str_append(yaml, parser->line_s, ilen);
if (!ss) E(YAML_E_MEMORY, parser->sentinel);
// Fill in the padding character.
ss[yaml->size + ilen - 1] = token;
// Update the string information in the YAML node.
yaml->vtype = YAML_TYPE_STRING;
yaml->value.string_ = ss;
// Update the string length in the YAML node.
yaml->size += ilen;
}
}
// If the value type doesn't match the expected type.
else
{
DEBUG("yaml->vtype %d\r\n", yaml->vtype);
E(YAML_E_MIX, parser->sentinel);
}
return parser->sentinel;
}
/**
* \brief Parse a string with quotes in the YAML text and handle string storage.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] text: Pointer to the YAML text to parse.
* \param[in] parser: Pointer to the PARSER structure for parsing state.
* \return Pointer to the position after the parsed string in the text, or NULL on failure.
*
* This function parses a string within quotes in the YAML text and manages its storage
* in the YAML node. It first records the start position of the valid part. Then, it
* calculates the length of the string by scanning the text, handling escape characters.
* If the closing quote is found, it marks the end of the string interval. If not, it
* adjusts the end position based on the current line. After that, it allocates memory
* for the string, copies the text (including handling empty lines and escape sequences),
* and updates the size of the string in the YAML node. In case of memory allocation
* failure or parsing errors (like unexpected characters after the quote), it returns NULL.
*
* Memory management:
* - Calls yaml_str_append to allocate memory for the string in the YAML node.
* - If yaml_str_append returns NULL (memory allocation failure), it calls E with YAML_E_MEMORY
* and returns NULL.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* yaml_text = "\"Hello, \\nWorld!\"";
* PARSER my_parser;
* // Assume my_parser is properly initialized.
* const char* end = parse_string_quote(&my_yaml_node, yaml_text, &my_parser);
* if (end) {
* // The string within quotes was successfully parsed and stored in the YAML node.
* } else {
* // There was an error during parsing, likely related to memory allocation or incorrect string format.
* }
* \endcode
*/
static const char* parse_string_quote(yaml_t yaml, const char* text, PARSER *parser)
{
char *ss = NULL;
const char* ptr;
char* ptr2;
int len = 0;
char quote = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE ? '\'' : '\"');
// Record the start position of the valid part.
parser->line_s = parser->sentinel;
/* Get the length of the string */
while (*(parser->sentinel) && *(parser->sentinel) != '\n' && *(parser->sentinel) != quote)
{
if (*parser->sentinel++ == '\\') parser->sentinel++; /* skip escaped quotes. */
len++;
}
// If the quote character is found, a closed interval of quotes is formed.
if (*(parser->sentinel) == quote)
{
// Record the end position of the valid part (excluding the quote).
parser->line_e = parser->sentinel - 1;
// Move the sentinel forward to check if the current line has ended.
parser->sentinel = skip_ln(parser->sentinel + 1);
// If it's not the end of the line or the text, there is a parsing error as there are still valid characters after the closed quote interval.
if (*parser->sentinel != 0 && *parser->sentinel != '\n')
{
E(YAML_E_LBREAK, parser->sentinel);
}
parser->flag.interval = 1;
}
// If the sentinel position is at the end of the line or the text.
else
{
// Find the end position of the valid part of the current line by moving the sentinel back.
parser->line_e = rskip(parser->sentinel - 1, parser->line_s);
// Subtract the length of the invalid part, which is not included in the valid part.
len -= (parser->sentinel - parser->line_e - 1);
}
// If there are empty lines before the current line, they need to be recorded.
if (parser->lines > 0)
{
len += parser->lines;
}
// Allocate additional storage.
ss = yaml_str_append(yaml, NULL, len);
if (!ss) E(YAML_E_MEMORY, parser->sentinel);
yaml->value.string_ = ss;
/* Copy text to new space */
ptr = parser->line_s;
ptr2 = &yaml->value.string_[yaml->size];
// First, assign the empty lines at the beginning, and empty lines will be converted to spaces.
for (int i = 0; i < parser->lines; i++)
{
*ptr2++ = ' ';
}
// Then, assign the valid characters one by one.
while (*ptr && *ptr != '\n' && *ptr != quote)
{
/* Normal character */
if (*ptr != '\\') { *ptr2++ = *ptr++; }
/* Escape character */
else
{
ptr++;
if (*ptr == 'b') { *ptr2++ = '\b'; }
else if (*ptr == 'f') { *ptr2++ = '\f'; }
else if (*ptr == 'n') { *ptr2++ = '\n'; }
else if (*ptr == 'r') { *ptr2++ = '\r'; }
else if (*ptr == 't') { *ptr2++ = '\t'; }
else if (*ptr == 'u') { yaml_utf(&ptr, &ptr2); }
else { *ptr2++ = *ptr; }
ptr++;
}
}
yaml->size += len;
return parser->sentinel;
}
/**
* \brief Parse the end of a string in the YAML text and handle string formatting at the end.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] text: Pointer to the YAML text to parse.
* \param[in] parser: Pointer to the PARSER structure for parsing state.
* \return Pointer to the position after the parsed string end in the text, or NULL on failure.
*
* This function processes the end of a string in YAML text, adjusting formatting based on
* the target base type (scalar or fold) and any specified string extra attributes. For
* scalar strings, it ensures proper newline handling. For folded strings, it manages
* whitespace formatting. The function can either strip trailing delimiters or add
* additional empty lines as specified by the parser flags.
*
* Memory management:
* - Uses yaml_str_append() to allocate additional memory if needed for appending lines.
* - Updates the YAML node's string pointer and size directly.
*
* Example usage:
* \code
* yaml_t yaml;
* PARSER parser;
* // Initialize yaml and parser...
*
* const char* result = parse_string_end(&yaml, text, &parser);
* if (result) {
* // String end processed successfully
* } else {
* // Error occurred (e.g., memory allocation failure)
* }
* \endcode
*/
static const char* parse_string_end(yaml_t yaml, const char* text, PARSER *parser)
{
if (yaml && yaml->vtype == YAML_TYPE_STRING)
{
// Handle scalar (literal block) or folded block string formatting
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR || parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD)
{
// Determine the delimiter based on formatting type
char token = (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR) ? '\n' : ' ';
// Handle "strip" modifier (remove trailing delimiter)
if (parser->flag.strextr == YAML_PFLAG_STREXTR_SUB)
{
if (yaml->value.string_[yaml->size - 1] == token)
{
yaml->value.string_[yaml->size - 1] = 0;
yaml->size--;
}
}
// Handle "keep" modifier (add trailing empty lines)
else if (parser->flag.strextr == YAML_PFLAG_STREXTR_ADD)
{
char *ss = NULL; // Temporary string pointer
int nlen = 0; // Number of trailing lines to add
// Calculate required trailing lines
nlen = parser->lines - 1;
if (nlen > 0)
{
// Allocate memory for additional lines
ss = yaml_str_append(yaml, NULL, nlen);
if (!ss) E(YAML_E_MEMORY, parser->sentinel);
// Update string pointer and fill with delimiters
ss = yaml->value.string_ + yaml->size;
yaml_str_padding(ss, nlen, token);
// Update string size
yaml->size += nlen;
}
}
}
}
return parser->sentinel;
}
/**
* \brief Parse a number from a string and convert it to the appropriate YAML numeric type.
* \param[in] yaml: Pointer to the YAML node containing the string to parse.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the modified YAML node with the parsed numeric value, or NULL on failure.
*
* This function parses a number represented as a string within a YAML node and converts it to either
* an integer or a floating-point value, depending on the format of the number. It first checks for
* signs, then handles hexadecimal, binary, and decimal representations. For decimal numbers, it
* processes the integer part, decimal part, and exponent part separately. If the number is an integer
* within the range of an `int`, it sets the YAML node's value to an integer; otherwise, it sets it
* to a floating-point value. In case of parsing errors (such as invalid characters), it returns NULL.
*
* Memory management:
* - Does not allocate or free memory directly within this function.
* - Updates the YAML node's value based on the parsed number.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* my_yaml_node.value.string_ = "3.14";
* PARSER my_parser;
* // Assume my_parser is properly initialized.
* yaml_t result = parse_number(&my_yaml_node, &my_parser);
* if (result) {
* // The number in the YAML node has been parsed and converted to the appropriate type.
* // my_yaml_node now holds the parsed number as an integer or float.
* } else {
* // There was an error during parsing, such as an invalid number format.
* }
* \endcode
*/
static yaml_t parse_number(yaml_t yaml, PARSER *parser)
{
char *text = yaml->value.string_;
double number = 0; /* Converted number */
int sign = 1, scale = 0; /* Sign and scale of integer part */
int e_sign = 1, e_scale = 0; /* Sign and scale of exponent part */
int isint = 1; /* Flag for whether there is only an integer part within a string */
/* First, check if there is a negative number with `-` before it */
if (*text == '-')
{
sign = -1;
text++;
}
// Skip the '+' symbol
else if (*text == '+')
{
text++;
}
if (*text == '0')
{
// Hexadecimal parsing
if (text[1] == 'x' || text[1] == 'X')
{
int int_ = 0;
char c = 0;
// Update position
text += 2;
// Convert hexadecimal characters one by one
while (c = *text++)
{
if (c >= '0' && c <= '9') c = c - '0';
else if (c >= 'A' && c <= 'F') c = c - 'A' + 10;
else if (c >= 'a' && c <= 'f') c = c - 'a' + 10;
else return NULL;
int_ = (int_ << 4) | c;
}
if (sign < 0) int_ = -int_;
// Update YAML value to hexadecimal integer
yaml_set_int(yaml, (int)int_);
return yaml;
}
// Binary parsing
else if (text[1] == 'b')
{
int int_ = 0;
char c = 0;
// Update position
text += 2;
// Convert binary characters one by one
while (c = *text++)
{
if (c == '0') int_ = (int_ << 1);
else if (c == '1') int_ = (int_ << 1) | 1;
else if (c == '_') continue;
else return NULL;
}
if (sign < 0) int_ = -int_;
// Update YAML value to binary integer
yaml_set_int(yaml, (int)int_);
return yaml;
}
}
else if (*text == '.')
{
// Check if it's NaN
if (yaml->size == 4)
{
if (strcncmp(text + 1, "nan", 3)) return yaml_set_float(yaml, NAN);
}
// Check if it's infinity
if (strcncmp(text + 1, "inf", 3)) return yaml_set_float(yaml, sign < 0 ? -INFINITY : INFINITY);
}
/* Skip invalid leading '0's */
while (*text == '0') text++;
/* Integer part */
if (*text >= '1' && *text <= '9')
{
do
{
number = (number * 10.0) + (*text++ - '0'); /* carry addition */
} while (*text >= '0' && *text <= '9');
}
else return yaml;
/* Decimal part */
if (*text == '.')
{
text++;
/* Check if the first character is a valid number */
if (!(*text >= '0' && *text <= '9'))
{
return NULL;
}
/* The number of decimal parts is also increased by 10 times first, and then reduced according to the scale later on */
do
{
number = (number * 10.0) + (*text++ - '0');
scale--;
} while (*text >= '0' && *text <= '9');
/* Decimal part present, marked as non-integer */
isint = 0;
}
/* Exponent part */
if (*text == 'e' || *text == 'E')
{
text++;
/* Symbol '+' skip */
if (*text == '+') text++;
/* Symbol '-' with sign */
else if (*text == '-')
{
e_sign = -1;
text++;
}
/* Check if the first character is a valid number */
if (!(*text >= '0' && *text <= '9'))
{
return NULL;
}
/* Conversion exponent part */
while (*text >= '0' && *text <= '9')
{
e_scale = (e_scale * 10) + (*text++ - '0'); /* number */
}
/* Exponent part present, marked as non-integer */
isint = 0;
}
if (*text != 0) return NULL;
/* Calculate conversion result */
number = (double)sign * number * pow(10.0, (scale + e_scale * e_sign));
/* As an integer and within the scope of an integer */
if (isint && INT_MIN <= number && number <= INT_MAX)
{
yaml_set_int(yaml, (int)number);
}
/* As a floating-point number */
else
{
yaml_set_float(yaml, number);
}
return yaml;
}
/**
* \brief Parse a date and time string into a YAML date type.
* \param[in] yaml: Pointer to the YAML node containing the string to parse.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the modified YAML node with the parsed date value, or NULL on failure.
*
* This function parses a date and time string from a YAML node's string value into a structured
* date-time format. It handles ISO 8601-like formats, including dates (YYYY-MM-DD), times
* (HH:MM:SS), fractional seconds, and UTC offsets. The parsed components are validated for
* correctness and stored in the YAML node's date structure. If parsing or validation fails,
* the function returns NULL.
*
* Memory management:
* - Does not allocate or free memory directly.
* - Modifies the existing YAML node's value type and content.
*
* Example usage:
* \code
* yaml_t node;
* // Initialize node with a date string, e.g., "2023-05-15T12:30:45.123+02:00"
*
* yaml_t result = parse_date(&node, parser);
* if (result) {
* // Date parsed successfully, access via node.value.date
* } else {
* // Parsing failed
* }
* \endcode
*/
static yaml_t parse_date(yaml_t yaml, PARSER *parser)
{
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minute = 0;
int second = 0;
int msec = 0;
int utcsign = 0; // 0 for positive offset, 1 for negative
int utchour = 0;
int utcminute = 0;
char flag = 0; // Bitmask flags for parsed components
if (yaml->vtype != YAML_TYPE_STRING) return yaml;
// Parse date part in YYYY-MM-DD format
char *s = yaml->value.string_;
if (sscanf(s, "%4d-%2d-%2d", &year, &month, &day) != 3) return yaml;
// Find time separator 'T' or space
s += 10;
if (*s == 0) goto END;
else if (*s == 't' || *s == 'T') s++;
else if (*s >= '0' && *s <= '9'); // Allow time without explicit separator
else return yaml;
// Parse time part in HH:MM:SS format
if (sscanf(s, "%2d:%2d:%2d", &hour, &minute, &second) != 3) return yaml;
flag |= 0x01; // Time part parsed
MSEC:
// Parse fractional seconds (milliseconds)
s += 8;
if (*s == 0) goto END;
else if (*s == '.') s++;
else if (*s == '+' || *s == '-') goto UTC;
else if (*s >= '0' && *s <= '9'); // Allow fractional part without leading dot
else return yaml;
// Parse up to three decimal places for milliseconds
if (*s >= '0' && *s <= '9') msec += ((*s++ - '0') * 100);
if (*s >= '0' && *s <= '9') msec += ((*s++ - '0') * 10);
if (*s >= '0' && *s <= '9') msec += ((*s++ - '0'));
while (*s >= '0' && *s <= '9') s++; // Skip remaining digits
flag |= 0x02; // Fractional part parsed
UTC:
// Parse UTC offset
if (*s == 0) goto END;
else if (*s == '+') { s++; utcsign = 0; }
else if (*s == '-') { s++; utcsign = 1; }
else if (*s >= '0' && *s <= '9'); // Allow missing offset sign
else return yaml;
// Parse UTC offset in HH:MM format
if (sscanf(s, "%d:%d", &utchour, &utcminute) != 2) return yaml;
flag |= 0x04; // UTC offset parsed
END:
// Validate parsed components
if (year < 0 || year > 4095) return NULL;
if (!date_isvalid(year, month, day)) return NULL;
if (hour < 0 || hour >= 24) return NULL;
if (minute < 0 || minute >= 60) return NULL;
if (second < 0 || second >= 60) return NULL;
if (msec < 0 || msec >= 1000) return NULL;
if (utchour < 0 || utchour > 12) return NULL;
if (utcminute < 0 || utcminute >= 60) return NULL;
// Update YAML node with parsed date-time values
yaml_set_null(yaml); // Reset node type
yaml->vtype = YAML_TYPE_DATE;
// Store parsed components in date structure
yaml->value.date.year = year;
yaml->value.date.month = month;
yaml->value.date.day = day;
yaml->value.date.hour = hour;
yaml->value.date.minute = minute;
yaml->value.date.second = second;
yaml->value.date.msec = msec;
yaml->value.date.utcsign = utcsign;
yaml->value.date.utchour = utchour;
yaml->value.date.utcminute = utcminute;
yaml->value.date.flag = flag;
return yaml;
}
/**
* \brief Parse a scalar value in a YAML node and convert it to an appropriate type.
* \param[in] yaml: Pointer to the YAML node containing the scalar value.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the modified YAML node with the parsed scalar value, or the original node if no conversion is needed.
*
* This function parses a scalar value within a YAML node. If the node's value type is a string and
* the target base flag indicates a null target base, it checks if the string matches YAML specification
* keywords (like "~", "no", "yes", "null", "true", "false"). If it matches, it sets the YAML node's
* value to the appropriate type (null, boolean). If it doesn't match, it attempts to parse the string
* as a number by calling the parse_number function. If the value type is not a string or the target
* base flag is not for a null target base, it returns the original YAML node.
*
* Memory management:
* - Calls yaml_set_null and yaml_set_bool which may handle memory related to setting the YAML node's value.
* - Calls parse_number which may also handle memory related to parsing and setting the node's value as a number.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* my_yaml_node.vtype = YAML_TYPE_STRING;
* my_yaml_node.value.string_ = "true";
* PARSER my_parser;
* my_parser.flag.tgtbase = YAML_PFLAG_TGTBASE_NULL;
*
* yaml_t result = parse_scalar(&my_yaml_node, &my_parser);
* if (result) {
* // The YAML node's value is set to the appropriate type (in this case, boolean true).
* } else {
* // There was an error during parsing or the node's type couldn't be converted.
* }
* \endcode
*/
static yaml_t parse_scalar(yaml_t yaml, PARSER *parser)
{
if (yaml->vtype == YAML_TYPE_STRING && parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL)
{
// Check if the current string is a YAML specification keyword.
if (yaml->size == 1)
{
if (yaml->value.string_[0] == '~') return yaml_set_null(yaml);
}
else if (yaml->size == 2)
{
if (strcncmp(yaml->value.string_, "no", 2)) return yaml_set_bool(yaml, 0);
}
else if (yaml->size == 3)
{
if (strcncmp(yaml->value.string_, "yes", 3)) return yaml_set_bool(yaml, 1);
}
else if (yaml->size == 4)
{
if (strcncmp(yaml->value.string_, "null", 4)) return yaml_set_null(yaml);
if (strcncmp(yaml->value.string_, "true", 4)) return yaml_set_bool(yaml, 1);
}
else if (yaml->size == 5)
{
if (strcncmp(yaml->value.string_, "false", 5)) return yaml_set_bool(yaml, 0);
}
// If not a keyword, try to convert and parse it as a number.
parse_number(yaml, parser);
}
return yaml;
}
/**
* \brief Parse and set the target type of a YAML node based on the text.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the position after the parsed type in the text, or NULL if no valid type is found.
*
* This function parses the text to identify the target type of a YAML node. It checks if the text
* matches specific keywords such as "null", "int", "float", "bool", "str", "binary", "timestamp", "set",
* or "map". If a match is found, it sets the target flag in the parser state and moves the sentinel
* to the position after the keyword. If no match is found, it returns NULL.
*
* Memory management:
* - This function does not allocate or free memory directly. It only modifies the parser state.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* PARSER my_parser;
* my_parser.sentinel = "float";
*
* const char* result = parse_type_convert(&my_yaml_node, &my_parser);
* if (result) {
* // The target type in the parser state is set to YAML_PFLAG_TARGET_FLOAT,
* // and the sentinel is moved to the position after "float".
* } else {
* // No valid type was found in the text.
* }
* \endcode
*/
static const char* parse_type_convert(yaml_t yaml, PARSER *parser)
{
if (0 == strncmp(parser->sentinel, "null", 4))
{
parser->flag.target = YAML_PFLAG_TARGET_NULL;
parser->sentinel += 4;
}
else if (0 == strncmp(parser->sentinel, "int", 3))
{
parser->flag.target = YAML_PFLAG_TARGET_INT;
parser->sentinel += 3;
}
else if (0 == strncmp(parser->sentinel, "float", 5))
{
parser->flag.target = YAML_PFLAG_TARGET_FLOAT;
parser->sentinel += 5;
}
else if (0 == strncmp(parser->sentinel, "bool", 4))
{
parser->flag.target = YAML_PFLAG_TARGET_BOOL;
parser->sentinel += 4;
}
else if (0 == strncmp(parser->sentinel, "str", 3))
{
parser->flag.target = YAML_PFLAG_TARGET_STR;
parser->sentinel += 3;
}
else if (0 == strncmp(parser->sentinel, "binary", 6))
{
parser->flag.target = YAML_PFLAG_TARGET_BINARY;
parser->sentinel += 6;
}
else if (0 == strncmp(parser->sentinel, "timestamp", 9))
{
parser->flag.target = YAML_PFLAG_TARGET_TIMESTAMP;
parser->sentinel += 9;
}
else if (0 == strncmp(parser->sentinel, "set", 3))
{
parser->flag.target = YAML_PFLAG_TARGET_SET;
parser->sentinel += 3;
}
else if (0 == strncmp(parser->sentinel, "seq", 3))
{
parser->flag.target = YAML_PFLAG_TARGET_SEQ;
parser->sentinel += 3;
}
else if (0 == strncmp(parser->sentinel, "map", 3))
{
parser->flag.target = YAML_PFLAG_TARGET_MAP;
parser->sentinel += 3;
}
else
{
return NULL;
}
return parser->sentinel;
}
/**
* \brief Check if the parsing interval of a YAML object is closed.
* \param[in] yaml: Pointer to the YAML node.
* \param[in] text: Pointer to the text being parsed.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the original text if the interval is closed, or calls an error function if the interval is not closed.
*
* This function checks whether the parsing interval of a YAML object is properly closed. It examines the type of the YAML node
* (string, sequence, or mapping) and the target base type in the parser state. If the YAML node is a string and the target base
* type is single or double, or if the YAML node is a sequence and the target base type is sequence, or if the YAML node is a
* mapping and the target base type is mapping, it checks the interval flag in the parser state. If the interval flag is 0,
* indicating an unclosed interval, it calls the E function with the YAML_E_LBREAK error code and the parser's sentinel.
* If the interval is closed, it simply returns the original text pointer.
*
* Memory management:
* - This function does not allocate or free memory directly. It only examines the YAML node and parser state.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* my_text = "Some YAML text";
* PARSER my_parser;
* // Assume my_yaml_node, my_text, and my_parser are properly initialized.
* const char* result = parse_check_interval(&my_yaml_node, my_text, &my_parser);
* if (result == my_text) {
* // The parsing interval is closed.
* } else {
* // An error occurred, and the E function was called for an unclosed interval.
* }
* \endcode
*/
static const char* parse_check_interval(yaml_t yaml, const char* text, PARSER *parser)
{
if (yaml->vtype == YAML_TYPE_STRING)
{
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE ||
parser->flag.tgtbase == YAML_PFLAG_TGTBASE_DOUBLE)
{
if (parser->flag.interval == 0)
{
E(YAML_E_LBREAK, parser->sentinel);
}
}
}
else if (yaml->vtype == YAML_TYPE_SEQUENCE)
{
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SEQUENCE)
{
if (parser->flag.interval == 0)
{
E(YAML_E_LBREAK, parser->sentinel);
}
}
}
else if (yaml->vtype == YAML_TYPE_MAPPING)
{
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_MAPPING)
{
if (parser->flag.interval == 0)
{
E(YAML_E_LBREAK, parser->sentinel);
}
}
}
return text;
}
/**
* \brief Parse a sequence in the YAML text and attach child nodes to the YAML node.
* \param[in] yaml: Pointer to the YAML node being parsed.
* \param[in] text: Pointer to the YAML text.
* \param[in] token: A token character related to the sequence.
* \param[in] parser: Pointer to the PARSER structure for parsing state.
* \param[in] p: Another pointer to the PARSER structure (might be used for recursive parsing).
* \return Pointer to the position after the parsed sequence in the text, or NULL on failure.
*
* This function parses a sequence within the YAML text. It first checks if the YAML node's type is appropriate for a sequence.
* If not, it calls an error function. It then creates a new child YAML node for the sequence. If memory allocation for the child
* fails, it calls an error function. It checks the position in the text and either moves the sentinel or parses the value of the
* sequence child. If there's an error during value parsing, it deletes the child node. Finally, it attaches the child node to the
* parent YAML node and returns the position after the parsed sequence in the text.
*
* Memory management:
* - Calls yaml_create to allocate memory for the child YAML node. If allocation fails, it calls E with YAML_E_MEMORY.
* - Calls yaml_delete to free the memory of the child YAML node if there's an error during parsing.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* yaml_text = "[1, \"hello\"]";
* PARSER my_parser;
* char sequence_token = '[';
* PARSER* p = &my_parser;
*
* const char* result = parse_sequence(&my_yaml_node, yaml_text, sequence_token, &my_parser, p);
* if (result) {
* // The sequence was successfully parsed and child nodes were attached to the YAML node.
* } else {
* // There was an error during parsing, such as memory allocation failure or incorrect sequence format.
* }
* \endcode
*/
static const char* parse_sequence(yaml_t yaml, const char* text, char token, PARSER *parser, PARSER *p)
{
yaml_t child = NULL;
// When parsing a sequence, the value type should not be of other types.
if (yaml->vtype != YAML_TYPE_NULL && yaml->vtype != YAML_TYPE_SEQUENCE)
{
E(YAML_E_MIX, parser->sentinel);
}
// When not parsing a [] sequence, there must be a child depth for the sequence.
if (rbrace <= 0 && parser->deep_cur == -1)
{
E(YAML_E_FLINE, parser->sentinel);
}
// Create a child object for the sequence.
child = yaml_create();
if (!child) E(YAML_E_MEMORY, parser->sentinel);
yaml->vtype = YAML_TYPE_SEQUENCE;
// When not parsing a [] sequence, if the next character is the end of the line or the end of the text,
// the current sequence child object is set to null and no further value parsing is done.
if (rbrace <= 0 && (parser->sentinel[1] == 0 || parser->sentinel[1] == '\n'))
{
parser->sentinel++;
}
else
{
if (rbrace <= 0) parser->sentinel += 2;
parser->sentinel = parse_value(child, parser->sentinel, parser->deep_cur, token, p);
if (etype != YAML_E_OK)
{
yaml_delete(child);
return parser->sentinel;
}
}
yaml_attach(yaml, yaml->size, child);
text = parser->sentinel;
return text;
}
/**
* \brief Parse a mapping in the YAML text and handle key-value pairs for the YAML node.
* \param[in] yaml: Pointer to the YAML node being parsed.
* \param[in] text: Pointer to the YAML text.
* \param[in] token: A token character related to the mapping.
* \param[in] parser: Pointer to the PARSER structure for parsing state.
* \param[in] p: Another pointer to the PARSER structure (might be used for recursive parsing).
* \return Pointer to the position after the parsed mapping in the text, or NULL on failure.
*
* This function parses a mapping (key-value pairs) in the YAML text. It first checks for the presence of a colon ':'
* to identify a key-value pair. If found, it creates a new child YAML node, sets the key from the parsed text, and
* recursively parses the value. Special handling is provided for merge keys ('<<') to handle references. If a comment
* or the end token is encountered, parsing stops. Memory allocation and error handling are managed throughout the process.
*
* Memory management:
* - Creates child nodes with yaml_create() and frees them with yaml_delete() on error.
* - Uses yaml_duplicate() for merging references, which allocates new memory that must be managed.
* - Attaches child nodes to the parent with yaml_attach(), transferring ownership.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* yaml_text = "key: value";
* PARSER my_parser;
* char token = '}';
*
* const char* result = parse_mapping(&my_yaml_node, yaml_text, token, &my_parser, &my_parser);
* if (result) {
* // Mapping parsed successfully, key-value pair added to my_yaml_node
* } else {
* // Error occurred during parsing
* }
* \endcode
*/
static const char* parse_mapping(yaml_t yaml, const char* text, char token, PARSER *parser, PARSER *p)
{
yaml_t child = NULL;
// PARSER p;
char comma = 0;
if (token) comma = ',';
// Track start of potential key
parser->line_s = parser->sentinel;
child = NULL;
// Scan line for key-value separator ':'
while (*parser->sentinel != 0 && *parser->sentinel != '\n')
{
if (*parser->sentinel == ':')
{
const char *t = skip(&parser->sentinel[1]);
// Validate key (non-empty and followed by value)
if (*t == 0 || t - parser->sentinel > 1)
{
// Check valid state for mapping
if (yaml->vtype != YAML_TYPE_NULL && yaml->vtype != YAML_TYPE_MAPPING)
{
E(YAML_E_MIX, parser->sentinel);
}
if (rbrace <= 0 && parser->deep_cur == -1)
{
E(YAML_E_FLINE, parser->sentinel);
}
// Determine key boundaries
parser->line_e = rskip(parser->sentinel - 1, parser->line_s);
// Create and initialize child node for key-value pair
child = yaml_create();
if (!child) E(YAML_E_MEMORY, parser->sentinel);
if (!yaml_set_key_l(child, parser->line_s, parser->line_e - parser->line_s + 1)) E(YAML_E_MEMORY, parser->sentinel);
DEBUG("[%d]key: %parser->sentinel\r\n", parser->deep_cur, child->key);
yaml->vtype = YAML_TYPE_MAPPING;
// Parse value after colon
parser->sentinel = parse_value(child, t, parser->deep_cur, token, p);
text = parser->sentinel;
if (etype != YAML_E_OK)
{
yaml_delete(child);
return parser->sentinel;
}
// Handle merge key ('<<') special case
if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_MAPPING && strcmp("<<", child->key) == 0)
{
yaml_t ref = child->value.child_;
// Validate reference
if (!ref->alias)
E(YAML_E_TANCHOR, parser->sentinel);
// Duplicate referenced node and attach to current mapping
yaml_t copy = yaml_duplicate(ref, YAML_F_RECURSE | YAML_F_COMPLEX | YAML_F_REFERENCE, parser->anchor);
if (!copy) E(YAML_E_MEMORY, parser->sentinel);
yaml_attach(yaml, yaml->size, copy->value.child_);
// Clean up temporary nodes
copy->value.child_ = NULL;
copy->vtype = YAML_TYPE_NULL;
copy->alias = NULL;
yaml_delete(copy);
child->value.child_ = NULL;
yaml_delete(child);
DEBUG("child->size %d\r\n", yaml->size);
}
else
{
// Attach regular key-value pair
yaml_attach(yaml, yaml->size, child);
}
break;
}
}
else if (*parser->sentinel == '#')
{
// Comment encountered, stop parsing current line
text = skip_ln(parser->sentinel);
break;
}
else if (*parser->sentinel == token || *parser->sentinel == comma)
{
// End of mapping or list separator
break;
}
parser->sentinel++;
}
return text;
}
/**
* \brief Parse a complex key in YAML and create a new YAML node for it.
* \param[in] yaml: Pointer to the parent YAML node.
* \param[in] text: Pointer to the YAML text to parse.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the position after the parsed complex key in the text, or NULL on failure.
*
* This function parses a complex key within the YAML text. It first creates two new YAML nodes: one for the child node
* and one for the complex key itself. Then, it parses the complex key as a value. If the parsing of the value is successful,
* it sets the type of the child node to indicate a complex key and attaches the key node to the child node. Finally, it attaches
* the child node to the parent YAML node. If any step fails (such as memory allocation or value parsing), it deletes the relevant
* nodes and returns an appropriate pointer.
*
* Memory management:
* - Calls yaml_create to allocate memory for new YAML nodes. If allocation fails, it calls E with YAML_E_MEMORY
* and deletes any partially created nodes.
* - Calls yaml_delete to free memory of nodes when an error occurs during parsing.
* - Uses yaml_attach to attach nodes, which may involve memory management related to the node structure.
*
* Example usage:
* \code
* yaml_t parent_yaml_node;
* const char* yaml_text = "complex_key: value";
* PARSER my_parser;
*
* const char* result = parse_complex_key(&parent_yaml_node, yaml_text, &my_parser);
* if (result) {
* // The complex key was successfully parsed and a new YAML node was created and attached to the parent node.
* } else {
* // There was an error during parsing, such as memory allocation failure or incorrect key format.
* }
* \endcode
*/
static const char* parse_complex_key(yaml_t yaml, const char* text, PARSER *parser)
{
yaml_t child = NULL;
yaml_t key = NULL;
PARSER p;
// Create a child YAML node.
child = yaml_create();
if (!child) E(YAML_E_MEMORY, parser->sentinel);
// Create a complex key YAML node.
key = yaml_create();
if (!key)
{
yaml_delete(child);
E(YAML_E_MEMORY, parser->sentinel);
}
// A complex key is also a YAML value, so parse it as a value.
text = parse_value(key, text, parser->deep_cur, 0, &p);
parser->sentinel = text;
if (etype != YAML_E_OK)
{
yaml_delete(child);
yaml_delete(key);
return parser->sentinel;
}
// Update child node information for complex key handling.
child->ktype = YAML_KTYPE_COMPLEX;
child->complex = key;
// Attach the child node to the parent YAML node.
yaml_attach(yaml, yaml->size, child);
return text;
}
/**
* \brief Parse a complex value in YAML that corresponds to a previously parsed complex key.
* \param[in] yaml: Pointer to the parent YAML node.
* \param[in] text: Pointer to the YAML text to parse.
* \param[in] parser: Pointer to the parser state.
* \return Pointer to the position after the parsed complex value in the text, or NULL on failure.
*
* This function parses a complex value in the YAML text that corresponds to a complex key.
* It first locates the last child node of the parent YAML node. Then it checks if this
* child node represents a complex key. If it is a complex key, it proceeds to parse the
* value associated with it. If the parsing of the value fails (i.e., the return value
* of parse_value is not YAML_E_OK), it returns the current position of the parser's sentinel.
* If the parsing is successful, it returns the position after the parsed value in the text.
*
* Memory management:
* - Does not directly allocate or free memory within this function. Relies on functions like
* yaml_iterator_to and parse_value which may handle memory in their own way.
*
* Example usage:
* \code
* yaml_t parent_yaml_node;
* const char* yaml_text = "complex_key: complex_value";
* PARSER my_parser;
*
* const char* result = parse_complex_value(&parent_yaml_node, yaml_text, &my_parser);
* if (result) {
* // The complex value corresponding to the complex key was successfully parsed.
* } else {
* // There was an error during parsing, such as an incorrect complex key or value format.
* }
* \endcode
*/
static const char* parse_complex_value(yaml_t yaml, const char* text, PARSER *parser)
{
yaml_t child = NULL;
PARSER p;
// Locate the last child node of the YAML node.
child = yaml_iterator_to(yaml, yaml->size - 1);
// Check if the last child node is a complex key YAML node.
if (child->ktype != YAML_KTYPE_COMPLEX) return NULL;
// Parse the value corresponding to the complex key.
text = parse_value(child, text, parser->deep_cur, 0, &p);
parser->sentinel = text;
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
return text;
}
/**
* \brief Parse a YAML value from text into a YAML node structure
* \param[in] yaml: Pointer to the YAML node being constructed
* \param[in] text: Pointer to the start of YAML text to parse
* \param[in] deep: Current indentation depth in the YAML structure
* \param[in] token: Delimiter token for the current value context
* \param[in] parser: Pointer to the parser state structure
* \return Pointer to the next character after the parsed value
*
* This function processes a YAML value, handling various data types
* including strings, sequences, mappings, anchors, and references.
* It uses indentation and special characters to determine the value type
* and structure, recursively parsing nested elements as needed.
*
* Memory management:
* - Calls functions like yaml_create to allocate memory for new YAML nodes when needed.
* - Manages the memory of child nodes when attaching them to parent nodes using functions like yaml_attach.
* - When an error occurs during parsing (e.g., incorrect format), it may call functions like yaml_delete
* to free the memory of partially constructed nodes.
*
* Example usage:
* \code
* yaml_t my_yaml_node;
* const char* yaml_text = "key: value";
* int indentation_depth = 0;
* char delimiter_token = '\0';
* PARSER my_parser;
*
* const char* result = parse_value(&my_yaml_node, yaml_text, indentation_depth, delimiter_token, &my_parser);
* if (result) {
* // The YAML value in the text has been parsed and the YAML node structure is updated accordingly.
* } else {
* // There was an error during parsing, such as incorrect YAML format or memory allocation failure.
* }
* \endcode
*/
static const char* parse_value(yaml_t yaml, const char* text, int deep, char token, PARSER *parser)
{
PARSER p;
char comma = 0;
// Initialize comma separator if token is specified (for sequences/mappings)
if (token) comma = ',';
/* Initialize parser state */
parser->sentinel = text; // Set parsing position to start of text
parser->deep_chd = -1; // Initialize child depth (invalid value)
parser->deep_cur = 0; // Current indentation depth
parser->lines = 0; // Count of empty lines before value
parser->line_s = NULL; // Start of current line's valid content
parser->line_e = NULL; // End of current line's valid content
parser->scope_s = text; // Start of value's scope
parser->scope_e = NULL; // End of value's scope (to be determined)
parser->flag.int_ = 0; // Clear integer parsing flag
// Share anchor list across all parser instances in this document
p.anchor = parser->anchor;
// Main parsing loop - processes lines until value scope ends
while (*text)
{
parser->lines = 0;
// Skip whitespace and identify next valid character
while (1)
{
// Skip leading whitespace
parser->sentinel = skip(text);
// Handle end of text
if (*parser->sentinel == 0)
{
text = parser->sentinel;
goto END;
}
// Handle empty line
else if (*parser->sentinel == '\n')
{
text = parser->sentinel + 1;
lbegin = text;
// Increment empty line count
parser->lines++;
// Update the line currently parsed
eline++;
}
// Handle comment line
else if (*parser->sentinel == '#')
{
// Position directly to the end of the line
text = skip_ln(parser->sentinel);
}
// Handle delimiter or end token
else if (*parser->sentinel == token || *parser->sentinel == comma)
{
text = parser->sentinel;
goto END;
}
// Found valid content
else
{
break;
}
}
// Reset current depth
parser->deep_cur = -1;
if (parser->lines > 0 || // New rows appear that need to be computed when pro depth
(yaml->ktype == YAML_KTYPE_ANCHOR && yaml->vtype == YAML_TYPE_NULL)) // When first start parsing a document, need to calculate the depth
{
// Calculate current indentation depth
parser->deep_cur = parser->sentinel - text;
// Initialize child depth if not set
if (parser->deep_chd == -1) parser->deep_chd = parser->deep_cur;
// Check for document separator (---)
if (parser->deep_cur == 0)
{
// Document splitter, ends parsing the current document
if (strnwcmp("---", parser->sentinel, 3) == 0)
{
// Ensure that there are no more valid characters on the current line,
// meaning that the document separator is on a single line, and no other valid characters are allowed
parser->line_s = parser->sentinel;
// Ensure document separator is on its own line
parser->sentinel = skip(parser->sentinel + 3);
if (*parser->sentinel == '\n' || *parser->sentinel == 0)
{
// Rewind to handle separator on next parse pass
// TODO: This place needs to be optimized to avoid repeated parsing of document splitters
text = parser->line_s - 1;
goto END;
}
}
}
// Check if current depth exceeds parent depth
// Ends parsing the current value
if (parser->deep_cur <= deep)
{
// Go back one line and continue parsing from the previous line
eline--;
text = text - 1;
goto END;
}
// Validate indentation consistency
if (parser->deep_cur < parser->deep_chd) E(YAML_E_TAB, text);
DEBUG("***[deep %d, deep_chd %d, deep_cur %d, lines %d]\r\n", deep, deep_chd, deep_cur, lines);
}
// Process value content if scope is not closed
if (parser->scope_e == NULL)
{
// Handle quoted strings
if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SINGLE ||
parser->flag.tgtbase == YAML_PFLAG_TGTBASE_DOUBLE)
{
// Start parsing the quoted string
text = parse_string_quote(yaml, text, parser);
if (etype != YAML_E_OK)
{
return text;
}
}
// Handle literal or folded block scalars
else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SCALAR ||
parser->flag.tgtbase == YAML_PFLAG_TGTBASE_FOLD)
{
// The start position of the current valid part is recorded
parser->line_s = parser->sentinel;
// Position directly to the end of the line
parser->sentinel = skip_ln(parser->sentinel);
// Parse the line control string
text = parse_string_line(yaml, text, parser);
if (etype != YAML_E_OK)
{
return text;
}
}
// Handle sequence ([])
else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_SEQUENCE)
{
// Parsing sequence
text = parse_sequence(yaml, text, ']', parser, &p);
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
// Process sequence delimiters
if (*parser->sentinel == ',')
{
parser->sentinel++;
}
// Parsed to the closed []
else if (*parser->sentinel == ']')
{
// Close sequence scope
rbrace--;
// Interval closed
parser->flag.interval = 1;
// Update the parse location
text = parser->sentinel + 1;
goto END;
}
text = parser->sentinel;
}
// Handle mapping ({})
else if (parser->flag.tgtbase == YAML_PFLAG_TGTBASE_MAPPING)
{
// Parsing mapping
DEBUG("text %d[%c]\r\n", *text, *text);
text = parse_mapping(yaml, text, '}', parser, &p);
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
// Process mapping delimiters
if (*parser->sentinel == ',')
{
parser->sentinel++;
}
// Parsed to the closed {}
else if (*parser->sentinel == '}')
{
// Close mapping scope
rbrace--;
// Interval closed
parser->flag.interval = 1;
// Update the parse location
text = parser->sentinel + 1;
goto END;
}
text = parser->sentinel;
}
// Handle untyped content - detect type based on initial character
else
{
// Handle quoted strings
if (*parser->sentinel == '\'' || *parser->sentinel == '\"')
{
// Set target base type based on quote type
if (*parser->sentinel == '\'')
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SINGLE;
else
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_DOUBLE;
// Initialize string value if type not set
if (yaml->vtype == YAML_TYPE_NULL)
{
yaml->value.string_ = NULL;
yaml->vtype = YAML_TYPE_STRING;
text = parser->sentinel + 1;
continue;
}
else if (yaml->vtype == YAML_TYPE_STRING)
{
if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL)
E(YAML_E_MIX, parser->sentinel);
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle block scalars (| or >)
else if (*parser->sentinel == '|' || *parser->sentinel == '>')
{
// Set target base type for block scalar
if (*parser->sentinel == '|')
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SCALAR;
else
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_FOLD;
if (yaml->vtype == YAML_TYPE_NULL)
{
// Reset child depth for block scalar
parser->deep_chd = -1;
parser->sentinel++;
// Parse block scalar modifiers (+, -, indentation)
if (*parser->sentinel == '+')
{
parser->flag.strextr = YAML_PFLAG_STREXTR_ADD;
parser->sentinel++;
}
else if (*parser->sentinel == '-')
{
parser->flag.strextr = YAML_PFLAG_STREXTR_SUB;
parser->sentinel++;
}
else if (*parser->sentinel >= '1' && *parser->sentinel <= '9')
{
// Parse explicit indentation level
int num = 0;
do {
num = (num * 10) + (*parser->sentinel++ - '0'); /* carry addition */
} while (*parser->sentinel >= '0' && *parser->sentinel <= '9');
// Validate indentation level
if (num > 256) E(YAML_E_LNUMBER, parser->sentinel);
// Record the number of Spaces
parser->flag.space_n = num;
parser->flag.strextr = YAML_PFLAG_STREXTR_NUM;
}
else parser->flag.strextr = YAML_PFLAG_STREXTR_NULL;
// Ensure no other content on this line
parser->sentinel = skip(parser->sentinel);
if (*parser->sentinel && *parser->sentinel != '\n') E(YAML_E_LBREAK, parser->sentinel);
// Update the parsing position
text = parser->sentinel;
continue;
}
else if (yaml->vtype == YAML_TYPE_STRING)
{
if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel);
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle anchor definition (&)
else if (*parser->sentinel == '&')
{
if (yaml->vtype == YAML_TYPE_NULL)
{
// Validate no existing anchor
if (yaml->alias) E(YAML_E_RANCHOR, parser->sentinel);
// Parse anchor name
parser->line_s = parser->sentinel + 1;
// doesn't stop until it encounters an invalid character
while (*parser->sentinel && *parser->sentinel > ' ') parser->sentinel++;
// Validate anchor name length
if (parser->sentinel <= parser->line_s) E(YAML_E_NANCHOR, parser->sentinel);
// The end position of alias is found, and the end position is recorded
parser->line_e = parser->sentinel - 1;
// Continue exploring in the current line
parser->sentinel = skip(parser->sentinel);
// Ensure no other content after anchor
if (*parser->sentinel != 0 &&
*parser->sentinel != '\n' &&
*parser->sentinel != '[' &&
*parser->sentinel != '{' &&
*parser->sentinel != '\'' &&
*parser->sentinel != '\"')
{
E(YAML_E_IANCHOR, parser->sentinel);
}
// Allocate and set anchor name
yaml->alias = yaml_strndup(parser->line_s, parser->line_e - parser->line_s + 1);
if (!yaml->alias) E(YAML_E_MEMORY, parser->sentinel);
// Add anchor to lookup table
if (!yaml_add_anchor(parser->anchor, yaml))
{
free(yaml->alias);
yaml->alias = NULL;
E(YAML_E_MEMORY, parser->sentinel);
}
DEBUG("yaml->alias %s\r\n", yaml->alias);
// Update the parsing position
text = parser->sentinel;
continue;
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle anchor reference (*)
else if (*parser->sentinel == '*')
{
if (yaml->vtype == YAML_TYPE_NULL)
{
// Parse anchor reference name
parser->line_s = parser->sentinel + 1;
// Just skip to the end of the line
parser->sentinel = skip_ln(parser->line_s);
// Go back from the end of the line to the end of the anchor name
parser->line_e = rskip(parser->sentinel - 1, parser->line_s);
// Lookup referenced anchor
yaml_t anchor = yaml_match_anchor(parser->anchor, parser->line_s, parser->line_e - parser->line_s + 1);
if (!anchor)
{
E(YAML_E_UANCHOR, parser->sentinel);
}
// Set value as reference
yaml->vtype = YAML_TYPE_REFERENCE;
yaml->value.child_ = anchor;
// Update the parsing position
text = parser->sentinel;
continue;
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle sequence start ([)
else if (*parser->sentinel == '[')
{
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_SEQUENCE;
// brace parsing recursion depth is increased
rbrace++;
// Initialize sequence type
if (yaml->vtype == YAML_TYPE_NULL)
{
yaml->value.child_ = NULL;
yaml->vtype = YAML_TYPE_SEQUENCE;
// Update the parsing position
parser->sentinel++;
text = parser->sentinel;
continue;
}
else if (yaml->vtype == YAML_TYPE_SEQUENCE)
{
if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel);
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle mapping start ({)
else if (*parser->sentinel == '{')
{
parser->flag.tgtbase = YAML_PFLAG_TGTBASE_MAPPING;
// brace parsing recursion depth is increased
rbrace++;
// Initialize mapping type
if (yaml->vtype == YAML_TYPE_NULL)
{
yaml->value.child_ = NULL;
yaml->vtype = YAML_TYPE_MAPPING;
// Update the parsing position
parser->sentinel++;
text = parser->sentinel;
continue;
}
else if (yaml->vtype == YAML_TYPE_MAPPING)
{
if (parser->flag.tgtbase != YAML_PFLAG_TGTBASE_NULL) E(YAML_E_MIX, parser->sentinel);
}
else
{
E(YAML_E_MIX, parser->sentinel);
}
}
// Handle sequence entry (-)
else if (*parser->sentinel == '-')
{
// A valid character is located
const char *t = skip(&parser->sentinel[1]);
// The format is a list format
if (*t == 0 || // End of text
t - parser->sentinel > 1) // Or not '-' followed by a valid character
{
text = parse_sequence(yaml, text, token, parser, &p);
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
continue;
}
}
// Handle type specification (!!)
else if (*parser->sentinel == '!')
{
// Two consecutive '! 'is to parse by the specified type
if (parser->sentinel[1] == '!')
{
// Only those who have just started parsing yaml objects can specify type resolution
if (yaml->vtype == YAML_TYPE_NULL && parser->flag.tgtbase == YAML_PFLAG_TGTBASE_NULL)
{
// skip !!
parser->sentinel += 2;
if (parse_type_convert(yaml, parser) == NULL)
{
E(YAML_E_TARTGET, parser->sentinel);
}
// Update the parsing position
parser->sentinel++;
text = parser->sentinel;
continue;
}
}
}
// Handle complex key (?)
else if (*parser->sentinel == '?')
{
parser->sentinel++;
text = parser->sentinel;
// Parse complex key
text = parse_complex_key(yaml, text, parser);
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
continue;
}
// Handle complex value (:)
else if (*parser->sentinel == ':')
{
// Parse value for complex key
if (parse_complex_value(yaml, parser->sentinel + 1, parser))
{
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
// Update the parsing position
text = parser->sentinel;
continue;
}
}
// There are no parse targets yet
// first check if it is a mapped object
// Default string parsing only if it's not a mapped object either
// Record the current sub-depth of yaml
unsigned int csize = yaml->size;
// First, follow the map to parse
text = parse_mapping(yaml, text, token, parser, &p);
if (etype != YAML_E_OK)
{
return parser->sentinel;
}
// If mapping parsing succeeded, continue
if (yaml->size > csize) continue;
// Fallback to parsing as plain string
text = parse_string_line(yaml, text, parser);
DEBUG("value: %parser->sentinel\r\n", yaml->value.string_);
}
}
}
END: // Single exit point for successful parsing
// Finalize value parsing
parser->scope_e = text;
DEBUG("> scope[%parser->sentinel] %d\r\n", yaml->key ? yaml->key : "-", parser->scope_e - parser->scope_s);
// Validate value interval
parse_check_interval(yaml, text, parser);
// Process trailing whitespace in strings
parse_string_end(yaml, text, parser);
// Convert plain strings to appropriate scalar types
parse_scalar(yaml, parser);
// Parse date/time values
if (parse_date(yaml, parser) == NULL) E(YAML_E_DATE, parser->sentinel);
// Validate parsed type against expected target type
if (parser->flag.target != YAML_PFLAG_TARGET_NONE)
{
if ((parser->flag.target == YAML_PFLAG_TARGET_NULL && yaml->vtype != YAML_TYPE_NULL) ||
(parser->flag.target == YAML_PFLAG_TARGET_BOOL && yaml->vtype != YAML_TYPE_BOOL) ||
(parser->flag.target == YAML_PFLAG_TARGET_INT && yaml->vtype != YAML_TYPE_INT) ||
(parser->flag.target == YAML_PFLAG_TARGET_FLOAT && yaml->vtype != YAML_TYPE_FLOAT) ||
(parser->flag.target == YAML_PFLAG_TARGET_STR && yaml->vtype != YAML_TYPE_STRING) ||
(parser->flag.target == YAML_PFLAG_TARGET_TIMESTAMP && yaml->vtype != YAML_TYPE_DATE) ||
(parser->flag.target == YAML_PFLAG_TARGET_SEQ && yaml->vtype != YAML_TYPE_SEQUENCE) ||
(parser->flag.target == YAML_PFLAG_TARGET_MAP && yaml->vtype != YAML_TYPE_MAPPING))
{
E(YAML_E_TARTGET, parser->sentinel);
}
}
return text;
}
/**
* \brief Parse a YAML string into a YAML document tree
* \param[in] text: Pointer to the YAML string to parse
* \param[in] flag: Parsing flags (e.g., YAML_F_LDOCS for multiple documents)
* \return Pointer to the root YAML node on success, NULL on failure
*
* This function parses a YAML string and constructs a tree of YAML nodes.
* It supports both single and multi-document YAML streams based on the flags provided.
* Each document is parsed into a separate root node with its own anchor table.
*
* Memory management:
* - Calls yaml_create to allocate memory for YAML nodes (both document nodes and child nodes).
* - Allocates memory for the anchor table using malloc for each document node.
* - Uses memset to initialize the anchor table to zero.
* - Calls yaml_delete to free the memory of YAML nodes when an error occurs or during cleanup.
* - Calls yaml_attach to attach child nodes to the document node, which may involve memory management related to the node structure.
*
* Example usage:
* \code
* const char* yaml_text = "key: value\n---\nanother_key: another_value";
* int flags = YAML_F_LDOCS;
*
* yaml_t root_node = yaml_loads(yaml_text, flags);
* if (root_node) {
* // The YAML string has been successfully parsed into a document tree.
* // The root_node points to the root of the YAML document tree.
* } else {
* // There was an error during parsing, such as memory allocation failure or incorrect YAML format.
* }
* \endcode
*/
yaml_t yaml_loads(const char* text, int flag)
{
yaml_t yaml = NULL;
yaml_t child = NULL;
PARSER p;
/* Reset error information */
lbegin = text;
eline = 1;
etype = YAML_E_OK;
rbrace = 0;
/* Parse each document in the stream until completion or error */
do
{
// Create new YAML node for the document
child = yaml_create();
if (!child)
{
E(YAML_E_MEMORY, NULL);
}
/* Each document has its own anchor table for tracking references */
child->anchor = (ANCHOR *)malloc(sizeof(ANCHOR));
if (!child->anchor)
{
yaml_delete(child);
E(YAML_E_MEMORY, NULL);
}
// Initialize anchor table
memset(child->anchor, 0, sizeof(ANCHOR));
// Mark as anchor type (shared memory with key)
child->ktype = YAML_KTYPE_ANCHOR;
// Initialize parser state
memset(&p, 0, sizeof(p));
p.anchor = child->anchor;
// Parse the document value
// Use -1 to indicate no parent indentation depth
text = parse_value(child, text, -1, 0, &p);
/* Handle parsing errors */
if (etype != YAML_E_OK)
{
yaml_delete(child);
yaml_delete(yaml);
return NULL;
}
/* Handle multi-document parsing */
if (flag & YAML_F_LDOCS)
{
if (!yaml)
{
// Exit if end of text is reached
if (*text == 0) break;
/* Create root document node */
yaml = yaml_create();
if (!yaml)
{
E(YAML_E_MEMORY, NULL);
}
/* Set as document type */
yaml->vtype = YAML_TYPE_DOCUMENT;
}
// Attach parsed document to the document list
if (!yaml_attach(yaml, yaml->size, child))
{
DEBUG("attach fail!\r\n");
}
}
/* Single document mode - exit after first document */
else
{
break;
}
// Exit if end of text is reached
if (*text == 0) break;
// Move to next document
text = p.sentinel + 1;
} while (*text);
// Use single document as root if not in multi-document mode
if (!yaml) yaml = child;
return yaml;
}
/**
* \brief Load and parse a YAML file into a YAML document tree
* \param[in] filename: Path to the YAML file
* \param[in] flag: Parsing flags (e.g., YAML_F_LDOCS for multiple documents)
* \return Pointer to the root YAML node on success, NULL on failure
*
* This function reads a YAML file from disk and parses its contents.
* It handles file operations and error checking before passing the content to yaml_loads.
* The function first opens the file, determines its size, allocates memory to store the file content,
* reads the file into the allocated buffer, null-terminates the buffer, and then passes the content
* to yaml_loads for parsing. After parsing, it frees the memory allocated for the buffer.
*
* Memory management:
* - Allocates memory using malloc to store the file content in a buffer.
* - Frees the buffer memory using free after the content has been passed to yaml_loads for parsing.
* - Closes the file using fclose to release associated resources.
*
* Example usage:
* \code
* char* yaml_file_path = "example.yaml";
* int parsing_flags = YAML_F_LDOCS;
*
* yaml_t root_node = yaml_file_load(yaml_file_path, parsing_flags);
* if (root_node) {
* // The YAML file has been successfully loaded and parsed into a document tree.
* // The root_node points to the root of the YAML document tree.
* } else {
* // There was an error during file reading, memory allocation, or YAML parsing.
* }
* \endcode
*/
yaml_t yaml_file_load(char* filename, int flag)
{
FILE* f;
yaml_t yaml = NULL;
long len;
char* data;
/* Open the YAML file */
f = fopen(filename, "rb");
if (!f) return NULL;
/* Determine file size */
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
/* Allocate memory and read file content */
data = (char*)malloc(len + 1);
if (data)
{
/* Read entire file into buffer */
fread(data, 1, len, f);
fclose(f);
}
else
{
fclose(f);
return NULL;
}
/* Null-terminate the buffer */
data[len] = 0;
/* Parse the YAML content */
yaml = yaml_loads(data, flag);
/* Clean up allocated resources */
free(data);
return yaml;
}