更新解析
parent
ff93b3a80c
commit
047ea038d5
533
src/conf.c
533
src/conf.c
|
|
@ -1,17 +1,71 @@
|
|||
#include "hash/hash.h"
|
||||
#include "stack/stack.h"
|
||||
#include <ctype.h>
|
||||
|
||||
//解析过程的各状态
|
||||
enum conf_stat
|
||||
{
|
||||
conf_stat_start,
|
||||
conf_stat_normal,
|
||||
conf_stat_note,
|
||||
conf_stat_key,
|
||||
conf_stat_more_key,
|
||||
conf_stat_quote,
|
||||
conf_stat_done
|
||||
};
|
||||
|
||||
#define POP (*data)
|
||||
#define ADVANCE (++data)
|
||||
#define BACK (--data)
|
||||
#define LEN (data-str)
|
||||
|
||||
#define CONF_GET_VALUE(value,stack) \
|
||||
{\
|
||||
if(conf_stack_get_data(stack) == NULL)\
|
||||
{\
|
||||
retcode=CONF_KEY_ERR;\
|
||||
printf("行:%d ",line);\
|
||||
goto end;\
|
||||
}\
|
||||
value=realloc(value,sizeof(char*)*(value_len+2));\
|
||||
if(value == NULL)\
|
||||
{\
|
||||
retcode=CONF_NO_MEM;\
|
||||
goto end;\
|
||||
}\
|
||||
value[value_len]=strdup(conf_stack_get_data(stack));\
|
||||
if(value[value_len] == NULL)\
|
||||
{\
|
||||
retcode=CONF_NO_MEM;\
|
||||
free(value);\
|
||||
goto end;\
|
||||
}\
|
||||
value[value_len+1]=NULL;\
|
||||
++value_len;\
|
||||
}
|
||||
|
||||
int line=1; //行
|
||||
|
||||
//解析对参数
|
||||
//data为配置文件内容
|
||||
//正确时返回0并置res为参数列表conf->len为参数个数
|
||||
//错误时返回错误代码
|
||||
int parse_value(CONF *conf,char *data,CONF_VALUE **res);
|
||||
//int parse_value(CONF *conf,char *data,CONF_VALUE **res);
|
||||
int parse_value(CONF *conf,char *data);
|
||||
//得到一行的偏移量
|
||||
int next_line(char *data);
|
||||
//释放内存
|
||||
void free_data(CONF_ARG *data);
|
||||
//得到键
|
||||
void get_key(char **key,int *index,CONF_ARG *arg);
|
||||
//读取键值
|
||||
int _conf_read_key(char *data,int len,STACK *stack);
|
||||
//获取值状态
|
||||
enum conf_stat _conf_value_status(char c);
|
||||
//读取值
|
||||
int _conf_read_value(char *data,int len,STACK *stack);
|
||||
//读取带有引号的值
|
||||
int _conf_read_value_with_quote(char *data,int len,STACK *stack,char quote);
|
||||
|
||||
//打开配置文件并初始化值
|
||||
//path为配置文件路径
|
||||
|
|
@ -36,6 +90,7 @@ CONF *conf_open(const char *path)
|
|||
|
||||
//初始化键值对
|
||||
conf->len=0;
|
||||
conf->size=HASH_DEFAULT;
|
||||
conf->hash_data=NULL;
|
||||
|
||||
return conf;
|
||||
|
|
@ -46,11 +101,9 @@ CONF *conf_open(const char *path)
|
|||
//解析成功返回错误代码
|
||||
int conf_parse(CONF *conf)
|
||||
{
|
||||
CONF_VALUE *value=NULL; //参数对列表
|
||||
char *data; //配置文件内容
|
||||
long file_len; //配置文件长度
|
||||
int retcode;
|
||||
int i;
|
||||
|
||||
//加载配置文件内容到内存
|
||||
if(fseek(conf->fp,0L,SEEK_END) != 0)
|
||||
|
|
@ -66,36 +119,15 @@ int conf_parse(CONF *conf)
|
|||
//关闭文件
|
||||
fclose(conf->fp);
|
||||
|
||||
//解析
|
||||
retcode=parse_value(conf,data,&value);
|
||||
|
||||
//开辟内存空间
|
||||
conf->hash_data=malloc(sizeof(CONF_ARG)*conf->len);
|
||||
//开辟内存空间出错
|
||||
if(conf->hash_data == NULL)
|
||||
{
|
||||
//如果已经解析了则进行释放
|
||||
if(value)
|
||||
free(value);
|
||||
|
||||
//返回错误
|
||||
retcode=CONF_NO_MEM;
|
||||
}
|
||||
|
||||
//初始化数组
|
||||
for(i=0;i != conf->len;++i)
|
||||
{
|
||||
conf->hash_data[i].len=0;
|
||||
conf->hash_data[i].next=NULL;
|
||||
conf->hash_data[i].value=NULL;
|
||||
}
|
||||
//插入数据
|
||||
for(i=0;i != conf->len;++i)
|
||||
conf_value_insert(conf->hash_data,&value[i],conf->len);
|
||||
|
||||
//创建表
|
||||
conf->hash_data=malloc(sizeof(CONF_ARG)*conf->size);
|
||||
if(conf->hash_data == NULL) return CONF_NO_MEM;
|
||||
//初始化键值对
|
||||
conf_hash_zero(conf->hash_data,conf->size);
|
||||
//解析
|
||||
retcode=parse_value(conf,data);
|
||||
free(data);
|
||||
//free(value);
|
||||
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +205,6 @@ int conf_insert(CONF_CREATER *creater,char *key,char *value,char *note)
|
|||
int conf_save(CONF_CREATER *creater)
|
||||
{
|
||||
CONF_CREATER *head=creater;
|
||||
// int i;
|
||||
|
||||
if(creater->len == 0)
|
||||
return CONF_NO_DATA;
|
||||
|
|
@ -186,14 +217,6 @@ int conf_save(CONF_CREATER *creater)
|
|||
if(creater->key != NULL && creater->value != NULL)
|
||||
fprintf(head->fp,"%s = %s\n",creater->key,creater->value);
|
||||
creater=creater->next;
|
||||
//如果该键有多个参数则用,分开
|
||||
/*if(creater->value->value[1] != NULL)
|
||||
{
|
||||
for(i=1;creater->value->value[i] != NULL;++i)
|
||||
fprintf(creater->fp,",%s",creater->value->value[i]);
|
||||
}
|
||||
|
||||
fwrite("\n",1,1,creater->fp);*/
|
||||
}
|
||||
|
||||
fclose(head->fp);
|
||||
|
|
@ -207,7 +230,7 @@ void conf_free(CONF *conf)
|
|||
int i;
|
||||
int j;
|
||||
|
||||
for(i=0;i != conf->len;++i)
|
||||
for(i=0;i != conf->size;++i)
|
||||
{
|
||||
if(conf->hash_data[i].len != 0)
|
||||
free_data(&conf->hash_data[i]);
|
||||
|
|
@ -255,157 +278,230 @@ void conf_error(int errcode)
|
|||
case CONF_VALUE_ERR:
|
||||
printf("错误:错误的值!\n");
|
||||
break;
|
||||
case STACK_MAX:
|
||||
printf("参数字符过多!\n");
|
||||
break;
|
||||
default:
|
||||
printf("未知错误!\n");
|
||||
}
|
||||
}
|
||||
|
||||
//解析参数
|
||||
int parse_value(CONF *conf,char *data,CONF_VALUE **res)
|
||||
int parse_value(CONF *conf,char *data)
|
||||
{
|
||||
int i=0; //字符偏移量
|
||||
int flags=0; //'与"的标记
|
||||
int key=0; //键标记
|
||||
int arg=0; //,标记
|
||||
int len=0; //键值对个数
|
||||
int count=0; //参数个数
|
||||
CONF_VALUE *value=NULL; //存放键值参数
|
||||
STACK stack; //存放参数的栈
|
||||
//保存解析字符串的首地址
|
||||
char *str=data;
|
||||
//状态
|
||||
int status;
|
||||
int save_status;
|
||||
int end_status;
|
||||
//引号
|
||||
char quote;
|
||||
//存储字符串的栈
|
||||
STACK stack;
|
||||
CONF_VALUE *value=NULL;
|
||||
int retcode=CONF_OK;
|
||||
//多参数个数
|
||||
int value_len=0;
|
||||
|
||||
//初始化栈和状态
|
||||
conf_stack_init(&stack);
|
||||
status=conf_stat_start;
|
||||
save_status=conf_stat_normal;
|
||||
end_status=conf_stat_done;
|
||||
|
||||
while(data[i])
|
||||
redo:
|
||||
while(POP)
|
||||
{
|
||||
switch(data[i])
|
||||
switch(POP)
|
||||
{
|
||||
case '=': //读取键,如果第一次读到=则表示键已读完
|
||||
if(!key)
|
||||
//注释
|
||||
case '#':
|
||||
if(save_status == conf_stat_normal)
|
||||
status=conf_stat_note;
|
||||
else if(save_status == conf_stat_key)
|
||||
{
|
||||
value=realloc(value,sizeof(CONF_VALUE)*(len+1));
|
||||
if(value == NULL)
|
||||
return CONF_NO_MEM;
|
||||
value[len].key=malloc(sizeof(char)*conf_stack_length(&stack)+1);
|
||||
if(value[len].key == NULL)
|
||||
return CONF_NO_MEM;
|
||||
snprintf(value[len].key,sizeof(char)*conf_stack_length(&stack)+1,"%s",stack.data);
|
||||
key=1;
|
||||
value[len].value=NULL;
|
||||
conf_stack_cleanup(&stack);
|
||||
retcode=CONF_VALUE_ERR;
|
||||
printf("行:%d ",line);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
break;
|
||||
//读取值
|
||||
case '=':
|
||||
if(save_status == conf_stat_key)
|
||||
status=conf_stat_key;
|
||||
else
|
||||
{
|
||||
if(conf_stack_push(&stack,data[i]) != 0) //否则则是值压入栈
|
||||
return STACK_MAX;
|
||||
}
|
||||
break;
|
||||
/* 如果第一次读到"则设置"标记
|
||||
* 如果不是第一次读入"则如果flags为1则表示读取完成
|
||||
* 如果为2则表示'标记则将"压入栈中
|
||||
*/
|
||||
case '"':
|
||||
if(!flags)
|
||||
flags=1;
|
||||
else if(flags == 1)
|
||||
flags=0;
|
||||
else if(flags == 2)
|
||||
{
|
||||
if(conf_stack_push(&stack,data[i]) != 0)
|
||||
return STACK_MAX;
|
||||
}
|
||||
break;
|
||||
case '\'': //上同
|
||||
if(!flags)
|
||||
flags=2;
|
||||
else if(flags == 2)
|
||||
flags=0;
|
||||
else if(flags == 1)
|
||||
{
|
||||
if(conf_stack_push(&stack,data[i]) != 0)
|
||||
return STACK_MAX;
|
||||
}
|
||||
break;
|
||||
/* 多参数标记
|
||||
* 如果已设置'/"标记则直接压入
|
||||
* 如果没有设置'/"标记且arg未设置则设置arg
|
||||
* 否则arg设置为0
|
||||
*/
|
||||
case ',':
|
||||
if(flags)
|
||||
conf_stack_push(&stack,data[i]);
|
||||
if(!flags && !arg)
|
||||
arg=1;
|
||||
if(arg) //存入多参数
|
||||
{
|
||||
value[len].value=realloc(value[len].value,sizeof(char *)*(count+1));
|
||||
if(value[len].value == NULL)
|
||||
return CONF_NO_MEM;
|
||||
value[len].value[count]=malloc(sizeof(char)*conf_stack_length(&stack)+1);
|
||||
if(value[len].value[count] == NULL)
|
||||
return CONF_NO_MEM;
|
||||
snprintf(value[len].value[count],sizeof(char)*conf_stack_length(&stack)+1,"%s",stack.data);
|
||||
++count;
|
||||
conf_stack_cleanup(&stack);
|
||||
arg=0;
|
||||
}
|
||||
break;
|
||||
case '#': //如果没有设置flags则是注释直接跳到下一行
|
||||
if(!flags)
|
||||
i+=next_line(data+i);
|
||||
else
|
||||
{
|
||||
if(conf_stack_push(&stack,data[i]) != 0)
|
||||
return STACK_MAX;
|
||||
retcode=CONF_KEY_ERR;
|
||||
printf("行:%d ",line);
|
||||
if(value)
|
||||
{
|
||||
if(value->key)
|
||||
free(value->key);
|
||||
free(value);
|
||||
}
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
break;
|
||||
//过滤空白符
|
||||
case ' ':
|
||||
case '\t':
|
||||
if(flags)
|
||||
if(end_status == conf_stat_done)
|
||||
while(isspace(POP))
|
||||
ADVANCE;
|
||||
goto redo;
|
||||
case '\n':
|
||||
if(save_status == conf_stat_normal)
|
||||
ADVANCE;
|
||||
else
|
||||
{
|
||||
if(conf_stack_push(&stack,data[i]) != 0)
|
||||
return STACK_MAX;
|
||||
retcode=CONF_KEY_ERR;
|
||||
printf("行:%d ",line);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
++line;
|
||||
goto redo;
|
||||
case '\0':
|
||||
if(status == conf_stat_normal)
|
||||
{
|
||||
retcode=CONF_KEY_ERR;
|
||||
printf("行:%d ",line);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
status=conf_stat_done;
|
||||
break;
|
||||
case '\n': //一行数据读完
|
||||
if(conf_stack_empty(&stack))
|
||||
break;
|
||||
value[len].value=realloc(value[len].value,sizeof(char *)*(count+2));
|
||||
if(value[len].value == NULL)
|
||||
return CONF_NO_MEM;
|
||||
value[len].value[count]=malloc(sizeof(char)*conf_stack_length(&stack)+1);
|
||||
if(value[len].value[count] == NULL)
|
||||
return CONF_NO_MEM;
|
||||
snprintf(value[len].value[count],sizeof(char)*conf_stack_length(&stack)+1,"%s",stack.data);
|
||||
value[len].value[count+1]=NULL;
|
||||
count=0;
|
||||
++len;
|
||||
key=0;
|
||||
conf_stack_cleanup(&stack);
|
||||
break;
|
||||
default: //插入字符
|
||||
if(conf_stack_push(&stack,data[i]) != 0)
|
||||
return STACK_MAX;
|
||||
default:
|
||||
if(save_status == conf_stat_key)
|
||||
{
|
||||
retcode=CONF_VALUE_ERR;
|
||||
printf("行:%d ",line);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
save_status=conf_stat_normal;
|
||||
status=conf_stat_start;
|
||||
}
|
||||
|
||||
++i;
|
||||
switch(status)
|
||||
{
|
||||
//过滤注释
|
||||
case conf_stat_note:
|
||||
data+=next_line(data);
|
||||
status=conf_stat_start;
|
||||
save_status=conf_stat_normal;
|
||||
++line;
|
||||
goto redo;
|
||||
//读取键
|
||||
case conf_stat_start:
|
||||
data+=_conf_read_key(data,LEN,&stack);
|
||||
value=malloc(sizeof(CONF_VALUE));
|
||||
if(value == NULL)
|
||||
{
|
||||
retcode=CONF_NO_MEM;
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
if(conf_stack_get_data(&stack) == NULL)
|
||||
{
|
||||
retcode=CONF_KEY_ERR;
|
||||
printf("行:%d ",line);
|
||||
free(value);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
value->key=strdup(conf_stack_get_data(&stack));
|
||||
if(value->key == NULL)
|
||||
{
|
||||
retcode=CONF_NO_MEM;
|
||||
free(value);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
conf_stack_cleanup(&stack);
|
||||
save_status=conf_stat_key;
|
||||
end_status=conf_stat_done;
|
||||
value->value=NULL;
|
||||
goto redo;
|
||||
//设置值状态
|
||||
case conf_stat_key:
|
||||
ADVANCE;
|
||||
while((POP == ' ') || (POP == '\t'))
|
||||
ADVANCE;
|
||||
save_status=conf_stat_key;
|
||||
end_status=_conf_value_status(POP);
|
||||
break;
|
||||
case conf_stat_done:
|
||||
retcode=CONF_OK;
|
||||
goto end;
|
||||
}
|
||||
|
||||
value_redo:
|
||||
switch(end_status)
|
||||
{
|
||||
//读取完成,保存数据到表中
|
||||
case conf_stat_done:
|
||||
if((save_status != conf_stat_key) ||
|
||||
(status == conf_stat_key))
|
||||
{
|
||||
retcode=CONF_VALUE_ERR;
|
||||
printf("行:%d ",line);
|
||||
conf_free(conf);
|
||||
goto end;
|
||||
}
|
||||
//重置状态
|
||||
status=conf_stat_start;
|
||||
save_status=conf_stat_normal;
|
||||
value_len=0;
|
||||
if(conf->len/conf->size >= HASH_SP)
|
||||
conf_hash_update(conf);
|
||||
if(conf_value_insert(conf->hash_data,value,conf->size))
|
||||
++conf->len;
|
||||
BACK;
|
||||
value=NULL;
|
||||
conf_stack_cleanup(&stack);
|
||||
break;
|
||||
//读取值
|
||||
case conf_stat_normal:
|
||||
data+=_conf_read_value(data,LEN,&stack);
|
||||
CONF_GET_VALUE(value->value,&stack);
|
||||
while(isspace(POP) && (POP != '\n'))
|
||||
ADVANCE;
|
||||
end_status=_conf_value_status(POP);
|
||||
status=conf_stat_start;
|
||||
goto value_redo;
|
||||
//读取带有引号的值
|
||||
case conf_stat_quote:
|
||||
quote=POP;
|
||||
ADVANCE;
|
||||
data+=_conf_read_value_with_quote(data,LEN,&stack,quote);
|
||||
CONF_GET_VALUE(value->value,&stack);
|
||||
ADVANCE;
|
||||
end_status=_conf_value_status(POP);
|
||||
goto value_redo;
|
||||
//多参数值读取
|
||||
case conf_stat_more_key:
|
||||
conf_stack_cleanup(&stack);
|
||||
ADVANCE;
|
||||
end_status=_conf_value_status(POP);
|
||||
goto value_redo;
|
||||
}
|
||||
|
||||
ADVANCE;
|
||||
}
|
||||
|
||||
*res=value;
|
||||
conf->len=len;
|
||||
|
||||
return 0;
|
||||
end:
|
||||
conf_stack_destroy(&stack);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
int next_line(char *data)
|
||||
{
|
||||
int i=0;
|
||||
|
||||
while(data[i] != '\n')
|
||||
while((data[i] != '\n') && (data[i] != '\0'))
|
||||
++i;
|
||||
|
||||
return i-1;
|
||||
return i+1;
|
||||
}
|
||||
|
||||
void free_data(CONF_ARG *data)
|
||||
|
|
@ -438,7 +534,7 @@ char **conf_key_list(CONF *conf)
|
|||
key[conf->len]=NULL;
|
||||
|
||||
//读取出所有键
|
||||
for(i=0;i < conf->len;++i)
|
||||
for(i=0;i < conf->size;++i)
|
||||
if(conf->hash_data[i].len > 0)
|
||||
get_key(key,&index,&conf->hash_data[i]);
|
||||
|
||||
|
|
@ -464,12 +560,12 @@ CONF_VALUE *conf_value_get(CONF *conf,const char *key)
|
|||
/* 首先使用第一个哈希函数计算出哈希值,如果找到匹配的key则返回
|
||||
* 否则使用第二个哈希函数计算值并比对key
|
||||
* 如果都未能匹配,则返回NULL */
|
||||
hash=conf_hash_func1(key)%conf->len;
|
||||
hash=conf_hash_func1(key)%conf->size;
|
||||
if(conf_hash_search(&conf->hash_data[hash],key,&value))
|
||||
return value;
|
||||
else
|
||||
{
|
||||
hash=conf_hash_func2(key)%conf->len;
|
||||
hash=conf_hash_func2(key)%conf->size;
|
||||
if(conf_hash_search(&conf->hash_data[hash],key,&value))
|
||||
return value;
|
||||
}
|
||||
|
|
@ -488,7 +584,7 @@ CONF_VALUE **conf_value_get_all(CONF *conf)
|
|||
return NULL;
|
||||
|
||||
//第一次扫描数组中所有存在数据的地方并计算长度
|
||||
for(i=0;i != conf->len;++i)
|
||||
for(i=0;i != conf->size;++i)
|
||||
if(conf->hash_data[i].len > 0)
|
||||
++len;
|
||||
|
||||
|
|
@ -498,7 +594,7 @@ CONF_VALUE **conf_value_get_all(CONF *conf)
|
|||
return NULL;
|
||||
value[len]=NULL;
|
||||
//第二次扫描,返回数组中所有有数据的值
|
||||
for(i=0;i != conf->len;++i)
|
||||
for(i=0;i != conf->size;++i)
|
||||
{
|
||||
if(conf->hash_data[i].len > 0)
|
||||
{
|
||||
|
|
@ -509,3 +605,112 @@ CONF_VALUE **conf_value_get_all(CONF *conf)
|
|||
|
||||
return value;
|
||||
}
|
||||
|
||||
int _conf_read_key(char *data,int len,STACK *stack)
|
||||
{
|
||||
int i=0;
|
||||
char c;
|
||||
|
||||
while(isspace(POP) && (POP != '\n'))
|
||||
{
|
||||
ADVANCE;
|
||||
++i;
|
||||
}
|
||||
if(POP == '\n')
|
||||
{
|
||||
printf("行:%d 列:%d 解析出错\n",line,i-len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
c=POP;
|
||||
if(isspace(c) || c == '=' || c == '#')
|
||||
break;
|
||||
conf_stack_push(stack,c);
|
||||
++i;
|
||||
ADVANCE;
|
||||
}while(c);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
enum conf_stat _conf_value_status(char c)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case ',':
|
||||
return conf_stat_more_key;
|
||||
case '\'':
|
||||
case '"':
|
||||
return conf_stat_quote;
|
||||
case '#':
|
||||
case '\n':
|
||||
return conf_stat_done;
|
||||
default:
|
||||
return conf_stat_normal;
|
||||
}
|
||||
}
|
||||
|
||||
int _conf_read_value(char *data,int len,STACK *stack)
|
||||
{
|
||||
char c;
|
||||
int i=0;
|
||||
|
||||
while(isspace(POP) && (POP != '\n'))
|
||||
{
|
||||
ADVANCE;
|
||||
++i;
|
||||
}
|
||||
if(POP == '\n')
|
||||
{
|
||||
printf("行:%d 列:%d 解析出错!\n",line,i-len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
c=POP;
|
||||
if(isspace(c) || c == ',' || c == '#')
|
||||
break;
|
||||
|
||||
conf_stack_push(stack,c);
|
||||
++i;
|
||||
ADVANCE;
|
||||
}while(c);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int _conf_read_value_with_quote(char *data,int len,STACK *stack,char quote)
|
||||
{
|
||||
char c;
|
||||
int i=0;
|
||||
|
||||
while(isspace(POP) && (POP != '\n'))
|
||||
{
|
||||
ADVANCE;
|
||||
++i;
|
||||
}
|
||||
if(POP == '\n')
|
||||
{
|
||||
printf("行:%d 列:%d 解析出错!\n",line,i-len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
c=POP;
|
||||
if(c == quote)
|
||||
break;
|
||||
|
||||
conf_stack_push(stack,c);
|
||||
++i;
|
||||
ADVANCE;
|
||||
}while(c);
|
||||
|
||||
if(c == '\0')
|
||||
return 0;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue