modules/rpsl/syntax_api.c

/* [<][>]
[^][v][top][bottom][index][help] */

FUNCTIONS

This source file includes following functions.
  1. chk_obj
  2. chk_attr
  3. chk_err
  4. chk_obj
  5. chk_attr
  6. chk_err
  7. is_rpsl_line_cont
  8. my_g_str_hash
  9. my_g_strcasecmp
  10. attribute_clean
  11. str_ends_with
  12. generic_list_split
  13. attribute_list_split
  14. ripe_list_split
  15. rpsl_error_assign
  16. rpsl_error_add
  17. rpsl_attr_syntax_check
  18. rpsl_attr_init
  19. rpsl_error_copy
  20. rpsl_attr_copy
  21. rpsl_attr_clean_copy
  22. rpsl_attr_delete
  23. rpsl_attr_delete_list
  24. rpsl_attr_get_name
  25. rpsl_attr_get_ofs
  26. rpsl_attr_get_value
  27. rpsl_attr_get_clean_value
  28. rpsl_attr_get_split_list
  29. rpsl_attr_replace_value
  30. rpsl_attr_errors
  31. object_is_comment
  32. template_check_needed
  33. renumber_attr
  34. rpsl_empty_attr
  35. rpsl_object_init
  36. rpsl_object_copy
  37. rpsl_object_copy_flattened
  38. rpsl_object_delete_helper
  39. rpsl_object_delete
  40. rpsl_object_get_class
  41. next_tabstop
  42. separate_leading_whitespace
  43. whitespace_length
  44. remove_columns
  45. add_aligned_val
  46. rpsl_object_get_text
  47. rpsl_object_get_num_attr
  48. rpsl_object_get_all_attr
  49. rpsl_object_get_attr
  50. rpsl_object_get_attr_by_ofs
  51. add_attr_to_object
  52. rpsl_object_append_attr
  53. rpsl_object_add_attr
  54. rpsl_object_remove_attr
  55. rpsl_object_remove_attr_name
  56. rpsl_object_errors
  57. rpsl_attr_is_required
  58. rpsl_attr_is_generated
  59. rpsl_attr_is_multivalued
  60. rpsl_attr_is_lookup
  61. rpsl_attr_is_key
  62. rpsl_object_is_deleted
  63. search_errors
  64. rpsl_attr_has_error
  65. rpsl_object_has_error
  66. rpsl_get_attr_id
  67. rpsl_get_class_id
  68. rpsl_load_dictionary
  69. rpsl_read_dictionary
  70. rpsl_error_check
  71. rpsl_attr_check
  72. count_attr_in_list
  73. rpsl_object_check
  74. main

   1 /******************
   2   Copyright (c) 2002                                        RIPE NCC
   3 
   4   All Rights Reserved
   5 
   6   Permission to use, copy, modify, and distribute this software and its
   7   documentation for any purpose and without fee is hereby granted,
   8   provided that the above copyright notice appear in all copies and that
   9   both that copyright notice and this permission notice appear in
  10   supporting documentation, and that the name of the author not be
  11   used in advertising or publicity pertaining to distribution of the
  12   software without specific, written prior permission.
  13 
  14   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  15   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  16   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  17   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  18   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  19   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  20   ***************************************/
  21 
  22 #include <ctype.h>
  23 #include <string.h>
  24 #include <stdlib.h>
  25 #include <stdarg.h>
  26 #include <glib.h>
  27 #include "syntax_api.h"
  28 #include "syntax.h"
  29 #include "attribute.h"
  30 #include "class.h"
  31 
  32 #include <assert.h>
  33 
  34 #define RUNTIME_CHECK 0
  35 
  36 #if RUNTIME_CHECK
  37 #include <stdio.h>
  38 #define chk_obj(o)  rpsl_object_check((o),__FILE__,__LINE__)
     /* [<][>][^][v][top][bottom][index][help] */
  39 #define chk_attr(a) rpsl_attr_check((a),__FILE__,__LINE__)
     /* [<][>][^][v][top][bottom][index][help] */
  40 #define chk_err(e)  rpsl_error_check((e),__FILE__,__LINE__)
     /* [<][>][^][v][top][bottom][index][help] */
  41 
  42 void rpsl_error_check(const GList *errors, const char *file, int line);
  43 void rpsl_attr_check(const rpsl_attr_t *attr, const char *file, int line);
  44 void rpsl_object_check(const rpsl_object_t *obj, const char *file, int line);
  45 #else
  46 #define chk_obj(o) 
     /* [<][>][^][v][top][bottom][index][help] */
  47 #define chk_attr(a) 
     /* [<][>][^][v][top][bottom][index][help] */
  48 #define chk_err(e) 
     /* [<][>][^][v][top][bottom][index][help] */
  49 #endif /* RUNTIME_CHECK */
  50 
  51 /* type of check currently in place */
  52 static int rpsl_level = RPSL_DICT_FRONT_END;
  53 
  54 /* return true if the character is an RPSL line-continuation */
  55 #define is_rpsl_line_cont(c) (((c)==' ') || ((c) == '\t')  || ((c) == '+'))
     /* [<][>][^][v][top][bottom][index][help] */
  56 
  57 /* needed by hash table */
  58 static guint
  59 my_g_str_hash (gconstpointer v)
     /* [<][>][^][v][top][bottom][index][help] */
  60 {
  61     gchar *s;
  62     guint hash;
  63 
  64     s = g_strdup(v);
  65     g_strdown(s);
  66     hash = g_str_hash(s);
  67     g_free(s);
  68     return hash;
  69 }
  70 
  71 static gint
  72 my_g_strcasecmp (gconstpointer a, gconstpointer b)
     /* [<][>][^][v][top][bottom][index][help] */
  73 {
  74     return (g_strcasecmp(a, b) == 0);
  75 }
  76 
  77 
  78 /* remove comments, join lines, and compress whitespace */
  79 static gchar *
  80 attribute_clean (const gchar *val)
     /* [<][>][^][v][top][bottom][index][help] */
  81 {
  82     gchar **lines;
  83     int i;
  84     guchar *p;
  85     guchar *q;
  86     guchar *ret_val;
  87 
  88     /* split our value up into lines */
  89     lines = g_strsplit(val, "\n", 0);
  90 
  91     for (i=0; lines[i] != NULL; i++) {
  92         /* remove comments */
  93         p = (guchar *)strchr(lines[i], '#');
  94         if (p != NULL) {
  95             *p = '\0';
  96         }
  97 
  98         /* convert line continuation characters to whitespace */
  99         if (i > 0) {
 100             /* XXX: error in attribute */
 101             assert(is_rpsl_line_cont(lines[i][0]));
 102             lines[i][0] = ' ';
 103         }
 104     }
 105 
 106     /* join the lines into a single string */
 107     ret_val = (guchar *)g_strjoinv("", lines);
 108     g_strfreev(lines);
 109 
 110     /* we read from p, and write to q */
 111     p = ret_val;
 112     q = ret_val;
 113 
 114     /* skip leading whitespace */
 115     while (isspace((int)*p)) {
 116         p++;
 117     }
 118 
 119     /* convert all series of whitespace into a single space */
 120     /*  (this will happily convert '\n' into ' ') */
 121     while (*p != '\0') {
 122         if (isspace((int)*p)) {
 123             *q = ' ';
 124             q++;
 125             do {
 126                 p++;
 127             } while (isspace((int)*p));
 128         } else {
 129             *q = *p;
 130             q++;
 131             p++;
 132         }
 133     }
 134 
 135     /* remove any trailing whitespace (there will be at most one space,
 136        because of the whitespace compression already performed above) */
 137     if ((q > ret_val) && isspace((int)*(q-1))) {
 138         q--;
 139     }
 140 
 141     /* NUL-terminate our string */
 142     *q = '\0';
 143 
 144     /* return our new line */
 145     return (gchar *)ret_val;
 146 }
 147 
 148 /* see if the given string ends with the other string */
 149 static gboolean
 150 str_ends_with (const char *s1, const char *s2)
     /* [<][>][^][v][top][bottom][index][help] */
 151 {
 152     int s1_len;
 153     int s2_len;
 154 
 155     s1_len = strlen(s1);
 156     s2_len = strlen(s2);
 157     if (s1_len < s2_len) {
 158         return FALSE;
 159     }
 160     if (strcmp(s1 + s1_len - s2_len, s2) == 0) {
 161         return TRUE;
 162     } else {
 163         return FALSE;
 164     }
 165 }
 166 
 167 /* return each element in the list as a seperate gchar* */
 168 /* be sure to call g_strfreev() when done with this result!!! */
 169 static gchar **
 170 generic_list_split (const char *val, const char *separator_char)
     /* [<][>][^][v][top][bottom][index][help] */
 171 {
 172     gchar *tmp_str;
 173     gchar **ret_val;
 174     gboolean has_empty_last_element;
 175     int i;
 176     int list_size;
 177 
 178     /* clean up to remove comments and newlines */
 179     tmp_str = attribute_clean(val);
 180 
 181     /* check for empty last element, which g_strsplit() will silently drop */
 182     has_empty_last_element = str_ends_with(tmp_str, separator_char);
 183 
 184     /* split based on separator character */
 185     ret_val = g_strsplit(tmp_str, separator_char, 0);
 186 
 187     /* free our temporary variable */
 188     g_free(tmp_str);
 189 
 190     /* clean whitespace from each element */
 191     list_size = 0;
 192     for (i=0; ret_val[i] != NULL; i++) {
 193         g_strstrip(ret_val[i]);
 194         list_size++;
 195     }
 196 
 197     /* if we have an empty last element, add it back in */
 198     if (has_empty_last_element) {
 199         ret_val = g_renew(gchar*, ret_val, list_size+2);
 200         ret_val[list_size] = g_strdup("");
 201         ret_val[list_size+1] = NULL;
 202     }
 203 
 204     /* return our array */
 205     return ret_val;
 206 }
 207 
 208 
 209 static gchar **
 210 attribute_list_split (const char *val)
     /* [<][>][^][v][top][bottom][index][help] */
 211 {
 212     return generic_list_split(val, ",");
 213 }
 214 
 215 static gchar **
 216 ripe_list_split (const char *val)
     /* [<][>][^][v][top][bottom][index][help] */
 217 {
 218     /* 
 219        We can call generic_list_split() because it calls 
 220        attribute_clean() before using g_strsplit() to divide the string,
 221        and attribute_clean() converts any runs of whitespace into a 
 222        single space. 
 223      */
 224     return generic_list_split(val, " ");
 225 }
 226 
 227 static void
 228 rpsl_error_assign (rpsl_error_t *error, 
     /* [<][>][^][v][top][bottom][index][help] */
 229                    gint level, 
 230                    gint code, 
 231                    gchar *descr_fmt, 
 232                    ...)
 233 {
 234     va_list args;
 235 
 236     if (error != NULL) {
 237         error->level = level;
 238         error->code = code;
 239         va_start(args, descr_fmt);
 240         error->descr = g_strdup_vprintf(descr_fmt, args);
 241         va_end(args);
 242         error->attr_num = -1;
 243     }
 244 }
 245 
 246 static void
 247 rpsl_error_add (GList **errors, gint level, gint code, gint attr_num, 
     /* [<][>][^][v][top][bottom][index][help] */
 248                 gchar *descr_fmt, ...)
 249 {
 250     rpsl_error_t *error;
 251     va_list args;
 252 
 253     error = g_new(rpsl_error_t, 1);
 254     error->level = level;
 255     error->code = code;
 256     va_start(args, descr_fmt);
 257     error->descr = g_strdup_vprintf(descr_fmt, args);
 258     va_end(args);
 259     error->attr_num = attr_num;
 260     *errors = g_list_append(*errors, error);
 261 }
 262 
 263 /* returns TRUE if okay, else FALSE */
 264 static gboolean 
 265 rpsl_attr_syntax_check (const attribute_t *attr_info,
     /* [<][>][^][v][top][bottom][index][help] */
 266                         const gchar *value, 
 267                         GList **errors)
 268 {
 269     int level;
 270     gchar **split_val;
 271     int i;
 272     int error_cnt;
 273     GPtrArray *parse_errors;
 274     gchar *parse_descr;
 275 
 276     /* set up for exit */
 277     split_val = NULL;
 278 
 279     /* set our syntax checking level */
 280     if (rpsl_level == RPSL_DICT_CORE) {
 281         level = SYNTAX_CHECK_CORE;
 282     } else {
 283         level = SYNTAX_CHECK_FRONT_END;
 284     }
 285 
 286     error_cnt = 0;
 287 
 288     /* check the syntax */
 289     if ((attr_info->is_list) || (attr_info->is_ripe_list)) {
 290         if (attr_info->is_list) {
 291             split_val = attribute_list_split(value);
 292         } else {
 293             split_val = ripe_list_split(value);
 294         }
 295         if (split_val[0] == NULL) {
 296             rpsl_error_add(errors,
 297                            RPSL_ERRLVL_ERROR,
 298                            RPSL_ERR_EMPTYLIST,
 299                            -1, 
 300                            "Attribute \"%s\" has an empty list", 
 301                            attr_info->name);
 302             goto exit_rpsl_syntax;
 303         }
 304     } else {
 305         split_val = g_new(gchar*, 2);
 306         split_val[0] = attribute_clean(value);
 307         split_val[1] = NULL;
 308     }
 309 
 310     for (i=0; split_val[i] != NULL; i++) {
 311         parse_errors = syntax_check_by_offset(attr_info->syntax_offset,
 312                                               level,
 313                                               split_val[i]);
 314         error_cnt += parse_errors->len;
 315         while (parse_errors->len > 0) {
 316             parse_descr = g_ptr_array_remove_index(parse_errors, 0);
 317             rpsl_error_add(errors,
 318                            RPSL_ERRLVL_ERROR,
 319                            RPSL_ERR_SYNERR,
 320                            -1,
 321                            "%s",
 322                            parse_descr);
 323             g_free(parse_descr);
 324         }
 325         g_ptr_array_free(parse_errors, TRUE);
 326     }
 327 
 328 exit_rpsl_syntax:
 329     if (split_val != NULL) {
 330         g_strfreev(split_val);
 331     }
 332     if (error_cnt == 0) {
 333         return TRUE;
 334     } else {
 335         return FALSE;
 336     }
 337 }
 338 
 339 /* 
 340    returns NULL on *coding errors*
 341       non-existant class specified
 342       attribute does not exist for class
 343       attribute without class in ambiguous
 344    returns a structure otherwise
 345       on *syntax errors* errors are in the rpsl_attr_t structure
 346  */
 347 
 348 /* XXX: there should be a way to preserve the original text, so 
 349         that garbage attributes still retain meaning
 350  */
 351 rpsl_attr_t *
 352 rpsl_attr_init (const gchar *s, const gchar *class)
     /* [<][>][^][v][top][bottom][index][help] */
 353 {
 354     rpsl_attr_t *retval;
 355     gchar **attr_val;
 356     const class_t *class_info;
 357     const class_attr_t *class_attr_info;
 358     const attribute_t *attr_info;
 359     gboolean is_ambiguous;
 360 
 361     /* return NULL if string is empty */
 362     if ((s == NULL) || (s[0] == '\0')) {
 363         return NULL;
 364     }
 365 
 366     /* get class info as early as possible */
 367     if (class != NULL) {
 368         class_info = class_lookup(class);
 369         if (class_info == NULL) {
 370             return NULL;
 371         }
 372     } else {
 373         class_info = NULL;
 374     }
 375 
 376     /* initialize the structure */
 377     retval = g_new(rpsl_attr_t, 1);
 378     retval->name = NULL;
 379     retval->lcase_name = NULL;
 380     retval->value = NULL;
 381     retval->errors = NULL;
 382     retval->num = -1;
 383     retval->attr_info = NULL;
 384 
 385     /* prepare for early return */
 386     attr_val = NULL;
 387 
 388     /* split into attribute and value */
 389     /* g_strsplit() puts max # of tokens + the rest of the string */
 390     /* so in this case we will have 1 token (attr name maybe) and the rest */
 391     if (strchr(s, ':') == NULL) {
 392         /* this is a critical error - very bad if it is a class attribute */
 393         rpsl_error_add(&retval->errors,
 394                        RPSL_ERRLVL_CRIT, 
 395                        RPSL_ERR_BADATTR, 
 396                        -1,
 397                        "Attribute missing colon, ':'");
 398         retval->name = g_strdup("");
 399         retval->lcase_name = g_strdup("");
 400         retval->value = g_strdup("");
 401         goto exit_rpsl_attr_init;
 402         
 403     }
 404     attr_val = g_strsplit(s, ":", 1);
 405     assert(attr_val[0] != NULL);
 406 
 407     /* assign our name and value */
 408     retval->name = g_strdup(attr_val[0]);
 409     retval->lcase_name = g_strdup(attr_val[0]);
 410     g_strdown(retval->lcase_name);
 411     if (attr_val[1] == NULL) {
 412         /* possible if nothing after the ':' */
 413         retval->value = g_strdup("");
 414     } else {
 415         /* the usual case, copy our data */
 416         retval->value = g_strdup(attr_val[1]);
 417         assert(attr_val[2] == NULL);
 418     }
 419 
 420     /* get our attribute information */
 421     if (class_info != NULL) {
 422         class_attr_info = class_attr_lookup(class_info, retval->name);
 423     } else {
 424         class_attr_info = NULL;
 425     }
 426     if ((class_info != NULL) && (class_attr_info != NULL)) {
 427         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
 428         assert(attr_info != NULL);
 429     } else {
 430         attr_info = attribute_lookup(retval->name, &is_ambiguous);
 431         if (is_ambiguous) {
 432             rpsl_attr_delete(retval);
 433             retval = NULL;
 434             goto exit_rpsl_attr_init;
 435         }
 436         if (attr_info == NULL) {
 437             /* this is a critical error - bad if it is a class attribute */
 438             rpsl_error_add(&retval->errors,
 439                            RPSL_ERRLVL_CRIT,
 440                            RPSL_ERR_UNKNOWNATTR,
 441                            -1,
 442                            "\"%s\" is not a known RPSL attribute",
 443                            retval->name);
 444             goto exit_rpsl_attr_init;
 445         }
 446     }
 447     /* dangerous, but we promise not to make any changes to this value! */
 448     retval->attr_info = (void *)attr_info;
 449 
 450     /* check for errors (adds to the error list) */
 451     rpsl_attr_syntax_check(attr_info, retval->value, &retval->errors);
 452 
 453     /* clean up and leave */
 454 exit_rpsl_attr_init:
 455     if (attr_val != NULL) {
 456         g_strfreev(attr_val);
 457     }
 458 
 459     chk_attr(retval);
 460 
 461     return retval;
 462 }
 463 
 464 static rpsl_error_t *
 465 rpsl_error_copy (const rpsl_error_t *err)
     /* [<][>][^][v][top][bottom][index][help] */
 466 {
 467     rpsl_error_t *retval;
 468 
 469     retval = g_new(rpsl_error_t, 1);
 470     retval->level = err->level;
 471     retval->code = err->code;
 472     retval->descr = g_strdup(err->descr);
 473     retval->attr_num = err->attr_num;
 474     return retval;
 475 }
 476 
 477 rpsl_attr_t *
 478 rpsl_attr_copy (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 479 {
 480     rpsl_attr_t *retval;
 481     GList *ptr;
 482 
 483     chk_attr(attr);
 484 
 485     retval = g_new(rpsl_attr_t, 1);
 486     retval->name = g_strdup(attr->name);
 487     retval->lcase_name = g_strdup(attr->lcase_name);
 488     retval->value = g_strdup(attr->value);
 489     retval->errors = NULL;
 490     for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) {
 491         retval->errors = g_list_append(retval->errors, 
 492                                        rpsl_error_copy(ptr->data));
 493     }
 494     retval->num = attr->num;
 495     retval->attr_info = attr->attr_info;
 496 
 497     chk_attr(retval);
 498 
 499     return retval;
 500 }
 501 
 502 rpsl_attr_t *
 503 rpsl_attr_clean_copy (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 504 {
 505     rpsl_attr_t *retval;
 506 
 507     chk_attr(attr);
 508 
 509     retval = g_new(rpsl_attr_t, 1);
 510     retval->name = g_strdup(attr->name);
 511     retval->lcase_name = g_strdup(attr->lcase_name);
 512     retval->value = attribute_clean(attr->value);
 513     retval->errors = NULL;
 514     retval->num = attr->num;
 515     retval->attr_info = attr->attr_info;
 516 
 517     chk_attr(retval);
 518 
 519     return retval;
 520 }
 521 
 522 
 523 void
 524 rpsl_attr_delete (rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 525 {
 526     GList *ptr;
 527     rpsl_error_t *err;
 528 
 529     chk_attr(attr);
 530 
 531     g_free(attr->name);
 532     attr->name = NULL;
 533     g_free(attr->lcase_name);
 534     attr->lcase_name = NULL;
 535     g_free(attr->value);
 536     attr->value = NULL;
 537     for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) {
 538         err = ptr->data;
 539         g_free(err->descr);
 540         g_free(err);
 541     }
 542     g_list_free(attr->errors);
 543     attr->errors = NULL;
 544     attr->num = -1;
 545     attr->attr_info = NULL;
 546     g_free(attr);
 547 }
 548 
 549 void 
 550 rpsl_attr_delete_list (GList *attributes)
     /* [<][>][^][v][top][bottom][index][help] */
 551 {
 552     GList *ptr;
 553 
 554     for (ptr=attributes; ptr != NULL; ptr = g_list_next(ptr)) {
 555         rpsl_attr_delete(ptr->data);
 556     }
 557     g_list_free(attributes);
 558 }
 559 
 560 const gchar *
 561 rpsl_attr_get_name (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 562 {
 563     chk_attr(attr);
 564 
 565     /* XXX: there should be a way to get the original name */
 566     /*return attr->name;*/
 567     return attr->lcase_name;
 568 }
 569 
 570 gint
 571 rpsl_attr_get_ofs (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 572 {
 573     chk_attr(attr);
 574 
 575     return attr->num;
 576 }
 577 
 578 const gchar *
 579 rpsl_attr_get_value (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 580 {
 581     chk_attr(attr);
 582 
 583     return attr->value;
 584 }
 585 
 586 gchar *
 587 rpsl_attr_get_clean_value (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 588 {
 589     gchar *tmp;
 590     gchar *retval;
 591 
 592     chk_attr(attr);
 593 
 594     /* don't just return the value from attribute_clean(), since we
 595        need to return memory that can be freed via free(), and the
 596        gchar* returned by attribute_clean needs to be freed via g_free() */
 597     tmp = attribute_clean(attr->value);
 598     retval = strdup(tmp);
 599     g_free(tmp);
 600     return retval;
 601 }
 602 
 603 GList *
 604 rpsl_attr_get_split_list (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 605 {
 606     const attribute_t *attr_info;
 607     GList *retval;
 608     gchar **split;
 609     int i;
 610     rpsl_attr_t *newattr;
 611 
 612     chk_attr(attr);
 613 
 614     attr_info = attr->attr_info;
 615     if ((attr_info!=NULL) && (attr_info->is_list || attr_info->is_ripe_list)) {
 616         if (attr_info->is_list) {
 617             split = attribute_list_split(attr->value);
 618         } else {
 619             split = ripe_list_split(attr->value);
 620         }
 621         retval = NULL;
 622         for (i=0; split[i] != NULL; i++) {
 623             /* XXX: perpaps consolidate this with rpsl_attr_init()? */
 624             newattr = g_new(rpsl_attr_t, 1);
 625             assert(newattr != NULL);
 626             newattr->name = g_strdup(attr->name);
 627             newattr->lcase_name = g_strdup(attr->lcase_name);
 628             newattr->value = g_strdup(split[i]);
 629             newattr->errors = NULL;
 630             newattr->num = attr->num;
 631             newattr->attr_info = attr->attr_info;
 632             retval = g_list_append(retval, newattr);
 633         }
 634         g_strfreev(split);
 635         return retval;
 636     } else {
 637         return g_list_append(NULL, rpsl_attr_clean_copy(attr));
 638     }
 639 }
 640 
 641 void
 642 rpsl_attr_replace_value (rpsl_attr_t *attr, const gchar *value)
     /* [<][>][^][v][top][bottom][index][help] */
 643 {
 644     chk_attr(attr);
 645 
 646     /* perform check to add any new errors */
 647     if (attr->attr_info != NULL) {
 648         rpsl_attr_syntax_check(attr->attr_info, value, &attr->errors);
 649     }
 650 
 651     /* copy the value */
 652     g_free(attr->value);
 653     attr->value = g_strdup(value);
 654 
 655     chk_attr(attr);
 656 }
 657 
 658 const GList *
 659 rpsl_attr_errors (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 660 {
 661     chk_attr(attr);
 662 
 663     return attr->errors;
 664 }
 665 
 666 static gboolean 
 667 object_is_comment (const gchar *s)
     /* [<][>][^][v][top][bottom][index][help] */
 668 {
 669     const gchar *p, *q;
 670 
 671     /* skip blank lines */
 672     p = s;
 673     for (;;) {
 674         while ((*p == ' ') || (*p == '\t')) {
 675             p++;
 676         }
 677         /* if object is only blank lines, then we are *not* a comment */
 678         if (*p == '\0') {
 679             return FALSE;
 680         }
 681         if (*p != '\n') {
 682             break;
 683         }
 684         p++;
 685     }
 686     /* skip comment lines */
 687     for (;;) {
 688         if ((*p != '%') && (*p != '#')) {
 689             break;
 690         }
 691         q = strchr(p, '\n');
 692         /* if we end on a comment without newline, we *are* a comment */
 693         if (q == NULL) {
 694             return TRUE;
 695         }
 696         p = q + 1;
 697     }
 698     /* skip trailing blank lines */
 699     for (;;) {
 700         while ((*p == ' ') || (*p == '\t')) {
 701             p++;
 702         }
 703         if (*p != '\n') {
 704             break;
 705         }
 706         p++;
 707     }
 708     /* see if we skipped the whole object */
 709     if (*p == '\0') {
 710         return TRUE;
 711     } else {
 712         return FALSE;
 713     }
 714 }
 715 
 716 /* we don't want to check whether an attribute belongs in the template 
 717    if it is a bad attribute, or and unknown attribute */
 718 static gboolean
 719 template_check_needed (const rpsl_attr_t *attr)
     /* [<][>][^][v][top][bottom][index][help] */
 720 {
 721     const GList *p;
 722     const rpsl_error_t *error;
 723 
 724     p = rpsl_attr_errors(attr);
 725     while (p != NULL) {
 726         error = p->data;
 727         if (error->code == RPSL_ERR_BADATTR) {
 728             return FALSE;
 729         }
 730         if (error->code == RPSL_ERR_UNKNOWNATTR) {
 731             return FALSE;
 732         }
 733         p = g_list_next(p);
 734     }
 735     return TRUE;
 736 }
 737 
 738 static void
 739 renumber_attr (rpsl_attr_t *attr, int num)
     /* [<][>][^][v][top][bottom][index][help] */
 740 {
 741     attr->num = num;
 742 }
 743 
 744 static rpsl_attr_t *
 745 rpsl_empty_attr ()
     /* [<][>][^][v][top][bottom][index][help] */
 746 {
 747     rpsl_attr_t *retval;
 748 
 749     retval = g_new(rpsl_attr_t, 1);
 750     retval->name = g_strdup("");
 751     retval->lcase_name = g_strdup("");
 752     retval->value = g_strdup("");
 753     retval->errors = NULL;
 754     retval->num = -1;
 755     retval->attr_info = NULL;
 756     return retval;
 757 }
 758 
 759 rpsl_object_t *
 760 rpsl_object_init (const gchar *s)
     /* [<][>][^][v][top][bottom][index][help] */
 761 {
 762     rpsl_object_t *retval;
 763     GPtrArray *lines;
 764     const gchar *p, *q;
 765     gchar *line;
 766     rpsl_attr_t *attr;
 767     const class_t *class_info;
 768     GList *attr_list;
 769     const class_attr_t *class_attr_info;
 770     const attribute_t *attr_info;
 771     const gchar *attr_name;
 772     const gchar *class_name;
 773     int i;
 774     gboolean removed_trailing_empty_lines;
 775 
 776     /* initialize the structure */
 777     retval = g_new(rpsl_object_t, 1);
 778     retval->attributes = NULL;
 779     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
 780     retval->errors = NULL;
 781     retval->class_info = NULL;
 782 
 783     /* make some lines */
 784     lines = g_ptr_array_new();
 785 
 786     /* see if entire string is comments */
 787     if (object_is_comment(s)) {
 788         rpsl_error_add(&retval->errors,
 789                        RPSL_ERRLVL_WARN,
 790                        RPSL_ERR_ONLYCOMMENTS,
 791                        -1,
 792                        "Object contains only comments");
 793         goto exit_rpsl_object_init;
 794     }
 795 
 796     /* p is the beginning of the current attribute, q searches for the end */
 797     p = s;
 798     for (;;) {
 799         /* done with string, finish */
 800         if (*p == '\0') {
 801             break;
 802         }
 803 
 804         /* search for end of attribute */
 805         q = strchr(p, '\n');
 806         while ((q != NULL) && is_rpsl_line_cont(q[1])) {
 807             q = strchr(q+1, '\n');
 808         }
 809 
 810         if (q == NULL) {
 811             /* add the final attribute */
 812             g_ptr_array_add(lines, g_strdup(p));
 813             /* and exit */
 814             break;
 815         } else {
 816             /* add this attribute */
 817             g_ptr_array_add(lines, g_strndup(p, q-p));
 818             /* and proceed to the next one */
 819             p = q+1;
 820         }
 821     }
 822 
 823     /* be nice and strip empty lines at the end */
 824     removed_trailing_empty_lines = FALSE;
 825     while (lines->len > 0) {
 826         line = g_ptr_array_index(lines, lines->len - 1);
 827         if (line[0] != '\0') {
 828             break;
 829         }
 830         g_ptr_array_remove_index_fast(lines, lines->len - 1);
 831         g_free(line);
 832         removed_trailing_empty_lines = TRUE;
 833     }
 834     if (removed_trailing_empty_lines) {
 835         rpsl_error_add(&retval->errors,
 836                        RPSL_ERRLVL_INFO,
 837                        RPSL_ERR_EMPTYATTR,
 838                        -1,
 839                        "Trailing blank lines ignored");
 840     }
 841 
 842     /* verify we have at least one line */
 843     if (lines->len <= 0) {
 844         rpsl_error_add(&retval->errors,
 845                        RPSL_ERRLVL_CRIT, 
 846                        RPSL_ERR_BADCLASS,
 847                        -1,
 848                        "Empty object");
 849         goto exit_rpsl_object_init;
 850     }
 851 
 852     /* create the magic first attribute, which is the class */
 853     attr = rpsl_attr_init(g_ptr_array_index(lines, 0), NULL);
 854     if (attr == NULL) {
 855         rpsl_error_add(&retval->errors,
 856                        RPSL_ERRLVL_CRIT, 
 857                        RPSL_ERR_BADCLASS,
 858                        -1,
 859                        "Error with class attribute, class invalid");
 860         goto exit_rpsl_object_init;
 861     }
 862     renumber_attr(attr, 0);
 863 
 864     /* check for errors with the class attribute */
 865     /* only critical errors matter - let innocent syntax errors pass through */
 866     if (rpsl_attr_has_error(attr, RPSL_ERRLVL_CRIT)) {
 867         rpsl_error_add(&retval->errors,
 868                        RPSL_ERRLVL_CRIT,
 869                        RPSL_ERR_BADCLASS,
 870                        -1,
 871                        "Error with class attribute, class invalid");
 872         rpsl_attr_delete(attr);
 873         goto exit_rpsl_object_init;
 874     }
 875     
 876 
 877     /* get the class information */
 878     class_name = rpsl_attr_get_name(attr);
 879     class_info = class_lookup(class_name);
 880     if (class_info == NULL) {
 881         rpsl_error_add(&retval->errors,
 882                        RPSL_ERRLVL_CRIT,
 883                        RPSL_ERR_UNKNOWNCLASS,
 884                        -1,
 885                        "First attribute, \"%s\", is not a known RPSL class", 
 886                        class_name);
 887         rpsl_attr_delete(attr);
 888         goto exit_rpsl_object_init;
 889     }
 890 
 891     /* check for syntax errors with the class attribute */
 892     if (rpsl_attr_errors(attr) != NULL) {
 893                 rpsl_error_add(&retval->errors,
 894                                RPSL_ERRLVL_ERROR,
 895                                RPSL_ERR_BADATTR,
 896                                0,
 897                                "Error with attribute \"%s\"", 
 898                                class_name);
 899     }
 900 
 901     /* possibly dangerous, but we promise only to read this value! */
 902     retval->class_info = (void *)class_info;
 903 
 904     /* add class attribute */
 905     retval->attributes = g_list_append(NULL, attr);
 906     attr_list = g_list_append(NULL, attr);
 907     g_hash_table_insert(retval->attr_lookup, 
 908                         (void *)rpsl_attr_get_name(attr), 
 909                         attr_list);
 910 
 911     /* valid class, process each attribute */
 912     for (i=1; i < lines->len; i++) {
 913         attr = rpsl_attr_init(g_ptr_array_index(lines, i), class_name);
 914         if (attr == NULL) {
 915             /* XXX: we should preserve the original information somehow */
 916             attr = rpsl_empty_attr();
 917             rpsl_error_add(&(attr->errors),
 918                            RPSL_ERRLVL_ERROR,
 919                            RPSL_ERR_BADATTR,
 920                            -1, 
 921                            "Attribute not valid in this class");
 922         }
 923         assert(attr != NULL);
 924         renumber_attr(attr, i);
 925 
 926         /* add the attribute to the list of attributes for this object */
 927         retval->attributes = g_list_append(retval->attributes, attr);
 928 
 929         /* check for errors at attribute level */
 930         if (rpsl_attr_errors(attr) != NULL) {
 931             attr_name = rpsl_attr_get_name(attr);
 932             if (attr_name != NULL) {
 933                 rpsl_error_add(&retval->errors,
 934                                RPSL_ERRLVL_ERROR,
 935                                RPSL_ERR_BADATTR,
 936                                i,
 937                                "Error with attribute \"%s\"", 
 938                                attr_name);
 939             } else {
 940                 rpsl_error_add(&retval->errors,
 941                                RPSL_ERRLVL_ERROR,
 942                                RPSL_ERR_BADATTR,
 943                                i,
 944                                "Error with attribute");
 945                 /* no name - no sense to process this attr further */
 946                 continue;
 947             }
 948         }
 949 
 950 
 951         /* get list of existing attributes of this name, if any */
 952         attr_list = g_hash_table_lookup(retval->attr_lookup, 
 953                                         rpsl_attr_get_name(attr));
 954 
 955 
 956         /* perform template checking if attribute is known type */
 957         if (template_check_needed(attr)) {
 958 
 959             /* verify this attribute can go in this class */
 960             class_attr_info = class_attr_lookup(class_info, 
 961                                                 rpsl_attr_get_name(attr));
 962             if (class_attr_info == NULL) {
 963                 rpsl_error_add(&retval->errors,
 964                                RPSL_ERRLVL_ERROR,
 965                                RPSL_ERR_ATTRNOTALLOWED,
 966                                i,
 967                                "Attribute \"%s\" is not allowed in this class",
 968                                rpsl_attr_get_name(attr));
 969             } else {
 970                 /* if we have added a "single" attribute more than once */
 971                 if ((class_attr_info->number == ATTR_SINGLE) && 
 972                     (attr_list != NULL)) 
 973                 {
 974                     rpsl_error_add(&retval->errors,
 975                                    RPSL_ERRLVL_ERROR,
 976                                    RPSL_ERR_ATTRSINGLE,
 977                                    i,
 978                                    "Attribute \"%s\" appears more than once",
 979                                    rpsl_attr_get_name(attr));
 980                 }
 981                 /* if we have tried to initialize a "generated" attribute */
 982                 if (class_attr_info->choice == ATTR_GENERATED) {
 983                     rpsl_error_add(&retval->errors,
 984                                    RPSL_ERRLVL_ERROR,
 985                                    RPSL_ERR_ATTRGENERATED,
 986                                    i,
 987                                    "Attribute \"%s\" is generated automatically",
 988                                    rpsl_attr_get_name(attr));
 989                 }
 990             }
 991 
 992         } /* template_check_required(attr)) */
 993 
 994         /* add the attribute to the hash table for the class */
 995         attr_list = g_list_append(attr_list, attr);
 996         g_hash_table_insert(retval->attr_lookup, 
 997                             (void *)rpsl_attr_get_name(attr),
 998                             attr_list);         /* replaces any old value */
 999     }
1000 
1001     /* check for missing required attributes */
1002     for (i=0; i<class_info->num_attr; i++) {
1003         class_attr_info = &class_info->attr[i];
1004         if (class_attr_info->choice == ATTR_MANDATORY) {
1005             attr_info = attribute_lookup_by_offset(class_attr_info->offset);
1006             attr_list = g_hash_table_lookup(retval->attr_lookup,
1007                                             attr_info->name);
1008             if (attr_list == NULL) {
1009                 rpsl_error_add(&retval->errors,
1010                                RPSL_ERRLVL_ERROR,
1011                                RPSL_ERR_MISSINGATTR,
1012                                -1,
1013                                "Required attribute \"%s\" is missing",
1014                                attr_info->name);
1015                 if (attr_info->is_primary) {
1016                     rpsl_error_add(&retval->errors,
1017                                    RPSL_ERRLVL_ERROR,
1018                                    RPSL_ERR_MISSINGKEY,
1019                                    -1,
1020                                    "Primary key \"%s\" is missing",
1021                                    attr_info->name);
1022                 }
1023             }
1024         }
1025     }
1026 
1027     /* done - enjoy your new object */
1028 
1029 exit_rpsl_object_init:
1030     /* free memory used by split lines */
1031     for (i=0; i<lines->len; i++) {
1032         g_free(g_ptr_array_index(lines, i));
1033     }
1034     g_ptr_array_free(lines, TRUE);
1035     lines = NULL;
1036 
1037     chk_obj(retval);
1038 
1039     /* return our object */
1040     return retval;
1041 }
1042 
1043 rpsl_object_t *
1044 rpsl_object_copy (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1045 {
1046     rpsl_object_t *retval;
1047     GList *p;
1048     rpsl_attr_t *attr;
1049     GList *attr_list;
1050 
1051     chk_obj(object);
1052 
1053     retval = g_new(rpsl_object_t, 1);
1054     retval->attributes = NULL;
1055     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
1056     retval->errors = NULL;
1057     retval->class_info = object->class_info;
1058 
1059     /* copy attributes */
1060     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
1061         /* insert copy of attribute into list */
1062         attr = rpsl_attr_copy(p->data);
1063         retval->attributes = g_list_append(retval->attributes, attr);
1064 
1065         /* insert copy of attribute into hash table */
1066         attr_list = g_hash_table_lookup(retval->attr_lookup,
1067                                         rpsl_attr_get_name(attr));
1068         attr_list = g_list_append(attr_list, attr);  /* works for NULL too */
1069         g_hash_table_insert(retval->attr_lookup, 
1070                             (void *)rpsl_attr_get_name(attr),
1071                             attr_list);          /* replaces any old value */
1072     }
1073 
1074     /* copy errors */
1075     for (p=object->errors; p != NULL; p = g_list_next(p)) {
1076         retval->errors = g_list_append(retval->errors, 
1077                                        rpsl_error_copy(p->data));
1078     }
1079 
1080     chk_obj(retval);
1081 
1082     /* return the copy */
1083     return retval;
1084 }
1085 
1086 rpsl_object_t *
1087 rpsl_object_copy_flattened (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1088 {
1089     rpsl_object_t *retval;
1090     GList *p1, *p2;
1091     GList *split_attr;
1092     rpsl_attr_t *attr;
1093     GList *attr_list;
1094     int num_attr;
1095 
1096     chk_obj(object);
1097 
1098     retval = g_new(rpsl_object_t, 1);
1099     retval->attributes = NULL;
1100     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
1101     retval->errors = NULL;
1102     retval->class_info = object->class_info;
1103 
1104     /* copy attributes */
1105     num_attr = 0;
1106     for (p1=object->attributes; p1 != NULL; p1 = g_list_next(p1)) {
1107         /* split the attribute into separate values (may be 1) */
1108         split_attr = rpsl_attr_get_split_list(p1->data);
1109 
1110         /* add each resulting attribute */
1111         for (p2=split_attr; p2 != NULL; p2 = g_list_next(p2)) {
1112             attr = p2->data;
1113 
1114             /* renumber attribute */
1115             renumber_attr(attr, num_attr);
1116             num_attr++;
1117 
1118             /* insert split attribute into list */
1119             retval->attributes = g_list_append(retval->attributes, attr);
1120 
1121             /* insert split attribute into hash table */
1122             attr_list = g_hash_table_lookup(retval->attr_lookup,
1123                                             rpsl_attr_get_name(attr));
1124             attr_list = g_list_append(attr_list, attr); /* works for NULL too */
1125             g_hash_table_insert(retval->attr_lookup, 
1126                                 (void *)rpsl_attr_get_name(attr),
1127                                 attr_list);         /* replaces any old value */
1128         }
1129 
1130         /* free the list */
1131         g_list_free(split_attr);
1132     }
1133 
1134     chk_obj(retval);
1135 
1136     /* return the copy */
1137     return retval;
1138 }
1139 
1140 static void 
1141 rpsl_object_delete_helper (gpointer attr_name, 
     /* [<][>][^][v][top][bottom][index][help] */
1142                            gpointer attr_list, 
1143                            gpointer null)
1144 {
1145     g_list_free((GList *)attr_list);
1146 }
1147 
1148 void 
1149 rpsl_object_delete (rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1150 {
1151     GList *p;
1152     rpsl_error_t *err;
1153 
1154     chk_obj(object);
1155   
1156     /* free the attributes */
1157     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
1158         rpsl_attr_delete(p->data);
1159     }
1160     g_list_free(object->attributes);
1161     object->attributes = NULL;
1162 
1163     /* remove the lists from the hash table */
1164     g_hash_table_foreach(object->attr_lookup, rpsl_object_delete_helper, NULL);
1165     g_hash_table_destroy(object->attr_lookup);
1166     object->attr_lookup = NULL;
1167 
1168     /* free the errors */
1169     for (p=object->errors; p != NULL; p = g_list_next(p)) {
1170         err = p->data;
1171         g_free(err->descr);
1172         g_free(err);
1173     }
1174     g_list_free(object->errors);
1175     object->errors = NULL;
1176 
1177     /* free the object itself */
1178     g_free(object);
1179 }
1180 
1181 const char *
1182 rpsl_object_get_class (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1183 {
1184     rpsl_attr_t *attr;
1185 
1186     chk_obj(object);
1187 
1188     if (object->attributes != NULL) {
1189         attr = (rpsl_attr_t *)object->attributes->data;
1190         return attr->lcase_name;
1191     } else {
1192         return NULL;
1193     }
1194 }
1195 
1196 /* default number of spaces per tab character */
1197 #define TABSTOP 8
1198 
1199 /* returns the position of the next tab stop */
1200 static guint
1201 next_tabstop (guint col)
     /* [<][>][^][v][top][bottom][index][help] */
1202 {
1203     guint tab;
1204 
1205     tab = (col / TABSTOP) + 1;
1206     return tab * TABSTOP;
1207 }
1208 
1209 /* gets the leading whitespace from the given string */
1210 static void
1211 separate_leading_whitespace (const gchar *str, GString **ws, GString **non_ws)
     /* [<][>][^][v][top][bottom][index][help] */
1212 {
1213     int n;
1214 
1215     n = 0; 
1216     while ((str[n] == ' ') || (str[n] == '\t')) {
1217         n++;
1218     }
1219 
1220     *ws = g_string_new(str);
1221     g_string_truncate(*ws, n);
1222     *non_ws = g_string_new(str + n);
1223 }
1224 
1225 /* gets the length of a string of whitespace */
1226 static int 
1227 whitespace_length (const gchar *str, int start_col)
     /* [<][>][^][v][top][bottom][index][help] */
1228 {
1229     int len;
1230 
1231     len = start_col;
1232     for (;;) {
1233         if (*str == ' ') {
1234             len++;
1235         } else if (*str == '\t') {
1236             len = next_tabstop(len);
1237         } else {
1238             break;
1239         }
1240         str++;
1241     }
1242     return len;
1243 }
1244 
1245 /* removes the number of columns specified from the string, from the right */
1246 static void
1247 remove_columns (GString *s, int col, int start_col)
     /* [<][>][^][v][top][bottom][index][help] */
1248 {
1249     int old_len;
1250     int new_len;
1251     int col_removed;
1252 
1253     col_removed = 0;
1254 
1255     /* first, remove characters until we've removed enough */
1256     while ((s->len > 0) && (col_removed < col)) {
1257         old_len = whitespace_length(s->str, start_col);
1258         g_string_truncate(s, s->len-1);
1259         new_len = whitespace_length(s->str, start_col);
1260         col_removed += old_len - new_len;
1261     }
1262 
1263     /* if we've removed too many, add some spaces back */
1264     while (col_removed > col) {
1265         g_string_append_c(s, ' ');
1266         col_removed--;
1267     }
1268 }
1269 
1270 /* align the text of the attribute to the specific column */
1271 static void
1272 add_aligned_val (GString *s, const rpsl_attr_t *attr, int col)
     /* [<][>][^][v][top][bottom][index][help] */
1273 {
1274     const gchar *name;
1275     const gchar *val;
1276     int start_col;
1277     const gchar *p, *q;
1278     int col_to_add;
1279     int col_to_sub;
1280     gchar **lines;
1281     int i, j;
1282     GString *ws;
1283     GString *non_ws;
1284 
1285     /* get the information from the attribute */
1286     name = rpsl_attr_get_name(attr);
1287     val = rpsl_attr_get_value(attr);
1288 
1289     /* calculate the column we're at after the attribute name */
1290     start_col = strlen(name) + 1;
1291 
1292     /* if the desired column is too small based on the name of the 
1293        attribute, set to the smallest allowed column */
1294     if (col < (start_col + 1)) {
1295         col = start_col + 1;
1296     }
1297 
1298 
1299     /* find out what column the attribute value currently starts at */
1300     p = val;
1301     for (;;) {
1302         if (*p == ' ') {
1303             start_col++;
1304         } else if (*p == '\t') {
1305             start_col = next_tabstop(start_col);
1306         } else {
1307             break;
1308         } 
1309         p++;
1310     }
1311 
1312     /* special case:
1313        if there are *only* whitespace on the first line, or if it only 
1314        contains a comment, then use "as-is" */
1315     if ((*p == '\0') || (*p == '\n') || (*p == '#')) {
1316           g_string_append(s, val);
1317           g_string_append_c(s, '\n');
1318           /* EARLY RETURN */
1319           return;
1320     }
1321 
1322     /* next, see how many columns we need to add or subtract */
1323     col_to_add = col - start_col;
1324 
1325     /* adding is (relatively) easy */
1326     if (col_to_add > 0) { 
1327         lines = g_strsplit(val, "\n", 0);
1328         /* for the first line, append the spaces and the line itself */
1329         q = lines[0];
1330         while ((*q == ' ') || (*q == '\t')) {
1331             g_string_append_c(s, *q);
1332             q++;
1333         }
1334         for (j=0; j<col_to_add; j++) {
1335             g_string_append_c(s, ' ');
1336         }
1337         g_string_append(s, q);
1338         g_string_append_c(s, '\n');
1339         for (i=1; lines[i] != NULL; i++) {
1340             /* for subsequent lines... */
1341             /* append the first (line continuation) character */
1342             g_string_append_c(s, lines[i][0]);
1343             /* append any leading whitespace */
1344             q = lines[i]+1;
1345             while ((*q == ' ') || (*q == '\t')) {
1346                 g_string_append_c(s, *q);
1347                 q++;
1348             }
1349             /* now append the spaces and the remainder of the line */
1350             for (j=0; j<col_to_add; j++) {
1351                 g_string_append_c(s, ' ');
1352             }
1353             g_string_append(s, q);
1354             g_string_append_c(s, '\n');
1355         }
1356         g_strfreev(lines);
1357     }
1358     /* subtracting is a bit more tricky, due to tabs (AKA "minions of evil") */
1359     else if (col_to_add < 0) {
1360         col_to_sub = -col_to_add;
1361 
1362         lines = g_strsplit(val, "\n", 0);
1363 
1364         /* add first line after subtracting columns */
1365         separate_leading_whitespace(lines[0], &ws, &non_ws);
1366         remove_columns(ws, col_to_sub, strlen(name)+1);
1367         g_string_append(s, ws->str);
1368         g_string_append(s, non_ws->str);
1369         g_string_append_c(s, '\n');
1370         g_string_free(ws, TRUE);
1371         g_string_free(non_ws, TRUE);
1372 
1373         for (i=1; lines[i] != NULL; i++) {
1374             separate_leading_whitespace(lines[i]+1, &ws, &non_ws);
1375              /* if the line continuation character is a tab and
1376                 we don't have enough columns, convert it to spaces */
1377             if (lines[i][0] == '\t') {
1378                 if (whitespace_length(ws->str, 0) < col_to_sub) { 
1379                     lines[i][0] = ' ';
1380                     for (j=1; j<TABSTOP; j++) {
1381                         g_string_prepend_c(ws, ' ');
1382                     }
1383                 }
1384             }
1385             remove_columns(ws, col_to_sub, 0);
1386             g_string_append_c(s, lines[i][0]);
1387             g_string_append(s, ws->str);
1388             g_string_append(s, non_ws->str);
1389             g_string_append_c(s, '\n');
1390             g_string_free(ws, TRUE);
1391             g_string_free(non_ws, TRUE);
1392         }
1393         g_strfreev(lines);
1394     }
1395     /* and if no adjustment is necessary, it's trivial */
1396     else {
1397         g_string_append(s, val);
1398         g_string_append_c(s, '\n');
1399     }
1400 }
1401 
1402 gchar *
1403 rpsl_object_get_text (const rpsl_object_t *object, guint data_column)
     /* [<][>][^][v][top][bottom][index][help] */
1404 {
1405     GString *s;
1406     GList *p;
1407     rpsl_attr_t *attr;
1408     gchar *retval;
1409     const gchar *name;
1410 
1411     chk_obj(object);
1412 
1413     /* return NULL on empty object, as promised */
1414     if (object->attributes == NULL) {
1415         return NULL;
1416     }
1417 
1418     /* concatinate attribute names and values together */
1419     s = g_string_new("");
1420     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
1421         attr = p->data;
1422         name = rpsl_attr_get_name(attr);
1423         if (name != NULL) {
1424             g_string_append(s, name);
1425             g_string_append_c(s, ':');
1426             if (data_column > 0) {
1427                 add_aligned_val(s, attr, data_column);
1428             } else {
1429                 g_string_append(s, rpsl_attr_get_value(attr));
1430                 g_string_append_c(s, '\n');
1431             }
1432         }
1433     }
1434 
1435     /* copy value to return */
1436     retval = (gchar *)malloc(s->len + 1);
1437     if (retval != NULL) {
1438         strcpy(retval, s->str);
1439     }
1440 
1441     /* free string */
1442     g_string_free(s, TRUE);
1443 
1444     /* return result (returns NULL if memory allocation failed) */
1445     return retval;
1446 }
1447 
1448 gint
1449 rpsl_object_get_num_attr (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1450 {
1451     chk_obj(object);
1452 
1453     return g_list_length(object->attributes);
1454 }
1455 
1456 const GList *
1457 rpsl_object_get_all_attr (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1458 {
1459     chk_obj(object);
1460 
1461     return object->attributes;
1462 }
1463 
1464 GList *
1465 rpsl_object_get_attr (const rpsl_object_t *object, const gchar *name)
     /* [<][>][^][v][top][bottom][index][help] */
1466 {
1467     GList *attr_list;
1468     GList *retval;
1469 
1470     chk_obj(object);
1471 
1472     retval = NULL;
1473     attr_list = g_hash_table_lookup(object->attr_lookup, name);
1474     while (attr_list != NULL) {
1475         retval = g_list_append(retval, rpsl_attr_copy(attr_list->data));
1476         attr_list = g_list_next(attr_list);
1477     }
1478     return retval;
1479 }
1480 
1481 const rpsl_attr_t *
1482 rpsl_object_get_attr_by_ofs (const rpsl_object_t *object, gint ofs)
     /* [<][>][^][v][top][bottom][index][help] */
1483 {
1484     rpsl_attr_t *attr;
1485 
1486     chk_obj(object);
1487     attr = g_list_nth_data(object->attributes, ofs);
1488     chk_attr(attr);
1489 
1490     return attr;
1491 }
1492 
1493 /* using -1 for offset (ofs) to append to the end of the object */
1494 static int 
1495 add_attr_to_object(rpsl_object_t *object,
     /* [<][>][^][v][top][bottom][index][help] */
1496                 rpsl_attr_t *attr,
1497                 gint ofs,
1498                 rpsl_error_t *error)
1499 {
1500     const gchar *attr_name;
1501     class_t *class_info;
1502     const class_attr_t *class_attr_info;
1503     GList *attr_list;
1504     gint num_attr;
1505     gint i;
1506     GList *p;
1507     rpsl_attr_t *tmp;
1508     GList *err_list;
1509     rpsl_error_t *err;
1510 
1511     chk_obj(object);
1512     chk_attr(attr);
1513 
1514     /* empty object - bogus, reject, abort, error */
1515     if (object->attributes == NULL) {
1516         rpsl_error_assign(error,
1517                           RPSL_ERRLVL_ERROR,
1518                           RPSL_ERR_BADCLASS,
1519                           "Empty class");
1520         chk_obj(object);
1521         chk_attr(attr);
1522         return 0;
1523     }
1524 
1525     /* check our offset number */
1526     num_attr = rpsl_object_get_num_attr(object);
1527     if ((ofs == 0) || (ofs > num_attr)) {
1528         rpsl_error_assign(error,
1529                           RPSL_ERRLVL_ERROR,
1530                           RPSL_ERR_BADOFFSET,
1531                           "Offset %d not between 1 and %d", ofs, num_attr);
1532         chk_obj(object);
1533         chk_attr(attr);
1534         return 0;
1535     }
1536 
1537     /* get attributes with this name (may be NULL, which is okay) */
1538     attr_name = rpsl_attr_get_name(attr);
1539     attr_list = g_hash_table_lookup(object->attr_lookup, attr_name);
1540 
1541     /* get class info */
1542     class_info = object->class_info;
1543     if (class_info != NULL) {    /* we can only check for valid classes... */
1544 
1545         /* verify this attribute can go in this class */
1546         class_attr_info = class_attr_lookup(class_info, attr_name);
1547         if (class_attr_info == NULL) {
1548             rpsl_error_assign(error,
1549                               RPSL_ERRLVL_ERROR,
1550                               RPSL_ERR_ATTRNOTALLOWED,
1551                               "Attribute \"%s\" is not allowed in this class",
1552                               attr_name);
1553             chk_obj(object);
1554             chk_attr(attr);
1555             return 0;
1556         }
1557 
1558         /* check to see if it is "single" and already found */
1559         if ((class_attr_info->number == ATTR_SINGLE) && (attr_list != NULL)) {
1560             rpsl_error_assign(error,
1561                               RPSL_ERRLVL_ERROR,
1562                               RPSL_ERR_ATTRSINGLE,
1563                               "Attribute \"%s\" already appears in this class",
1564                               attr_name);
1565             chk_obj(object);
1566             chk_attr(attr);
1567             return 0;
1568         }
1569 
1570         /* otherwise we can safely add this attribute */
1571     }
1572 
1573     /* update any attribute offsets in the error list */
1574     err_list = object->errors;
1575     while (err_list != NULL) {
1576         err = err_list->data;
1577         if (err->attr_num >= ofs) { 
1578             /* increment errors from later attributes */
1579             err->attr_num++;
1580         }
1581         err_list = g_list_next(err_list);
1582     }
1583 
1584     /* add attribute to attribute list */
1585     if ((ofs < 0) || (ofs >= num_attr)) {
1586         renumber_attr(attr, num_attr);
1587         object->attributes = g_list_append(object->attributes, attr);
1588     } else {
1589         /* insert the entry at the appriate offset */
1590         renumber_attr(attr, ofs);
1591         object->attributes = g_list_insert(object->attributes, attr, ofs);
1592         num_attr++;
1593 
1594         /* renumber entries moved down */
1595         p = g_list_nth(object->attributes, ofs+1);
1596         for (i=ofs+1; p != NULL; i++, p = g_list_next(p)) {
1597             tmp = p->data;
1598             renumber_attr(tmp, i);
1599         }
1600     }
1601 
1602     /* add attribute to hash table */
1603     attr_list = g_list_append(attr_list, attr);
1604     g_hash_table_insert(object->attr_lookup, (void *)attr_name, attr_list);
1605 
1606     chk_obj(object);
1607     chk_attr(attr);
1608 
1609     return 1;
1610 }
1611 
1612 int 
1613 rpsl_object_append_attr (rpsl_object_t *object,
     /* [<][>][^][v][top][bottom][index][help] */
1614                          rpsl_attr_t *attr,
1615                          rpsl_error_t *error)
1616 {        
1617     return add_attr_to_object(object, attr, -1, error);
1618 }
1619 
1620 int 
1621 rpsl_object_add_attr (rpsl_object_t *object,
     /* [<][>][^][v][top][bottom][index][help] */
1622                       rpsl_attr_t *attr,
1623                       gint ofs,
1624                       rpsl_error_t *error)
1625 {
1626     if (ofs <= 0) {
1627         rpsl_error_assign(error,
1628                           RPSL_ERRLVL_ERROR,
1629                           RPSL_ERR_BADOFFSET,
1630                           "Offset %d is less than 1", ofs);
1631         return 0;
1632     } else {
1633         return add_attr_to_object(object, attr, ofs, error);
1634     }
1635 }
1636 
1637 rpsl_attr_t *
1638 rpsl_object_remove_attr (rpsl_object_t *object, gint ofs, rpsl_error_t *error)
     /* [<][>][^][v][top][bottom][index][help] */
1639 {
1640     gint num_attr;
1641     rpsl_attr_t *attr;
1642     const gchar *attr_name;
1643     const gchar *new_attr_name;
1644     class_t *class_info;
1645     const class_attr_t *class_attr_info;
1646     GList *attr_list;
1647     GList *p;
1648     gint i;
1649     rpsl_attr_t *tmp;
1650     GList *err_list, *tmp_err_list;
1651     rpsl_error_t *err;
1652 
1653     chk_obj(object);
1654 
1655     num_attr = rpsl_object_get_num_attr(object);
1656     if ((ofs <= 0) || (ofs >= num_attr)) {
1657         rpsl_error_assign(error,
1658                           RPSL_ERRLVL_ERROR,
1659                           RPSL_ERR_BADOFFSET,
1660                           "Offset %d not between 1 and %d", ofs, num_attr);
1661         chk_obj(object);
1662         return NULL;
1663     }
1664     attr = g_list_nth_data(object->attributes, ofs);
1665     attr_name = rpsl_attr_get_name(attr);
1666 
1667     /* get class info */
1668     class_info = object->class_info;
1669     if (class_info != NULL) {    /* we must check valid classes... */
1670 
1671         /* verify this attribute can be removed */
1672         class_attr_info = class_attr_lookup(class_info, attr_name);
1673         if ((class_attr_info != NULL) && 
1674             (class_attr_info->choice == ATTR_MANDATORY)) 
1675         {
1676             rpsl_error_assign(error,
1677                               RPSL_ERRLVL_ERROR,
1678                               RPSL_ERR_ATTRNOTALLOWED,
1679                               "Attribute \"%s\" is required in this class",
1680                               attr_name);
1681         }
1682     }
1683 
1684     /* remove from list and renumber */
1685     object->attributes = g_list_remove(object->attributes, attr);
1686     for (i=0, p=object->attributes; p != NULL; i++, p = g_list_next(p)) {
1687         tmp = p->data;
1688         renumber_attr(tmp, i);
1689     }
1690 
1691     /* remove from hash table */
1692     attr_list = g_hash_table_lookup(object->attr_lookup, attr_name);
1693     assert(attr_list != NULL);
1694     g_hash_table_remove(object->attr_lookup, attr_name);
1695     attr_list = g_list_remove(attr_list, attr);
1696     if (attr_list != NULL) {
1697         new_attr_name = rpsl_attr_get_name((rpsl_attr_t *)attr_list->data);
1698         g_hash_table_insert(object->attr_lookup, 
1699                             (void *)new_attr_name, 
1700                             attr_list);
1701     }
1702 
1703     /* fix any attribute offsets in the error list */
1704     err_list = object->errors;
1705     while (err_list != NULL) {
1706         err = err_list->data;
1707         if (err->attr_num == ofs) { 
1708             /* remove errors from this attribute */
1709             /* XXX: is this safe? should I just scan from the beginning? */
1710             tmp_err_list = g_list_next(err_list);
1711             object->errors = g_list_remove_link(object->errors, err_list);
1712             g_free(err->descr);
1713             g_free(err);
1714             g_list_free(err_list);
1715             err_list = tmp_err_list;
1716         } else if (err->attr_num > ofs) {
1717             /* decrement errors from later attributes */
1718             err->attr_num--;
1719             err_list = g_list_next(err_list);
1720         } else {
1721             /* ignore earlier attributes */
1722             err_list = g_list_next(err_list);
1723         }
1724     }
1725 
1726     chk_obj(object);
1727     chk_attr(attr);
1728 
1729     return attr;
1730 }
1731 
1732 rpsl_attr_t *
1733 rpsl_object_remove_attr_name (rpsl_object_t *object,
     /* [<][>][^][v][top][bottom][index][help] */
1734                               const gchar *name,
1735                               rpsl_error_t *error)
1736 {
1737     GList *attr_list;
1738     rpsl_attr_t *attr;
1739     rpsl_attr_t *retval;
1740 
1741     chk_obj(object);
1742 
1743     attr_list = g_hash_table_lookup(object->attr_lookup, name);
1744     if (attr_list == NULL) {
1745         rpsl_error_assign(error,
1746                           RPSL_ERRLVL_ERROR,
1747                           RPSL_ERR_NOSUCHATTR,
1748                           "Attribute \"%s\" not in this object",
1749                           name);
1750         return NULL;
1751     }
1752     attr = attr_list->data;
1753 
1754     retval = rpsl_object_remove_attr(object, attr->num, error);
1755 
1756     chk_obj(object);
1757     if (retval != NULL) {
1758         chk_attr(retval);
1759     }
1760 
1761     return retval;
1762 }
1763 
1764 const GList *
1765 rpsl_object_errors (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1766 {
1767     chk_obj(object);
1768 
1769     return object->errors;
1770 }
1771 
1772 gboolean 
1773 rpsl_attr_is_required (const rpsl_object_t *object, const gchar *attr)
     /* [<][>][^][v][top][bottom][index][help] */
1774 {
1775     const class_attr_t *class_attr_info;
1776 
1777     chk_obj(object);
1778 
1779     class_attr_info = class_attr_lookup(object->class_info, attr);
1780     return (class_attr_info != NULL) && 
1781            (class_attr_info->choice == ATTR_MANDATORY);
1782 }
1783 
1784 gboolean 
1785 rpsl_attr_is_generated (const rpsl_object_t *object, const gchar *attr)
     /* [<][>][^][v][top][bottom][index][help] */
1786 {
1787     const class_attr_t *class_attr_info;
1788 
1789     chk_obj(object);
1790 
1791     class_attr_info = class_attr_lookup(object->class_info, attr);
1792     return (class_attr_info != NULL) && 
1793            (class_attr_info->choice == ATTR_GENERATED);
1794 }
1795 
1796 gboolean 
1797 rpsl_attr_is_multivalued (const rpsl_object_t *object, const gchar *attr)
     /* [<][>][^][v][top][bottom][index][help] */
1798 {
1799     const class_attr_t *class_attr_info;
1800 
1801     chk_obj(object);
1802 
1803     class_attr_info = class_attr_lookup(object->class_info, attr);
1804     return (class_attr_info == NULL) ||
1805            (class_attr_info->number == ATTR_MULTIPLE);
1806 }
1807 
1808 gboolean 
1809 rpsl_attr_is_lookup (const rpsl_object_t *object, const gchar *attr)
     /* [<][>][^][v][top][bottom][index][help] */
1810 {
1811     const class_attr_t *class_attr_info;
1812     const attribute_t *attr_info;
1813 
1814     chk_obj(object);
1815 
1816     class_attr_info = class_attr_lookup(object->class_info, attr);
1817     if (class_attr_info == NULL) {
1818         return FALSE;
1819     } else {
1820         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
1821         assert(attr_info != NULL);
1822         return attr_info->is_lookup || attr_info->is_inverse;
1823     }
1824 }
1825 
1826 gboolean 
1827 rpsl_attr_is_key (const rpsl_object_t *object, const gchar *attr)
     /* [<][>][^][v][top][bottom][index][help] */
1828 {
1829     const class_attr_t *class_attr_info;
1830     const attribute_t *attr_info;
1831 
1832     chk_obj(object);
1833 
1834     class_attr_info = class_attr_lookup(object->class_info, attr);
1835     if (class_attr_info == NULL) {
1836         return FALSE;
1837     } else {
1838         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
1839         assert(attr_info != NULL);
1840         return attr_info->is_primary;
1841     }
1842 }
1843 
1844 gboolean 
1845 rpsl_object_is_deleted (const rpsl_object_t *object)
     /* [<][>][^][v][top][bottom][index][help] */
1846 {
1847     GList *attr_list;
1848 
1849     chk_obj(object);
1850 
1851     attr_list = g_hash_table_lookup(object->attr_lookup, "delete");
1852     if (attr_list != NULL) {
1853         return TRUE;
1854     } else {
1855         return FALSE;
1856     }
1857 }
1858 
1859 static gboolean
1860 search_errors (const GList *errors, int error_level)
     /* [<][>][^][v][top][bottom][index][help] */
1861 {
1862     rpsl_error_t *e;
1863 
1864     while (errors != NULL) {
1865         e = errors->data;
1866         if (e->level >= error_level) {
1867             return TRUE;
1868         }
1869         errors = g_list_next(errors);
1870     }
1871     return FALSE;
1872 }
1873 
1874 
1875 gboolean 
1876 rpsl_attr_has_error (const rpsl_attr_t *attr, int error_level)
     /* [<][>][^][v][top][bottom][index][help] */
1877 {
1878     chk_attr(attr);
1879 
1880     return search_errors(attr->errors, error_level);
1881 }
1882 
1883 gboolean 
1884 rpsl_object_has_error (const rpsl_object_t *object, int error_level)
     /* [<][>][^][v][top][bottom][index][help] */
1885 {
1886     chk_obj(object);
1887 
1888     return search_errors(object->errors, error_level);
1889 }
1890 
1891 gint 
1892 rpsl_get_attr_id (const gchar *attr_name)
     /* [<][>][^][v][top][bottom][index][help] */
1893 {
1894     const attribute_t *attr_info;
1895     gboolean is_ambiguous;
1896     
1897     attr_info = attribute_lookup(attr_name, &is_ambiguous);
1898     if (attr_info == NULL) {
1899         return -1;
1900     } else {
1901         return attr_info->id;
1902     }
1903 }
1904 
1905 gint 
1906 rpsl_get_class_id (const gchar *class_name)
     /* [<][>][^][v][top][bottom][index][help] */
1907 {
1908     const class_t *class_info;
1909 
1910     if (class_name == NULL) {
1911         return -1;
1912     }
1913 
1914     class_info = class_lookup(class_name);
1915     if (class_info == NULL) {
1916         return -1;
1917     } else {
1918         return class_info->id;
1919     }
1920 }
1921 
1922 void 
1923 rpsl_load_dictionary (int level)
     /* [<][>][^][v][top][bottom][index][help] */
1924 {
1925     rpsl_level = level;
1926 }
1927 
1928 int 
1929 rpsl_read_dictionary ()
     /* [<][>][^][v][top][bottom][index][help] */
1930 {
1931     return rpsl_level;
1932 }
1933 
1934 #if RUNTIME_CHECK
1935 static void
1936 rpsl_error_check (const GList *errors, const char *file, int line)
     /* [<][>][^][v][top][bottom][index][help] */
1937 {
1938     const rpsl_error_t *err;
1939 
1940     while (errors != NULL) {
1941         err = errors->data;
1942         switch (err->level) {
1943             case RPSL_ERRLVL_NONE:
1944             case RPSL_ERRLVL_DEBUG:
1945             case RPSL_ERRLVL_INFO:
1946             case RPSL_ERRLVL_NOTICE:
1947             case RPSL_ERRLVL_WARN:
1948             case RPSL_ERRLVL_ERROR:
1949             case RPSL_ERRLVL_CRIT:
1950             case RPSL_ERRLVL_FATAL:
1951                 break;
1952             default:
1953                 fprintf(stderr, "rpsl_error_check: bad level %d at %s:%d\n",
1954                         err->level, file, line);
1955                 exit(1);
1956         }
1957         /* XXX: could check attr-codes ONLY appear in attr, and so on */
1958         switch (err->code) {
1959             case RPSL_ERR_BADATTR:
1960             case RPSL_ERR_UNKNOWNATTR:
1961             case RPSL_ERR_EMPTYLIST:
1962             case RPSL_ERR_EMPTYATTR:
1963             case RPSL_ERR_SYNERR:
1964             case RPSL_ERR_ONLYCOMMENTS:
1965             case RPSL_ERR_BADCLASS:
1966             case RPSL_ERR_UNKNOWNCLASS:
1967             case RPSL_ERR_ATTRNOTALLOWED:
1968             case RPSL_ERR_ATTRSINGLE:
1969             case RPSL_ERR_ATTRGENERATED:
1970             case RPSL_ERR_MISSINGATTR:
1971             case RPSL_ERR_MISSINGKEY:
1972             case RPSL_ERR_BADOFFSET:
1973             case RPSL_ERR_NOSUCHATTR:
1974                 break;
1975             default:
1976                 fprintf(stderr, "rpsl_error_check: bad code %d at %s:%d\n",
1977                         err->code, file, line);
1978                 exit(1);
1979         }
1980         if (err->descr == NULL) {
1981             fprintf(stderr, "rpsl_error_check: NULL descr at %s:%d\n",
1982                     file, line);
1983             exit(1);
1984         }
1985         /* XXX: should check attr_num is within object */
1986         if (err->attr_num < -1) {
1987             fprintf(stderr, "rpsl_error_check: bad attr_num %d at %s:%d\n",
1988                     err->attr_num, file, line);
1989             exit(1);
1990         }
1991         errors = g_list_next(errors);
1992     }
1993 }
1994 
1995 static void
1996 rpsl_attr_check (const rpsl_attr_t *attr, const char *file, int line)
     /* [<][>][^][v][top][bottom][index][help] */
1997 {
1998     const GList *errors;
1999     const rpsl_error_t *err;
2000 
2001     if (attr == NULL) {
2002         fprintf(stderr, "rpsl_attr_check: NULL attr at %s:%d\n",
2003                 file, line);
2004         exit(1);
2005     }
2006     if (attr->name == NULL) {
2007         fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n",
2008                 file, line);
2009         exit(1);
2010     }
2011     if (attr->lcase_name == NULL) {
2012         fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n",
2013                 file, line);
2014         exit(1);
2015     }
2016     if (attr->value == NULL) {
2017         fprintf(stderr, "rpsl_attr_check: NULL value at %s:%d\n",
2018                 file, line);
2019         exit(1);
2020     }
2021     rpsl_error_check(attr->errors, file, line);
2022     if (attr->num < -1) {
2023         fprintf(stderr, "rpsl_attr_check: bad num %d at %s:%d\n",
2024                 attr->num, file, line);
2025         exit(1);
2026     }
2027     for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) {
2028         err = errors->data;
2029         if (err->attr_num != -1) {
2030             fprintf(stderr, 
2031                     "rpsl_attr_check: attr_num (%d) != -1 at %s:%d\n",
2032                     err->attr_num, file, line);
2033             exit(1);
2034         }
2035     }
2036     /* XXX: think of a check for attr_info.... */
2037 }
2038 
2039 /* XXX: could also verify keys - but that's a bit extreme */
2040 static void 
2041 count_attr_in_list (gpointer key, gpointer value, gpointer user_data)
     /* [<][>][^][v][top][bottom][index][help] */
2042 {
2043     GList *l;
2044     int sum;
2045     int *cnt;
2046 
2047     sum = 0;
2048     for (l=value; l != NULL; l = g_list_next(l)) {
2049         sum++;
2050     }
2051     cnt = (int *)user_data;
2052     *cnt += sum;
2053 }
2054 
2055 static void
2056 rpsl_object_check (const rpsl_object_t *obj, const char *file, int line)
     /* [<][>][^][v][top][bottom][index][help] */
2057 {
2058     const GList *l;
2059     int i;
2060     const rpsl_attr_t *attr;
2061     const GList *errors;
2062     const rpsl_error_t *err;
2063     int num_attr;
2064     gboolean attr_in_list;
2065     int cnt;
2066 
2067     if (obj == NULL) {
2068         fprintf(stderr, "rpsl_object_check: NULL object at %s:%d\n",
2069                 file, line);
2070         exit(1);
2071     }
2072     if (obj->attributes == NULL) {
2073         fprintf(stderr, "rpsl_object_check: NULL attributes at %s:%d\n",
2074                 file, line);
2075         exit(1);
2076     }
2077     if (obj->attr_lookup == NULL) {
2078         fprintf(stderr, "rpsl_object_check: NULL attr_lookup at %s:%d\n",
2079                 file, line);
2080         exit(1);
2081     }
2082     /* make sure each attribute in the hash is in the list */
2083     num_attr = g_list_length(obj->attributes);
2084     cnt = 0;
2085     g_hash_table_foreach(obj->attr_lookup, count_attr_in_list, &cnt);
2086     if (num_attr != cnt) {
2087         fprintf(stderr, 
2088             "rpsl_object_check: list count (%d) != hash count (%d) at %s:%d\n",
2089             num_attr, cnt,
2090             file, line);
2091         exit(1);
2092     }
2093     for (l=obj->attributes, i=0; l != NULL; l=g_list_next(l), i++) {
2094         attr = l->data;
2095         rpsl_attr_check(attr, file, line);
2096         /* make sure each attribute is in the hash table */
2097         l = g_hash_table_lookup(obj->attr_lookup, rpsl_attr_get_name(attr));
2098         attr_in_list = FALSE;
2099         while ((l != NULL) && !attr_in_list) {
2100             if (l->data == attr) {
2101                 attr_in_list = TRUE;
2102             }
2103             l = g_list_next(l);
2104         }
2105         if (!attr_in_list) {
2106             fprintf(stderr, 
2107                     "rpsl_object_check: attr #%d not in hash for %p %s:%d\n",
2108                     i, obj, file, line);
2109             exit(1);
2110         }
2111         if (attr->num != i) {
2112             fprintf(stderr, 
2113                 "rpsl_object_check: attr #%d does not match offset %d %s:%d\n",
2114                     attr->num, i, file, line);
2115             exit(1);
2116         }
2117     }
2118     rpsl_error_check(obj->errors, file, line);
2119     for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) {
2120         err = errors->data;
2121         if (err->attr_num >= num_attr) {
2122             fprintf(stderr, 
2123                 "rpsl_object_check: attr_num (%d) >= num_attr (%d) at %s:%d\n",
2124                 err->attr_num, num_attr, file, line);
2125             exit(1);
2126         }
2127     }
2128     /* XXX: think of a check for class_info... */
2129 }
2130 #endif /* RUNTIME_CHECK */
2131 
2132 #ifdef TEST
2133 
2134 #include <stdio.h>
2135 
2136 /* for a test, check to make sure our we convert the following values into
2137    the expected results */
2138 struct {
2139     gchar *input;
2140     gchar *expected_result;
2141 } test_attr[] = {
2142     /* all tests on a single-line attributes */
2143     { "unmodified", "unmodified" },
2144     { "also unmodified", "also unmodified" },
2145     { "   leading whitespace", "leading whitespace" },
2146     { "trailing whitespace ", "trailing whitespace" },
2147     { "compressed    \t whitespace", "compressed whitespace" },
2148     { "value   # some comment", "value" },
2149     { " lots of      stuff# here too  ", "lots of stuff" },
2150     { "", "" },
2151     /* basic tests on multi-line attributes */
2152     { "multiple\n"
2153       " lines",
2154       "multiple lines" },
2155     { "multiple\n"
2156       "\ttablines",
2157       "multiple tablines" },
2158     { "multiple\n"
2159       "+pluslines",
2160       "multiple pluslines" },
2161     { "\n"
2162       " \n"
2163       "\t\n"
2164       "+\n",
2165       "" },
2166     /* multi-line whitespace tests */
2167     { " leading\n"
2168       " multiline whitespace",
2169       "leading multiline whitespace" },
2170     { "\tleading\n"
2171       "\ttabs multiline",
2172       "leading tabs multiline" },
2173     { "\t \tleading\n"
2174       "++ multiline",
2175       "leading + multiline" },
2176     { "trailing\n"
2177       " multiline   \t",
2178       "trailing multiline" },
2179     { "trailing\n"
2180       "\ttabful multiline     ",
2181       "trailing tabful multiline" },
2182     { "trailing\n" 
2183       "+plus multiline\t",
2184       "trailing plus multiline" },
2185     { "multiline   \n"
2186       "    whitespace   \n"
2187       "+compression",
2188       "multiline whitespace compression" },
2189     { "    more \t\tmultiline  \t\n"
2190       "+  whitespace \t \t\n"
2191       "+compression   \t",
2192       "more multiline whitespace compression" },
2193     /* multi-line comment tests */
2194     { "There # once was a man from Nantucket,\n"
2195       "\tWhose nic-hdl # fell in the bitbucket.\n"
2196       "\t\tHe grabbed his # nic-handle,\n"
2197       "\t\tAnd made the mail queue # full.\n"
2198       "\tBut # the mail bounced (we just chucked it).",
2199       "There Whose nic-hdl He grabbed his And made the mail queue But" },
2200     { " # this is an evil,\n"
2201       " # but legal,\n"
2202       " # thing to do",
2203       "" },
2204     { "this # is also \n"
2205       "+    # legal, but less evil I suppose\n",
2206       "this" },
2207     
2208 };
2209 
2210 #define NUM_TEST_ATTR (sizeof(test_attr) / sizeof(test_attr[0]))
2211 
2212 int 
2213 main()
     /* [<][>][^][v][top][bottom][index][help] */
2214 {
2215     int i;
2216     gchar *actual_result;
2217     int num_error;
2218 
2219     num_error = 0;
2220 
2221     /* test the attribute_clean() function */
2222     for (i=0; i<NUM_TEST_ATTR; i++) {
2223         actual_result = attribute_clean(test_attr[i].input);
2224         if (strcmp(actual_result, test_attr[i].expected_result) != 0) {
2225             puts("ERROR: test failed");
2226             puts("--------[ input ]--------");
2227             puts(test_attr[i].input);
2228             puts("---[ expected result ]---");
2229             puts(test_attr[i].expected_result);
2230             puts("----[ actual result ]----");
2231             puts(actual_result);
2232             puts("-------------------------");
2233             num_error++;
2234         }
2235     }
2236     if (num_error == 0) {
2237         printf("SUCCESS: all tests passed\n");
2238         return 0;
2239     } else {
2240         return 1;
2241     }
2242 }
2243 
2244 #endif

/* [<][>][^][v][top][bottom][index][help] */