676 lines
16 KiB
C
676 lines
16 KiB
C
/* $Xorg: imLcPrs.c,v 1.3 2000/08/17 19:45:14 cpqbld Exp $ */
|
|
/******************************************************************
|
|
|
|
Copyright 1992 by Oki Technosystems Laboratory, Inc.
|
|
Copyright 1992 by Fuji Xerox Co., Ltd.
|
|
|
|
Permission to use, copy, modify, distribute, and sell this software
|
|
and its documentation for any purpose is hereby granted without fee,
|
|
provided that the above copyright notice appear in all copies and
|
|
that both that copyright notice and this permission notice appear
|
|
in supporting documentation, and that the name of Oki Technosystems
|
|
Laboratory and Fuji Xerox not be used in advertising or publicity
|
|
pertaining to distribution of the software without specific, written
|
|
prior permission.
|
|
Oki Technosystems Laboratory and Fuji Xerox make no representations
|
|
about the suitability of this software for any purpose. It is provided
|
|
"as is" without express or implied warranty.
|
|
|
|
OKI TECHNOSYSTEMS LABORATORY AND FUJI XEROX DISCLAIM ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL OKI TECHNOSYSTEMS
|
|
LABORATORY AND FUJI XEROX BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
|
|
OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
Author: Yasuhiro Kawai Oki Technosystems Laboratory
|
|
Author: Kazunori Nishihara Fuji Xerox
|
|
|
|
******************************************************************/
|
|
|
|
/* $XFree86: xc/lib/X11/imLcPrs.c,v 1.10 2003/09/06 14:06:32 pascal Exp $ */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xmd.h>
|
|
#include <X11/Xos.h>
|
|
#include "Xlibint.h"
|
|
#include "Xlcint.h"
|
|
#include "Ximint.h"
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
|
|
extern int _Xmbstowcs(
|
|
wchar_t *wstr,
|
|
char *str,
|
|
int len
|
|
);
|
|
|
|
extern int _Xmbstoutf8(
|
|
char *ustr,
|
|
const char *str,
|
|
int len
|
|
);
|
|
|
|
/*
|
|
* Parsing File Format:
|
|
*
|
|
* FILE ::= { [PRODUCTION] [COMMENT] "\n"}
|
|
* PRODUCTION ::= LHS ":" RHS [ COMMENT ]
|
|
* COMMENT ::= "#" {<any character except null or newline>}
|
|
* LHS ::= EVENT { EVENT }
|
|
* EVENT ::= [MODIFIER_LIST] "<" keysym ">"
|
|
* MODIFIER_LIST ::= ("!" {MODIFIER} ) | "None"
|
|
* MODIFIER ::= ["~"] modifier_name
|
|
* RHS ::= ( STRING | keysym | STRING keysym )
|
|
* STRING ::= '"' { CHAR } '"'
|
|
* CHAR ::= GRAPHIC_CHAR | ESCAPED_CHAR
|
|
* GRAPHIC_CHAR ::= locale (codeset) dependent code
|
|
* ESCAPED_CHAR ::= ('\\' | '\"' | OCTAL | HEX )
|
|
* OCTAL ::= '\' OCTAL_CHAR [OCTAL_CHAR [OCTAL_CHAR]]
|
|
* OCTAL_CHAR ::= (0|1|2|3|4|5|6|7)
|
|
* HEX ::= '\' (x|X) HEX_CHAR [HEX_CHAR]]
|
|
* HEX_CHAR ::= (0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|a|b|c|d|e|f)
|
|
*
|
|
*/
|
|
|
|
static int
|
|
nextch(
|
|
FILE *fp,
|
|
int *lastch)
|
|
{
|
|
int c;
|
|
|
|
if (*lastch != 0) {
|
|
c = *lastch;
|
|
*lastch = 0;
|
|
} else {
|
|
c = getc(fp);
|
|
if (c == '\\') {
|
|
c = getc(fp);
|
|
if (c == '\n') {
|
|
c = getc(fp);
|
|
} else {
|
|
ungetc(c, fp);
|
|
c = '\\';
|
|
}
|
|
}
|
|
}
|
|
return(c);
|
|
}
|
|
|
|
static void
|
|
putbackch(
|
|
int c,
|
|
int *lastch)
|
|
{
|
|
*lastch = c;
|
|
}
|
|
|
|
#define ENDOFFILE 0
|
|
#define ENDOFLINE 1
|
|
#define COLON 2
|
|
#define LESS 3
|
|
#define GREATER 4
|
|
#define EXCLAM 5
|
|
#define TILDE 6
|
|
#define STRING 7
|
|
#define KEY 8
|
|
#define ERROR 9
|
|
|
|
#ifndef isalnum
|
|
#define isalnum(c) \
|
|
(('0' <= (c) && (c) <= '9') || \
|
|
('A' <= (c) && (c) <= 'Z') || \
|
|
('a' <= (c) && (c) <= 'z'))
|
|
#endif
|
|
|
|
static int
|
|
nexttoken(
|
|
FILE *fp,
|
|
char *tokenbuf,
|
|
int *lastch)
|
|
{
|
|
int c;
|
|
int token;
|
|
char *p;
|
|
int i, j;
|
|
|
|
while ((c = nextch(fp, lastch)) == ' ' || c == '\t') {
|
|
}
|
|
switch (c) {
|
|
case EOF:
|
|
token = ENDOFFILE;
|
|
break;
|
|
case '\n':
|
|
token = ENDOFLINE;
|
|
break;
|
|
case '<':
|
|
token = LESS;
|
|
break;
|
|
case '>':
|
|
token = GREATER;
|
|
break;
|
|
case ':':
|
|
token = COLON;
|
|
break;
|
|
case '!':
|
|
token = EXCLAM;
|
|
break;
|
|
case '~':
|
|
token = TILDE;
|
|
break;
|
|
case '"':
|
|
p = tokenbuf;
|
|
while ((c = nextch(fp, lastch)) != '"') {
|
|
if (c == '\n' || c == EOF) {
|
|
putbackch(c, lastch);
|
|
token = ERROR;
|
|
goto string_error;
|
|
} else if (c == '\\') {
|
|
c = nextch(fp, lastch);
|
|
switch (c) {
|
|
case '\\':
|
|
case '"':
|
|
*p++ = c;
|
|
break;
|
|
case 'n':
|
|
*p++ = '\n';
|
|
break;
|
|
case 'r':
|
|
*p++ = '\r';
|
|
break;
|
|
case 't':
|
|
*p++ = '\t';
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
i = c - '0';
|
|
c = nextch(fp, lastch);
|
|
for (j = 0; j < 2 && c >= '0' && c <= '7'; j++) {
|
|
i <<= 3;
|
|
i += c - '0';
|
|
c = nextch(fp, lastch);
|
|
}
|
|
putbackch(c, lastch);
|
|
*p++ = (char)i;
|
|
break;
|
|
case 'X':
|
|
case 'x':
|
|
i = 0;
|
|
for (j = 0; j < 2; j++) {
|
|
c = nextch(fp, lastch);
|
|
i <<= 4;
|
|
if (c >= '0' && c <= '9') {
|
|
i += c - '0';
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
i += c - 'A' + 10;
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
i += c - 'a' + 10;
|
|
} else {
|
|
putbackch(c, lastch);
|
|
i >>= 4;
|
|
break;
|
|
}
|
|
}
|
|
if (j == 0) {
|
|
token = ERROR;
|
|
goto string_error;
|
|
}
|
|
*p++ = (char)i;
|
|
break;
|
|
case EOF:
|
|
putbackch(c, lastch);
|
|
token = ERROR;
|
|
goto string_error;
|
|
default:
|
|
*p++ = c;
|
|
break;
|
|
}
|
|
} else {
|
|
*p++ = c;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
token = STRING;
|
|
break;
|
|
case '#':
|
|
while ((c = nextch(fp, lastch)) != '\n' && c != EOF) {
|
|
}
|
|
if (c == '\n') {
|
|
token = ENDOFLINE;
|
|
} else {
|
|
token = ENDOFFILE;
|
|
}
|
|
break;
|
|
default:
|
|
if (isalnum(c) || c == '_' || c == '-') {
|
|
p = tokenbuf;
|
|
*p++ = c;
|
|
c = nextch(fp, lastch);
|
|
while (isalnum(c) || c == '_' || c == '-') {
|
|
*p++ = c;
|
|
c = nextch(fp, lastch);
|
|
}
|
|
*p = '\0';
|
|
putbackch(c, lastch);
|
|
token = KEY;
|
|
} else {
|
|
token = ERROR;
|
|
}
|
|
break;
|
|
}
|
|
string_error:
|
|
return(token);
|
|
}
|
|
|
|
static long
|
|
modmask(
|
|
char *name)
|
|
{
|
|
struct _modtbl {
|
|
const char name[6];
|
|
long mask;
|
|
};
|
|
|
|
static const struct _modtbl tbl[] = {
|
|
{ "Ctrl", ControlMask },
|
|
{ "Lock", LockMask },
|
|
{ "Caps", LockMask },
|
|
{ "Shift", ShiftMask },
|
|
{ "Alt", Mod1Mask },
|
|
{ "Meta", Mod1Mask }};
|
|
|
|
int i, num_entries = sizeof (tbl) / sizeof (tbl[0]);
|
|
|
|
for (i = 0; i < num_entries; i++)
|
|
if (!strcmp (name, tbl[i].name))
|
|
return tbl[i].mask;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char*
|
|
TransFileName(Xim im, char *name)
|
|
{
|
|
char *home = NULL, *lcCompose = NULL;
|
|
char *i = name, *ret, *j;
|
|
int l = 0;
|
|
|
|
while (*i) {
|
|
if (*i == '%') {
|
|
i++;
|
|
switch (*i) {
|
|
case '%':
|
|
l++;
|
|
break;
|
|
case 'H':
|
|
home = getenv("HOME");
|
|
if (home)
|
|
l += strlen(home);
|
|
break;
|
|
case 'L':
|
|
lcCompose = _XlcFileName(im->core.lcd, COMPOSE_FILE);
|
|
if (lcCompose)
|
|
l += strlen(lcCompose);
|
|
break;
|
|
}
|
|
} else {
|
|
l++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
j = ret = Xmalloc(l+1);
|
|
if (ret == NULL)
|
|
return ret;
|
|
i = name;
|
|
while (*i) {
|
|
if (*i == '%') {
|
|
i++;
|
|
switch (*i) {
|
|
case '%':
|
|
*j++ = '%';
|
|
break;
|
|
case 'H':
|
|
if (home) {
|
|
strcpy(j, home);
|
|
j += strlen(home);
|
|
}
|
|
break;
|
|
case 'L':
|
|
if (lcCompose) {
|
|
strcpy(j, lcCompose);
|
|
j += strlen(lcCompose);
|
|
Xfree(lcCompose);
|
|
}
|
|
break;
|
|
}
|
|
i++;
|
|
} else {
|
|
*j++ = *i++;
|
|
}
|
|
}
|
|
*j = '\0';
|
|
return ret;
|
|
}
|
|
|
|
#ifndef MB_LEN_MAX
|
|
#define MB_LEN_MAX 6
|
|
#endif
|
|
|
|
static int
|
|
get_mb_string (Xim im, char *buf, KeySym ks)
|
|
{
|
|
XPointer from, to;
|
|
int from_len, to_len, len;
|
|
XPointer args[1];
|
|
XlcCharSet charset;
|
|
char local_buf[MB_LEN_MAX];
|
|
unsigned int ucs;
|
|
ucs = KeySymToUcs4(ks);
|
|
|
|
from = (XPointer) &ucs;
|
|
to = (XPointer) local_buf;
|
|
from_len = 1;
|
|
to_len = MB_LEN_MAX;
|
|
args[0] = (XPointer) &charset;
|
|
if (_XlcConvert(im->private.local.ucstoc_conv,
|
|
&from, &from_len, &to, &to_len, args, 1 ) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
from = (XPointer) local_buf;
|
|
to = (XPointer) buf;
|
|
from_len = MB_LEN_MAX - to_len;
|
|
to_len = MB_LEN_MAX + 1;
|
|
args[0] = (XPointer) charset;
|
|
if (_XlcConvert(im->private.local.cstomb_conv,
|
|
&from, &from_len, &to, &to_len, args, 1 ) != 0) {
|
|
return 0;
|
|
}
|
|
len = MB_LEN_MAX + 1 - to_len;
|
|
buf[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
#define AllMask (ShiftMask | LockMask | ControlMask | Mod1Mask)
|
|
#define LOCAL_WC_BUFSIZE 128
|
|
#define LOCAL_UTF8_BUFSIZE 256
|
|
#define SEQUENCE_MAX 10
|
|
|
|
static int
|
|
parseline(
|
|
FILE *fp,
|
|
Xim im,
|
|
char* tokenbuf)
|
|
{
|
|
int token;
|
|
DTModifier modifier_mask;
|
|
DTModifier modifier;
|
|
DTModifier tmp;
|
|
KeySym keysym = NoSymbol;
|
|
DTIndex *top = &im->private.local.top;
|
|
DefTreeBase *b = &im->private.local.base;
|
|
DTIndex t;
|
|
DefTree *p = NULL;
|
|
Bool exclam, tilde;
|
|
KeySym rhs_keysym = 0;
|
|
char *rhs_string_mb;
|
|
int l;
|
|
int lastch = 0;
|
|
char local_mb_buf[MB_LEN_MAX+1];
|
|
wchar_t local_wc_buf[LOCAL_WC_BUFSIZE], *rhs_string_wc;
|
|
char local_utf8_buf[LOCAL_UTF8_BUFSIZE], *rhs_string_utf8;
|
|
|
|
struct DefBuffer {
|
|
DTModifier modifier_mask;
|
|
DTModifier modifier;
|
|
KeySym keysym;
|
|
};
|
|
|
|
struct DefBuffer buf[SEQUENCE_MAX];
|
|
int i, n;
|
|
|
|
do {
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
} while (token == ENDOFLINE);
|
|
|
|
if (token == ENDOFFILE) {
|
|
return(-1);
|
|
}
|
|
|
|
n = 0;
|
|
do {
|
|
if ((token == KEY) && (strcmp("include", tokenbuf) == 0)) {
|
|
char *filename;
|
|
FILE *infp;
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token != KEY && token != STRING)
|
|
goto error;
|
|
if ((filename = TransFileName(im, tokenbuf)) == NULL)
|
|
goto error;
|
|
infp = _XFopenFile(filename, "r");
|
|
Xfree(filename);
|
|
if (infp == NULL)
|
|
goto error;
|
|
_XimParseStringFile(infp, im);
|
|
fclose(infp);
|
|
return (0);
|
|
} else if ((token == KEY) && (strcmp("None", tokenbuf) == 0)) {
|
|
modifier = 0;
|
|
modifier_mask = AllMask;
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
} else {
|
|
modifier_mask = modifier = 0;
|
|
exclam = False;
|
|
if (token == EXCLAM) {
|
|
exclam = True;
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
}
|
|
while (token == TILDE || token == KEY) {
|
|
tilde = False;
|
|
if (token == TILDE) {
|
|
tilde = True;
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token != KEY)
|
|
goto error;
|
|
}
|
|
tmp = modmask(tokenbuf);
|
|
if (!tmp) {
|
|
goto error;
|
|
}
|
|
modifier_mask |= tmp;
|
|
if (tilde) {
|
|
modifier &= ~tmp;
|
|
} else {
|
|
modifier |= tmp;
|
|
}
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
}
|
|
if (exclam) {
|
|
modifier_mask = AllMask;
|
|
}
|
|
}
|
|
|
|
if (token != LESS) {
|
|
goto error;
|
|
}
|
|
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token != KEY) {
|
|
goto error;
|
|
}
|
|
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token != GREATER) {
|
|
goto error;
|
|
}
|
|
|
|
keysym = XStringToKeysym(tokenbuf);
|
|
if (keysym == NoSymbol) {
|
|
goto error;
|
|
}
|
|
|
|
buf[n].keysym = keysym;
|
|
buf[n].modifier = modifier;
|
|
buf[n].modifier_mask = modifier_mask;
|
|
n++;
|
|
if( n >= SEQUENCE_MAX )
|
|
goto error;
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
} while (token != COLON);
|
|
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token == STRING) {
|
|
l = strlen(tokenbuf) + 1;
|
|
while (b->mbused + l > b->mbsize) {
|
|
b->mbsize = b->mbsize ? b->mbsize * 1.5 : 1024;
|
|
if (! (b->mb = Xrealloc (b->mb, b->mbsize)) )
|
|
goto error;
|
|
}
|
|
rhs_string_mb = &b->mb[b->mbused];
|
|
b->mbused += l;
|
|
strcpy(rhs_string_mb, tokenbuf);
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token == KEY) {
|
|
rhs_keysym = XStringToKeysym(tokenbuf);
|
|
if (rhs_keysym == NoSymbol) {
|
|
goto error;
|
|
}
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
}
|
|
if (token != ENDOFLINE && token != ENDOFFILE) {
|
|
goto error;
|
|
}
|
|
} else if (token == KEY) {
|
|
rhs_keysym = XStringToKeysym(tokenbuf);
|
|
if (rhs_keysym == NoSymbol) {
|
|
goto error;
|
|
}
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
if (token != ENDOFLINE && token != ENDOFFILE) {
|
|
goto error;
|
|
}
|
|
|
|
l = get_mb_string(im, local_mb_buf, rhs_keysym);
|
|
while (b->mbused + l + 1 > b->mbsize) {
|
|
b->mbsize = b->mbsize ? b->mbsize * 1.5 : 1024;
|
|
if (! (b->mb = Xrealloc (b->mb, b->mbsize)) )
|
|
goto error;
|
|
}
|
|
rhs_string_mb = &b->mb[b->mbused];
|
|
b->mbused += l + 1;
|
|
memcpy(rhs_string_mb, local_mb_buf, l);
|
|
rhs_string_mb[l] = '\0';
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
l = _Xmbstowcs(local_wc_buf, rhs_string_mb, LOCAL_WC_BUFSIZE - 1);
|
|
if (l == LOCAL_WC_BUFSIZE - 1) {
|
|
local_wc_buf[l] = (wchar_t)'\0';
|
|
}
|
|
while (b->wcused + l + 1 > b->wcsize) {
|
|
b->wcsize = b->wcsize ? b->wcsize * 1.5 : 512;
|
|
if (! (b->wc = Xrealloc (b->wc, sizeof(wchar_t) * b->wcsize)) )
|
|
goto error;
|
|
}
|
|
rhs_string_wc = &b->wc[b->wcused];
|
|
b->wcused += l + 1;
|
|
memcpy((char *)rhs_string_wc, (char *)local_wc_buf, (l + 1) * sizeof(wchar_t) );
|
|
|
|
l = _Xmbstoutf8(local_utf8_buf, rhs_string_mb, LOCAL_UTF8_BUFSIZE - 1);
|
|
if (l == LOCAL_UTF8_BUFSIZE - 1) {
|
|
local_wc_buf[l] = '\0';
|
|
}
|
|
while (b->utf8used + l + 1 > b->utf8size) {
|
|
b->utf8size = b->utf8size ? b->utf8size * 1.5 : 1024;
|
|
if (! (b->utf8 = Xrealloc (b->utf8, b->utf8size)) )
|
|
goto error;
|
|
}
|
|
rhs_string_utf8 = &b->utf8[b->utf8used];
|
|
b->utf8used += l + 1;
|
|
memcpy(rhs_string_utf8, local_utf8_buf, l + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
for (t = *top; t; t = b->tree[t].next) {
|
|
if (buf[i].keysym == b->tree[t].keysym &&
|
|
buf[i].modifier == b->tree[t].modifier &&
|
|
buf[i].modifier_mask == b->tree[t].modifier_mask) {
|
|
break;
|
|
}
|
|
}
|
|
if (t) {
|
|
p = &b->tree[t];
|
|
top = &p->succession;
|
|
} else {
|
|
while (b->treeused >= b->treesize) {
|
|
DefTree *old = b->tree;
|
|
int oldsize = b->treesize;
|
|
b->treesize = b->treesize ? b->treesize * 1.5 : 256;
|
|
if (! (b->tree = Xrealloc (b->tree, sizeof(DefTree) * b->treesize)) )
|
|
goto error;
|
|
if (top >= (DTIndex *) old && top < (DTIndex *) &old[oldsize])
|
|
top = (DTIndex *) (((char *) top) + (((char *)b->tree)-(char *)old));
|
|
}
|
|
p = &b->tree[b->treeused];
|
|
p->keysym = buf[i].keysym;
|
|
p->modifier = buf[i].modifier;
|
|
p->modifier_mask = buf[i].modifier_mask;
|
|
p->succession = 0;
|
|
p->next = *top;
|
|
p->mb = 0;
|
|
p->wc = 0;
|
|
p->utf8 = 0;
|
|
p->ks = NoSymbol;
|
|
*top = b->treeused;
|
|
top = &p->succession;
|
|
b->treeused++;
|
|
}
|
|
}
|
|
|
|
/* old entries no longer freed... */
|
|
p->mb = rhs_string_mb - b->mb;
|
|
p->wc = rhs_string_wc - b->wc;
|
|
p->utf8 = rhs_string_utf8 - b->utf8;
|
|
p->ks = rhs_keysym;
|
|
return(n);
|
|
error:
|
|
while (token != ENDOFLINE && token != ENDOFFILE) {
|
|
token = nexttoken(fp, tokenbuf, &lastch);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
void
|
|
_XimParseStringFile(
|
|
FILE *fp,
|
|
Xim im)
|
|
{
|
|
char tb[8192];
|
|
char* tbp;
|
|
struct stat st;
|
|
|
|
if (fstat (fileno (fp), &st) != -1) {
|
|
unsigned long size = (unsigned long) st.st_size;
|
|
if (size <= sizeof tb) tbp = tb;
|
|
else tbp = malloc (size);
|
|
|
|
if (tbp != NULL) {
|
|
while (parseline(fp, im, tbp) >= 0) {}
|
|
if (tbp != tb) free (tbp);
|
|
}
|
|
}
|
|
}
|