3417 lines
103 KiB
C
3417 lines
103 KiB
C
/**
|
|
* PROJECT: XTchain
|
|
* LICENSE: See COPYING.md in the top level directory
|
|
* FILE: tools/xtnlsc.c
|
|
* DESCRIPTION: Lossless NLS and YAML converter
|
|
* DEVELOPERS: Aiken Harris <harraiken91@gmail.com>
|
|
*/
|
|
|
|
#include "xtchain.h"
|
|
#include "unicode.h"
|
|
|
|
|
|
#define XTNLSC_VERSION "1.0.0"
|
|
|
|
typedef struct
|
|
{
|
|
const unsigned char *p;
|
|
size_t n, pos;
|
|
YAML_BUFFER out;
|
|
int reading;
|
|
} NlsIO;
|
|
|
|
typedef struct
|
|
{
|
|
const char *name;
|
|
unsigned width;
|
|
char ref;
|
|
} LocaleField;
|
|
|
|
static const LocaleField locale_fields[] = {
|
|
{"sname", 4, 'T'},
|
|
{"sopentypelanguagetag", 4, 'T'},
|
|
{"ilanguage", 2, 0},
|
|
{"unique_lcid", 2, 0},
|
|
{"idigits", 2, 0},
|
|
{"inegnumber", 2, 0},
|
|
{"icurrdigits", 2, 0},
|
|
{"icurrency", 2, 0},
|
|
{"inegcurr", 2, 0},
|
|
{"ilzero", 2, 0},
|
|
{"inotneutral", 2, 0},
|
|
{"ifirstdayofweek", 2, 0},
|
|
{"ifirstweekofyear", 2, 0},
|
|
{"icountry", 2, 0},
|
|
{"imeasure", 2, 0},
|
|
{"idigitsubstitution", 2, 0},
|
|
{"sgrouping", 4, 'T'},
|
|
{"smongrouping", 4, 'T'},
|
|
{"slist", 4, 'T'},
|
|
{"sdecimal", 4, 'T'},
|
|
{"sthousand", 4, 'T'},
|
|
{"scurrency", 4, 'T'},
|
|
{"smondecimalsep", 4, 'T'},
|
|
{"smonthousandsep", 4, 'T'},
|
|
{"spositivesign", 4, 'T'},
|
|
{"snegativesign", 4, 'T'},
|
|
{"s1159", 4, 'T'},
|
|
{"s2359", 4, 'T'},
|
|
{"snativedigits", 4, 'A'},
|
|
{"stimeformat", 4, 'A'},
|
|
{"sshortdate", 4, 'A'},
|
|
{"slongdate", 4, 'A'},
|
|
{"syearmonth", 4, 'A'},
|
|
{"sduration", 4, 'A'},
|
|
{"idefaultlanguage", 2, 0},
|
|
{"idefaultansicodepage", 2, 0},
|
|
{"idefaultcodepage", 2, 0},
|
|
{"idefaultmaccodepage", 2, 0},
|
|
{"idefaultebcdiccodepage", 2, 0},
|
|
{"old_geoid", 2, 0},
|
|
{"ipapersize", 2, 0},
|
|
{"islamic_cal_0", 1, 0},
|
|
{"islamic_cal_1", 1, 0},
|
|
{"scalendartype", 4, 'T'},
|
|
{"sabbrevlangname", 4, 'T'},
|
|
{"siso639langname", 4, 'T'},
|
|
{"senglanguage", 4, 'T'},
|
|
{"snativelangname", 4, 'T'},
|
|
{"sengcountry", 4, 'T'},
|
|
{"snativectryname", 4, 'T'},
|
|
{"sabbrevctryname", 4, 'T'},
|
|
{"siso3166ctryname", 4, 'T'},
|
|
{"sintlsymbol", 4, 'T'},
|
|
{"sengcurrname", 4, 'T'},
|
|
{"snativecurrname", 4, 'T'},
|
|
{"fontsignature", 4, 'F'},
|
|
{"siso639langname2", 4, 'T'},
|
|
{"siso3166ctryname2", 4, 'T'},
|
|
{"sparent", 4, 'T'},
|
|
{"sdayname", 4, 'A'},
|
|
{"sabbrevdayname", 4, 'A'},
|
|
{"smonthname", 4, 'A'},
|
|
{"sabbrevmonthname", 4, 'A'},
|
|
{"sgenitivemonth", 4, 'A'},
|
|
{"sabbrevgenitivemonth", 4, 'A'},
|
|
{"calnames", 4, 'A'},
|
|
{"customsorts", 4, 'A'},
|
|
{"inegativepercent", 2, 0},
|
|
{"ipositivepercent", 2, 0},
|
|
{"unknown1", 2, 0},
|
|
{"ireadinglayout", 2, 0},
|
|
{"unknown2_0", 2, 0},
|
|
{"unknown2_1", 2, 0},
|
|
{"unused1", 4, 0},
|
|
{"sengdisplayname", 4, 'T'},
|
|
{"snativedisplayname", 4, 'T'},
|
|
{"spercent", 4, 'T'},
|
|
{"snan", 4, 'T'},
|
|
{"sposinfinity", 4, 'T'},
|
|
{"sneginfinity", 4, 'T'},
|
|
{"unused2", 4, 0},
|
|
{"serastring", 4, 'T'},
|
|
{"sabbreverastring", 4, 'T'},
|
|
{"unused3", 4, 0},
|
|
{"sconsolefallbackname", 4, 'T'},
|
|
{"sshorttime", 4, 'A'},
|
|
{"sshortestdayname", 4, 'A'},
|
|
{"unused4", 4, 0},
|
|
{"ssortlocale", 4, 'T'},
|
|
{"skeyboardstoinstall", 4, 'T'},
|
|
{"sscripts", 4, 'T'},
|
|
{"srelativelongdate", 4, 'T'},
|
|
{"igeoid", 4, 0},
|
|
{"sshortestam", 4, 'T'},
|
|
{"sshortestpm", 4, 'T'},
|
|
{"smonthday", 4, 'A'},
|
|
{"keyboard_layout", 4, 'T'},
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
NlsIO *x;
|
|
YAML_NODE *needed;
|
|
size_t words;
|
|
const unsigned char *source;
|
|
unsigned char *data, *used;
|
|
} Pool;
|
|
|
|
typedef struct
|
|
{
|
|
size_t glyph, range_pos, dbcs_base, flag, wide, end, blocks[256];
|
|
unsigned leads[256], block_count;
|
|
uint32_t size, marker, ranges;
|
|
} Codepage;
|
|
|
|
static char *temporary;
|
|
static FILE *output_stream;
|
|
|
|
typedef void (*Codec)(NlsIO *, YAML_NODE *);
|
|
|
|
typedef enum
|
|
{
|
|
NLS_CHARACTER_TYPES,
|
|
NLS_GEOGRAPHY,
|
|
NLS_CASE_EXCEPTIONS,
|
|
NLS_UNICODE_MAPPINGS,
|
|
NLS_SORT_TABLES_LEGACY,
|
|
NLS_LOCALE_DATABASE,
|
|
NLS_CODEPAGE,
|
|
NLS_CASE_MAP,
|
|
NLS_SORTKEY_LEGACY,
|
|
NLS_FORMAT_COUNT
|
|
} NLS_FORMAT;
|
|
|
|
static YAML_NODE *NlsDecode(const unsigned char *data, size_t len, const char *type);
|
|
static YAML_BUFFER NlsEncode(YAML_NODE *root);
|
|
static YAML_BUFFER NlsEncodeV1(const unsigned char *data, size_t len);
|
|
static void NlsBounds(NlsIO *x, size_t count);
|
|
static uint32_t NlsRaw(NlsIO *x, unsigned width, uint32_t value);
|
|
static YAML_NODE *NlsField(NlsIO *x, YAML_NODE *parent, const char *key, YAML_NODE *value);
|
|
static YAML_NODE *NlsGroup(NlsIO *x, YAML_NODE *parent, const char *key, YAML_NODE_TYPE type);
|
|
static YAML_NODE *NlsItem(NlsIO *x, YAML_NODE *seq, size_t i);
|
|
static uint32_t NlsNum(NlsIO *x, YAML_NODE *parent, const char *key, unsigned width, int hex);
|
|
static void NlsFixed(NlsIO *x, YAML_NODE *parent, const char *key, unsigned units);
|
|
static char *NlsUtf8(const unsigned char *p, size_t units);
|
|
static YAML_BUFFER NlsUtf16(const char *s);
|
|
static void NlsCheckConsumed(YAML_NODE *n);
|
|
static void NlsTable(NlsIO *x, YAML_NODE *parent, const char *key, unsigned words, int mode);
|
|
static void NlsCtype(NlsIO *x, YAML_NODE *root);
|
|
static void NlsGeo(NlsIO *x, YAML_NODE *root);
|
|
static void NlsExceptions(NlsIO *x, YAML_NODE *root);
|
|
static void NlsUnicode(NlsIO *x, YAML_NODE *root, int embedded);
|
|
static void NlsSort(NlsIO *x, YAML_NODE *root);
|
|
static void NlsLocale(NlsIO *x, YAML_NODE *root);
|
|
static YAML_NODE *NlsDecodeFormat(const unsigned char *p, size_t n, size_t format);
|
|
static int NlsProbeFormat(const unsigned char *p, size_t n, size_t format);
|
|
static void NlsExtraUnicodeFile(NlsIO *x, YAML_NODE *n);
|
|
static int NlsEncodeExtra(const char *kind, YAML_NODE *body, YAML_BUFFER *out);
|
|
|
|
static const struct
|
|
{
|
|
const char *kind;
|
|
Codec codec;
|
|
} codecs[NLS_FORMAT_COUNT] = {
|
|
{"character-types", NlsCtype},
|
|
{"geography", NlsGeo},
|
|
{"case-exceptions", NlsExceptions},
|
|
{"unicode-mappings", NlsExtraUnicodeFile},
|
|
{"sort-tables-legacy", NlsSort},
|
|
{"locale-database", NlsLocale},
|
|
{"codepage", NULL},
|
|
{"case-map", NULL},
|
|
{"sortkey-legacy", NULL}
|
|
};
|
|
|
|
static jmp_buf NlsProbeJump;
|
|
|
|
static
|
|
const
|
|
char *
|
|
NlsGetCharacterName(uint32_t Codepoint)
|
|
{
|
|
size_t Low = 0;
|
|
size_t High = sizeof(unicode_names) / sizeof(unicode_names[0]);
|
|
while(Low < High)
|
|
{
|
|
size_t Middle = Low + (High - Low) / 2;
|
|
if(unicode_names[Middle].code < Codepoint)
|
|
{
|
|
Low = Middle + 1;
|
|
}
|
|
else
|
|
{
|
|
High = Middle;
|
|
}
|
|
}
|
|
return Low < sizeof(unicode_names) / sizeof(unicode_names[0]) && unicode_names[Low].code == Codepoint
|
|
? unicode_names[Low].name
|
|
: "CONTROL / UNASSIGNED";
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsBounds(NlsIO *x,
|
|
size_t count)
|
|
{
|
|
if(x->pos > x->n || count > x->n - x->pos)
|
|
{
|
|
YamlError("truncated NLS structure at byte %zu", x->pos);
|
|
}
|
|
}
|
|
|
|
static
|
|
uint32_t
|
|
NlsRaw(NlsIO *x,
|
|
unsigned width,
|
|
uint32_t v)
|
|
{
|
|
if(width != 1 && width != 2 && width != 4)
|
|
{
|
|
YamlError("internal NLS field width");
|
|
}
|
|
if(x->reading)
|
|
{
|
|
NlsBounds(x, width);
|
|
v = width == 4 ? ReadUInt32(x->p + x->pos) : width == 2 ? ReadUInt16(x->p + x->pos) : x->p[x->pos];
|
|
}
|
|
else
|
|
{
|
|
if(width == 4)
|
|
{
|
|
WriteUInt32(&x->out, v);
|
|
}
|
|
else if(width == 2)
|
|
{
|
|
WriteUInt16(&x->out, v);
|
|
}
|
|
else
|
|
{
|
|
if(v > 255)
|
|
{
|
|
YamlError("byte overflow");
|
|
}
|
|
unsigned char b = (unsigned char)v;
|
|
YamlAppendBuffer(&x->out, &b, 1);
|
|
}
|
|
}
|
|
x->pos += width;
|
|
return v;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsField(NlsIO *x,
|
|
YAML_NODE *parent,
|
|
const char *key,
|
|
YAML_NODE *v)
|
|
{
|
|
if(x->reading)
|
|
{
|
|
YamlAddField(parent, key, v);
|
|
return v;
|
|
}
|
|
parent->length++;
|
|
return YamlGetField(parent, key);
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsGroup(NlsIO *x,
|
|
YAML_NODE *parent,
|
|
const char *key,
|
|
YAML_NODE_TYPE type)
|
|
{
|
|
YAML_NODE *n = NlsField(x, parent, key, x->reading ? YamlCreateNode(type) : NULL);
|
|
if(n->type != type)
|
|
{
|
|
YamlError("invalid structure: %s", key);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsItem(NlsIO *x,
|
|
YAML_NODE *seq,
|
|
size_t i)
|
|
{
|
|
if(x->reading)
|
|
{
|
|
YAML_NODE *n = YamlCreateNode(YAML_MAP);
|
|
YamlAppendItem(seq, n);
|
|
return n;
|
|
}
|
|
if(i >= seq->count || seq->values[i]->type != YAML_MAP)
|
|
{
|
|
YamlError("invalid record count or type");
|
|
}
|
|
return seq->values[i];
|
|
}
|
|
|
|
static
|
|
uint32_t
|
|
NlsNum(NlsIO *x,
|
|
YAML_NODE *parent,
|
|
const char *key,
|
|
unsigned width,
|
|
int hex)
|
|
{
|
|
uint32_t v;
|
|
if(x->reading)
|
|
{
|
|
v = NlsRaw(x, width, 0);
|
|
NlsField(x, parent, key, hex ? YamlCreateHexadecimal(v, width * 2) : YamlCreateInteger(v));
|
|
}
|
|
else
|
|
{
|
|
YAML_NODE *n = NlsField(x, parent, key, NULL);
|
|
uint32_t max = width == 4 ? UINT32_MAX : width == 2 ? 65535 : 255;
|
|
v = hex ? YamlGetHexadecimal(n, max) : YamlGetUnsigned(n, max);
|
|
NlsRaw(x, width, v);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static
|
|
char *
|
|
NlsUtf8(const unsigned char *p,
|
|
size_t units)
|
|
{
|
|
char *s = YamlAllocateMemory(units * 4 + 1);
|
|
size_t j = 0;
|
|
for(size_t i = 0; i < units; i++)
|
|
{
|
|
uint32_t c = ReadUInt16(p + i * 2);
|
|
if(!c)
|
|
{
|
|
YamlError("embedded NUL in textual NLS field");
|
|
}
|
|
if(c >= 0xd800 && c <= 0xdbff)
|
|
{
|
|
if(++i >= units)
|
|
{
|
|
YamlError("unpaired UTF-16 surrogate");
|
|
}
|
|
uint32_t lo = ReadUInt16(p + i * 2);
|
|
if(lo < 0xdc00 || lo > 0xdfff)
|
|
{
|
|
YamlError("unpaired UTF-16 surrogate");
|
|
}
|
|
c = 0x10000 + ((c - 0xd800) << 10) + lo - 0xdc00;
|
|
}
|
|
else if(c >= 0xdc00 && c <= 0xdfff)
|
|
{
|
|
YamlError("unpaired UTF-16 surrogate");
|
|
}
|
|
if(c < 0x80)
|
|
{
|
|
s[j++] = (char)c;
|
|
}
|
|
else if(c < 0x800)
|
|
{
|
|
s[j++] = (char)(0xc0 | (c >> 6));
|
|
s[j++] = (char)(0x80 | (c & 63));
|
|
}
|
|
else if(c < 0x10000)
|
|
{
|
|
s[j++] = (char)(0xe0 | (c >> 12));
|
|
s[j++] = (char)(0x80 | ((c >> 6) & 63));
|
|
s[j++] = (char)(0x80 | (c & 63));
|
|
}
|
|
else
|
|
{
|
|
s[j++] = (char)(0xf0 | (c >> 18));
|
|
s[j++] = (char)(0x80 | ((c >> 12) & 63));
|
|
s[j++] = (char)(0x80 | ((c >> 6) & 63));
|
|
s[j++] = (char)(0x80 | (c & 63));
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static
|
|
YAML_BUFFER
|
|
NlsUtf16(const char *s)
|
|
{
|
|
YAML_BUFFER b = {0};
|
|
const unsigned char *p = (const unsigned char *)s;
|
|
while(*p)
|
|
{
|
|
uint32_t c = *p++;
|
|
unsigned more = 0;
|
|
uint32_t min = 0;
|
|
if(c >= 0xf0 && c <= 0xf4)
|
|
{
|
|
c &= 7;
|
|
more = 3;
|
|
min = 0x10000;
|
|
}
|
|
else if(c >= 0xe0 && c <= 0xef)
|
|
{
|
|
c &= 15;
|
|
more = 2;
|
|
min = 0x800;
|
|
}
|
|
else if(c >= 0xc2 && c <= 0xdf)
|
|
{
|
|
c &= 31;
|
|
more = 1;
|
|
min = 0x80;
|
|
}
|
|
else if(c >= 0x80)
|
|
{
|
|
YamlError("invalid UTF-8 string");
|
|
}
|
|
for(unsigned i = 0; i < more; i++)
|
|
{
|
|
if((*p & 0xc0) != 0x80)
|
|
{
|
|
YamlError("invalid UTF-8 string");
|
|
}
|
|
c = (c << 6) | (*p++ & 63);
|
|
}
|
|
if(c < min || c > 0x10ffff || (c >= 0xd800 && c <= 0xdfff))
|
|
{
|
|
YamlError("invalid Unicode scalar");
|
|
}
|
|
if(c >= 0x10000)
|
|
{
|
|
c -= 0x10000;
|
|
WriteUInt16(&b, 0xd800 + (c >> 10));
|
|
WriteUInt16(&b, 0xdc00 + (c & 1023));
|
|
}
|
|
else
|
|
{
|
|
WriteUInt16(&b, c);
|
|
}
|
|
}
|
|
return b;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsFixed(NlsIO *x,
|
|
YAML_NODE *parent,
|
|
const char *key,
|
|
unsigned units)
|
|
{
|
|
if(x->reading)
|
|
{
|
|
NlsBounds(x, 2 * units);
|
|
size_t n = 0;
|
|
while(n < units && ReadUInt16(x->p + x->pos + 2 * n))
|
|
{
|
|
n++;
|
|
}
|
|
if(n == units)
|
|
{
|
|
YamlError("unterminated fixed string: %s", key);
|
|
}
|
|
NlsField(x, parent, key, YamlCreateString(NlsUtf8(x->p + x->pos, n)));
|
|
/* Nonzero padding is represented separately, not silently discarded. */
|
|
YAML_NODE *pad = YamlCreateNode(YAML_MAP);
|
|
for(size_t i = n + 1; i < units; i++)
|
|
{
|
|
if(ReadUInt16(x->p + x->pos + 2 * i))
|
|
{
|
|
YamlAddField(
|
|
pad, YamlFormatString("%zu", i), YamlCreateHexadecimal(ReadUInt16(x->p + x->pos + 2 * i), 4));
|
|
}
|
|
}
|
|
if(pad->count)
|
|
{
|
|
NlsField(x, parent, YamlFormatString("%s_padding", key), pad);
|
|
}
|
|
x->pos += 2 * units;
|
|
}
|
|
else
|
|
{
|
|
YAML_BUFFER b = NlsUtf16(YamlGetScalar(NlsField(x, parent, key, NULL)));
|
|
if(b.len / 2 >= units)
|
|
{
|
|
YamlError("fixed string too long: %s", key);
|
|
}
|
|
unsigned char *p = YamlAllocateMemory(2 * units);
|
|
memcpy(p, b.data, b.len);
|
|
YAML_NODE *pad = YamlFindField(parent, YamlFormatString("%s_padding", key));
|
|
if(pad)
|
|
{
|
|
parent->length++;
|
|
if(pad->type != YAML_MAP)
|
|
{
|
|
YamlError("invalid string padding");
|
|
}
|
|
for(size_t i = 0; i < pad->count; i++)
|
|
{
|
|
uint32_t at = YamlParseNumber(pad->keys[i], 10, units - 1),
|
|
v = YamlGetHexadecimal(pad->values[i], 65535);
|
|
if(at <= b.len / 2)
|
|
{
|
|
YamlError("string padding overlaps text");
|
|
}
|
|
p[2 * at] = (unsigned char)v;
|
|
p[2 * at + 1] = (unsigned char)(v >> 8);
|
|
}
|
|
pad->length = pad->count;
|
|
}
|
|
YamlAppendBuffer(&x->out, p, 2 * units);
|
|
x->pos += 2 * units;
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsCheckConsumed(YAML_NODE *n)
|
|
{
|
|
if(n->type == YAML_MAP && n->length != n->count)
|
|
{
|
|
YamlError("unexpected fields in NLS structure (%zu consumed of %zu)", n->length, n->count);
|
|
}
|
|
if(n->type == YAML_MAP || n->type == YAML_SEQUENCE)
|
|
{
|
|
for(size_t i = 0; i < n->count; i++)
|
|
{
|
|
NlsCheckConsumed(n->values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsExtraUnicodeFile(NlsIO *x,
|
|
YAML_NODE *n)
|
|
{
|
|
NlsUnicode(x, n, 0);
|
|
}
|
|
|
|
|
|
static
|
|
int
|
|
NlsEncodeExtra(const char *kind,
|
|
YAML_NODE *body,
|
|
YAML_BUFFER *out)
|
|
{
|
|
for(size_t i = 0; i < sizeof(codecs) / sizeof(codecs[0]); i++)
|
|
{
|
|
if(codecs[i].codec && !strcmp(kind, codecs[i].kind))
|
|
{
|
|
NlsIO x = {0};
|
|
codecs[i].codec(&x, body);
|
|
NlsCheckConsumed(body);
|
|
/* Revalidate the emitted structure before the CLI commits the output. */
|
|
NlsIO check = {.p = x.out.data, .n = x.out.len, .reading = 1};
|
|
YAML_NODE *r = YamlCreateNode(YAML_MAP);
|
|
codecs[i].codec(&check, r);
|
|
if(check.pos != check.n)
|
|
{
|
|
YamlError("encoded NLS has trailing data");
|
|
}
|
|
*out = x.out;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Unicode, character type and geography tables */
|
|
|
|
static
|
|
uint32_t
|
|
NlsTablesCpvalue(YAML_NODE *n)
|
|
{
|
|
const char *s = YamlGetScalar(n);
|
|
size_t len = strlen(s);
|
|
if(len < 6 || len > 8 || strncmp(s, "U+", 2))
|
|
{
|
|
YamlError("expected Unicode code point U+XXXX[XX]");
|
|
}
|
|
return YamlParseNumber(s + 2, 16, 0x10ffff);
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsTablesMark(unsigned char *role,
|
|
size_t count,
|
|
size_t at,
|
|
unsigned width,
|
|
unsigned char r)
|
|
{
|
|
if(at > count || width > count - at)
|
|
{
|
|
YamlError("NLS trie offset outside table");
|
|
}
|
|
for(unsigned j = 0; j < width; j++)
|
|
{
|
|
if(role[at + j] && role[at + j] != r)
|
|
{
|
|
YamlError("overlapping NLS trie indices and values");
|
|
}
|
|
role[at + j] = r;
|
|
}
|
|
}
|
|
|
|
static
|
|
uint32_t *
|
|
NlsTablesGraph(uint32_t *t,
|
|
size_t words,
|
|
unsigned char *role,
|
|
int mode,
|
|
unsigned *limit)
|
|
{
|
|
if(words < 256)
|
|
{
|
|
YamlError("NLS trie too short");
|
|
}
|
|
int high = mode == 0 && t[0] >= 1280;
|
|
*limit = high ? 0x110000 : 65536;
|
|
uint32_t *slots = YamlAllocateMemory((size_t)*limit * sizeof(*slots));
|
|
NlsTablesMark(role, words, 0, high ? 1280 : 256, 1);
|
|
for(unsigned c = 0; c < *limit; c++)
|
|
{
|
|
size_t mid, leaf;
|
|
unsigned width;
|
|
if(c < 65536)
|
|
{
|
|
mid = (size_t)t[c >> 8] + ((c >> 4) & 15);
|
|
NlsTablesMark(role, words, mid, 1, 1);
|
|
width = mode == 2 ? 2 : 1;
|
|
leaf = (size_t)t[mid] + width * (c & 15);
|
|
}
|
|
else
|
|
{
|
|
unsigned v = c - 65536;
|
|
mid = (size_t)t[256 + (v >> 10)] + ((v >> 5) & 31);
|
|
NlsTablesMark(role, words, mid, 1, 1);
|
|
width = 2;
|
|
leaf = (size_t)t[mid] + 2 * (v & 31);
|
|
}
|
|
NlsTablesMark(role, words, leaf, width, 2);
|
|
slots[c] = (uint32_t)leaf;
|
|
}
|
|
return slots;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsTable(NlsIO *x,
|
|
YAML_NODE *parent,
|
|
const char *key,
|
|
unsigned words,
|
|
int mode)
|
|
{
|
|
if(words < 256 || words > 65535)
|
|
{
|
|
YamlError("invalid mapping table length");
|
|
}
|
|
YAML_NODE *r = NlsGroup(x, parent, key, YAML_MAP), *map, *layout, *indices, *unused;
|
|
uint32_t *t = YamlAllocateMemory(words * sizeof(*t));
|
|
unsigned char *role = YamlAllocateMemory(words);
|
|
uint32_t *slots;
|
|
unsigned limit;
|
|
if(x->reading)
|
|
{
|
|
NlsBounds(x, words * 2);
|
|
for(unsigned i = 0; i < words; i++)
|
|
{
|
|
t[i] = NlsRaw(x, 2, 0);
|
|
}
|
|
slots = NlsTablesGraph(t, words, role, mode, &limit);
|
|
NlsField(x, r, "default", YamlCreateString(mode == 0 ? "identity" : mode == 1 ? "zero" : "none"));
|
|
map = NlsGroup(x, r, "map", YAML_MAP);
|
|
layout = NlsGroup(x, r, "binary_layout", YAML_MAP);
|
|
indices = NlsGroup(x, layout, "index_words", YAML_MAP);
|
|
unused = NlsGroup(x, layout, "unused_words", YAML_MAP);
|
|
for(unsigned c = 0; c < limit; c++)
|
|
{
|
|
uint32_t at = slots[c], v = t[at];
|
|
if(c >= 65536)
|
|
{
|
|
v |= t[at + 1] << 16;
|
|
}
|
|
if(mode == 2)
|
|
{
|
|
if(v || t[at + 1])
|
|
{
|
|
YAML_NODE *pair = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(pair, "base", YamlCreateCodepoint(t[at + 1]));
|
|
YamlAddField(pair, "combining", YamlCreateCodepoint(v));
|
|
YamlAddField(map, YamlFormatString("U+%04X", c), pair);
|
|
}
|
|
}
|
|
else if(v)
|
|
{
|
|
YamlAddField(map,
|
|
YamlFormatString("U+%04X", c),
|
|
mode == 0 ? YamlCreateCodepoint(c < 65536 ? (c + v) & 65535 : c + v)
|
|
: YamlCreateInteger(v));
|
|
}
|
|
}
|
|
for(unsigned i = 0; i < words; i++)
|
|
{
|
|
if(role[i] == 1)
|
|
{
|
|
YamlAddField(indices, YamlFormatString("%u", i), YamlCreateInteger(t[i]));
|
|
}
|
|
else if(!role[i])
|
|
{
|
|
YamlAddField(unused, YamlFormatString("%u", i), YamlCreateInteger(t[i]));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *def = YamlGetScalar(NlsField(x, r, "default", NULL));
|
|
if(strcmp(def, mode == 0 ? "identity" : mode == 1 ? "zero" : "none"))
|
|
{
|
|
YamlError("invalid mapping default");
|
|
}
|
|
map = NlsGroup(x, r, "map", YAML_MAP);
|
|
layout = NlsGroup(x, r, "binary_layout", YAML_MAP);
|
|
indices = NlsGroup(x, layout, "index_words", YAML_MAP);
|
|
unused = NlsGroup(x, layout, "unused_words", YAML_MAP);
|
|
unsigned char *assigned = YamlAllocateMemory(words);
|
|
YAML_NODE *sets[2] = {indices, unused};
|
|
for(unsigned s = 0; s < 2; s++)
|
|
{
|
|
YAML_NODE *g = sets[s];
|
|
g->length = g->count;
|
|
for(size_t j = 0; j < g->count; j++)
|
|
{
|
|
unsigned at = YamlParseNumber(g->keys[j], 10, words - 1);
|
|
if(assigned[at])
|
|
{
|
|
YamlError("overlapping mapping metadata");
|
|
}
|
|
assigned[at] = s ? 3 : 1;
|
|
t[at] = YamlGetUnsigned(g->values[j], 65535);
|
|
}
|
|
}
|
|
slots = NlsTablesGraph(t, words, role, mode, &limit);
|
|
for(unsigned i = 0; i < words; i++)
|
|
{
|
|
if(assigned[i] != (role[i] == 1 ? 1 : role[i] == 2 ? 0 : 3))
|
|
{
|
|
YamlError("mapping metadata omits or hides words");
|
|
}
|
|
}
|
|
uint32_t *targets = YamlAllocateMemory((size_t)limit * 2 * sizeof(*targets));
|
|
unsigned char *seen = YamlAllocateMemory(limit);
|
|
map->length = map->count;
|
|
for(size_t j = 0; j < map->count; j++)
|
|
{
|
|
YAML_NODE k = {.type = YAML_SCALAR, .text = map->keys[j]};
|
|
unsigned c = NlsTablesCpvalue(&k);
|
|
if(c >= limit || seen[c])
|
|
{
|
|
YamlError("duplicate or out of range mapping key");
|
|
}
|
|
seen[c] = 1;
|
|
if(mode == 2)
|
|
{
|
|
YAML_NODE *pair = map->values[j];
|
|
targets[c * 2] = YamlGetCodepoint(NlsField(x, pair, "combining", NULL));
|
|
targets[c * 2 + 1] = YamlGetCodepoint(NlsField(x, pair, "base", NULL));
|
|
}
|
|
else if(mode == 1)
|
|
{
|
|
targets[c * 2] = YamlGetUnsigned(map->values[j], 65535);
|
|
}
|
|
else
|
|
{
|
|
uint32_t target = NlsTablesCpvalue(map->values[j]);
|
|
if(c < 65536 && target > 65535)
|
|
{
|
|
YamlError("BMP target outside BMP");
|
|
}
|
|
targets[c * 2] = c < 65536 ? (target - c) & 65535 : target - c;
|
|
}
|
|
}
|
|
unsigned char *written = YamlAllocateMemory(words);
|
|
for(unsigned c = 0; c < limit; c++)
|
|
{
|
|
unsigned at = slots[c], width = (mode == 2 || c >= 65536) ? 2 : 1;
|
|
uint32_t v[2] = {targets[c * 2] & 65535, mode == 2 ? targets[c * 2 + 1] : targets[c * 2] >> 16};
|
|
for(unsigned j = 0; j < width; j++)
|
|
{
|
|
if(written[at + j] && t[at + j] != v[j])
|
|
{
|
|
YamlError("mapping edit conflicts with shared layout at U+%04X", c);
|
|
}
|
|
t[at + j] = v[j];
|
|
written[at + j] = 1;
|
|
}
|
|
}
|
|
for(unsigned i = 0; i < words; i++)
|
|
{
|
|
NlsRaw(x, 2, t[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsCtype(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
size_t start = x->pos;
|
|
unsigned size = NlsNum(x, r, "byte_size", 2, 0), types_size = NlsNum(x, r, "class_table_size", 2, 0);
|
|
if(size < 4 || types_size < 2 || (types_size - 2) % 6 || types_size + 2 > size)
|
|
{
|
|
YamlError("invalid CTYPE header");
|
|
}
|
|
unsigned classes = (types_size - 2) / 6;
|
|
if(classes > 256)
|
|
{
|
|
YamlError("too many character classes");
|
|
}
|
|
YAML_NODE *defs = NlsGroup(x, r, "classes", YAML_SEQUENCE);
|
|
if(!x->reading && defs->count != classes)
|
|
{
|
|
YamlError("class count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < classes; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, defs, i);
|
|
NlsNum(x, v, "ctype1", 2, 1);
|
|
NlsNum(x, v, "ctype2", 2, 0);
|
|
NlsNum(x, v, "ctype3", 2, 1);
|
|
}
|
|
unsigned bytes = size - types_size - 2;
|
|
if(bytes < 512)
|
|
{
|
|
YamlError("CTYPE trie too short");
|
|
}
|
|
unsigned char *data = YamlAllocateMemory(bytes), *role = YamlAllocateMemory(bytes),
|
|
*assigned = YamlAllocateMemory(bytes);
|
|
unsigned *slots = YamlAllocateMemory(65536 * sizeof(*slots));
|
|
YAML_NODE *layout = NlsGroup(x, r, "binary_layout", YAML_MAP),
|
|
*indices = NlsGroup(x, layout, "index_words", YAML_MAP),
|
|
*unused = NlsGroup(x, layout, "unused_bytes", YAML_MAP);
|
|
if(x->reading)
|
|
{
|
|
NlsBounds(x, bytes);
|
|
memcpy(data, x->p + x->pos, bytes);
|
|
x->pos += bytes;
|
|
}
|
|
else
|
|
{
|
|
YAML_NODE *sets[2] = {indices, unused};
|
|
for(unsigned s = 0; s < 2; s++)
|
|
{
|
|
YAML_NODE *g = sets[s];
|
|
g->length = g->count;
|
|
for(size_t j = 0; j < g->count; j++)
|
|
{
|
|
unsigned at = YamlParseNumber(g->keys[j], 10, bytes - 1), width = s ? 1 : 2,
|
|
v = YamlGetUnsigned(g->values[j], s ? 255 : 65535);
|
|
if(at + width > bytes)
|
|
{
|
|
YamlError("CTYPE metadata outside trie");
|
|
}
|
|
for(unsigned k = 0; k < width; k++)
|
|
{
|
|
if(assigned[at + k])
|
|
{
|
|
YamlError("CTYPE metadata overlap");
|
|
}
|
|
assigned[at + k] = s ? 3 : 1;
|
|
data[at + k] = (unsigned char)(v >> (8 * k));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
NlsTablesMark(role, bytes, 0, 512, 1);
|
|
for(unsigned c = 0; c < 65536; c++)
|
|
{
|
|
unsigned mid = ReadUInt16(data + 2 * (c >> 8)) + 2 * ((c >> 4) & 15);
|
|
NlsTablesMark(role, bytes, mid, 2, 1);
|
|
unsigned at = ReadUInt16(data + mid) + (c & 15);
|
|
NlsTablesMark(role, bytes, at, 1, 2);
|
|
slots[c] = at;
|
|
if(x->reading && data[at] >= classes)
|
|
{
|
|
YamlError("CTYPE references missing class");
|
|
}
|
|
}
|
|
if(x->reading)
|
|
{
|
|
for(unsigned i = 0; i < bytes; i++)
|
|
{
|
|
if(role[i] == 1)
|
|
{
|
|
if(i + 1 >= bytes || role[i + 1] != 1)
|
|
{
|
|
YamlError("misaligned CTYPE index");
|
|
}
|
|
YamlAddField(indices, YamlFormatString("%u", i), YamlCreateInteger(ReadUInt16(data + i)));
|
|
i++;
|
|
}
|
|
else if(!role[i])
|
|
{
|
|
YamlAddField(unused, YamlFormatString("%u", i), YamlCreateInteger(data[i]));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(unsigned i = 0; i < bytes; i++)
|
|
{
|
|
if(assigned[i] != (role[i] == 1 ? 1 : role[i] == 2 ? 0 : 3))
|
|
{
|
|
YamlError("CTYPE metadata hides or omits bytes");
|
|
}
|
|
}
|
|
}
|
|
YAML_NODE *ranges = NlsGroup(x, r, "character_ranges", YAML_SEQUENCE);
|
|
if(x->reading)
|
|
{
|
|
for(unsigned c = 0; c < 65536;)
|
|
{
|
|
unsigned end = c;
|
|
while(end < 65535 && data[slots[end + 1]] == data[slots[c]])
|
|
{
|
|
end++;
|
|
}
|
|
YAML_NODE *v = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(v, "first", YamlCreateCodepoint(c));
|
|
YamlAddField(v, "last", YamlCreateCodepoint(end));
|
|
YamlAddField(v, "class", YamlCreateInteger(data[slots[c]]));
|
|
YamlAppendItem(ranges, v);
|
|
c = end + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned next = 0;
|
|
unsigned char *written = YamlAllocateMemory(bytes);
|
|
for(size_t i = 0; i < ranges->count; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, ranges, i);
|
|
unsigned first = YamlGetCodepoint(NlsField(x, v, "first", NULL)),
|
|
last = YamlGetCodepoint(NlsField(x, v, "last", NULL));
|
|
unsigned cls = YamlGetUnsigned(NlsField(x, v, "class", NULL), classes - 1);
|
|
if(first != next || last < first)
|
|
{
|
|
YamlError("CTYPE ranges must cover BMP exactly in order");
|
|
}
|
|
for(unsigned c = first; c <= last; c++)
|
|
{
|
|
unsigned at = slots[c];
|
|
if(written[at] && data[at] != cls)
|
|
{
|
|
YamlError("CTYPE edit conflicts with shared leaf");
|
|
}
|
|
written[at] = 1;
|
|
data[at] = (unsigned char)cls;
|
|
}
|
|
next = last + 1;
|
|
}
|
|
if(next != 65536)
|
|
{
|
|
YamlError("incomplete CTYPE ranges");
|
|
}
|
|
YamlAppendBuffer(&x->out, data, bytes);
|
|
x->pos += bytes;
|
|
}
|
|
if(x->pos - start != size)
|
|
{
|
|
YamlError("CTYPE size mismatch");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsGeo(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
size_t start = x->pos;
|
|
NlsFixed(x, r, "signature", 4);
|
|
if(strcmp(YamlGetScalar(YamlGetField(r, "signature")), "geo"))
|
|
{
|
|
YamlError("invalid geography signature");
|
|
}
|
|
unsigned size = NlsNum(x, r, "byte_size", 4, 0), ids_at = NlsNum(x, r, "ids_offset", 4, 0),
|
|
ids = NlsNum(x, r, "id_count", 4, 0), index_at = NlsNum(x, r, "index_offset", 4, 0),
|
|
count = NlsNum(x, r, "index_count", 4, 0);
|
|
if(ids_at != 28 || !ids || index_at < 28 || (index_at - 28) % ids || ids > 100000 || count > 100000)
|
|
{
|
|
YamlError("unsupported geography layout");
|
|
}
|
|
unsigned stride = (index_at - 28) / ids;
|
|
if(stride != 80 && stride != 104)
|
|
{
|
|
YamlError("unsupported geography record size");
|
|
}
|
|
if((uint64_t)index_at + 12ull * count != size)
|
|
{
|
|
YamlError("invalid geography index size");
|
|
}
|
|
YAML_NODE *items = NlsGroup(x, r, "places", YAML_SEQUENCE);
|
|
if(!x->reading && items->count != ids)
|
|
{
|
|
YamlError("geography count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < ids; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, items, i);
|
|
NlsNum(x, v, "geoid", 4, 0);
|
|
NlsFixed(x, v, "latitude", 12);
|
|
NlsFixed(x, v, "longitude", 12);
|
|
NlsNum(x, v, "class", 4, 0);
|
|
NlsNum(x, v, "parent", 4, 0);
|
|
NlsFixed(x, v, "iso2", 4);
|
|
NlsFixed(x, v, "iso3", 4);
|
|
NlsNum(x, v, "un_code", 2, 0);
|
|
NlsNum(x, v, "dial_code", 2, 0);
|
|
if(stride == 104)
|
|
{
|
|
NlsFixed(x, v, "currency_code", 4);
|
|
NlsFixed(x, v, "currency_symbol", 8);
|
|
}
|
|
}
|
|
YAML_NODE *index = NlsGroup(x, r, stride == 80 ? "locale_index" : "name_index", YAML_SEQUENCE);
|
|
if(!x->reading && index->count != count)
|
|
{
|
|
YamlError("geography index count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < count; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, index, i);
|
|
if(stride == 80)
|
|
{
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
NlsNum(x, v, "geoid", 4, 0);
|
|
NlsNum(x, v, "alternate_lcid", 4, 1);
|
|
}
|
|
else
|
|
{
|
|
NlsFixed(x, v, "name", 4);
|
|
unsigned idx = NlsNum(x, v, "place_index", 4, 0);
|
|
if(idx >= ids)
|
|
{
|
|
YamlError("geography index outside places");
|
|
}
|
|
}
|
|
}
|
|
if(x->pos - start != size)
|
|
{
|
|
YamlError("geography size mismatch");
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsExceptions(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
unsigned count = NlsNum(x, r, "locale_count", 4, 0);
|
|
if(count > 10000)
|
|
{
|
|
YamlError("too many exception locales");
|
|
}
|
|
YAML_NODE *items = NlsGroup(x, r, "locales", YAML_SEQUENCE);
|
|
if(!x->reading && items->count != count)
|
|
{
|
|
YamlError("exception count mismatch");
|
|
}
|
|
unsigned *offset = YamlAllocateMemory(count * sizeof(*offset)), *upper = YamlAllocateMemory(count * sizeof(*upper)),
|
|
*lower = YamlAllocateMemory(count * sizeof(*lower));
|
|
unsigned words = 0;
|
|
for(unsigned i = 0; i < count; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, items, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
offset[i] = NlsNum(x, v, "word_offset", 4, 0);
|
|
upper[i] = NlsNum(x, v, "uppercase_count", 4, 0);
|
|
lower[i] = NlsNum(x, v, "lowercase_count", 4, 0);
|
|
if(offset[i] != words || upper[i] > 65536 || lower[i] > 65536)
|
|
{
|
|
YamlError("unsupported exception layout");
|
|
}
|
|
words += 2 * (upper[i] + lower[i]);
|
|
}
|
|
for(unsigned i = 0; i < count; i++)
|
|
{
|
|
for(unsigned k = 0; k < 2; k++)
|
|
{
|
|
YAML_NODE *v = items->values[i], *map = NlsGroup(x, v, k ? "lowercase" : "uppercase", YAML_SEQUENCE);
|
|
unsigned n = k ? lower[i] : upper[i];
|
|
if(!x->reading && map->count != n)
|
|
{
|
|
YamlError("case exception record count mismatch");
|
|
}
|
|
for(unsigned j = 0; j < n; j++)
|
|
{
|
|
YAML_NODE *e = NlsItem(x, map, j);
|
|
if(x->reading)
|
|
{
|
|
unsigned c = NlsRaw(x, 2, 0), delta = NlsRaw(x, 2, 0);
|
|
NlsField(x, e, "source", YamlCreateCodepoint(c));
|
|
NlsField(x, e, "target", YamlCreateCodepoint((c + delta) & 65535));
|
|
}
|
|
else
|
|
{
|
|
unsigned c = YamlGetCodepoint(NlsField(x, e, "source", NULL)),
|
|
target = YamlGetCodepoint(NlsField(x, e, "target", NULL));
|
|
NlsRaw(x, 2, c);
|
|
NlsRaw(x, 2, (target - c) & 65535);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsUnicode(NlsIO *x,
|
|
YAML_NODE *r,
|
|
int embedded)
|
|
{
|
|
static const char *names[] = {"fold_digits",
|
|
"compatibility",
|
|
"hiragana",
|
|
"katakana",
|
|
"halfwidth",
|
|
"fullwidth",
|
|
"traditional_chinese",
|
|
"simplified_chinese",
|
|
"decomposition"};
|
|
YAML_NODE *sizes = NlsGroup(x, r, "table_word_counts", YAML_MAP);
|
|
for(unsigned i = 0; i < (embedded ? 8u : 9u); i++)
|
|
{
|
|
unsigned n = NlsNum(x, sizes, names[i], 2, 0);
|
|
if(n < 257)
|
|
{
|
|
YamlError("invalid Unicode map length");
|
|
}
|
|
NlsTable(x, r, names[i], n - 1, i == 8 ? 2 : 0);
|
|
}
|
|
if(embedded)
|
|
{
|
|
return;
|
|
}
|
|
YAML_NODE *comp = NlsGroup(x, r, "composition", YAML_MAP);
|
|
unsigned bases_len = NlsNum(x, comp, "base_index_words", 2, 0),
|
|
marks_len = NlsNum(x, comp, "combining_index_words", 2, 0);
|
|
unsigned packed;
|
|
if(x->reading)
|
|
{
|
|
packed = NlsRaw(x, 2, 0);
|
|
NlsField(x, comp, "base_count", YamlCreateInteger(packed & 255));
|
|
NlsField(x, comp, "combining_count", YamlCreateInteger(packed >> 8));
|
|
}
|
|
else
|
|
{
|
|
unsigned a = YamlGetUnsigned(NlsField(x, comp, "base_count", NULL), 255),
|
|
b = YamlGetUnsigned(NlsField(x, comp, "combining_count", NULL), 255);
|
|
packed = a | (b << 8);
|
|
NlsRaw(x, 2, packed);
|
|
}
|
|
NlsTable(x, comp, "base_indices", bases_len, 1);
|
|
NlsTable(x, comp, "combining_indices", marks_len, 1);
|
|
unsigned base_count = packed & 255, mark_count = packed >> 8;
|
|
YAML_NODE *base_map = YamlGetField(YamlGetField(comp, "base_indices"), "map"),
|
|
*mark_map = YamlGetField(YamlGetField(comp, "combining_indices"), "map");
|
|
const char **base_names = YamlAllocateMemory((base_count + 1) * sizeof(*base_names)),
|
|
**mark_names = YamlAllocateMemory((mark_count + 1) * sizeof(*mark_names));
|
|
YAML_NODE *maps[2] = {base_map, mark_map};
|
|
const char **namesets[2] = {base_names, mark_names};
|
|
unsigned counts[2] = {base_count, mark_count};
|
|
for(unsigned k = 0; k < 2; k++)
|
|
{
|
|
YAML_NODE *m = maps[k];
|
|
for(size_t j = 0; j < m->count; j++)
|
|
{
|
|
unsigned idx = YamlGetUnsigned(m->values[j], counts[k]);
|
|
if(!idx || namesets[k][idx])
|
|
{
|
|
YamlError("composition index must be unique and nonzero");
|
|
}
|
|
namesets[k][idx] = m->keys[j];
|
|
}
|
|
for(unsigned j = 1; j <= counts[k]; j++)
|
|
{
|
|
if(!namesets[k][j])
|
|
{
|
|
YamlError("missing composition index");
|
|
}
|
|
}
|
|
}
|
|
YAML_NODE *pairs = NlsGroup(x, comp, "pairs", YAML_MAP);
|
|
for(unsigned a = 1; a <= base_count; a++)
|
|
{
|
|
for(unsigned b = 1; b <= mark_count; b++)
|
|
{
|
|
char *key = YamlFormatString("%s %s", base_names[a], mark_names[b]);
|
|
unsigned v = 0;
|
|
if(x->reading)
|
|
{
|
|
v = NlsRaw(x, 2, 0);
|
|
if(v)
|
|
{
|
|
YamlAddField(pairs, key, YamlCreateCodepoint(v));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
YAML_NODE *n = YamlFindField(pairs, key);
|
|
if(n)
|
|
{
|
|
pairs->length++;
|
|
v = YamlGetCodepoint(n);
|
|
}
|
|
NlsRaw(x, 2, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsSortCharacter(NlsIO *x,
|
|
YAML_NODE *r,
|
|
const char *key)
|
|
{
|
|
if(x->reading)
|
|
{
|
|
NlsField(x, r, key, YamlCreateCodepoint(NlsRaw(x, 2, 0)));
|
|
}
|
|
else
|
|
{
|
|
NlsRaw(x, 2, YamlGetCodepoint(NlsField(x, r, key, NULL)));
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsSortWeight(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
YAML_NODE *w = NlsGroup(x, r, "weight", YAML_MAP);
|
|
NlsNum(x, w, "primary", 1, 0);
|
|
NlsNum(x, w, "script", 1, 0);
|
|
NlsNum(x, w, "diacritic", 1, 0);
|
|
NlsNum(x, w, "case", 1, 0);
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsSortCounted(NlsIO *x,
|
|
YAML_NODE *r,
|
|
const char *key,
|
|
unsigned width,
|
|
unsigned *count)
|
|
{
|
|
YAML_NODE *s = NlsGroup(x, r, key, YAML_SEQUENCE);
|
|
if(!x->reading && s->count > 65535)
|
|
{
|
|
YamlError("too many sort records");
|
|
}
|
|
*count = NlsRaw(x, width, x->reading ? 0 : (unsigned)s->count);
|
|
if(*count > 65535)
|
|
{
|
|
YamlError("too many sort records");
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsSort(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
unsigned n;
|
|
YAML_NODE *s;
|
|
const char *versions[] = {"nls_versions", "defined_versions"};
|
|
for(unsigned k = 0; k < 2; k++)
|
|
{
|
|
s = NlsSortCounted(x, r, versions[k], 4, &n);
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "version", 4, 1);
|
|
NlsNum(x, v, "reserved", 4, 1);
|
|
}
|
|
}
|
|
const char *lists[] = {"reverse_diacritics", "double_compression"};
|
|
for(unsigned k = 0; k < 2; k++)
|
|
{
|
|
s = NlsSortCounted(x, r, lists[k], 4, &n);
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
}
|
|
}
|
|
s = NlsSortCounted(x, r, "cjk_sort_files", 4, &n);
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
NlsFixed(x, v, "filename", 14);
|
|
}
|
|
s = NlsSortCounted(x, r, "expansions", 4, &n);
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsSortCharacter(x, v, "first");
|
|
NlsSortCharacter(x, v, "second");
|
|
}
|
|
s = NlsSortCounted(x, r, "compression_locales", 4, &n);
|
|
unsigned *offset = YamlAllocateMemory(n * sizeof(*offset)), *two = YamlAllocateMemory(n * sizeof(*two)),
|
|
*three = YamlAllocateMemory(n * sizeof(*three));
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
offset[i] = NlsNum(x, v, "word_offset", 4, 0);
|
|
two[i] = NlsNum(x, v, "two_character_count", 2, 0);
|
|
three[i] = NlsNum(x, v, "three_character_count", 2, 0);
|
|
}
|
|
YAML_NODE *groups = NlsGroup(x, r, "compression_groups", YAML_MAP);
|
|
unsigned cursor = 0, done = 0;
|
|
unsigned char *used = YamlAllocateMemory(n);
|
|
while(done < n)
|
|
{
|
|
unsigned best = UINT32_MAX, idx = 0;
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
if(!used[i] && offset[i] < best)
|
|
{
|
|
best = offset[i];
|
|
idx = i;
|
|
}
|
|
}
|
|
if(best != cursor)
|
|
{
|
|
YamlError("noncontiguous compression groups");
|
|
}
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
if(offset[i] == best)
|
|
{
|
|
if(two[i] != two[idx] || three[i] != three[idx])
|
|
{
|
|
YamlError("conflicting shared compression lengths");
|
|
}
|
|
used[i] = 1;
|
|
done++;
|
|
}
|
|
}
|
|
YAML_NODE *g = NlsGroup(x, groups, YamlFormatString("%u", best), YAML_MAP);
|
|
for(unsigned k = 0; k < 2; k++)
|
|
{
|
|
unsigned count = k ? three[idx] : two[idx];
|
|
YAML_NODE *items = NlsGroup(x, g, k ? "three_characters" : "two_characters", YAML_SEQUENCE);
|
|
if(!x->reading && items->count != count)
|
|
{
|
|
YamlError("compression entry count mismatch");
|
|
}
|
|
for(unsigned j = 0; j < count; j++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, items, j);
|
|
NlsSortCharacter(x, v, "first");
|
|
NlsSortCharacter(x, v, "second");
|
|
if(k)
|
|
{
|
|
NlsSortCharacter(x, v, "third");
|
|
NlsNum(x, v, "padding", 2, 1);
|
|
}
|
|
NlsSortWeight(x, v);
|
|
}
|
|
}
|
|
cursor += 4 * two[idx] + 6 * three[idx];
|
|
}
|
|
s = NlsSortCounted(x, r, "exception_locales", 4, &n);
|
|
offset = YamlAllocateMemory(n * sizeof(*offset));
|
|
unsigned *counts = YamlAllocateMemory(n * sizeof(*counts));
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
offset[i] = NlsNum(x, v, "word_offset", 4, 0);
|
|
counts[i] = NlsNum(x, v, "entry_count", 4, 0);
|
|
if(counts[i] > 65536)
|
|
{
|
|
YamlError("too many sort exceptions");
|
|
}
|
|
}
|
|
groups = NlsGroup(x, r, "exception_groups", YAML_MAP);
|
|
used = YamlAllocateMemory(n);
|
|
cursor = done = 0;
|
|
while(done < n)
|
|
{
|
|
unsigned best = UINT32_MAX, idx = 0;
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
if(!used[i] && offset[i] < best)
|
|
{
|
|
best = offset[i];
|
|
idx = i;
|
|
}
|
|
}
|
|
if(best != cursor)
|
|
{
|
|
YamlError("noncontiguous exception groups");
|
|
}
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
if(offset[i] == best)
|
|
{
|
|
if(counts[i] != counts[idx])
|
|
{
|
|
YamlError("conflicting shared sort exceptions");
|
|
}
|
|
used[i] = 1;
|
|
done++;
|
|
}
|
|
}
|
|
YAML_NODE *items = NlsGroup(x, groups, YamlFormatString("%u", best), YAML_SEQUENCE);
|
|
if(!x->reading && items->count != counts[idx])
|
|
{
|
|
YamlError("sort exception count mismatch");
|
|
}
|
|
for(unsigned j = 0; j < counts[idx]; j++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, items, j);
|
|
NlsSortCharacter(x, v, "character");
|
|
NlsSortWeight(x, v);
|
|
}
|
|
cursor += 3 * counts[idx];
|
|
}
|
|
s = NlsSortCounted(x, r, "multiple_weights", 2, &n);
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "script", 1, 0);
|
|
NlsNum(x, v, "weight_count", 1, 0);
|
|
}
|
|
s = NlsSortCounted(x, r, "jamo", 4, &n);
|
|
if(n != 256)
|
|
{
|
|
YamlError("unsupported legacy Jamo table size");
|
|
}
|
|
for(unsigned i = 0; i < n; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
for(unsigned j = 0; j < 5; j++)
|
|
{
|
|
NlsNum(x, v, YamlFormatString("weight_%u", j), 1, 0);
|
|
}
|
|
NlsNum(x, v, "offset", 1, 0);
|
|
NlsNum(x, v, "length", 1, 0);
|
|
NlsNum(x, v, "reserved", 1, 0);
|
|
}
|
|
s = NlsGroup(x, r, "jamo_second_characters", YAML_SEQUENCE);
|
|
if(!x->reading && s->count != 256)
|
|
{
|
|
YamlError("invalid second Jamo table length");
|
|
}
|
|
for(unsigned i = 0; i < 256; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsSortCharacter(x, v, "character");
|
|
for(unsigned j = 0; j < 5; j++)
|
|
{
|
|
NlsNum(x, v, YamlFormatString("weight_%u", j), 1, 0);
|
|
}
|
|
NlsNum(x, v, "length", 1, 0);
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
const char *
|
|
NlsLocalePoolPrefix(char type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case 'T':
|
|
return "text_";
|
|
case 'A':
|
|
return "array_";
|
|
case 'F':
|
|
return "font_";
|
|
case 'E':
|
|
return "eras_";
|
|
case 'R':
|
|
return "era_";
|
|
default:
|
|
YamlError("invalid locale pool type");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static
|
|
char *
|
|
NlsLocalePoolKey(char type,
|
|
unsigned offset)
|
|
{
|
|
return YamlFormatString("%s%u", NlsLocalePoolPrefix(type), offset);
|
|
}
|
|
|
|
static
|
|
unsigned
|
|
NlsLocalePoolOffset(const char *key,
|
|
char type)
|
|
{
|
|
const char *prefix = NlsLocalePoolPrefix(type);
|
|
size_t n = strlen(prefix);
|
|
if(strncmp(key, prefix, n))
|
|
{
|
|
YamlError("invalid locale reference: %s", key);
|
|
}
|
|
return YamlParseNumber(key + n, 10, YAML_MAX_BINARY / 2);
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocaleRequire(Pool *p,
|
|
char type,
|
|
unsigned offset)
|
|
{
|
|
if(offset >= p->words)
|
|
{
|
|
YamlError("locale pool reference outside section");
|
|
}
|
|
char *key = NlsLocalePoolKey(type, offset);
|
|
if(!YamlFindField(p->needed, key))
|
|
{
|
|
YamlAddField(p->needed, key, YamlCreateInteger((unsigned char)type));
|
|
}
|
|
}
|
|
|
|
static
|
|
unsigned
|
|
NlsLocaleReference(Pool *p,
|
|
YAML_NODE *r,
|
|
const char *key,
|
|
unsigned width,
|
|
char type)
|
|
{
|
|
unsigned offset;
|
|
if(p->x->reading)
|
|
{
|
|
offset = NlsRaw(p->x, width, 0);
|
|
NlsField(p->x, r, key, YamlCreateString(NlsLocalePoolKey(type, offset)));
|
|
}
|
|
else
|
|
{
|
|
offset = NlsLocalePoolOffset(YamlGetScalar(NlsField(p->x, r, key, NULL)), type);
|
|
NlsRaw(p->x, width, offset);
|
|
}
|
|
NlsLocaleRequire(p, type, offset);
|
|
return offset;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocaleWritePool(Pool *p,
|
|
size_t byte_offset,
|
|
const unsigned char *data,
|
|
size_t bytes)
|
|
{
|
|
if(byte_offset > p->words * 2 || bytes > p->words * 2 - byte_offset)
|
|
{
|
|
YamlError("locale value exceeds pool boundaries");
|
|
}
|
|
for(size_t i = 0; i < bytes; i++)
|
|
{
|
|
size_t at = byte_offset + i;
|
|
if(p->used[at] && p->data[at] != data[i])
|
|
{
|
|
YamlError("locale edit conflicts with shared pool storage at byte %zu", at);
|
|
}
|
|
p->used[at] = 1;
|
|
p->data[at] = data[i];
|
|
}
|
|
}
|
|
|
|
static
|
|
unsigned
|
|
NlsLocalePoolWord(Pool *p,
|
|
size_t at)
|
|
{
|
|
if(at >= p->words)
|
|
{
|
|
YamlError("truncated locale pool value");
|
|
}
|
|
return ReadUInt16(p->source + 2 * at);
|
|
}
|
|
|
|
static
|
|
unsigned
|
|
NlsLocalePoolDword(Pool *p,
|
|
size_t at)
|
|
{
|
|
return NlsLocalePoolWord(p, at) | (NlsLocalePoolWord(p, at + 1) << 16);
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocalePoolValues(Pool *p,
|
|
YAML_NODE *r)
|
|
{
|
|
NlsIO *x = p->x;
|
|
YAML_NODE *values = NlsGroup(x, r, "values", YAML_MAP);
|
|
/* Arrays can add references to strings; consume the growing work list. */
|
|
for(size_t i = 0; i < p->needed->count; i++)
|
|
{
|
|
const char *key = p->needed->keys[i];
|
|
char type = (char)YamlGetUnsigned(p->needed->values[i], 255);
|
|
unsigned at = NlsLocalePoolOffset(key, type);
|
|
YAML_NODE *v = NlsGroup(x, values, key, YAML_MAP);
|
|
YAML_BUFFER b = {0};
|
|
unsigned len = x->reading ? NlsLocalePoolWord(p, at) : 0;
|
|
if(type == 'T')
|
|
{
|
|
YAML_NODE *text;
|
|
if(x->reading)
|
|
{
|
|
if((size_t)at + len + 1 >= p->words)
|
|
{
|
|
YamlError("unterminated locale text");
|
|
}
|
|
text = NlsField(x, v, "text", YamlCreateString(NlsUtf8(p->source + 2 * (at + 1), len)));
|
|
}
|
|
else
|
|
{
|
|
text = NlsField(x, v, "text", NULL);
|
|
}
|
|
YAML_BUFFER text16 = NlsUtf16(YamlGetScalar(text));
|
|
if(text16.len / 2 > 65535)
|
|
{
|
|
YamlError("locale text too long");
|
|
}
|
|
WriteUInt16(&b, (uint32_t)(text16.len / 2));
|
|
YamlAppendBuffer(&b, text16.data, text16.len);
|
|
unsigned end;
|
|
if(x->reading)
|
|
{
|
|
end = NlsLocalePoolWord(p, at + len + 1);
|
|
NlsField(x, v, "terminator", YamlCreateInteger(end));
|
|
}
|
|
else
|
|
{
|
|
end = YamlGetUnsigned(NlsField(x, v, "terminator", NULL), 65535);
|
|
}
|
|
if(end)
|
|
{
|
|
YamlError("nonzero locale text terminator");
|
|
}
|
|
WriteUInt16(&b, end);
|
|
}
|
|
else if(type == 'R')
|
|
{
|
|
if(x->reading && len != 6)
|
|
{
|
|
YamlError("unsupported calendar era record");
|
|
}
|
|
WriteUInt16(&b, 6);
|
|
const char *names[] = {
|
|
"era_id", "start_year", "start_month", "start_day", "year_offset", "first_year", "terminator"};
|
|
for(unsigned j = 0; j < 7; j++)
|
|
{
|
|
unsigned val;
|
|
if(x->reading)
|
|
{
|
|
val = NlsLocalePoolWord(p, at + 1 + j);
|
|
YAML_NODE *item = YamlCreateInteger(val);
|
|
if(j == 4 && val >= 32768)
|
|
{
|
|
item = YamlCreateString(YamlFormatString("%d", (int)val - 65536));
|
|
item->IsInteger = 1;
|
|
}
|
|
NlsField(x, v, names[j], item);
|
|
}
|
|
else
|
|
{
|
|
YAML_NODE *item = NlsField(x, v, names[j], NULL);
|
|
const char *str = YamlGetScalar(item);
|
|
if(j == 4 && str[0] == '-')
|
|
{
|
|
val = (65536 - YamlParseNumber(str + 1, 10, 32768)) & 65535;
|
|
}
|
|
else
|
|
{
|
|
val = YamlGetUnsigned(item, j == 4 ? 32767 : 65535);
|
|
}
|
|
}
|
|
if(j == 6 && val)
|
|
{
|
|
YamlError("invalid era terminator");
|
|
}
|
|
WriteUInt16(&b, val);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int array = type == 'A' || type == 'E';
|
|
char child = type == 'E' ? 'R' : 'T';
|
|
YAML_NODE *items = NlsGroup(x, v, array ? "items" : "uint32_values", YAML_SEQUENCE);
|
|
if(x->reading)
|
|
{
|
|
if(type == 'F' && len % 2)
|
|
{
|
|
YamlError("odd font signature size");
|
|
}
|
|
unsigned count = array ? len : len / 2;
|
|
for(unsigned j = 0; j < count; j++)
|
|
{
|
|
unsigned value = NlsLocalePoolDword(p, at + 1 + 2 * j);
|
|
if(array)
|
|
{
|
|
NlsLocaleRequire(p, child, value);
|
|
YamlAppendItem(items, YamlCreateString(NlsLocalePoolKey(child, value)));
|
|
}
|
|
else
|
|
{
|
|
YamlAppendItem(items, YamlCreateHexadecimal(value, 8));
|
|
}
|
|
}
|
|
}
|
|
if(items->count > (array ? 65535 : 32767))
|
|
{
|
|
YamlError("locale array too large");
|
|
}
|
|
WriteUInt16(&b, (uint32_t)items->count * (array ? 1 : 2));
|
|
for(size_t j = 0; j < items->count; j++)
|
|
{
|
|
unsigned value = array ? NlsLocalePoolOffset(YamlGetScalar(items->values[j]), child)
|
|
: YamlGetHexadecimal(items->values[j], UINT32_MAX);
|
|
if(array)
|
|
{
|
|
NlsLocaleRequire(p, child, value);
|
|
}
|
|
WriteUInt32(&b, value);
|
|
}
|
|
}
|
|
NlsLocaleWritePool(p, 2 * at, b.data, b.len);
|
|
}
|
|
YAML_NODE *unused = NlsGroup(x, r, "unreferenced_words", YAML_MAP);
|
|
if(x->reading)
|
|
{
|
|
for(size_t i = 0; i < p->words; i++)
|
|
{
|
|
if(p->used[2 * i] != p->used[2 * i + 1])
|
|
{
|
|
YamlError("unaligned locale pool coverage");
|
|
}
|
|
if(!p->used[2 * i])
|
|
{
|
|
YamlAddField(unused, YamlFormatString("%zu", i), YamlCreateInteger(NlsLocalePoolWord(p, i)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unused->length = unused->count;
|
|
for(size_t i = 0; i < unused->count; i++)
|
|
{
|
|
unsigned at = YamlParseNumber(unused->keys[i], 10, (unsigned)p->words - 1),
|
|
value = YamlGetUnsigned(unused->values[i], 65535);
|
|
if(p->used[2 * at] || p->used[2 * at + 1])
|
|
{
|
|
YamlError("unused word overlaps locale value");
|
|
}
|
|
unsigned char b[2] = {(unsigned char)value, (unsigned char)(value >> 8)};
|
|
NlsLocaleWritePool(p, 2 * at, b, 2);
|
|
}
|
|
for(size_t i = 0; i < p->words * 2; i++)
|
|
{
|
|
if(!p->used[i])
|
|
{
|
|
YamlError("locale pool has missing storage");
|
|
}
|
|
}
|
|
}
|
|
if(x->reading)
|
|
{
|
|
for(size_t i = 0; i < p->words * 2; i++)
|
|
{
|
|
if(p->used[i] && p->source[i] != p->data[i])
|
|
{
|
|
YamlError("locale pool semantic reconstruction mismatch");
|
|
}
|
|
}
|
|
x->pos += p->words * 2;
|
|
}
|
|
else
|
|
{
|
|
YamlAppendBuffer(&x->out, p->data, p->words * 2);
|
|
x->pos += p->words * 2;
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocalePosition(NlsIO *x,
|
|
size_t at)
|
|
{
|
|
if(x->pos != at)
|
|
{
|
|
YamlError("unsupported locale layout at %zu; expected %zu", x->pos, at);
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocaleLocaleDatabase(NlsIO *x,
|
|
YAML_NODE *r,
|
|
size_t section_size)
|
|
{
|
|
size_t start = x->pos;
|
|
YAML_NODE *h = NlsGroup(x, r, "header", YAML_MAP);
|
|
NlsNum(x, h, "version_offset", 4, 0);
|
|
NlsNum(x, h, "unknown_04", 4, 1);
|
|
unsigned version = NlsNum(x, h, "version", 4, 0), magic = NlsNum(x, h, "signature", 4, 1);
|
|
if(version != 7 || magic != 0x5344534e)
|
|
{
|
|
YamlError("unsupported locale database version");
|
|
}
|
|
for(unsigned i = 0; i < 3; i++)
|
|
{
|
|
NlsNum(x, h, YamlFormatString("unknown_%02x", 16 + 4 * i), 4, 1);
|
|
}
|
|
NlsNum(x, h, "header_size", 2, 0);
|
|
unsigned lcids = NlsNum(x, h, "lcid_count", 2, 0), locales = NlsNum(x, h, "locale_count", 2, 0),
|
|
locale_size = NlsNum(x, h, "locale_record_size", 2, 0);
|
|
unsigned locale_at = NlsNum(x, h, "locales_offset", 4, 0), names = NlsNum(x, h, "name_count", 2, 0);
|
|
NlsNum(x, h, "padding", 2, 1);
|
|
unsigned ids_at = NlsNum(x, h, "lcids_offset", 4, 0), names_at = NlsNum(x, h, "names_offset", 4, 0);
|
|
NlsNum(x, h, "unknown_34", 4, 1);
|
|
unsigned calendars = NlsNum(x, h, "calendar_count", 2, 0),
|
|
calendar_size = NlsNum(x, h, "calendar_record_size", 2, 0),
|
|
cal_at = NlsNum(x, h, "calendars_offset", 4, 0), strings_at = NlsNum(x, h, "strings_offset", 4, 0);
|
|
for(unsigned i = 0; i < 4; i++)
|
|
{
|
|
NlsNum(x, h, YamlFormatString("unknown_%02x", 68 + 2 * i), 2, 1);
|
|
}
|
|
if(locale_size != 328 || calendar_size != 72 || strings_at > section_size || (section_size - strings_at) % 2)
|
|
{
|
|
YamlError("unsupported locale record layout");
|
|
}
|
|
Pool pool = {.x = x, .needed = YamlCreateNode(YAML_MAP), .words = (section_size - strings_at) / 2};
|
|
if(x->reading)
|
|
{
|
|
NlsBounds(x, section_size - (x->pos - start));
|
|
pool.source = x->p + start + strings_at;
|
|
}
|
|
pool.data = YamlAllocateMemory(pool.words * 2);
|
|
pool.used = YamlAllocateMemory(pool.words * 2);
|
|
NlsLocalePosition(x, start + ids_at);
|
|
YAML_NODE *s = NlsGroup(x, r, "lcid_index", YAML_SEQUENCE);
|
|
if(!x->reading && s->count != lcids)
|
|
{
|
|
YamlError("locale index count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < lcids; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
unsigned idx = NlsNum(x, v, "locale_index", 2, 0);
|
|
if(idx >= locales)
|
|
{
|
|
YamlError("LCID index outside locales");
|
|
}
|
|
NlsLocaleReference(&pool, v, "name", 2, 'T');
|
|
}
|
|
NlsLocalePosition(x, start + names_at);
|
|
s = NlsGroup(x, r, "name_index", YAML_SEQUENCE);
|
|
if(!x->reading && s->count != names)
|
|
{
|
|
YamlError("locale name count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < names; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsLocaleReference(&pool, v, "name", 2, 'T');
|
|
unsigned idx = NlsNum(x, v, "locale_index", 2, 0);
|
|
if(idx >= locales)
|
|
{
|
|
YamlError("name index outside locales");
|
|
}
|
|
NlsNum(x, v, "lcid", 4, 1);
|
|
}
|
|
NlsLocalePosition(x, start + locale_at);
|
|
s = NlsGroup(x, r, "locales", YAML_SEQUENCE);
|
|
if(!x->reading && s->count != locales)
|
|
{
|
|
YamlError("locale count mismatch");
|
|
}
|
|
for(unsigned i = 0; i < locales; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
for(size_t j = 0; j < sizeof(locale_fields) / sizeof(locale_fields[0]); j++)
|
|
{
|
|
const LocaleField *f = &locale_fields[j];
|
|
if(f->ref)
|
|
{
|
|
NlsLocaleReference(&pool, v, f->name, f->width, f->ref);
|
|
}
|
|
else
|
|
{
|
|
NlsNum(x, v, f->name, f->width, 0);
|
|
}
|
|
}
|
|
}
|
|
NlsLocalePosition(x, start + cal_at);
|
|
s = NlsGroup(x, r, "calendars", YAML_SEQUENCE);
|
|
if(!x->reading && s->count != calendars)
|
|
{
|
|
YamlError("calendar count mismatch");
|
|
}
|
|
const char *calfields[] = {"short_date",
|
|
"year_month",
|
|
"long_date",
|
|
"era_names",
|
|
"year_offset_range",
|
|
"day_names",
|
|
"abbreviated_day_names",
|
|
"month_names",
|
|
"abbreviated_month_names",
|
|
"name",
|
|
"month_day",
|
|
"abbreviated_era_names",
|
|
"shortest_day_names",
|
|
"relative_long_date"};
|
|
for(unsigned i = 0; i < calendars; i++)
|
|
{
|
|
YAML_NODE *v = NlsItem(x, s, i);
|
|
NlsNum(x, v, "calendar_id", 2, 0);
|
|
NlsNum(x, v, "two_digit_year_max", 2, 0);
|
|
for(unsigned j = 0; j < 14; j++)
|
|
{
|
|
NlsLocaleReference(&pool, v, calfields[j], 4, j == 4 ? 'E' : (j == 9 || j == 13) ? 'T' : 'A');
|
|
}
|
|
for(unsigned j = 0; j < 3; j++)
|
|
{
|
|
NlsNum(x, v, YamlFormatString("unused_%u", j), 4, 1);
|
|
}
|
|
}
|
|
NlsLocalePosition(x, start + strings_at);
|
|
YAML_NODE *p = NlsGroup(x, r, "string_pool", YAML_MAP);
|
|
NlsLocalePoolValues(&pool, p);
|
|
NlsLocalePosition(x, start + section_size);
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsLocale(NlsIO *x,
|
|
YAML_NODE *r)
|
|
{
|
|
YAML_NODE *h = NlsGroup(x, r, "sections", YAML_MAP);
|
|
unsigned ctypes = NlsNum(x, h, "character_types_offset", 4, 0);
|
|
for(unsigned i = 0; i < 3; i++)
|
|
{
|
|
if(NlsNum(x, h, YamlFormatString("reserved_%u", i), 4, 1))
|
|
{
|
|
YamlError("unsupported extra locale section");
|
|
}
|
|
}
|
|
unsigned locales = NlsNum(x, h, "locales_offset", 4, 0), charmaps = NlsNum(x, h, "character_maps_offset", 4, 0),
|
|
geo = NlsNum(x, h, "geography_offset", 4, 0), scripts = NlsNum(x, h, "scripts_offset", 4, 0);
|
|
if(ctypes != 32 || ctypes > locales || locales > charmaps || charmaps > geo || geo > scripts ||
|
|
scripts > YAML_MAX_BINARY)
|
|
{
|
|
YamlError("invalid locale section offsets");
|
|
}
|
|
NlsLocalePosition(x, ctypes);
|
|
NlsCtype(x, NlsGroup(x, r, "character_types", YAML_MAP));
|
|
if(x->pos > locales || (locales - x->pos) % 2 || locales - x->pos > 16)
|
|
{
|
|
YamlError("invalid character type alignment");
|
|
}
|
|
size_t padding = (locales - x->pos) / 2;
|
|
YAML_NODE *pad = NlsGroup(x, r, "character_types_alignment", YAML_SEQUENCE);
|
|
if(!x->reading && pad->count != padding)
|
|
{
|
|
YamlError("character type alignment size mismatch");
|
|
}
|
|
for(size_t i = 0; i < padding; i++)
|
|
{
|
|
if(x->reading)
|
|
{
|
|
YamlAppendItem(pad, YamlCreateInteger(NlsRaw(x, 2, 0)));
|
|
}
|
|
else
|
|
{
|
|
NlsRaw(x, 2, YamlGetUnsigned(pad->values[i], 65535));
|
|
}
|
|
}
|
|
NlsLocalePosition(x, locales);
|
|
NlsLocaleLocaleDatabase(x, NlsGroup(x, r, "regional_settings", YAML_MAP), charmaps - locales);
|
|
NlsLocalePosition(x, charmaps);
|
|
NlsUnicode(x, NlsGroup(x, r, "character_maps", YAML_MAP), 1);
|
|
NlsLocalePosition(x, geo);
|
|
NlsGeo(x, NlsGroup(x, r, "geography", YAML_MAP));
|
|
NlsLocalePosition(x, scripts);
|
|
|
|
if(x->reading && x->pos != x->n)
|
|
{
|
|
YamlError("unsupported nonempty locale script section");
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
int
|
|
NlsCodecProbeCp(const unsigned char *p,
|
|
size_t len,
|
|
Codepage *c)
|
|
{
|
|
memset(c, 0, sizeof(*c));
|
|
if(len < 546 || ReadUInt16(p) != 13)
|
|
{
|
|
return 0;
|
|
}
|
|
c->size = ReadUInt16(p + 4);
|
|
if(c->size != 1 && c->size != 2)
|
|
{
|
|
return 0;
|
|
}
|
|
c->flag = 26 + 2 * ReadUInt16(p + 26);
|
|
c->wide = c->flag + 2;
|
|
c->end = c->wide + 65536 * c->size;
|
|
if(c->end > len)
|
|
{
|
|
return 0;
|
|
}
|
|
c->marker = ReadUInt16(p + 540);
|
|
c->glyph = 542;
|
|
c->range_pos = 542 + (c->marker ? 512 : 0);
|
|
if(c->range_pos + 2 > c->flag)
|
|
{
|
|
return 0;
|
|
}
|
|
c->ranges = ReadUInt16(p + c->range_pos);
|
|
size_t pos = c->range_pos + 2;
|
|
if(c->ranges)
|
|
{
|
|
if(c->size != 2 || pos + 512 > c->flag)
|
|
{
|
|
return 0;
|
|
}
|
|
c->dbcs_base = pos;
|
|
pos += 512;
|
|
for(unsigned lead = 0; lead < 256; lead++)
|
|
{
|
|
uint32_t off = ReadUInt16(p + c->dbcs_base + 2 * lead);
|
|
if(!off)
|
|
{
|
|
continue;
|
|
}
|
|
size_t addr = c->dbcs_base + off * 2;
|
|
if(addr < pos || addr + 512 > c->flag)
|
|
{
|
|
return 0;
|
|
}
|
|
unsigned i = 0;
|
|
while(i < c->block_count && c->blocks[i] < addr)
|
|
{
|
|
i++;
|
|
}
|
|
if(i < c->block_count && c->blocks[i] == addr)
|
|
{
|
|
continue;
|
|
}
|
|
for(unsigned j = c->block_count; j > i; j--)
|
|
{
|
|
c->blocks[j] = c->blocks[j - 1];
|
|
c->leads[j] = c->leads[j - 1];
|
|
}
|
|
c->blocks[i] = addr;
|
|
c->leads[i] = lead;
|
|
c->block_count++;
|
|
}
|
|
for(unsigned i = 0; i < c->block_count; i++)
|
|
{
|
|
if(c->blocks[i] < pos)
|
|
{
|
|
return 0;
|
|
}
|
|
pos = c->blocks[i] + 512;
|
|
}
|
|
}
|
|
return pos <= c->flag;
|
|
}
|
|
|
|
static
|
|
int
|
|
NlsCodecCompareU32(const void *a,
|
|
const void *b)
|
|
{
|
|
uint32_t x = *(const uint32_t *)a, y = *(const uint32_t *)b;
|
|
return (x > y) - (x < y);
|
|
}
|
|
|
|
static
|
|
uint32_t *
|
|
NlsCodecReadValues(const unsigned char *p,
|
|
size_t count,
|
|
unsigned width)
|
|
{
|
|
uint32_t *values = YamlAllocateMemory(count * sizeof(*values));
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
values[i] = width == 1 ? p[i] : width == 2 ? ReadUInt16(p + 2 * i) : ReadUInt32(p + 4 * i);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
static
|
|
uint32_t
|
|
NlsCodecMode(const uint32_t *values,
|
|
size_t count)
|
|
{
|
|
uint32_t *sorted = YamlAllocateMemory(count * sizeof(*sorted));
|
|
memcpy(sorted, values, count * sizeof(*sorted));
|
|
qsort(sorted, count, sizeof(*sorted), NlsCodecCompareU32);
|
|
size_t best = 0;
|
|
uint32_t result = 0;
|
|
for(size_t i = 0; i < count;)
|
|
{
|
|
size_t end = i + 1;
|
|
while(end < count && sorted[end] == sorted[i])
|
|
{
|
|
end++;
|
|
}
|
|
if(end - i > best)
|
|
{
|
|
best = end - i;
|
|
result = sorted[i];
|
|
}
|
|
i = end;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsCodecSparse(const uint32_t *values,
|
|
size_t count,
|
|
int direction,
|
|
unsigned width,
|
|
uint32_t def)
|
|
{
|
|
YAML_NODE *n = YamlCreateNode(YAML_MAP), *map = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(n, "default", direction ? YamlCreateHexadecimal(def, width * 2) : YamlCreateCodepoint(def));
|
|
YamlAddField(n, "map", map);
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
if(values[i] != def)
|
|
{
|
|
YamlAddField(map,
|
|
direction ? YamlFormatString("U+%04X", (unsigned)i) : YamlFormatString("0x%02X", (unsigned)i),
|
|
direction ? YamlCreateHexadecimal(values[i], width * 2) : YamlCreateCodepoint(values[i]));
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static
|
|
uint32_t *
|
|
NlsCodecExpand(YAML_NODE *n,
|
|
size_t count,
|
|
int direction,
|
|
uint32_t max)
|
|
{
|
|
YamlValidateFields(n, "default map", "");
|
|
uint32_t def =
|
|
direction ? YamlGetHexadecimal(YamlGetField(n, "default"), max) : YamlGetCodepoint(YamlGetField(n, "default"));
|
|
uint32_t *values = YamlAllocateMemory(count * sizeof(*values));
|
|
unsigned char *seen = YamlAllocateMemory(count);
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
values[i] = def;
|
|
}
|
|
YAML_NODE *map = YamlGetField(n, "map");
|
|
if(map->type != YAML_MAP || map->count > count)
|
|
{
|
|
YamlError("invalid sparse mapping");
|
|
}
|
|
for(size_t i = 0; i < map->count; i++)
|
|
{
|
|
YAML_NODE key = {.type = YAML_SCALAR, .text = map->keys[i]};
|
|
uint32_t index = direction ? YamlGetCodepoint(&key) : YamlGetHexadecimal(&key, 255);
|
|
if(index >= count || seen[index])
|
|
{
|
|
YamlError("duplicate or out-of-range normalized mapping key");
|
|
}
|
|
seen[index] = 1;
|
|
values[index] = direction ? YamlGetHexadecimal(map->values[i], max) : YamlGetCodepoint(map->values[i]);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsCodecWriteValues(YAML_BUFFER *out,
|
|
const uint32_t *values,
|
|
size_t count,
|
|
unsigned width)
|
|
{
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
if(width == 1)
|
|
{
|
|
if(values[i] > 255)
|
|
{
|
|
YamlError("byte overflow");
|
|
}
|
|
unsigned char c = (unsigned char)values[i];
|
|
YamlAppendBuffer(out, &c, 1);
|
|
}
|
|
else if(width == 2)
|
|
{
|
|
WriteUInt16(out, values[i]);
|
|
}
|
|
else
|
|
{
|
|
WriteUInt32(out, values[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsCodecBlobAppend(YAML_BUFFER *out,
|
|
YAML_NODE *n)
|
|
{
|
|
if(!n || n->type != YAML_BINARY)
|
|
{
|
|
YamlError("expected YAML !!binary");
|
|
}
|
|
YamlAppendBuffer(out, n->bytes, n->length);
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsCodecDecodeCp(const unsigned char *p,
|
|
size_t len,
|
|
Codepage *c)
|
|
{
|
|
YAML_NODE *root = YamlCreateNode(YAML_MAP), *h = YamlCreateNode(YAML_MAP), *ranges = YamlCreateNode(YAML_SEQUENCE),
|
|
*layout = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(root, "header", h);
|
|
YamlAddField(h, "code_page", YamlCreateInteger(ReadUInt16(p + 2)));
|
|
YamlAddField(h, "max_char_size", YamlCreateInteger(c->size));
|
|
YamlAddField(h, "default_encoded", YamlCreateHexadecimal(ReadUInt16(p + 6), c->size * 2));
|
|
YamlAddField(h, "default_unicode", YamlCreateCodepoint(ReadUInt16(p + 8)));
|
|
YamlAddField(h, "translated_default", YamlCreateCodepoint(ReadUInt16(p + 10)));
|
|
YamlAddField(h, "translated_unicode_default", YamlCreateHexadecimal(ReadUInt16(p + 12), c->size * 2));
|
|
unsigned pairs = 6;
|
|
while(pairs && !p[14 + 2 * (pairs - 1)] && !p[15 + 2 * (pairs - 1)])
|
|
{
|
|
pairs--;
|
|
}
|
|
for(unsigned i = 0; i < pairs; i++)
|
|
{
|
|
YAML_NODE *pair = YamlCreateNode(YAML_SEQUENCE);
|
|
YamlAppendItem(pair, YamlCreateHexadecimal(p[14 + i * 2], 2));
|
|
YamlAppendItem(pair, YamlCreateHexadecimal(p[15 + i * 2], 2));
|
|
YamlAppendItem(ranges, pair);
|
|
}
|
|
YamlAddField(h, "lead_byte_ranges", ranges);
|
|
YamlAddField(root, "to_unicode", NlsCodecSparse(NlsCodecReadValues(p + 28, 256, 2), 256, 0, 2, ReadUInt16(p + 8)));
|
|
uint32_t *wide = NlsCodecReadValues(p + c->wide, 65536, c->size);
|
|
YamlAddField(root, "from_unicode", NlsCodecSparse(wide, 65536, 1, c->size, NlsCodecMode(wide, 65536)));
|
|
if(c->marker)
|
|
{
|
|
YamlAddField(
|
|
root, "glyphs", NlsCodecSparse(NlsCodecReadValues(p + c->glyph, 256, 2), 256, 0, 2, ReadUInt16(p + 8)));
|
|
}
|
|
YamlAddField(layout, "glyph_marker", YamlCreateInteger(c->marker));
|
|
YamlAddField(layout, "dbcs_range_count", YamlCreateInteger(c->ranges));
|
|
YamlAddField(layout, "wide_table_flag", YamlCreateInteger(ReadUInt16(p + c->flag)));
|
|
size_t pos = c->range_pos + 2;
|
|
if(c->ranges)
|
|
{
|
|
YAML_NODE *dbcs = YamlCreateNode(YAML_MAP), *leads = YamlCreateNode(YAML_MAP),
|
|
*tables = YamlCreateNode(YAML_MAP), *storage = YamlCreateNode(YAML_SEQUENCE);
|
|
YamlAddField(root, "dbcs", dbcs);
|
|
YamlAddField(dbcs, "lead_bytes", leads);
|
|
YamlAddField(dbcs, "tables", tables);
|
|
for(unsigned lead = 0; lead < 256; lead++)
|
|
{
|
|
uint32_t off = ReadUInt16(p + c->dbcs_base + lead * 2);
|
|
if(!off)
|
|
{
|
|
continue;
|
|
}
|
|
for(unsigned j = 0; j < c->block_count; j++)
|
|
{
|
|
if(c->blocks[j] == c->dbcs_base + off * 2)
|
|
{
|
|
YamlAddField(leads,
|
|
YamlFormatString("0x%02X", lead),
|
|
YamlCreateString(YamlFormatString("DBCS_%02X", c->leads[j])));
|
|
}
|
|
}
|
|
}
|
|
pos = c->dbcs_base + 512;
|
|
for(unsigned i = 0; i < c->block_count; i++)
|
|
{
|
|
if(c->blocks[i] > pos)
|
|
{
|
|
YAML_NODE *gap = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(gap, "padding", YamlCreateBinary(p + pos, c->blocks[i] - pos));
|
|
YamlAppendItem(storage, gap);
|
|
}
|
|
char *name = YamlFormatString("DBCS_%02X", c->leads[i]);
|
|
uint32_t *v = NlsCodecReadValues(p + c->blocks[i], 256, 2);
|
|
YamlAddField(tables, name, NlsCodecSparse(v, 256, 0, 2, NlsCodecMode(v, 256)));
|
|
YAML_NODE *part = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(part, "table", YamlCreateString(name));
|
|
YamlAppendItem(storage, part);
|
|
pos = c->blocks[i] + 512;
|
|
}
|
|
YamlAddField(layout, "dbcs_storage", storage);
|
|
}
|
|
if(pos < c->flag)
|
|
{
|
|
YamlAddField(layout, "pre_wide_padding", YamlCreateBinary(p + pos, c->flag - pos));
|
|
}
|
|
if(c->end < len)
|
|
{
|
|
YamlAddField(layout, "trailing_bytes", YamlCreateBinary(p + c->end, len - c->end));
|
|
}
|
|
YamlAddField(root, "binary_layout", layout);
|
|
return root;
|
|
}
|
|
|
|
static
|
|
YAML_BUFFER
|
|
NlsCodecEncodeCp(YAML_NODE *root)
|
|
{
|
|
YamlValidateFields(root, "header to_unicode from_unicode binary_layout", "glyphs dbcs");
|
|
YAML_NODE *h = YamlGetField(root, "header");
|
|
YamlValidateFields(h,
|
|
"code_page max_char_size default_encoded default_unicode translated_default "
|
|
"translated_unicode_default lead_byte_ranges",
|
|
"");
|
|
unsigned size = YamlGetUnsigned(YamlGetField(h, "max_char_size"), 2);
|
|
if(!size)
|
|
{
|
|
YamlError("max_char_size must be 1 or 2");
|
|
}
|
|
uint32_t max = size == 1 ? 255 : 65535;
|
|
YAML_BUFFER out = {0}, body = {0};
|
|
WriteUInt16(&out, 13);
|
|
WriteUInt16(&out, YamlGetUnsigned(YamlGetField(h, "code_page"), 65535));
|
|
WriteUInt16(&out, size);
|
|
WriteUInt16(&out, YamlGetHexadecimal(YamlGetField(h, "default_encoded"), max));
|
|
WriteUInt16(&out, YamlGetCodepoint(YamlGetField(h, "default_unicode")));
|
|
WriteUInt16(&out, YamlGetCodepoint(YamlGetField(h, "translated_default")));
|
|
WriteUInt16(&out, YamlGetHexadecimal(YamlGetField(h, "translated_unicode_default"), max));
|
|
YAML_NODE *ranges = YamlGetField(h, "lead_byte_ranges");
|
|
if(ranges->type != YAML_SEQUENCE || ranges->count > 6)
|
|
{
|
|
YamlError("expected up to six lead byte pairs");
|
|
}
|
|
unsigned char pairs[12] = {0};
|
|
for(size_t i = 0; i < ranges->count; i++)
|
|
{
|
|
YAML_NODE *pair = ranges->values[i];
|
|
if(pair->type != YAML_SEQUENCE || pair->count != 2)
|
|
{
|
|
YamlError("invalid lead byte pair");
|
|
}
|
|
pairs[i * 2] = (unsigned char)YamlGetHexadecimal(pair->values[0], 255);
|
|
pairs[i * 2 + 1] = (unsigned char)YamlGetHexadecimal(pair->values[1], 255);
|
|
}
|
|
YamlAppendBuffer(&out, pairs, 12);
|
|
NlsCodecWriteValues(&body, NlsCodecExpand(YamlGetField(root, "to_unicode"), 256, 0, 65535), 256, 2);
|
|
YAML_NODE *layout = YamlGetField(root, "binary_layout");
|
|
YamlValidateFields(
|
|
layout, "glyph_marker dbcs_range_count wide_table_flag", "dbcs_storage pre_wide_padding trailing_bytes");
|
|
uint32_t marker = YamlGetUnsigned(YamlGetField(layout, "glyph_marker"), 65535),
|
|
count = YamlGetUnsigned(YamlGetField(layout, "dbcs_range_count"), 65535);
|
|
YAML_NODE *glyphs = YamlFindField(root, "glyphs");
|
|
if(!!marker != !!glyphs)
|
|
{
|
|
YamlError("glyph marker and table disagree");
|
|
}
|
|
WriteUInt16(&body, marker);
|
|
if(glyphs)
|
|
{
|
|
NlsCodecWriteValues(&body, NlsCodecExpand(glyphs, 256, 0, 65535), 256, 2);
|
|
}
|
|
WriteUInt16(&body, count);
|
|
YAML_NODE *dbcs = YamlFindField(root, "dbcs"), *storage = YamlFindField(layout, "dbcs_storage");
|
|
if(!!count != !!dbcs || !!count != !!storage)
|
|
{
|
|
YamlError("DBCS metadata and tables disagree");
|
|
}
|
|
if(count)
|
|
{
|
|
if(size != 2 || storage->type != YAML_SEQUENCE)
|
|
{
|
|
YamlError("invalid DBCS structure");
|
|
}
|
|
YamlValidateFields(dbcs, "lead_bytes tables", "");
|
|
YAML_NODE *tables = YamlGetField(dbcs, "tables"), *leads = YamlGetField(dbcs, "lead_bytes"),
|
|
*positions = YamlCreateNode(YAML_MAP);
|
|
if(tables->type != YAML_MAP || leads->type != YAML_MAP || tables->count > 256 || leads->count > 256)
|
|
{
|
|
YamlError("invalid DBCS table count");
|
|
}
|
|
YAML_BUFFER block = {0};
|
|
for(size_t i = 0; i < storage->count; i++)
|
|
{
|
|
YAML_NODE *part = storage->values[i];
|
|
if(part->type != YAML_MAP || part->count != 1)
|
|
{
|
|
YamlError("invalid DBCS storage entry");
|
|
}
|
|
YAML_NODE *padding = YamlFindField(part, "padding");
|
|
if(padding)
|
|
{
|
|
NlsCodecBlobAppend(&block, padding);
|
|
}
|
|
else
|
|
{
|
|
YamlValidateFields(part, "table", "");
|
|
const char *name = YamlGetScalar(YamlGetField(part, "table"));
|
|
if(block.len % 2 || block.len / 2 + 256 > 65535)
|
|
{
|
|
YamlError("DBCS offset overflow or misalignment");
|
|
}
|
|
YamlAddField(positions, name, YamlCreateInteger(256 + (uint32_t)block.len / 2));
|
|
NlsCodecWriteValues(&block, NlsCodecExpand(YamlGetField(tables, name), 256, 0, 65535), 256, 2);
|
|
}
|
|
}
|
|
if(positions->count != tables->count)
|
|
{
|
|
YamlError("DBCS table missing from storage");
|
|
}
|
|
uint32_t offsets[256] = {0};
|
|
unsigned char used[256] = {0};
|
|
YAML_NODE *references = YamlCreateNode(YAML_MAP);
|
|
for(size_t i = 0; i < leads->count; i++)
|
|
{
|
|
YAML_NODE key = {.type = YAML_SCALAR, .text = leads->keys[i]};
|
|
uint32_t lead = YamlGetHexadecimal(&key, 255);
|
|
if(used[lead])
|
|
{
|
|
YamlError("duplicate normalized lead byte");
|
|
}
|
|
used[lead] = 1;
|
|
const char *name = YamlGetScalar(leads->values[i]);
|
|
offsets[lead] = YamlGetUnsigned(YamlGetField(positions, name), 65535);
|
|
if(!YamlFindField(references, name))
|
|
{
|
|
YamlAddField(references, name, YamlCreateInteger(1));
|
|
}
|
|
}
|
|
if(references->count != tables->count)
|
|
{
|
|
YamlError("unreferenced DBCS table");
|
|
}
|
|
NlsCodecWriteValues(&body, offsets, 256, 2);
|
|
YamlAppendBuffer(&body, block.data, block.len);
|
|
}
|
|
YAML_NODE *padding = YamlFindField(layout, "pre_wide_padding");
|
|
if(padding)
|
|
{
|
|
NlsCodecBlobAppend(&body, padding);
|
|
}
|
|
if(body.len % 2 || 1 + body.len / 2 > 65535)
|
|
{
|
|
YamlError("wide table offset overflow or misalignment");
|
|
}
|
|
WriteUInt16(&out, 1 + (uint32_t)body.len / 2);
|
|
YamlAppendBuffer(&out, body.data, body.len);
|
|
WriteUInt16(&out, YamlGetUnsigned(YamlGetField(layout, "wide_table_flag"), 65535));
|
|
NlsCodecWriteValues(&out, NlsCodecExpand(YamlGetField(root, "from_unicode"), 65536, 1, max), 65536, size);
|
|
YAML_NODE *tail = YamlFindField(layout, "trailing_bytes");
|
|
if(tail)
|
|
{
|
|
NlsCodecBlobAppend(&out, tail);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static
|
|
int
|
|
NlsCodecCaseGraph(const uint32_t *t,
|
|
size_t count,
|
|
unsigned char *roles,
|
|
uint32_t *slots)
|
|
{
|
|
if(count < 256)
|
|
{
|
|
return 0;
|
|
}
|
|
memset(roles, 1, 256);
|
|
for(unsigned c = 0; c < 65536; c++)
|
|
{
|
|
size_t second = t[c >> 8] + ((c >> 4) & 15);
|
|
if(second >= count)
|
|
{
|
|
return 0;
|
|
}
|
|
if(roles[second] == 2)
|
|
{
|
|
return 0;
|
|
}
|
|
roles[second] = 1;
|
|
size_t third = t[second] + (c & 15);
|
|
if(third >= count || roles[third] == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
roles[third] = 2;
|
|
if(slots)
|
|
{
|
|
slots[c] = (uint32_t)third;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static
|
|
int
|
|
NlsCodecProbeCase(const unsigned char *p,
|
|
size_t len)
|
|
{
|
|
if(len < 6 || len % 2 || ReadUInt16(p) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
size_t pos = 2;
|
|
for(unsigned i = 0; i < 2; i++)
|
|
{
|
|
if(pos + 2 > len)
|
|
{
|
|
return 0;
|
|
}
|
|
uint32_t size = ReadUInt16(p + pos);
|
|
if(size < 257 || pos + 2 * size > len)
|
|
{
|
|
return 0;
|
|
}
|
|
uint32_t *t = NlsCodecReadValues(p + pos + 2, size - 1, 2);
|
|
unsigned char *roles = YamlAllocateMemory(size - 1);
|
|
if(!NlsCodecCaseGraph(t, size - 1, roles, NULL))
|
|
{
|
|
return 0;
|
|
}
|
|
pos += size * 2;
|
|
}
|
|
return pos == len;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsCodecDecodeCase(const unsigned char *p)
|
|
{
|
|
YAML_NODE *root = YamlCreateNode(YAML_MAP), *layout = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(layout, "version", YamlCreateInteger(1));
|
|
size_t pos = 2;
|
|
const char *labels[] = {"uppercase", "lowercase"};
|
|
for(unsigned i = 0; i < 2; i++)
|
|
{
|
|
uint32_t count = ReadUInt16(p + pos) - 1;
|
|
uint32_t *t = NlsCodecReadValues(p + pos + 2, count, 2), *slots = YamlAllocateMemory(65536 * sizeof(*slots));
|
|
unsigned char *roles = YamlAllocateMemory(count);
|
|
if(!NlsCodecCaseGraph(t, count, roles, slots))
|
|
{
|
|
YamlError("invalid case graph");
|
|
}
|
|
YAML_NODE *record = YamlCreateNode(YAML_MAP), *map = YamlCreateNode(YAML_MAP), *meta = YamlCreateNode(YAML_MAP),
|
|
*indices = YamlCreateNode(YAML_MAP), *unused = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(record, "default", YamlCreateString("identity"));
|
|
YamlAddField(record, "map", map);
|
|
for(unsigned c = 0; c < 65536; c++)
|
|
{
|
|
if(t[slots[c]])
|
|
{
|
|
YamlAddField(map, YamlFormatString("U+%04X", c), YamlCreateCodepoint((c + t[slots[c]]) & 65535));
|
|
}
|
|
}
|
|
for(unsigned j = 0; j < count; j++)
|
|
{
|
|
if(roles[j] == 1)
|
|
{
|
|
YamlAddField(indices, YamlFormatString("%u", j), YamlCreateInteger(t[j]));
|
|
}
|
|
else if(!roles[j])
|
|
{
|
|
YamlAddField(unused, YamlFormatString("%u", j), YamlCreateInteger(t[j]));
|
|
}
|
|
}
|
|
YamlAddField(meta, "word_count", YamlCreateInteger(count));
|
|
YamlAddField(meta, "index_words", indices);
|
|
YamlAddField(meta, "unused_words", unused);
|
|
YamlAddField(root, labels[i], record);
|
|
YamlAddField(layout, labels[i], meta);
|
|
pos += (count + 1) * 2;
|
|
}
|
|
YamlAddField(root, "binary_layout", layout);
|
|
return root;
|
|
}
|
|
|
|
static
|
|
YAML_BUFFER
|
|
NlsCodecEncodeCase(YAML_NODE *root)
|
|
{
|
|
YamlValidateFields(root, "uppercase lowercase binary_layout", "");
|
|
YAML_NODE *layout = YamlGetField(root, "binary_layout");
|
|
YamlValidateFields(layout, "version uppercase lowercase", "");
|
|
if(YamlGetUnsigned(YamlGetField(layout, "version"), 65535) != 1)
|
|
{
|
|
YamlError("unsupported case version");
|
|
}
|
|
YAML_BUFFER out = {0};
|
|
WriteUInt16(&out, 1);
|
|
const char *labels[] = {"uppercase", "lowercase"};
|
|
for(unsigned which = 0; which < 2; which++)
|
|
{
|
|
YAML_NODE *record = YamlGetField(root, labels[which]), *meta = YamlGetField(layout, labels[which]);
|
|
YamlValidateFields(record, "default map", "");
|
|
if(strcmp(YamlGetScalar(YamlGetField(record, "default")), "identity"))
|
|
{
|
|
YamlError("case default must be identity");
|
|
}
|
|
YamlValidateFields(meta, "word_count index_words unused_words", "");
|
|
uint32_t count = YamlGetUnsigned(YamlGetField(meta, "word_count"), 65534);
|
|
if(count < 256)
|
|
{
|
|
YamlError("case table too short");
|
|
}
|
|
uint32_t *table = YamlAllocateMemory(count * sizeof(*table)),
|
|
*slots = YamlAllocateMemory(65536 * sizeof(*slots));
|
|
unsigned char *assigned = YamlAllocateMemory(count), *roles = YamlAllocateMemory(count);
|
|
const char *groups[] = {"index_words", "unused_words"};
|
|
for(unsigned g = 0; g < 2; g++)
|
|
{
|
|
YAML_NODE *group = YamlGetField(meta, groups[g]);
|
|
if(group->type != YAML_MAP)
|
|
{
|
|
YamlError("invalid case metadata");
|
|
}
|
|
for(size_t i = 0; i < group->count; i++)
|
|
{
|
|
uint32_t idx = YamlParseNumber(group->keys[i], 10, count - 1);
|
|
if(assigned[idx])
|
|
{
|
|
YamlError("overlapping case metadata");
|
|
}
|
|
assigned[idx] = (unsigned char)(g ? 3 : 1);
|
|
table[idx] = YamlGetUnsigned(group->values[i], 65535);
|
|
}
|
|
}
|
|
if(!NlsCodecCaseGraph(table, count, roles, slots))
|
|
{
|
|
YamlError("invalid case graph");
|
|
}
|
|
for(unsigned i = 0; i < count; i++)
|
|
{
|
|
if(assigned[i] != (roles[i] == 1 ? 1 : roles[i] == 2 ? 0 : 3))
|
|
{
|
|
YamlError("case metadata hides or omits words");
|
|
}
|
|
}
|
|
uint32_t *targets = YamlAllocateMemory(65536 * sizeof(*targets));
|
|
unsigned char *seen = YamlAllocateMemory(65536);
|
|
for(unsigned c = 0; c < 65536; c++)
|
|
{
|
|
targets[c] = c;
|
|
}
|
|
YAML_NODE *map = YamlGetField(record, "map");
|
|
if(map->type != YAML_MAP || map->count > 65536)
|
|
{
|
|
YamlError("invalid case map");
|
|
}
|
|
for(size_t i = 0; i < map->count; i++)
|
|
{
|
|
YAML_NODE key = {.type = YAML_SCALAR, .text = map->keys[i]};
|
|
uint32_t c = YamlGetCodepoint(&key);
|
|
if(seen[c])
|
|
{
|
|
YamlError("duplicate normalized case key");
|
|
}
|
|
seen[c] = 1;
|
|
targets[c] = YamlGetCodepoint(map->values[i]);
|
|
}
|
|
unsigned char *written = YamlAllocateMemory(count);
|
|
for(unsigned c = 0; c < 65536; c++)
|
|
{
|
|
uint32_t slot = slots[c], delta = (targets[c] - c) & 65535;
|
|
if(written[slot] && table[slot] != delta)
|
|
{
|
|
YamlError("case edit conflicts with shared layout at U+%04X", c);
|
|
}
|
|
table[slot] = delta;
|
|
written[slot] = 1;
|
|
}
|
|
WriteUInt16(&out, count + 1);
|
|
NlsCodecWriteValues(&out, table, count, 2);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsDecodeFormat(const unsigned char *p,
|
|
size_t len,
|
|
size_t format)
|
|
{
|
|
if(codecs[format].codec)
|
|
{
|
|
NlsIO x = {.p = p, .n = len, .reading = 1};
|
|
YAML_NODE *body = YamlCreateNode(YAML_MAP);
|
|
codecs[format].codec(&x, body);
|
|
if(x.pos != len)
|
|
{
|
|
YamlError("trailing data in %s at byte %zu", codecs[format].kind, x.pos);
|
|
}
|
|
return body;
|
|
}
|
|
if(format == NLS_CODEPAGE)
|
|
{
|
|
Codepage cp;
|
|
if(!NlsCodecProbeCp(p, len, &cp))
|
|
{
|
|
YamlError("invalid codepage structure");
|
|
}
|
|
return NlsCodecDecodeCp(p, len, &cp);
|
|
}
|
|
if(format == NLS_CASE_MAP)
|
|
{
|
|
if(!NlsCodecProbeCase(p, len))
|
|
{
|
|
YamlError("invalid case-map structure");
|
|
}
|
|
return NlsCodecDecodeCase(p);
|
|
}
|
|
if(format == NLS_SORTKEY_LEGACY && len == 4 + 65536 * 4)
|
|
{
|
|
YAML_NODE *body = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(body, "header", YamlCreateInteger(ReadUInt32(p)));
|
|
uint32_t *values = NlsCodecReadValues(p + 4, 65536, 4);
|
|
YamlAddField(body, "weights", NlsCodecSparse(values, 65536, 1, 4, NlsCodecMode(values, 65536)));
|
|
return body;
|
|
}
|
|
YamlError("invalid sortkey-legacy size");
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsRejectFormat(void)
|
|
{
|
|
longjmp(NlsProbeJump, 1);
|
|
}
|
|
|
|
static
|
|
int
|
|
NlsProbeFormat(const unsigned char *p,
|
|
size_t len,
|
|
size_t format)
|
|
{
|
|
/* Cheap header checks avoid allocating trees for unrelated formats. */
|
|
if(len < 4)
|
|
{
|
|
return 0;
|
|
}
|
|
switch(format)
|
|
{
|
|
case NLS_CHARACTER_TYPES:
|
|
if(ReadUInt16(p) != len)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_GEOGRAPHY:
|
|
if(len < 28 || memcmp(p, "g\0e\0o\0\0", 8))
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_CASE_EXCEPTIONS:
|
|
if(ReadUInt32(p) > 10000 || ReadUInt32(p) > (len - 4) / 16)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_UNICODE_MAPPINGS:
|
|
if(ReadUInt16(p) < 257 || (size_t)ReadUInt16(p) * 2 > len)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_SORT_TABLES_LEGACY:
|
|
if(ReadUInt32(p) > 65535 || ReadUInt32(p) > (len - 4) / 8)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_LOCALE_DATABASE:
|
|
if(len < 32 || ReadUInt32(p) != 32)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_CODEPAGE:
|
|
if(ReadUInt16(p) != 13)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_CASE_MAP:
|
|
if(ReadUInt16(p) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case NLS_SORTKEY_LEGACY:
|
|
/* This legacy raw table has no identifying signature. */
|
|
return len == 4 + 65536 * 4;
|
|
}
|
|
|
|
YAML_ALLOCATION *checkpoint = YamlAllocations;
|
|
void (*previous_handler)(void) = YamlValidationError;
|
|
if(setjmp(NlsProbeJump))
|
|
{
|
|
YamlValidationError = previous_handler;
|
|
YamlReleaseAllocations(checkpoint);
|
|
return 0;
|
|
}
|
|
YamlValidationError = NlsRejectFormat;
|
|
NlsDecodeFormat(p, len, format);
|
|
YamlValidationError = previous_handler;
|
|
YamlReleaseAllocations(checkpoint);
|
|
return 1;
|
|
}
|
|
|
|
static
|
|
YAML_NODE *
|
|
NlsDecode(const unsigned char *p,
|
|
size_t len,
|
|
const char *type)
|
|
{
|
|
size_t count = NLS_FORMAT_COUNT;
|
|
size_t selected = count;
|
|
if(type)
|
|
{
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
if(!strcmp(type, codecs[i].kind))
|
|
{
|
|
selected = i;
|
|
break;
|
|
}
|
|
}
|
|
if(selected == count)
|
|
{
|
|
YamlError("unsupported NLS type: %s", type);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(size_t i = 0; i < count; i++)
|
|
{
|
|
if(NlsProbeFormat(p, len, i))
|
|
{
|
|
if(selected != count)
|
|
{
|
|
YamlError("ambiguous NLS format (%s or %s); use --type KIND",
|
|
codecs[selected].kind, codecs[i].kind);
|
|
}
|
|
selected = i;
|
|
}
|
|
}
|
|
if(selected == count)
|
|
{
|
|
YamlError("unsupported NLS format or corrupt structure (no opaque fallback)");
|
|
}
|
|
}
|
|
YAML_NODE *body = NlsDecodeFormat(p, len, selected);
|
|
YAML_NODE *root = YamlCreateNode(YAML_MAP);
|
|
YamlAddField(root, "format", YamlCreateString("xtnlsc"));
|
|
YamlAddField(root, "version", YamlCreateInteger(2));
|
|
YamlAddField(root, "kind", YamlCreateString(codecs[selected].kind));
|
|
for(size_t i = 0; i < body->count; i++)
|
|
{
|
|
YamlAddField(root, body->keys[i], body->values[i]);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
static
|
|
YAML_BUFFER
|
|
NlsEncode(YAML_NODE *root)
|
|
{
|
|
if(strcmp(YamlGetScalar(YamlGetField(root, "format")), "xtnlsc") ||
|
|
YamlGetUnsigned(YamlGetField(root, "version"), 65535) != 2)
|
|
{
|
|
YamlError("expected xtnlsc YAML version 2");
|
|
}
|
|
const char *kind = YamlGetScalar(YamlGetField(root, "kind"));
|
|
YAML_NODE *body = YamlCreateNode(YAML_MAP);
|
|
for(size_t i = 0; i < root->count; i++)
|
|
{
|
|
if(strcmp(root->keys[i], "format") && strcmp(root->keys[i], "version") && strcmp(root->keys[i], "kind"))
|
|
{
|
|
YamlAddField(body, root->keys[i], root->values[i]);
|
|
}
|
|
}
|
|
if(!strcmp(kind, "codepage"))
|
|
{
|
|
return NlsCodecEncodeCp(body);
|
|
}
|
|
if(!strcmp(kind, "case-map"))
|
|
{
|
|
return NlsCodecEncodeCase(body);
|
|
}
|
|
YAML_BUFFER out = {0};
|
|
if(NlsEncodeExtra(kind, body, &out))
|
|
{
|
|
return out;
|
|
}
|
|
if(!strcmp(kind, "sortkey-legacy"))
|
|
{
|
|
YamlValidateFields(body, "header weights", "");
|
|
WriteUInt32(&out, YamlGetUnsigned(YamlGetField(body, "header"), UINT32_MAX));
|
|
NlsCodecWriteValues(&out, NlsCodecExpand(YamlGetField(body, "weights"), 65536, 1, UINT32_MAX), 65536, 4);
|
|
}
|
|
else if(!strcmp(kind, "opaque"))
|
|
{
|
|
YamlValidateFields(body, "data", "");
|
|
NlsCodecBlobAppend(&out, YamlGetField(body, "data"));
|
|
}
|
|
else
|
|
{
|
|
YamlError("unsupported YAML kind: %s", kind);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static
|
|
char *
|
|
NlsLegacyTrim(char *s)
|
|
{
|
|
while(isspace((unsigned char)*s))
|
|
{
|
|
s++;
|
|
}
|
|
size_t n = strlen(s);
|
|
while(n && isspace((unsigned char)s[n - 1]))
|
|
{
|
|
s[--n] = 0;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static
|
|
YAML_BUFFER
|
|
NlsEncodeV1(const unsigned char *data,
|
|
size_t len)
|
|
{
|
|
char *text = YamlAllocateMemory(len + 1);
|
|
memcpy(text, data, len);
|
|
if(strlen(text) != len)
|
|
{
|
|
YamlError("NUL in legacy text");
|
|
}
|
|
YAML_BUFFER out = {0};
|
|
YAML_NODE *names = YamlCreateNode(YAML_MAP);
|
|
unsigned header = 0, width = 0;
|
|
uint32_t size = 0, count = 0, used = 0;
|
|
size_t line = 0;
|
|
char *cursor = text;
|
|
while(*cursor)
|
|
{
|
|
char *next = strchr(cursor, '\n');
|
|
if(next)
|
|
{
|
|
*next++ = 0;
|
|
}
|
|
char *comment = strchr(cursor, '#');
|
|
if(comment)
|
|
{
|
|
*comment = 0;
|
|
}
|
|
char *s = NlsLegacyTrim(cursor);
|
|
line++;
|
|
if(*s)
|
|
{
|
|
if(header < 3)
|
|
{
|
|
if(!header && strcmp(s, "XTNLS 1"))
|
|
{
|
|
YamlError("legacy line %zu: expected XTNLS 1", line);
|
|
}
|
|
if(header == 1 && strcmp(s, "KIND codepage") && strcmp(s, "KIND case-map") &&
|
|
strcmp(s, "KIND sortkey-legacy") && strcmp(s, "KIND opaque"))
|
|
{
|
|
YamlError("invalid legacy KIND");
|
|
}
|
|
if(header == 2)
|
|
{
|
|
if(strncmp(s, "SIZE ", 5))
|
|
{
|
|
YamlError("missing legacy SIZE");
|
|
}
|
|
size = YamlParseNumber(s + 5, 10, YAML_MAX_BINARY);
|
|
}
|
|
header++;
|
|
}
|
|
else if(*s == '[')
|
|
{
|
|
if(used != count)
|
|
{
|
|
YamlError("incomplete legacy section");
|
|
}
|
|
char name[128], offset[17], type[4], items[11];
|
|
int end = 0;
|
|
if(sscanf(s,
|
|
"[%127[A-Za-z0-9_] offset=0x%16[0-9a-fA-F] type=%3s count=%10[0-9]]%n",
|
|
name,
|
|
offset,
|
|
type,
|
|
items,
|
|
&end) != 4 ||
|
|
!end || s[end] || !isalpha((unsigned char)name[0]))
|
|
{
|
|
YamlError("invalid legacy section at line %zu", line);
|
|
}
|
|
if(YamlParseNumber(offset, 16, YAML_MAX_BINARY) != out.len)
|
|
{
|
|
YamlError("legacy section gap or overlap");
|
|
}
|
|
width = !strcmp(type, "u8") ? 1 : !strcmp(type, "u16") ? 2 : !strcmp(type, "u32") ? 4 : 0;
|
|
count = YamlParseNumber(items, 10, YAML_MAX_BINARY);
|
|
used = 0;
|
|
if(!width || !count || (uint64_t)count * width + out.len > size)
|
|
{
|
|
YamlError("invalid legacy section size");
|
|
}
|
|
YamlAddField(names, name, YamlCreateInteger(1));
|
|
}
|
|
else
|
|
{
|
|
if(!width)
|
|
{
|
|
YamlError("legacy data outside a section");
|
|
}
|
|
char *colon = strchr(s, ':');
|
|
if(!colon)
|
|
{
|
|
YamlError("invalid legacy data row");
|
|
}
|
|
*colon = 0;
|
|
if(YamlParseNumber(NlsLegacyTrim(s), 16, YAML_MAX_BINARY) != used)
|
|
{
|
|
YamlError("noncontiguous legacy index");
|
|
}
|
|
char *payload = NlsLegacyTrim(colon + 1), *star = strchr(payload, '*');
|
|
uint32_t repeat = 1;
|
|
if(star)
|
|
{
|
|
*star = 0;
|
|
repeat = YamlParseNumber(NlsLegacyTrim(star + 1), 10, YAML_MAX_BINARY);
|
|
if(!repeat)
|
|
{
|
|
YamlError("zero repeat");
|
|
}
|
|
}
|
|
payload = NlsLegacyTrim(payload);
|
|
unsigned tokens = 0;
|
|
while(*payload)
|
|
{
|
|
char *stop = payload;
|
|
while(*stop && !isspace((unsigned char)*stop))
|
|
{
|
|
stop++;
|
|
}
|
|
char save = *stop;
|
|
*stop = 0;
|
|
uint32_t value = YamlParseNumber(payload, 16, width == 1 ? 255 : width == 2 ? 65535 : UINT32_MAX);
|
|
tokens++;
|
|
if((star && tokens > 1) || repeat > count - used)
|
|
{
|
|
YamlError("legacy row exceeds section");
|
|
}
|
|
for(uint32_t j = 0; j < repeat; j++)
|
|
{
|
|
if(width == 1)
|
|
{
|
|
unsigned char c = (unsigned char)value;
|
|
YamlAppendBuffer(&out, &c, 1);
|
|
}
|
|
else if(width == 2)
|
|
{
|
|
WriteUInt16(&out, value);
|
|
}
|
|
else
|
|
{
|
|
WriteUInt32(&out, value);
|
|
}
|
|
}
|
|
used += repeat;
|
|
if(!save)
|
|
{
|
|
break;
|
|
}
|
|
payload = NlsLegacyTrim(stop + 1);
|
|
}
|
|
if(!tokens)
|
|
{
|
|
YamlError("empty legacy row");
|
|
}
|
|
}
|
|
}
|
|
if(!next)
|
|
{
|
|
break;
|
|
}
|
|
cursor = next;
|
|
}
|
|
if(header != 3 || used != count || out.len != size)
|
|
{
|
|
YamlError("incomplete legacy document");
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsMainCleanFile(void)
|
|
{
|
|
if(output_stream)
|
|
{
|
|
fclose(output_stream);
|
|
}
|
|
if(temporary)
|
|
{
|
|
remove(temporary);
|
|
}
|
|
}
|
|
|
|
static
|
|
unsigned char *
|
|
NlsMainReadFile(const char *path,
|
|
size_t limit,
|
|
size_t *length)
|
|
{
|
|
FILE *f = fopen(path, "rb");
|
|
if(!f)
|
|
{
|
|
YamlError("cannot open %s: %s", path, strerror(errno));
|
|
}
|
|
/* Read incrementally: pipes and files changed during a read are bounded too. */
|
|
size_t cap = 65536, len = 0;
|
|
unsigned char *data = YamlAllocateMemory(cap + 1);
|
|
for(;;)
|
|
{
|
|
if(len == cap)
|
|
{
|
|
size_t newcap = cap < limit / 2 ? cap * 2 : limit + 1;
|
|
if(cap >= limit + 1)
|
|
{
|
|
fclose(f);
|
|
YamlError("input exceeds size limit");
|
|
}
|
|
unsigned char *next = YamlAllocateMemory(newcap + 1);
|
|
memcpy(next, data, len);
|
|
data = next;
|
|
cap = newcap;
|
|
}
|
|
size_t n = fread(data + len, 1, cap - len, f);
|
|
len += n;
|
|
if(len > limit)
|
|
{
|
|
fclose(f);
|
|
YamlError("input exceeds size limit");
|
|
}
|
|
if(!n)
|
|
{
|
|
if(ferror(f))
|
|
{
|
|
fclose(f);
|
|
YamlError("cannot read %s", path);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if(fclose(f))
|
|
{
|
|
YamlError("cannot close input");
|
|
}
|
|
data[len] = 0;
|
|
*length = len;
|
|
return data;
|
|
}
|
|
|
|
static
|
|
void
|
|
NlsMainUsage(FILE *f)
|
|
{
|
|
fputs("xtnlsc " XTNLSC_VERSION " - lossless NLS / YAML converter\n"
|
|
"Usage: xtnlsc (-d|--decode|-e|--encode) -i INPUT -o OUTPUT [--type KIND]\n"
|
|
" -d, --decode NLS to YAML\n"
|
|
" -e, --encode YAML v2 or legacy XTNLS v1 to NLS\n"
|
|
" -i, --input PATH Input file\n"
|
|
" -o, --output PATH Output file\n"
|
|
" -t, --type KIND Decode as KIND when content is ambiguous\n"
|
|
" Normally detected from content, regardless of filename\n"
|
|
" Kinds: character-types, geography, case-exceptions,\n"
|
|
" unicode-mappings, sort-tables-legacy, locale-database,\n"
|
|
" codepage, case-map, sortkey-legacy\n"
|
|
" -h, --help Show help\n"
|
|
" --version Show version\n",
|
|
f);
|
|
}
|
|
|
|
static
|
|
int
|
|
NlsMainArgumentError(const char *message)
|
|
{
|
|
fprintf(stderr, "xtnlsc: %s\n", message);
|
|
NlsMainUsage(stderr);
|
|
return 2;
|
|
}
|
|
|
|
int
|
|
main(int argc,
|
|
char **argv)
|
|
{
|
|
YamlProgramName = "xtnlsc";
|
|
YamlCharacterName = NlsGetCharacterName;
|
|
atexit(YamlCleanup);
|
|
atexit(NlsMainCleanFile);
|
|
const char *input = NULL, *output = NULL, *type = NULL;
|
|
int mode = 0;
|
|
for(int i = 1; i < argc; i++)
|
|
{
|
|
const char *arg = argv[i];
|
|
if(!strcmp(arg, "--version"))
|
|
{
|
|
puts("xtnlsc " XTNLSC_VERSION);
|
|
return 0;
|
|
}
|
|
if(!strcmp(arg, "-h") || !strcmp(arg, "--help"))
|
|
{
|
|
NlsMainUsage(stdout);
|
|
return 0;
|
|
}
|
|
if(!strcmp(arg, "-d") || !strcmp(arg, "--decode") || !strcmp(arg, "-e") || !strcmp(arg, "--encode"))
|
|
{
|
|
if(mode)
|
|
{
|
|
return NlsMainArgumentError("select exactly one mode");
|
|
}
|
|
mode = !strcmp(arg, "-d") || !strcmp(arg, "--decode") ? 1 : 2;
|
|
}
|
|
else if(!strcmp(arg, "-t") || !strcmp(arg, "--type"))
|
|
{
|
|
if(i + 1 >= argc || type)
|
|
{
|
|
return NlsMainArgumentError("missing or duplicate NLS type");
|
|
}
|
|
type = argv[++i];
|
|
}
|
|
else if(!strcmp(arg, "-i") || !strcmp(arg, "--input") || !strcmp(arg, "-o") || !strcmp(arg, "--output"))
|
|
{
|
|
if(i + 1 >= argc)
|
|
{
|
|
return NlsMainArgumentError("missing filename");
|
|
}
|
|
const char **slot = !strcmp(arg, "-i") || !strcmp(arg, "--input") ? &input : &output;
|
|
if(*slot)
|
|
{
|
|
return NlsMainArgumentError("duplicate input/output argument");
|
|
}
|
|
*slot = argv[++i];
|
|
}
|
|
else
|
|
{
|
|
return NlsMainArgumentError("unknown argument");
|
|
}
|
|
}
|
|
if(!mode || !input || !output)
|
|
{
|
|
return NlsMainArgumentError("mode, input and output are required");
|
|
}
|
|
if(type && mode != 1)
|
|
{
|
|
return NlsMainArgumentError("--type is only valid in decode mode");
|
|
}
|
|
struct stat a, b;
|
|
if(!strcmp(input, output) ||
|
|
(!stat(input, &a) && !stat(output, &b) && a.st_ino && a.st_dev == b.st_dev && a.st_ino == b.st_ino))
|
|
{
|
|
YamlError("input and output must be different files");
|
|
}
|
|
size_t length;
|
|
unsigned char *data = NlsMainReadFile(input, mode == 1 ? YAML_MAX_BINARY : YAML_MAX_TEXT, &length);
|
|
YAML_NODE *root = NULL;
|
|
YAML_BUFFER result = {0};
|
|
if(mode == 1)
|
|
{
|
|
root = NlsDecode(data, length, type);
|
|
}
|
|
else
|
|
{
|
|
unsigned char *start = data;
|
|
size_t remaining = length;
|
|
if(remaining >= 3 && !memcmp(start, "\xEF\xBB\xBF", 3))
|
|
{
|
|
start += 3;
|
|
remaining -= 3;
|
|
}
|
|
while(remaining && (*start == ' ' || *start == '\n' || *start == '\r' || *start == '\t'))
|
|
{
|
|
start++;
|
|
remaining--;
|
|
}
|
|
result = remaining >= 7 && !memcmp(start, "XTNLS 1", 7) ? NlsEncodeV1(start, remaining)
|
|
: NlsEncode(YamlReadDocument(start, remaining));
|
|
}
|
|
for(unsigned attempt = 0; attempt < 100; attempt++)
|
|
{
|
|
char *candidate = YamlFormatString("%s.xtnlsc-%ld-%u.tmp", output, (long)XTCHAIN_PROCESS_ID(), attempt);
|
|
output_stream = fopen(candidate, "wbx");
|
|
if(output_stream)
|
|
{
|
|
temporary = candidate;
|
|
break;
|
|
}
|
|
if(errno != EEXIST)
|
|
{
|
|
YamlError("cannot create output temporary file: %s", strerror(errno));
|
|
}
|
|
}
|
|
if(!output_stream)
|
|
{
|
|
YamlError("cannot allocate temporary output filename");
|
|
}
|
|
if(mode == 1)
|
|
{
|
|
YamlWriteDocument(output_stream, root, "xtnlsc " XTNLSC_VERSION);
|
|
}
|
|
else if(result.len && fwrite(result.data, 1, result.len, output_stream) != result.len)
|
|
{
|
|
YamlError("cannot write binary output");
|
|
}
|
|
if(fflush(output_stream))
|
|
{
|
|
YamlError("cannot flush output");
|
|
}
|
|
#ifdef _WIN32
|
|
if(_commit(_fileno(output_stream)))
|
|
{
|
|
YamlError("cannot sync output");
|
|
}
|
|
#else
|
|
if(fsync(fileno(output_stream)))
|
|
{
|
|
YamlError("cannot sync output");
|
|
}
|
|
#endif
|
|
FILE *finished = output_stream;
|
|
output_stream = NULL;
|
|
if(fclose(finished))
|
|
{
|
|
YamlError("cannot close output");
|
|
}
|
|
#ifdef _WIN32
|
|
if(!MoveFileExA(temporary, output, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
|
{
|
|
YamlError("cannot replace output file");
|
|
}
|
|
#else
|
|
if(rename(temporary, output))
|
|
{
|
|
YamlError("cannot replace output: %s", strerror(errno));
|
|
}
|
|
#endif
|
|
temporary = NULL;
|
|
fprintf(stderr, "xtnlsc: %s -> %s\n", mode == 1 ? YamlGetScalar(YamlGetField(root, "kind")) : "binary", output);
|
|
return 0;
|
|
}
|