#include <stdio.h>
#include <string.h>
#include <mit-copyright.h>

/* $XConsortium: Template.c,v 1.2 88/10/25 17:40:25 swick Exp $ */
/* Copyright	Massachusetts Institute of Technology	1987, 1988 */
#include <Xm/XmP.h>    
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>    
#include <X11/Shell.h>
#include <X11/ShellP.h>
#include <Xm/MenuShell.h>
#include <Xm/MenuShellP.h>
#include "MuMenuP.h"
#include <Xm/PushBG.h>
#include <Xm/ToggleBG.h>
#include <Xm/SeparatoG.h>
#include <Xm/CascadeB.h>
#include <ctype.h>    

#define DEFAULT_RESOURCES "\"No resources have been loaded.\";"
    
#define offset(field) XtOffset(MuMenuWidget, muMenu.field)
static XtResource resources[] = {
    {XmNitems, XmCItems, XtRString,
     sizeof(String), offset(items), XtRString, DEFAULT_RESOURCES}
};
#undef offset

#ifdef __STDC__
# define        P(s) s
#else
# define P(s) ()
#endif

static void ClassInitialize P((void ));
static char *indexq P((char *where , int what ));
static void Initialize P((MuMenuWidget request , MuMenuWidget new ));
#undef P

#define superclass (&xmRowColumnClassRec)

MuMenuClassRec muMenuClassRec = {
  { /* core fields */
    /* superclass		*/	(WidgetClass) superclass,
    /* class_name		*/	"MuMenu",
    /* widget_size		*/	sizeof(MuMenuRec),
    /* class_initialize		*/	ClassInitialize,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	(XtInitProc) Initialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	XtInheritRealize,
    /* actions			*/	NULL,
    /* num_actions		*/	0,
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	FALSE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	NULL,
    /* resize			*/	XtInheritResize,
    /* expose			*/	XtInheritExpose,
    /* set_values		*/	NULL,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	XtInheritTranslations,
    /* query_geometry		*/	XtInheritQueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },
    {                  /* composite class record */
        XtInheritGeometryManager, /* childrens geo mgr proc */
        XtInheritChangeManaged,   /* set changed proc */
        XtInheritInsertChild,     /* add a child */
        XtInheritDeleteChild,     /* remove a child */
        NULL,                     /* extension */
    },
    {                  /* constraint class record */
        NULL,                    /* constraint resources */
        0,                       /* constraint resource_count */
        sizeof(XmRowColumnConstraintRec),  /* constraint_size */
        NULL,                    /* initialize */
        NULL,                    /* destroy */
        NULL,                    /* set_values */
        NULL,                    /* extension */
    },
    {                  /* manager class record */
        XtInheritTranslations /*(XtTranslations)_XtInherit*/,  /* translations */
        NULL,                        /* get_resources */
        0,                           /* num_get_resources */
        NULL,                    /* get_constraint_resources */
        0,                       /* num_get_constraint_resources */
        NULL,                    /* extension */
    },
    {                  /* row column class record */
        NULL,			/* proc to interface with menu widgets*/
        NULL,			/* proc to arm&activate menu */
        NULL,			/* traversal handler */
        NULL,			/* extension */
    },
    { /* muMenu fields */
      /* empty		     */	0
    }
};

WidgetClass muMenuWidgetClass = (WidgetClass)&muMenuClassRec;


/* Hash table declarations */

struct bucket {
    struct bucket *next;
    int key;
    Widget data;
};
struct hash {
    int size;
    struct bucket **data;
};

/* hashing routines should be with ints to be cast to Widgets
 * and XtCallbackProcs, not cast from Widgets...
 */
extern struct hash *create_hash();
extern Widget hash_lookup();
extern int hash_store();
static struct hash *table = NULL;
#define HASHSIZE 67

