blob: 475f3c2d1aa6af5084033b4a9bf7b237ed91a5e6 [file] [log] [blame]
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include "tagz.h"
#ifdef TAGZ_UNICODE
#define _TX(X) L##X
#define t_strdup wcsdup
#define t_strlen wcslen
#define t_strnicmp wcsnicmp
#define t_atoi(x) wcstol(x,0,10)
#define t_stricmp wcsicmp
#define t_strstr wcsstr
#define sprintf swprintf
#else
#define _TX(X) X
#define t_strdup strdup
#define t_strlen strlen
#define t_strnicmp strnicmp
#define t_atoi atoi
#define t_stricmp stricmp
#define t_strstr strstr
#endif
#define TABSIZE(x) (sizeof(x)/sizeof(x[0]))
class T_String
{
private:
T_CHAR * data;
UINT size,used;
public:
T_String() {data=0;size=0;used=0;}
void AddChar(T_CHAR c)
{
if (!data)
{
data=(T_CHAR*)malloc((size=512)*sizeof(T_CHAR));
used=0;
}
else if (size==used)
{
size<<=1;
data=(T_CHAR*)realloc((char*)data,size*sizeof(T_CHAR));
}
if (data) data[used++]=c;
}
void AddInt(int i)
{
T_CHAR foo[16];
sprintf(foo,_TX("%i"),i);
AddString(foo);
}
void AddString(const T_CHAR * z)
{
while(*z) {AddChar(*z);z++;}
}
void AddString(T_String & s)
{
AddString(s.Peek());
}
~T_String()
{
if (data) free(data);
}
T_CHAR * GetBuf()
{
if (!data) return ::t_strdup(_TX(""));
T_CHAR * r=(T_CHAR*)realloc(data,(used+1)*sizeof(T_CHAR));
r[used]=0;
data=0;
return r;
}
T_CHAR operator[](UINT i)
{
if (!data || i>=used) return 0;
else return data[i];
}
UINT Len() {return data ? used : 0;}
void Reset()
{
if (data) {free(data);data=0;}
}
const T_CHAR * Peek()
{
AddChar(0);
used--;
return data;
}
T_CHAR * strdup()
{
return ::t_strdup(Peek());
}
};
static int separator(T_CHAR x)
{
if (!x || x==' ') return 1;
if (x=='\'' || x=='_') return 0;
#ifdef TAGZ_UNICODE
return !iswalnum(x);
#else
return !isalnum(x);
#endif
}
static int sepcmp(T_CHAR* src,T_CHAR* val)
{
UINT l=t_strlen(val);
return !t_strnicmp(src,val,l) && separator(src[l]);
}
static char roman_num[]=
{
'I','V','X','L','C','D','M'
};
static int is_roman(T_CHAR * ptr)/* could be more smart i think */
{
if (ptr[0]==']' && ptr[1]=='[' && separator(ptr[2])) return 1;
while(!separator(*ptr))
{
UINT n;
bool found=0;
for(n=0;n<TABSIZE(roman_num);n++)
{
if (*ptr==roman_num[n]) {found=1;break;}
}
if (!found) return 0;
ptr++;
}
return 1;
}
static int need_full(T_CHAR* ptr)
{
if (is_roman(ptr)) return 1;
if (sepcmp(ptr,_TX("RPG"))) return 1;
while(!separator(*ptr))
{
if (*ptr<'0' || *ptr>'9') return 0;
ptr++;
}
return 1;
}
typedef bool (*TEXTFUNC)(UINT n_src,T_CHAR **src,UINT*,T_String &out);
#define MAKEFUNC(X) static bool X(UINT n_src,T_CHAR ** src,UINT *found_src,T_String &out)
MAKEFUNC(If)
{
if (n_src!=3) return false;
out.AddString(src[found_src[0] ? 1 : 2]);
return true;
}
MAKEFUNC(If2)
{
if (n_src!=2) return false;
out.AddString(src[found_src[0] ? 0 : 1]);
return true;
}
MAKEFUNC(Iflonger)
{
if (n_src!=4) return false;
out.AddString(src[(int)t_strlen(src[0])>t_atoi(src[1]) ? 2 : 3]);
return true;
}
MAKEFUNC(Ifgreater)
{
if (n_src!=4) return false;
out.AddString(src[t_atoi(src[0])>t_atoi(src[1]) ? 2 : 3]);
return true;
}
MAKEFUNC(Upper)
{
if (n_src!=1) return false;
T_CHAR * s=src[0];
while(*s)
out.AddChar(toupper(*(s++)));
return true;
}
MAKEFUNC(Lower)
{
if (n_src!=1) return false;
T_CHAR * s=src[0];
while(*s)
out.AddChar(tolower(*(s++)));
return true;
}
MAKEFUNC(Pad)
{
if (n_src<2 || n_src>3) return false;
T_CHAR *fill=_TX(" ");
if (n_src==3 && src[2][0])
fill = src[2];
int num = t_atoi(src[1]);
T_CHAR *p = src[0];
while (*p) { out.AddChar(*(p++)); num--; }
UINT fl = t_strlen(fill);
while (num>0)
out.AddChar(fill[(--num)%fl]);
return true;
}
MAKEFUNC(Cut)
{
if (n_src!=2) return false;
UINT num = t_atoi(src[1]);
T_CHAR *p = src[0];
while (*p && num>0) {out.AddChar(*(p++));num--;}
return true;
}
MAKEFUNC(PadCut)
{
if (n_src<2 || n_src>3) return false;
T_CHAR *fill = _TX(" ");
if (n_src==3 && src[2][0])
fill = src[2];
int num = t_atoi(src[1]);
T_CHAR *p = src[0];
while(*p && num>0) {out.AddChar(*(p++));num--;}
UINT fl=t_strlen(fill);
while (num>0)
out.AddChar(fill[(--num)%fl]);
return true;
}
/* abbr(string) */
/* abbr(string,len) */
MAKEFUNC(Abbr)
{
if (n_src==0 || n_src>2) return false;
if (n_src==2 && (int)t_strlen(src[0])<t_atoi(src[1]))
{
out.AddString(src[0]);
return true;
}
T_CHAR * meta=src[0];
bool w=0, r=0;
while(*meta)
{
bool an=!separator(*meta) || *meta==']' || *meta=='[';
if (w && !an)
w=0;
else if (!w && an)
{
w=1;
r=need_full(meta)?1:0;
out.AddChar(*meta);
}
else if (w && r)
out.AddChar(*meta);
meta++;
}
return true;
}
MAKEFUNC(Caps)
{
if (n_src!=1) return false;
T_CHAR* sp=src[0];
int sep = 1;
while(*sp)
{
T_CHAR c=*(sp++);
int s = separator(c);
if (!s && sep)
c=toupper(c);
else if (!sep) c=tolower(c);
sep=s;
out.AddChar(c);
}
return true;
}
MAKEFUNC(Caps2)
{
if (n_src!=1) return false;
T_CHAR* sp=src[0];
int sep=1;
while(*sp)
{
T_CHAR c=*(sp++);
int s = separator(c);
if (!s && sep)
c=toupper(c);
sep=s;
out.AddChar(c);
}
return true;
}
MAKEFUNC(Longest)
{
T_CHAR *ptr=0;
UINT n, m=0;
for(n=0;n<n_src;n++)
{
UINT l=t_strlen(src[n]);
if (l>m) {m=l;ptr=src[n];}
}
if (ptr) out.AddString(ptr);
return true;
}
MAKEFUNC(Shortest)
{
T_CHAR * ptr=0;
UINT n,m=(UINT)(-1);
for(n=0;n<n_src;n++)
{
UINT l=t_strlen(src[n]);
if (l<m) {m=l;ptr=src[n];}
}
if (ptr) out.AddString(ptr);
return true;
}
MAKEFUNC(Num)
{
if (n_src!=2) return false;
T_CHAR tmp[16];
T_CHAR tmp1[16];
sprintf(tmp1,_TX("%%0%uu"),t_atoi(src[1]));
sprintf(tmp,tmp1,t_atoi(src[0]));
out.AddString(tmp);
return true;
}
MAKEFUNC(Hex)
{
if (n_src!=2) return false;
T_CHAR tmp[16];
T_CHAR tmp1[16];
sprintf(tmp1,_TX("%%0%ux"),t_atoi(src[1]));
sprintf(tmp,tmp1,t_atoi(src[0]));
out.AddString(tmp);
return true;
}
MAKEFUNC(StrChr)
{
if (n_src!=2) return false;
T_CHAR * p=src[0];
T_CHAR s=src[1][0];
while (*p && *p!=s) p++;
if (*p==s)
out.AddInt(1+p-src[0]);
else out.AddChar('0');
return true;
}
MAKEFUNC(StrRChr)
{
if (n_src!=2) return false;
T_CHAR * p=src[0],*p1=0;
T_CHAR s=src[1][0];
while(*p)
{
if (*p==s) p1=p;
p++;
}
if (p1)
out.AddInt(1+p1-src[0]);
else out.AddChar('0');
return true;
}
MAKEFUNC(StrStr)
{
if (n_src!=2) return false;
T_CHAR * p = t_strstr(src[0],src[1]);
if (p)
out.AddInt(1+p-src[0]);
else out.AddChar('0');
return true;
}
/* substr(string, index) */
/* substr(string, index, length) */
MAKEFUNC(SubStr)
{
if (n_src<2 || n_src>3) return false;
int n1 = t_atoi(src[1]), n2;
if (n_src == 3)
n2 = t_atoi(src[2]);
else n2 = n1;
if (n1 < 1) n1=1;
if (n2 >= n1)
{
n1--;
n2--;
while(n1<=n2 && src[0][n1])
out.AddChar(src[0][n1++]);
}
return true;
}
MAKEFUNC(Len)
{
if (n_src!=1) return false;
out.AddInt(t_strlen(src[0]));
return true;
}
MAKEFUNC(Add)
{
UINT n;
int s=0;
for (n=0;n<n_src;n++)
s+=t_atoi(src[n]);
out.AddInt(s);
return true;
}
MAKEFUNC(Sub)
{
if (n_src==0) return false;
UINT n;
int s=t_atoi(src[0]);
for (n=1;n<n_src;n++)
s-=t_atoi(src[n]);
out.AddInt(s);
return true;
}
MAKEFUNC(Mul)
{
UINT n;
int s=1;
for(n=0;n<n_src;n++)
s*=t_atoi(src[n]);
out.AddInt(s);
return true;
}
MAKEFUNC(Div)
{
if (n_src==0) return false;
UINT n;
int s=t_atoi(src[0]);
for(n=1;n<n_src;n++)
{
int t=t_atoi(src[n]);
if (t) s/=t;
else t=0;
}
out.AddInt(s);
return true;
}
MAKEFUNC(Mod)
{
if (n_src==0) return false;
UINT n;
int s=t_atoi(src[0]);
for(n=1;n<n_src;n++)
{
int t=t_atoi(src[n]);
if (t) s%=t;
else t=0;
}
out.AddInt(s);
return true;
}
MAKEFUNC(Max)
{
if (!n_src) return false;
int m = t_atoi(src[0]);
UINT n;
for (n=1; n<n_src; n++)
{
int t = t_atoi(src[n]);
if (t > m) m = t;
}
out.AddInt(m);
return true;
}
MAKEFUNC(Min)
{
if (!n_src) return false;
int m=t_atoi(src[0]);
UINT n;
for(n=1;n<n_src;n++)
{
int t=t_atoi(src[n]);
if (t<m) m=t;
}
out.AddInt(m);
return true;
}
/* replace(string, what_to_replace, replacement) */
MAKEFUNC(Replace)
{
if (n_src!=3) return false;
T_CHAR *p = src[0];
while (*p)
{
UINT n=0;
while (src[1][n] && p[n]==src[1][n]) n++;
if (!src[1][n])
{
out.AddString(src[2]);
p += n;
}
else out.AddChar(*p++);
}
return true;
}
struct
{
TEXTFUNC func;
const T_CHAR * name;
}
FUNCS[] =
{
If,_TX("if"),
If2,_TX("if2"),
Upper,_TX("upper"),
Lower,_TX("lower"),
Pad,_TX("pad"),
Cut,_TX("cut"),
PadCut,_TX("padcut"),
Abbr,_TX("abbr"),
Caps,_TX("caps"),
Caps2,_TX("caps2"),
Longest,_TX("longest"),
Shortest,_TX("shortest"),
Iflonger,_TX("iflonger"),
Ifgreater,_TX("ifgreater"),
Num,_TX("num"),Num,_TX("dec"),
Hex,_TX("hex"),
StrChr,_TX("strchr"),
StrChr,_TX("strlchr"),
StrRChr,_TX("strrchr"),
StrStr,_TX("strstr"),
SubStr,_TX("substr"),
Len,_TX("len"),
Add,_TX("add"),
Sub,_TX("sub"),
Mul,_TX("mul"),
Div,_TX("div"),
Mod,_TX("mod"),
Min,_TX("min"),
Max,_TX("max"),
Replace,_TX("replace"),
};
class FMT
{
private:
T_String str;
T_CHAR * spec;
TAGFUNC f;
TAGFREEFUNC ff;
void * fp;
T_CHAR * org_spec;
int found;
void Error(T_CHAR *e=0)
{
str.Reset();
str.AddString(e ? e : _TX("[SYNTAX ERROR IN FORMATTING STRING]"));
found++; /* force displaying */
}
T_CHAR * _FMT(T_CHAR * s,UINT *f=0)
{
FMT fmt(this,s);
T_CHAR * c=(T_CHAR*)fmt;
if (f) *f=fmt.found;
found+=fmt.found;
return c;
}
static bool skipshit(T_CHAR** _p,T_CHAR *bl)
{
T_CHAR * p=*_p;
int bc1=0,bc2=0;
while(*p)
{
if (!bc1 && !bc2 && bl)
{
T_CHAR *z=bl;
while(*z)
{
if (*z==*p) break;
z++;
}
if (*z) break;
}
if (*p=='\'')
{
p++;
while(*p && *p!='\'') p++;
if (!*p) return 0;
}
else if (*p=='(') bc1++;
else if (*p==')')
{
if (--bc1<0) return 0;
}
else if (*p=='[') bc2++;
else if (*p==']')
{
if (--bc2<0) return 0;
}
p++;
}
*_p=p;
return *p && !bc1 && !bc2;
}
void run()
{
if (!spec) {Error();return;}
while(*spec)
{
if (*spec=='%')
{
spec++;
if (*spec=='%') {str.AddChar('%');spec++;continue;}
T_CHAR* s1=spec+1;
while(*s1 && *s1!='%') s1++;
if (!*s1) {Error();break;}
*s1=0;
T_CHAR * tag=f(spec,fp);
*s1='%';
/*if (!tag) tag=tag_unknown; */
if (tag && tag[0])
{
found++;
str.AddString(tag);
}
else
{
str.AddString(_TX("?"));
}
if (tag && ff) ff(tag,fp);
spec=s1+1;
}
else if (*spec=='$')
{
spec++;
if (*spec=='$') {str.AddChar('$');spec++;continue;}
T_CHAR * s1=spec+1;
while(*s1 && *s1!='(') s1++;
if (!*s1) {Error();break;}
T_CHAR * s2=s1+1;
if (!skipshit(&s2,_TX(")"))) {Error();break;}
if (!*s2) {Error();break;};
T_CHAR * p=s1+1;
T_CHAR* temp[64];
UINT temp_f[64];
UINT nt=0;
T_CHAR * p1=s1+1;
while(p<=s2 && nt<64)
{
if (!skipshit(&p,_TX(",)"))) {Error();return;}
if (p>s2 || (*p!=',' && *p!=')')) {Error(_TX("internal error"));return;}
T_CHAR bk=*p;
*p=0;
temp[nt]=_FMT(p1,&temp_f[nt]);
nt++;
*p=bk;;
p1=p+1;
p++;
}
*s1=0;
UINT n;
for (n=0; n<TABSIZE(FUNCS); n++)
if (!t_stricmp(spec, FUNCS[n].name))
break;
*s1='(';
if (n != TABSIZE(FUNCS))
{
if (!FUNCS[n].func(nt, temp, temp_f, str))
{
Error(_TX("[INVALID $"));
str.AddString(FUNCS[n].name);
str.AddString(_TX(" SYNTAX]"));
return;
}
}
else
{
Error(_TX("[UNKNOWN FUNCTION]"));
return;
}
for(n=0;n<nt;n++) free(temp[n]);
spec=s2+1;
}
else if (*spec=='\'')
{
spec++;
if (*spec=='\'') {str.AddChar('\'');spec++;continue;}
T_CHAR * s1=spec+1;
while(*s1 && *s1!='\'') s1++;
if (!*s1) {Error();break;}
*s1=0;
str.AddString(spec);
*s1='\'';
spec=s1+1;
}
else if (*spec=='[')
{
spec++;
T_CHAR * s1=spec;
UINT bc=0;
if (!skipshit(&s1,_TX("]"))) {Error();break;}
T_CHAR bk=*s1;
*s1=0;
FMT fmt(this,spec);
fmt.run();
if (fmt.found)
{
str.AddString(fmt.str);
found+=fmt.found;
}
*s1=bk;
spec=s1+1;
}
else if (*spec == ']') {Error();break;}
else
{
str.AddChar(*spec);
spec++;
}
}
}
FMT(FMT* base,T_CHAR * _spec)
{
found=0;
org_spec=0;
f=base->f;
ff=base->ff;
fp=base->fp;
spec=_spec;
}
public:
FMT(const T_CHAR * p_spec,TAGFUNC _f,TAGFREEFUNC _ff,void * _fp)
{
found=0;
org_spec=spec=t_strdup(p_spec);
f=_f;
ff=_ff;
fp=_fp;
}
operator T_CHAR*()
{
run();
return str.GetBuf();
}
~FMT()
{
if (org_spec) free(org_spec);
}
};
extern "C"
{
UINT tagz_format(const T_CHAR * spec,TAGFUNC f,TAGFREEFUNC ff,void *fp,T_CHAR* out,UINT max)
{
T_CHAR * zz=tagz_format_r(spec,f,ff,fp);
UINT r=0;
while(r<max-1 && zz[r])
{
out[r]=zz[r];
r++;
}
out[r]=0;
free(zz);
return r;
}
T_CHAR * tagz_format_r(const T_CHAR* spec,TAGFUNC f,TAGFREEFUNC ff,void * fp)
{
return FMT(spec,f,ff,fp);
}
const char tagz_manual[]="Syntax reference: \n"
"\n"
"* %tagname% - inserts field named <tagname>, eg. \"%artist%\"\n"
"* $abbr(x) - inserts abbreviation of x, eg. \"$abbr(%album%)\" - will convert album name of \"Final Fantasy VI\" to \"FFVI\"\n"
"* $abbr(x,y) - inserts abbreviation of x if x is longer than y characters; otherwise inserts full value of x, eg. \"$abbr(%album%,10)\"\n"
"* $lower(x), $upper(x) - converts x to in lower/uppercase, eg. \"$upper(%title%)\"\n"
"* $num(x,y) - displays x number and pads with zeros up to y characters (useful for track numbers), eg. $num(%tracknumber%,2)\n"
"* $caps(x) - converts first letter in every word of x to uppercase, and all other letters to lowercase, eg. \"blah BLAH\" -> \"Blah Blah\"\n"
"* $caps2(x) - similar to $caps, but leaves uppercase letters as they are, eg. \"blah BLAH\" -> \"Blah BLAH\"\n"
"* $if(A,B,C) - if A contains at least one valid tag, displays B, otherwise displays C; eg. \"$if(%artist%,%artist%,unknown artist)\" will display artist name if present; otherwise will display \"unknown artist\"; note that \"$if(A,A,)\" is equivalent to \"[A]\" (see below)\n"
"* $if2(A,B) - equals to $if(A,A,B)\n"
"* $longest(A,B,C,....) - compares lengths of output strings produced by A,B,C... and displays the longest one, eg. \"$longest(%title%,%comment%)\" will display either title if it's longer than comment; otherwise it will display comment\n"
"* $pad(x,y) - pads x with spaces up to y characters\n"
"* $cut(x,y) - truncates x to y characters\n"
"* $padcut(x,y) - pads x to y characters and truncates to y if longer\n"
"* [ .... ] - displays contents of brackets only if at least one of fields referenced inside has been found, eg. \"%artist% - [%album% / ]%title%\" will hide [] block if album field is not present\n"
"* \' (single quotation mark) - outputs raw text without parsing, eg, \'blah$blah%blah[][]\' will output the contained string and ignore all reserved characters (%,$,[,]) in it; you can use this feature to insert square brackets for an example.\n"
"\n"
"eg. \"[%artist% - ][$abbr(%album%,10)[ %tracknumber%] / ]%title%[ %streamtitle%]\"\n";
}