/* Copyright 2010-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "parser.h" #include "convert.h" #include "source_marks.h" #include "labels.h" /* Array of recorded elements with labels. */ ELEMENT **target_elements_list = 0; size_t labels_number = 0; size_t labels_space = 0; /* Register a target element associated to a label that may be the target of a reference and must be unique in the document. Corresponds to @node, @anchor, and @float (float label corresponds to the second argument). */ void register_label (ELEMENT *target_element) { /* register the element in the list. */ if (labels_number == labels_space) { labels_space += 1; labels_space *= 1.5; target_elements_list = realloc (target_elements_list, labels_space * sizeof (ELEMENT *)); if (!target_elements_list) fatal ("realloc failed"); } target_elements_list[labels_number++] = target_element; } void reset_labels (void) { labels_number = 0; } void check_register_target_element_label (ELEMENT *label_element, ELEMENT *target_element) { if (label_element) { /* check that the label used as an anchor for link target has no external manual part */ NODE_SPEC_EXTRA *label_info = parse_node_manual (label_element, 0); if (label_info && label_info->manual_content) { /* show contents only to avoid leading/trailing spaces */ char *texi = convert_contents_to_texinfo (label_element); line_error ("syntax for an external node used for `%s'", texi); free (texi); } destroy_node_spec (label_info); } register_label (target_element); } /* NODE->contents is the Texinfo for the specification of a node. This function sets two fields on the returned object: manual_content - Texinfo tree for a manual name extracted from the node specification. node_content - Texinfo tree for the node name on its own Objects returned from this function are used as an 'extra' key in the element for elements linking to nodes (such as @*ref, menu_entry_node or node direction arguments). In that case modify_node is set to 1 and the node contents are modified in-place to hold the same elements as the returned objects. This function is also used for elements that are targets of links (@node and @anchor first argument, float second argument) mainly to check that the syntax for an external node is not used. In that case modify_node is set to 0 and the node is not modified, and added elements are collected in a third field of the returned object, out_of_tree_elements - elements collected in manual_content or node_content and not in the node */ NODE_SPEC_EXTRA * parse_node_manual (ELEMENT *node, int modify_node) { NODE_SPEC_EXTRA *result; ELEMENT *node_content = 0; int idx = 0; /* index into node->contents */ result = malloc (sizeof (NODE_SPEC_EXTRA)); result->manual_content = result->node_content = 0; /* if not modifying the tree, and there is a manual name, the elements added for the manual name and for the node content that are based on texts from tree elements are not anywhere in the tree. They are collected in result->out_of_tree_element to be freed later. These elements correspond to the text after the first manual name opening brace and text before and after the closing manual name brace */ result->out_of_tree_elements = 0; /* If the content starts with a '(', try to get a manual name. */ if (node->contents.number > 0 && node->contents.list[0]->text.end > 0 && node->contents.list[0]->text.text[0] == '(') { ELEMENT *manual, *first; ELEMENT *new_first = 0; ELEMENT *opening_brace = 0; char *opening_bracket, *closing_bracket; /* Handle nested parentheses in the manual name, for whatever reason. */ int bracket_count = 1; /* Number of ( seen minus number of ) seen. */ manual = new_element (ET_NONE); /* If the first contents element is "(" followed by more text, split the leading "(" into its own element. */ first = node->contents.list[0]; if (first->text.end > 1) { if (modify_node) { opening_brace = new_element (0); text_append_n (&opening_brace->text, "(", 1); } new_first = new_element (0); text_append_n (&new_first->text, first->text.text +1, first->text.end -1); } else { /* first element is "(", keep it */ idx++; } for (; idx < node->contents.number; idx++) { ELEMENT *e; char *p, *q; if (idx == 0) e = new_first; else e = node->contents.list[idx]; if (e->text.end == 0) { /* Put this element in the manual contents. */ add_to_contents_as_array (manual, e); continue; } p = e->text.text; while (p < e->text.text + e->text.end && bracket_count > 0) { opening_bracket = strchr (p, '('); closing_bracket = strchr (p, ')'); if (!opening_bracket && !closing_bracket) { break; } else if (opening_bracket && !closing_bracket) { bracket_count++; p = opening_bracket + 1; } else if (!opening_bracket && closing_bracket) { bracket_count--; p = closing_bracket + 1; } else if (opening_bracket < closing_bracket) { bracket_count++; p = opening_bracket + 1; } else if (opening_bracket > closing_bracket) { bracket_count--; p = closing_bracket + 1; } } if (bracket_count > 0) add_to_contents_as_array (manual, e); else /* end of filename component */ { size_t current_position = 0; /* At this point, we are sure that there is a manual part, so the pending removal/addition of elements at the beginning of the manual can proceed (if modify_node). */ /* Also, split the element in two, putting the part before the ")" in the manual name, leaving the part afterwards for the node name. */ if (modify_node) { if (opening_brace) { /* remove the original first element and prepend the split "(" and text elements */ remove_from_contents (node, 0); /* remove first element */ insert_into_contents (node, new_first, 0); insert_into_contents (node, opening_brace, 0); idx++; if (first->source_mark_list.number > 0) { size_t current_position = relocate_source_marks (&(first->source_mark_list), opening_brace, 0, count_convert_u8 (opening_brace->text.text)); relocate_source_marks (&(first->source_mark_list), new_first, current_position, count_convert_u8 (new_first->text.text)); } destroy_element (first); } remove_from_contents (node, idx); /* Remove current element e with closing brace from the tree. */ } else { /* collect elements out of tree */ result->out_of_tree_elements = calloc (3, sizeof (ELEMENT *)); if (new_first) result->out_of_tree_elements[0] = new_first; } p--; /* point at ) */ if (p > e->text.text) { /* text before ), part of the manual name */ ELEMENT *last_manual_element = new_element (ET_NONE); text_append_n (&last_manual_element->text, e->text.text, p - e->text.text); add_to_contents_as_array (manual, last_manual_element); if (modify_node) { insert_into_contents (node, last_manual_element, idx++); current_position = relocate_source_marks (&(e->source_mark_list), last_manual_element, current_position, count_convert_u8 (last_manual_element->text.text)); } else result->out_of_tree_elements[1] = last_manual_element; } if (modify_node) { ELEMENT *closing_brace = new_element (0); text_append_n (&closing_brace->text, ")", 1); insert_into_contents (node, closing_brace, idx++); current_position = relocate_source_marks (&(e->source_mark_list), closing_brace, current_position, count_convert_u8 (closing_brace->text.text)); } /* Skip ')' and any following whitespace. Note that we don't manage to skip any multibyte UTF-8 space characters here. */ p++; q = p + strspn (p, whitespace_chars); if (q > p && modify_node) { ELEMENT *spaces_element = new_element (0); text_append_n (&spaces_element->text, p, q - p); insert_into_contents (node, spaces_element, idx++); current_position = relocate_source_marks (&(e->source_mark_list), spaces_element, current_position, count_convert_u8 (spaces_element->text.text)); } p = q; if (*p) { /* text after ), part of the node name. */ ELEMENT *leading_node_content = new_element (ET_NONE); text_append_n (&leading_node_content->text, p, e->text.text + e->text.end - p); /* start node_content */ node_content = new_element (0); add_to_contents_as_array (node_content, leading_node_content); if (modify_node) { insert_into_contents (node, leading_node_content, idx); current_position = relocate_source_marks (&(e->source_mark_list), leading_node_content, current_position, count_convert_u8 (leading_node_content->text.text)); } else result->out_of_tree_elements[2] = leading_node_content; idx++; } if (modify_node) destroy_element (e); break; } } /* for */ if (bracket_count == 0) result->manual_content = manual; else /* Unbalanced parentheses, consider that there is no manual afterall. So far the node has not been modified, so the only thing that needs to be done is to remove the manual element and the elements allocated for the beginning of the manual, and start over */ { destroy_element (manual); if (new_first) destroy_element (new_first); if (opening_brace) destroy_element (opening_brace); idx = 0; /* Back to the start, and consider the whole thing as a node name. */ } } /* If anything left, it is part of the node name. */ if (idx < node->contents.number) { if (!node_content) node_content = new_element (0); insert_slice_into_contents (node_content, node_content->contents.number, node, idx, node->contents.number); } if (node_content) result->node_content = node_content; return result; } ELEMENT **internal_xref_list = 0; size_t internal_xref_number = 0; size_t internal_xref_space = 0; void remember_internal_xref (ELEMENT *element) { if (internal_xref_number == internal_xref_space) { internal_xref_list = realloc (internal_xref_list, (internal_xref_space += 2) * sizeof (*internal_xref_list)); } internal_xref_list[internal_xref_number++] = element; } void reset_internal_xrefs (void) { internal_xref_number = 0; }