void
MuRegisterMenuCallbacks( c, num )
     MuCallbacksRec c[];
     int num;
{
  int i;
  XrmQuark quark;
  char err_buf[100];

  if ( table == NULL )
    table = create_hash(HASHSIZE);

  for ( i = 0; i < num; i++ )
    {
      quark = XrmStringToQuark( c[i].name );
      if ( 1 == hash_store( table, quark, (Widget) c[i].proc ) )
	{
	  sprintf( err_buf,
	  "MuRegisterMenuCallbacks: duplicate procedure registration: '%s'",
		  c[i].name );
	  XtWarning( err_buf );
	}
    }
}

XtCallbackProc MuGetMenuCallback( name )
     char *name;
{
  XrmQuark quark;

  quark = XrmStringToQuark( name );
  if ( table != NULL )
    return ( XtCallbackProc )(hash_lookup( table, quark ));
  else
    return NULL;
}


static void ClassInitialize()
{
/* got to fix up inheritance myself, since RowColumn won't do it for me */
#define inherit(field) muMenuClassRec.row_column_class.field = superclass->row_column_class.field
    inherit(menuProcedures);
    inherit(armAndActivate);
    inherit(traversalHandler);
    inherit(extension);
#undef inherit    
}

#define ITYPENONE 0
#define ITYPEBUTTON 1
#define ITYPESEPARATOR 2
#define ITYPECASCADE 3
#define ITYPETOGGLEON 4
#define ITYPETOGGLEOFF 5

#define PTYPENULL 0
#define PTYPESTR 1
#define PTYPEINT 2

#define SHIFT 1
#define CTRL  2
#define ALT   4
#define META  8
#define LOCK  16

/*
 * Return first occurrence of what that's not in quotes
 * does the right thing for what == '"' :)
 */

static char *indexq( where, what )
     char *where;
     int what;
{
int inquotes = 0, found = 0;

while ( !found && *where )
  {
    if ( !inquotes && *where == (char) what )
      found = 1;
    else
      {
	if ( *where == '"' )
	  inquotes = 1 - inquotes;
	where++;
      }
  }
return (char *) (found * (int) where);
}

#define min( a, b ) a < b ? a : b

#define MAXLINE 80

