/* $Id: mlstring.c,v 1.666 2004/09/20 10:49:51 shrike Exp $ */
/************************************************************************************
* Copyright 2004 Astrum Metaphora consortium *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* *
************************************************************************************/
/************************************************************************************
* ANATOLIA 2.1 is copyright 1996-1997 Serdar BULUT, Ibrahim CANPUNAR *
* ANATOLIA has been brought to you by ANATOLIA consortium *
* Serdar BULUT {Chronos} bulut@rorqual.cc.metu.edu.tr *
* Ibrahim Canpunar {Asena} canpunar@rorqual.cc.metu.edu.tr *
* Murat BICER {KIO} mbicer@rorqual.cc.metu.edu.tr *
* D.Baris ACAR {Powerman} dbacar@rorqual.cc.metu.edu.tr *
* By using this code, you have agreed to follow the terms of the *
* ANATOLIA license, in the file Anatolia/anatolia.licence *
***********************************************************************************/
/************************************************************************************
* Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, *
* Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. *
* *
* Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael *
* Chastain, Michael Quan, and Mitchell Tse. *
* *
* In order to use any part of this Merc Diku Mud, you must comply with *
* both the original Diku license in 'license.doc' as well the Merc *
* license in 'license.txt'. In particular, you may not remove either of *
* these copyright notices. *
* *
* Much time and thought has gone into this software and you are *
* benefitting. We hope that you share your changes too. What goes *
* around, comes around. *
************************************************************************************/
/************************************************************************************
* ROM 2.4 is copyright 1993-1995 Russ Taylor *
* ROM has been brought to you by the ROM consortium *
* Russ Taylor (rtaylor@pacinfo.com) *
* Gabrielle Taylor (gtaylor@pacinfo.com) *
* Brian Moore (rom@rom.efn.org) *
* By using this code, you have agreed to follow the terms of the *
* ROM license, in the file Rom24/doc/rom.license *
*************************************************************************************/
/************************************************************************************
* 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. *
************************************************************************************/
#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"
#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++;
mlstr_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[0] == '^' ? p + 1 : 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[0] == '^' ? p + 1 : 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;
for (lang = 1 ; lang < langs.nused ; lang++) {
(*mlp)->u.lstr [lang] = str_dup (old);
}
//if (newlang > 0) (*mlp)->u.lstr[newlang] = str_dup (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;
}
/* new functions */
mlstring *mlstr_addstr(mlstring *ml, const char *s)
{
mlstring *res;
int lang;
char tmp[MAX_STRING_LENGTH];
if (s == NULL) {
res = mlstr_dup(ml);
return res;
}
res = mlstr_new(NULL);
if (ml->nlang == 0) {
strnzcpy(tmp, sizeof(tmp), ml->u.str);
strnzcat(tmp, sizeof(tmp), s);
res->u.str = str_dup(tmp);
}
else {
res->u.lstr = malloc(sizeof(char*) * ml->nlang);
for (lang = 0; lang < ml->nlang; lang++)
if (ml->u.lstr[lang] != NULL) {
strnzcpy(tmp, sizeof(tmp), ml->u.lstr[lang]);
strnzcat(tmp, sizeof(tmp), s);
res->u.lstr[lang] = str_dup(tmp);
}
else
res->u.lstr[lang] = str_empty;
}
res->nlang = ml->nlang;
return res;
}
mlstring *mlstr_addmlstr(mlstring *ml1, mlstring *ml2)
{
mlstring *res;
int lang;
char tmp[MAX_STRING_LENGTH];
if (mlstr_null(ml1)) {
if (mlstr_null(ml2))
return &mlstr_empty;
else
return mlstr_dup(ml2);
}
if (mlstr_null(ml2))
return mlstr_dup(ml1);
if (ml2->nlang == 0)
return mlstr_addstr(ml1, ml2->u.str);
if (ml1->nlang == 0) {
res = mlstr_new(NULL);
res->nlang = ml2->nlang;
res->u.lstr = malloc(sizeof(char*) * ml2->nlang);
for (lang = 0; lang < ml2->nlang; lang++)
if (ml2->u.lstr[lang] != NULL && ml1->u.str != NULL) {
strnzcpy(tmp, sizeof(tmp), ml1->u.str);
strnzcat(tmp, sizeof(tmp), ml2->u.lstr[lang]);
res->u.lstr[lang] = str_dup(tmp);
}
else
res->u.lstr[lang] = str_empty;
return res;
}
if (ml1->nlang != ml2->nlang)
return mlstr_addstr(ml1, mlstr_val(ml2, 0));
res = mlstr_new(NULL);
res->nlang = ml1->nlang;
res->u.lstr = malloc(sizeof(char*) * ml1->nlang);
for (lang = 0; lang < ml1->nlang; lang++)
if (ml1->u.lstr[lang] == NULL) {
if (ml2->u.lstr[lang] == NULL)
res->u.lstr[lang] = str_empty;
else res->u.lstr[lang] = str_dup(ml2->u.lstr[lang]);
}
else if (ml2->u.lstr[lang] == NULL)
res->u.lstr[lang] = str_dup(ml1->u.lstr[lang]);
else {
strnzcpy(tmp, sizeof(tmp), ml1->u.lstr[lang]);
strnzcat(tmp, sizeof(tmp), ml2->u.lstr[lang]);
res->u.lstr[lang] = str_dup(tmp);
}
return res;
}
#ifndef XORADEAD
const char * mlstr_val_true(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);
}
#endif