/*-
* Copyright (c) 1998 fjoe <fjoe@iclub.nsu.ru>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: mlstring.c,v 1.34 1999/04/16 15:52:20 fjoe Exp $
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "merc.h"
#include "db/db.h"
#include "db/lang.h"
/*
* multi-language string implementation
*/
/*
* multi-language string
* if nlang == 0 the value is stored in u.str
* otherwise the value is stored in array of strings u.lstr
* the size of array is equal to 'nlang'
* 'ref' = number of references (COW semantics)
*/
struct mlstring {
union {
const char* str;
const char** lstr;
} u;
int nlang;
int ref;
};
static const char* smash_a(const char *s, int len);
static char* fix_mlstring(const char* s);
static mlstring *mlstr_split(mlstring *ml);
int mlstr_count;
int mlstr_real_count;
mlstring mlstr_empty;
mlstring *mlstr_new(const char *mval)
{
mlstring *res = malloc(sizeof(*res));
res->u.str = str_dup(mval);
res->nlang = 0;
res->ref = 1;
mlstr_real_count++;
return res;
}
mlstring *mlstr_fread(FILE *fp)
{
const char *p;
const char *s;
int lang;
mlstring *res;
p = fread_string(fp);
if (IS_NULLSTR(p))
return &mlstr_empty;
mlstr_count++;
res = mlstr_new(NULL);
if (*p != '@' || *(p+1) == '@') {
res->u.str = smash_a(p, -1);
free_string(p);
return res;
}
res->u.lstr = calloc(1, sizeof(char*) * langs.nused);
res->nlang = langs.nused;
s = p+1;
for (;;) {
const char *q;
/* s points at lang id */
q = strchr(s, ' ');
if (q == NULL) {
db_error("mlstr_fread", "no ` ' after `@' found");
return res;
}
if ((lang = lang_nlookup(s, q-s)) < 0) {
db_error("mlstr_fread", "%s: unknown language", s);
return res;
}
if (res->u.lstr[lang] != NULL) {
db_error("mlstr_fread", "lang %s: redefined", s);
return res;
}
/* find next '@', skip "@@" */
for (s = ++q; (s = strchr(s, '@'));) {
if (*(s+1) != '@')
break;
s += 2;
}
if (s == NULL)
s = strchr(q, '\0');
res->u.lstr[lang] = smash_a(q, s-q);
if (!*s++)
break;
}
free_string(p);
return res;
}
void mlstr_fwrite(FILE *fp, const char* name, const mlstring *ml)
{
int lang;
if (name)
fprintf(fp, "%s ", name);
if (!ml) {
fprintf(fp, "~\n");
return;
}
if (ml->nlang == 0) {
fprintf(fp, "%s~\n", fix_mlstring(ml->u.str));
return;
}
for (lang = 0; lang < ml->nlang && lang < langs.nused; lang++) {
const char* p = ml->u.lstr[lang];
lang_t *l;
if (IS_NULLSTR(p))
continue;
l = VARR_GET(&langs, lang);
fprintf(fp, "@%s %s", l->name, fix_mlstring(p));
}
fputs("~\n", fp);
}
void mlstr_free(mlstring *ml)
{
if (ml == NULL || ml == &mlstr_empty)
return;
mlstr_count--;
if (ml->ref < 1 || --ml->ref)
return;
if (ml->nlang == 0)
free_string(ml->u.str);
else {
int lang;
for (lang = 0; lang < ml->nlang; lang++)
free_string(ml->u.lstr[lang]);
free(ml->u.lstr);
}
free(ml);
mlstr_real_count--;
}
mlstring *mlstr_dup(mlstring *ml)
{
if (ml == NULL)
return NULL;
mlstr_count++;
if (ml != &mlstr_empty)
ml->ref++;
return ml;
}
mlstring *mlstr_printf(mlstring *ml,...)
{
char buf[MAX_STRING_LENGTH];
va_list ap;
mlstring *res;
if (ml == NULL)
return NULL;
mlstr_count++;
res = mlstr_new(NULL);
res->nlang = ml->nlang;
va_start(ap, ml);
if (ml->nlang == 0) {
vsnprintf(buf, sizeof(buf), ml->u.str, ap);
res->u.str = str_dup(buf);
}
else {
int lang;
res->u.lstr = calloc(1, sizeof(char*) * res->nlang);
for (lang = 0; lang < ml->nlang; lang++) {
if (IS_NULLSTR(ml->u.lstr[lang]))
continue;
vsnprintf(buf, sizeof(buf), ml->u.lstr[lang], ap);
res->u.lstr[lang] = str_dup(buf);
}
}
va_end(ap);
return res;
}
int mlstr_nlang(const mlstring *ml)
{
if (ml == NULL)
return 0;
return ml->nlang;
}
const char * mlstr_val(const mlstring *ml, int lang)
{
const char *p;
if (ml == NULL)
return str_empty;
if (ml->nlang == 0) {
p = ml->u.str;
return (p ? p : str_empty);
}
if (lang >= ml->nlang
|| lang < 0
|| IS_NULLSTR(ml->u.lstr[lang])) {
lang_t *l;
if ((l = varr_get(&langs, lang))
&& l->slang_of >= 0
&& l->slang_of < ml->nlang)
lang = l->slang_of;
else
return str_empty;
}
p = ml->u.lstr[lang];
return (p ? p : str_empty);
}
bool mlstr_null(const mlstring *ml)
{
const char *mval = mlstr_mval(ml);
return (mval == NULL) || (*mval == '\0');
}
int mlstr_cmp(const mlstring *ml1, const mlstring *ml2)
{
int lang;
int res;
if (ml1 == NULL)
if (ml2 == NULL)
return 0;
else
return 1;
else if (ml2 == NULL)
return -1;
if (ml1->nlang != ml2->nlang)
return ml1->nlang - ml2->nlang;
if (ml1->nlang == 0)
return str_cmp(ml1->u.str, ml2->u.str);
for (lang = 0; lang < ml1->nlang; lang++) {
res = str_cmp(ml1->u.lstr[lang], ml2->u.lstr[lang]);
if (res)
return res;
}
return 0;
}
const char** mlstr_convert(mlstring **mlp, int newlang)
{
const char *old;
int lang;
*mlp = mlstr_split(*mlp);
if (newlang < 0) {
/* convert to language-independent */
if ((*mlp)->nlang) {
old = (*mlp)->u.lstr[0];
for (lang = 1; lang < (*mlp)->nlang; lang++)
free_string((*mlp)->u.lstr[lang]);
free((*mlp)->u.lstr);
(*mlp)->nlang = 0;
(*mlp)->u.str = old;
}
return &((*mlp)->u.str);
}
/* convert to language-dependent */
if ((*mlp)->nlang == 0) {
old = (*mlp)->u.str;
(*mlp)->nlang = langs.nused;
(*mlp)->u.lstr = calloc(1, sizeof(char*) * langs.nused);
(*mlp)->u.lstr[0] = old;
}
return ((*mlp)->u.lstr)+newlang;
}
bool mlstr_append(CHAR_DATA *ch, mlstring **mlp, const char *arg)
{
int lang;
lang = lang_lookup(arg);
if (lang < 0 && str_cmp(arg, "all"))
return FALSE;
string_append(ch, mlstr_convert(mlp, lang));
return TRUE;
}
void mlstr_for_each(mlstring **mlp, void *arg,
void (*cb)(int lang, const char **p, void *arg))
{
int lang;
if (*mlp == NULL)
return;
*mlp = mlstr_split(*mlp);
if ((*mlp)->nlang == 0) {
cb(0, &((*mlp)->u.str), arg);
return;
}
for (lang = 0; lang < (*mlp)->nlang; lang++)
cb(lang, (*mlp)->u.lstr + lang, arg);
}
bool mlstr_edit(mlstring **mlp, const char *argument)
{
char arg[MAX_STRING_LENGTH];
int lang;
const char **p;
argument = one_argument(argument, arg, sizeof(arg));
lang = lang_lookup(arg);
if (lang < 0 && str_cmp(arg, "all"))
return FALSE;
p = mlstr_convert(mlp, lang);
free_string(*p);
*p = str_dup(argument);
return TRUE;
}
/*
* The same as mlstr_edit, but '\n' is appended.
*/
bool mlstr_editnl(mlstring **mlp, const char *argument)
{
char arg[MAX_STRING_LENGTH];
int lang;
const char **p;
argument = one_argument(argument, arg, sizeof(arg));
lang = lang_lookup(arg);
if (lang < 0 && str_cmp(arg, "all"))
return FALSE;
p = mlstr_convert(mlp, lang);
free_string(*p);
*p = str_printf("%s\n", argument);
return TRUE;
}
void mlstr_dump(BUFFER *buf, const char *name, const mlstring *ml)
{
char space[MAX_STRING_LENGTH];
size_t namelen;
int lang;
static char FORMAT[] = "%s[%s] [%s]\n";
lang_t *l;
if (ml == NULL || ml->nlang == 0) {
buf_printf(buf, FORMAT, name, "all",
ml == NULL ? "(null)" : ml->u.str);
return;
}
if (langs.nused == 0)
return;
l = VARR_GET(&langs, 0);
buf_printf(buf, FORMAT, name, l->name, ml->u.lstr[0]);
if (langs.nused < 1)
return;
namelen = strlen(name);
namelen = URANGE(0, namelen, sizeof(space)-1);
memset(space, ' ', namelen);
space[namelen] = '\0';
for (lang = 1; lang < ml->nlang && lang < langs.nused; lang++) {
l = VARR_GET(&langs, lang);
buf_printf(buf, FORMAT,
space, l->name, ml->u.lstr[lang]);
}
}
static const char *smash_a(const char *s, int len)
{
char buf[MAX_STRING_LENGTH];
char *p = buf;
if (len < 0 || len > sizeof(buf)-1)
len = sizeof(buf)-1;
while (p-buf < len && *s) {
if (*s == '@' && *(s+1) == '@')
s++;
*p++ = *s++;
}
*p = '\0';
return str_dup(buf);
}
static char *fix_mlstring(const char *s)
{
char *p;
static char buf[MAX_STRING_LENGTH*2];
buf[0] = '\0';
if (s == NULL)
return buf;
s = fix_string(s);
while((p = strchr(s, '@')) != NULL) {
*p = '\0';
strnzcat(buf, sizeof(buf), s);
strnzcat(buf, sizeof(buf), "@@");
s = p+1;
}
strnzcat(buf, sizeof(buf), s);
return buf;
}
static mlstring *mlstr_split(mlstring *ml)
{
int lang;
mlstring *res;
if (ml != NULL && ml != &mlstr_empty && ml->ref < 2)
return ml;
res = mlstr_new(NULL);
if (ml == NULL || ml == &mlstr_empty)
return res;
res->nlang = ml->nlang;
ml->ref--;
if (ml->nlang == 0) {
res->u.str = str_qdup(ml->u.str);
return res;
}
res->u.lstr = malloc(sizeof(char*) * res->nlang);
for (lang = 0; lang < res->nlang; lang++)
res->u.lstr[lang] = str_qdup(ml->u.lstr[lang]);
return res;
}