static void
Initialize( request, new )
     MuMenuWidget request;
     MuMenuWidget new;
{
    static char rtn[] = "MuCreateMenuBar:";
    char *i, *items, *endquote, *ptr, *ptr2;
    XmString label;
    char *end, *somethingtoparse;

    char un, itemname[100], subname[100], procname[100], scratch[100];
    char accelText[100], accelKey[100];
    int  accelModifiers;
    char err_buf[300], thisline[100];

    int ptype, ishelp;
    char strparm[100];
    int intparm;
    char widgetname[100];
    char registered_name[100];
    Widget w;
    char *cbName;
    XtCallbackProc proc;
    caddr_t data;
    Arg args[5];
    int n;

    int numitems, type;

    items = new->muMenu.items;
    end = strchr( items, '\0' );

    for ( i = items, numitems=0; i < end; numitems++ )
      {
	sprintf( widgetname, "item%d", numitems );
	itemname[0] = '\0';	/* displayed menu item name */
	registered_name[0] = '\0'; /* name to register in hash table */
	subname[0] = '\0';	/* resource name of submenu */
	procname[0] = '\0';	/* name of procedure to call */
	accelText[0] = '\0';	/* accelerator string to display */
	accelKey[0] = '\0';     /* accelerator key */
	accelModifiers = 0;	/* accelerator modifier keys */
	type = ITYPENONE;	/* type of menu item */
	ishelp = 0;
	un = '\0'; /* what character to underline in menu item */

	somethingtoparse = indexq( i, ';' );
	if ( somethingtoparse )
	  {
	    strncpy( thisline, i, min(100, somethingtoparse - i + 1));
	    thisline[ min(99, somethingtoparse - i + 1) ] = '\0';
	  }
	else
	  {
	    sprintf( err_buf, "%s no ';' found in:\n%s\n", rtn, i );
	    XtWarning( err_buf );
	    i = end;
	  }

	/* skip over white space */
	while (isspace(*i)) i++;

	while ( somethingtoparse )
	  {
	    switch( *i )
	      {
	      case '"': /* parse name */
		i++;
		endquote = strchr( i, '"' );
		/*
		 * there has to be an endquote in this parse interpretation
		 * so endquote guaranteed not == 0
		 */
		if ( !strncmp( i, "------------", endquote - i ) )
		  {
		    type = ITYPESEPARATOR;
		    i = endquote + 1;
		  }
		else /* parse non-separator name */
		  {
		    if ( type == ITYPENONE ) /* don't override toggle */
		      type = ITYPEBUTTON;
		    
		    ptr = itemname;

		    while ( i < endquote )
		      {
			if ( *i == '_' )
			  un = *(++i);
			*ptr++ = *i++;
		      }
		    *ptr = '\0';
		    i++;
		  }
		break;

	      case '-': /* parse submenu stuff */
		type = ITYPECASCADE;
		endquote = strchr( i+1, '>' );
		if ( !endquote || endquote > somethingtoparse )
		  {
		    sprintf( err_buf,
			    "%s missing symbol '>' in submenu:\n%s\n",
			    rtn, thisline );
		    XtWarning( err_buf );
		    i = somethingtoparse; /* ignore line */
		    type = ITYPENONE;
		  }
		else
		  {
		    i = endquote + 1;
		    while (isspace(*i)) i++;
		    ptr = subname;
		    while ( !isspace(*i) && i < somethingtoparse )
		      *ptr++ = *i++;
		    *ptr = '\0';
		  }
		break;

	      case '[': /* parse accelerator stuff */
		endquote = strchr(i+1, ']');
		if ( !endquote || endquote > somethingtoparse ) {
		    sprintf( err_buf,
			    "%s missing symbol ']' in submenu:\n%s\n",
			    rtn, thisline );
		    XtWarning( err_buf );
		    i = somethingtoparse; /* ignore line */
		    type = ITYPENONE;
		}
	        else {
		    i++;
		    while(isspace(*i)) i++;
		    ptr = accelText;
		    while(i < endquote) *ptr++ = *i++;
		    *ptr = '\0';
		    while (isspace(*(--ptr))) *ptr = '\0';
		    ptr = accelText;
		    while (*ptr) {
			while ((*ptr) && (!isalnum(*ptr))) {
			    if (*ptr == '^') accelModifiers |= CTRL;
			    ptr++;
			}
			if (!*ptr) break;
			if (!strncasecmp(ptr, "shift", 5)) {
			    ptr += 5;
			    accelModifiers |= SHIFT;
			    continue;
			}
			else if (!strncasecmp(ptr, "ctrl", 4)) {
			    ptr += 4;
			    accelModifiers |= CTRL;
			    continue;
			}
			else if (!strncasecmp(ptr, "alt", 3)) {
			    ptr += 3;
			    accelModifiers |= ALT;
			    continue;
			}
			else if (!strncasecmp(ptr, "meta", 4)) {
			    ptr += 4;
			    accelModifiers |= META;
			    continue;
			}
			else if (!strncasecmp(ptr, "lock", 4)) {
			    ptr += 4;
			    accelModifiers |= LOCK;
			    continue;
			}
			else {
			    ptr2 = accelKey;
			    while (isalnum(*ptr)) *ptr2++ = *ptr++;
			    *ptr2 = '\0';
			    break;
			}
		    }
		    i = endquote +1;
		}
		break;
		
	      default: /* parse everything else; s.b. guaranteed not ';' */
		ptr = scratch;
		while ( !isspace(*i)
		       && *i != '('
		       && *i != ':'
		       && i < somethingtoparse )
		  *ptr++ = *i++;
		*ptr = '\0';
	      
		switch(*i)
		  {
		  case ':':	/* It's a name for the widget. */
		    strcpy(registered_name, scratch);
		    i++;
		    break;

		  case '(':	/* It's a procedure. */
		    if ( type == ITYPENONE ) /* don't override toggle */
		      type = ITYPEBUTTON;

		    strcpy( procname, scratch );

		    i++;
		    while (isspace(*i)) i++;

		    if ( *i == '"' ) /* string */
		      {
			ptype = PTYPESTR;
			i++;
			endquote = strchr( i, '"' );

			strncpy( strparm, i, endquote - i );
			strparm[endquote - i] = '\0';

			i = endquote + 1;
			while (isspace(*i)) i++;
			if ( *i != ')' )
			  {
			    sprintf( err_buf,
				    "%s missing ')' after parameter in:\n%s\n",
				    rtn, thisline );
			    XtWarning( err_buf );
			    i = somethingtoparse; /* ignore line */
			    type = ITYPENONE;
			  }
			else
			  i++;
		      }
		    else /* int or NULL */
		      {
			if ( *i == ')' ) /* NULL */
			  {
			    ptype = PTYPENULL;
			    i++;
			  }
			else /* int */
			  {
			    ptr = strparm;
			    while ( *i != ')' && i < somethingtoparse )
			      *ptr++ = *i++;
			    *ptr = '\0';

			    intparm = atoi( strparm );
			    ptype = PTYPEINT;

			    if ( *i != ')' )
			      {
				sprintf( err_buf,
				    "%s missing ')' after parameter in:\n%s\n",
					rtn, thisline );
				XtWarning( err_buf );
				i = somethingtoparse; /* ignore line */
				type = ITYPENONE;
			      }
			    else
			      i++;
			  }
		      }
		    break;
		    
		  default:	/* then a flag */
		    if ( !strcmp( scratch, "togon" ) )
		      type = ITYPETOGGLEON;
		    else if ( !strcmp( scratch, "togoff" ) )
		      type = ITYPETOGGLEOFF;
		    else if ( !strcmp( scratch, "help" ) )
		      ishelp = 1;
		    else
		      {
			sprintf( err_buf,
				"%s unknown flag: '%.25' in:\n%s\n",
				rtn, scratch, thisline );
			XtWarning( err_buf );
			i = somethingtoparse; /* ignore line */
			type = ITYPENONE;
		      }
		  }  /* end of inner switch */
	      } /* end of switch */

	    while (isspace(*i)) i++;
	    if ( *i == ';' )
	      {
		i++;
		somethingtoparse = NULL;
	      }
	    while (isspace(*i)) i++;

	    if ( *i == '\0' )
	      somethingtoparse = NULL;
	  } /* end while (somethingtoparse) */
	/* now it's all been parsed into the proper variables */

	n = 0;
	/* choose the label */
	if ( type != ITYPESEPARATOR )
	  {
	    if ( itemname[0] != '\0' )
	      ptr = itemname;
	    else
	      if ( itemname[0] != '\0' )
		ptr = subname;
	      else
		if ( itemname[0] != '\0' )
		  ptr = procname;
		else
		  ptr = widgetname;
	    label = XmStringCreateLtoR( ptr, XmSTRING_DEFAULT_CHARSET );
	    XtSetArg( args[n], XmNlabelString, label ); n++;
/*	    XmStringFree(label); */
	  }

	if ( un != '\0' ) /* not possible for separator (put inside anyway) */
	  {
	    XtSetArg( args[n], XmNmnemonic, un );
	    n++;
	  }

	if (accelText[0] != '\0') {
	    label = XmStringCreateLtoR(accelText, XmSTRING_DEFAULT_CHARSET);
	    XtSetArg(args[n], XmNacceleratorText, label);
/*	    XmStringFree(label); */
		     
	    n++;
	    scratch[0] = '\0';
	    if (accelModifiers & SHIFT)
		strcat(scratch, "Shift ");
	    if (accelModifiers & CTRL)
		strcat(scratch, "Ctrl ");
	    if (accelModifiers & META)
		strcat(scratch, "Meta ");
	    if (accelModifiers & ALT)
		strcat(scratch, "Alt ");
	    if (accelModifiers & LOCK)
		strcat(scratch, "Lock ");
	    strcat(scratch, "<Key>");
	    strcat(scratch, accelKey);
	    XtSetArg(args[n], XmNaccelerator, scratch);
	    n++;
	}

	switch( type )
	  {
	  case ITYPESEPARATOR:
	    w = (Widget) XmCreateSeparatorGadget( (Widget) new,
						 widgetname, NULL, 0 );
	    break;
	  case ITYPEBUTTON:
	    w = (Widget) XmCreatePushButtonGadget( (Widget) new,
						  widgetname, args, n );
	    cbName = XmNactivateCallback;
	    break;
	  case ITYPETOGGLEON:
	    XtSetArg( args[n], XmNset, (Boolean) True ); n++;
	  case ITYPETOGGLEOFF:
	    w = (Widget) XmCreateToggleButtonGadget( (Widget) new,
						    widgetname, args, n );
	    cbName = XmNvalueChangedCallback;
	    break;
	  case ITYPECASCADE:
	    w = (Widget) MuCreateMenuPane( (Widget) new,
					  subname, (Arg *)NULL, 0);
	    XtSetArg( args[n], XmNsubMenuId, w ); n++;
	    sprintf( scratch, "%s_button", subname );
	    w = (Widget) XmCreateCascadeButton( (Widget) new,
					       scratch, args, n );
	    break;
	  default:
	    w = (Widget) NULL;
	  }

	if ( w )
	  {
	    if ( ishelp )
	      {
		XtSetArg( args[0], XmNmenuHelpWidget, w );
		XtSetValues( (Widget)new, args, 1 );
	      }
	    if ( procname[0] != '\0' )
	      {
		switch( ptype )
		  {
		  case PTYPENULL:
		    data = (caddr_t) NULL;
		    break;
		  case PTYPESTR:
		    data = (caddr_t) XtNewString( (String) strparm );
		    break;
		  case PTYPEINT:
		    data = (caddr_t) intparm;
		    break;
		  }
		proc = MuGetMenuCallback( procname );
		if ( proc != NULL )
		  XtAddCallback( w, cbName, proc, data );
		else
		  {
		    sprintf( err_buf,
			    "%s unregistered function '%s'",
			    rtn, procname );
		    XtWarning( err_buf );
		  }
	      }
	    if ( registered_name[0] != '\0' )
	      MuRegisterWidget(w, registered_name);
	    XtManageChild( w );
	  }
      }
  }

