/********************************************************************************************************* * ------------------------------------------------------------------------------------------------------ * 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 #include #include #include #include // #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; }