mirror of
https://github.com/Unidata/netcdf-c.git
synced 2025-02-11 16:40:36 +08:00
2164 lines
53 KiB
C
2164 lines
53 KiB
C
/*
|
|
* Copyright 2008, 2009 University Corporation for Atmospheric Research
|
|
*
|
|
* This file is part of the UDUNITS-2 package. See the file LICENSE
|
|
* in the top-level source-directory of the package for copying and
|
|
* redistribution conditions.
|
|
*/
|
|
/*
|
|
* This module is thread-compatible but not thread-safe. Multi-threaded
|
|
* access must be externally synchronized.
|
|
*/
|
|
|
|
/*LINTLIBRARY*/
|
|
|
|
#ifndef _XOPEN_SOURCE
|
|
# define _XOPEN_SOURCE 500
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <libgen.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "expat.h"
|
|
#include "udunits2.h"
|
|
|
|
#define NAME_SIZE 128
|
|
#define ACCUMULATE_TEXT \
|
|
XML_SetCharacterDataHandler(currFile->parser, accumulateText)
|
|
#define IGNORE_TEXT \
|
|
XML_SetCharacterDataHandler(currFile->parser, NULL)
|
|
|
|
typedef enum {
|
|
START,
|
|
UNIT_SYSTEM,
|
|
PREFIX,
|
|
UNIT,
|
|
UNIT_NAME,
|
|
ALIASES,
|
|
ALIAS_NAME
|
|
} ElementType;
|
|
|
|
typedef struct {
|
|
const char* path;
|
|
char singular[NAME_SIZE];
|
|
char plural[NAME_SIZE];
|
|
char symbol[NAME_SIZE];
|
|
double value;
|
|
XML_Parser parser;
|
|
ut_unit* unit;
|
|
ElementType context;
|
|
ut_encoding xmlEncoding;
|
|
ut_encoding textEncoding;
|
|
int fd;
|
|
int skipDepth;
|
|
int prefixAdded;
|
|
int haveValue;
|
|
int isBase;
|
|
int isDimensionless;
|
|
int noPLural;
|
|
int nameSeen;
|
|
int symbolSeen;
|
|
} File;
|
|
|
|
typedef struct {
|
|
char ascii[NAME_SIZE];
|
|
char latin1[NAME_SIZE];
|
|
char latin1Nbsp[NAME_SIZE];
|
|
char utf8[NAME_SIZE];
|
|
char utf8Nbsp[NAME_SIZE];
|
|
} Identifiers;
|
|
|
|
static ut_status readXml(
|
|
const char* const path);
|
|
|
|
static File* currFile = NULL;
|
|
static ut_system* unitSystem = NULL;
|
|
static char* text = NULL;
|
|
static size_t nbytes = 0;
|
|
|
|
|
|
/*
|
|
* Returns the plural form of a name.
|
|
*
|
|
* Arguments:
|
|
* singular Pointer to the singular form of a name.
|
|
* Returns:
|
|
* Pointer to the plural form of "singular". Client must not free. May be
|
|
* overwritten by subsequent calls.
|
|
*/
|
|
const char*
|
|
ut_form_plural(
|
|
const char* singular)
|
|
{
|
|
static char buf[NAME_SIZE];
|
|
const char* plural = NULL; /* failure */
|
|
|
|
if (singular != NULL) {
|
|
int length = strlen(singular);
|
|
|
|
if (length + 3 >= sizeof(buf)) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("Singular form is too long");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (length > 0) {
|
|
(void)strcpy(buf, singular);
|
|
|
|
if (length == 1) {
|
|
(void)strcpy(buf+length, "s");
|
|
}
|
|
else {
|
|
char lastChar = singular[length-1];
|
|
|
|
if (lastChar == 'y') {
|
|
char penultimateChar = singular[length-2];
|
|
|
|
if (penultimateChar == 'a' || penultimateChar == 'e' ||
|
|
penultimateChar == 'i' || penultimateChar == 'o' ||
|
|
penultimateChar == 'u') {
|
|
(void)strcpy(buf+length, "s");
|
|
}
|
|
else {
|
|
(void)strcpy(buf+length-1, "ies");
|
|
}
|
|
}
|
|
else {
|
|
if (lastChar == 's' || lastChar == 'x' || lastChar == 'z' ||
|
|
(length >= 2 && (
|
|
strcmp(singular+length-2, "ch") == 0 ||
|
|
strcmp(singular+length-2, "sh") == 0))) {
|
|
(void)strcpy(buf+length, "es");
|
|
}
|
|
else {
|
|
(void)strcpy(buf+length, "s");
|
|
}
|
|
}
|
|
}
|
|
|
|
plural = buf;
|
|
}
|
|
}
|
|
|
|
return plural;
|
|
}
|
|
|
|
|
|
/*
|
|
* Substitutes one substring for all occurrences another in a string.
|
|
*
|
|
* Arguments:
|
|
* inString Pointer to the input string.
|
|
* str Pointer to the substring to be replaced.
|
|
* outString Pointer to the output string buffer.
|
|
* repl Pointer to the replacement substring.
|
|
* size Size of the output string buffer, including the
|
|
* terminating NUL.
|
|
* Returns:
|
|
* 0 Failure. The output string buffer is too small.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
substitute(
|
|
const char* const inString,
|
|
const char* str,
|
|
char* const outString,
|
|
const char* repl,
|
|
const size_t size)
|
|
{
|
|
const char* in = inString;
|
|
char* out = outString;
|
|
char* beyond = outString + size;
|
|
size_t strLen = strlen(str);
|
|
size_t replLen = strlen(repl);
|
|
|
|
while (*in) {
|
|
size_t nbytes;
|
|
char* cp = strstr(in, str);
|
|
|
|
if (cp == NULL) {
|
|
nbytes = strlen(in);
|
|
|
|
if (out + nbytes >= beyond) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("String \"%s\" is too long", inString);
|
|
return 0;
|
|
}
|
|
|
|
(void)strncpy(out, in, nbytes);
|
|
|
|
out += nbytes;
|
|
|
|
break;
|
|
}
|
|
else {
|
|
nbytes = (size_t)(cp - in);
|
|
|
|
if (out + nbytes + replLen >= beyond) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("String \"%s\" is too long", inString);
|
|
return 0;
|
|
}
|
|
|
|
(void)strncpy(out, in, nbytes);
|
|
|
|
out += nbytes;
|
|
|
|
(void)strncpy(out, repl, replLen);
|
|
|
|
out += replLen;
|
|
in += nbytes + strLen;
|
|
}
|
|
}
|
|
|
|
*out = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define IS_ASCII(c) (((c) & '\x80') == 0)
|
|
|
|
|
|
/*
|
|
* Returns the UTF-8 string equivalent to a Latin-1 string.
|
|
*
|
|
* Arguments:
|
|
* inString Pointer to the input, Latin-1 string.
|
|
* outString Pointer to the output, UTF-8, string buffer.
|
|
* size Size of "outString".
|
|
* Returns:
|
|
* 0 Failure. "outString" is too small.
|
|
* 1 Success.
|
|
*/
|
|
static int
|
|
latin1_to_utf8(
|
|
const char* inString,
|
|
char* outString,
|
|
size_t size)
|
|
{
|
|
size_t numSpecial = 0;
|
|
const unsigned char* in;
|
|
unsigned char* out;
|
|
|
|
assert(inString != NULL);
|
|
assert(outString != NULL);
|
|
|
|
/*
|
|
* Compute the number of non-ASCII characters.
|
|
*/
|
|
for (in = (const unsigned char*)inString; *in; in++)
|
|
if (!IS_ASCII(*in))
|
|
numSpecial++;
|
|
|
|
if (size < ((size_t)((char*)in - (char*)inString) + numSpecial + 1)) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("Identifier \"%s\" is too long", inString);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Convert the string.
|
|
*/
|
|
for (in = (const unsigned char*)inString, out = (unsigned char*)outString;
|
|
*in; ++in) {
|
|
if (IS_ASCII(*in)) {
|
|
*out++ = *in;
|
|
}
|
|
else {
|
|
*out++ = 0xC0 | ((0xC0 & *in) >> 6);
|
|
*out++ = 0x80 | (0x3F & *in);
|
|
}
|
|
}
|
|
|
|
*out = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the Latin-1 string equivalent to a UTF-8 string, if possible.
|
|
*
|
|
* Arguments:
|
|
* inString Pointer to the input, UTF-8 string.
|
|
* outString Pointer to the output, Latin-1, string buffer.
|
|
* size Size of "outString".
|
|
* Returns:
|
|
* -1 Failure. "outString" is too small.
|
|
* 0 "inString" can't be represented in Latin-1.
|
|
* 1 Success.
|
|
*/
|
|
static int
|
|
utf8_to_latin1(
|
|
const char* inString,
|
|
char* outString,
|
|
size_t size)
|
|
{
|
|
size_t numSpecial = 0;
|
|
const unsigned char* in;
|
|
unsigned char* out;
|
|
|
|
assert(inString != NULL);
|
|
assert(outString != NULL);
|
|
|
|
/*
|
|
* Compute the number of non-ASCII characters.
|
|
*/
|
|
for (in = (const unsigned char*)inString; *in; in++) {
|
|
if (*in > 0xC3)
|
|
return 0;
|
|
|
|
if (!IS_ASCII(*in)) {
|
|
numSpecial++;
|
|
in++;
|
|
}
|
|
}
|
|
|
|
if (size < ((size_t)((char*)in - (char*)inString) - numSpecial + 1)) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("Identifier \"%s\" is too long", inString);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Convert the string.
|
|
*/
|
|
for (in = (const unsigned char*)inString, out = (unsigned char*)outString;
|
|
*in; ++out) {
|
|
if (IS_ASCII(*in)) {
|
|
*out = *in++;
|
|
}
|
|
else {
|
|
*out = (*in++ & 0x3) << 6;
|
|
*out |= (*in++ & 0x3F);
|
|
}
|
|
}
|
|
|
|
*out = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define LATIN1_NBSP "\xA0"
|
|
#define UTF8_NBSP "\xC2\xA0"
|
|
|
|
|
|
/*
|
|
* Creates the regular and NBSP forms of a Latin-1 string.
|
|
*
|
|
* Arguments:
|
|
* latin1 Pointer to the input Latin-1 string.
|
|
* nonNbsp Pointer to an output string buffer of size NAME_SIZE
|
|
* that will be identical to "latin1" but with all
|
|
* non-breaking spaces replaced with underscores.
|
|
* nbsp Pointer to an output string buffer of size NAME_SIZE
|
|
* that will be identical to "latin1" but with all
|
|
* underscores replaced with non-breaking spaces.
|
|
*/
|
|
static void
|
|
make_latin1_forms(
|
|
const char* latin1,
|
|
char* nonNbsp,
|
|
char* nbsp)
|
|
{
|
|
if (strchr(latin1, '_')) {
|
|
substitute(latin1, "_", nbsp, LATIN1_NBSP, NAME_SIZE);
|
|
substitute(nbsp, LATIN1_NBSP, nonNbsp, "_", NAME_SIZE);
|
|
}
|
|
else if (strstr(latin1, LATIN1_NBSP)) {
|
|
substitute(latin1, LATIN1_NBSP, nonNbsp, "_", NAME_SIZE);
|
|
substitute(nonNbsp, "_", nbsp, LATIN1_NBSP, NAME_SIZE);
|
|
}
|
|
else {
|
|
(void)strcpy(nonNbsp, latin1);
|
|
*nbsp = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Creates the regular and NBSP forms of a UTF-8 string.
|
|
*
|
|
* Arguments:
|
|
* utf8 Pointer to the input UTF-8 string.
|
|
* nonNbsp Pointer to an output string buffer of size NAME_SIZE
|
|
* that will be identical to "utf8" but with all
|
|
* non-breaking spaces replaced with underscores.
|
|
* nbsp Pointer to an output string buffer of size NAME_SIZE
|
|
* that will be identical to "utf8" but with all
|
|
* underscores replaced with non-breaking spaces.
|
|
*/
|
|
static int
|
|
make_utf8_forms(
|
|
const char* utf8,
|
|
char* nonNbsp,
|
|
char* nbsp)
|
|
{
|
|
int success;
|
|
|
|
if (strchr(utf8, '_')) {
|
|
success = substitute(utf8, "_", nbsp, UTF8_NBSP, NAME_SIZE);
|
|
|
|
if (success)
|
|
success = substitute(nbsp, UTF8_NBSP, nonNbsp, "_", NAME_SIZE);
|
|
}
|
|
else if (strstr(utf8, UTF8_NBSP)) {
|
|
success = substitute(utf8, UTF8_NBSP, nonNbsp, "_", NAME_SIZE);
|
|
|
|
if (success)
|
|
success = substitute(nonNbsp, "_", nbsp, UTF8_NBSP, NAME_SIZE);
|
|
}
|
|
else {
|
|
(void)strcpy(nonNbsp, utf8);
|
|
*nbsp = 0;
|
|
success = 1;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Creates derivatives of an identifier, which are all legitimate combinations
|
|
* of ASCII, Latin-1, and UTF-8, on the one hand, and underscore vs.
|
|
* non-breaking spaces on the other.
|
|
*
|
|
* Arguments:
|
|
* id Pointer to the identifier.
|
|
* encoding The encoding of "id".
|
|
* ids Pointer to an "Identifier" structure.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success. If a combination is not possible or is
|
|
* identical to a simpler combination, then the first
|
|
* character of the relevant member of "ids" will be NUL.
|
|
*/
|
|
static int
|
|
makeDerivatives(
|
|
const char* const id,
|
|
const ut_encoding encoding,
|
|
Identifiers* ids)
|
|
{
|
|
int success = 1;
|
|
|
|
assert(id != NULL);
|
|
assert(ids != NULL);
|
|
|
|
if (strlen(id) > NAME_SIZE - 1) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("Identifier \"%s\" is too long", id);
|
|
success = 0;
|
|
}
|
|
else {
|
|
ids->ascii[0] = 0;
|
|
ids->latin1[0] = 0;
|
|
ids->latin1Nbsp[0] = 0;
|
|
ids->utf8[0] = 0;
|
|
ids->utf8Nbsp[0] = 0;
|
|
|
|
if (encoding == UT_ASCII) {
|
|
(void)strcpy(ids->ascii, id);
|
|
|
|
if (strchr(id, '_')) {
|
|
(void)substitute(id, "_", ids->latin1Nbsp, LATIN1_NBSP,
|
|
NAME_SIZE);
|
|
success =
|
|
latin1_to_utf8(ids->latin1Nbsp, ids->utf8Nbsp, NAME_SIZE);
|
|
}
|
|
}
|
|
else if (encoding == UT_LATIN1) {
|
|
make_latin1_forms(id, ids->latin1, ids->latin1Nbsp);
|
|
|
|
success = latin1_to_utf8(ids->latin1, ids->utf8, NAME_SIZE) &&
|
|
latin1_to_utf8(ids->latin1Nbsp, ids->utf8Nbsp, NAME_SIZE);
|
|
}
|
|
else {
|
|
success = make_utf8_forms(id, ids->utf8, ids->utf8Nbsp) &&
|
|
utf8_to_latin1(ids->utf8, ids->latin1, NAME_SIZE) != -1 &&
|
|
utf8_to_latin1(ids->utf8Nbsp, ids->latin1Nbsp, NAME_SIZE) != -1;
|
|
}
|
|
|
|
if (success) {
|
|
if (strcmp(ids->ascii, ids->latin1) == 0)
|
|
ids->latin1[0] = 0;
|
|
if (strcmp(ids->ascii, ids->latin1Nbsp) == 0)
|
|
ids->latin1Nbsp[0] = 0;
|
|
if (strcmp(ids->ascii, ids->utf8) == 0)
|
|
ids->utf8[0] = 0;
|
|
if (strcmp(ids->ascii, ids->utf8Nbsp) == 0)
|
|
ids->utf8Nbsp[0] = 0;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a unit to an identifier.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* id Pointer to the identifier.
|
|
* encoding The encoding of "id".
|
|
* isName Whether or not "id" is a name.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitToId(
|
|
ut_unit* const unit,
|
|
const char* const id,
|
|
ut_encoding encoding,
|
|
int isName)
|
|
{
|
|
int success = 0; /* failure */
|
|
ut_status (*func)(const ut_unit*, const char*, ut_encoding);
|
|
const char* desc;
|
|
|
|
if (isName) {
|
|
func = ut_map_unit_to_name;
|
|
desc = "name";
|
|
}
|
|
else {
|
|
func = ut_map_unit_to_symbol;
|
|
desc = "symbol";
|
|
}
|
|
|
|
if (func(unit, id, encoding) != UT_SUCCESS) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't map unit to %s \"%s\"", desc, id);
|
|
}
|
|
else {
|
|
success = 1;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a unit to identifiers.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* id Pointer to the identifier upon wich to base all
|
|
* derived identifiers.
|
|
* encoding The encoding of "id".
|
|
* isName Whether or not "id" is a name.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitToIds(
|
|
ut_unit* const unit,
|
|
const char* const id,
|
|
ut_encoding encoding,
|
|
int isName)
|
|
{
|
|
int success = 1; /* success */
|
|
Identifiers ids;
|
|
|
|
if (!makeDerivatives(id, encoding, &ids)) {
|
|
success = 0;
|
|
}
|
|
else {
|
|
if (ids.ascii[0])
|
|
success = mapUnitToId(unit, ids.ascii, UT_ASCII, isName);
|
|
if (success && ids.latin1[0])
|
|
success = mapUnitToId(unit, ids.latin1, UT_LATIN1, isName);
|
|
if (success && ids.latin1Nbsp[0])
|
|
success = mapUnitToId(unit, ids.latin1Nbsp, UT_LATIN1, isName);
|
|
if (success && ids.utf8[0])
|
|
success = mapUnitToId(unit, ids.utf8, UT_UTF8, isName);
|
|
if (success && ids.utf8Nbsp[0])
|
|
success = mapUnitToId(unit, ids.utf8Nbsp, UT_UTF8, isName);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a unit to a name and all derivatives of the name.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* name Pointer to the name upon wich to base all derived names.
|
|
* encoding The encoding of "name".
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitToNames(
|
|
ut_unit* const unit,
|
|
const char* const name,
|
|
ut_encoding encoding)
|
|
{
|
|
return mapUnitToIds(unit, name, encoding, 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a unit to a symbol and all derivatives of the symbol.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* symbol Pointer to the symbol upon wich to base all derived
|
|
* symbols.
|
|
* encoding The encoding of "symbol".
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitToSymbols(
|
|
ut_unit* const unit,
|
|
const char* const symbol,
|
|
ut_encoding encoding)
|
|
{
|
|
return mapUnitToIds(unit, symbol, encoding, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps an identifier to a unit.
|
|
*
|
|
* Arguments:
|
|
* id Pointer to the identifier.
|
|
* encoding The character encoding of "id".
|
|
* unit Pointer to the unit.
|
|
* isName Whether or not "id" is a name.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapIdToUnit(
|
|
const char* id,
|
|
const ut_encoding encoding,
|
|
ut_unit* unit,
|
|
const int isName)
|
|
{
|
|
int success = 0; /* failure */
|
|
ut_unit* prev = ut_get_unit_by_name(unitSystem, id);
|
|
|
|
if (prev == NULL)
|
|
prev = ut_get_unit_by_symbol(unitSystem, id);
|
|
|
|
if (prev != NULL) {
|
|
char buf[128];
|
|
int nchar = ut_format(prev, buf, sizeof(buf),
|
|
UT_ASCII | UT_DEFINITION | UT_NAMES);
|
|
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"Duplicate definition for \"%s\" at \"%s\":%d", id,
|
|
currFile->path, XML_GetCurrentLineNumber(currFile->parser));
|
|
|
|
if (nchar < 0)
|
|
nchar =
|
|
ut_format(prev, buf, sizeof(buf), UT_ASCII | UT_DEFINITION);
|
|
|
|
if (nchar >= 0 && nchar < sizeof(buf)) {
|
|
buf[nchar] = 0;
|
|
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Previous definition was \"%s\"", buf);
|
|
}
|
|
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
/*
|
|
* Take prefixes into account for a prior definition by using
|
|
* ut_parse().
|
|
*/
|
|
prev = ut_parse(unitSystem, id, encoding);
|
|
|
|
if ((isName
|
|
? ut_map_name_to_unit(id, encoding, unit)
|
|
: ut_map_symbol_to_unit(id, encoding, unit))
|
|
!= UT_SUCCESS) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't map %s \"%s\" to unit",
|
|
isName ? "name" : "symbol", id);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (prev != NULL) {
|
|
char buf[128];
|
|
int nchar = ut_format(prev, buf, sizeof(buf),
|
|
UT_ASCII | UT_DEFINITION | UT_NAMES);
|
|
|
|
if (nchar < 0)
|
|
nchar = ut_format(prev, buf, sizeof(buf),
|
|
UT_ASCII | UT_DEFINITION);
|
|
|
|
if (nchar < 0 || nchar >= sizeof(buf)) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Definition of \"%s\" in \"%s\", "
|
|
"line %d, overrides prefixed-unit", id,
|
|
currFile->path,
|
|
XML_GetCurrentLineNumber(currFile->parser));
|
|
}
|
|
else {
|
|
buf[nchar] = 0;
|
|
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Definition of \"%s\" in \"%s\", "
|
|
"line %d, overrides prefixed-unit \"%s\"",
|
|
id, currFile->path,
|
|
XML_GetCurrentLineNumber(currFile->parser), buf);
|
|
}
|
|
}
|
|
|
|
success = 1;
|
|
}
|
|
}
|
|
|
|
ut_free(prev); /* NULL safe */
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps an identifier and all derivatives to a unit.
|
|
*
|
|
* Arguments:
|
|
* id Pointer to the identifier.
|
|
* encoding The encoding of "id".
|
|
* unit Pointer to the unit.
|
|
* isName Whether or not "id" is a name.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapIdsToUnit(
|
|
const char* const id,
|
|
const ut_encoding encoding,
|
|
ut_unit* const unit,
|
|
const int isName)
|
|
{
|
|
Identifiers ids;
|
|
int success = 1;
|
|
|
|
if (!makeDerivatives(id, encoding, &ids)) {
|
|
success = 0;
|
|
}
|
|
else {
|
|
if (ids.ascii[0])
|
|
success = mapIdToUnit(ids.ascii, UT_ASCII, unit, isName);
|
|
if (success && ids.latin1[0])
|
|
success = mapIdToUnit(ids.latin1, UT_LATIN1, unit, isName);
|
|
if (success && ids.latin1Nbsp[0])
|
|
success = mapIdToUnit(ids.latin1Nbsp, UT_LATIN1, unit, isName);
|
|
if (success && ids.utf8[0])
|
|
success = mapIdToUnit(ids.utf8, UT_UTF8, unit, isName);
|
|
if (success && ids.utf8Nbsp[0])
|
|
success = mapIdToUnit(ids.utf8Nbsp, UT_UTF8, unit, isName);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a name and all derivatives to a unit.
|
|
*
|
|
* Arguments:
|
|
* name Pointer to the name.
|
|
* encoding The encoding of "name".
|
|
* unit Pointer to the unit.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapNamesToUnit(
|
|
const char* const name,
|
|
const ut_encoding encoding,
|
|
ut_unit* const unit)
|
|
{
|
|
return mapIdsToUnit(name, encoding, unit, 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps a symbol and all derivatives to a unit.
|
|
*
|
|
* Arguments:
|
|
* symbol Pointer to the symbol.
|
|
* encoding The encoding of "symbol".
|
|
* unit Pointer to the unit.
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapSymbolsToUnit(
|
|
const char* const symbol,
|
|
const ut_encoding encoding,
|
|
ut_unit* const unit)
|
|
{
|
|
return mapIdsToUnit(symbol, encoding, unit, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps between a unit and a name.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* name Pointer to the name .
|
|
* encoding The encoding of "name".
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitAndName(
|
|
ut_unit* const unit,
|
|
const char* const name,
|
|
ut_encoding encoding)
|
|
{
|
|
return
|
|
mapNamesToUnit(name, encoding, unit) &&
|
|
mapUnitToNames(unit, name, encoding);
|
|
}
|
|
|
|
|
|
/*
|
|
* Maps between a unit and a symbol.
|
|
*
|
|
* Arguments:
|
|
* unit Pointer to the unit.
|
|
* symbol Pointer to the symbol .
|
|
* encoding The encoding of "symbol".
|
|
* Returns:
|
|
* 0 Failure.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
mapUnitAndSymbol(
|
|
ut_unit* const unit,
|
|
const char* const symbol,
|
|
ut_encoding encoding)
|
|
{
|
|
return
|
|
mapSymbolsToUnit(symbol, encoding, unit) &&
|
|
mapUnitToSymbols(unit, symbol, encoding);
|
|
}
|
|
|
|
|
|
/*
|
|
* Initializes a "File" data-structure to the default state.
|
|
*/
|
|
static void
|
|
fileInit(
|
|
File* const file)
|
|
{
|
|
file->context = START;
|
|
file->skipDepth = 0;
|
|
file->value = 0;
|
|
file->xmlEncoding = UT_ASCII;
|
|
file->textEncoding = UT_ASCII;
|
|
file->unit = NULL;
|
|
file->fd = -1;
|
|
file->parser = NULL;
|
|
file->isBase = 0;
|
|
file->isDimensionless = 0;
|
|
file->haveValue = 0;
|
|
file->noPLural = 0;
|
|
file->nameSeen = 0;
|
|
file->symbolSeen = 0;
|
|
file->path = NULL;
|
|
(void)memset(file->singular, 0, sizeof(file->singular));
|
|
(void)memset(file->plural, 0, sizeof(file->plural));
|
|
(void)memset(file->symbol, 0, sizeof(file->symbol));
|
|
}
|
|
|
|
|
|
/*
|
|
* Clears the text buffer for elements.
|
|
*/
|
|
static void
|
|
clearText(void)
|
|
{
|
|
if (text != NULL)
|
|
*text = 0;
|
|
|
|
nbytes = 0;
|
|
currFile->textEncoding = UT_ASCII;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Accumulates the textual portion of an element.
|
|
*/
|
|
static void
|
|
accumulateText(
|
|
void* data,
|
|
const char* string, /* input text in UTF-8 */
|
|
int len)
|
|
{
|
|
char* tmp = realloc(text, nbytes + len + 1);
|
|
|
|
if (tmp == NULL) {
|
|
ut_set_status(UT_OS);
|
|
ut_handle_error_message(strerror(errno));
|
|
ut_handle_error_message("Couldn't reallocate %lu-byte text buffer",
|
|
nbytes+len+1);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
int i;
|
|
|
|
text = tmp;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
text[nbytes++] = string[i];
|
|
|
|
if (!IS_ASCII(string[i]))
|
|
currFile->textEncoding = UT_UTF8;
|
|
}
|
|
|
|
text[nbytes] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
/*
|
|
* Converts the accumulated text from UTF-8 to user-specified encoding.
|
|
*
|
|
* Returns:
|
|
* 0 Failure: the text could not be converted.
|
|
* else Success.
|
|
*/
|
|
static int
|
|
convertText(void)
|
|
{
|
|
int success = 1;
|
|
|
|
if (currFile->encoding == UT_ASCII) {
|
|
unsigned char* cp;
|
|
|
|
for (cp = text; *cp && IS_ASCII(*cp); cp++)
|
|
/* EMPTY */;
|
|
|
|
if (*cp) {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message("Character isn't US-ASCII: %#x", *cp);
|
|
XML_StopParser(currFile->parser, 0);
|
|
|
|
success = 0;
|
|
}
|
|
else {
|
|
textEncoding = UT_ASCII;
|
|
}
|
|
}
|
|
else if (currFile->encoding == UT_LATIN1) {
|
|
unsigned char* in;
|
|
unsigned char* out;
|
|
|
|
for (in = out = text; *in; ++in, ++out) {
|
|
if (IS_ASCII(*in)) {
|
|
*out = *in;
|
|
}
|
|
else {
|
|
if (*in <= 0xC3) {
|
|
*out = (unsigned char)((*in++ & 0x3) << 6);
|
|
*out |= (unsigned char)(*in & 0x3F);
|
|
}
|
|
else {
|
|
ut_set_status(UT_SYNTAX);
|
|
ut_handle_error_message(
|
|
"Character is not representable in ISO-8859-1 "
|
|
"(Latin-1): %d", 1+(int)((char*)out - text));
|
|
XML_StopParser(currFile->parser, 0);
|
|
|
|
success = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*out = 0;
|
|
nbytes = out - (unsigned char*)text;
|
|
textEncoding = UT_LATIN1;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Handles the start of a <unit-system> element.
|
|
*/
|
|
static void
|
|
startUnitSystem(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != START) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <unit-system> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
|
|
currFile->context = UNIT_SYSTEM;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <unit-system> element.
|
|
*/
|
|
static void
|
|
endUnitSystem(
|
|
void* data)
|
|
{}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <prefix> element.
|
|
*/
|
|
static void
|
|
startPrefix(
|
|
void* data,
|
|
const char* const* atts)
|
|
{
|
|
if (currFile->context != UNIT_SYSTEM) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <prefix> element");
|
|
}
|
|
else {
|
|
currFile->prefixAdded = 0;
|
|
currFile->haveValue = 0;
|
|
}
|
|
|
|
currFile->context = PREFIX;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <prefix> element.
|
|
*/
|
|
static void
|
|
endPrefix(
|
|
void* data)
|
|
{
|
|
if (!currFile->haveValue || !currFile->prefixAdded) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Prefix incompletely specified");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->haveValue = 0;
|
|
}
|
|
|
|
currFile->context = UNIT_SYSTEM;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <unit> element.
|
|
*/
|
|
static void
|
|
startUnit(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT_SYSTEM) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <unit> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
ut_free(currFile->unit);
|
|
currFile->unit = NULL;
|
|
currFile->isBase = 0;
|
|
currFile->isDimensionless = 0;
|
|
currFile->singular[0] = 0;
|
|
currFile->plural[0] = 0;
|
|
currFile->symbol[0] = 0;
|
|
currFile->nameSeen = 0;
|
|
currFile->symbolSeen = 0;
|
|
}
|
|
|
|
currFile->context = UNIT;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <unit> element.
|
|
*/
|
|
static void
|
|
endUnit(
|
|
void* data)
|
|
{
|
|
if (currFile->isBase) {
|
|
if (!currFile->nameSeen) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Base unit needs a name");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
if (!currFile->symbolSeen) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Base unit needs a symbol");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
ut_free(currFile->unit);
|
|
currFile->unit = NULL;
|
|
currFile->context = UNIT_SYSTEM;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <base> element.
|
|
*/
|
|
static void
|
|
startBase(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <base> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (currFile->isDimensionless) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"<dimensionless> and <base> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->unit != NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<base> and <def> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->isBase) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<base> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <base> element.
|
|
*/
|
|
static void
|
|
endBase(
|
|
void* data)
|
|
{
|
|
currFile->unit = ut_new_base_unit(unitSystem);
|
|
|
|
if (currFile->unit == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't create new base unit");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->isBase = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <dimensionless> element.
|
|
*/
|
|
static void
|
|
startDimensionless(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <dimensionless> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (currFile->isBase) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"<dimensionless> and <base> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->unit != NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"<dimensionless> and <def> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->isDimensionless) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<dimensionless> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <dimensionless> element.
|
|
*/
|
|
static void
|
|
endDimensionless(
|
|
void* data)
|
|
{
|
|
currFile->unit = ut_new_dimensionless_unit(unitSystem);
|
|
|
|
if (currFile->unit == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't create new dimensionless unit");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->isDimensionless = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <def> element.
|
|
*/
|
|
static void
|
|
startDef(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <def> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->isBase) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"<base> and <def> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->isDimensionless) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"<dimensionless> and <def> are mutually exclusive");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->unit != NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<def> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <def> element.
|
|
*/
|
|
static void
|
|
endDef(
|
|
void* data)
|
|
{
|
|
if (nbytes == 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Empty unit definition");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->unit = ut_parse(unitSystem, text, currFile->textEncoding);
|
|
|
|
if (currFile->unit == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"Couldn't parse unit specification \"%s\"", text);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <name> element.
|
|
*/
|
|
static void
|
|
startName(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context == PREFIX) {
|
|
if (!currFile->haveValue) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("No previous <value> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
else if (currFile->context == UNIT || currFile->context == ALIASES) {
|
|
if (currFile->unit == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"No previous <base>, <dimensionless>, or <def> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->noPLural = 0;
|
|
currFile->singular[0] = 0;
|
|
currFile->plural[0] = 0;
|
|
currFile->context =
|
|
currFile->context == UNIT ? UNIT_NAME : ALIAS_NAME;
|
|
}
|
|
}
|
|
else {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <name> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <name> element.
|
|
*/
|
|
static void
|
|
endName(
|
|
void* data)
|
|
{
|
|
if (currFile->context == PREFIX) {
|
|
if (!currFile->haveValue) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("No previous <value> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (ut_add_name_prefix(unitSystem, text, currFile->value) !=
|
|
UT_SUCCESS) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"Couldn't map name-prefix \"%s\" to value %g", text,
|
|
currFile->value);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->prefixAdded = 1;
|
|
}
|
|
}
|
|
}
|
|
else if (currFile->context == UNIT_NAME) {
|
|
if (currFile->singular[0] == 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<name> needs a <singular>");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (!mapUnitAndName(currFile->unit, currFile->singular,
|
|
currFile->textEncoding)) {
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (!currFile->noPLural) {
|
|
const char* plural = NULL;
|
|
|
|
if (currFile->plural[0] != 0) {
|
|
plural = currFile->plural;
|
|
}
|
|
else if (currFile->singular[0] != 0) {
|
|
plural = ut_form_plural(currFile->singular);
|
|
|
|
if (plural == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't form plural of "
|
|
"\"%s\"", currFile->singular);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
if (plural != NULL) {
|
|
/*
|
|
* Because the unit is already mapped to the singular
|
|
* name, it is not mapped to the plural name.
|
|
*/
|
|
if (!mapNamesToUnit(plural, currFile->textEncoding,
|
|
currFile->unit)) {
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
} /* <noplural/> not specified */
|
|
} /* unit mapped to singular name */
|
|
} /* singular name specified */
|
|
|
|
currFile->nameSeen = 1;
|
|
currFile->context = UNIT;
|
|
} /* defining name for unit */
|
|
else if (currFile->context == ALIAS_NAME) {
|
|
if (currFile->singular[0] == 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<name> needs a <singular>");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
if (!mapNamesToUnit(currFile->singular, currFile->textEncoding,
|
|
currFile->unit)) {
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
|
|
if (!currFile->noPLural) {
|
|
const char* plural = NULL;
|
|
|
|
if (currFile->plural[0] != 0) {
|
|
plural = currFile->plural;
|
|
}
|
|
else if (currFile->singular[0] != 0) {
|
|
plural = ut_form_plural(currFile->singular);
|
|
|
|
if (plural == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Couldn't form plural of "
|
|
"\"%s\"", currFile->singular);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
if (plural != NULL) {
|
|
if (!mapNamesToUnit(plural, currFile->textEncoding,
|
|
currFile->unit))
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
} /* <noplural> not specified */
|
|
} /* singular name specified */
|
|
|
|
currFile->context = ALIASES;
|
|
} /* defining name for alias */
|
|
else {
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <singular> element.
|
|
*/
|
|
static void
|
|
startSingular(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT_NAME && currFile->context != ALIAS_NAME) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <singular> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->singular[0] != 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<singular> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <singular> element.
|
|
*/
|
|
static void
|
|
endSingular(
|
|
void* data)
|
|
{
|
|
if (nbytes >= NAME_SIZE) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Name \"%s\" is too long", text);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
(void)strcpy(currFile->singular, text);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <plural> element.
|
|
*/
|
|
static void
|
|
startPlural(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT_NAME && currFile->context != ALIAS_NAME ) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <plural> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->noPLural || currFile->plural[0] != 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<plural> or <noplural> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <plural> element.
|
|
*/
|
|
static void
|
|
endPlural(
|
|
void* data)
|
|
{
|
|
if (nbytes == 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Empty <plural> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (nbytes >= NAME_SIZE) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Plural name \"%s\" is too long", text);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
(void)strcpy(currFile->plural, text);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <noplural> element.
|
|
*/
|
|
static void
|
|
startNoPlural(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT_NAME || currFile->context != ALIAS_NAME) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <noplural> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->plural[0] != 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<plural> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <noplural> element.
|
|
*/
|
|
static void
|
|
endNoPlural(
|
|
void* data)
|
|
{
|
|
currFile->noPLural = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <symbol> element.
|
|
*/
|
|
static void
|
|
startSymbol(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context == PREFIX) {
|
|
if (!currFile->haveValue) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("No previous <value> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
else if (currFile->context == UNIT || currFile->context == ALIASES) {
|
|
if (currFile->unit == NULL) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"No previous <base>, <dimensionless>, or <def> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
else {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <symbol> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <symbol> element.
|
|
*/
|
|
static void
|
|
endSymbol(
|
|
void* data)
|
|
{
|
|
if (currFile->context == PREFIX) {
|
|
if (ut_add_symbol_prefix(unitSystem, text, currFile->value) !=
|
|
UT_SUCCESS) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(
|
|
"Couldn't map symbol-prefix \"%s\" to value %g",
|
|
text, currFile->value);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->prefixAdded = 1;
|
|
}
|
|
}
|
|
else if (currFile->context == UNIT) {
|
|
if (!mapUnitAndSymbol(currFile->unit, text, currFile->textEncoding))
|
|
XML_StopParser(currFile->parser, 0);
|
|
|
|
currFile->symbolSeen = 1;
|
|
}
|
|
else if (currFile->context == ALIASES) {
|
|
if (!mapSymbolsToUnit(text, currFile->textEncoding, currFile->unit))
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of a <value> element.
|
|
*/
|
|
static void
|
|
startValue(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != PREFIX) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <value> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (currFile->haveValue) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("<value> element already seen");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of a <value> element.
|
|
*/
|
|
static void
|
|
endValue(
|
|
void* data)
|
|
{
|
|
char* endPtr;
|
|
|
|
errno = 0;
|
|
currFile->value = strtod(text, &endPtr);
|
|
|
|
if (errno != 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message(strerror(errno));
|
|
ut_handle_error_message("Couldn't decode numeric prefix value \"%s\"",
|
|
text);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else if (*endPtr != 0) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Invalid numeric prefix value \"%s\"", text);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
currFile->haveValue = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of an <alias> element.
|
|
*/
|
|
static void
|
|
startAliases(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <aliases> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
|
|
currFile->context = ALIASES;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of an <alias> element.
|
|
*/
|
|
static void
|
|
endAliases(
|
|
void* data)
|
|
{
|
|
currFile->context = UNIT;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of an <import> element.
|
|
*/
|
|
static void
|
|
startImport(
|
|
void* data,
|
|
const char** atts)
|
|
{
|
|
if (currFile->context != UNIT_SYSTEM) {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Wrong place for <import> element");
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
else {
|
|
clearText();
|
|
ACCUMULATE_TEXT;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of an <import> element.
|
|
*/
|
|
static void
|
|
endImport(
|
|
void* data)
|
|
{
|
|
char buf[_POSIX_PATH_MAX];
|
|
const char* path;
|
|
|
|
if (text[0] == '/') {
|
|
path = text;
|
|
}
|
|
else {
|
|
(void)snprintf(buf, sizeof(buf), "%s/%s",
|
|
XML_GetBase(currFile->parser), text);
|
|
|
|
buf[sizeof(buf)-1] = 0;
|
|
path = buf;
|
|
}
|
|
|
|
ut_set_status(readXml(path));
|
|
|
|
if (ut_get_status() != UT_SUCCESS)
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the start of an element.
|
|
*/
|
|
static void
|
|
startElement(
|
|
void* data,
|
|
const XML_Char* name,
|
|
const XML_Char** atts)
|
|
{
|
|
if (currFile->skipDepth) {
|
|
currFile->skipDepth++;
|
|
}
|
|
else {
|
|
clearText();
|
|
|
|
if (strcasecmp(name, "unit-system") == 0) {
|
|
startUnitSystem(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "prefix") == 0) {
|
|
startPrefix(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "unit") == 0) {
|
|
startUnit(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "base") == 0) {
|
|
startBase(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "dimensionless") == 0) {
|
|
startDimensionless(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "def") == 0) {
|
|
startDef(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "value") == 0) {
|
|
startValue(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "name") == 0) {
|
|
startName(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "singular") == 0) {
|
|
startSingular(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "plural") == 0) {
|
|
startPlural(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "symbol") == 0) {
|
|
startSymbol(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "aliases") == 0) {
|
|
startAliases(data, atts);
|
|
}
|
|
else if (strcasecmp(name, "import") == 0) {
|
|
startImport(data, atts);
|
|
}
|
|
else {
|
|
currFile->skipDepth = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the end of an element.
|
|
*/
|
|
static void
|
|
endElement(
|
|
void* data,
|
|
const XML_Char* name)
|
|
{
|
|
if (currFile->skipDepth != 0) {
|
|
--currFile->skipDepth;
|
|
}
|
|
else {
|
|
if (strcasecmp(name, "unit-system") == 0) {
|
|
endUnitSystem(data);
|
|
}
|
|
else if (strcasecmp(name, "prefix") == 0) {
|
|
endPrefix(data);
|
|
}
|
|
else if (strcasecmp(name, "unit") == 0) {
|
|
endUnit(data);
|
|
}
|
|
else if (strcasecmp(name, "base") == 0) {
|
|
endBase(data);
|
|
}
|
|
else if (strcasecmp(name, "dimensionless") == 0) {
|
|
endDimensionless(data);
|
|
}
|
|
else if (strcasecmp(name, "def") == 0) {
|
|
endDef(data);
|
|
}
|
|
else if (strcasecmp(name, "value") == 0) {
|
|
endValue(data);
|
|
}
|
|
else if (strcasecmp(name, "name") == 0) {
|
|
endName(data);
|
|
}
|
|
else if (strcasecmp(name, "singular") == 0) {
|
|
endSingular(data);
|
|
}
|
|
else if (strcasecmp(name, "plural") == 0) {
|
|
endPlural(data);
|
|
}
|
|
else if (strcasecmp(name, "symbol") == 0) {
|
|
endSymbol(data);
|
|
}
|
|
else if (strcasecmp(name, "aliases") == 0) {
|
|
endAliases(data);
|
|
}
|
|
else if (strcasecmp(name, "import") == 0) {
|
|
endImport(data);
|
|
}
|
|
else {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Unknown element \"<%s>\"", name);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
IGNORE_TEXT;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handles the header of an XML file.
|
|
*/
|
|
static void
|
|
declareXml(
|
|
void* data,
|
|
const char* version,
|
|
const char* encoding,
|
|
int standalone)
|
|
{
|
|
if (strcasecmp(encoding, "US-ASCII") == 0) {
|
|
currFile->xmlEncoding = UT_ASCII;
|
|
}
|
|
else if (strcasecmp(encoding, "ISO-8859-1") == 0) {
|
|
currFile->xmlEncoding = UT_LATIN1;
|
|
}
|
|
else if (strcasecmp(encoding, "UTF-8") == 0) {
|
|
currFile->xmlEncoding = UT_UTF8;
|
|
}
|
|
else {
|
|
ut_set_status(UT_PARSE);
|
|
ut_handle_error_message("Unknown XML encoding \"%s\"", encoding);
|
|
XML_StopParser(currFile->parser, 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Reads an XML file into the unit-system with the given XML parser.
|
|
*
|
|
* Arguments:
|
|
* parser Pointer to the XML parser.
|
|
* path Pointer to the pathname of the XML file.
|
|
* Returns:
|
|
* UT_SUCCESS Success.
|
|
* UT_OPEN_ARG File "path" couldn't be opened. See "errno".
|
|
* UT_OS Operating-system error. See "errno".
|
|
* UT_PARSE Parse failure.
|
|
*/
|
|
static ut_status
|
|
readXmlWithParser(
|
|
XML_Parser parser,
|
|
const char* const path)
|
|
{
|
|
ut_status status = UT_SUCCESS; /* success */
|
|
File file;
|
|
|
|
assert(parser != NULL);
|
|
assert(path != NULL);
|
|
|
|
fileInit(&file);
|
|
|
|
file.fd = open(path, O_RDONLY);
|
|
|
|
if (file.fd == -1) {
|
|
status = UT_OPEN_ARG;
|
|
ut_set_status(status);
|
|
ut_handle_error_message(strerror(errno));
|
|
ut_handle_error_message("Couldn't open file \"%s\"", path);
|
|
}
|
|
else {
|
|
int nbytes;
|
|
File* const prevFile = currFile;
|
|
|
|
file.path = path;
|
|
file.parser = parser;
|
|
currFile = &file;
|
|
|
|
do {
|
|
char buf[BUFSIZ]; /* from <stdio.h> */
|
|
|
|
nbytes = read(file.fd, buf, sizeof(buf));
|
|
|
|
if (nbytes < 0) {
|
|
status = UT_OS;
|
|
ut_set_status(status);
|
|
ut_handle_error_message(strerror(errno));
|
|
}
|
|
else {
|
|
if (XML_Parse(file.parser, buf, nbytes, nbytes == 0)
|
|
!= XML_STATUS_OK) {
|
|
status = UT_PARSE;
|
|
ut_set_status(status);
|
|
ut_handle_error_message(
|
|
XML_ErrorString(XML_GetErrorCode(file.parser)));
|
|
}
|
|
}
|
|
} while (status == UT_SUCCESS && nbytes > 0);
|
|
|
|
if (status != UT_SUCCESS) {
|
|
/*
|
|
* Parsing of the XML file terminated prematurely.
|
|
*/
|
|
ut_handle_error_message("File \"%s\", line %d, column %d",
|
|
path, XML_GetCurrentLineNumber(file.parser),
|
|
XML_GetCurrentColumnNumber(file.parser));
|
|
}
|
|
|
|
currFile = prevFile;
|
|
|
|
(void)close(file.fd);
|
|
file.fd = -1;
|
|
} /* "file.fd" open */
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Reads an XML file into the unit-system.
|
|
*
|
|
* Arguments:
|
|
* path Pointer to the pathname of the XML file.
|
|
* Returns:
|
|
* UT_SUCCESS Success.
|
|
* UT_OPEN_ARG File "path" couldn't be opened. See "errno".
|
|
* UT_OS Operating-system error. See "errno".
|
|
* UT_PARSE Parse failure.
|
|
*/
|
|
static ut_status
|
|
readXml(
|
|
const char* const path)
|
|
{
|
|
ut_status status;
|
|
XML_Parser parser = XML_ParserCreate(NULL);
|
|
|
|
if (parser == NULL) {
|
|
status = UT_OS;
|
|
ut_set_status(status);
|
|
ut_handle_error_message(strerror(errno));
|
|
ut_handle_error_message("Couldn't create XML parser");
|
|
}
|
|
else {
|
|
char base[_POSIX_PATH_MAX];
|
|
|
|
(void)strncpy(base, path, sizeof(base));
|
|
base[sizeof(base)-1] = 0;
|
|
(void)memmove(base, dirname(base), sizeof(base));
|
|
base[sizeof(base)-1] = 0;
|
|
|
|
if (XML_SetBase(parser, base) != XML_STATUS_OK) {
|
|
status = UT_OS;
|
|
ut_set_status(status);
|
|
ut_handle_error_message(strerror(errno));
|
|
ut_handle_error_message("XML_SetBase(\"%s\") failure", base);
|
|
}
|
|
else {
|
|
XML_SetXmlDeclHandler(parser, declareXml);
|
|
XML_SetElementHandler(parser, startElement, endElement);
|
|
XML_SetCharacterDataHandler(parser, NULL);
|
|
|
|
status = readXmlWithParser(parser, path);
|
|
} /* parser "base" set */
|
|
|
|
XML_ParserFree(parser);
|
|
} /* parser != NULL */
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the unit-system corresponding to an XML file. This is the usual way
|
|
* that a client will obtain a unit-system.
|
|
*
|
|
* Arguments:
|
|
* path The pathname of the XML file or NULL. If NULL, then the
|
|
* pathname specified by the environment variable UDUNITS2_XML_PATH
|
|
* is used if set; otherwise, the compile-time pathname of the
|
|
* installed, default, unit database is used.
|
|
* Returns:
|
|
* NULL Failure. "ut_get_status()" will be
|
|
* UT_OPEN_ARG "path" is non-NULL but file couldn't be
|
|
* opened. See "errno" for reason.
|
|
* UT_OPEN_ENV "path" is NULL and environment variable
|
|
* UDUNITS2_XML_PATH is set but file
|
|
* couldn't be opened. See "errno" for
|
|
* reason.
|
|
* UT_OPEN_DEFAULT "path" is NULL, environment variable
|
|
* UDUNITS2_XML_PATH is unset, and the
|
|
* installed, default, unit database
|
|
* couldn't be opened. See "errno" for
|
|
* reason.
|
|
* UT_PARSE Couldn't parse unit database.
|
|
* UT_OS Operating-system error. See "errno".
|
|
* else Pointer to the unit-system defined by "path".
|
|
*/
|
|
ut_system*
|
|
ut_read_xml(
|
|
const char* path)
|
|
{
|
|
ut_set_status(UT_SUCCESS);
|
|
|
|
unitSystem = ut_new_system();
|
|
|
|
if (unitSystem == NULL) {
|
|
ut_handle_error_message("Couldn't create new unit-system");
|
|
}
|
|
else {
|
|
ut_status status;
|
|
ut_status openError;
|
|
|
|
if (path != NULL) {
|
|
openError = UT_OPEN_ARG;
|
|
}
|
|
else {
|
|
path = getenv("UDUNITS2_XML_PATH");
|
|
|
|
if (path != NULL) {
|
|
openError = UT_OPEN_ENV;
|
|
}
|
|
else {
|
|
path = DEFAULT_UDUNITS2_XML_PATH;
|
|
openError = UT_OPEN_DEFAULT;
|
|
}
|
|
}
|
|
|
|
status = readXml(path);
|
|
|
|
if (status == UT_OPEN_ARG) {
|
|
status = openError;
|
|
}
|
|
else if (status == UT_SUCCESS) {
|
|
ut_unit* second = ut_get_unit_by_name(unitSystem, "second");
|
|
|
|
if (second != NULL) {
|
|
if (ut_set_second(second) != UT_SUCCESS)
|
|
ut_handle_error_message(
|
|
"Couldn't set \"second\" unit in unit-system");
|
|
|
|
ut_free(second);
|
|
} /* second != NULL */
|
|
} /* XML successfully read */
|
|
|
|
if (status != UT_SUCCESS) {
|
|
ut_free_system(unitSystem);
|
|
unitSystem = NULL;
|
|
}
|
|
|
|
ut_set_status(status);
|
|
} /* unitSystem != NULL */
|
|
|
|
return unitSystem;
|
|
}
|