#define IsPopup(m)     \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_POPUP)
#define IsPulldown(m)  \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_PULLDOWN)
#define IsOption(m)    \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_OPTION)
#define IsBar(m)       \
    (((XmRowColumnWidget) (m))->row_column.type == XmMENU_BAR)

Widget
MuCreateMenuBar( p, name, args, count)
     Widget p;
     String name;
     Arg *args;
     int count;
{
    Arg al[50];
    int ac = 0;
    Widget m;
    int i;

    /* copy arglist and add the rowColumnType resource */
    for (i=0; i<count; i++) al[ac++] = args[i];  
    XtSetArg (al[ac], XmNrowColumnType,  XmMENU_BAR); ac++;
    m = XtCreateWidget (name, muMenuWidgetClass, p, al, ac);
    return (m);
}

Widget
MuCreateMenuPane( p, name, args, count )
     Widget p;
     String name;
     Arg *args;
     int count;
{
    Arg al[50];
    int ac = 0;
    Widget m;
    int i;
    Arg s_al[25];
    XmMenuShellWidget pop = NULL;
    Widget pw;
    int s_ac = 0;
    char b[200];

    for (i=0; i<count; i++) al[ac++] = args[i];  /* copy into our list */

    /* force it to be a menu pane */
    XtSetArg (al[ac], XmNrowColumnType,  XmMENU_PULLDOWN); ac++;

    /*
     * if this is a pulldown of a pulldown or popup then the parent
     * should really be the shell of the parent not the indicated 
     * parent, this keeps the cascade tree correct
     */
    
    if ((XtParent(p) != NULL) && XmIsMenuShell(XtParent (p)))
	pw = XtParent (p);
    else
	pw = p;
    
    /* now we need a shell widget */

    /* 
     * Shared menupanes are supported for all menu types but the option
     * menu.  If this is not an option menupane, then see if a shell is
     * already present; if so, then we'll use it.
     */
    if (XmIsRowColumn(p) && (IsBar(p) || IsPopup(p) || IsPulldown(p)))
    {
	for (i = 0; i < pw->core.num_popups; i++)
	{
	    if ((XmIsMenuShell(pw->core.popup_list[i])) &&
		(((XmMenuShellWidget)pw->core.popup_list[i])->menu_shell.
		 private_shell) &&
		(!(pw->core.popup_list[i])->core.being_destroyed))
	    {
		pop = (XmMenuShellWidget)pw->core.popup_list[i];
		break;
	    }
	}
    }
    
    /* No shell - create a new one */
    if (pop == NULL)
    {
	XtSetArg (s_al[s_ac], XmNwidth,             5);     s_ac++;
	XtSetArg (s_al[s_ac], XmNheight,        5);     s_ac++;
	XtSetArg (s_al[s_ac], XmNallowShellResize, TRUE);   s_ac++;
	XtSetArg (s_al[s_ac], XtNoverrideRedirect, TRUE);   s_ac++;
	
	sprintf (b, "popup_%s", name);
	
	pop = (XmMenuShellWidget)
	    XtCreatePopupShell(b, xmMenuShellWidgetClass, pw, s_al, s_ac);
	
	/* Mark the shell as having been created by us */
	pop->menu_shell.private_shell = True;
    }
    
    m = XtCreateWidget (name, muMenuWidgetClass, (Widget)pop, al, ac);
    
    return (m);
}

