/* * LibAxl: Another XML library * Copyright (C) 2013 Advanced Software Production Line, S.L. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * * You may find a copy of the license under this software is released * at COPYING file. This is LGPL software: you are welcome to * develop proprietary applications using this library without any * royalty or fee but returning back any change, improvement or * addition in the form of source code, project image, documentation * patches, etc. * * For commercial support on build XML enabled solutions contact us: * * Postal address: * Advanced Software Production Line, S.L. * Edificio Alius A, Oficina 102, * C/ Antonio Suarez Nº 10, * Alcalá de Henares 28802 Madrid * Spain * * Email address: * info@aspl.es - http://www.aspl.es/xml */ #include #include #define LOG_DOMAIN "axl-dtd" struct _axlDtdElementListNode { NodeType type; AxlDtdTimes times; axlPointer data; }; struct _axlDtdElementList { /** * @brief Allows to configure how is given top level * configuration for nodes to be defined inside the xml * document being configured. As defined in the XML 1.0 * Recomendation, available top level choices are: choice or * sequence. * * They allow to configure allowed nodes to be selected as * childs, from a set of node names, called choice or to * configure which are the set of nodes to be used, in a * particular order, called sequence. * * This variable allows to configure which is the top level * section configuration: either a choice or a sequence. * * Keep in mind that, having only one element inside the * itemList, there is no difference between the sequence and * the choice. */ AxlDtdNestedType type; /** * @brief Allows to configure how many times is repeated a * selection provided (by this element). */ AxlDtdTimes times; /** * @brief Item list, which contains more axlDtdElementList * nodes, configuring elements allowed. */ axlList * itemList; }; struct _axlDtdElement { /** * @brief The document type element declaration name. This is * the name of the xml node being constrained. */ char * name; /** * @brief This is the type of the xml node being constrained. */ AxlDtdElementType type; /** * @brief List of available items. * * This variable holds current top level list selection. See * axlDtdElementList.type variable. */ axlDtdElementList * list; /** * @brief Minimum item list count to be matched while using * this DTD element rule. */ int minimum_match; }; struct _axlDtdAttributeDecl { /** * @brief Attribute name. This is the attribute value defined * for the node. */ char * name; /** * @brief This is the attribute declaration type. It shows if */ AxlDtdAttributeType type; /** * @brief Allows to model whoe is */ AxlDtdAttributeDefaults defaults; /** * @brief This is a default value for the ). */ axlList * entities; /** * @brief All elements inside the DTD declaration * ( ). */ axlList * elements; /** * @brief All attribute type declerations inside the DTD * () */ axlList * attributes; /** * @brief The element root, for the given DTD declaration. */ axlDtdElement * root; /** * @brief Internal flag that allows to notify that the DTD * contains ID attribute declaration, making the DTD this * references. */ axl_bool haveIdDecl; /** * @brief Flag that the dtd declaration have attributes which * are flaged as IDREF. */ axl_bool haveIdRefDecl; }; /** * \defgroup axl_dtd_module Axl DTD: Document type declaration interface (functions, validation, and DTD parsing) */ /** * \addtogroup axl_dtd_module * @{ */ /** * @internal * * Allows to create a new dtd element list item, which represents a * content particule inside an item list or a item list. This allows * the recursion defined on the XML 1.0 standard. * * The function receives the node name and a reference list. According * to the values the function creates a node which contains a leaf * value or a node which contains a reference to the a new list which * is nested. */ axlDtdElementListNode * __create_axl_dtd_element_list (char * node_name, axlDtdElementList * list) { axlDtdElementListNode * node; node = axl_new (axlDtdElementListNode, 1); /* create a node element reference */ if (node_name != NULL) { node->data = node_name; node->type = AXL_ELEMENT_NODE; return node; } /* create an element list reference */ if (list != NULL) { node->data = list; node->type = AXL_ELEMENT_LIST; return node; } /* if another type is requested, return NULL */ return NULL; } /** * @internal * * Support function used to destroy all items stored on a item list. * * @param node */ void __destroy_axl_dtd_element_list (axlDtdElementListNode * node) { if (node == NULL) return; /* free the reference to the leaf node if defined */ if (node->type == AXL_ELEMENT_NODE) axl_free (node->data); /* do not do nothing if the reference is not element list */ if (node->type == AXL_ELEMENT_LIST) axl_dtd_item_list_free (node->data); /* free de node itself */ axl_free (node); return; } /** * @internal * * @brief Support function to \ref axl_dtd_parse which creates a new * empty DTD reference. * * * @return A newly allocated \ref axlDtd reference. */ axlDtd * __axl_dtd_new (void) { axlDtd * dtd; /* create the DTD element and nothing else. The rest of items * created on demand */ dtd = axl_new (axlDtd, 1); return dtd; } axl_bool __queue_items (axlPointer data, axlPointer _stack) { axlStack * stack = _stack; /* queue the data */ axl_stack_push (stack, data); /* return axl_false to make the function to not stop */ return axl_false; } void __axl_dtd_queue_childs (axlStack * stack, axlNode * parent) { axlNode * child; /* get the first child */ child = axl_node_get_first_child (parent); while (child != NULL) { /* queue the child */ axl_stack_push (stack, child); /* get the next child */ child = axl_node_get_next (child); } /* end while */ return; } /** * @internal * * Support internal function which allows to queue all items inside an * axlDtdElementList to be checked. * * @param stack The stack where all data will be placed. * * @param dtd_element_list The dtd element list where the data will be * extracted. */ void __axl_dtd_queue_items (axlStack * stack, axlList * list) { /* call to queue items */ axl_list_lookup (list, __queue_items, stack); /* nothing more */ return; } /** * @internal * * Support function which allows to check if the provided two dtd * elements are in fact, parent and child. * * DTD element have a parent-child relation based in the fact that the * first define top level xml nodes that are followed, in the form of * childs nodes, by other DTD elements that define more childs, etc... * * This function allows to check if the provided parent dtd element * have references inside its content specification that proves that * it is indeed a parent definition. * * @param dtd_element_parent The supposed DTD parent element. * @param dtd_element_child The supposedd DTD child element. * * @return \ref axl_true if the function can confirm that the parent-child * relation exists, \ref axl_false if not or it could be proved. */ axl_bool __axl_dtd_get_is_parent (axlDtdElement * dtd_element_parent, axlDtdElement * dtd_element_child) { axlStack * stack; axlDtdElementListNode * node; axlDtdElementList * list; /* check for leaf nodes, that, by definition, could be a * parent of nothing. */ if (dtd_element_parent->list == NULL || dtd_element_parent->list->itemList == NULL) { return axl_false; } /* prepare all elements inside the stack to be checked */ stack = axl_stack_new (NULL); __axl_dtd_queue_items (stack, dtd_element_parent->list->itemList); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "stack size to operate: %d, list: %d", axl_stack_size (stack), axl_list_length (dtd_element_parent->list->itemList)); /* now search for a content particule that makes are reference * to the child DTD element */ do { node = axl_stack_pop (stack); switch (node->type) { case AXL_ELEMENT_NODE: /* leaf node case */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a leaf node, checking it"); /* seems this is a final node */ if (axl_cmp (node->data, dtd_element_child->name)) { /* seems that the content * specification makes a reference to * the child node. */ axl_stack_free (stack); return axl_true; } break; case AXL_ELEMENT_LIST: /* a nested list case */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a complex node queuing its internal elements, while checking parent=%s for child=%s", dtd_element_parent->name, dtd_element_child->name); /* the item list read, is a complex value, * queue all items inside to be inspected */ list = node->data; __axl_dtd_queue_items (stack, list->itemList); break; case AXL_ELEMENT_NOT_DEFINED: /* do nothing */ break; } /* iterate until all elements are evaluated */ }while (! axl_stack_is_empty (stack)); /* deallocates no longer used stack */ axl_stack_free (stack); /* either it isn't the parent or it can't be proved. */ return axl_false; } /** * @internal * * Support function which allows to get which is the most top root * node for the provided set of DTD elements. */ axlDtdElement * __axl_dtd_get_new_root (axlDtd * dtd) { int iterator; axl_bool change_detected; axlDtdElement * dtd_element_aux; axlDtdElement * dtd_element_the_root_is_on_fire; /* set the very first root node */ dtd_element_the_root_is_on_fire = axl_list_get_nth (dtd->elements, 0); do { /* check which is the top */ iterator = 0; change_detected = axl_false; while (iterator < axl_list_length (dtd->elements)) { /* get the next reference */ dtd_element_aux = axl_list_get_nth (dtd->elements, iterator); /* check which is the top */ if (__axl_dtd_get_is_parent (dtd_element_aux, dtd_element_the_root_is_on_fire)) { /* it seems that the new element is the root * one, update the reference */ dtd_element_the_root_is_on_fire = dtd_element_aux; change_detected = axl_true; } /* update inner loop iterator */ iterator ++; } /* while end */ }while (change_detected); /* return the root found */ return dtd_element_the_root_is_on_fire; } /** * @internal * * @brief Adds the axlDtdElement into the given axlDtd definition, * checking that everything is properly configured, and ensuring that * the root element gets properly configured. * * @param dtd The \ref axlDtd object that will receive the * axlDtdElement. * * @param stream The \ref axlStream object that will be destroyed if * something wrong is found. * * @param element The axlDtdElement to be added to the give axlDtd * object. * * @return axl_true if the given axlDtdElement is compatible inside * the axlDtd declaration or axl_false if a error is found. */ axl_bool __axl_dtd_add_element (axlDtd * dtd, axlDtdElement * element, axlStream * stream, axlError ** error) { int iterator = 0; axlDtdElement * dtd_element_aux = NULL; /* check that there is no element already named like the * element received. If it is the case drop an error */ while (iterator < axl_list_length (dtd->elements)) { dtd_element_aux = axl_list_get_nth (dtd->elements, iterator); if (axl_cmp (dtd_element_aux->name, element->name)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "DTD element for <%s> == <%s> was defined twice", dtd_element_aux->name, element->name); axl_error_new (-1, "Find that an DTD element was defined twice (no more than one time is allowed)", stream, error); axl_stream_free (stream); return axl_false; } /* update current iterator */ iterator++; } /* add the new DTD element to the list */ axl_list_add (dtd->elements, element); return axl_true; } /** * @internal * * Internal support function which adds the provided content particule * to the dtd item list received. It also perform all operations * required for the chunk_matched option received. * * In the case the function fails to do its work, it will deallocate * the stream, filling the error received. * * According to the chunk matched value, the function will react * adding the element and configuring current element list. * */ axl_bool __axl_dtd_element_content_particule_add (axlDtdElementList * dtd_item_list, char * string_aux, int chunk_matched, axlStream * stream, axlError **error) { axlDtdElementListNode * node; /* check if the item list was creted or not */ if (dtd_item_list->itemList == NULL) { dtd_item_list->itemList = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) __destroy_axl_dtd_element_list); } /* create the node to be added */ node = __create_axl_dtd_element_list (string_aux, NULL); /* know add the element found */ axl_list_add (dtd_item_list->itemList, node); /* set configuration for item repetition */ switch (chunk_matched) { case 4: /* one or many times */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "setting one to many repeat pattern: (+)"); node->times = ONE_OR_MANY; break; case 5: /* zero or many times */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "setting zero to many repeat pattern: (*)"); node->times = ZERO_OR_MANY; break; case 6: /* zero or one time */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "setting one to one repeat pattern: (?)"); node->times = ZERO_OR_ONE; break; default: /* one and only one time */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "setting one and only one repeat pattern: ()"); node->times = ONE_AND_ONLY_ONE; } /* return that all is ok */ return axl_true; } /** * @internal * * @brief Support function which allows to get current repetition * configuration. * * @param stream The stream where the operation will be performed. * * @return Current configuration read, the function will properly work * if it is called when it is espected to find a content specification * repetition. If not found, the \ref ONE_AND_ONLY_ONE is returned. */ AxlDtdTimes __axl_dtd_get_repetition_conf (axlStream * stream) { axl_return_val_if_fail (stream, ONE_AND_ONLY_ONE); if (axl_stream_inspect (stream, "?", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found '?' repetition conf"); /* seems the content specification could appear zero * or one time */ return ZERO_OR_ONE; } else if (axl_stream_inspect (stream, "+", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found '+' repetition conf"); /* seems the content specification must appear one up * to many */ return ONE_OR_MANY; } else if (axl_stream_inspect (stream, "*", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found '*' repetition conf"); /* seems the content specification could appear zero * up to many */ return ZERO_OR_MANY; } /* the content specification must appear */ return ONE_AND_ONLY_ONE; } /** * @internal * * Support function which creates a child item list, insert it to the * parent item list received. * * @param parent * * @return */ axlDtdElementList * __axl_dtd_create_and_queue (axlDtdElementList * parent) { axlDtdElementList * child; axlDtdElementListNode * node; /* create the DTD item list */ child = axl_new (axlDtdElementList, 1); /* make by default the item list to be defined as "not * defined" until the first separator is found */ child->type = STILL_UNDEF; /* create a node that */ node = __create_axl_dtd_element_list (NULL, child); /* create the parent list reference if weren't */ if (parent->itemList == NULL) { parent->itemList = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) __destroy_axl_dtd_element_list); } /* add the node */ axl_list_add (parent->itemList, node); /* return the new child created */ return child; } /** * @internal * * Updates current chunk read information to allow perform a better * code after calling this function. * */ void __axl_dtd_element_spec_update_chunk_matched (axlStream * stream, int * chunk_matched) { /* check for for sequence or choice characters */ if (axl_stream_inspect (stream, ",", 1) > 0) { /* flag that we have found a , (choice) * separator */ (*chunk_matched) = 1; } else if (axl_stream_inspect (stream, "|", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 2; } else if (axl_stream_inspect (stream, ")", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 3; } else if (axl_stream_inspect (stream, "+", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 4; } else if (axl_stream_inspect (stream, "*", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 5; } else if (axl_stream_inspect (stream, "?", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 6; } return; } /** * @internal * * Support function to read the content particule separator once the * repeat pattern was found * */ axl_bool __axl_dtd_element_spec_update_chunk_matched_for_cp_separator (axlStream * stream, int * chunk_matched) { /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* check for for sequence or choice characters */ if (axl_stream_inspect (stream, ",", 1) > 0) { /* flag that we have found a , (choice) * separator */ (*chunk_matched) = 1; return axl_true; } else if (axl_stream_inspect (stream, "|", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 2; return axl_true; } else if (axl_stream_inspect (stream, ")", 1) > 0) { /* flag that we have found a | (sequence) * separator */ (*chunk_matched) = 3; return axl_true; } return axl_false; } /** * @internal * * Support function which allows to read the next content particule. */ char * __axl_dtd_read_content_particule (axlStream * stream, int * chunk_matched, axlStack * dtd_item_stack, axlError ** error) { char * string_aux; /* read the spec particule stopping when a white space * or other character is found */ string_aux = axl_stream_get_until (stream, NULL, chunk_matched, axl_true, 8, /* basic, default delimiters: 0, 1, 2, 3 */ " ", ",", "|", ")", /* repetition configuration: 4, 5, 6 */ "+", "*", "?", /* new dtd item list being opened: 8 */ "("); if (string_aux == NULL) { axl_error_new (-1, "Expected to find a element content specification particule, but it wasn't found", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return NULL; } /* check the user doesn't nest item list in a not * proper way */ if (*chunk_matched == 8) { axl_error_new (-1, "Found a not proper nesting item list for a DTD element, before using ( a separator must be used (CHOICE: |, SEQUENCE: ,)", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return NULL; } /* nullify stream internal reference */ axl_stream_nullify (stream, LAST_CHUNK); /* return the content particule found */ return string_aux; } /** * @internal * * Support function which reads current type = STILL_UNDEF; /* set the content spec list to the dtd element read */ dtd_element->list = dtd_item_list; /* push the item created */ /* axl_stack_push (dtd_item_stack, dtd_item_list); */ /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* check that the content specification have an ( */ if (! (axl_stream_inspect (stream, "(", 1))) { axl_error_new (-1, "Expected to find a element content specification opener \"(\", but it wasn't found", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return axl_false; } do { /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* a new item list have been opened */ if (axl_stream_inspect (stream, "(", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a DTD item list openining: %d", axl_stack_size (dtd_item_stack)); /* a new item list is being defined, we have * to queue current dtd_item_list and create a * new item list */ axl_stack_push (dtd_item_stack, dtd_item_list); /* create the DTD item list */ dtd_item_list = __axl_dtd_create_and_queue (dtd_item_list); /* let's continue at the begining */ continue; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "iterating again to get a new content particule (item list size: %d)", axl_dtd_item_list_count (dtd_item_list)); /* read the next content particule: here is the chunk * matched codes found: * basic, default delimiters: * 0, 1, 2, 3 -> " ", ",", "|", ")" * repetition configuration: * 4, 5, 6 -> "+", "*", "?", * new dtd item list being opened: * 8 -> "(" */ string_aux = __axl_dtd_read_content_particule (stream, &chunk_matched, dtd_item_stack, error); if (string_aux == NULL) return axl_false; /* check, and record, that the string read is * PCDATA */ is_pcdata = axl_cmp (string_aux, "#PCDATA"); /* add the item read if have something defined */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found content spec particule: (size: %d) '%s'", strlen (string_aux), string_aux); /* check if the have matched a white space: next check is * based on the call to axl_stream_get_until at the caller * function: " " */ if (chunk_matched == 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found white spaces as delimiter, consuming them (current chunk matched: %d)", chunk_matched); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* update current chunk_matched to conform to * an stream that have all elements really * close: the following function tries to read * and update chunk_matched variable to point * to the value read for ",", "|", "+", "*", * "?" and ")" because white spaces were found */ __axl_dtd_element_spec_update_chunk_matched (stream, &chunk_matched); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "current chunk matched before update (%d)", chunk_matched); } /* add the content particule found, this function * already detect that a white space was found and * consumes all white spaces found */ if (!__axl_dtd_element_content_particule_add (dtd_item_list, string_aux, chunk_matched, stream, error)) return axl_false; if (chunk_matched == 4 || chunk_matched == 5 || chunk_matched == 6) { /* found a repetition pattern */ if (! __axl_dtd_element_spec_update_chunk_matched_for_cp_separator (stream, &chunk_matched)) { axl_error_new (-1, "Before a repetition pattern (*,+,?) expected to find a content particule separator", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return axl_false; } } /* set current sequence type accoring to separators * used */ switch (chunk_matched) { case 1: if (dtd_item_list->type == CHOICE) { axl_error_new (-1, "Detected that the DTD definition is mixing content particules separators at the same level ('|' and ','). First detected a sequence spec (,) but then detected a choice element (|)", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return axl_false; } dtd_item_list->type = SEQUENCE; break; case 2: if (dtd_item_list->type == SEQUENCE) { axl_error_new (-1, "Detected that the DTD definition is mixing content particules separators at the same level ('|' and ','). First detected a choice spec (|) but then detected a sequence element (,)", stream, error); axl_stack_free (dtd_item_stack); axl_stream_free (stream); return axl_false; } dtd_item_list->type = CHOICE; break; } /* set element type if a element list terminator was * found ( 3 = ')' = chunk_matched) */ if ((chunk_matched == 3) && is_pcdata) { if (axl_list_length (dtd_item_list->itemList) == 1) dtd_element->type = ELEMENT_TYPE_PCDATA; else if (axl_list_length (dtd_item_list->itemList) > 1) dtd_element->type = ELEMENT_TYPE_MIXED; } /* pop current element list header */ if (chunk_matched == 3) { do { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a DTD item list termination: stack status: %d", axl_stack_size (dtd_item_stack)); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); dtd_item_list->times = __axl_dtd_get_repetition_conf (stream); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); if (axl_stream_inspect (stream, ",", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a sequence (,) separator while reading terminator list"); chunk_matched = 1; } else if (axl_stream_inspect (stream, "|", 1) > 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a choice (|) separator while reading terminator list"); chunk_matched = 2; } /* this means that a ) was found, we have to * pop current queue */ is_empty = axl_stack_is_empty (dtd_item_stack); if (! is_empty) { dtd_item_list = axl_stack_pop (dtd_item_stack); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "getting the next item list in the stack, stack status: %d", axl_stack_size (dtd_item_stack)); } /* special case: check if the next element to * be read is a new ) */ /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); }while ((axl_stream_inspect (stream, ")", 1) > 0) && !is_empty); /* drop a log */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "terminator sequence status: chunk matched=%d ans stack status: %d", chunk_matched, axl_stack_size (dtd_item_stack)); } /* check if we have finished */ } while (chunk_matched != 3 || (! axl_stack_is_empty (dtd_item_stack))); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "content spec terminated, now lookup for the termination"); /* consume previous white spaces */ /* AXL_CONSUME_SPACES (stream);*/ /* read here repetition specification */ /* dtd_item_list->times = __axl_dtd_get_repetition_conf (stream); */ /* set default content element separator */ if (dtd_item_list->type == STILL_UNDEF) dtd_item_list->type = SEQUENCE; /* free the stack used */ axl_stack_free (dtd_item_stack); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "DTD content element specification found and parsed ok"); /* content spec read properly */ return axl_true; } /** * @internal * * Calculates the number of nodes to be matched at minimum for the * provided DTD element. * * @param element The DTD element to configure with its minimum item * count to be matched. */ int __axl_dtd_parse_element_get_compulsory_num (axlDtdElementList * list) { axlDtdElementListNode * itemNode; int count = 0; int iterator = 0; /* check for null parameters */ if (list == NULL) return 0; /* only count for repetitiong patterns that makes obligatory * to have childs */ if (list->times == ONE_AND_ONLY_ONE || list->times == ONE_OR_MANY) { while (iterator < axl_list_length (list->itemList)) { /* get the reference for the item node */ itemNode = axl_list_get_nth (list->itemList, iterator); /* check if the repetitiong patter is * compulsory */ if (itemNode->times == ONE_OR_MANY || itemNode->times == ONE_AND_ONLY_ONE) { /* check if we have an itemNode that has an * Node or a list */ if (itemNode->type == AXL_ELEMENT_NODE) { /* we have an item node */ count++; if (list->type == CHOICE) { /* because we have a * choice list, once * validated one item, * it is the minimum * requirement. */ return count; } } else { /* we have a list */ count += __axl_dtd_parse_element_get_compulsory_num (itemNode->data); } } /* update the index */ iterator++; } } /* return current count */ return count; } /** * @internal * * Parses a document type element that it is expected to be found at * the given stream. * * @param dtd The axlDtd where the element type read must be added. * * @param stream The stream where the element type if expected to be found. * * @param error An axlError, optional, reference where error will be * reported. * * @return axl_true if the element was parsed properly, axl_false if * not. The stream associated will be unrefered and the axlError * provided will be filled if an error is found. */ axl_bool __axl_dtd_parse_element (axlDtd * dtd, axlStream * stream, axlError ** error) { char * string_aux; int matched_chunk = -1; axlDtdElement * element; /* init the dtd element list */ if (dtd->elements == NULL) dtd->elements = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) axl_dtd_element_free); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* get for the first element declaration */ if (! (axl_stream_inspect (stream, " 0)) { axl_error_new (-1, "Expected to receive a ", "(", " ", "name = string_aux; /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* now, check for the basic cases: ANY and EMPTY */ if (axl_stream_peek (stream, "EMPTY", 5) > 0) { /* accept previous peek */ axl_stream_accept (stream); /* found empty declaration */ element->type = ELEMENT_TYPE_EMPTY; } else if (axl_stream_peek (stream, "ANY", 3) > 0) { /* accept previous peek */ axl_stream_accept (stream); /* found any declaration */ element->type = ELEMENT_TYPE_ANY; } else { /* complex element type declaration, let's roll now * get the element content type read current dtd * element spec. * * By default, any comple element type definition, * have childrens, until PC data definition is found, * which leads to the two possible values: Mixed and * PcData */ element->type = ELEMENT_TYPE_CHILDREN; if (!__axl_dtd_read_element_spec (stream, element, error)) return axl_false; } /* add element found */ if (! __axl_dtd_add_element (dtd, element, stream, error)) return axl_false; /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* check for the last DTD declaration */ if (! (axl_stream_inspect (stream, ">", 1))) { axl_error_new (-1, "Unable to find last, > terminator for the DTD minimum_match = __axl_dtd_parse_element_get_compulsory_num (element->list); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found DTD element declaration read complete: minimum matching elements: %d", element->minimum_match); /* element type declaration completely read */ return axl_true; } /** * @internal * * Destroy the provided reference and its associated data. * * @param decl The reference declaration. */ void axl_dtd_attribute_decl_free (axlDtdAttributeDecl * decl) { /* free the rule name */ if (decl->name != NULL) axl_free (decl->name); /* free the default value */ if (decl->default_value != NULL) axl_free (decl->default_value); /* free enum declaration list if defined */ if (decl->enumvalues != NULL) axl_list_free (decl->enumvalues); /* free the node itself */ axl_free (decl); /* nothing more to do */ return; } /** * @internal function to dealloc an single attribute set decleration. * * @param attribute The reference to dealloc. */ void axl_dtd_attribute_free (axlDtdAttribute * attribute) { /* free the attribute list, name and the node itself */ axl_free (attribute->name); axl_list_free (attribute->list); axl_free (attribute); return; } axl_bool __find_attr_decl (axlPointer _element, axlPointer data) { axlDtdAttributeDecl * decl = _element; char * name = data; /* check the name */ if (axl_cmp (decl->name, name)) return axl_true; /* it is not the element */ return axl_false; } /** * @brief Allows to check if the stream contains a reference to a * entity, calling the resolver to get the replacement text to be * placed. * * @param resolver The function to be called with the replacement * text. This function must return the replacement text or NULL if it * fails. Failing to return a reference resolution will make the * entity reference to appear as is. * * @param resolver The entity reference resolver function to be called * to solve references found. * * @param data User defined data provided to the function, passed * directly to the resolver function once executed. * * @param stream The stream where the entity reference could appear. * * @param prefix The reference prefix to recognize. Values allowed * are: % (DTD references) and & (general entity references). * * @param error Optional reference to the axlError to report textual * diagnostic errors. * * @return The function return \ref axl_false if some error while * resolving entity references was found. Otherwise the function * return axl_true. */ axl_bool axl_dtd_check_entity_ref_and_expand (axlDtdEntityResolver resolver, axlPointer data, axlStream * stream, const char * prefix, axlError ** error) { char * string_aux; char * new_value; int index; /* check if we have an entity reference using the provided prefix */ index = axl_stream_get_index (stream); if (! (axl_stream_inspect (stream, prefix, 1) > 0)) return axl_true; /* get the entity reference until the end */ string_aux = axl_stream_get_until (stream, NULL, NULL, axl_true, 1, ";"); if (string_aux == NULL) { axl_error_new (-1, "null value received while expecting to find the entity reference to resolve.", stream, error); axl_stream_free (stream); return axl_false; } /* end if */ axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found entity reference: %s%s;...resolving", prefix, string_aux); /* resolve the reference */ new_value = (char *) resolver (string_aux, data); if (new_value == NULL) { axl_stream_move (stream, index); return axl_true; } /* end if */ /* accept content consumed */ axl_stream_accept (stream); axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "entity resolved to: %s", new_value); /* place the replacement data at the start of the stream */ new_value = axl_strdup_printf ("%s ", new_value); axl_stream_push (stream, new_value, strlen (new_value)); axl_free (new_value); return axl_true; } /** * @internal Entity resolver used by __axl_dtd_parse_attlist. */ const char * __axl_dtd_entity_resolver (const char * entityName, axlPointer data) { /* return the entity resolution */ return axl_dtd_entity_value ((axlDtd *) data, entityName, PARAMETER_ENTITY); } /* end if */ axlList * __axl_dtd_parse_enumvalues (const char * _enum_values) { char ** result; int iterator; axlList * list; result = axl_stream_split (_enum_values, 1, "|"); iterator = 0; list = axl_list_new (axl_list_always_return_1, axl_free); while (result[iterator]) { /* clean the enum value */ axl_stream_trim (result[iterator]); /* add to the list */ axl_list_add (list, axl_strdup (result[iterator])); /* update the iterator value */ iterator++; } /* end while */ /* free tokens */ axl_stream_freev (result); /* return the list */ return list; } /** * @internal function used by \ref axl_dtd_attr_validation function to * lookup ATTLIST contraints flaged as unique ID. */ axl_bool __find_id_decl (axlPointer _element, axlPointer data) { /* return the comparision */ return (((axlDtdAttributeDecl *) _element)->type == TOKENIZED_TYPE_ID); } /* end __find_id_decl */ /** * @internal * * Parse the attributes == NULL) dtd->attributes = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) axl_dtd_attribute_free); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* get the element name */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_false, 1, " "); if (string_aux == NULL) { axl_error_new (-1, "Expected to receive a DTD attribute name for ", string_aux); /* find the node that holds all attr declarations for the node found */ attribute = axl_dtd_get_attr (dtd, string_aux); /* check if found */ if (attribute == NULL) { /* create the axlDtdAttribute holder */ attribute = axl_new (axlDtdAttribute, 1); /* record the node to which the list of rules applies */ axl_stream_nullify (stream, LAST_CHUNK); attribute->name = string_aux; /* init the attribute rule list */ attribute->list = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) axl_dtd_attribute_decl_free); /* now configure this new attribute inside the dtd */ axl_list_add (dtd->attributes, attribute); } /* end if */ /* now get the list of attributes */ while (1) { axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "finding next att declaration"); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* check if we have finished */ if (axl_stream_inspect (stream, ">", 1) > 0) break; /* get the attribute name the rules applies */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_false, 1, " "); if (string_aux == NULL) { axl_error_new (-1, "Expected to receive an attribute name for name = string_aux; /* add the attribute constraint to the list */ axl_list_add (attribute->list, decl); axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "find constraint for attribute name=%s", decl->name); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "checking constraint type.."); /* check for an entity reference and expand the stream * content with its resolution */ if (! axl_dtd_check_entity_ref_and_expand (__axl_dtd_entity_resolver, dtd, stream, "%", error)) return axl_false; axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "about to check attr constraint type, stream status: '%s'", axl_stream_get_following (stream, 30)); /* now check the contraint type */ if (axl_stream_inspect (stream, "NOTATION", 8) > 0) { /* parse notation declaration */ }else if (axl_stream_inspect (stream, "(", 1) > 0) { /* parse enum declaration */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_true, 1, ")"); if (string_aux == NULL) { axl_error_new (-1, "expected to find enum declaration but termination caracter ')' was not found", stream, error); axl_stream_free (stream); return axl_false; } /* end if */ decl->type = ENUMERATION_TYPE; decl->enumvalues = __axl_dtd_parse_enumvalues (string_aux); }else { /* set the attribute type */ if (axl_stream_inspect (stream, "CDATA", 5) > 0) { decl->type = CDATA_ATTRIBUTE; } else if (axl_stream_inspect (stream, "IDREFS", 6) > 0) { /* flag the type */ decl->type = TOKENIZED_TYPE_IDREFS; /* flag the dtd to have a IDREF declaration */ dtd->haveIdRefDecl = axl_true; } else if (axl_stream_inspect (stream, "IDREF", 5) > 0) { /* notify type found */ decl->type = TOKENIZED_TYPE_IDREF; /* flag the dtd to have a IDREF declaration */ dtd->haveIdRefDecl = axl_true; } else if (axl_stream_inspect (stream, "ID", 2) > 0) { /* notify the type found */ decl->type = TOKENIZED_TYPE_ID; /* flag the dtd to have a ID declaration */ dtd->haveIdDecl = axl_true; } else if (axl_stream_inspect (stream, "ENTITY", 6) > 0) decl->type = TOKENIZED_TYPE_ENTITY; else if (axl_stream_inspect (stream, "ENTITIES", 8) > 0) decl->type = TOKENIZED_TYPE_ENTITIES; else if (axl_stream_inspect (stream, "NMTOKENS", 8) > 0) decl->type = TOKENIZED_TYPE_NMTOKENS; else if (axl_stream_inspect (stream, "NMTOKEN", 7) > 0) decl->type = TOKENIZED_TYPE_NMTOKEN; else { axl_error_new (-1, "Unrecognied attr type declaration found, check your 0) { decl->defaults = ATT_REQUIRED; } else if (axl_stream_inspect (stream, "#IMPLIED", 8) > 0) { decl->defaults = ATT_IMPLIED; } else { decl->defaults = ATT_IMPLIED; if (axl_stream_inspect (stream, "#FIXED", 6) > 0) { decl->defaults = ATT_FIXED; /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); } /* check default value for this case */ if (! (axl_stream_peek (stream, "\"", 1) > 0 || axl_stream_peek (stream, "'", 1) > 0)) { err_msg = axl_strdup_printf ("Unable to find default attribute declaration (#REQUIRED, #IMPLIED, #FIXED) for attribute %s, node <%s>", decl->name, attribute->name); axl_error_new (-1, err_msg, stream, error); axl_stream_free (stream); axl_free (err_msg); return axl_false; } /* end if */ } /* end if */ /* check constraint for ID types */ if (decl->type == TOKENIZED_TYPE_ID) { /* check that the node doesn't have any unique * id declared */ /* check if the node have TOKENIZED_TYPE_ID */ declAux = axl_list_lookup (attribute->list, __find_id_decl, NULL); if (declAux != NULL && !axl_cmp (declAux->name, decl->name)) { err_msg = axl_strdup_printf ("Found ATTLIST declaration, with several ID declarations name, decl->name); axl_error_new (-1, err_msg, stream, error); axl_stream_free (stream); axl_free (err_msg); return axl_false; } /* end if */ /* check required and implied */ if (decl->defaults != ATT_REQUIRED && decl->defaults != ATT_IMPLIED) { err_msg = axl_strdup_printf ("Found ATTLIST declaration, with ID, that don't have configured either #IMPLICIT or #REQUIRED for attribute %s, node <%s>", decl->name, attribute->name); axl_error_new (-1, err_msg, stream, error); axl_stream_free (stream); axl_free (err_msg); return axl_false; } /* end if */ } /* end if */ /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* nullify to check this value later */ string_aux = NULL; if (axl_stream_inspect (stream, "\"", 1) > 0) { /* get until */ string_aux = axl_stream_get_until (stream, NULL, NULL, axl_true, 1, "\""); } else if (axl_stream_inspect (stream, "'", 1) > 0) { /* get until */ string_aux = axl_stream_get_until (stream, NULL, NULL, axl_true, 1, "\'"); } /* end if */ /* check if default value was found */ if (string_aux != NULL) { /* found default value, check if we have an * enumeration type, enforcing that the value * defined to be inside the enumeration */ if (decl->type == ENUMERATION_TYPE) { if (axl_list_lookup (decl->enumvalues, axl_list_find_string, string_aux) == NULL) { axl_error_new (-1, "Configured a default value for an attribute list which only accepts a set of enum values that do not containt it.", stream, error); axl_stream_free (stream); return axl_false; } /* end if */ } /* end if */ /* nullify value and make string_aux to be * owned by the axlDtdAttributeDecl * reference */ axl_stream_nullify (stream, LAST_CHUNK); decl->default_value = string_aux; } /* end if */ } /* end while */ /* properly parsed */ return axl_true; } /** * @internal * * Destroy the provided entity reference and all allocated memory. * * @param entity The entity the deallocate. */ void axl_dtd_entity_free (axlDtdEntity * entity) { /* the entity reference */ axl_return_if_fail (entity); /* free the entity name */ if (entity->name) axl_free (entity->name); /* free the content */ if (entity->content) axl_free (entity->content); /* free external data if defined */ if (entity->data) { /* free system literal */ if (entity->data->system_literal) axl_free (entity->data->system_literal); /* free public literal */ if (entity->data->public_literal) axl_free (entity->data->public_literal); /* free ndata literal */ if (entity->data->ndata) axl_free (entity->data->ndata); /* free the node itself */ axl_free (entity->data); } /* free the node */ axl_free (entity); return; } /** * @internal * * Parses an entity definition from the current status of the stream * provided. */ axl_bool __axl_dtd_parse_entity (axlDtd * dtd, axlStream * stream, axlError ** error) { char * string_aux; int matched_chunk; axlDtdEntity * entity; /* init the dtd element list */ if (dtd->entities == NULL) dtd->entities = axl_list_new (axl_list_always_return_1, (axlDestroyFunc) axl_dtd_entity_free); /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* get for the first element declaration */ if (! (axl_stream_inspect (stream, " 0)) { axl_error_new (-1, "Expected to receive a entities, entity); /* check for parameter entity definition */ if (axl_stream_inspect (stream, "%", 1) > 0) { /* set the entity type */ entity->type = PARAMETER_ENTITY; /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); } else entity->type = GENERAL_ENTITY; /* get the element name */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_false, 1, " "); if (string_aux == NULL) { axl_error_new (-1, "Expected to receive a DTD entity name for name = string_aux; /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* now check if we have an external reference */ if (axl_stream_inspect (stream, "PUBLIC", 6) > 0) { /* we have a public external resource definition */ }else if (axl_stream_inspect (stream, "SYSTEM", 6) > 0) { /* we have a system definition */ }else { /* we have a plain value get the content remove next " and ' if defined */ if (! ((axl_stream_inspect (stream, "\"", 1) > 0))) { if (! (axl_stream_inspect (stream, "\'", 1) > 0)) { axl_error_new (-2, "Expected to find entity value initiator (\") or ('), every entity value must start with them", stream, error); axl_stream_free (stream); return axl_false; } /* knowing that ' was matched, now get the attribute value */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_true, 1, "'"); }else { /* knowhing that " was matched, now get the attribute value */ string_aux = axl_stream_get_until (stream, NULL, &matched_chunk, axl_true, 1, "\""); } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "entity value found: [%s]", string_aux); /* nullify internal reference so we have the * only one reference to entity content value * inside string_aux */ axl_stream_nullify (stream, LAST_CHUNK); /* set the value */ entity->content = string_aux; } /* consume previous white spaces */ AXL_CONSUME_SPACES (stream); /* check last item to parse */ if (! (axl_stream_inspect (stream, ">", 1) > 0)) { axl_error_new (-2, "Expected to find entity definition terminator (>), but it wasn't found", stream, error); axl_stream_free (stream); return axl_false; } return axl_true; } /** * @internal * * Implements DTD parsing, reading it from a direct buffer, or a file * path or a file handle. */ axlDtd * __axl_dtd_parse_common (const char * entity, int entity_size, const char * file_path, int fd_handle, axlError ** error) { axlStream * stream; axlDtd * dtd; int iterator; /* create the stream associated */ stream = axl_stream_new (entity, entity_size, file_path, fd_handle, error); axl_return_val_if_fail (stream, NULL); dtd = __axl_dtd_new (); axl_stream_link (stream, dtd, (axlDestroyFunc) axl_dtd_free); iterator = 0; while (axl_stream_remains (stream)) { /* get rid from comments found */ if (! axl_doc_consume_comments (NULL, stream, error)) return NULL; /* check for element declaration */ if (axl_stream_peek (stream, " 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found DTD element declaration"); /* found element declaration */ if (! __axl_dtd_parse_element (dtd, stream, error)) return NULL; continue; } /* check for attribute list declarations */ if (axl_stream_inspect (stream, " 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found DTD attribute list declaration"); /* parse it */ if (! __axl_dtd_parse_attlist (dtd, stream, error)) return NULL; continue; } /* check for the entity declaration */ if (axl_stream_peek (stream, " 0) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found DTD entity declaration"); /* parse the entity definition */ if (! __axl_dtd_parse_entity (dtd, stream, error)) return NULL; continue; } /* stop the loop */ if (iterator == 3) { axl_error_new (-1, "unable to process DTD content, unable to find expected information (no elements != NULL) dtd->root = __axl_dtd_get_new_root (dtd); /* check if the DTD has ID declarations if found IDREF * declarations */ if (! dtd->haveIdDecl && dtd->haveIdRefDecl) { axl_error_new (-1, "DTD semantic error, found IDREF attribute declaration but no attribute ID declaration was found.", stream, error); axl_stream_free (stream); return NULL; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "DTD load COMPLETE"); axl_stream_unlink (stream); axl_stream_free (stream); return dtd; } /** * @brief Allows to parse the provided entity, which is expected to * contain a DTD (Document Type Definition). * * @param entity The document type definition to parse. * * @param entity_size The document size, or -1 to make the function to * figure out current size. * * @param error An optional \ref axlError where errors will be reported. * * @return A newly allocated \ref axlDtd that must be deallocated when * no longer need with \ref axl_dtd_free. The function could return * NULL on failure detected. On that case, it is requred to check \ref * axlError variable, if defined. */ axlDtd * axl_dtd_parse (const char * entity, int entity_size, axlError ** error) { return __axl_dtd_parse_common (entity, entity_size, NULL, -1, error); } /** * @brief Allows to parse the provided DTD definition, which is found * on the provided file path. * * @param file_path The file path where it is expected to receive a * DTD file. * * @param error An optional \ref axlError reference where all errors found will be reported. * * @return A newly allocated \ref axlDtd instance or NULL if it fails. * * Making a DTD to be inline loaded:
* * It may be helpful to make the DTD definition available at your * binary, inline compiled, to avoid distributing DTD files along with * libraries, etc. This also solves installation problems like * provisioning a default location to make your application to find * such files. * * With the following command you can create an inline representation * from your DTD file: * \code * >> axl-knife --input your-file.dtd --dtd-to-c --output your-file.dtd.h --ifnewer * \endcode * * This will create a header with an C-macro style definition of your * DTD. Now, you can include it using: * * \code * #include * \endcode * * In the case you are developing a library, it is recommended to do * such include at the body implementation (usually .c or .cpp files, * to avoid requiring your API consumers to also include your DTD * inline definition). * * Now, to load your DTD file, use the following: * * \code * axlError * err = NULL; * axlDtd * dtd = axl_dtd_parse (YOUR_FILE_DTD, -1, &err); * if (dtd == NULL) { * // This won't happen unless axl-runtime error found, since axl-knife * // checks your dtd file before producing the in-line definition. * // However, bug happens! check this. * } * \endcode */ axlDtd * axl_dtd_parse_from_file (const char * file_path, axlError ** error) { return __axl_dtd_parse_common (NULL, -1, file_path, -1, error); } /** * @internal * * Support function for axl_dtd_validate which checks if the provided * parent have its childs configuration according to the values * expresed on the sequenced represented by the itemList. * * The function return axl_true if the validation was ok, or axl_false * if something have failed. It also creates an error, using the * optional axlError reference received. */ axl_bool __axl_dtd_validate_sequence (axlNode * parent, int * child_position, axlDtdElementList * itemList, axlError ** error, axl_bool try_match, axl_bool top_level) { int iterator = 0; int child_pos = *child_position; axlNode * node; axlDtdElementListNode * itemNode; axl_bool status = axl_false; axl_bool one_matched; AxlDtdTimes times; axl_return_val_if_fail (parent, axl_false); axl_return_val_if_fail (itemList, axl_false); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "validating a sequence list: iterator=%d, item list count=%d, at child position=%d", iterator, axl_dtd_item_list_count (itemList), child_pos); /* iterate over the sequence, checking its order */ while (iterator < axl_dtd_item_list_count (itemList)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "getting next item node from the DTD item list at: %d", iterator); /* get the item node specification */ itemNode = axl_dtd_item_list_get_node (itemList, iterator); one_matched = axl_false; times = axl_dtd_item_node_get_repeat (itemNode); do { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "getting node at child position: %d", child_pos); /* get the node that is located at the same position * than the sequence */ if (child_pos < axl_node_get_child_num (parent)) { node = axl_node_get_child_nth (parent, child_pos); } else node = NULL; /* the node child list have ended, check if * this situation was expected */ if (node == NULL) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "no more child nodes to validate at %d, for parent: %s, times: %d, iterator: %d, item count: %d", child_pos, axl_node_get_name (parent), times, iterator, axl_dtd_item_list_count (itemList)); /* check if we were working with a * list, which have matched at least * one item */ if (times == ONE_OR_MANY && one_matched && status) { if ((iterator + 1) == axl_dtd_item_list_count (itemList)) { *child_position = child_pos; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sequence validated with child position (III): %d", child_pos); return axl_true; } /* reached this point we have matched a one to many with at least one match */ break; } /* check that the rest of the * specification item is optional, * including the one used */ status = axl_true; do { if (times != ZERO_OR_MANY && times != ZERO_OR_ONE) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found item, inside the DTD item list, that is not optional: %d (repeat value: %d)", iterator, times); status = axl_false; break; } /* update index and get the next item */ iterator++; if (iterator < axl_dtd_item_list_count (itemList)) itemNode = axl_dtd_item_list_get_node (itemList, iterator); }while (status && (iterator < axl_dtd_item_list_count (itemList))); /* check status before checking the rest of the item spec */ if (status) { *child_position = child_pos; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sequence validated with child position (II): %d", child_pos); return axl_true; } /* check if a try match is being runned */ if (! try_match) { axl_error_report (error, -1 , "Found that DTD specifies more nodes to be hold by the parent (<%s>), but no more childs were found", axl_node_get_name (parent)); } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found that no nodes left to satisfy DTD validation operation"); *child_position = child_pos; return axl_false; } /* check node type */ if (axl_dtd_item_node_get_type (itemNode) == AXL_ELEMENT_LIST) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "the item node is an item list, dtd item list position: %d, child position: %d=<%s>", iterator, child_pos, axl_node_get_name (node)); /* element list found, validate its content */ if (! __axl_dtd_validate_item_list (axl_dtd_item_node_get_list (itemNode), parent, &child_pos, error, axl_false)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sub item list validation have failed (not critical)"); /* check if we are the top * level list and the itemNode * checked is the last one * item on the item list */ if (top_level && ((iterator + 1) == axl_node_get_child_num (parent))) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "found that the last item list wasn't matched"); } *child_position = child_pos; return axl_false; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "validated item list, child position after: %d", child_pos); /* because child position updating and * repeat matching is already handled * by dtd_validate_item_list function * we just continue with the next * iteration */ break; } else if (axl_dtd_item_node_get_type (itemNode) == AXL_ELEMENT_NODE) { /* check the name against the spec */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "the item node is a final content particule definition: %s", axl_dtd_item_node_get_value (itemNode)); status = NODE_CMP_NAME (node, axl_dtd_item_node_get_value (itemNode)); } /* check previous status */ if ((times == ONE_AND_ONLY_ONE) || (times == ONE_OR_MANY && one_matched == axl_false)) { if (! status) { /* only report an upper level * error if we are not running * a try match */ if (! try_match) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "Found different node (<%s>) for a sequence expected (<%s>), at child position: %d, item list pos: %d", axl_node_get_name (node), axl_dtd_item_node_get_value (itemNode), child_pos, iterator); axl_error_report (error, -1, "Found different node (<%s>) for a sequence expected (<%s>), at child position: %d, item list pos: %d", axl_node_get_name (node), axl_dtd_item_node_get_value (itemNode), child_pos, iterator); } /* return that a match wasn't possible */ *child_position = child_pos; return axl_false; } } /* according to the repetition pattern, update loop indexes */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "updating child nodes references: %d, repeat type: %d, status=%d", child_pos, times, status); /* one only item to match and exactly one */ if (times == ONE_AND_ONLY_ONE) { child_pos++; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "updated child position to: %d, repeat type: %d, status=%d", child_pos, times, status); break; } /* one or many items to match */ if (times == ONE_OR_MANY) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "matched one to many item node: status=%d one_matched=%d", status, one_matched); /* if the match have failed and * previous matches was ok, it seems * we have reached the next * items. Just break the loop */ if (status == axl_false && one_matched == axl_true) break; child_pos++; one_matched = axl_true; continue; /* don't break the loop */ } /* zero or optionally one item to match */ if (times == ZERO_OR_ONE) { /* if previous status was ok, it seems * that we have matched the optional * character. In that case, move the * index to the following value. If * not, just break the loop. */ if (status == axl_true) child_pos++; break; } /* zero or many items to match */ if (times == ZERO_OR_MANY) { if (status == axl_true) { one_matched = axl_true; child_pos++; continue; } break; } /* until break the loop */ }while (axl_true); /* update iterator index */ iterator++; } /* check if more nodes where specified than the DTD spec */ times = axl_dtd_item_list_repeat (itemList); if ((times == ONE_OR_MANY || times == ONE_AND_ONLY_ONE) && top_level && (child_pos < axl_node_get_child_num (parent))) { /* drop a log */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "parent node <%s> have more childs=%d than the childs iterated=%d, top_level=%d", axl_node_get_name (parent), axl_node_get_child_num (parent), child_pos, top_level); /* do not report an error found if a try match is * being run */ if (! try_match) { axl_error_new (-1, "More childs, than the ones especified in the DTD, were found", NULL, error); } /* return that the match wasn't possible */ *child_position = child_pos; return axl_false; } /* return that the sequence has been validated */ *child_position = child_pos; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sequence validated with child position (I): %d", child_pos); return axl_true; } /** * @internal * * Internal support function to validate the choice list. */ axl_bool __axl_dtd_validate_choice (axlNode * parent, int * child_position, axlDtdElementList * itemList, axlError ** error, axl_bool try_match, axl_bool top_level) { axlNode * node; axlDtdElementListNode * itemNode; int iterator; axl_bool status; AxlDtdTimes times; axl_bool one_match; if (*child_position < axl_node_get_child_num (parent)) { /* get a reference to be matched by the choice list */ node = axl_node_get_child_nth (parent, *child_position); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "validated choice list at position: %d=<%s>", *child_position, axl_node_get_name (node)); } else { /* tried to match a choice list with a child index * outside the maximum number of childs */ if (! try_match) { axl_error_new (-1, "Unable to match choice list, it seems that the are not enough childs to validate the choice list", NULL, error); } return axl_false; } iterator = 0; while (iterator < axl_dtd_item_list_count (itemList)) { /* get the DTD item list to match */ itemNode = axl_dtd_item_list_get_node (itemList, iterator); times = axl_dtd_item_node_get_repeat (itemNode); if (axl_dtd_item_node_get_type (itemNode) == AXL_ELEMENT_NODE) { /* reset match configuration */ one_match = axl_false; repeat_for_node: /* a node was found */ status = NODE_CMP_NAME (node, axl_dtd_item_node_get_value (itemNode)); /* know, if the node was matched check it * repetition configuration */ if (status) { /* update child position */ (*child_position)++; if (times == ONE_AND_ONLY_ONE || times == ZERO_OR_ONE) { /* the node was matched and * the itemNode has a one and * only one configuration, * just return that the choice * list was matched */ return axl_true; } if (times == ONE_OR_MANY || times == ZERO_OR_MANY) { /* because the node was matched, but the repetition * pattern allows to match more nodes we have to * iterate a bit more */ node = axl_node_get_child_nth (parent, *child_position); if (node == NULL) { /* because we already matched at least one item, * we can assume that the itemNode was successfully * matched for both cases (*) and (+). */ return axl_true; } /* flag the one match */ one_match = axl_true; /* if the node reference is * not NULL, try to match the * next item */ goto repeat_for_node; } } /* end if */ /* before returning, that that we have matched * previously, at least, one node for * one-to-many and zero-to-many pattern */ if ((times == ONE_OR_MANY || times == ZERO_OR_MANY) && one_match) { return axl_true; } } else if (axl_dtd_item_node_get_type (itemNode) == AXL_ELEMENT_LIST) { /* an element list was found, call to validate it */ /* element list found, validate its content */ if (__axl_dtd_validate_item_list (axl_dtd_item_node_get_list (itemNode), parent, child_position, error, axl_false)) { /* item list matched */ return axl_true; } } /* no item was matched, update iterator indexes */ iterator++; } /* seems that the choice list wasn't matched */ if (! try_match) { axl_error_new (-1, "Unable to match choice list, after checking all posibilities, choice list wasn't validated", NULL, error); } return axl_false; } /** * @internal * * Tries to perform a validation, based on the item list received and * the repetition configuration. * * @param itemList The item list containing DTD content spec * information used to validate. * * @param parent The parent node where the validation process is being * applied. The content spec refers to the childs the parent has. * * @param stack An stack used by the overall process to store the * subsequent parents to be validated. This stack must be released if * a error is found. * * @param error An optional axlError reference containing the error * textual diagnostic if found. * * @return axl_true if the validation was ok, otherwise axl_false is * returned. */ axl_bool __axl_dtd_validate_item_list (axlDtdElementList * itemList, axlNode * parent, int * child_position, axlError ** error, axl_bool top_level) { int temp_child_pos; axl_bool status; axl_bool already_matched; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "validating an item list with repeat pattern: %d, at %d, top level=%d", axl_dtd_item_list_repeat (itemList), *child_position, top_level); /* now check repetition type */ switch (axl_dtd_item_list_repeat (itemList)) { case ONE_AND_ONLY_ONE: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a (one and only one) spec.."); if (axl_dtd_item_list_type (itemList) == SEQUENCE) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "using a SEQUENCE form"); /* it is a choice, so the item list specifies * the nodes that could appear */ if (!__axl_dtd_validate_sequence (parent, child_position, itemList, error, axl_false, top_level)) { return axl_false; } }else { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "using a CHOICE form"); /* it is a sequence, so, item list * specification represents the nodes, in the * order they must appear */ if (!__axl_dtd_validate_choice (parent, child_position, itemList, error, axl_false, top_level)) { return axl_false; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "choice list was properly validated"); } break; case ZERO_OR_ONE: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a (zero or one) spec.."); if (axl_dtd_item_list_type (itemList) == SEQUENCE) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "using a SEQUENCE form, parent: <%s>", axl_node_get_name (parent)); /* because we are running a zero or one item * list matching, we don't care if it doesn't * match. In the case it match, the child * position is updated and next calls will be * properly aligned. In the match doesn't * happens, it also don't matter because the * pattern allow to not match */ temp_child_pos = *child_position; if (!__axl_dtd_validate_sequence (parent, child_position, itemList, error, axl_true, top_level)) { /* check that the match wasn't * produced, at any level */ if (temp_child_pos != *child_position) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "item list mismatch (%d != %d)", temp_child_pos, *child_position); axl_error_new (-1, "Found an DTD item list definition, that should be matched entirely or not, zero or one time, but it was matched partially", NULL, error); return axl_false; } return axl_false; } }else { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " using a CHOICE form"); /* it is a sequence, so, item list * specification represents the nodes, in the * order they must appear */ __axl_dtd_validate_choice (parent, child_position, itemList, error, axl_true, top_level); } break; case ZERO_OR_MANY: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a (zero or many) spec.."); if (axl_dtd_item_list_type (itemList) == SEQUENCE) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " using a SEQUENCE (size: %d) form ", axl_dtd_item_list_count (itemList)); /* one this case, several matches must be * tried, until the validation fails */ do { temp_child_pos = *child_position; status = __axl_dtd_validate_sequence (parent, child_position, itemList, error, axl_true, top_level); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sequence match status=%d", status); if (! status) { /* check that the match wasn't * produced, at any level */ if ((temp_child_pos != *child_position)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "item list mismatch (%d != %d)", temp_child_pos, *child_position); axl_error_new (-1, "Found an DTD item list definition, that should be matched entirely or not, zero or many times, but it was matched partially", NULL, error); return axl_false; } } }while (status); }else { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " using a CHOICE form"); /* it is a sequence, so, item list * specification represents the nodes, in the * order they must appear */ do { status = __axl_dtd_validate_choice (parent, child_position, itemList, error, axl_true, top_level); }while (status); } break; case ONE_OR_MANY: /* one or many sequence spec (+) */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found a (zero or many) spec.."); if (axl_dtd_item_list_type (itemList) == SEQUENCE) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " using a SEQUENCE (size: %d) form ", axl_dtd_item_list_count (itemList)); /* one this case, several matches must be * tried, until the validation fails */ already_matched = axl_false; do { temp_child_pos = *child_position; /* try to match the one or many sequence according to the value stored inside already matched */ status = __axl_dtd_validate_sequence (parent, child_position, itemList, error, already_matched, top_level); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "sequence match status=%d", status); if (! status) { /* check that the match wasn't * produced, at any level */ if ((temp_child_pos != *child_position)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "item list mismatch, matched partially (%d != %d)", temp_child_pos, *child_position); axl_error_new (-1, "Found an DTD item list definition, that should be matched entirely or not, one or many times, but it was matched partially", NULL, error); return axl_false; } }else { /* set that we have matched, at least, one item */ already_matched = axl_true; } }while (status); }else { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " using a CHOICE form"); /* it is a sequence, so, item list * specification represents the nodes, in the * order they must appear */ already_matched = axl_false; do { /* the next choice matching is done * according to the value stored in * already matched */ status = __axl_dtd_validate_choice (parent, child_position, itemList, error, already_matched, top_level); /* if the validation successed, set * that next matched are not required * to be successful ones */ if (status) already_matched = axl_true; }while (status); } break; default: /* this case will never be reached */ #define INTERNAL_ERROR_01 "critical error reached a place that shows the dtd parser is not properly defining the repetition pattern for the current itemList." __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, INTERNAL_ERROR_01); axl_error_new (-1, INTERNAL_ERROR_01, NULL, error); return axl_false; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "validate item list terminated, now check post-conditions, top_level=%d, item list type=%d, child pos=%d, childs num=%d", top_level, axl_dtd_item_list_type (itemList), *child_position, axl_node_get_child_num (parent)); /* check that, in the case that the choice item list is being * validated, ensure it has validated all nodes, especially if * we are the top level definition */ if (top_level && (axl_dtd_item_list_type (itemList) == CHOICE)) { if (((*child_position) + 1) < axl_node_get_child_num (parent)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "found that the choice list didn't cover all childs (%d), while parent=<%s> has: (%d)", (*child_position), axl_node_get_name (parent), axl_node_get_child_num (parent)); axl_error_new (-1, "Found that the validation process didn't cover all nodes, while using a choice list. This means that the xml document have more content than the DTD spec", NULL, error); return axl_false; } } /* element type children validated */ return axl_true; } /** * @internal * * Support function validate parent nodes which are element type * children ones. */ axl_bool __axl_dtd_validate_element_type_children (axlDtdElement * element, axlNode * parent, axl_bool top_level, axlError ** error) { axlDtdElementList * itemList; int child_pos = 0; char * err_msg; /* get a reference to the item list */ itemList = axl_dtd_get_item_list (element); /* check for xml nodes with fewer content than the initially * expected. */ if (axl_node_get_child_num (parent) < element->minimum_match) { err_msg = axl_strdup_printf ("Found that the parent node (<%s>) received doesn't contains enough xml nodes inside to get a proper validation (childs found (%d) != childs that should be found (%d)). This means that the xml document have fewer content than the DTD spec.", axl_node_get_name (parent), axl_node_get_child_num (parent), element->minimum_match); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* validate the item list, starting from the child 0 */ if (__axl_dtd_validate_item_list (itemList, parent, &child_pos, error, top_level)) { /* check if, at least, all minimum elements was * matched */ __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "checking minimum node match: (%d < min: %d) (%d < childs: %d) node=<%s>", child_pos, element->minimum_match, child_pos, axl_node_get_child_num (parent), axl_node_get_name (parent)); if (child_pos < axl_node_get_child_num (parent)) { axl_error_report (error, -1, "Found that the validation process didn't cover all nodes (%d < min:%d) (%d < childs:%d). All xml child nodes inside the parent=<%s> wasn't covered. This means that the xml document have more content than the DTD spec defines.", child_pos, element->minimum_match, child_pos, axl_node_get_child_num (parent), axl_node_get_name (parent)); return axl_false; } /* seems that the minimum match */ return axl_true; } return axl_false; } /** * @internal * Internal support function to validate #PCDATA nodes. */ axl_bool __axl_dtd_validate_element_type_pcdata (axlDtdElement * element, axlNode * parent, axlStack * stack, axlError ** error) { /* check for childs */ if (axl_node_have_childs (parent)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "node <%s> should be #PCDATA and it contains childs", axl_node_get_name (parent)); axl_error_new (-1, "Found a node for which its espeficiation makes it to be a node with only data and no childs, and it currently contains childs", NULL, error); return axl_false; } /* return that the validation was ok */ return axl_true; } /** * @internal * * Support function to validate empty nodes. */ axl_bool __axl_dtd_validate_element_type_empty (axlDtdElement * element, axlNode * parent, axlStack * stack, axlError ** error) { char * err_msg; /* check the node is indeed, empty */ if (! axl_node_is_empty (parent)) { err_msg = axl_strdup_printf ( "Found a node <%s> that it is especified that must be empty, but it isn't", axl_node_get_name (parent)); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* check the node doesn't have childs */ if (axl_node_have_childs (parent)) { err_msg = axl_strdup_printf ( "Found a node <%s> that it is especified that must be empty, but it has childs", axl_node_get_name (parent)); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* return that the validation was ok */ return axl_true; } axl_bool __axl_dtd_attr_validate_foreach (const char * key, const char * value, axlPointer data, axlPointer data2) { axlDtdAttribute * attribute = data; axlError ** error = data2; axlDtdAttributeDecl * decl; char * err_msg; /* get declaration associated */ decl = axl_list_lookup (attribute->list, __find_attr_decl, (axlPointer) key); if (decl == NULL) { /* found an error */ err_msg = axl_strdup_printf ("Found an attribute (%s) which is not specified by the attribute declaration for <%s>", key, attribute->name); axl_error_new (-1, err_msg, NULL, error); /* free the cursor and the error message */ axl_free (err_msg); /* return axl_true here because we want to stop the process */ return axl_true; } /* end if */ /* if the declaration is found, now check its * contraints */ axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "checking contraint for attribute: %s=%s", decl->name, value); if (decl->type == CDATA_ATTRIBUTE) { /* found free attribute declaration, go ahed */ } else if (decl->type == ENUMERATION_TYPE) { /* found an enumeration type, check and return */ if (axl_list_lookup (decl->enumvalues, axl_list_find_string, (char *) value) == NULL) { /* found an error */ err_msg = axl_strdup_printf ("Found an attribute (%s) with a value not allowed by the enum declaration (%s) for the node <%s>", key, value, attribute->name); axl_error_new (-1, err_msg, NULL, error); /* free the cursor and the error message */ axl_free (err_msg); return axl_true; } } else { /* not supported yet */ } /* return axl_false to continue with the process */ return axl_false; } axl_bool __axl_dtd_attr_validate_required (axlPointer element, axlPointer data) { axlNode * node = data; axlDtdAttributeDecl * decl = element; switch (decl->defaults) { case ATT_REQUIRED: /* attribute required */ return !HAS_ATTR (node, decl->name); case ATT_FIXED: return !HAS_ATTR_VALUE (node, decl->name, decl->default_value); default: break; } /* end switch */ /* return axl_false for this because it is not obligatory * to have the attribute defined. */ return axl_false; } /** * @internal Functions which validates the attribute declaration for * the node provided, using attribute declarations found. * * @param node The node to check for its attributes. * * @param dtd The dtd used to validate the node provided. * * @param error A reference to the axlError where the textual * diagnostic error will be reported. * * @return axl_true if the node is validated, axl_false if not. */ axl_bool axl_dtd_attr_validate (axlNode * node, axlDtd * dtd, axlError ** error, axlHash * id_validation, axlList * idref_validation) { axlDtdAttribute * attribute; axlDtdAttributeDecl * decl; char * err_msg; int iterator; axlError * _error = NULL; /* find attribute contraints for the node */ attribute = axl_dtd_get_attr (dtd, axl_node_get_name (node)); if (attribute == NULL) return axl_true; /* we have an especification, run it */ /* for each attribute found, check against the spec */ axl_node_attr_foreach (node, __axl_dtd_attr_validate_foreach, attribute, &_error); /* check the error */ if (! axl_error_was_ok (_error)) { /* reconfigure error returned */ if (error != NULL) *error = _error; return axl_false; } /* end if */ /* now, for each contraint, check that all required nodes * exists */ decl = axl_list_lookup (attribute->list, __axl_dtd_attr_validate_required, node); if (decl != NULL) { if (decl->defaults == ATT_FIXED) err_msg = axl_strdup_printf ("attribute required '%s' (or its value), due to #FIXED declaration, not found for node <%s>", decl->name, attribute->name); else err_msg = axl_strdup_printf ("attribute required '%s', due to #REQUIRED declaration, not found for node <%s>", decl->name, attribute->name); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* end if */ /* check declarations */ if (dtd->haveIdDecl) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found DTD has ID unique attribute declaration.."); /* check if the node have TOKENIZED_TYPE_ID */ decl = axl_list_lookup (attribute->list, __find_id_decl, NULL); /* if we have a tokenized */ if (decl != NULL) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "found ID unique attribute declaration %s=\"%s\"..", decl->name, ATTR_VALUE (node, decl->name)); /* check if the attribute value for the decl that is * flagged as ID is already found at the * id_validation */ if (axl_hash_exists (id_validation, (axlPointer) ATTR_VALUE (node, decl->name))) { err_msg = axl_strdup_printf ("DTD declared the attribute '%s' as unique (ID) for the node %s, but was found used several times", decl->name, attribute->name); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* end if */ /* seems the attribute was not used, nice!, store it */ axl_hash_insert (id_validation, (axlPointer) ATTR_VALUE (node, decl->name), (axlPointer) ATTR_VALUE (node, decl->name)); } /* end if */ } /* end if */ if (dtd->haveIdRefDecl) { /* find the id ref declaration */ iterator = 0; while (iterator < axl_list_length (attribute->list)) { /* get the attribute declaration at the * particular position */ decl = axl_list_get_nth (attribute->list, iterator); if (decl->type == TOKENIZED_TYPE_IDREF) { /* found a reference, but do not check * it at this place becase the * reference could be placed at any * part in the document event after * the reference pointed is * defined. store and check later */ if (ATTR_VALUE (node, decl->name)) { /* store the id ref reference * if defined */ axl_list_add (idref_validation, (axlPointer) ATTR_VALUE (node, decl->name)); } } /* end if */ /* get the next */ iterator++; } /* end if */ } /* end if */ axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "attributes validated for node=<%s>", attribute->name); return axl_true; } /** * @internal Function used by axl_dtd_validate_references to ensure * that all references found point to a valid reference defined. */ axl_bool __axl_dtd_reference_check (axlPointer _element, axlPointer data) { #if defined(SHOW_DEBUG_LOG) const char * value = _element; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "checking id ref: %s", value); #endif return ! axl_hash_exists ((axlHash *) data, _element); } /** * @internal Function that validates all references found (from IDREF * attribute) to unique references (defined by ID attributes). * */ axl_bool axl_dtd_validate_references (axlHash * id_validation, axlList * idref_validation, axlError ** error) { char * reference; char * err_msg; /* if no empty at the valiadtion reference list, means not * reference was done, so there is no room for errors */ if (idref_validation == NULL) return axl_true; __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "id_validation reference: 0x%x", id_validation); /* find first reference not found */ reference = axl_list_lookup (idref_validation, __axl_dtd_reference_check, id_validation); if (reference != NULL) { /* found a reference not defined, report it to the * application level */ err_msg = axl_strdup_printf ("Found a reference defined ('%s') which is not found in any ID attribute in the document", reference); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* end if */ /* validation ok */ return axl_true; } /** * @brief Allows to validate the given XML document (\ref axlDoc) * against the given document type definition (DTD, \ref axlDtd). * * This function allows to validate your XML documents providing the * document type definition, that was read using \ref axl_dtd_parse or * \ref axl_dtd_parse_from_file. * * Keep in mind that a document could be well-formed and valid. The * only difference is that valid XML document are those that, meet all * XML rules, but also are clasified and recognized as XML documents * with some particular structure, that is represented (or * constrained) with providing a DTD definition. * * @param doc The \ref axlDoc containing the XML document to be * validated. * * @param dtd The \ref axlDtd containing the DTD definition used to * validate the document. * * @param error An optional reference to a \ref axlError object where * validation errors are reported. * * @return axl_true if the document is valid, axl_false if not. */ axl_bool axl_dtd_validate (axlDoc * doc, axlDtd * dtd, axlError ** error) { axlNode * parent; axlStack * stack; axlHash * id_validation = NULL; axlList * idref_validation = NULL; axlDtdElement * element; axl_bool top_level; char * err_msg; axl_bool result; /* perform some checkings */ axl_return_val_if_fail (doc, axl_false); axl_return_val_if_fail (dtd, axl_false); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "starting DTD validation"); /* validate the very first root node */ parent = axl_doc_get_root (doc); element = axl_dtd_get_root (dtd); if ((element != NULL) && ! NODE_CMP_NAME (parent, axl_dtd_get_element_name (element))) { /* because a DTD document could have several top level * elements, ensure this is not the case */ element = axl_dtd_get_element (dtd, axl_node_get_name (parent)); if (element == NULL) { /* || ! axl_dtd_element_is_toplevel (dtd, element)) { */ /* root node doesn't match */ err_msg = axl_strdup_printf ("Found that root node doesn't match (%s != %s!", axl_node_get_name (parent), axl_dtd_get_element_name (element)); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* end if */ } /* end if */ /* check if the node has DTD element declaration */ if (element == NULL) { err_msg = axl_strdup_printf ("There is not DTD element declaration to validate the node <%s>", axl_node_get_name (parent)); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); return axl_false; } /* end if */ /* check if the dtd contains a Id declaration */ if (dtd->haveIdDecl) { /* seems the user have declarted ID attributes init the hash */ id_validation = axl_hash_new (axl_hash_string, axl_hash_equal_string); } /* end if */ /* check if the dtd contains Id ref declarations */ if (dtd->haveIdRefDecl) { /* create a list that could contain all references done */ idref_validation = axl_list_new (axl_list_always_return_1, NULL); } /* end if */ /* check empty content spec */ if (axl_dtd_get_element_type (element) == ELEMENT_TYPE_EMPTY) { /* check if the document provided have only one node */ result = axl_node_is_empty (parent) && !axl_node_have_childs (parent) && axl_dtd_attr_validate (parent, dtd, error, id_validation, idref_validation); /* check references */ if (result) result = axl_dtd_validate_references (id_validation, idref_validation, error); /* free and return */ axl_hash_free (id_validation); /* free the list */ axl_list_free (idref_validation); return result; } /* end if */ /* queue initial nodes to validate */ stack = axl_stack_new (NULL); /* set that the only top level node is the first one */ top_level = axl_true; do { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "doing a DTD iteration: <%s>...", axl_node_get_name (parent)); /* validate attributes */ if (! axl_dtd_attr_validate (parent, dtd, error, id_validation, idref_validation)) { /* free the stack */ axl_stack_free (stack); /* free id_validation */ axl_hash_free (id_validation); /* free the list */ axl_list_free (idref_validation); return axl_false; } /* reach this position, the reference contains * a reference to the parent node, which will be used * to validate current child content against current * configuration for dtd element constraining it. * * equally, the reference contains a dtd * reference to the already checked DTD element which * configure this parent node. */ switch (axl_dtd_get_element_type (element)) { case ELEMENT_TYPE_PCDATA: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " find PCDATA dtd element=%s: parent=<%s>, ", axl_dtd_get_element_name (element), axl_node_get_name (parent)); /* ok, a leaf node was found, know it is * required to check that the node doesn't * have more childs and only have content, * that is, it is not empty */ if (!__axl_dtd_validate_element_type_pcdata (element, parent, stack, error)) { /* free id_validation */ axl_hash_free (id_validation); /* free the stack */ axl_stack_free (stack); /* free the list */ axl_list_free (idref_validation); return axl_false; } break; case ELEMENT_TYPE_CHILDREN: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " find CHILDREN dtd element"); /* ok, a parent node that have childs */ if (!__axl_dtd_validate_element_type_children (element, parent, top_level, error)) { /* free id_validation */ axl_hash_free (id_validation); /* free the stack */ axl_stack_free (stack); /* free the list */ axl_list_free (idref_validation); return axl_false; } break; case ELEMENT_TYPE_EMPTY: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " find EMPTY dtd element"); /* the element especification is empty, the * node being validated must also be the * same */ if (!__axl_dtd_validate_element_type_empty (element, parent, stack, error)) { /* free id_validation */ axl_hash_free (id_validation); /* free the stack */ axl_stack_free (stack); /* free the list */ axl_list_free (idref_validation); return axl_false; } break; case ELEMENT_TYPE_ANY: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " find ANY dtd element"); /* the anything is allowed cased from this * parent node. */ goto continue_with_validation; case ELEMENT_TYPE_MIXED: __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, " find MIXED dtd element"); /* the mixed case, where nodes and PC data * could be mixed */ break; default: /* do not do any thing on this case */ break; } /* queue more childs, as future parents to be * validated on the provided queue, only in the case * the parent node have childs */ if (axl_node_have_childs (parent)) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "parent node <%s> have childs, adding its childs (stack size: %d)", axl_node_get_name (parent), axl_stack_size (stack)); /* queue childs to be processed */ __axl_dtd_queue_childs (stack, parent); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "parent node <%s> childs: %d, (stack size: %d)", axl_node_get_name (parent), axl_node_get_child_num (parent), axl_stack_size (stack)); } /* set the parent reference to NULL */ parent = NULL; /* update the reference to the new parent node, only * if there are new parents on the stack */ continue_with_validation: if (! axl_stack_is_empty (stack)) { /* get a new reference */ parent = axl_stack_pop (stack); /* get a reference to the DTD element to used */ element = axl_dtd_get_element (dtd, axl_node_get_name (parent)); if (element == NULL) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "found that the node <%s> doesn't have DTD especification", axl_node_get_name (parent)); /* prepare the error message */ err_msg = axl_strdup_printf ("Found a node <%s> that doesn't have a DTD element espefication to validate it, DTD validation failed", axl_node_get_name (parent)); axl_error_new (-1, err_msg, NULL, error); axl_free (err_msg); /* free id_validation */ axl_hash_free (id_validation); /* free the list */ axl_list_free (idref_validation); /* free the stack */ axl_stack_free (stack); return axl_false; } /* end if */ } /* end if */ /* set the top level status */ top_level = axl_false; /* until the stack is empty */ }while (parent != NULL); /* check references */ result = axl_dtd_validate_references (id_validation, idref_validation, error); /* deallocate stack used */ axl_stack_free (stack); /* free id_validation */ axl_hash_free (id_validation); /* free the list */ axl_list_free (idref_validation); __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "DTD validation, %s", result ? "ok" : "failed"); /* the document is valid */ return result; } /** * @brief Allows to check if the provided two references represents * DTD documents with the same rules. * * @param dtd First reference to compare. * @param dtd2 Second reference to compare. * * @return axl_true if both references represent the same document. If * some of the references received are NULL the function returns * axl_false. * * NOTE: The function does not have the ability to perform a smart * equal operation like detecting DTD that are semantically * equivalent. It only checks internal structure. */ axl_bool axl_dtd_are_equal (axlDtd * dtd, axlDtd * dtd2) { int iterator; int iterator2; int iterator3; axlDtdEntity * entity, * entity2; axlDtdElement * element, * element2; axlDtdElementListNode * node, * node2; axlDtdAttribute * attribute, * attribute2; axlDtdAttributeDecl * attr_decl, * attr_decl2; /* check references received */ if (dtd == NULL) return axl_false; if (dtd2 == NULL) return axl_false; /* check each rule inside both documents */ if (axl_list_length (dtd->entities) != axl_list_length (dtd2->entities)) return axl_false; if (axl_list_length (dtd->elements) != axl_list_length (dtd2->elements)) return axl_false; if (axl_list_length (dtd->attributes) != axl_list_length (dtd2->attributes)) return axl_false; if (dtd->haveIdRefDecl != dtd2->haveIdRefDecl) return axl_false; if (dtd->haveIdDecl != dtd2->haveIdDecl) return axl_false; /* now check inner elements (ENTITIES) */ iterator = 0; while (iterator < axl_list_length (dtd->entities)) { /* get referneces */ entity = axl_list_get_nth (dtd->entities, iterator); entity2 = axl_list_get_nth (dtd2->entities, iterator); /* check types */ if (entity->type != entity2->type) return axl_false; /* check names */ if (! axl_cmp (entity->name, entity2->name)) return axl_false; /* check content */ if (! axl_cmp (entity->content, entity2->content)) return axl_false; /* check external data */ if (entity->data == NULL && entity2->data != NULL) return axl_false; if (entity->data != NULL && entity2->data == NULL) return axl_false; if (entity->data != NULL && entity2->data != NULL) { if (! axl_cmp (entity->data->system_literal, entity2->data->system_literal)) return axl_false; if (! axl_cmp (entity->data->public_literal, entity2->data->public_literal)) return axl_false; if (! axl_cmp (entity->data->ndata, entity2->data->ndata)) return axl_false; } /* end if */ /* next iterator */ iterator++; } /* end while */ /* now check inner elements (ELEMENTS) */ iterator = 0; while (iterator < axl_list_length (dtd->elements)) { /* get referneces */ element = axl_list_get_nth (dtd->elements, iterator); element2 = axl_list_get_nth (dtd2->elements, iterator); /* check types */ if (element->type != element2->type) return axl_false; /* minimum match */ if (element->minimum_match != element2->minimum_match) return axl_false; /* check names */ if (! axl_cmp (element->name, element2->name)) return axl_false; /* check element list */ if (element->list == NULL && element2->list != NULL) return axl_false; if (element->list != NULL && element2->list == NULL) return axl_false; if (element->list != NULL && element2->list != NULL) { /* check internal values */ if (element->list->type != element2->list->type) return axl_false; if (element->list->times != element2->list->times) return axl_false; iterator2 = 0; while (iterator2 < axl_list_length (element->list->itemList)) { /* get references */ node = axl_list_get_nth (element->list->itemList, iterator2); node2 = axl_list_get_nth (element2->list->itemList, iterator2); if (node->type != node2->type) return axl_false; if (node->times != node2->times) return axl_false; /* next value */ iterator2++; } /* end while */ } /* end if */ /* next iterator */ iterator++; } /* end while */ /* now check inner elements (ATTRIBUTES) */ iterator = 0; while (iterator < axl_list_length (dtd->attributes)) { /* get referneces */ attribute = axl_list_get_nth (dtd->attributes, iterator); attribute2 = axl_list_get_nth (dtd2->attributes, iterator); /* check names */ if (! axl_cmp (attribute->name, attribute2->name)) return axl_false; /* check values */ if (attribute->list == NULL && attribute2->list != NULL) return axl_false; if (attribute->list != NULL && attribute2->list == NULL) return axl_false; if (attribute->list != NULL && attribute2->list != NULL) { /* check list length */ if (axl_list_length (attribute->list) != axl_list_length (attribute2->list)) return axl_false; /* check internal values */ iterator2 = 0; while (iterator2 < axl_list_length (attribute->list)) { /* get references */ attr_decl = axl_list_get_nth (attribute->list, iterator2); attr_decl2 = axl_list_get_nth (attribute2->list, iterator2); if (attr_decl->type != attr_decl2->type) return axl_false; if (attr_decl->defaults != attr_decl2->defaults) return axl_false; if (! axl_cmp (attr_decl->name, attr_decl2->name)) return axl_false; if (attr_decl->enumvalues == NULL && attr_decl2->enumvalues != NULL) return axl_false; if (attr_decl->enumvalues != NULL && attr_decl2->enumvalues == NULL) return axl_false; if (attr_decl->enumvalues != NULL && attr_decl2->enumvalues != NULL) { if (axl_list_length (attr_decl->enumvalues) != axl_list_length (attr_decl2->enumvalues)) return axl_false; iterator3 = 0; while (iterator3 < axl_list_length (attr_decl->enumvalues)) { /* check values */ if (! axl_cmp (axl_list_get_nth (attr_decl->enumvalues, iterator3), axl_list_get_nth (attr_decl2->enumvalues, iterator3))) return axl_false; /* next value */ iterator3++; } /* end while */ } /* end if */ /* next value */ iterator2++; } /* end while */ } /* end if */ /* next iterator */ iterator++; } /* end while */ return axl_true; } /** * @brief Allows to get the root node for the provided DTD. * * Every DTD have a root node defined, which is the root node accepted * for the set of XML document considered to be valid under the * definition of the DTD provided. * * The value returned is the name of the root node that must have the * XML document being validated. * * @param dtd The \ref axlDtd where the root node name will be * returned. * * @return A reference to the internal representation of the root node * Value must not be deallocated. */ axlDtdElement * axl_dtd_get_root (axlDtd * dtd) { axl_return_val_if_fail (dtd, NULL); /* return current status for the root node */ if (dtd->root == NULL) { __axl_log (LOG_DOMAIN, AXL_LEVEL_CRITICAL, "dtd root element not defined"); return NULL; } return dtd->root; } /** * @internal function used by \ref axl_dtd_get_element to perform node * lookups. */ axl_bool __find_dtd_element (axlPointer _element, axlPointer data) { axlDtdElement * element = _element; char * name = data; /* check the name */ if (axl_cmp (element->name, name)) return axl_true; /* it is not the element */ return axl_false; } /** * @brief Allows to get the DTD element (\ref axlDtdElement), inside * the provided DTD (\ref axlDtd), that represent the spefication for * the node called by the provided name. * * @param dtd The DTD (\ref axlDtd) where the lookup will be * performed. * * @param name The element name to lookup. * * @return A reference to the \ref axlDtdElement searched or NULL if * fails. The function also returns NULL if values received are NULL. */ axlDtdElement * axl_dtd_get_element (axlDtd * dtd, const char * name) { axl_return_val_if_fail (dtd, NULL); axl_return_val_if_fail (name, NULL); /* perform the lookup */ return axl_list_lookup (dtd->elements, __find_dtd_element, (axlPointer) name); } /** * @internal function used by \ref axl_dtd_get_attr to perform node * lookups. */ axl_bool __find_dtd_attr (axlPointer _element, axlPointer data) { axlDtdAttribute * attr = _element; char * name = data; /* check the name */ if (axl_cmp (attr->name, name)) return axl_true; /* it is not the element */ return axl_false; } /** * @brief Allows to get the set of attribute declerations for a * particular node. * * The \ref axlDtdAttribute declaration contains all constraints * configured for attributes found for the particular xml node * (identified by name). * * @param dtd A reference to the DTD document. * * @param nodeName The xml node that is requested to return all attribute * declarations. * * @return A reference to the \ref axlDtdAttribute or NULL if it * fails. */ axlDtdAttribute * axl_dtd_get_attr (axlDtd * dtd, const char * nodeName) { axl_return_val_if_fail (dtd, NULL); axl_return_val_if_fail (nodeName, NULL); /* perform the lookup */ return axl_list_lookup (dtd->attributes, __find_dtd_attr, (axlPointer) nodeName); } /** * @brief Allows to get the number of constraints that have been * configured for the particular node. * * @param dtd The reference to the DTD document. * * @param nodeName The name of the node that is being asked for its * constraints. * * @return 0 or the number of contraints. The function return -1 if * any of the parameter received is null. */ int axl_dtd_get_attr_contraints (axlDtd * dtd, const char * nodeName) { axlDtdAttribute * attr; axl_return_val_if_fail (dtd, -1); axl_return_val_if_fail (nodeName, -1); /* get the attribute specification for the node */ attr = axl_dtd_get_attr (dtd, nodeName); /* return the number of items */ return axl_list_length (attr->list); } /** * @brief Returns the name of the provided \ref axlDtdElement. * * @param element A reference to a \ref axlDtdElement where the name * will be returned. * * @return A reference to the internal DTD element name. Returned * value mustn't be deallocated. */ char * axl_dtd_get_element_name (axlDtdElement * element) { axl_return_val_if_fail (element, NULL); return element->name; } /** * @brief Returns current element type for the provided \ref axlDtdElement. * * @param element The axlDtdElement where its type will be returned. * * @return Current element type for the provided node. */ AxlDtdElementType axl_dtd_get_element_type (axlDtdElement * element) { axl_return_val_if_fail (element, ELEMENT_TYPE_UNKNOWN); return element->type; } /** * @brief Returns current DTD content specification, represented by the Item list. * * @param element The DTD element (\ref axlDtdElement) which is being * requested to return its \ref axlDtdElementList. * * @return The \ref axlDtdElementList reference. The value returned * must not be deallocated. The function returns NULL if the reference received is NULL. */ axlDtdElementList * axl_dtd_get_item_list (axlDtdElement * element) { axl_return_val_if_fail (element, NULL); return element->list; } /** * @brief Allows to check if the provided DTD ELEMENT representation * is a top level definition. * * @param dtd The DTD document where the operation will be performed. * @param element The \ref axlDtdElement to check. * * @return \ref axl_true if the dtd element is a top level element or * \ref axl_false if not. The function returns \ref axl_false if the * provided reference is NULL. */ axl_bool axl_dtd_element_is_toplevel (axlDtd * dtd, axlDtdElement * element) { /* support several top level definitions */ int iterator; axlDtdElement * dtd_element_aux; axl_return_val_if_fail (dtd, axl_false); axl_return_val_if_fail (element, axl_false); /* check which is the top */ iterator = 0; while (iterator < axl_list_length (dtd->elements)) { /* get the next reference */ dtd_element_aux = axl_list_get_nth (dtd->elements, iterator); /* check which is the top */ if (__axl_dtd_get_is_parent (dtd_element_aux, element)) { /* the element provided have a parent */ return axl_false; } /* update inner loop iterator */ iterator ++; } /* while end */ /* return that the provided node doesn't have a parent node */ return axl_true; } /** * @brief Returns the number of item nodes (\ref * axlDtdElementListNode) inside the item list received (\ref axlDtdElementList). * * @param itemList The \ref axlDtdElementList where the count * operation is being requested. * * @return The number of item list the provided \ref axlDtdElementList * reference has. The function return -1 if the provided reference is * NULL. */ int axl_dtd_item_list_count (axlDtdElementList * itemList) { axl_return_val_if_fail (itemList, -1); if (itemList->itemList == NULL) return 0; return axl_list_length (itemList->itemList); } /** * @brief Allows to get current configuration for the provided item * list, which is the content specification for a DTD element. * * @param itemList The item list where the operation will be * performed. * * @return Current configuration (\ref SEQUENCE or a \ref CHOICE). */ AxlDtdNestedType axl_dtd_item_list_type (axlDtdElementList * itemList) { axl_return_val_if_fail (itemList, -1); return itemList->type; } /** * @brief Allows to get current configuration for DTD content spec * repetition. * * @param itemList The content spec where the query will be performed. * * @return Current configuration for times to be repeated DTD element * content specification. */ AxlDtdTimes axl_dtd_item_list_repeat (axlDtdElementList * itemList) { axl_return_val_if_fail (itemList, DTD_TIMES_UNKNOWN); /* returns current times configuration */ return itemList->times; } /** * @brief Allows to get the provided item node reference (\ref * axlDtdElementListNode) from the provided item list (\ref * axlDtdElementList). * * Provided position ranges from 0 up to \ref axl_dtd_item_list_count. * * @param itemList The itemList where the operation will be performed. * @param position The position where the item node will be looked up. * * @return A reference to the \ref axlDtdElementListNode, or NULL if * there is no item node at the selected index. The function return * NULL if the provided position is a non positive value or it is * greater than the current item list count (\ref * axl_dtd_item_list_count) or the provided item list reference is * NULL. */ axlDtdElementListNode * axl_dtd_item_list_get_node (axlDtdElementList * itemList, int position) { axl_return_val_if_fail (itemList, NULL); axl_return_val_if_fail (position >= 0, NULL); axl_return_val_if_fail (position < axl_dtd_item_list_count (itemList), NULL); return axl_list_get_nth (itemList->itemList, position); } /** * @brief Allows to get current node type for the provided DTD element * type content particule or item node (\ref axlDtdElementListNode). * * @param node The node where the type is being requested. * * @return It returns if the item node contains a final leaf node, * making a reference to an explicit node naming that is allowed to be * used in the context where is found the provided \ref * axlDtdElementListNode or a \ref axlDtdElementList containing more * nodes or lists. */ NodeType axl_dtd_item_node_get_type (axlDtdElementListNode * node) { axl_return_val_if_fail (node, AXL_ELEMENT_NOT_DEFINED); return node->type; } /** * @brief Returns the item list inside the provided node. * * The node is supported to contain an item list reference or NULL * will be returned. Check \ref axl_dtd_item_node_get_type. * * @param node The node where the operation will be performed. * * @return The item list inside the node or NULL if fails. */ axlDtdElementList * axl_dtd_item_node_get_list (axlDtdElementListNode * node) { axl_return_val_if_fail (node, NULL); axl_return_val_if_fail (node->type == AXL_ELEMENT_LIST, NULL); return node->data; } /** * @brief Allows to get the dtd item list value, which represents the * node name that is being constrained/represented. * * @param node The item node where the value is being requested. * * @return The value inside the item node, supposing it contains an * leaf item node or NULL if fails. The value returned must not be * deallocated. */ char * axl_dtd_item_node_get_value (axlDtdElementListNode * node) { axl_return_val_if_fail (node, NULL); if (node->type != AXL_ELEMENT_NODE) return "requested-value-on-a-list"; return node->data; } /** * @brief Allows to get current configuration for the provided content * particule for the times to be repeated. * * @param node The content particule where the query will be * performed. * * @return Return current repetition configuration. */ AxlDtdTimes axl_dtd_item_node_get_repeat (axlDtdElementListNode * node) { axlDtdElementList * list; axl_return_val_if_fail (node, DTD_TIMES_UNKNOWN); if (node->type == AXL_ELEMENT_NODE) { /* return value requested */ return node->times; } if (node->type == AXL_ELEMENT_LIST) { /* return the requested value for an item list */ list = node->data; return list->times; } /* return that we don't know man */ return DTD_TIMES_UNKNOWN; } /** * @internal * * Internal function which allows to lookup the DTD entity reference * provided the name and the type. */ axlDtdEntity * __axl_dtd_entity_lookup (axlDtd * dtd, const char * name, axlDtdEntityType type) { axlDtdEntity * entity; int iterator; int length; /* check values received */ axl_return_val_if_fail (dtd, NULL); axl_return_val_if_fail (name, NULL); /* lookup for the item */ iterator = 0; length = axl_list_length (dtd->entities); while (iterator < length) { /* get the entity at the provided position */ entity = axl_list_get_nth (dtd->entities, iterator); /* check the type and the name */ if ((entity->type == type) && axl_cmp (entity->name, name)) return entity; /* update iterator */ iterator++; } /* end while */ return NULL; } /** * @brief Allows to check if the provided entity name, with the * provided type is defined on the given DTD object. * * @param dtd The \ref axlDtd instance where the entity lookup will be * performed. * * @param name The entity name to lookup. * * @param type The entity type to lookup. * * @return axl_true if an entity is found named as provided with the type * provided. Othewise, axl_false is returned. */ axl_bool axl_dtd_entity_exists (axlDtd * dtd, const char * name, axlDtdEntityType type) { /* return if the entity exists */ return (__axl_dtd_entity_lookup (dtd, name, type) != NULL); } /** * @brief Allows to get the content configured inside the entity that * is identified by the provided name and the provided type. * * @param dtd The DTD where the lookup will be performed. * * @param name The entity name to lookup for its content. * * @param type The entity type to match. * * @return An internal reference to the content associated to the * entity found or NULL. In case the content is defined (as return * value) it must not be deallocated. */ char * axl_dtd_entity_value (axlDtd * dtd, const char * name, axlDtdEntityType type) { axlDtdEntity * entity; /* get the entity reference */ entity = __axl_dtd_entity_lookup (dtd, name, type); /* check the entity reference */ axl_return_val_if_fail (entity, NULL); /* return the content */ return entity->content; } /** * @brief Allows to destroy the provided \ref axlDtd document. * * @param dtd The \ref axlDtd document to destroy. */ void axl_dtd_free (axlDtd * dtd) { if (dtd == NULL) { __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "received a null DTD reference, doing nothing"); return; } __axl_log (LOG_DOMAIN, AXL_LEVEL_DEBUG, "releasing the DTD reference"); /* free dtd elements */ if (dtd->elements) axl_list_free (dtd->elements); /* free entities */ if (dtd->entities) axl_list_free (dtd->entities); /* free attributes */ if (dtd->attributes) axl_list_free (dtd->attributes); /* free the node itself */ axl_free (dtd); return; } /** * @internal * * @brief Allows to release the memory hold by the given * axlDtdElement. * * @param element The axlDtdElement to release. */ void axl_dtd_element_free (axlDtdElement * element) { if (element == NULL) return; /* free element name */ if (element->name != NULL) axl_free (element->name); /* free element list definitions */ axl_dtd_item_list_free (element->list); /* free element itself */ axl_free (element); return; } /** * @internal * * @brief Deallocates memory used by the \ref axlDtdElementList * reference. * * @param list The reference to deallocate. */ void axl_dtd_item_list_free (axlDtdElementList * list) { if (list == NULL) return; /* check and deallocate the list provided */ if (list->itemList != NULL) axl_list_free (list->itemList); /* deallocates the node itself */ axl_free (list); return; } /* @} */