1  /* Copyright 2010-2023 Free Software Foundation, Inc.
       2  
       3     This program is free software: you can redistribute it and/or modify
       4     it under the terms of the GNU General Public License as published by
       5     the Free Software Foundation, either version 3 of the License, or
       6     (at your option) any later version.
       7  
       8     This program is distributed in the hope that it will be useful,
       9     but WITHOUT ANY WARRANTY; without even the implied warranty of
      10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      11     GNU General Public License for more details.
      12  
      13     You should have received a copy of the GNU General Public License
      14     along with this program.  If not, see <http://www.gnu.org/licenses/>. */
      15  
      16  #include <config.h>
      17  #include <stdlib.h>
      18  #include <string.h>
      19  
      20  #include "parser.h"
      21  #include "debug.h"
      22  #include "input.h"
      23  #include "text.h"
      24  #include "convert.h"
      25  #include "labels.h"
      26  #include "source_marks.h"
      27  
      28  /* Save 'menu_entry_node' extra keys. */
      29  ELEMENT *
      30  register_extra_menu_entry_information (ELEMENT *current)
      31  {
      32    int i;
      33    ELEMENT *menu_entry_node = 0;
      34  
      35    for (i = 0; i < current->contents.number; i++)
      36      {
      37        ELEMENT *arg = current->contents.list[i];
      38  
      39        if (arg->type == ET_menu_entry_name)
      40          {
      41            if (arg->contents.number == 0)
      42              {
      43                char *texi = convert_to_texinfo (current);
      44                line_warn ("empty menu entry name in `%s'", texi);
      45                free (texi);
      46              }
      47          }
      48        else if (arg->type == ET_menu_entry_node)
      49          {
      50            NODE_SPEC_EXTRA *parsed_entry_node;
      51  
      52            isolate_last_space (arg);
      53  
      54            parsed_entry_node = parse_node_manual (arg, 1);
      55            if (!parsed_entry_node->manual_content
      56                && !parsed_entry_node->node_content)
      57              {
      58                if (conf.show_menu)
      59                  line_error ("empty node name in menu entry");
      60              }
      61            else
      62              {
      63                menu_entry_node = arg;
      64                if (parsed_entry_node->node_content)
      65                  add_extra_contents (arg, "node_content",
      66                                      parsed_entry_node->node_content);
      67                if (parsed_entry_node->manual_content)
      68                  add_extra_contents (arg, "manual_content",
      69                                      parsed_entry_node->manual_content);
      70              }
      71            free (parsed_entry_node);
      72          }
      73      }
      74    return menu_entry_node;
      75  }
      76  
      77  /* Process the destination of the menu entry, and start a menu entry
      78     description.  */
      79  ELEMENT *
      80  enter_menu_entry_node (ELEMENT *current)
      81  {
      82    ELEMENT *description, *preformatted;
      83    ELEMENT *menu_entry_node;
      84  
      85    current->source_info = current_source_info;
      86  
      87    menu_entry_node = register_extra_menu_entry_information (current);
      88    if (menu_entry_node)
      89      remember_internal_xref (menu_entry_node);
      90  
      91    description = new_element (ET_menu_entry_description);
      92    add_to_element_contents (current, description);
      93  
      94    current = description;
      95    preformatted = new_element (ET_preformatted);
      96    add_to_element_contents (current, preformatted);
      97    current = preformatted;
      98    return current;
      99  }
     100  
     101  /* Called from 'process_remaining_on_line' in parser.c.  Return 1 if we find
     102     menu syntax to process, otherwise return 0. */
     103  int
     104  handle_menu_entry_separators (ELEMENT **current_inout, char **line_inout)
     105  {
     106    ELEMENT *current = *current_inout;
     107    char *line = *line_inout;
     108    int retval = 1;
     109  
     110    /* A "*" at the start of a line beginning a menu entry. */
     111    if (*line == '*'
     112        && current->type == ET_preformatted
     113        && (current->parent->type == ET_menu_comment
     114            || current->parent->type == ET_menu_entry_description)
     115        && current->contents.number > 0
     116        && last_contents_child(current)->type == ET_empty_line
     117        && last_contents_child(current)->text.end == 0)
     118      {
     119        ELEMENT *star;
     120  
     121        debug ("MENU STAR");
     122        abort_empty_line (¤t, 0);
     123        line++; /* Past the '*'. */
     124  
     125        star = new_element (ET_internal_menu_star);
     126        text_append (&star->text, "*");
     127        add_to_element_contents (current, star);
     128  
     129        /* The ET_internal_menu_star element won't appear in the final tree. */
     130      }
     131    /* A space after a "*" at the beginning of a line. */
     132    else if (strchr (whitespace_chars, *line)
     133             && current->contents.number > 0
     134             && last_contents_child(current)->type == ET_internal_menu_star)
     135      {
     136        ELEMENT *menu_entry, *leading_text, *entry_name;
     137        ELEMENT *menu_star_element;
     138        int leading_spaces;
     139  
     140        debug ("MENU ENTRY (certainly)");
     141  
     142        /* this is the menu star collected previously */
     143        menu_star_element = pop_element_from_contents (current);
     144  
     145        leading_spaces = strspn (line, whitespace_chars);
     146  
     147        if (current->type == ET_preformatted
     148            && current->parent->type == ET_menu_comment)
     149          {
     150            /* Close ET_preformatted, and ET_menu_comment. */
     151            current = close_container (current);
     152            current = close_container (current);
     153          }
     154        else
     155          {
     156            /* current should be ET_preformatted,
     157               1st parent ET_menu_entry_description,
     158               2nd parent ET_menu_entry,
     159               3rd parent @menu.
     160               Close current, 1st and 2nd parent (which cannot be empty) */
     161            current = close_container (current);
     162            current = close_container (current);
     163            current = close_container (current);
     164          }
     165  
     166        menu_entry = new_element (ET_menu_entry);
     167        leading_text = new_element (ET_menu_entry_leading_text);
     168        /* transfer source marks from removed menu star to leading text */
     169        transfer_source_marks (menu_star_element, leading_text);
     170        destroy_element (menu_star_element);
     171        entry_name = new_element (ET_menu_entry_name);
     172        add_to_element_contents (current, menu_entry);
     173        add_to_element_contents (menu_entry, leading_text);
     174        add_to_element_contents (menu_entry, entry_name);
     175        current = entry_name;
     176  
     177        text_append (&leading_text->text, "*");
     178        text_append_n (&leading_text->text, line, leading_spaces);
     179        line += leading_spaces;
     180      }
     181    /* A "*" followed by anything other than a space. */
     182    else if (current->contents.number > 0
     183             && last_contents_child(current)->type == ET_internal_menu_star)
     184      {
     185        debug_nonl ("ABORT MENU STAR before: ");
     186        debug_print_protected_string (line); debug ("");
     187  
     188        last_contents_child(current)->type = ET_NONE;
     189      }
     190    /* After a separator in a menu, end of menu entry node or menu entry name
     191     (. must be followed by a space to stop the node). */
     192    else if (*line != '\0'
     193             && ((*line == ':' && current->type == ET_menu_entry_name)
     194                 || (strchr (",\t.", *line)
     195                     && current->type == ET_menu_entry_node)))
     196      {
     197        ELEMENT *e;
     198        char menu_separator = *line;
     199        line++;
     200  
     201        current = current->parent;
     202        e = new_element (ET_menu_entry_separator);
     203        text_append_n (&e->text, &menu_separator, 1);
     204        add_to_element_contents (current, e);
     205  
     206        /* Note, if a '.' is not followed by whitespace, we revert was was
     207           done here below. */
     208      }
     209    /* After a separator in a menu */
     210    else if (current->contents.number > 0
     211             && last_contents_child (current)->type == ET_menu_entry_separator)
     212      {
     213        ELEMENT *last_child;
     214        char *separator;
     215  
     216        last_child = last_contents_child (current);
     217        separator = last_child->text.text;
     218  
     219        debug ("AFTER menu_entry_separator %s", separator);
     220  
     221        /* Separator is "::". */
     222        if (!strcmp (separator, ":") && *line == ':')
     223          {
     224            text_append (&last_child->text, ":");
     225            line++;
     226            /* Whitespace following the "::" is subsequently appended to
     227               the separator element. */
     228          }
     229        /* A "." not followed by a space.  Not a separator. */
     230        else if (!strcmp (separator, ".") && !strchr (whitespace_chars, *line))
     231          {
     232            pop_element_from_contents (current);
     233            current = last_contents_child (current);
     234            merge_text (current, last_child->text.text, last_child);
     235            destroy_element (last_child);
     236          }
     237        /* here we collect spaces following separators. */
     238        else if (strchr (whitespace_chars_except_newline, *line))
     239          {
     240            int n;
     241  
     242            n = strspn (line, whitespace_chars_except_newline);
     243            text_append_n (&last_child->text, line, n);
     244            line += n;
     245          }
     246        /* :: after a menu entry name => change to a menu entry node */
     247        else if (!strncmp (separator, "::", 2))
     248          {
     249            ELEMENT *entry_name;
     250  
     251            debug ("MENU NODE done (change from menu entry name) %s", separator);
     252            entry_name = contents_child_by_index (current, -2);
     253  
     254            /* Change from menu_entry_name (i.e. a label)
     255               to a menu entry node */
     256            entry_name->type = ET_menu_entry_node;
     257            current = enter_menu_entry_node (current);
     258          }
     259        /* a :, but not ::, after a menu entry name => end of menu entry name */
     260        else if (*separator == ':')
     261          {
     262            ELEMENT *entry_node;
     263  
     264            debug ("MENU ENTRY done %s", separator);
     265            entry_node = new_element (ET_menu_entry_node);
     266            add_to_element_contents (current, entry_node);
     267            current = entry_node;
     268          }
     269        else
     270        /* anything else corresponds to a separator that does not contain
     271           : and is after a menu node (itself following a menu_entry_name) */
     272          {
     273            debug ("MENU NODE done %s", separator);
     274            current = enter_menu_entry_node (current);
     275          }
     276      }
     277    else
     278      retval = 0;
     279  
     280    *current_inout = current;
     281    *line_inout = line;
     282  
     283    return retval;
     284  }
     285  
     286  ELEMENT *
     287  end_line_menu_entry (ELEMENT *current)
     288  {
     289    ELEMENT *end_comment = 0;
     290    int empty_menu_entry_node = 0;
     291  
     292    if (current->type == ET_menu_entry_node)
     293      {
     294        ELEMENT *last = last_contents_child (current);
     295  
     296        if (current->contents.number > 0
     297            && (last->cmd == CM_c || last->cmd == CM_comment))
     298          {
     299            end_comment = pop_element_from_contents (current);
     300          }
     301  
     302        /* If contents empty or is all whitespace. */
     303        if (current->contents.number == 0
     304            || (current->contents.number == 1
     305                && last->text.end > 0
     306                && !last->text.text[strspn (last->text.text,
     307                                            whitespace_chars)]))
     308          {
     309            empty_menu_entry_node = 1;
     310            if (end_comment)
     311              add_to_element_contents (current, end_comment);
     312          }
     313      }
     314    /* Abort the menu entry if there is no destination node given. */
     315    if (empty_menu_entry_node || current->type == ET_menu_entry_name)
     316      {
     317        ELEMENT *menu, *menu_entry, *description_or_menu_comment = 0;
     318        debug ("FINALLY NOT MENU ENTRY");
     319        menu = current->parent->parent;
     320        menu_entry = pop_element_from_contents (menu);
     321        if (menu->contents.number > 0
     322            && last_contents_child(menu)->type == ET_menu_entry)
     323          {
     324            ELEMENT *entry, *description = 0;
     325            int j;
     326  
     327            entry = last_contents_child(menu);
     328            for (j = entry->contents.number - 1; j >= 0; j--)
     329              {
     330                ELEMENT *e = contents_child_by_index (entry, j);
     331                if (e->type == ET_menu_entry_description)
     332                  {
     333                    description = e;
     334                    break;
     335                  }
     336              }
     337            if (description)
     338              description_or_menu_comment = description;
     339            else
     340              {
     341                ELEMENT *e;
     342                /* "Normally this cannot happen." */
     343                bug ("no description in menu entry");
     344                e = new_element (ET_menu_entry_description);
     345                add_to_element_contents (entry, e);
     346                description_or_menu_comment = e;
     347              }
     348          }
     349        else if (menu->contents.number > 0
     350                 && last_contents_child(menu)->type == ET_menu_comment)
     351          {
     352            description_or_menu_comment = last_contents_child(menu);
     353          }
     354        if (description_or_menu_comment)
     355          {
     356            current = description_or_menu_comment;
     357            if (current->contents.number > 0
     358                && last_contents_child(current)->type == ET_preformatted)
     359              current = last_contents_child(current);
     360            else
     361              {
     362                ELEMENT *e;
     363                /* This should not happen */
     364                bug ("description or menu comment not in preformatted");
     365                e = new_element (ET_preformatted);
     366                add_to_element_contents (current, e);
     367                current = e;
     368              }
     369          }
     370        else
     371          {
     372            ELEMENT *e;
     373            e = new_element (ET_menu_comment);
     374            add_to_element_contents (menu, e);
     375            current = e;
     376            e = new_element (ET_preformatted);
     377            add_to_element_contents (current, e);
     378            current = e;
     379            debug ("THEN MENU_COMMENT OPEN");
     380          }
     381        {
     382        int i, j;
     383        for (i = 0; i < menu_entry->contents.number; i++)
     384          {
     385            ELEMENT *arg = contents_child_by_index(menu_entry, i);
     386            if (arg->text.end > 0)
     387              current = merge_text (current, arg->text.text, arg);
     388            else
     389              {
     390                ELEMENT *e;
     391                for (j = 0; j < arg->contents.number; j++)
     392                  {
     393                    e = contents_child_by_index (arg, j);
     394                    if (e->text.end > 0)
     395                      {
     396                        current = merge_text (current, e->text.text, e);
     397                        destroy_element (e);
     398                      }
     399                    else
     400                      {
     401                        add_to_element_contents (current, e);
     402                      }
     403                  }
     404              }
     405            destroy_element (arg);
     406          }
     407        destroy_element (menu_entry);
     408        }
     409      }
     410    else
     411      {
     412        debug ("MENU ENTRY END LINE");
     413        current = current->parent;
     414        current = enter_menu_entry_node (current);
     415        if (end_comment)
     416          add_to_element_contents (current, end_comment);
     417      }
     418  
     419    return current;
     420  }