Widget MuCreateOptionMenu(p, name)
     Widget p;
     String name;
{
    Widget pane,  option;
    char panename[100];
    Arg a;

    strcpy(panename, name);
    strcat(panename, "Pane");
    pane = MuCreateMenuPane(p, panename, NULL, 0);
    XtSetArg(a, XmNsubMenuId, pane);
    option = XmCreateOptionMenu(p, name, &a, 1);
    return option;
}

void MuOptionMenuSetSelectedItem(w, n)
     Widget w;
     int n;
{
    XmRowColumnWidget option = (XmRowColumnWidget)w;
    CompositeWidget pane;
    Widget selected;
    Arg a;

    pane = (CompositeWidget) option->row_column.option_submenu;
    if (n >= pane->composite.num_children)
	n = pane->composite.num_children -1;
    if (n < 0)
	n = 0;
    selected = pane->composite.children[n];
    XtSetArg(a, XmNmenuHistory, selected);
    XtSetValues(w, &a, 1);
}

int MuOptionMenuGetSelectedItem(w)
     Widget w;
{
    XmRowColumnWidget option = (XmRowColumnWidget)w;
    CompositeWidget pane;
    Widget selected;
    int i;

    pane = (CompositeWidget) option->row_column.option_submenu;
    selected = option->row_column.memory_subwidget;
    for(i=0; i<pane->composite.num_children; i++) 
	if (pane->composite.children[i] == selected) break;
    if (i == pane->composite.num_children) i = -1;
    return i;
}

