quakeforge/libs/util/qfplist.c

1038 lines
20 KiB
C
Raw Normal View History

/*
qfplist.c
Property list management
Copyright (C) 2000 Jeff Teunissen <deek@d2dc.net>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
2011-08-25 13:35:20 +00:00
#if defined(_WIN32) && defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/qfplist.h"
#include "QF/qtypes.h"
2001-08-01 07:05:28 +00:00
#include "QF/sys.h"
/*
Generic property list item.
*/
struct plitem_s {
pltype_t type;
void *data;
};
/*
Dictionaries
*/
struct dictkey_s {
char *key;
plitem_t *value;
};
typedef struct dictkey_s dictkey_t;
/*
Arrays
*/
struct plarray_s {
int numvals; ///< Number of items in array
int maxvals; ///< Number of items that can be stored
///< before a realloc is necesary.
struct plitem_s **values; ///< Array data
};
typedef struct plarray_s plarray_t;
/*
Typeless, unformatted binary data
*/
struct plbinary_s {
size_t size;
void *data;
};
typedef struct plbinary_s plbinary_t;
typedef struct pldata_s { // Unparsed property list string
const char *ptr;
unsigned int end;
unsigned int pos;
unsigned int line;
const char *error;
} pldata_t;
// Ugly defines for fast checking and conversion from char to number
#define inrange(ch,min,max) ((ch) >= (min) && (ch) <= (max))
#define char2num(ch) \
(inrange((ch), '0', '9') ? ((ch) - '0') \
: 10 + (inrange((ch), 'a', 'f') ? ((ch) - 'a') \
: ((ch) - 'A')))
static byte quotable_bitmap[32];
static inline int
is_quotable (byte x)
{
return quotable_bitmap[x / 8] & (1 << (x % 8));
}
static void
init_quotables (void)
{
const char *unquotables = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^";
2005-04-11 20:59:03 +00:00
const byte *c;
memset (quotable_bitmap, ~0, sizeof (quotable_bitmap));
for (c = (byte *) unquotables; *c; c++)
quotable_bitmap[*c / 8] &= ~(1 << (*c % 8));
}
static plitem_t *PL_ParsePropertyListItem (pldata_t *);
static qboolean PL_SkipSpace (pldata_t *);
static char *PL_ParseQuotedString (pldata_t *);
static char *PL_ParseUnquotedString (pldata_t *);
static char *PL_ParseData (pldata_t *, int *);
static const char *
dict_get_key (const void *i, void *unused)
{
dictkey_t *item = (dictkey_t *) i;
return item->key;
}
static void
dict_free (void *i, void *unused)
{
dictkey_t *item = (dictkey_t *) i;
free (item->key);
if (item->value) // Make descended stuff get freed
PL_Free (item->value);
free (item);
}
static plitem_t *
PL_NewItem (pltype_t type)
{
plitem_t *item = calloc (1, sizeof (plitem_t));
item->type = type;
return item;
}
VISIBLE plitem_t *
PL_NewDictionary (void)
{
plitem_t *item = PL_NewItem (QFDictionary);
hashtab_t *dict = Hash_NewTable (1021, dict_get_key, dict_free, NULL);
item->data = dict;
return item;
}
VISIBLE plitem_t *
PL_NewArray (void)
{
plitem_t *item = PL_NewItem (QFArray);
plarray_t *array = calloc (1, sizeof (plarray_t));
item->data = array;
return item;
}
VISIBLE plitem_t *
PL_NewData (void *data, size_t size)
{
plitem_t *item = PL_NewItem (QFBinary);
plbinary_t *bin = malloc (sizeof (plbinary_t));
item->data = bin;
bin->data = data;
bin->size = size;
return item;
}
static plitem_t *
new_string (char *str)
{
plitem_t *item = PL_NewItem (QFString);
item->data = str;
return item;
}
VISIBLE plitem_t *
PL_NewString (const char *str)
{
return new_string (strdup (str));
}
VISIBLE void
PL_Free (plitem_t *item)
{
switch (item->type) {
case QFDictionary:
Hash_DelTable (item->data);
break;
case QFArray: {
int i = ((plarray_t *) item->data)->numvals;
while (i-- > 0) {
PL_Free (((plarray_t *) item->data)->values[i]);
}
2003-04-07 19:29:52 +00:00
free (((plarray_t *) item->data)->values);
free (item->data);
}
break;
case QFBinary:
free (((plbinary_t *) item->data)->data);
free (item->data);
break;
case QFString:
free (item->data);
break;
}
free (item);
}
VISIBLE const char *
PL_String (plitem_t *string)
{
if (string->type != QFString)
return NULL;
return string->data;
}
VISIBLE plitem_t *
PL_ObjectForKey (plitem_t *dict, const char *key)
{
hashtab_t *table = (hashtab_t *) dict->data;
dictkey_t *k;
if (dict->type != QFDictionary)
return NULL;
k = (dictkey_t *) Hash_Find (table, key);
return k ? k->value : NULL;
}
VISIBLE plitem_t *
PL_RemoveObjectForKey (plitem_t *dict, const char *key)
{
hashtab_t *table = (hashtab_t *) dict->data;
dictkey_t *k;
plitem_t *value;
if (dict->type != QFDictionary)
return NULL;
k = (dictkey_t *) Hash_Del (table, key);
if (!k)
return NULL;
value = k->value;
k->value = 0;
dict_free (k, 0);
return value;
}
VISIBLE plitem_t *
PL_D_AllKeys (plitem_t *dict)
{
void **list, **l;
dictkey_t *current;
plitem_t *array;
if (dict->type != QFDictionary)
return NULL;
if (!(l = list = Hash_GetList ((hashtab_t *) dict->data)))
return NULL;
if (!(array = PL_NewArray ()))
return NULL;
while ((current = (dictkey_t *) *l++)) {
PL_A_AddObject (array, PL_NewString (current->key));
}
free (list);
return array;
}
VISIBLE int
PL_D_NumKeys (plitem_t *dict)
{
if (dict->type != QFDictionary)
return 0;
return Hash_NumElements ((hashtab_t *) dict->data);
}
VISIBLE plitem_t *
2004-01-10 06:11:41 +00:00
PL_ObjectAtIndex (plitem_t *array, int index)
{
2004-01-10 06:11:41 +00:00
plarray_t *arr = (plarray_t *) array->data;
2004-01-10 06:11:41 +00:00
if (array->type != QFArray)
return NULL;
2004-01-10 06:11:41 +00:00
return index >= 0 && index < arr->numvals ? arr->values[index] : NULL;
}
VISIBLE qboolean
PL_D_AddObject (plitem_t *dict, const char *key, plitem_t *value)
{
dictkey_t *k;
if (dict->type != QFDictionary)
return false;
if ((k = Hash_Find ((hashtab_t *)dict->data, key))) {
PL_Free ((plitem_t *) k->value);
k->value = value;
} else {
k = malloc (sizeof (dictkey_t));
if (!k)
return false;
k->key = strdup (key);
k->value = value;
Hash_Add ((hashtab_t *)dict->data, k);
}
return true;
}
VISIBLE qboolean
PL_A_InsertObjectAtIndex (plitem_t *array, plitem_t *item, int index)
{
plarray_t *arr;
if (array->type != QFArray)
return false;
arr = (plarray_t *)array->data;
if (arr->numvals == arr->maxvals) {
int size = (arr->maxvals + 128) * sizeof (plitem_t *);
plitem_t **tmp = realloc (arr->values, size);
2003-04-07 19:29:52 +00:00
if (!tmp)
return false;
arr->maxvals += 128;
arr->values = tmp;
memset (arr->values + arr->numvals, 0,
(arr->maxvals - arr->numvals) * sizeof (plitem_t *));
2003-04-07 19:29:52 +00:00
}
if (index == -1)
index = arr->numvals;
if (index < 0 || index > arr->numvals)
return false;
memmove (arr->values + index + 1, arr->values + index,
(arr->numvals - index) * sizeof (plitem_t *));
arr->values[index] = item;
arr->numvals++;
return true;
}
VISIBLE qboolean
PL_A_AddObject (plitem_t *array, plitem_t *item)
{
return PL_A_InsertObjectAtIndex (array, item, -1);
}
VISIBLE int
PL_A_NumObjects (plitem_t *array)
{
if (array->type != QFArray)
return 0;
return ((plarray_t *) array->data)->numvals;
}
VISIBLE plitem_t *
2006-12-09 02:33:08 +00:00
PL_RemoveObjectAtIndex (plitem_t *array, int index)
{
plarray_t *arr;
plitem_t *item;
if (array->type != QFArray)
return 0;
arr = (plarray_t *)array->data;
if (index < 0 || index >= arr->numvals)
return 0;
item = arr->values[index];
arr->numvals--;
while (index < arr->numvals) {
arr->values[index] = arr->values[index + 1];
index++;
}
return item;
}
static qboolean
PL_SkipSpace (pldata_t *pl)
{
while (pl->pos < pl->end) {
char c = pl->ptr[pl->pos];
2001-06-19 02:26:19 +00:00
if (!isspace ((byte) c)) {
if (c == '/' && pl->pos < pl->end - 1) { // check for comments
if (pl->ptr[pl->pos + 1] == '/') {
pl->pos += 2;
while (pl->pos < pl->end) {
c = pl->ptr[pl->pos];
if (c == '\n')
break;
pl->pos++;
}
if (pl->pos >= pl->end) {
pl->error = "Reached end of string in comment";
return false;
}
} else if (pl->ptr[pl->pos + 1] == '*') { // "/*" comments
pl->pos += 2;
while (pl->pos < pl->end) {
c = pl->ptr[pl->pos];
if (c == '\n') {
pl->line++;
} else if (c == '*' && pl->pos < pl->end - 1
&& pl->ptr[pl->pos+1] == '/') {
pl->pos++;
break;
}
pl->pos++;
}
if (pl->pos >= pl->end) {
pl->error = "Reached end of string in comment";
return false;
}
} else {
return true;
}
} else {
return true;
}
}
if (c == '\n') {
pl->line++;
}
pl->pos++;
}
pl->error = "Reached end of string";
return false;
}
static inline byte
2007-05-13 03:27:54 +00:00
from_hex (byte a)
{
if (a >= '0' && a <= '9')
return a - '0';
if (a >= 'A' && a <= 'F')
return a - 'A' + 10;
return a - 'a' + 10;
}
static inline byte
make_byte (byte h, byte l)
{
2007-05-13 03:27:54 +00:00
return (from_hex (h) << 4) | from_hex (l);
}
static char *
PL_ParseData (pldata_t *pl, int *len)
{
unsigned start = ++pl->pos;
int nibbles = 0, i;
char *str, c;
while (pl->pos < pl->end) {
c = pl->ptr[pl->pos++];
2012-08-18 02:02:52 +00:00
if (isxdigit ((byte) c)) {
nibbles++;
continue;
}
if (c == '>') {
if (nibbles & 1) {
pl->error = "invalid data, missing nibble";
return NULL;
}
*len = nibbles / 2;
str = malloc (*len);
for (i = 0; i < *len; i++)
str[i] = make_byte (pl->ptr[start + i * 2],
pl->ptr[start + i * 2 + 1]);
return str;
}
pl->error = "invalid character in data";
return NULL;
}
pl->error = "Reached end of string while parsing data";
return NULL;
}
static char *
PL_ParseQuotedString (pldata_t *pl)
{
unsigned int start = ++pl->pos;
unsigned int escaped = 0;
unsigned int shrink = 0;
qboolean hex = false;
qboolean long_string = false;
char *str;
if (pl->ptr[pl->pos] == '"' &&
pl->ptr[pl->pos + 1] == '"') {
long_string = true;
start += 2;
pl->pos += 2;
}
while (pl->pos < pl->end) {
char c = pl->ptr[pl->pos];
if (escaped) {
if (escaped == 1 && inrange (c, '0', '7')) {
escaped++;
hex = false;
} else if (escaped > 1) {
if (escaped == 2 && c == 'x') {
hex = true;
shrink++;
escaped++;
} else if (hex && inrange (escaped, 3, 4)
&& isxdigit ((byte) c)) {
shrink++;
escaped++;
} else if (inrange (escaped, 1, 3) && inrange (c, '0', '7')) {
shrink++;
escaped++;
} else {
pl->pos--;
escaped = 0;
}
} else {
escaped = 0;
}
} else {
if (c == '\\') {
escaped = 1;
shrink++;
} else if (c == '"'
&& (!long_string || (pl->ptr[pl->pos + 1] == '"'
&& pl->ptr[pl->pos + 2] == '"'))) {
break;
}
}
if (c == '\n') {
pl->line++;
}
pl->pos++;
}
if (pl->pos >= pl->end) {
pl->error = "Reached end of string while parsing quoted string";
return NULL;
}
2001-06-19 02:26:19 +00:00
if (pl->pos - start - shrink == 0) {
str = strdup ("");
} else {
char *chars = alloca(pl->pos - start - shrink);
unsigned int j;
unsigned int k;
escaped = 0;
hex = false;
for (j = start, k = 0; j < pl->pos; j++) {
char c = pl->ptr[j];
if (escaped) {
if (escaped == 1 && inrange (c, '0', '7')) {
chars[k] = c - '0';
hex = false;
escaped++;
} else if (escaped > 1) {
if (escaped == 2 && c == 'x') {
hex = true;
escaped++;
} else if (hex && inrange (escaped, 3, 4)
&& isxdigit ((byte) c)) {
chars[k] <<= 4;
chars[k] |= char2num (c);
escaped++;
} else if (inrange (escaped, 1, 3)
&& inrange (c, '0', '7')) {
chars[k] <<= 3;
chars[k] |= (c - '0');
escaped++;
} else {
escaped = 0;
j--;
k++;
}
} else {
escaped = 0;
switch (c) {
case 'a':
chars[k] = '\a';
break;
case 'b':
chars[k] = '\b';
break;
case 't':
chars[k] = '\t';
break;
case 'r':
chars[k] = '\r';
break;
case 'n':
chars[k] = '\n';
break;
case 'v':
chars[k] = '\v';
break;
case 'f':
chars[k] = '\f';
break;
default:
chars[k] = c;
break;
}
k++;
}
} else {
chars[k] = c;
if (c == '\\') {
escaped = 1;
} else {
k++;
}
}
}
str = strncat (calloc ((pl->pos - start - shrink) + 1, 1), chars,
pl->pos - start - shrink);
}
if (long_string)
pl->pos += 2;
pl->pos++;
return str;
}
static char *
PL_ParseUnquotedString (pldata_t *pl)
{
unsigned int start = pl->pos;
char *str;
while (pl->pos < pl->end) {
if (is_quotable (pl->ptr[pl->pos]))
break;
pl->pos++;
}
str = strncat (calloc ((pl->pos - start) + 1, 1), &pl->ptr[start],
pl->pos - start);
return str;
}
static plitem_t *
PL_ParsePropertyListItem (pldata_t *pl)
{
plitem_t *item = NULL;
if (!PL_SkipSpace (pl))
return NULL;
2001-06-19 02:26:19 +00:00
switch (pl->ptr[pl->pos]) {
case '{':
{
item = PL_NewDictionary ();
pl->pos++;
2001-06-19 02:26:19 +00:00
while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != '}') {
plitem_t *key;
plitem_t *value;
if (!(key = PL_ParsePropertyListItem (pl))) {
PL_Free (item);
return NULL;
}
if (!(PL_SkipSpace (pl))) {
PL_Free (key);
PL_Free (item);
return NULL;
}
if (key->type != QFString) {
pl->error = "Key is not a string";
PL_Free (key);
PL_Free (item);
return NULL;
}
if (pl->ptr[pl->pos] != '=') {
pl->error = "Unexpected character (expected '=')";
PL_Free (key);
PL_Free (item);
return NULL;
}
pl->pos++;
// If there is no value, lose the key
if (!(value = PL_ParsePropertyListItem (pl))) {
PL_Free (key);
PL_Free (item);
return NULL;
}
if (!(PL_SkipSpace (pl))) {
PL_Free (key);
PL_Free (value);
PL_Free (item);
return NULL;
}
if (pl->ptr[pl->pos] == ';') {
pl->pos++;
} else if (pl->ptr[pl->pos] != '}') {
pl->error = "Unexpected character (wanted ';' or '}')";
PL_Free (key);
PL_Free (value);
PL_Free (item);
return NULL;
}
// Add the key/value pair to the dictionary
if (!PL_D_AddObject (item, PL_String (key), value)) {
PL_Free (key);
PL_Free (value);
PL_Free (item);
return NULL;
}
PL_Free (key);
}
if (pl->pos >= pl->end) { // Catch the error
pl->error = "Unexpected end of string when parsing dictionary";
PL_Free (item);
return NULL;
}
pl->pos++;
return item;
}
case '(': {
item = PL_NewArray ();
pl->pos++;
2001-06-19 02:26:19 +00:00
while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != ')') {
plitem_t *value;
2001-06-19 02:26:19 +00:00
if (!(value = PL_ParsePropertyListItem (pl))) {
PL_Free (item);
return NULL;
}
2001-06-19 02:26:19 +00:00
if (!(PL_SkipSpace (pl))) {
PL_Free (value);
PL_Free (item);
return NULL;
}
2001-06-19 02:26:19 +00:00
if (pl->ptr[pl->pos] == ',') {
pl->pos++;
} else if (pl->ptr[pl->pos] != ')') {
pl->error = "Unexpected character (wanted ',' or ')')";
PL_Free (value);
PL_Free (item);
return NULL;
}
2001-06-19 02:26:19 +00:00
if (!PL_A_AddObject (item, value)) {
pl->error = "Unexpected character (too many items in array)";
PL_Free (value);
PL_Free (item);
return NULL;
}
}
pl->pos++;
return item;
}
case '<': {
int len;
char *str = PL_ParseData (pl, &len);
if (!str) {
return NULL;
} else {
return PL_NewData (str, len);
}
}
case '"': {
char *str = PL_ParseQuotedString (pl);
if (!str) {
return NULL;
} else {
return new_string (str);
}
}
default: {
char *str = PL_ParseUnquotedString (pl);
if (!str) {
return NULL;
} else {
return new_string (str);
}
}
} // switch
}
VISIBLE plitem_t *
PL_GetPropertyList (const char *string)
{
pldata_t *pl = calloc (1, sizeof (pldata_t));
plitem_t *newpl = NULL;
if (!quotable_bitmap[0])
init_quotables ();
pl->ptr = string;
pl->pos = 0;
pl->end = strlen (string);
pl->error = NULL;
pl->line = 1;
2001-08-01 07:05:28 +00:00
if ((newpl = PL_ParsePropertyListItem (pl))) {
free (pl);
return newpl;
} else {
if (pl && pl->error && pl->error[0])
Sys_Printf ("plist: %d,%d: %s", pl->line, pl->pos, pl->error);
free (pl);
return NULL;
}
}
static void
write_tabs (dstring_t *dstr, int num)
{
2007-05-13 03:13:01 +00:00
char *tabs = dstring_reservestr (dstr, num);
2007-05-13 03:13:01 +00:00
memset (tabs, '\t', num);
tabs[num] = 0;
}
2007-05-13 03:27:54 +00:00
static void
write_string_len (dstring_t *dstr, const char *str, int len)
{
char *dst = dstring_reservestr (dstr, len);
memcpy (dst, str, len);
dst[len] = 0;
}
static char
to_hex (byte b)
{
char c = (b & 0xf) + '0';
if (c > '9')
c = c - '0' + 'A';
return c;
}
static void
write_binary (dstring_t *dstr, byte *binary, int len)
{
int i;
char *dst = dstring_reservestr (dstr, len * 2);
for (i = 0; i < len; i++) {
*dst++ = to_hex (binary[i] >> 4);
*dst++ = to_hex (binary[i]);
}
}
static void
write_string (dstring_t *dstr, const char *str)
{
const char *s;
2007-05-13 03:13:01 +00:00
int len = 0;
char *dst;
int quoted = 0;
for (s = str; *s; s++) {
if (is_quotable (*s))
quoted = 1;
len++;
}
if (!quoted) {
dst = dstring_reservestr (dstr, len);
strcpy (dst, str);
return;
}
2007-05-13 03:13:01 +00:00
// assume worst case of all octal chars plus two quotes.
dst = dstring_reservestr (dstr, strlen (str) * 4 + 2);
*dst++= '\"';
while (*str) {
2007-05-13 03:13:01 +00:00
if (*str && isascii ((byte) *str) && isprint ((byte) *str)
&& *str != '\\' && *str != '\"') {
*dst++ = *str++;
continue;
}
if (*str) {
*dst++ = '\\';
2007-05-13 03:13:01 +00:00
switch (*str) {
case '\"':
case '\\':
2007-05-13 03:13:01 +00:00
*dst++ = *str;
break;
case '\n':
2007-05-13 03:13:01 +00:00
*dst++ = 'n';
break;
case '\a':
2007-05-13 03:13:01 +00:00
*dst++ = 'a';
break;
case '\b':
2007-05-13 03:13:01 +00:00
*dst++ = 'b';
break;
case '\f':
2007-05-13 03:13:01 +00:00
*dst++ = 'f';
break;
case '\r':
2007-05-13 03:13:01 +00:00
*dst++ = 'r';
break;
case '\t':
2007-05-13 03:13:01 +00:00
*dst++ = 't';
break;
case '\v':
2007-05-13 03:13:01 +00:00
*dst++ = 'v';
break;
default:
2007-05-13 03:13:01 +00:00
*dst++ = '0' + ((((byte) *str) >> 6) & 3);
*dst++ = '0' + ((((byte) *str) >> 3) & 7);
*dst++ = '0' + (((byte) *str) & 7);
break;
}
2007-05-13 03:13:01 +00:00
str++;
}
}
2007-05-13 03:13:01 +00:00
*dst++ = '\"';
*dst++ = 0;
dstr->size = dst - dstr->str;
}
static void
write_item (dstring_t *dstr, plitem_t *item, int level)
{
void **list, **l;
dictkey_t *current;
plarray_t *array;
plbinary_t *binary;
int i;
switch (item->type) {
case QFDictionary:
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "{\n", 2);
l = list = Hash_GetList ((hashtab_t *) item->data);
while ((current = (dictkey_t *) *l++)) {
write_tabs (dstr, level + 1);
write_string (dstr, current->key);
2007-05-13 03:27:54 +00:00
write_string_len (dstr, " = ", 3);
write_item (dstr, current->value, level + 1);
2007-05-13 03:27:54 +00:00
write_string_len (dstr, ";\n", 2);
}
free (list);
write_tabs (dstr, level);
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "}", 1);
break;
case QFArray:
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "(\n", 2);
array = (plarray_t *) item->data;
for (i = 0; i < array->numvals; i++) {
write_tabs (dstr, level + 1);
write_item (dstr, array->values[i], level + 1);
if (i < array->numvals - 1)
2007-05-13 03:27:54 +00:00
write_string_len (dstr, ",\n", 2);
}
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "\n", 1);
write_tabs (dstr, level);
2007-05-13 03:27:54 +00:00
write_string_len (dstr, ")", 1);
break;
case QFBinary:
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "<", 1);
binary = (plbinary_t *) item->data;
2007-05-13 03:27:54 +00:00
write_binary (dstr, binary->data, binary->size);
write_string_len (dstr, ">", 1);
break;
case QFString:
write_string (dstr, item->data);
break;
default:
break;
}
}
VISIBLE char *
PL_WritePropertyList (plitem_t *pl)
{
dstring_t *dstr = dstring_newstr ();
if (!quotable_bitmap[0])
init_quotables ();
write_item (dstr, pl, 0);
2007-05-13 03:27:54 +00:00
write_string_len (dstr, "\n", 1);
return dstring_freeze (dstr);
}
VISIBLE pltype_t
PL_Type (plitem_t *item)
{
return item->type;
}