void MuOptionMenuSetSelectedString(w, str)
     Widget w;
     char *str;
{
    XmRowColumnWidget option = (XmRowColumnWidget)w;
    CompositeWidget pane;
    Widget selected;
    Arg a;
    XmString cs, value;
    int i;

    pane = (CompositeWidget) option->row_column.option_submenu;
    cs = XmStringCreateLtoR(str, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(a, XmNlabelString, &value);
    for(i=0; i<pane->composite.num_children; i++) {
	XtGetValues(pane->composite.children[i], &a, 1);
	if (XmStringCompare(value, cs)) break;
    }
    XmStringFree(cs);
    if (i < pane->composite.num_children) {
	selected = pane->composite.children[i];
	XtSetArg(a, XmNmenuHistory, selected);
	XtSetValues(w, &a, 1);
    }
}
    

char *MuOptionMenuGetSelectedString(w)
     Widget w;
{
    XmRowColumnWidget option = (XmRowColumnWidget)w;
    Widget selected;
    Arg a;
    XmString cs;
    char *str;

    selected = option->row_column.memory_subwidget;
    XtSetArg(a, XmNlabelString,  &cs);
    XtGetValues(selected, &a, 1);
    XmStringGetLtoR(cs, XmSTRING_DEFAULT_CHARSET, &str);
    return str;
}
